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,106 @@
|
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
import { Bundle } from "foldkit-bundle";
|
|
4
|
+
import { unlessSame } from "../internal.js";
|
|
5
|
+
import * as ManagedResource from "foldkit/managedResource";
|
|
6
|
+
// src/device/media-stream.ts
|
|
7
|
+
const MediaStreamModel = Schema.Struct({
|
|
8
|
+
status: Schema.Literals([
|
|
9
|
+
"idle",
|
|
10
|
+
"requesting",
|
|
11
|
+
"live",
|
|
12
|
+
"denied"
|
|
13
|
+
]),
|
|
14
|
+
lastError: Schema.OptionFromNullOr(Schema.String)
|
|
15
|
+
});
|
|
16
|
+
const MediaStreamMessage = defineMessageUnion({
|
|
17
|
+
Started: {},
|
|
18
|
+
Stopped: {},
|
|
19
|
+
Live: {},
|
|
20
|
+
Ended: {},
|
|
21
|
+
Denied: {},
|
|
22
|
+
Failed: { message: Schema.String }
|
|
23
|
+
});
|
|
24
|
+
/** The live stream, for commands that attach it to an element. One assembly holds one. */
|
|
25
|
+
const LiveStream = ManagedResource.tag()("media-stream");
|
|
26
|
+
const failMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
27
|
+
const isDenied = (error) => typeof error?.name === "string" && ["NotAllowedError", "SecurityError"].includes(error.name);
|
|
28
|
+
/**
|
|
29
|
+
* A camera/mic stream as a bundle factory: one live MediaStream held in a
|
|
30
|
+
* Managed Resource while the Model asks for it. `Started` requests,
|
|
31
|
+
* `Stopped` releases; denial parks at `denied` (actionable UI), any other
|
|
32
|
+
* acquire failure parks at `idle` with the error noted — both clear
|
|
33
|
+
* requirements, so a failing device never spins an acquire loop. Release
|
|
34
|
+
* stops every track. No media-devices API fails the acquire instead of
|
|
35
|
+
* throwing.
|
|
36
|
+
*/
|
|
37
|
+
const mediaStream = (config) => {
|
|
38
|
+
const request = (constraints) => {
|
|
39
|
+
if (config.request !== void 0) return config.request(constraints);
|
|
40
|
+
if (typeof navigator === "undefined" || typeof navigator.mediaDevices?.getUserMedia !== "function") return Promise.reject(/* @__PURE__ */ new Error("media devices are unavailable"));
|
|
41
|
+
return navigator.mediaDevices.getUserMedia(constraints);
|
|
42
|
+
};
|
|
43
|
+
return Bundle.make(config.name, {
|
|
44
|
+
Model: MediaStreamModel,
|
|
45
|
+
Message: MediaStreamMessage,
|
|
46
|
+
args: Schema.Struct({
|
|
47
|
+
audio: Schema.Boolean,
|
|
48
|
+
video: Schema.Boolean
|
|
49
|
+
}),
|
|
50
|
+
init: () => ({ model: {
|
|
51
|
+
status: "idle",
|
|
52
|
+
lastError: Option.none()
|
|
53
|
+
} }),
|
|
54
|
+
update: (model, message) => MediaStreamMessage.match(message, {
|
|
55
|
+
Started: () => model.status === "requesting" || model.status === "live" ? { model } : { model: {
|
|
56
|
+
...model,
|
|
57
|
+
status: "requesting"
|
|
58
|
+
} },
|
|
59
|
+
Stopped: () => ({ model: unlessSame(model, {
|
|
60
|
+
...model,
|
|
61
|
+
status: "idle"
|
|
62
|
+
}) }),
|
|
63
|
+
Live: () => ({ model: unlessSame(model, {
|
|
64
|
+
...model,
|
|
65
|
+
status: "live",
|
|
66
|
+
lastError: Option.none()
|
|
67
|
+
}) }),
|
|
68
|
+
Ended: () => ({ model: unlessSame(model, {
|
|
69
|
+
...model,
|
|
70
|
+
status: "idle"
|
|
71
|
+
}) }),
|
|
72
|
+
Denied: () => ({ model: unlessSame(model, {
|
|
73
|
+
...model,
|
|
74
|
+
status: "denied"
|
|
75
|
+
}) }),
|
|
76
|
+
Failed: ({ message }) => ({ model: unlessSame(model, {
|
|
77
|
+
...model,
|
|
78
|
+
status: "idle",
|
|
79
|
+
lastError: Option.some(message)
|
|
80
|
+
}) })
|
|
81
|
+
}),
|
|
82
|
+
resources: (args) => ManagedResource.make()((entry) => ({ stream: entry(Schema.Option(Schema.Struct({
|
|
83
|
+
audio: Schema.Boolean,
|
|
84
|
+
video: Schema.Boolean
|
|
85
|
+
})), {
|
|
86
|
+
resource: LiveStream,
|
|
87
|
+
modelToMaybeRequirements: (model) => model.status === "requesting" || model.status === "live" ? Option.some({
|
|
88
|
+
audio: args.audio,
|
|
89
|
+
video: args.video
|
|
90
|
+
}) : Option.none(),
|
|
91
|
+
acquire: (constraints) => Effect.tryPromise({
|
|
92
|
+
try: () => request(constraints),
|
|
93
|
+
catch: (error) => error
|
|
94
|
+
}),
|
|
95
|
+
onAcquired: () => MediaStreamMessage.Live(),
|
|
96
|
+
onReleased: () => MediaStreamMessage.Ended(),
|
|
97
|
+
onAcquireError: (error) => isDenied(error) ? MediaStreamMessage.Denied() : MediaStreamMessage.Failed({ message: failMessage(error) }),
|
|
98
|
+
release: (stream) => Effect.sync(() => {
|
|
99
|
+
for (const track of stream.getTracks()) track.stop();
|
|
100
|
+
})
|
|
101
|
+
}) }))
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
export { LiveStream, MediaStreamMessage, MediaStreamModel, mediaStream };
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=media-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-stream.js","names":[],"sources":["../../src/device/media-stream.ts"],"sourcesContent":["import { Effect, Option, Schema } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as ManagedResource from 'foldkit/managedResource'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\nimport { unlessSame } from '../internal.js'\n\nexport const MediaStreamModel = Schema.Struct({\n status: Schema.Literals(['idle', 'requesting', 'live', 'denied']),\n lastError: Schema.OptionFromNullOr(Schema.String),\n})\nexport type MediaStreamModel = typeof MediaStreamModel.Type\n\nexport const MediaStreamMessage = defineMessageUnion({\n Started: {},\n Stopped: {},\n Live: {},\n Ended: {},\n Denied: {},\n Failed: { message: Schema.String },\n})\nexport type MediaStreamMessage = typeof MediaStreamMessage.Type\n\n/** A single media track: stopped on release. */\nexport interface MediaTrackHandle {\n stop(): void\n}\n\n/** The live stream: its tracks stop on release. */\nexport interface MediaStreamHandle {\n getTracks(): ReadonlyArray<MediaTrackHandle>\n}\n\n/** Which tracks `mediaStream` asks `getUserMedia` for. */\nexport interface MediaStreamConstraints {\n readonly audio: boolean\n readonly video: boolean\n}\n\n/** The live stream, for commands that attach it to an element. One assembly holds one. */\nexport const LiveStream = ManagedResource.tag<MediaStreamHandle>()('media-stream')\n\n/** The stream service a placed camera requires. */\nexport type LiveStreamService = ManagedResource.ServiceOf<typeof LiveStream>\n\ntype StreamResources = Readonly<{\n stream: ManagedResource.Entry<\n MediaStreamModel,\n MediaStreamMessage,\n Option.Option<MediaStreamConstraints>,\n MediaStreamHandle,\n LiveStreamService,\n () => MediaStreamMessage\n >\n}>\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst isDenied = (error: unknown): boolean =>\n typeof (error as { readonly name?: unknown } | null)?.name === 'string' &&\n ['NotAllowedError', 'SecurityError'].includes((error as { readonly name: string }).name)\n\n/**\n * A camera/mic stream as a bundle factory: one live MediaStream held in a\n * Managed Resource while the Model asks for it. `Started` requests,\n * `Stopped` releases; denial parks at `denied` (actionable UI), any other\n * acquire failure parks at `idle` with the error noted — both clear\n * requirements, so a failing device never spins an acquire loop. Release\n * stops every track. No media-devices API fails the acquire instead of\n * throwing.\n */\nexport const mediaStream = <const Name extends string>(config: {\n readonly name: Name\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n readonly request?:\n | ((constraints: MediaStreamConstraints) => Promise<MediaStreamHandle>)\n | undefined\n}) => {\n const request = (constraints: MediaStreamConstraints): Promise<MediaStreamHandle> => {\n if (config.request !== undefined) return config.request(constraints)\n if (\n typeof navigator === 'undefined' ||\n typeof navigator.mediaDevices?.getUserMedia !== 'function'\n ) {\n return Promise.reject(new Error('media devices are unavailable'))\n }\n return navigator.mediaDevices.getUserMedia(constraints)\n }\n\n return Bundle.make(config.name, {\n Model: MediaStreamModel,\n Message: MediaStreamMessage,\n args: Schema.Struct({ audio: Schema.Boolean, video: Schema.Boolean }),\n init: () => ({ model: { status: 'idle' as const, lastError: Option.none() } }),\n update: (model, message) =>\n MediaStreamMessage.match<\n Update.ReturnWithOutMessage<MediaStreamModel, MediaStreamMessage, never>\n >(message, {\n // Starting a live stream would wait at `requesting` for an acquire\n // that never comes: the requirements are the same.\n Started: () =>\n model.status === 'requesting' || model.status === 'live'\n ? { model }\n : { model: { ...model, status: 'requesting' as const } },\n Stopped: () => ({ model: unlessSame(model, { ...model, status: 'idle' }) }),\n Live: () => ({\n model: unlessSame(model, { ...model, status: 'live', lastError: Option.none() }),\n }),\n // Stopping releases the stream, whose release reports it ended.\n Ended: () => ({ model: unlessSame(model, { ...model, status: 'idle' }) }),\n Denied: () => ({ model: unlessSame(model, { ...model, status: 'denied' }) }),\n Failed: ({ message }) => ({\n model: unlessSame(model, { ...model, status: 'idle', lastError: Option.some(message) }),\n }),\n }),\n resources: args =>\n ManagedResource.make<MediaStreamModel, MediaStreamMessage>()(entry => ({\n stream: entry(\n Schema.Option(Schema.Struct({ audio: Schema.Boolean, video: Schema.Boolean })),\n {\n resource: LiveStream,\n modelToMaybeRequirements: model =>\n model.status === 'requesting' || model.status === 'live'\n ? Option.some({ audio: args.audio, video: args.video })\n : Option.none(),\n acquire: (constraints: MediaStreamConstraints) =>\n Effect.tryPromise({\n try: () => request(constraints),\n catch: (error: unknown) => error,\n }),\n onAcquired: () => MediaStreamMessage.Live(),\n onReleased: () => MediaStreamMessage.Ended(),\n onAcquireError: error =>\n isDenied(error)\n ? MediaStreamMessage.Denied()\n : MediaStreamMessage.Failed({ message: failMessage(error) }),\n release: stream =>\n Effect.sync(() => {\n for (const track of stream.getTracks()) track.stop()\n }),\n },\n ),\n })),\n })\n}\n"],"mappings":";;;;;;AAOA,MAAa,mBAAmB,OAAO,OAAO;CAC5C,QAAQ,OAAO,SAAS;EAAC;EAAQ;EAAc;EAAQ;CAAQ,CAAC;CAChE,WAAW,OAAO,iBAAiB,OAAO,MAAM;AAClD,CAAC;AAGD,MAAa,qBAAqB,mBAAmB;CACnD,SAAS,CAAC;CACV,SAAS,CAAC;CACV,MAAM,CAAC;CACP,OAAO,CAAC;CACR,QAAQ,CAAC;CACT,QAAQ,EAAE,SAAS,OAAO,OAAO;AACnC,CAAC;;AAoBD,MAAa,aAAa,gBAAgB,IAAuB,CAAC,CAAC,cAAc;AAgBjF,MAAM,eAAe,UACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAEvD,MAAM,YAAY,UAChB,OAAQ,OAA8C,SAAS,YAC/D,CAAC,mBAAmB,eAAe,CAAC,CAAC,SAAU,MAAoC,IAAI;;;;;;;;;;AAWzF,MAAa,eAA0C,WAMjD;CACJ,MAAM,WAAW,gBAAoE;EACnF,IAAI,OAAO,YAAY,KAAA,GAAW,OAAO,OAAO,QAAQ,WAAW;EACnE,IACE,OAAO,cAAc,eACrB,OAAO,UAAU,cAAc,iBAAiB,YAEhD,OAAO,QAAQ,uBAAO,IAAI,MAAM,+BAA+B,CAAC;EAElE,OAAO,UAAU,aAAa,aAAa,WAAW;CACxD;CAEA,OAAO,OAAO,KAAK,OAAO,MAAM;EAC9B,OAAO;EACP,SAAS;EACT,MAAM,OAAO,OAAO;GAAE,OAAO,OAAO;GAAS,OAAO,OAAO;EAAQ,CAAC;EACpE,aAAa,EAAE,OAAO;GAAE,QAAQ;GAAiB,WAAW,OAAO,KAAK;EAAE,EAAE;EAC5E,SAAS,OAAO,YACd,mBAAmB,MAEjB,SAAS;GAGT,eACE,MAAM,WAAW,gBAAgB,MAAM,WAAW,SAC9C,EAAE,MAAM,IACR,EAAE,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAsB,EAAE;GAC3D,gBAAgB,EAAE,OAAO,WAAW,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAO,CAAC,EAAE;GACzE,aAAa,EACX,OAAO,WAAW,OAAO;IAAE,GAAG;IAAO,QAAQ;IAAQ,WAAW,OAAO,KAAK;GAAE,CAAC,EACjF;GAEA,cAAc,EAAE,OAAO,WAAW,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAO,CAAC,EAAE;GACvE,eAAe,EAAE,OAAO,WAAW,OAAO;IAAE,GAAG;IAAO,QAAQ;GAAS,CAAC,EAAE;GAC1E,SAAS,EAAE,eAAe,EACxB,OAAO,WAAW,OAAO;IAAE,GAAG;IAAO,QAAQ;IAAQ,WAAW,OAAO,KAAK,OAAO;GAAE,CAAC,EACxF;EACF,CAAC;EACH,YAAW,SACT,gBAAgB,KAA2C,CAAC,EAAC,WAAU,EACrE,QAAQ,MACN,OAAO,OAAO,OAAO,OAAO;GAAE,OAAO,OAAO;GAAS,OAAO,OAAO;EAAQ,CAAC,CAAC,GAC7E;GACE,UAAU;GACV,2BAA0B,UACxB,MAAM,WAAW,gBAAgB,MAAM,WAAW,SAC9C,OAAO,KAAK;IAAE,OAAO,KAAK;IAAO,OAAO,KAAK;GAAM,CAAC,IACpD,OAAO,KAAK;GAClB,UAAU,gBACR,OAAO,WAAW;IAChB,WAAW,QAAQ,WAAW;IAC9B,QAAQ,UAAmB;GAC7B,CAAC;GACH,kBAAkB,mBAAmB,KAAK;GAC1C,kBAAkB,mBAAmB,MAAM;GAC3C,iBAAgB,UACd,SAAS,KAAK,IACV,mBAAmB,OAAO,IAC1B,mBAAmB,OAAO,EAAE,SAAS,YAAY,KAAK,EAAE,CAAC;GAC/D,UAAS,WACP,OAAO,WAAW;IAChB,KAAK,MAAM,SAAS,OAAO,UAAU,GAAG,MAAM,KAAK;GACrD,CAAC;EACL,CACF,EACF,EAAE;CACN,CAAC;AACH"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Option, Queue, Schema } from "effect";
|
|
2
|
+
import { Bundle } from "foldkit-bundle";
|
|
3
|
+
import * as ManagedResource from "foldkit/managedResource";
|
|
4
|
+
export declare const PermissionState: Schema.Literals<readonly ["granted", "denied", "prompt"]>;
|
|
5
|
+
export type PermissionState = typeof PermissionState.Type;
|
|
6
|
+
export declare const PermissionsModel: Schema.Struct<{
|
|
7
|
+
readonly states: Schema.$Record<Schema.String, Schema.Literals<readonly ["granted", "denied", "prompt"]>>;
|
|
8
|
+
readonly lastError: Schema.OptionFromNullOr<Schema.String>;
|
|
9
|
+
}>;
|
|
10
|
+
export type PermissionsModel = typeof PermissionsModel.Type;
|
|
11
|
+
export declare const PermissionsMessage: import("foldkit/message").MessageUnion<{
|
|
12
|
+
readonly Snapshot: {
|
|
13
|
+
states: Schema.$Record<Schema.String, Schema.Literals<readonly ["granted", "denied", "prompt"]>>;
|
|
14
|
+
};
|
|
15
|
+
readonly Changed: {
|
|
16
|
+
name: Schema.String;
|
|
17
|
+
state: Schema.Literals<readonly ["granted", "denied", "prompt"]>;
|
|
18
|
+
};
|
|
19
|
+
readonly Cleared: {};
|
|
20
|
+
readonly Failed: {
|
|
21
|
+
message: Schema.String;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
export type PermissionsMessage = typeof PermissionsMessage.Type;
|
|
25
|
+
/** A queried permission status; `onchange` fires while watched. */
|
|
26
|
+
export interface PermissionStatusHandle {
|
|
27
|
+
readonly state: string;
|
|
28
|
+
onchange: (() => void) | null;
|
|
29
|
+
}
|
|
30
|
+
/** The slice of the Permissions API the bundle needs. */
|
|
31
|
+
export interface PermissionsHandle {
|
|
32
|
+
query(descriptor: {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
}): Promise<PermissionStatusHandle>;
|
|
35
|
+
}
|
|
36
|
+
interface Acquired {
|
|
37
|
+
readonly statuses: ReadonlyMap<string, PermissionStatusHandle>;
|
|
38
|
+
readonly snapshot: Readonly<Record<string, PermissionState>>;
|
|
39
|
+
readonly events: Queue.Queue<PermissionsMessage>;
|
|
40
|
+
}
|
|
41
|
+
/** The watched statuses, for the changes subscription. One assembly holds one. */
|
|
42
|
+
export declare const Watch: ManagedResource.ManagedResource<Acquired, ManagedResource.ManagedResourceService<"permissions">>;
|
|
43
|
+
/** The watch service a placed Permissions bundle requires. */
|
|
44
|
+
export type WatchService = ManagedResource.ServiceOf<typeof Watch>;
|
|
45
|
+
/**
|
|
46
|
+
* Permission states as a bundle factory: queried on placement, kept current
|
|
47
|
+
* by each status object's `onchange`. A resource holds the status objects
|
|
48
|
+
* (they cannot live in the Model); its queue feeds the subscription, and
|
|
49
|
+
* release detaches every handler. An unknown permission name fails the
|
|
50
|
+
* acquire; an unknown state string fails it too — both become `Failed`,
|
|
51
|
+
* never Model facts.
|
|
52
|
+
*/
|
|
53
|
+
export declare const permissions: <const Name extends string>(config: {
|
|
54
|
+
readonly name: Name;
|
|
55
|
+
/** Read lazily so tests can substitute a double, like the SSE bundle. */
|
|
56
|
+
readonly create?: ((navigator: Navigator) => PermissionsHandle | null) | undefined;
|
|
57
|
+
}) => Bundle.Bundle<Name, {
|
|
58
|
+
readonly names: readonly string[];
|
|
59
|
+
}, {
|
|
60
|
+
readonly states: {
|
|
61
|
+
readonly [x: string]: "denied" | "granted" | "prompt";
|
|
62
|
+
};
|
|
63
|
+
readonly lastError: Option.Option<string>;
|
|
64
|
+
}, {
|
|
65
|
+
readonly _tag: "Changed";
|
|
66
|
+
readonly name: string;
|
|
67
|
+
readonly state: "denied" | "granted" | "prompt";
|
|
68
|
+
} | {
|
|
69
|
+
readonly _tag: "Cleared";
|
|
70
|
+
} | {
|
|
71
|
+
readonly _tag: "Failed";
|
|
72
|
+
readonly message: string;
|
|
73
|
+
} | {
|
|
74
|
+
readonly _tag: "Snapshot";
|
|
75
|
+
readonly states: {
|
|
76
|
+
readonly [x: string]: "denied" | "granted" | "prompt";
|
|
77
|
+
};
|
|
78
|
+
}, never, never, ManagedResource.ManagedResourceService<"permissions">, void, {
|
|
79
|
+
watch: ManagedResource.Entry<{
|
|
80
|
+
readonly states: {
|
|
81
|
+
readonly [x: string]: "denied" | "granted" | "prompt";
|
|
82
|
+
};
|
|
83
|
+
readonly lastError: Option.Option<string>;
|
|
84
|
+
}, {
|
|
85
|
+
readonly _tag: "Changed";
|
|
86
|
+
readonly name: string;
|
|
87
|
+
readonly state: "denied" | "granted" | "prompt";
|
|
88
|
+
} | {
|
|
89
|
+
readonly _tag: "Cleared";
|
|
90
|
+
} | {
|
|
91
|
+
readonly _tag: "Failed";
|
|
92
|
+
readonly message: string;
|
|
93
|
+
} | {
|
|
94
|
+
readonly _tag: "Snapshot";
|
|
95
|
+
readonly states: {
|
|
96
|
+
readonly [x: string]: "denied" | "granted" | "prompt";
|
|
97
|
+
};
|
|
98
|
+
}, Option.Option<readonly string[]>, Acquired, ManagedResource.ManagedResourceService<"permissions">, ({ snapshot }: Acquired) => {
|
|
99
|
+
readonly _tag: "Snapshot";
|
|
100
|
+
readonly states: {
|
|
101
|
+
readonly [x: string]: "denied" | "granted" | "prompt";
|
|
102
|
+
};
|
|
103
|
+
}>;
|
|
104
|
+
}, {}>;
|
|
105
|
+
export {};
|
|
106
|
+
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","names":[],"sources":["../../src/device/permissions.ts"],"mappings":";;;qBAQa,iBAAe,OAAA;YAChB,yBAAyB,gBAAgB;qBAExC,kBAAgB,OAAA;;;;YAIjB,0BAA0B,iBAAiB;qBAE1C,8CAAkB;;IACjB,QAAM,OAAA,QAAA,OAAA,QAAA,OAAA;;;IACP,MAAI,OAAA;IAAiB,OAAK,OAAA;;;;IAE3B,SAAO,OAAA;;;YAEP,4BAA4B,mBAAmB;;iBAG1C;WACN;EACT;;;iBAIe;EACf,MAAM;aAAuB;MAAiB,QAAQ;;UAG9C;WACC,UAAU,oBAAoB;WAC9B,UAAU,SAAS,eAAe;WAClC,QAAQ,MAAM,MAAM;;;qBAIlB,OAAK,gBAAA,gBAAA,UAAA,gBAAA;;YAGN,eAAe,gBAAgB,iBAAiB;;;;;;;;;qBAoB/C,oBAAqB,qBAAmB;WAC1C,MAAM;;WAEN,WAAW,WAAW,cAAc;MAC9C,OAAA,OAAA"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Effect, Option, Queue, 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 { unlessSame } from "../internal.js";
|
|
6
|
+
import * as ManagedResource from "foldkit/managedResource";
|
|
7
|
+
// src/device/permissions.ts
|
|
8
|
+
const PermissionState = Schema.Literals([
|
|
9
|
+
"granted",
|
|
10
|
+
"denied",
|
|
11
|
+
"prompt"
|
|
12
|
+
]);
|
|
13
|
+
const PermissionsModel = Schema.Struct({
|
|
14
|
+
states: Schema.Record(Schema.String, PermissionState),
|
|
15
|
+
lastError: Schema.OptionFromNullOr(Schema.String)
|
|
16
|
+
});
|
|
17
|
+
const PermissionsMessage = defineMessageUnion({
|
|
18
|
+
Snapshot: { states: Schema.Record(Schema.String, PermissionState) },
|
|
19
|
+
Changed: {
|
|
20
|
+
name: Schema.String,
|
|
21
|
+
state: PermissionState
|
|
22
|
+
},
|
|
23
|
+
Cleared: {},
|
|
24
|
+
Failed: { message: Schema.String }
|
|
25
|
+
});
|
|
26
|
+
/** The watched statuses, for the changes subscription. One assembly holds one. */
|
|
27
|
+
const Watch = ManagedResource.tag()("permissions");
|
|
28
|
+
const failMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
29
|
+
const readState = (state, name) => {
|
|
30
|
+
if (state !== "granted" && state !== "denied" && state !== "prompt") throw new Error(`unknown permission state for ${name}: ${state}`);
|
|
31
|
+
return state;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Permission states as a bundle factory: queried on placement, kept current
|
|
35
|
+
* by each status object's `onchange`. A resource holds the status objects
|
|
36
|
+
* (they cannot live in the Model); its queue feeds the subscription, and
|
|
37
|
+
* release detaches every handler. An unknown permission name fails the
|
|
38
|
+
* acquire; an unknown state string fails it too — both become `Failed`,
|
|
39
|
+
* never Model facts.
|
|
40
|
+
*/
|
|
41
|
+
const permissions = (config) => {
|
|
42
|
+
const handle = () => {
|
|
43
|
+
if (typeof navigator === "undefined") return null;
|
|
44
|
+
if (config.create !== void 0) return config.create(navigator);
|
|
45
|
+
return navigator.permissions ?? null;
|
|
46
|
+
};
|
|
47
|
+
return Bundle.make(config.name, {
|
|
48
|
+
Model: PermissionsModel,
|
|
49
|
+
Message: PermissionsMessage,
|
|
50
|
+
args: Schema.Struct({ names: Schema.Array(Schema.String) }),
|
|
51
|
+
init: () => ({ model: {
|
|
52
|
+
states: {},
|
|
53
|
+
lastError: Option.none()
|
|
54
|
+
} }),
|
|
55
|
+
update: (model, message) => PermissionsMessage.match(message, {
|
|
56
|
+
Snapshot: ({ states }) => {
|
|
57
|
+
const next = unlessSame(model.states, { ...states });
|
|
58
|
+
return next === model.states ? { model } : { model: {
|
|
59
|
+
...model,
|
|
60
|
+
states: next
|
|
61
|
+
} };
|
|
62
|
+
},
|
|
63
|
+
Changed: ({ name, state }) => Object.hasOwn(model.states, name) && model.states[name] === state ? { model } : { model: {
|
|
64
|
+
...model,
|
|
65
|
+
states: {
|
|
66
|
+
...model.states,
|
|
67
|
+
[name]: state
|
|
68
|
+
}
|
|
69
|
+
} },
|
|
70
|
+
Cleared: () => Object.keys(model.states).length === 0 ? { model } : { model: {
|
|
71
|
+
...model,
|
|
72
|
+
states: {}
|
|
73
|
+
} },
|
|
74
|
+
Failed: ({ message }) => ({ model: unlessSame(model, {
|
|
75
|
+
...model,
|
|
76
|
+
lastError: Option.some(message)
|
|
77
|
+
}) })
|
|
78
|
+
}),
|
|
79
|
+
resources: (args) => {
|
|
80
|
+
const names = Option.some([...new Set(args.names)]);
|
|
81
|
+
return ManagedResource.make()((entry) => ({ watch: entry(Schema.Option(Schema.Array(Schema.String)), {
|
|
82
|
+
resource: Watch,
|
|
83
|
+
modelToMaybeRequirements: () => names,
|
|
84
|
+
acquire: (names) => Effect.gen(function* () {
|
|
85
|
+
const api = handle();
|
|
86
|
+
if (api === null) throw new Error("permissions are unavailable");
|
|
87
|
+
const events = yield* Queue.unbounded();
|
|
88
|
+
const statuses = /* @__PURE__ */ new Map();
|
|
89
|
+
let tail = Promise.resolve();
|
|
90
|
+
const offer = (message) => {
|
|
91
|
+
const run = () => Effect.runPromise(Queue.offer(events, message)).then(() => void 0, () => void 0);
|
|
92
|
+
tail = tail.then(run, run);
|
|
93
|
+
};
|
|
94
|
+
const queried = [];
|
|
95
|
+
for (const name of names) {
|
|
96
|
+
const status = yield* Effect.tryPromise({
|
|
97
|
+
try: () => api.query({ name }),
|
|
98
|
+
catch: (error) => error
|
|
99
|
+
});
|
|
100
|
+
queried.push([
|
|
101
|
+
name,
|
|
102
|
+
status,
|
|
103
|
+
readState(status.state, name)
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
const snapshot = {};
|
|
107
|
+
for (const [name, status, state] of queried) {
|
|
108
|
+
status.onchange = () => {
|
|
109
|
+
try {
|
|
110
|
+
offer(PermissionsMessage.Changed({
|
|
111
|
+
name,
|
|
112
|
+
state: readState(status.state, name)
|
|
113
|
+
}));
|
|
114
|
+
} catch {
|
|
115
|
+
offer(PermissionsMessage.Failed({ message: `unknown permission state for ${name}` }));
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
statuses.set(name, status);
|
|
119
|
+
snapshot[name] = state;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
statuses,
|
|
123
|
+
snapshot,
|
|
124
|
+
events
|
|
125
|
+
};
|
|
126
|
+
}),
|
|
127
|
+
onAcquired: ({ snapshot }) => PermissionsMessage.Snapshot({ states: { ...snapshot } }),
|
|
128
|
+
onReleased: () => PermissionsMessage.Cleared(),
|
|
129
|
+
onAcquireError: (error) => PermissionsMessage.Failed({ message: failMessage(error) }),
|
|
130
|
+
release: ({ statuses, events }) => Effect.asVoid(Effect.andThen(Effect.sync(() => {
|
|
131
|
+
for (const status of statuses.values()) status.onchange = null;
|
|
132
|
+
}), Queue.shutdown(events)))
|
|
133
|
+
}) }));
|
|
134
|
+
},
|
|
135
|
+
subscriptions: () => Subscription.make()(() => ({ changes: Subscription.persistentEntry(Stream.unwrap(Effect.matchEffect(Watch.get, {
|
|
136
|
+
onFailure: () => Effect.succeed(Stream.empty),
|
|
137
|
+
onSuccess: ({ events }) => Effect.succeed(Stream.fromQueue(events))
|
|
138
|
+
}))) }))
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
export { PermissionState, PermissionsMessage, PermissionsModel, Watch, permissions };
|
|
142
|
+
|
|
143
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.js","names":[],"sources":["../../src/device/permissions.ts"],"sourcesContent":["import { Effect, Option, Queue, Schema, Stream } from 'effect'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as ManagedResource from 'foldkit/managedResource'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle } from 'foldkit-bundle'\nimport { unlessSame } from '../internal.js'\n\nexport const PermissionState = Schema.Literals(['granted', 'denied', 'prompt'])\nexport type PermissionState = typeof PermissionState.Type\n\nexport const PermissionsModel = Schema.Struct({\n states: Schema.Record(Schema.String, PermissionState),\n lastError: Schema.OptionFromNullOr(Schema.String),\n})\nexport type PermissionsModel = typeof PermissionsModel.Type\n\nexport const PermissionsMessage = defineMessageUnion({\n Snapshot: { states: Schema.Record(Schema.String, PermissionState) },\n Changed: { name: Schema.String, state: PermissionState },\n Cleared: {},\n Failed: { message: Schema.String },\n})\nexport type PermissionsMessage = typeof PermissionsMessage.Type\n\n/** A queried permission status; `onchange` fires while watched. */\nexport interface PermissionStatusHandle {\n readonly state: string\n onchange: (() => void) | null\n}\n\n/** The slice of the Permissions API the bundle needs. */\nexport interface PermissionsHandle {\n query(descriptor: { readonly name: string }): Promise<PermissionStatusHandle>\n}\n\ninterface Acquired {\n readonly statuses: ReadonlyMap<string, PermissionStatusHandle>\n readonly snapshot: Readonly<Record<string, PermissionState>>\n readonly events: Queue.Queue<PermissionsMessage>\n}\n\n/** The watched statuses, for the changes subscription. One assembly holds one. */\nexport const Watch = ManagedResource.tag<Acquired>()('permissions')\n\n/** The watch service a placed Permissions bundle requires. */\nexport type WatchService = ManagedResource.ServiceOf<typeof Watch>\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\nconst readState = (state: string, name: string): PermissionState => {\n if (state !== 'granted' && state !== 'denied' && state !== 'prompt') {\n throw new Error(`unknown permission state for ${name}: ${state}`)\n }\n return state\n}\n\n/**\n * Permission states as a bundle factory: queried on placement, kept current\n * by each status object's `onchange`. A resource holds the status objects\n * (they cannot live in the Model); its queue feeds the subscription, and\n * release detaches every handler. An unknown permission name fails the\n * acquire; an unknown state string fails it too — both become `Failed`,\n * never Model facts.\n */\nexport const permissions = <const Name extends string>(config: {\n readonly name: Name\n /** Read lazily so tests can substitute a double, like the SSE bundle. */\n readonly create?: ((navigator: Navigator) => PermissionsHandle | null) | undefined\n}) => {\n const handle = (): PermissionsHandle | null => {\n if (typeof navigator === 'undefined') return null\n if (config.create !== undefined) return config.create(navigator)\n const api = navigator.permissions as unknown as PermissionsHandle | undefined\n return api ?? null\n }\n\n return Bundle.make(config.name, {\n Model: PermissionsModel,\n Message: PermissionsMessage,\n args: Schema.Struct({ names: Schema.Array(Schema.String) }),\n init: () => ({ model: { states: {}, lastError: Option.none() } }),\n update: (model, message) =>\n PermissionsMessage.match<\n Update.ReturnWithOutMessage<PermissionsModel, PermissionsMessage, never>\n >(message, {\n Snapshot: ({ states }) => {\n const next = unlessSame(model.states, { ...states })\n return next === model.states ? { model } : { model: { ...model, states: next } }\n },\n Changed: ({ name, state }) =>\n Object.hasOwn(model.states, name) && model.states[name] === state\n ? { model }\n : { model: { ...model, states: { ...model.states, [name]: state } } },\n Cleared: () =>\n Object.keys(model.states).length === 0 ? { model } : { model: { ...model, states: {} } },\n Failed: ({ message }) => ({\n model: unlessSame(model, { ...model, lastError: Option.some(message) }),\n }),\n }),\n resources: args => {\n const names = Option.some([...new Set(args.names)])\n return ManagedResource.make<PermissionsModel, PermissionsMessage>()(entry => ({\n watch: entry(Schema.Option(Schema.Array(Schema.String)), {\n resource: Watch,\n modelToMaybeRequirements: () => names,\n acquire: (names: ReadonlyArray<string>) =>\n Effect.gen(function* () {\n const api = handle()\n if (api === null) throw new Error('permissions are unavailable')\n const events = yield* Queue.unbounded<PermissionsMessage>()\n const statuses = new Map<string, PermissionStatusHandle>()\n // Offers run in arrival order; see the WebSocket bundle.\n let tail: Promise<void> = Promise.resolve()\n const offer = (message: PermissionsMessage) => {\n const run = () =>\n Effect.runPromise(Queue.offer(events, message)).then(\n () => undefined,\n () => undefined,\n )\n tail = tail.then(run, run)\n }\n // Two phases: query and validate everything first, so a later\n // name failing cannot abandon handlers attached to earlier ones.\n const queried: Array<readonly [string, PermissionStatusHandle, PermissionState]> = []\n for (const name of names) {\n const status = yield* Effect.tryPromise({\n try: () => api.query({ name }),\n catch: (error: unknown) => error,\n })\n queried.push([name, status, readState(status.state, name)] as const)\n }\n const snapshot: Record<string, PermissionState> = {}\n for (const [name, status, state] of queried) {\n status.onchange = () => {\n try {\n offer(\n PermissionsMessage.Changed({ name, state: readState(status.state, name) }),\n )\n } catch {\n // readState evaluates before offer runs, so a state the\n // schema rejects lands here instead of crashing the handler.\n offer(\n PermissionsMessage.Failed({\n message: `unknown permission state for ${name}`,\n }),\n )\n }\n }\n statuses.set(name, status)\n snapshot[name] = state\n }\n return { statuses, snapshot, events }\n }),\n // The snapshot is captured during acquire, so onAcquired cannot\n // throw re-reading a state that changed mid-acquire.\n onAcquired: ({ snapshot }) => PermissionsMessage.Snapshot({ states: { ...snapshot } }),\n onReleased: () => PermissionsMessage.Cleared(),\n onAcquireError: error => PermissionsMessage.Failed({ message: failMessage(error) }),\n release: ({ statuses, events }) =>\n Effect.asVoid(\n Effect.andThen(\n Effect.sync(() => {\n for (const status of statuses.values()) status.onchange = null\n }),\n Queue.shutdown(events),\n ),\n ),\n }),\n }))\n },\n subscriptions: (): Subscription.Subscriptions<\n PermissionsModel,\n PermissionsMessage,\n WatchService\n > =>\n Subscription.make<PermissionsModel, PermissionsMessage, WatchService>()(() => ({\n // No model gate: the queue outlives every state change, and release\n // ends the stream through teardown, not through dependencies.\n changes: Subscription.persistentEntry(\n Stream.unwrap(\n Effect.matchEffect(Watch.get, {\n // Released between placement and attach: end quietly.\n onFailure: () => Effect.succeed(Stream.empty),\n onSuccess: ({ events }) => Effect.succeed(Stream.fromQueue(events)),\n }),\n ),\n ),\n })),\n })\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import * as Mount from "foldkit/mount";
|
|
3
|
+
export declare const Focused: Schema.TaggedStruct<"Focused", {}>;
|
|
4
|
+
export type Focused = typeof Focused.Type;
|
|
5
|
+
/**
|
|
6
|
+
* Autofocus as a Mount: focuses the element on insert, then emits `Focused`.
|
|
7
|
+
* The message means focus was requested — a non-focusable element or a
|
|
8
|
+
* hidden tab may decline, and the browser does not report that. Plain
|
|
9
|
+
* `focus()`, no scroll options: scroll behavior stays the application's
|
|
10
|
+
* choice.
|
|
11
|
+
*/
|
|
12
|
+
export declare const Autofocus: Mount.MountDefinitionNoArgs<"Autofocus", {
|
|
13
|
+
readonly _tag: "Focused";
|
|
14
|
+
}>;
|
|
15
|
+
//# sourceMappingURL=autofocus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autofocus.d.ts","names":[],"sources":["../../src/dom/autofocus.ts"],"mappings":";;qBAGa,SAAO,OAAA;YACR,iBAAiB,QAAQ;;;;;;;;qBASxB,WAAS,MAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import * as Mount from "foldkit/mount";
|
|
3
|
+
// src/dom/autofocus.ts
|
|
4
|
+
const Focused = Schema.TaggedStruct("Focused", {});
|
|
5
|
+
/**
|
|
6
|
+
* Autofocus as a Mount: focuses the element on insert, then emits `Focused`.
|
|
7
|
+
* The message means focus was requested — a non-focusable element or a
|
|
8
|
+
* hidden tab may decline, and the browser does not report that. Plain
|
|
9
|
+
* `focus()`, no scroll options: scroll behavior stays the application's
|
|
10
|
+
* choice.
|
|
11
|
+
*/
|
|
12
|
+
const Autofocus = Mount.define("Autofocus", {
|
|
13
|
+
messages: [Focused],
|
|
14
|
+
execute: ({ element }) => Effect.sync(() => {
|
|
15
|
+
const focusable = element;
|
|
16
|
+
if (typeof focusable.focus === "function") focusable.focus.call(element);
|
|
17
|
+
return Focused.make({});
|
|
18
|
+
})
|
|
19
|
+
});
|
|
20
|
+
export { Autofocus, Focused };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=autofocus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autofocus.js","names":[],"sources":["../../src/dom/autofocus.ts"],"sourcesContent":["import { Effect, Schema } from 'effect'\nimport * as Mount from 'foldkit/mount'\n\nexport const Focused = Schema.TaggedStruct('Focused', {})\nexport type Focused = typeof Focused.Type\n\n/**\n * Autofocus as a Mount: focuses the element on insert, then emits `Focused`.\n * The message means focus was requested — a non-focusable element or a\n * hidden tab may decline, and the browser does not report that. Plain\n * `focus()`, no scroll options: scroll behavior stays the application's\n * choice.\n */\nexport const Autofocus = Mount.define('Autofocus', {\n messages: [Focused],\n execute: ({ element }) =>\n Effect.sync(() => {\n const focusable = element as unknown as { readonly focus?: unknown }\n if (typeof focusable.focus === 'function') {\n ;(focusable.focus as () => void).call(element)\n }\n return Focused.make({})\n }),\n})\n"],"mappings":";;;AAGA,MAAa,UAAU,OAAO,aAAa,WAAW,CAAC,CAAC;;;;;;;;AAUxD,MAAa,YAAY,MAAM,OAAO,aAAa;CACjD,UAAU,CAAC,OAAO;CAClB,UAAU,EAAE,cACV,OAAO,WAAW;EAChB,MAAM,YAAY;EAClB,IAAI,OAAO,UAAU,UAAU,YAC5B,UAAW,MAAqB,KAAK,OAAO;EAE/C,OAAO,QAAQ,KAAK,CAAC,CAAC;CACxB,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Command } from "foldkit/command";
|
|
3
|
+
export declare const ClipboardMessage: import("foldkit/message").MessageUnion<{
|
|
4
|
+
readonly Copied: {};
|
|
5
|
+
readonly CopyFailed: {
|
|
6
|
+
message: Schema.String;
|
|
7
|
+
};
|
|
8
|
+
}>;
|
|
9
|
+
export type ClipboardMessage = typeof ClipboardMessage.Type;
|
|
10
|
+
export declare const ClipboardReadMessage: import("foldkit/message").MessageUnion<{
|
|
11
|
+
readonly Read: {
|
|
12
|
+
text: Schema.String;
|
|
13
|
+
};
|
|
14
|
+
readonly ReadFailed: {
|
|
15
|
+
message: Schema.String;
|
|
16
|
+
};
|
|
17
|
+
}>;
|
|
18
|
+
export type ClipboardReadMessage = typeof ClipboardReadMessage.Type;
|
|
19
|
+
/**
|
|
20
|
+
* Copies text to the clipboard. Without a clipboard API (SSR, insecure
|
|
21
|
+
* context, denied permission) the command yields `CopyFailed` instead of
|
|
22
|
+
* throwing, so `update` handles both outcomes as Messages.
|
|
23
|
+
*/
|
|
24
|
+
export declare const copyText: (text: string) => Command<ClipboardMessage, never, never>;
|
|
25
|
+
/**
|
|
26
|
+
* Reads the clipboard's text. The browser may ask the user first; a refusal,
|
|
27
|
+
* an insecure context or no clipboard API yields `ReadFailed`.
|
|
28
|
+
*/
|
|
29
|
+
export declare const readText: () => Command<ClipboardReadMessage, never, never>;
|
|
30
|
+
//# sourceMappingURL=clipboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.d.ts","names":[],"sources":["../../src/dom/clipboard.ts"],"mappings":";;qBASa,4CAAgB;;;IAEb,SAAO,OAAA;;;YAEX,0BAA0B,iBAAiB;qBAE1C,gDAAoB;;IACvB,MAAI,OAAA;;;IACE,SAAO,OAAA;;;YAEX,8BAA8B,qBAAqB;;;;;;qBAUlD,WAAQ,iBAAmB,QAAQ;;;;;qBAsBnC,gBAAe,QAAQ"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { defineMessageUnion } from "foldkit/message";
|
|
3
|
+
// src/dom/clipboard.ts
|
|
4
|
+
/**
|
|
5
|
+
* The clipboard as Commands: run in `update` beside any bundle. A copy yields
|
|
6
|
+
* `Copied` or `CopyFailed`, a read `Read` or `ReadFailed`. No Model involved:
|
|
7
|
+
* the clipboard is not application state.
|
|
8
|
+
*/
|
|
9
|
+
const ClipboardMessage = defineMessageUnion({
|
|
10
|
+
Copied: {},
|
|
11
|
+
CopyFailed: { message: Schema.String }
|
|
12
|
+
});
|
|
13
|
+
const ClipboardReadMessage = defineMessageUnion({
|
|
14
|
+
Read: { text: Schema.String },
|
|
15
|
+
ReadFailed: { message: Schema.String }
|
|
16
|
+
});
|
|
17
|
+
const failMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
18
|
+
/**
|
|
19
|
+
* Copies text to the clipboard. Without a clipboard API (SSR, insecure
|
|
20
|
+
* context, denied permission) the command yields `CopyFailed` instead of
|
|
21
|
+
* throwing, so `update` handles both outcomes as Messages.
|
|
22
|
+
*/
|
|
23
|
+
const copyText = (text) => ({
|
|
24
|
+
name: "Clipboard.copy",
|
|
25
|
+
args: { text },
|
|
26
|
+
effect: typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function" ? Effect.matchEffect(Effect.tryPromise({
|
|
27
|
+
try: () => navigator.clipboard.writeText(text),
|
|
28
|
+
catch: failMessage
|
|
29
|
+
}), {
|
|
30
|
+
onFailure: (message) => Effect.succeed(ClipboardMessage.CopyFailed({ message })),
|
|
31
|
+
onSuccess: () => Effect.succeed(ClipboardMessage.Copied())
|
|
32
|
+
}) : Effect.succeed(ClipboardMessage.CopyFailed({ message: "clipboard is unavailable" }))
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Reads the clipboard's text. The browser may ask the user first; a refusal,
|
|
36
|
+
* an insecure context or no clipboard API yields `ReadFailed`.
|
|
37
|
+
*/
|
|
38
|
+
const readText = () => ({
|
|
39
|
+
name: "Clipboard.read",
|
|
40
|
+
effect: typeof navigator !== "undefined" && typeof navigator.clipboard?.readText === "function" ? Effect.matchEffect(Effect.tryPromise({
|
|
41
|
+
try: () => navigator.clipboard.readText(),
|
|
42
|
+
catch: failMessage
|
|
43
|
+
}), {
|
|
44
|
+
onFailure: (message) => Effect.succeed(ClipboardReadMessage.ReadFailed({ message })),
|
|
45
|
+
onSuccess: (text) => Effect.succeed(ClipboardReadMessage.Read({ text }))
|
|
46
|
+
}) : Effect.succeed(ClipboardReadMessage.ReadFailed({ message: "clipboard is unavailable" }))
|
|
47
|
+
});
|
|
48
|
+
export { ClipboardMessage, ClipboardReadMessage, copyText, readText };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=clipboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.js","names":[],"sources":["../../src/dom/clipboard.ts"],"sourcesContent":["/**\n * The clipboard as Commands: run in `update` beside any bundle. A copy yields\n * `Copied` or `CopyFailed`, a read `Read` or `ReadFailed`. No Model involved:\n * the clipboard is not application state.\n */\nimport { Effect, Schema } from 'effect'\nimport type { Command } from 'foldkit/command'\nimport { defineMessageUnion } from 'foldkit/message'\n\nexport const ClipboardMessage = defineMessageUnion({\n Copied: {},\n CopyFailed: { message: Schema.String },\n})\nexport type ClipboardMessage = typeof ClipboardMessage.Type\n\nexport const ClipboardReadMessage = defineMessageUnion({\n Read: { text: Schema.String },\n ReadFailed: { message: Schema.String },\n})\nexport type ClipboardReadMessage = typeof ClipboardReadMessage.Type\n\nconst failMessage = (error: unknown): string =>\n error instanceof Error ? error.message : String(error)\n\n/**\n * Copies text to the clipboard. Without a clipboard API (SSR, insecure\n * context, denied permission) the command yields `CopyFailed` instead of\n * throwing, so `update` handles both outcomes as Messages.\n */\nexport const copyText = (text: string): Command<ClipboardMessage, never, never> => ({\n name: 'Clipboard.copy',\n args: { text },\n effect:\n typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'\n ? Effect.matchEffect(\n Effect.tryPromise({\n try: () => navigator.clipboard.writeText(text),\n catch: failMessage,\n }),\n {\n onFailure: message => Effect.succeed(ClipboardMessage.CopyFailed({ message })),\n onSuccess: () => Effect.succeed(ClipboardMessage.Copied()),\n },\n )\n : Effect.succeed(ClipboardMessage.CopyFailed({ message: 'clipboard is unavailable' })),\n})\n\n/**\n * Reads the clipboard's text. The browser may ask the user first; a refusal,\n * an insecure context or no clipboard API yields `ReadFailed`.\n */\nexport const readText = (): Command<ClipboardReadMessage, never, never> => ({\n name: 'Clipboard.read',\n effect:\n typeof navigator !== 'undefined' && typeof navigator.clipboard?.readText === 'function'\n ? Effect.matchEffect(\n Effect.tryPromise({ try: () => navigator.clipboard.readText(), catch: failMessage }),\n {\n onFailure: message => Effect.succeed(ClipboardReadMessage.ReadFailed({ message })),\n onSuccess: text => Effect.succeed(ClipboardReadMessage.Read({ text })),\n },\n )\n : Effect.succeed(ClipboardReadMessage.ReadFailed({ message: 'clipboard is unavailable' })),\n})\n"],"mappings":";;;;;;;;AASA,MAAa,mBAAmB,mBAAmB;CACjD,QAAQ,CAAC;CACT,YAAY,EAAE,SAAS,OAAO,OAAO;AACvC,CAAC;AAGD,MAAa,uBAAuB,mBAAmB;CACrD,MAAM,EAAE,MAAM,OAAO,OAAO;CAC5B,YAAY,EAAE,SAAS,OAAO,OAAO;AACvC,CAAC;AAGD,MAAM,eAAe,UACnB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;;;;;;AAOvD,MAAa,YAAY,UAA2D;CAClF,MAAM;CACN,MAAM,EAAE,KAAK;CACb,QACE,OAAO,cAAc,eAAe,OAAO,UAAU,WAAW,cAAc,aAC1E,OAAO,YACL,OAAO,WAAW;EAChB,WAAW,UAAU,UAAU,UAAU,IAAI;EAC7C,OAAO;CACT,CAAC,GACD;EACE,YAAW,YAAW,OAAO,QAAQ,iBAAiB,WAAW,EAAE,QAAQ,CAAC,CAAC;EAC7E,iBAAiB,OAAO,QAAQ,iBAAiB,OAAO,CAAC;CAC3D,CACF,IACA,OAAO,QAAQ,iBAAiB,WAAW,EAAE,SAAS,2BAA2B,CAAC,CAAC;AAC3F;;;;;AAMA,MAAa,kBAA+D;CAC1E,MAAM;CACN,QACE,OAAO,cAAc,eAAe,OAAO,UAAU,WAAW,aAAa,aACzE,OAAO,YACL,OAAO,WAAW;EAAE,WAAW,UAAU,UAAU,SAAS;EAAG,OAAO;CAAY,CAAC,GACnF;EACE,YAAW,YAAW,OAAO,QAAQ,qBAAqB,WAAW,EAAE,QAAQ,CAAC,CAAC;EACjF,YAAW,SAAQ,OAAO,QAAQ,qBAAqB,KAAK,EAAE,KAAK,CAAC,CAAC;CACvE,CACF,IACA,OAAO,QAAQ,qBAAqB,WAAW,EAAE,SAAS,2BAA2B,CAAC,CAAC;AAC/F"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import * as Mount from "foldkit/mount";
|
|
3
|
+
export declare const TextEdited: Schema.TaggedStruct<"TextEdited", {
|
|
4
|
+
/** The field, by its marking attribute's value. */
|
|
5
|
+
readonly field: Schema.String;
|
|
6
|
+
readonly text: Schema.String;
|
|
7
|
+
}>;
|
|
8
|
+
export type TextEdited = typeof TextEdited.Type;
|
|
9
|
+
export declare const TextCommitted: Schema.TaggedStruct<"TextCommitted", {
|
|
10
|
+
readonly field: Schema.String;
|
|
11
|
+
readonly text: Schema.String;
|
|
12
|
+
}>;
|
|
13
|
+
export type TextCommitted = typeof TextCommitted.Type;
|
|
14
|
+
export declare const TextCancelled: Schema.TaggedStruct<"TextCancelled", {
|
|
15
|
+
readonly field: Schema.String;
|
|
16
|
+
/** The text the field had when editing began, which the DOM shows again. */
|
|
17
|
+
readonly initial: Schema.String;
|
|
18
|
+
}>;
|
|
19
|
+
export type TextCancelled = typeof TextCancelled.Type;
|
|
20
|
+
export declare const EditAsked: Schema.TaggedStruct<"EditAsked", {
|
|
21
|
+
/** A marked field, not editable yet, that was double-clicked. */
|
|
22
|
+
readonly field: Schema.String;
|
|
23
|
+
}>;
|
|
24
|
+
export type EditAsked = typeof EditAsked.Type;
|
|
25
|
+
export type TextFact = TextEdited | TextCommitted | TextCancelled | EditAsked;
|
|
26
|
+
/**
|
|
27
|
+
* A field's text. A browser ends an edited element with a line break it adds
|
|
28
|
+
* itself, which is not the author's; in one line, the author's breaks are spaces.
|
|
29
|
+
*/
|
|
30
|
+
export declare const textOf: (element: HTMLElement) => string;
|
|
31
|
+
/**
|
|
32
|
+
* Text typed into an editable descendant of an element, as a Mount: one set of
|
|
33
|
+
* listeners on the container, like `Targets`. A field is a descendant marked
|
|
34
|
+
* by an attribute whose value names it, while it is `contenteditable`. The
|
|
35
|
+
* view decides which one is; the Mount focuses it as it becomes so (made
|
|
36
|
+
* editable, or drawn editable), with the caret at its end, and at no other
|
|
37
|
+
* time. When Enter or Escape ends an edit and the view then removes the field
|
|
38
|
+
* or makes it no longer editable, focus, left on nothing, comes back to the
|
|
39
|
+
* container, so the next key reaches it; give the container a `tabindex`. A double-click on a marked field that is not editable yet asks for it
|
|
40
|
+
* to be (`EditAsked`).
|
|
41
|
+
*
|
|
42
|
+
* It reads `innerText`, never `innerHTML`, so what arrives is text. A field is
|
|
43
|
+
* one line unless it carries `aria-multiline="true"`: in one line, a line
|
|
44
|
+
* break becomes a space. Nothing is reported while an input method composes;
|
|
45
|
+
* the composed text arrives once, when it is done.
|
|
46
|
+
*
|
|
47
|
+
* Enter commits (Shift+Enter breaks a line in a multiline field), and so does
|
|
48
|
+
* leaving the field. Escape cancels, and puts the text the field had when
|
|
49
|
+
* editing began back in the DOM, since a view that stopped redrawing the field
|
|
50
|
+
* while it was edited will not. Where the browser lacks `plaintext-only`, a
|
|
51
|
+
* paste is inserted as text. What an edit means is the parent's `update`.
|
|
52
|
+
*/
|
|
53
|
+
export declare const EditableText: Mount.MountDefinitionWithArgs<"EditableText", {
|
|
54
|
+
/** The attribute that marks a field, holding its name. */
|
|
55
|
+
attribute: Schema.String;
|
|
56
|
+
}, {
|
|
57
|
+
readonly _tag: "TextEdited";
|
|
58
|
+
readonly field: string;
|
|
59
|
+
readonly text: string;
|
|
60
|
+
} | {
|
|
61
|
+
readonly _tag: "TextCommitted";
|
|
62
|
+
readonly field: string;
|
|
63
|
+
readonly text: string;
|
|
64
|
+
} | {
|
|
65
|
+
readonly _tag: "TextCancelled";
|
|
66
|
+
readonly field: string;
|
|
67
|
+
readonly initial: string;
|
|
68
|
+
} | {
|
|
69
|
+
readonly _tag: "EditAsked";
|
|
70
|
+
readonly field: string;
|
|
71
|
+
}>;
|
|
72
|
+
//# sourceMappingURL=editable-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editable-text.d.ts","names":[],"sources":["../../src/dom/editable-text.ts"],"mappings":";;qBAGa,YAAU,OAAA;;;;;YAKX,oBAAoB,WAAW;qBAE9B,eAAa,OAAA;;;;YAId,uBAAuB,cAAc;qBAEpC,eAAa,OAAA;;;;;YAKd,uBAAuB,cAAc;qBAEpC,WAAS,OAAA;;;;YAIV,mBAAmB,UAAU;YAE7B,WAAW,aAAa,gBAAgB,gBAAgB;;;;;qBASvD,SAAM,SAAa;;;;;;;;;;;;;;;;;;;;;;;qBA0CnB,cAAY,MAAA"}
|