@the-inclusionist/engine 10.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/app/css/style.css +216 -42
- package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
- package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
- package/app/public/vendor/fonts.css +11 -1175
- package/dist-pkg/boot/create-game.d.ts +84 -33
- package/dist-pkg/boot/create-game.js +708 -307
- package/dist-pkg/core/a11y-sr.d.ts +1 -1
- package/dist-pkg/core/a11y-sr.js +3 -5
- package/dist-pkg/core/accommodations.d.ts +26 -8
- package/dist-pkg/core/accommodations.js +27 -10
- package/dist-pkg/core/actions.d.ts +39 -10
- package/dist-pkg/core/actions.js +28 -6
- package/dist-pkg/core/contract.d.ts +0 -2
- package/dist-pkg/core/contract.js +0 -2
- package/dist-pkg/core/i18n.d.ts +36 -71
- package/dist-pkg/core/i18n.js +131 -198
- package/dist-pkg/core/session-clock.d.ts +23 -0
- package/dist-pkg/core/session-clock.js +37 -0
- package/dist-pkg/i18n/en.js +44 -49
- package/dist-pkg/i18n/es.js +44 -49
- package/dist-pkg/i18n/pt.js +49 -54
- package/dist-pkg/input/gamepad.d.ts +42 -11
- package/dist-pkg/input/gamepad.js +50 -15
- package/dist-pkg/input/key-default.d.ts +30 -0
- package/dist-pkg/input/key-default.js +60 -0
- package/dist-pkg/input/keyboard-runtime.d.ts +0 -4
- package/dist-pkg/input/keyboard.d.ts +0 -1
- package/dist-pkg/input/keydown.d.ts +5 -11
- package/dist-pkg/input/keydown.js +3 -3
- package/dist-pkg/input/latch-edge.d.ts +10 -2
- package/dist-pkg/input/latch-edge.js +4 -3
- package/dist-pkg/input/latch-scope.d.ts +27 -22
- package/dist-pkg/input/latch-scope.js +25 -37
- package/dist-pkg/input/latch-store.d.ts +9 -12
- package/dist-pkg/input/latch-store.js +13 -18
- package/dist-pkg/input/latch-sync.d.ts +5 -4
- package/dist-pkg/input/latch-sync.js +5 -5
- package/dist-pkg/input/pad-reading.js +7 -2
- package/dist-pkg/input/pad-wizard.d.ts +4 -1
- package/dist-pkg/input/pad-wizard.js +60 -9
- package/dist-pkg/input/switch-scan.d.ts +29 -12
- package/dist-pkg/input/switch-scan.js +15 -2
- package/dist-pkg/input/touch-bindings.d.ts +3 -6
- package/dist-pkg/input/touch.d.ts +17 -2
- package/dist-pkg/input/touch.js +55 -17
- package/dist-pkg/input/transport-in-use.d.ts +5 -5
- package/dist-pkg/input/transport-in-use.js +5 -5
- package/dist-pkg/input/transports.d.ts +0 -9
- package/dist-pkg/input/transports.js +1 -1
- package/dist-pkg/input/virtual-controller.d.ts +39 -3
- package/dist-pkg/input/virtual-controller.js +32 -4
- package/dist-pkg/input/voice-map.d.ts +27 -11
- package/dist-pkg/input/voice-map.js +41 -43
- package/dist-pkg/platform/audio-ambient.d.ts +0 -1
- package/dist-pkg/platform/audio-ambient.js +3 -1
- package/dist-pkg/platform/audio-mixer.js +4 -9
- package/dist-pkg/platform/audio-sonar.d.ts +8 -94
- package/dist-pkg/platform/audio-sonar.js +22 -207
- package/dist-pkg/platform/audio.d.ts +10 -1
- package/dist-pkg/platform/audio.js +35 -2
- package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
- package/dist-pkg/platform/choose-by-voice.js +157 -0
- package/dist-pkg/platform/font-library.d.ts +77 -0
- package/dist-pkg/platform/font-library.js +98 -0
- package/dist-pkg/platform/font-library.json +721 -0
- package/dist-pkg/platform/heavy-catalogue.d.ts +46 -0
- package/dist-pkg/platform/heavy-catalogue.js +47 -14
- package/dist-pkg/platform/heavy-mirror.d.ts +3 -9
- package/dist-pkg/platform/heavy-mirror.js +14 -17
- package/dist-pkg/platform/heavy.d.ts +40 -8
- package/dist-pkg/platform/heavy.js +177 -22
- package/dist-pkg/platform/interruptible-speech.d.ts +15 -8
- package/dist-pkg/platform/interruptible-speech.js +37 -13
- package/dist-pkg/platform/listener-scope.js +22 -13
- package/dist-pkg/platform/locale-host.d.ts +7 -3
- package/dist-pkg/platform/locale-host.js +13 -2
- package/dist-pkg/platform/reading-in-worker.d.ts +24 -0
- package/dist-pkg/platform/reading-in-worker.js +38 -0
- package/dist-pkg/platform/reading-model.d.ts +21 -0
- package/dist-pkg/platform/reading-model.js +25 -0
- package/dist-pkg/platform/reading-runtime.js +13 -8
- package/dist-pkg/platform/reading.d.ts +12 -1
- package/dist-pkg/platform/reading.js +9 -1
- package/dist-pkg/platform/speech-recognition.d.ts +10 -1
- package/dist-pkg/platform/speech-recognition.js +29 -9
- package/dist-pkg/platform/speech.d.ts +10 -1
- package/dist-pkg/platform/speech.js +24 -13
- package/dist-pkg/platform/tts.d.ts +37 -4
- package/dist-pkg/platform/tts.js +279 -57
- package/dist-pkg/platform/voice-listener.d.ts +6 -2
- package/dist-pkg/platform/voice-listener.js +1 -1
- package/dist-pkg/platform/voice-plan.d.ts +13 -0
- package/dist-pkg/platform/voice-plan.js +57 -2
- package/dist-pkg/platform/vosk-runtime.d.ts +25 -0
- package/dist-pkg/platform/vosk-runtime.js +59 -1
- package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
- package/dist-pkg/platform/vosk-vocabulary.js +178 -0
- package/dist-pkg/render/crt.d.ts +11 -9
- package/dist-pkg/render/crt.js +6 -10
- package/dist-pkg/render/cvd-matrices.d.ts +5 -1
- package/dist-pkg/render/cvd-matrices.js +5 -1
- package/dist-pkg/render/high-contrast.d.ts +1 -2
- package/dist-pkg/render/high-contrast.js +5 -1
- package/dist-pkg/render/viz-axes.d.ts +9 -0
- package/dist-pkg/render/viz-axes.js +15 -2
- package/dist-pkg/ui/aac-sets.d.ts +29 -24
- package/dist-pkg/ui/aac-sets.js +22 -30
- package/dist-pkg/ui/audio-choices.d.ts +10 -5
- package/dist-pkg/ui/audio-choices.js +14 -10
- package/dist-pkg/ui/camera-control.d.ts +47 -0
- package/dist-pkg/ui/camera-control.js +35 -1
- package/dist-pkg/ui/declared-words.d.ts +21 -0
- package/dist-pkg/ui/declared-words.js +66 -0
- package/dist-pkg/ui/drawing-problems.js +4 -3
- package/dist-pkg/ui/eye-control.js +17 -17
- package/dist-pkg/ui/face-control.js +15 -15
- package/dist-pkg/ui/fonts.d.ts +42 -8
- package/dist-pkg/ui/fonts.js +58 -24
- package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
- package/dist-pkg/ui/footer-scroll-driver.js +201 -0
- package/dist-pkg/ui/footer-scroll.d.ts +38 -0
- package/dist-pkg/ui/footer-scroll.js +55 -0
- package/dist-pkg/ui/game-options.d.ts +14 -9
- package/dist-pkg/ui/game-options.js +41 -21
- package/dist-pkg/ui/hand-control.js +15 -15
- package/dist-pkg/ui/help-panel.d.ts +19 -8
- package/dist-pkg/ui/help-panel.js +15 -5
- package/dist-pkg/ui/hud-bands.d.ts +29 -12
- package/dist-pkg/ui/hud-bands.js +63 -25
- package/dist-pkg/ui/hud-row.d.ts +34 -0
- package/dist-pkg/ui/hud-row.js +80 -0
- package/dist-pkg/ui/hud.d.ts +0 -3
- package/dist-pkg/ui/layout.js +5 -2
- package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
- package/dist-pkg/ui/libras-avatar-clip.js +86 -0
- package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
- package/dist-pkg/ui/libras-avatar-load.js +134 -0
- package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
- package/dist-pkg/ui/libras-avatar-plan.js +211 -0
- package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
- package/dist-pkg/ui/libras-avatar-player.js +226 -0
- package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
- package/dist-pkg/ui/libras-avatar-stage.js +208 -0
- package/dist-pkg/ui/libras-glosses.d.ts +28 -0
- package/dist-pkg/ui/libras-glosses.js +161 -0
- package/dist-pkg/ui/menu-intent.d.ts +29 -0
- package/dist-pkg/ui/menu-intent.js +35 -1
- package/dist-pkg/ui/menu-items.d.ts +10 -0
- package/dist-pkg/ui/menu-items.js +29 -0
- package/dist-pkg/ui/menu-nav.d.ts +40 -4
- package/dist-pkg/ui/menu-nav.js +132 -47
- package/dist-pkg/ui/panel-widgets.d.ts +34 -0
- package/dist-pkg/ui/panel-widgets.js +86 -0
- package/dist-pkg/ui/pause-icons.d.ts +30 -14
- package/dist-pkg/ui/pause-icons.js +106 -45
- package/dist-pkg/ui/scan-overlay.d.ts +3 -3
- package/dist-pkg/ui/scan-overlay.js +11 -3
- package/dist-pkg/ui/screen-text.d.ts +25 -0
- package/dist-pkg/ui/screen-text.js +152 -0
- package/dist-pkg/ui/session-clock.d.ts +32 -0
- package/dist-pkg/ui/session-clock.js +67 -0
- package/dist-pkg/ui/settings-aac.d.ts +3 -1
- package/dist-pkg/ui/settings-aac.js +32 -13
- package/dist-pkg/ui/settings-audio.d.ts +10 -5
- package/dist-pkg/ui/settings-audio.js +101 -35
- package/dist-pkg/ui/settings-controls.d.ts +0 -4
- package/dist-pkg/ui/settings-mobility.d.ts +3 -6
- package/dist-pkg/ui/settings-mobility.js +4 -46
- package/dist-pkg/ui/settings-motion.js +2 -2
- package/dist-pkg/ui/settings-panel.d.ts +10 -0
- package/dist-pkg/ui/settings-panel.js +25 -1
- package/dist-pkg/ui/settings-typo.d.ts +5 -6
- package/dist-pkg/ui/settings-typo.js +20 -11
- package/dist-pkg/ui/settings-visual.js +2 -2
- package/dist-pkg/ui/shell.d.ts +1 -3
- package/dist-pkg/ui/shell.js +8 -4
- package/dist-pkg/ui/title.d.ts +2 -11
- package/dist-pkg/ui/title.js +39 -6
- package/dist-pkg/ui/top-band.d.ts +5 -3
- package/dist-pkg/ui/top-band.js +21 -25
- package/dist-pkg/ui/typo-choices.d.ts +3 -3
- package/dist-pkg/ui/typo-choices.js +9 -8
- package/dist-pkg/ui/vlibras.d.ts +71 -27
- package/dist-pkg/ui/vlibras.js +116 -44
- package/dist-pkg/ui/voice-control.d.ts +23 -1
- package/dist-pkg/ui/voice-control.js +149 -16
- package/dist-pkg/ui/voice-settings.d.ts +2 -3
- package/dist-pkg/ui/voice-settings.js +53 -3
- package/dist-pkg/ui/where-the-child-is.js +2 -2
- package/docs/CREDITS.md +122 -10
- package/docs/LICENSES.md +36 -7
- package/package.json +34 -9
- package/scripts/check-cartridge.mjs +78 -0
- package/scripts/game-build.d.mts +25 -0
- package/scripts/game-build.mjs +144 -0
- package/scripts/heavy-into-the-delivery.mjs +284 -40
- package/scripts/libras-avatar.json +680 -0
- package/scripts/libras-avatar.mjs +246 -0
- package/scripts/libras-glosses/gloss.py +95 -0
- package/scripts/libras-glosses/pyproject.toml +32 -0
- package/scripts/libras-glosses/uv.lock +781 -0
- package/scripts/libras-glosses.mjs +351 -0
- package/scripts/licences/OFL-1.1.txt +86 -0
- package/scripts/licences/UFL-1.0.txt +96 -0
- package/scripts/licences/fonts.mjs +389 -0
- package/scripts/licences/third-party.mjs +35 -21
- package/app/public/vendor/fonts/bitter-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/bitter-var.woff2 +0 -0
- package/app/public/vendor/fonts/bodonimoda-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/bodonimoda-var.woff2 +0 -0
- package/app/public/vendor/fonts/cat-abril_fatface-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-abril_fatface-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-adlam_display-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-adlam_display-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-agu_display-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-agu_display-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-alfa_slab_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-alfa_slab_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-are_you_serious-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-are_you_serious-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-asimovian-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-asimovian-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-atkinson_hyperlegible_next-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-atkinson_hyperlegible_next-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bakbak_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bakbak_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bangers-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bangers-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-barriecito-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-barriecito-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-berkshire_swash-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-berkshire_swash-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bevan-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bevan-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bitcount-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bitcount-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bowlby_one_sc-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bowlby_one_sc-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bungee_shade-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bungee_shade-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-charm-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-charm-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-charm-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-charm-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-cherry_bomb_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-cherry_bomb_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-chokokutai-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-chokokutai-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-chonburi-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-chonburi-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-climate_crisis-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-climate_crisis-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-cookie-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-dela_gothic_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-dela_gothic_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-fascinate_inline-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-fascinate_inline-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ga_maamli-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ga_maamli-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-itim-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-itim-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-jacquard_24-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-jacquard_24-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kablammo-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kablammo-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kalam-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kalam-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kalam-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kalam-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-katibeh-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-katibeh-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lemonada-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lemonada-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lilita_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lilita_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lobster-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lobster-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-luckiest_guy-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-luckiest_guy-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-marhey-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-marhey-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-modak-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-modak-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-momo_signature-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-momo_signature-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monofett-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monofett-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monomakh-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monomakh-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monoton-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monoton-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-nerko_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-nerko_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_adlam-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_adlam-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_arabic-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_arabic-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_devanagari-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_devanagari-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_hebrew-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_hebrew-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_jp-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_jp-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_khmer-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_khmer-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_kr-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_kr-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_sc-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_sc-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_thai-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_thai-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ole-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ole-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-pacifico-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-pacifico-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-patrick_hand-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-patrick_hand-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-petit_formal_script-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-petit_formal_script-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-pixelify_sans-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-pixelify_sans-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-plaster-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-plaster-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ar_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_at-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_at_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_nsw-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_nsw_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_qld-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_qld_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_sa-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_sa_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_tas-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_tas_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_vic-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_vic_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_be_vlg-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_be_vlg_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_be_wal-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_be_wal_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_br_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ca_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_cl_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_co_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_cu_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_cz-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_cz_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_grund-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_grund_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_la-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_la_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_sas-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_sas_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_va-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_va_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_dk_loopet-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_dk_loopet_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_dk_uloopet-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_dk_uloopet_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_es_deco_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_es_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_fr_moderne-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_fr_moderne_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_fr_trad-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_fr_trad_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_gb_j_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_gb_s_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hr-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hr_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hr_lijeva-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hr_lijeva_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hu-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hu_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_id-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_id_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ie-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ie_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_in-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_in_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_is-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_is_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_it_moderna-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_it_moderna_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_it_trad-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_it_trad_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_mx_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ng_modern-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ng_modern_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nl-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nl_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_no-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_no_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nz-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nz_basic-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nz_basic_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nz_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_pe_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_pl-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_pl_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_pt_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ro-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ro_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_sk-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_sk_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_tz-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_tz_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_us_modern_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_us_trad_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_vn-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_vn_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_za-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_za_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-potta_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-potta_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranchers-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranchers-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranga-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranga-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranga-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranga-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-risque-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-risque-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-rowdies-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-rowdies-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-rowdies-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-rowdies-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ruge_boogie-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ruge_boogie-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ruslan_display-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ruslan_display-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sacramento-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sacramento-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sancreek-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sancreek-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sankofa_display-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sankofa_display-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sedgwick_ave_display-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sedgwick_ave_display-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-shantell_sans-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-shantell_sans-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-smokum-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-smokum-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-solitreo-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-solitreo-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-spicy_rice-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-spicy_rice-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sriracha-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sriracha-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-srisakdi-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-srisakdi-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-srisakdi-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-srisakdi-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-stix_two_math-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-tillana-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-tillana-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-tillana-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-tillana-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-titan_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-titan_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-yatra_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-yatra_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-yomogi-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-yomogi-400.woff2 +0 -0
- package/app/public/vendor/fonts/dmserifdisplay-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/dmserifdisplay-400.woff2 +0 -0
- package/app/public/vendor/fonts/domine-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/domine-var.woff2 +0 -0
- package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
- package/app/public/vendor/fonts/fraunces-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/fraunces-var.woff2 +0 -0
- package/app/public/vendor/fonts/fredoka-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/fredoka-var.woff2 +0 -0
- package/app/public/vendor/fonts/inter-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-700.woff2 +0 -0
- package/app/public/vendor/fonts/jakarta-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/jakarta-var.woff2 +0 -0
- package/app/public/vendor/fonts/lato-400.woff2 +0 -0
- package/app/public/vendor/fonts/lato-700.woff2 +0 -0
- package/app/public/vendor/fonts/literata-400.woff2 +0 -0
- package/app/public/vendor/fonts/literata-700.woff2 +0 -0
- package/app/public/vendor/fonts/lora-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/lora-var.woff2 +0 -0
- package/app/public/vendor/fonts/merriweather-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/merriweather-var.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
- package/app/public/vendor/fonts/notosans-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/notosans-var.woff2 +0 -0
- package/app/public/vendor/fonts/nunito-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/nunito-var.woff2 +0 -0
- package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
- package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
- package/app/public/vendor/fonts/playfair-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/playfair-var.woff2 +0 -0
- package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
- package/app/public/vendor/fonts/quicksand-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/quicksand-var.woff2 +0 -0
- package/app/public/vendor/fonts/robotoflex-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/robotoflex-var.woff2 +0 -0
- package/app/public/vendor/fonts/sora-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/sora-var.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
- package/app/public/vendor/fonts/spacegrotesk-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/spacegrotesk-var.woff2 +0 -0
- package/app/public/vendor/fonts/spectral-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/spectral-400.woff2 +0 -0
- package/app/public/vendor/fonts/spectral-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/spectral-700.woff2 +0 -0
- package/app/public/vendor/fonts/teachers-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/teachers-var.woff2 +0 -0
- package/app/public/vendor/fonts/ubuntu-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/ubuntu-400.woff2 +0 -0
- package/app/public/vendor/fonts/ubuntu-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/ubuntu-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
- package/dist-pkg/platform/guide-intensity.d.ts +0 -39
- package/dist-pkg/platform/guide-intensity.js +0 -71
- package/dist-pkg/ui/latch-refusal.d.ts +0 -33
- package/dist-pkg/ui/latch-refusal.js +0 -60
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
2
|
import type { NavKeys } from '../input/edges.js';
|
|
3
|
+
import type { Action } from '../core/actions.js';
|
|
4
|
+
/**
|
|
5
|
+
* The position a menu reads as «yes»: confirming the item under the cursor. Named once because a second reader presses it —
|
|
6
|
+
* the voice, which puts the cursor on an item said by name and confirms it the way every transport does (ADR-0194 §2).
|
|
7
|
+
*/
|
|
8
|
+
export declare const MENU_CONFIRM: Action;
|
|
3
9
|
/** Confirm/enter. `NumpadEnter` counts here (but does NOT pause — see `input/keydown`). */
|
|
4
10
|
export declare const KEY_YES: ReadonlySet<string>;
|
|
5
11
|
/** Back. ⚠️ DEFECT 2: `Escape` is the SAME intent as the gamepad's "special" action. */
|
|
@@ -13,6 +19,29 @@ export declare const KEY_RIGHT: ReadonlySet<string>;
|
|
|
13
19
|
* player (a generic key).
|
|
14
20
|
*/
|
|
15
21
|
export declare function menuKeyIntent(code: string, act: string | null): NavKeys;
|
|
22
|
+
/** A step of a menu one-button scanning offers — what a menu does, not a key (ADR-0218 erratum of 2026-09-26). */
|
|
23
|
+
export type MenuStep = 'next' | 'previous' | 'confirm' | 'back' | 'increase' | 'decrease';
|
|
24
|
+
/**
|
|
25
|
+
* WHAT ONE-BUTTON SCANNING OFFERS INSIDE A MENU, in order after «cancel» (interface log 2026-09-26). By how often a child needs
|
|
26
|
+
* each: every menu here is a ring, so «next» alone reaches every item, and it comes first; «confirm» ends every choice; «back»
|
|
27
|
+
* leaves — the quick pause, a sub-list, a panel, the card —; «previous» is a shortcut the ring already covers, so it waits last.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MENU_SCAN: readonly MenuStep[];
|
|
30
|
+
/**
|
|
31
|
+
* WHAT THE CONTROL UNDER A MENU'S CURSOR LETS A SIDEWAYS STEP DO — the question `ui/menu-nav` answers with the rule its left
|
|
32
|
+
* and right keys follow: `item` has no value (a button, a switch, the quick bar, the pause card); `list` is a `<select>`, whose
|
|
33
|
+
* value left and right step and whose «confirm» goes round; `value` is a slider or a ⯇ ⯈ steps control, which left and right
|
|
34
|
+
* adjust and «confirm» leaves untouched.
|
|
35
|
+
*/
|
|
36
|
+
export type MenuCursor = 'item' | 'list' | 'value';
|
|
37
|
+
/**
|
|
38
|
+
* THE PASS IN A MENU, for the control under its cursor (ADR-0218 erratum; interface log 2026-09-26, the sideways step). Only a
|
|
39
|
+
* control with a value offers «increase» and «decrease» — they come right after «next», which keeps its place as the first
|
|
40
|
+
* step of every menu — and a slider or a steps control does not offer «confirm», which does nothing there: a cycle never
|
|
41
|
+
* stops on a position that does nothing (ADR-0155).
|
|
42
|
+
*/
|
|
43
|
+
export declare function menuScanFor(cursor: MenuCursor): readonly MenuStep[];
|
|
44
|
+
export declare function menuStepKeys(step: MenuStep): NavKeys;
|
|
16
45
|
/** `select` with left/right: one step, WITHOUT wrapping — adjusting a VALUE is not navigating a list. */
|
|
17
46
|
export declare function selectStep(selectedIndex: number, optionsLen: number, delta: number): number;
|
|
18
47
|
/** `select` with "yes": one step, WITH wrapping. That is the deliberate difference between confirming and adjusting. */
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
2
|
import { stepInRing } from '../core/ring.js';
|
|
3
|
+
/**
|
|
4
|
+
* The position a menu reads as «yes»: confirming the item under the cursor. Named once because a second reader presses it —
|
|
5
|
+
* the voice, which puts the cursor on an item said by name and confirms it the way every transport does (ADR-0194 §2).
|
|
6
|
+
*/
|
|
7
|
+
export const MENU_CONFIRM = 'action2';
|
|
3
8
|
/* The GENERIC key tables (the ones that hold for any player, even with no remap). They are `Set`s because that is
|
|
4
9
|
what they are — membership, not order — and because a named `Set` keeps the table auditable from outside (the
|
|
5
10
|
test imports the constant instead of repeating the literals). */
|
|
@@ -17,7 +22,7 @@ export const KEY_RIGHT = new Set(['ArrowRight', 'KeyD']);
|
|
|
17
22
|
*/
|
|
18
23
|
export function menuKeyIntent(code, act) {
|
|
19
24
|
return {
|
|
20
|
-
yes: KEY_YES.has(code) || act ===
|
|
25
|
+
yes: KEY_YES.has(code) || act === MENU_CONFIRM,
|
|
21
26
|
no: KEY_NO.has(code) || act === 'action3',
|
|
22
27
|
up: KEY_UP.has(code) || act === 'up',
|
|
23
28
|
down: KEY_DOWN.has(code) || act === 'down',
|
|
@@ -25,6 +30,35 @@ export function menuKeyIntent(code, act) {
|
|
|
25
30
|
right: KEY_RIGHT.has(code) || act === 'right',
|
|
26
31
|
};
|
|
27
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* WHAT ONE-BUTTON SCANNING OFFERS INSIDE A MENU, in order after «cancel» (interface log 2026-09-26). By how often a child needs
|
|
35
|
+
* each: every menu here is a ring, so «next» alone reaches every item, and it comes first; «confirm» ends every choice; «back»
|
|
36
|
+
* leaves — the quick pause, a sub-list, a panel, the card —; «previous» is a shortcut the ring already covers, so it waits last.
|
|
37
|
+
*/
|
|
38
|
+
export const MENU_SCAN = ['next', 'confirm', 'back', 'previous'];
|
|
39
|
+
/**
|
|
40
|
+
* THE PASS IN A MENU, for the control under its cursor (ADR-0218 erratum; interface log 2026-09-26, the sideways step). Only a
|
|
41
|
+
* control with a value offers «increase» and «decrease» — they come right after «next», which keeps its place as the first
|
|
42
|
+
* step of every menu — and a slider or a steps control does not offer «confirm», which does nothing there: a cycle never
|
|
43
|
+
* stops on a position that does nothing (ADR-0155).
|
|
44
|
+
*/
|
|
45
|
+
export function menuScanFor(cursor) {
|
|
46
|
+
if (cursor === 'item')
|
|
47
|
+
return MENU_SCAN;
|
|
48
|
+
const sideways = ['next', 'increase', 'decrease'];
|
|
49
|
+
return cursor === 'list' ? [...sideways, 'confirm', 'back', 'previous'] : [...sideways, 'back', 'previous'];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The intent a step is — the one a key with that meaning would carry, so it moves a menu the way the key does. «increase» is the
|
|
53
|
+
* right key and «decrease» the left one: the same step a keyboard adjusts a value with.
|
|
54
|
+
*/
|
|
55
|
+
const STEP_INTENT = {
|
|
56
|
+
next: { down: true }, previous: { up: true }, confirm: { yes: true }, back: { no: true },
|
|
57
|
+
increase: { right: true }, decrease: { left: true },
|
|
58
|
+
};
|
|
59
|
+
export function menuStepKeys(step) {
|
|
60
|
+
return { ...STEP_INTENT[step] };
|
|
61
|
+
}
|
|
28
62
|
/** `select` with left/right: one step, WITHOUT wrapping — adjusting a VALUE is not navigating a list. */
|
|
29
63
|
export function selectStep(selectedIndex, optionsLen, delta) {
|
|
30
64
|
return Math.max(0, Math.min(optionsLen - 1, selectedIndex + delta));
|
|
@@ -8,3 +8,13 @@ export declare const ITEM_SELECTOR = "button:not([disabled]), select:not([disabl
|
|
|
8
8
|
* cursor lands on an invisible control and the screen reader announces something nobody sees.
|
|
9
9
|
*/
|
|
10
10
|
export declare function navigableItems(card: ParentNode): HTMLElement[];
|
|
11
|
+
/**
|
|
12
|
+
* KEEPS A MENU ITEM WHOLLY IN VIEW inside the box that scrolls it — the card, never the page (the Dev: the page itself
|
|
13
|
+
* does not scroll). A cursor that walks off the visible part of a list is a cursor the child has lost: this is what makes
|
|
14
|
+
* a list longer than the screen usable, whatever moved the cursor (issue #134, the Dev's «os menus não rolam»).
|
|
15
|
+
*
|
|
16
|
+
* The box's `scroll-padding` is honoured, so the band of explanation at the bottom of the screen, which the card runs
|
|
17
|
+
* under, never covers the item. Nothing moves when the item is already in view. A document with no layout (a node
|
|
18
|
+
* double) is left alone.
|
|
19
|
+
*/
|
|
20
|
+
export declare function keepInView(item: HTMLElement): void;
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// copy of the selector is how «2 de 7» and the item the cursor is on drift apart. (Until ADR-0167 it also drew a
|
|
6
6
|
// visible number per stop; that left — the place is heard, after the name.)
|
|
7
7
|
//
|
|
8
|
+
// And how a stop is kept in view inside the card that scrolls it (`keepInView`), which every mover of a cursor shares.
|
|
9
|
+
//
|
|
8
10
|
// 📌 A LEAF, importing nothing: `ui/mount-panel` needs the stops and must not pull `ui/menu-nav`, which pulls the
|
|
9
11
|
// whole pause slice.
|
|
10
12
|
/** The controls that are a stop of the cursor. `[data-passos]` is ONE stop: its arrows are finger targets. */
|
|
@@ -20,3 +22,30 @@ export function navigableItems(card) {
|
|
|
20
22
|
}
|
|
21
23
|
// 🔴 `numerarItens` AND `ATRIBUTO_DO_NUMERO` LEFT (ADR-0167): no stop shows a number. The list above still gives the
|
|
22
24
|
// spoken index its position — «…, 2 de 7» after the name, from `ui/item-announcement.announceItem`.
|
|
25
|
+
/**
|
|
26
|
+
* KEEPS A MENU ITEM WHOLLY IN VIEW inside the box that scrolls it — the card, never the page (the Dev: the page itself
|
|
27
|
+
* does not scroll). A cursor that walks off the visible part of a list is a cursor the child has lost: this is what makes
|
|
28
|
+
* a list longer than the screen usable, whatever moved the cursor (issue #134, the Dev's «os menus não rolam»).
|
|
29
|
+
*
|
|
30
|
+
* The box's `scroll-padding` is honoured, so the band of explanation at the bottom of the screen, which the card runs
|
|
31
|
+
* under, never covers the item. Nothing moves when the item is already in view. A document with no layout (a node
|
|
32
|
+
* double) is left alone.
|
|
33
|
+
*/
|
|
34
|
+
export function keepInView(item) {
|
|
35
|
+
const view = item.ownerDocument?.defaultView;
|
|
36
|
+
if (!view || typeof item.getBoundingClientRect !== 'function')
|
|
37
|
+
return;
|
|
38
|
+
let box = item.parentElement;
|
|
39
|
+
while (box && !(/(auto|scroll)/.test(view.getComputedStyle(box).overflowY) && box.scrollHeight > box.clientHeight))
|
|
40
|
+
box = box.parentElement;
|
|
41
|
+
if (!box || box === item.ownerDocument.documentElement || box === item.ownerDocument.body)
|
|
42
|
+
return;
|
|
43
|
+
const style = view.getComputedStyle(box);
|
|
44
|
+
const top = box.getBoundingClientRect().top + box.clientTop + (parseFloat(style.scrollPaddingTop) || 0);
|
|
45
|
+
const bottom = box.getBoundingClientRect().top + box.clientTop + box.clientHeight - (parseFloat(style.scrollPaddingBottom) || 0);
|
|
46
|
+
const r = item.getBoundingClientRect();
|
|
47
|
+
if (r.top < top)
|
|
48
|
+
box.scrollTop -= top - r.top;
|
|
49
|
+
else if (r.bottom > bottom)
|
|
50
|
+
box.scrollTop += Math.min(r.bottom - bottom, r.top - top); // a tall item shows its top
|
|
51
|
+
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
/**
|
|
3
|
-
export type { DomQuery } from '../core/dom-query.js';
|
|
4
|
-
/** The intent, not the key. Defined once in input/edges; only re-exported here. */
|
|
2
|
+
/** The intent, not the key. Defined once in input/edges. */
|
|
5
3
|
import type { NavKeys } from '../input/edges.js';
|
|
6
|
-
export type { NavKeys } from '../input/edges.js';
|
|
7
4
|
/** What this module reads from a `KeyboardEvent`. */
|
|
8
5
|
export interface NavKeyEvent {
|
|
9
6
|
code: string;
|
|
@@ -13,6 +10,7 @@ export interface NavKeyEvent {
|
|
|
13
10
|
import type { EventTargetLike } from '../input/touch-bindings.js';
|
|
14
11
|
import type { DomQuery } from '../core/dom-query.js';
|
|
15
12
|
import type { Translate } from '../core/i18n.js';
|
|
13
|
+
import { type MenuCursor } from './menu-intent.js';
|
|
16
14
|
export { hasNavIntent as hasIntent } from '../input/edges.js';
|
|
17
15
|
export { stepInRing } from '../core/ring.js';
|
|
18
16
|
export interface MenuNavCtx {
|
|
@@ -72,6 +70,12 @@ export interface MenuNavCtx {
|
|
|
72
70
|
closePadWiz: (save: boolean) => void;
|
|
73
71
|
/** input/keyboard-runtime.ts: whose key is this? (-1 = generic). */
|
|
74
72
|
whichPlayer: (code: string) => number;
|
|
73
|
+
/**
|
|
74
|
+
* Does player `i` steer this screen's menus — the dialog on top and the pause card? On a screen several players share, only
|
|
75
|
+
* the first does (ADR-0144, erratum of 2026-09-26): another player's direction, confirm and back move, press and close nothing.
|
|
76
|
+
* Absent: every player does — each has a screen of her own. The quick bar is asked per player already (`onBar`).
|
|
77
|
+
*/
|
|
78
|
+
leadsTheScreen?: (i: number) => boolean;
|
|
75
79
|
/** input/keyboard-runtime.ts: which action is this key FOR that player (honouring the remap)? */
|
|
76
80
|
actionOf: (code: string, playerIndex: number) => string | null;
|
|
77
81
|
/** Where `attach()` installs the menus' listeners, in CAPTURE. See `EventTargetLike` in input/touch-bindings. */
|
|
@@ -94,6 +98,38 @@ export interface MenuNavApi {
|
|
|
94
98
|
navPause: (menu: HTMLElement, playerIndex: number, k: NavKeys) => void;
|
|
95
99
|
/** The keyboard translator. Exported on its own so a test can fire it without depending on the propagation phase. */
|
|
96
100
|
menuNavKey: (e: NavKeyEvent) => void;
|
|
101
|
+
/**
|
|
102
|
+
* Did the menus CONSUME this key event? `stopPropagation()` keeps it from the nodes further on, not from the listeners after
|
|
103
|
+
* `menuNavKey` on the same window capture — and the keyboard conductor is one. It asks here, so a key that moved, confirmed
|
|
104
|
+
* or CLOSED a menu is never also played in the same event (ADR-0111 erratum of 2026-09-26): with the menu already closed,
|
|
105
|
+
* «is a menu open?» answers no, and resume was answering a question too.
|
|
106
|
+
*/
|
|
107
|
+
consumed: (e: object) => boolean;
|
|
108
|
+
/**
|
|
109
|
+
* ONE STEP OF A MENU WITH NO KEY: the intents move the menu a key of player `playerIndex` would move now — the quick bar,
|
|
110
|
+
* the dialog on top or that player's open pause card — through the same path and guards as `menuNavKey`. Answers whether a
|
|
111
|
+
* menu took it. One-button scanning is the caller (ADR-0218 erratum of 2026-09-26): its press is not a menu key.
|
|
112
|
+
*/
|
|
113
|
+
navIntent: (playerIndex: number, keys: NavKeys) => boolean;
|
|
114
|
+
/**
|
|
115
|
+
* WHAT A SIDEWAYS STEP OF PLAYER `playerIndex` WOULD DO NOW (ADR-0218 erratum; one-button scanning offers «increase» and
|
|
116
|
+
* «decrease» only where they act): the kind of the control under the cursor of the panel on top — the focused item, or the
|
|
117
|
+
* first, where a step enters — under the guards a name said by voice has; `item` wherever a step would adjust nothing: the
|
|
118
|
+
* quick bar, a pause card, no menu, a remap, the controller-mapping panel.
|
|
119
|
+
*/
|
|
120
|
+
underCursor: (playerIndex: number) => MenuCursor;
|
|
121
|
+
/**
|
|
122
|
+
* THE NAMES A CHILD CAN SAY (ADR-0194 §1): the accessible names of the items a key of player `playerIndex` would move now —
|
|
123
|
+
* the dialog on top, else that player's open pause card — and none while a key would move no menu. LOCKED items are in it
|
|
124
|
+
* (ADR-0194 §5): saying one confirms it like any transport, and a locked item confirmed says its reason and does nothing.
|
|
125
|
+
*/
|
|
126
|
+
itemNames: (playerIndex: number) => string[];
|
|
127
|
+
/**
|
|
128
|
+
* PUTS THE CURSOR on the item with this accessible name, without activating it and without announcing it — the menu the
|
|
129
|
+
* name belongs to is the one `itemNames` reads. The caller then confirms through the virtual controller like any
|
|
130
|
+
* transport (ADR-0194 §2). `false` when no such item is there now: nothing moved.
|
|
131
|
+
*/
|
|
132
|
+
pointAt: (name: string, playerIndex: number) => boolean;
|
|
97
133
|
/** Installs `menuNavKey` on the window, in CAPTURE. */
|
|
98
134
|
attach: () => void;
|
|
99
135
|
}
|
package/dist-pkg/ui/menu-nav.js
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
// ---------------------------------------------------------------------------------------------------------
|
|
52
52
|
/** Was any intent expressed? Defined once in input/edges; here under the name this module has always used. */
|
|
53
53
|
import { hasNavIntent as hasIntent } from '../input/edges.js';
|
|
54
|
-
import {
|
|
54
|
+
import { backToRoot, markPauseItem, PM_VISIBLE_ITEMS } from './pause-icons.js';
|
|
55
55
|
import { announceItem } from './item-announcement.js';
|
|
56
56
|
import { accessibleLabel } from '../core/accessible-label.js';
|
|
57
57
|
import { stepInRing } from '../core/ring.js';
|
|
@@ -103,6 +103,17 @@ const CONTROL_KINDS = [
|
|
|
103
103
|
];
|
|
104
104
|
/** Where the items live: the dialog's card (`.overlay__card`) or the pause card (`.pause-card`). */
|
|
105
105
|
const CARD_SELECTOR = '.overlay__card, .pause-card';
|
|
106
|
+
/**
|
|
107
|
+
* WHAT LEFT AND RIGHT DO TO A PANEL'S CONTROL — ONE answer, read by the keys (`sideways`, `confirm`) and by one-button scanning
|
|
108
|
+
* (`underCursor`), so a scan never offers «increase» where the right key would walk the ring instead: a `<select>` is a list, a
|
|
109
|
+
* slider (the only `<input>` an item can be, `ui/menu-items`) or a ⯇ ⯈ steps control (ADR-0151) is a value, anything else
|
|
110
|
+
* is an item.
|
|
111
|
+
*/
|
|
112
|
+
function cursorKind(el) {
|
|
113
|
+
if (el.tagName === 'SELECT')
|
|
114
|
+
return 'list';
|
|
115
|
+
return el.tagName === 'INPUT' || el.hasAttribute('data-passos') ? 'value' : 'item';
|
|
116
|
+
}
|
|
106
117
|
export function initMenuNav(ctx) {
|
|
107
118
|
const { t } = ctx;
|
|
108
119
|
/* ===================== settings dialogs ===================== */
|
|
@@ -168,6 +179,10 @@ export function initMenuNav(ctx) {
|
|
|
168
179
|
*/
|
|
169
180
|
function sideways(items, idx, d) {
|
|
170
181
|
const cur = items[idx];
|
|
182
|
+
if (cursorKind(cur) === 'item') {
|
|
183
|
+
focusAndSay(items, stepInRing(items.length, idx, d));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
171
186
|
if (cur.tagName === 'SELECT') {
|
|
172
187
|
const before = cur.value;
|
|
173
188
|
tweakSelect(cur, d);
|
|
@@ -182,23 +197,19 @@ export function initMenuNav(ctx) {
|
|
|
182
197
|
return;
|
|
183
198
|
}
|
|
184
199
|
// A steps control ⯇ ⯈ (ADR-0151): left and right are the adjustment itself; its owner listens to `passo` (and announces).
|
|
185
|
-
|
|
186
|
-
cur.dispatchEvent(new CustomEvent('passo', { detail: d, bubbles: true }));
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
focusAndSay(items, stepInRing(items.length, idx, d));
|
|
200
|
+
cur.dispatchEvent(new CustomEvent('passo', { detail: d, bubbles: true }));
|
|
190
201
|
}
|
|
191
202
|
/** «Yes»: a list goes round and says the new option; a slider or a steps control has nothing to confirm; anything else is clicked. */
|
|
192
203
|
function confirm(items, idx) {
|
|
193
204
|
const cur = items[idx];
|
|
194
|
-
|
|
205
|
+
const kind = cursorKind(cur);
|
|
206
|
+
if (kind === 'list') {
|
|
195
207
|
tweakSelect(cur, 'wrap');
|
|
196
208
|
sayItem(items, idx);
|
|
197
209
|
return;
|
|
198
210
|
}
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
cur.click();
|
|
211
|
+
if (kind === 'item')
|
|
212
|
+
cur.click();
|
|
202
213
|
}
|
|
203
214
|
function navDialog(menu, k) {
|
|
204
215
|
const items = menuItems(menu);
|
|
@@ -228,8 +239,7 @@ export function initMenuNav(ctx) {
|
|
|
228
239
|
function pauseSetSel(menu, el) {
|
|
229
240
|
if (!el)
|
|
230
241
|
return; // an index outside the list (an empty card): touch nothing
|
|
231
|
-
menu
|
|
232
|
-
el.classList.add('pm-sel');
|
|
242
|
+
markPauseItem(menu, el); // the one place the card's cursor moves, and is kept in view (issue #134)
|
|
233
243
|
}
|
|
234
244
|
function navPause(menu, playerIndex, k) {
|
|
235
245
|
if (k.no) {
|
|
@@ -238,8 +248,9 @@ export function initMenuNav(ctx) {
|
|
|
238
248
|
// «is this the options list?»: with three lists (ADR-0146), asking the second would make «no» from the game's own
|
|
239
249
|
// options resume the game when the child asked to go back.
|
|
240
250
|
const open = menu.querySelector('.pause-menu:not([hidden])');
|
|
251
|
+
// ⚠️ AND THE CURSOR LANDS ON THE DOOR THAT OPENED THE LIST (ADR-0130 rule 1), not on the root's first item.
|
|
241
252
|
if (open && open.dataset.sub && open.dataset.sub !== 'raiz') {
|
|
242
|
-
|
|
253
|
+
backToRoot(menu);
|
|
243
254
|
return;
|
|
244
255
|
}
|
|
245
256
|
ctx.setPhase('playing');
|
|
@@ -275,20 +286,21 @@ export function initMenuNav(ctx) {
|
|
|
275
286
|
ctx.explainItem?.(reason || null);
|
|
276
287
|
}
|
|
277
288
|
/* ===================== keyboard ===================== */
|
|
289
|
+
const padWizOpen = () => { const pw = ctx.$('#padwiz'); return !!pw && !pw.hidden; };
|
|
278
290
|
/** The controller-mapping panel sits ON TOP of everything: while it is open, only Escape gets through (and cancels). */
|
|
279
291
|
function padWizKey(e) {
|
|
280
|
-
|
|
281
|
-
if (!pw || pw.hidden)
|
|
292
|
+
if (!padWizOpen())
|
|
282
293
|
return false;
|
|
283
294
|
if (e.code === 'Escape') {
|
|
284
295
|
ctx.closePadWiz(false);
|
|
285
|
-
e
|
|
286
|
-
e.stopPropagation();
|
|
296
|
+
consume(e);
|
|
287
297
|
}
|
|
288
298
|
return true; // open = consumed (even when it is not Escape: no menu underneath navigates while it is open)
|
|
289
299
|
}
|
|
290
|
-
/**
|
|
291
|
-
const
|
|
300
|
+
/** The key events the menus consumed, for `consumed` — weak, so an event is forgotten with it. */
|
|
301
|
+
const consumedKeys = new WeakSet();
|
|
302
|
+
/** Consumes the key: it was ours, and nobody else should see it. One function, so the three never come apart. */
|
|
303
|
+
const consume = (e) => { e.preventDefault(); e.stopPropagation(); consumedKeys.add(e); };
|
|
292
304
|
function menuNavKey(e) {
|
|
293
305
|
if (ctx.isCapturing())
|
|
294
306
|
return; // a remap in progress: the key is its
|
|
@@ -303,16 +315,54 @@ export function initMenuNav(ctx) {
|
|
|
303
315
|
// through `input/gamepad`; the keyboard has no pair of its own for it, because Enter is already «confirm» and taking
|
|
304
316
|
// it would leave the child no way to ACTIVATE the icon under the cursor.
|
|
305
317
|
const { player, keys } = intentOf(e.code);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
consume(e);
|
|
309
|
-
ctx.navBar(player, keys);
|
|
310
|
-
}
|
|
311
|
-
return; // on the bar, a menu key belongs to the bar — with or without an intent, it does not reach the character
|
|
312
|
-
}
|
|
313
|
-
if (!ctx.isNavigable() || !hasIntent(keys))
|
|
318
|
+
// on the bar, a key with no intent is not consumed either — it simply moves nothing
|
|
319
|
+
if (!hasIntent(keys) || !menusTake(player))
|
|
314
320
|
return;
|
|
315
|
-
|
|
321
|
+
// THE KEY IS CONSUMED BEFORE THE MENU MOVES: a step that closes the last menu leaves no menu for a later listener to see
|
|
322
|
+
consume(e);
|
|
323
|
+
steerMenus(player, keys);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Would a key of player `pi` with an intent move a menu now? The quick bar first — it works with the game RUNNING — then the
|
|
327
|
+
* dialog on top or that player's open pause card, and only where a menu may be navigated.
|
|
328
|
+
*
|
|
329
|
+
* 📌 THE KEY IS CONSUMED ONLY IF THERE IS SOMETHING TO NAVIGATE. A game whose settings are always open answers
|
|
330
|
+
* `isNavigable(): true`, and consuming before knowing would swallow every key with a menu intent — the arrows that choose
|
|
331
|
+
* an answer in the quiz, for one. DEFECT 2's safety net stays: with a dialog open the key IS consumed, so one Escape closes
|
|
332
|
+
* the dialog and no later listener sees it.
|
|
333
|
+
*/
|
|
334
|
+
function menusTake(pi) {
|
|
335
|
+
return ctx.onBar(pi) || (ctx.isNavigable() && !!menuUnderKeys(pi));
|
|
336
|
+
}
|
|
337
|
+
/** One step of the menu a key of `pi` moves: the bar, else the dialog on top, else that player's own pause card. */
|
|
338
|
+
function steerMenus(pi, k) {
|
|
339
|
+
if (ctx.onBar(pi)) {
|
|
340
|
+
ctx.navBar(pi, k);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const open = menuUnderKeys(pi);
|
|
344
|
+
if (open?.inPause)
|
|
345
|
+
navPause(open.menu, pi, k);
|
|
346
|
+
else if (open)
|
|
347
|
+
navDialog(open.menu, k);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* AN INTENT WITH NO KEY (ADR-0218 erratum of 2026-09-26): one-button scanning offers a menu's own steps and takes one with a
|
|
351
|
+
* press that is not a menu key — so it asks here, and the step goes the way a key's would, under the same guards.
|
|
352
|
+
* The controller-mapping panel on top takes only «back», which cancels it, as Escape does.
|
|
353
|
+
*/
|
|
354
|
+
function navIntent(pi, k) {
|
|
355
|
+
if (ctx.isCapturing())
|
|
356
|
+
return false;
|
|
357
|
+
if (padWizOpen()) {
|
|
358
|
+
if (k.no)
|
|
359
|
+
ctx.closePadWiz(false);
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
if (!hasIntent(k) || !menusTake(pi))
|
|
363
|
+
return false;
|
|
364
|
+
steerMenus(pi, k);
|
|
365
|
+
return true;
|
|
316
366
|
}
|
|
317
367
|
/** Whose key this is — a key no player owns is Player 1's — and what it asks of a menu. */
|
|
318
368
|
function intentOf(code) {
|
|
@@ -320,26 +370,60 @@ export function initMenuNav(ctx) {
|
|
|
320
370
|
const player = owner < 0 ? 0 : owner;
|
|
321
371
|
return { player, keys: menuKeyIntent(code, owner >= 0 ? ctx.actionOf(code, player) : null) };
|
|
322
372
|
}
|
|
323
|
-
/**
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
// listener sees it.
|
|
373
|
+
/**
|
|
374
|
+
* The menu a key of player `pi` moves: the dialog on top if there is one, else that player's OWN open pause card — and none for
|
|
375
|
+
* a player who does not lead this screen (ADR-0144 erratum): the dialog on top is the screen's, not every seat's.
|
|
376
|
+
*/
|
|
377
|
+
function menuUnderKeys(pi) {
|
|
378
|
+
if (ctx.leadsTheScreen && !ctx.leadsTheScreen(pi))
|
|
379
|
+
return null;
|
|
331
380
|
const dlg = sharedDialogOpen();
|
|
332
|
-
if (dlg)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
381
|
+
if (dlg)
|
|
382
|
+
return { menu: dlg, inPause: false };
|
|
383
|
+
const menu = ctx.getPauseMenu(pi);
|
|
384
|
+
return menu && !menu.hidden ? { menu, inPause: true } : null;
|
|
385
|
+
}
|
|
386
|
+
/* ===================== an item said by name (ADR-0194) ===================== */
|
|
387
|
+
/**
|
|
388
|
+
* The stops of the menu a key of `pi` would move NOW, under the same guards `menuNavKey` applies before moving one: a name is
|
|
389
|
+
* sayable exactly where confirming would reach it, so the confirm that follows `pointAt` lands on the named item.
|
|
390
|
+
*
|
|
391
|
+
* 🔴 LOCKED STOPS INCLUDED (ADR-0194 §5, ADR-0161): the cursor stops on a locked item, so its name is sayable too — and what
|
|
392
|
+
* saying it does is what confirming it does, because it IS a confirm: the item's own press says its reason and acts on nothing
|
|
393
|
+
* (`ui/pause-icons.pressPauseItem`, a panel's refusal). No second, voice-only «say the reason» exists to drift from that one.
|
|
394
|
+
*/
|
|
395
|
+
function sayableItems(pi) {
|
|
396
|
+
if (ctx.isCapturing() || padWizOpen() || ctx.onBar(pi) || !ctx.isNavigable())
|
|
397
|
+
return null;
|
|
398
|
+
const open = menuUnderKeys(pi);
|
|
399
|
+
if (!open)
|
|
400
|
+
return null;
|
|
401
|
+
return { ...open, items: open.inPause ? [...open.menu.querySelectorAll(PM_VISIBLE_ITEMS)] : menuItems(open.menu) };
|
|
402
|
+
}
|
|
403
|
+
function itemNames(pi) {
|
|
404
|
+
return (sayableItems(pi)?.items ?? []).map((el) => accessibleLabel(el)).filter(Boolean);
|
|
405
|
+
}
|
|
406
|
+
/** A panel's control under the cursor — where `navDialog` would put a step: the focused item, else the first (`cursorIn`). */
|
|
407
|
+
function underCursor(pi) {
|
|
408
|
+
const open = sayableItems(pi);
|
|
409
|
+
if (!open || open.inPause)
|
|
410
|
+
return 'item';
|
|
411
|
+
const at = open.items.indexOf(ctx.getActiveElement());
|
|
412
|
+
const el = open.items[at >= 0 ? at : 0];
|
|
413
|
+
return el ? cursorKind(el) : 'item';
|
|
414
|
+
}
|
|
415
|
+
function pointAt(name, pi) {
|
|
416
|
+
const open = sayableItems(pi);
|
|
417
|
+
const el = open?.items.find((it) => accessibleLabel(it) === name);
|
|
418
|
+
if (!open || !el)
|
|
419
|
+
return false;
|
|
420
|
+
if (open.inPause) {
|
|
421
|
+
pauseSetSel(open.menu, el);
|
|
422
|
+
ctx.explainItem?.(null);
|
|
341
423
|
}
|
|
342
|
-
|
|
424
|
+
else
|
|
425
|
+
el.focus();
|
|
426
|
+
return true;
|
|
343
427
|
}
|
|
344
428
|
/* ===================== holding on an item ===================== */
|
|
345
429
|
/*
|
|
@@ -413,5 +497,6 @@ export function initMenuNav(ctx) {
|
|
|
413
497
|
ctx.win.addEventListener('pointercancel', onRelease, true);
|
|
414
498
|
ctx.win.addEventListener('click', onClick, true);
|
|
415
499
|
}
|
|
416
|
-
|
|
500
|
+
const consumed = (e) => consumedKeys.has(e);
|
|
501
|
+
return { sharedDialogOpen, menuItems, menuFocus, dialogBack, navDialog, pauseSetSel, navPause, menuNavKey, consumed, navIntent, underCursor, itemNames, pointAt, attach };
|
|
417
502
|
}
|
|
@@ -112,5 +112,39 @@ export declare function nextStep(from: number, total: number, delta: number): nu
|
|
|
112
112
|
* controller's left and right emit it through `ui/menu-nav`. The user listens to a single event.
|
|
113
113
|
*/
|
|
114
114
|
export declare function mountSteps(ctx: PanelShellCtx, spec: StepsSpec): HTMLElement;
|
|
115
|
+
/**
|
|
116
|
+
* How many positions still CYCLE. The Dev's number (ADR-0130 erratum, 2026-09-12): «Acima de cinco itens deve ser dropdown».
|
|
117
|
+
* Five or fewer are one row «◀ Label: value ▶» — every position is one step away and the row always says where it is;
|
|
118
|
+
* more than five, a dropdown, because a cycle that long is walked more than it is chosen.
|
|
119
|
+
*/
|
|
120
|
+
export declare const MAX_CYCLE_POSITIONS = 5;
|
|
121
|
+
/** One position of an exclusive choice: what is stored, and what is shown and heard (already translated). */
|
|
122
|
+
export interface ChoicePosition {
|
|
123
|
+
readonly value: string;
|
|
124
|
+
readonly label: string;
|
|
125
|
+
}
|
|
126
|
+
/** An exclusive choice: its spoken name, its positions in order, and the value in use. */
|
|
127
|
+
export interface ChoiceSpec {
|
|
128
|
+
readonly label: string;
|
|
129
|
+
readonly values: readonly ChoicePosition[];
|
|
130
|
+
readonly current: string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Builds the control for ONE EXCLUSIVE CHOICE, and its SIZE picks the control, not the panel's author (ADR-0130 rule 3):
|
|
134
|
+
* up to `MAX_CYCLE_POSITIONS` a steps control (`mountSteps`), more a `<select>`. A choice whose size depends on the moment —
|
|
135
|
+
* the game's named positions, the audio outputs plugged in — takes the control its size asks for NOW, drawn again when it
|
|
136
|
+
* changes.
|
|
137
|
+
*
|
|
138
|
+
* Both shapes answer the same way, so whoever builds a row does not branch: `pick(value)` runs when the child chooses, the
|
|
139
|
+
* value in use is in `data-value`, and a bubbling `change` follows the pick — a list that listens for `change` hears either.
|
|
140
|
+
* A value not among the positions shows the first, as a `<select>` does.
|
|
141
|
+
*/
|
|
142
|
+
export declare function mountChoice(ctx: PanelShellCtx, spec: ChoiceSpec, pick: (value: string) => void): HTMLElement;
|
|
143
|
+
/**
|
|
144
|
+
* REWRITES a choice built by `mountChoice`: its name, its positions' words and the value in use — the redraw a panel does
|
|
145
|
+
* on every open, after a language change or when the value moved elsewhere. The SHAPE stays: a choice whose size can
|
|
146
|
+
* cross five is built again, not updated.
|
|
147
|
+
*/
|
|
148
|
+
export declare function updateChoice(el: HTMLElement, spec: ChoiceSpec): void;
|
|
115
149
|
/** Reflects the current position: the written value, what is heard, and the ends that no longer move. */
|
|
116
150
|
export declare function updateSteps(el: HTMLElement, spec: StepsSpec): void;
|
|
@@ -187,6 +187,92 @@ export function mountSteps(ctx, spec) {
|
|
|
187
187
|
updateSteps(el, spec);
|
|
188
188
|
return el;
|
|
189
189
|
}
|
|
190
|
+
/* ===================== ONE EXCLUSIVE CHOICE, DRAWN BY ITS SIZE (ADR-0130 rule 3 and erratum) ===================== */
|
|
191
|
+
/**
|
|
192
|
+
* How many positions still CYCLE. The Dev's number (ADR-0130 erratum, 2026-09-12): «Acima de cinco itens deve ser dropdown».
|
|
193
|
+
* Five or fewer are one row «◀ Label: value ▶» — every position is one step away and the row always says where it is;
|
|
194
|
+
* more than five, a dropdown, because a cycle that long is walked more than it is chosen.
|
|
195
|
+
*/
|
|
196
|
+
export const MAX_CYCLE_POSITIONS = 5;
|
|
197
|
+
/**
|
|
198
|
+
* Builds the control for ONE EXCLUSIVE CHOICE, and its SIZE picks the control, not the panel's author (ADR-0130 rule 3):
|
|
199
|
+
* up to `MAX_CYCLE_POSITIONS` a steps control (`mountSteps`), more a `<select>`. A choice whose size depends on the moment —
|
|
200
|
+
* the game's named positions, the audio outputs plugged in — takes the control its size asks for NOW, drawn again when it
|
|
201
|
+
* changes.
|
|
202
|
+
*
|
|
203
|
+
* Both shapes answer the same way, so whoever builds a row does not branch: `pick(value)` runs when the child chooses, the
|
|
204
|
+
* value in use is in `data-value`, and a bubbling `change` follows the pick — a list that listens for `change` hears either.
|
|
205
|
+
* A value not among the positions shows the first, as a `<select>` does.
|
|
206
|
+
*/
|
|
207
|
+
export function mountChoice(ctx, spec, pick) {
|
|
208
|
+
if (spec.values.length <= MAX_CYCLE_POSITIONS) {
|
|
209
|
+
const steps = mountSteps(ctx, stepsOf(spec));
|
|
210
|
+
writeCycle(steps, spec);
|
|
211
|
+
steps.addEventListener('passo', (ev) => {
|
|
212
|
+
// read back from the element, never from this closure: an `updateChoice` since may have brought other words
|
|
213
|
+
const live = readCycle(steps);
|
|
214
|
+
const from = indexOfValue(live);
|
|
215
|
+
const to = nextStep(from, live.values.length, ev.detail);
|
|
216
|
+
if (to === from)
|
|
217
|
+
return; // at the wall nothing moved, and nothing is picked
|
|
218
|
+
const value = live.values[to].value;
|
|
219
|
+
writeCycle(steps, { ...live, current: value });
|
|
220
|
+
pick(value);
|
|
221
|
+
steps.dispatchEvent(new Event('change', { bubbles: true }));
|
|
222
|
+
});
|
|
223
|
+
return steps;
|
|
224
|
+
}
|
|
225
|
+
const list = ctx.create('select');
|
|
226
|
+
list.className = 'vol';
|
|
227
|
+
fillList(list, spec);
|
|
228
|
+
list.addEventListener('change', () => { list.dataset.value = list.value; pick(list.value); });
|
|
229
|
+
return list;
|
|
230
|
+
}
|
|
231
|
+
/** Where the current value sits among the positions; one not among them shows the first, as a `<select>` does. */
|
|
232
|
+
const indexOfValue = (s) => Math.max(0, s.values.findIndex((v) => v.value === s.current));
|
|
233
|
+
const stepsOf = (s) => ({ label: s.label, values: s.values.map((v) => v.label), current: indexOfValue(s) });
|
|
234
|
+
/**
|
|
235
|
+
* A cycle row keeps its whole choice ON THE ELEMENT — the positions in `data-positions`, the value in `data-value`, the
|
|
236
|
+
* name in `aria-label` — so the control carries no state of its own that a redraw could leave behind.
|
|
237
|
+
*/
|
|
238
|
+
function writeCycle(el, s) {
|
|
239
|
+
el.dataset.positions = JSON.stringify(s.values);
|
|
240
|
+
el.dataset.value = s.current;
|
|
241
|
+
updateSteps(el, stepsOf(s));
|
|
242
|
+
}
|
|
243
|
+
function readCycle(el) {
|
|
244
|
+
return {
|
|
245
|
+
label: el.getAttribute('aria-label') ?? '',
|
|
246
|
+
values: JSON.parse(el.dataset.positions ?? '[]'),
|
|
247
|
+
current: el.dataset.value ?? '',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function fillList(list, s) {
|
|
251
|
+
// a `<select>` whose label is a sibling is announced as «combo box» and nothing else without its own name
|
|
252
|
+
list.setAttribute('aria-label', s.label);
|
|
253
|
+
while (list.firstChild)
|
|
254
|
+
list.removeChild(list.firstChild);
|
|
255
|
+
for (const v of s.values) {
|
|
256
|
+
const option = list.ownerDocument.createElement('option');
|
|
257
|
+
option.value = v.value;
|
|
258
|
+
option.textContent = v.label; // text, never markup: a game's word goes through here
|
|
259
|
+
list.appendChild(option);
|
|
260
|
+
}
|
|
261
|
+
list.value = s.current;
|
|
262
|
+
list.dataset.value = list.value;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* REWRITES a choice built by `mountChoice`: its name, its positions' words and the value in use — the redraw a panel does
|
|
266
|
+
* on every open, after a language change or when the value moved elsewhere. The SHAPE stays: a choice whose size can
|
|
267
|
+
* cross five is built again, not updated.
|
|
268
|
+
*/
|
|
269
|
+
export function updateChoice(el, spec) {
|
|
270
|
+
if (el.tagName === 'SELECT')
|
|
271
|
+
fillList(el, spec);
|
|
272
|
+
// read by the attribute `writeCycle` always writes, and not by `hasAttribute`: a host's minimal document may lack it
|
|
273
|
+
else if (el.dataset?.positions !== undefined)
|
|
274
|
+
writeCycle(el, spec);
|
|
275
|
+
}
|
|
190
276
|
/** Reflects the current position: the written value, what is heard, and the ends that no longer move. */
|
|
191
277
|
export function updateSteps(el, spec) {
|
|
192
278
|
const lastIndex = Math.max(0, spec.values.length - 1);
|