foldkit-primitives 0.15.3 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/device/fullscreen.d.ts +28 -0
- package/dist/device/fullscreen.d.ts.map +1 -0
- package/dist/device/fullscreen.js +69 -0
- package/dist/device/fullscreen.js.map +1 -0
- package/dist/device/geolocation.d.ts +56 -0
- package/dist/device/geolocation.d.ts.map +1 -0
- package/dist/device/geolocation.js +87 -0
- package/dist/device/geolocation.js.map +1 -0
- package/dist/device/index.d.ts +5 -0
- package/dist/device/index.js +6 -0
- package/dist/device/media-devices.d.ts +88 -0
- package/dist/device/media-devices.d.ts.map +1 -0
- package/dist/device/media-devices.js +117 -0
- package/dist/device/media-devices.js.map +1 -0
- package/dist/device/media-stream.d.ts +93 -0
- package/dist/device/media-stream.d.ts.map +1 -0
- package/dist/device/media-stream.js +106 -0
- package/dist/device/media-stream.js.map +1 -0
- package/dist/device/permissions.d.ts +106 -0
- package/dist/device/permissions.d.ts.map +1 -0
- package/dist/device/permissions.js +143 -0
- package/dist/device/permissions.js.map +1 -0
- package/dist/dom/autofocus.d.ts +15 -0
- package/dist/dom/autofocus.d.ts.map +1 -0
- package/dist/dom/autofocus.js +22 -0
- package/dist/dom/autofocus.js.map +1 -0
- package/dist/dom/clipboard.d.ts +30 -0
- package/dist/dom/clipboard.d.ts.map +1 -0
- package/dist/dom/clipboard.js +50 -0
- package/dist/dom/clipboard.js.map +1 -0
- package/dist/dom/editable-text.d.ts +72 -0
- package/dist/dom/editable-text.d.ts.map +1 -0
- package/dist/dom/editable-text.js +232 -0
- package/dist/dom/editable-text.js.map +1 -0
- package/dist/dom/fluid-hover.d.ts +93 -0
- package/dist/dom/fluid-hover.d.ts.map +1 -0
- package/dist/dom/fluid-hover.js +294 -0
- package/dist/dom/fluid-hover.js.map +1 -0
- package/dist/dom/focus-scope.d.ts +21 -0
- package/dist/dom/focus-scope.d.ts.map +1 -0
- package/dist/dom/focus-scope.js +93 -0
- package/dist/dom/focus-scope.js.map +1 -0
- package/dist/dom/follow-tab-stop.d.ts +11 -0
- package/dist/dom/follow-tab-stop.d.ts.map +1 -0
- package/dist/dom/follow-tab-stop.js +54 -0
- package/dist/dom/follow-tab-stop.js.map +1 -0
- package/dist/dom/image-decode.d.ts +27 -0
- package/dist/dom/image-decode.d.ts.map +1 -0
- package/dist/dom/image-decode.js +50 -0
- package/dist/dom/image-decode.js.map +1 -0
- package/dist/dom/index.d.ts +24 -0
- package/dist/dom/index.js +25 -0
- package/dist/dom/input-mask.d.ts +35 -0
- package/dist/dom/input-mask.d.ts.map +1 -0
- package/dist/dom/input-mask.js +90 -0
- package/dist/dom/input-mask.js.map +1 -0
- package/dist/dom/keep-in-view.d.ts +16 -0
- package/dist/dom/keep-in-view.d.ts.map +1 -0
- package/dist/dom/keep-in-view.js +38 -0
- package/dist/dom/keep-in-view.js.map +1 -0
- package/dist/dom/keyboard-inset.d.ts +29 -0
- package/dist/dom/keyboard-inset.d.ts.map +1 -0
- package/dist/dom/keyboard-inset.js +45 -0
- package/dist/dom/keyboard-inset.js.map +1 -0
- package/dist/dom/layers.d.ts +6 -0
- package/dist/dom/layers.d.ts.map +1 -0
- package/dist/dom/layers.js +35 -0
- package/dist/dom/layers.js.map +1 -0
- package/dist/dom/measure.d.ts +42 -0
- package/dist/dom/measure.d.ts.map +1 -0
- package/dist/dom/measure.js +123 -0
- package/dist/dom/measure.js.map +1 -0
- package/dist/dom/move.d.ts +45 -0
- package/dist/dom/move.d.ts.map +1 -0
- package/dist/dom/move.js +96 -0
- package/dist/dom/move.js.map +1 -0
- package/dist/dom/pointed-links.d.ts +13 -0
- package/dist/dom/pointed-links.d.ts.map +1 -0
- package/dist/dom/pointed-links.js +59 -0
- package/dist/dom/pointed-links.js.map +1 -0
- package/dist/dom/pointer-drag.d.ts +132 -0
- package/dist/dom/pointer-drag.d.ts.map +1 -0
- package/dist/dom/pointer-drag.js +307 -0
- package/dist/dom/pointer-drag.js.map +1 -0
- package/dist/dom/pointer-position.d.ts +133 -0
- package/dist/dom/pointer-position.d.ts.map +1 -0
- package/dist/dom/pointer-position.js +173 -0
- package/dist/dom/pointer-position.js.map +1 -0
- package/dist/dom/script-loader.d.ts +12 -0
- package/dist/dom/script-loader.d.ts.map +1 -0
- package/dist/dom/script-loader.js +54 -0
- package/dist/dom/script-loader.js.map +1 -0
- package/dist/dom/scroll-bars.d.ts +52 -0
- package/dist/dom/scroll-bars.d.ts.map +1 -0
- package/dist/dom/scroll-bars.js +259 -0
- package/dist/dom/scroll-bars.js.map +1 -0
- package/dist/dom/scroll-settle.d.ts +33 -0
- package/dist/dom/scroll-settle.d.ts.map +1 -0
- package/dist/dom/scroll-settle.js +84 -0
- package/dist/dom/scroll-settle.js.map +1 -0
- package/dist/dom/scroll.d.ts +21 -0
- package/dist/dom/scroll.d.ts.map +1 -0
- package/dist/dom/scroll.js +141 -0
- package/dist/dom/scroll.js.map +1 -0
- package/dist/dom/share.d.ts +19 -0
- package/dist/dom/share.d.ts.map +1 -0
- package/dist/dom/share.js +41 -0
- package/dist/dom/share.js.map +1 -0
- package/dist/dom/swipe.d.ts +121 -0
- package/dist/dom/swipe.d.ts.map +1 -0
- package/dist/dom/swipe.js +291 -0
- package/dist/dom/swipe.js.map +1 -0
- package/dist/dom/targets.d.ts +45 -0
- package/dist/dom/targets.d.ts.map +1 -0
- package/dist/dom/targets.js +70 -0
- package/dist/dom/targets.js.map +1 -0
- package/dist/dom/track-box.d.ts +61 -0
- package/dist/dom/track-box.d.ts.map +1 -0
- package/dist/dom/track-box.js +131 -0
- package/dist/dom/track-box.js.map +1 -0
- package/dist/events/active-element.d.ts +15 -0
- package/dist/events/active-element.d.ts.map +1 -0
- package/dist/events/active-element.js +33 -0
- package/dist/events/active-element.js.map +1 -0
- package/dist/events/hotkeys.d.ts +22 -0
- package/dist/events/hotkeys.d.ts.map +1 -0
- package/dist/events/hotkeys.js +56 -0
- package/dist/events/hotkeys.js.map +1 -0
- package/dist/events/idle.d.ts +29 -0
- package/dist/events/idle.d.ts.map +1 -0
- package/dist/events/idle.js +51 -0
- package/dist/events/idle.js.map +1 -0
- package/dist/events/index.d.ts +9 -0
- package/dist/events/index.js +10 -0
- package/dist/{input-modality-BY-bSrEc.d.mts → events/input-modality.d.ts} +9 -12
- package/dist/events/input-modality.d.ts.map +1 -0
- package/dist/{input-modality-BXD383jg.mjs → events/input-modality.js} +3 -4
- package/dist/events/input-modality.js.map +1 -0
- package/dist/events/keyboard.d.ts +30 -0
- package/dist/events/keyboard.d.ts.map +1 -0
- package/dist/events/keyboard.js +55 -0
- package/dist/events/keyboard.js.map +1 -0
- package/dist/events/pointer.d.ts +15 -0
- package/dist/events/pointer.d.ts.map +1 -0
- package/dist/events/pointer.js +28 -0
- package/dist/events/pointer.js.map +1 -0
- package/dist/events/scroll.d.ts +16 -0
- package/dist/events/scroll.d.ts.map +1 -0
- package/dist/events/scroll.js +29 -0
- package/dist/events/scroll.js.map +1 -0
- package/dist/events/visibility.d.ts +25 -0
- package/dist/events/visibility.d.ts.map +1 -0
- package/dist/events/visibility.js +28 -0
- package/dist/events/visibility.js.map +1 -0
- package/dist/events/window-size.d.ts +30 -0
- package/dist/events/window-size.d.ts.map +1 -0
- package/dist/events/window-size.js +63 -0
- package/dist/events/window-size.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +10 -0
- package/dist/interaction/bundle-behavior.d.ts +37 -0
- package/dist/interaction/bundle-behavior.d.ts.map +1 -0
- package/dist/interaction/bundle-behavior.js +34 -0
- package/dist/interaction/bundle-behavior.js.map +1 -0
- package/dist/interaction/dismiss-layer.d.ts +100 -0
- package/dist/interaction/dismiss-layer.d.ts.map +1 -0
- package/dist/interaction/dismiss-layer.js +140 -0
- package/dist/interaction/dismiss-layer.js.map +1 -0
- package/dist/interaction/editable-text.d.ts +13 -0
- package/dist/interaction/editable-text.d.ts.map +1 -0
- package/dist/interaction/editable-text.js +17 -0
- package/dist/interaction/editable-text.js.map +1 -0
- package/dist/interaction/focus-scope.d.ts +21 -0
- package/dist/interaction/focus-scope.d.ts.map +1 -0
- package/dist/interaction/focus-scope.js +25 -0
- package/dist/interaction/focus-scope.js.map +1 -0
- package/dist/interaction/focus-visible.d.ts +9 -0
- package/dist/interaction/focus-visible.d.ts.map +1 -0
- package/dist/interaction/focus-visible.js +10 -0
- package/dist/interaction/focus-visible.js.map +1 -0
- package/dist/interaction/grid-navigation.d.ts +73 -0
- package/dist/interaction/grid-navigation.d.ts.map +1 -0
- package/dist/interaction/grid-navigation.js +137 -0
- package/dist/interaction/grid-navigation.js.map +1 -0
- package/dist/interaction/index.d.ts +21 -0
- package/dist/interaction/index.js +21 -0
- package/dist/interaction/layers.d.ts +12 -0
- package/dist/interaction/layers.d.ts.map +1 -0
- package/dist/interaction/layers.js +14 -0
- package/dist/interaction/layers.js.map +1 -0
- package/dist/interaction/list-navigation.d.ts +105 -0
- package/dist/interaction/list-navigation.d.ts.map +1 -0
- package/dist/interaction/list-navigation.js +168 -0
- package/dist/interaction/list-navigation.js.map +1 -0
- package/dist/interaction/live-announce.d.ts +70 -0
- package/dist/interaction/live-announce.d.ts.map +1 -0
- package/dist/interaction/live-announce.js +115 -0
- package/dist/interaction/live-announce.js.map +1 -0
- package/dist/interaction/long-press.d.ts +102 -0
- package/dist/interaction/long-press.d.ts.map +1 -0
- package/dist/interaction/long-press.js +115 -0
- package/dist/interaction/long-press.js.map +1 -0
- package/dist/interaction/move.d.ts +10 -0
- package/dist/interaction/move.d.ts.map +1 -0
- package/dist/interaction/move.js +17 -0
- package/dist/interaction/move.js.map +1 -0
- package/dist/interaction/overlay.d.ts +45 -0
- package/dist/interaction/overlay.d.ts.map +1 -0
- package/dist/interaction/overlay.js +57 -0
- package/dist/interaction/overlay.js.map +1 -0
- package/dist/interaction/placing.d.ts +86 -0
- package/dist/interaction/placing.d.ts.map +1 -0
- package/dist/interaction/placing.js +239 -0
- package/dist/interaction/placing.js.map +1 -0
- package/dist/interaction/pointer-drag.d.ts +23 -0
- package/dist/interaction/pointer-drag.d.ts.map +1 -0
- package/dist/interaction/pointer-drag.js +21 -0
- package/dist/interaction/pointer-drag.js.map +1 -0
- package/dist/interaction/press.d.ts +152 -0
- package/dist/interaction/press.d.ts.map +1 -0
- package/dist/interaction/press.js +229 -0
- package/dist/interaction/press.js.map +1 -0
- package/dist/interaction/roving-tabindex.d.ts +85 -0
- package/dist/interaction/roving-tabindex.d.ts.map +1 -0
- package/dist/interaction/roving-tabindex.js +142 -0
- package/dist/interaction/roving-tabindex.js.map +1 -0
- package/dist/interaction/selection.d.ts +72 -0
- package/dist/interaction/selection.d.ts.map +1 -0
- package/dist/interaction/selection.js +129 -0
- package/dist/interaction/selection.js.map +1 -0
- package/dist/interaction/targets.d.ts +15 -0
- package/dist/interaction/targets.d.ts.map +1 -0
- package/dist/interaction/targets.js +20 -0
- package/dist/interaction/targets.js.map +1 -0
- package/dist/interaction/tree-navigation.d.ts +111 -0
- package/dist/interaction/tree-navigation.d.ts.map +1 -0
- package/dist/interaction/tree-navigation.js +207 -0
- package/dist/interaction/tree-navigation.js.map +1 -0
- package/dist/interaction/typeahead.d.ts +73 -0
- package/dist/interaction/typeahead.d.ts.map +1 -0
- package/dist/interaction/typeahead.js +113 -0
- package/dist/interaction/typeahead.js.map +1 -0
- package/dist/internal.d.ts +16 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/{internal-DP-xLKbT.mjs → internal.js} +8 -7
- package/dist/internal.js.map +1 -0
- package/dist/media/breakpoints.d.ts +37 -0
- package/dist/media/breakpoints.d.ts.map +1 -0
- package/dist/media/breakpoints.js +64 -0
- package/dist/media/breakpoints.js.map +1 -0
- package/dist/media/index.d.ts +3 -0
- package/dist/media/index.js +4 -0
- package/dist/media/mediaquery.d.ts +40 -0
- package/dist/media/mediaquery.d.ts.map +1 -0
- package/dist/media/mediaquery.js +42 -0
- package/dist/media/mediaquery.js.map +1 -0
- package/dist/media/platform.d.ts +30 -0
- package/dist/media/platform.d.ts.map +1 -0
- package/dist/media/platform.js +50 -0
- package/dist/media/platform.js.map +1 -0
- package/dist/motion/index.d.ts +5 -0
- package/dist/motion/index.js +5 -0
- package/dist/motion/motion.d.ts +16 -0
- package/dist/motion/motion.d.ts.map +1 -0
- package/dist/motion/motion.js +25 -0
- package/dist/motion/motion.js.map +1 -0
- package/dist/motion/presence.d.ts +37 -0
- package/dist/motion/presence.d.ts.map +1 -0
- package/dist/motion/presence.js +65 -0
- package/dist/motion/presence.js.map +1 -0
- package/dist/motion/spring.d.ts +50 -0
- package/dist/motion/spring.d.ts.map +1 -0
- package/dist/motion/spring.js +92 -0
- package/dist/motion/spring.js.map +1 -0
- package/dist/motion/tween.d.ts +44 -0
- package/dist/motion/tween.d.ts.map +1 -0
- package/dist/motion/tween.js +65 -0
- package/dist/motion/tween.js.map +1 -0
- package/dist/net/broadcast.d.ts +39 -0
- package/dist/net/broadcast.d.ts.map +1 -0
- package/dist/net/broadcast.js +73 -0
- package/dist/net/broadcast.js.map +1 -0
- package/dist/net/index.d.ts +5 -0
- package/dist/net/index.js +6 -0
- package/dist/net/online.d.ts +24 -0
- package/dist/net/online.d.ts.map +1 -0
- package/dist/net/online.js +26 -0
- package/dist/net/online.js.map +1 -0
- package/dist/net/sharedHost.d.ts +64 -0
- package/dist/net/sharedHost.d.ts.map +1 -0
- package/dist/net/sharedHost.js +124 -0
- package/dist/net/sharedHost.js.map +1 -0
- package/dist/net/sse.d.ts +115 -0
- package/dist/net/sse.d.ts.map +1 -0
- package/dist/net/sse.js +138 -0
- package/dist/net/sse.js.map +1 -0
- package/dist/net/websocket.d.ts +172 -0
- package/dist/net/websocket.d.ts.map +1 -0
- package/dist/net/websocket.js +199 -0
- package/dist/net/websocket.js.map +1 -0
- package/dist/observers/bounds.d.ts +24 -0
- package/dist/observers/bounds.d.ts.map +1 -0
- package/dist/observers/bounds.js +51 -0
- package/dist/observers/bounds.js.map +1 -0
- package/dist/observers/index.d.ts +4 -0
- package/dist/observers/index.js +5 -0
- package/dist/observers/intersection.d.ts +18 -0
- package/dist/observers/intersection.d.ts.map +1 -0
- package/dist/observers/intersection.js +33 -0
- package/dist/observers/intersection.js.map +1 -0
- package/dist/observers/mutation.d.ts +25 -0
- package/dist/observers/mutation.d.ts.map +1 -0
- package/dist/observers/mutation.js +53 -0
- package/dist/observers/mutation.js.map +1 -0
- package/dist/observers/resize.d.ts +18 -0
- package/dist/observers/resize.d.ts.map +1 -0
- package/dist/observers/resize.js +33 -0
- package/dist/observers/resize.js.map +1 -0
- package/dist/state/history.d.ts +115 -0
- package/dist/state/history.d.ts.map +1 -0
- package/dist/state/history.js +167 -0
- package/dist/state/history.js.map +1 -0
- package/dist/state/index.d.ts +6 -0
- package/dist/state/index.js +7 -0
- package/dist/state/locale.d.ts +27 -0
- package/dist/state/locale.d.ts.map +1 -0
- package/dist/state/locale.js +25 -0
- package/dist/state/locale.js.map +1 -0
- package/dist/state/pagination.d.ts +53 -0
- package/dist/state/pagination.d.ts.map +1 -0
- package/dist/state/pagination.js +72 -0
- package/dist/state/pagination.js.map +1 -0
- package/dist/state/range.d.ts +9 -0
- package/dist/state/range.d.ts.map +1 -0
- package/dist/state/range.js +18 -0
- package/dist/state/range.js.map +1 -0
- package/dist/state/selection.d.ts +47 -0
- package/dist/state/selection.d.ts.map +1 -0
- package/dist/state/selection.js +39 -0
- package/dist/state/selection.js.map +1 -0
- package/dist/state/virtual.d.ts +201 -0
- package/dist/state/virtual.d.ts.map +1 -0
- package/dist/state/{index.mjs → virtual.js} +11 -313
- package/dist/state/virtual.js.map +1 -0
- package/dist/time/debounce.d.ts +43 -0
- package/dist/time/debounce.d.ts.map +1 -0
- package/dist/time/debounce.js +65 -0
- package/dist/time/debounce.js.map +1 -0
- package/dist/time/index.d.ts +6 -0
- package/dist/time/index.js +7 -0
- package/dist/time/interval.d.ts +37 -0
- package/dist/time/interval.d.ts.map +1 -0
- package/dist/time/interval.js +52 -0
- package/dist/time/interval.js.map +1 -0
- package/dist/time/relative.d.ts +8 -0
- package/dist/time/relative.d.ts.map +1 -0
- package/dist/time/relative.js +27 -0
- package/dist/time/relative.js.map +1 -0
- package/dist/time/throttle.d.ts +38 -0
- package/dist/time/throttle.d.ts.map +1 -0
- package/dist/time/throttle.js +39 -0
- package/dist/time/throttle.js.map +1 -0
- package/dist/time/ticks.d.ts +21 -0
- package/dist/time/ticks.d.ts.map +1 -0
- package/dist/time/ticks.js +30 -0
- package/dist/time/ticks.js.map +1 -0
- package/dist/time/timer.d.ts +32 -0
- package/dist/time/timer.d.ts.map +1 -0
- package/dist/time/timer.js +50 -0
- package/dist/time/timer.js.map +1 -0
- package/package.json +31 -31
- package/dist/device/index.d.mts +0 -345
- package/dist/device/index.d.mts.map +0 -1
- package/dist/device/index.mjs +0 -498
- package/dist/device/index.mjs.map +0 -1
- package/dist/dom/index.d.mts +0 -750
- package/dist/dom/index.d.mts.map +0 -1
- package/dist/dom/index.mjs +0 -1857
- package/dist/dom/index.mjs.map +0 -1
- package/dist/editable-text-1D31qthY.d.mts +0 -319
- package/dist/editable-text-1D31qthY.d.mts.map +0 -1
- package/dist/events/index.d.mts +0 -184
- package/dist/events/index.d.mts.map +0 -1
- package/dist/events/index.mjs +0 -315
- package/dist/events/index.mjs.map +0 -1
- package/dist/follow-tab-stop-Caog2qg1.mjs +0 -863
- package/dist/follow-tab-stop-Caog2qg1.mjs.map +0 -1
- package/dist/index-CmmxqSEi.d.mts +0 -149
- package/dist/index-CmmxqSEi.d.mts.map +0 -1
- package/dist/index.d.mts +0 -12
- package/dist/index.mjs +0 -12
- package/dist/input-modality-BXD383jg.mjs.map +0 -1
- package/dist/input-modality-BY-bSrEc.d.mts.map +0 -1
- package/dist/interaction/index.d.mts +0 -1194
- package/dist/interaction/index.d.mts.map +0 -1
- package/dist/interaction/index.mjs +0 -2012
- package/dist/interaction/index.mjs.map +0 -1
- package/dist/internal-DP-xLKbT.mjs.map +0 -1
- package/dist/media/index.d.mts +0 -109
- package/dist/media/index.d.mts.map +0 -1
- package/dist/media/index.mjs +0 -149
- package/dist/media/index.mjs.map +0 -1
- package/dist/motion/index.d.mts +0 -2
- package/dist/motion/index.mjs +0 -1
- package/dist/motion-DmFDppRn.mjs +0 -240
- package/dist/motion-DmFDppRn.mjs.map +0 -1
- package/dist/net/index.d.mts +0 -348
- package/dist/net/index.d.mts.map +0 -1
- package/dist/net/index.mjs +0 -535
- package/dist/net/index.mjs.map +0 -1
- package/dist/observers/index.d.mts +0 -84
- package/dist/observers/index.d.mts.map +0 -1
- package/dist/observers/index.mjs +0 -159
- package/dist/observers/index.mjs.map +0 -1
- package/dist/rolldown-runtime-D7D4PA-g.mjs +0 -13
- package/dist/state/index.d.mts +0 -444
- package/dist/state/index.d.mts.map +0 -1
- package/dist/state/index.mjs.map +0 -1
- package/dist/time/index.d.mts +0 -179
- package/dist/time/index.d.mts.map +0 -1
- package/dist/time/index.mjs +0 -242
- package/dist/time/index.mjs.map +0 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Effect, Queue, Schema, Stream } from "effect";
|
|
2
|
+
import * as Mount from "foldkit/mount";
|
|
3
|
+
// src/observers/mutation.ts
|
|
4
|
+
const Mutated = Schema.TaggedStruct("Mutated", {
|
|
5
|
+
type: Schema.Literals([
|
|
6
|
+
"childList",
|
|
7
|
+
"attributes",
|
|
8
|
+
"characterData"
|
|
9
|
+
]),
|
|
10
|
+
added: Schema.Array(Schema.String),
|
|
11
|
+
removed: Schema.Array(Schema.String),
|
|
12
|
+
attribute: Schema.NullOr(Schema.String)
|
|
13
|
+
});
|
|
14
|
+
const recordType = (type) => type === "childList" || type === "attributes" || type === "characterData" ? type : null;
|
|
15
|
+
/**
|
|
16
|
+
* DOM mutations as a Mount: MutationObserver reports child, attribute, and
|
|
17
|
+
* text changes as Messages for the element carrying this Mount. Nodes cross
|
|
18
|
+
* as their names (serializable); the live nodes stay in the DOM, never in
|
|
19
|
+
* the Model. No observer (SSR, old browser) emits nothing instead of
|
|
20
|
+
* throwing. Observes the whole subtree — narrow by placing on a smaller
|
|
21
|
+
* element, not by options.
|
|
22
|
+
*/
|
|
23
|
+
const Mutation = Mount.defineStream("Mutation", {
|
|
24
|
+
messages: [Mutated],
|
|
25
|
+
execute: ({ element }) => Stream.callback((queue) => Effect.gen(function* () {
|
|
26
|
+
const Observed = globalThis.MutationObserver;
|
|
27
|
+
if (Observed === void 0) return;
|
|
28
|
+
yield* Effect.acquireRelease(Effect.sync(() => {
|
|
29
|
+
const observer = new Observed((records) => {
|
|
30
|
+
for (const record of records) {
|
|
31
|
+
const type = recordType(record.type);
|
|
32
|
+
if (type === null) continue;
|
|
33
|
+
Queue.offerUnsafe(queue, Mutated.make({
|
|
34
|
+
type,
|
|
35
|
+
added: type === "childList" ? Array.from(record.addedNodes, (node) => node.nodeName) : [],
|
|
36
|
+
removed: type === "childList" ? Array.from(record.removedNodes, (node) => node.nodeName) : [],
|
|
37
|
+
attribute: type === "attributes" ? record.attributeName ?? null : null
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
observer.observe(element, {
|
|
42
|
+
childList: true,
|
|
43
|
+
attributes: true,
|
|
44
|
+
characterData: true,
|
|
45
|
+
subtree: true
|
|
46
|
+
});
|
|
47
|
+
return observer;
|
|
48
|
+
}), (observer) => Effect.sync(() => observer.disconnect()));
|
|
49
|
+
}))
|
|
50
|
+
});
|
|
51
|
+
export { Mutated, Mutation };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=mutation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation.js","names":[],"sources":["../../src/observers/mutation.ts"],"sourcesContent":["import { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const Mutated = Schema.TaggedStruct('Mutated', {\n type: Schema.Literals(['childList', 'attributes', 'characterData']),\n added: Schema.Array(Schema.String),\n removed: Schema.Array(Schema.String),\n attribute: Schema.NullOr(Schema.String),\n})\nexport type Mutated = typeof Mutated.Type\n\ntype ObserverCtor = new (callback: MutationCallback) => MutationObserver\n\nconst recordType = (type: string): Mutated['type'] | null =>\n type === 'childList' || type === 'attributes' || type === 'characterData' ? type : null\n\n/**\n * DOM mutations as a Mount: MutationObserver reports child, attribute, and\n * text changes as Messages for the element carrying this Mount. Nodes cross\n * as their names (serializable); the live nodes stay in the DOM, never in\n * the Model. No observer (SSR, old browser) emits nothing instead of\n * throwing. Observes the whole subtree — narrow by placing on a smaller\n * element, not by options.\n */\nexport const Mutation = Mount.defineStream('Mutation', {\n messages: [Mutated],\n execute: ({ element }) =>\n Stream.callback<typeof Mutated.Type>(queue =>\n Effect.gen(function* () {\n const Observed = (globalThis as { MutationObserver?: ObserverCtor }).MutationObserver\n if (Observed === undefined) return\n yield* Effect.acquireRelease(\n Effect.sync(() => {\n const observer = new Observed(records => {\n for (const record of records) {\n const type = recordType(record.type)\n if (type === null) continue\n Queue.offerUnsafe(\n queue,\n Mutated.make({\n type,\n added:\n type === 'childList'\n ? Array.from(record.addedNodes, node => node.nodeName)\n : [],\n removed:\n type === 'childList'\n ? Array.from(record.removedNodes, node => node.nodeName)\n : [],\n attribute: type === 'attributes' ? (record.attributeName ?? null) : null,\n }),\n )\n }\n })\n observer.observe(element, {\n childList: true,\n attributes: true,\n characterData: true,\n subtree: true,\n })\n return observer\n }),\n observer => Effect.sync(() => observer.disconnect()),\n )\n }),\n ),\n})\n"],"mappings":";;;AAGA,MAAa,UAAU,OAAO,aAAa,WAAW;CACpD,MAAM,OAAO,SAAS;EAAC;EAAa;EAAc;CAAe,CAAC;CAClE,OAAO,OAAO,MAAM,OAAO,MAAM;CACjC,SAAS,OAAO,MAAM,OAAO,MAAM;CACnC,WAAW,OAAO,OAAO,OAAO,MAAM;AACxC,CAAC;AAKD,MAAM,cAAc,SAClB,SAAS,eAAe,SAAS,gBAAgB,SAAS,kBAAkB,OAAO;;;;;;;;;AAUrF,MAAa,WAAW,MAAM,aAAa,YAAY;CACrD,UAAU,CAAC,OAAO;CAClB,UAAU,EAAE,cACV,OAAO,UAA8B,UACnC,OAAO,IAAI,aAAa;EACtB,MAAM,WAAY,WAAmD;EACrE,IAAI,aAAa,KAAA,GAAW;EAC5B,OAAO,OAAO,eACZ,OAAO,WAAW;GAChB,MAAM,WAAW,IAAI,UAAS,YAAW;IACvC,KAAK,MAAM,UAAU,SAAS;KAC5B,MAAM,OAAO,WAAW,OAAO,IAAI;KACnC,IAAI,SAAS,MAAM;KACnB,MAAM,YACJ,OACA,QAAQ,KAAK;MACX;MACA,OACE,SAAS,cACL,MAAM,KAAK,OAAO,aAAY,SAAQ,KAAK,QAAQ,IACnD,CAAC;MACP,SACE,SAAS,cACL,MAAM,KAAK,OAAO,eAAc,SAAQ,KAAK,QAAQ,IACrD,CAAC;MACP,WAAW,SAAS,eAAgB,OAAO,iBAAiB,OAAQ;KACtE,CAAC,CACH;IACF;GACF,CAAC;GACD,SAAS,QAAQ,SAAS;IACxB,WAAW;IACX,YAAY;IACZ,eAAe;IACf,SAAS;GACX,CAAC;GACD,OAAO;EACT,CAAC,IACD,aAAY,OAAO,WAAW,SAAS,WAAW,CAAC,CACrD;CACF,CAAC,CACH;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import * as Mount from "foldkit/mount";
|
|
3
|
+
export declare const Resized: Schema.TaggedStruct<"Resized", {
|
|
4
|
+
readonly width: Schema.Number;
|
|
5
|
+
readonly height: Schema.Number;
|
|
6
|
+
}>;
|
|
7
|
+
export type Resized = typeof Resized.Type;
|
|
8
|
+
/**
|
|
9
|
+
* Element size as a Mount: ResizeObserver reports content-box changes as
|
|
10
|
+
* Messages for the element carrying this Mount. No observer (SSR, old
|
|
11
|
+
* browser) emits nothing instead of throwing.
|
|
12
|
+
*/
|
|
13
|
+
export declare const Resize: Mount.MountDefinitionNoArgs<"Resize", {
|
|
14
|
+
readonly _tag: "Resized";
|
|
15
|
+
readonly width: number;
|
|
16
|
+
readonly height: number;
|
|
17
|
+
}>;
|
|
18
|
+
//# sourceMappingURL=resize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resize.d.ts","names":[],"sources":["../../src/observers/resize.ts"],"mappings":";;qBAGa,SAAO,OAAA;;;;YAIR,iBAAiB,QAAQ;;;;;;qBASxB,QAAM,MAAA"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Effect, Queue, Schema, Stream } from "effect";
|
|
2
|
+
import * as Mount from "foldkit/mount";
|
|
3
|
+
// src/observers/resize.ts
|
|
4
|
+
const Resized = Schema.TaggedStruct("Resized", {
|
|
5
|
+
width: Schema.Number,
|
|
6
|
+
height: Schema.Number
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Element size as a Mount: ResizeObserver reports content-box changes as
|
|
10
|
+
* Messages for the element carrying this Mount. No observer (SSR, old
|
|
11
|
+
* browser) emits nothing instead of throwing.
|
|
12
|
+
*/
|
|
13
|
+
const Resize = Mount.defineStream("Resize", {
|
|
14
|
+
messages: [Resized],
|
|
15
|
+
execute: ({ element }) => Stream.callback((queue) => Effect.gen(function* () {
|
|
16
|
+
const Observed = globalThis.ResizeObserver;
|
|
17
|
+
if (Observed === void 0) return;
|
|
18
|
+
yield* Effect.acquireRelease(Effect.sync(() => {
|
|
19
|
+
const observer = new Observed((entries) => {
|
|
20
|
+
const rect = entries[0]?.contentRect;
|
|
21
|
+
if (rect !== void 0) Queue.offerUnsafe(queue, Resized.make({
|
|
22
|
+
width: rect.width,
|
|
23
|
+
height: rect.height
|
|
24
|
+
}));
|
|
25
|
+
});
|
|
26
|
+
observer.observe(element);
|
|
27
|
+
return observer;
|
|
28
|
+
}), (observer) => Effect.sync(() => observer.disconnect()));
|
|
29
|
+
}))
|
|
30
|
+
});
|
|
31
|
+
export { Resize, Resized };
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=resize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resize.js","names":[],"sources":["../../src/observers/resize.ts"],"sourcesContent":["import { Effect, Queue, Schema, Stream } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const Resized = Schema.TaggedStruct('Resized', {\n width: Schema.Number,\n height: Schema.Number,\n})\nexport type Resized = typeof Resized.Type\n\ntype ObserverCtor = new (callback: ResizeObserverCallback) => ResizeObserver\n\n/**\n * Element size as a Mount: ResizeObserver reports content-box changes as\n * Messages for the element carrying this Mount. No observer (SSR, old\n * browser) emits nothing instead of throwing.\n */\nexport const Resize = Mount.defineStream('Resize', {\n messages: [Resized],\n execute: ({ element }) =>\n Stream.callback<typeof Resized.Type>(queue =>\n Effect.gen(function* () {\n const Observed = (globalThis as { ResizeObserver?: ObserverCtor }).ResizeObserver\n if (Observed === undefined) return\n yield* Effect.acquireRelease(\n Effect.sync(() => {\n const observer = new Observed(entries => {\n const rect = entries[0]?.contentRect\n if (rect !== undefined) {\n Queue.offerUnsafe(queue, Resized.make({ width: rect.width, height: rect.height }))\n }\n })\n observer.observe(element)\n return observer\n }),\n observer => Effect.sync(() => observer.disconnect()),\n )\n }),\n ),\n})\n"],"mappings":";;;AAGA,MAAa,UAAU,OAAO,aAAa,WAAW;CACpD,OAAO,OAAO;CACd,QAAQ,OAAO;AACjB,CAAC;;;;;;AAUD,MAAa,SAAS,MAAM,aAAa,UAAU;CACjD,UAAU,CAAC,OAAO;CAClB,UAAU,EAAE,cACV,OAAO,UAA8B,UACnC,OAAO,IAAI,aAAa;EACtB,MAAM,WAAY,WAAiD;EACnE,IAAI,aAAa,KAAA,GAAW;EAC5B,OAAO,OAAO,eACZ,OAAO,WAAW;GAChB,MAAM,WAAW,IAAI,UAAS,YAAW;IACvC,MAAM,OAAO,QAAQ,EAAE,EAAE;IACzB,IAAI,SAAS,KAAA,GACX,MAAM,YAAY,OAAO,QAAQ,KAAK;KAAE,OAAO,KAAK;KAAO,QAAQ,KAAK;IAAO,CAAC,CAAC;GAErF,CAAC;GACD,SAAS,QAAQ,OAAO;GACxB,OAAO;EACT,CAAC,IACD,aAAY,OAAO,WAAW,SAAS,WAAW,CAAC,CACrD;CACF,CAAC,CACH;AACJ,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle } from "foldkit-bundle";
|
|
3
|
+
export declare const HistoryModel: <Value>(value: Schema.Codec<Value, unknown>) => Schema.Struct<{
|
|
4
|
+
readonly past: Schema.$Array<Schema.Codec<Value, unknown, never, never>>;
|
|
5
|
+
readonly present: Schema.Codec<Value, unknown, never, never>;
|
|
6
|
+
readonly future: Schema.$Array<Schema.Codec<Value, unknown, never, never>>;
|
|
7
|
+
/** The group of the last push, so the next push of the same group joins it; `null` for none. */
|
|
8
|
+
readonly group: Schema.NullOr<Schema.String>;
|
|
9
|
+
}>;
|
|
10
|
+
export type HistoryModel<Value> = {
|
|
11
|
+
readonly past: ReadonlyArray<Value>;
|
|
12
|
+
readonly present: Value;
|
|
13
|
+
readonly future: ReadonlyArray<Value>;
|
|
14
|
+
readonly group: string | null;
|
|
15
|
+
};
|
|
16
|
+
declare const start: <Value>(present: Value) => HistoryModel<Value>;
|
|
17
|
+
declare const push: <Value>(model: HistoryModel<Value>, value: Value, options: {
|
|
18
|
+
readonly capacity: number;
|
|
19
|
+
readonly group?: string | null | undefined;
|
|
20
|
+
}) => HistoryModel<Value>;
|
|
21
|
+
declare const undo: <Value>(model: HistoryModel<Value>) => HistoryModel<Value>;
|
|
22
|
+
declare const redo: <Value>(model: HistoryModel<Value>) => HistoryModel<Value>;
|
|
23
|
+
declare const goTo: <Value>(model: HistoryModel<Value>, step: number) => HistoryModel<Value>;
|
|
24
|
+
declare const clear: <Value>(model: HistoryModel<Value>) => HistoryModel<Value>;
|
|
25
|
+
declare const close: <Value>(model: HistoryModel<Value>) => HistoryModel<Value>;
|
|
26
|
+
declare const revert: <Value>(model: HistoryModel<Value>, group: string) => HistoryModel<Value>;
|
|
27
|
+
/** Whether Undo would move: the past is non-empty. */
|
|
28
|
+
export declare const canUndo: <Value>(model: HistoryModel<Value>) => boolean;
|
|
29
|
+
/** Whether Redo would move: the future is non-empty. */
|
|
30
|
+
export declare const canRedo: <Value>(model: HistoryModel<Value>) => boolean;
|
|
31
|
+
/** The steps of a history as pure functions of its Model. */
|
|
32
|
+
export declare const History: {
|
|
33
|
+
/** A history whose present is `value`, with nothing to undo. */
|
|
34
|
+
start: typeof start;
|
|
35
|
+
/** Records `value` as the present; the same `group` as the last push joins its step. */
|
|
36
|
+
push: typeof push;
|
|
37
|
+
/** One step back, or the Model as it was when there is none. */
|
|
38
|
+
undo: typeof undo;
|
|
39
|
+
/** One step forward, or the Model as it was when there is none. */
|
|
40
|
+
redo: typeof redo;
|
|
41
|
+
/**
|
|
42
|
+
* The value at `step` made the present, counting every kept value from the
|
|
43
|
+
* oldest (0), so the present is at `past.length`: what a click on a step's
|
|
44
|
+
* thumbnail does. The Model as it was for the present or a step not kept.
|
|
45
|
+
*/
|
|
46
|
+
goTo: typeof goTo;
|
|
47
|
+
/** Nothing to undo or redo, the present kept. */
|
|
48
|
+
clear: typeof clear;
|
|
49
|
+
/** The next push starts a step of its own, whatever its group: a group ends here. */
|
|
50
|
+
close: typeof close;
|
|
51
|
+
/**
|
|
52
|
+
* The step `group` is making taken back, and not redoable: what a cancelled
|
|
53
|
+
* edit, or one that ended where it began, leaves. The Model as it was when
|
|
54
|
+
* the last push was not of `group`. What the group's first push dropped
|
|
55
|
+
* stays dropped: the redo future it cleared, and an entry capacity evicted;
|
|
56
|
+
* with a capacity of 0 nothing was kept, so the present stays and only the
|
|
57
|
+
* group ends.
|
|
58
|
+
*/
|
|
59
|
+
revert: typeof revert;
|
|
60
|
+
canUndo: typeof canUndo;
|
|
61
|
+
canRedo: typeof canRedo;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Undo/redo as a bundle factory: the past, present, and future of one value
|
|
65
|
+
* live in the Model, so every step replays. Pushing records, even an unchanged
|
|
66
|
+
* value; skipping duplicates is the application's policy, not the stack's.
|
|
67
|
+
*
|
|
68
|
+
* A push may name a `group`. Consecutive pushes of the same group are one step:
|
|
69
|
+
* the present moves and nothing new is recorded, so typing a word undoes as a
|
|
70
|
+
* whole. Grouping needs no clock; the application names what belongs together.
|
|
71
|
+
*
|
|
72
|
+
* The steps are also pure functions (`History.push`, `undo`, `redo`, `goTo`, `clear`),
|
|
73
|
+
* for a parent that records an edit in the same transition that makes it
|
|
74
|
+
* rather than dispatching a Message to a placed bundle.
|
|
75
|
+
*/
|
|
76
|
+
export declare const history: <const Name extends string, Value>(config: {
|
|
77
|
+
readonly name: Name;
|
|
78
|
+
readonly value: Schema.Codec<Value, unknown>;
|
|
79
|
+
readonly capacity?: number | undefined;
|
|
80
|
+
}) => Bundle.Bundle<Name, {
|
|
81
|
+
readonly initial: Value;
|
|
82
|
+
}, {
|
|
83
|
+
readonly past: readonly Value[];
|
|
84
|
+
readonly present: Value;
|
|
85
|
+
readonly future: readonly Value[];
|
|
86
|
+
readonly group: string | null;
|
|
87
|
+
}, {
|
|
88
|
+
readonly _tag: "Clear";
|
|
89
|
+
} | {
|
|
90
|
+
readonly _tag: "GoTo";
|
|
91
|
+
readonly step: number;
|
|
92
|
+
} | {
|
|
93
|
+
readonly _tag: "Push";
|
|
94
|
+
readonly value: Value;
|
|
95
|
+
readonly group?: string | undefined;
|
|
96
|
+
} | {
|
|
97
|
+
readonly _tag: "Redo";
|
|
98
|
+
} | {
|
|
99
|
+
readonly _tag: "Undo";
|
|
100
|
+
}, never, never, never, void, {}, {}> & {
|
|
101
|
+
Message: import("foldkit/message").MessageUnion<{
|
|
102
|
+
readonly Push: {
|
|
103
|
+
value: Schema.Codec<Value, unknown, never, never>;
|
|
104
|
+
group: Schema.optional<Schema.String>;
|
|
105
|
+
};
|
|
106
|
+
readonly Undo: {};
|
|
107
|
+
readonly Redo: {};
|
|
108
|
+
readonly GoTo: {
|
|
109
|
+
step: Schema.Number;
|
|
110
|
+
};
|
|
111
|
+
readonly Clear: {};
|
|
112
|
+
}>;
|
|
113
|
+
};
|
|
114
|
+
export {};
|
|
115
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","names":[],"sources":["../../src/state/history.ts"],"mappings":";;qBAIa,eAAgB,OAAK,OAAS,OAAO,MAAM,oBAAe,OAAA;;;;;;;YAQ3D,aAAa;WACd,MAAM,cAAc;WACpB,SAAS;WACT,QAAQ,cAAc;WACtB;;cAGL,QAAS,OAAK,SAAW,UAAQ,aAAa;cAe9C,OAAQ,OAAK,OACV,aAAa,QAAM,OACnB,OAAK;WACQ;WAA2B;MAC9C,aAAa;cAcV,OAAQ,OAAK,OAAS,aAAa,WAAS,aAAa;cAWzD,OAAQ,OAAK,OAAS,aAAa,WAAS,aAAa;cAWzD,OAAQ,OAAK,OAAS,aAAa,QAAM,iBAAiB,aAAa;cAYvE,QAAS,OAAK,OAAS,aAAa,WAAS,aAAa;cAK1D,QAAS,OAAK,OAAS,aAAa,WAAS,aAAa;cAG1D,SAAU,OAAK,OAAS,aAAa,QAAM,kBAAkB,aAAa;;qBAanE,UAAW,OAAK,OAAS,aAAa;;qBAGtC,UAAW,OAAK,OAAS,aAAa;;qBAGtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBA6CA,gBAAiB,qBAAqB,OAAK;WAC7C,MAAM;WACN,OAAO,OAAO,MAAM;WACpB;MACV,OAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;MAIW,OAAK,OAAA,MAAA;MAAgB,OAAK,OAAA,SAAA,OAAA;;;;;MAG1B,MAAI,OAAA"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import { Bundle } from "foldkit-bundle";
|
|
4
|
+
// src/state/history.ts
|
|
5
|
+
const HistoryModel = (value) => Schema.Struct({
|
|
6
|
+
past: Schema.Array(value),
|
|
7
|
+
present: value,
|
|
8
|
+
future: Schema.Array(value),
|
|
9
|
+
/** The group of the last push, so the next push of the same group joins it; `null` for none. */
|
|
10
|
+
group: Schema.NullOr(Schema.String)
|
|
11
|
+
});
|
|
12
|
+
const start = (present) => ({
|
|
13
|
+
past: [],
|
|
14
|
+
present,
|
|
15
|
+
future: [],
|
|
16
|
+
group: null
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* No redo steps, keeping an already empty future: a view that reads the
|
|
20
|
+
* future compares it by identity, so a push per keystroke or stroke cell
|
|
21
|
+
* must not hand it a new empty array each time.
|
|
22
|
+
*/
|
|
23
|
+
const noFuture = (model) => model.future.length === 0 ? model.future : [];
|
|
24
|
+
const push = (model, value, options) => {
|
|
25
|
+
const group = options.group ?? null;
|
|
26
|
+
if (group !== null && group === model.group && model.past.length > 0) return {
|
|
27
|
+
...model,
|
|
28
|
+
present: value,
|
|
29
|
+
future: noFuture(model)
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
past: options.capacity === 0 ? [] : [...model.past, model.present].slice(-options.capacity),
|
|
33
|
+
present: value,
|
|
34
|
+
future: noFuture(model),
|
|
35
|
+
group
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const undo = (model) => {
|
|
39
|
+
const previous = model.past[model.past.length - 1];
|
|
40
|
+
if (previous === void 0) return model;
|
|
41
|
+
return {
|
|
42
|
+
past: model.past.slice(0, -1),
|
|
43
|
+
present: previous,
|
|
44
|
+
future: [model.present, ...model.future],
|
|
45
|
+
group: null
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
const redo = (model) => {
|
|
49
|
+
const next = model.future[0];
|
|
50
|
+
if (next === void 0) return model;
|
|
51
|
+
return {
|
|
52
|
+
past: [...model.past, model.present],
|
|
53
|
+
present: next,
|
|
54
|
+
future: model.future.slice(1),
|
|
55
|
+
group: null
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const goTo = (model, step) => {
|
|
59
|
+
const steps = [
|
|
60
|
+
...model.past,
|
|
61
|
+
model.present,
|
|
62
|
+
...model.future
|
|
63
|
+
];
|
|
64
|
+
if (!Number.isInteger(step) || step < 0 || step >= steps.length || step === model.past.length) return model;
|
|
65
|
+
return {
|
|
66
|
+
past: steps.slice(0, step),
|
|
67
|
+
present: steps[step],
|
|
68
|
+
future: steps.slice(step + 1),
|
|
69
|
+
group: null
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
const clear = (model) => model.past.length === 0 && model.future.length === 0 && model.group === null ? model : start(model.present);
|
|
73
|
+
const close = (model) => model.group === null ? model : {
|
|
74
|
+
...model,
|
|
75
|
+
group: null
|
|
76
|
+
};
|
|
77
|
+
const revert = (model, group) => model.group === group ? {
|
|
78
|
+
...undo(model),
|
|
79
|
+
future: [],
|
|
80
|
+
group: null
|
|
81
|
+
} : model;
|
|
82
|
+
const checkCapacity = (name, capacity) => {
|
|
83
|
+
if (!Number.isInteger(capacity) || capacity < 0) throw new Error(`History "${name}": capacity must be a non-negative integer`);
|
|
84
|
+
return capacity;
|
|
85
|
+
};
|
|
86
|
+
/** Whether Undo would move: the past is non-empty. */
|
|
87
|
+
const canUndo = (model) => model.past.length > 0;
|
|
88
|
+
/** Whether Redo would move: the future is non-empty. */
|
|
89
|
+
const canRedo = (model) => model.future.length > 0;
|
|
90
|
+
/** The steps of a history as pure functions of its Model. */
|
|
91
|
+
const History = {
|
|
92
|
+
/** A history whose present is `value`, with nothing to undo. */
|
|
93
|
+
start,
|
|
94
|
+
/** Records `value` as the present; the same `group` as the last push joins its step. */
|
|
95
|
+
push,
|
|
96
|
+
/** One step back, or the Model as it was when there is none. */
|
|
97
|
+
undo,
|
|
98
|
+
/** One step forward, or the Model as it was when there is none. */
|
|
99
|
+
redo,
|
|
100
|
+
/**
|
|
101
|
+
* The value at `step` made the present, counting every kept value from the
|
|
102
|
+
* oldest (0), so the present is at `past.length`: what a click on a step's
|
|
103
|
+
* thumbnail does. The Model as it was for the present or a step not kept.
|
|
104
|
+
*/
|
|
105
|
+
goTo,
|
|
106
|
+
/** Nothing to undo or redo, the present kept. */
|
|
107
|
+
clear,
|
|
108
|
+
/** The next push starts a step of its own, whatever its group: a group ends here. */
|
|
109
|
+
close,
|
|
110
|
+
/**
|
|
111
|
+
* The step `group` is making taken back, and not redoable: what a cancelled
|
|
112
|
+
* edit, or one that ended where it began, leaves. The Model as it was when
|
|
113
|
+
* the last push was not of `group`. What the group's first push dropped
|
|
114
|
+
* stays dropped: the redo future it cleared, and an entry capacity evicted;
|
|
115
|
+
* with a capacity of 0 nothing was kept, so the present stays and only the
|
|
116
|
+
* group ends.
|
|
117
|
+
*/
|
|
118
|
+
revert,
|
|
119
|
+
canUndo,
|
|
120
|
+
canRedo
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Undo/redo as a bundle factory: the past, present, and future of one value
|
|
124
|
+
* live in the Model, so every step replays. Pushing records, even an unchanged
|
|
125
|
+
* value; skipping duplicates is the application's policy, not the stack's.
|
|
126
|
+
*
|
|
127
|
+
* A push may name a `group`. Consecutive pushes of the same group are one step:
|
|
128
|
+
* the present moves and nothing new is recorded, so typing a word undoes as a
|
|
129
|
+
* whole. Grouping needs no clock; the application names what belongs together.
|
|
130
|
+
*
|
|
131
|
+
* The steps are also pure functions (`History.push`, `undo`, `redo`, `goTo`, `clear`),
|
|
132
|
+
* for a parent that records an edit in the same transition that makes it
|
|
133
|
+
* rather than dispatching a Message to a placed bundle.
|
|
134
|
+
*/
|
|
135
|
+
const history = (config) => {
|
|
136
|
+
const capacity = checkCapacity(config.name, config.capacity ?? 100);
|
|
137
|
+
const Model = HistoryModel(config.value);
|
|
138
|
+
const Message = defineMessageUnion({
|
|
139
|
+
Push: {
|
|
140
|
+
value: config.value,
|
|
141
|
+
group: Schema.optional(Schema.String)
|
|
142
|
+
},
|
|
143
|
+
Undo: {},
|
|
144
|
+
Redo: {},
|
|
145
|
+
GoTo: { step: Schema.Number },
|
|
146
|
+
Clear: {}
|
|
147
|
+
});
|
|
148
|
+
return Object.assign(Bundle.make(config.name, {
|
|
149
|
+
Model,
|
|
150
|
+
Message,
|
|
151
|
+
args: Schema.Struct({ initial: config.value }),
|
|
152
|
+
init: (args) => ({ model: start(args.initial) }),
|
|
153
|
+
update: (model, message) => Message.match(message, {
|
|
154
|
+
Push: ({ value, group }) => ({ model: push(model, value, {
|
|
155
|
+
capacity,
|
|
156
|
+
group
|
|
157
|
+
}) }),
|
|
158
|
+
Undo: () => ({ model: undo(model) }),
|
|
159
|
+
Redo: () => ({ model: redo(model) }),
|
|
160
|
+
GoTo: ({ step }) => ({ model: goTo(model, step) }),
|
|
161
|
+
Clear: () => ({ model: clear(model) })
|
|
162
|
+
})
|
|
163
|
+
}), { Message });
|
|
164
|
+
};
|
|
165
|
+
export { History, HistoryModel, canRedo, canUndo, history };
|
|
166
|
+
|
|
167
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","names":[],"sources":["../../src/state/history.ts"],"sourcesContent":["import { Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const HistoryModel = <Value>(value: Schema.Codec<Value, unknown>) =>\n Schema.Struct({\n past: Schema.Array(value),\n present: value,\n future: Schema.Array(value),\n /** The group of the last push, so the next push of the same group joins it; `null` for none. */\n group: Schema.NullOr(Schema.String),\n })\nexport type HistoryModel<Value> = {\n readonly past: ReadonlyArray<Value>\n readonly present: Value\n readonly future: ReadonlyArray<Value>\n readonly group: string | null\n}\n\nconst start = <Value>(present: Value): HistoryModel<Value> => ({\n past: [],\n present,\n future: [],\n group: null,\n})\n\n/**\n * No redo steps, keeping an already empty future: a view that reads the\n * future compares it by identity, so a push per keystroke or stroke cell\n * must not hand it a new empty array each time.\n */\nconst noFuture = <Value>(model: HistoryModel<Value>): ReadonlyArray<Value> =>\n model.future.length === 0 ? model.future : []\n\nconst push = <Value>(\n model: HistoryModel<Value>,\n value: Value,\n options: { readonly capacity: number; readonly group?: string | null | undefined },\n): HistoryModel<Value> => {\n const group = options.group ?? null\n // The same group again joins the last step: the present moves, the past stays.\n if (group !== null && group === model.group && model.past.length > 0)\n return { ...model, present: value, future: noFuture(model) }\n return {\n // slice(-0) is slice(0): a zero capacity keeps nothing, explicitly.\n past: options.capacity === 0 ? [] : [...model.past, model.present].slice(-options.capacity),\n present: value,\n future: noFuture(model),\n group,\n }\n}\n\nconst undo = <Value>(model: HistoryModel<Value>): HistoryModel<Value> => {\n const previous = model.past[model.past.length - 1]\n if (previous === undefined) return model\n return {\n past: model.past.slice(0, -1),\n present: previous,\n future: [model.present, ...model.future],\n group: null,\n }\n}\n\nconst redo = <Value>(model: HistoryModel<Value>): HistoryModel<Value> => {\n const next = model.future[0]\n if (next === undefined) return model\n return {\n past: [...model.past, model.present],\n present: next,\n future: model.future.slice(1),\n group: null,\n }\n}\n\nconst goTo = <Value>(model: HistoryModel<Value>, step: number): HistoryModel<Value> => {\n const steps = [...model.past, model.present, ...model.future]\n if (!Number.isInteger(step) || step < 0 || step >= steps.length || step === model.past.length)\n return model\n return {\n past: steps.slice(0, step),\n present: steps[step]!,\n future: steps.slice(step + 1),\n group: null,\n }\n}\n\nconst clear = <Value>(model: HistoryModel<Value>): HistoryModel<Value> =>\n model.past.length === 0 && model.future.length === 0 && model.group === null\n ? model\n : start(model.present)\n\nconst close = <Value>(model: HistoryModel<Value>): HistoryModel<Value> =>\n model.group === null ? model : { ...model, group: null }\n\nconst revert = <Value>(model: HistoryModel<Value>, group: string): HistoryModel<Value> =>\n // The group is ended even where there is nothing to take back (capacity 0).\n model.group === group ? { ...undo(model), future: [], group: null } : model\n\nconst checkCapacity = (name: string, capacity: number): number => {\n // A negative or fractional capacity would silently keep a wrong-sized past;\n // fail where it is given, naming the caller, instead.\n if (!Number.isInteger(capacity) || capacity < 0)\n throw new Error(`History \"${name}\": capacity must be a non-negative integer`)\n return capacity\n}\n\n/** Whether Undo would move: the past is non-empty. */\nexport const canUndo = <Value>(model: HistoryModel<Value>): boolean => model.past.length > 0\n\n/** Whether Redo would move: the future is non-empty. */\nexport const canRedo = <Value>(model: HistoryModel<Value>): boolean => model.future.length > 0\n\n/** The steps of a history as pure functions of its Model. */\nexport const History = {\n /** A history whose present is `value`, with nothing to undo. */\n start,\n /** Records `value` as the present; the same `group` as the last push joins its step. */\n push,\n /** One step back, or the Model as it was when there is none. */\n undo,\n /** One step forward, or the Model as it was when there is none. */\n redo,\n /**\n * The value at `step` made the present, counting every kept value from the\n * oldest (0), so the present is at `past.length`: what a click on a step's\n * thumbnail does. The Model as it was for the present or a step not kept.\n */\n goTo,\n /** Nothing to undo or redo, the present kept. */\n clear,\n /** The next push starts a step of its own, whatever its group: a group ends here. */\n close,\n /**\n * The step `group` is making taken back, and not redoable: what a cancelled\n * edit, or one that ended where it began, leaves. The Model as it was when\n * the last push was not of `group`. What the group's first push dropped\n * stays dropped: the redo future it cleared, and an entry capacity evicted;\n * with a capacity of 0 nothing was kept, so the present stays and only the\n * group ends.\n */\n revert,\n canUndo,\n canRedo,\n}\n\n/**\n * Undo/redo as a bundle factory: the past, present, and future of one value\n * live in the Model, so every step replays. Pushing records, even an unchanged\n * value; skipping duplicates is the application's policy, not the stack's.\n *\n * A push may name a `group`. Consecutive pushes of the same group are one step:\n * the present moves and nothing new is recorded, so typing a word undoes as a\n * whole. Grouping needs no clock; the application names what belongs together.\n *\n * The steps are also pure functions (`History.push`, `undo`, `redo`, `goTo`, `clear`),\n * for a parent that records an edit in the same transition that makes it\n * rather than dispatching a Message to a placed bundle.\n */\nexport const history = <const Name extends string, Value>(config: {\n readonly name: Name\n readonly value: Schema.Codec<Value, unknown>\n readonly capacity?: number | undefined\n}) => {\n const capacity = checkCapacity(config.name, config.capacity ?? 100)\n const Model = HistoryModel(config.value)\n const Message = defineMessageUnion({\n Push: { value: config.value, group: Schema.optional(Schema.String) },\n Undo: {},\n Redo: {},\n GoTo: { step: Schema.Number },\n Clear: {},\n })\n return Object.assign(\n Bundle.make(config.name, {\n Model,\n Message,\n args: Schema.Struct({ initial: config.value }),\n init: args => ({ model: start(args.initial) }),\n update: (model, message) =>\n Message.match(message, {\n Push: ({ value, group }) => ({ model: push(model, value, { capacity, group }) }),\n Undo: () => ({ model: undo(model) }),\n Redo: () => ({ model: redo(model) }),\n GoTo: ({ step }) => ({ model: goTo(model, step) }),\n Clear: () => ({ model: clear(model) }),\n }),\n }),\n // The union object is both the Message codec and its constructors, so a\n // placement dispatches `EditHistory.Message.Push(...)` like any bundle's\n // exported Message.\n { Message },\n )\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle } from "foldkit-bundle";
|
|
3
|
+
export declare const LocaleModel: Schema.Struct<{
|
|
4
|
+
readonly locale: Schema.String;
|
|
5
|
+
}>;
|
|
6
|
+
export type LocaleModel = typeof LocaleModel.Type;
|
|
7
|
+
export declare const LocaleMessage: import("foldkit/message").MessageUnion<{
|
|
8
|
+
readonly SetLocale: {
|
|
9
|
+
locale: Schema.String;
|
|
10
|
+
};
|
|
11
|
+
}>;
|
|
12
|
+
export type LocaleMessage = typeof LocaleMessage.Type;
|
|
13
|
+
/**
|
|
14
|
+
* Locale as a bundle: the current locale string in the Model. Starts from
|
|
15
|
+
* `navigator.language` where available, falling back to the configured
|
|
16
|
+
* default; changes arrive as Messages. No subscriptions: language changes
|
|
17
|
+
* take effect on reload.
|
|
18
|
+
*/
|
|
19
|
+
export declare const Locale: Bundle.Bundle<"Locale", {
|
|
20
|
+
readonly default: string;
|
|
21
|
+
}, {
|
|
22
|
+
readonly locale: string;
|
|
23
|
+
}, {
|
|
24
|
+
readonly _tag: "SetLocale";
|
|
25
|
+
readonly locale: string;
|
|
26
|
+
}, never, never, never, void, {}, {}>;
|
|
27
|
+
//# sourceMappingURL=locale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale.d.ts","names":[],"sources":["../../src/state/locale.ts"],"mappings":";;qBAIa,aAAW,OAAA;;;YACZ,qBAAqB,YAAY;qBAEhC,yCAAa;;IAAqC,QAAM,OAAA;;;YACzD,uBAAuB,cAAc;;;;;;;qBAQpC,QAAM,OAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import { Bundle } from "foldkit-bundle";
|
|
4
|
+
// src/state/locale.ts
|
|
5
|
+
const LocaleModel = Schema.Struct({ locale: Schema.String });
|
|
6
|
+
const LocaleMessage = defineMessageUnion({ SetLocale: { locale: Schema.String } });
|
|
7
|
+
/**
|
|
8
|
+
* Locale as a bundle: the current locale string in the Model. Starts from
|
|
9
|
+
* `navigator.language` where available, falling back to the configured
|
|
10
|
+
* default; changes arrive as Messages. No subscriptions: language changes
|
|
11
|
+
* take effect on reload.
|
|
12
|
+
*/
|
|
13
|
+
const Locale = Bundle.make("Locale", {
|
|
14
|
+
Model: LocaleModel,
|
|
15
|
+
Message: LocaleMessage,
|
|
16
|
+
args: Schema.Struct({ default: Schema.String }),
|
|
17
|
+
init: (args) => ({ model: { locale: typeof navigator !== "undefined" && typeof navigator.language === "string" ? navigator.language : args.default } }),
|
|
18
|
+
update: (model, message) => LocaleMessage.match(message, { SetLocale: ({ locale }) => locale === model.locale ? { model } : { model: {
|
|
19
|
+
...model,
|
|
20
|
+
locale
|
|
21
|
+
} } })
|
|
22
|
+
});
|
|
23
|
+
export { Locale, LocaleMessage, LocaleModel };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=locale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale.js","names":[],"sources":["../../src/state/locale.ts"],"sourcesContent":["import { Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\n\nexport const LocaleModel = Schema.Struct({ locale: Schema.String })\nexport type LocaleModel = typeof LocaleModel.Type\n\nexport const LocaleMessage = defineMessageUnion({ SetLocale: { locale: Schema.String } })\nexport type LocaleMessage = typeof LocaleMessage.Type\n\n/**\n * Locale as a bundle: the current locale string in the Model. Starts from\n * `navigator.language` where available, falling back to the configured\n * default; changes arrive as Messages. No subscriptions: language changes\n * take effect on reload.\n */\nexport const Locale = Bundle.make('Locale', {\n Model: LocaleModel,\n Message: LocaleMessage,\n args: Schema.Struct({ default: Schema.String }),\n init: args => ({\n model: {\n locale:\n typeof navigator !== 'undefined' && typeof navigator.language === 'string'\n ? navigator.language\n : args.default,\n },\n }),\n update: (model, message) =>\n LocaleMessage.match(message, {\n SetLocale: ({ locale }) =>\n locale === model.locale ? { model } : { model: { ...model, locale } },\n }),\n})\n"],"mappings":";;;;AAIA,MAAa,cAAc,OAAO,OAAO,EAAE,QAAQ,OAAO,OAAO,CAAC;AAGlE,MAAa,gBAAgB,mBAAmB,EAAE,WAAW,EAAE,QAAQ,OAAO,OAAO,EAAE,CAAC;;;;;;;AASxF,MAAa,SAAS,OAAO,KAAK,UAAU;CAC1C,OAAO;CACP,SAAS;CACT,MAAM,OAAO,OAAO,EAAE,SAAS,OAAO,OAAO,CAAC;CAC9C,OAAM,UAAS,EACb,OAAO,EACL,QACE,OAAO,cAAc,eAAe,OAAO,UAAU,aAAa,WAC9D,UAAU,WACV,KAAK,QACb,EACF;CACA,SAAS,OAAO,YACd,cAAc,MAAM,SAAS,EAC3B,YAAY,EAAE,aACZ,WAAW,MAAM,SAAS,EAAE,MAAM,IAAI,EAAE,OAAO;EAAE,GAAG;EAAO;CAAO,EAAE,EACxE,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Bundle } from "foldkit-bundle";
|
|
3
|
+
export declare const PaginationModel: Schema.Struct<{
|
|
4
|
+
readonly page: Schema.Number;
|
|
5
|
+
readonly perPage: Schema.Number;
|
|
6
|
+
readonly total: Schema.NullOr<Schema.Number>;
|
|
7
|
+
}>;
|
|
8
|
+
export type PaginationModel = typeof PaginationModel.Type;
|
|
9
|
+
export declare const PaginationMessage: import("foldkit/message").MessageUnion<{
|
|
10
|
+
readonly GoToPage: {
|
|
11
|
+
page: Schema.Number;
|
|
12
|
+
};
|
|
13
|
+
readonly NextPage: {};
|
|
14
|
+
readonly PrevPage: {};
|
|
15
|
+
readonly SetPerPage: {
|
|
16
|
+
perPage: Schema.Number;
|
|
17
|
+
};
|
|
18
|
+
readonly SetTotal: {
|
|
19
|
+
total: Schema.NullOr<Schema.Number>;
|
|
20
|
+
};
|
|
21
|
+
}>;
|
|
22
|
+
export type PaginationMessage = typeof PaginationMessage.Type;
|
|
23
|
+
/** The number of pages, or null while the total is unknown. */
|
|
24
|
+
export declare const pageCount: (model: PaginationModel) => number | null;
|
|
25
|
+
/** The offset of the first item on the page, for a slice or a query. */
|
|
26
|
+
export declare const offset: (model: PaginationModel) => number;
|
|
27
|
+
/**
|
|
28
|
+
* Pagination as a bundle: the page, page size, and total live in the Model,
|
|
29
|
+
* so page changes replay and every transition clamps into range. Pure logic,
|
|
30
|
+
* no streams: data loading stays the application's job.
|
|
31
|
+
*/
|
|
32
|
+
export declare const Pagination: Bundle.Bundle<"Pagination", {
|
|
33
|
+
readonly perPage: number;
|
|
34
|
+
readonly total?: number | null | undefined;
|
|
35
|
+
}, {
|
|
36
|
+
readonly page: number;
|
|
37
|
+
readonly perPage: number;
|
|
38
|
+
readonly total: number | null;
|
|
39
|
+
}, {
|
|
40
|
+
readonly _tag: "GoToPage";
|
|
41
|
+
readonly page: number;
|
|
42
|
+
} | {
|
|
43
|
+
readonly _tag: "NextPage";
|
|
44
|
+
} | {
|
|
45
|
+
readonly _tag: "PrevPage";
|
|
46
|
+
} | {
|
|
47
|
+
readonly _tag: "SetPerPage";
|
|
48
|
+
readonly perPage: number;
|
|
49
|
+
} | {
|
|
50
|
+
readonly _tag: "SetTotal";
|
|
51
|
+
readonly total: number | null;
|
|
52
|
+
}, never, never, never, void, {}, {}>;
|
|
53
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","names":[],"sources":["../../src/state/pagination.ts"],"mappings":";;qBAKa,iBAAe,OAAA;;;;;YAKhB,yBAAyB,gBAAgB;qBAExC,6CAAiB;;IAChB,MAAI,OAAA;;;;;IAGF,SAAO,OAAA;;;IACT,OAAK,OAAA,OAAA,OAAA;;;YAEP,2BAA2B,kBAAkB;;qBAU5C,YAAS,OAAW;;qBAIpB,SAAM,OAAW;;;;;;qBAOjB,YAAU,OAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import { Bundle } from "foldkit-bundle";
|
|
4
|
+
import { unlessSame } from "../internal.js";
|
|
5
|
+
// src/state/pagination.ts
|
|
6
|
+
const PaginationModel = Schema.Struct({
|
|
7
|
+
page: Schema.Number,
|
|
8
|
+
perPage: Schema.Number,
|
|
9
|
+
total: Schema.NullOr(Schema.Number)
|
|
10
|
+
});
|
|
11
|
+
const PaginationMessage = defineMessageUnion({
|
|
12
|
+
GoToPage: { page: Schema.Number },
|
|
13
|
+
NextPage: {},
|
|
14
|
+
PrevPage: {},
|
|
15
|
+
SetPerPage: { perPage: Schema.Number },
|
|
16
|
+
SetTotal: { total: Schema.NullOr(Schema.Number) }
|
|
17
|
+
});
|
|
18
|
+
const clampPage = (page, perPage, total) => {
|
|
19
|
+
if (!Number.isInteger(page)) return 1;
|
|
20
|
+
if (page < 1) return 1;
|
|
21
|
+
if (total === null) return page;
|
|
22
|
+
return Math.min(page, Math.max(1, Math.ceil(total / perPage)));
|
|
23
|
+
};
|
|
24
|
+
/** The number of pages, or null while the total is unknown. */
|
|
25
|
+
const pageCount = (model) => model.total === null ? null : Math.max(1, Math.ceil(model.total / model.perPage));
|
|
26
|
+
/** The offset of the first item on the page, for a slice or a query. */
|
|
27
|
+
const offset = (model) => (model.page - 1) * model.perPage;
|
|
28
|
+
/**
|
|
29
|
+
* Pagination as a bundle: the page, page size, and total live in the Model,
|
|
30
|
+
* so page changes replay and every transition clamps into range. Pure logic,
|
|
31
|
+
* no streams: data loading stays the application's job.
|
|
32
|
+
*/
|
|
33
|
+
const Pagination = Bundle.make("Pagination", {
|
|
34
|
+
Model: PaginationModel,
|
|
35
|
+
Message: PaginationMessage,
|
|
36
|
+
args: Schema.Struct({
|
|
37
|
+
perPage: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),
|
|
38
|
+
total: Schema.optional(Schema.NullOr(Schema.Number.pipe(Schema.check(Schema.isFinite()))))
|
|
39
|
+
}),
|
|
40
|
+
init: (args) => ({ model: {
|
|
41
|
+
page: 1,
|
|
42
|
+
perPage: args.perPage,
|
|
43
|
+
total: args.total ?? null
|
|
44
|
+
} }),
|
|
45
|
+
update: (model, message) => PaginationMessage.match(message, {
|
|
46
|
+
GoToPage: ({ page }) => ({ model: unlessSame(model, {
|
|
47
|
+
...model,
|
|
48
|
+
page: clampPage(page, model.perPage, model.total)
|
|
49
|
+
}) }),
|
|
50
|
+
NextPage: () => ({ model: unlessSame(model, {
|
|
51
|
+
...model,
|
|
52
|
+
page: clampPage(model.page + 1, model.perPage, model.total)
|
|
53
|
+
}) }),
|
|
54
|
+
PrevPage: () => ({ model: unlessSame(model, {
|
|
55
|
+
...model,
|
|
56
|
+
page: clampPage(model.page - 1, model.perPage, model.total)
|
|
57
|
+
}) }),
|
|
58
|
+
SetPerPage: ({ perPage }) => perPage > 0 ? { model: unlessSame(model, {
|
|
59
|
+
...model,
|
|
60
|
+
perPage,
|
|
61
|
+
page: clampPage(model.page, perPage, model.total)
|
|
62
|
+
}) } : { model },
|
|
63
|
+
SetTotal: ({ total }) => total === null || total >= 0 ? { model: unlessSame(model, {
|
|
64
|
+
...model,
|
|
65
|
+
total,
|
|
66
|
+
page: clampPage(model.page, model.perPage, total)
|
|
67
|
+
}) } : { model }
|
|
68
|
+
})
|
|
69
|
+
});
|
|
70
|
+
export { Pagination, PaginationMessage, PaginationModel, offset, pageCount };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=pagination.js.map
|