Files
Aislo/ui_template/ui_template_equation_defs.ts
T
eomsangdonandClaude Sonnet 5.5 f47991f698 feat(800-2): 수식 영역 / 변수 정의 영역 화면 · 식 바로 고침 · 새 기호 연결 지정(800-3)
- 수식 영역 줄 번호 · 구분선 · 줄마다 풀이 걷음 · 오른쪽 변수 정의 영역 부품 ui_template_equation_defs
- 식 칸을 눌러 그 자리 고침(줄 선택 · 아이콘 줄 걷음) · 틀린 식 그 자리 빨강 + 까닭 · 미리 셈
- 새 기호 = 정의 영역 맨 위 줄에서 연결(입력 · 표 · 요소 · 로직 · 상수) · 이름 · 기호 · 첨자 고침 · 지움(쓰는 변수 막음)
- 낡은 시험 test_796_2_equation_screen 두 건: 아이콘 줄 · 줄 선택 고침 → 바로 고침 꼴로 바꿈(동작 걷음)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 16:58:55 +09:00

94 lines
4.4 KiB
TypeScript

/* =============================================================================
* ui_template_equation_defs.ts
* 부품 「수식」 넷째 파일(800-2 · 요구 660-2 · 3 · 6) — 오른쪽 「변수 정의 영역」.
* 한 덩어리 목록 · 같은 기호는 한 줄(서버가 합침) · 줄 = 기호 : 국문 이름 — 연결 설명(입력 · 표 찾기 · 요소 · 로직 · 계산을 사람 말로)
* 값 · 단위 · DB 열쇠 글은 안 그림(값은 왼쪽 값 식에 있음) · 고른 옵션 갈래의 변수만(서버 `정의`)
* 상태 = 고침 열린 줄 `data-editing`(줄 아래 `editor` 칸) · 연결 안 한 새 기호 줄 `data-draft`(`drafts` 요소 — 부르는 쪽이 만듦) · 눌러 보인 줄 `data-flash`
* 쓰는 법 = `createEqDefs(defs, { editing, onEdit, editor, drafts, head })` → 요소 · `flashEqDef(root, node)` = 식의 변수를 눌렀을 때 그 정의 줄로 이동
* ========================================================================== */
import { symbolOf, type EqDef, type EqNode } from "./ui_template_equation";
export interface EqDefsOptions {
/** 고침이 열린 정의의 기호 글(`defSign`) */
editing?: string | null;
/** 정의 줄을 누름(없으면 고칠 수 없음 — 고침 가능한 줄만 눌림) */
onEdit?: (def: EqDef) => void;
/** 고침이 열린 줄 아래 칸 */
editor?: (def: EqDef) => HTMLElement;
/** 연결을 지정할 새 기호 줄들 */
drafts?: HTMLElement[];
/** 머리 오른쪽 단추 자리 */
head?: HTMLElement[];
}
const node = (tag: "div" | "span", className: string, text = ""): HTMLElement => {
const out = document.createElement(tag);
out.className = className;
if (text) out.textContent = text;
return out;
};
/** 정의의 기호 글(기호 · 아래 · 위) — 줄 이름표 · 식의 변수와 맞대는 열쇠 */
export const defSign = (d: Pick<EqDef, "기호" | "위" | "아래">): string =>
`${d.기호}${d.아래 ? `_${d.아래}` : ""}${d.위 ? `^${d.위}` : ""}`;
/** 식 변수 마디의 기호 글 — `defSign` 과 같은 꼴 */
export const nodeSign = (n: Pick<EqNode, "기호" | "열쇠" | "위" | "아래">): string =>
defSign({
기호: n.기호 || n.열쇠 || "",
위: typeof n.위 === "string" ? n.위 : "",
아래: typeof n.아래 === "string" ? n.아래 : "",
});
function defRow(d: EqDef, o: EqDefsOptions): HTMLElement {
const sign = defSign(d);
const row = node("div", "ui-eq__def");
row.dataset.eqDef = sign;
row.dataset.kind = d.갈래;
const main = node("div", "ui-eq__def-main");
main.append(symbolOf({ 열쇠: d.열쇠, 기호: d.기호, 위: d.위, 아래: d.아래 }));
if (d.이름 && d.이름 !== d.기호)
main.append(node("span", "ui-eq__op", ":"), node("span", "ui-eq__def-name", d.이름));
if (d.안씀) main.append(node("span", "ui-eq__def-tag", "쓰는 식 없음"));
const links = node("div", "ui-eq__def-links");
for (const text of d.연결) links.append(node("div", "ui-eq__def-link", text));
const top = node("div", "ui-eq__def-top", "");
top.append(main, links);
if (o.onEdit && d.고침) {
row.classList.add("is-pick");
top.title = "눌러서 이름 · 기호 · 첨자 고침 · 지움";
top.addEventListener("click", () => o.onEdit?.(d));
}
row.append(top);
if (o.editing === sign && o.editor) {
row.dataset.editing = "1";
row.append(o.editor(d));
}
return row;
}
export function createEqDefs(defs: EqDef[], o: EqDefsOptions = {}): HTMLElement {
const root = node("div", "ui-eq__defs");
root.dataset.eqDefs = "";
const head = node("div", "ui-eq__defs-head");
head.append(node("span", "ui-eq__defs-title", "변수 정의"), ...(o.head ?? []));
root.append(head);
for (const draft of o.drafts ?? []) root.append(draft); // 새 기호는 맨 위 — 식을 고치는 곳 옆에서 바로 보이게
for (const d of defs) root.append(defRow(d, o));
if (!defs.length && !o.drafts?.length) root.append(node("div", "ui-eq__def-empty", "—"));
return root;
}
/** 식의 변수를 눌렀을 때 — 그 정의 줄로 이동 · 잠깐 표시 */
export function flashEqDef(root: HTMLElement, n: EqNode): void {
const sign = nodeSign(n);
const row = [...root.querySelectorAll<HTMLElement>("[data-eq-def]")].find(
(r) => r.dataset.eqDef === sign,
);
if (!row) return;
row.scrollIntoView({ block: "nearest", behavior: "smooth" });
row.dataset.flash = "1";
window.setTimeout(() => delete row.dataset.flash, 1200);
}