Files
Aislo/B06_Section/B06_Section_UI_Cross_Center.ts
T
eomsangdonandClaude Opus 5.5 8f5286ad79 feat(424 횡단 설계중심): 중심점 좌우 걷음 — ◀▶ 단추 · 가로 끌기 · 노선 좌우 배선 없음 · ▲▼ 종단 계획고 그대로 (요구 260)
- B06 중심점 칸 = 버튼 없음 · 「▲▼ = 종단 계획고 · 좌우 이동은 평면에서」 · ◀▶ 키 먹어 막음
- B06_Section_UI_Cross_Center = 판정 원 · 한 축 끌기(암 경계선)만 남김
- B05 노선 좌우 host(_RouteEdit_Shift) · 고름 계산(logic/B05_Profile_RouteShift) 지움 · 페이지 · 편집기 배선 한 줄씩 걷음
- 시험 356 = 가짜 DOM 으로 중심점 고름 · 버튼 없음 · 키 · 끌기 없음 확인 · 356_4 = 중심점 좌우 걸음 뺌

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-18 15:37:36 +09:00

74 lines
3.3 KiB
TypeScript

/* =============================================================================
* B06_Section_UI_Cross_Center.ts
* 횡단 카드 중심점 고름(356-3) — 보라 선 고름(`_Cross_Design_Pick`)의 끝 조각(키 `center`).
*
* · 판정 원 = 중심 십자 자리 · 노면 판정보다 위(`stackPickTargets`) · 다른 고름과 같은 칸(하나만)
* · 고르면 ▲▼ = 종단 계획고(지금 길 · 종단도 듣개) · 좌우는 평면에서만(424 · ◀▶ · 가로 끌기 없음)
* · 한 축 끌기(`bindAxisDrag`)는 암 경계선 세로 끌기(370-3)가 씀
* ========================================================================== */
const SVG_NS = "http://www.w3.org/2000/svg";
/** 판정 원 클래스 — 고르기 차례가 보라 선 위로 올림. */
export const CENTER_HIT_CLASS = "b06-chart__center-hit";
/** 판정 원 반지름(px). */
const HIT_RADIUS_PX = 8;
/** 끌기 걸음(m). */
const DRAG_STEP_M = 0.1;
/** 판정 원(중심 십자 자리). */
export function centerHit(cx: number, cy: number): SVGCircleElement {
const hit = document.createElementNS(SVG_NS, "circle");
hit.setAttribute("cx", String(cx));
hit.setAttribute("cy", String(cy));
hit.setAttribute("r", String(HIT_RADIUS_PX));
hit.setAttribute("class", CENTER_HIT_CLASS);
return hit;
}
const snapStep = (valueM: number): number =>
Math.round(Math.round(valueM / DRAG_STEP_M) * DRAG_STEP_M * 10) / 10;
/**
* 고른 판정을 한 축으로 끌기(370-3 암 경계선 세로) — 고른 뒤에만(안 고른 것을 끌면 카드
* 팬 그대로) · 걸음 0.1 m · 끄는 동안 `preview` · 놓을 때 움직였으면 한 번 `send`(안 움직이면 `preview(0)`) ·
* 놓은 자리 click 은 한 번 막음(같은 것 다시 누름 = 풂으로 안 번짐). `pxPerM` = 그 축 화면 px / m(layer 좌표).
*/
export function bindAxisDrag(input: {
hit: SVGElement;
layer: SVGGraphicsElement;
axis: "x" | "y";
pxPerM: number;
active: () => boolean;
preview: (deltaM: number) => void;
send: (deltaM: number) => void;
}): void {
const { hit, layer, axis, pxPerM, active } = input;
const at = (event: PointerEvent): number => (axis === "x" ? event.clientX : event.clientY);
hit.addEventListener("pointerdown", (event) => {
const ctm = layer.getScreenCTM();
if (event.button !== 0 || !active() || !ctm) return;
event.preventDefault();
event.stopPropagation(); // 카드 팬으로 안 번짐
const start = at(event);
const scale = (axis === "x" ? ctm.a : ctm.d) * pxPerM;
let deltaM = 0;
const move = (next: PointerEvent): void => {
deltaM = snapStep((at(next) - start) / scale);
input.preview(deltaM);
};
const up = (): void => {
window.removeEventListener("pointermove", move);
window.removeEventListener("pointerup", up);
window.removeEventListener("pointercancel", up);
if (!deltaM) return input.preview(0);
const swallow = (click: Event): void => click.stopPropagation();
window.addEventListener("click", swallow, { capture: true, once: true });
setTimeout(() => window.removeEventListener("click", swallow, { capture: true }), 0);
input.send(deltaM);
};
window.addEventListener("pointermove", move);
window.addEventListener("pointerup", up);
window.addEventListener("pointercancel", up);
});
}