diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Keys.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Keys.ts index c7db6c03d..98a82b7dd 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Keys.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Keys.ts @@ -9,6 +9,7 @@ * 일은 끌기(`_Pointer`)와 같음. 걸음 · 결과 한 줄 셈은 `logic/B05_Profile_RouteEdit_KeyNudge`. * ========================================================================== */ +import { listenWhileAlive } from "@ui/ui_template_window_watch"; import { lastPointerWithin } from "../B06_Section/B06_Section_UI_Cross_Panel_Base"; import { HOLD_DELAY_MS, HOLD_INTERVAL_MS } from "./B05_Profile_UI_Profile_Edit"; import type { RouteEditPointerParams } from "./B05_Profile_UI_RouteEdit_Pointer"; @@ -109,14 +110,8 @@ export function bindRouteEditKeys(params: RouteEditPointerParams, dragging: () = params.draw(); } - const detach = (): void => { - window.removeEventListener("keydown", onKeyDown, true); - window.removeEventListener("keyup", onKeyUp, true); - window.removeEventListener("blur", release); - }; - function onKeyDown(event: KeyboardEvent): void { - if (!canvas.isConnected) return detach(); + if (!canvas.isConnected) return; if (!isArrowKey(event.key) || !lastPointerWithin(".b05-routeedit")) return; // B06 겹침 평면도 같음(436) const target = event.target as HTMLElement | null; if (target?.closest?.("input, select, textarea, [contenteditable='true']")) return; @@ -147,14 +142,15 @@ export function bindRouteEditKeys(params: RouteEditPointerParams, dragging: () = } function onKeyUp(event: KeyboardEvent): void { - if (!canvas.isConnected) return detach(); + if (!canvas.isConnected) return; if (!hold || event.key !== hold.key) return; event.preventDefault(); event.stopPropagation(); release(); } - window.addEventListener("keydown", onKeyDown, true); - window.addEventListener("keyup", onKeyUp, true); - window.addEventListener("blur", release); + // 창 듣기는 캔버스 수명에 묶음 — 다시 세운 옛 페이지를 안 붙듦(298-3) + listenWhileAlive(window, "keydown", canvas, onKeyDown, true); + listenWhileAlive(window, "keyup", canvas, onKeyUp, true); + listenWhileAlive(window, "blur", canvas, release); } diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_StructureMarks.ts b/B05_Profile/B05_Profile_UI_RouteEdit_StructureMarks.ts index 9a59ceafc..00a6f244a 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_StructureMarks.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_StructureMarks.ts @@ -15,6 +15,7 @@ * ========================================================================== */ import { pickPx } from "../A00_Common/touch_bridge"; +import { listenWhileAlive } from "@ui/ui_template_window_watch"; import { draftProjectId } from "../A00_Common/design/design_store"; import { NO_SELECTION, @@ -365,13 +366,8 @@ export function bindStructureMarks( holdKey = null; finish(); }; - const detach = (): void => { - window.removeEventListener("keydown", onKeyDown, true); - window.removeEventListener("keyup", onKeyUp, true); - window.removeEventListener("blur", release); - }; function onKeyDown(event: KeyboardEvent): void { - if (!canvas.isConnected) return detach(); + if (!canvas.isConnected) return; const escape = event.key === "Escape"; if ((!escape && !isArrowKey(event.key)) || !lastPointerWithin(".b05-routeedit")) return; const target = event.target as HTMLElement | null; @@ -392,12 +388,13 @@ export function bindStructureMarks( }, HOLD_DELAY_MS); } function onKeyUp(event: KeyboardEvent): void { - if (!canvas.isConnected) return detach(); + if (!canvas.isConnected) return; if (event.key === holdKey) release(); } - window.addEventListener("keydown", onKeyDown, true); - window.addEventListener("keyup", onKeyUp, true); - window.addEventListener("blur", release); + // 창 듣기는 캔버스 수명에 묶음 — 다시 세운 옛 페이지를 안 붙듦(298-3) + listenWhileAlive(window, "keydown", canvas, onKeyDown, true); + listenWhileAlive(window, "keyup", canvas, onKeyUp, true); + listenWhileAlive(window, "blur", canvas, release); return { press(px, py) { diff --git a/resources/tester/helper_399_plan_structure_marks.cjs b/resources/tester/helper_399_plan_structure_marks.cjs index 0420bb689..540d11f70 100644 --- a/resources/tester/helper_399_plan_structure_marks.cjs +++ b/resources/tester/helper_399_plan_structure_marks.cjs @@ -104,6 +104,8 @@ function loadTs(full) { cache.set(full, box.exports); const localRequire = (request) => { if (STUBS[request]) return STUBS[request]; + if (request.startsWith("@ui/")) + return loadTs(path.join(B05, "..", "ui_template", `${request.slice(4)}.ts`)); if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); return require(request); }; diff --git a/resources/tester/test_298_3_page_dispose.py b/resources/tester/test_298_3_page_dispose.py index 2664f51d8..927f1c65b 100644 --- a/resources/tester/test_298_3_page_dispose.py +++ b/resources/tester/test_298_3_page_dispose.py @@ -53,6 +53,8 @@ def test_옛_페이지_풀림_지금_페이지는_들음(out) -> None: "scroll|resize", ), # 끌기 동안 pointer 듣기는 끝에 뗌 ("B05_Profile/B05_Profile_UI_Panel_Settings.ts", "pointerup|keyup"), + ("B05_Profile/B05_Profile_UI_RouteEdit_StructureMarks.ts", "keydown|keyup|blur"), + ("B05_Profile/B05_Profile_UI_RouteEdit_Keys.ts", "keydown|keyup|blur"), ], ) def test_전역_듣기는_노드_수명으로만(rel: str, types: str) -> None: diff --git a/resources/tester/test_383_2_route_keys.py b/resources/tester/test_383_2_route_keys.py index 13bf957c9..bece6c4c3 100644 --- a/resources/tester/test_383_2_route_keys.py +++ b/resources/tester/test_383_2_route_keys.py @@ -63,7 +63,9 @@ def test_이음_끌기_걸기_한_줄_창_가로채기() -> None: pointer = _read("B05_Profile_UI_RouteEdit_Pointer.ts") assert pointer.count("bindRouteEditKeys(") == 1 keys = _read("B05_Profile_UI_RouteEdit_Keys.ts") - assert 'window.addEventListener("keydown", onKeyDown, true)' in keys # 앞서 받음(capture) + assert ( + 'listenWhileAlive(window, "keydown", canvas, onKeyDown, true)' in keys + ) # 앞서 받음(capture) # 마지막 누름이 평면 안 — B06 겹침 평면도 같음(436) assert 'lastPointerWithin(".b05-routeedit")' in keys assert "event.preventDefault();\n event.stopPropagation();" in keys