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 @@
|
|
|
1
|
+
{"version":3,"file":"dismiss-layer.js","names":[],"sources":["../../src/interaction/dismiss-layer.ts"],"sourcesContent":["/**\n * Dismissable layers: Escape closes the topmost open layer, a pointer press\n * outside closes the layers it is outside of. One Bundle, placed once, owns\n * the document listeners; each layer's Behavior marks its container (and its\n * trigger) with data attributes. The stack is the DOM order of the marked\n * elements at the moment of the event, so a layer opening or closing needs no\n * registration. A press inside a parent layer is outside its children, so the\n * parent stays and the children go; a press on a layer's trigger counts as\n * inside it, so a trigger never dismisses and reopens in one click. What a\n * dismissal does is the placement's `onOut`.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport type { HtmlBuilder } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Capability } from 'foldkit-mixins'\n\nexport const LAYER_ATTRIBUTE = 'data-foldkit-plus-layer'\nexport const TRIGGER_ATTRIBUTE = 'data-foldkit-plus-layer-trigger'\nconst OUTSIDE_ATTRIBUTE = 'data-foldkit-plus-layer-outside'\nconst ESCAPE_ATTRIBUTE = 'data-foldkit-plus-layer-escape'\n\n/** An open layer as the document showed it: bottom to top is DOM order. */\nexport const Layer = Schema.Struct({\n id: Schema.String,\n /** Dismiss on a press outside it. */\n outside: Schema.Boolean,\n /** Dismiss on Escape when topmost. */\n escape: Schema.Boolean,\n})\nexport type Layer = typeof Layer.Type\n\nexport const Model = Schema.Struct({\n /** The open layers at the last event, bottom to top. */\n layers: Schema.Array(Layer),\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n /** A pointer went down; `inside` are the layers whose element or trigger was on the event's path. */\n PressedAt: { layers: Schema.Array(Layer), inside: Schema.Array(Schema.String) },\n PressedEscape: { layers: Schema.Array(Layer) },\n})\nexport type Message = typeof Message.Type\n\nexport const Dismiss = Schema.TaggedStruct('Dismiss', { ids: Schema.Array(Schema.String) })\nexport type Dismiss = typeof Dismiss.Type\n\n/** Which layers a press inside `inside` dismisses: every layer above the topmost one hit, or all when none was. */\nexport const toDismiss = (\n layers: ReadonlyArray<Layer>,\n inside: ReadonlyArray<string>,\n): ReadonlyArray<string> => {\n let topmostHit = -1\n layers.forEach((layer, index) => {\n if (inside.includes(layer.id)) topmostHit = index\n })\n return layers\n .slice(topmostHit + 1)\n .filter(layer => layer.outside)\n .map(layer => layer.id)\n}\n\ntype Return = Update.ReturnWithOutMessage<Model, Message, Dismiss>\n\nconst sameLayers = (a: ReadonlyArray<Layer>, b: ReadonlyArray<Layer>): boolean =>\n a.length === b.length &&\n a.every(\n (layer, index) =>\n layer.id === b[index]!.id &&\n layer.outside === b[index]!.outside &&\n layer.escape === b[index]!.escape,\n )\n\nexport const bundle = Bundle.make('DismissLayer', {\n Model,\n Message,\n init: () => ({ model: { layers: [] } }),\n update: (model, message): Return => {\n // Every press in the document reports the layers, which are usually the\n // ones already held: keep the Model, or each click re-renders the page.\n const next = sameLayers(model.layers, message.layers) ? model : { layers: message.layers }\n return Message.match<Return>(message, {\n PressedAt: ({ layers, inside }) => {\n const ids = toDismiss(layers, inside)\n return ids.length === 0\n ? { model: next }\n : { model: next, outMessage: Dismiss.make({ ids }) }\n },\n PressedEscape: ({ layers }) => {\n const top = layers[layers.length - 1]\n return top !== undefined && top.escape\n ? { model: next, outMessage: Dismiss.make({ ids: [top.id] }) }\n : { model: next }\n },\n })\n },\n subscriptions: () =>\n Subscription.make<Model, Message>()(() => ({\n document: Subscription.persistentEntry(documentEvents()),\n })),\n})\n\nconst openLayers = (root: ParentNode): ReadonlyArray<Layer> =>\n Array.from(root.querySelectorAll(`[${LAYER_ATTRIBUTE}]`)).map(element => ({\n id: element.getAttribute(LAYER_ATTRIBUTE) ?? '',\n outside: element.getAttribute(OUTSIDE_ATTRIBUTE) !== 'false',\n escape: element.getAttribute(ESCAPE_ATTRIBUTE) !== 'false',\n }))\n\n/** The layer ids whose element or trigger is on the event's composed path. */\nexport const layersOnPath = (path: ReadonlyArray<EventTarget>): ReadonlyArray<string> => {\n const ids: Array<string> = []\n for (const target of path) {\n if (!(target instanceof Element)) continue\n const id = target.getAttribute(LAYER_ATTRIBUTE) ?? target.getAttribute(TRIGGER_ATTRIBUTE)\n if (id !== null && !ids.includes(id)) ids.push(id)\n }\n return ids\n}\n\n/**\n * The facts are read inside the listener, not after: `composedPath()` is\n * empty once dispatch has finished, and the open layers must be the ones the\n * press saw, not the ones a later render left.\n */\nconst documentEvents = (): Stream.Stream<Message> => {\n if (typeof document === 'undefined') return Stream.empty\n return Stream.callback<Message>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const onPointerDown = (event: Event) =>\n Queue.offerUnsafe(\n queue,\n Message.PressedAt({\n layers: openLayers(document),\n inside: layersOnPath(event.composedPath()),\n }),\n )\n const onKeyDown = (event: Event) => {\n if ((event as KeyboardEvent).key !== 'Escape') return\n Queue.offerUnsafe(queue, Message.PressedEscape({ layers: openLayers(document) }))\n }\n document.addEventListener('pointerdown', onPointerDown, { capture: true })\n document.addEventListener('keydown', onKeyDown, { capture: true })\n return { onPointerDown, onKeyDown }\n }),\n ({ onPointerDown, onKeyDown }) =>\n Effect.sync(() => {\n document.removeEventListener('pointerdown', onPointerDown, { capture: true })\n document.removeEventListener('keydown', onKeyDown, { capture: true })\n }),\n ),\n )\n}\n\nexport interface BehaviorOptions<Input, Slots> {\n /** The layer's root, rendered only while the layer is open. */\n readonly layer: keyof Slots & string\n /** The element that opens the layer, so a press on it is inside. */\n readonly trigger?: keyof Slots & string\n readonly id: (input: Input) => string\n /** Dismiss on a press outside. Default `true`. */\n readonly outsidePress?: boolean\n /** Dismiss on Escape when topmost. Default `true`. */\n readonly escape?: boolean\n}\n\n/**\n * Marks the layer's container and trigger for the placed stack. The layer\n * takes part only while its element is rendered, so an `open` flag in the\n * parent Model is the whole lifecycle. Not for an element with `popover`,\n * which the browser dismisses itself.\n */\nexport const behavior =\n <Field extends string>(_declared: Declared<typeof bundle, Field>) =>\n <Slots>(slots: Slots) =>\n <Input, ParentMessage>(\n options: BehaviorOptions<Input, Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.layer]: Behavior.slot({\n requires: { capability: Capability.Container },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => [\n h.Attribute(LAYER_ATTRIBUTE, options.id(input)),\n ...(options.outsidePress === false ? [h.Attribute(OUTSIDE_ATTRIBUTE, 'false')] : []),\n ...(options.escape === false ? [h.Attribute(ESCAPE_ATTRIBUTE, 'false')] : []),\n ],\n }),\n ...(options.trigger === undefined\n ? {}\n : {\n [options.trigger]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => [h.Attribute(TRIGGER_ATTRIBUTE, options.id(input))],\n }),\n }),\n // Keyed by values the caller chose; `forSlots` checks the keys exist.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'DismissLayer' },\n )\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EditAsked, EditableText as mount, TextCancelled, TextCommitted, TextEdited, TextFact, TextFact as TextFact$1, textOf } from "../dom/editable-text.js";
|
|
2
|
+
import { Behavior } from "foldkit-mixins";
|
|
3
|
+
export interface BehaviorOptions<Slots, ParentMessage> {
|
|
4
|
+
/** The slot holding the fields. */
|
|
5
|
+
readonly container: keyof Slots & string;
|
|
6
|
+
/** The attribute that marks a field, holding its name. */
|
|
7
|
+
readonly attribute: string;
|
|
8
|
+
/** Turns each fact into the view's Message. */
|
|
9
|
+
readonly toMessage: (fact: TextFact$1) => ParentMessage;
|
|
10
|
+
}
|
|
11
|
+
export declare const behavior: <Slots>(slots: Slots) => <Input, ParentMessage>(options: BehaviorOptions<Slots, ParentMessage>) => Behavior.NamedBehavior<Slots, Input, ParentMessage>;
|
|
12
|
+
export { EditAsked, TextCancelled, TextCommitted, TextEdited, type TextFact, mount, textOf };
|
|
13
|
+
//# sourceMappingURL=editable-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editable-text.d.ts","names":["TextFact"],"sources":["../../src/interaction/editable-text.ts"],"mappings":";;iBAoBiB,gBAAgB,OAAO;;WAE7B,iBAAiB;;WAEjB;;WAEA,YAAY,MAAMA,eAAa;;qBAG7B,WACV,OAAK,OAAS,WACd,OAAO,eAAa,SACV,gBAAgB,OAAO,mBAC/B,SAAS,cAAc,OAAO,OAAO"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as Mount from "foldkit/mount";
|
|
2
|
+
import { EditAsked, EditableText, EditableText as mount, TextCancelled, TextCommitted, TextEdited, textOf } from "../dom/editable-text.js";
|
|
3
|
+
import { Behavior, Capability } from "foldkit-mixins";
|
|
4
|
+
// src/interaction/editable-text.ts
|
|
5
|
+
/**
|
|
6
|
+
* `EditableText` as a Behavior: attaches the `foldkit-primitives/dom` Mount to
|
|
7
|
+
* a container slot and maps what it reports into the view's Messages. No
|
|
8
|
+
* Bundle: the text typed is a fact, and what it changes is the parent's
|
|
9
|
+
* `update`.
|
|
10
|
+
*/
|
|
11
|
+
const behavior = (slots) => (options) => Behavior.forSlots(slots)({ [options.container]: Behavior.slot({
|
|
12
|
+
requires: { capability: Capability.Container },
|
|
13
|
+
mount: () => Mount.mapMessage(EditableText({ attribute: options.attribute }), options.toMessage)
|
|
14
|
+
}) }, { name: "EditableText" });
|
|
15
|
+
export { EditAsked, TextCancelled, TextCommitted, TextEdited, behavior, mount, textOf };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=editable-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editable-text.js","names":["EditableTextMount"],"sources":["../../src/interaction/editable-text.ts"],"sourcesContent":["/**\n * `EditableText` as a Behavior: attaches the `foldkit-primitives/dom` Mount to\n * a container slot and maps what it reports into the view's Messages. No\n * Bundle: the text typed is a fact, and what it changes is the parent's\n * `update`.\n */\nimport * as Mount from 'foldkit/mount'\nimport { Behavior, Capability } from 'foldkit-mixins'\nimport { EditableText as EditableTextMount, type TextFact } from '../dom/editable-text.js'\n\nexport {\n EditAsked,\n EditableText as mount,\n TextCancelled,\n TextCommitted,\n TextEdited,\n textOf,\n type TextFact,\n} from '../dom/editable-text.js'\n\nexport interface BehaviorOptions<Slots, ParentMessage> {\n /** The slot holding the fields. */\n readonly container: keyof Slots & string\n /** The attribute that marks a field, holding its name. */\n readonly attribute: string\n /** Turns each fact into the view's Message. */\n readonly toMessage: (fact: TextFact) => ParentMessage\n}\n\nexport const behavior =\n <Slots>(slots: Slots) =>\n <Input, ParentMessage>(\n options: BehaviorOptions<Slots, ParentMessage>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.container]: Behavior.slot({\n requires: { capability: Capability.Container },\n mount: () =>\n Mount.mapMessage(\n EditableTextMount({ attribute: options.attribute }),\n options.toMessage,\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: 'EditableText' },\n )\n"],"mappings":";;;;;;;;;;AA6BA,MAAa,YACH,WAEN,YAEA,SAAS,SAAS,KAAK,CAAC,CACtB,GACG,QAAQ,YAAY,SAAS,KAAK;CACjC,UAAU,EAAE,YAAY,WAAW,UAAU;CAC7C,aACE,MAAM,WACJA,aAAkB,EAAE,WAAW,QAAQ,UAAU,CAAC,GAClD,QAAQ,SACV;AACJ,CAAC,EAEH,GACA,EAAE,MAAM,eAAe,CACzB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FocusScope as mount, tabbableWithin } from "../dom/focus-scope.js";
|
|
2
|
+
import { Behavior } from "foldkit-mixins";
|
|
3
|
+
export interface BehaviorOptions<Slots> {
|
|
4
|
+
readonly container: keyof Slots & string;
|
|
5
|
+
/** Keep Tab, Shift+Tab, and a stray focus inside. Default `true`. */
|
|
6
|
+
readonly contain?: boolean;
|
|
7
|
+
/** Give focus back to what had it when the container unmounts. Default `true`. */
|
|
8
|
+
readonly restore?: boolean;
|
|
9
|
+
/** A selector inside the container to focus first. */
|
|
10
|
+
readonly initialFocus?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* On insert the container focuses `initialFocus`, else its first tabbable
|
|
14
|
+
* descendant, else itself; with `contain`, Tab and Shift+Tab wrap inside and
|
|
15
|
+
* focus that lands outside comes back to the first tabbable; on unmount, with
|
|
16
|
+
* `restore`, focus returns to the element that had it. A dialog that is not
|
|
17
|
+
* a native `<dialog>`, a menu, or a command palette takes this.
|
|
18
|
+
*/
|
|
19
|
+
export declare const behavior: <Slots>(slots: Slots) => <Input, ParentMessage>(options: BehaviorOptions<Slots>) => Behavior.NamedBehavior<Slots, Input, ParentMessage>;
|
|
20
|
+
export { mount, tabbableWithin };
|
|
21
|
+
//# sourceMappingURL=focus-scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-scope.d.ts","names":[],"sources":["../../src/interaction/focus-scope.ts"],"mappings":";;iBASiB,gBAAgB;WACtB,iBAAiB;;WAEjB;;WAEA;;WAEA;;;;;;;;;qBAUE,WACV,OAAK,OAAS,WACd,OAAO,eAAa,SACV,gBAAgB,WACxB,SAAS,cAAc,OAAO,OAAO"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FocusScope, FocusScope as mount, tabbableWithin } from "../dom/focus-scope.js";
|
|
2
|
+
import { Behavior, Capability } from "foldkit-mixins";
|
|
3
|
+
// src/interaction/focus-scope.ts
|
|
4
|
+
/**
|
|
5
|
+
* `FocusScope` as a Behavior: attaches the `foldkit-primitives/dom` Mount to a
|
|
6
|
+
* container slot. No Bundle, because which element has focus is a DOM fact.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* On insert the container focuses `initialFocus`, else its first tabbable
|
|
10
|
+
* descendant, else itself; with `contain`, Tab and Shift+Tab wrap inside and
|
|
11
|
+
* focus that lands outside comes back to the first tabbable; on unmount, with
|
|
12
|
+
* `restore`, focus returns to the element that had it. A dialog that is not
|
|
13
|
+
* a native `<dialog>`, a menu, or a command palette takes this.
|
|
14
|
+
*/
|
|
15
|
+
const behavior = (slots) => (options) => Behavior.forSlots(slots)({ [options.container]: Behavior.slot({
|
|
16
|
+
requires: { capability: Capability.Container },
|
|
17
|
+
mount: () => FocusScope({
|
|
18
|
+
contain: options.contain ?? true,
|
|
19
|
+
restore: options.restore ?? true,
|
|
20
|
+
initialFocus: options.initialFocus ?? null
|
|
21
|
+
})
|
|
22
|
+
}) }, { name: "FocusScope" });
|
|
23
|
+
export { behavior, mount, tabbableWithin };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=focus-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-scope.js","names":["FocusScopeMount"],"sources":["../../src/interaction/focus-scope.ts"],"sourcesContent":["/**\n * `FocusScope` as a Behavior: attaches the `foldkit-primitives/dom` Mount to a\n * container slot. No Bundle, because which element has focus is a DOM fact.\n */\nimport { Behavior, Capability } from 'foldkit-mixins'\nimport { FocusScope as FocusScopeMount } from '../dom/focus-scope.js'\n\nexport { FocusScope as mount, tabbableWithin } from '../dom/focus-scope.js'\n\nexport interface BehaviorOptions<Slots> {\n readonly container: keyof Slots & string\n /** Keep Tab, Shift+Tab, and a stray focus inside. Default `true`. */\n readonly contain?: boolean\n /** Give focus back to what had it when the container unmounts. Default `true`. */\n readonly restore?: boolean\n /** A selector inside the container to focus first. */\n readonly initialFocus?: string\n}\n\n/**\n * On insert the container focuses `initialFocus`, else its first tabbable\n * descendant, else itself; with `contain`, Tab and Shift+Tab wrap inside and\n * focus that lands outside comes back to the first tabbable; on unmount, with\n * `restore`, focus returns to the element that had it. A dialog that is not\n * a native `<dialog>`, a menu, or a command palette takes this.\n */\nexport const behavior =\n <Slots>(slots: Slots) =>\n <Input, ParentMessage>(\n options: BehaviorOptions<Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.container]: Behavior.slot({\n requires: { capability: Capability.Container },\n mount: () =>\n FocusScopeMount({\n contain: options.contain ?? true,\n restore: options.restore ?? true,\n initialFocus: options.initialFocus ?? null,\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: 'FocusScope' },\n )\n"],"mappings":";;;;;;;;;;;;;;AA0BA,MAAa,YACH,WAEN,YAEA,SAAS,SAAS,KAAK,CAAC,CACtB,GACG,QAAQ,YAAY,SAAS,KAAK;CACjC,UAAU,EAAE,YAAY,WAAW,UAAU;CAC7C,aACEA,WAAgB;EACd,SAAS,QAAQ,WAAW;EAC5B,SAAS,QAAQ,WAAW;EAC5B,cAAc,QAAQ,gBAAgB;CACxC,CAAC;AACL,CAAC,EAEH,GACA,EAAE,MAAM,aAAa,CACvB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Declared } from "foldkit-bundle";
|
|
2
|
+
import { InputModality, InputModality as bundle, InputModalityModel, InputModalityModel as Model, Modality, modalityOfKey } from "../events/input-modality.js";
|
|
3
|
+
import { Behavior } from "foldkit-mixins";
|
|
4
|
+
export interface BehaviorOptions<Slots> {
|
|
5
|
+
readonly target: keyof Slots & string;
|
|
6
|
+
}
|
|
7
|
+
export declare const behavior: <Field extends string>(declared: Declared<typeof InputModality, Field>) => <Slots>(slots: Slots) => <Input extends { readonly [K in Field]: InputModalityModel; }, ParentMessage>(options: BehaviorOptions<Slots>) => Behavior.NamedBehavior<Slots, Input, ParentMessage>;
|
|
8
|
+
export { Modality, type Model, bundle, modalityOfKey };
|
|
9
|
+
//# sourceMappingURL=focus-visible.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-visible.d.ts","names":[],"sources":["../../src/interaction/focus-visible.ts"],"mappings":";;;iBAeiB,gBAAgB;WACtB,cAAc;;qBAGZ,WACV,sBAAoB,UAAY,gBAAgB,eAAe,YAC/D,OAAK,OAAS,WACd,0BAA0B,KAAK,QAAQ,uBAAsB,eAAa,SAChE,gBAAgB,WACxB,SAAS,cAAc,OAAO,OAAO"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { InputModality as bundle, Modality, modalityOfKey } from "../events/input-modality.js";
|
|
2
|
+
import { Behavior, Capability } from "foldkit-mixins";
|
|
3
|
+
// src/interaction/focus-visible.ts
|
|
4
|
+
const behavior = (declared) => (slots) => (options) => Behavior.forSlots(slots)({ [options.target]: Behavior.slot({
|
|
5
|
+
requires: { capability: Capability.Focusable },
|
|
6
|
+
attributes: ({ input, h }) => input[declared.field].modality === "keyboard" ? [h.DataAttribute("focus-visible", "true")] : []
|
|
7
|
+
}) }, { name: "FocusVisible" });
|
|
8
|
+
export { Modality, behavior, bundle, modalityOfKey };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=focus-visible.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-visible.js","names":[],"sources":["../../src/interaction/focus-visible.ts"],"sourcesContent":["/**\n * `FocusVisible` as a Behavior over a placed `InputModality`: the target gets\n * `data-focus-visible` while the page is being driven by keyboard, so a\n * stylesheet can show a ring with `[data-focus-visible]:focus` and hide it\n * for pointer users. CSS `:focus-visible` does this without any of it; this\n * is for a design system that must decide in the Model.\n */\nimport type { HtmlBuilder } from 'foldkit/html'\nimport type { Declared } from 'foldkit-bundle'\nimport { Behavior, Capability } from 'foldkit-mixins'\nimport { InputModality, type InputModalityModel } from '../events/input-modality.js'\n\nexport { InputModality as bundle, Modality, modalityOfKey } from '../events/input-modality.js'\nexport type { InputModalityModel as Model } from '../events/input-modality.js'\n\nexport interface BehaviorOptions<Slots> {\n readonly target: keyof Slots & string\n}\n\nexport const behavior =\n <Field extends string>(declared: Declared<typeof InputModality, Field>) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: InputModalityModel }, 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.Focusable },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) =>\n input[declared.field].modality === 'keyboard'\n ? [h.DataAttribute('focus-visible', 'true')]\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: 'FocusVisible' },\n )\n"],"mappings":";;;AAmBA,MAAa,YACY,cACf,WAEN,YAEA,SAAS,SAAS,KAAK,CAAC,CACtB,GACG,QAAQ,SAAS,SAAS,KAAK;CAC9B,UAAU,EAAE,YAAY,WAAW,UAAU;CAC7C,aAAa,EACX,OACA,QAKA,MAAM,SAAS,MAAM,CAAC,aAAa,aAC/B,CAAC,EAAE,cAAc,iBAAiB,MAAM,CAAC,IACzC,CAAC;AACT,CAAC,EAEH,GACA,EAAE,MAAM,eAAe,CACzB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle, Declared } from "foldkit-bundle";
|
|
3
|
+
import { Behavior, Behaviors } from "foldkit-mixins";
|
|
4
|
+
import * as RovingTabindex from "./roving-tabindex.js";
|
|
5
|
+
import { KeyboardModifiers } from "foldkit/html";
|
|
6
|
+
export declare const Model: Schema.Struct<{
|
|
7
|
+
/** The current cell's id, or `null` before any cell has been focused. */
|
|
8
|
+
readonly current: Schema.NullOr<Schema.String>;
|
|
9
|
+
}>;
|
|
10
|
+
export type Model = typeof Model.Type;
|
|
11
|
+
export declare const Message: import("foldkit/message").MessageUnion<{
|
|
12
|
+
/** A cell became current: the user focused it, or a key moved there. */
|
|
13
|
+
readonly Focused: {
|
|
14
|
+
id: Schema.String;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
|
17
|
+
export type Message = typeof Message.Type;
|
|
18
|
+
export declare const Args: Schema.Struct<{
|
|
19
|
+
/** Cells per row; the last row may be short. */
|
|
20
|
+
readonly columns: Schema.Number;
|
|
21
|
+
/** Left and right continue into the previous and next row; up and down
|
|
22
|
+
* continue into the previous and next column. Off, they stop at the edge. */
|
|
23
|
+
readonly wrap: Schema.Boolean;
|
|
24
|
+
/** Keep DOM focus on the container and point at the current cell with
|
|
25
|
+
* `aria-activedescendant` instead of moving focus. */
|
|
26
|
+
readonly virtual: Schema.Boolean;
|
|
27
|
+
}>;
|
|
28
|
+
export type Args = typeof Args.Type;
|
|
29
|
+
export declare const bundle: Bundle.Bundle<"GridNavigation", {
|
|
30
|
+
readonly columns: number;
|
|
31
|
+
readonly wrap: boolean;
|
|
32
|
+
readonly virtual: boolean;
|
|
33
|
+
}, {
|
|
34
|
+
readonly current: string | null;
|
|
35
|
+
}, {
|
|
36
|
+
readonly _tag: "Focused";
|
|
37
|
+
readonly id: string;
|
|
38
|
+
}, never, never, never, void, {}, {}>;
|
|
39
|
+
export interface MoveOptions {
|
|
40
|
+
readonly columns: number;
|
|
41
|
+
readonly wrap: boolean;
|
|
42
|
+
readonly direction: RovingTabindex.Direction;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The index the key moves to from `current` over a grid of `count` cells in
|
|
46
|
+
* rows of `columns`, skipping cells not in `enabled`, or `undefined` when the
|
|
47
|
+
* key is not a grid key. Left and right step within the row, and across rows
|
|
48
|
+
* under `wrap`; up and down step within the column, and across columns under
|
|
49
|
+
* `wrap`. Home and End are the row's first and last enabled cell; with Ctrl
|
|
50
|
+
* they are the grid's. At an edge without `wrap` the key is consumed and
|
|
51
|
+
* `current` is returned. From `-1` (nothing current) a forward key lands on
|
|
52
|
+
* the first enabled cell and a backward key on the last. Under `rtl`, left
|
|
53
|
+
* and right swap.
|
|
54
|
+
*/
|
|
55
|
+
export declare const move: (enabled: ReadonlyArray<number>, count: number, current: number, key: string, modifiers: KeyboardModifiers, options: MoveOptions) => number | undefined;
|
|
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 cell; must carry the cell'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) => RovingTabindex.Direction;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Wires a placed `GridNavigation` to the slots exactly as `RovingTabindex`
|
|
67
|
+
* does, with `move` above deciding the cell: the container's arrows, Home and
|
|
68
|
+
* End focus the next cell (synchronously, then dispatch) or, under `virtual`,
|
|
69
|
+
* only dispatch and point with `aria-activedescendant`; each cell gets
|
|
70
|
+
* `tabindex` 0 or -1 and reports `Focused` when focused.
|
|
71
|
+
*/
|
|
72
|
+
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>;
|
|
73
|
+
//# sourceMappingURL=grid-navigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grid-navigation.d.ts","names":[],"sources":["../../src/interaction/grid-navigation.ts"],"mappings":";;;;;qBAiBa,OAAK,OAAA;;;;YAIN,eAAe,MAAM;qBAEpB,mCAAO;;;IAEP,IAAE,OAAA;;;YAEH,iBAAiB,QAAQ;qBAExB,MAAI,OAAA;;;;;;;;;;YAUL,cAAc,KAAK;qBAElB,QAAM,OAAA;;;;;;;;;;iBAUF;WACN;WACA;WACA,WAAW,eAAe;;;;;;;;;;;;;qBAcxB,OAAI,SACN,uBAAqB,eACjB,iBACE,aACJ,WACA,mBAAiB,SACnB;iBAyDM,gBAAgB,OAAO;;WAE7B,iBAAiB;;WAEjB,YAAY;WACZ,QAAQ,OAAO,UAAU,UAAU,WAAW;;WAE9C,aAAa,OAAO,UAAU,eAAe;;;;;;;;;qBAU3C,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,137 @@
|
|
|
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
|
+
import * as RovingTabindex from "./roving-tabindex.js";
|
|
7
|
+
// src/interaction/grid-navigation.ts
|
|
8
|
+
/**
|
|
9
|
+
* Two-dimensional arrow keys over a set of items laid out in rows of
|
|
10
|
+
* `columns` (a calendar grid, a color swatch picker, an emoji palette). The
|
|
11
|
+
* Model slice is the current item's id, as with `RovingTabindex`, so a
|
|
12
|
+
* reorder keeps the same item current. Which cell a key lands on is the pure
|
|
13
|
+
* `move`; the Behavior wires it to a container slot and a per-item slot and
|
|
14
|
+
* shares `RovingTabindex`'s item attributes, so the two are interchangeable
|
|
15
|
+
* on a view.
|
|
16
|
+
*/
|
|
17
|
+
const Model = Schema.Struct({
|
|
18
|
+
/** The current cell's id, or `null` before any cell has been focused. */
|
|
19
|
+
current: Schema.NullOr(Schema.String) });
|
|
20
|
+
const Message = defineMessageUnion({
|
|
21
|
+
/** A cell became current: the user focused it, or a key moved there. */
|
|
22
|
+
Focused: { id: Schema.String } });
|
|
23
|
+
const Args = Schema.Struct({
|
|
24
|
+
/** Cells per row; the last row may be short. */
|
|
25
|
+
columns: Schema.Number.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(1))),
|
|
26
|
+
/** Left and right continue into the previous and next row; up and down
|
|
27
|
+
* continue into the previous and next column. Off, they stop at the edge. */
|
|
28
|
+
wrap: Schema.Boolean,
|
|
29
|
+
/** Keep DOM focus on the container and point at the current cell with
|
|
30
|
+
* `aria-activedescendant` instead of moving focus. */
|
|
31
|
+
virtual: Schema.Boolean
|
|
32
|
+
});
|
|
33
|
+
const bundle = Bundle.make("GridNavigation", {
|
|
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
|
+
/**
|
|
41
|
+
* The index the key moves to from `current` over a grid of `count` cells in
|
|
42
|
+
* rows of `columns`, skipping cells not in `enabled`, or `undefined` when the
|
|
43
|
+
* key is not a grid key. Left and right step within the row, and across rows
|
|
44
|
+
* under `wrap`; up and down step within the column, and across columns under
|
|
45
|
+
* `wrap`. Home and End are the row's first and last enabled cell; with Ctrl
|
|
46
|
+
* they are the grid's. At an edge without `wrap` the key is consumed and
|
|
47
|
+
* `current` is returned. From `-1` (nothing current) a forward key lands on
|
|
48
|
+
* the first enabled cell and a backward key on the last. Under `rtl`, left
|
|
49
|
+
* and right swap.
|
|
50
|
+
*/
|
|
51
|
+
const move = (enabled, count, current, key, modifiers, options) => {
|
|
52
|
+
if (enabled.length === 0 || modifiers.altKey || modifiers.metaKey) return void 0;
|
|
53
|
+
const first = enabled[0];
|
|
54
|
+
const last = enabled[enabled.length - 1];
|
|
55
|
+
const isEnabled = (index) => enabled.includes(index);
|
|
56
|
+
const { columns } = options;
|
|
57
|
+
const row = (index) => Math.floor(index / columns);
|
|
58
|
+
if (key === "Home" || key === "End") {
|
|
59
|
+
if (modifiers.ctrlKey || current === -1) return key === "Home" ? first : last;
|
|
60
|
+
const inRow = enabled.filter((index) => row(index) === row(current));
|
|
61
|
+
return key === "Home" ? inRow[0] ?? current : inRow[inRow.length - 1] ?? current;
|
|
62
|
+
}
|
|
63
|
+
if (modifiers.ctrlKey) return void 0;
|
|
64
|
+
const forwardKey = options.direction === "rtl" ? "ArrowLeft" : "ArrowRight";
|
|
65
|
+
const backwardKey = options.direction === "rtl" ? "ArrowRight" : "ArrowLeft";
|
|
66
|
+
const axis = key === forwardKey || key === backwardKey ? "row" : key === "ArrowDown" || key === "ArrowUp" ? "column" : void 0;
|
|
67
|
+
if (axis === void 0) return void 0;
|
|
68
|
+
const step = key === forwardKey || key === "ArrowDown" ? 1 : -1;
|
|
69
|
+
if (current === -1) return step === 1 ? first : last;
|
|
70
|
+
if (axis === "row") {
|
|
71
|
+
for (let index = current + step; index >= 0 && index < count; index += step) {
|
|
72
|
+
if (!options.wrap && row(index) !== row(current)) return current;
|
|
73
|
+
if (isEnabled(index)) return index;
|
|
74
|
+
}
|
|
75
|
+
return current;
|
|
76
|
+
}
|
|
77
|
+
const column = current % columns;
|
|
78
|
+
for (let index = current + step * columns;; index += step * columns) {
|
|
79
|
+
if (index < 0 || index >= count) {
|
|
80
|
+
if (!options.wrap) return current;
|
|
81
|
+
const nextColumn = column + step;
|
|
82
|
+
if (nextColumn < 0 || nextColumn >= columns) return current;
|
|
83
|
+
const start = step === 1 ? nextColumn : nextColumn + columns * Math.floor((count - 1 - nextColumn) / columns);
|
|
84
|
+
for (let next = start; next >= 0 && next < count; next += step * columns) if (isEnabled(next)) return next;
|
|
85
|
+
return current;
|
|
86
|
+
}
|
|
87
|
+
if (isEnabled(index)) return index;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Wires a placed `GridNavigation` to the slots exactly as `RovingTabindex`
|
|
92
|
+
* does, with `move` above deciding the cell: the container's arrows, Home and
|
|
93
|
+
* End focus the next cell (synchronously, then dispatch) or, under `virtual`,
|
|
94
|
+
* only dispatch and point with `aria-activedescendant`; each cell gets
|
|
95
|
+
* `tabindex` 0 or -1 and reports `Focused` when focused.
|
|
96
|
+
*/
|
|
97
|
+
const behavior = (declared, args) => (slots) => (options) => {
|
|
98
|
+
const wrap = (id) => declared.wrapper.make(Message.Focused({ id }));
|
|
99
|
+
const slice = (input) => input[declared.field];
|
|
100
|
+
const itemsOf = perInput(options.items);
|
|
101
|
+
return Behavior.forSlots(slots)({
|
|
102
|
+
[options.container]: Behavior.slot({
|
|
103
|
+
requires: { capability: Capability.Interactive },
|
|
104
|
+
attributes: ({ input, h }) => {
|
|
105
|
+
const items = itemsOf(input);
|
|
106
|
+
const current = slice(input).current;
|
|
107
|
+
const from = current === null ? -1 : items.indexOf(current);
|
|
108
|
+
const direction = options.direction?.(input) ?? "ltr";
|
|
109
|
+
const stop = RovingTabindex.tabStop(items, current);
|
|
110
|
+
const stopId = stop === -1 ? void 0 : items.ids[stop];
|
|
111
|
+
const target = (key, modifiers) => {
|
|
112
|
+
const next = move(items.enabled, items.size, from, key, modifiers, {
|
|
113
|
+
columns: args.columns,
|
|
114
|
+
wrap: args.wrap,
|
|
115
|
+
direction
|
|
116
|
+
});
|
|
117
|
+
return next === void 0 ? void 0 : items.ids[next];
|
|
118
|
+
};
|
|
119
|
+
if (args.virtual) return [h.OnKeyDownPreventDefault((key, modifiers) => Option.map(Option.fromNullishOr(target(key, modifiers)), wrap)), ...stopId === void 0 ? [] : [h.AriaActiveDescendant(stopId)]];
|
|
120
|
+
return [h.OnKeyDownFocus((key, modifiers) => Option.map(Option.fromNullishOr(target(key, modifiers)), (id) => ({
|
|
121
|
+
focusSelector: RovingTabindex.idSelector(id),
|
|
122
|
+
message: wrap(id)
|
|
123
|
+
})))];
|
|
124
|
+
}
|
|
125
|
+
}),
|
|
126
|
+
[options.item]: Behavior.slot({
|
|
127
|
+
requires: { capability: Capability.Focusable },
|
|
128
|
+
attributes: ({ input, h, item }) => {
|
|
129
|
+
if (item === void 0) return [];
|
|
130
|
+
return RovingTabindex.itemAttributes(h, itemsOf(input), slice(input).current, item, args.virtual, wrap);
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
}, { name: "GridNavigation" });
|
|
134
|
+
};
|
|
135
|
+
export { Args, Message, Model, behavior, bundle, move };
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=grid-navigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grid-navigation.js","names":[],"sources":["../../src/interaction/grid-navigation.ts"],"sourcesContent":["/**\n * Two-dimensional arrow keys over a set of items laid out in rows of\n * `columns` (a calendar grid, a color swatch picker, an emoji palette). The\n * Model slice is the current item's id, as with `RovingTabindex`, so a\n * reorder keeps the same item current. Which cell a key lands on is the pure\n * `move`; the Behavior wires it to a container slot and a per-item slot and\n * shares `RovingTabindex`'s item attributes, so the two are interchangeable\n * on a view.\n */\nimport { Option, Schema } from 'effect'\nimport type { HtmlBuilder, KeyboardModifiers } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Behaviors, Capability, type SlotItem } from 'foldkit-mixins'\nimport * as RovingTabindex from './roving-tabindex.js'\nimport { perInput } from '../internal.js'\n\nexport const Model = Schema.Struct({\n /** The current cell's id, or `null` before any cell has been focused. */\n current: Schema.NullOr(Schema.String),\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n /** A cell became current: the user focused it, or a key moved there. */\n Focused: { id: Schema.String },\n})\nexport type Message = typeof Message.Type\n\nexport const Args = Schema.Struct({\n /** Cells per row; the last row may be short. */\n columns: Schema.Number.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(1))),\n /** Left and right continue into the previous and next row; up and down\n * continue into the previous and next column. Off, they stop at the edge. */\n wrap: Schema.Boolean,\n /** Keep DOM focus on the container and point at the current cell with\n * `aria-activedescendant` instead of moving focus. */\n virtual: Schema.Boolean,\n})\nexport type Args = typeof Args.Type\n\nexport const bundle = Bundle.make('GridNavigation', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { current: null } }),\n // Moving focus by key dispatches `Focused`, and the cell's `OnFocus` reports it again.\n update: (model, message) =>\n model.current === message.id ? { model } : { model: { current: message.id } },\n})\n\nexport interface MoveOptions {\n readonly columns: number\n readonly wrap: boolean\n readonly direction: RovingTabindex.Direction\n}\n\n/**\n * The index the key moves to from `current` over a grid of `count` cells in\n * rows of `columns`, skipping cells not in `enabled`, or `undefined` when the\n * key is not a grid key. Left and right step within the row, and across rows\n * under `wrap`; up and down step within the column, and across columns under\n * `wrap`. Home and End are the row's first and last enabled cell; with Ctrl\n * they are the grid's. At an edge without `wrap` the key is consumed and\n * `current` is returned. From `-1` (nothing current) a forward key lands on\n * the first enabled cell and a backward key on the last. Under `rtl`, left\n * and right swap.\n */\nexport const move = (\n enabled: ReadonlyArray<number>,\n count: number,\n current: number,\n key: string,\n modifiers: KeyboardModifiers,\n options: MoveOptions,\n): number | undefined => {\n if (enabled.length === 0 || modifiers.altKey || modifiers.metaKey) return undefined\n const first = enabled[0]!\n const last = enabled[enabled.length - 1]!\n const isEnabled = (index: number) => enabled.includes(index)\n const { columns } = options\n const row = (index: number) => Math.floor(index / columns)\n\n if (key === 'Home' || key === 'End') {\n if (modifiers.ctrlKey || current === -1) return key === 'Home' ? first : last\n const inRow = enabled.filter(index => row(index) === row(current))\n return key === 'Home' ? (inRow[0] ?? current) : (inRow[inRow.length - 1] ?? current)\n }\n if (modifiers.ctrlKey) return undefined\n\n const forwardKey = options.direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight'\n const backwardKey = options.direction === 'rtl' ? 'ArrowRight' : 'ArrowLeft'\n const axis: 'row' | 'column' | undefined =\n key === forwardKey || key === backwardKey\n ? 'row'\n : key === 'ArrowDown' || key === 'ArrowUp'\n ? 'column'\n : undefined\n if (axis === undefined) return undefined\n const step = key === forwardKey || key === 'ArrowDown' ? 1 : -1\n if (current === -1) return step === 1 ? first : last\n\n if (axis === 'row') {\n // Reading order; without wrap the walk stops at the row's edge.\n for (let index = current + step; index >= 0 && index < count; index += step) {\n if (!options.wrap && row(index) !== row(current)) return current\n if (isEnabled(index)) return index\n }\n return current\n }\n // Column-major; without wrap the walk stops at the column's edge.\n const column = current % columns\n for (let index = current + step * columns; ; index += step * columns) {\n if (index < 0 || index >= count) {\n if (!options.wrap) return current\n const nextColumn = column + step\n if (nextColumn < 0 || nextColumn >= columns) return current\n // Continue from the top (or bottom) of the neighbouring column.\n const start =\n step === 1\n ? nextColumn\n : nextColumn + columns * Math.floor((count - 1 - nextColumn) / columns)\n for (let next = start; next >= 0 && next < count; next += step * columns) {\n if (isEnabled(next)) return next\n }\n return current\n }\n if (isEnabled(index)) return index\n }\n}\n\nexport interface BehaviorOptions<Input, Slots> {\n /** The slot that receives the arrow keys. */\n readonly container: keyof Slots & string\n /** The slot rendered once per cell; must carry the cell's id (see `Behaviors.Collection`). */\n readonly item: keyof Slots & string\n readonly items: (input: Input) => Behaviors.Collection.Items<unknown>\n /** Default `'ltr'`; return `'rtl'` to swap left and right. */\n readonly direction?: (input: Input) => RovingTabindex.Direction\n}\n\n/**\n * Wires a placed `GridNavigation` to the slots exactly as `RovingTabindex`\n * does, with `move` above deciding the cell: the container's arrows, Home and\n * End focus the next cell (synchronously, then dispatch) or, under `virtual`,\n * only dispatch and point with `aria-activedescendant`; each cell gets\n * `tabindex` 0 or -1 and reports `Focused` when focused.\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>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> => {\n const wrap = (id: string): ParentMessage =>\n declared.wrapper.make(Message.Focused({ id })) as unknown as ParentMessage\n const slice = (input: Input): Model => input[declared.field]\n const itemsOf = perInput(options.items)\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 current = slice(input).current\n const from = current === null ? -1 : items.indexOf(current)\n const direction = options.direction?.(input) ?? 'ltr'\n const stop = RovingTabindex.tabStop(items, current)\n const stopId = stop === -1 ? undefined : items.ids[stop]\n const target = (key: string, modifiers: KeyboardModifiers): string | undefined => {\n const next = move(items.enabled, items.size, from, key, modifiers, {\n columns: args.columns,\n wrap: args.wrap,\n direction,\n })\n return next === undefined ? undefined : items.ids[next]\n }\n if (args.virtual) {\n return [\n h.OnKeyDownPreventDefault((key, modifiers) =>\n Option.map(Option.fromNullishOr(target(key, modifiers)), wrap),\n ),\n ...(stopId === undefined ? [] : [h.AriaActiveDescendant(stopId)]),\n ]\n }\n return [\n h.OnKeyDownFocus((key, modifiers) =>\n Option.map(Option.fromNullishOr(target(key, modifiers)), id => ({\n focusSelector: RovingTabindex.idSelector(id),\n message: wrap(id),\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 if (item === undefined) return []\n return RovingTabindex.itemAttributes(\n h,\n itemsOf(input),\n slice(input).current,\n item,\n args.virtual,\n wrap,\n )\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: 'GridNavigation' },\n )\n }\n"],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as RovingTabindex from "./roving-tabindex.js";
|
|
2
|
+
import * as BundleBehavior from "./bundle-behavior.js";
|
|
3
|
+
import * as DismissLayer from "./dismiss-layer.js";
|
|
4
|
+
import * as EditableText from "./editable-text.js";
|
|
5
|
+
import * as FocusScope from "./focus-scope.js";
|
|
6
|
+
import * as FocusVisible from "./focus-visible.js";
|
|
7
|
+
import * as GridNavigation from "./grid-navigation.js";
|
|
8
|
+
import * as Layers from "./layers.js";
|
|
9
|
+
import * as LiveAnnounce from "./live-announce.js";
|
|
10
|
+
import * as ListNavigation from "./list-navigation.js";
|
|
11
|
+
import * as LongPress from "./long-press.js";
|
|
12
|
+
import * as Move from "./move.js";
|
|
13
|
+
import * as Overlay from "./overlay.js";
|
|
14
|
+
import * as Press from "./press.js";
|
|
15
|
+
import * as Selection from "./selection.js";
|
|
16
|
+
import * as Targets from "./targets.js";
|
|
17
|
+
import * as PointerDrag from "./pointer-drag.js";
|
|
18
|
+
import * as Placing from "./placing.js";
|
|
19
|
+
import * as TreeNavigation from "./tree-navigation.js";
|
|
20
|
+
import * as Typeahead from "./typeahead.js";
|
|
21
|
+
export { BundleBehavior, DismissLayer, EditableText, FocusScope, FocusVisible, GridNavigation, Layers, ListNavigation, LiveAnnounce, LongPress, Move, Overlay, Placing, PointerDrag, Press, RovingTabindex, Selection, Targets, TreeNavigation, Typeahead };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as RovingTabindex from "./roving-tabindex.js";
|
|
2
|
+
import * as BundleBehavior from "./bundle-behavior.js";
|
|
3
|
+
import * as DismissLayer from "./dismiss-layer.js";
|
|
4
|
+
import * as EditableText from "./editable-text.js";
|
|
5
|
+
import * as FocusScope from "./focus-scope.js";
|
|
6
|
+
import * as FocusVisible from "./focus-visible.js";
|
|
7
|
+
import * as GridNavigation from "./grid-navigation.js";
|
|
8
|
+
import * as Layers from "./layers.js";
|
|
9
|
+
import * as LiveAnnounce from "./live-announce.js";
|
|
10
|
+
import * as ListNavigation from "./list-navigation.js";
|
|
11
|
+
import * as LongPress from "./long-press.js";
|
|
12
|
+
import * as Move from "./move.js";
|
|
13
|
+
import * as Overlay from "./overlay.js";
|
|
14
|
+
import * as Press from "./press.js";
|
|
15
|
+
import * as Selection from "./selection.js";
|
|
16
|
+
import * as Targets from "./targets.js";
|
|
17
|
+
import * as PointerDrag from "./pointer-drag.js";
|
|
18
|
+
import * as Placing from "./placing.js";
|
|
19
|
+
import * as TreeNavigation from "./tree-navigation.js";
|
|
20
|
+
import * as Typeahead from "./typeahead.js";
|
|
21
|
+
export { BundleBehavior, DismissLayer, EditableText, FocusScope, FocusVisible, GridNavigation, Layers, ListNavigation, LiveAnnounce, LongPress, Move, Overlay, Placing, PointerDrag, Press, RovingTabindex, Selection, Targets, TreeNavigation, Typeahead };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { HideOutside as hideOutsideMount, ScrollLock as scrollLockMount } from "../dom/layers.js";
|
|
2
|
+
import { Behavior } from "foldkit-mixins";
|
|
3
|
+
/** Locks the document's scroll while the container is mounted; nested locks release together. */
|
|
4
|
+
export declare const scrollLock: <Slots>(slots: Slots) => <Input, ParentMessage>(options: {
|
|
5
|
+
readonly container: keyof Slots & string;
|
|
6
|
+
}) => Behavior.NamedBehavior<Slots, Input, ParentMessage>;
|
|
7
|
+
/** Marks everything outside the container inert while it is mounted. */
|
|
8
|
+
export declare const hideOutside: <Slots>(slots: Slots) => <Input, ParentMessage>(options: {
|
|
9
|
+
readonly container: keyof Slots & string;
|
|
10
|
+
}) => Behavior.NamedBehavior<Slots, Input, ParentMessage>;
|
|
11
|
+
export { hideOutsideMount, scrollLockMount };
|
|
12
|
+
//# sourceMappingURL=layers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layers.d.ts","names":[],"sources":["../../src/interaction/layers.ts"],"mappings":";;;qBA8Ba,aAhBV,OAAK,OAAS,WACd,OAAO,eAAa;WACV,iBAAiB;MACxB,SAAS,cAAc,OAAO,OAAO;;qBAgB9B,cAnBV,OAAK,OAAS,WACd,OAAO,eAAa;WACV,iBAAiB;MACxB,SAAS,cAAc,OAAO,OAAO"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { HideOutside, HideOutside as hideOutsideMount, ScrollLock, ScrollLock as scrollLockMount } from "../dom/layers.js";
|
|
2
|
+
import { Behavior, Capability } from "foldkit-mixins";
|
|
3
|
+
// src/interaction/layers.ts
|
|
4
|
+
const mountOn = (name, mount) => (slots) => (options) => Behavior.forSlots(slots)({ [options.container]: Behavior.slot({
|
|
5
|
+
requires: { capability: Capability.Container },
|
|
6
|
+
mount
|
|
7
|
+
}) }, { name });
|
|
8
|
+
/** Locks the document's scroll while the container is mounted; nested locks release together. */
|
|
9
|
+
const scrollLock = mountOn("ScrollLock", () => ScrollLock());
|
|
10
|
+
/** Marks everything outside the container inert while it is mounted. */
|
|
11
|
+
const hideOutside = mountOn("HideOutside", () => HideOutside());
|
|
12
|
+
export { hideOutside, hideOutsideMount, scrollLock, scrollLockMount };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=layers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layers.js","names":["ScrollLockMount","HideOutsideMount"],"sources":["../../src/interaction/layers.ts"],"sourcesContent":["/**\n * `ScrollLock` and `HideOutside` as Behaviors: each attaches its\n * `foldkit-primitives/dom` Mount to a container slot. No Bundle, because the\n * lock and the inert set are document facts Foldkit already refcounts and\n * keys.\n */\nimport type { MountAction } from 'foldkit/mount'\nimport { Behavior, Capability } from 'foldkit-mixins'\nimport { HideOutside as HideOutsideMount, ScrollLock as ScrollLockMount } from '../dom/layers.js'\n\nexport { HideOutside as hideOutsideMount, ScrollLock as scrollLockMount } from '../dom/layers.js'\n\nconst mountOn =\n (name: string, mount: () => MountAction<never>) =>\n <Slots>(slots: Slots) =>\n <Input, ParentMessage>(options: {\n readonly container: keyof Slots & string\n }): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.container]: Behavior.slot({\n requires: { capability: Capability.Container },\n mount,\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 },\n )\n\n/** Locks the document's scroll while the container is mounted; nested locks release together. */\nexport const scrollLock = mountOn('ScrollLock', () => ScrollLockMount())\n\n/** Marks everything outside the container inert while it is mounted. */\nexport const hideOutside = mountOn('HideOutside', () => HideOutsideMount())\n"],"mappings":";;;AAYA,MAAM,WACH,MAAc,WACP,WACe,YAGrB,SAAS,SAAS,KAAK,CAAC,CACtB,GACG,QAAQ,YAAY,SAAS,KAAK;CACjC,UAAU,EAAE,YAAY,WAAW,UAAU;CAC7C;AACF,CAAC,EAEH,GACA,EAAE,KAAK,CACT;;AAGJ,MAAa,aAAa,QAAQ,oBAAoBA,WAAgB,CAAC;;AAGvE,MAAa,cAAc,QAAQ,qBAAqBC,YAAiB,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle, Declared } from "foldkit-bundle";
|
|
3
|
+
import { Behavior, Behaviors } from "foldkit-mixins";
|
|
4
|
+
import * as RovingTabindex from "./roving-tabindex.js";
|
|
5
|
+
import { KeyboardModifiers } from "foldkit/html";
|
|
6
|
+
export declare const Model: Schema.Struct<{
|
|
7
|
+
readonly current: Schema.NullOr<Schema.String>;
|
|
8
|
+
readonly query: 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
|
+
/** An item became current: focused, or reached by a key. */
|
|
14
|
+
readonly Focused: {
|
|
15
|
+
id: Schema.String;
|
|
16
|
+
};
|
|
17
|
+
/** A printable key extended the query; `match` is the item it now picks, if any. */
|
|
18
|
+
readonly Typed: {
|
|
19
|
+
char: Schema.String;
|
|
20
|
+
match: Schema.NullOr<Schema.String>;
|
|
21
|
+
};
|
|
22
|
+
readonly Expired: {
|
|
23
|
+
generation: Schema.Number;
|
|
24
|
+
};
|
|
25
|
+
readonly Cleared: {};
|
|
26
|
+
}>;
|
|
27
|
+
export type Message = typeof Message.Type;
|
|
28
|
+
export declare const Args: Schema.Struct<{
|
|
29
|
+
readonly orientation: Schema.Literals<readonly ["vertical", "horizontal", "both"]>;
|
|
30
|
+
readonly loop: Schema.Boolean;
|
|
31
|
+
readonly virtual: Schema.Boolean;
|
|
32
|
+
/** How long the typeahead query survives after the last key. */
|
|
33
|
+
readonly timeoutMs: Schema.Number;
|
|
34
|
+
/** How many enabled items PageUp and PageDown move. */
|
|
35
|
+
readonly page: Schema.Number;
|
|
36
|
+
}>;
|
|
37
|
+
export type Args = typeof Args.Type;
|
|
38
|
+
export declare const bundle: Bundle.Bundle<"ListNavigation", {
|
|
39
|
+
readonly orientation: "both" | "horizontal" | "vertical";
|
|
40
|
+
readonly loop: boolean;
|
|
41
|
+
readonly virtual: boolean;
|
|
42
|
+
readonly timeoutMs: number;
|
|
43
|
+
readonly page: number;
|
|
44
|
+
}, {
|
|
45
|
+
readonly current: string | null;
|
|
46
|
+
readonly query: string;
|
|
47
|
+
readonly generation: number;
|
|
48
|
+
}, {
|
|
49
|
+
readonly _tag: "Cleared";
|
|
50
|
+
} | {
|
|
51
|
+
readonly _tag: "Expired";
|
|
52
|
+
readonly generation: number;
|
|
53
|
+
} | {
|
|
54
|
+
readonly _tag: "Focused";
|
|
55
|
+
readonly id: string;
|
|
56
|
+
} | {
|
|
57
|
+
readonly _tag: "Typed";
|
|
58
|
+
readonly char: string;
|
|
59
|
+
readonly match: string | null;
|
|
60
|
+
}, never, never, never, void, {}, {}>;
|
|
61
|
+
export interface BehaviorOptions<Input, Slots, ParentMessage> {
|
|
62
|
+
readonly container: keyof Slots & string;
|
|
63
|
+
/** The slot rendered once per item; must carry the item's id (see `Behaviors.Collection`). */
|
|
64
|
+
readonly item: keyof Slots & string;
|
|
65
|
+
readonly items: (input: Input) => Behaviors.Collection.Items<unknown>;
|
|
66
|
+
/** The text an item is matched by when typing. */
|
|
67
|
+
readonly text: (input: Input, index: number) => string;
|
|
68
|
+
readonly direction?: (input: Input) => RovingTabindex.Direction;
|
|
69
|
+
/**
|
|
70
|
+
* Whether a printable key moves the pointer. Off when the container is a
|
|
71
|
+
* text field: letters belong to the field, and arrows still navigate.
|
|
72
|
+
* Defaults to on.
|
|
73
|
+
*/
|
|
74
|
+
readonly typeahead?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Enter on the current enabled item. One handler with the arrows: a second
|
|
77
|
+
* key handler on the same slot would be a second owner of `keydown`.
|
|
78
|
+
*/
|
|
79
|
+
readonly commit?: (input: Input, id: string) => ParentMessage;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The key's outcome, before it is turned into an attribute: navigation to an
|
|
83
|
+
* id, a typed character with its match, or nothing.
|
|
84
|
+
*/
|
|
85
|
+
export type KeyOutcome = {
|
|
86
|
+
readonly _tag: 'Navigate';
|
|
87
|
+
readonly id: string;
|
|
88
|
+
} | {
|
|
89
|
+
readonly _tag: 'Type';
|
|
90
|
+
readonly char: string;
|
|
91
|
+
readonly match: string | null;
|
|
92
|
+
};
|
|
93
|
+
export declare const keyOutcome: (items: Behaviors.Collection.Items<unknown>, texts: ReadonlyArray<string>, state: {
|
|
94
|
+
readonly current: string | null;
|
|
95
|
+
readonly query: string;
|
|
96
|
+
}, key: string, modifiers: KeyboardModifiers, options: RovingTabindex.MoveOptions) => KeyOutcome | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Wires a placed `ListNavigation` to the slots. The container gets one key
|
|
99
|
+
* handler: arrows, Home, End, PageUp and PageDown move by id; a printable key
|
|
100
|
+
* extends the query and moves to its match. Each item gets `tabindex` and
|
|
101
|
+
* `OnFocus` as under `RovingTabindex`. Under `virtual` nothing takes DOM focus
|
|
102
|
+
* and the container carries `aria-activedescendant`.
|
|
103
|
+
*/
|
|
104
|
+
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, ParentMessage>) => Behavior.NamedBehavior<Slots, Input, ParentMessage>;
|
|
105
|
+
//# sourceMappingURL=list-navigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-navigation.d.ts","names":[],"sources":["../../src/interaction/list-navigation.ts"],"mappings":";;;;;qBAkBa,OAAK,OAAA;;;;;YAKN,eAAe,MAAM;qBAEpB,mCAAO;;;IAEP,IAAE,OAAA;;;;IAEJ,MAAI,OAAA;IAAiB,OAAK,OAAA,OAAA,OAAA;;;IACxB,YAAU,OAAA;;;;YAGX,iBAAiB,QAAQ;qBAExB,MAAI,OAAA;;;;;;;;;YAYL,cAAc,KAAK;qBAElB,QAAM,OAAA;;;;;;;;;;;;;;;;;;;;;;;iBA6BF,gBAAgB,OAAO,OAAO;WACpC,iBAAiB;;WAEjB,YAAY;WACZ,QAAQ,OAAO,UAAU,UAAU,WAAW;;WAE9C,OAAO,OAAO,OAAO;WACrB,aAAa,OAAO,UAAU,eAAe;;;;;;WAM7C;;;;;WAKA,UAAU,OAAO,OAAO,eAAe;;;;;;YAOtC;WACG;WAA2B;;WAC3B;WAAuB;WAAuB;;qBAEhD,aAAU,OACd,UAAU,WAAW,gBAAc,OACnC,uBAAqB;WACV;WAAiC;GAAe,aACvD,WACA,mBAAiB,SACnB,eAAe,gBACvB;;;;;;;;qBAkBU,WACV,sBAAoB,UAAY,gBAAgB,QAAQ,QAAM,MAAQ,UACtE,OAAK,OAAS,WACd,0BAA0B,KAAK,QAAQ,UAAS,eAAa,SACnD,gBAAgB,OAAO,OAAO,mBACtC,SAAS,cAAc,OAAO,OAAO"}
|