diff --git a/A00_Common/design/design_structure_form.ts b/A00_Common/design/design_structure_form.ts index b46ea9d03..f084652f0 100644 --- a/A00_Common/design/design_structure_form.ts +++ b/A00_Common/design/design_structure_form.ts @@ -202,7 +202,8 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = { const instanceBox = (c: SpecContainer, values: Instance, index: number): HTMLElement => { const title = containerTitle(c, names, groupNames[c.link]); - const box = group(c.required ? title : `${title} ${index + 1}`); + // 하나만 서는 상자(max 1)는 번호 없음(54-50) + const box = group(c.required || c.max === 1 ? title : `${title} ${index + 1}`); box.root.dataset.specContainer = c.id; box.root.dataset.specIndex = String(index); // 칸을 고친 뒤 다시 그려도 몇째 상자인지(54-30) // 필수 상자 빈 고르기 칸 = 정의 기본값(없으면 첫 보기)을 실제 값으로 — 저장도 그 값(54-43) diff --git a/A00_Common/design/design_structure_pipe.ts b/A00_Common/design/design_structure_pipe.ts index f00cae056..655e31ff8 100644 --- a/A00_Common/design/design_structure_pipe.ts +++ b/A00_Common/design/design_structure_pipe.ts @@ -171,6 +171,7 @@ export const PIPE_SPEC: StructureSpec = { required: false, prefix: "chute_add", link: "gchuteadd", + max: 1, // 하나만(54-48) — 옛 둘 이상은 보이되 [추가]만 숨김 items: [ { id: "structure", diff --git a/A00_Common/design/design_view_cross.ts b/A00_Common/design/design_view_cross.ts index 9f7de9d59..753f19d24 100644 --- a/A00_Common/design/design_view_cross.ts +++ b/A00_Common/design/design_view_cross.ts @@ -377,6 +377,8 @@ export async function mountCrossView( from: "b06", }), (chainageM) => sectionView.refreshCard(chainageM), + // 지금 보기의 목록 고름 길 — 횡단 = 측점 카드 · 종단 = 측점선 · 알약(54-47) + (chainageM) => shared.bindings[shared.view()]?.structures?.onPipeSelect?.(chainageM), ); // 횡단도(카드) 자체를 골라도 그 측점 구조물 정보를 폼에 올린다(2026-08-29 사용자). // 연동으로 옆에서 넘어온 카드는 **소유 측점** 시설을 보여 준다. diff --git a/B06_Section/B06_Section_UI_Page_Structures_Panel.ts b/B06_Section/B06_Section_UI_Page_Structures_Panel.ts index dfc668894..387a15a63 100644 --- a/B06_Section/B06_Section_UI_Page_Structures_Panel.ts +++ b/B06_Section/B06_Section_UI_Page_Structures_Panel.ts @@ -571,6 +571,8 @@ export function wireStructureSelection( remember?: (chainageM: number, key: string) => void, /** 폼에서 고른 벽을 카드 조정창에도 세움(54-30 · 목록 넘김 `applyStructurePick` 과 같은 차례). */ refreshCard?: (chainageM: number) => void, + /** 폼에 올린 시설로 보기를 옮김 — 목록에서 고른 길(54-47). */ + reveal?: (chainageM: number) => void, ): { syncInletStructure: () => void } { const ownerChainageOf = (chainageM: number): number => { const sectionAt = detail()?.cross_sections.find( @@ -614,23 +616,54 @@ export function wireStructureSelection( }; // 관 상자 칸을 고치거나 고르면 그 부분을 고름 — 횡단에서 누른 것과 같은 길(54-30) - panel.root.addEventListener("change", (event) => { - // 단 수 칸은 고른 단을 지킴(단 수 바꿈 뒤 강조 유지) - if ((event.target as HTMLElement).closest('[data-spec-item="outlet.tiers"]')) return; - const box = (event.target as HTMLElement).closest("fieldset[data-spec-container]"); + const keyOf = (box: HTMLElement): RevetKey | null => { + const id = box.dataset.specContainer; + if (id === "inlet" || id === "outlet") return id; + return id === "soil_add" ? `extra${Number(box.dataset.specIndex)}` : null; + }; + const pickBox = (box: HTMLElement): void => { const chainageM = panel.currentChainage(); const target = chainageM === null ? undefined : sectionAt(chainageM); - if (!box || !target?.culvert) return; - const id = box.dataset.specContainer; - const key: RevetKey | null = - id === "inlet" || id === "outlet" - ? id - : id === "soil_add" - ? `extra${Number(box.dataset.specIndex)}` - : null; - if (!key || stationControls.revetOffset.highlightFor(target) === key) return; + const key = keyOf(box); + if (!key || !target?.culvert || stationControls.revetOffset.highlightFor(target) === key) + return; stationControls.revetOffset.select(target.chainage_m, key); refreshCard?.(target.chainage_m); + }; + // 그 측점 카드 · 알약으로 감(목록에서 고른 길 · 54-47) — 같은 칸을 잇달아 고치면 한 번만(다시 그려도 칸 이름으로) + const fieldOf = (el: HTMLElement): string => { + const box = el.closest("fieldset[data-spec-container]"); + const item = el.closest("[data-spec-item]")?.dataset.specItem; + return `${box?.dataset.specContainer}:${box?.dataset.specIndex}:${item}`; + }; + let revealed: string | null = null; + const revealAt = (chainageM: number, field: string | null): void => { + if (field && field === revealed) return; + revealed = field; + reveal?.(chainageM); + }; + panel.root.addEventListener("focusin", (event) => { + if (fieldOf(event.target as HTMLElement) !== revealed) revealed = null; + }); + panel.root.addEventListener("change", ({ target: el }) => { + const box = (el as HTMLElement).closest("fieldset[data-spec-container]"); + const chainageM = panel.currentChainage(); + if (!box || chainageM === null) return; + revealAt(chainageM, fieldOf(el as HTMLElement)); + // 단 수 칸은 고른 단을 지킴(단 수 바꿈 뒤 강조 유지) — 새 단은 다시 그린 뒤 아래에서 고름 + if (!(el as HTMLElement).closest('[data-spec-item="outlet.tiers"]')) pickBox(box); + }); + // 상자가 새로 서면(단 늘림 · [추가]) 그 상자를 고름 — 같은 시설을 다시 그린 때만 + let painted: { chainageM: number | null; keys: Set } | null = null; + panel.root.addEventListener("specpaint", () => { + const chainageM = panel.currentChainage(); + const boxes = [...panel.root.querySelectorAll("fieldset[data-spec-container]")]; + const before = painted?.chainageM === chainageM ? painted.keys : null; + painted = { chainageM, keys: new Set(boxes.map(fieldOf)) }; + const born = before ? boxes.filter((box) => !before.has(fieldOf(box))).at(-1) : undefined; + if (!born || chainageM === null) return; + revealAt(chainageM, null); + pickBox(born); }); const origFord = stationControls.ford.select;