foldkit-primitives 0.15.4 → 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-l7InESYh.d.mts → events/input-modality.d.ts} +9 -10
- package/dist/events/input-modality.d.ts.map +1 -0
- package/dist/{input-modality-XwQOFLip.mjs → events/input-modality.js} +2 -2
- 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-C4gHodLc.mjs → internal.js} +7 -5
- 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} +10 -306
- 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 -335
- package/dist/device/index.d.mts.map +0 -1
- package/dist/device/index.mjs +0 -493
- package/dist/device/index.mjs.map +0 -1
- package/dist/dom/index.d.mts +0 -714
- package/dist/dom/index.d.mts.map +0 -1
- package/dist/dom/index.mjs +0 -1840
- package/dist/dom/index.mjs.map +0 -1
- package/dist/editable-text-Bg7vil7h.d.mts +0 -307
- package/dist/editable-text-Bg7vil7h.d.mts.map +0 -1
- package/dist/events/index.d.mts +0 -168
- package/dist/events/index.d.mts.map +0 -1
- package/dist/events/index.mjs +0 -307
- package/dist/events/index.mjs.map +0 -1
- package/dist/follow-tab-stop-DM5EI4Ci.mjs +0 -856
- package/dist/follow-tab-stop-DM5EI4Ci.mjs.map +0 -1
- package/dist/index-Bomdwb7e.d.mts +0 -142
- package/dist/index-Bomdwb7e.d.mts.map +0 -1
- package/dist/index.d.mts +0 -12
- package/dist/index.mjs +0 -12
- package/dist/input-modality-XwQOFLip.mjs.map +0 -1
- package/dist/input-modality-l7InESYh.d.mts.map +0 -1
- package/dist/interaction/index.d.mts +0 -1193
- package/dist/interaction/index.d.mts.map +0 -1
- package/dist/interaction/index.mjs +0 -1992
- package/dist/interaction/index.mjs.map +0 -1
- package/dist/internal-C4gHodLc.mjs.map +0 -1
- package/dist/media/index.d.mts +0 -103
- package/dist/media/index.d.mts.map +0 -1
- package/dist/media/index.mjs +0 -146
- 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-BmlJCjn6.mjs +0 -236
- package/dist/motion-BmlJCjn6.mjs.map +0 -1
- package/dist/net/index.d.mts +0 -338
- package/dist/net/index.d.mts.map +0 -1
- package/dist/net/index.mjs +0 -530
- package/dist/net/index.mjs.map +0 -1
- package/dist/observers/index.d.mts +0 -76
- package/dist/observers/index.d.mts.map +0 -1
- package/dist/observers/index.mjs +0 -155
- package/dist/observers/index.mjs.map +0 -1
- package/dist/rolldown-runtime-D8jQpDG7.mjs +0 -12
- package/dist/state/index.d.mts +0 -432
- package/dist/state/index.d.mts.map +0 -1
- package/dist/state/index.mjs.map +0 -1
- package/dist/time/index.d.mts +0 -167
- package/dist/time/index.d.mts.map +0 -1
- package/dist/time/index.mjs +0 -236
- package/dist/time/index.mjs.map +0 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle, Declared } from "foldkit-bundle";
|
|
3
|
+
import * as Mount from "foldkit/mount";
|
|
4
|
+
import { Behavior } from "foldkit-mixins";
|
|
5
|
+
export declare const PointerType: Schema.Literals<readonly ["mouse", "touch", "pen", "keyboard", "virtual"]>;
|
|
6
|
+
export type PointerType = typeof PointerType.Type;
|
|
7
|
+
export declare const Model: Schema.Struct<{
|
|
8
|
+
/** Down and not yet released or cancelled. For styling. */
|
|
9
|
+
readonly pressed: Schema.Boolean;
|
|
10
|
+
/** The pointer that pressed, so another pointer's up cannot release it. */
|
|
11
|
+
readonly pointerId: Schema.NullOr<Schema.Number>;
|
|
12
|
+
/** Enter or Space is down. */
|
|
13
|
+
readonly key: Schema.NullOr<Schema.String>;
|
|
14
|
+
/** A click arriving before this generation's window ends is the ghost of a pointer press. */
|
|
15
|
+
readonly suppressing: Schema.NullOr<Schema.Number>;
|
|
16
|
+
readonly generation: Schema.Number;
|
|
17
|
+
}>;
|
|
18
|
+
export type Model = typeof Model.Type;
|
|
19
|
+
export declare const Message: import("foldkit/message").MessageUnion<{
|
|
20
|
+
readonly PointerDown: {
|
|
21
|
+
pointerId: Schema.Number;
|
|
22
|
+
button: Schema.Number;
|
|
23
|
+
pointerType: Schema.String;
|
|
24
|
+
};
|
|
25
|
+
readonly PointerUp: {
|
|
26
|
+
pointerId: Schema.Number;
|
|
27
|
+
pointerType: Schema.String;
|
|
28
|
+
shiftKey: Schema.Boolean;
|
|
29
|
+
};
|
|
30
|
+
readonly PointerCancelled: {
|
|
31
|
+
pointerId: Schema.Number;
|
|
32
|
+
};
|
|
33
|
+
readonly KeyDown: {
|
|
34
|
+
key: Schema.String;
|
|
35
|
+
repeat: Schema.Boolean;
|
|
36
|
+
};
|
|
37
|
+
readonly KeyUp: {
|
|
38
|
+
key: Schema.String;
|
|
39
|
+
shiftKey: Schema.Boolean;
|
|
40
|
+
};
|
|
41
|
+
/** `detail` is 0 for a click no pointer made: keyboard on a native control, or assistive technology. */
|
|
42
|
+
readonly Clicked: {
|
|
43
|
+
detail: Schema.Number;
|
|
44
|
+
shiftKey: Schema.Boolean;
|
|
45
|
+
};
|
|
46
|
+
readonly Unsuppressed: {
|
|
47
|
+
generation: Schema.Number;
|
|
48
|
+
};
|
|
49
|
+
}>;
|
|
50
|
+
export type Message = typeof Message.Type;
|
|
51
|
+
export declare const Pressed: Schema.TaggedStruct<"Pressed", {
|
|
52
|
+
readonly pointerType: Schema.Literals<readonly ["mouse", "touch", "pen", "keyboard", "virtual"]>;
|
|
53
|
+
/** Shift was held, for a range selection. */
|
|
54
|
+
readonly shiftKey: Schema.Boolean;
|
|
55
|
+
}>;
|
|
56
|
+
export type Pressed = typeof Pressed.Type;
|
|
57
|
+
export declare const Args: Schema.Struct<{
|
|
58
|
+
/** How long after a pointer press a `click` is taken for its ghost. */
|
|
59
|
+
readonly clickSuppressionMs: Schema.Number;
|
|
60
|
+
}>;
|
|
61
|
+
export type Args = typeof Args.Type;
|
|
62
|
+
export declare const bundle: Bundle.Bundle<"Press", {
|
|
63
|
+
readonly clickSuppressionMs: number;
|
|
64
|
+
}, {
|
|
65
|
+
readonly pressed: boolean;
|
|
66
|
+
readonly pointerId: number | null;
|
|
67
|
+
readonly key: string | null;
|
|
68
|
+
readonly suppressing: number | null;
|
|
69
|
+
readonly generation: number;
|
|
70
|
+
}, {
|
|
71
|
+
readonly _tag: "Clicked";
|
|
72
|
+
readonly detail: number;
|
|
73
|
+
readonly shiftKey: boolean;
|
|
74
|
+
} | {
|
|
75
|
+
readonly _tag: "KeyDown";
|
|
76
|
+
readonly key: string;
|
|
77
|
+
readonly repeat: boolean;
|
|
78
|
+
} | {
|
|
79
|
+
readonly _tag: "KeyUp";
|
|
80
|
+
readonly key: string;
|
|
81
|
+
readonly shiftKey: boolean;
|
|
82
|
+
} | {
|
|
83
|
+
readonly _tag: "PointerCancelled";
|
|
84
|
+
readonly pointerId: number;
|
|
85
|
+
} | {
|
|
86
|
+
readonly _tag: "PointerDown";
|
|
87
|
+
readonly pointerId: number;
|
|
88
|
+
readonly button: number;
|
|
89
|
+
readonly pointerType: string;
|
|
90
|
+
} | {
|
|
91
|
+
readonly _tag: "PointerUp";
|
|
92
|
+
readonly pointerId: number;
|
|
93
|
+
readonly pointerType: string;
|
|
94
|
+
readonly shiftKey: boolean;
|
|
95
|
+
} | {
|
|
96
|
+
readonly _tag: "Unsuppressed";
|
|
97
|
+
readonly generation: number;
|
|
98
|
+
}, {
|
|
99
|
+
readonly _tag: "Pressed";
|
|
100
|
+
readonly pointerType: "keyboard" | "mouse" | "pen" | "touch" | "virtual";
|
|
101
|
+
readonly shiftKey: boolean;
|
|
102
|
+
}, never, never, void, {}, {}>;
|
|
103
|
+
/** What the element reports; `Unsuppressed` comes from the timer, not the DOM. */
|
|
104
|
+
export type Fact = Exclude<Message, {
|
|
105
|
+
readonly _tag: 'Unsuppressed';
|
|
106
|
+
}>;
|
|
107
|
+
/**
|
|
108
|
+
* The element's press-relevant events as facts. Enter and Space key downs
|
|
109
|
+
* are default-prevented so a native control does not also click, and Space
|
|
110
|
+
* does not scroll; the Bundle owns activation. An element marked
|
|
111
|
+
* `aria-disabled="true"` (which the Behavior writes) reports nothing, read
|
|
112
|
+
* at event time so a toggle needs no remount. Nothing else is decided here.
|
|
113
|
+
*/
|
|
114
|
+
export declare const events: Mount.MountDefinitionNoArgs<"PressEvents", {
|
|
115
|
+
readonly _tag: "Clicked";
|
|
116
|
+
readonly detail: number;
|
|
117
|
+
readonly shiftKey: boolean;
|
|
118
|
+
} | {
|
|
119
|
+
readonly _tag: "KeyDown";
|
|
120
|
+
readonly key: string;
|
|
121
|
+
readonly repeat: boolean;
|
|
122
|
+
} | {
|
|
123
|
+
readonly _tag: "KeyUp";
|
|
124
|
+
readonly key: string;
|
|
125
|
+
readonly shiftKey: boolean;
|
|
126
|
+
} | {
|
|
127
|
+
readonly _tag: "PointerCancelled";
|
|
128
|
+
readonly pointerId: number;
|
|
129
|
+
} | {
|
|
130
|
+
readonly _tag: "PointerDown";
|
|
131
|
+
readonly pointerId: number;
|
|
132
|
+
readonly button: number;
|
|
133
|
+
readonly pointerType: string;
|
|
134
|
+
} | {
|
|
135
|
+
readonly _tag: "PointerUp";
|
|
136
|
+
readonly pointerId: number;
|
|
137
|
+
readonly pointerType: string;
|
|
138
|
+
readonly shiftKey: boolean;
|
|
139
|
+
}>;
|
|
140
|
+
export interface BehaviorOptions<Input, Slots> {
|
|
141
|
+
readonly target: keyof Slots & string;
|
|
142
|
+
/** Marks the target `aria-disabled`, which also silences its events. */
|
|
143
|
+
readonly disabled?: (input: Input) => boolean;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Attaches the events Mount to the target slot, mapped into the placement's
|
|
147
|
+
* Messages, and writes `data-pressed` while down for styling. The element
|
|
148
|
+
* decides nothing; a click, a touch, Enter, Space, or an assistive
|
|
149
|
+
* technology's click all reach `update` and come out as one `Pressed`.
|
|
150
|
+
*/
|
|
151
|
+
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<Input, Slots>) => Behavior.NamedBehavior<Slots, Input, ParentMessage>;
|
|
152
|
+
//# sourceMappingURL=press.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"press.d.ts","names":[],"sources":["../../src/interaction/press.ts"],"mappings":";;;;qBAiBa,aAAW,OAAA;YACZ,qBAAqB,YAAY;qBAEhC,OAAK,OAAA;;;;;;;;;;;YAWN,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;;;;IAE1B,QAAM,OAAA;IAAiB,UAAQ,OAAA;;;IAC1B,YAAU,OAAA;;;YAEhB,iBAAiB,QAAQ;qBAExB,SAAO,OAAA;;;;;YAKR,iBAAiB,QAAQ;qBAExB,MAAI,OAAA;;;;YAOL,cAAc,KAAK;qBAWlB,QAAM,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YA4DP,OAAO,QAAQ;WAAoB;;;;;;;;;qBASlC,QAAM,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8FF,gBAAgB,OAAO;WAC7B,cAAc;;WAEd,YAAY,OAAO;;;;;;;;qBASjB,WACV,sBAAoB,UAAY,gBAAgB,QAAQ,YACxD,OAAK,OAAS,WACd,0BAA0B,KAAK,QAAQ,UAAS,eAAa,SACnD,gBAAgB,OAAO,WAC/B,SAAS,cAAc,OAAO,OAAO"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { Effect, Queue, Schema, Stream } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import { Bundle } from "foldkit-bundle";
|
|
4
|
+
import * as Mount from "foldkit/mount";
|
|
5
|
+
import { Capability } from "foldkit-mixins";
|
|
6
|
+
import { BundleBehavior } from "./bundle-behavior.js";
|
|
7
|
+
// src/interaction/press.ts
|
|
8
|
+
/**
|
|
9
|
+
* Press: pointer and keyboard activation of one element as one fact. A Mount
|
|
10
|
+
* reports what the element saw (pointer down, up, cancel, key down, up,
|
|
11
|
+
* click) with the detail Foldkit's declarative attributes do not carry; the
|
|
12
|
+
* Bundle's `update` decides what counts as a press; the ghost click that
|
|
13
|
+
* follows a touch is suppressed by a timed Command on Effect's clock, never
|
|
14
|
+
* by a clock read in a handler. Activation is an OutMessage, `Pressed`, so
|
|
15
|
+
* the placement must say what a press does.
|
|
16
|
+
*/
|
|
17
|
+
const PointerType = Schema.Literals([
|
|
18
|
+
"mouse",
|
|
19
|
+
"touch",
|
|
20
|
+
"pen",
|
|
21
|
+
"keyboard",
|
|
22
|
+
"virtual"
|
|
23
|
+
]);
|
|
24
|
+
const Model = Schema.Struct({
|
|
25
|
+
/** Down and not yet released or cancelled. For styling. */
|
|
26
|
+
pressed: Schema.Boolean,
|
|
27
|
+
/** The pointer that pressed, so another pointer's up cannot release it. */
|
|
28
|
+
pointerId: Schema.NullOr(Schema.Number),
|
|
29
|
+
/** Enter or Space is down. */
|
|
30
|
+
key: Schema.NullOr(Schema.String),
|
|
31
|
+
/** A click arriving before this generation's window ends is the ghost of a pointer press. */
|
|
32
|
+
suppressing: Schema.NullOr(Schema.Number),
|
|
33
|
+
generation: Schema.Number
|
|
34
|
+
});
|
|
35
|
+
const Message = defineMessageUnion({
|
|
36
|
+
PointerDown: {
|
|
37
|
+
pointerId: Schema.Number,
|
|
38
|
+
button: Schema.Number,
|
|
39
|
+
pointerType: Schema.String
|
|
40
|
+
},
|
|
41
|
+
PointerUp: {
|
|
42
|
+
pointerId: Schema.Number,
|
|
43
|
+
pointerType: Schema.String,
|
|
44
|
+
shiftKey: Schema.Boolean
|
|
45
|
+
},
|
|
46
|
+
PointerCancelled: { pointerId: Schema.Number },
|
|
47
|
+
KeyDown: {
|
|
48
|
+
key: Schema.String,
|
|
49
|
+
repeat: Schema.Boolean
|
|
50
|
+
},
|
|
51
|
+
KeyUp: {
|
|
52
|
+
key: Schema.String,
|
|
53
|
+
shiftKey: Schema.Boolean
|
|
54
|
+
},
|
|
55
|
+
/** `detail` is 0 for a click no pointer made: keyboard on a native control, or assistive technology. */
|
|
56
|
+
Clicked: {
|
|
57
|
+
detail: Schema.Number,
|
|
58
|
+
shiftKey: Schema.Boolean
|
|
59
|
+
},
|
|
60
|
+
Unsuppressed: { generation: Schema.Number }
|
|
61
|
+
});
|
|
62
|
+
const Pressed = Schema.TaggedStruct("Pressed", {
|
|
63
|
+
pointerType: PointerType,
|
|
64
|
+
/** Shift was held, for a range selection. */
|
|
65
|
+
shiftKey: Schema.Boolean
|
|
66
|
+
});
|
|
67
|
+
const Args = Schema.Struct({
|
|
68
|
+
/** How long after a pointer press a `click` is taken for its ghost. */
|
|
69
|
+
clickSuppressionMs: Schema.Number.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.check(Schema.isFinite())) });
|
|
70
|
+
const idle = (model) => ({
|
|
71
|
+
...model,
|
|
72
|
+
pressed: false,
|
|
73
|
+
pointerId: null,
|
|
74
|
+
key: null
|
|
75
|
+
});
|
|
76
|
+
const isActivationKey = (key) => key === "Enter" || key === " ";
|
|
77
|
+
const asPointerType = (raw) => raw === "touch" || raw === "pen" ? raw : "mouse";
|
|
78
|
+
const bundle = Bundle.make("Press", {
|
|
79
|
+
Model,
|
|
80
|
+
Message,
|
|
81
|
+
args: Args,
|
|
82
|
+
init: () => ({ model: {
|
|
83
|
+
pressed: false,
|
|
84
|
+
pointerId: null,
|
|
85
|
+
key: null,
|
|
86
|
+
suppressing: null,
|
|
87
|
+
generation: 0
|
|
88
|
+
} }),
|
|
89
|
+
update: (model, message, args) => Message.match(message, {
|
|
90
|
+
PointerDown: ({ pointerId, button }) => button !== 0 || model.pointerId !== null ? { model } : { model: {
|
|
91
|
+
...model,
|
|
92
|
+
pressed: true,
|
|
93
|
+
pointerId
|
|
94
|
+
} },
|
|
95
|
+
PointerUp: ({ pointerId, pointerType, shiftKey }) => {
|
|
96
|
+
if (model.pointerId !== pointerId) return { model };
|
|
97
|
+
const generation = model.generation + 1;
|
|
98
|
+
return {
|
|
99
|
+
model: {
|
|
100
|
+
...idle(model),
|
|
101
|
+
suppressing: generation,
|
|
102
|
+
generation
|
|
103
|
+
},
|
|
104
|
+
commands: [{
|
|
105
|
+
name: "Press.unsuppress",
|
|
106
|
+
args: { generation },
|
|
107
|
+
effect: Effect.as(Effect.sleep(args.clickSuppressionMs), Message.Unsuppressed({ generation }))
|
|
108
|
+
}],
|
|
109
|
+
outMessage: Pressed.make({
|
|
110
|
+
pointerType: asPointerType(pointerType),
|
|
111
|
+
shiftKey
|
|
112
|
+
})
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
PointerCancelled: ({ pointerId }) => model.pointerId === pointerId ? { model: idle(model) } : { model },
|
|
116
|
+
KeyDown: ({ key, repeat }) => !isActivationKey(key) || repeat || model.key !== null || model.pointerId !== null ? { model } : { model: {
|
|
117
|
+
...model,
|
|
118
|
+
pressed: true,
|
|
119
|
+
key
|
|
120
|
+
} },
|
|
121
|
+
KeyUp: ({ key, shiftKey }) => model.key === key ? {
|
|
122
|
+
model: idle(model),
|
|
123
|
+
outMessage: Pressed.make({
|
|
124
|
+
pointerType: "keyboard",
|
|
125
|
+
shiftKey
|
|
126
|
+
})
|
|
127
|
+
} : { model },
|
|
128
|
+
Clicked: ({ detail, shiftKey }) => detail === 0 ? {
|
|
129
|
+
model,
|
|
130
|
+
outMessage: Pressed.make({
|
|
131
|
+
pointerType: "virtual",
|
|
132
|
+
shiftKey
|
|
133
|
+
})
|
|
134
|
+
} : model.suppressing !== null ? { model } : {
|
|
135
|
+
model,
|
|
136
|
+
outMessage: Pressed.make({
|
|
137
|
+
pointerType: "mouse",
|
|
138
|
+
shiftKey
|
|
139
|
+
})
|
|
140
|
+
},
|
|
141
|
+
Unsuppressed: ({ generation }) => model.suppressing === generation ? { model: {
|
|
142
|
+
...model,
|
|
143
|
+
suppressing: null
|
|
144
|
+
} } : { model }
|
|
145
|
+
})
|
|
146
|
+
});
|
|
147
|
+
/**
|
|
148
|
+
* The element's press-relevant events as facts. Enter and Space key downs
|
|
149
|
+
* are default-prevented so a native control does not also click, and Space
|
|
150
|
+
* does not scroll; the Bundle owns activation. An element marked
|
|
151
|
+
* `aria-disabled="true"` (which the Behavior writes) reports nothing, read
|
|
152
|
+
* at event time so a toggle needs no remount. Nothing else is decided here.
|
|
153
|
+
*/
|
|
154
|
+
const events = Mount.defineStream("PressEvents", {
|
|
155
|
+
messages: [
|
|
156
|
+
Message.PointerDown,
|
|
157
|
+
Message.PointerUp,
|
|
158
|
+
Message.PointerCancelled,
|
|
159
|
+
Message.KeyDown,
|
|
160
|
+
Message.KeyUp,
|
|
161
|
+
Message.Clicked
|
|
162
|
+
],
|
|
163
|
+
execute: ({ element }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
|
|
164
|
+
const offer = (message) => {
|
|
165
|
+
if (element.getAttribute("aria-disabled") === "true") return;
|
|
166
|
+
Queue.offerUnsafe(queue, message);
|
|
167
|
+
};
|
|
168
|
+
const listeners = [
|
|
169
|
+
["pointerdown", (event) => {
|
|
170
|
+
const pointer = event;
|
|
171
|
+
offer(Message.PointerDown({
|
|
172
|
+
pointerId: pointer.pointerId ?? 0,
|
|
173
|
+
button: pointer.button ?? 0,
|
|
174
|
+
pointerType: pointer.pointerType ?? "mouse"
|
|
175
|
+
}));
|
|
176
|
+
}],
|
|
177
|
+
["pointerup", (event) => {
|
|
178
|
+
const pointer = event;
|
|
179
|
+
offer(Message.PointerUp({
|
|
180
|
+
pointerId: pointer.pointerId ?? 0,
|
|
181
|
+
pointerType: pointer.pointerType ?? "mouse",
|
|
182
|
+
shiftKey: pointer.shiftKey ?? false
|
|
183
|
+
}));
|
|
184
|
+
}],
|
|
185
|
+
["pointerleave", (event) => offer(Message.PointerCancelled({ pointerId: event.pointerId ?? 0 }))],
|
|
186
|
+
["pointercancel", (event) => offer(Message.PointerCancelled({ pointerId: event.pointerId ?? 0 }))],
|
|
187
|
+
["keydown", (event) => {
|
|
188
|
+
const key = event;
|
|
189
|
+
if (isActivationKey(key.key)) key.preventDefault();
|
|
190
|
+
offer(Message.KeyDown({
|
|
191
|
+
key: key.key,
|
|
192
|
+
repeat: key.repeat
|
|
193
|
+
}));
|
|
194
|
+
}],
|
|
195
|
+
["keyup", (event) => {
|
|
196
|
+
const key = event;
|
|
197
|
+
offer(Message.KeyUp({
|
|
198
|
+
key: key.key,
|
|
199
|
+
shiftKey: key.shiftKey
|
|
200
|
+
}));
|
|
201
|
+
}],
|
|
202
|
+
["click", (event) => {
|
|
203
|
+
const click = event;
|
|
204
|
+
offer(Message.Clicked({
|
|
205
|
+
detail: click.detail ?? 0,
|
|
206
|
+
shiftKey: click.shiftKey ?? false
|
|
207
|
+
}));
|
|
208
|
+
}]
|
|
209
|
+
];
|
|
210
|
+
for (const [type, listener] of listeners) element.addEventListener(type, listener);
|
|
211
|
+
return listeners;
|
|
212
|
+
}), (listeners) => Effect.sync(() => {
|
|
213
|
+
for (const [type, listener] of listeners) element.removeEventListener(type, listener);
|
|
214
|
+
})))
|
|
215
|
+
});
|
|
216
|
+
/**
|
|
217
|
+
* Attaches the events Mount to the target slot, mapped into the placement's
|
|
218
|
+
* Messages, and writes `data-pressed` while down for styling. The element
|
|
219
|
+
* decides nothing; a click, a touch, Enter, Space, or an assistive
|
|
220
|
+
* technology's click all reach `update` and come out as one `Pressed`.
|
|
221
|
+
*/
|
|
222
|
+
const behavior = (declared) => (slots) => (options) => BundleBehavior.fromBundle(declared)(slots)({ [options.target]: {
|
|
223
|
+
requires: { capability: Capability.Interactive },
|
|
224
|
+
attributes: ({ input, h }) => [...input[declared.field].pressed ? [h.DataAttribute("pressed", "true")] : [], ...options.disabled?.(input) === true ? [h.AriaDisabled(true)] : []],
|
|
225
|
+
events: events()
|
|
226
|
+
} }, { name: "Press" });
|
|
227
|
+
export { Args, Message, Model, PointerType, Pressed, behavior, bundle, events };
|
|
228
|
+
|
|
229
|
+
//# sourceMappingURL=press.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"press.js","names":[],"sources":["../../src/interaction/press.ts"],"sourcesContent":["/**\n * Press: pointer and keyboard activation of one element as one fact. A Mount\n * reports what the element saw (pointer down, up, cancel, key down, up,\n * click) with the detail Foldkit's declarative attributes do not carry; the\n * Bundle's `update` decides what counts as a press; the ghost click that\n * follows a touch is suppressed by a timed Command on Effect's clock, never\n * by a clock read in a handler. Activation is an OutMessage, `Pressed`, so\n * the placement must say what a press does.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\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 { BundleBehavior } from './bundle-behavior.js'\n\nexport const PointerType = Schema.Literals(['mouse', 'touch', 'pen', 'keyboard', 'virtual'])\nexport type PointerType = typeof PointerType.Type\n\nexport const Model = Schema.Struct({\n /** Down and not yet released or cancelled. For styling. */\n pressed: Schema.Boolean,\n /** The pointer that pressed, so another pointer's up cannot release it. */\n pointerId: Schema.NullOr(Schema.Number),\n /** Enter or Space is down. */\n key: Schema.NullOr(Schema.String),\n /** A click arriving before this generation's window ends is the ghost of a pointer press. */\n suppressing: Schema.NullOr(Schema.Number),\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 /** `detail` is 0 for a click no pointer made: keyboard on a native control, or assistive technology. */\n Clicked: { detail: Schema.Number, shiftKey: Schema.Boolean },\n Unsuppressed: { generation: Schema.Number },\n})\nexport type Message = typeof Message.Type\n\nexport const Pressed = Schema.TaggedStruct('Pressed', {\n pointerType: PointerType,\n /** Shift was held, for a range selection. */\n shiftKey: Schema.Boolean,\n})\nexport type Pressed = typeof Pressed.Type\n\nexport const Args = Schema.Struct({\n /** How long after a pointer press a `click` is taken for its ghost. */\n clickSuppressionMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n ),\n})\nexport type Args = typeof Args.Type\n\nconst idle = (model: Model): Model => ({ ...model, pressed: false, pointerId: null, key: null })\n\nconst isActivationKey = (key: string): boolean => key === 'Enter' || key === ' '\n\nconst asPointerType = (raw: string): PointerType =>\n raw === 'touch' || raw === 'pen' ? raw : 'mouse'\n\ntype Return = Update.ReturnWithOutMessage<Model, Message, Pressed>\n\nexport const bundle = Bundle.make('Press', {\n Model,\n Message,\n args: Args,\n init: () => ({\n model: { pressed: false, pointerId: null, key: null, suppressing: null, generation: 0 },\n }),\n update: (model, message, args): Return =>\n Message.match<Return>(message, {\n PointerDown: ({ pointerId, button }) =>\n button !== 0 || model.pointerId !== null\n ? { model }\n : { model: { ...model, pressed: true, pointerId } },\n PointerUp: ({ pointerId, pointerType, shiftKey }) => {\n if (model.pointerId !== pointerId) return { model }\n const generation = model.generation + 1\n return {\n model: { ...idle(model), suppressing: generation, generation },\n commands: [\n {\n name: 'Press.unsuppress',\n args: { generation },\n effect: Effect.as(\n Effect.sleep(args.clickSuppressionMs),\n Message.Unsuppressed({ generation }),\n ),\n },\n ],\n outMessage: Pressed.make({ pointerType: asPointerType(pointerType), shiftKey }),\n }\n },\n PointerCancelled: ({ pointerId }) =>\n model.pointerId === pointerId ? { model: idle(model) } : { model },\n KeyDown: ({ key, repeat }) =>\n !isActivationKey(key) || repeat || model.key !== null || model.pointerId !== null\n ? { model }\n : { model: { ...model, pressed: true, key } },\n KeyUp: ({ key, shiftKey }) =>\n model.key === key\n ? { model: idle(model), outMessage: Pressed.make({ pointerType: 'keyboard', shiftKey }) }\n : { model },\n Clicked: ({ detail, shiftKey }) =>\n detail === 0\n ? { model, outMessage: Pressed.make({ pointerType: 'virtual', shiftKey }) }\n : model.suppressing !== null\n ? { model }\n : { model, outMessage: Pressed.make({ pointerType: 'mouse', shiftKey }) },\n Unsuppressed: ({ generation }) =>\n model.suppressing === generation ? { model: { ...model, suppressing: null } } : { model },\n }),\n})\n\ntype PointerLike = Event & {\n readonly pointerId?: number\n readonly button?: number\n readonly pointerType?: string\n readonly shiftKey?: boolean\n}\n\n/** What the element reports; `Unsuppressed` comes from the timer, not the DOM. */\nexport type Fact = Exclude<Message, { readonly _tag: 'Unsuppressed' }>\n\n/**\n * The element's press-relevant events as facts. Enter and Space key downs\n * are default-prevented so a native control does not also click, and Space\n * does not scroll; the Bundle owns activation. An element marked\n * `aria-disabled=\"true\"` (which the Behavior writes) reports nothing, read\n * at event time so a toggle needs no remount. Nothing else is decided here.\n */\nexport const events = Mount.defineStream('PressEvents', {\n messages: [\n Message.PointerDown,\n Message.PointerUp,\n Message.PointerCancelled,\n Message.KeyDown,\n Message.KeyUp,\n Message.Clicked,\n ],\n execute: ({ element }) =>\n Stream.callback<Fact>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const offer = (message: Fact) => {\n if (element.getAttribute('aria-disabled') === 'true') return\n Queue.offerUnsafe(queue, message)\n }\n const listeners: ReadonlyArray<readonly [string, (event: Event) => void]> = [\n [\n 'pointerdown',\n event => {\n const pointer = event as PointerLike\n offer(\n Message.PointerDown({\n pointerId: pointer.pointerId ?? 0,\n button: pointer.button ?? 0,\n pointerType: pointer.pointerType ?? 'mouse',\n }),\n )\n },\n ],\n [\n 'pointerup',\n event => {\n const pointer = event as PointerLike\n offer(\n Message.PointerUp({\n pointerId: pointer.pointerId ?? 0,\n pointerType: pointer.pointerType ?? 'mouse',\n shiftKey: pointer.shiftKey ?? false,\n }),\n )\n },\n ],\n [\n 'pointerleave',\n event =>\n offer(\n Message.PointerCancelled({ pointerId: (event as PointerLike).pointerId ?? 0 }),\n ),\n ],\n [\n 'pointercancel',\n event =>\n offer(\n Message.PointerCancelled({ pointerId: (event as PointerLike).pointerId ?? 0 }),\n ),\n ],\n [\n 'keydown',\n event => {\n const key = event as KeyboardEvent\n if (isActivationKey(key.key)) key.preventDefault()\n offer(Message.KeyDown({ key: key.key, repeat: key.repeat }))\n },\n ],\n [\n 'keyup',\n event => {\n const key = event as KeyboardEvent\n offer(Message.KeyUp({ key: key.key, shiftKey: key.shiftKey }))\n },\n ],\n [\n 'click',\n event => {\n const click = event as MouseEvent\n offer(\n Message.Clicked({ detail: click.detail ?? 0, shiftKey: click.shiftKey ?? false }),\n )\n },\n ],\n ]\n for (const [type, listener] of listeners) element.addEventListener(type, listener)\n return listeners\n }),\n listeners =>\n Effect.sync(() => {\n for (const [type, listener] of listeners) element.removeEventListener(type, listener)\n }),\n ),\n ),\n})\n\nexport interface BehaviorOptions<Input, Slots> {\n readonly target: keyof Slots & string\n /** Marks the target `aria-disabled`, which also silences its events. */\n readonly disabled?: (input: Input) => boolean\n}\n\n/**\n * Attaches the events Mount to the target slot, mapped into the placement's\n * Messages, and writes `data-pressed` while down for styling. The element\n * decides nothing; a click, a touch, Enter, Space, or an assistive\n * technology's click all reach `update` and come out as one `Pressed`.\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<Input, Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n BundleBehavior.fromBundle(declared)(slots)<Input, ParentMessage>(\n {\n [options.target]: {\n requires: { capability: Capability.Interactive },\n attributes: ({ input, h }) => [\n ...(input[declared.field].pressed ? [h.DataAttribute('pressed', 'true')] : []),\n ...(options.disabled?.(input) === true ? [h.AriaDisabled(true)] : []),\n ],\n events: events(),\n },\n },\n { name: 'Press' },\n )\n"],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle, Declared } from "foldkit-bundle";
|
|
3
|
+
import { Behavior, Behaviors, SlotItem } from "foldkit-mixins";
|
|
4
|
+
import { Attribute, HtmlBuilder, KeyboardModifiers } from "foldkit/html";
|
|
5
|
+
export declare const Orientation: Schema.Literals<readonly ["vertical", "horizontal", "both"]>;
|
|
6
|
+
export type Orientation = typeof Orientation.Type;
|
|
7
|
+
export declare const Model: Schema.Struct<{
|
|
8
|
+
/** The current item's id, or `null` before any item has been focused. */
|
|
9
|
+
readonly current: Schema.NullOr<Schema.String>;
|
|
10
|
+
}>;
|
|
11
|
+
export type Model = typeof Model.Type;
|
|
12
|
+
export declare const Message: import("foldkit/message").MessageUnion<{
|
|
13
|
+
/** An item became current: the user focused it, or a key moved there. */
|
|
14
|
+
readonly Focused: {
|
|
15
|
+
id: Schema.String;
|
|
16
|
+
};
|
|
17
|
+
}>;
|
|
18
|
+
export type Message = typeof Message.Type;
|
|
19
|
+
export declare const Args: Schema.Struct<{
|
|
20
|
+
readonly orientation: Schema.Literals<readonly ["vertical", "horizontal", "both"]>;
|
|
21
|
+
/** Wrap from the last enabled item to the first, and back. */
|
|
22
|
+
readonly loop: Schema.Boolean;
|
|
23
|
+
/** Keep DOM focus on the container and point at the current item with
|
|
24
|
+
* `aria-activedescendant` instead of moving focus. */
|
|
25
|
+
readonly virtual: Schema.Boolean;
|
|
26
|
+
}>;
|
|
27
|
+
export type Args = typeof Args.Type;
|
|
28
|
+
export declare const bundle: Bundle.Bundle<"RovingTabindex", {
|
|
29
|
+
readonly orientation: "both" | "horizontal" | "vertical";
|
|
30
|
+
readonly loop: boolean;
|
|
31
|
+
readonly virtual: boolean;
|
|
32
|
+
}, {
|
|
33
|
+
readonly current: string | null;
|
|
34
|
+
}, {
|
|
35
|
+
readonly _tag: "Focused";
|
|
36
|
+
readonly id: string;
|
|
37
|
+
}, never, never, never, void, {}, {}>;
|
|
38
|
+
export type Direction = 'ltr' | 'rtl';
|
|
39
|
+
export interface MoveOptions {
|
|
40
|
+
readonly orientation: Orientation;
|
|
41
|
+
readonly loop: boolean;
|
|
42
|
+
readonly direction: Direction;
|
|
43
|
+
/** How many enabled items PageUp and PageDown move; absent, they are not handled. */
|
|
44
|
+
readonly page?: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The index the key moves to from `current`, over the enabled indices only, or
|
|
48
|
+
* `undefined` when the key is not a navigation key here. `current` may be
|
|
49
|
+
* `-1` (nothing current yet): arrows then land on the first or last enabled
|
|
50
|
+
* item. Under `rtl`, left and right swap. PageUp and PageDown move by `page`
|
|
51
|
+
* and clamp at the ends, never wrapping.
|
|
52
|
+
*/
|
|
53
|
+
export declare const move: (enabled: ReadonlyArray<number>, current: number, key: string, modifiers: KeyboardModifiers, options: MoveOptions) => number | undefined;
|
|
54
|
+
/** The item that holds the tab stop: the current one, else the first enabled. */
|
|
55
|
+
export declare const tabStop: (items: Behaviors.Collection.Items<unknown>, current: string | null) => number;
|
|
56
|
+
export interface BehaviorOptions<Input, Slots> {
|
|
57
|
+
/** The slot that receives the arrow keys. */
|
|
58
|
+
readonly container: keyof Slots & string;
|
|
59
|
+
/** The slot rendered once per item; must carry the item's id (see `Behaviors.Collection`). */
|
|
60
|
+
readonly item: keyof Slots & string;
|
|
61
|
+
readonly items: (input: Input) => Behaviors.Collection.Items<unknown>;
|
|
62
|
+
/** Default `'ltr'`; return `'rtl'` to swap left and right. */
|
|
63
|
+
readonly direction?: (input: Input) => Direction;
|
|
64
|
+
}
|
|
65
|
+
/** A selector for an element by id, safe for any id. */
|
|
66
|
+
export declare const idSelector: (id: string) => string;
|
|
67
|
+
/**
|
|
68
|
+
* The attributes one item gets: `tabindex` 0 when it holds the tab stop and
|
|
69
|
+
* -1 otherwise (none under `virtual`), and `OnFocus` reporting it current.
|
|
70
|
+
* Shared with `ListNavigation`.
|
|
71
|
+
*/
|
|
72
|
+
export declare const itemAttributes: <ParentMessage>(h: HtmlBuilder<ParentMessage>, items: Behaviors.Collection.Items<unknown>, current: string | null, item: SlotItem, virtual: boolean, focused: (id: string) => ParentMessage) => ReadonlyArray<Attribute<ParentMessage>>;
|
|
73
|
+
/**
|
|
74
|
+
* Wires a placed `RovingTabindex` to the slots: the container's arrow, Home
|
|
75
|
+
* and End keys focus the next item (synchronously, then dispatch), each item
|
|
76
|
+
* gets `tabindex` 0 or -1 and reports `Focused` when focused. Under `virtual`
|
|
77
|
+
* DOM focus never leaves the container: the key only dispatches, the
|
|
78
|
+
* container carries `aria-activedescendant`, and items keep no `tabindex`.
|
|
79
|
+
*
|
|
80
|
+
* Every handled key is default-prevented. Nothing is written on dispose: the
|
|
81
|
+
* attributes are data, so a view that no longer attaches this leaves no
|
|
82
|
+
* `tabindex` behind.
|
|
83
|
+
*/
|
|
84
|
+
export declare const behavior: <Field extends string>(declared: Declared<typeof bundle, Field>, args: Args) => <Slots>(slots: Slots) => <Input extends { readonly [K in Field]: Model; }, ParentMessage>(options: BehaviorOptions<Input, Slots>) => Behavior.NamedBehavior<Slots, Input, ParentMessage>;
|
|
85
|
+
//# sourceMappingURL=roving-tabindex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roving-tabindex.d.ts","names":[],"sources":["../../src/interaction/roving-tabindex.ts"],"mappings":";;;;qBAea,aAAW,OAAA;YACZ,qBAAqB,YAAY;qBAEhC,OAAK,OAAA;;;;YAIN,eAAe,MAAM;qBAEpB,mCAAO;;;IAEP,IAAE,OAAA;;;YAEH,iBAAiB,QAAQ;qBAExB,MAAI,OAAA;;;;;;;;YAQL,cAAc,KAAK;qBAElB,QAAM,OAAA;;;;;;;;;;YAUP;iBAEK;WACN,aAAa;WACb;WACA,WAAW;;WAEX;;;;;;;;;qBAaE,OAAI,SACN,uBAAqB,iBACf,aACJ,WACA,mBAAiB,SACnB;;qBAiCE,UAAO,OACX,UAAU,WAAW,gBAAc;iBAQ3B,gBAAgB,OAAO;;WAE7B,iBAAiB;;WAEjB,YAAY;WACZ,QAAQ,OAAO,UAAU,UAAU,WAAW;;WAE9C,aAAa,OAAO,UAAU;;;qBAI5B,aAAU;;;;;;qBAOV,iBAAkB,eAAa,GACvC,YAAY,gBAAc,OACtB,UAAU,WAAW,gBAAc,wBACpB,MAChB,UAAQ,kBACE,UACN,eAAe,kBACxB,cAAc,UAAU;;;;;;;;;;;;qBAkBd,WACV,sBAAoB,UAAY,gBAAgB,QAAQ,QAAM,MAAQ,UACtE,OAAK,OAAS,WACd,0BAA0B,KAAK,QAAQ,UAAS,eAAa,SACnD,gBAAgB,OAAO,WAC/B,SAAS,cAAc,OAAO,OAAO"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Option, Schema } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import { Bundle } from "foldkit-bundle";
|
|
4
|
+
import { perInput } from "../internal.js";
|
|
5
|
+
import { Behavior, Capability } from "foldkit-mixins";
|
|
6
|
+
// src/interaction/roving-tabindex.ts
|
|
7
|
+
/**
|
|
8
|
+
* One tab stop for a set of items (a toolbar, a tab list, a listbox): arrows
|
|
9
|
+
* move focus between them, the rest stay out of the tab order. The current
|
|
10
|
+
* item's id is the Model slice, so a reorder keeps the same item current and a
|
|
11
|
+
* resumed page knows where focus was. Which item comes next is a pure
|
|
12
|
+
* function of the items and the key; the Behavior wires it to the slots.
|
|
13
|
+
*/
|
|
14
|
+
const Orientation = Schema.Literals([
|
|
15
|
+
"vertical",
|
|
16
|
+
"horizontal",
|
|
17
|
+
"both"
|
|
18
|
+
]);
|
|
19
|
+
const Model = Schema.Struct({
|
|
20
|
+
/** The current item's id, or `null` before any item has been focused. */
|
|
21
|
+
current: Schema.NullOr(Schema.String) });
|
|
22
|
+
const Message = defineMessageUnion({
|
|
23
|
+
/** An item became current: the user focused it, or a key moved there. */
|
|
24
|
+
Focused: { id: Schema.String } });
|
|
25
|
+
const Args = Schema.Struct({
|
|
26
|
+
orientation: Orientation,
|
|
27
|
+
/** Wrap from the last enabled item to the first, and back. */
|
|
28
|
+
loop: Schema.Boolean,
|
|
29
|
+
/** Keep DOM focus on the container and point at the current item with
|
|
30
|
+
* `aria-activedescendant` instead of moving focus. */
|
|
31
|
+
virtual: Schema.Boolean
|
|
32
|
+
});
|
|
33
|
+
const bundle = Bundle.make("RovingTabindex", {
|
|
34
|
+
Model,
|
|
35
|
+
Message,
|
|
36
|
+
args: Args,
|
|
37
|
+
init: () => ({ model: { current: null } }),
|
|
38
|
+
update: (model, message) => model.current === message.id ? { model } : { model: { current: message.id } }
|
|
39
|
+
});
|
|
40
|
+
const isPlain = (modifiers) => !modifiers.ctrlKey && !modifiers.altKey && !modifiers.metaKey;
|
|
41
|
+
/**
|
|
42
|
+
* The index the key moves to from `current`, over the enabled indices only, or
|
|
43
|
+
* `undefined` when the key is not a navigation key here. `current` may be
|
|
44
|
+
* `-1` (nothing current yet): arrows then land on the first or last enabled
|
|
45
|
+
* item. Under `rtl`, left and right swap. PageUp and PageDown move by `page`
|
|
46
|
+
* and clamp at the ends, never wrapping.
|
|
47
|
+
*/
|
|
48
|
+
const move = (enabled, current, key, modifiers, options) => {
|
|
49
|
+
if (enabled.length === 0 || !isPlain(modifiers)) return void 0;
|
|
50
|
+
const first = enabled[0];
|
|
51
|
+
const last = enabled[enabled.length - 1];
|
|
52
|
+
if (key === "Home") return first;
|
|
53
|
+
if (key === "End") return last;
|
|
54
|
+
if (options.page !== void 0 && (key === "PageUp" || key === "PageDown")) {
|
|
55
|
+
const position = enabled.indexOf(current);
|
|
56
|
+
if (position === -1) return key === "PageDown" ? first : last;
|
|
57
|
+
const jumped = position + (key === "PageDown" ? options.page : -options.page);
|
|
58
|
+
return enabled[Math.min(enabled.length - 1, Math.max(0, jumped))];
|
|
59
|
+
}
|
|
60
|
+
const vertical = options.orientation !== "horizontal";
|
|
61
|
+
const horizontal = options.orientation !== "vertical";
|
|
62
|
+
const forwardKey = options.direction === "rtl" ? "ArrowLeft" : "ArrowRight";
|
|
63
|
+
const backwardKey = options.direction === "rtl" ? "ArrowRight" : "ArrowLeft";
|
|
64
|
+
const step = vertical && key === "ArrowDown" || horizontal && key === forwardKey ? 1 : vertical && key === "ArrowUp" || horizontal && key === backwardKey ? -1 : 0;
|
|
65
|
+
if (step === 0) return void 0;
|
|
66
|
+
const position = enabled.indexOf(current);
|
|
67
|
+
if (position === -1) return step === 1 ? first : last;
|
|
68
|
+
const next = position + step;
|
|
69
|
+
if (next < 0) return options.loop ? last : first;
|
|
70
|
+
if (next >= enabled.length) return options.loop ? first : last;
|
|
71
|
+
return enabled[next];
|
|
72
|
+
};
|
|
73
|
+
/** The item that holds the tab stop: the current one, else the first enabled. */
|
|
74
|
+
const tabStop = (items, current) => {
|
|
75
|
+
const index = current === null ? -1 : items.indexOf(current);
|
|
76
|
+
if (index !== -1 && !items.isDisabled(index)) return index;
|
|
77
|
+
return items.enabled[0] ?? -1;
|
|
78
|
+
};
|
|
79
|
+
/** A selector for an element by id, safe for any id. */
|
|
80
|
+
const idSelector = (id) => `[id="${id.replace(/["\\]/g, "\\$&")}"]`;
|
|
81
|
+
/**
|
|
82
|
+
* The attributes one item gets: `tabindex` 0 when it holds the tab stop and
|
|
83
|
+
* -1 otherwise (none under `virtual`), and `OnFocus` reporting it current.
|
|
84
|
+
* Shared with `ListNavigation`.
|
|
85
|
+
*/
|
|
86
|
+
const itemAttributes = (h, items, current, item, virtual, focused) => {
|
|
87
|
+
const id = item.id ?? items.ids[item.index];
|
|
88
|
+
if (id === void 0) return [];
|
|
89
|
+
const isStop = tabStop(items, current) === item.index;
|
|
90
|
+
return [...virtual ? [] : [h.Tabindex(isStop ? 0 : -1)], h.OnFocus(focused(id))];
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Wires a placed `RovingTabindex` to the slots: the container's arrow, Home
|
|
94
|
+
* and End keys focus the next item (synchronously, then dispatch), each item
|
|
95
|
+
* gets `tabindex` 0 or -1 and reports `Focused` when focused. Under `virtual`
|
|
96
|
+
* DOM focus never leaves the container: the key only dispatches, the
|
|
97
|
+
* container carries `aria-activedescendant`, and items keep no `tabindex`.
|
|
98
|
+
*
|
|
99
|
+
* Every handled key is default-prevented. Nothing is written on dispose: the
|
|
100
|
+
* attributes are data, so a view that no longer attaches this leaves no
|
|
101
|
+
* `tabindex` behind.
|
|
102
|
+
*/
|
|
103
|
+
const behavior = (declared, args) => (slots) => (options) => {
|
|
104
|
+
const wrap = (id) => declared.wrapper.make(Message.Focused({ id }));
|
|
105
|
+
const slice = (input) => input[declared.field];
|
|
106
|
+
const itemsOf = perInput(options.items);
|
|
107
|
+
return Behavior.forSlots(slots)({
|
|
108
|
+
[options.container]: Behavior.slot({
|
|
109
|
+
requires: { capability: Capability.Interactive },
|
|
110
|
+
attributes: ({ input, h }) => {
|
|
111
|
+
const items = itemsOf(input);
|
|
112
|
+
const current = slice(input).current;
|
|
113
|
+
const from = current === null ? -1 : items.indexOf(current);
|
|
114
|
+
const direction = options.direction?.(input) ?? "ltr";
|
|
115
|
+
const stop = tabStop(items, current);
|
|
116
|
+
const stopId = stop === -1 ? void 0 : items.ids[stop];
|
|
117
|
+
const target = (key, modifiers) => {
|
|
118
|
+
const next = move(items.enabled, from, key, modifiers, {
|
|
119
|
+
...args,
|
|
120
|
+
direction
|
|
121
|
+
});
|
|
122
|
+
return next === void 0 ? void 0 : items.ids[next];
|
|
123
|
+
};
|
|
124
|
+
if (args.virtual) return [h.OnKeyDownPreventDefault((key, modifiers) => Option.map(Option.fromNullishOr(target(key, modifiers)), wrap)), ...stopId === void 0 ? [] : [h.AriaActiveDescendant(stopId)]];
|
|
125
|
+
return [h.OnKeyDownFocus((key, modifiers) => Option.map(Option.fromNullishOr(target(key, modifiers)), (id) => ({
|
|
126
|
+
focusSelector: idSelector(id),
|
|
127
|
+
message: wrap(id)
|
|
128
|
+
})))];
|
|
129
|
+
}
|
|
130
|
+
}),
|
|
131
|
+
[options.item]: Behavior.slot({
|
|
132
|
+
requires: { capability: Capability.Focusable },
|
|
133
|
+
attributes: ({ input, h, item }) => {
|
|
134
|
+
if (item === void 0) return [];
|
|
135
|
+
return itemAttributes(h, itemsOf(input), slice(input).current, item, args.virtual, wrap);
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
}, { name: "RovingTabindex" });
|
|
139
|
+
};
|
|
140
|
+
export { Args, Message, Model, Orientation, behavior, bundle, idSelector, itemAttributes, move, tabStop };
|
|
141
|
+
|
|
142
|
+
//# sourceMappingURL=roving-tabindex.js.map
|