feat(M02-흐름도): 공용 단위 고르기 UnitPick · 끝수 줄 · 함수 판 단위 고르기로 바꿈

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D2nUMUMTsqm93PbQtipo6c
This commit is contained in:
2026-10-01 19:42:27 +09:00
co-authored by Claude Sonnet 5.5
parent 4cd2930ff3
commit 348003cf5a
6 changed files with 411 additions and 25 deletions
@@ -0,0 +1,77 @@
/* =============================================================================
* M02_DF_RoundRow.ts
* 끝수 고르기 줄 — 소수 몇 자리 · 반올림/올림/버림 · 단위 표준에서 자리 제안(적용 단추)
* 칸 · 단추는 공용 작은 크기 · 글 [한, 영] 짝은 아래 TEXT
* ========================================================================== */
import { currentLanguageIndex } from "@ui/ui_template_locale";
import { createButton, el } from "@ui/ui_template_elements";
import { parseRound, ROUND_FN, type RoundMode, type RoundSpec } from "./M02_DF_Round";
const TEXT = {
Label: ["끝수", "Rounding"],
None: ["안 씀", "none"],
Places: ["소수 {n}자리", "{n} decimals"],
Suggest: ["표준 제안: 소수 {n}자리", "Standard suggests {n} decimals"],
Apply: ["적용", "Apply"],
Places_aria: ["소수자리", "Decimal places"],
Mode_aria: ["방법", "Method"],
} as const;
const tr = (key: keyof typeof TEXT, n?: number): string =>
TEXT[key][currentLanguageIndex === 1 ? 1 : 0].replace("{n}", String(n ?? ""));
export interface RoundRowOptions {
/** 출력 칸 수식(앞 = 뺀 글) — 없으면 줄을 안 그림 */
formula: string | undefined;
/** 단위 표준이 제안한 소수자리 */
suggest: number | null;
readOnly?: boolean;
/** null = 끝수 벗김 */
onApply(spec: RoundSpec | null): void;
}
export function createRoundRow(opts: RoundRowOptions): HTMLElement | null {
if (opts.formula === undefined) return null;
const now = parseRound(opts.formula)?.spec ?? null;
const places = el("select", {
className: "ui-select ui-select--sm",
attrs: { "aria-label": tr("Places_aria") },
});
places.append(el("option", { attrs: { value: "" }, text: tr("None") }));
for (let n = 0; n <= 6; n++) {
places.append(el("option", { attrs: { value: String(n) }, text: tr("Places", n) }));
}
places.value = now ? String(now.places) : "";
const mode = el("select", {
className: "ui-select ui-select--sm",
attrs: { "aria-label": tr("Mode_aria") },
});
for (const m of Object.keys(ROUND_FN) as RoundMode[]) {
mode.append(el("option", { attrs: { value: m }, text: m }));
}
mode.value = now?.mode ?? "반올림";
if (opts.readOnly) places.disabled = mode.disabled = true;
const emit = (): void =>
opts.onApply(
places.value === "" ? null : { places: Number(places.value), mode: mode.value as RoundMode },
);
places.addEventListener("change", emit);
mode.addEventListener("change", () => places.value !== "" && emit());
const row = el("div", {
className: "m02-dfpanel-row",
children: [el("span", { className: "m02-dfpanel-label", text: tr("Label") }), places, mode],
});
if (opts.suggest !== null && !now && !opts.readOnly) {
row.append(
el("span", { className: "m02-dfpanel-note", text: tr("Suggest", opts.suggest) }),
createButton({
label: tr("Apply"),
variant: "ghost",
size: "sm",
onClick: () => opts.onApply({ places: opts.suggest!, mode: "반올림" }),
}),
);
}
return row;
}