From 7172d5654aecdaabc4ce16a369db2de72fee2162 Mon Sep 17 00:00:00 2001 From: MasterBabol Date: Sun, 30 Aug 2026 11:42:54 +0900 Subject: [PATCH] fix: announce 3d camera orientation --- apps/array-builder/main.js | 37 ++++++++++++++++++---- tests/integration/array-builder-ui.test.js | 24 ++++++++++++++ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/apps/array-builder/main.js b/apps/array-builder/main.js index 5951345..455f243 100644 --- a/apps/array-builder/main.js +++ b/apps/array-builder/main.js @@ -85,8 +85,7 @@ const beamPatternSurfaceView = createReceiveBeamPatternSurfaceView( elements["beam-pattern-surface"], { onViewDirectionChange(direction) { - elements["beam-pattern-axis-view"].value = direction; - document.documentElement.dataset.beamPatternViewAxis = direction; + syncBeamViewDirection(direction); }, }, ); @@ -372,8 +371,29 @@ const VIEW_DIRECTION_LABELS = Object.freeze({ custom: "자유 회전", }); +function resolvedViewDirection(direction) { + return Object.hasOwn(VIEW_DIRECTION_LABELS, direction) ? direction : "iso"; +} + +function viewDirectionDescription(direction) { + const resolved = resolvedViewDirection(direction); + return resolved === "custom" + ? VIEW_DIRECTION_LABELS.custom + : VIEW_DIRECTION_LABELS[resolved] + " 원점 보기"; +} + +function syncBeamViewDirection(direction) { + const resolved = resolvedViewDirection(direction); + elements["beam-pattern-axis-view"].value = resolved; + document.documentElement.dataset.beamPatternViewAxis = resolved; + elements["beam-pattern-surface"].setAttribute( + "aria-label", + "3D 수신 빔 패턴 표면 · " + viewDirectionDescription(resolved), + ); +} + function syncArrayViewDirection(direction) { - const resolved = Object.hasOwn(VIEW_DIRECTION_LABELS, direction) ? direction : "iso"; + const resolved = resolvedViewDirection(direction); elements["array-axis-view"].value = resolved; elements["array-canvas"].dataset.viewAxis = resolved; document.documentElement.dataset.arrayViewAxis = resolved; @@ -381,6 +401,10 @@ function syncArrayViewDirection(direction) { elements["canvas-front-badge"].textContent = resolved === "custom" ? "+Z FRONT · 자유 회전" : "시점 " + VIEW_DIRECTION_LABELS[resolved] + " 원점 보기 · +Z FRONT"; + elements["array-canvas"].setAttribute( + "aria-label", + "마이크 어레이 3D 검사 canvas · " + viewDirectionDescription(resolved), + ); } } @@ -434,7 +458,6 @@ function setArrayViewMode(mode, options = {}) { elements["canvas-front-badge"].textContent = resolved === "3d" ? "+Z FRONT · 초록 축" : "+Z FRONT · 화면 바깥쪽"; - if (resolved === "3d") syncArrayViewDirection(elements["array-axis-view"].value); elements["array-canvas"].setAttribute( "aria-label", resolved === "3d" @@ -447,6 +470,7 @@ function setArrayViewMode(mode, options = {}) { ? "ArrowLeft ArrowRight ArrowUp ArrowDown F R Escape" : "V A F Escape Delete ArrowLeft ArrowRight ArrowUp ArrowDown", ); + if (resolved === "3d") syncArrayViewDirection(elements["array-axis-view"].value); setTool(state.tool); canvasView.render(canvasState()); if (options.focus) canvasView.focus(); @@ -1409,7 +1433,7 @@ elements["array-view-3d-button"].addEventListener("click", () => elements["array-axis-view"].addEventListener("change", () => { const direction = elements["array-axis-view"].value; canvasView.setViewDirection(direction); - announceSelection("3D 시점 · " + VIEW_DIRECTION_LABELS[direction] + " 원점 보기"); + announceSelection("3D 시점 · " + viewDirectionDescription(direction)); }); elements["fit-view-button"].addEventListener("click", () => canvasView.fit()); elements["zoom-in-button"].addEventListener("click", () => canvasView.zoomBy(1.25)); @@ -1659,8 +1683,7 @@ window.addEventListener("unload", () => { elements["snap-enabled"].checked = state.snapEnabled; setNumber(elements["snap-step"], state.snapStepM * 1000); syncArrayViewDirection("iso"); -elements["beam-pattern-axis-view"].value = "iso"; -document.documentElement.dataset.beamPatternViewAxis = "iso"; +syncBeamViewDirection("iso"); setArrayViewMode("2d", { announce: false }); setBeamPatternViewMode("cuts", { render: false }); syncBeamPatternFrequency(); diff --git a/tests/integration/array-builder-ui.test.js b/tests/integration/array-builder-ui.test.js index 0c3882a..c8a6449 100644 --- a/tests/integration/array-builder-ui.test.js +++ b/tests/integration/array-builder-ui.test.js @@ -282,6 +282,10 @@ test("Array Builder가 preset·직접 편집·검증·표준 JSON round trip을 const beamAxisView = documentObject.querySelector("#beam-pattern-axis-view"); assertEqual(beamAxisView.value, "iso", "3D beam surface starts in isometric view"); assertEqual(beamSurface.dataset.viewAxis, "iso", "beam canvas exposes isometric provenance"); + assert( + beamSurface.getAttribute("aria-label").includes("등각 원점 보기"), + "beam canvas accessible name exposes its current view", + ); const beamComputeBeforeAxisView = documentObject.documentElement.dataset.beamPatternComputeMs; const beamFrequencyBeforeAxisView = beamSurface.dataset.frequencyHz; @@ -357,6 +361,10 @@ test("Array Builder가 preset·직접 편집·검증·표준 JSON round trip을 ); assertEqual(beamSurface.dataset.viewAxis, "custom", "beam drag enters free-orbit mode"); assertEqual(beamAxisView.value, "custom", "beam view selector reflects free orbit"); + assert( + beamSurface.getAttribute("aria-label").includes("자유 회전"), + "beam canvas accessible name follows free orbit", + ); beamSurface.dispatchEvent(new windowObject.KeyboardEvent("keydown", { bubbles: true, cancelable: true, @@ -365,6 +373,10 @@ test("Array Builder가 preset·직접 편집·검증·표준 JSON round trip을 await nextPaint(windowObject); assertEqual(beamSurface.dataset.viewAxis, "iso", "beam R shortcut restores isometric view"); assertEqual(beamAxisView.value, "iso", "beam selector follows the R shortcut"); + assert( + beamSurface.getAttribute("aria-label").includes("등각 원점 보기"), + "beam accessible name follows the R shortcut", + ); assertEqual( documentObject.documentElement.dataset.beamPatternComputeMs, beamComputeBeforeAxisView, @@ -446,6 +458,10 @@ test("Array Builder가 preset·직접 편집·검증·표준 JSON round trip을 const arrayAxisView = documentObject.querySelector("#array-axis-view"); assert(!arrayAxisControl.hidden, "array axis selector appears only for 3D inspection"); assertEqual(arrayAxisView.value, "iso", "array 3D starts in isometric view"); + assert( + canvas.getAttribute("aria-label").includes("등각 원점 보기"), + "array canvas accessible name exposes its current view", + ); const arraySaveStateBeforeAxisView = documentObject.querySelector("#save-state").textContent; const array3dBeforeAxisView = canvas.toDataURL(); @@ -517,6 +533,10 @@ test("Array Builder가 preset·직접 편집·검증·표준 JSON round trip을 ); assertEqual(canvas.dataset.viewAxis, "custom", "array drag enters free-orbit mode"); assertEqual(arrayAxisView.value, "custom", "array view selector reflects free orbit"); + assert( + canvas.getAttribute("aria-label").includes("자유 회전"), + "array canvas accessible name follows free orbit", + ); assertEqual( documentObject.querySelector('#microphone-list [aria-selected="true"]') ?.dataset.microphoneId, @@ -531,6 +551,10 @@ test("Array Builder가 preset·직접 편집·검증·표준 JSON round trip을 await nextPaint(windowObject); assertEqual(canvas.dataset.viewAxis, "iso", "array R shortcut restores isometric view"); assertEqual(arrayAxisView.value, "iso", "array selector follows the R shortcut"); + assert( + canvas.getAttribute("aria-label").includes("등각 원점 보기"), + "array accessible name follows the R shortcut", + ); assertEqual( documentObject.querySelector("#save-state").textContent, arraySaveStateBeforeAxisView,