/* ============================================================================= * map_draw_kind.ts * 공용 긋기 방식 칸(PLAN 626-3 · 599-1 · 625-1) — 예정노선(D03) 그리기 칸 · 노드 판 · B05 평면 예정노선 그리기가 같이 씀. * 꼴 = 마디 고름 2 × 2(자유지정 · 길이·기울기 고정 / 길이 고정 · 기울기 고정) + 길이 · 기울기 두 열(기울기 아래 ≈ °) · * 방식에 안 쓰는 칸은 숨김(막힘 = 권한 없음만) · 설명 글 없음(요구 463-6). * 쓰는 법 = `createDrawKindFields({ value, onChange })` → { root, get, set, setDisabled }. * ========================================================================== */ import type { UiSize } from "@ui/ui_template_button"; import { createInputField } from "@ui/ui_template_input"; import { createSegment } from "@ui/ui_template_segment"; import { createText } from "@ui/ui_template_text"; import "./map_draw_kind.css"; /** 기울기 % → 각(°) 한 자리 글. */ const slopeDegText1 = (pct: number): string => ((Math.atan(pct / 100) * 180) / Math.PI).toFixed(1); /** 제약 칸 처음 값 — 길이 = 측점 간격(지식DB 설계제원_총괄) · 기울기 = 간선 20 km/h 일반지형 상한(종단선형). */ export const DRAW_DEFAULT_LENGTH_M = 20; export const DRAW_DEFAULT_SLOPE_PCT = 9; export type DrawConstraint = "free" | "length" | "slope" | "both"; export interface DrawKindValue { kind: DrawConstraint; lengthM: number; slopePct: number; } export const DRAW_KIND_LABELS: Record = { free: "자유지정", both: "길이·기울기 고정", length: "길이 고정", slope: "기울기 고정", }; export interface DrawKindFields { root: HTMLElement; get: () => DrawKindValue; /** 값을 바꿈(onChange 는 안 부름). */ set: (value: Partial) => void; setDisabled: (disabled: boolean) => void; } /** 긋기 방식 칸 — 고름 · 길이 · 기울기가 바뀔 때마다 `onChange(지금 값)`(빈 · 틀린 수는 칸 오류만). */ export function createDrawKindFields(opts: { value: DrawKindValue; onChange: (value: DrawKindValue) => void; size?: UiSize; }): DrawKindFields { const size = opts.size ?? 24; let value = { ...opts.value }; let disabled = false; const changed = (): void => opts.onChange({ ...value }); const segment = createSegment({ options: (Object.keys(DRAW_KIND_LABELS) as DrawConstraint[]).map((kind) => ({ value: kind, label: DRAW_KIND_LABELS[kind], })), value: value.kind, size, columns: 2, ariaLabel: "긋기 방식", onChange: (next) => { value.kind = next as DrawConstraint; fieldsState(); changed(); }, }); segment.root.dataset.role = "draw-constraint"; const lengthField = createInputField({ label: "길이", type: "number", value: String(value.lengthM), min: 1, unit: "m", size, onInput: (text) => { const next = Number(text); lengthField.setError(next > 0 ? undefined : "0 보다 큰 수"); if (!(next > 0)) return; value.lengthM = next; changed(); }, }); lengthField.input.dataset.role = "draw-length"; const slopeField = createInputField({ label: "기울기", type: "number", value: String(value.slopePct), unit: "%", size, onInput: (text) => { const next = Number(text); const ok = text.trim() !== "" && Number.isFinite(next); slopeField.setError(ok ? undefined : "숫자"); if (!ok) return; value.slopePct = next; degText(); changed(); }, }); slopeField.input.dataset.role = "draw-slope"; const slopeDegText = createText("", "muted"); slopeDegText.dataset.role = "draw-slope-deg"; const degText = (): void => { slopeDegText.textContent = `≈ ${slopeDegText1(Math.abs(value.slopePct))}°`; }; const slopeColumn = document.createElement("div"); slopeColumn.append(slopeField.root, slopeDegText); const fields = document.createElement("div"); fields.className = "map-draw-kind__fields"; fields.append(lengthField.root, slopeColumn); const root = document.createElement("div"); root.className = "map-draw-kind"; root.append(segment.root, fields); function fieldsState(): void { for (const button of segment.root.querySelectorAll("button")) button.disabled = disabled; lengthField.input.disabled = slopeField.input.disabled = disabled; fields.hidden = value.kind === "free"; lengthField.root.hidden = value.kind === "slope"; slopeColumn.hidden = value.kind === "length"; } degText(); fieldsState(); return { root, get: () => ({ ...value }), set(next) { value = { ...value, ...next }; segment.set(value.kind); lengthField.input.value = String(value.lengthM); slopeField.input.value = String(value.slopePct); lengthField.setError(undefined); slopeField.setError(undefined); degText(); fieldsState(); }, setDisabled(next) { disabled = next; fieldsState(); }, }; }