Files
Aislo/ui_template/ui_template_item_picker_ask.ts
T
eomsangdonandClaude Sonnet 5.5 26eac086a8 feat(ui_template): 고르기 모달 — 구조물 복사본 기준 · [재적용] · [개인에 저장] · 나무 오른쪽 클릭 새 항목 · 경로 이름 (PLAN 26-3b)
- 카드 · 편집이 그 구조물의 복사본(폴더 = 구조물 id · 관 = pipe@측점)을 읽고 고침 · 같은 항목을 고른 구조물끼리 값이 따로
- [재적용](덮이는 것을 확인 창으로 알리고 덮임 부분을 알림) · [개인에 저장](경로 · 이름 물음) · 상태 줄에 고침 · 마스터가 바뀜 · 원본 없음 표시
- 나무 개인 그룹 · 폴더 오른쪽 클릭 「새 항목」 → 돌려받은 summary_item 을 폼에 지정 · [적용] 뒤 [저장] 안내 한 줄
- B05 구조물 폼이 구조물 id 를 모달에 넘김

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EauA2ATmqNRbeV48s9CcBr
2026-09-30 08:15:55 +09:00

98 lines
3.1 KiB
TypeScript

/* =============================================================================
* ui_template_item_picker_ask.ts
* 고르기 모달에서 글을 물을 때 쓰는 작은 입력 창(계획서 26-3b) — [개인에 저장](경로 · 이름) ·
* 나무 오른쪽 클릭 「새 항목」(이름 · 단위). 확인 = 넣은 글들 · 취소 · 닫기 = null.
* ========================================================================== */
import { createButton, createInputField, el } from "./ui_template_elements";
import { openModal } from "./ui_template_modal";
export interface AskField {
key: string;
label: string;
value?: string;
placeholder?: string;
/** 비우면 확인 안 됨 */
required?: boolean;
}
export interface AskOptions {
title: string;
/** 창 위 한 줄 설명 */
note?: string;
fields: AskField[];
okLabel: string;
cancelLabel: string;
closeLabel: string;
/** 빈 값 알림 — `{label}` 자리에 칸 이름 */
emptyText: string;
}
export function askFields(opts: AskOptions): Promise<Record<string, string> | null> {
return new Promise((resolve) => {
let done = false;
const finish = (value: Record<string, string> | null): void => {
if (done) return;
done = true;
resolve(value);
};
openModal({
title: opts.title,
closeLabel: opts.closeLabel,
dialogClass: "ui-item-ask",
backdropClass: "ui-item-ask__backdrop",
beforeClose: () => {
finish(null);
return true;
},
mount: (body, close) => {
const inputs = opts.fields.map((field) => ({
field,
input: createInputField({
label: field.label,
value: field.value ?? "",
placeholder: field.placeholder,
}),
}));
const submit = (): void => {
let bad = false;
for (const { field, input } of inputs) {
const empty = field.required && input.input.value.trim() === "";
input.setError(empty ? opts.emptyText.replace("{label}", field.label) : undefined);
bad ||= !!empty;
}
if (bad) return;
finish(
Object.fromEntries(
inputs.map(({ field, input }) => [field.key, input.input.value.trim()]),
),
);
close();
};
for (const { input } of inputs)
input.input.addEventListener("keydown", (event) => {
if (event.key === "Enter") submit();
});
body.append(
...(opts.note ? [el("p", { className: "ui-item-picker__where", text: opts.note })] : []),
...inputs.map(({ input }) => input.root),
el("div", {
className: "ui-item-picker__actions",
children: [
createButton({
label: opts.cancelLabel,
variant: "ghost",
onClick: () => {
finish(null);
close();
},
}),
createButton({ label: opts.okLabel, variant: "filled", onClick: submit }),
],
}),
);
},
});
});
}