feat(M01): 단가산출 막대 시험판 넷을 「시험판 ▾」 목록 하나로 묶음 · CostFlow 원가 칸에 입력 값도 넘김

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
2026-10-01 16:49:02 +09:00
co-authored by Claude Opus 5.5
parent 2be767d186
commit 19fed5b90f
2 changed files with 64 additions and 10 deletions
+55 -9
View File
@@ -7,7 +7,7 @@
* 409 = 그 사이 파일이 바뀜 · 422 = 검사 걸림(아무것도 안 씀) — 까닭은 서버 글 그대로 보임.
* ========================================================================== */
import { t as L } from "@ui/ui_template_locale";
import { currentLanguageIndex, t as L } from "@ui/ui_template_locale";
import {
createButton,
el,
@@ -75,6 +75,50 @@ interface Opened extends Draft {
const cacheKey = "m01_logic_drafts_lab";
const ownHost = (): HTMLElement => el("div", { className: "m01lab__own" });
/** 시험판 묶음 — 「시험판 ▾」 단추 하나 · 누르면 시험판 단추들이 목록으로(바깥 · Esc · 고르면 닫힘) */
function trialMenu(items: HTMLElement[]): HTMLElement {
const list = el("div", { attrs: { role: "menu" }, children: items });
Object.assign(list.style, {
position: "absolute",
top: "calc(100% + 4px)",
left: "0",
zIndex: "20",
display: "none",
flexDirection: "column",
gap: "var(--spacing-4)",
padding: "var(--spacing-8)",
border: "1px solid var(--color-border)",
borderRadius: "var(--radius-lg)",
background: "var(--color-surface-raised)",
boxShadow: "var(--shadow-lg)",
whiteSpace: "nowrap",
});
const shut = (): void => {
list.style.display = "none";
button.setAttribute("aria-expanded", "false");
document.removeEventListener("click", outside, true);
};
const outside = (ev: Event): void => {
if (!wrap.contains(ev.target as Node)) shut();
};
const button = createButton({
label: ["시험판 ▾", "Trials ▾"][currentLanguageIndex === 1 ? 1 : 0],
variant: "ghost",
onClick: () => {
if (list.style.display !== "none") return shut();
list.style.display = "flex";
button.setAttribute("aria-expanded", "true");
document.addEventListener("click", outside, true);
},
});
button.setAttribute("aria-haspopup", "menu");
list.addEventListener("click", () => shut()); // 고른 시험판은 제 단추가 엶
const wrap = el("div", { children: [button, list] });
Object.assign(wrap.style, { position: "relative", display: "inline-flex" });
wrap.addEventListener("keydown", (ev) => ev.key === "Escape" && shut());
return wrap;
}
const clone = <T>(v: T): T => JSON.parse(JSON.stringify(v)) as T;
function loadDrafts(): Record<string, Draft> {
@@ -406,14 +450,16 @@ export async function mountM01LogicLab(
el("h2", { text: tx("Title") }),
pending,
createButton({ label: tn("Open"), variant: "filled", onClick: startNew }),
// 쉬운 단가산출 시험 길 — 폴더째 지우면 이 두 줄도 지움
createEasyCalcButton(),
// 로직 만들기 시험 길 — 폴더째 지우면 이 두 줄도 지움
createLogicBuilderButton(),
// 시트 로직 시험 길 — 폴더째 지우면 이 두 줄도 지움
createSheetLogicButton(),
// 단가산출 다이어그램 시험 길 — 폴더째 지우면 이 두 줄도 지움
createCostFlowButton(),
trialMenu([
// 쉬운 단가산출 시험 길 — 폴더째 지우면 이 두 줄도 지움
createEasyCalcButton(),
// 로직 만들기 시험 길 — 폴더째 지우면 이 두 줄도 지움
createLogicBuilderButton(),
// 시트 로직 시험 길 — 폴더째 지우면 이 두 줄도 지움
createSheetLogicButton(),
// 단가산출 다이어그램 시험 길 — 폴더째 지우면 이 두 줄도 지움
createCostFlowButton(),
]),
createButton({ label: tx("Bar_Delete"), variant: "danger", onClick: () => void onDelete() }),
discardButton,
saveButton,