Files
Aislo/B05_Profile/B05_Profile_UI_Structures_Widening.ts
T
eomsangdonandClaude Sonnet 5.5 33119bcc67 feat(B05): 노면공 확폭 구간 상자 · 물넘이 겹침 안내 문구 · 화면 시험 맞춤(87-3 · 87-4)
- 상세 내용 아래 「확폭 구간」 상자: 범위 안 확폭 > 0 측점을 이어진 구간으로 묶어 구간마다 켬/끔 단추(기본 켬) · 끈 구간 = options.widening_off "시작-끝,…"(측점 m) · 안 맞는 값 무시 · 횡단 자료 없으면 안내
- 횡단 측점 읽기 손잡이 pipeSections(design_structure_pipe_calc) · 구간 규칙 design_structure_widening(순수)
- 물넘이 겹침 안내 = 시작 · 종료 측점을 고치라는 문구
- 시험: test_87_widening_runs(새) · ui_checks 52-10 · 52-15 시작 · 종료 측점 꼴로 맞춤

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
2026-10-08 12:08:15 +09:00

103 lines
4.0 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_Structures_Widening.ts
* 노면공 「확폭 구간」 상자(PLAN 87-3) — 상세 내용 아래. 범위 안 확폭 > 0 측점을 이어진 구간으로 묶어
* 구간마다 단추(켬 = 확폭 포함 · 끔 = 규격 폭만 · 기본 켬). 끈 구간 = `options.widening_off`.
* 규칙(구간 묶기 · 글 읽고 쓰기)은 `design_structure_widening` · 횡단 측점 읽기는 `pipeSections`.
* ========================================================================== */
import { pipeSections } from "../A00_Common/design/design_structure_pipe_calc";
import {
isRunOff,
offForRuns,
toggleRun,
wideningRuns,
type WideningRun,
} from "../A00_Common/design/design_structure_widening";
import { stationLabel } from "../common_util/common_util_station";
import { group } from "./B05_Profile_UI_Drainage_Facility_Fields";
export interface WideningBox {
root: HTMLElement;
/** 종류 · 저장 값으로 다시 그림(범위형이 아니면 가림). */
paint: (visible: boolean, stored: string | number | undefined) => void;
/** 저장할 값 — 끈 구간이 없으면 빈 묶음. 횡단 자료가 없으면 저장 값을 그대로 지킴. */
read: () => Record<string, string>;
/** 범위 · 횡단 자료가 바뀌었을 때 다시 그림. */
refresh: () => void;
}
export function createWideningBox(hooks: {
range: () => { start: number | null; end: number | null };
interval: () => number;
onChange: () => void;
}): WideningBox {
const box = group("확폭 구간");
box.root.classList.add("b05-structure__widening");
box.root.hidden = true;
let off = "";
/** 지금 범위의 구간들 — 횡단 자료 · 범위가 없으면 null(+ 안내 글). */
const runs = (): { list: WideningRun[] | null; note: string } => {
const sections = pipeSections();
if (!sections?.length) return { list: null, note: "횡단 자료 없음" };
const { start, end } = hooks.range();
if (start === null || end === null || end <= start)
return { list: null, note: "시작 · 종료 측점을 넣으면 보임" };
const list = wideningRuns(sections, start, end);
return { list, note: list.length ? "" : "범위 안 확폭 없음" };
};
const paintBody = (): void => {
const { list, note } = runs();
if (!list?.length) {
const text = document.createElement("span");
text.className = "b05-structure__widening-note";
text.textContent = note;
box.body.replaceChildren(text);
return;
}
const step = hooks.interval();
box.body.replaceChildren(
...list.map((run) => {
const on = !isRunOff(run, off);
const button = document.createElement("button");
button.type = "button";
button.className = `b05-route__irregular-btn${on ? " is-primary" : ""}`;
button.setAttribute("aria-pressed", String(on));
button.dataset.wideningRun = `${run.start_m}-${run.end_m}`;
button.textContent = `${stationLabel(run.start_m, step)} ~ ${stationLabel(run.end_m, step)} · 확폭 ${Number(run.widening_m.toFixed(2))} m`;
button.title = on ? "확폭 포함 — 누르면 규격 폭만" : "규격 폭만 — 누르면 확폭 포함";
button.addEventListener("click", () => {
off = toggleRun(list, off, run);
paintBody();
hooks.onChange();
});
return button;
}),
);
};
// 횡단 자료가 늦게 오면 상자 위에 올 때 다시 그림
box.root.addEventListener("pointerenter", () => {
if (!box.root.hidden) paintBody();
});
return {
root: box.root,
paint(visible, stored) {
box.root.hidden = !visible;
off = typeof stored === "string" ? stored : "";
if (visible) paintBody();
},
read() {
const { list } = runs();
const text = list ? offForRuns(list, off) : off;
const out: Record<string, string> = text ? { widening_off: text } : {};
return out;
},
refresh() {
if (!box.root.hidden) paintBody();
},
};
}