foldkit-primitives 0.15.3 → 0.16.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/dist/device/fullscreen.d.ts +28 -0
- package/dist/device/fullscreen.d.ts.map +1 -0
- package/dist/device/fullscreen.js +69 -0
- package/dist/device/fullscreen.js.map +1 -0
- package/dist/device/geolocation.d.ts +56 -0
- package/dist/device/geolocation.d.ts.map +1 -0
- package/dist/device/geolocation.js +87 -0
- package/dist/device/geolocation.js.map +1 -0
- package/dist/device/index.d.ts +5 -0
- package/dist/device/index.js +6 -0
- package/dist/device/media-devices.d.ts +88 -0
- package/dist/device/media-devices.d.ts.map +1 -0
- package/dist/device/media-devices.js +117 -0
- package/dist/device/media-devices.js.map +1 -0
- package/dist/device/media-stream.d.ts +93 -0
- package/dist/device/media-stream.d.ts.map +1 -0
- package/dist/device/media-stream.js +106 -0
- package/dist/device/media-stream.js.map +1 -0
- package/dist/device/permissions.d.ts +106 -0
- package/dist/device/permissions.d.ts.map +1 -0
- package/dist/device/permissions.js +143 -0
- package/dist/device/permissions.js.map +1 -0
- package/dist/dom/autofocus.d.ts +15 -0
- package/dist/dom/autofocus.d.ts.map +1 -0
- package/dist/dom/autofocus.js +22 -0
- package/dist/dom/autofocus.js.map +1 -0
- package/dist/dom/clipboard.d.ts +30 -0
- package/dist/dom/clipboard.d.ts.map +1 -0
- package/dist/dom/clipboard.js +50 -0
- package/dist/dom/clipboard.js.map +1 -0
- package/dist/dom/editable-text.d.ts +72 -0
- package/dist/dom/editable-text.d.ts.map +1 -0
- package/dist/dom/editable-text.js +232 -0
- package/dist/dom/editable-text.js.map +1 -0
- package/dist/dom/fluid-hover.d.ts +93 -0
- package/dist/dom/fluid-hover.d.ts.map +1 -0
- package/dist/dom/fluid-hover.js +294 -0
- package/dist/dom/fluid-hover.js.map +1 -0
- package/dist/dom/focus-scope.d.ts +21 -0
- package/dist/dom/focus-scope.d.ts.map +1 -0
- package/dist/dom/focus-scope.js +93 -0
- package/dist/dom/focus-scope.js.map +1 -0
- package/dist/dom/follow-tab-stop.d.ts +11 -0
- package/dist/dom/follow-tab-stop.d.ts.map +1 -0
- package/dist/dom/follow-tab-stop.js +54 -0
- package/dist/dom/follow-tab-stop.js.map +1 -0
- package/dist/dom/image-decode.d.ts +27 -0
- package/dist/dom/image-decode.d.ts.map +1 -0
- package/dist/dom/image-decode.js +50 -0
- package/dist/dom/image-decode.js.map +1 -0
- package/dist/dom/index.d.ts +24 -0
- package/dist/dom/index.js +25 -0
- package/dist/dom/input-mask.d.ts +35 -0
- package/dist/dom/input-mask.d.ts.map +1 -0
- package/dist/dom/input-mask.js +90 -0
- package/dist/dom/input-mask.js.map +1 -0
- package/dist/dom/keep-in-view.d.ts +16 -0
- package/dist/dom/keep-in-view.d.ts.map +1 -0
- package/dist/dom/keep-in-view.js +38 -0
- package/dist/dom/keep-in-view.js.map +1 -0
- package/dist/dom/keyboard-inset.d.ts +29 -0
- package/dist/dom/keyboard-inset.d.ts.map +1 -0
- package/dist/dom/keyboard-inset.js +45 -0
- package/dist/dom/keyboard-inset.js.map +1 -0
- package/dist/dom/layers.d.ts +6 -0
- package/dist/dom/layers.d.ts.map +1 -0
- package/dist/dom/layers.js +35 -0
- package/dist/dom/layers.js.map +1 -0
- package/dist/dom/measure.d.ts +42 -0
- package/dist/dom/measure.d.ts.map +1 -0
- package/dist/dom/measure.js +123 -0
- package/dist/dom/measure.js.map +1 -0
- package/dist/dom/move.d.ts +45 -0
- package/dist/dom/move.d.ts.map +1 -0
- package/dist/dom/move.js +96 -0
- package/dist/dom/move.js.map +1 -0
- package/dist/dom/pointed-links.d.ts +13 -0
- package/dist/dom/pointed-links.d.ts.map +1 -0
- package/dist/dom/pointed-links.js +59 -0
- package/dist/dom/pointed-links.js.map +1 -0
- package/dist/dom/pointer-drag.d.ts +132 -0
- package/dist/dom/pointer-drag.d.ts.map +1 -0
- package/dist/dom/pointer-drag.js +307 -0
- package/dist/dom/pointer-drag.js.map +1 -0
- package/dist/dom/pointer-position.d.ts +133 -0
- package/dist/dom/pointer-position.d.ts.map +1 -0
- package/dist/dom/pointer-position.js +173 -0
- package/dist/dom/pointer-position.js.map +1 -0
- package/dist/dom/script-loader.d.ts +12 -0
- package/dist/dom/script-loader.d.ts.map +1 -0
- package/dist/dom/script-loader.js +54 -0
- package/dist/dom/script-loader.js.map +1 -0
- package/dist/dom/scroll-bars.d.ts +52 -0
- package/dist/dom/scroll-bars.d.ts.map +1 -0
- package/dist/dom/scroll-bars.js +259 -0
- package/dist/dom/scroll-bars.js.map +1 -0
- package/dist/dom/scroll-settle.d.ts +33 -0
- package/dist/dom/scroll-settle.d.ts.map +1 -0
- package/dist/dom/scroll-settle.js +84 -0
- package/dist/dom/scroll-settle.js.map +1 -0
- package/dist/dom/scroll.d.ts +21 -0
- package/dist/dom/scroll.d.ts.map +1 -0
- package/dist/dom/scroll.js +141 -0
- package/dist/dom/scroll.js.map +1 -0
- package/dist/dom/share.d.ts +19 -0
- package/dist/dom/share.d.ts.map +1 -0
- package/dist/dom/share.js +41 -0
- package/dist/dom/share.js.map +1 -0
- package/dist/dom/swipe.d.ts +121 -0
- package/dist/dom/swipe.d.ts.map +1 -0
- package/dist/dom/swipe.js +291 -0
- package/dist/dom/swipe.js.map +1 -0
- package/dist/dom/targets.d.ts +45 -0
- package/dist/dom/targets.d.ts.map +1 -0
- package/dist/dom/targets.js +70 -0
- package/dist/dom/targets.js.map +1 -0
- package/dist/dom/track-box.d.ts +61 -0
- package/dist/dom/track-box.d.ts.map +1 -0
- package/dist/dom/track-box.js +131 -0
- package/dist/dom/track-box.js.map +1 -0
- package/dist/events/active-element.d.ts +15 -0
- package/dist/events/active-element.d.ts.map +1 -0
- package/dist/events/active-element.js +33 -0
- package/dist/events/active-element.js.map +1 -0
- package/dist/events/hotkeys.d.ts +22 -0
- package/dist/events/hotkeys.d.ts.map +1 -0
- package/dist/events/hotkeys.js +56 -0
- package/dist/events/hotkeys.js.map +1 -0
- package/dist/events/idle.d.ts +29 -0
- package/dist/events/idle.d.ts.map +1 -0
- package/dist/events/idle.js +51 -0
- package/dist/events/idle.js.map +1 -0
- package/dist/events/index.d.ts +9 -0
- package/dist/events/index.js +10 -0
- package/dist/{input-modality-BY-bSrEc.d.mts → events/input-modality.d.ts} +9 -12
- package/dist/events/input-modality.d.ts.map +1 -0
- package/dist/{input-modality-BXD383jg.mjs → events/input-modality.js} +3 -4
- package/dist/events/input-modality.js.map +1 -0
- package/dist/events/keyboard.d.ts +30 -0
- package/dist/events/keyboard.d.ts.map +1 -0
- package/dist/events/keyboard.js +55 -0
- package/dist/events/keyboard.js.map +1 -0
- package/dist/events/pointer.d.ts +15 -0
- package/dist/events/pointer.d.ts.map +1 -0
- package/dist/events/pointer.js +28 -0
- package/dist/events/pointer.js.map +1 -0
- package/dist/events/scroll.d.ts +16 -0
- package/dist/events/scroll.d.ts.map +1 -0
- package/dist/events/scroll.js +29 -0
- package/dist/events/scroll.js.map +1 -0
- package/dist/events/visibility.d.ts +25 -0
- package/dist/events/visibility.d.ts.map +1 -0
- package/dist/events/visibility.js +28 -0
- package/dist/events/visibility.js.map +1 -0
- package/dist/events/window-size.d.ts +30 -0
- package/dist/events/window-size.d.ts.map +1 -0
- package/dist/events/window-size.js +63 -0
- package/dist/events/window-size.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +10 -0
- package/dist/interaction/bundle-behavior.d.ts +37 -0
- package/dist/interaction/bundle-behavior.d.ts.map +1 -0
- package/dist/interaction/bundle-behavior.js +34 -0
- package/dist/interaction/bundle-behavior.js.map +1 -0
- package/dist/interaction/dismiss-layer.d.ts +100 -0
- package/dist/interaction/dismiss-layer.d.ts.map +1 -0
- package/dist/interaction/dismiss-layer.js +140 -0
- package/dist/interaction/dismiss-layer.js.map +1 -0
- package/dist/interaction/editable-text.d.ts +13 -0
- package/dist/interaction/editable-text.d.ts.map +1 -0
- package/dist/interaction/editable-text.js +17 -0
- package/dist/interaction/editable-text.js.map +1 -0
- package/dist/interaction/focus-scope.d.ts +21 -0
- package/dist/interaction/focus-scope.d.ts.map +1 -0
- package/dist/interaction/focus-scope.js +25 -0
- package/dist/interaction/focus-scope.js.map +1 -0
- package/dist/interaction/focus-visible.d.ts +9 -0
- package/dist/interaction/focus-visible.d.ts.map +1 -0
- package/dist/interaction/focus-visible.js +10 -0
- package/dist/interaction/focus-visible.js.map +1 -0
- package/dist/interaction/grid-navigation.d.ts +73 -0
- package/dist/interaction/grid-navigation.d.ts.map +1 -0
- package/dist/interaction/grid-navigation.js +137 -0
- package/dist/interaction/grid-navigation.js.map +1 -0
- package/dist/interaction/index.d.ts +21 -0
- package/dist/interaction/index.js +21 -0
- package/dist/interaction/layers.d.ts +12 -0
- package/dist/interaction/layers.d.ts.map +1 -0
- package/dist/interaction/layers.js +14 -0
- package/dist/interaction/layers.js.map +1 -0
- package/dist/interaction/list-navigation.d.ts +105 -0
- package/dist/interaction/list-navigation.d.ts.map +1 -0
- package/dist/interaction/list-navigation.js +168 -0
- package/dist/interaction/list-navigation.js.map +1 -0
- package/dist/interaction/live-announce.d.ts +70 -0
- package/dist/interaction/live-announce.d.ts.map +1 -0
- package/dist/interaction/live-announce.js +115 -0
- package/dist/interaction/live-announce.js.map +1 -0
- package/dist/interaction/long-press.d.ts +102 -0
- package/dist/interaction/long-press.d.ts.map +1 -0
- package/dist/interaction/long-press.js +115 -0
- package/dist/interaction/long-press.js.map +1 -0
- package/dist/interaction/move.d.ts +10 -0
- package/dist/interaction/move.d.ts.map +1 -0
- package/dist/interaction/move.js +17 -0
- package/dist/interaction/move.js.map +1 -0
- package/dist/interaction/overlay.d.ts +45 -0
- package/dist/interaction/overlay.d.ts.map +1 -0
- package/dist/interaction/overlay.js +57 -0
- package/dist/interaction/overlay.js.map +1 -0
- package/dist/interaction/placing.d.ts +86 -0
- package/dist/interaction/placing.d.ts.map +1 -0
- package/dist/interaction/placing.js +239 -0
- package/dist/interaction/placing.js.map +1 -0
- package/dist/interaction/pointer-drag.d.ts +23 -0
- package/dist/interaction/pointer-drag.d.ts.map +1 -0
- package/dist/interaction/pointer-drag.js +21 -0
- package/dist/interaction/pointer-drag.js.map +1 -0
- package/dist/interaction/press.d.ts +152 -0
- package/dist/interaction/press.d.ts.map +1 -0
- package/dist/interaction/press.js +229 -0
- package/dist/interaction/press.js.map +1 -0
- package/dist/interaction/roving-tabindex.d.ts +85 -0
- package/dist/interaction/roving-tabindex.d.ts.map +1 -0
- package/dist/interaction/roving-tabindex.js +142 -0
- package/dist/interaction/roving-tabindex.js.map +1 -0
- package/dist/interaction/selection.d.ts +72 -0
- package/dist/interaction/selection.d.ts.map +1 -0
- package/dist/interaction/selection.js +129 -0
- package/dist/interaction/selection.js.map +1 -0
- package/dist/interaction/targets.d.ts +15 -0
- package/dist/interaction/targets.d.ts.map +1 -0
- package/dist/interaction/targets.js +20 -0
- package/dist/interaction/targets.js.map +1 -0
- package/dist/interaction/tree-navigation.d.ts +111 -0
- package/dist/interaction/tree-navigation.d.ts.map +1 -0
- package/dist/interaction/tree-navigation.js +207 -0
- package/dist/interaction/tree-navigation.js.map +1 -0
- package/dist/interaction/typeahead.d.ts +73 -0
- package/dist/interaction/typeahead.d.ts.map +1 -0
- package/dist/interaction/typeahead.js +113 -0
- package/dist/interaction/typeahead.js.map +1 -0
- package/dist/internal.d.ts +16 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/{internal-DP-xLKbT.mjs → internal.js} +8 -7
- package/dist/internal.js.map +1 -0
- package/dist/media/breakpoints.d.ts +37 -0
- package/dist/media/breakpoints.d.ts.map +1 -0
- package/dist/media/breakpoints.js +64 -0
- package/dist/media/breakpoints.js.map +1 -0
- package/dist/media/index.d.ts +3 -0
- package/dist/media/index.js +4 -0
- package/dist/media/mediaquery.d.ts +40 -0
- package/dist/media/mediaquery.d.ts.map +1 -0
- package/dist/media/mediaquery.js +42 -0
- package/dist/media/mediaquery.js.map +1 -0
- package/dist/media/platform.d.ts +30 -0
- package/dist/media/platform.d.ts.map +1 -0
- package/dist/media/platform.js +50 -0
- package/dist/media/platform.js.map +1 -0
- package/dist/motion/index.d.ts +5 -0
- package/dist/motion/index.js +5 -0
- package/dist/motion/motion.d.ts +16 -0
- package/dist/motion/motion.d.ts.map +1 -0
- package/dist/motion/motion.js +25 -0
- package/dist/motion/motion.js.map +1 -0
- package/dist/motion/presence.d.ts +37 -0
- package/dist/motion/presence.d.ts.map +1 -0
- package/dist/motion/presence.js +65 -0
- package/dist/motion/presence.js.map +1 -0
- package/dist/motion/spring.d.ts +50 -0
- package/dist/motion/spring.d.ts.map +1 -0
- package/dist/motion/spring.js +92 -0
- package/dist/motion/spring.js.map +1 -0
- package/dist/motion/tween.d.ts +44 -0
- package/dist/motion/tween.d.ts.map +1 -0
- package/dist/motion/tween.js +65 -0
- package/dist/motion/tween.js.map +1 -0
- package/dist/net/broadcast.d.ts +39 -0
- package/dist/net/broadcast.d.ts.map +1 -0
- package/dist/net/broadcast.js +73 -0
- package/dist/net/broadcast.js.map +1 -0
- package/dist/net/index.d.ts +5 -0
- package/dist/net/index.js +6 -0
- package/dist/net/online.d.ts +24 -0
- package/dist/net/online.d.ts.map +1 -0
- package/dist/net/online.js +26 -0
- package/dist/net/online.js.map +1 -0
- package/dist/net/sharedHost.d.ts +64 -0
- package/dist/net/sharedHost.d.ts.map +1 -0
- package/dist/net/sharedHost.js +124 -0
- package/dist/net/sharedHost.js.map +1 -0
- package/dist/net/sse.d.ts +115 -0
- package/dist/net/sse.d.ts.map +1 -0
- package/dist/net/sse.js +138 -0
- package/dist/net/sse.js.map +1 -0
- package/dist/net/websocket.d.ts +172 -0
- package/dist/net/websocket.d.ts.map +1 -0
- package/dist/net/websocket.js +199 -0
- package/dist/net/websocket.js.map +1 -0
- package/dist/observers/bounds.d.ts +24 -0
- package/dist/observers/bounds.d.ts.map +1 -0
- package/dist/observers/bounds.js +51 -0
- package/dist/observers/bounds.js.map +1 -0
- package/dist/observers/index.d.ts +4 -0
- package/dist/observers/index.js +5 -0
- package/dist/observers/intersection.d.ts +18 -0
- package/dist/observers/intersection.d.ts.map +1 -0
- package/dist/observers/intersection.js +33 -0
- package/dist/observers/intersection.js.map +1 -0
- package/dist/observers/mutation.d.ts +25 -0
- package/dist/observers/mutation.d.ts.map +1 -0
- package/dist/observers/mutation.js +53 -0
- package/dist/observers/mutation.js.map +1 -0
- package/dist/observers/resize.d.ts +18 -0
- package/dist/observers/resize.d.ts.map +1 -0
- package/dist/observers/resize.js +33 -0
- package/dist/observers/resize.js.map +1 -0
- package/dist/state/history.d.ts +115 -0
- package/dist/state/history.d.ts.map +1 -0
- package/dist/state/history.js +167 -0
- package/dist/state/history.js.map +1 -0
- package/dist/state/index.d.ts +6 -0
- package/dist/state/index.js +7 -0
- package/dist/state/locale.d.ts +27 -0
- package/dist/state/locale.d.ts.map +1 -0
- package/dist/state/locale.js +25 -0
- package/dist/state/locale.js.map +1 -0
- package/dist/state/pagination.d.ts +53 -0
- package/dist/state/pagination.d.ts.map +1 -0
- package/dist/state/pagination.js +72 -0
- package/dist/state/pagination.js.map +1 -0
- package/dist/state/range.d.ts +9 -0
- package/dist/state/range.d.ts.map +1 -0
- package/dist/state/range.js +18 -0
- package/dist/state/range.js.map +1 -0
- package/dist/state/selection.d.ts +47 -0
- package/dist/state/selection.d.ts.map +1 -0
- package/dist/state/selection.js +39 -0
- package/dist/state/selection.js.map +1 -0
- package/dist/state/virtual.d.ts +201 -0
- package/dist/state/virtual.d.ts.map +1 -0
- package/dist/state/{index.mjs → virtual.js} +11 -313
- package/dist/state/virtual.js.map +1 -0
- package/dist/time/debounce.d.ts +43 -0
- package/dist/time/debounce.d.ts.map +1 -0
- package/dist/time/debounce.js +65 -0
- package/dist/time/debounce.js.map +1 -0
- package/dist/time/index.d.ts +6 -0
- package/dist/time/index.js +7 -0
- package/dist/time/interval.d.ts +37 -0
- package/dist/time/interval.d.ts.map +1 -0
- package/dist/time/interval.js +52 -0
- package/dist/time/interval.js.map +1 -0
- package/dist/time/relative.d.ts +8 -0
- package/dist/time/relative.d.ts.map +1 -0
- package/dist/time/relative.js +27 -0
- package/dist/time/relative.js.map +1 -0
- package/dist/time/throttle.d.ts +38 -0
- package/dist/time/throttle.d.ts.map +1 -0
- package/dist/time/throttle.js +39 -0
- package/dist/time/throttle.js.map +1 -0
- package/dist/time/ticks.d.ts +21 -0
- package/dist/time/ticks.d.ts.map +1 -0
- package/dist/time/ticks.js +30 -0
- package/dist/time/ticks.js.map +1 -0
- package/dist/time/timer.d.ts +32 -0
- package/dist/time/timer.d.ts.map +1 -0
- package/dist/time/timer.js +50 -0
- package/dist/time/timer.js.map +1 -0
- package/package.json +31 -31
- package/dist/device/index.d.mts +0 -345
- package/dist/device/index.d.mts.map +0 -1
- package/dist/device/index.mjs +0 -498
- package/dist/device/index.mjs.map +0 -1
- package/dist/dom/index.d.mts +0 -750
- package/dist/dom/index.d.mts.map +0 -1
- package/dist/dom/index.mjs +0 -1857
- package/dist/dom/index.mjs.map +0 -1
- package/dist/editable-text-1D31qthY.d.mts +0 -319
- package/dist/editable-text-1D31qthY.d.mts.map +0 -1
- package/dist/events/index.d.mts +0 -184
- package/dist/events/index.d.mts.map +0 -1
- package/dist/events/index.mjs +0 -315
- package/dist/events/index.mjs.map +0 -1
- package/dist/follow-tab-stop-Caog2qg1.mjs +0 -863
- package/dist/follow-tab-stop-Caog2qg1.mjs.map +0 -1
- package/dist/index-CmmxqSEi.d.mts +0 -149
- package/dist/index-CmmxqSEi.d.mts.map +0 -1
- package/dist/index.d.mts +0 -12
- package/dist/index.mjs +0 -12
- package/dist/input-modality-BXD383jg.mjs.map +0 -1
- package/dist/input-modality-BY-bSrEc.d.mts.map +0 -1
- package/dist/interaction/index.d.mts +0 -1194
- package/dist/interaction/index.d.mts.map +0 -1
- package/dist/interaction/index.mjs +0 -2012
- package/dist/interaction/index.mjs.map +0 -1
- package/dist/internal-DP-xLKbT.mjs.map +0 -1
- package/dist/media/index.d.mts +0 -109
- package/dist/media/index.d.mts.map +0 -1
- package/dist/media/index.mjs +0 -149
- package/dist/media/index.mjs.map +0 -1
- package/dist/motion/index.d.mts +0 -2
- package/dist/motion/index.mjs +0 -1
- package/dist/motion-DmFDppRn.mjs +0 -240
- package/dist/motion-DmFDppRn.mjs.map +0 -1
- package/dist/net/index.d.mts +0 -348
- package/dist/net/index.d.mts.map +0 -1
- package/dist/net/index.mjs +0 -535
- package/dist/net/index.mjs.map +0 -1
- package/dist/observers/index.d.mts +0 -84
- package/dist/observers/index.d.mts.map +0 -1
- package/dist/observers/index.mjs +0 -159
- package/dist/observers/index.mjs.map +0 -1
- package/dist/rolldown-runtime-D7D4PA-g.mjs +0 -13
- package/dist/state/index.d.mts +0 -444
- package/dist/state/index.d.mts.map +0 -1
- package/dist/state/index.mjs.map +0 -1
- package/dist/time/index.d.mts +0 -179
- package/dist/time/index.d.mts.map +0 -1
- package/dist/time/index.mjs +0 -242
- package/dist/time/index.mjs.map +0 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import { Bundle } from "foldkit-bundle";
|
|
4
|
+
import { clearQuery, perInput } from "../internal.js";
|
|
5
|
+
import { Behavior, Capability } from "foldkit-mixins";
|
|
6
|
+
import * as RovingTabindex from "./roving-tabindex.js";
|
|
7
|
+
import * as Typeahead from "./typeahead.js";
|
|
8
|
+
// src/interaction/list-navigation.ts
|
|
9
|
+
/**
|
|
10
|
+
* Keyboard navigation of a list in one placement: a roving tab stop, type to
|
|
11
|
+
* find, and PageUp and PageDown. One Bundle holds the current item's id and
|
|
12
|
+
* the typeahead query, so a printable key can move the pointer and extend the
|
|
13
|
+
* query in one transition, which two placements cannot do under `virtual`.
|
|
14
|
+
* The resolver allows one owner per event on a slot, so a host that needs
|
|
15
|
+
* both arrows and typeahead takes this, not `RovingTabindex` plus `Typeahead`.
|
|
16
|
+
*/
|
|
17
|
+
const Model = Schema.Struct({
|
|
18
|
+
current: Schema.NullOr(Schema.String),
|
|
19
|
+
query: Schema.String,
|
|
20
|
+
generation: Schema.Number
|
|
21
|
+
});
|
|
22
|
+
const Message = defineMessageUnion({
|
|
23
|
+
/** An item became current: focused, or reached by a key. */
|
|
24
|
+
Focused: { id: Schema.String },
|
|
25
|
+
/** A printable key extended the query; `match` is the item it now picks, if any. */
|
|
26
|
+
Typed: {
|
|
27
|
+
char: Schema.String,
|
|
28
|
+
match: Schema.NullOr(Schema.String)
|
|
29
|
+
},
|
|
30
|
+
Expired: { generation: Schema.Number },
|
|
31
|
+
Cleared: {}
|
|
32
|
+
});
|
|
33
|
+
const Args = Schema.Struct({
|
|
34
|
+
orientation: RovingTabindex.Orientation,
|
|
35
|
+
loop: Schema.Boolean,
|
|
36
|
+
virtual: Schema.Boolean,
|
|
37
|
+
/** How long the typeahead query survives after the last key. */
|
|
38
|
+
timeoutMs: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),
|
|
39
|
+
/** How many enabled items PageUp and PageDown move. */
|
|
40
|
+
page: Schema.Number.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.isGreaterThan(0)))
|
|
41
|
+
});
|
|
42
|
+
const bundle = Bundle.make("ListNavigation", {
|
|
43
|
+
Model,
|
|
44
|
+
Message,
|
|
45
|
+
args: Args,
|
|
46
|
+
init: () => ({ model: {
|
|
47
|
+
current: null,
|
|
48
|
+
query: "",
|
|
49
|
+
generation: 0
|
|
50
|
+
} }),
|
|
51
|
+
update: (model, message, args) => Message.match(message, {
|
|
52
|
+
Focused: ({ id }) => model.current === id ? { model } : { model: {
|
|
53
|
+
...model,
|
|
54
|
+
current: id
|
|
55
|
+
} },
|
|
56
|
+
Typed: ({ char, match }) => {
|
|
57
|
+
const generation = model.generation + 1;
|
|
58
|
+
return {
|
|
59
|
+
model: {
|
|
60
|
+
current: match ?? model.current,
|
|
61
|
+
query: model.query + char,
|
|
62
|
+
generation
|
|
63
|
+
},
|
|
64
|
+
commands: [{
|
|
65
|
+
name: "ListNavigation.expire",
|
|
66
|
+
args: { generation },
|
|
67
|
+
effect: Effect.as(Effect.sleep(args.timeoutMs), Message.Expired({ generation }))
|
|
68
|
+
}]
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
Expired: ({ generation }) => generation === model.generation ? clearQuery(model) : { model },
|
|
72
|
+
Cleared: () => clearQuery(model)
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
const keyOutcome = (items, texts, state, key, modifiers, options) => {
|
|
76
|
+
const from = state.current === null ? -1 : items.indexOf(state.current);
|
|
77
|
+
const moved = RovingTabindex.move(items.enabled, from, key, modifiers, options);
|
|
78
|
+
const movedId = moved === void 0 ? void 0 : items.ids[moved];
|
|
79
|
+
if (movedId !== void 0) return {
|
|
80
|
+
_tag: "Navigate",
|
|
81
|
+
id: movedId
|
|
82
|
+
};
|
|
83
|
+
if (!Typeahead.isPrintable(key, modifiers)) return void 0;
|
|
84
|
+
if (key === " " && state.query === "") return void 0;
|
|
85
|
+
const found = Typeahead.match(texts, items.enabled, state.query + key, from);
|
|
86
|
+
return {
|
|
87
|
+
_tag: "Type",
|
|
88
|
+
char: key,
|
|
89
|
+
match: found === void 0 ? null : items.ids[found] ?? null
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Wires a placed `ListNavigation` to the slots. The container gets one key
|
|
94
|
+
* handler: arrows, Home, End, PageUp and PageDown move by id; a printable key
|
|
95
|
+
* extends the query and moves to its match. Each item gets `tabindex` and
|
|
96
|
+
* `OnFocus` as under `RovingTabindex`. Under `virtual` nothing takes DOM focus
|
|
97
|
+
* and the container carries `aria-activedescendant`.
|
|
98
|
+
*/
|
|
99
|
+
const behavior = (declared, args) => (slots) => (options) => {
|
|
100
|
+
const wrap = (message) => declared.wrapper.make(message);
|
|
101
|
+
const focused = (id) => wrap(Message.Focused({ id }));
|
|
102
|
+
const slice = (input) => input[declared.field];
|
|
103
|
+
const itemsOf = perInput(options.items);
|
|
104
|
+
const outcomeMessage = (outcome) => outcome._tag === "Navigate" ? focused(outcome.id) : wrap(Message.Typed({
|
|
105
|
+
char: outcome.char,
|
|
106
|
+
match: outcome.match
|
|
107
|
+
}));
|
|
108
|
+
return Behavior.forSlots(slots)({
|
|
109
|
+
[options.container]: Behavior.slot({
|
|
110
|
+
requires: { capability: Capability.Interactive },
|
|
111
|
+
attributes: ({ input, h }) => {
|
|
112
|
+
const items = itemsOf(input);
|
|
113
|
+
const state = slice(input);
|
|
114
|
+
const moveOptions = {
|
|
115
|
+
orientation: args.orientation,
|
|
116
|
+
loop: args.loop,
|
|
117
|
+
direction: options.direction?.(input) ?? "ltr",
|
|
118
|
+
page: args.page
|
|
119
|
+
};
|
|
120
|
+
const typeahead = options.typeahead !== false;
|
|
121
|
+
const decide = (key, modifiers) => {
|
|
122
|
+
const current = state.current;
|
|
123
|
+
if (key === "Enter" && options.commit !== void 0 && !modifiers.shiftKey && !modifiers.ctrlKey && !modifiers.altKey && !modifiers.metaKey && current !== null) {
|
|
124
|
+
const currentIndex = items.indexOf(current);
|
|
125
|
+
if (currentIndex !== -1 && !items.isDisabled(currentIndex)) return Option.some({
|
|
126
|
+
_tag: "Commit",
|
|
127
|
+
id: current,
|
|
128
|
+
message: options.commit(input, current)
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const result = keyOutcome(items, items.ids.map((_, index) => options.text(input, index)), state, key, modifiers, moveOptions);
|
|
132
|
+
if (!typeahead && result !== void 0 && result._tag === "Type") return Option.none();
|
|
133
|
+
if (result === void 0) return Option.none();
|
|
134
|
+
return Option.some({
|
|
135
|
+
_tag: "Key",
|
|
136
|
+
outcome: result
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
const messageOf = (decision) => {
|
|
140
|
+
if (decision._tag === "Commit") return decision.message;
|
|
141
|
+
return outcomeMessage(decision.outcome);
|
|
142
|
+
};
|
|
143
|
+
const stop = RovingTabindex.tabStop(items, state.current);
|
|
144
|
+
const stopId = stop === -1 ? void 0 : items.ids[stop];
|
|
145
|
+
if (args.virtual) return [h.OnKeyDownPreventDefault((key, modifiers) => Option.map(decide(key, modifiers), messageOf)), ...stopId === void 0 ? [] : [h.AriaActiveDescendant(stopId)]];
|
|
146
|
+
return [h.OnKeyDownFocus((key, modifiers) => Option.map(decide(key, modifiers), (decision) => {
|
|
147
|
+
if (decision._tag === "Commit") return {
|
|
148
|
+
focusSelector: RovingTabindex.idSelector(decision.id),
|
|
149
|
+
message: decision.message
|
|
150
|
+
};
|
|
151
|
+
const result = decision.outcome;
|
|
152
|
+
const target = result._tag === "Navigate" ? result.id : result.match ?? state.current;
|
|
153
|
+
return {
|
|
154
|
+
focusSelector: target === null ? ":not(*)" : RovingTabindex.idSelector(target),
|
|
155
|
+
message: outcomeMessage(result)
|
|
156
|
+
};
|
|
157
|
+
}))];
|
|
158
|
+
}
|
|
159
|
+
}),
|
|
160
|
+
[options.item]: Behavior.slot({
|
|
161
|
+
requires: { capability: Capability.Focusable },
|
|
162
|
+
attributes: ({ input, h, item }) => item === void 0 ? [] : RovingTabindex.itemAttributes(h, itemsOf(input), slice(input).current, item, args.virtual, focused)
|
|
163
|
+
})
|
|
164
|
+
}, { name: "ListNavigation" });
|
|
165
|
+
};
|
|
166
|
+
export { Args, Message, Model, behavior, bundle, keyOutcome };
|
|
167
|
+
|
|
168
|
+
//# sourceMappingURL=list-navigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-navigation.js","names":[],"sources":["../../src/interaction/list-navigation.ts"],"sourcesContent":["/**\n * Keyboard navigation of a list in one placement: a roving tab stop, type to\n * find, and PageUp and PageDown. One Bundle holds the current item's id and\n * the typeahead query, so a printable key can move the pointer and extend the\n * query in one transition, which two placements cannot do under `virtual`.\n * The resolver allows one owner per event on a slot, so a host that needs\n * both arrows and typeahead takes this, not `RovingTabindex` plus `Typeahead`.\n */\nimport { Effect, Option, Schema } from 'effect'\nimport type { HtmlBuilder, KeyboardModifiers } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type * as Update from 'foldkit/update'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Behaviors, Capability, type SlotItem } from 'foldkit-mixins'\nimport * as RovingTabindex from './roving-tabindex.js'\nimport * as Typeahead from './typeahead.js'\nimport { clearQuery, perInput } from '../internal.js'\n\nexport const Model = Schema.Struct({\n current: Schema.NullOr(Schema.String),\n query: Schema.String,\n generation: Schema.Number,\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n /** An item became current: focused, or reached by a key. */\n Focused: { id: Schema.String },\n /** A printable key extended the query; `match` is the item it now picks, if any. */\n Typed: { char: Schema.String, match: Schema.NullOr(Schema.String) },\n Expired: { generation: Schema.Number },\n Cleared: {},\n})\nexport type Message = typeof Message.Type\n\nexport const Args = Schema.Struct({\n orientation: RovingTabindex.Orientation,\n loop: Schema.Boolean,\n virtual: Schema.Boolean,\n /** How long the typeahead query survives after the last key. */\n timeoutMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n /** How many enabled items PageUp and PageDown move. */\n page: Schema.Number.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.isGreaterThan(0))),\n})\nexport type Args = typeof Args.Type\n\nexport const bundle = Bundle.make('ListNavigation', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { current: null, query: '', generation: 0 } }),\n update: (model, message, args) =>\n Message.match<Update.Return<Model, Message>>(message, {\n // Moving focus by key dispatches `Focused`, and the item's `OnFocus` reports it again.\n Focused: ({ id }) =>\n model.current === id ? { model } : { model: { ...model, current: id } },\n Typed: ({ char, match }) => {\n const generation = model.generation + 1\n return {\n model: { current: match ?? model.current, query: model.query + char, generation },\n commands: [\n {\n name: 'ListNavigation.expire',\n args: { generation },\n effect: Effect.as(Effect.sleep(args.timeoutMs), Message.Expired({ generation })),\n },\n ],\n }\n },\n Expired: ({ generation }) =>\n generation === model.generation ? clearQuery(model) : { model },\n Cleared: () => clearQuery(model),\n }),\n})\n\nexport interface BehaviorOptions<Input, Slots, ParentMessage> {\n readonly container: keyof Slots & string\n /** The slot rendered once per item; must carry the item's id (see `Behaviors.Collection`). */\n readonly item: keyof Slots & string\n readonly items: (input: Input) => Behaviors.Collection.Items<unknown>\n /** The text an item is matched by when typing. */\n readonly text: (input: Input, index: number) => string\n readonly direction?: (input: Input) => RovingTabindex.Direction\n /**\n * Whether a printable key moves the pointer. Off when the container is a\n * text field: letters belong to the field, and arrows still navigate.\n * Defaults to on.\n */\n readonly typeahead?: boolean\n /**\n * Enter on the current enabled item. One handler with the arrows: a second\n * key handler on the same slot would be a second owner of `keydown`.\n */\n readonly commit?: (input: Input, id: string) => ParentMessage\n}\n\n/**\n * The key's outcome, before it is turned into an attribute: navigation to an\n * id, a typed character with its match, or nothing.\n */\nexport type KeyOutcome =\n | { readonly _tag: 'Navigate'; readonly id: string }\n | { readonly _tag: 'Type'; readonly char: string; readonly match: string | null }\n\nexport const keyOutcome = (\n items: Behaviors.Collection.Items<unknown>,\n texts: ReadonlyArray<string>,\n state: { readonly current: string | null; readonly query: string },\n key: string,\n modifiers: KeyboardModifiers,\n options: RovingTabindex.MoveOptions,\n): KeyOutcome | undefined => {\n const from = state.current === null ? -1 : items.indexOf(state.current)\n const moved = RovingTabindex.move(items.enabled, from, key, modifiers, options)\n const movedId = moved === undefined ? undefined : items.ids[moved]\n if (movedId !== undefined) return { _tag: 'Navigate', id: movedId }\n if (!Typeahead.isPrintable(key, modifiers)) return undefined\n if (key === ' ' && state.query === '') return undefined\n const found = Typeahead.match(texts, items.enabled, state.query + key, from)\n return { _tag: 'Type', char: key, match: found === undefined ? null : (items.ids[found] ?? null) }\n}\n\n/**\n * Wires a placed `ListNavigation` to the slots. The container gets one key\n * handler: arrows, Home, End, PageUp and PageDown move by id; a printable key\n * extends the query and moves to its match. Each item gets `tabindex` and\n * `OnFocus` as under `RovingTabindex`. Under `virtual` nothing takes DOM focus\n * and the container carries `aria-activedescendant`.\n */\nexport const behavior =\n <Field extends string>(declared: Declared<typeof bundle, Field>, args: Args) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: Model }, ParentMessage>(\n options: BehaviorOptions<Input, Slots, ParentMessage>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> => {\n const wrap = (message: Message): ParentMessage =>\n declared.wrapper.make(message) as unknown as ParentMessage\n const focused = (id: string): ParentMessage => wrap(Message.Focused({ id }))\n const slice = (input: Input): Model => input[declared.field]\n const itemsOf = perInput(options.items)\n const outcomeMessage = (outcome: KeyOutcome): ParentMessage =>\n outcome._tag === 'Navigate'\n ? focused(outcome.id)\n : wrap(Message.Typed({ char: outcome.char, match: outcome.match }))\n return Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.container]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => {\n const items = itemsOf(input)\n const state = slice(input)\n const moveOptions: RovingTabindex.MoveOptions = {\n orientation: args.orientation,\n loop: args.loop,\n direction: options.direction?.(input) ?? 'ltr',\n page: args.page,\n }\n const typeahead = options.typeahead !== false\n // Commit is already a parent message. A key outcome still has to\n // become one, and the focus path needs the id it should move to.\n type Decision =\n | { readonly _tag: 'Commit'; readonly id: string; readonly message: ParentMessage }\n | { readonly _tag: 'Key'; readonly outcome: KeyOutcome }\n const decide = (key: string, modifiers: KeyboardModifiers): Option.Option<Decision> => {\n const current = state.current\n if (\n key === 'Enter' &&\n options.commit !== undefined &&\n !modifiers.shiftKey &&\n !modifiers.ctrlKey &&\n !modifiers.altKey &&\n !modifiers.metaKey &&\n current !== null\n ) {\n const currentIndex = items.indexOf(current)\n if (currentIndex !== -1 && !items.isDisabled(currentIndex)) {\n return Option.some({\n _tag: 'Commit',\n id: current,\n message: options.commit(input, current),\n })\n }\n }\n const result = keyOutcome(\n items,\n // Read on a key, not on every render.\n items.ids.map((_, index) => options.text(input, index)),\n state,\n key,\n modifiers,\n moveOptions,\n )\n // A text field owns printable keys. Arrows still move the pointer.\n if (!typeahead && result !== undefined && result._tag === 'Type') return Option.none()\n if (result === undefined) return Option.none()\n return Option.some({ _tag: 'Key', outcome: result })\n }\n const messageOf = (decision: Decision): ParentMessage => {\n if (decision._tag === 'Commit') return decision.message\n return outcomeMessage(decision.outcome)\n }\n const stop = RovingTabindex.tabStop(items, state.current)\n const stopId = stop === -1 ? undefined : items.ids[stop]\n if (args.virtual) {\n return [\n h.OnKeyDownPreventDefault((key, modifiers) =>\n Option.map(decide(key, modifiers), messageOf),\n ),\n ...(stopId === undefined ? [] : [h.AriaActiveDescendant(stopId)]),\n ]\n }\n return [\n h.OnKeyDownFocus((key, modifiers) =>\n Option.map(decide(key, modifiers), decision => {\n if (decision._tag === 'Commit') {\n return {\n focusSelector: RovingTabindex.idSelector(decision.id),\n message: decision.message,\n }\n }\n const result = decision.outcome\n const target =\n result._tag === 'Navigate' ? result.id : (result.match ?? state.current)\n return {\n // `:not(*)` matches nothing: a typed key with no match and\n // nothing current moves no focus but is still recorded.\n focusSelector: target === null ? ':not(*)' : RovingTabindex.idSelector(target),\n message: outcomeMessage(result),\n }\n }),\n ),\n ]\n },\n }),\n [options.item]: Behavior.slot({\n requires: { capability: Capability.Focusable },\n attributes: ({\n input,\n h,\n item,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n readonly item?: SlotItem\n }) =>\n item === undefined\n ? []\n : RovingTabindex.itemAttributes(\n h,\n itemsOf(input),\n slice(input).current,\n item,\n args.virtual,\n focused,\n ),\n }),\n // Keyed by values the caller chose; `forSlots` checks both keys exist.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'ListNavigation' },\n )\n }\n"],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle, Declared } from "foldkit-bundle";
|
|
3
|
+
import { Html, HtmlBuilder } from "foldkit/html";
|
|
4
|
+
export declare const Politeness: Schema.Literals<readonly ["polite", "assertive"]>;
|
|
5
|
+
export type Politeness = typeof Politeness.Type;
|
|
6
|
+
export declare const Model: Schema.Struct<{
|
|
7
|
+
readonly polite: Schema.String;
|
|
8
|
+
readonly assertive: Schema.String;
|
|
9
|
+
/** Waiting for the debounce to read. */
|
|
10
|
+
readonly pending: Schema.NullOr<Schema.Struct<{
|
|
11
|
+
readonly message: Schema.String;
|
|
12
|
+
readonly politeness: Schema.Literals<readonly ["polite", "assertive"]>;
|
|
13
|
+
}>>;
|
|
14
|
+
readonly generation: Schema.Number;
|
|
15
|
+
}>;
|
|
16
|
+
export type Model = typeof Model.Type;
|
|
17
|
+
export declare const Message: import("foldkit/message").MessageUnion<{
|
|
18
|
+
readonly Announced: {
|
|
19
|
+
message: Schema.String;
|
|
20
|
+
politeness: Schema.Literals<readonly ["polite", "assertive"]>;
|
|
21
|
+
};
|
|
22
|
+
readonly Read: {
|
|
23
|
+
generation: Schema.Number;
|
|
24
|
+
};
|
|
25
|
+
readonly Cleared: {
|
|
26
|
+
generation: Schema.Number;
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
29
|
+
export type Message = typeof Message.Type;
|
|
30
|
+
export declare const Args: Schema.Struct<{
|
|
31
|
+
/** A burst of announcements inside this window reads once, the last one. */
|
|
32
|
+
readonly debounceMs: Schema.Number;
|
|
33
|
+
/** How long the text stays in the region. */
|
|
34
|
+
readonly clearAfterMs: Schema.Number;
|
|
35
|
+
}>;
|
|
36
|
+
export type Args = typeof Args.Type;
|
|
37
|
+
/** The text to put in the region so that `next` is read even when it equals `current`. */
|
|
38
|
+
export declare const reread: (current: string, next: string) => string;
|
|
39
|
+
export declare const bundle: Bundle.Bundle<"LiveAnnounce", {
|
|
40
|
+
readonly debounceMs: number;
|
|
41
|
+
readonly clearAfterMs: number;
|
|
42
|
+
}, {
|
|
43
|
+
readonly polite: string;
|
|
44
|
+
readonly assertive: string;
|
|
45
|
+
readonly pending: {
|
|
46
|
+
readonly message: string;
|
|
47
|
+
readonly politeness: "assertive" | "polite";
|
|
48
|
+
} | null;
|
|
49
|
+
readonly generation: number;
|
|
50
|
+
}, {
|
|
51
|
+
readonly _tag: "Announced";
|
|
52
|
+
readonly message: string;
|
|
53
|
+
readonly politeness: "assertive" | "polite";
|
|
54
|
+
} | {
|
|
55
|
+
readonly _tag: "Cleared";
|
|
56
|
+
readonly generation: number;
|
|
57
|
+
} | {
|
|
58
|
+
readonly _tag: "Read";
|
|
59
|
+
readonly generation: number;
|
|
60
|
+
}, never, never, never, void, {}, {}>;
|
|
61
|
+
/** The parent Message that announces `text`, to return from `update` or `onOut`. */
|
|
62
|
+
export declare const say: <Field extends string>(declared: Declared<typeof bundle, Field>) => <ParentMessage>(text: string, politeness?: Politeness) => ParentMessage;
|
|
63
|
+
export declare const REGION_ATTRIBUTE = "data-foldkit-plus-live";
|
|
64
|
+
/**
|
|
65
|
+
* The two live regions. Render once, anywhere in the page, and hide them
|
|
66
|
+
* visually with a rule on `[data-foldkit-plus-live]`; they must stay in the
|
|
67
|
+
* accessibility tree, so no `display: none`.
|
|
68
|
+
*/
|
|
69
|
+
export declare const view: <ParentMessage>(model: Model, h: HtmlBuilder<ParentMessage>) => Html;
|
|
70
|
+
//# sourceMappingURL=live-announce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-announce.d.ts","names":[],"sources":["../../src/interaction/live-announce.ts"],"mappings":";;;qBAea,YAAU,OAAA;YACX,oBAAoB,WAAW;qBAE9B,OAAK,OAAA;;;;;;;;;;YAON,eAAe,MAAM;qBAEpB,mCAAO;;IACL,SAAO,OAAA;IAAiB,YAAU,OAAA;;;IACvC,YAAU,OAAA;;;IACP,YAAU,OAAA;;;YAEX,iBAAiB,QAAQ;qBAOxB,MAAI,OAAA;;;;;;YAML,cAAc,KAAK;;qBAKlB,SAAM,iBAAmB;qBAWzB,QAAM,OAAA;;;;;;;;;;;;;;;;;;;;;;;qBAwCN,MACV,sBAAoB,UAAY,gBAAgB,QAAQ,YACxD,eAAa,cAAc,aAAc,eAAwB;qBAKvD;;;;;;qBAOA,OAAQ,eAAa,OAAS,OAAK,GAAK,YAAY,mBAAiB"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import { Bundle } from "foldkit-bundle";
|
|
4
|
+
// src/interaction/live-announce.ts
|
|
5
|
+
/**
|
|
6
|
+
* Announcements for assistive technology: one Bundle, placed once, holds the
|
|
7
|
+
* text of a polite and an assertive live region. An announcement waits
|
|
8
|
+
* `debounceMs` so a burst reads once, then clears after `clearAfterMs`, both
|
|
9
|
+
* on Effect's clock with a generation so a superseded timer changes nothing.
|
|
10
|
+
* The same text twice gets a trailing no-break space toggled, which is what
|
|
11
|
+
* makes a screen reader read it again. `view` renders the two regions; put it
|
|
12
|
+
* once in the page and style them visually hidden.
|
|
13
|
+
*/
|
|
14
|
+
const Politeness = Schema.Literals(["polite", "assertive"]);
|
|
15
|
+
const Model = Schema.Struct({
|
|
16
|
+
polite: Schema.String,
|
|
17
|
+
assertive: Schema.String,
|
|
18
|
+
/** Waiting for the debounce to read. */
|
|
19
|
+
pending: Schema.NullOr(Schema.Struct({
|
|
20
|
+
message: Schema.String,
|
|
21
|
+
politeness: Politeness
|
|
22
|
+
})),
|
|
23
|
+
generation: Schema.Number
|
|
24
|
+
});
|
|
25
|
+
const Message = defineMessageUnion({
|
|
26
|
+
Announced: {
|
|
27
|
+
message: Schema.String,
|
|
28
|
+
politeness: Politeness
|
|
29
|
+
},
|
|
30
|
+
Read: { generation: Schema.Number },
|
|
31
|
+
Cleared: { generation: Schema.Number }
|
|
32
|
+
});
|
|
33
|
+
const positive = Schema.Number.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.check(Schema.isFinite()));
|
|
34
|
+
const Args = Schema.Struct({
|
|
35
|
+
/** A burst of announcements inside this window reads once, the last one. */
|
|
36
|
+
debounceMs: positive,
|
|
37
|
+
/** How long the text stays in the region. */
|
|
38
|
+
clearAfterMs: positive
|
|
39
|
+
});
|
|
40
|
+
const NBSP = "\xA0";
|
|
41
|
+
/** The text to put in the region so that `next` is read even when it equals `current`. */
|
|
42
|
+
const reread = (current, next) => current === next ? `${next}${NBSP}` : current === `${next}${NBSP}` ? next : next;
|
|
43
|
+
const after = (ms, message, name, generation) => ({
|
|
44
|
+
name,
|
|
45
|
+
args: { generation },
|
|
46
|
+
effect: Effect.as(Effect.sleep(ms), message)
|
|
47
|
+
});
|
|
48
|
+
const bundle = Bundle.make("LiveAnnounce", {
|
|
49
|
+
Model,
|
|
50
|
+
Message,
|
|
51
|
+
args: Args,
|
|
52
|
+
init: () => ({ model: {
|
|
53
|
+
polite: "",
|
|
54
|
+
assertive: "",
|
|
55
|
+
pending: null,
|
|
56
|
+
generation: 0
|
|
57
|
+
} }),
|
|
58
|
+
update: (model, message, args) => Message.match(message, {
|
|
59
|
+
Announced: ({ message: text, politeness }) => {
|
|
60
|
+
const generation = model.generation + 1;
|
|
61
|
+
return {
|
|
62
|
+
model: {
|
|
63
|
+
...model,
|
|
64
|
+
pending: {
|
|
65
|
+
message: text,
|
|
66
|
+
politeness
|
|
67
|
+
},
|
|
68
|
+
generation
|
|
69
|
+
},
|
|
70
|
+
commands: [after(args.debounceMs, Message.Read({ generation }), "LiveAnnounce.read", generation)]
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
Read: ({ generation }) => {
|
|
74
|
+
if (generation !== model.generation || model.pending === null) return { model };
|
|
75
|
+
const { message: text, politeness } = model.pending;
|
|
76
|
+
const region = politeness === "polite" ? "polite" : "assertive";
|
|
77
|
+
return {
|
|
78
|
+
model: {
|
|
79
|
+
...model,
|
|
80
|
+
pending: null,
|
|
81
|
+
[region]: reread(model[region], text)
|
|
82
|
+
},
|
|
83
|
+
commands: [after(args.clearAfterMs, Message.Cleared({ generation }), "LiveAnnounce.clear", generation)]
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
Cleared: ({ generation }) => generation === model.generation ? { model: {
|
|
87
|
+
...model,
|
|
88
|
+
polite: "",
|
|
89
|
+
assertive: ""
|
|
90
|
+
} } : { model }
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
/** The parent Message that announces `text`, to return from `update` or `onOut`. */
|
|
94
|
+
const say = (declared) => (text, politeness = "polite") => declared.wrapper.make(Message.Announced({
|
|
95
|
+
message: text,
|
|
96
|
+
politeness
|
|
97
|
+
}));
|
|
98
|
+
const REGION_ATTRIBUTE = "data-foldkit-plus-live";
|
|
99
|
+
/**
|
|
100
|
+
* The two live regions. Render once, anywhere in the page, and hide them
|
|
101
|
+
* visually with a rule on `[data-foldkit-plus-live]`; they must stay in the
|
|
102
|
+
* accessibility tree, so no `display: none`.
|
|
103
|
+
*/
|
|
104
|
+
const view = (model, h) => h.div([h.Attribute(REGION_ATTRIBUTE, "regions")], [h.div([
|
|
105
|
+
h.Attribute(REGION_ATTRIBUTE, "polite"),
|
|
106
|
+
h.AriaLive("polite"),
|
|
107
|
+
h.AriaAtomic(true)
|
|
108
|
+
], [model.polite]), h.div([
|
|
109
|
+
h.Attribute(REGION_ATTRIBUTE, "assertive"),
|
|
110
|
+
h.AriaLive("assertive"),
|
|
111
|
+
h.AriaAtomic(true)
|
|
112
|
+
], [model.assertive])]);
|
|
113
|
+
export { Args, Message, Model, Politeness, REGION_ATTRIBUTE, bundle, reread, say, view };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=live-announce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live-announce.js","names":[],"sources":["../../src/interaction/live-announce.ts"],"sourcesContent":["/**\n * Announcements for assistive technology: one Bundle, placed once, holds the\n * text of a polite and an assertive live region. An announcement waits\n * `debounceMs` so a burst reads once, then clears after `clearAfterMs`, both\n * on Effect's clock with a generation so a superseded timer changes nothing.\n * The same text twice gets a trailing no-break space toggled, which is what\n * makes a screen reader read it again. `view` renders the two regions; put it\n * once in the page and style them visually hidden.\n */\nimport { Effect, Schema } from 'effect'\nimport type { Html, HtmlBuilder } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type * as Update from 'foldkit/update'\nimport { Bundle, type Declared } from 'foldkit-bundle'\n\nexport const Politeness = Schema.Literals(['polite', 'assertive'])\nexport type Politeness = typeof Politeness.Type\n\nexport const Model = Schema.Struct({\n polite: Schema.String,\n assertive: Schema.String,\n /** Waiting for the debounce to read. */\n pending: Schema.NullOr(Schema.Struct({ message: Schema.String, politeness: Politeness })),\n generation: Schema.Number,\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n Announced: { message: Schema.String, politeness: Politeness },\n Read: { generation: Schema.Number },\n Cleared: { generation: Schema.Number },\n})\nexport type Message = typeof Message.Type\n\nconst positive = Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n)\n\nexport const Args = Schema.Struct({\n /** A burst of announcements inside this window reads once, the last one. */\n debounceMs: positive,\n /** How long the text stays in the region. */\n clearAfterMs: positive,\n})\nexport type Args = typeof Args.Type\n\nconst NBSP = ' '\n\n/** The text to put in the region so that `next` is read even when it equals `current`. */\nexport const reread = (current: string, next: string): string =>\n current === next ? `${next}${NBSP}` : current === `${next}${NBSP}` ? next : next\n\ntype Return = Update.Return<Model, Message>\n\nconst after = (ms: number, message: Message, name: string, generation: number) => ({\n name,\n args: { generation },\n effect: Effect.as(Effect.sleep(ms), message),\n})\n\nexport const bundle = Bundle.make('LiveAnnounce', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { polite: '', assertive: '', pending: null, generation: 0 } }),\n update: (model, message, args): Return =>\n Message.match<Return>(message, {\n Announced: ({ message: text, politeness }) => {\n const generation = model.generation + 1\n return {\n model: { ...model, pending: { message: text, politeness }, generation },\n commands: [\n after(args.debounceMs, Message.Read({ generation }), 'LiveAnnounce.read', generation),\n ],\n }\n },\n Read: ({ generation }) => {\n if (generation !== model.generation || model.pending === null) return { model }\n const { message: text, politeness } = model.pending\n const region = politeness === 'polite' ? 'polite' : 'assertive'\n return {\n model: { ...model, pending: null, [region]: reread(model[region], text) },\n commands: [\n after(\n args.clearAfterMs,\n Message.Cleared({ generation }),\n 'LiveAnnounce.clear',\n generation,\n ),\n ],\n }\n },\n Cleared: ({ generation }) =>\n generation === model.generation\n ? { model: { ...model, polite: '', assertive: '' } }\n : { model },\n }),\n})\n\n/** The parent Message that announces `text`, to return from `update` or `onOut`. */\nexport const say =\n <Field extends string>(declared: Declared<typeof bundle, Field>) =>\n <ParentMessage>(text: string, politeness: Politeness = 'polite'): ParentMessage =>\n declared.wrapper.make(\n Message.Announced({ message: text, politeness }),\n ) as unknown as ParentMessage\n\nexport const REGION_ATTRIBUTE = 'data-foldkit-plus-live'\n\n/**\n * The two live regions. Render once, anywhere in the page, and hide them\n * visually with a rule on `[data-foldkit-plus-live]`; they must stay in the\n * accessibility tree, so no `display: none`.\n */\nexport const view = <ParentMessage>(model: Model, h: HtmlBuilder<ParentMessage>): Html =>\n h.div(\n [h.Attribute(REGION_ATTRIBUTE, 'regions')],\n [\n h.div(\n [h.Attribute(REGION_ATTRIBUTE, 'polite'), h.AriaLive('polite'), h.AriaAtomic(true)],\n [model.polite],\n ),\n h.div(\n [h.Attribute(REGION_ATTRIBUTE, 'assertive'), h.AriaLive('assertive'), h.AriaAtomic(true)],\n [model.assertive],\n ),\n ],\n )\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAa,aAAa,OAAO,SAAS,CAAC,UAAU,WAAW,CAAC;AAGjE,MAAa,QAAQ,OAAO,OAAO;CACjC,QAAQ,OAAO;CACf,WAAW,OAAO;;CAElB,SAAS,OAAO,OAAO,OAAO,OAAO;EAAE,SAAS,OAAO;EAAQ,YAAY;CAAW,CAAC,CAAC;CACxF,YAAY,OAAO;AACrB,CAAC;AAGD,MAAa,UAAU,mBAAmB;CACxC,WAAW;EAAE,SAAS,OAAO;EAAQ,YAAY;CAAW;CAC5D,MAAM,EAAE,YAAY,OAAO,OAAO;CAClC,SAAS,EAAE,YAAY,OAAO,OAAO;AACvC,CAAC;AAGD,MAAM,WAAW,OAAO,OAAO,KAC7B,OAAO,MAAM,OAAO,uBAAuB,CAAC,CAAC,GAC7C,OAAO,MAAM,OAAO,SAAS,CAAC,CAChC;AAEA,MAAa,OAAO,OAAO,OAAO;;CAEhC,YAAY;;CAEZ,cAAc;AAChB,CAAC;AAGD,MAAM,OAAO;;AAGb,MAAa,UAAU,SAAiB,SACtC,YAAY,OAAO,GAAG,OAAO,SAAS,YAAY,GAAG,OAAO,SAAS,OAAO;AAI9E,MAAM,SAAS,IAAY,SAAkB,MAAc,gBAAwB;CACjF;CACA,MAAM,EAAE,WAAW;CACnB,QAAQ,OAAO,GAAG,OAAO,MAAM,EAAE,GAAG,OAAO;AAC7C;AAEA,MAAa,SAAS,OAAO,KAAK,gBAAgB;CAChD;CACA;CACA,MAAM;CACN,aAAa,EAAE,OAAO;EAAE,QAAQ;EAAI,WAAW;EAAI,SAAS;EAAM,YAAY;CAAE,EAAE;CAClF,SAAS,OAAO,SAAS,SACvB,QAAQ,MAAc,SAAS;EAC7B,YAAY,EAAE,SAAS,MAAM,iBAAiB;GAC5C,MAAM,aAAa,MAAM,aAAa;GACtC,OAAO;IACL,OAAO;KAAE,GAAG;KAAO,SAAS;MAAE,SAAS;MAAM;KAAW;KAAG;IAAW;IACtE,UAAU,CACR,MAAM,KAAK,YAAY,QAAQ,KAAK,EAAE,WAAW,CAAC,GAAG,qBAAqB,UAAU,CACtF;GACF;EACF;EACA,OAAO,EAAE,iBAAiB;GACxB,IAAI,eAAe,MAAM,cAAc,MAAM,YAAY,MAAM,OAAO,EAAE,MAAM;GAC9E,MAAM,EAAE,SAAS,MAAM,eAAe,MAAM;GAC5C,MAAM,SAAS,eAAe,WAAW,WAAW;GACpD,OAAO;IACL,OAAO;KAAE,GAAG;KAAO,SAAS;MAAO,SAAS,OAAO,MAAM,SAAS,IAAI;IAAE;IACxE,UAAU,CACR,MACE,KAAK,cACL,QAAQ,QAAQ,EAAE,WAAW,CAAC,GAC9B,sBACA,UACF,CACF;GACF;EACF;EACA,UAAU,EAAE,iBACV,eAAe,MAAM,aACjB,EAAE,OAAO;GAAE,GAAG;GAAO,QAAQ;GAAI,WAAW;EAAG,EAAE,IACjD,EAAE,MAAM;CAChB,CAAC;AACL,CAAC;;AAGD,MAAa,OACY,cACP,MAAc,aAAyB,aACrD,SAAS,QAAQ,KACf,QAAQ,UAAU;CAAE,SAAS;CAAM;AAAW,CAAC,CACjD;AAEJ,MAAa,mBAAmB;;;;;;AAOhC,MAAa,QAAuB,OAAc,MAChD,EAAE,IACA,CAAC,EAAE,UAAU,kBAAkB,SAAS,CAAC,GACzC,CACE,EAAE,IACA;CAAC,EAAE,UAAU,kBAAkB,QAAQ;CAAG,EAAE,SAAS,QAAQ;CAAG,EAAE,WAAW,IAAI;AAAC,GAClF,CAAC,MAAM,MAAM,CACf,GACA,EAAE,IACA;CAAC,EAAE,UAAU,kBAAkB,WAAW;CAAG,EAAE,SAAS,WAAW;CAAG,EAAE,WAAW,IAAI;AAAC,GACxF,CAAC,MAAM,SAAS,CAClB,CACF,CACF"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle, Declared } from "foldkit-bundle";
|
|
3
|
+
import { Behavior } from "foldkit-mixins";
|
|
4
|
+
export declare const Model: Schema.Struct<{
|
|
5
|
+
/** Down, and the threshold has not passed. */
|
|
6
|
+
readonly holding: Schema.Boolean;
|
|
7
|
+
readonly pointerId: Schema.NullOr<Schema.Number>;
|
|
8
|
+
readonly key: Schema.NullOr<Schema.String>;
|
|
9
|
+
readonly generation: Schema.Number;
|
|
10
|
+
}>;
|
|
11
|
+
export type Model = typeof Model.Type;
|
|
12
|
+
export declare const Message: import("foldkit/message").MessageUnion<{
|
|
13
|
+
readonly PointerDown: {
|
|
14
|
+
pointerId: Schema.Number;
|
|
15
|
+
button: Schema.Number;
|
|
16
|
+
pointerType: Schema.String;
|
|
17
|
+
};
|
|
18
|
+
readonly PointerUp: {
|
|
19
|
+
pointerId: Schema.Number;
|
|
20
|
+
pointerType: Schema.String;
|
|
21
|
+
shiftKey: Schema.Boolean;
|
|
22
|
+
};
|
|
23
|
+
readonly PointerCancelled: {
|
|
24
|
+
pointerId: Schema.Number;
|
|
25
|
+
};
|
|
26
|
+
readonly KeyDown: {
|
|
27
|
+
key: Schema.String;
|
|
28
|
+
repeat: Schema.Boolean;
|
|
29
|
+
};
|
|
30
|
+
readonly KeyUp: {
|
|
31
|
+
key: Schema.String;
|
|
32
|
+
shiftKey: Schema.Boolean;
|
|
33
|
+
};
|
|
34
|
+
readonly Clicked: {
|
|
35
|
+
detail: Schema.Number;
|
|
36
|
+
shiftKey: Schema.Boolean;
|
|
37
|
+
};
|
|
38
|
+
readonly Elapsed: {
|
|
39
|
+
generation: Schema.Number;
|
|
40
|
+
pointerType: Schema.Literals<readonly ["mouse", "touch", "pen", "keyboard", "virtual"]>;
|
|
41
|
+
};
|
|
42
|
+
}>;
|
|
43
|
+
export type Message = typeof Message.Type;
|
|
44
|
+
export declare const LongPressed: Schema.TaggedStruct<"LongPressed", {
|
|
45
|
+
readonly pointerType: Schema.Literals<readonly ["mouse", "touch", "pen", "keyboard", "virtual"]>;
|
|
46
|
+
}>;
|
|
47
|
+
export type LongPressed = typeof LongPressed.Type;
|
|
48
|
+
export declare const Args: Schema.Struct<{
|
|
49
|
+
readonly thresholdMs: Schema.Number;
|
|
50
|
+
}>;
|
|
51
|
+
export type Args = typeof Args.Type;
|
|
52
|
+
export declare const bundle: Bundle.Bundle<"LongPress", {
|
|
53
|
+
readonly thresholdMs: number;
|
|
54
|
+
}, {
|
|
55
|
+
readonly holding: boolean;
|
|
56
|
+
readonly pointerId: number | null;
|
|
57
|
+
readonly key: string | null;
|
|
58
|
+
readonly generation: number;
|
|
59
|
+
}, {
|
|
60
|
+
readonly _tag: "Clicked";
|
|
61
|
+
readonly detail: number;
|
|
62
|
+
readonly shiftKey: boolean;
|
|
63
|
+
} | {
|
|
64
|
+
readonly _tag: "Elapsed";
|
|
65
|
+
readonly generation: number;
|
|
66
|
+
readonly pointerType: "keyboard" | "mouse" | "pen" | "touch" | "virtual";
|
|
67
|
+
} | {
|
|
68
|
+
readonly _tag: "KeyDown";
|
|
69
|
+
readonly key: string;
|
|
70
|
+
readonly repeat: boolean;
|
|
71
|
+
} | {
|
|
72
|
+
readonly _tag: "KeyUp";
|
|
73
|
+
readonly key: string;
|
|
74
|
+
readonly shiftKey: boolean;
|
|
75
|
+
} | {
|
|
76
|
+
readonly _tag: "PointerCancelled";
|
|
77
|
+
readonly pointerId: number;
|
|
78
|
+
} | {
|
|
79
|
+
readonly _tag: "PointerDown";
|
|
80
|
+
readonly pointerId: number;
|
|
81
|
+
readonly button: number;
|
|
82
|
+
readonly pointerType: string;
|
|
83
|
+
} | {
|
|
84
|
+
readonly _tag: "PointerUp";
|
|
85
|
+
readonly pointerId: number;
|
|
86
|
+
readonly pointerType: string;
|
|
87
|
+
readonly shiftKey: boolean;
|
|
88
|
+
}, {
|
|
89
|
+
readonly _tag: "LongPressed";
|
|
90
|
+
readonly pointerType: "keyboard" | "mouse" | "pen" | "touch" | "virtual";
|
|
91
|
+
}, never, never, void, {}, {}>;
|
|
92
|
+
export interface BehaviorOptions<Slots> {
|
|
93
|
+
readonly target: keyof Slots & string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Attaches `Press.events` to the target, mapped into this placement's
|
|
97
|
+
* Messages, and writes `data-holding` while the element is down. `Press` and
|
|
98
|
+
* `LongPress` on one slot are refused by the resolver (two Mounts of one
|
|
99
|
+
* name), so today a slot takes one of them.
|
|
100
|
+
*/
|
|
101
|
+
export declare const behavior: <Field extends string>(declared: Declared<typeof bundle, Field>) => <Slots>(slots: Slots) => <Input extends { readonly [K in Field]: Model; }, ParentMessage>(options: BehaviorOptions<Slots>) => Behavior.NamedBehavior<Slots, Input, ParentMessage>;
|
|
102
|
+
//# sourceMappingURL=long-press.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"long-press.d.ts","names":[],"sources":["../../src/interaction/long-press.ts"],"mappings":";;;qBAgBa,OAAK,OAAA;;;;;;;YAON,eAAe,MAAM;qBAEpB,mCAAO;;IACH,WAAS,OAAA;IAAiB,QAAM,OAAA;IAAiB,aAAW,OAAA;;;IAC9D,WAAS,OAAA;IAAiB,aAAW,OAAA;IAAiB,UAAQ,OAAA;;;IACvD,WAAS,OAAA;;;IAClB,KAAG,OAAA;IAAiB,QAAM,OAAA;;;IAC5B,KAAG,OAAA;IAAiB,UAAQ,OAAA;;;IAC1B,QAAM,OAAA;IAAiB,UAAQ,OAAA;;;IAC/B,YAAU,OAAA;IAAiB,aAAW,OAAA;;;YAEvC,iBAAiB,QAAQ;qBAExB,aAAW,OAAA;;;YACZ,qBAAqB,YAAY;qBAEhC,MAAI,OAAA;;;YAML,cAAc,KAAK;qBAmClB,QAAM,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCF,gBAAgB;WACtB,cAAc;;;;;;;;qBASZ,WACV,sBAAoB,UAAY,gBAAgB,QAAQ,YACxD,OAAK,OAAS,WACd,0BAA0B,KAAK,QAAQ,UAAS,eAAa,SACnD,gBAAgB,WACxB,SAAS,cAAc,OAAO,OAAO"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import { Bundle } from "foldkit-bundle";
|
|
4
|
+
import * as Mount from "foldkit/mount";
|
|
5
|
+
import { Behavior, Capability } from "foldkit-mixins";
|
|
6
|
+
import * as Press from "./press.js";
|
|
7
|
+
// src/interaction/long-press.ts
|
|
8
|
+
/**
|
|
9
|
+
* LongPress: holding an element down for `thresholdMs` is one fact. It reads
|
|
10
|
+
* the same facts `Press.events` reports; the threshold is a Command on
|
|
11
|
+
* Effect's clock carrying a generation, so a release before it fires makes
|
|
12
|
+
* its `Elapsed` a no-op. `LongPressed` is an OutMessage the placement must
|
|
13
|
+
* handle. A short press is `Press`'s business, not this Bundle's.
|
|
14
|
+
*/
|
|
15
|
+
const Model = Schema.Struct({
|
|
16
|
+
/** Down, and the threshold has not passed. */
|
|
17
|
+
holding: Schema.Boolean,
|
|
18
|
+
pointerId: Schema.NullOr(Schema.Number),
|
|
19
|
+
key: Schema.NullOr(Schema.String),
|
|
20
|
+
generation: Schema.Number
|
|
21
|
+
});
|
|
22
|
+
const Message = defineMessageUnion({
|
|
23
|
+
PointerDown: {
|
|
24
|
+
pointerId: Schema.Number,
|
|
25
|
+
button: Schema.Number,
|
|
26
|
+
pointerType: Schema.String
|
|
27
|
+
},
|
|
28
|
+
PointerUp: {
|
|
29
|
+
pointerId: Schema.Number,
|
|
30
|
+
pointerType: Schema.String,
|
|
31
|
+
shiftKey: Schema.Boolean
|
|
32
|
+
},
|
|
33
|
+
PointerCancelled: { pointerId: Schema.Number },
|
|
34
|
+
KeyDown: {
|
|
35
|
+
key: Schema.String,
|
|
36
|
+
repeat: Schema.Boolean
|
|
37
|
+
},
|
|
38
|
+
KeyUp: {
|
|
39
|
+
key: Schema.String,
|
|
40
|
+
shiftKey: Schema.Boolean
|
|
41
|
+
},
|
|
42
|
+
Clicked: {
|
|
43
|
+
detail: Schema.Number,
|
|
44
|
+
shiftKey: Schema.Boolean
|
|
45
|
+
},
|
|
46
|
+
Elapsed: {
|
|
47
|
+
generation: Schema.Number,
|
|
48
|
+
pointerType: Press.PointerType
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
const LongPressed = Schema.TaggedStruct("LongPressed", { pointerType: Press.PointerType });
|
|
52
|
+
const Args = Schema.Struct({ thresholdMs: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())) });
|
|
53
|
+
const isActivationKey = (key) => key === "Enter" || key === " ";
|
|
54
|
+
const released = (model) => ({
|
|
55
|
+
...model,
|
|
56
|
+
holding: false,
|
|
57
|
+
pointerId: null,
|
|
58
|
+
key: null
|
|
59
|
+
});
|
|
60
|
+
const start = (model, args, by, pointerType) => {
|
|
61
|
+
const generation = model.generation + 1;
|
|
62
|
+
return {
|
|
63
|
+
model: {
|
|
64
|
+
holding: true,
|
|
65
|
+
pointerId: "pointerId" in by ? by.pointerId : null,
|
|
66
|
+
key: "key" in by ? by.key : null,
|
|
67
|
+
generation
|
|
68
|
+
},
|
|
69
|
+
commands: [{
|
|
70
|
+
name: "LongPress.elapse",
|
|
71
|
+
args: { generation },
|
|
72
|
+
effect: Effect.as(Effect.sleep(args.thresholdMs), Message.Elapsed({
|
|
73
|
+
generation,
|
|
74
|
+
pointerType
|
|
75
|
+
}))
|
|
76
|
+
}]
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
const bundle = Bundle.make("LongPress", {
|
|
80
|
+
Model,
|
|
81
|
+
Message,
|
|
82
|
+
args: Args,
|
|
83
|
+
init: () => ({ model: {
|
|
84
|
+
holding: false,
|
|
85
|
+
pointerId: null,
|
|
86
|
+
key: null,
|
|
87
|
+
generation: 0
|
|
88
|
+
} }),
|
|
89
|
+
update: (model, message, args) => Message.match(message, {
|
|
90
|
+
PointerDown: ({ pointerId, button, pointerType }) => button !== 0 || model.holding ? { model } : start(model, args, { pointerId }, pointerType === "touch" || pointerType === "pen" ? pointerType : "mouse"),
|
|
91
|
+
PointerUp: ({ pointerId }) => model.pointerId === pointerId ? { model: released(model) } : { model },
|
|
92
|
+
PointerCancelled: ({ pointerId }) => model.pointerId === pointerId ? { model: released(model) } : { model },
|
|
93
|
+
KeyDown: ({ key, repeat }) => !isActivationKey(key) || repeat || model.holding ? { model } : start(model, args, { key }, "keyboard"),
|
|
94
|
+
KeyUp: ({ key }) => model.key === key ? { model: released(model) } : { model },
|
|
95
|
+
Clicked: () => ({ model }),
|
|
96
|
+
Elapsed: ({ generation, pointerType }) => model.holding && model.generation === generation ? {
|
|
97
|
+
model: released(model),
|
|
98
|
+
outMessage: LongPressed.make({ pointerType })
|
|
99
|
+
} : { model }
|
|
100
|
+
})
|
|
101
|
+
});
|
|
102
|
+
/**
|
|
103
|
+
* Attaches `Press.events` to the target, mapped into this placement's
|
|
104
|
+
* Messages, and writes `data-holding` while the element is down. `Press` and
|
|
105
|
+
* `LongPress` on one slot are refused by the resolver (two Mounts of one
|
|
106
|
+
* name), so today a slot takes one of them.
|
|
107
|
+
*/
|
|
108
|
+
const behavior = (declared) => (slots) => (options) => Behavior.forSlots(slots)({ [options.target]: Behavior.slot({
|
|
109
|
+
requires: { capability: Capability.Interactive },
|
|
110
|
+
attributes: ({ input, h }) => input[declared.field].holding ? [h.DataAttribute("holding", "true")] : [],
|
|
111
|
+
mount: () => Mount.mapMessage(Press.events(), (fact) => declared.wrapper.make(fact))
|
|
112
|
+
}) }, { name: "LongPress" });
|
|
113
|
+
export { Args, LongPressed, Message, Model, behavior, bundle };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=long-press.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"long-press.js","names":[],"sources":["../../src/interaction/long-press.ts"],"sourcesContent":["/**\n * LongPress: holding an element down for `thresholdMs` is one fact. It reads\n * the same facts `Press.events` reports; the threshold is a Command on\n * Effect's clock carrying a generation, so a release before it fires makes\n * its `Elapsed` a no-op. `LongPressed` is an OutMessage the placement must\n * handle. A short press is `Press`'s business, not this Bundle's.\n */\nimport { Effect, Schema } from 'effect'\nimport type { HtmlBuilder } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Mount from 'foldkit/mount'\nimport type * as Update from 'foldkit/update'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Capability } from 'foldkit-mixins'\nimport * as Press from './press.js'\n\nexport const Model = Schema.Struct({\n /** Down, and the threshold has not passed. */\n holding: Schema.Boolean,\n pointerId: Schema.NullOr(Schema.Number),\n key: Schema.NullOr(Schema.String),\n generation: Schema.Number,\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n PointerDown: { pointerId: Schema.Number, button: Schema.Number, pointerType: Schema.String },\n PointerUp: { pointerId: Schema.Number, pointerType: Schema.String, shiftKey: Schema.Boolean },\n PointerCancelled: { pointerId: Schema.Number },\n KeyDown: { key: Schema.String, repeat: Schema.Boolean },\n KeyUp: { key: Schema.String, shiftKey: Schema.Boolean },\n Clicked: { detail: Schema.Number, shiftKey: Schema.Boolean },\n Elapsed: { generation: Schema.Number, pointerType: Press.PointerType },\n})\nexport type Message = typeof Message.Type\n\nexport const LongPressed = Schema.TaggedStruct('LongPressed', { pointerType: Press.PointerType })\nexport type LongPressed = typeof LongPressed.Type\n\nexport const Args = Schema.Struct({\n thresholdMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n})\nexport type Args = typeof Args.Type\n\nconst isActivationKey = (key: string): boolean => key === 'Enter' || key === ' '\n\nconst released = (model: Model): Model => ({ ...model, holding: false, pointerId: null, key: null })\n\ntype Return = Update.ReturnWithOutMessage<Model, Message, LongPressed>\n\nconst start = (\n model: Model,\n args: Args,\n by: { readonly pointerId: number } | { readonly key: string },\n pointerType: Press.PointerType,\n): Return => {\n const generation = model.generation + 1\n return {\n model: {\n holding: true,\n pointerId: 'pointerId' in by ? by.pointerId : null,\n key: 'key' in by ? by.key : null,\n generation,\n },\n commands: [\n {\n name: 'LongPress.elapse',\n args: { generation },\n effect: Effect.as(\n Effect.sleep(args.thresholdMs),\n Message.Elapsed({ generation, pointerType }),\n ),\n },\n ],\n }\n}\n\nexport const bundle = Bundle.make('LongPress', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { holding: false, pointerId: null, key: null, generation: 0 } }),\n update: (model, message, args): Return =>\n Message.match<Return>(message, {\n PointerDown: ({ pointerId, button, pointerType }) =>\n button !== 0 || model.holding\n ? { model }\n : start(\n model,\n args,\n { pointerId },\n pointerType === 'touch' || pointerType === 'pen' ? pointerType : 'mouse',\n ),\n PointerUp: ({ pointerId }) =>\n model.pointerId === pointerId ? { model: released(model) } : { model },\n PointerCancelled: ({ pointerId }) =>\n model.pointerId === pointerId ? { model: released(model) } : { model },\n KeyDown: ({ key, repeat }) =>\n !isActivationKey(key) || repeat || model.holding\n ? { model }\n : start(model, args, { key }, 'keyboard'),\n KeyUp: ({ key }) => (model.key === key ? { model: released(model) } : { model }),\n Clicked: () => ({ model }),\n Elapsed: ({ generation, pointerType }) =>\n model.holding && model.generation === generation\n ? { model: released(model), outMessage: LongPressed.make({ pointerType }) }\n : { model },\n }),\n})\n\nexport interface BehaviorOptions<Slots> {\n readonly target: keyof Slots & string\n}\n\n/**\n * Attaches `Press.events` to the target, mapped into this placement's\n * Messages, and writes `data-holding` while the element is down. `Press` and\n * `LongPress` on one slot are refused by the resolver (two Mounts of one\n * name), so today a slot takes one of them.\n */\nexport const behavior =\n <Field extends string>(declared: Declared<typeof bundle, Field>) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: Model }, ParentMessage>(\n options: BehaviorOptions<Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.target]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => (input[declared.field].holding ? [h.DataAttribute('holding', 'true')] : []),\n mount: () =>\n Mount.mapMessage(\n Press.events(),\n // The facts are the same tagged structs; only the union differs.\n (fact): ParentMessage =>\n declared.wrapper.make(fact as unknown as Message) as unknown as ParentMessage,\n ),\n }),\n // Keyed by a value the caller chose; `forSlots` checks the key exists.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'LongPress' },\n )\n"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Fact, Fact as Fact$1, Move as mount, MoveEnded, MoveStarted, Moved } from "../dom/move.js";
|
|
2
|
+
import { Behavior } from "foldkit-mixins";
|
|
3
|
+
export interface BehaviorOptions<Slots, ParentMessage> {
|
|
4
|
+
readonly handle: keyof Slots & string;
|
|
5
|
+
/** Turns each fact into the view's Message. */
|
|
6
|
+
readonly toMessage: (fact: Fact$1) => ParentMessage;
|
|
7
|
+
}
|
|
8
|
+
export declare const behavior: <Slots>(slots: Slots) => <Input, ParentMessage>(options: BehaviorOptions<Slots, ParentMessage>) => Behavior.NamedBehavior<Slots, Input, ParentMessage>;
|
|
9
|
+
export { type Fact, MoveEnded, MoveStarted, Moved, mount };
|
|
10
|
+
//# sourceMappingURL=move.d.ts.map
|