diff --git a/B05_Profile/B05_Profile_UI_Structures_Panel.ts b/B05_Profile/B05_Profile_UI_Structures_Panel.ts index e9bd9aec..175d049b 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Panel.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Panel.ts @@ -192,6 +192,10 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu * 미리 넣어 세부유역·설계유량 미리보기를 얻고, [추가]로만 확정한다. 취소(삭제· * 리셋·다른 항목 선택·종류 변경)하면 관을 물려 추가 직전 상태로 되돌린다. */ let tempPipeChainage: number | null = null; + /** 위치 입력이 "확정"됐는지 — 측점번호+잔여거리를 다 받고 입력군을 빠져나간 + * 순간(또는 폼에 위치를 실어 연 순간)에만 true. 옵션 활성화 시점도 임시 배치와 + * 같은 이 순간이다(2026-08-18 사용자 지시). */ + let positionConfirmed = false; let optionInputs: Array<{ key: string; required: boolean; @@ -255,7 +259,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu * 입력 순서 가이드(2026-08-18 사용자 지시). 자동 배치 시설은 측점을 갖고 폼에 * 올라오므로 항상 열려 있다. */ function syncOptionLock(): void { - const locked = !hasPosition(); + const locked = !(positionConfirmed && hasPosition()); optionInputs.forEach((entry) => { entry.input.disabled = locked; }); @@ -286,8 +290,18 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu syncButtons(); } - /** 측점 입력 변경 — 옵션 잠금만 다시 판정한다(임시 배치는 commitTempPipe 몫). */ + /** 측점 입력 변경 — 값을 지우면 확정이 풀려 옵션이 즉시 다시 잠긴다. + * 확정(활성화)은 입력군 이탈 시 commitPosition이 한다. */ function handleStationInput(): void { + if (!hasPosition()) positionConfirmed = false; + syncOptionLock(); + } + + /** 위치 입력 확정 — 측점번호+잔여거리를 다 받고 입력군을 빠져나온 순간. + * 옵션 활성화와 A군 임시 배치가 이 한 지점에서 같이 나간다(2026-08-18). */ + function commitPosition(): void { + positionConfirmed = hasPosition(); + commitTempPipe(); syncOptionLock(); } @@ -419,6 +433,8 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu cancelTempPipe(); editingId = target?.structure_id ?? null; selectedPipeChainage = null; + // 위치가 실려 열리는 폼(기존 항목)은 확정 상태, 빈 폼은 미확정으로 시작한다. + positionConfirmed = target !== null; const step = interval(); if (target) { const type = typeMap().get(target.type_id); @@ -469,6 +485,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu const hadStructure = editingId !== null; editingId = null; selectedPipeChainage = pipe.chainage_m; + positionConfirmed = true; // 기존 관 — 위치가 실려 열린다. const step = interval(); const type = typeMap().get(pipe.facility); if (type) { @@ -657,7 +674,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu // 따라 비어 온다). window.requestAnimationFrame(() => { if (positionRow.contains(document.activeElement)) return; - commitTempPipe(); + commitPosition(); }); }); positionRow.addEventListener("keydown", (event) => { @@ -761,6 +778,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu typeSelect.value = typeId; editingId = null; selectedPipeChainage = null; + positionConfirmed = true; // 우클릭은 측점이 확정돼 들어온다 — 옵션도 바로 연다. const placement = placementOf(typeId); const step = interval(); const isInterval = !type.managed_by && placement === "interval"; @@ -773,7 +791,7 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu // BOX암거는 레지스트리 기본값(본체 2.0×2.0·날개벽 있음 1m/2m/45°)을 실어 폼을 // 연다 — [추가]만 눌러도 하류가 제원을 받는다(2026-08-17 사용자 확정 유지). syncFacilityForm(typeId === "box_culvert" ? defaultOptions(type) : {}); - // 우클릭은 측점이 이미 확정돼 들어온다 — 임시 배치 즉시 진행. + // 임시 배치도 즉시 진행(A군). if (type.managed_by) commitTempPipe(); syncButtons(); renderList();