/* ============================================================================= * B06_Section_UI_Cross_View_Box.ts * 횡단 카드 ↔ **BOX암거 조정창** 배선과 구조물 선택 토글 — 카드 렌더러 * (`_UI_Cross_View.ts`)에서 700줄 제한으로 분리했다(2026-08-25). * 기하나 그리기는 하지 않는다. * ========================================================================== */ import type { BoxLayout } from "./B06_Section_UI_Cross_Box"; import type { BoxControl, BoxPanelDeps } from "./B06_Section_UI_Cross_Box_Panel"; export interface BoxPanelContext { chainageM: number; box: BoxControl; /** 마지막 계산 결과 — 창이 길이·물매를 읽는다. */ layout: () => BoxLayout | null; /** 유입(상류)이 화면 좌측인가 — 좌·우를 폼의 유입/유출 칸으로 옮길 때 쓴다 * (기하가 날개벽을 고르는 기준과 같다, 2026-08-30). */ inletOnLeft: boolean; close: () => void; } export function boxPanelDeps(context: BoxPanelContext): BoxPanelDeps { const { chainageM, box } = context; return { adjustFor: (role) => box.adjustFor(chainageM)[role], // 화면 좌(◀) = offset 증가. 좌측 끝은 그대로, 우측 끝은 부호를 뒤집어야 바깥이 된다. nudgeLength: (role, screenDeltaM) => box.update(chainageM, role, { lengthM: box.adjustFor(chainageM)[role].lengthM + screenDeltaM * (role === "left" ? 1 : -1), }), nudgeRise: (role, deltaM) => box.update(chainageM, role, { riseM: box.adjustFor(chainageM)[role].riseM + deltaM }), reset: (role) => box.reset(chainageM, role), wingRoleFor: (role) => ((role === "left") === context.inletOnLeft ? "inlet" : "outlet"), bodyLengthM: () => context.layout()?.bodyLengthM ?? 0, slopeRatio: () => context.layout()?.slopeRatio ?? null, close: context.close, }; } /** * 구조물 선택 토글 한 벌 — 세월교 측벽·BOX암거 끝이 같은 규칙을 쓴다(2026-08-21 확정 * 규칙: 구조물 선택과 면적 강조는 배타, 고르면 그 측점 카드도 함께 선택된다). */ export function structureToggle(deps: { current: () => T | null; setCurrent: (value: T | null) => void; setActive: (value: T | null) => void; showPanel: (visible: boolean) => void; persist: (value: T | null) => void; isCardSelected: () => boolean; clearArea: () => void; selectCard: () => void; }): (value: T) => void { return (value: T): void => { const wasSelected = deps.isCardSelected(); const next = deps.current() === value ? null : value; deps.setCurrent(next); deps.setActive(next); deps.showPanel(next !== null); deps.persist(next); if (next === null) return; deps.clearArea(); // 미선택 카드는 카드 선택만 부른다 — 그 경로가 부모의 면적 강조를 이미 비운다. if (!wasSelected) deps.selectCard(); }; }