diff --git a/app/styles.css b/app/styles.css index 25b5983..46482c8 100644 --- a/app/styles.css +++ b/app/styles.css @@ -1687,7 +1687,7 @@ .cube-rotation-controls { display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); + grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 6px; padding: 8px 12px 6px; border-bottom: 1px solid #27272a; @@ -1710,6 +1710,12 @@ color: #f4f4f5; } + .cube-rotation-btn[aria-pressed="true"] { + background: #312e81; + border-color: #818cf8; + color: #e0e7ff; + } + .cube-marker-mode-control { grid-column: 1 / -1; display: flex; @@ -2108,6 +2114,31 @@ border-bottom: none; } + .kab-layout.is-cube-focus { + grid-template-rows: minmax(0, 1fr); + } + + .kab-layout.is-cube-focus > .kab-detail-panel { + display: none; + } + + .kab-layout.is-cube-focus > .kab-tree-panel { + max-height: none; + min-height: 0; + height: 100%; + border-bottom: none; + } + + .kab-layout.is-cube-focus .kab-tree-container { + align-items: center; + padding: 18px; + } + + .kab-layout.is-cube-focus .kab-tree-container > .cube-svg { + width: min(100%, 760px); + max-height: calc(100vh - 190px); + } + .kab-tree-container { flex: 1; padding: 12px 14px 16px; diff --git a/app/ui-cube.js b/app/ui-cube.js index 52ff254..a927a33 100644 --- a/app/ui-cube.js +++ b/app/ui-cube.js @@ -15,7 +15,8 @@ showPrimalPoint: true, selectedConnectorId: null, selectedWallId: null, - selectedEdgeId: null + selectedEdgeId: null, + focusMode: false }; const CUBE_VERTICES = [ @@ -126,12 +127,15 @@ function getElements() { return { + cubeSectionEl: document.getElementById("cube-section"), + cubeLayoutEl: document.getElementById("cube-layout"), viewContainerEl: document.getElementById("cube-view-container"), rotateLeftEl: document.getElementById("cube-rotate-left"), rotateRightEl: document.getElementById("cube-rotate-right"), rotateUpEl: document.getElementById("cube-rotate-up"), rotateDownEl: document.getElementById("cube-rotate-down"), rotateResetEl: document.getElementById("cube-rotate-reset"), + focusToggleEl: document.getElementById("cube-focus-toggle"), markerModeEl: document.getElementById("cube-marker-mode"), connectorToggleEl: document.getElementById("cube-connector-toggle"), primalToggleEl: document.getElementById("cube-primal-toggle"), @@ -301,23 +305,58 @@ return cubeMathUi.getEdgeDirectionLabelForWall(wallId, edgeId); } + function rotateAndRender(deltaX, deltaY) { + setRotation(state.rotationX + deltaX, state.rotationY + deltaY); + render(getElements()); + } + + function resetRotationAndRender() { + setRotation(18, -28); + render(getElements()); + } + + function isKeyboardEditableTarget(target) { + if (!(target instanceof HTMLElement)) { + return false; + } + + if (target.isContentEditable) { + return true; + } + + return ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName); + } + + function isCubeFocusKeyboardModeActive(elements = getElements()) { + return Boolean(state.focusMode) + && elements?.cubeSectionEl instanceof HTMLElement + && !elements.cubeSectionEl.hidden; + } + + function syncFocusControls(elements) { + if (elements?.cubeLayoutEl instanceof HTMLElement) { + elements.cubeLayoutEl.classList.toggle("is-cube-focus", Boolean(state.focusMode)); + } + + if (elements?.focusToggleEl instanceof HTMLButtonElement) { + elements.focusToggleEl.setAttribute("aria-pressed", state.focusMode ? "true" : "false"); + elements.focusToggleEl.textContent = state.focusMode ? "Show Full Cube" : "Focus Cube"; + } + } + function bindRotationControls(elements) { if (state.controlsBound) { return; } - const rotateAndRender = (deltaX, deltaY) => { - setRotation(state.rotationX + deltaX, state.rotationY + deltaY); - render(getElements()); - }; - elements.rotateLeftEl?.addEventListener("click", () => rotateAndRender(0, -9)); elements.rotateRightEl?.addEventListener("click", () => rotateAndRender(0, 9)); elements.rotateUpEl?.addEventListener("click", () => rotateAndRender(-9, 0)); elements.rotateDownEl?.addEventListener("click", () => rotateAndRender(9, 0)); - elements.rotateResetEl?.addEventListener("click", () => { - setRotation(18, -28); - render(getElements()); + elements.rotateResetEl?.addEventListener("click", resetRotationAndRender); + elements.focusToggleEl?.addEventListener("click", () => { + state.focusMode = !state.focusMode; + syncFocusControls(getElements()); }); elements.markerModeEl?.addEventListener("change", (event) => { @@ -351,6 +390,41 @@ }); } + document.addEventListener("keydown", (event) => { + if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) { + return; + } + + if (!isCubeFocusKeyboardModeActive(getElements())) { + return; + } + + if (isKeyboardEditableTarget(event.target)) { + return; + } + + switch (String(event.key || "").toLowerCase()) { + case "a": + event.preventDefault(); + rotateAndRender(0, -9); + break; + case "d": + event.preventDefault(); + rotateAndRender(0, 9); + break; + case "w": + event.preventDefault(); + rotateAndRender(-9, 0); + break; + case "s": + event.preventDefault(); + rotateAndRender(9, 0); + break; + default: + break; + } + }); + state.controlsBound = true; } @@ -620,6 +694,8 @@ } function render(elements) { + syncFocusControls(elements); + if (elements?.markerModeEl) { elements.markerModeEl.value = state.markerDisplayMode; } diff --git a/index.html b/index.html index 79a890b..6761977 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@ - +