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 = "메모(선택)";