feat(M02): 저장 안 한 고침 물음 단추 셋 — 취소 · 버리고 넘어감 · 저장 후 넘어감, settle 카드 저장 실패 흠(86-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/* =============================================================================
|
||||
* ui_template_leave_dialog.ts
|
||||
* 저장 안 한 고침이 있을 때 넘어가기 전 물음 — 단추 셋: [취소] [버리고 넘어감] [저장 후 넘어감](주 단추).
|
||||
* 기본 초점 = 취소 · Esc = 취소 · 저장이 실패하거나 고침이 남으면 머묾.
|
||||
* 몸은 공용 확인 창(`showConfirmDialog`)과 같은 틀(.ui-confirm).
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, el } from "./ui_template_elements";
|
||||
import { t as L } from "./ui_template_locale";
|
||||
|
||||
export type LeaveChoice = "save" | "discard" | "cancel";
|
||||
|
||||
/** 3단추 물음 — 닫기 · Esc = "cancel" */
|
||||
export function askLeave(message: string): Promise<LeaveChoice> {
|
||||
return new Promise((resolve) => {
|
||||
const overlay = el("div", { className: "ui-confirm" });
|
||||
const panel = el("div", { className: "ui-confirm__panel" });
|
||||
const actions = el("div", { className: "ui-confirm__actions" });
|
||||
const back = document.activeElement as HTMLElement | null; // 닫힐 때 초점을 돌려놓을 자리
|
||||
const done = (choice: LeaveChoice): void => {
|
||||
overlay.remove();
|
||||
if (back?.isConnected && back !== document.body) back.focus({ preventScroll: true });
|
||||
resolve(choice);
|
||||
};
|
||||
const cancel = createButton({
|
||||
label: L("M02_TreeCancel"),
|
||||
variant: "ghost",
|
||||
onClick: () => done("cancel"),
|
||||
});
|
||||
actions.append(
|
||||
cancel,
|
||||
createButton({
|
||||
label: L("M02_TreeDiscardGo"),
|
||||
variant: "danger",
|
||||
onClick: () => done("discard"),
|
||||
}),
|
||||
createButton({
|
||||
label: L("M02_TreeSaveGo"),
|
||||
variant: "filled",
|
||||
onClick: () => done("save"),
|
||||
}),
|
||||
);
|
||||
overlay.addEventListener("keydown", (ev) => {
|
||||
if (ev.key !== "Escape") return;
|
||||
ev.stopPropagation(); // 뒤 창 · 페이지 Esc 까지 닫지 않음
|
||||
done("cancel");
|
||||
});
|
||||
panel.append(el("p", { className: "ui-confirm__message", text: message }), actions);
|
||||
overlay.append(panel);
|
||||
document.body.append(overlay);
|
||||
cancel.focus(); // 창에 초점 — 뒤 칸에 글이 들어가지 않고 Esc 가 먹음
|
||||
});
|
||||
}
|
||||
|
||||
export interface LeaveHooks {
|
||||
/** 저장 한 번 — 실패는 던지거나 `stillDirty` 로 알림(알림 글은 저장 길이 띄움) */
|
||||
save: () => Promise<unknown>;
|
||||
/** 저장 뒤에도 고침이 남았나 — 남으면 머묾 */
|
||||
stillDirty: () => boolean;
|
||||
/** [버리고 넘어감] 때 */
|
||||
discard?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 물음 → 고름대로 처리. 넘어가도 되면 "saved" | "discarded", 머물면 "stay".
|
||||
* 저장 실패(던짐) · 저장 뒤 고침 남음 · 취소 = "stay".
|
||||
*/
|
||||
export async function leaveOrStay(
|
||||
message: string,
|
||||
hooks: LeaveHooks,
|
||||
): Promise<"saved" | "discarded" | "stay"> {
|
||||
const choice = await askLeave(message);
|
||||
if (choice === "cancel") return "stay";
|
||||
if (choice === "discard") {
|
||||
await hooks.discard?.();
|
||||
return "discarded";
|
||||
}
|
||||
try {
|
||||
await hooks.save();
|
||||
} catch {
|
||||
return "stay";
|
||||
}
|
||||
return hooks.stillDirty() ? "stay" : "saved";
|
||||
}
|
||||
@@ -154,8 +154,8 @@ export const ui_locales_m2 = {
|
||||
"저장 안 한 표 고침이 있음 — 나무를 고치기 전에 처리",
|
||||
"Unsaved table edits — settle them before changing the tree",
|
||||
],
|
||||
M02_TreeSaveGo: ["저장하고 계속", "Save and continue"],
|
||||
M02_TreeDiscardGo: ["버리고 계속", "Discard and continue"],
|
||||
M02_TreeSaveGo: ["저장 후 넘어감", "Save and continue"],
|
||||
M02_TreeDiscardGo: ["버리고 넘어감", "Discard and continue"],
|
||||
M02_TreeDone: ["나무를 고침", "Tree updated"],
|
||||
M02_PracticeTitle: ["실무문서", "Practice documents"],
|
||||
M02_PracticeRow: ["실무문서 목록", "Practice document list"],
|
||||
|
||||
Reference in New Issue
Block a user