From 7df9ae622ac61b881268c6a353ca335c33fbfb69 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 18 Aug 2026 19:51:08 +0900 Subject: [PATCH] =?UTF-8?q?feat(B05):=20=EC=B8=A1=EC=A0=90=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EC=A0=84=EC=97=AD=20=EC=A0=95=EA=B7=9C=ED=99=94=20?= =?UTF-8?q?=E2=80=94=20=EC=9E=94=EC=97=AC=EA=B1=B0=EB=A6=AC=20=EB=84=98?= =?UTF-8?q?=EC=B9=A8=EC=9D=84=20=EC=B8=A1=EC=A0=90=EB=B2=88=ED=98=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=98=AC=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 측점 표기 규칙(2026-08-18 사용자 확정): 잔여거리는 [0, 간격 20m) 범위만 유효, 넘치면 측점번호로 올려 표시한다(1+30 → 2+10). 저장 수치는 원래 누가거리라 계산은 이미 동일했고, 입력 칸 표시를 확정(change) 시점에 정규화한다. 시작·기준·종료 측점 세 칸 공통(stationFields 확장). 검증(헤드 브라우저): 1+30→2+10.0, 0+45→2+5.0, 3+19.9 유지, 3+20→4+0.0 PASS. Co-Authored-By: Claude Opus 5 (1M context) --- .../B05_Profile_UI_Structures_Fields.ts | 32 +++++++++++++++++-- .../B05_Profile_UI_Structures_Panel.ts | 8 +++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/B05_Profile/B05_Profile_UI_Structures_Fields.ts b/B05_Profile/B05_Profile_UI_Structures_Fields.ts index 95eecae5..4950c574 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Fields.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Fields.ts @@ -7,7 +7,7 @@ * 저장은 누가거리(m), 화면 입출력만 측점번호+잔여거리. * ========================================================================== */ -import { chainageToStation } from "./B05_Profile_Util_Station"; +import { chainageToStation, stationToChainage } from "./B05_Profile_Util_Station"; export function field(labelText: string, input: HTMLElement): HTMLLabelElement { const wrapper = document.createElement("label"); @@ -44,7 +44,14 @@ export interface StationFields { clearInvalid: () => void; } -export function stationFields(labelText: string): StationFields { +export function stationFields( + labelText: string, + /** 측점간격(m) 공급자 — 주면 입력 확정(change) 때 표시를 정규화한다: + * 잔여거리는 [0, 간격) 범위만 유효하고, 넘치면 측점번호로 올린다(1+30 → 2+10). + * 프로그램 전역 공통 규칙(2026-08-18 사용자 확정). 저장 수치는 원래부터 + * 누가거리라 표시만 맞추면 된다. */ + getInterval?: () => number, +): StationFields { const station = numberInput("1", "0"); station.placeholder = "측점"; const remainder = numberInput("0.1", "0"); @@ -57,6 +64,27 @@ export function stationFields(labelText: string): StationFields { station.classList.toggle("is-invalid", bad); remainder.classList.toggle("is-invalid", bad); }; + + if (getInterval) { + const normalize = (): void => { + const intervalM = getInterval(); + if (!(intervalM > 0)) return; + const stationText = station.value.trim(); + const remainderText = remainder.value.trim(); + if (!stationText && !remainderText) return; + const stationNo = stationText ? Number(stationText) : 0; + const rest = remainderText ? Number(remainderText) : 0; + if (!Number.isFinite(stationNo) || stationNo < 0 || !Number.isFinite(rest) || rest < 0) + return; + // 잔여 넘침(≥간격)·소수 측점번호만 손본다 — 그 외 표기는 사용자가 친 그대로 둔다. + if (rest < intervalM && Number.isInteger(stationNo)) return; + const parts = chainageToStation(stationToChainage(stationNo, rest, intervalM), intervalM); + station.value = String(parts.station); + remainder.value = parts.remainder.toFixed(1); + }; + // 다른 change 리스너(옵션 잠금·임시 배치)보다 먼저 등록돼 정규화된 값이 읽힌다. + [station, remainder].forEach((input) => input.addEventListener("change", normalize)); + } return { wrap, station, diff --git a/B05_Profile/B05_Profile_UI_Structures_Panel.ts b/B05_Profile/B05_Profile_UI_Structures_Panel.ts index d026640f..ea2b8d96 100644 --- a/B05_Profile/B05_Profile_UI_Structures_Panel.ts +++ b/B05_Profile/B05_Profile_UI_Structures_Panel.ts @@ -119,9 +119,11 @@ export function createStructuresSection(callbacks: StructuresCallbacks): Structu const typeSelect = select([]); // 위치 입력 순서 = 시작 측점 → 기준 측점 → 종료 측점 (2026-08-17 사용자 확정). // 점형은 기준 측점만 보인다. - const startFields = stationFields("시작 측점"); - const anchorFields = stationFields("기준 측점"); - const endFields = stationFields("종료 측점"); + // 측점 표기 전역 규칙(2026-08-18): 잔여거리는 [0, 간격) — 넘치면 측점번호로 + // 올려 표시한다(1+30 → 2+10). 정규화는 stationFields가 입력 확정 때 처리. + const startFields = stationFields("시작 측점", () => callbacks.getInterval()); + const anchorFields = stationFields("기준 측점", () => callbacks.getInterval()); + const endFields = stationFields("종료 측점", () => callbacks.getInterval()); const memoField = document.createElement("input"); memoField.type = "text"; memoField.placeholder = "메모(선택)";