feat(M02): 48-2 구조물 옵션 정의 편집 화면 — 군 · 종류 · 옵션 · 선택지 · 배치 격자 · 그룹 형상 · 규칙 · 판 이력
- 옛 「옵션 보이기 · 그룹」 화면을 정의 편집 화면으로 넓힘(시스템 관리자 고쳐 씀 · 다른 역할 읽기만) - 자료 손질 = M02_SO_Def_Model.ts(정의 틀 48-1 꼴) · 시험 test_m02_so_def_model.py Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
/* =============================================================================
|
||||
* M02_SO_Def_Ui.ts
|
||||
* 구조물 정의 편집 화면(PLAN 48-2)의 작은 입력 부품 — 글 · 숫자 · 체크 · 고르기 · 위아래 단추.
|
||||
* 값은 받은 손잡이로 사본만 고침 · 글은 칸을 떠날 때(`change`) 다시 그림(초점 안 끊김).
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import { step } from "./M02_SO_Def_Model";
|
||||
import { to } from "./M02_SO_Text";
|
||||
|
||||
/** 글 칸 — 글자마다 `set` · 떠날 때 `done` */
|
||||
export function textBox(
|
||||
value: string,
|
||||
set: (v: string) => void,
|
||||
done: () => void,
|
||||
opt: { edit: boolean; attr?: string; place?: string; cls?: string; max?: number },
|
||||
): HTMLInputElement {
|
||||
const box = el("input", {
|
||||
className: `ui-input ui-input--sm ${opt.cls ?? ""}`,
|
||||
attrs: {
|
||||
type: "text",
|
||||
maxlength: String(opt.max ?? 40),
|
||||
...(opt.attr ? { [opt.attr]: "" } : {}),
|
||||
},
|
||||
}) as HTMLInputElement;
|
||||
box.value = value;
|
||||
box.placeholder = opt.place ?? "";
|
||||
box.disabled = !opt.edit;
|
||||
box.addEventListener("input", () => {
|
||||
set(box.value);
|
||||
box.classList.toggle("is-invalid", !box.value.trim() && !opt.place);
|
||||
});
|
||||
box.addEventListener("change", done);
|
||||
return box;
|
||||
}
|
||||
|
||||
/** 숫자 칸 — 범위 밖 · 빈 칸은 무름 */
|
||||
export function numberBox(
|
||||
value: number,
|
||||
set: (v: number) => void,
|
||||
opt: { edit: boolean; min: number; max: number; attr?: string },
|
||||
): HTMLInputElement {
|
||||
const box = el("input", {
|
||||
className: "ui-input ui-input--sm m02-sod__num",
|
||||
attrs: {
|
||||
type: "number",
|
||||
min: String(opt.min),
|
||||
max: String(opt.max),
|
||||
...(opt.attr ? { [opt.attr]: "" } : {}),
|
||||
},
|
||||
}) as HTMLInputElement;
|
||||
box.value = String(value);
|
||||
box.disabled = !opt.edit;
|
||||
box.addEventListener("change", () => {
|
||||
const n = Math.round(Number(box.value));
|
||||
if (box.value === "" || !Number.isFinite(n) || n < opt.min || n > opt.max) {
|
||||
box.value = String(value);
|
||||
return;
|
||||
}
|
||||
set(n);
|
||||
});
|
||||
return box;
|
||||
}
|
||||
|
||||
/** 체크 + 글 */
|
||||
export function checkBox(
|
||||
label: string,
|
||||
on: boolean,
|
||||
set: (v: boolean) => void,
|
||||
opt: { edit: boolean; attr?: string },
|
||||
): HTMLLabelElement {
|
||||
const box = el("input", {
|
||||
attrs: { type: "checkbox", ...(opt.attr ? { [opt.attr]: "" } : {}) },
|
||||
}) as HTMLInputElement;
|
||||
box.checked = on;
|
||||
box.disabled = !opt.edit;
|
||||
box.addEventListener("change", () => set(box.checked));
|
||||
return el("label", { className: "m02-sod__check", children: [box, el("span", { text: label })] });
|
||||
}
|
||||
|
||||
/** 고르기 — [값, 글] 짝 */
|
||||
export function pickBox(
|
||||
pairs: readonly (readonly [string, string])[],
|
||||
value: string,
|
||||
set: (v: string) => void,
|
||||
opt: { edit: boolean; attr?: string; cls?: string },
|
||||
): HTMLSelectElement {
|
||||
const box = el("select", {
|
||||
className: `ui-input ui-input--sm ${opt.cls ?? ""}`,
|
||||
attrs: opt.attr ? { [opt.attr]: "" } : {},
|
||||
}) as HTMLSelectElement;
|
||||
box.append(...pairs.map(([v, t]) => new Option(t, v)));
|
||||
box.value = value;
|
||||
box.disabled = !opt.edit;
|
||||
box.addEventListener("change", () => set(box.value));
|
||||
return box;
|
||||
}
|
||||
|
||||
/** 위로 · 아래로 단추 둘 — 고치는 사람만 · 옮겼으면 `changed` */
|
||||
export function moveButtons<T>(
|
||||
list: T[],
|
||||
item: T,
|
||||
changed: () => void,
|
||||
edit: boolean,
|
||||
): HTMLElement[] {
|
||||
if (!edit) return [];
|
||||
const index = list.indexOf(item);
|
||||
const make = (label: string, delta: number, off: boolean): HTMLButtonElement => {
|
||||
const b = createButton({ label, variant: "ghost", size: "sm" });
|
||||
b.disabled = off;
|
||||
b.addEventListener("click", () => step(list, list.indexOf(item), delta) && changed());
|
||||
return b;
|
||||
};
|
||||
return [make(to("GroupUp"), -1, index <= 0), make(to("GroupDown"), 1, index >= list.length - 1)];
|
||||
}
|
||||
|
||||
/** 절 제목 + 안내 한 줄 */
|
||||
export const head = (title: string, note?: string): HTMLElement[] => [
|
||||
el("h4", { className: "m02-opts__group", text: title }),
|
||||
...(note ? [el("p", { className: "m02-side__empty", text: note })] : []),
|
||||
];
|
||||
Reference in New Issue
Block a user