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,131 @@
|
|
|
1
|
+
import { Effect, Queue, Schema, Stream } from "effect";
|
|
2
|
+
import * as Mount from "foldkit/mount";
|
|
3
|
+
// src/dom/track-box.ts
|
|
4
|
+
/** The attribute on the mounted element naming the element `TrackBox` follows, by id. */
|
|
5
|
+
const TRACK_TARGET_ATTRIBUTE = "data-target";
|
|
6
|
+
/** The custom properties `TrackBox` writes for `name`, as `measured` names `Measure`'s. */
|
|
7
|
+
const trackedBox = (name) => ({
|
|
8
|
+
x: `--fk-${name}-x`,
|
|
9
|
+
y: `--fk-${name}-y`,
|
|
10
|
+
w: `--fk-${name}-w`,
|
|
11
|
+
h: `--fk-${name}-h`,
|
|
12
|
+
radius: `--fk-${name}-radius`
|
|
13
|
+
});
|
|
14
|
+
/** Sent as the followed element changes: its id, and whether it is on the page. */
|
|
15
|
+
const TrackedTarget = Schema.TaggedStruct("TrackedTarget", {
|
|
16
|
+
target: Schema.String,
|
|
17
|
+
found: Schema.Boolean
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* The radius a box of `width` by `height` draws for a computed
|
|
21
|
+
* `border-top-left-radius` of `value` (`8px`, `50%`, `8px 4px`): its
|
|
22
|
+
* horizontal length in pixels, a share of the width for a percentage, and
|
|
23
|
+
* never more than half the shorter side, where the browser stops it.
|
|
24
|
+
*/
|
|
25
|
+
const cornerRadius = (value, width, height) => {
|
|
26
|
+
const first = value.trim().split(/\s+/)[0] ?? "";
|
|
27
|
+
const length = parseFloat(first);
|
|
28
|
+
if (!Number.isFinite(length)) return 0;
|
|
29
|
+
const pixels = first.endsWith("%") ? length * width / 100 : length;
|
|
30
|
+
return Math.max(0, Math.min(pixels, width / 2, height / 2));
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Follows another element's box, as a Mount: the element whose id is the
|
|
34
|
+
* mounted element's `data-target` (`TRACK_TARGET_ATTRIBUTE`). Its box in
|
|
35
|
+
* viewport pixels is written on the mounted element as `--fk-<name>-x`,
|
|
36
|
+
* `-y`, `-w` and `-h`, and with `radius`, its top left corner's radius as
|
|
37
|
+
* `--fk-<name>-radius` (`cornerRadius`), so a fixed overlay drawn from them
|
|
38
|
+
* (a tour's cutout, a coach mark's ring) sits over it. While nothing is
|
|
39
|
+
* followed they are removed, so a stylist's fallbacks apply.
|
|
40
|
+
*
|
|
41
|
+
* The box is read again on a scroll anywhere (heard on the way down, since
|
|
42
|
+
* `scroll` does not bubble), a window resize, the followed element's own
|
|
43
|
+
* resize, and any change to the document's elements, which is also how a
|
|
44
|
+
* target drawn anew under the same id is found. `data-target` is read again
|
|
45
|
+
* whenever it changes, so one element can glide from target to target
|
|
46
|
+
* without being drawn again, which a Mount's args, read once, could not do.
|
|
47
|
+
* What moves the target by any other way (a stylesheet added) is seen at the
|
|
48
|
+
* next of these.
|
|
49
|
+
*
|
|
50
|
+
* Whether a target is on the page is the parent's to act on (a tour shows
|
|
51
|
+
* the step in the middle instead), so each change of the target or of
|
|
52
|
+
* whether it is found sends `TrackedTarget`, starting with the first one.
|
|
53
|
+
* Since it sends a Message, a component may draw it by default: a Scene test
|
|
54
|
+
* resolves it with what the parent maps it to. An empty `data-target` follows
|
|
55
|
+
* nothing and sends nothing.
|
|
56
|
+
*/
|
|
57
|
+
const TrackBox = Mount.defineStream("TrackBox", {
|
|
58
|
+
messages: [TrackedTarget],
|
|
59
|
+
args: {
|
|
60
|
+
/** The custom properties' prefix: `tour` writes `--fk-tour-x`. */
|
|
61
|
+
name: Schema.String,
|
|
62
|
+
/** Whether to write the target's corner radius too; it reads the target's computed style. */
|
|
63
|
+
radius: Schema.optional(Schema.Boolean)
|
|
64
|
+
},
|
|
65
|
+
execute: ({ element, name, radius = false }) => Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
|
|
66
|
+
const document = element.ownerDocument;
|
|
67
|
+
const view = document.defaultView;
|
|
68
|
+
const properties = trackedBox(name);
|
|
69
|
+
const style = element instanceof HTMLElement ? element.style : void 0;
|
|
70
|
+
let target = null;
|
|
71
|
+
let told;
|
|
72
|
+
const place = () => {
|
|
73
|
+
if (style === void 0) return;
|
|
74
|
+
if (target === null) {
|
|
75
|
+
for (const property of Object.values(properties)) style.removeProperty(property);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const box = target.getBoundingClientRect();
|
|
79
|
+
style.setProperty(properties.x, `${box.left}px`);
|
|
80
|
+
style.setProperty(properties.y, `${box.top}px`);
|
|
81
|
+
style.setProperty(properties.w, `${box.width}px`);
|
|
82
|
+
style.setProperty(properties.h, `${box.height}px`);
|
|
83
|
+
if (radius) {
|
|
84
|
+
const corner = getComputedStyle(target).borderTopLeftRadius;
|
|
85
|
+
style.setProperty(properties.radius, `${cornerRadius(corner, box.width, box.height)}px`);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const follow = () => {
|
|
89
|
+
const id = element.getAttribute("data-target") ?? "";
|
|
90
|
+
const found = id === "" ? null : document.getElementById(id);
|
|
91
|
+
if (found !== target) {
|
|
92
|
+
if (target !== null) resized.unobserve(target);
|
|
93
|
+
if (found !== null) resized.observe(found);
|
|
94
|
+
target = found;
|
|
95
|
+
}
|
|
96
|
+
place();
|
|
97
|
+
if (id !== "" && (told?.target !== id || told.found !== (found !== null))) {
|
|
98
|
+
told = TrackedTarget.make({
|
|
99
|
+
target: id,
|
|
100
|
+
found: found !== null
|
|
101
|
+
});
|
|
102
|
+
Queue.offerUnsafe(queue, told);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const resized = new ResizeObserver(place);
|
|
106
|
+
const changed = new MutationObserver(follow);
|
|
107
|
+
changed.observe(element, {
|
|
108
|
+
attributes: true,
|
|
109
|
+
attributeFilter: [TRACK_TARGET_ATTRIBUTE]
|
|
110
|
+
});
|
|
111
|
+
changed.observe(document, {
|
|
112
|
+
childList: true,
|
|
113
|
+
subtree: true
|
|
114
|
+
});
|
|
115
|
+
view?.addEventListener("scroll", place, {
|
|
116
|
+
capture: true,
|
|
117
|
+
passive: true
|
|
118
|
+
});
|
|
119
|
+
view?.addEventListener("resize", place);
|
|
120
|
+
follow();
|
|
121
|
+
return () => {
|
|
122
|
+
changed.disconnect();
|
|
123
|
+
resized.disconnect();
|
|
124
|
+
view?.removeEventListener("scroll", place, { capture: true });
|
|
125
|
+
view?.removeEventListener("resize", place);
|
|
126
|
+
};
|
|
127
|
+
}), (stop) => Effect.sync(stop)))
|
|
128
|
+
});
|
|
129
|
+
export { TRACK_TARGET_ATTRIBUTE, TrackBox, TrackedTarget, cornerRadius, trackedBox };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=track-box.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track-box.js","names":[],"sources":["../../src/dom/track-box.ts"],"sourcesContent":["import { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\n/** The attribute on the mounted element naming the element `TrackBox` follows, by id. */\nexport const TRACK_TARGET_ATTRIBUTE = 'data-target'\n\n/** The custom properties `TrackBox` writes for `name`, as `measured` names `Measure`'s. */\nexport const trackedBox = (name: string) =>\n ({\n x: `--fk-${name}-x`,\n y: `--fk-${name}-y`,\n w: `--fk-${name}-w`,\n h: `--fk-${name}-h`,\n radius: `--fk-${name}-radius`,\n }) as const\n\n/** Sent as the followed element changes: its id, and whether it is on the page. */\nexport const TrackedTarget = Schema.TaggedStruct('TrackedTarget', {\n target: Schema.String,\n found: Schema.Boolean,\n})\nexport type TrackedTarget = typeof TrackedTarget.Type\n\n/**\n * The radius a box of `width` by `height` draws for a computed\n * `border-top-left-radius` of `value` (`8px`, `50%`, `8px 4px`): its\n * horizontal length in pixels, a share of the width for a percentage, and\n * never more than half the shorter side, where the browser stops it.\n */\nexport const cornerRadius = (value: string, width: number, height: number): number => {\n const first = value.trim().split(/\\s+/)[0] ?? ''\n const length = parseFloat(first)\n if (!Number.isFinite(length)) return 0\n const pixels = first.endsWith('%') ? (length * width) / 100 : length\n return Math.max(0, Math.min(pixels, width / 2, height / 2))\n}\n\n/**\n * Follows another element's box, as a Mount: the element whose id is the\n * mounted element's `data-target` (`TRACK_TARGET_ATTRIBUTE`). Its box in\n * viewport pixels is written on the mounted element as `--fk-<name>-x`,\n * `-y`, `-w` and `-h`, and with `radius`, its top left corner's radius as\n * `--fk-<name>-radius` (`cornerRadius`), so a fixed overlay drawn from them\n * (a tour's cutout, a coach mark's ring) sits over it. While nothing is\n * followed they are removed, so a stylist's fallbacks apply.\n *\n * The box is read again on a scroll anywhere (heard on the way down, since\n * `scroll` does not bubble), a window resize, the followed element's own\n * resize, and any change to the document's elements, which is also how a\n * target drawn anew under the same id is found. `data-target` is read again\n * whenever it changes, so one element can glide from target to target\n * without being drawn again, which a Mount's args, read once, could not do.\n * What moves the target by any other way (a stylesheet added) is seen at the\n * next of these.\n *\n * Whether a target is on the page is the parent's to act on (a tour shows\n * the step in the middle instead), so each change of the target or of\n * whether it is found sends `TrackedTarget`, starting with the first one.\n * Since it sends a Message, a component may draw it by default: a Scene test\n * resolves it with what the parent maps it to. An empty `data-target` follows\n * nothing and sends nothing.\n */\nexport const TrackBox = Mount.defineStream('TrackBox', {\n messages: [TrackedTarget],\n args: {\n /** The custom properties' prefix: `tour` writes `--fk-tour-x`. */\n name: Schema.String,\n /** Whether to write the target's corner radius too; it reads the target's computed style. */\n radius: Schema.optional(Schema.Boolean),\n },\n execute: ({ element, name, radius = false }) =>\n Stream.callback<TrackedTarget>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const document = element.ownerDocument\n const view = document.defaultView\n const properties = trackedBox(name)\n const style = element instanceof HTMLElement ? element.style : undefined\n let target: Element | null = null\n let told: TrackedTarget | undefined\n\n const place = () => {\n if (style === undefined) return\n if (target === null) {\n for (const property of Object.values(properties)) style.removeProperty(property)\n return\n }\n const box = target.getBoundingClientRect()\n style.setProperty(properties.x, `${box.left}px`)\n style.setProperty(properties.y, `${box.top}px`)\n style.setProperty(properties.w, `${box.width}px`)\n style.setProperty(properties.h, `${box.height}px`)\n if (radius) {\n const corner = getComputedStyle(target).borderTopLeftRadius\n style.setProperty(\n properties.radius,\n `${cornerRadius(corner, box.width, box.height)}px`,\n )\n }\n }\n // Looks the target up again, telling the parent when what it follows, or whether\n // that is found, changed.\n const follow = () => {\n const id = element.getAttribute(TRACK_TARGET_ATTRIBUTE) ?? ''\n const found = id === '' ? null : document.getElementById(id)\n if (found !== target) {\n if (target !== null) resized.unobserve(target)\n if (found !== null) resized.observe(found)\n target = found\n }\n place()\n if (id !== '' && (told?.target !== id || told.found !== (found !== null))) {\n told = TrackedTarget.make({ target: id, found: found !== null })\n Queue.offerUnsafe(queue, told)\n }\n }\n\n // Every observer is made before any observes, so one that throws (jsdom has no\n // ResizeObserver) leaves nothing attached.\n const resized = new ResizeObserver(place)\n const changed = new MutationObserver(follow)\n changed.observe(element, {\n attributes: true,\n attributeFilter: [TRACK_TARGET_ATTRIBUTE],\n })\n changed.observe(document, { childList: true, subtree: true })\n view?.addEventListener('scroll', place, { capture: true, passive: true })\n view?.addEventListener('resize', place)\n follow()\n return () => {\n changed.disconnect()\n resized.disconnect()\n view?.removeEventListener('scroll', place, { capture: true })\n view?.removeEventListener('resize', place)\n }\n }),\n stop => Effect.sync(stop),\n ),\n ),\n})\n"],"mappings":";;;;AAIA,MAAa,yBAAyB;;AAGtC,MAAa,cAAc,UACxB;CACC,GAAG,QAAQ,KAAK;CAChB,GAAG,QAAQ,KAAK;CAChB,GAAG,QAAQ,KAAK;CAChB,GAAG,QAAQ,KAAK;CAChB,QAAQ,QAAQ,KAAK;AACvB;;AAGF,MAAa,gBAAgB,OAAO,aAAa,iBAAiB;CAChE,QAAQ,OAAO;CACf,OAAO,OAAO;AAChB,CAAC;;;;;;;AASD,MAAa,gBAAgB,OAAe,OAAe,WAA2B;CACpF,MAAM,QAAQ,MAAM,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;CAC9C,MAAM,SAAS,WAAW,KAAK;CAC/B,IAAI,CAAC,OAAO,SAAS,MAAM,GAAG,OAAO;CACrC,MAAM,SAAS,MAAM,SAAS,GAAG,IAAK,SAAS,QAAS,MAAM;CAC9D,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,QAAQ,QAAQ,GAAG,SAAS,CAAC,CAAC;AAC5D;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,WAAW,MAAM,aAAa,YAAY;CACrD,UAAU,CAAC,aAAa;CACxB,MAAM;;EAEJ,MAAM,OAAO;;EAEb,QAAQ,OAAO,SAAS,OAAO,OAAO;CACxC;CACA,UAAU,EAAE,SAAS,MAAM,SAAS,YAClC,OAAO,UAAwB,UAC7B,OAAO,eACL,OAAO,WAAW;EAChB,MAAM,WAAW,QAAQ;EACzB,MAAM,OAAO,SAAS;EACtB,MAAM,aAAa,WAAW,IAAI;EAClC,MAAM,QAAQ,mBAAmB,cAAc,QAAQ,QAAQ,KAAA;EAC/D,IAAI,SAAyB;EAC7B,IAAI;EAEJ,MAAM,cAAc;GAClB,IAAI,UAAU,KAAA,GAAW;GACzB,IAAI,WAAW,MAAM;IACnB,KAAK,MAAM,YAAY,OAAO,OAAO,UAAU,GAAG,MAAM,eAAe,QAAQ;IAC/E;GACF;GACA,MAAM,MAAM,OAAO,sBAAsB;GACzC,MAAM,YAAY,WAAW,GAAG,GAAG,IAAI,KAAK,GAAG;GAC/C,MAAM,YAAY,WAAW,GAAG,GAAG,IAAI,IAAI,GAAG;GAC9C,MAAM,YAAY,WAAW,GAAG,GAAG,IAAI,MAAM,GAAG;GAChD,MAAM,YAAY,WAAW,GAAG,GAAG,IAAI,OAAO,GAAG;GACjD,IAAI,QAAQ;IACV,MAAM,SAAS,iBAAiB,MAAM,CAAC,CAAC;IACxC,MAAM,YACJ,WAAW,QACX,GAAG,aAAa,QAAQ,IAAI,OAAO,IAAI,MAAM,EAAE,GACjD;GACF;EACF;EAGA,MAAM,eAAe;GACnB,MAAM,KAAK,QAAQ,aAAA,aAAmC,KAAK;GAC3D,MAAM,QAAQ,OAAO,KAAK,OAAO,SAAS,eAAe,EAAE;GAC3D,IAAI,UAAU,QAAQ;IACpB,IAAI,WAAW,MAAM,QAAQ,UAAU,MAAM;IAC7C,IAAI,UAAU,MAAM,QAAQ,QAAQ,KAAK;IACzC,SAAS;GACX;GACA,MAAM;GACN,IAAI,OAAO,OAAO,MAAM,WAAW,MAAM,KAAK,WAAW,UAAU,QAAQ;IACzE,OAAO,cAAc,KAAK;KAAE,QAAQ;KAAI,OAAO,UAAU;IAAK,CAAC;IAC/D,MAAM,YAAY,OAAO,IAAI;GAC/B;EACF;EAIA,MAAM,UAAU,IAAI,eAAe,KAAK;EACxC,MAAM,UAAU,IAAI,iBAAiB,MAAM;EAC3C,QAAQ,QAAQ,SAAS;GACvB,YAAY;GACZ,iBAAiB,CAAC,sBAAsB;EAC1C,CAAC;EACD,QAAQ,QAAQ,UAAU;GAAE,WAAW;GAAM,SAAS;EAAK,CAAC;EAC5D,MAAM,iBAAiB,UAAU,OAAO;GAAE,SAAS;GAAM,SAAS;EAAK,CAAC;EACxE,MAAM,iBAAiB,UAAU,KAAK;EACtC,OAAO;EACP,aAAa;GACX,QAAQ,WAAW;GACnB,QAAQ,WAAW;GACnB,MAAM,oBAAoB,UAAU,OAAO,EAAE,SAAS,KAAK,CAAC;GAC5D,MAAM,oBAAoB,UAAU,KAAK;EAC3C;CACF,CAAC,IACD,SAAQ,OAAO,KAAK,IAAI,CAC1B,CACF;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Schema, Stream } from "effect";
|
|
2
|
+
export declare const ActiveElementMessage: import("foldkit/message").MessageUnion<{
|
|
3
|
+
readonly Changed: {
|
|
4
|
+
tag: Schema.String;
|
|
5
|
+
id: Schema.NullOr<Schema.String>;
|
|
6
|
+
};
|
|
7
|
+
}>;
|
|
8
|
+
export type ActiveElementMessage = typeof ActiveElementMessage.Type;
|
|
9
|
+
/**
|
|
10
|
+
* Focus changes on the document. Without a document the stream is empty
|
|
11
|
+
* instead of throwing. Lift with `Subscription.persistentEntry`, mapping into
|
|
12
|
+
* the parent's Message.
|
|
13
|
+
*/
|
|
14
|
+
export declare const activeElementEvents: () => Stream.Stream<ActiveElementMessage>;
|
|
15
|
+
//# sourceMappingURL=active-element.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-element.d.ts","names":[],"sources":["../../src/events/active-element.ts"],"mappings":";qBASa,gDAAoB;;IACpB,KAAG,OAAA;IAAiB,IAAE,OAAA,OAAA,OAAA;;;YAEvB,8BAA8B,qBAAqB;;;;;;qBAOlD,2BAA0B,OAAO,OAAO"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Schema, Stream } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
// src/events/active-element.ts
|
|
4
|
+
/**
|
|
5
|
+
* Focused element as an entry — not a bundle, because elements are not
|
|
6
|
+
* Model facts: this reports the active element's tag and id (both
|
|
7
|
+
* serializable), and the parent stores what matters. Starts with the
|
|
8
|
+
* current answer, then follows `focusin`/`focusout`.
|
|
9
|
+
*/
|
|
10
|
+
const ActiveElementMessage = defineMessageUnion({ Changed: {
|
|
11
|
+
tag: Schema.String,
|
|
12
|
+
id: Schema.NullOr(Schema.String)
|
|
13
|
+
} });
|
|
14
|
+
/**
|
|
15
|
+
* Focus changes on the document. Without a document the stream is empty
|
|
16
|
+
* instead of throwing. Lift with `Subscription.persistentEntry`, mapping into
|
|
17
|
+
* the parent's Message.
|
|
18
|
+
*/
|
|
19
|
+
const activeElementEvents = () => {
|
|
20
|
+
if (typeof document === "undefined") return Stream.empty;
|
|
21
|
+
const read = () => {
|
|
22
|
+
const element = document.activeElement;
|
|
23
|
+
return ActiveElementMessage.Changed({
|
|
24
|
+
tag: element?.tagName ?? "",
|
|
25
|
+
id: element?.id !== void 0 && element.id !== "" ? element.id : null
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const changes = Stream.mergeAll([Stream.fromEventListener(document, "focusin").pipe(Stream.map(read)), Stream.fromEventListener(document, "focusout").pipe(Stream.map(read))], { concurrency: "unbounded" });
|
|
29
|
+
return Stream.concat(Stream.make(read()), changes);
|
|
30
|
+
};
|
|
31
|
+
export { ActiveElementMessage, activeElementEvents };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=active-element.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"active-element.js","names":[],"sources":["../../src/events/active-element.ts"],"sourcesContent":["/**\n * Focused element as an entry — not a bundle, because elements are not\n * Model facts: this reports the active element's tag and id (both\n * serializable), and the parent stores what matters. Starts with the\n * current answer, then follows `focusin`/`focusout`.\n */\nimport { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const ActiveElementMessage = defineMessageUnion({\n Changed: { tag: Schema.String, id: Schema.NullOr(Schema.String) },\n})\nexport type ActiveElementMessage = typeof ActiveElementMessage.Type\n\n/**\n * Focus changes on the document. Without a document the stream is empty\n * instead of throwing. Lift with `Subscription.persistentEntry`, mapping into\n * the parent's Message.\n */\nexport const activeElementEvents = (): Stream.Stream<ActiveElementMessage> => {\n if (typeof document === 'undefined') return Stream.empty\n const read = (): ActiveElementMessage => {\n const element = document.activeElement as Element | null | undefined\n return ActiveElementMessage.Changed({\n tag: element?.tagName ?? '',\n id: element?.id !== undefined && element.id !== '' ? element.id : null,\n })\n }\n const changes: Stream.Stream<ActiveElementMessage> = Stream.mergeAll(\n [\n Stream.fromEventListener(document, 'focusin').pipe(Stream.map(read)),\n Stream.fromEventListener(document, 'focusout').pipe(Stream.map(read)),\n ],\n { concurrency: 'unbounded' },\n )\n return Stream.concat(Stream.make(read()), changes)\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,uBAAuB,mBAAmB,EACrD,SAAS;CAAE,KAAK,OAAO;CAAQ,IAAI,OAAO,OAAO,OAAO,MAAM;AAAE,EAClE,CAAC;;;;;;AAQD,MAAa,4BAAiE;CAC5E,IAAI,OAAO,aAAa,aAAa,OAAO,OAAO;CACnD,MAAM,aAAmC;EACvC,MAAM,UAAU,SAAS;EACzB,OAAO,qBAAqB,QAAQ;GAClC,KAAK,SAAS,WAAW;GACzB,IAAI,SAAS,OAAO,KAAA,KAAa,QAAQ,OAAO,KAAK,QAAQ,KAAK;EACpE,CAAC;CACH;CACA,MAAM,UAA+C,OAAO,SAC1D,CACE,OAAO,kBAAkB,UAAU,SAAS,CAAC,CAAC,KAAK,OAAO,IAAI,IAAI,CAAC,GACnE,OAAO,kBAAkB,UAAU,UAAU,CAAC,CAAC,KAAK,OAAO,IAAI,IAAI,CAAC,CACtE,GACA,EAAE,aAAa,YAAY,CAC7B;CACA,OAAO,OAAO,OAAO,OAAO,KAAK,KAAK,CAAC,GAAG,OAAO;AACnD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface KeyPress {
|
|
2
|
+
readonly key: string;
|
|
3
|
+
readonly repeat: boolean;
|
|
4
|
+
readonly ctrl: boolean;
|
|
5
|
+
readonly shift: boolean;
|
|
6
|
+
readonly alt: boolean;
|
|
7
|
+
readonly meta: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Hotkey matching as a pure function over a `Pressed` payload: `matchHotkey`
|
|
11
|
+
* answers whether a press is the shortcut, so `update` stays a table of
|
|
12
|
+
* chords instead of a nest of modifier checks. Patterns read like
|
|
13
|
+
* `"ctrl+shift+k"`: modifiers first (any order), the key last,
|
|
14
|
+
* case-insensitive, with `cmd`/`command` and `option` as the Mac names for
|
|
15
|
+
* meta and alt. Matching is exact — unlisted modifiers must be up — and
|
|
16
|
+
* auto-repeat never matches, so holding a chord fires once. `space` (or a
|
|
17
|
+
* bare space) means the space key; `esc` means escape. A pattern with
|
|
18
|
+
* no key, or two keys, matches nothing: predicates return false, they do
|
|
19
|
+
* not throw on the hot path.
|
|
20
|
+
*/
|
|
21
|
+
export declare const matchHotkey: (pattern: string, press: KeyPress) => boolean;
|
|
22
|
+
//# sourceMappingURL=hotkeys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotkeys.d.ts","names":[],"sources":["../../src/events/hotkeys.ts"],"mappings":"A,iBAAiB;WACN;WACA;WACA;WACA;WACA;WACA;;;;;;;;;;;;;;qBA8CE,cAAW,iBAAmB,OAAS"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/events/hotkeys.ts
|
|
2
|
+
const modifierOf = (part) => {
|
|
3
|
+
switch (part) {
|
|
4
|
+
case "ctrl":
|
|
5
|
+
case "control": return "ctrl";
|
|
6
|
+
case "shift": return "shift";
|
|
7
|
+
case "alt":
|
|
8
|
+
case "option": return "alt";
|
|
9
|
+
case "meta":
|
|
10
|
+
case "cmd":
|
|
11
|
+
case "command": return "meta";
|
|
12
|
+
default: return null;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const keyNameOf = (part) => {
|
|
16
|
+
switch (part) {
|
|
17
|
+
case "spacebar":
|
|
18
|
+
case " ": return "space";
|
|
19
|
+
case "esc": return "escape";
|
|
20
|
+
default: return part;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Hotkey matching as a pure function over a `Pressed` payload: `matchHotkey`
|
|
25
|
+
* answers whether a press is the shortcut, so `update` stays a table of
|
|
26
|
+
* chords instead of a nest of modifier checks. Patterns read like
|
|
27
|
+
* `"ctrl+shift+k"`: modifiers first (any order), the key last,
|
|
28
|
+
* case-insensitive, with `cmd`/`command` and `option` as the Mac names for
|
|
29
|
+
* meta and alt. Matching is exact — unlisted modifiers must be up — and
|
|
30
|
+
* auto-repeat never matches, so holding a chord fires once. `space` (or a
|
|
31
|
+
* bare space) means the space key; `esc` means escape. A pattern with
|
|
32
|
+
* no key, or two keys, matches nothing: predicates return false, they do
|
|
33
|
+
* not throw on the hot path.
|
|
34
|
+
*/
|
|
35
|
+
const matchHotkey = (pattern, press) => {
|
|
36
|
+
if (press.repeat) return false;
|
|
37
|
+
const trimmed = pattern.split("+").map((part) => part.trim().toLowerCase()).filter((part) => part !== "");
|
|
38
|
+
const parts = trimmed.length === 0 && pattern.length > 0 ? ["space"] : trimmed;
|
|
39
|
+
const keys = parts.filter((part) => modifierOf(part) === null);
|
|
40
|
+
if (keys.length !== 1) return false;
|
|
41
|
+
if (keyNameOf(keys[0]) !== keyNameOf(press.key.toLowerCase())) return false;
|
|
42
|
+
const required = {
|
|
43
|
+
ctrl: false,
|
|
44
|
+
shift: false,
|
|
45
|
+
alt: false,
|
|
46
|
+
meta: false
|
|
47
|
+
};
|
|
48
|
+
for (const part of parts) {
|
|
49
|
+
const modifier = modifierOf(part);
|
|
50
|
+
if (modifier !== null) required[modifier] = true;
|
|
51
|
+
}
|
|
52
|
+
return required.ctrl === press.ctrl && required.shift === press.shift && required.alt === press.alt && required.meta === press.meta;
|
|
53
|
+
};
|
|
54
|
+
export { matchHotkey };
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=hotkeys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotkeys.js","names":[],"sources":["../../src/events/hotkeys.ts"],"sourcesContent":["export interface KeyPress {\n readonly key: string\n readonly repeat: boolean\n readonly ctrl: boolean\n readonly shift: boolean\n readonly alt: boolean\n readonly meta: boolean\n}\n\nconst modifierOf = (part: string): 'ctrl' | 'shift' | 'alt' | 'meta' | null => {\n switch (part) {\n case 'ctrl':\n case 'control':\n return 'ctrl'\n case 'shift':\n return 'shift'\n case 'alt':\n case 'option':\n return 'alt'\n case 'meta':\n case 'cmd':\n case 'command':\n return 'meta'\n default:\n return null\n }\n}\n\nconst keyNameOf = (part: string): string => {\n switch (part) {\n case 'spacebar':\n case ' ':\n return 'space'\n case 'esc':\n return 'escape'\n default:\n return part\n }\n}\n\n/**\n * Hotkey matching as a pure function over a `Pressed` payload: `matchHotkey`\n * answers whether a press is the shortcut, so `update` stays a table of\n * chords instead of a nest of modifier checks. Patterns read like\n * `\"ctrl+shift+k\"`: modifiers first (any order), the key last,\n * case-insensitive, with `cmd`/`command` and `option` as the Mac names for\n * meta and alt. Matching is exact — unlisted modifiers must be up — and\n * auto-repeat never matches, so holding a chord fires once. `space` (or a\n * bare space) means the space key; `esc` means escape. A pattern with\n * no key, or two keys, matches nothing: predicates return false, they do\n * not throw on the hot path.\n */\nexport const matchHotkey = (pattern: string, press: KeyPress): boolean => {\n if (press.repeat) return false\n const trimmed = pattern\n .split('+')\n .map(part => part.trim().toLowerCase())\n .filter(part => part !== '')\n // A bare whitespace pattern means the space key; an empty pattern is malformed.\n const parts = trimmed.length === 0 && pattern.length > 0 ? ['space'] : trimmed\n const keys = parts.filter(part => modifierOf(part) === null)\n if (keys.length !== 1) return false\n if (keyNameOf(keys[0]!) !== keyNameOf(press.key.toLowerCase())) return false\n const required: Record<'ctrl' | 'shift' | 'alt' | 'meta', boolean> = {\n ctrl: false,\n shift: false,\n alt: false,\n meta: false,\n }\n for (const part of parts) {\n const modifier = modifierOf(part)\n if (modifier !== null) required[modifier] = true\n }\n return (\n required.ctrl === press.ctrl &&\n required.shift === press.shift &&\n required.alt === press.alt &&\n required.meta === press.meta\n )\n}\n"],"mappings":";AASA,MAAM,cAAc,SAA2D;CAC7E,QAAQ,MAAR;EACE,KAAK;EACL,KAAK,WACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,KAAK;EACL,KAAK,UACH,OAAO;EACT,KAAK;EACL,KAAK;EACL,KAAK,WACH,OAAO;EACT,SACE,OAAO;CACX;AACF;AAEA,MAAM,aAAa,SAAyB;CAC1C,QAAQ,MAAR;EACE,KAAK;EACL,KAAK,KACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;;;;;;;;;AAcA,MAAa,eAAe,SAAiB,UAA6B;CACxE,IAAI,MAAM,QAAQ,OAAO;CACzB,MAAM,UAAU,QACb,MAAM,GAAG,CAAC,CACV,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CACtC,QAAO,SAAQ,SAAS,EAAE;CAE7B,MAAM,QAAQ,QAAQ,WAAW,KAAK,QAAQ,SAAS,IAAI,CAAC,OAAO,IAAI;CACvE,MAAM,OAAO,MAAM,QAAO,SAAQ,WAAW,IAAI,MAAM,IAAI;CAC3D,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,IAAI,UAAU,KAAK,EAAG,MAAM,UAAU,MAAM,IAAI,YAAY,CAAC,GAAG,OAAO;CACvE,MAAM,WAA+D;EACnE,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;CACR;CACA,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,WAAW,WAAW,IAAI;EAChC,IAAI,aAAa,MAAM,SAAS,YAAY;CAC9C;CACA,OACE,SAAS,SAAS,MAAM,QACxB,SAAS,UAAU,MAAM,SACzB,SAAS,QAAQ,MAAM,OACvB,SAAS,SAAS,MAAM;AAE5B"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle } from "foldkit-bundle";
|
|
3
|
+
export declare const IdleModel: Schema.Struct<{
|
|
4
|
+
readonly idle: Schema.Boolean;
|
|
5
|
+
}>;
|
|
6
|
+
export type IdleModel = typeof IdleModel.Type;
|
|
7
|
+
export declare const IdleMessage: import("foldkit/message").MessageUnion<{
|
|
8
|
+
readonly BecameIdle: {};
|
|
9
|
+
readonly BecameActive: {};
|
|
10
|
+
}>;
|
|
11
|
+
export type IdleMessage = typeof IdleMessage.Type;
|
|
12
|
+
/**
|
|
13
|
+
* Idleness as a bundle: `idle` in the Model, flipped by activity and
|
|
14
|
+
* silence. While active, activity debounced past `timeoutMs` settles to
|
|
15
|
+
* `BecameIdle`; while idle, the first activity wakes to `BecameActive` and
|
|
16
|
+
* the dependency flip restarts the watch. Starts active — a lurker idles
|
|
17
|
+
* when the timeout elapses. The clock is Effect's, so tests drive it with
|
|
18
|
+
* TestClock.
|
|
19
|
+
*/
|
|
20
|
+
export declare const Idle: Bundle.Bundle<"Idle", {
|
|
21
|
+
readonly timeoutMs: number;
|
|
22
|
+
}, {
|
|
23
|
+
readonly idle: boolean;
|
|
24
|
+
}, {
|
|
25
|
+
readonly _tag: "BecameActive";
|
|
26
|
+
} | {
|
|
27
|
+
readonly _tag: "BecameIdle";
|
|
28
|
+
}, never, never, never, void, {}, {}>;
|
|
29
|
+
//# sourceMappingURL=idle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idle.d.ts","names":[],"sources":["../../src/events/idle.ts"],"mappings":";;qBAMa,WAAS,OAAA;;;YACV,mBAAmB,UAAU;qBAE5B,uCAAW;;;;YACZ,qBAAqB,YAAY;;;;;;;;;qBAsBhC,MAAI,OAAA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Schema, Stream } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import * as Subscription from "foldkit/subscription";
|
|
4
|
+
import { Bundle } from "foldkit-bundle";
|
|
5
|
+
// src/events/idle.ts
|
|
6
|
+
const IdleModel = Schema.Struct({ idle: Schema.Boolean });
|
|
7
|
+
const IdleMessage = defineMessageUnion({
|
|
8
|
+
BecameIdle: {},
|
|
9
|
+
BecameActive: {}
|
|
10
|
+
});
|
|
11
|
+
const activity = () => {
|
|
12
|
+
if (typeof window === "undefined") return Stream.empty;
|
|
13
|
+
const senses = [
|
|
14
|
+
Stream.fromEventListener(window, "mousemove"),
|
|
15
|
+
Stream.fromEventListener(window, "keydown"),
|
|
16
|
+
Stream.fromEventListener(window, "pointerdown"),
|
|
17
|
+
Stream.fromEventListener(window, "scroll")
|
|
18
|
+
];
|
|
19
|
+
return Stream.mergeAll(senses, { concurrency: "unbounded" });
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Idleness as a bundle: `idle` in the Model, flipped by activity and
|
|
23
|
+
* silence. While active, activity debounced past `timeoutMs` settles to
|
|
24
|
+
* `BecameIdle`; while idle, the first activity wakes to `BecameActive` and
|
|
25
|
+
* the dependency flip restarts the watch. Starts active — a lurker idles
|
|
26
|
+
* when the timeout elapses. The clock is Effect's, so tests drive it with
|
|
27
|
+
* TestClock.
|
|
28
|
+
*/
|
|
29
|
+
const Idle = Bundle.make("Idle", {
|
|
30
|
+
Model: IdleModel,
|
|
31
|
+
Message: IdleMessage,
|
|
32
|
+
args: Schema.Struct({ timeoutMs: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())) }),
|
|
33
|
+
init: () => ({ model: { idle: false } }),
|
|
34
|
+
update: (model, message) => IdleMessage.match(message, {
|
|
35
|
+
BecameIdle: () => model.idle ? { model } : { model: {
|
|
36
|
+
...model,
|
|
37
|
+
idle: true
|
|
38
|
+
} },
|
|
39
|
+
BecameActive: () => model.idle ? { model: {
|
|
40
|
+
...model,
|
|
41
|
+
idle: false
|
|
42
|
+
} } : { model }
|
|
43
|
+
}),
|
|
44
|
+
subscriptions: ({ timeoutMs }) => Subscription.make()((entry) => ({ watch: entry({ idle: Schema.Boolean }, {
|
|
45
|
+
modelToDependencies: (model) => ({ idle: model.idle }),
|
|
46
|
+
dependenciesToStream: ({ idle }) => idle ? activity().pipe(Stream.take(1), Stream.map(() => IdleMessage.BecameActive())) : Stream.concat(Stream.make(null), activity()).pipe(Stream.debounce(timeoutMs), Stream.map(() => IdleMessage.BecameIdle()))
|
|
47
|
+
}) }))
|
|
48
|
+
});
|
|
49
|
+
export { Idle, IdleMessage, IdleModel };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=idle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idle.js","names":[],"sources":["../../src/events/idle.ts"],"sourcesContent":["import { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const IdleModel = Schema.Struct({ idle: Schema.Boolean })\nexport type IdleModel = typeof IdleModel.Type\n\nexport const IdleMessage = defineMessageUnion({ BecameIdle: {}, BecameActive: {} })\nexport type IdleMessage = typeof IdleMessage.Type\n\nconst activity = (): Stream.Stream<unknown> => {\n if (typeof window === 'undefined') return Stream.empty\n // One stream per sense; the merge fans into the debounce below.\n const senses: Array<Stream.Stream<unknown>> = [\n Stream.fromEventListener(window, 'mousemove'),\n Stream.fromEventListener(window, 'keydown'),\n Stream.fromEventListener(window, 'pointerdown'),\n Stream.fromEventListener(window, 'scroll'),\n ]\n return Stream.mergeAll(senses, { concurrency: 'unbounded' })\n}\n\n/**\n * Idleness as a bundle: `idle` in the Model, flipped by activity and\n * silence. While active, activity debounced past `timeoutMs` settles to\n * `BecameIdle`; while idle, the first activity wakes to `BecameActive` and\n * the dependency flip restarts the watch. Starts active — a lurker idles\n * when the timeout elapses. The clock is Effect's, so tests drive it with\n * TestClock.\n */\nexport const Idle = Bundle.make('Idle', {\n Model: IdleModel,\n Message: IdleMessage,\n args: Schema.Struct({\n timeoutMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: () => ({ model: { idle: false } }),\n update: (model, message) =>\n IdleMessage.match<Update.ReturnWithOutMessage<IdleModel, IdleMessage, never>>(message, {\n // One already in flight when the watch restarts can repeat the state.\n BecameIdle: () => (model.idle ? { model } : { model: { ...model, idle: true } }),\n BecameActive: () => (model.idle ? { model: { ...model, idle: false } } : { model }),\n }),\n subscriptions: ({ timeoutMs }): Subscription.Subscriptions<IdleModel, IdleMessage> =>\n Subscription.make<IdleModel, IdleMessage>()(entry => ({\n watch: entry(\n { idle: Schema.Boolean },\n {\n modelToDependencies: model => ({ idle: model.idle }),\n dependenciesToStream: ({ idle }) =>\n idle\n ? activity().pipe(\n Stream.take(1),\n Stream.map(() => IdleMessage.BecameActive()),\n )\n : // Seeded with subscribe time as last-known-alive: silence from\n // here still settles, so a lurker idles without moving first.\n Stream.concat(Stream.make(null), activity()).pipe(\n Stream.debounce(timeoutMs),\n Stream.map(() => IdleMessage.BecameIdle()),\n ),\n },\n ),\n })),\n})\n"],"mappings":";;;;;AAMA,MAAa,YAAY,OAAO,OAAO,EAAE,MAAM,OAAO,QAAQ,CAAC;AAG/D,MAAa,cAAc,mBAAmB;CAAE,YAAY,CAAC;CAAG,cAAc,CAAC;AAAE,CAAC;AAGlF,MAAM,iBAAyC;CAC7C,IAAI,OAAO,WAAW,aAAa,OAAO,OAAO;CAEjD,MAAM,SAAwC;EAC5C,OAAO,kBAAkB,QAAQ,WAAW;EAC5C,OAAO,kBAAkB,QAAQ,SAAS;EAC1C,OAAO,kBAAkB,QAAQ,aAAa;EAC9C,OAAO,kBAAkB,QAAQ,QAAQ;CAC3C;CACA,OAAO,OAAO,SAAS,QAAQ,EAAE,aAAa,YAAY,CAAC;AAC7D;;;;;;;;;AAUA,MAAa,OAAO,OAAO,KAAK,QAAQ;CACtC,OAAO;CACP,SAAS;CACT,MAAM,OAAO,OAAO,EAClB,WAAW,OAAO,OAAO,KACvB,OAAO,MAAM,OAAO,cAAc,CAAC,CAAC,GACpC,OAAO,MAAM,OAAO,SAAS,CAAC,CAChC,EACF,CAAC;CACD,aAAa,EAAE,OAAO,EAAE,MAAM,MAAM,EAAE;CACtC,SAAS,OAAO,YACd,YAAY,MAAkE,SAAS;EAErF,kBAAmB,MAAM,OAAO,EAAE,MAAM,IAAI,EAAE,OAAO;GAAE,GAAG;GAAO,MAAM;EAAK,EAAE;EAC9E,oBAAqB,MAAM,OAAO,EAAE,OAAO;GAAE,GAAG;GAAO,MAAM;EAAM,EAAE,IAAI,EAAE,MAAM;CACnF,CAAC;CACH,gBAAgB,EAAE,gBAChB,aAAa,KAA6B,CAAC,EAAC,WAAU,EACpD,OAAO,MACL,EAAE,MAAM,OAAO,QAAQ,GACvB;EACE,sBAAqB,WAAU,EAAE,MAAM,MAAM,KAAK;EAClD,uBAAuB,EAAE,WACvB,OACI,SAAS,CAAC,CAAC,KACT,OAAO,KAAK,CAAC,GACb,OAAO,UAAU,YAAY,aAAa,CAAC,CAC7C,IAGA,OAAO,OAAO,OAAO,KAAK,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,KAC3C,OAAO,SAAS,SAAS,GACzB,OAAO,UAAU,YAAY,WAAW,CAAC,CAC3C;CACR,CACF,EACF,EAAE;AACN,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./active-element.js";
|
|
2
|
+
export * from "./hotkeys.js";
|
|
3
|
+
export * from "./idle.js";
|
|
4
|
+
export * from "./input-modality.js";
|
|
5
|
+
export * from "./keyboard.js";
|
|
6
|
+
export * from "./pointer.js";
|
|
7
|
+
export * from "./scroll.js";
|
|
8
|
+
export * from "./visibility.js";
|
|
9
|
+
export * from "./window-size.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./active-element.js";
|
|
2
|
+
export * from "./hotkeys.js";
|
|
3
|
+
export * from "./idle.js";
|
|
4
|
+
export * from "./input-modality.js";
|
|
5
|
+
export * from "./keyboard.js";
|
|
6
|
+
export * from "./pointer.js";
|
|
7
|
+
export * from "./scroll.js";
|
|
8
|
+
export * from "./visibility.js";
|
|
9
|
+
export * from "./window-size.js";
|
|
10
|
+
export {};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
import { Bundle } from "foldkit-bundle";
|
|
3
|
-
declare const Modality: Schema.Literals<readonly ["keyboard", "pointer", "unknown"]>;
|
|
4
|
-
type Modality = typeof Modality.Type;
|
|
5
|
-
declare const InputModalityModel: Schema.Struct<{
|
|
3
|
+
export declare const Modality: Schema.Literals<readonly ["keyboard", "pointer", "unknown"]>;
|
|
4
|
+
export type Modality = typeof Modality.Type;
|
|
5
|
+
export declare const InputModalityModel: Schema.Struct<{
|
|
6
6
|
readonly modality: Schema.Literals<readonly ["keyboard", "pointer", "unknown"]>;
|
|
7
7
|
}>;
|
|
8
|
-
type InputModalityModel = typeof InputModalityModel.Type;
|
|
9
|
-
declare const InputModalityMessage: import("foldkit/message").MessageUnion<{
|
|
8
|
+
export type InputModalityModel = typeof InputModalityModel.Type;
|
|
9
|
+
export declare const InputModalityMessage: import("foldkit/message").MessageUnion<{
|
|
10
10
|
readonly Changed: {
|
|
11
11
|
modality: Schema.Literals<readonly ["keyboard", "pointer", "unknown"]>;
|
|
12
12
|
};
|
|
13
13
|
}>;
|
|
14
|
-
type InputModalityMessage = typeof InputModalityMessage.Type;
|
|
14
|
+
export type InputModalityMessage = typeof InputModalityMessage.Type;
|
|
15
15
|
/** A modifier alone says nothing about how the page is being driven. */
|
|
16
|
-
declare const modalityOfKey: (key: string) => Modality | null;
|
|
16
|
+
export declare const modalityOfKey: (key: string) => Modality | null;
|
|
17
17
|
/**
|
|
18
18
|
* Input modality as a bundle: whether the user last drove the page by
|
|
19
19
|
* keyboard or by pointer. The browser reports the facts; the Model keeps the
|
|
@@ -21,11 +21,10 @@ declare const modalityOfKey: (key: string) => Modality | null;
|
|
|
21
21
|
* reading the DOM, and a replay sees the same answer. CSS `:focus-visible`
|
|
22
22
|
* is the floor; this is for JavaScript that must know.
|
|
23
23
|
*/
|
|
24
|
-
declare const InputModality: Bundle.Bundle<"InputModality", void, {
|
|
24
|
+
export declare const InputModality: Bundle.Bundle<"InputModality", void, {
|
|
25
25
|
readonly modality: "keyboard" | "pointer" | "unknown";
|
|
26
26
|
}, {
|
|
27
27
|
readonly _tag: "Changed";
|
|
28
28
|
readonly modality: "keyboard" | "pointer" | "unknown";
|
|
29
29
|
}, never, never, never, void, {}, {}>;
|
|
30
|
-
|
|
31
|
-
//# sourceMappingURL=input-modality-l7InESYh.d.mts.map
|
|
30
|
+
//# sourceMappingURL=input-modality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-modality.d.ts","names":[],"sources":["../../src/events/input-modality.ts"],"mappings":";;qBAKa,UAAQ,OAAA;YACT,kBAAkB,SAAS;qBAE1B,oBAAkB,OAAA;;;YACnB,4BAA4B,mBAAmB;qBAE9C,gDAAoB;;IAAmC,UAAQ,OAAA;;;YAChE,8BAA8B,qBAAqB;;qBAKlD,gBAAa,gBAAkB;;;;;;;;qBA0B/B,eAAa,OAAA"}
|
|
@@ -37,6 +37,6 @@ const InputModality = Bundle.make("InputModality", {
|
|
|
37
37
|
update: (model, message) => model.modality === message.modality ? { model } : { model: { modality: message.modality } },
|
|
38
38
|
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.persistentEntry(modalityStream()) }))
|
|
39
39
|
});
|
|
40
|
-
export {
|
|
40
|
+
export { InputModality, InputModalityMessage, InputModalityModel, Modality, modalityOfKey };
|
|
41
41
|
|
|
42
|
-
//# sourceMappingURL=input-modality
|
|
42
|
+
//# sourceMappingURL=input-modality.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-modality.js","names":[],"sources":["../../src/events/input-modality.ts"],"sourcesContent":["import { Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const Modality = Schema.Literals(['keyboard', 'pointer', 'unknown'])\nexport type Modality = typeof Modality.Type\n\nexport const InputModalityModel = Schema.Struct({ modality: Modality })\nexport type InputModalityModel = typeof InputModalityModel.Type\n\nexport const InputModalityMessage = defineMessageUnion({ Changed: { modality: Modality } })\nexport type InputModalityMessage = typeof InputModalityMessage.Type\n\nconst MODIFIERS = new Set(['Shift', 'Control', 'Alt', 'Meta', 'CapsLock'])\n\n/** A modifier alone says nothing about how the page is being driven. */\nexport const modalityOfKey = (key: string): Modality | null =>\n MODIFIERS.has(key) ? null : 'keyboard'\n\nconst modalityStream = (): Stream.Stream<InputModalityMessage> => {\n if (typeof window === 'undefined') return Stream.empty\n return Stream.mergeAll(\n [\n Stream.fromEventListener<KeyboardEvent>(window, 'keydown', { capture: true }).pipe(\n Stream.filter(event => modalityOfKey(event.key) !== null),\n Stream.map(() => InputModalityMessage.Changed({ modality: 'keyboard' })),\n ),\n Stream.fromEventListener(window, 'pointerdown', { capture: true }).pipe(\n Stream.map(() => InputModalityMessage.Changed({ modality: 'pointer' })),\n ),\n ],\n { concurrency: 'unbounded' },\n )\n}\n\n/**\n * Input modality as a bundle: whether the user last drove the page by\n * keyboard or by pointer. The browser reports the facts; the Model keeps the\n * answer, so a view can show a focus ring only for keyboard users without\n * reading the DOM, and a replay sees the same answer. CSS `:focus-visible`\n * is the floor; this is for JavaScript that must know.\n */\nexport const InputModality = Bundle.make('InputModality', {\n Model: InputModalityModel,\n Message: InputModalityMessage,\n init: () => ({ model: { modality: 'unknown' as const } }),\n update: (model, message) =>\n model.modality === message.modality ? { model } : { model: { modality: message.modality } },\n subscriptions: (): Subscription.Subscriptions<InputModalityModel, InputModalityMessage> =>\n Subscription.make<InputModalityModel, InputModalityMessage>()(() => ({\n changes: Subscription.persistentEntry(modalityStream()),\n })),\n})\n"],"mappings":";;;;;AAKA,MAAa,WAAW,OAAO,SAAS;CAAC;CAAY;CAAW;AAAS,CAAC;AAG1E,MAAa,qBAAqB,OAAO,OAAO,EAAE,UAAU,SAAS,CAAC;AAGtE,MAAa,uBAAuB,mBAAmB,EAAE,SAAS,EAAE,UAAU,SAAS,EAAE,CAAC;AAG1F,MAAM,4BAAY,IAAI,IAAI;CAAC;CAAS;CAAW;CAAO;CAAQ;AAAU,CAAC;;AAGzE,MAAa,iBAAiB,QAC5B,UAAU,IAAI,GAAG,IAAI,OAAO;AAE9B,MAAM,uBAA4D;CAChE,IAAI,OAAO,WAAW,aAAa,OAAO,OAAO;CACjD,OAAO,OAAO,SACZ,CACE,OAAO,kBAAiC,QAAQ,WAAW,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,KAC5E,OAAO,QAAO,UAAS,cAAc,MAAM,GAAG,MAAM,IAAI,GACxD,OAAO,UAAU,qBAAqB,QAAQ,EAAE,UAAU,WAAW,CAAC,CAAC,CACzE,GACA,OAAO,kBAAkB,QAAQ,eAAe,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,KACjE,OAAO,UAAU,qBAAqB,QAAQ,EAAE,UAAU,UAAU,CAAC,CAAC,CACxE,CACF,GACA,EAAE,aAAa,YAAY,CAC7B;AACF;;;;;;;;AASA,MAAa,gBAAgB,OAAO,KAAK,iBAAiB;CACxD,OAAO;CACP,SAAS;CACT,aAAa,EAAE,OAAO,EAAE,UAAU,UAAmB,EAAE;CACvD,SAAS,OAAO,YACd,MAAM,aAAa,QAAQ,WAAW,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,UAAU,QAAQ,SAAS,EAAE;CAC5F,qBACE,aAAa,KAA+C,CAAC,QAAQ,EACnE,SAAS,aAAa,gBAAgB,eAAe,CAAC,EACxD,EAAE;AACN,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Schema, Stream } from "effect";
|
|
2
|
+
import { KeyPress } from "./hotkeys.js";
|
|
3
|
+
export declare const KeyboardMessage: import("foldkit/message").MessageUnion<{
|
|
4
|
+
readonly Pressed: {
|
|
5
|
+
key: Schema.String;
|
|
6
|
+
repeat: Schema.Boolean;
|
|
7
|
+
ctrl: Schema.Boolean;
|
|
8
|
+
shift: Schema.Boolean;
|
|
9
|
+
alt: Schema.Boolean;
|
|
10
|
+
meta: Schema.Boolean;
|
|
11
|
+
};
|
|
12
|
+
readonly Released: {
|
|
13
|
+
key: Schema.String;
|
|
14
|
+
};
|
|
15
|
+
}>;
|
|
16
|
+
export type KeyboardMessage = typeof KeyboardMessage.Type;
|
|
17
|
+
/**
|
|
18
|
+
* Key presses and releases on the window. Without a window the stream is
|
|
19
|
+
* empty instead of throwing. Lift with `Subscription.persistentEntry`, mapping
|
|
20
|
+
* into the parent's Message.
|
|
21
|
+
*/
|
|
22
|
+
export declare const keyboardEvents: (options?: {
|
|
23
|
+
/**
|
|
24
|
+
* Whether to cancel a press's default (the page scrolling under an arrow
|
|
25
|
+
* key, the browser's own shortcut), decided in the listener, since a
|
|
26
|
+
* default can only be cancelled before the event finishes dispatching.
|
|
27
|
+
*/
|
|
28
|
+
readonly preventDefault?: (press: KeyPress) => boolean;
|
|
29
|
+
}) => Stream.Stream<KeyboardMessage>;
|
|
30
|
+
//# sourceMappingURL=keyboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard.d.ts","names":[],"sources":["../../src/events/keyboard.ts"],"mappings":";;qBAUa,2CAAe;;IAExB,KAAG,OAAA;IACH,QAAM,OAAA;IACN,MAAI,OAAA;IACJ,OAAK,OAAA;IACL,KAAG,OAAA;IACH,MAAI,OAAA;;;IAEM,KAAG,OAAA;;;YAEL,yBAAyB,gBAAgB;;;;;;qBAOxC,iBAAc;;;;;;WAOd,kBAAkB,OAAO;MAEnC,OAAO,OAAO"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Effect, Queue, Schema, Stream } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
// src/events/keyboard.ts
|
|
4
|
+
/**
|
|
5
|
+
* Keyboard as an entry — not a bundle, because the parent owns whatever key
|
|
6
|
+
* state it keeps (a held-keys set, a shortcut map, a focused field): this
|
|
7
|
+
* reports presses and releases, including auto-repeat, and the parent
|
|
8
|
+
* stores what matters. Hotkey matching stays application policy.
|
|
9
|
+
*/
|
|
10
|
+
const KeyboardMessage = defineMessageUnion({
|
|
11
|
+
Pressed: {
|
|
12
|
+
key: Schema.String,
|
|
13
|
+
repeat: Schema.Boolean,
|
|
14
|
+
ctrl: Schema.Boolean,
|
|
15
|
+
shift: Schema.Boolean,
|
|
16
|
+
alt: Schema.Boolean,
|
|
17
|
+
meta: Schema.Boolean
|
|
18
|
+
},
|
|
19
|
+
Released: { key: Schema.String }
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Key presses and releases on the window. Without a window the stream is
|
|
23
|
+
* empty instead of throwing. Lift with `Subscription.persistentEntry`, mapping
|
|
24
|
+
* into the parent's Message.
|
|
25
|
+
*/
|
|
26
|
+
const keyboardEvents = (options = {}) => {
|
|
27
|
+
if (typeof window === "undefined") return Stream.empty;
|
|
28
|
+
return Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => {
|
|
29
|
+
const down = (event) => {
|
|
30
|
+
const press = KeyboardMessage.Pressed({
|
|
31
|
+
key: event.key,
|
|
32
|
+
repeat: event.repeat,
|
|
33
|
+
ctrl: event.ctrlKey,
|
|
34
|
+
shift: event.shiftKey,
|
|
35
|
+
alt: event.altKey,
|
|
36
|
+
meta: event.metaKey
|
|
37
|
+
});
|
|
38
|
+
if (options.preventDefault?.(press) === true) event.preventDefault();
|
|
39
|
+
Queue.offerUnsafe(queue, press);
|
|
40
|
+
};
|
|
41
|
+
const up = (event) => Queue.offerUnsafe(queue, KeyboardMessage.Released({ key: event.key }));
|
|
42
|
+
window.addEventListener("keydown", down);
|
|
43
|
+
window.addEventListener("keyup", up);
|
|
44
|
+
return {
|
|
45
|
+
down,
|
|
46
|
+
up
|
|
47
|
+
};
|
|
48
|
+
}), ({ down, up }) => Effect.sync(() => {
|
|
49
|
+
window.removeEventListener("keydown", down);
|
|
50
|
+
window.removeEventListener("keyup", up);
|
|
51
|
+
})));
|
|
52
|
+
};
|
|
53
|
+
export { KeyboardMessage, keyboardEvents };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=keyboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard.js","names":[],"sources":["../../src/events/keyboard.ts"],"sourcesContent":["/**\n * Keyboard as an entry — not a bundle, because the parent owns whatever key\n * state it keeps (a held-keys set, a shortcut map, a focused field): this\n * reports presses and releases, including auto-repeat, and the parent\n * stores what matters. Hotkey matching stays application policy.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type { KeyPress } from './hotkeys.js'\n\nexport const KeyboardMessage = defineMessageUnion({\n Pressed: {\n key: Schema.String,\n repeat: Schema.Boolean,\n ctrl: Schema.Boolean,\n shift: Schema.Boolean,\n alt: Schema.Boolean,\n meta: Schema.Boolean,\n },\n Released: { key: Schema.String },\n})\nexport type KeyboardMessage = typeof KeyboardMessage.Type\n\n/**\n * Key presses and releases on the window. Without a window the stream is\n * empty instead of throwing. Lift with `Subscription.persistentEntry`, mapping\n * into the parent's Message.\n */\nexport const keyboardEvents = (\n options: {\n /**\n * Whether to cancel a press's default (the page scrolling under an arrow\n * key, the browser's own shortcut), decided in the listener, since a\n * default can only be cancelled before the event finishes dispatching.\n */\n readonly preventDefault?: (press: KeyPress) => boolean\n } = {},\n): Stream.Stream<KeyboardMessage> => {\n if (typeof window === 'undefined') return Stream.empty\n return Stream.callback<KeyboardMessage>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const down = (event: KeyboardEvent) => {\n const press = KeyboardMessage.Pressed({\n key: event.key,\n repeat: event.repeat,\n ctrl: event.ctrlKey,\n shift: event.shiftKey,\n alt: event.altKey,\n meta: event.metaKey,\n })\n if (options.preventDefault?.(press) === true) event.preventDefault()\n Queue.offerUnsafe(queue, press)\n }\n const up = (event: KeyboardEvent) =>\n Queue.offerUnsafe(queue, KeyboardMessage.Released({ key: event.key }))\n window.addEventListener('keydown', down)\n window.addEventListener('keyup', up)\n return { down, up }\n }),\n ({ down, up }) =>\n Effect.sync(() => {\n window.removeEventListener('keydown', down)\n window.removeEventListener('keyup', up)\n }),\n ),\n )\n}\n"],"mappings":";;;;;;;;;AAUA,MAAa,kBAAkB,mBAAmB;CAChD,SAAS;EACP,KAAK,OAAO;EACZ,QAAQ,OAAO;EACf,MAAM,OAAO;EACb,OAAO,OAAO;EACd,KAAK,OAAO;EACZ,MAAM,OAAO;CACf;CACA,UAAU,EAAE,KAAK,OAAO,OAAO;AACjC,CAAC;;;;;;AAQD,MAAa,kBACX,UAOI,CAAC,MAC8B;CACnC,IAAI,OAAO,WAAW,aAAa,OAAO,OAAO;CACjD,OAAO,OAAO,UAA0B,UACtC,OAAO,eACL,OAAO,WAAW;EAChB,MAAM,QAAQ,UAAyB;GACrC,MAAM,QAAQ,gBAAgB,QAAQ;IACpC,KAAK,MAAM;IACX,QAAQ,MAAM;IACd,MAAM,MAAM;IACZ,OAAO,MAAM;IACb,KAAK,MAAM;IACX,MAAM,MAAM;GACd,CAAC;GACD,IAAI,QAAQ,iBAAiB,KAAK,MAAM,MAAM,MAAM,eAAe;GACnE,MAAM,YAAY,OAAO,KAAK;EAChC;EACA,MAAM,MAAM,UACV,MAAM,YAAY,OAAO,gBAAgB,SAAS,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC;EACvE,OAAO,iBAAiB,WAAW,IAAI;EACvC,OAAO,iBAAiB,SAAS,EAAE;EACnC,OAAO;GAAE;GAAM;EAAG;CACpB,CAAC,IACA,EAAE,MAAM,SACP,OAAO,WAAW;EAChB,OAAO,oBAAoB,WAAW,IAAI;EAC1C,OAAO,oBAAoB,SAAS,EAAE;CACxC,CAAC,CACL,CACF;AACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Schema, Stream } from "effect";
|
|
2
|
+
export declare const PointerMessage: import("foldkit/message").MessageUnion<{
|
|
3
|
+
readonly Moved: {
|
|
4
|
+
x: Schema.Number;
|
|
5
|
+
y: Schema.Number;
|
|
6
|
+
};
|
|
7
|
+
}>;
|
|
8
|
+
export type PointerMessage = typeof PointerMessage.Type;
|
|
9
|
+
/**
|
|
10
|
+
* Pointer moves on the window. Without a window the stream is empty instead
|
|
11
|
+
* of throwing. Lift with `Subscription.persistentEntry`, mapping into the
|
|
12
|
+
* parent's Message.
|
|
13
|
+
*/
|
|
14
|
+
export declare const pointerEvents: () => Stream.Stream<PointerMessage>;
|
|
15
|
+
//# sourceMappingURL=pointer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pointer.d.ts","names":[],"sources":["../../src/events/pointer.ts"],"mappings":";qBASa,0CAAc;;IAChB,GAAC,OAAA;IAAiB,GAAC,OAAA;;;YAElB,wBAAwB,eAAe;;;;;;qBAOtC,qBAAoB,OAAO,OAAO"}
|