- 형상 고름 판정면(B06_Section_UI_Cross_Shape_Pick) — data-structure-id 단 형상마다 · 구조물 판정면 앞(아래)에 꽂아 보라 손잡이 판정이 위(요구 185 ①)
- 누름만 고름 — 3 px 넘게 움직이면 끌기라 고름 아님(②) · 노면공 포장층(범위 표식)은 밖 · 포장 고름과 안 섞임(③)
- 누름 → toggleRevet("pipe") = 세월교 · BOX 와 같은 창구(폼 · 알약 · 측점선) · 물넘이 범위 옆 카드도 그 물넘이 자리로
- 목록 · 알약 · 폼 고름 → 카드 형상 강조(highlightShapes) · 물넘이 포장층 표식 = 관 지점 알약 id
- 시험 test_287(판정면 차례 · 누름 · 끌기 · 강조)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
61 lines
2.9 KiB
TypeScript
61 lines
2.9 KiB
TypeScript
/* =============================================================================
|
|
* B06_Section_UI_Cross_Shape_Pick.ts
|
|
* 물넘이포장 · 맹암거 형상 고름(287-5 · 288-2 · 요구 185).
|
|
*
|
|
* 그린 형상(`data-structure-id` 단 포장층 · 돌 무늬 둘레)마다 투명 판정면 — 누르면 그 구조물을 고름
|
|
* (다른 시설 형상 고름과 같은 창구 `onPick`). 지킬 것:
|
|
* ① 보라 손잡이(설계선 판정 · 뒤에 붙음)가 위 — 판정면은 구조물 판정면 바로 앞(아래)에 꽂고
|
|
* 구조물 판정 클래스(`b06-chart__culvert-revet-hit`)를 안 씀(보라 판정이 그 앞에 꽂혀 형상 위로 옴).
|
|
* ② 누름만 고름 — 눌렀다 3 px 넘게 움직이면(끌기) 고름 아님.
|
|
* ③ 노면공 포장 고름과 안 섞임 — 노면공 범위 표식(`data-pave-type`) 없는 형상만 · 누름은 번지지 않음.
|
|
* ========================================================================== */
|
|
|
|
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
/** 끌기로 볼 움직임(px) — 이보다 크면 고름 아님. */
|
|
export const SHAPE_DRAG_PX = 3;
|
|
|
|
/** 판정면을 꽂고 강조 setter 를 돌려줌 — 형상이 없으면 null. */
|
|
export function attachShapePick(
|
|
layer: SVGElement,
|
|
onPick?: (structureId: string) => void,
|
|
): ((active: boolean) => void) | null {
|
|
const shapes = [...layer.querySelectorAll<SVGElement>("[data-structure-id]")].filter(
|
|
(el) => !el.hasAttribute("data-pave-type"),
|
|
);
|
|
if (!shapes.length) return null;
|
|
const kids = [...layer.children];
|
|
const firstStructureHit = kids.find((el) =>
|
|
el.classList.contains("b06-chart__culvert-revet-hit"),
|
|
);
|
|
for (const shape of onPick ? shapes : []) {
|
|
const id = shape.getAttribute("data-structure-id") ?? "";
|
|
const hit = document.createElementNS(SVG_NS, "polygon");
|
|
hit.setAttribute("points", shape.getAttribute("points") ?? "");
|
|
hit.setAttribute("class", "b06-chart__shape-hit");
|
|
let down: { x: number; y: number } | null = null;
|
|
hit.addEventListener("pointerdown", (event) => {
|
|
down = { x: event.clientX, y: event.clientY };
|
|
});
|
|
hit.addEventListener("click", (event) => {
|
|
const moved =
|
|
down && Math.hypot(event.clientX - down.x, event.clientY - down.y) > SHAPE_DRAG_PX;
|
|
down = null;
|
|
if (moved) return; // 끌기 = 고름 아님(②)
|
|
event.stopPropagation();
|
|
onPick?.(id);
|
|
});
|
|
layer.insertBefore(hit, firstStructureHit ?? null);
|
|
}
|
|
return (active) => shapes.forEach((shape) => shape.classList.toggle("is-active", active));
|
|
}
|
|
|
|
/** 카드 안 형상 강조 — 고른 구조물 id 와 같은 형상만(목록 · 알약 · 폼 고름이 따라옴). */
|
|
export function highlightShapes(root: ParentNode, structureId: string | null): void {
|
|
for (const shape of root.querySelectorAll<SVGElement>("[data-structure-id]")) {
|
|
shape.classList.toggle(
|
|
"is-active",
|
|
!!structureId && shape.getAttribute("data-structure-id") === structureId,
|
|
);
|
|
}
|
|
}
|