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":"platform.js","names":[],"sources":["../../src/media/platform.ts"],"sourcesContent":["/**\n * Platform detection as pure functions. `platformFromUA` reads a user-agent\n * string (passed in, so tests pin it without stubbing globals); order\n * matters — Android contains \"Linux\", iPhones mention \"Mac\" — so mobile\n * checks come first. `isBrowser`/`isServer` split SSR from client for init\n * defaults.\n */\nexport const Platform = ['mac', 'windows', 'linux', 'android', 'ios', 'unknown'] as const\nexport type Platform = (typeof Platform)[number]\n\n/**\n * Client Hints (`navigator.userAgentData`) plus touch points\n * (`navigator.maxTouchPoints`), passed in so detection stays pure and\n * pinned. Only `platform` and `touchPoints` can change the answer — the\n * `mobile` flag never disambiguates on its own, so it is not accepted.\n */\nexport interface PlatformHints {\n readonly platform?: string | undefined\n readonly touchPoints?: number | undefined\n}\n\nconst hintedPlatform = (platform: string): Platform | null => {\n switch (platform.toLowerCase()) {\n case 'macos':\n return 'mac'\n case 'windows':\n return 'windows'\n case 'linux':\n return 'linux'\n case 'android':\n return 'android'\n case 'ios':\n return 'ios'\n default:\n return null\n }\n}\n\n/**\n * The platform a user-agent string names, preferring Client Hints when given\n * (`navigator.userAgentData.platform`), with touch points telling a desktop-mode\n * iPad from a Mac. `unknown` when nothing matches.\n */\nexport const platformFromUA = (userAgent: string, hints: PlatformHints = {}): Platform => {\n const hinted = hints.platform === undefined ? null : hintedPlatform(hints.platform)\n const ua = userAgent.toLowerCase()\n let sniffed: Platform = 'unknown'\n if (ua.includes('android')) sniffed = 'android'\n else if (ua.includes('iphone') || ua.includes('ipad') || ua.includes('ipod')) sniffed = 'ios'\n else if (ua.includes('windows') || ua.includes('win32') || ua.includes('win64')) {\n sniffed = 'windows'\n } else if (ua.includes('macintosh') || ua.includes('mac os')) sniffed = 'mac'\n else if (ua.includes('linux')) sniffed = 'linux'\n const candidate = hinted ?? sniffed\n // Desktop-mode iPads report \"macOS\" with a tablet touchscreen: only a\n // multi-touch Mac UA is really an iPad. Touchscreen Windows and Linux\n // machines keep their platform.\n return candidate === 'mac' && (hints.touchPoints ?? 0) > 1 ? 'ios' : candidate\n}\n\n/** Whether a window exists: the client side of an SSR split. */\nexport const isBrowser = (): boolean => typeof window !== 'undefined'\n\n/** Whether no window exists: the server side of an SSR split. */\nexport const isServer = (): boolean => !isBrowser()\n"],"mappings":";;;;;;;;AAOA,MAAa,WAAW;CAAC;CAAO;CAAW;CAAS;CAAW;CAAO;AAAS;AAc/E,MAAM,kBAAkB,aAAsC;CAC5D,QAAQ,SAAS,YAAY,GAA7B;EACE,KAAK,SACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,KAAK,OACH,OAAO;EACT,SACE,OAAO;CACX;AACF;;;;;;AAOA,MAAa,kBAAkB,WAAmB,QAAuB,CAAC,MAAgB;CACxF,MAAM,SAAS,MAAM,aAAa,KAAA,IAAY,OAAO,eAAe,MAAM,QAAQ;CAClF,MAAM,KAAK,UAAU,YAAY;CACjC,IAAI,UAAoB;CACxB,IAAI,GAAG,SAAS,SAAS,GAAG,UAAU;MACjC,IAAI,GAAG,SAAS,QAAQ,KAAK,GAAG,SAAS,MAAM,KAAK,GAAG,SAAS,MAAM,GAAG,UAAU;MACnF,IAAI,GAAG,SAAS,SAAS,KAAK,GAAG,SAAS,OAAO,KAAK,GAAG,SAAS,OAAO,GAC5E,UAAU;MACL,IAAI,GAAG,SAAS,WAAW,KAAK,GAAG,SAAS,QAAQ,GAAG,UAAU;MACnE,IAAI,GAAG,SAAS,OAAO,GAAG,UAAU;CACzC,MAAM,YAAY,UAAU;CAI5B,OAAO,cAAc,UAAU,MAAM,eAAe,KAAK,IAAI,QAAQ;AACvE;;AAGA,MAAa,kBAA2B,OAAO,WAAW;;AAG1D,MAAa,iBAA0B,CAAC,UAAU"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Context, Effect, Layer } from "effect";
|
|
2
|
+
export interface MotionShape {
|
|
3
|
+
readonly reduced: Effect.Effect<boolean>;
|
|
4
|
+
}
|
|
5
|
+
declare const Motion_base: Context.ServiceClass<Motion, "foldkit-primitives/Motion", MotionShape>;
|
|
6
|
+
export declare class Motion extends Motion_base {}
|
|
7
|
+
/** Reduced or full motion, fixed: for a test or a setting the application owns. */
|
|
8
|
+
export declare const layer: (reduced: boolean) => Layer.Layer<Motion>;
|
|
9
|
+
export declare const reduced: Layer.Layer<Motion>;
|
|
10
|
+
export declare const full: Layer.Layer<Motion>;
|
|
11
|
+
/** The user's `prefers-reduced-motion`, read each time a transition starts. */
|
|
12
|
+
export declare const live: Layer.Layer<Motion>;
|
|
13
|
+
/** Whether to reduce motion now: the service's answer, or `false` without one. */
|
|
14
|
+
export declare const reducedMotion: Effect.Effect<boolean>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=motion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion.d.ts","names":[],"sources":["../../src/motion/motion.ts"],"mappings":";iBAUiB;WACN,SAAS,OAAO;;;qBAGd,eAAe;;qBAGf,QAAK,qBAAuB,MAAM,MAAM;qBAGxC,SAAS,MAAM,MAAM;qBACrB,MAAM,MAAM,MAAM;;qBAGlB,MAAM,MAAM,MAAM;;qBAUlB,eAAe,OAAO"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option } from "effect";
|
|
2
|
+
// src/motion/motion.ts
|
|
3
|
+
/**
|
|
4
|
+
* Whether motion should be reduced, as a service read when a transition
|
|
5
|
+
* starts, not sniffed once. Absent, motion is full, so a placement that
|
|
6
|
+
* provides nothing behaves as before; a page provides `live` (the user's
|
|
7
|
+
* `prefers-reduced-motion`) and a test provides `reduced` or `full`. Under
|
|
8
|
+
* reduced motion a presence exits at once and a tween or spring jumps to its
|
|
9
|
+
* end, in the same Messages, so the Model sees the same transitions.
|
|
10
|
+
*/
|
|
11
|
+
var Motion = class extends Context.Service()("foldkit-primitives/Motion") {};
|
|
12
|
+
/** Reduced or full motion, fixed: for a test or a setting the application owns. */
|
|
13
|
+
const layer = (reduced) => Layer.succeed(Motion, { reduced: Effect.succeed(reduced) });
|
|
14
|
+
const reduced = layer(true);
|
|
15
|
+
const full = layer(false);
|
|
16
|
+
/** The user's `prefers-reduced-motion`, read each time a transition starts. */
|
|
17
|
+
const live = Layer.succeed(Motion, { reduced: Effect.sync(() => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) });
|
|
18
|
+
/** Whether to reduce motion now: the service's answer, or `false` without one. */
|
|
19
|
+
const reducedMotion = Effect.flatMap(Effect.serviceOption(Motion), Option.match({
|
|
20
|
+
onNone: () => Effect.succeed(false),
|
|
21
|
+
onSome: (motion) => motion.reduced
|
|
22
|
+
}));
|
|
23
|
+
export { Motion, full, layer, live, reduced, reducedMotion };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=motion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion.js","names":[],"sources":["../../src/motion/motion.ts"],"sourcesContent":["/**\n * Whether motion should be reduced, as a service read when a transition\n * starts, not sniffed once. Absent, motion is full, so a placement that\n * provides nothing behaves as before; a page provides `live` (the user's\n * `prefers-reduced-motion`) and a test provides `reduced` or `full`. Under\n * reduced motion a presence exits at once and a tween or spring jumps to its\n * end, in the same Messages, so the Model sees the same transitions.\n */\nimport { Context, Effect, Layer, Option } from 'effect'\n\nexport interface MotionShape {\n readonly reduced: Effect.Effect<boolean>\n}\n\nexport class Motion extends Context.Service<Motion, MotionShape>()('foldkit-primitives/Motion') {}\n\n/** Reduced or full motion, fixed: for a test or a setting the application owns. */\nexport const layer = (reduced: boolean): Layer.Layer<Motion> =>\n Layer.succeed(Motion, { reduced: Effect.succeed(reduced) })\n\nexport const reduced: Layer.Layer<Motion> = layer(true)\nexport const full: Layer.Layer<Motion> = layer(false)\n\n/** The user's `prefers-reduced-motion`, read each time a transition starts. */\nexport const live: Layer.Layer<Motion> = Layer.succeed(Motion, {\n reduced: Effect.sync(\n () =>\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches,\n ),\n})\n\n/** Whether to reduce motion now: the service's answer, or `false` without one. */\nexport const reducedMotion: Effect.Effect<boolean> = Effect.flatMap(\n Effect.serviceOption(Motion),\n Option.match({ onNone: () => Effect.succeed(false), onSome: motion => motion.reduced }),\n)\n"],"mappings":";;;;;;;;;;AAcA,IAAa,SAAb,cAA4B,QAAQ,QAA6B,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC;;AAGjG,MAAa,SAAS,YACpB,MAAM,QAAQ,QAAQ,EAAE,SAAS,OAAO,QAAQ,OAAO,EAAE,CAAC;AAE5D,MAAa,UAA+B,MAAM,IAAI;AACtD,MAAa,OAA4B,MAAM,KAAK;;AAGpD,MAAa,OAA4B,MAAM,QAAQ,QAAQ,EAC7D,SAAS,OAAO,WAEZ,OAAO,WAAW,eAClB,OAAO,OAAO,eAAe,cAC7B,OAAO,WAAW,kCAAkC,CAAC,CAAC,OAC1D,EACF,CAAC;;AAGD,MAAa,gBAAwC,OAAO,QAC1D,OAAO,cAAc,MAAM,GAC3B,OAAO,MAAM;CAAE,cAAc,OAAO,QAAQ,KAAK;CAAG,SAAQ,WAAU,OAAO;AAAQ,CAAC,CACxF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle } from "foldkit-bundle";
|
|
3
|
+
export declare const PresenceModel: Schema.Struct<{
|
|
4
|
+
readonly phase: Schema.Literals<readonly ["shown", "hiding", "hidden"]>;
|
|
5
|
+
readonly generation: Schema.Number;
|
|
6
|
+
}>;
|
|
7
|
+
export type PresenceModel = typeof PresenceModel.Type;
|
|
8
|
+
export declare const PresenceMessage: import("foldkit/message").MessageUnion<{
|
|
9
|
+
readonly Show: {};
|
|
10
|
+
readonly Hide: {};
|
|
11
|
+
readonly Hidden: {
|
|
12
|
+
generation: Schema.Number;
|
|
13
|
+
};
|
|
14
|
+
}>;
|
|
15
|
+
export type PresenceMessage = typeof PresenceMessage.Type;
|
|
16
|
+
/** Whether the content renders: shown or mid-exit. */
|
|
17
|
+
export declare const isVisible: (model: PresenceModel) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Presence as a bundle: mount-transition state for exit animations. `Hide`
|
|
20
|
+
* starts a timed `hiding` phase; the `Hidden` fact it yields carries the
|
|
21
|
+
* generation it was scheduled under, so a `Show` in between wins and the
|
|
22
|
+
* late fact is ignored. The clock is Effect's, so tests drive it.
|
|
23
|
+
*/
|
|
24
|
+
export declare const Presence: Bundle.Bundle<"Presence", {
|
|
25
|
+
readonly durationMs: number;
|
|
26
|
+
}, {
|
|
27
|
+
readonly phase: "hidden" | "hiding" | "shown";
|
|
28
|
+
readonly generation: number;
|
|
29
|
+
}, {
|
|
30
|
+
readonly _tag: "Hidden";
|
|
31
|
+
readonly generation: number;
|
|
32
|
+
} | {
|
|
33
|
+
readonly _tag: "Hide";
|
|
34
|
+
} | {
|
|
35
|
+
readonly _tag: "Show";
|
|
36
|
+
}, never, never, never, void, {}, {}>;
|
|
37
|
+
//# sourceMappingURL=presence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.d.ts","names":[],"sources":["../../src/motion/presence.ts"],"mappings":";;qBAMa,eAAa,OAAA;;;;YAId,uBAAuB,cAAc;qBAEpC,2CAAe;;;;IAGhB,YAAU,OAAA;;;YAEV,yBAAyB,gBAAgB;;qBAGxC,YAAS,OAAW;;;;;;;qBAQpB,UAAQ,OAAA"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import { Bundle } from "foldkit-bundle";
|
|
4
|
+
import { reducedMotion } from "./motion.js";
|
|
5
|
+
// src/motion/presence.ts
|
|
6
|
+
const PresenceModel = Schema.Struct({
|
|
7
|
+
phase: Schema.Literals([
|
|
8
|
+
"shown",
|
|
9
|
+
"hiding",
|
|
10
|
+
"hidden"
|
|
11
|
+
]),
|
|
12
|
+
generation: Schema.Number
|
|
13
|
+
});
|
|
14
|
+
const PresenceMessage = defineMessageUnion({
|
|
15
|
+
Show: {},
|
|
16
|
+
Hide: {},
|
|
17
|
+
Hidden: { generation: Schema.Number }
|
|
18
|
+
});
|
|
19
|
+
/** Whether the content renders: shown or mid-exit. */
|
|
20
|
+
const isVisible = (model) => model.phase !== "hidden";
|
|
21
|
+
/**
|
|
22
|
+
* Presence as a bundle: mount-transition state for exit animations. `Hide`
|
|
23
|
+
* starts a timed `hiding` phase; the `Hidden` fact it yields carries the
|
|
24
|
+
* generation it was scheduled under, so a `Show` in between wins and the
|
|
25
|
+
* late fact is ignored. The clock is Effect's, so tests drive it.
|
|
26
|
+
*/
|
|
27
|
+
const Presence = Bundle.make("Presence", {
|
|
28
|
+
Model: PresenceModel,
|
|
29
|
+
Message: PresenceMessage,
|
|
30
|
+
args: Schema.Struct({ durationMs: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())) }),
|
|
31
|
+
init: () => ({ model: {
|
|
32
|
+
phase: "hidden",
|
|
33
|
+
generation: 0
|
|
34
|
+
} }),
|
|
35
|
+
update: (model, message, args) => PresenceMessage.match(message, {
|
|
36
|
+
Show: () => model.phase === "shown" ? { model } : { model: {
|
|
37
|
+
...model,
|
|
38
|
+
phase: "shown",
|
|
39
|
+
generation: model.generation + 1
|
|
40
|
+
} },
|
|
41
|
+
Hide: () => {
|
|
42
|
+
if (model.phase === "hidden") return { model };
|
|
43
|
+
const generation = model.generation + 1;
|
|
44
|
+
return {
|
|
45
|
+
model: {
|
|
46
|
+
...model,
|
|
47
|
+
phase: "hiding",
|
|
48
|
+
generation
|
|
49
|
+
},
|
|
50
|
+
commands: [{
|
|
51
|
+
name: "Presence.hide",
|
|
52
|
+
args: { generation },
|
|
53
|
+
effect: Effect.flatMap(reducedMotion, (reduced) => reduced ? Effect.succeed(PresenceMessage.Hidden({ generation })) : Effect.as(Effect.sleep(args.durationMs), PresenceMessage.Hidden({ generation })))
|
|
54
|
+
}]
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
Hidden: ({ generation }) => generation === model.generation ? { model: {
|
|
58
|
+
...model,
|
|
59
|
+
phase: "hidden"
|
|
60
|
+
} } : { model }
|
|
61
|
+
})
|
|
62
|
+
});
|
|
63
|
+
export { Presence, PresenceMessage, PresenceModel, isVisible };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=presence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.js","names":[],"sources":["../../src/motion/presence.ts"],"sourcesContent":["import { Effect, Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const PresenceModel = Schema.Struct({\n phase: Schema.Literals(['shown', 'hiding', 'hidden']),\n generation: Schema.Number,\n})\nexport type PresenceModel = typeof PresenceModel.Type\n\nexport const PresenceMessage = defineMessageUnion({\n Show: {},\n Hide: {},\n Hidden: { generation: Schema.Number },\n})\nexport type PresenceMessage = typeof PresenceMessage.Type\n\n/** Whether the content renders: shown or mid-exit. */\nexport const isVisible = (model: PresenceModel): boolean => model.phase !== 'hidden'\n\n/**\n * Presence as a bundle: mount-transition state for exit animations. `Hide`\n * starts a timed `hiding` phase; the `Hidden` fact it yields carries the\n * generation it was scheduled under, so a `Show` in between wins and the\n * late fact is ignored. The clock is Effect's, so tests drive it.\n */\nexport const Presence = Bundle.make('Presence', {\n Model: PresenceModel,\n Message: PresenceMessage,\n args: Schema.Struct({\n durationMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: () => ({ model: { phase: 'hidden', generation: 0 } }),\n update: (model, message, args) =>\n PresenceMessage.match<Update.ReturnWithOutMessage<PresenceModel, PresenceMessage, never>>(\n message,\n {\n // Every new transition invalidates pending timeouts. While shown none is\n // pending, and hiding what is hidden would show it for `durationMs`.\n Show: () =>\n model.phase === 'shown'\n ? { model }\n : { model: { ...model, phase: 'shown' as const, generation: model.generation + 1 } },\n Hide: () => {\n if (model.phase === 'hidden') return { model }\n const generation = model.generation + 1\n return {\n model: { ...model, phase: 'hiding' as const, generation },\n commands: [\n {\n name: 'Presence.hide',\n args: { generation },\n effect: Effect.flatMap(reducedMotion, reduced =>\n reduced\n ? Effect.succeed(PresenceMessage.Hidden({ generation }))\n : Effect.as(\n Effect.sleep(args.durationMs),\n PresenceMessage.Hidden({ generation }),\n ),\n ),\n },\n ],\n }\n },\n Hidden: ({ generation }) =>\n generation === model.generation\n ? { model: { ...model, phase: 'hidden' as const } }\n : { model },\n },\n ),\n})\n"],"mappings":";;;;;AAMA,MAAa,gBAAgB,OAAO,OAAO;CACzC,OAAO,OAAO,SAAS;EAAC;EAAS;EAAU;CAAQ,CAAC;CACpD,YAAY,OAAO;AACrB,CAAC;AAGD,MAAa,kBAAkB,mBAAmB;CAChD,MAAM,CAAC;CACP,MAAM,CAAC;CACP,QAAQ,EAAE,YAAY,OAAO,OAAO;AACtC,CAAC;;AAID,MAAa,aAAa,UAAkC,MAAM,UAAU;;;;;;;AAQ5E,MAAa,WAAW,OAAO,KAAK,YAAY;CAC9C,OAAO;CACP,SAAS;CACT,MAAM,OAAO,OAAO,EAClB,YAAY,OAAO,OAAO,KACxB,OAAO,MAAM,OAAO,cAAc,CAAC,CAAC,GACpC,OAAO,MAAM,OAAO,SAAS,CAAC,CAChC,EACF,CAAC;CACD,aAAa,EAAE,OAAO;EAAE,OAAO;EAAU,YAAY;CAAE,EAAE;CACzD,SAAS,OAAO,SAAS,SACvB,gBAAgB,MACd,SACA;EAGE,YACE,MAAM,UAAU,UACZ,EAAE,MAAM,IACR,EAAE,OAAO;GAAE,GAAG;GAAO,OAAO;GAAkB,YAAY,MAAM,aAAa;EAAE,EAAE;EACvF,YAAY;GACV,IAAI,MAAM,UAAU,UAAU,OAAO,EAAE,MAAM;GAC7C,MAAM,aAAa,MAAM,aAAa;GACtC,OAAO;IACL,OAAO;KAAE,GAAG;KAAO,OAAO;KAAmB;IAAW;IACxD,UAAU,CACR;KACE,MAAM;KACN,MAAM,EAAE,WAAW;KACnB,QAAQ,OAAO,QAAQ,gBAAe,YACpC,UACI,OAAO,QAAQ,gBAAgB,OAAO,EAAE,WAAW,CAAC,CAAC,IACrD,OAAO,GACL,OAAO,MAAM,KAAK,UAAU,GAC5B,gBAAgB,OAAO,EAAE,WAAW,CAAC,CACvC,CACN;IACF,CACF;GACF;EACF;EACA,SAAS,EAAE,iBACT,eAAe,MAAM,aACjB,EAAE,OAAO;GAAE,GAAG;GAAO,OAAO;EAAkB,EAAE,IAChD,EAAE,MAAM;CAChB,CACF;AACJ,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle } from "foldkit-bundle";
|
|
3
|
+
export declare const SpringModel: Schema.Struct<{
|
|
4
|
+
readonly value: Schema.Number;
|
|
5
|
+
readonly velocity: Schema.Number;
|
|
6
|
+
readonly running: Schema.Boolean;
|
|
7
|
+
}>;
|
|
8
|
+
export type SpringModel = typeof SpringModel.Type;
|
|
9
|
+
export declare const SpringMessage: import("foldkit/message").MessageUnion<{
|
|
10
|
+
readonly Started: {};
|
|
11
|
+
readonly Stopped: {};
|
|
12
|
+
readonly Ticked: {
|
|
13
|
+
value: Schema.Number;
|
|
14
|
+
velocity: Schema.Number;
|
|
15
|
+
};
|
|
16
|
+
/** Carries the exact end value: the integrator stops near it, not on it. */
|
|
17
|
+
readonly Finished: {
|
|
18
|
+
value: Schema.Number;
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
21
|
+
export type SpringMessage = typeof SpringMessage.Type;
|
|
22
|
+
/**
|
|
23
|
+
* A spring as a bundle: a number pulled toward a target with stiffness and
|
|
24
|
+
* damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the
|
|
25
|
+
* trajectory is deterministic under TestClock; the stream ends with
|
|
26
|
+
* Finished, and the value rests exactly at `to`. Underdamped springs
|
|
27
|
+
* overshoot — that is the point — but the rest value is still exact.
|
|
28
|
+
*/
|
|
29
|
+
export declare const Spring: Bundle.Bundle<"Spring", {
|
|
30
|
+
readonly from: number;
|
|
31
|
+
readonly to: number;
|
|
32
|
+
readonly stiffness: number;
|
|
33
|
+
readonly damping: number;
|
|
34
|
+
}, {
|
|
35
|
+
readonly value: number;
|
|
36
|
+
readonly velocity: number;
|
|
37
|
+
readonly running: boolean;
|
|
38
|
+
}, {
|
|
39
|
+
readonly _tag: "Finished";
|
|
40
|
+
readonly value: number;
|
|
41
|
+
} | {
|
|
42
|
+
readonly _tag: "Started";
|
|
43
|
+
} | {
|
|
44
|
+
readonly _tag: "Stopped";
|
|
45
|
+
} | {
|
|
46
|
+
readonly _tag: "Ticked";
|
|
47
|
+
readonly value: number;
|
|
48
|
+
readonly velocity: number;
|
|
49
|
+
}, never, never, never, void, {}, {}>;
|
|
50
|
+
//# sourceMappingURL=spring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spring.d.ts","names":[],"sources":["../../src/motion/spring.ts"],"mappings":";;qBAMa,aAAW,OAAA;;;;;YAKZ,qBAAqB,YAAY;qBAEhC,yCAAa;;;;IAGd,OAAK,OAAA;IAAiB,UAAQ,OAAA;;;;IAE5B,OAAK,OAAA;;;YAEP,uBAAuB,cAAc;;;;;;;;qBAoBpC,QAAM,OAAA"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Effect, Schema, Stream } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import * as Subscription from "foldkit/subscription";
|
|
4
|
+
import { Bundle } from "foldkit-bundle";
|
|
5
|
+
import { reducedMotion } from "./motion.js";
|
|
6
|
+
// src/motion/spring.ts
|
|
7
|
+
const SpringModel = Schema.Struct({
|
|
8
|
+
value: Schema.Number,
|
|
9
|
+
velocity: Schema.Number,
|
|
10
|
+
running: Schema.Boolean
|
|
11
|
+
});
|
|
12
|
+
const SpringMessage = defineMessageUnion({
|
|
13
|
+
Started: {},
|
|
14
|
+
Stopped: {},
|
|
15
|
+
Ticked: {
|
|
16
|
+
value: Schema.Number,
|
|
17
|
+
velocity: Schema.Number
|
|
18
|
+
},
|
|
19
|
+
/** Carries the exact end value: the integrator stops near it, not on it. */
|
|
20
|
+
Finished: { value: Schema.Number }
|
|
21
|
+
});
|
|
22
|
+
const step = 16;
|
|
23
|
+
const epsilon = .001;
|
|
24
|
+
const settled = (point, to) => Math.abs(to - point.x) < epsilon && Math.abs(point.v) < epsilon;
|
|
25
|
+
/**
|
|
26
|
+
* A spring as a bundle: a number pulled toward a target with stiffness and
|
|
27
|
+
* damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the
|
|
28
|
+
* trajectory is deterministic under TestClock; the stream ends with
|
|
29
|
+
* Finished, and the value rests exactly at `to`. Underdamped springs
|
|
30
|
+
* overshoot — that is the point — but the rest value is still exact.
|
|
31
|
+
*/
|
|
32
|
+
const Spring = Bundle.make("Spring", {
|
|
33
|
+
Model: SpringModel,
|
|
34
|
+
Message: SpringMessage,
|
|
35
|
+
args: Schema.Struct({
|
|
36
|
+
from: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
37
|
+
to: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
38
|
+
stiffness: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),
|
|
39
|
+
damping: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite()))
|
|
40
|
+
}),
|
|
41
|
+
init: (args) => ({ model: {
|
|
42
|
+
value: args.from,
|
|
43
|
+
velocity: 0,
|
|
44
|
+
running: false
|
|
45
|
+
} }),
|
|
46
|
+
update: (model, message) => SpringMessage.match(message, {
|
|
47
|
+
Started: () => model.running ? { model } : { model: {
|
|
48
|
+
...model,
|
|
49
|
+
running: true
|
|
50
|
+
} },
|
|
51
|
+
Stopped: () => model.running ? { model: {
|
|
52
|
+
...model,
|
|
53
|
+
running: false
|
|
54
|
+
} } : { model },
|
|
55
|
+
Ticked: ({ value, velocity }) => ({ model: {
|
|
56
|
+
...model,
|
|
57
|
+
value,
|
|
58
|
+
velocity
|
|
59
|
+
} }),
|
|
60
|
+
Finished: ({ value }) => ({ model: {
|
|
61
|
+
...model,
|
|
62
|
+
value,
|
|
63
|
+
velocity: 0,
|
|
64
|
+
running: false
|
|
65
|
+
} })
|
|
66
|
+
}),
|
|
67
|
+
subscriptions: ({ from, to, stiffness, damping }) => Subscription.make()((entry) => ({ ticks: entry({ running: Schema.Boolean }, {
|
|
68
|
+
modelToDependencies: (model) => ({ running: model.running }),
|
|
69
|
+
dependenciesToStream: ({ running }) => {
|
|
70
|
+
if (!running) return Stream.empty;
|
|
71
|
+
const dt = step / 1e3;
|
|
72
|
+
const points = Stream.tick(step).pipe(Stream.scan(() => ({
|
|
73
|
+
x: from,
|
|
74
|
+
v: 0
|
|
75
|
+
}), (point) => {
|
|
76
|
+
const force = -stiffness * (point.x - to) - damping * point.v;
|
|
77
|
+
const v = point.v + force * dt;
|
|
78
|
+
return {
|
|
79
|
+
x: point.x + v * dt,
|
|
80
|
+
v
|
|
81
|
+
};
|
|
82
|
+
}), Stream.drop(1));
|
|
83
|
+
return Stream.unwrap(Effect.map(reducedMotion, (reduced) => reduced ? Stream.make(SpringMessage.Finished({ value: to })) : Stream.concat(points.pipe(Stream.takeUntil((point) => settled(point, to)), Stream.map((point) => SpringMessage.Ticked({
|
|
84
|
+
value: point.x,
|
|
85
|
+
velocity: point.v
|
|
86
|
+
}))), Stream.make(SpringMessage.Finished({ value: to })))));
|
|
87
|
+
}
|
|
88
|
+
}) }))
|
|
89
|
+
});
|
|
90
|
+
export { Spring, SpringMessage, SpringModel };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=spring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spring.js","names":[],"sources":["../../src/motion/spring.ts"],"sourcesContent":["import { Effect, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const SpringModel = Schema.Struct({\n value: Schema.Number,\n velocity: Schema.Number,\n running: Schema.Boolean,\n})\nexport type SpringModel = typeof SpringModel.Type\n\nexport const SpringMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Ticked: { value: Schema.Number, velocity: Schema.Number },\n /** Carries the exact end value: the integrator stops near it, not on it. */\n Finished: { value: Schema.Number },\n})\nexport type SpringMessage = typeof SpringMessage.Type\n\nconst step = 16\nconst epsilon = 0.001\n\ninterface Point {\n readonly x: number\n readonly v: number\n}\n\nconst settled = (point: Point, to: number): boolean =>\n Math.abs(to - point.x) < epsilon && Math.abs(point.v) < epsilon\n\n/**\n * A spring as a bundle: a number pulled toward a target with stiffness and\n * damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the\n * trajectory is deterministic under TestClock; the stream ends with\n * Finished, and the value rests exactly at `to`. Underdamped springs\n * overshoot — that is the point — but the rest value is still exact.\n */\nexport const Spring = Bundle.make('Spring', {\n Model: SpringModel,\n Message: SpringMessage,\n args: Schema.Struct({\n from: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n to: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n stiffness: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n damping: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n }),\n init: args => ({ model: { value: args.from, velocity: 0, running: false } }),\n update: (model, message) =>\n SpringMessage.match(message, {\n Started: () => (model.running ? { model } : { model: { ...model, running: true } }),\n Stopped: () => (model.running ? { model: { ...model, running: false } } : { model }),\n Ticked: ({ value, velocity }) => ({ model: { ...model, value, velocity } }),\n Finished: ({ value }) => ({ model: { ...model, value, velocity: 0, running: false } }),\n }),\n subscriptions: ({\n from,\n to,\n stiffness,\n damping,\n }): Subscription.Subscriptions<SpringModel, SpringMessage> =>\n Subscription.make<SpringModel, SpringMessage>()(entry => ({\n ticks: entry(\n { running: Schema.Boolean },\n {\n modelToDependencies: model => ({ running: model.running }),\n dependenciesToStream: ({ running }) => {\n if (!running) return Stream.empty\n // One integration step per tick, not per elapsed wall time: the\n // trajectory is identical on the live clock and TestClock, and a\n // stalled event loop slows the animation instead of catching up\n // all at once.\n const dt = step / 1000\n const points = Stream.tick(step).pipe(\n Stream.scan(\n (): Point => ({ x: from, v: 0 }),\n point => {\n const force = -stiffness * (point.x - to) - damping * point.v\n const v = point.v + force * dt\n return { x: point.x + v * dt, v }\n },\n ),\n // scan emits the seed (no movement yet) before the first step.\n Stream.drop(1),\n )\n return Stream.unwrap(\n Effect.map(reducedMotion, reduced =>\n reduced\n ? Stream.make(SpringMessage.Finished({ value: to }))\n : Stream.concat(\n points.pipe(\n Stream.takeUntil(point => settled(point, to)),\n Stream.map(point =>\n SpringMessage.Ticked({ value: point.x, velocity: point.v }),\n ),\n ),\n Stream.make(SpringMessage.Finished({ value: to })),\n ),\n ),\n )\n },\n },\n ),\n })),\n})\n"],"mappings":";;;;;;AAMA,MAAa,cAAc,OAAO,OAAO;CACvC,OAAO,OAAO;CACd,UAAU,OAAO;CACjB,SAAS,OAAO;AAClB,CAAC;AAGD,MAAa,gBAAgB,mBAAmB;CAC9C,SAAS,CAAC;CACV,SAAS,CAAC;CACV,QAAQ;EAAE,OAAO,OAAO;EAAQ,UAAU,OAAO;CAAO;;CAExD,UAAU,EAAE,OAAO,OAAO,OAAO;AACnC,CAAC;AAGD,MAAM,OAAO;AACb,MAAM,UAAU;AAOhB,MAAM,WAAW,OAAc,OAC7B,KAAK,IAAI,KAAK,MAAM,CAAC,IAAI,WAAW,KAAK,IAAI,MAAM,CAAC,IAAI;;;;;;;;AAS1D,MAAa,SAAS,OAAO,KAAK,UAAU;CAC1C,OAAO;CACP,SAAS;CACT,MAAM,OAAO,OAAO;EAClB,MAAM,OAAO,OAAO,KAAK,OAAO,MAAM,OAAO,SAAS,CAAC,CAAC;EACxD,IAAI,OAAO,OAAO,KAAK,OAAO,MAAM,OAAO,SAAS,CAAC,CAAC;EACtD,WAAW,OAAO,OAAO,KACvB,OAAO,MAAM,OAAO,cAAc,CAAC,CAAC,GACpC,OAAO,MAAM,OAAO,SAAS,CAAC,CAChC;EACA,SAAS,OAAO,OAAO,KACrB,OAAO,MAAM,OAAO,cAAc,CAAC,CAAC,GACpC,OAAO,MAAM,OAAO,SAAS,CAAC,CAChC;CACF,CAAC;CACD,OAAM,UAAS,EAAE,OAAO;EAAE,OAAO,KAAK;EAAM,UAAU;EAAG,SAAS;CAAM,EAAE;CAC1E,SAAS,OAAO,YACd,cAAc,MAAM,SAAS;EAC3B,eAAgB,MAAM,UAAU,EAAE,MAAM,IAAI,EAAE,OAAO;GAAE,GAAG;GAAO,SAAS;EAAK,EAAE;EACjF,eAAgB,MAAM,UAAU,EAAE,OAAO;GAAE,GAAG;GAAO,SAAS;EAAM,EAAE,IAAI,EAAE,MAAM;EAClF,SAAS,EAAE,OAAO,gBAAgB,EAAE,OAAO;GAAE,GAAG;GAAO;GAAO;EAAS,EAAE;EACzE,WAAW,EAAE,aAAa,EAAE,OAAO;GAAE,GAAG;GAAO;GAAO,UAAU;GAAG,SAAS;EAAM,EAAE;CACtF,CAAC;CACH,gBAAgB,EACd,MACA,IACA,WACA,cAEA,aAAa,KAAiC,CAAC,EAAC,WAAU,EACxD,OAAO,MACL,EAAE,SAAS,OAAO,QAAQ,GAC1B;EACE,sBAAqB,WAAU,EAAE,SAAS,MAAM,QAAQ;EACxD,uBAAuB,EAAE,cAAc;GACrC,IAAI,CAAC,SAAS,OAAO,OAAO;GAK5B,MAAM,KAAK,OAAO;GAClB,MAAM,SAAS,OAAO,KAAK,IAAI,CAAC,CAAC,KAC/B,OAAO,YACS;IAAE,GAAG;IAAM,GAAG;GAAE,KAC9B,UAAS;IACP,MAAM,QAAQ,CAAC,aAAa,MAAM,IAAI,MAAM,UAAU,MAAM;IAC5D,MAAM,IAAI,MAAM,IAAI,QAAQ;IAC5B,OAAO;KAAE,GAAG,MAAM,IAAI,IAAI;KAAI;IAAE;GAClC,CACF,GAEA,OAAO,KAAK,CAAC,CACf;GACA,OAAO,OAAO,OACZ,OAAO,IAAI,gBAAe,YACxB,UACI,OAAO,KAAK,cAAc,SAAS,EAAE,OAAO,GAAG,CAAC,CAAC,IACjD,OAAO,OACL,OAAO,KACL,OAAO,WAAU,UAAS,QAAQ,OAAO,EAAE,CAAC,GAC5C,OAAO,KAAI,UACT,cAAc,OAAO;IAAE,OAAO,MAAM;IAAG,UAAU,MAAM;GAAE,CAAC,CAC5D,CACF,GACA,OAAO,KAAK,cAAc,SAAS,EAAE,OAAO,GAAG,CAAC,CAAC,CACnD,CACN,CACF;EACF;CACF,CACF,EACF,EAAE;AACN,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle } from "foldkit-bundle";
|
|
3
|
+
export declare const TweenModel: Schema.Struct<{
|
|
4
|
+
readonly value: Schema.Number;
|
|
5
|
+
readonly running: Schema.Boolean;
|
|
6
|
+
}>;
|
|
7
|
+
export type TweenModel = typeof TweenModel.Type;
|
|
8
|
+
export declare const TweenMessage: import("foldkit/message").MessageUnion<{
|
|
9
|
+
readonly Started: {};
|
|
10
|
+
readonly Stopped: {};
|
|
11
|
+
readonly Ticked: {
|
|
12
|
+
value: Schema.Number;
|
|
13
|
+
};
|
|
14
|
+
/** Carries the exact end value: the stream may stop on either side of it. */
|
|
15
|
+
readonly Finished: {
|
|
16
|
+
value: Schema.Number;
|
|
17
|
+
};
|
|
18
|
+
}>;
|
|
19
|
+
export type TweenMessage = typeof TweenMessage.Type;
|
|
20
|
+
/**
|
|
21
|
+
* A tween as a bundle: a number animated from one value to another over a
|
|
22
|
+
* duration, in the Model. Progress comes from Effect's clock, so tests drive
|
|
23
|
+
* it with TestClock; the stream ends with Finished, and the value rests at
|
|
24
|
+
* `to`. Linear interpolation only: easing curves stay the application's job.
|
|
25
|
+
*/
|
|
26
|
+
export declare const Tween: Bundle.Bundle<"Tween", {
|
|
27
|
+
readonly from: number;
|
|
28
|
+
readonly to: number;
|
|
29
|
+
readonly ms: number;
|
|
30
|
+
}, {
|
|
31
|
+
readonly value: number;
|
|
32
|
+
readonly running: boolean;
|
|
33
|
+
}, {
|
|
34
|
+
readonly _tag: "Started";
|
|
35
|
+
} | {
|
|
36
|
+
readonly _tag: "Stopped";
|
|
37
|
+
} | {
|
|
38
|
+
readonly _tag: "Finished";
|
|
39
|
+
readonly value: number;
|
|
40
|
+
} | {
|
|
41
|
+
readonly _tag: "Ticked";
|
|
42
|
+
readonly value: number;
|
|
43
|
+
}, never, never, never, void, {}, {}>;
|
|
44
|
+
//# sourceMappingURL=tween.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tween.d.ts","names":[],"sources":["../../src/motion/tween.ts"],"mappings":";;qBAMa,YAAU,OAAA;;;;YACX,oBAAoB,WAAW;qBAE9B,wCAAY;;;;IAGb,OAAK,OAAA;;;;IAEH,OAAK,OAAA;;;YAEP,sBAAsB,aAAa;;;;;;;qBAQlC,OAAK,OAAA"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Clock, Effect, Schema, Stream } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import * as Subscription from "foldkit/subscription";
|
|
4
|
+
import { Bundle } from "foldkit-bundle";
|
|
5
|
+
import { reducedMotion } from "./motion.js";
|
|
6
|
+
// src/motion/tween.ts
|
|
7
|
+
const TweenModel = Schema.Struct({
|
|
8
|
+
value: Schema.Number,
|
|
9
|
+
running: Schema.Boolean
|
|
10
|
+
});
|
|
11
|
+
const TweenMessage = defineMessageUnion({
|
|
12
|
+
Started: {},
|
|
13
|
+
Stopped: {},
|
|
14
|
+
Ticked: { value: Schema.Number },
|
|
15
|
+
/** Carries the exact end value: the stream may stop on either side of it. */
|
|
16
|
+
Finished: { value: Schema.Number }
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* A tween as a bundle: a number animated from one value to another over a
|
|
20
|
+
* duration, in the Model. Progress comes from Effect's clock, so tests drive
|
|
21
|
+
* it with TestClock; the stream ends with Finished, and the value rests at
|
|
22
|
+
* `to`. Linear interpolation only: easing curves stay the application's job.
|
|
23
|
+
*/
|
|
24
|
+
const Tween = Bundle.make("Tween", {
|
|
25
|
+
Model: TweenModel,
|
|
26
|
+
Message: TweenMessage,
|
|
27
|
+
args: Schema.Struct({
|
|
28
|
+
from: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
29
|
+
to: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
30
|
+
ms: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite()))
|
|
31
|
+
}),
|
|
32
|
+
init: (args) => ({ model: {
|
|
33
|
+
value: args.from,
|
|
34
|
+
running: false
|
|
35
|
+
} }),
|
|
36
|
+
update: (model, message) => TweenMessage.match(message, {
|
|
37
|
+
Started: () => model.running ? { model } : { model: {
|
|
38
|
+
...model,
|
|
39
|
+
running: true
|
|
40
|
+
} },
|
|
41
|
+
Stopped: () => model.running ? { model: {
|
|
42
|
+
...model,
|
|
43
|
+
running: false
|
|
44
|
+
} } : { model },
|
|
45
|
+
Ticked: ({ value }) => value === model.value ? { model } : { model: {
|
|
46
|
+
...model,
|
|
47
|
+
value
|
|
48
|
+
} },
|
|
49
|
+
Finished: ({ value }) => ({ model: {
|
|
50
|
+
...model,
|
|
51
|
+
value,
|
|
52
|
+
running: false
|
|
53
|
+
} })
|
|
54
|
+
}),
|
|
55
|
+
subscriptions: ({ from, to, ms }) => Subscription.make()((entry) => ({ ticks: entry({ running: Schema.Boolean }, {
|
|
56
|
+
modelToDependencies: (model) => ({ running: model.running }),
|
|
57
|
+
dependenciesToStream: ({ running }) => running ? Stream.unwrap(Effect.flatMap(reducedMotion, (reduced) => reduced ? Effect.succeed(Stream.make(TweenMessage.Finished({ value: to }))) : Clock.currentTimeMillis.pipe(Effect.map((startedAt) => {
|
|
58
|
+
const progress = Stream.tick(16).pipe(Stream.mapEffect(() => Clock.currentTimeMillis), Stream.map((now) => Math.min(1, (now - startedAt) / ms)));
|
|
59
|
+
return Stream.concat(progress.pipe(Stream.takeUntil((done) => done >= 1), Stream.map((done) => TweenMessage.Ticked({ value: from + (to - from) * done }))), Stream.make(TweenMessage.Finished({ value: to })));
|
|
60
|
+
})))) : Stream.empty
|
|
61
|
+
}) }))
|
|
62
|
+
});
|
|
63
|
+
export { Tween, TweenMessage, TweenModel };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=tween.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tween.js","names":[],"sources":["../../src/motion/tween.ts"],"sourcesContent":["import { Clock, Effect, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport { Bundle } from 'foldkit-bundle'\nimport { reducedMotion } from './motion.js'\n\nexport const TweenModel = Schema.Struct({ value: Schema.Number, running: Schema.Boolean })\nexport type TweenModel = typeof TweenModel.Type\n\nexport const TweenMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Ticked: { value: Schema.Number },\n /** Carries the exact end value: the stream may stop on either side of it. */\n Finished: { value: Schema.Number },\n})\nexport type TweenMessage = typeof TweenMessage.Type\n\n/**\n * A tween as a bundle: a number animated from one value to another over a\n * duration, in the Model. Progress comes from Effect's clock, so tests drive\n * it with TestClock; the stream ends with Finished, and the value rests at\n * `to`. Linear interpolation only: easing curves stay the application's job.\n */\nexport const Tween = Bundle.make('Tween', {\n Model: TweenModel,\n Message: TweenMessage,\n args: Schema.Struct({\n from: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n to: Schema.Number.pipe(Schema.check(Schema.isFinite())),\n ms: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),\n }),\n init: args => ({ model: { value: args.from, running: false } }),\n update: (model, message) =>\n TweenMessage.match(message, {\n Started: () => (model.running ? { model } : { model: { ...model, running: true } }),\n Stopped: () => (model.running ? { model: { ...model, running: false } } : { model }),\n // The first tick lands at the start, on the value the Model already holds.\n Ticked: ({ value }) => (value === model.value ? { model } : { model: { ...model, value } }),\n Finished: ({ value }) => ({ model: { ...model, value, running: false } }),\n }),\n subscriptions: ({ from, to, ms }): Subscription.Subscriptions<TweenModel, TweenMessage> =>\n Subscription.make<TweenModel, TweenMessage>()(entry => ({\n ticks: entry(\n { running: Schema.Boolean },\n {\n modelToDependencies: model => ({ running: model.running }),\n dependenciesToStream: ({ running }) =>\n running\n ? Stream.unwrap(\n Effect.flatMap(reducedMotion, reduced =>\n reduced\n ? Effect.succeed(Stream.make(TweenMessage.Finished({ value: to })))\n : Clock.currentTimeMillis.pipe(\n Effect.map(startedAt => {\n const progress = Stream.tick(16).pipe(\n Stream.mapEffect(() => Clock.currentTimeMillis),\n Stream.map(now => Math.min(1, (now - startedAt) / ms)),\n )\n return Stream.concat(\n progress.pipe(\n Stream.takeUntil(done => done >= 1),\n Stream.map(done =>\n TweenMessage.Ticked({ value: from + (to - from) * done }),\n ),\n ),\n Stream.make(TweenMessage.Finished({ value: to })),\n )\n }),\n ),\n ),\n )\n : Stream.empty,\n },\n ),\n })),\n})\n"],"mappings":";;;;;;AAMA,MAAa,aAAa,OAAO,OAAO;CAAE,OAAO,OAAO;CAAQ,SAAS,OAAO;AAAQ,CAAC;AAGzF,MAAa,eAAe,mBAAmB;CAC7C,SAAS,CAAC;CACV,SAAS,CAAC;CACV,QAAQ,EAAE,OAAO,OAAO,OAAO;;CAE/B,UAAU,EAAE,OAAO,OAAO,OAAO;AACnC,CAAC;;;;;;;AASD,MAAa,QAAQ,OAAO,KAAK,SAAS;CACxC,OAAO;CACP,SAAS;CACT,MAAM,OAAO,OAAO;EAClB,MAAM,OAAO,OAAO,KAAK,OAAO,MAAM,OAAO,SAAS,CAAC,CAAC;EACxD,IAAI,OAAO,OAAO,KAAK,OAAO,MAAM,OAAO,SAAS,CAAC,CAAC;EACtD,IAAI,OAAO,OAAO,KAAK,OAAO,MAAM,OAAO,cAAc,CAAC,CAAC,GAAG,OAAO,MAAM,OAAO,SAAS,CAAC,CAAC;CAC/F,CAAC;CACD,OAAM,UAAS,EAAE,OAAO;EAAE,OAAO,KAAK;EAAM,SAAS;CAAM,EAAE;CAC7D,SAAS,OAAO,YACd,aAAa,MAAM,SAAS;EAC1B,eAAgB,MAAM,UAAU,EAAE,MAAM,IAAI,EAAE,OAAO;GAAE,GAAG;GAAO,SAAS;EAAK,EAAE;EACjF,eAAgB,MAAM,UAAU,EAAE,OAAO;GAAE,GAAG;GAAO,SAAS;EAAM,EAAE,IAAI,EAAE,MAAM;EAElF,SAAS,EAAE,YAAa,UAAU,MAAM,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO;GAAE,GAAG;GAAO;EAAM,EAAE;EACzF,WAAW,EAAE,aAAa,EAAE,OAAO;GAAE,GAAG;GAAO;GAAO,SAAS;EAAM,EAAE;CACzE,CAAC;CACH,gBAAgB,EAAE,MAAM,IAAI,SAC1B,aAAa,KAA+B,CAAC,EAAC,WAAU,EACtD,OAAO,MACL,EAAE,SAAS,OAAO,QAAQ,GAC1B;EACE,sBAAqB,WAAU,EAAE,SAAS,MAAM,QAAQ;EACxD,uBAAuB,EAAE,cACvB,UACI,OAAO,OACL,OAAO,QAAQ,gBAAe,YAC5B,UACI,OAAO,QAAQ,OAAO,KAAK,aAAa,SAAS,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,IAChE,MAAM,kBAAkB,KACtB,OAAO,KAAI,cAAa;GACtB,MAAM,WAAW,OAAO,KAAK,EAAE,CAAC,CAAC,KAC/B,OAAO,gBAAgB,MAAM,iBAAiB,GAC9C,OAAO,KAAI,QAAO,KAAK,IAAI,IAAI,MAAM,aAAa,EAAE,CAAC,CACvD;GACA,OAAO,OAAO,OACZ,SAAS,KACP,OAAO,WAAU,SAAQ,QAAQ,CAAC,GAClC,OAAO,KAAI,SACT,aAAa,OAAO,EAAE,OAAO,QAAQ,KAAK,QAAQ,KAAK,CAAC,CAC1D,CACF,GACA,OAAO,KAAK,aAAa,SAAS,EAAE,OAAO,GAAG,CAAC,CAAC,CAClD;EACF,CAAC,CACH,CACN,CACF,IACA,OAAO;CACf,CACF,EACF,EAAE;AACN,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Schema, Stream } from "effect";
|
|
2
|
+
import { Command } from "foldkit/command";
|
|
3
|
+
export declare const BroadcastMessage: import("foldkit/message").MessageUnion<{
|
|
4
|
+
/** Notifies without storing: project the payload into your own field to keep it. */
|
|
5
|
+
readonly Received: {
|
|
6
|
+
data: Schema.Unknown;
|
|
7
|
+
};
|
|
8
|
+
readonly Posted: {};
|
|
9
|
+
readonly BroadcastFailed: {
|
|
10
|
+
message: Schema.String;
|
|
11
|
+
};
|
|
12
|
+
}>;
|
|
13
|
+
export type BroadcastMessage = typeof BroadcastMessage.Type;
|
|
14
|
+
/** The slice of a BroadcastChannel this module needs; satisfied by the platform class. */
|
|
15
|
+
export interface BroadcastChannelHandle {
|
|
16
|
+
postMessage(data: unknown): void;
|
|
17
|
+
close(): void;
|
|
18
|
+
onmessage: ((event: {
|
|
19
|
+
readonly data: unknown;
|
|
20
|
+
}) => void) | null;
|
|
21
|
+
}
|
|
22
|
+
type ChannelFactory = (name: string) => BroadcastChannelHandle;
|
|
23
|
+
/**
|
|
24
|
+
* Every post to `name` from another channel, as Messages. Without a
|
|
25
|
+
* BroadcastChannel API the stream is empty instead of throwing; closing the
|
|
26
|
+
* stream closes the channel.
|
|
27
|
+
*/
|
|
28
|
+
export declare const broadcastMessages: (name: string,
|
|
29
|
+
/** Read lazily so tests can substitute a double, like the SSE bundle. */
|
|
30
|
+
create?: ChannelFactory | undefined) => Stream.Stream<BroadcastMessage>;
|
|
31
|
+
/**
|
|
32
|
+
* Posts one message to `name`. Opens a channel, posts, and closes it: the
|
|
33
|
+
* send is one-shot, so it is a Command, not a resource. Without a
|
|
34
|
+
* BroadcastChannel API — or when the post throws — it yields
|
|
35
|
+
* `BroadcastFailed` instead of throwing.
|
|
36
|
+
*/
|
|
37
|
+
export declare const postBroadcast: (name: string, data: unknown, create?: ChannelFactory | undefined) => Command<BroadcastMessage, never, never>;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=broadcast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broadcast.d.ts","names":[],"sources":["../../src/net/broadcast.ts"],"mappings":";;qBAaa,4CAAgB;;;IAEf,MAAI,OAAA;;;;IAEG,SAAO,OAAA;;;YAEhB,0BAA0B,iBAAiB;;iBAGtC;EACf,YAAY;EACZ;EACA,aAAa;aAAkB;;;KAG5B,kBAAkB,iBAAiB;;;;;;qBAmB3B,oBAAiB;;AAGnB,SAAA,+BACR,OAAO,OAAO;;;;;;;qBAyBJ,gBAAa,cACZ,eACC,SACJ,+BACR,QAAQ"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Effect, Queue, Schema, Stream } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
// src/net/broadcast.ts
|
|
4
|
+
/**
|
|
5
|
+
* Cross-tab messaging as an entry plus a Command — not a bundle, because the
|
|
6
|
+
* channel owns no state: the parent maps `Received` into its own Message and
|
|
7
|
+
* stores whatever it keeps. One assembly may open the same name twice (two
|
|
8
|
+
* listeners); share the entry instead. By spec a post reaches every channel
|
|
9
|
+
* of its name but the one it was posted on, and `postBroadcast` posts on a
|
|
10
|
+
* channel of its own: a tab's `broadcastMessages` hears that tab's posts.
|
|
11
|
+
* Name the sender in `data` to tell them apart.
|
|
12
|
+
*/
|
|
13
|
+
const BroadcastMessage = defineMessageUnion({
|
|
14
|
+
/** Notifies without storing: project the payload into your own field to keep it. */
|
|
15
|
+
Received: { data: Schema.Unknown },
|
|
16
|
+
Posted: {},
|
|
17
|
+
BroadcastFailed: { message: Schema.String }
|
|
18
|
+
});
|
|
19
|
+
const failMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
20
|
+
const createChannel = (factory, name) => {
|
|
21
|
+
if (factory !== void 0) return factory(name);
|
|
22
|
+
if (typeof BroadcastChannel === "undefined") return null;
|
|
23
|
+
return new BroadcastChannel(name);
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Every post to `name` from another channel, as Messages. Without a
|
|
27
|
+
* BroadcastChannel API the stream is empty instead of throwing; closing the
|
|
28
|
+
* stream closes the channel.
|
|
29
|
+
*/
|
|
30
|
+
const broadcastMessages = (name, create) => Stream.unwrap(Effect.gen(function* () {
|
|
31
|
+
const channel = createChannel(create, name);
|
|
32
|
+
if (channel === null) return Stream.empty;
|
|
33
|
+
const queue = yield* Queue.unbounded();
|
|
34
|
+
channel.onmessage = (event) => {
|
|
35
|
+
Effect.runFork(Queue.offer(queue, BroadcastMessage.Received({ data: event.data })));
|
|
36
|
+
};
|
|
37
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
38
|
+
channel.onmessage = null;
|
|
39
|
+
channel.close();
|
|
40
|
+
}));
|
|
41
|
+
return Stream.fromQueue(queue);
|
|
42
|
+
}));
|
|
43
|
+
/**
|
|
44
|
+
* Posts one message to `name`. Opens a channel, posts, and closes it: the
|
|
45
|
+
* send is one-shot, so it is a Command, not a resource. Without a
|
|
46
|
+
* BroadcastChannel API — or when the post throws — it yields
|
|
47
|
+
* `BroadcastFailed` instead of throwing.
|
|
48
|
+
*/
|
|
49
|
+
const postBroadcast = (name, data, create) => ({
|
|
50
|
+
name: "Broadcast.post",
|
|
51
|
+
args: {
|
|
52
|
+
name,
|
|
53
|
+
data
|
|
54
|
+
},
|
|
55
|
+
effect: Effect.matchEffect(Effect.try({
|
|
56
|
+
try: () => {
|
|
57
|
+
const channel = createChannel(create, name);
|
|
58
|
+
if (channel === null) throw new Error("broadcast is unavailable");
|
|
59
|
+
try {
|
|
60
|
+
channel.postMessage(data);
|
|
61
|
+
} finally {
|
|
62
|
+
channel.close();
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
catch: failMessage
|
|
66
|
+
}), {
|
|
67
|
+
onFailure: (message) => Effect.succeed(BroadcastMessage.BroadcastFailed({ message })),
|
|
68
|
+
onSuccess: () => Effect.succeed(BroadcastMessage.Posted())
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
export { BroadcastMessage, broadcastMessages, postBroadcast };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=broadcast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broadcast.js","names":[],"sources":["../../src/net/broadcast.ts"],"sourcesContent":["/**\n * Cross-tab messaging as an entry plus a Command — not a bundle, because the\n * channel owns no state: the parent maps `Received` into its own Message and\n * stores whatever it keeps. One assembly may open the same name twice (two\n * listeners); share the entry instead. By spec a post reaches every channel\n * of its name but the one it was posted on, and `postBroadcast` posts on a\n * channel of its own: a tab's `broadcastMessages` hears that tab's posts.\n * Name the sender in `data` to tell them apart.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport type { Command } from 'foldkit/command'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const BroadcastMessage = defineMessageUnion({\n /** Notifies without storing: project the payload into your own field to keep it. */\n Received: { data: Schema.Unknown },\n Posted: {},\n BroadcastFailed: { message: Schema.String },\n})\nexport type BroadcastMessage = typeof BroadcastMessage.Type\n\n/** The slice of a BroadcastChannel this module needs; satisfied by the platform class. */\nexport interface BroadcastChannelHandle {\n postMessage(data: unknown): void\n close(): void\n onmessage: ((event: { readonly data: unknown }) => void) | null\n}\n\ntype ChannelFactory = (name: string) => BroadcastChannelHandle\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst createChannel = (\n factory: ChannelFactory | undefined,\n name: string,\n): BroadcastChannelHandle | null => {\n if (factory !== undefined) return factory(name)\n if (typeof BroadcastChannel === 'undefined') return null\n return new BroadcastChannel(name) as unknown as BroadcastChannelHandle\n}\n\n/**\n * Every post to `name` from another channel, as Messages. Without a\n * BroadcastChannel API the stream is empty instead of throwing; closing the\n * stream closes the channel.\n */\nexport const broadcastMessages = (\n name: string,\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n create?: ChannelFactory | undefined,\n): Stream.Stream<BroadcastMessage> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const channel = createChannel(create, name)\n if (channel === null) return Stream.empty\n const queue = yield* Queue.unbounded<BroadcastMessage>()\n channel.onmessage = event => {\n Effect.runFork(Queue.offer(queue, BroadcastMessage.Received({ data: event.data })))\n }\n yield* Effect.addFinalizer(() =>\n Effect.sync(() => {\n channel.onmessage = null\n channel.close()\n }),\n )\n return Stream.fromQueue(queue)\n }),\n )\n\n/**\n * Posts one message to `name`. Opens a channel, posts, and closes it: the\n * send is one-shot, so it is a Command, not a resource. Without a\n * BroadcastChannel API — or when the post throws — it yields\n * `BroadcastFailed` instead of throwing.\n */\nexport const postBroadcast = (\n name: string,\n data: unknown,\n create?: ChannelFactory | undefined,\n): Command<BroadcastMessage, never, never> => ({\n name: 'Broadcast.post',\n args: { name, data },\n // One path for every failure: no factory and no platform API, or a post\n // that throws (a closed channel) all become BroadcastFailed.\n effect: Effect.matchEffect(\n Effect.try({\n try: () => {\n const channel = createChannel(create, name)\n if (channel === null) throw new Error('broadcast is unavailable')\n try {\n channel.postMessage(data)\n } finally {\n channel.close()\n }\n },\n catch: failMessage,\n }),\n {\n onFailure: message => Effect.succeed(BroadcastMessage.BroadcastFailed({ message })),\n onSuccess: () => Effect.succeed(BroadcastMessage.Posted()),\n },\n ),\n})\n"],"mappings":";;;;;;;;;;;;AAaA,MAAa,mBAAmB,mBAAmB;;CAEjD,UAAU,EAAE,MAAM,OAAO,QAAQ;CACjC,QAAQ,CAAC;CACT,iBAAiB,EAAE,SAAS,OAAO,OAAO;AAC5C,CAAC;AAYD,MAAM,eAAe,UACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAEvD,MAAM,iBACJ,SACA,SACkC;CAClC,IAAI,YAAY,KAAA,GAAW,OAAO,QAAQ,IAAI;CAC9C,IAAI,OAAO,qBAAqB,aAAa,OAAO;CACpD,OAAO,IAAI,iBAAiB,IAAI;AAClC;;;;;;AAOA,MAAa,qBACX,MAEA,WAEA,OAAO,OACL,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,cAAc,QAAQ,IAAI;CAC1C,IAAI,YAAY,MAAM,OAAO,OAAO;CACpC,MAAM,QAAQ,OAAO,MAAM,UAA4B;CACvD,QAAQ,aAAY,UAAS;EAC3B,OAAO,QAAQ,MAAM,MAAM,OAAO,iBAAiB,SAAS,EAAE,MAAM,MAAM,KAAK,CAAC,CAAC,CAAC;CACpF;CACA,OAAO,OAAO,mBACZ,OAAO,WAAW;EAChB,QAAQ,YAAY;EACpB,QAAQ,MAAM;CAChB,CAAC,CACH;CACA,OAAO,OAAO,UAAU,KAAK;AAC/B,CAAC,CACH;;;;;;;AAQF,MAAa,iBACX,MACA,MACA,YAC6C;CAC7C,MAAM;CACN,MAAM;EAAE;EAAM;CAAK;CAGnB,QAAQ,OAAO,YACb,OAAO,IAAI;EACT,WAAW;GACT,MAAM,UAAU,cAAc,QAAQ,IAAI;GAC1C,IAAI,YAAY,MAAM,MAAM,IAAI,MAAM,0BAA0B;GAChE,IAAI;IACF,QAAQ,YAAY,IAAI;GAC1B,UAAU;IACR,QAAQ,MAAM;GAChB;EACF;EACA,OAAO;CACT,CAAC,GACD;EACE,YAAW,YAAW,OAAO,QAAQ,iBAAiB,gBAAgB,EAAE,QAAQ,CAAC,CAAC;EAClF,iBAAiB,OAAO,QAAQ,iBAAiB,OAAO,CAAC;CAC3D,CACF;AACF"}
|