/* ============================================================================= * 지도 우클릭 메뉴 (B04 2D 지도 · B05 배수유역도 공용) * * 지도 위 객체를 우클릭했을 때 뜨는 작은 메뉴. 뷰포트의 자식으로 넣고 뷰포트 기준 절대 * 위치로 띄운다. * * **주의**: 메뉴는 뷰포트 안에 있으므로 메뉴 위에서 난 pointer 이벤트가 지도 조작으로 * 새어 들어간다. 걸러 내지 않으면 메뉴를 닫는 처리가 항목의 click보다 먼저 돌아 아무 일도 * 일어나지 않는다(2026-08-01 B04에서 실제로 발생). 그래서 `contains()`를 함께 제공한다 — * 지도 쪽 pointerdown/pointerup/contextmenu는 이 판정으로 먼저 걸러야 한다. * ========================================================================== */ export interface MapContextMenu { /** 뷰포트에 append할 요소. */ element: HTMLElement; /** 항목은 [문구, 눌렀을 때 할 일] 짝. 위치는 뷰포트 기준 px. */ open: (x: number, y: number, items: Array<[label: string, action: () => void]>) => void; close: () => void; /** 이벤트가 메뉴 안에서 났는지. 지도 조작에서 걸러 내는 데 쓴다. */ contains: (target: EventTarget | null) => boolean; } /** `blockClass`는 페이지 CSS 블록명(예: `b04-map`, `b05-drainage`). */ export function createMapContextMenu(blockClass: string): MapContextMenu { const element = document.createElement("div"); element.className = `${blockClass}__context-menu`; element.hidden = true; function close(): void { element.hidden = true; // 항목까지 비운다 — 남겨 두면 다음에 열었을 때 옛 동작이 함께 걸린다. element.replaceChildren(); } return { element, open(x, y, items) { element.replaceChildren(); items.forEach(([label, action]) => { const entry = document.createElement("button"); entry.type = "button"; entry.className = `${blockClass}__context-menu-item`; entry.textContent = label; entry.addEventListener("click", () => { close(); action(); }); element.append(entry); }); element.style.left = `${x}px`; element.style.top = `${y}px`; element.hidden = false; }, close, contains: (target) => target instanceof Node && element.contains(target), }; }