@wardic/solid 0.0.0-stage → 1.0.0-rc.1
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/LICENSE +21 -0
- package/README.md +26 -2
- package/THIRD_PARTY_NOTICES.md +145 -0
- package/dist/browser/animation-controls.js +150 -0
- package/dist/browser/animation-controls.js.map +1 -0
- package/dist/browser/animation-frame.js +29 -0
- package/dist/browser/animation-frame.js.map +1 -0
- package/dist/browser/appear.js +727 -0
- package/dist/browser/appear.js.map +1 -0
- package/dist/browser/arc.js +21 -0
- package/dist/browser/arc.js.map +1 -0
- package/dist/browser/bridge.js +69 -0
- package/dist/browser/bridge.js.map +1 -0
- package/dist/browser/complete.js +30 -0
- package/dist/browser/complete.js.map +1 -0
- package/dist/browser/config.js +2 -0
- package/dist/browser/config.js.map +1 -0
- package/dist/browser/create-animate.js +41 -0
- package/dist/browser/create-animate.js.map +1 -0
- package/dist/browser/create-ward.js +1001 -0
- package/dist/browser/create-ward.js.map +1 -0
- package/dist/browser/cycle-index.js +17 -0
- package/dist/browser/cycle-index.js.map +1 -0
- package/dist/browser/cycle.js +31 -0
- package/dist/browser/cycle.js.map +1 -0
- package/dist/browser/directive.js +80 -0
- package/dist/browser/directive.js.map +1 -0
- package/dist/browser/drag-constraints.js +76 -0
- package/dist/browser/drag-constraints.js.map +1 -0
- package/dist/browser/drag-controls.js +51 -0
- package/dist/browser/drag-controls.js.map +1 -0
- package/dist/browser/drag-samples.js +40 -0
- package/dist/browser/drag-samples.js.map +1 -0
- package/dist/browser/drag-sensor.js +35 -0
- package/dist/browser/drag-sensor.js.map +1 -0
- package/dist/browser/drag-track.js +817 -0
- package/dist/browser/drag-track.js.map +1 -0
- package/dist/browser/drag-transition.js +56 -0
- package/dist/browser/drag-transition.js.map +1 -0
- package/dist/browser/drag.js +44 -0
- package/dist/browser/drag.js.map +1 -0
- package/dist/browser/element-style.js +67 -0
- package/dist/browser/element-style.js.map +1 -0
- package/dist/browser/element.js +353 -0
- package/dist/browser/element.js.map +1 -0
- package/dist/browser/feature-section.js +17 -0
- package/dist/browser/feature-section.js.map +1 -0
- package/dist/browser/feature.js +261 -0
- package/dist/browser/feature.js.map +1 -0
- package/dist/browser/features/all.js +10 -0
- package/dist/browser/features/all.js.map +1 -0
- package/dist/browser/features/colors.js +33 -0
- package/dist/browser/features/colors.js.map +1 -0
- package/dist/browser/features/dom-animation.js +7 -0
- package/dist/browser/features/dom-animation.js.map +1 -0
- package/dist/browser/features/dom-max.js +4 -0
- package/dist/browser/features/dom-max.js.map +1 -0
- package/dist/browser/features/drag.js +28 -0
- package/dist/browser/features/drag.js.map +1 -0
- package/dist/browser/features/focus.js +18 -0
- package/dist/browser/features/focus.js.map +1 -0
- package/dist/browser/features/hover.js +18 -0
- package/dist/browser/features/hover.js.map +1 -0
- package/dist/browser/features/in-view.js +18 -0
- package/dist/browser/features/in-view.js.map +1 -0
- package/dist/browser/features/input.js +30 -0
- package/dist/browser/features/input.js.map +1 -0
- package/dist/browser/features/keys.js +19 -0
- package/dist/browser/features/keys.js.map +1 -0
- package/dist/browser/features/layout.js +33 -0
- package/dist/browser/features/layout.js.map +1 -0
- package/dist/browser/features/pan.js +18 -0
- package/dist/browser/features/pan.js.map +1 -0
- package/dist/browser/features/press.js +12 -0
- package/dist/browser/features/press.js.map +1 -0
- package/dist/browser/focus-sensor.js +23 -0
- package/dist/browser/focus-sensor.js.map +1 -0
- package/dist/browser/frame-time.js +40 -0
- package/dist/browser/frame-time.js.map +1 -0
- package/dist/browser/gesture-callbacks.js +172 -0
- package/dist/browser/gesture-callbacks.js.map +1 -0
- package/dist/browser/gesture-features.js +29 -0
- package/dist/browser/gesture-features.js.map +1 -0
- package/dist/browser/gesture-info.js +43 -0
- package/dist/browser/gesture-info.js.map +1 -0
- package/dist/browser/hover-sensor.js +29 -0
- package/dist/browser/hover-sensor.js.map +1 -0
- package/dist/browser/imperative.js +614 -0
- package/dist/browser/imperative.js.map +1 -0
- package/dist/browser/index.js +165 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/instant.js +51 -0
- package/dist/browser/instant.js.map +1 -0
- package/dist/browser/layout-group.js +27 -0
- package/dist/browser/layout-group.js.map +1 -0
- package/dist/browser/layout-member.js +362 -0
- package/dist/browser/layout-member.js.map +1 -0
- package/dist/browser/layout-projection.js +19 -0
- package/dist/browser/layout-projection.js.map +1 -0
- package/dist/browser/layout.js +91 -0
- package/dist/browser/layout.js.map +1 -0
- package/dist/browser/live-style.js +313 -0
- package/dist/browser/live-style.js.map +1 -0
- package/dist/browser/live-value.js +252 -0
- package/dist/browser/live-value.js.map +1 -0
- package/dist/browser/motion-props.js +172 -0
- package/dist/browser/motion-props.js.map +1 -0
- package/dist/browser/owner-scope.js +29 -0
- package/dist/browser/owner-scope.js.map +1 -0
- package/dist/browser/page-point.js +92 -0
- package/dist/browser/page-point.js.map +1 -0
- package/dist/browser/presence-controller.js +1324 -0
- package/dist/browser/presence-controller.js.map +1 -0
- package/dist/browser/presence-record.js +27 -0
- package/dist/browser/presence-record.js.map +1 -0
- package/dist/browser/presence.js +207 -0
- package/dist/browser/presence.js.map +1 -0
- package/dist/browser/press-sensor.js +30 -0
- package/dist/browser/press-sensor.js.map +1 -0
- package/dist/browser/press.js +24 -0
- package/dist/browser/press.js.map +1 -0
- package/dist/browser/provider.js +445 -0
- package/dist/browser/provider.js.map +1 -0
- package/dist/browser/reactivity.js +9 -0
- package/dist/browser/reactivity.js.map +1 -0
- package/dist/browser/reorder-input.js +46 -0
- package/dist/browser/reorder-input.js.map +1 -0
- package/dist/browser/reorder.js +543 -0
- package/dist/browser/reorder.js.map +1 -0
- package/dist/browser/retarget-legs.js +1019 -0
- package/dist/browser/retarget-legs.js.map +1 -0
- package/dist/browser/runtime.js +249 -0
- package/dist/browser/runtime.js.map +1 -0
- package/dist/browser/sensors.js +172 -0
- package/dist/browser/sensors.js.map +1 -0
- package/dist/browser/server-interpolators.js +20 -0
- package/dist/browser/server-interpolators.js.map +1 -0
- package/dist/browser/server.js +20 -0
- package/dist/browser/server.js.map +1 -0
- package/dist/browser/solid-reactivity.js +26 -0
- package/dist/browser/solid-reactivity.js.map +1 -0
- package/dist/browser/spine.js +2939 -0
- package/dist/browser/spine.js.map +1 -0
- package/dist/browser/stagger.js +13 -0
- package/dist/browser/stagger.js.map +1 -0
- package/dist/browser/unstable/v2/bridge.js +76 -0
- package/dist/browser/unstable/v2/bridge.js.map +1 -0
- package/dist/browser/unstable/v2/complete.js +29 -0
- package/dist/browser/unstable/v2/complete.js.map +1 -0
- package/dist/browser/unstable/v2/config-view.js +111 -0
- package/dist/browser/unstable/v2/config-view.js.map +1 -0
- package/dist/browser/unstable/v2/create-animate.js +102 -0
- package/dist/browser/unstable/v2/create-animate.js.map +1 -0
- package/dist/browser/unstable/v2/cycle.js +30 -0
- package/dist/browser/unstable/v2/cycle.js.map +1 -0
- package/dist/browser/unstable/v2/drag-sensor.js +34 -0
- package/dist/browser/unstable/v2/drag-sensor.js.map +1 -0
- package/dist/browser/unstable/v2/element.js +393 -0
- package/dist/browser/unstable/v2/element.js.map +1 -0
- package/dist/browser/unstable/v2/feature-section.js +27 -0
- package/dist/browser/unstable/v2/feature-section.js.map +1 -0
- package/dist/browser/unstable/v2/focus-sensor.js +23 -0
- package/dist/browser/unstable/v2/focus-sensor.js.map +1 -0
- package/dist/browser/unstable/v2/hover-sensor.js +28 -0
- package/dist/browser/unstable/v2/hover-sensor.js.map +1 -0
- package/dist/browser/unstable/v2/index.js +114 -0
- package/dist/browser/unstable/v2/index.js.map +1 -0
- package/dist/browser/unstable/v2/layout-projection.js +18 -0
- package/dist/browser/unstable/v2/layout-projection.js.map +1 -0
- package/dist/browser/unstable/v2/layout.js +71 -0
- package/dist/browser/unstable/v2/layout.js.map +1 -0
- package/dist/browser/unstable/v2/lifecycle.js +455 -0
- package/dist/browser/unstable/v2/lifecycle.js.map +1 -0
- package/dist/browser/unstable/v2/live-value.js +264 -0
- package/dist/browser/unstable/v2/live-value.js.map +1 -0
- package/dist/browser/unstable/v2/presence-handle.js +146 -0
- package/dist/browser/unstable/v2/presence-handle.js.map +1 -0
- package/dist/browser/unstable/v2/presence-hooks.js +142 -0
- package/dist/browser/unstable/v2/presence-hooks.js.map +1 -0
- package/dist/browser/unstable/v2/presence.js +124 -0
- package/dist/browser/unstable/v2/presence.js.map +1 -0
- package/dist/browser/unstable/v2/press-sensor.js +29 -0
- package/dist/browser/unstable/v2/press-sensor.js.map +1 -0
- package/dist/browser/unstable/v2/provider.js +457 -0
- package/dist/browser/unstable/v2/provider.js.map +1 -0
- package/dist/browser/unstable/v2/reorder.js +663 -0
- package/dist/browser/unstable/v2/reorder.js.map +1 -0
- package/dist/browser/unstable/v2/sensors.js +138 -0
- package/dist/browser/unstable/v2/sensors.js.map +1 -0
- package/dist/browser/unstable/v2/solid-reactivity.js +82 -0
- package/dist/browser/unstable/v2/solid-reactivity.js.map +1 -0
- package/dist/browser/unstable/v2/variant-node.js +395 -0
- package/dist/browser/unstable/v2/variant-node.js.map +1 -0
- package/dist/browser/unstable/v2/w.js +23 -0
- package/dist/browser/unstable/v2/w.js.map +1 -0
- package/dist/browser/unstable/v2/waiters.js +185 -0
- package/dist/browser/unstable/v2/waiters.js.map +1 -0
- package/dist/browser/unstable/v2/ward.js +1620 -0
- package/dist/browser/unstable/v2/ward.js.map +1 -0
- package/dist/browser/unstable/v2/wardic-config.js +40 -0
- package/dist/browser/unstable/v2/wardic-config.js.map +1 -0
- package/dist/browser/unstable/v2/wardic.js +16 -0
- package/dist/browser/unstable/v2/wardic.js.map +1 -0
- package/dist/browser/variant-node.js +308 -0
- package/dist/browser/variant-node.js.map +1 -0
- package/dist/browser/variant-target.js +224 -0
- package/dist/browser/variant-target.js.map +1 -0
- package/dist/browser/variant-tree.js +394 -0
- package/dist/browser/variant-tree.js.map +1 -0
- package/dist/browser/variants.js +43 -0
- package/dist/browser/variants.js.map +1 -0
- package/dist/browser/w.js +25 -0
- package/dist/browser/w.js.map +1 -0
- package/dist/browser/ward-keys.js +2 -0
- package/dist/browser/ward-keys.js.map +1 -0
- package/dist/browser/wardic-component.js +74 -0
- package/dist/browser/wardic-component.js.map +1 -0
- package/dist/browser/wardic-config-value.js +91 -0
- package/dist/browser/wardic-config-value.js.map +1 -0
- package/dist/browser/wardic-config.js +42 -0
- package/dist/browser/wardic-config.js.map +1 -0
- package/dist/browser/wardic.js +31 -0
- package/dist/browser/wardic.js.map +1 -0
- package/dist/server/animation-controls.js +150 -0
- package/dist/server/animation-controls.js.map +1 -0
- package/dist/server/animation-frame.js +29 -0
- package/dist/server/animation-frame.js.map +1 -0
- package/dist/server/appear.js +727 -0
- package/dist/server/appear.js.map +1 -0
- package/dist/server/arc.js +21 -0
- package/dist/server/arc.js.map +1 -0
- package/dist/server/bridge.js +69 -0
- package/dist/server/bridge.js.map +1 -0
- package/dist/server/complete.js +30 -0
- package/dist/server/complete.js.map +1 -0
- package/dist/server/config.js +2 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/create-animate.js +41 -0
- package/dist/server/create-animate.js.map +1 -0
- package/dist/server/create-ward.js +1001 -0
- package/dist/server/create-ward.js.map +1 -0
- package/dist/server/cycle-index.js +17 -0
- package/dist/server/cycle-index.js.map +1 -0
- package/dist/server/cycle.js +31 -0
- package/dist/server/cycle.js.map +1 -0
- package/dist/server/directive.js +80 -0
- package/dist/server/directive.js.map +1 -0
- package/dist/server/drag-constraints.js +76 -0
- package/dist/server/drag-constraints.js.map +1 -0
- package/dist/server/drag-controls.js +51 -0
- package/dist/server/drag-controls.js.map +1 -0
- package/dist/server/drag-samples.js +40 -0
- package/dist/server/drag-samples.js.map +1 -0
- package/dist/server/drag-sensor.js +35 -0
- package/dist/server/drag-sensor.js.map +1 -0
- package/dist/server/drag-track.js +817 -0
- package/dist/server/drag-track.js.map +1 -0
- package/dist/server/drag-transition.js +56 -0
- package/dist/server/drag-transition.js.map +1 -0
- package/dist/server/drag.js +44 -0
- package/dist/server/drag.js.map +1 -0
- package/dist/server/element-style.js +67 -0
- package/dist/server/element-style.js.map +1 -0
- package/dist/server/element.js +353 -0
- package/dist/server/element.js.map +1 -0
- package/dist/server/feature-section.js +17 -0
- package/dist/server/feature-section.js.map +1 -0
- package/dist/server/feature.js +261 -0
- package/dist/server/feature.js.map +1 -0
- package/dist/server/features/all.js +10 -0
- package/dist/server/features/all.js.map +1 -0
- package/dist/server/features/colors.js +33 -0
- package/dist/server/features/colors.js.map +1 -0
- package/dist/server/features/dom-animation.js +7 -0
- package/dist/server/features/dom-animation.js.map +1 -0
- package/dist/server/features/dom-max.js +4 -0
- package/dist/server/features/dom-max.js.map +1 -0
- package/dist/server/features/drag.js +28 -0
- package/dist/server/features/drag.js.map +1 -0
- package/dist/server/features/focus.js +18 -0
- package/dist/server/features/focus.js.map +1 -0
- package/dist/server/features/hover.js +18 -0
- package/dist/server/features/hover.js.map +1 -0
- package/dist/server/features/in-view.js +18 -0
- package/dist/server/features/in-view.js.map +1 -0
- package/dist/server/features/input.js +30 -0
- package/dist/server/features/input.js.map +1 -0
- package/dist/server/features/keys.js +19 -0
- package/dist/server/features/keys.js.map +1 -0
- package/dist/server/features/layout.js +33 -0
- package/dist/server/features/layout.js.map +1 -0
- package/dist/server/features/pan.js +18 -0
- package/dist/server/features/pan.js.map +1 -0
- package/dist/server/features/press.js +12 -0
- package/dist/server/features/press.js.map +1 -0
- package/dist/server/focus-sensor.js +23 -0
- package/dist/server/focus-sensor.js.map +1 -0
- package/dist/server/frame-time.js +40 -0
- package/dist/server/frame-time.js.map +1 -0
- package/dist/server/gesture-callbacks.js +172 -0
- package/dist/server/gesture-callbacks.js.map +1 -0
- package/dist/server/gesture-features.js +29 -0
- package/dist/server/gesture-features.js.map +1 -0
- package/dist/server/gesture-info.js +43 -0
- package/dist/server/gesture-info.js.map +1 -0
- package/dist/server/hover-sensor.js +29 -0
- package/dist/server/hover-sensor.js.map +1 -0
- package/dist/server/imperative.js +614 -0
- package/dist/server/imperative.js.map +1 -0
- package/dist/server/index.js +165 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/instant.js +51 -0
- package/dist/server/instant.js.map +1 -0
- package/dist/server/layout-group.js +27 -0
- package/dist/server/layout-group.js.map +1 -0
- package/dist/server/layout-member.js +362 -0
- package/dist/server/layout-member.js.map +1 -0
- package/dist/server/layout-projection.js +19 -0
- package/dist/server/layout-projection.js.map +1 -0
- package/dist/server/layout.js +90 -0
- package/dist/server/layout.js.map +1 -0
- package/dist/server/live-style.js +313 -0
- package/dist/server/live-style.js.map +1 -0
- package/dist/server/live-value.js +252 -0
- package/dist/server/live-value.js.map +1 -0
- package/dist/server/motion-props.js +172 -0
- package/dist/server/motion-props.js.map +1 -0
- package/dist/server/owner-scope.js +29 -0
- package/dist/server/owner-scope.js.map +1 -0
- package/dist/server/page-point.js +92 -0
- package/dist/server/page-point.js.map +1 -0
- package/dist/server/presence-controller.js +1324 -0
- package/dist/server/presence-controller.js.map +1 -0
- package/dist/server/presence-record.js +27 -0
- package/dist/server/presence-record.js.map +1 -0
- package/dist/server/presence.js +207 -0
- package/dist/server/presence.js.map +1 -0
- package/dist/server/press-sensor.js +30 -0
- package/dist/server/press-sensor.js.map +1 -0
- package/dist/server/press.js +24 -0
- package/dist/server/press.js.map +1 -0
- package/dist/server/provider.js +439 -0
- package/dist/server/provider.js.map +1 -0
- package/dist/server/reactivity.js +9 -0
- package/dist/server/reactivity.js.map +1 -0
- package/dist/server/reorder-input.js +46 -0
- package/dist/server/reorder-input.js.map +1 -0
- package/dist/server/reorder.js +543 -0
- package/dist/server/reorder.js.map +1 -0
- package/dist/server/retarget-legs.js +1019 -0
- package/dist/server/retarget-legs.js.map +1 -0
- package/dist/server/runtime.js +249 -0
- package/dist/server/runtime.js.map +1 -0
- package/dist/server/sensors.js +172 -0
- package/dist/server/sensors.js.map +1 -0
- package/dist/server/server-interpolators.js +20 -0
- package/dist/server/server-interpolators.js.map +1 -0
- package/dist/server/server.js +20 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/solid-reactivity.js +26 -0
- package/dist/server/solid-reactivity.js.map +1 -0
- package/dist/server/spine.js +2939 -0
- package/dist/server/spine.js.map +1 -0
- package/dist/server/stagger.js +13 -0
- package/dist/server/stagger.js.map +1 -0
- package/dist/server/unstable/v2/bridge.js +76 -0
- package/dist/server/unstable/v2/bridge.js.map +1 -0
- package/dist/server/unstable/v2/complete.js +29 -0
- package/dist/server/unstable/v2/complete.js.map +1 -0
- package/dist/server/unstable/v2/config-view.js +111 -0
- package/dist/server/unstable/v2/config-view.js.map +1 -0
- package/dist/server/unstable/v2/create-animate.js +102 -0
- package/dist/server/unstable/v2/create-animate.js.map +1 -0
- package/dist/server/unstable/v2/cycle.js +30 -0
- package/dist/server/unstable/v2/cycle.js.map +1 -0
- package/dist/server/unstable/v2/drag-sensor.js +34 -0
- package/dist/server/unstable/v2/drag-sensor.js.map +1 -0
- package/dist/server/unstable/v2/element.js +402 -0
- package/dist/server/unstable/v2/element.js.map +1 -0
- package/dist/server/unstable/v2/feature-section.js +27 -0
- package/dist/server/unstable/v2/feature-section.js.map +1 -0
- package/dist/server/unstable/v2/focus-sensor.js +23 -0
- package/dist/server/unstable/v2/focus-sensor.js.map +1 -0
- package/dist/server/unstable/v2/hover-sensor.js +28 -0
- package/dist/server/unstable/v2/hover-sensor.js.map +1 -0
- package/dist/server/unstable/v2/index.js +114 -0
- package/dist/server/unstable/v2/index.js.map +1 -0
- package/dist/server/unstable/v2/layout-projection.js +18 -0
- package/dist/server/unstable/v2/layout-projection.js.map +1 -0
- package/dist/server/unstable/v2/layout.js +88 -0
- package/dist/server/unstable/v2/layout.js.map +1 -0
- package/dist/server/unstable/v2/lifecycle.js +455 -0
- package/dist/server/unstable/v2/lifecycle.js.map +1 -0
- package/dist/server/unstable/v2/live-value.js +264 -0
- package/dist/server/unstable/v2/live-value.js.map +1 -0
- package/dist/server/unstable/v2/presence-handle.js +146 -0
- package/dist/server/unstable/v2/presence-handle.js.map +1 -0
- package/dist/server/unstable/v2/presence-hooks.js +142 -0
- package/dist/server/unstable/v2/presence-hooks.js.map +1 -0
- package/dist/server/unstable/v2/presence.js +142 -0
- package/dist/server/unstable/v2/presence.js.map +1 -0
- package/dist/server/unstable/v2/press-sensor.js +29 -0
- package/dist/server/unstable/v2/press-sensor.js.map +1 -0
- package/dist/server/unstable/v2/provider.js +475 -0
- package/dist/server/unstable/v2/provider.js.map +1 -0
- package/dist/server/unstable/v2/reorder.js +733 -0
- package/dist/server/unstable/v2/reorder.js.map +1 -0
- package/dist/server/unstable/v2/sensors.js +138 -0
- package/dist/server/unstable/v2/sensors.js.map +1 -0
- package/dist/server/unstable/v2/solid-reactivity.js +82 -0
- package/dist/server/unstable/v2/solid-reactivity.js.map +1 -0
- package/dist/server/unstable/v2/variant-node.js +395 -0
- package/dist/server/unstable/v2/variant-node.js.map +1 -0
- package/dist/server/unstable/v2/w.js +23 -0
- package/dist/server/unstable/v2/w.js.map +1 -0
- package/dist/server/unstable/v2/waiters.js +185 -0
- package/dist/server/unstable/v2/waiters.js.map +1 -0
- package/dist/server/unstable/v2/ward.js +1653 -0
- package/dist/server/unstable/v2/ward.js.map +1 -0
- package/dist/server/unstable/v2/wardic-config.js +49 -0
- package/dist/server/unstable/v2/wardic-config.js.map +1 -0
- package/dist/server/unstable/v2/wardic.js +16 -0
- package/dist/server/unstable/v2/wardic.js.map +1 -0
- package/dist/server/variant-node.js +308 -0
- package/dist/server/variant-node.js.map +1 -0
- package/dist/server/variant-target.js +224 -0
- package/dist/server/variant-target.js.map +1 -0
- package/dist/server/variant-tree.js +394 -0
- package/dist/server/variant-tree.js.map +1 -0
- package/dist/server/variants.js +43 -0
- package/dist/server/variants.js.map +1 -0
- package/dist/server/w.js +25 -0
- package/dist/server/w.js.map +1 -0
- package/dist/server/ward-keys.js +2 -0
- package/dist/server/ward-keys.js.map +1 -0
- package/dist/server/wardic-component.js +74 -0
- package/dist/server/wardic-component.js.map +1 -0
- package/dist/server/wardic-config-value.js +91 -0
- package/dist/server/wardic-config-value.js.map +1 -0
- package/dist/server/wardic-config.js +42 -0
- package/dist/server/wardic-config.js.map +1 -0
- package/dist/server/wardic.js +31 -0
- package/dist/server/wardic.js.map +1 -0
- package/dist/solid/animation-controls.d.ts +104 -0
- package/dist/solid/animation-controls.d.ts.map +1 -0
- package/dist/solid/animation-controls.js +129 -0
- package/dist/solid/animation-controls.js.map +1 -0
- package/dist/solid/animation-frame.d.ts +11 -0
- package/dist/solid/animation-frame.d.ts.map +1 -0
- package/dist/solid/animation-frame.js +31 -0
- package/dist/solid/animation-frame.js.map +1 -0
- package/dist/solid/appear.d.ts +205 -0
- package/dist/solid/appear.d.ts.map +1 -0
- package/dist/solid/appear.js +519 -0
- package/dist/solid/appear.js.map +1 -0
- package/dist/solid/arc.d.ts +25 -0
- package/dist/solid/arc.d.ts.map +1 -0
- package/dist/solid/arc.js +21 -0
- package/dist/solid/arc.js.map +1 -0
- package/dist/solid/bridge.d.ts +53 -0
- package/dist/solid/bridge.d.ts.map +1 -0
- package/dist/solid/bridge.js +66 -0
- package/dist/solid/bridge.js.map +1 -0
- package/dist/solid/complete.d.ts +31 -0
- package/dist/solid/complete.d.ts.map +1 -0
- package/dist/solid/complete.js +27 -0
- package/dist/solid/complete.js.map +1 -0
- package/dist/solid/config.d.ts +693 -0
- package/dist/solid/config.d.ts.map +1 -0
- package/dist/solid/config.js +2 -0
- package/dist/solid/config.js.map +1 -0
- package/dist/solid/create-animate.d.ts +17 -0
- package/dist/solid/create-animate.d.ts.map +1 -0
- package/dist/solid/create-animate.js +42 -0
- package/dist/solid/create-animate.js.map +1 -0
- package/dist/solid/create-ward.d.ts +278 -0
- package/dist/solid/create-ward.d.ts.map +1 -0
- package/dist/solid/create-ward.jsx +874 -0
- package/dist/solid/create-ward.jsx.map +1 -0
- package/dist/solid/cycle-index.d.ts +7 -0
- package/dist/solid/cycle-index.d.ts.map +1 -0
- package/dist/solid/cycle-index.js +17 -0
- package/dist/solid/cycle-index.js.map +1 -0
- package/dist/solid/cycle.d.ts +21 -0
- package/dist/solid/cycle.d.ts.map +1 -0
- package/dist/solid/cycle.js +31 -0
- package/dist/solid/cycle.js.map +1 -0
- package/dist/solid/directive.d.ts +21 -0
- package/dist/solid/directive.d.ts.map +1 -0
- package/dist/solid/directive.js +76 -0
- package/dist/solid/directive.js.map +1 -0
- package/dist/solid/drag-constraints.d.ts +46 -0
- package/dist/solid/drag-constraints.d.ts.map +1 -0
- package/dist/solid/drag-constraints.js +49 -0
- package/dist/solid/drag-constraints.js.map +1 -0
- package/dist/solid/drag-controls.d.ts +50 -0
- package/dist/solid/drag-controls.d.ts.map +1 -0
- package/dist/solid/drag-controls.js +51 -0
- package/dist/solid/drag-controls.js.map +1 -0
- package/dist/solid/drag-samples.d.ts +34 -0
- package/dist/solid/drag-samples.d.ts.map +1 -0
- package/dist/solid/drag-samples.js +28 -0
- package/dist/solid/drag-samples.js.map +1 -0
- package/dist/solid/drag-sensor.d.ts +23 -0
- package/dist/solid/drag-sensor.d.ts.map +1 -0
- package/dist/solid/drag-sensor.js +33 -0
- package/dist/solid/drag-sensor.js.map +1 -0
- package/dist/solid/drag-track.d.ts +227 -0
- package/dist/solid/drag-track.d.ts.map +1 -0
- package/dist/solid/drag-track.js +631 -0
- package/dist/solid/drag-track.js.map +1 -0
- package/dist/solid/drag-transition.d.ts +20 -0
- package/dist/solid/drag-transition.d.ts.map +1 -0
- package/dist/solid/drag-transition.js +31 -0
- package/dist/solid/drag-transition.js.map +1 -0
- package/dist/solid/drag.d.ts +23 -0
- package/dist/solid/drag.d.ts.map +1 -0
- package/dist/solid/drag.js +44 -0
- package/dist/solid/drag.js.map +1 -0
- package/dist/solid/element-style.d.ts +60 -0
- package/dist/solid/element-style.d.ts.map +1 -0
- package/dist/solid/element-style.js +50 -0
- package/dist/solid/element-style.js.map +1 -0
- package/dist/solid/element.d.ts +73 -0
- package/dist/solid/element.d.ts.map +1 -0
- package/dist/solid/element.jsx +326 -0
- package/dist/solid/element.jsx.map +1 -0
- package/dist/solid/feature-section.d.ts +18 -0
- package/dist/solid/feature-section.d.ts.map +1 -0
- package/dist/solid/feature-section.js +13 -0
- package/dist/solid/feature-section.js.map +1 -0
- package/dist/solid/feature.d.ts +114 -0
- package/dist/solid/feature.d.ts.map +1 -0
- package/dist/solid/feature.js +248 -0
- package/dist/solid/feature.js.map +1 -0
- package/dist/solid/features/all.d.ts +10 -0
- package/dist/solid/features/all.d.ts.map +1 -0
- package/dist/solid/features/all.js +10 -0
- package/dist/solid/features/all.js.map +1 -0
- package/dist/solid/features/colors.d.ts +12 -0
- package/dist/solid/features/colors.d.ts.map +1 -0
- package/dist/solid/features/colors.js +34 -0
- package/dist/solid/features/colors.js.map +1 -0
- package/dist/solid/features/dom-animation.d.ts +11 -0
- package/dist/solid/features/dom-animation.d.ts.map +1 -0
- package/dist/solid/features/dom-animation.js +7 -0
- package/dist/solid/features/dom-animation.js.map +1 -0
- package/dist/solid/features/dom-max.d.ts +10 -0
- package/dist/solid/features/dom-max.d.ts.map +1 -0
- package/dist/solid/features/dom-max.js +4 -0
- package/dist/solid/features/dom-max.js.map +1 -0
- package/dist/solid/features/drag.d.ts +5 -0
- package/dist/solid/features/drag.d.ts.map +1 -0
- package/dist/solid/features/drag.js +28 -0
- package/dist/solid/features/drag.js.map +1 -0
- package/dist/solid/features/focus.d.ts +5 -0
- package/dist/solid/features/focus.d.ts.map +1 -0
- package/dist/solid/features/focus.js +16 -0
- package/dist/solid/features/focus.js.map +1 -0
- package/dist/solid/features/hover.d.ts +5 -0
- package/dist/solid/features/hover.d.ts.map +1 -0
- package/dist/solid/features/hover.js +16 -0
- package/dist/solid/features/hover.js.map +1 -0
- package/dist/solid/features/in-view.d.ts +5 -0
- package/dist/solid/features/in-view.d.ts.map +1 -0
- package/dist/solid/features/in-view.js +16 -0
- package/dist/solid/features/in-view.js.map +1 -0
- package/dist/solid/features/input.d.ts +26 -0
- package/dist/solid/features/input.d.ts.map +1 -0
- package/dist/solid/features/input.js +31 -0
- package/dist/solid/features/input.js.map +1 -0
- package/dist/solid/features/keys.d.ts +70 -0
- package/dist/solid/features/keys.d.ts.map +1 -0
- package/dist/solid/features/keys.js +26 -0
- package/dist/solid/features/keys.js.map +1 -0
- package/dist/solid/features/layout.d.ts +32 -0
- package/dist/solid/features/layout.d.ts.map +1 -0
- package/dist/solid/features/layout.js +34 -0
- package/dist/solid/features/layout.js.map +1 -0
- package/dist/solid/features/pan.d.ts +5 -0
- package/dist/solid/features/pan.d.ts.map +1 -0
- package/dist/solid/features/pan.js +16 -0
- package/dist/solid/features/pan.js.map +1 -0
- package/dist/solid/features/press.d.ts +11 -0
- package/dist/solid/features/press.d.ts.map +1 -0
- package/dist/solid/features/press.js +10 -0
- package/dist/solid/features/press.js.map +1 -0
- package/dist/solid/focus-sensor.d.ts +19 -0
- package/dist/solid/focus-sensor.d.ts.map +1 -0
- package/dist/solid/focus-sensor.js +23 -0
- package/dist/solid/focus-sensor.js.map +1 -0
- package/dist/solid/frame-time.d.ts +21 -0
- package/dist/solid/frame-time.d.ts.map +1 -0
- package/dist/solid/frame-time.js +41 -0
- package/dist/solid/frame-time.js.map +1 -0
- package/dist/solid/gesture-callbacks.d.ts +53 -0
- package/dist/solid/gesture-callbacks.d.ts.map +1 -0
- package/dist/solid/gesture-callbacks.js +184 -0
- package/dist/solid/gesture-callbacks.js.map +1 -0
- package/dist/solid/gesture-features.d.ts +28 -0
- package/dist/solid/gesture-features.d.ts.map +1 -0
- package/dist/solid/gesture-features.js +29 -0
- package/dist/solid/gesture-features.js.map +1 -0
- package/dist/solid/gesture-info.d.ts +59 -0
- package/dist/solid/gesture-info.d.ts.map +1 -0
- package/dist/solid/gesture-info.js +23 -0
- package/dist/solid/gesture-info.js.map +1 -0
- package/dist/solid/hover-sensor.d.ts +19 -0
- package/dist/solid/hover-sensor.d.ts.map +1 -0
- package/dist/solid/hover-sensor.js +23 -0
- package/dist/solid/hover-sensor.js.map +1 -0
- package/dist/solid/imperative.d.ts +195 -0
- package/dist/solid/imperative.d.ts.map +1 -0
- package/dist/solid/imperative.js +536 -0
- package/dist/solid/imperative.js.map +1 -0
- package/dist/solid/index.d.ts +180 -0
- package/dist/solid/index.d.ts.map +1 -0
- package/dist/solid/index.js +165 -0
- package/dist/solid/index.js.map +1 -0
- package/dist/solid/instant.d.ts +44 -0
- package/dist/solid/instant.d.ts.map +1 -0
- package/dist/solid/instant.js +49 -0
- package/dist/solid/instant.js.map +1 -0
- package/dist/solid/layout-group.d.ts +14 -0
- package/dist/solid/layout-group.d.ts.map +1 -0
- package/dist/solid/layout-group.js +27 -0
- package/dist/solid/layout-group.js.map +1 -0
- package/dist/solid/layout-member.d.ts +117 -0
- package/dist/solid/layout-member.d.ts.map +1 -0
- package/dist/solid/layout-member.js +164 -0
- package/dist/solid/layout-member.js.map +1 -0
- package/dist/solid/layout-projection.d.ts +22 -0
- package/dist/solid/layout-projection.d.ts.map +1 -0
- package/dist/solid/layout-projection.js +19 -0
- package/dist/solid/layout-projection.js.map +1 -0
- package/dist/solid/layout.d.ts +76 -0
- package/dist/solid/layout.d.ts.map +1 -0
- package/dist/solid/layout.jsx +80 -0
- package/dist/solid/layout.jsx.map +1 -0
- package/dist/solid/live-style.d.ts +147 -0
- package/dist/solid/live-style.d.ts.map +1 -0
- package/dist/solid/live-style.js +304 -0
- package/dist/solid/live-style.js.map +1 -0
- package/dist/solid/live-value.d.ts +144 -0
- package/dist/solid/live-value.d.ts.map +1 -0
- package/dist/solid/live-value.js +223 -0
- package/dist/solid/live-value.js.map +1 -0
- package/dist/solid/motion-props.d.ts +75 -0
- package/dist/solid/motion-props.d.ts.map +1 -0
- package/dist/solid/motion-props.js +314 -0
- package/dist/solid/motion-props.js.map +1 -0
- package/dist/solid/owner-scope.d.ts +16 -0
- package/dist/solid/owner-scope.d.ts.map +1 -0
- package/dist/solid/owner-scope.js +31 -0
- package/dist/solid/owner-scope.js.map +1 -0
- package/dist/solid/page-point.d.ts +35 -0
- package/dist/solid/page-point.d.ts.map +1 -0
- package/dist/solid/page-point.js +87 -0
- package/dist/solid/page-point.js.map +1 -0
- package/dist/solid/presence-controller.d.ts +122 -0
- package/dist/solid/presence-controller.d.ts.map +1 -0
- package/dist/solid/presence-controller.js +1207 -0
- package/dist/solid/presence-controller.js.map +1 -0
- package/dist/solid/presence-record.d.ts +167 -0
- package/dist/solid/presence-record.d.ts.map +1 -0
- package/dist/solid/presence-record.js +26 -0
- package/dist/solid/presence-record.js.map +1 -0
- package/dist/solid/presence.d.ts +95 -0
- package/dist/solid/presence.d.ts.map +1 -0
- package/dist/solid/presence.jsx +195 -0
- package/dist/solid/presence.jsx.map +1 -0
- package/dist/solid/press-sensor.d.ts +20 -0
- package/dist/solid/press-sensor.d.ts.map +1 -0
- package/dist/solid/press-sensor.js +24 -0
- package/dist/solid/press-sensor.js.map +1 -0
- package/dist/solid/press.d.ts +24 -0
- package/dist/solid/press.d.ts.map +1 -0
- package/dist/solid/press.js +19 -0
- package/dist/solid/press.js.map +1 -0
- package/dist/solid/provider.d.ts +263 -0
- package/dist/solid/provider.d.ts.map +1 -0
- package/dist/solid/provider.jsx +411 -0
- package/dist/solid/provider.jsx.map +1 -0
- package/dist/solid/reactivity.d.ts +27 -0
- package/dist/solid/reactivity.d.ts.map +1 -0
- package/dist/solid/reactivity.js +9 -0
- package/dist/solid/reactivity.js.map +1 -0
- package/dist/solid/reorder-input.d.ts +22 -0
- package/dist/solid/reorder-input.d.ts.map +1 -0
- package/dist/solid/reorder-input.js +45 -0
- package/dist/solid/reorder-input.js.map +1 -0
- package/dist/solid/reorder.d.ts +241 -0
- package/dist/solid/reorder.d.ts.map +1 -0
- package/dist/solid/reorder.jsx +427 -0
- package/dist/solid/reorder.jsx.map +1 -0
- package/dist/solid/retarget-legs.d.ts +189 -0
- package/dist/solid/retarget-legs.d.ts.map +1 -0
- package/dist/solid/retarget-legs.js +825 -0
- package/dist/solid/retarget-legs.js.map +1 -0
- package/dist/solid/runtime.d.ts +179 -0
- package/dist/solid/runtime.d.ts.map +1 -0
- package/dist/solid/runtime.js +237 -0
- package/dist/solid/runtime.js.map +1 -0
- package/dist/solid/sensors.d.ts +95 -0
- package/dist/solid/sensors.d.ts.map +1 -0
- package/dist/solid/sensors.js +154 -0
- package/dist/solid/sensors.js.map +1 -0
- package/dist/solid/server-interpolators.d.ts +13 -0
- package/dist/solid/server-interpolators.d.ts.map +1 -0
- package/dist/solid/server-interpolators.js +20 -0
- package/dist/solid/server-interpolators.js.map +1 -0
- package/dist/solid/server.d.ts +9 -0
- package/dist/solid/server.d.ts.map +1 -0
- package/dist/solid/server.js +20 -0
- package/dist/solid/server.js.map +1 -0
- package/dist/solid/solid-reactivity.d.ts +3 -0
- package/dist/solid/solid-reactivity.d.ts.map +1 -0
- package/dist/solid/solid-reactivity.js +19 -0
- package/dist/solid/solid-reactivity.js.map +1 -0
- package/dist/solid/spine.d.ts +597 -0
- package/dist/solid/spine.d.ts.map +1 -0
- package/dist/solid/spine.js +2761 -0
- package/dist/solid/spine.js.map +1 -0
- package/dist/solid/stagger.d.ts +29 -0
- package/dist/solid/stagger.d.ts.map +1 -0
- package/dist/solid/stagger.js +13 -0
- package/dist/solid/stagger.js.map +1 -0
- package/dist/solid/unstable/v2/bridge.d.ts +33 -0
- package/dist/solid/unstable/v2/bridge.d.ts.map +1 -0
- package/dist/solid/unstable/v2/bridge.js +76 -0
- package/dist/solid/unstable/v2/bridge.js.map +1 -0
- package/dist/solid/unstable/v2/complete.d.ts +21 -0
- package/dist/solid/unstable/v2/complete.d.ts.map +1 -0
- package/dist/solid/unstable/v2/complete.js +27 -0
- package/dist/solid/unstable/v2/complete.js.map +1 -0
- package/dist/solid/unstable/v2/config-view.d.ts +49 -0
- package/dist/solid/unstable/v2/config-view.d.ts.map +1 -0
- package/dist/solid/unstable/v2/config-view.js +114 -0
- package/dist/solid/unstable/v2/config-view.js.map +1 -0
- package/dist/solid/unstable/v2/create-animate.d.ts +31 -0
- package/dist/solid/unstable/v2/create-animate.d.ts.map +1 -0
- package/dist/solid/unstable/v2/create-animate.js +102 -0
- package/dist/solid/unstable/v2/create-animate.js.map +1 -0
- package/dist/solid/unstable/v2/cycle.d.ts +20 -0
- package/dist/solid/unstable/v2/cycle.d.ts.map +1 -0
- package/dist/solid/unstable/v2/cycle.js +30 -0
- package/dist/solid/unstable/v2/cycle.js.map +1 -0
- package/dist/solid/unstable/v2/drag-sensor.d.ts +23 -0
- package/dist/solid/unstable/v2/drag-sensor.d.ts.map +1 -0
- package/dist/solid/unstable/v2/drag-sensor.js +32 -0
- package/dist/solid/unstable/v2/drag-sensor.js.map +1 -0
- package/dist/solid/unstable/v2/element.d.ts +178 -0
- package/dist/solid/unstable/v2/element.d.ts.map +1 -0
- package/dist/solid/unstable/v2/element.jsx +325 -0
- package/dist/solid/unstable/v2/element.jsx.map +1 -0
- package/dist/solid/unstable/v2/feature-section.d.ts +12 -0
- package/dist/solid/unstable/v2/feature-section.d.ts.map +1 -0
- package/dist/solid/unstable/v2/feature-section.js +23 -0
- package/dist/solid/unstable/v2/feature-section.js.map +1 -0
- package/dist/solid/unstable/v2/focus-sensor.d.ts +13 -0
- package/dist/solid/unstable/v2/focus-sensor.d.ts.map +1 -0
- package/dist/solid/unstable/v2/focus-sensor.js +23 -0
- package/dist/solid/unstable/v2/focus-sensor.js.map +1 -0
- package/dist/solid/unstable/v2/hover-sensor.d.ts +12 -0
- package/dist/solid/unstable/v2/hover-sensor.d.ts.map +1 -0
- package/dist/solid/unstable/v2/hover-sensor.js +22 -0
- package/dist/solid/unstable/v2/hover-sensor.js.map +1 -0
- package/dist/solid/unstable/v2/index.d.ts +131 -0
- package/dist/solid/unstable/v2/index.d.ts.map +1 -0
- package/dist/solid/unstable/v2/index.js +114 -0
- package/dist/solid/unstable/v2/index.js.map +1 -0
- package/dist/solid/unstable/v2/layout-projection.d.ts +21 -0
- package/dist/solid/unstable/v2/layout-projection.d.ts.map +1 -0
- package/dist/solid/unstable/v2/layout-projection.js +18 -0
- package/dist/solid/unstable/v2/layout-projection.js.map +1 -0
- package/dist/solid/unstable/v2/layout.d.ts +47 -0
- package/dist/solid/unstable/v2/layout.d.ts.map +1 -0
- package/dist/solid/unstable/v2/layout.jsx +62 -0
- package/dist/solid/unstable/v2/layout.jsx.map +1 -0
- package/dist/solid/unstable/v2/lifecycle.d.ts +267 -0
- package/dist/solid/unstable/v2/lifecycle.d.ts.map +1 -0
- package/dist/solid/unstable/v2/lifecycle.js +457 -0
- package/dist/solid/unstable/v2/lifecycle.js.map +1 -0
- package/dist/solid/unstable/v2/live-value.d.ts +146 -0
- package/dist/solid/unstable/v2/live-value.d.ts.map +1 -0
- package/dist/solid/unstable/v2/live-value.js +235 -0
- package/dist/solid/unstable/v2/live-value.js.map +1 -0
- package/dist/solid/unstable/v2/presence-handle.d.ts +55 -0
- package/dist/solid/unstable/v2/presence-handle.d.ts.map +1 -0
- package/dist/solid/unstable/v2/presence-handle.js +119 -0
- package/dist/solid/unstable/v2/presence-handle.js.map +1 -0
- package/dist/solid/unstable/v2/presence-hooks.d.ts +51 -0
- package/dist/solid/unstable/v2/presence-hooks.d.ts.map +1 -0
- package/dist/solid/unstable/v2/presence-hooks.js +115 -0
- package/dist/solid/unstable/v2/presence-hooks.js.map +1 -0
- package/dist/solid/unstable/v2/presence.d.ts +89 -0
- package/dist/solid/unstable/v2/presence.d.ts.map +1 -0
- package/dist/solid/unstable/v2/presence.jsx +107 -0
- package/dist/solid/unstable/v2/presence.jsx.map +1 -0
- package/dist/solid/unstable/v2/press-sensor.d.ts +13 -0
- package/dist/solid/unstable/v2/press-sensor.d.ts.map +1 -0
- package/dist/solid/unstable/v2/press-sensor.js +23 -0
- package/dist/solid/unstable/v2/press-sensor.js.map +1 -0
- package/dist/solid/unstable/v2/provider.d.ts +239 -0
- package/dist/solid/unstable/v2/provider.d.ts.map +1 -0
- package/dist/solid/unstable/v2/provider.jsx +423 -0
- package/dist/solid/unstable/v2/provider.jsx.map +1 -0
- package/dist/solid/unstable/v2/reorder.d.ts +292 -0
- package/dist/solid/unstable/v2/reorder.d.ts.map +1 -0
- package/dist/solid/unstable/v2/reorder.jsx +525 -0
- package/dist/solid/unstable/v2/reorder.jsx.map +1 -0
- package/dist/solid/unstable/v2/sensors.d.ts +91 -0
- package/dist/solid/unstable/v2/sensors.d.ts.map +1 -0
- package/dist/solid/unstable/v2/sensors.js +124 -0
- package/dist/solid/unstable/v2/sensors.js.map +1 -0
- package/dist/solid/unstable/v2/solid-reactivity.d.ts +61 -0
- package/dist/solid/unstable/v2/solid-reactivity.d.ts.map +1 -0
- package/dist/solid/unstable/v2/solid-reactivity.js +62 -0
- package/dist/solid/unstable/v2/solid-reactivity.js.map +1 -0
- package/dist/solid/unstable/v2/variant-node.d.ts +51 -0
- package/dist/solid/unstable/v2/variant-node.d.ts.map +1 -0
- package/dist/solid/unstable/v2/variant-node.js +336 -0
- package/dist/solid/unstable/v2/variant-node.js.map +1 -0
- package/dist/solid/unstable/v2/w.d.ts +42 -0
- package/dist/solid/unstable/v2/w.d.ts.map +1 -0
- package/dist/solid/unstable/v2/w.jsx +22 -0
- package/dist/solid/unstable/v2/w.jsx.map +1 -0
- package/dist/solid/unstable/v2/waiters.d.ts +72 -0
- package/dist/solid/unstable/v2/waiters.d.ts.map +1 -0
- package/dist/solid/unstable/v2/waiters.js +175 -0
- package/dist/solid/unstable/v2/waiters.js.map +1 -0
- package/dist/solid/unstable/v2/ward.d.ts +393 -0
- package/dist/solid/unstable/v2/ward.d.ts.map +1 -0
- package/dist/solid/unstable/v2/ward.jsx +1475 -0
- package/dist/solid/unstable/v2/ward.jsx.map +1 -0
- package/dist/solid/unstable/v2/wardic-config.d.ts +68 -0
- package/dist/solid/unstable/v2/wardic-config.d.ts.map +1 -0
- package/dist/solid/unstable/v2/wardic-config.jsx +28 -0
- package/dist/solid/unstable/v2/wardic-config.jsx.map +1 -0
- package/dist/solid/unstable/v2/wardic.d.ts +28 -0
- package/dist/solid/unstable/v2/wardic.d.ts.map +1 -0
- package/dist/solid/unstable/v2/wardic.jsx +16 -0
- package/dist/solid/unstable/v2/wardic.jsx.map +1 -0
- package/dist/solid/variant-node.d.ts +32 -0
- package/dist/solid/variant-node.d.ts.map +1 -0
- package/dist/solid/variant-node.js +257 -0
- package/dist/solid/variant-node.js.map +1 -0
- package/dist/solid/variant-target.d.ts +102 -0
- package/dist/solid/variant-target.d.ts.map +1 -0
- package/dist/solid/variant-target.js +180 -0
- package/dist/solid/variant-target.js.map +1 -0
- package/dist/solid/variant-tree.d.ts +349 -0
- package/dist/solid/variant-tree.d.ts.map +1 -0
- package/dist/solid/variant-tree.js +358 -0
- package/dist/solid/variant-tree.js.map +1 -0
- package/dist/solid/variants.d.ts +21 -0
- package/dist/solid/variants.d.ts.map +1 -0
- package/dist/solid/variants.js +43 -0
- package/dist/solid/variants.js.map +1 -0
- package/dist/solid/w.d.ts +42 -0
- package/dist/solid/w.d.ts.map +1 -0
- package/dist/solid/w.jsx +23 -0
- package/dist/solid/w.jsx.map +1 -0
- package/dist/solid/ward-keys.d.ts +236 -0
- package/dist/solid/ward-keys.d.ts.map +1 -0
- package/dist/solid/ward-keys.js +2 -0
- package/dist/solid/ward-keys.js.map +1 -0
- package/dist/solid/wardic-component.d.ts +50 -0
- package/dist/solid/wardic-component.d.ts.map +1 -0
- package/dist/solid/wardic-component.js +74 -0
- package/dist/solid/wardic-component.js.map +1 -0
- package/dist/solid/wardic-config-value.d.ts +87 -0
- package/dist/solid/wardic-config-value.d.ts.map +1 -0
- package/dist/solid/wardic-config-value.js +106 -0
- package/dist/solid/wardic-config-value.js.map +1 -0
- package/dist/solid/wardic-config.d.ts +69 -0
- package/dist/solid/wardic-config.d.ts.map +1 -0
- package/dist/solid/wardic-config.jsx +30 -0
- package/dist/solid/wardic-config.jsx.map +1 -0
- package/dist/solid/wardic.d.ts +45 -0
- package/dist/solid/wardic.d.ts.map +1 -0
- package/dist/solid/wardic.jsx +31 -0
- package/dist/solid/wardic.jsx.map +1 -0
- package/package.json +98 -4
- package/src/animation-controls.ts +204 -0
- package/src/animation-frame.ts +32 -0
- package/src/appear.ts +804 -0
- package/src/arc.ts +34 -0
- package/src/bridge.ts +98 -0
- package/src/complete.ts +45 -0
- package/src/config.ts +805 -0
- package/src/create-animate.ts +41 -0
- package/src/create-ward.tsx +1271 -0
- package/src/cycle-index.ts +18 -0
- package/src/cycle.ts +34 -0
- package/src/directive.ts +87 -0
- package/src/drag-constraints.ts +103 -0
- package/src/drag-controls.ts +94 -0
- package/src/drag-samples.ts +54 -0
- package/src/drag-sensor.ts +46 -0
- package/src/drag-track.ts +937 -0
- package/src/drag-transition.ts +43 -0
- package/src/drag.ts +58 -0
- package/src/element-style.ts +112 -0
- package/src/element.tsx +400 -0
- package/src/feature-section.ts +33 -0
- package/src/feature.ts +342 -0
- package/src/features/all.ts +18 -0
- package/src/features/colors.ts +36 -0
- package/src/features/dom-animation.ts +15 -0
- package/src/features/dom-max.ts +11 -0
- package/src/features/drag.ts +30 -0
- package/src/features/focus.ts +17 -0
- package/src/features/hover.ts +17 -0
- package/src/features/in-view.ts +17 -0
- package/src/features/input.ts +42 -0
- package/src/features/keys.ts +89 -0
- package/src/features/layout.ts +54 -0
- package/src/features/pan.ts +17 -0
- package/src/features/press.ts +17 -0
- package/src/focus-sensor.ts +32 -0
- package/src/frame-time.ts +45 -0
- package/src/gesture-callbacks.ts +212 -0
- package/src/gesture-features.ts +35 -0
- package/src/gesture-info.ts +81 -0
- package/src/hover-sensor.ts +32 -0
- package/src/imperative.ts +782 -0
- package/src/index.ts +334 -0
- package/src/instant.ts +90 -0
- package/src/layout-group.ts +32 -0
- package/src/layout-member.ts +300 -0
- package/src/layout-projection.ts +28 -0
- package/src/layout.tsx +127 -0
- package/src/live-style.ts +449 -0
- package/src/live-value.ts +378 -0
- package/src/motion-props.ts +325 -0
- package/src/owner-scope.ts +31 -0
- package/src/page-point.ts +98 -0
- package/src/presence-controller.ts +1483 -0
- package/src/presence-record.ts +189 -0
- package/src/presence.tsx +269 -0
- package/src/press-sensor.ts +33 -0
- package/src/press.ts +40 -0
- package/src/provider.tsx +526 -0
- package/src/reactivity.ts +29 -0
- package/src/reorder-input.ts +53 -0
- package/src/reorder.tsx +703 -0
- package/src/retarget-legs.ts +1048 -0
- package/src/runtime.ts +388 -0
- package/src/sensors.ts +189 -0
- package/src/server-interpolators.ts +33 -0
- package/src/server.ts +21 -0
- package/src/solid-reactivity.ts +21 -0
- package/src/spine.ts +3385 -0
- package/src/stagger.ts +31 -0
- package/src/unstable/v2/bridge.ts +79 -0
- package/src/unstable/v2/complete.ts +47 -0
- package/src/unstable/v2/config-view.ts +156 -0
- package/src/unstable/v2/create-animate.ts +123 -0
- package/src/unstable/v2/cycle.ts +33 -0
- package/src/unstable/v2/drag-sensor.ts +44 -0
- package/src/unstable/v2/element.tsx +478 -0
- package/src/unstable/v2/feature-section.ts +32 -0
- package/src/unstable/v2/focus-sensor.ts +31 -0
- package/src/unstable/v2/hover-sensor.ts +30 -0
- package/src/unstable/v2/index.ts +270 -0
- package/src/unstable/v2/layout-projection.ts +27 -0
- package/src/unstable/v2/layout.tsx +89 -0
- package/src/unstable/v2/lifecycle.ts +612 -0
- package/src/unstable/v2/live-value.ts +414 -0
- package/src/unstable/v2/presence-handle.ts +159 -0
- package/src/unstable/v2/presence-hooks.ts +157 -0
- package/src/unstable/v2/presence.tsx +174 -0
- package/src/unstable/v2/press-sensor.ts +31 -0
- package/src/unstable/v2/provider.tsx +524 -0
- package/src/unstable/v2/reorder.tsx +880 -0
- package/src/unstable/v2/sensors.ts +169 -0
- package/src/unstable/v2/solid-reactivity.ts +69 -0
- package/src/unstable/v2/variant-node.ts +446 -0
- package/src/unstable/v2/w.tsx +49 -0
- package/src/unstable/v2/waiters.ts +212 -0
- package/src/unstable/v2/ward.tsx +2068 -0
- package/src/unstable/v2/wardic-config.tsx +84 -0
- package/src/unstable/v2/wardic.tsx +41 -0
- package/src/variant-node.ts +368 -0
- package/src/variant-target.ts +234 -0
- package/src/variant-tree.ts +638 -0
- package/src/variants.ts +47 -0
- package/src/w.tsx +59 -0
- package/src/ward-keys.ts +343 -0
- package/src/wardic-component.ts +96 -0
- package/src/wardic-config-value.ts +150 -0
- package/src/wardic-config.tsx +78 -0
- package/src/wardic.tsx +59 -0
package/src/spine.ts
ADDED
|
@@ -0,0 +1,3385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The motion spine's Retarget (ADR-0005), Solid-free (#209): what an element does from its mount on
|
|
3
|
+
* when its target changes, for the Solid 1 binding (`create-ward.tsx`'s `runSpine`) and the Solid 2
|
|
4
|
+
* one (`unstable/v2/ward.tsx`) alike. A binding mounts the element, resolves its Override stack
|
|
5
|
+
* reactively ({@link resolutionOf}), and calls {@link ElementSpine.retarget} on each change, read
|
|
6
|
+
* where its Solid doesn't track; everything a Retarget does is here:
|
|
7
|
+
*
|
|
8
|
+
* end the running legs -> carry the keys no entry sets back to their Base values (ADR-0022) ->
|
|
9
|
+
* start each key from its Snapshot (ADR-0024) -> take over an Appear animation (ADR-0044) ->
|
|
10
|
+
* build the legs (`retarget-legs.ts`) and run each into the gate -> `gate.write`.
|
|
11
|
+
*
|
|
12
|
+
* With it, the helpers both bindings' Wards share: the Starting values, the Base values' start links,
|
|
13
|
+
* the sink wrappers, and what a server render reads.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import * as BaseValue from "@wardic/core/BaseValue"
|
|
18
|
+
import type * as DefaultTransition from "@wardic/core/DefaultTransition"
|
|
19
|
+
import type * as Frame from "@wardic/core/Frame"
|
|
20
|
+
import type * as FrameLoop from "@wardic/core/FrameLoop"
|
|
21
|
+
import * as InterpolatorRegistry from "@wardic/core/InterpolatorRegistry"
|
|
22
|
+
import * as Machine from "@wardic/core/Machine"
|
|
23
|
+
import * as Override from "@wardic/core/Override"
|
|
24
|
+
import * as Playback from "@wardic/core/Playback"
|
|
25
|
+
import * as Projection from "@wardic/core/Projection"
|
|
26
|
+
import type * as ReducedMotion from "@wardic/core/ReducedMotion"
|
|
27
|
+
import type * as Renderer from "@wardic/core/Renderer"
|
|
28
|
+
import * as Supersede from "@wardic/core/Supersede"
|
|
29
|
+
import * as Values from "@wardic/core/Values"
|
|
30
|
+
import * as Appear from "@wardic/dom/Appear"
|
|
31
|
+
import * as DomRenderer from "@wardic/dom/DomRenderer"
|
|
32
|
+
import type * as FlushGate from "@wardic/dom/FlushGate"
|
|
33
|
+
import * as OwnValues from "@wardic/dom/OwnValues"
|
|
34
|
+
import * as Report from "@wardic/dom/Report"
|
|
35
|
+
import * as MeasuredPair from "@wardic/dom/MeasuredPair"
|
|
36
|
+
import * as Style from "@wardic/dom/Style"
|
|
37
|
+
import * as Variables from "@wardic/dom/Variables"
|
|
38
|
+
import * as Arr from "effect/Array"
|
|
39
|
+
import * as Bool from "effect/Boolean"
|
|
40
|
+
import * as Cause from "effect/Cause"
|
|
41
|
+
import * as Effect from "effect/Effect"
|
|
42
|
+
import * as Equivalence from "effect/Equivalence"
|
|
43
|
+
import * as Exit from "effect/Exit"
|
|
44
|
+
import * as Fiber from "effect/Fiber"
|
|
45
|
+
import { constFalse, constUndefined, constVoid, identity } from "effect/Function"
|
|
46
|
+
import * as Iter from "effect/Iterable"
|
|
47
|
+
import * as Match from "effect/Match"
|
|
48
|
+
import * as Option from "effect/Option"
|
|
49
|
+
import * as Predicate from "effect/Predicate"
|
|
50
|
+
import * as Rec from "effect/Record"
|
|
51
|
+
import { AtomRegistry } from "effect/reactivity"
|
|
52
|
+
import * as UndefinedOr from "effect/UndefinedOr"
|
|
53
|
+
import { enterServer, type Handoff, keyOf, type MountAnimation, repeatsForever, type ServerAppear, skippedField, startAtOf } from "./appear.ts"
|
|
54
|
+
import type {
|
|
55
|
+
WardConfig,
|
|
56
|
+
WardicAnimationDefinition,
|
|
57
|
+
WardicTransition,
|
|
58
|
+
WardicVariant,
|
|
59
|
+
WardicVariantLabels,
|
|
60
|
+
WardicVariants,
|
|
61
|
+
WardicVariantTransition,
|
|
62
|
+
} from "./config.ts"
|
|
63
|
+
import { type ControlsChange, join, leave } from "./animation-controls.ts"
|
|
64
|
+
import { clockNowOf, frameNow, MAX_VELOCITY_DELTA, NOMINAL_FRAME } from "./frame-time.ts"
|
|
65
|
+
import { type LiveStyle, serverRegistryOf, turnsWillChangeOn, valuesOf } from "./live-style.ts"
|
|
66
|
+
import { type ExitGroup, type PresenceContextValue, type PresenceExit, presenceMade, type PresenceRecord, type PresenceSeed, type PresenceTree } from "./presence-record.ts"
|
|
67
|
+
import type { DragTrack } from "./drag-track.ts"
|
|
68
|
+
import type { Accessor, Reactivity } from "./reactivity.ts"
|
|
69
|
+
import { delayedBy, type Follows, hasManyStops, keyframesFrom, type Leg, legsOf, mapMachine, type MeasuredPairs, startingAt, type StopsByKey, transitionForKey, type Velocities, withPathAxes } from "./retarget-legs.ts"
|
|
70
|
+
import type { WardicRuntime, WardicServices } from "./runtime.ts"
|
|
71
|
+
import {
|
|
72
|
+
changesContext,
|
|
73
|
+
hasOwnVariants,
|
|
74
|
+
GESTURE_LAYERS,
|
|
75
|
+
type GestureLayer,
|
|
76
|
+
type GestureNames,
|
|
77
|
+
type NameOrchestration,
|
|
78
|
+
activeNamesOf,
|
|
79
|
+
type GestureHandDown,
|
|
80
|
+
gesturesOn,
|
|
81
|
+
type GestureStates,
|
|
82
|
+
handDownFrom,
|
|
83
|
+
handDownOf,
|
|
84
|
+
NO_GESTURE_STATES,
|
|
85
|
+
NO_HAND_DOWN,
|
|
86
|
+
isVariantChild,
|
|
87
|
+
nameOf,
|
|
88
|
+
NO_GESTURE_NAMES,
|
|
89
|
+
ownOrInherited,
|
|
90
|
+
sameGestureNames,
|
|
91
|
+
type ServerVariantNode,
|
|
92
|
+
startingValuesOf,
|
|
93
|
+
startsAtAnimate,
|
|
94
|
+
startsQuiet,
|
|
95
|
+
type WardicVariantContextValue,
|
|
96
|
+
} from "./variant-tree.ts"
|
|
97
|
+
import { type Entry, type Keyframes, mergedOf, type NamedTarget, NO_VARIANT_STATE, resolveTarget, resolveTargets, sameLabels, type Target, transitionOfTarget, type VariantState } from "./variant-target.ts"
|
|
98
|
+
|
|
99
|
+
export { NO_VARIANT_STATE, resolveTarget, type Target, transitionOfTarget, type VariantState } from "./variant-target.ts"
|
|
100
|
+
|
|
101
|
+
/** What an element's exit is resolved from (#277): its last config, kept current while it's alive. */
|
|
102
|
+
export interface ExitConfig {
|
|
103
|
+
readonly exit: WardicVariant | WardicVariantLabels | undefined
|
|
104
|
+
readonly variants: WardicVariants | undefined
|
|
105
|
+
readonly custom: unknown
|
|
106
|
+
/** Its `transition`, or the one it inherits. */
|
|
107
|
+
readonly transition: WardicTransition | undefined
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** No exit: an element outside a presence never resolves one. */
|
|
111
|
+
export const NO_EXIT_CONFIG: ExitConfig = { exit: undefined, variants: undefined, custom: undefined, transition: undefined }
|
|
112
|
+
|
|
113
|
+
/** A record's place in the variant tree (#290), from its config at setup. */
|
|
114
|
+
export const presenceTreeOf = (c: WardConfig): PresenceTree => ({
|
|
115
|
+
joins: isVariantChild(c),
|
|
116
|
+
provides: changesContext(c),
|
|
117
|
+
twin: Predicate.isNotUndefined(c.layoutId),
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* An element's ref that tells its presence it was made (#286, ADR-0048): its `key`, read once, and
|
|
122
|
+
* whether it's a twin. With no presence, `set` itself: nothing is made per element.
|
|
123
|
+
*/
|
|
124
|
+
export const madeWith = <E extends Element>(presence: PresenceContextValue | undefined, set: (el: E) => void, c: WardConfig): ((el: E) => void) => {
|
|
125
|
+
if (Predicate.isUndefined(presence)) return set
|
|
126
|
+
const key = c.key
|
|
127
|
+
const twin = Predicate.isNotUndefined(c.layoutId)
|
|
128
|
+
return (el) => {
|
|
129
|
+
presenceMade(presence, el, key, twin)
|
|
130
|
+
set(el)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** An element's {@link ExitConfig} from its config now. */
|
|
135
|
+
export const exitConfigOf = (c: WardConfig, parent: WardicVariantContextValue | undefined): ExitConfig => ({
|
|
136
|
+
exit: c.exit,
|
|
137
|
+
variants: c.variants,
|
|
138
|
+
custom: c.custom,
|
|
139
|
+
transition: ownOrInherited(c.transition, parent),
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
/** An exit resolved (#277): its values, and the transition it runs on. */
|
|
143
|
+
export type ExitTarget = PresenceExit
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Resolve an element's exit as it leaves (#277): with its `<AnimatePresence custom>` when that's
|
|
147
|
+
* given (Motion's: the leaving element's own props can't change any more), else its own `custom`; on
|
|
148
|
+
* its own transition, else the element's.
|
|
149
|
+
*/
|
|
150
|
+
export const exitTargetOf = (config: ExitConfig, presenceCustom: unknown, state: VariantState, inherited: WardicVariantLabels | undefined): ExitTarget => {
|
|
151
|
+
const custom = UndefinedOr.match(presenceCustom, { onUndefined: () => config.custom, onDefined: identity })
|
|
152
|
+
// Its own `exit` wins; a Variant child with none exits on the name its provider hands down (#290).
|
|
153
|
+
const entry: Entry = UndefinedOr.match(config.exit, { onDefined: identity, onUndefined: (): Entry => inherited })
|
|
154
|
+
const name = UndefinedOr.match(nameOf(config.exit), { onDefined: identity, onUndefined: () => inherited })
|
|
155
|
+
if (Arr.isArray(entry)) {
|
|
156
|
+
// A list (#297): merged, and each name's own keys on its own transition.
|
|
157
|
+
const targets = resolveTargets(entry, config.variants, custom, state)
|
|
158
|
+
const merged = mergedOf(targets)
|
|
159
|
+
return {
|
|
160
|
+
values: merged.values,
|
|
161
|
+
transition: transitionOfTarget(merged.transition, config.transition),
|
|
162
|
+
name,
|
|
163
|
+
groups: exitGroupsOf(targets, config.transition),
|
|
164
|
+
keyframes: merged.keyframes,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const target = resolveTarget(entry, config.variants, custom, state)
|
|
168
|
+
return { values: target.values, transition: transitionOfTarget(target.transition, config.transition), name, keyframes: target.keyframes }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** A list's exit groups (#297): each name, with the keys it's the last to set, on its own transition else `element`. */
|
|
172
|
+
const exitGroupsOf = (targets: ReadonlyArray<NamedTarget>, element: WardicTransition | undefined): ReadonlyArray<ExitGroup> =>
|
|
173
|
+
Arr.map(targets, (target, i) => ({
|
|
174
|
+
name: target.name as string,
|
|
175
|
+
values: Rec.filter(target.values, (_, key) => Bool.not(Arr.some(Arr.drop(targets, i + 1), (later) => Object.hasOwn(later.values, key)))),
|
|
176
|
+
transition: transitionOfTarget(target.transition, element),
|
|
177
|
+
}))
|
|
178
|
+
|
|
179
|
+
/** A config given as a value or as an accessor, as an accessor. A config is always an object, so a function is the accessor. */
|
|
180
|
+
export const accessorOf = <A extends object>(config: A | Accessor<A>): Accessor<A> =>
|
|
181
|
+
Bool.match(Predicate.isFunction(config), { onTrue: () => config as Accessor<A>, onFalse: () => () => config as A })
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* `transition` with a stagger delay folded into its own and its entries' (ADR-0007, #301), reused
|
|
185
|
+
* while its fields are (#66).
|
|
186
|
+
*/
|
|
187
|
+
const staggeredOf = (
|
|
188
|
+
transition: WardicTransition | undefined,
|
|
189
|
+
extra: number,
|
|
190
|
+
reuse: (transition: WardicTransition) => WardicTransition,
|
|
191
|
+
): WardicTransition | undefined => UndefinedOr.map(delayedBy(transition, extra), reuse)
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* A Retarget's keys by the entry that drives each (#291), when an active entry (the base's included)
|
|
195
|
+
* has its own transition: the highest active entry that sets it, and the base for a key none sets (a
|
|
196
|
+
* return). `undefined` when none has: one transition for every key.
|
|
197
|
+
*/
|
|
198
|
+
const keysByLayerOf = (
|
|
199
|
+
t: Values.Values,
|
|
200
|
+
layers: ReadonlyArray<OverrideEntry>,
|
|
201
|
+
extraDelay: ((name: string | undefined) => number) | undefined,
|
|
202
|
+
extra: number,
|
|
203
|
+
listed: boolean,
|
|
204
|
+
shut: ReadonlyArray<boolean> | undefined,
|
|
205
|
+
): ReadonlyArray<ReadonlySet<string>> | undefined => {
|
|
206
|
+
// Per Retarget (#460): statements.
|
|
207
|
+
if (!Arr.some(layers, hasOwnTransition) && !delaysDiffer(layers, extraDelay, extra) && !anyShutOf(shut)) return undefined
|
|
208
|
+
// A list's returns in a group of their own, after its entries' (#297).
|
|
209
|
+
let returnsAt = 0
|
|
210
|
+
if (listed) returnsAt = layers.length
|
|
211
|
+
const groups = Arr.makeBy(Math.max(layers.length, returnsAt + 1), () => new Set<string>())
|
|
212
|
+
Arr.forEach(Rec.keys(t), (key) => {
|
|
213
|
+
// A key only a Transition end sets (#428) is in its entry's group too, as the entry's own.
|
|
214
|
+
const at = Option.getOrElse(Arr.findLastIndex(layers, (layer) => Bool.and(layer.active, Bool.or(Object.hasOwn(layer.values, key), Object.hasOwn(layer.ends, key)))), () => returnsAt)
|
|
215
|
+
UndefinedOr.map(groups[at], (group) => group.add(key))
|
|
216
|
+
})
|
|
217
|
+
return groups
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The transition `key` animates on in a Retarget: its group's (#277, #297), else the Retarget's one,
|
|
222
|
+
* with no stagger (#448 reads its fields, not its delay).
|
|
223
|
+
*/
|
|
224
|
+
const keyTransitionOf = (
|
|
225
|
+
resolution: Resolution,
|
|
226
|
+
groups: ReadonlyArray<ReadonlySet<string>> | undefined,
|
|
227
|
+
key: string,
|
|
228
|
+
transition: WardicTransition | undefined,
|
|
229
|
+
element: WardicTransition | undefined,
|
|
230
|
+
): WardicTransition | undefined => {
|
|
231
|
+
if (Predicate.isUndefined(groups)) return transition
|
|
232
|
+
return groupTransitionOf(resolution, groups, Option.getOrElse(Arr.findFirstIndex(groups, (group) => group.has(key)), () => -1), transition, element, undefined, identity)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Whether `given` gives `key` a velocity (#448, Motion's `valueTransition.velocity`): it kicks the key, so it animates though at its target. */
|
|
236
|
+
const givesVelocity = (given: WardicTransition | undefined, key: string): boolean => {
|
|
237
|
+
const own = transitionForKey(given, key) as { readonly velocity?: unknown } | undefined
|
|
238
|
+
if (Predicate.isUndefined(own)) return false
|
|
239
|
+
return Bool.and(Predicate.isNumber(own.velocity), own.velocity !== 0)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** A key's velocity in the Snapshot, 0 with none (#448). Per key, per Retarget: a guard. */
|
|
243
|
+
const velocityOf = (snapshot: { readonly velocities: Readonly<Record<string, number>> }, key: string): number => {
|
|
244
|
+
const velocity = snapshot.velocities[key]
|
|
245
|
+
if (Predicate.isUndefined(velocity)) return 0
|
|
246
|
+
return velocity
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** `t`'s keys in the `i`th group, or all of them when there are no groups. */
|
|
250
|
+
const pickKeys = (t: Values.Values, groups: ReadonlyArray<ReadonlySet<string>> | undefined, i: number): Values.Values => {
|
|
251
|
+
if (Predicate.isUndefined(groups)) return t
|
|
252
|
+
const group = groups[i]
|
|
253
|
+
if (Predicate.isUndefined(group)) return {}
|
|
254
|
+
return Rec.filter(t, (_, key) => group.has(key))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** A transition's `delay`, 0 without one. */
|
|
258
|
+
export const delayOf = (transition: WardicTransition | undefined): number =>
|
|
259
|
+
UndefinedOr.match(transition, { onUndefined: () => 0, onDefined: ({ delay = 0 }) => delay })
|
|
260
|
+
|
|
261
|
+
/** No keys: what a drag moves on a projected element, or one with no drag. */
|
|
262
|
+
const NO_KEYS: ReadonlyArray<string> = []
|
|
263
|
+
|
|
264
|
+
/** The keys a drag of `axis` moves directly. Built once, at module load. */
|
|
265
|
+
const dragMovesOf: (axis: boolean | "x" | "y") => ReadonlyArray<string> = Match.type<boolean | "x" | "y">().pipe(
|
|
266
|
+
Match.when("x", (): ReadonlyArray<string> => ["x"]),
|
|
267
|
+
Match.when("y", (): ReadonlyArray<string> => ["y"]),
|
|
268
|
+
Match.orElse((): ReadonlyArray<string> => ["x", "y"]),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
/** Whether a start link's value takes its target's shape: both numbers, or both strings. */
|
|
272
|
+
const isSameKind = (value: number | string, like: number | string): boolean => Predicate.isNumber(value) === Predicate.isNumber(like)
|
|
273
|
+
|
|
274
|
+
/** The keys a drag direct-sets (ADR-0004). */
|
|
275
|
+
const TRANSLATE_KEYS: ReadonlySet<string> = new Set(["x", "y", "translateX", "translateY"])
|
|
276
|
+
const TRANSLATE_KEY_LIST: ReadonlyArray<string> = Arr.fromIterable(TRANSLATE_KEYS)
|
|
277
|
+
|
|
278
|
+
/** Whether a value map has any key. */
|
|
279
|
+
const hasKeys: Predicate.Predicate<Values.Values> = Predicate.not(Rec.isEmptyRecord)
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Whether two transitions have the same keys, each holding the same value (`===`), an entry the
|
|
283
|
+
* same fields (#301: a stagger makes each entry anew).
|
|
284
|
+
*/
|
|
285
|
+
const sameFields = (a: object, b: object): boolean => {
|
|
286
|
+
const ra = a as Readonly<Record<string, unknown>>
|
|
287
|
+
const rb = b as Readonly<Record<string, unknown>>
|
|
288
|
+
const keys = Rec.keys(ra)
|
|
289
|
+
return Bool.and(keys.length === Rec.keys(rb).length, Arr.every(keys, (key) => Bool.and(key in rb, sameField(ra[key], rb[key]))))
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Whether two fields are the same: `===`, or two entries with the same fields (#301). */
|
|
293
|
+
const sameField = (a: unknown, b: unknown): boolean => {
|
|
294
|
+
if (a === b) return true
|
|
295
|
+
if (Bool.and(Predicate.isObject(a), Predicate.isObject(b))) return sameFields(a as object, b as object)
|
|
296
|
+
return false
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** The Override stack resolved: a Retarget's input. */
|
|
300
|
+
export interface Resolution {
|
|
301
|
+
/** Each key's value from the highest active entry. */
|
|
302
|
+
readonly resolved: Values.Values
|
|
303
|
+
/** The base entry, the first link of a key's Base value (ADR-0022). */
|
|
304
|
+
readonly base: Values.Values
|
|
305
|
+
/** Whether a drag holds the translate keys (ADR-0004's strip). */
|
|
306
|
+
readonly dragging: boolean
|
|
307
|
+
/** The variant name (or list, #297) the base entry resolved: its own `animate`'s, else the one it inherits; none for a values map. */
|
|
308
|
+
readonly variant: WardicVariantLabels | undefined
|
|
309
|
+
/** The entries it was resolved with (#257): a landing's gestures are the ones its Retarget started with. */
|
|
310
|
+
readonly active: Active
|
|
311
|
+
/** The base target's own transition (#277): the keys the base entry drives animate on it. */
|
|
312
|
+
readonly transition: WardicVariantTransition | undefined
|
|
313
|
+
/**
|
|
314
|
+
* The Override stack's entries (#291): the base, then each gesture's, lowest first. A Retarget runs
|
|
315
|
+
* each key on the own transition of the highest active entry that sets it.
|
|
316
|
+
*/
|
|
317
|
+
readonly layers: ReadonlyArray<OverrideEntry>
|
|
318
|
+
/** The names its active gesture layers resolved (#291): its own `while*` names, else those it took. */
|
|
319
|
+
readonly names: GestureNames
|
|
320
|
+
/**
|
|
321
|
+
* Which gesture layers are on and set values or a Transition end (#470); a named one is in `names`.
|
|
322
|
+
* One turning on or off is a new target, whatever it resolves, as a Motion type turning active
|
|
323
|
+
* animates (`animation-state.ts`).
|
|
324
|
+
*/
|
|
325
|
+
readonly layersOn: GestureStates
|
|
326
|
+
/** Its entries' **Transition ends** (#428), when any active one has one. */
|
|
327
|
+
readonly ends: Ends | undefined
|
|
328
|
+
/** Each key's **Keyframes**, as the entry that drives it gives them (#429), when any active entry has some. */
|
|
329
|
+
readonly keyframes: KeyframesNow | undefined
|
|
330
|
+
/**
|
|
331
|
+
* The **Controls change** in effect (#433): its own controls' latest, else the one its provider is
|
|
332
|
+
* in, for a Variant child. A new one is a Retarget, whatever it resolves to.
|
|
333
|
+
*/
|
|
334
|
+
readonly change: ControlsChange | undefined
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* A key's **Keyframes** as a Retarget resolves them (#429): their stops, and the entry that drives the
|
|
339
|
+
* key, by its place in the Override stack and the variant name it resolved. Two are the same when all
|
|
340
|
+
* three are, Motion's rule for when Keyframes play again (`animation-state.ts`): an equal list, stop by
|
|
341
|
+
* stop, that a re-render makes runs on, where new stops, another entry driving the key (a hover over it
|
|
342
|
+
* ending) or a new name for its entry (`variantDidChange`) plays them again from their first stop.
|
|
343
|
+
*/
|
|
344
|
+
export interface KeyframesAt {
|
|
345
|
+
readonly stops: Style.Stops
|
|
346
|
+
readonly layer: number
|
|
347
|
+
readonly name: string | undefined
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Each key's {@link KeyframesAt}. */
|
|
351
|
+
export type KeyframesNow = Readonly<Record<string, KeyframesAt>>
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* A resolution's **Transition ends** (#428): each key only an entry's Transition end sets, with the
|
|
355
|
+
* entry that drives it (the highest active one that sets it, as for any key), and each entry's end
|
|
356
|
+
* values for the keys it drives, a key it also animates among them.
|
|
357
|
+
*/
|
|
358
|
+
export interface Ends {
|
|
359
|
+
readonly endOnly: ReadonlyMap<string, number>
|
|
360
|
+
readonly byLayer: ReadonlyArray<Values.Values | undefined>
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* One entry of the Override stack (#291): whether it's on, its values, its target's own transition,
|
|
365
|
+
* and the variant name it resolved (#297), `undefined` for a values map. A list of names is an entry
|
|
366
|
+
* a name, in order.
|
|
367
|
+
*/
|
|
368
|
+
export interface OverrideEntry {
|
|
369
|
+
readonly active: boolean
|
|
370
|
+
readonly values: Values.Values
|
|
371
|
+
readonly transition: WardicVariantTransition | undefined
|
|
372
|
+
readonly name: string | undefined
|
|
373
|
+
/** Its target's **Transition end** (#428): its keys are the entry's, set once its animated keys land. */
|
|
374
|
+
readonly ends: Values.Values
|
|
375
|
+
/** Its target's **Keyframes** (#429): each such key's stops, its value in `values` their last. */
|
|
376
|
+
readonly keyframes: Keyframes | undefined
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** A gesture layer that's off: nothing is resolved for it. */
|
|
380
|
+
const OFF: OverrideEntry = { active: false, values: {}, transition: undefined, name: undefined, ends: {}, keyframes: undefined }
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* A gesture layer (#291), while it's on (its own, or its provider's): its own `while*` (a target, or
|
|
384
|
+
* a variant name), else the label its provider hands down. Off, or with neither, nothing is resolved.
|
|
385
|
+
* Whether it set values or a Transition end (#470).
|
|
386
|
+
*/
|
|
387
|
+
const gestureLayersInto = (
|
|
388
|
+
layers: Array<OverrideEntry>,
|
|
389
|
+
own: Entry,
|
|
390
|
+
on: boolean,
|
|
391
|
+
label: WardicVariantLabels | undefined,
|
|
392
|
+
variants: WardicVariants | undefined,
|
|
393
|
+
custom: unknown,
|
|
394
|
+
state: VariantState,
|
|
395
|
+
): boolean => {
|
|
396
|
+
if (Bool.not(on)) {
|
|
397
|
+
layers.push(OFF)
|
|
398
|
+
return false
|
|
399
|
+
}
|
|
400
|
+
const entry = UndefinedOr.match(own, { onDefined: identity, onUndefined: (): Entry => label })
|
|
401
|
+
if (Predicate.isUndefined(entry)) {
|
|
402
|
+
layers.push(OFF)
|
|
403
|
+
return false
|
|
404
|
+
}
|
|
405
|
+
// A list is an entry a name, in order (#297): the last name that sets a key drives it.
|
|
406
|
+
if (Arr.isArray(entry)) {
|
|
407
|
+
let sets = false
|
|
408
|
+
Arr.forEach(resolveTargets(entry, variants, custom, state), (target) => {
|
|
409
|
+
const layer = namedLayerOf(target)
|
|
410
|
+
layers.push(layer)
|
|
411
|
+
if (setsValues(layer)) sets = true
|
|
412
|
+
})
|
|
413
|
+
return sets
|
|
414
|
+
}
|
|
415
|
+
const layer = layerOf(resolveTarget(entry, variants, custom, state), labelOrUndefined(entry))
|
|
416
|
+
layers.push(layer)
|
|
417
|
+
return setsValues(layer)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Whether a gesture layer sets values or a Transition end (#470). A values map with none sets
|
|
422
|
+
* nothing; a name that resolves none is told by its name (`names`), as Motion animates it.
|
|
423
|
+
*/
|
|
424
|
+
const setsValues = (layer: OverrideEntry): boolean => {
|
|
425
|
+
if (hasKeys(layer.values)) return true
|
|
426
|
+
return hasKeys(layer.ends)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** A single entry's name: itself, if a name; `undefined` for a values map. */
|
|
430
|
+
const labelOrUndefined = (entry: Entry): string | undefined => Bool.match(Predicate.isString(entry), { onTrue: () => entry as string, onFalse: constUndefined })
|
|
431
|
+
|
|
432
|
+
/** Each gesture on: the element's own, or its provider's too when it has one handing any down. */
|
|
433
|
+
const gesturesOnOf = (active: Active, above: GestureHandDown): GestureStates => {
|
|
434
|
+
if (above === NO_HAND_DOWN) return active
|
|
435
|
+
return gesturesOn(active, above.on)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* The name each gesture layer resolves while it's on (#291): its own `while*`, if a name, else the
|
|
440
|
+
* label it took. With none on, the shared empty one: nothing made.
|
|
441
|
+
*/
|
|
442
|
+
const activeNamesFor = (c: WardConfig, on: GestureStates, labels: GestureNames): GestureNames => {
|
|
443
|
+
if (Bool.not(anyOn(on))) return NO_GESTURE_NAMES
|
|
444
|
+
return activeNamesOf(handDownOf(c, on, { on: NO_GESTURE_STATES, names: labels }))
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Whether any gesture is on: per Retarget, so no closure. */
|
|
448
|
+
const anyOn = (on: GestureStates): boolean => on.inView || on.focused || on.hovered || on.pressed || on.dragged
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Whether an active entry's name delays its keys otherwise than the first entry's (#297): a Variant
|
|
452
|
+
* child's names each take their own stagger slot. Never for an element that joins none, nor for one
|
|
453
|
+
* entry.
|
|
454
|
+
*/
|
|
455
|
+
const delaysDiffer = (layers: ReadonlyArray<OverrideEntry>, extraDelay: ((name: string | undefined) => number) | undefined, extra: number): boolean => {
|
|
456
|
+
if (Bool.or(Predicate.isUndefined(extraDelay), layers.length < 2)) return false
|
|
457
|
+
return Arr.some(layers, (layer) => Bool.and(layer.active, extraOf(extraDelay, layer) !== extra))
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** An entry's stagger delay (ADR-0007, #297): its name's, for a Variant child; 0 for any other element. */
|
|
461
|
+
const extraOf = (extraDelay: ((name: string | undefined) => number) | undefined, layer: OverrideEntry | undefined): number => {
|
|
462
|
+
if (Predicate.isUndefined(extraDelay)) return 0
|
|
463
|
+
if (Predicate.isUndefined(layer)) return extraDelay(undefined)
|
|
464
|
+
return extraDelay(layer.name)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* The transition a key group runs on (#291, #297): unsplit, the base's (`transition`, its delay
|
|
469
|
+
* folded in); split, its entry's own, else the element's, at its name's delay. A list's first
|
|
470
|
+
* group carries its returns too, which run on the element's transition, as a list's base has no one
|
|
471
|
+
* transition of its own.
|
|
472
|
+
*/
|
|
473
|
+
const groupTransitionOf = (
|
|
474
|
+
resolution: Resolution,
|
|
475
|
+
groups: ReadonlyArray<ReadonlySet<string>> | undefined,
|
|
476
|
+
i: number,
|
|
477
|
+
transition: WardicTransition | undefined,
|
|
478
|
+
element: WardicTransition | undefined,
|
|
479
|
+
extraDelay: ((name: string | undefined) => number) | undefined,
|
|
480
|
+
reuse: (transition: WardicTransition) => WardicTransition,
|
|
481
|
+
): WardicTransition | undefined => {
|
|
482
|
+
if (Predicate.isUndefined(groups)) return transition
|
|
483
|
+
if (Bool.and(i === 0, Bool.not(Arr.isArray(resolution.variant)))) return transition
|
|
484
|
+
const layer = resolution.layers[i]
|
|
485
|
+
// A list's returns: the element's, at its whole orchestration's delay.
|
|
486
|
+
if (Predicate.isUndefined(layer)) return staggeredOf(element, extraOf(extraDelay, undefined), reuse)
|
|
487
|
+
return staggeredOf(transitionOfTarget(layer.transition, element), extraOf(extraDelay, layer), reuse)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/** No names. */
|
|
491
|
+
const NO_NAMES: ReadonlyArray<string> = []
|
|
492
|
+
|
|
493
|
+
/** No legs. */
|
|
494
|
+
const NO_LEGS: ReadonlyArray<Leg> = []
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Where a Retarget's legs land (#257, #325): what each running leg's frames and end are told to.
|
|
498
|
+
* The last Retarget's, which every running leg counts toward, the ones it carried included.
|
|
499
|
+
*/
|
|
500
|
+
interface Landing {
|
|
501
|
+
/** A leg drew a frame: its last, when `done`. */
|
|
502
|
+
readonly report: (done: boolean) => void
|
|
503
|
+
/** A leg settled. */
|
|
504
|
+
readonly settled: () => void
|
|
505
|
+
/** A leg failed (#257 item 4). */
|
|
506
|
+
readonly failed: (cause: Cause.Cause<unknown>) => void
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* A gated Retarget's landing (ADR-0007 `when`, #325): the legs it carries report the element
|
|
511
|
+
* unsettled, as it still owes an animation, and land nothing; the Retarget once the gate opens
|
|
512
|
+
* counts them.
|
|
513
|
+
*/
|
|
514
|
+
const gatedLandingOf = (onFrame: ((done: boolean) => void) | undefined): Landing => ({
|
|
515
|
+
report: () => {
|
|
516
|
+
if (Predicate.isNotUndefined(onFrame)) onFrame(false)
|
|
517
|
+
},
|
|
518
|
+
settled: constVoid,
|
|
519
|
+
failed: constVoid,
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* A running leg (#325): the keys it draws, each with the target its Retarget resolved, and the
|
|
524
|
+
* landing its frames and end count toward. It's its machines' run options, with its own token
|
|
525
|
+
* (#200): a later Retarget that resolves a key to the same target leaves the leg that key, every
|
|
526
|
+
* other key is given up, and a leg left with none is ended.
|
|
527
|
+
*/
|
|
528
|
+
interface Running extends Machine.RunOptions {
|
|
529
|
+
/** Each key it animates, with the target its Retarget gave it. */
|
|
530
|
+
readonly targets: Values.Values
|
|
531
|
+
/** The resolution of the Retarget that started it: a `when` gate holds that change's legs. */
|
|
532
|
+
readonly resolution: Resolution
|
|
533
|
+
/** How many of its keys it still draws. */
|
|
534
|
+
count: number
|
|
535
|
+
/** A frame's values for the keys it still draws: the frame's own while it draws all of them. */
|
|
536
|
+
values: (value: Values.Values) => Values.Values
|
|
537
|
+
/** The same, for a frame's velocities. */
|
|
538
|
+
velocities: (velocity: Readonly<Record<string, number>>) => Readonly<Record<string, number>>
|
|
539
|
+
/** It drew its last frame: its keys landed. */
|
|
540
|
+
done: boolean
|
|
541
|
+
/** It gave up a key in this Retarget: what it still draws is worked out at the Retarget's end. */
|
|
542
|
+
losing: boolean
|
|
543
|
+
fiber: Fiber.Fiber<void> | undefined
|
|
544
|
+
/** The landing it counts toward: its Retarget's, then the one of each Retarget that carried it. */
|
|
545
|
+
landing: Landing
|
|
546
|
+
/** A list's names its end is told to (#297), for the Retarget it counts toward. */
|
|
547
|
+
names: ((end: unknown) => void) | undefined
|
|
548
|
+
/** Its kind (#464): a Blend leg's keys' velocities are its frames', a Snap's none, the rest write their own. */
|
|
549
|
+
readonly kind: Leg["_tag"]
|
|
550
|
+
/** The frames it has drawn (#464). */
|
|
551
|
+
frames: number
|
|
552
|
+
/** When it drew its last frame, on {@link frameNow}'s clock; -Infinity before its first (#464). */
|
|
553
|
+
lastAt: number
|
|
554
|
+
/** A Blend leg's last frame's values, and the ones before them (the Snapshot's, before its first) and when (#464). */
|
|
555
|
+
last: Values.Values | undefined
|
|
556
|
+
before: Values.Values | undefined
|
|
557
|
+
beforeAt: number
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** No running legs. */
|
|
561
|
+
const NO_RUNNING: ReadonlyArray<Running> = []
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* An entry's Transition end waiting for its keys to land (#428, #508): no fiber of its own. Its `running`
|
|
565
|
+
* holds the keys only it sets, counts toward its landing and is given up as any leg's; the landing of
|
|
566
|
+
* its keys' last leg makes it due, and it's written at the next flush.
|
|
567
|
+
*/
|
|
568
|
+
interface PendingEnd {
|
|
569
|
+
readonly running: Running
|
|
570
|
+
/** The keys its entry animates, each held by the leg drawing it. */
|
|
571
|
+
readonly animated: ReadonlyArray<string>
|
|
572
|
+
/** Its Transition end, the keys it also animates among them. */
|
|
573
|
+
readonly writes: Values.Values
|
|
574
|
+
/**
|
|
575
|
+
* An end that owns no key, its values all keys its entry animates: the leg drawing each of
|
|
576
|
+
* `animated` as it was made. Nothing gives such an end up, so a key's leg given up or replaced
|
|
577
|
+
* before it lands, the target interrupted, drops it (ADR-0057 Q1). Undefined for one that owns keys,
|
|
578
|
+
* which an interrupt gives up.
|
|
579
|
+
*/
|
|
580
|
+
readonly legs: ReadonlyArray<Running | undefined> | undefined
|
|
581
|
+
state: "waiting" | "due" | "written" | "dropped"
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** An element's waiting Transition ends (#508). */
|
|
585
|
+
interface WaitingEnds {
|
|
586
|
+
/** An entry's end, waiting for its keys, or due already when they've landed. */
|
|
587
|
+
readonly add: (end: PendingEnd) => void
|
|
588
|
+
/** A leg landed: each end whose keys now all have is due, written at the next flush. */
|
|
589
|
+
readonly land: () => void
|
|
590
|
+
/** The end `running` is, dropped, whether it waits or is due: given up, stopped or ended. */
|
|
591
|
+
readonly drop: (running: Running) => void
|
|
592
|
+
/** The element is gone: none is written. */
|
|
593
|
+
readonly dispose: () => void
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* The waiting Transition ends of element `id` (#508): made at its first end, so an element with none,
|
|
598
|
+
* nearly every one, pays nothing for them. `owners` holds the leg drawing each key.
|
|
599
|
+
*/
|
|
600
|
+
const waitingEndsOf = (owners: Owners, snapshot: Snapshot, gate: FlushGate.Shape, id: string, recordFrame: (value: Values.Values) => void): WaitingEnds => {
|
|
601
|
+
const pending: Array<PendingEnd> = []
|
|
602
|
+
let disposed = false
|
|
603
|
+
/** Whether `key`'s leg has landed: it drew its last frame, or none draws it. */
|
|
604
|
+
const keyLanded = (key: string): boolean => ownerLanded(owners.get(key))
|
|
605
|
+
/** `end` off the list. */
|
|
606
|
+
const remove = (end: PendingEnd): void => {
|
|
607
|
+
const at = pending.indexOf(end)
|
|
608
|
+
if (at >= 0) pending.splice(at, 1)
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* An end written, at the flush after its keys landed: the frame its leg's write would have drawn in,
|
|
612
|
+
* and Motion's (`frame.update` once its animations resolve). It counts toward its landing as that leg
|
|
613
|
+
* did, so the landing's settle, told after the next flush, sees it (ADR-0057 Q4).
|
|
614
|
+
*/
|
|
615
|
+
const write = (end: PendingEnd): void => {
|
|
616
|
+
if (Bool.or(disposed, end.state !== "due")) return
|
|
617
|
+
end.state = "written"
|
|
618
|
+
remove(end)
|
|
619
|
+
const running = end.running
|
|
620
|
+
const value = running.values(end.writes)
|
|
621
|
+
snapshot.values = { ...snapshot.values, ...value }
|
|
622
|
+
gate.write(id, value)
|
|
623
|
+
recordFrame(value)
|
|
624
|
+
drew(running, true, snapshot.now())
|
|
625
|
+
running.landing.settled()
|
|
626
|
+
const tell = running.names
|
|
627
|
+
if (Predicate.isNotUndefined(tell)) tell(undefined)
|
|
628
|
+
}
|
|
629
|
+
/** Whether a key-less end's target was interrupted: a leg it waits for given up or replaced before it landed. */
|
|
630
|
+
const interrupted = (end: PendingEnd): boolean => {
|
|
631
|
+
const legs = end.legs
|
|
632
|
+
if (Predicate.isUndefined(legs)) return false
|
|
633
|
+
return Arr.some(end.animated, (key, i) => {
|
|
634
|
+
const leg = legs[i]
|
|
635
|
+
if (Predicate.isUndefined(leg)) return false
|
|
636
|
+
if (leg.done) return false
|
|
637
|
+
return owners.get(key) !== leg
|
|
638
|
+
})
|
|
639
|
+
}
|
|
640
|
+
/** An end due once every key it waits for has landed, written at the next flush; dropped when its target was interrupted. */
|
|
641
|
+
const landOne = (end: PendingEnd): void => {
|
|
642
|
+
if (end.state !== "waiting") return
|
|
643
|
+
if (interrupted(end)) {
|
|
644
|
+
end.state = "dropped"
|
|
645
|
+
return
|
|
646
|
+
}
|
|
647
|
+
if (Bool.not(Arr.every(end.animated, keyLanded))) return
|
|
648
|
+
end.state = "due"
|
|
649
|
+
gate.afterFlush(() => write(end))
|
|
650
|
+
}
|
|
651
|
+
const dropped = (end: PendingEnd): boolean => end.state === "dropped"
|
|
652
|
+
const land = (): void => {
|
|
653
|
+
Arr.forEach(pending, landOne)
|
|
654
|
+
if (Arr.some(pending, dropped)) {
|
|
655
|
+
const kept = Arr.filter(pending, Predicate.not(dropped))
|
|
656
|
+
pending.length = 0
|
|
657
|
+
pending.push(...kept)
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
add: (end) => {
|
|
662
|
+
pending.push(end)
|
|
663
|
+
land()
|
|
664
|
+
},
|
|
665
|
+
land,
|
|
666
|
+
drop: (running) =>
|
|
667
|
+
Option.match(Arr.findFirst(pending, (end) => end.running === running), {
|
|
668
|
+
onNone: constVoid,
|
|
669
|
+
onSome: (end) => {
|
|
670
|
+
end.state = "dropped"
|
|
671
|
+
remove(end)
|
|
672
|
+
},
|
|
673
|
+
}),
|
|
674
|
+
dispose: () => {
|
|
675
|
+
disposed = true
|
|
676
|
+
pending.length = 0
|
|
677
|
+
},
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/** A leg's last frame lands the Transition ends waiting on it (#508); an element with none has nothing to ask. */
|
|
682
|
+
const landEndsOf = (waiting: WaitingEnds | undefined): void => {
|
|
683
|
+
if (Predicate.isNotUndefined(waiting)) waiting.land()
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* The resolution an **Imperative animation**'s legs run under (#433): none of the Override stack's,
|
|
688
|
+
* so a Retarget that takes a key back never carries one as its own Keyframes or names.
|
|
689
|
+
*/
|
|
690
|
+
const IMPERATIVE: Resolution = {
|
|
691
|
+
resolved: {},
|
|
692
|
+
base: {},
|
|
693
|
+
dragging: false,
|
|
694
|
+
variant: undefined,
|
|
695
|
+
active: { inView: false, focused: false, hovered: false, pressed: false, dragged: false, dragging: false },
|
|
696
|
+
transition: undefined,
|
|
697
|
+
layers: [],
|
|
698
|
+
names: NO_GESTURE_NAMES,
|
|
699
|
+
layersOn: NO_GESTURE_STATES,
|
|
700
|
+
ends: undefined,
|
|
701
|
+
keyframes: undefined,
|
|
702
|
+
change: undefined,
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/** No values: what a spine's Retarget keys are before its first. */
|
|
706
|
+
const NO_VALUES: Values.Values = {}
|
|
707
|
+
|
|
708
|
+
/** Whether a key's Keyframes stops hold a Variable value (#55). Built once (#460). */
|
|
709
|
+
const stopsHoldVariable = (stops: ReadonlyArray<number | string>): boolean => Arr.some(stops, Variables.isValue)
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Whether a change's starts, targets or Keyframes stops hold a Variable value (#542): only then can
|
|
713
|
+
* the element's Key rule, its Variable values (#55), take a pair, so only then is a leg's machine
|
|
714
|
+
* built under it. It declines every pair with no `var()` at either end.
|
|
715
|
+
*/
|
|
716
|
+
const holdsVariable = (from: Values.Values, to: Values.Values, stops: StopsByKey | undefined): boolean => {
|
|
717
|
+
// Per Retarget (#460): statements, Effect's calls data-first.
|
|
718
|
+
if (Rec.some(from, Variables.isValue)) return true
|
|
719
|
+
if (Rec.some(to, Variables.isValue)) return true
|
|
720
|
+
if (Predicate.isUndefined(stops)) return false
|
|
721
|
+
return Rec.some(stops, stopsHoldVariable)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/** A Path's turn (#438, ADR-0065): the reserved key the dom transform adds onto `rotate`. */
|
|
725
|
+
const PATH_ROTATION = "pathRotation"
|
|
726
|
+
|
|
727
|
+
/** The turn back at 0, written once no leg draws it (#460). */
|
|
728
|
+
const TURNED_BACK: Values.Values = { [PATH_ROTATION]: 0 }
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Each key's Leg (#325): the one that draws it, or the last one that did, landed; `undefined` once
|
|
732
|
+
* none does. A key's entry is cleared in place, never deleted: a Retarget that gives up and restarts
|
|
733
|
+
* every key would otherwise leave the Map a hole a key each time, and rehash it.
|
|
734
|
+
*/
|
|
735
|
+
type Owners = Map<string, Running | undefined>
|
|
736
|
+
|
|
737
|
+
/** A leg over the keys of `to` (#325), counting toward `landing`: the spine gives it the keys. */
|
|
738
|
+
const runningOf = (to: Values.Values, resolution: Resolution, landing: Landing, kind: Leg["_tag"]): Running => ({
|
|
739
|
+
supersede: Supersede.make(),
|
|
740
|
+
targets: to,
|
|
741
|
+
resolution,
|
|
742
|
+
count: 0,
|
|
743
|
+
values: identity,
|
|
744
|
+
velocities: identity,
|
|
745
|
+
done: false,
|
|
746
|
+
losing: false,
|
|
747
|
+
fiber: undefined,
|
|
748
|
+
landing,
|
|
749
|
+
names: undefined,
|
|
750
|
+
kind,
|
|
751
|
+
frames: 0,
|
|
752
|
+
// Doubles from the start (#464): a field made an integer turns every leg's shape when its first double
|
|
753
|
+
// comes, and a read of each leg made before goes the slow way (the bench's unmount, +6%).
|
|
754
|
+
lastAt: Number.NEGATIVE_INFINITY,
|
|
755
|
+
last: undefined,
|
|
756
|
+
before: undefined,
|
|
757
|
+
beforeAt: Number.NEGATIVE_INFINITY,
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* A Blend leg's velocity for `key` (#464, Motion's `getVelocity`): its last two frames' change over
|
|
762
|
+
* their time apart, at most `MAX_VELOCITY_DELTA`; none for a string, nor for a Snap, a leg that landed
|
|
763
|
+
* on its first frame (a `set`, a `duration: 0`).
|
|
764
|
+
*/
|
|
765
|
+
const blendVelocityOf = (running: Running, key: string): number => {
|
|
766
|
+
if (Bool.and(running.done, running.frames < 2)) return 0
|
|
767
|
+
// Per key, per Retarget: guards, nothing made.
|
|
768
|
+
const lastValues = running.last
|
|
769
|
+
const beforeValues = running.before
|
|
770
|
+
if (Predicate.isUndefined(lastValues)) return 0
|
|
771
|
+
if (Predicate.isUndefined(beforeValues)) return 0
|
|
772
|
+
const last = lastValues[key]
|
|
773
|
+
const before = beforeValues[key]
|
|
774
|
+
if (Bool.not(Predicate.isNumber(last))) return 0
|
|
775
|
+
if (Bool.not(Predicate.isNumber(before))) return 0
|
|
776
|
+
const apart = Math.min(running.lastAt - running.beforeAt, MAX_VELOCITY_DELTA)
|
|
777
|
+
if (apart <= 0) return 0
|
|
778
|
+
return (((last as number) - (before as number)) * 1000) / apart
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/** A leg's frame drawn (#325) at `now`: its last lands the keys it draws. Per frame: plain calls. */
|
|
782
|
+
const drew = (running: Running, done: boolean, now: number): void => {
|
|
783
|
+
if (done) running.done = true
|
|
784
|
+
running.frames++
|
|
785
|
+
running.lastAt = now
|
|
786
|
+
running.landing.report(done)
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* A frame's values for `kept` alone (#325): a leg a later Retarget took keys from draws only the
|
|
791
|
+
* rest. Built once, when it gives them up; each frame copies into the one object it returns.
|
|
792
|
+
*/
|
|
793
|
+
const keepingOnly = <A,>(kept: ReadonlyArray<string>): ((value: Readonly<Record<string, A>>) => Readonly<Record<string, A>>) => {
|
|
794
|
+
const out: Record<string, A> = {}
|
|
795
|
+
let frame: Readonly<Record<string, A>> = out
|
|
796
|
+
const copy = (key: string): void => {
|
|
797
|
+
out[key] = frame[key] as A
|
|
798
|
+
}
|
|
799
|
+
return (value) => {
|
|
800
|
+
frame = value
|
|
801
|
+
Arr.forEach(kept, copy)
|
|
802
|
+
return out
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Where `axis` rests when `target` leaves it out (#574): a leg drawing it though its own Retarget
|
|
808
|
+
* didn't resolve it, a Path's whose target left it out too (#438) or an Imperative animation's (#433,
|
|
809
|
+
* whose legs resolve nothing), was taking it somewhere, and it rests there. `undefined` with none in
|
|
810
|
+
* flight.
|
|
811
|
+
*/
|
|
812
|
+
const restOf = (owners: Owners, target: Values.Values, axis: "x" | "y"): number | string | undefined => {
|
|
813
|
+
if (Object.hasOwn(target, axis)) return undefined
|
|
814
|
+
const owner = owners.get(axis)
|
|
815
|
+
if (Predicate.isUndefined(owner)) return undefined
|
|
816
|
+
if (owner.done) return undefined
|
|
817
|
+
if (Object.hasOwn(owner.resolution.resolved, axis)) return undefined
|
|
818
|
+
return owner.targets[axis]
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Where a Path's axes that `target` leaves out rest (#574), read before their legs give them up: a
|
|
823
|
+
* new Path ends them there, so a toss caught mid-air curves back to its line. `undefined` for none:
|
|
824
|
+
* every element's Retarget asks, nearly always with no Path in flight, so guards, nothing built.
|
|
825
|
+
*/
|
|
826
|
+
const restsOf = (owners: Owners, target: Values.Values): Values.Values | undefined => {
|
|
827
|
+
const x = restOf(owners, target, "x")
|
|
828
|
+
const y = restOf(owners, target, "y")
|
|
829
|
+
if (Bool.and(Predicate.isUndefined(x), Predicate.isUndefined(y))) return undefined
|
|
830
|
+
if (Predicate.isUndefined(x)) return { y: UndefinedOr.getOrThrow(y) }
|
|
831
|
+
if (Predicate.isUndefined(y)) return { x }
|
|
832
|
+
return { x, y }
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/** The groups of the keys a carried leg draws (#297, #325): the first alone when the keys aren't split. */
|
|
836
|
+
const groupsOfLeg = (running: Running, groups: ReadonlyArray<ReadonlySet<string>> | undefined, owners: Owners): ReadonlyArray<number> => {
|
|
837
|
+
if (Predicate.isUndefined(groups)) return [0]
|
|
838
|
+
const keys = Arr.filter(Rec.keys(running.targets), (key) => owners.get(key) === running)
|
|
839
|
+
return Arr.filter(Arr.makeBy(groups.length, identity), (g) => Arr.some(keys, (key) => UndefinedOr.match(groups[g], { onUndefined: constFalse, onDefined: (group) => group.has(key) })))
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/** Each entry's gate shut now (#297's PR 2): a named one whose `when` holds its keys back. */
|
|
843
|
+
const shutOf = (layers: ReadonlyArray<OverrideEntry>, byName: NameOrchestration | undefined): ReadonlyArray<boolean> | undefined => {
|
|
844
|
+
if (Predicate.isUndefined(byName)) return undefined
|
|
845
|
+
return Arr.map(layers, (layer) => {
|
|
846
|
+
const name = layer.name
|
|
847
|
+
if (Predicate.isUndefined(name)) return false
|
|
848
|
+
return Bool.not(byName.mayStart(name))
|
|
849
|
+
})
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/** Whether any entry's gate is shut (#297's PR 2): none without a list. */
|
|
853
|
+
const anyShutOf = (shut: ReadonlyArray<boolean> | undefined): boolean => {
|
|
854
|
+
if (Predicate.isUndefined(shut)) return false
|
|
855
|
+
return Arr.some(shut, identity)
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/** A split Retarget's legs, each key group's (#291, #297), and the group each leg animates. */
|
|
859
|
+
interface SplitLegs {
|
|
860
|
+
readonly legs: ReadonlyArray<Leg>
|
|
861
|
+
readonly tags: ReadonlyArray<number>
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* A split Retarget's legs (#291, #297): each key group on its transition, over the keys that start a
|
|
866
|
+
* leg (`t`, #325). A group with none of them makes no leg: a list's settles at once as Motion's empty
|
|
867
|
+
* variant does, unless keys it drives stay running. A list's returns' Live values follow with its
|
|
868
|
+
* returns' group, last; any other's with the first group.
|
|
869
|
+
*/
|
|
870
|
+
const splitLegsOf = (
|
|
871
|
+
t: Values.Values,
|
|
872
|
+
from: Values.Values,
|
|
873
|
+
groups: ReadonlyArray<ReadonlySet<string>>,
|
|
874
|
+
resolution: Resolution,
|
|
875
|
+
listed: boolean,
|
|
876
|
+
transition: WardicTransition | undefined,
|
|
877
|
+
element: WardicTransition | undefined,
|
|
878
|
+
extraDelay: ((name: string | undefined) => number) | undefined,
|
|
879
|
+
reuse: (transition: WardicTransition) => WardicTransition,
|
|
880
|
+
reduce: boolean,
|
|
881
|
+
skipped: boolean,
|
|
882
|
+
positionalKeys: ReducedMotion.PositionalKeys["Service"],
|
|
883
|
+
defaultTransition: DefaultTransition.DefaultTransition["Service"],
|
|
884
|
+
velocities: Velocities,
|
|
885
|
+
follow: Follows | undefined,
|
|
886
|
+
stops: StopsByKey | undefined,
|
|
887
|
+
measured: MeasuredPairs | undefined,
|
|
888
|
+
current: Values.Values,
|
|
889
|
+
rests: Values.Values | undefined,
|
|
890
|
+
): SplitLegs => {
|
|
891
|
+
let returnsAt = 0
|
|
892
|
+
if (listed) returnsAt = resolution.layers.length
|
|
893
|
+
const legs: Array<Leg> = []
|
|
894
|
+
const tags: Array<number> = []
|
|
895
|
+
Arr.forEach(groups, (_, i) => {
|
|
896
|
+
// A group whose keys all stay starts none (#325), as one that drives no key.
|
|
897
|
+
const picked = pickKeys(t, groups, i)
|
|
898
|
+
if (Rec.isEmptyRecord(picked)) return
|
|
899
|
+
let followed: Follows | undefined
|
|
900
|
+
if (i === returnsAt) followed = follow
|
|
901
|
+
const groupLegs = legsOf(from, picked, groupTransitionOf(resolution, groups, i, transition, element, extraDelay, reuse), reduce, skipped, positionalKeys, defaultTransition, velocities, followed, stops, measured, current, rests)
|
|
902
|
+
Arr.forEach(groupLegs, (built) => {
|
|
903
|
+
legs.push(built)
|
|
904
|
+
tags.push(i)
|
|
905
|
+
})
|
|
906
|
+
})
|
|
907
|
+
return { legs, tags }
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/** What a list's names make of a Retarget (#297): their settles told, and each shut one's legs' gate. */
|
|
911
|
+
interface Listing {
|
|
912
|
+
readonly names: NameSettles | undefined
|
|
913
|
+
readonly legGates: ReadonlyArray<string | undefined> | undefined
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/** What {@link listingOf} reads of a Retarget. */
|
|
917
|
+
interface ListingOf {
|
|
918
|
+
readonly listed: boolean
|
|
919
|
+
readonly told: ToldNames | undefined
|
|
920
|
+
readonly byName: NameOrchestration | undefined
|
|
921
|
+
readonly resolution: Resolution
|
|
922
|
+
readonly groups: ReadonlyArray<ReadonlySet<string>> | undefined
|
|
923
|
+
readonly legGroups: ReadonlyArray<number> | undefined
|
|
924
|
+
readonly legCount: number
|
|
925
|
+
/** For each running leg it carries (#325), the groups of the keys it draws. */
|
|
926
|
+
readonly carriedGroups: ReadonlyArray<ReadonlyArray<number>>
|
|
927
|
+
readonly shut: ReadonlyArray<boolean> | undefined
|
|
928
|
+
/** Write a shut group's keys where they start. */
|
|
929
|
+
readonly hold: (group: number) => void
|
|
930
|
+
readonly afterFlush: (fn: () => void) => void
|
|
931
|
+
readonly config: WardConfig
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* A list's names at a Retarget (#297): told settled as the legs of the keys each drives settle, to
|
|
936
|
+
* the binding's callbacks (Solid 1's, PR 1) and to its orchestration (PR 2), told moving now; and each
|
|
937
|
+
* shut name's legs waiting on its gate (PR 2), its keys held where they start, so another name's first
|
|
938
|
+
* commit, which hands the element's style back (ADR-0034), doesn't drop them to their CSS value.
|
|
939
|
+
*/
|
|
940
|
+
const listingOf = (of: ListingOf): Listing => {
|
|
941
|
+
const { listed, told, byName, resolution, groups, legGroups, legCount, carriedGroups, shut, config } = of
|
|
942
|
+
const listNames = Bool.match(listed, { onTrue: () => resolution.variant as ReadonlyArray<string>, onFalse: () => NO_NAMES })
|
|
943
|
+
const orchestrated = Bool.and(listed, Predicate.isNotUndefined(byName))
|
|
944
|
+
const toldNames = UndefinedOr.match(told, { onUndefined: () => NO_NAMES, onDefined: (list) => list.names })
|
|
945
|
+
let names: NameSettles | undefined
|
|
946
|
+
if (Bool.or(orchestrated, Predicate.isNotUndefined(told))) {
|
|
947
|
+
if (orchestrated) (byName as NameOrchestration).moving(listNames)
|
|
948
|
+
names = nameSettlesOf(Arr.dedupe([...listNames, ...toldNames]), resolution, groups, legGroups, legCount, carriedGroups, (settled) =>
|
|
949
|
+
of.afterFlush(() => {
|
|
950
|
+
UndefinedOr.map(told, (list) => {
|
|
951
|
+
const mine = Arr.filter(settled, (name) => Arr.contains(list.names, name))
|
|
952
|
+
if (mine.length > 0) list.tell(mine, config)
|
|
953
|
+
})
|
|
954
|
+
if (orchestrated) (byName as NameOrchestration).settled(Arr.filter(settled, (name) => Arr.contains(listNames, name)))
|
|
955
|
+
}))
|
|
956
|
+
}
|
|
957
|
+
let legGates: ReadonlyArray<string | undefined> | undefined
|
|
958
|
+
if (Bool.and(anyShutOf(shut), Predicate.isNotUndefined(legGroups))) {
|
|
959
|
+
const closed = shut as ReadonlyArray<boolean>
|
|
960
|
+
legGates = Arr.map(legGroups as ReadonlyArray<number>, (g) => gateNameOf(resolution.layers, closed, g))
|
|
961
|
+
Arr.forEach(closed, (isShut, g) => {
|
|
962
|
+
if (isShut) of.hold(g)
|
|
963
|
+
})
|
|
964
|
+
}
|
|
965
|
+
return { names, legGates }
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/** The name a leg of key group `g` waits for, if its entry's gate is shut (#297's PR 2): a list's returns wait for none. */
|
|
969
|
+
const gateNameOf = (layers: ReadonlyArray<OverrideEntry>, shut: ReadonlyArray<boolean>, g: number): string | undefined => {
|
|
970
|
+
if (shut[g] !== true) return undefined
|
|
971
|
+
return UndefinedOr.match(layers[g], { onUndefined: constUndefined, onDefined: (layer) => layer.name })
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/** A gate's wait (#297's PR 2): done once `open()` says so, read each tick. */
|
|
975
|
+
const gateWaitOf = (open: () => boolean): Machine.Machine<Frame.Frame<undefined>> =>
|
|
976
|
+
Machine.make({ settles: true, span: GATE_SPAN, start: () => () => ({ value: undefined, done: open() }) })
|
|
977
|
+
|
|
978
|
+
const GATE_SPAN = { name: "wardic.spine.whenGate", attributes: {} }
|
|
979
|
+
|
|
980
|
+
/** Nothing drawn while a gate is waited on. */
|
|
981
|
+
const NO_SINK = (): undefined => undefined
|
|
982
|
+
|
|
983
|
+
/** Whether a key's leg has landed (#428): its owner's done, or it has none. A guard: asked at each leg's landing while a Transition end waits (#508). */
|
|
984
|
+
const ownerLanded = (owner: Running | undefined): boolean => {
|
|
985
|
+
if (Predicate.isUndefined(owner)) return true
|
|
986
|
+
return owner.done
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** Whether `runtime` keeps a Debug log: read once a runtime, so a log no level keeps forks nothing (#618). */
|
|
990
|
+
// Module state (#460, Q7): each runtime's answer, held weakly by the runtime.
|
|
991
|
+
const logsDebug = new WeakMap<WardicRuntime, boolean>()
|
|
992
|
+
const logsDebugOn = (runtime: WardicRuntime): boolean => {
|
|
993
|
+
const known = logsDebug.get(runtime)
|
|
994
|
+
if (Predicate.isNotUndefined(known)) return known
|
|
995
|
+
const on = runtime.runSync(Report.debugKept)
|
|
996
|
+
logsDebug.set(runtime, on)
|
|
997
|
+
return on
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* No key taken by an Imperative animation (#433): every spine's until its first, shared, so an element
|
|
1002
|
+
* none animates holds no map of its own (#460). Never written: a spine sets keys in a map of its own,
|
|
1003
|
+
* and deletes only from one that has some.
|
|
1004
|
+
*/
|
|
1005
|
+
// Module state (#460, Q7): a constant: an empty map every spine shares until its first, which none writes.
|
|
1006
|
+
const NONE_TAKEN: Map<string, number | string | undefined> = new Map()
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* A leg that waits for its name's gate first (#297's PR 2), as Motion runs a variant's `when` per
|
|
1010
|
+
* name: started once the gate opens, on the same token, so a later Retarget ends the wait as it ends
|
|
1011
|
+
* a leg. Without a name, the leg as it is.
|
|
1012
|
+
*/
|
|
1013
|
+
const gatedBy = <R,>(
|
|
1014
|
+
name: string | undefined,
|
|
1015
|
+
byName: NameOrchestration | undefined,
|
|
1016
|
+
options: Machine.RunOptions,
|
|
1017
|
+
program: Effect.Effect<unknown, never, R>,
|
|
1018
|
+
): Effect.Effect<unknown, never, R | FrameLoop.FrameLoop> => {
|
|
1019
|
+
if (Bool.or(Predicate.isUndefined(name), Predicate.isUndefined(byName))) return program
|
|
1020
|
+
const orchestration = byName as NameOrchestration
|
|
1021
|
+
const gated = name as string
|
|
1022
|
+
return gateWaitOf(() => orchestration.mayStart(gated)).pipe(
|
|
1023
|
+
Machine.run(NO_SINK, options),
|
|
1024
|
+
Effect.flatMap((end) => Bool.match(Supersede.isSuperseded(end), { onTrue: () => Effect.succeed(end as unknown), onFalse: () => program })),
|
|
1025
|
+
)
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/** Whether an entry is on with its own transition: hoisted, as it's asked per Retarget. */
|
|
1029
|
+
const hasOwnTransition = (layer: OverrideEntry): boolean => Bool.and(layer.active, Predicate.isNotUndefined(layer.transition))
|
|
1030
|
+
|
|
1031
|
+
/** A target as an active entry, with the name it resolved. */
|
|
1032
|
+
const layerOf = (target: Target, name: string | undefined): OverrideEntry => ({
|
|
1033
|
+
active: true,
|
|
1034
|
+
values: target.values,
|
|
1035
|
+
transition: target.transition,
|
|
1036
|
+
name,
|
|
1037
|
+
ends: endsOfTarget(target),
|
|
1038
|
+
keyframes: target.keyframes,
|
|
1039
|
+
})
|
|
1040
|
+
|
|
1041
|
+
/** A list's target as an active entry (#297). */
|
|
1042
|
+
const namedLayerOf = (target: NamedTarget): OverrideEntry => layerOf(target, target.name)
|
|
1043
|
+
|
|
1044
|
+
/** A target's Transition end as an entry's (#428): none shared, so the common case allocates nothing. Per entry, per Retarget (#460): a guard. */
|
|
1045
|
+
const endsOfTarget = (target: Target): Values.Values => {
|
|
1046
|
+
if (Predicate.isUndefined(target.transitionEnd)) return NO_VALUES
|
|
1047
|
+
return target.transitionEnd
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/** Whether an entry is on with a Transition end (#428). */
|
|
1051
|
+
const hasEnds = (layer: OverrideEntry): boolean => Bool.and(layer.active, layer.ends !== NO_VALUES)
|
|
1052
|
+
|
|
1053
|
+
/** The highest active entry that sets `key`, in its values or its Transition end (#428). */
|
|
1054
|
+
const driverOf = (layers: ReadonlyArray<OverrideEntry>, key: string): number =>
|
|
1055
|
+
Option.getOrElse(Arr.findLastIndex(layers, (layer) => Bool.and(layer.active, Bool.or(Object.hasOwn(layer.values, key), Object.hasOwn(layer.ends, key)))), () => -1)
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* The Override stack resolved (#428): `Override.resolve` as before when no active entry has a
|
|
1059
|
+
* Transition end; else over each entry's values and Transition end, its values winning a key both set, so
|
|
1060
|
+
* a key only an end sets is the entry's (Motion's `{ ...target, ...transitionEnd }`), and the ends
|
|
1061
|
+
* each entry drives.
|
|
1062
|
+
*/
|
|
1063
|
+
const resolvedOf = (layers: ReadonlyArray<OverrideEntry>): Resolved => {
|
|
1064
|
+
if (!Arr.some(layers, hasEnds)) {
|
|
1065
|
+
const resolved = Override.resolve(layers)
|
|
1066
|
+
return { resolved, ends: undefined, keyframes: keyframesNowOf(layers, resolved) }
|
|
1067
|
+
}
|
|
1068
|
+
const merged = Arr.map(layers, (layer): OverrideEntry => Bool.match(hasEnds(layer), { onTrue: () => ({ ...layer, values: { ...layer.ends, ...layer.values } }), onFalse: () => layer }))
|
|
1069
|
+
const resolved = Override.resolve(merged)
|
|
1070
|
+
const endOnly = new Map<string, number>()
|
|
1071
|
+
const byLayer: Array<Record<string, number | string> | undefined> = Arr.makeBy(layers.length, constUndefined)
|
|
1072
|
+
Arr.forEach(Rec.keys(resolved), (key) => {
|
|
1073
|
+
const at = driverOf(layers, key)
|
|
1074
|
+
const layer = layers[at]
|
|
1075
|
+
if (Bool.or(Predicate.isUndefined(layer), Bool.not(Object.hasOwn((layer as OverrideEntry).ends, key)))) return
|
|
1076
|
+
const driving = layer as OverrideEntry
|
|
1077
|
+
if (Bool.not(Object.hasOwn(driving.values, key))) endOnly.set(key, at)
|
|
1078
|
+
const into = UndefinedOr.match(byLayer[at], { onUndefined: (): Record<string, number | string> => ({}), onDefined: identity })
|
|
1079
|
+
into[key] = driving.ends[key] as number | string
|
|
1080
|
+
byLayer[at] = into
|
|
1081
|
+
})
|
|
1082
|
+
return { resolved, ends: { endOnly, byLayer }, keyframes: keyframesNowOf(layers, resolved) }
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/** The Override stack resolved: each key's value, its entries' Transition ends (#428) and Keyframes (#429). */
|
|
1086
|
+
interface Resolved {
|
|
1087
|
+
readonly resolved: Values.Values
|
|
1088
|
+
readonly ends: Ends | undefined
|
|
1089
|
+
readonly keyframes: KeyframesNow | undefined
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/** Whether an entry is on with Keyframes (#429). Per entry, per Retarget (#452): guards, nothing built. */
|
|
1093
|
+
const hasKeyframes = (layer: OverrideEntry): boolean => {
|
|
1094
|
+
if (Predicate.isUndefined(layer.keyframes)) return false
|
|
1095
|
+
return layer.active
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Each resolved key's Keyframes (#429): the stops of the entry that drives it, when that entry gives
|
|
1100
|
+
* it Keyframes. `undefined`, with nothing made, when no active entry has any: every element's common
|
|
1101
|
+
* case.
|
|
1102
|
+
*/
|
|
1103
|
+
const keyframesNowOf = (layers: ReadonlyArray<OverrideEntry>, resolved: Values.Values): KeyframesNow | undefined => {
|
|
1104
|
+
if (!Arr.some(layers, hasKeyframes)) return undefined
|
|
1105
|
+
const now: Record<string, KeyframesAt> = {}
|
|
1106
|
+
Arr.forEach(Rec.keys(resolved), (key) => {
|
|
1107
|
+
const at = driverOf(layers, key)
|
|
1108
|
+
const layer = layers[at]
|
|
1109
|
+
if (Predicate.isUndefined(layer)) return
|
|
1110
|
+
const stops = UndefinedOr.map((layer as OverrideEntry).keyframes, (byKey) => byKey[key])
|
|
1111
|
+
if (Bool.or(Predicate.isUndefined(stops), Bool.not(Object.hasOwn((layer as OverrideEntry).values, key)))) return
|
|
1112
|
+
now[key] = { stops: stops as Style.Stops, layer: at, name: (layer as OverrideEntry).name }
|
|
1113
|
+
})
|
|
1114
|
+
return Bool.match(Rec.isEmptyRecord(now), { onTrue: constUndefined, onFalse: (): KeyframesNow => now })
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
/** Two values maps with the same keys, each holding the same value. Built once, at module load. */
|
|
1119
|
+
const sameValues: Equivalence.Equivalence<Values.Values> = Rec.makeEquivalence(Equivalence.strictEqual<number | string>())
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Whether two resolutions are the same target (#209, #470): the same values for the same keys, in
|
|
1123
|
+
* the base entry and resolved, the same drag, the same variant name, Keyframes and Transition ends,
|
|
1124
|
+
* and the same gesture layers on, with the same names. Motion's rule: a config that resolves to the
|
|
1125
|
+
* same values doesn't animate (a `transition` change applies from the next change), and a new
|
|
1126
|
+
* variant name does, even to the same values, as a gesture turning on or off does. Both bindings'
|
|
1127
|
+
* target memos compare with it, so a `style`, `transition` or callback change doesn't Retarget.
|
|
1128
|
+
*/
|
|
1129
|
+
export const sameResolution = (a: Resolution, b: Resolution): boolean =>
|
|
1130
|
+
a.dragging === b.dragging && sameLabels(a.variant, b.variant) && a.change === b.change && sameValues(a.resolved, b.resolved) && sameValues(a.base, b.base) &&
|
|
1131
|
+
sameKeyframesNow(a.keyframes, b.keyframes) && sameLayersOn(a.layersOn, b.layersOn) && sameNamesOn(a.names, b.names) && sameEnds(a.ends, b.ends)
|
|
1132
|
+
|
|
1133
|
+
/** Two resolutions' gesture layers on that set values (#470): none on both, the common case, at once. */
|
|
1134
|
+
const sameLayersOn = (a: GestureStates, b: GestureStates): boolean => {
|
|
1135
|
+
if (a === b) return true
|
|
1136
|
+
return a.inView === b.inView && a.focused === b.focused && a.hovered === b.hovered && a.pressed === b.pressed && a.dragged === b.dragged
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/** Two resolutions' active gesture names (#470): a named entry turning on or off, or a new name. None on both, the common case, at once. */
|
|
1140
|
+
const sameNamesOn = (a: GestureNames, b: GestureNames): boolean => {
|
|
1141
|
+
if (a === b) return true
|
|
1142
|
+
return sameGestureNames(a, b)
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/**
|
|
1146
|
+
* Two resolutions' Transition ends the same (#470), entry by entry: a key an entry both animates and
|
|
1147
|
+
* ends resolves to its value, so its end is compared here. `undefined` on both, the common case, at once.
|
|
1148
|
+
*/
|
|
1149
|
+
const sameEnds = (a: Ends | undefined, b: Ends | undefined): boolean => {
|
|
1150
|
+
if (a === b) return true
|
|
1151
|
+
if (Predicate.isUndefined(a)) return false
|
|
1152
|
+
if (Predicate.isUndefined(b)) return false
|
|
1153
|
+
const y = b.byLayer
|
|
1154
|
+
return Bool.and(a.byLayer.length === y.length, Arr.every(a.byLayer, (ends, i) => sameEndsAt(ends, y[i])))
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/** The value `key`'s Transition end sets it to in `ends` (#470): its driving entry's, the one entry that ends it. */
|
|
1158
|
+
const endFor = (ends: Ends | undefined, key: string): number | string | undefined => {
|
|
1159
|
+
if (Predicate.isUndefined(ends)) return undefined
|
|
1160
|
+
return Option.match(Arr.findFirst(ends.byLayer, (values) => endsKey(values, key)), { onNone: constUndefined, onSome: (values) => (values as Values.Values)[key] })
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/** Whether an entry's Transition end sets `key`. */
|
|
1164
|
+
const endsKey = (values: Values.Values | undefined, key: string): boolean => {
|
|
1165
|
+
if (Predicate.isUndefined(values)) return false
|
|
1166
|
+
return Object.hasOwn(values, key)
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Whether `key`'s Transition end changed since the last Retarget (#470): Motion compares a target's
|
|
1171
|
+
* `transitionEnd` with its values, so a new end animates its key again. None on either, the common
|
|
1172
|
+
* case, at once.
|
|
1173
|
+
*/
|
|
1174
|
+
const endMoved = (last: Resolution | undefined, now: Resolution, key: string): boolean => {
|
|
1175
|
+
if (Predicate.isUndefined(last)) return false
|
|
1176
|
+
if (last.ends === now.ends) return false
|
|
1177
|
+
return endFor(last.ends, key) !== endFor(now.ends, key)
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/** An entry's Transition end the same in two resolutions (#470): none on both, or the same values. */
|
|
1181
|
+
const sameEndsAt = (a: Values.Values | undefined, b: Values.Values | undefined): boolean => {
|
|
1182
|
+
if (a === b) return true
|
|
1183
|
+
if (Predicate.isUndefined(a)) return false
|
|
1184
|
+
if (Predicate.isUndefined(b)) return false
|
|
1185
|
+
return sameValues(a, b)
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/** Two lists of stops equal stop by stop (#429, Motion's `shallowCompare`). */
|
|
1189
|
+
const sameStops = (a: Style.Stops, b: Style.Stops): boolean => Bool.or(a === b, Bool.and(a.length === b.length, Arr.every(a, (stop, i) => stop === b[i])))
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Whether a start that can't take its target's shape stays as it is (#430): it makes a Measured pair
|
|
1193
|
+
* with it, so its leg measures both ends rather than starting at the target.
|
|
1194
|
+
*/
|
|
1195
|
+
const keepsStart = (key: string, start: number | string | undefined, target: number | string): boolean => {
|
|
1196
|
+
if (Predicate.isUndefined(start)) return false
|
|
1197
|
+
return MeasuredPair.needs(key, start, target)
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/** The keys that may be Measured pairs (#430): those found, and each Keyframes key, its start now its first stop. */
|
|
1201
|
+
const measuringOf = (found: ReadonlyArray<string> | undefined, keyframes: KeyframesNow | undefined): ReadonlyArray<string> =>
|
|
1202
|
+
Arr.appendAll(UndefinedOr.match(found, { onUndefined: (): ReadonlyArray<string> => [], onDefined: identity }), UndefinedOr.match(keyframes, { onUndefined: (): ReadonlyArray<string> => [], onDefined: Rec.keys }))
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* A Retarget's Measured pairs (#430): each of `keys` whose start and target are one, asked of the
|
|
1206
|
+
* end-of-task batch with the values the element draws now, but a key with Keyframes of more than two
|
|
1207
|
+
* stops (`many`, #460). `undefined` with none.
|
|
1208
|
+
*/
|
|
1209
|
+
const measuredOf = (element: HTMLElement, keys: ReadonlyArray<string>, from: Values.Values, to: Values.Values, values: Values.Values, many: StopsByKey | undefined): MeasuredPairs | undefined => {
|
|
1210
|
+
const pairs: Record<string, { readonly from: number | string; readonly to: number | string }> = {}
|
|
1211
|
+
Arr.forEach(keys, (key) => {
|
|
1212
|
+
if (hasManyStops(many, key)) return
|
|
1213
|
+
const start = from[key]
|
|
1214
|
+
const target = to[key]
|
|
1215
|
+
if (Bool.or(Predicate.isUndefined(start), Predicate.isUndefined(target))) return
|
|
1216
|
+
if (MeasuredPair.needs(key, start as number | string, target as number | string)) pairs[key] = { from: start as number | string, to: target as number | string }
|
|
1217
|
+
})
|
|
1218
|
+
if (Rec.isEmptyRecord(pairs)) return undefined
|
|
1219
|
+
return MeasuredPair.measure(element, pairs, values)
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/** A key's Keyframes in `keyframes` (#429): per key, per Retarget, so a guard, nothing made. */
|
|
1223
|
+
const keyframesAtOf = (keyframes: KeyframesNow | undefined, key: string): KeyframesAt | undefined => {
|
|
1224
|
+
if (Predicate.isUndefined(keyframes)) return undefined
|
|
1225
|
+
return keyframes[key]
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/** Whether a key's Keyframes are the same (#429, {@link KeyframesAt}): its stops, its entry and that entry's name. */
|
|
1229
|
+
export const sameKeyframesAt = (a: KeyframesAt | undefined, b: KeyframesAt | undefined): boolean => {
|
|
1230
|
+
// Per key, per Retarget (#452): guards, the common case (neither) first.
|
|
1231
|
+
if (a === b) return true
|
|
1232
|
+
if (Predicate.isUndefined(a)) return false
|
|
1233
|
+
if (Predicate.isUndefined(b)) return false
|
|
1234
|
+
return Bool.and(Bool.and(a.layer === b.layer, a.name === b.name), sameStops(a.stops, b.stops))
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/** Whether two resolutions' Keyframes are the same, key by key (#429): `undefined` on both, the common case, at once. */
|
|
1238
|
+
const sameKeyframesNow = (a: KeyframesNow | undefined, b: KeyframesNow | undefined): boolean => {
|
|
1239
|
+
if (Bool.or(Predicate.isUndefined(a), Predicate.isUndefined(b))) return a === b
|
|
1240
|
+
const x = a as KeyframesNow
|
|
1241
|
+
const y = b as KeyframesNow
|
|
1242
|
+
const keys = Rec.keys(x)
|
|
1243
|
+
return Bool.and(keys.length === Rec.keys(y).length, Arr.every(keys, (key) => sameKeyframesAt(x[key], y[key])))
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/** Whether `resolution` is `last` with only its `dragging` handed back (#340). */
|
|
1247
|
+
const handsBackOnly = (resolution: Resolution, last: Resolution | undefined): boolean =>
|
|
1248
|
+
UndefinedOr.match(last, { onUndefined: () => false, onDefined: (l) => sameResolution({ ...resolution, dragging: l.dragging }, l) })
|
|
1249
|
+
|
|
1250
|
+
/** Motion's `definition` of an animation (#257): a variant name, or its target values. */
|
|
1251
|
+
export type AnimationDefinition = WardicVariantLabels | Values.Values
|
|
1252
|
+
|
|
1253
|
+
/**
|
|
1254
|
+
* A Retarget's `definition`, as Motion's `onAnimationStart` / `onAnimationComplete` hand it over
|
|
1255
|
+
* (#257): the variant name its base entry resolved, when that name differs from the last started
|
|
1256
|
+
* Retarget's (`previous`), else the target's values. Motion's definition is per animation type: a
|
|
1257
|
+
* new `animate` variant animates as its name, and a gesture turning on or off as values (its own,
|
|
1258
|
+
* or the keys it leaves falling back), so a variant's name is only told when the variant changed.
|
|
1259
|
+
*/
|
|
1260
|
+
export const definitionOf = (resolution: Resolution, previous: Resolution | undefined): AnimationDefinition => {
|
|
1261
|
+
const name = resolution.variant
|
|
1262
|
+
// A list is told as a list (#297): a change name by name, as Motion's `checkVariantsDidChange`.
|
|
1263
|
+
if (Bool.and(Predicate.isNotUndefined(name), Bool.not(sameLabels(name, UndefinedOr.map(previous, (p) => p.variant))))) return name as WardicVariantLabels
|
|
1264
|
+
// A gesture's name that just came on (#291), the highest first, as Motion's names its type's.
|
|
1265
|
+
const before = UndefinedOr.match(previous, { onUndefined: () => NO_GESTURE_NAMES, onDefined: (p) => p.names })
|
|
1266
|
+
const started = Arr.findFirst(GESTURES_DOWN, (layer) =>
|
|
1267
|
+
Bool.and(Predicate.isNotUndefined(resolution.names[layer]), Bool.not(sameLabels(resolution.names[layer], before[layer]))))
|
|
1268
|
+
return Option.match(started, { onNone: (): AnimationDefinition => resolution.resolved, onSome: (layer) => resolution.names[layer] as WardicVariantLabels })
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/** The gesture layers, highest first. */
|
|
1272
|
+
const GESTURES_DOWN: ReadonlyArray<GestureLayer> = Arr.reverse(GESTURE_LAYERS)
|
|
1273
|
+
|
|
1274
|
+
/**
|
|
1275
|
+
* What a Retarget tells its binding (#257): that it started, and that its last leg settled. Each is
|
|
1276
|
+
* handed the Retarget's resolution, the variant name the last started Retarget resolved before it
|
|
1277
|
+
* (for {@link definitionOf}), and the config it read.
|
|
1278
|
+
*/
|
|
1279
|
+
export interface AnimationHooks {
|
|
1280
|
+
/** A Retarget started its legs (the `when` gate let it); with no leg, it settles at once. */
|
|
1281
|
+
readonly started: (resolution: Resolution, previous: Resolution | undefined, config: WardConfig) => void
|
|
1282
|
+
/** That Retarget's last leg settled: not for one the next Retarget superseded, nor one a leg's failure cut off. */
|
|
1283
|
+
readonly settled: (resolution: Resolution, previous: Resolution | undefined, config: WardConfig) => void
|
|
1284
|
+
/**
|
|
1285
|
+
* A leg of that Retarget failed (#257 item 4): it can't land. Told once a Retarget, at once (a
|
|
1286
|
+
* leg that fails before it writes leaves no flush to wait for), and never before `started`; the
|
|
1287
|
+
* failure is logged and reported apart (ADR-0030).
|
|
1288
|
+
*/
|
|
1289
|
+
readonly failed?: ((resolution: Resolution, error: unknown) => void) | undefined
|
|
1290
|
+
/**
|
|
1291
|
+
* Names of a list that settled (#297): a binding told each name's end on its own (Solid 1's
|
|
1292
|
+
* callbacks, Motion's `animateVariant` a name) has it, called as the legs of the keys each drives
|
|
1293
|
+
* settle; one with none hears the list's end through `settled` alone.
|
|
1294
|
+
*/
|
|
1295
|
+
readonly namesSettled?: ((names: ReadonlyArray<string>, config: WardConfig) => void) | undefined
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/** A list a Retarget tells settled name by name (#297): its names, and what it tells. */
|
|
1299
|
+
interface ToldNames {
|
|
1300
|
+
readonly names: ReadonlyArray<string>
|
|
1301
|
+
readonly tell: (names: ReadonlyArray<string>, config: WardConfig) => void
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/** Whether a resolution resolved a list (#297): its base, or an active gesture's. Cheap for the common case. */
|
|
1305
|
+
const hasList = (resolution: Resolution): boolean => {
|
|
1306
|
+
if (Arr.isArray(resolution.variant)) return true
|
|
1307
|
+
if (resolution.names === NO_GESTURE_NAMES) return false
|
|
1308
|
+
return Arr.some(GESTURE_LAYERS, (layer) => Arr.isArray(resolution.names[layer]))
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/** The list a Retarget tells settled name by name (#297): when its binding is told so, and its definition is a list. */
|
|
1312
|
+
const namesToldOf = (animation: AnimationHooks | undefined, resolution: Resolution, previous: Resolution | undefined): ToldNames | undefined => {
|
|
1313
|
+
if (Predicate.isUndefined(animation)) return undefined
|
|
1314
|
+
const tell = animation.namesSettled
|
|
1315
|
+
if (Predicate.isUndefined(tell)) return undefined
|
|
1316
|
+
if (Bool.not(hasList(resolution))) return undefined
|
|
1317
|
+
const definition = definitionOf(resolution, previous)
|
|
1318
|
+
if (Bool.not(Arr.isArray(definition))) return undefined
|
|
1319
|
+
return { names: definition as ReadonlyArray<string>, tell }
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* A list's names told settled one by one (#297): what each leg's end is told to, legs started in the
|
|
1324
|
+
* Retarget's order, and each running leg it carries (#325), by its place in `carriedGroups`.
|
|
1325
|
+
*/
|
|
1326
|
+
interface NameSettles {
|
|
1327
|
+
readonly leg: () => (end: unknown) => void
|
|
1328
|
+
readonly carried: (i: number) => (end: unknown) => void
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/**
|
|
1332
|
+
* A list's names, each told settled with the last leg of the keys it drives (#297): its entry's group,
|
|
1333
|
+
* or every leg's when the keys aren't split, the running legs the Retarget carries among them (#325).
|
|
1334
|
+
* A name that drives none is told at once.
|
|
1335
|
+
*/
|
|
1336
|
+
const nameSettlesOf = (
|
|
1337
|
+
names: ReadonlyArray<string>,
|
|
1338
|
+
resolution: Resolution,
|
|
1339
|
+
groups: ReadonlyArray<ReadonlySet<string>> | undefined,
|
|
1340
|
+
legGroups: ReadonlyArray<number> | undefined,
|
|
1341
|
+
legCount: number,
|
|
1342
|
+
carriedGroups: ReadonlyArray<ReadonlyArray<number>>,
|
|
1343
|
+
tell: (names: ReadonlyArray<string>) => void,
|
|
1344
|
+
): NameSettles => {
|
|
1345
|
+
const groupOf = (name: string): number => {
|
|
1346
|
+
if (Predicate.isUndefined(groups)) return 0
|
|
1347
|
+
return Option.getOrElse(Arr.findFirstIndex(resolution.layers, (layer) => layer.name === name), () => 0)
|
|
1348
|
+
}
|
|
1349
|
+
// The legs still running in each group.
|
|
1350
|
+
const left = new Map<number, number>()
|
|
1351
|
+
const add = (g: number): void => {
|
|
1352
|
+
left.set(g, Option.getOrElse(Option.fromUndefinedOr(left.get(g)), () => 0) + 1)
|
|
1353
|
+
}
|
|
1354
|
+
UndefinedOr.match(legGroups, {
|
|
1355
|
+
onUndefined: () => {
|
|
1356
|
+
if (legCount > 0) left.set(0, legCount)
|
|
1357
|
+
},
|
|
1358
|
+
onDefined: (tags) => Arr.forEach(tags, add),
|
|
1359
|
+
})
|
|
1360
|
+
Arr.forEach(carriedGroups, (gs) => Arr.forEach(gs, add))
|
|
1361
|
+
const running = (name: string): boolean => left.has(groupOf(name))
|
|
1362
|
+
let pending = Arr.filter(names, running)
|
|
1363
|
+
const idle = Arr.filter(names, (name) => Bool.not(running(name)))
|
|
1364
|
+
if (idle.length > 0) tell(idle)
|
|
1365
|
+
// A leg in group `g` ended: the names of that group are told once its last has.
|
|
1366
|
+
const endIn = (g: number): void => {
|
|
1367
|
+
const n = Option.getOrElse(Option.fromUndefinedOr(left.get(g)), () => 1) - 1
|
|
1368
|
+
if (n > 0) {
|
|
1369
|
+
left.set(g, n)
|
|
1370
|
+
return
|
|
1371
|
+
}
|
|
1372
|
+
left.delete(g)
|
|
1373
|
+
const done = Arr.filter(pending, (name) => groupOf(name) === g)
|
|
1374
|
+
pending = Arr.filter(pending, (name) => groupOf(name) !== g)
|
|
1375
|
+
if (done.length > 0) tell(done)
|
|
1376
|
+
}
|
|
1377
|
+
let next = 0
|
|
1378
|
+
return {
|
|
1379
|
+
leg: () => {
|
|
1380
|
+
const g = UndefinedOr.match(legGroups, { onUndefined: () => 0, onDefined: (tags) => Option.getOrElse(Option.fromUndefinedOr(tags[next]), () => 0) })
|
|
1381
|
+
next++
|
|
1382
|
+
return (end) => {
|
|
1383
|
+
// A Superseded leg (#200) settles nothing: the next Retarget's tells.
|
|
1384
|
+
if (Supersede.isSuperseded(end)) return
|
|
1385
|
+
endIn(g)
|
|
1386
|
+
}
|
|
1387
|
+
},
|
|
1388
|
+
carried: (i) => (end) => {
|
|
1389
|
+
if (Supersede.isSuperseded(end)) return
|
|
1390
|
+
Arr.forEach(UndefinedOr.match(carriedGroups[i], { onUndefined: (): ReadonlyArray<number> => [], onDefined: identity }), endIn)
|
|
1391
|
+
},
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* Solid 1's animation callbacks (#257, Motion's props): each Retarget's start and settle told to the
|
|
1397
|
+
* config's `onAnimationStart` / `onAnimationComplete`, with its `definition`, as the Retarget read
|
|
1398
|
+
* the config. A callback that throws is reported, and the element animates on. The Solid 2 binding's
|
|
1399
|
+
* config has neither: it hands the same facts over as async values.
|
|
1400
|
+
*/
|
|
1401
|
+
export const animationCallbacks = (runtime: WardicRuntime, id: string): AnimationHooks => {
|
|
1402
|
+
// Per Retarget, per element: the callback looked up first, and nothing made without one.
|
|
1403
|
+
const tell = (name: "onAnimationStart" | "onAnimationComplete", callback: (definition: WardicAnimationDefinition) => void, definition: WardicAnimationDefinition): void => {
|
|
1404
|
+
try {
|
|
1405
|
+
callback(definition)
|
|
1406
|
+
} catch (defect) {
|
|
1407
|
+
runtime.runFork(Report.failure(`wardic.spine: ${name} failed`, Cause.die(defect)).pipe(Effect.annotateLogs({ id })))
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
return {
|
|
1411
|
+
// A list starts as each of its names, in order (#297, Motion's `animateVisualElement` a name).
|
|
1412
|
+
started: (resolution, previous, config) => {
|
|
1413
|
+
const callback = config.onAnimationStart
|
|
1414
|
+
if (Predicate.isUndefined(callback)) return
|
|
1415
|
+
const definition = definitionOf(resolution, previous)
|
|
1416
|
+
if (Arr.isArray(definition)) {
|
|
1417
|
+
Arr.forEach(definition as ReadonlyArray<string>, (name) => tell("onAnimationStart", callback, name))
|
|
1418
|
+
return
|
|
1419
|
+
}
|
|
1420
|
+
tell("onAnimationStart", callback, definition)
|
|
1421
|
+
},
|
|
1422
|
+
// A list's names were each told as they settled (`namesSettled`).
|
|
1423
|
+
settled: (resolution, previous, config) => {
|
|
1424
|
+
const callback = config.onAnimationComplete
|
|
1425
|
+
if (Predicate.isUndefined(callback)) return
|
|
1426
|
+
const definition = definitionOf(resolution, previous)
|
|
1427
|
+
if (Arr.isArray(definition)) return
|
|
1428
|
+
tell("onAnimationComplete", callback, definition)
|
|
1429
|
+
},
|
|
1430
|
+
namesSettled: (names, config) => {
|
|
1431
|
+
const callback = config.onAnimationComplete
|
|
1432
|
+
if (Predicate.isUndefined(callback)) return
|
|
1433
|
+
Arr.forEach(names, (name) => tell("onAnimationComplete", callback, name))
|
|
1434
|
+
},
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
/** Which entries of the Override stack are active now, as the binding reads them. */
|
|
1439
|
+
export interface Active {
|
|
1440
|
+
/** In view (#273): `whileInView` applies. */
|
|
1441
|
+
readonly inView: boolean
|
|
1442
|
+
/** Focused, as the Environment shows it (`:focus-visible`, #273): `whileFocus` applies. */
|
|
1443
|
+
readonly focused: boolean
|
|
1444
|
+
readonly hovered: boolean
|
|
1445
|
+
readonly pressed: boolean
|
|
1446
|
+
/** A drag, or a **Keyboard reorder**'s lift (ADR-0037): `whileDrag` applies. */
|
|
1447
|
+
readonly dragged: boolean
|
|
1448
|
+
/** A drag: the translate keys are the drag's (ADR-0004). */
|
|
1449
|
+
readonly dragging: boolean
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/** The variant name an element animates to: its own `animate`'s, else the one its provider hands down. */
|
|
1453
|
+
const variantOf = (animate: WardConfig["animate"], parent: WardicVariantContextValue | undefined): WardicVariantLabels | undefined => {
|
|
1454
|
+
if (Predicate.isNotUndefined(animate)) return nameOf(animate)
|
|
1455
|
+
if (Predicate.isUndefined(parent)) return undefined
|
|
1456
|
+
return parent.variant()
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/** The base target (ADR-0007): its own `animate`'s, else the inherited name's, else the seeded `initial`. */
|
|
1460
|
+
const baseTargetOf = (
|
|
1461
|
+
own: Entry | undefined,
|
|
1462
|
+
variant: WardicVariantLabels | undefined,
|
|
1463
|
+
variants: WardicVariants | undefined,
|
|
1464
|
+
custom: unknown,
|
|
1465
|
+
state: VariantState,
|
|
1466
|
+
initialLink: Values.Values,
|
|
1467
|
+
): Target => {
|
|
1468
|
+
if (Predicate.isNotUndefined(own)) return resolveTarget(own, variants, custom, state)
|
|
1469
|
+
if (Predicate.isNotUndefined(variant)) return resolveTarget(variant, variants, custom, state)
|
|
1470
|
+
return { values: initialLink, transition: undefined, transitionEnd: undefined, keyframes: undefined }
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* The Override stack resolved (ascending priority, Motion's `variantPriorityOrder`): base (animate, or
|
|
1475
|
+
* initial when no animate) -> whileInView -> whileFocus -> whileHover -> whileTap -> whileDrag (#273
|
|
1476
|
+
* added the first two). `Override.resolve` picks the last active entry per key. Pure:
|
|
1477
|
+
* a binding computes it in a memo, so a config change, an inherited variant name's
|
|
1478
|
+
* (`parent.variant()`, read only when there's no `animate`) or an entry's drives a Retarget.
|
|
1479
|
+
*/
|
|
1480
|
+
export const resolutionOf = (
|
|
1481
|
+
c: WardConfig,
|
|
1482
|
+
parent: WardicVariantContextValue | undefined,
|
|
1483
|
+
initialLink: Values.Values,
|
|
1484
|
+
active: Active,
|
|
1485
|
+
state: VariantState,
|
|
1486
|
+
own?: ControlsChange | undefined,
|
|
1487
|
+
): Resolution => {
|
|
1488
|
+
// The Controls change in effect (#433): its own controls', else its provider's, read here so a new one Retargets.
|
|
1489
|
+
const change = changeIn(own, parent)
|
|
1490
|
+
const variants = c.variants
|
|
1491
|
+
// Base target resolution priority (ADR-0007):
|
|
1492
|
+
// (1) local `animate` (a Values map or a variant name) — overrides any
|
|
1493
|
+
// inherited variant entirely;
|
|
1494
|
+
// (2) else an INHERITED variant name from the parent context, resolved
|
|
1495
|
+
// against THIS child's OWN `variants` (a child's "open" != its
|
|
1496
|
+
// sibling's "open"); read parent.variant() here so a parent flip
|
|
1497
|
+
// recomputes the memo and retargets;
|
|
1498
|
+
// (3) else the SSR-seeded `initial`.
|
|
1499
|
+
// Per Retarget, per element (#460, compose once): statements.
|
|
1500
|
+
const variant = variantOf(c.animate, parent)
|
|
1501
|
+
// A list of names (#297, Motion's `VariantLabels`): a base entry a name, in order, so the last name
|
|
1502
|
+
// that sets a key drives it. A single name or a values map takes the path below, as before.
|
|
1503
|
+
if (Arr.isArray(c.animate) || (Predicate.isUndefined(c.animate) && Arr.isArray(variant))) {
|
|
1504
|
+
const targets = resolveTargets(variant as ReadonlyArray<string>, variants, c.custom, state)
|
|
1505
|
+
const merged = mergedOf(targets)
|
|
1506
|
+
const listLayers: Array<OverrideEntry> = Arr.map(targets, namedLayerOf)
|
|
1507
|
+
return withGestures(c, parent, listLayers, merged.values, variant, active, merged.transition, state, change)
|
|
1508
|
+
}
|
|
1509
|
+
// A dynamic variant resolves with this element's own `custom` (#277), as Motion's each child's.
|
|
1510
|
+
// Animation controls never reach here: its binding reads them once, each change its entry (#433).
|
|
1511
|
+
const target = baseTargetOf(c.animate as Entry | undefined, variant, variants, c.custom, state, initialLink)
|
|
1512
|
+
const base = target.values
|
|
1513
|
+
const baseLayer: OverrideEntry = layerOf(target, variant as string | undefined)
|
|
1514
|
+
// A Variant child's gestures are on while its provider's are (#291, Motion's `setActive`), each on
|
|
1515
|
+
// its own `while*`, else the label the provider hands down. With no provider, its own: nothing made.
|
|
1516
|
+
// The common case: nothing handed down and no gesture on. The base entry alone, and nothing else
|
|
1517
|
+
// made or called, at every element's mount and Retarget.
|
|
1518
|
+
if (handDownFrom(parent) === NO_HAND_DOWN && !anyOn(active)) {
|
|
1519
|
+
const alone: ReadonlyArray<OverrideEntry> = [baseLayer]
|
|
1520
|
+
const { resolved, ends, keyframes } = resolvedOf(alone)
|
|
1521
|
+
return { resolved, base, dragging: active.dragging, variant, active, transition: target.transition, layers: alone, names: NO_GESTURE_NAMES, layersOn: NO_GESTURE_STATES, ends, keyframes, change }
|
|
1522
|
+
}
|
|
1523
|
+
return withGestures(c, parent, [baseLayer], base, variant, active, target.transition, state, change)
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
/** The **Controls change** in effect for an element (#433): its own, else its provider's; per Retarget, a guard. */
|
|
1527
|
+
const changeIn = (own: ControlsChange | undefined, parent: WardicVariantContextValue | undefined): ControlsChange | undefined => {
|
|
1528
|
+
if (Predicate.isNotUndefined(own)) return own
|
|
1529
|
+
if (Predicate.isUndefined(parent)) return undefined
|
|
1530
|
+
const inherited = parent.change
|
|
1531
|
+
if (Predicate.isUndefined(inherited)) return undefined
|
|
1532
|
+
return inherited()
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/**
|
|
1536
|
+
* `resolution` run on its Controls change's override (#433, Motion's `transitionOverride`): its base
|
|
1537
|
+
* entries' own transitions beaten by it, its gestures' kept. The first Retarget for the change only.
|
|
1538
|
+
*/
|
|
1539
|
+
const overriddenOf = (resolution: Resolution, override: WardicTransition): Resolution => {
|
|
1540
|
+
const variant = resolution.variant
|
|
1541
|
+
let base = 1
|
|
1542
|
+
if (Arr.isArray(variant)) base = variant.length
|
|
1543
|
+
return {
|
|
1544
|
+
...resolution,
|
|
1545
|
+
transition: override as WardicVariantTransition,
|
|
1546
|
+
layers: Arr.map(resolution.layers, (layer, i) => Bool.match(i < base, { onTrue: (): OverrideEntry => ({ ...layer, transition: override as WardicVariantTransition }), onFalse: () => layer })),
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* A resolution from its base entries (one, or one a name of a list, #297) and its gesture layers on
|
|
1552
|
+
* top (#291), each gesture's an entry a name too: the Override stack resolved, and the names its
|
|
1553
|
+
* active gestures resolved.
|
|
1554
|
+
*/
|
|
1555
|
+
const withGestures = (
|
|
1556
|
+
c: WardConfig,
|
|
1557
|
+
parent: WardicVariantContextValue | undefined,
|
|
1558
|
+
layers: Array<OverrideEntry>,
|
|
1559
|
+
base: Values.Values,
|
|
1560
|
+
variant: WardicVariantLabels | undefined,
|
|
1561
|
+
active: Active,
|
|
1562
|
+
transition: WardicVariantTransition | undefined,
|
|
1563
|
+
state: VariantState,
|
|
1564
|
+
change: ControlsChange | undefined,
|
|
1565
|
+
): Resolution => {
|
|
1566
|
+
const variants = c.variants
|
|
1567
|
+
const above = handDownFrom(parent)
|
|
1568
|
+
if (Bool.and(above === NO_HAND_DOWN, Bool.not(anyOn(active)))) {
|
|
1569
|
+
const { resolved, ends, keyframes } = resolvedOf(layers)
|
|
1570
|
+
return { resolved, base, dragging: active.dragging, variant, active, transition, layers, names: NO_GESTURE_NAMES, layersOn: NO_GESTURE_STATES, ends, keyframes, change }
|
|
1571
|
+
}
|
|
1572
|
+
const on = gesturesOnOf(active, above)
|
|
1573
|
+
const labels = above.names
|
|
1574
|
+
// In Motion's priority, lowest first: a literal's fields are evaluated in order.
|
|
1575
|
+
const layersOn: GestureStates = {
|
|
1576
|
+
inView: gestureLayersInto(layers, c.whileInView, on.inView, labels.inView, variants, c.custom, state),
|
|
1577
|
+
focused: gestureLayersInto(layers, c.whileFocus, on.focused, labels.focused, variants, c.custom, state),
|
|
1578
|
+
hovered: gestureLayersInto(layers, c.whileHover, on.hovered, labels.hovered, variants, c.custom, state),
|
|
1579
|
+
pressed: gestureLayersInto(layers, c.whileTap, on.pressed, labels.pressed, variants, c.custom, state),
|
|
1580
|
+
// whileDrag sits ABOVE tap (highest priority) — driven by the spring,
|
|
1581
|
+
// NOT stripped, so e.g. `scale: 1.1` animates while held and returns
|
|
1582
|
+
// after release.
|
|
1583
|
+
dragged: gestureLayersInto(layers, c.whileDrag, on.dragged, labels.dragged, variants, c.custom, state),
|
|
1584
|
+
}
|
|
1585
|
+
const { resolved, ends, keyframes } = resolvedOf(layers)
|
|
1586
|
+
const names = activeNamesFor(c, on, labels)
|
|
1587
|
+
return { resolved, base, dragging: active.dragging, variant, active, transition, layers, names, layersOn, ends, keyframes, change }
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/** The values an element starts from, read once at setup (ADR-0003, #134). */
|
|
1591
|
+
export interface Start {
|
|
1592
|
+
/** Its Starting style's values: its `initial` (own or inherited, #50), or its `animate` when it starts there. */
|
|
1593
|
+
readonly initialValues: Values.Values
|
|
1594
|
+
/** Its `initial` link in the Base-value chain and its fallback base entry: none when it started at `animate`. */
|
|
1595
|
+
readonly initialLink: Values.Values
|
|
1596
|
+
/** When it started at `animate`, those values, which come after `style` (Motion's `baseTarget`, ADR-0022). */
|
|
1597
|
+
readonly startedLink: Values.Values
|
|
1598
|
+
/**
|
|
1599
|
+
* Whether its mount tells nothing (#448, Motion's `animation-state.ts`): it starts at its `animate`
|
|
1600
|
+
* (`initial={false}`, or an `<AnimatePresence initial={false}>`'s first render), or its `initial` is
|
|
1601
|
+
* the same name, list or values map as its `animate`, each its own or inherited. Its first Retarget,
|
|
1602
|
+
* landing with nothing to animate, is told to no one.
|
|
1603
|
+
*/
|
|
1604
|
+
readonly quiet: boolean
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/**
|
|
1608
|
+
* An element's {@link Start}, from its config read once and the context it inherits from. With
|
|
1609
|
+
* `initial={false}` (its own, inherited, or an `<AnimatePresence initial={false}>`'s first render)
|
|
1610
|
+
* it starts at its `animate`, and nothing animates at mount (#134).
|
|
1611
|
+
*/
|
|
1612
|
+
export const startOf = (
|
|
1613
|
+
config: WardConfig,
|
|
1614
|
+
parent: WardicVariantContextValue | undefined,
|
|
1615
|
+
presenceBlocks: boolean,
|
|
1616
|
+
untrack: Reactivity["untrack"],
|
|
1617
|
+
): Start => {
|
|
1618
|
+
const startedAtAnimate = startsAtAnimate(config, parent, presenceBlocks)
|
|
1619
|
+
const initialValues = startingValuesOf(config, parent, presenceBlocks, untrack)
|
|
1620
|
+
return {
|
|
1621
|
+
quiet: startsQuiet(config, parent, presenceBlocks, untrack),
|
|
1622
|
+
initialValues,
|
|
1623
|
+
initialLink: Bool.match(startedAtAnimate, { onTrue: () => ({}), onFalse: () => initialValues }),
|
|
1624
|
+
startedLink: Bool.match(startedAtAnimate, { onTrue: () => initialValues, onFalse: () => ({}) }),
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* The Snapshot: each key's last emitted value, a number or a string, and the numeric velocities.
|
|
1630
|
+
* A Retarget starts each key from where it is, with its velocity carried across the seam (ADR-0005,
|
|
1631
|
+
* ADR-0024). Plain, not reactive; a drag writes its `x`/`y` into it too (ADR-0004).
|
|
1632
|
+
*/
|
|
1633
|
+
export interface Snapshot {
|
|
1634
|
+
values: Values.Values
|
|
1635
|
+
/**
|
|
1636
|
+
* Each numeric key's velocity (#464, Motion's): a spring's own, a drag's pointer's, or, for a key a
|
|
1637
|
+
* leg let go, the one its last two frames gave it. Read through the spine, which takes a running
|
|
1638
|
+
* leg's and reads one drawn more than `MAX_VELOCITY_DELTA` ago as 0.
|
|
1639
|
+
*/
|
|
1640
|
+
velocities: Record<string, number>
|
|
1641
|
+
/**
|
|
1642
|
+
* When each key's velocity here was last drawn, on {@link frameNow}'s clock (#464): the last frame of
|
|
1643
|
+
* the leg that let it go, or a drag's or a presence seed's setting.
|
|
1644
|
+
*/
|
|
1645
|
+
drawnAt: Record<string, number>
|
|
1646
|
+
/** The leg drawing each key, its spine's (#464): a running leg's velocity is read from it. None before its spine runs. */
|
|
1647
|
+
owners: ReadonlyMap<string, Running | undefined> | undefined
|
|
1648
|
+
/** The time now, its provider's Clock's once its spine runs (#460, Q7), {@link frameNow} before. */
|
|
1649
|
+
now: () => number
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
/** A Snapshot seeded from the Starting values, velocities 0, so frame 1 flows out of the seed (no snap). */
|
|
1653
|
+
export const snapshotOf = (start: Start): Snapshot => emptySnapshotOf({ ...start.initialValues })
|
|
1654
|
+
|
|
1655
|
+
/** A Snapshot of `values`, nothing moving: read as it is until a spine reads it. */
|
|
1656
|
+
export const emptySnapshotOf = (values: Values.Values): Snapshot => ({ values, velocities: {}, drawnAt: {}, owners: undefined, now: frameNow })
|
|
1657
|
+
|
|
1658
|
+
// --- Velocities (#464) ---------------------------------------------------------------------------
|
|
1659
|
+
|
|
1660
|
+
/** A leg's velocity for `key`: a Blend leg's from its frames, a Snap's none, a spring's or a follow's as it wrote it. */
|
|
1661
|
+
const legVelocityOf = (snapshot: Snapshot, running: Running, key: string): number => {
|
|
1662
|
+
if (running.kind === "Blend") return blendVelocityOf(running, key)
|
|
1663
|
+
if (running.kind === "Snap") return 0
|
|
1664
|
+
return velocityOf(snapshot, key)
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
/**
|
|
1668
|
+
* `key`'s velocity at `now` (#464, Motion's `getVelocity`): the leg drawing it, once it has drawn a
|
|
1669
|
+
* frame, else what the Snapshot holds for it (the last leg to let it go left it, a drag or a seed set
|
|
1670
|
+
* it), whichever drew it last; 0 once nothing has moved it for `MAX_VELOCITY_DELTA`. Read only for a
|
|
1671
|
+
* key a spring or a follow starts, or one a leg lets go: guards, nothing made.
|
|
1672
|
+
*/
|
|
1673
|
+
const velocityAt = (snapshot: Snapshot, owner: Running | undefined, key: string, now: number): number => {
|
|
1674
|
+
const at = snapshot.drawnAt[key]
|
|
1675
|
+
// Guards, not `Bool.and`, which reads both sides: a leg that hasn't drawn yet stands for nothing.
|
|
1676
|
+
if (Predicate.isNotUndefined(owner)) {
|
|
1677
|
+
if (owner.frames > 0) {
|
|
1678
|
+
if (Predicate.isUndefined(at) || owner.lastAt >= (at as number)) {
|
|
1679
|
+
if (now - owner.lastAt > MAX_VELOCITY_DELTA) return 0
|
|
1680
|
+
return legVelocityOf(snapshot, owner, key)
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
if (Predicate.isUndefined(at)) return 0
|
|
1685
|
+
if (now - at > MAX_VELOCITY_DELTA) return 0
|
|
1686
|
+
return velocityOf(snapshot, key)
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
/**
|
|
1690
|
+
* `running` lets `key` go, having drawn it (#464): the velocity it gives the key now (0 once it's drawn
|
|
1691
|
+
* nothing for 30 ms) and its last frame's time stay on the Snapshot, as numbers. Keeping the leg itself
|
|
1692
|
+
* kept its Retarget's closures alive until the unmount, which paid for them. A string carries none.
|
|
1693
|
+
*/
|
|
1694
|
+
const letGo = (snapshot: Snapshot, key: string, running: Running): void => {
|
|
1695
|
+
if (running.frames === 0) return
|
|
1696
|
+
if (!Predicate.isNumber(snapshot.values[key])) return
|
|
1697
|
+
snapshot.velocities[key] = velocityAt(snapshot, running, key, snapshot.now())
|
|
1698
|
+
snapshot.drawnAt[key] = running.lastAt
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
/** Every numeric key's velocity now (#464): a presence's exit and takeover read it (#286). */
|
|
1702
|
+
export const velocitiesNowOf = (snapshot: Snapshot): Readonly<Record<string, number>> => {
|
|
1703
|
+
const now = snapshot.now()
|
|
1704
|
+
const owners = snapshot.owners
|
|
1705
|
+
return Rec.map(Rec.filter(snapshot.values, Predicate.isNumber), (_, key) =>
|
|
1706
|
+
velocityAt(snapshot, UndefinedOr.match(owners, { onUndefined: constUndefined, onDefined: (map) => map.get(key) }), key, now))
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
/**
|
|
1710
|
+
* The transition a Retarget runs on (#66): the element's last one when the new one has the same
|
|
1711
|
+
* fields, so core's decode cache hits when the object is rebuilt with the same values (a staggered
|
|
1712
|
+
* child's, ADR-0007, or one Solid rebuilds as it reads a signal). One per element.
|
|
1713
|
+
*/
|
|
1714
|
+
export const makeReuse = (): ((transition: WardicTransition) => WardicTransition) => {
|
|
1715
|
+
let lastTransition: WardicTransition | undefined
|
|
1716
|
+
// Per Retarget (#460, compose once): statements.
|
|
1717
|
+
return (transition) => {
|
|
1718
|
+
if (Predicate.isNotUndefined(lastTransition) && sameFields(lastTransition, transition)) return lastTransition
|
|
1719
|
+
lastTransition = transition
|
|
1720
|
+
return transition
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
/**
|
|
1725
|
+
* A mounted element's Base-value links (ADR-0022): a key with no value starts from its `style`
|
|
1726
|
+
* value, else its Own value. The Own value is read from the element once per key, at first need
|
|
1727
|
+
* (D3): after mount, so never on a server (ADR-0021). Each link is coerced to the shape of the
|
|
1728
|
+
* key's target (`like`). `styleNow` reads the user's style untracked; a Live value in it is read now.
|
|
1729
|
+
*/
|
|
1730
|
+
export const startLinksOf = (element: HTMLElement, styleNow: (() => object | string | undefined) | undefined, live: LiveStyle | undefined) => {
|
|
1731
|
+
const ownValue = OwnValues.of(element)
|
|
1732
|
+
// A `style` value read now: a Live value's current value (#130), anything else as it is.
|
|
1733
|
+
const readStyle = (raw: unknown): unknown => {
|
|
1734
|
+
if (Predicate.isUndefined(live)) return raw
|
|
1735
|
+
return live.read(raw)
|
|
1736
|
+
}
|
|
1737
|
+
const linksFor = (like: number | string) => ({
|
|
1738
|
+
style: (key: string) =>
|
|
1739
|
+
Option.flatMap(Option.fromUndefinedOr(styleNow), (now) =>
|
|
1740
|
+
Option.flatMap(OwnValues.styleValueOf(now(), key), (raw) => OwnValues.valueLike(readStyle(raw), like)),
|
|
1741
|
+
),
|
|
1742
|
+
own: (key: string) => Option.flatMap(ownValue(key), (raw) => OwnValues.valueLike(raw, like)),
|
|
1743
|
+
})
|
|
1744
|
+
const startLinkOf = (key: string, like: number | string) => BaseValue.start(key, linksFor(like))
|
|
1745
|
+
return { ownValue, linksFor, startLinkOf }
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
/** {@link startLinksOf}'s links. */
|
|
1749
|
+
export type StartLinks = ReturnType<typeof startLinksOf>
|
|
1750
|
+
|
|
1751
|
+
/**
|
|
1752
|
+
* An element's presence record (ADR-0007 exit): seeded with its Starting values, so an exit that
|
|
1753
|
+
* fires before any frame still has a `from`, and its `exit` ref, which the ward keeps current. An exit key with no value starts
|
|
1754
|
+
* from its Base value's start links (ADR-0022), read from the retained node at the exit.
|
|
1755
|
+
*/
|
|
1756
|
+
export const presenceRecordOf = (
|
|
1757
|
+
element: HTMLElement,
|
|
1758
|
+
start: Start,
|
|
1759
|
+
exitOf: (custom: unknown, inherited: WardicVariantLabels | undefined) => ExitTarget,
|
|
1760
|
+
links: StartLinks,
|
|
1761
|
+
tree: PresenceTree,
|
|
1762
|
+
snapshot: Snapshot,
|
|
1763
|
+
reducedMotion: () => boolean,
|
|
1764
|
+
skipAnimations: () => boolean,
|
|
1765
|
+
): PresenceRecord => ({
|
|
1766
|
+
element,
|
|
1767
|
+
snapshot: { current: { ...start.initialValues } },
|
|
1768
|
+
exitOf,
|
|
1769
|
+
tree,
|
|
1770
|
+
startOf: (key, like) => Option.getOrUndefined(Option.map(links.startLinkOf(key, like), (s) => s.value)),
|
|
1771
|
+
// Its spine's, read when a child back under its key takes over from it (#286): its running legs' too (#464).
|
|
1772
|
+
velocities: () => velocitiesNowOf(snapshot),
|
|
1773
|
+
// Its own, under its nearest WardicConfig (#434): its exit honours it, not the presence's.
|
|
1774
|
+
reducedMotion,
|
|
1775
|
+
// Its nearest WardicConfig's (#468): an exit is skipped as its element's animations are.
|
|
1776
|
+
skipAnimations,
|
|
1777
|
+
})
|
|
1778
|
+
|
|
1779
|
+
/**
|
|
1780
|
+
* Merge an emitted frame's value-map into a presence record's snapshot (nothing without one). It
|
|
1781
|
+
* mirrors what the gate accumulates, so an exit departs from the element's actual resting value-map.
|
|
1782
|
+
*/
|
|
1783
|
+
export const recordFrameOf = (presence: PresenceRecord | undefined): ((value: Values.Values) => void) => (value) => {
|
|
1784
|
+
if (Predicate.isNotUndefined(presence)) Object.assign(presence.snapshot.current, value)
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
/**
|
|
1788
|
+
* Turn the element's will-change value on (#443, Motion's `addValueToWillChange`) when its `style`
|
|
1789
|
+
* gives it one and one of `legs` animates a transform key or one a browser composites. A key already
|
|
1790
|
+
* at its target starts no leg (#448), and a layout animation runs none, so neither does. Per start:
|
|
1791
|
+
* a lookup in its routed keys, none for a style that routes nothing.
|
|
1792
|
+
*/
|
|
1793
|
+
const turnWillChangeOn = (live: LiveStyle | undefined, legs: ReadonlyArray<Leg>): void => {
|
|
1794
|
+
if (Predicate.isUndefined(live)) return
|
|
1795
|
+
const turnOn = live.willChange()
|
|
1796
|
+
if (Predicate.isUndefined(turnOn)) return
|
|
1797
|
+
if (Arr.some(legs, (leg) => Arr.some(Rec.keys(leg.to), turnsWillChangeOn))) turnOn()
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
/**
|
|
1801
|
+
* A binding's `setDragging`, turning the element's will-change value on as a drag starts (#443,
|
|
1802
|
+
* Motion's): `"transform"`, as its drag draws `x` and `y`.
|
|
1803
|
+
*/
|
|
1804
|
+
export const draggingWithWillChange = (live: LiveStyle | undefined, setDragging: (on: boolean) => void): ((on: boolean) => void) =>
|
|
1805
|
+
UndefinedOr.match(live, {
|
|
1806
|
+
onUndefined: () => setDragging,
|
|
1807
|
+
onDefined: (style) => (on: boolean) => {
|
|
1808
|
+
if (on) UndefinedOr.map(style.willChange(), (turnOn) => turnOn())
|
|
1809
|
+
setDragging(on)
|
|
1810
|
+
},
|
|
1811
|
+
})
|
|
1812
|
+
|
|
1813
|
+
/** What {@link makeElementSpine} reads. */
|
|
1814
|
+
export interface ElementSpineOptions {
|
|
1815
|
+
/** The provider runtime the legs fork on. */
|
|
1816
|
+
readonly runtime: WardicRuntime
|
|
1817
|
+
/** The provider-owned flush gate (ADR-0006). */
|
|
1818
|
+
readonly gate: FlushGate.Shape
|
|
1819
|
+
/** The element's gate id. */
|
|
1820
|
+
readonly id: string
|
|
1821
|
+
/** The mounted element. */
|
|
1822
|
+
readonly element: HTMLElement
|
|
1823
|
+
/** Its tag, for a Handoff's check (SVG's routing): `undefined` when it isn't known. */
|
|
1824
|
+
readonly tag: string | undefined
|
|
1825
|
+
/** Its config now, read where the binding doesn't track. */
|
|
1826
|
+
readonly config: () => WardConfig
|
|
1827
|
+
/** The variant context it inherits names and its default transition from (`variant-tree.ts`). */
|
|
1828
|
+
readonly parent: WardicVariantContextValue | undefined
|
|
1829
|
+
/** Whether a Retarget may start now (ADR-0007 `when`); omitted, always. */
|
|
1830
|
+
readonly canStart: (() => boolean) | undefined
|
|
1831
|
+
/** Its stagger delay in seconds, read at each Retarget (ADR-0007); omitted, none. */
|
|
1832
|
+
readonly extraDelay: ((name: string | undefined) => number) | undefined
|
|
1833
|
+
/** Told every frame whether it has settled (ADR-0007 orchestration). */
|
|
1834
|
+
readonly onFrame: ((done: boolean) => void) | undefined
|
|
1835
|
+
/** Whether reduced motion applies (ADR-0029), read at each Retarget. */
|
|
1836
|
+
readonly reducedMotion: () => boolean
|
|
1837
|
+
/**
|
|
1838
|
+
* Whether its animations are skipped (#468, its nearest `WardicConfig`'s `skipAnimations`), read at
|
|
1839
|
+
* each Retarget and each Imperative animation: every key lands at its end on the next tick. Omitted, never.
|
|
1840
|
+
*/
|
|
1841
|
+
readonly skipAnimations?: (() => boolean) | undefined
|
|
1842
|
+
/** Which keys reduced motion Snaps (ADR-0036). */
|
|
1843
|
+
readonly positionalKeys: ReducedMotion.PositionalKeys["Service"]
|
|
1844
|
+
/** What a key with no transition of its own runs on (ADR-0051). */
|
|
1845
|
+
readonly defaultTransition: DefaultTransition.DefaultTransition["Service"]
|
|
1846
|
+
readonly start: Start
|
|
1847
|
+
readonly snapshot: Snapshot
|
|
1848
|
+
readonly reuse: (transition: WardicTransition) => WardicTransition
|
|
1849
|
+
readonly links: StartLinks
|
|
1850
|
+
/** The user's style now, read untracked: a key's `style` link (ADR-0022). */
|
|
1851
|
+
readonly styleNow: (() => object | string | undefined) | undefined
|
|
1852
|
+
/** Its routed `style` keys (#130, ADR-0043). */
|
|
1853
|
+
readonly live: LiveStyle | undefined
|
|
1854
|
+
/** Its presence record, under an `<AnimatePresence>`. */
|
|
1855
|
+
readonly presence: PresenceRecord | undefined
|
|
1856
|
+
/**
|
|
1857
|
+
* Whether a projection node owns its translate keys (ADR-0016): its drag drives its layout node. A
|
|
1858
|
+
* getter while its track waits for `drag` (#551, #582), so it's read where it's used.
|
|
1859
|
+
*/
|
|
1860
|
+
readonly projected: boolean
|
|
1861
|
+
/** Its drag axis, `undefined` without a drag (ADR-0004). */
|
|
1862
|
+
readonly dragAxis: boolean | "x" | "y" | undefined
|
|
1863
|
+
/** Its drag track, whose release a Retarget may take `x`/`y` from (#340); omitted, the drag holds them while `dragging`. */
|
|
1864
|
+
readonly drag?: DragTrack | undefined
|
|
1865
|
+
/** Its Appear animation, while hydrating (#48, ADR-0044 decision 6). */
|
|
1866
|
+
readonly handoff: Handoff | undefined
|
|
1867
|
+
/** Told each Retarget's start and settle (#257); omitted, nothing is told. */
|
|
1868
|
+
readonly animation?: AnimationHooks | undefined
|
|
1869
|
+
/**
|
|
1870
|
+
* A list's names orchestrated each on their own (#297's PR 2): a name's keys start once its gate
|
|
1871
|
+
* opens, and its starts and settles are told. Omitted for an element that joins nothing and
|
|
1872
|
+
* provides nothing.
|
|
1873
|
+
*/
|
|
1874
|
+
readonly byName?: NameOrchestration | undefined
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
/** What the follower reads of the element's Override stack and drag, untracked. */
|
|
1878
|
+
export interface FollowNow {
|
|
1879
|
+
readonly resolved: () => Values.Values
|
|
1880
|
+
readonly dragging: () => boolean
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
/** What an **Imperative animation** tells of its legs on one element (#433). */
|
|
1884
|
+
export interface ImperativeHooks {
|
|
1885
|
+
/** Every leg it started on the element landed, told after the gate's flush of their last frames. */
|
|
1886
|
+
readonly settled: () => void
|
|
1887
|
+
/** A leg failed, reported already (ADR-0030). */
|
|
1888
|
+
readonly failed: (cause: Cause.Cause<unknown>) => void
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
/** An **Imperative animation** on one element (#433): its legs, built at the call, run through its Playhead. */
|
|
1892
|
+
export interface ImperativeTrack {
|
|
1893
|
+
/** Run every leg from the Playhead's time: those running end first, so a seek starts them again there. */
|
|
1894
|
+
readonly run: () => void
|
|
1895
|
+
/** End every leg where it is: its keys stay there, the animation's. */
|
|
1896
|
+
readonly stop: () => void
|
|
1897
|
+
/** End every leg, and put its keys back where it started them. */
|
|
1898
|
+
readonly cancel: () => void
|
|
1899
|
+
/** How long its longest leg runs, in seconds (`Machine.durationOf`, Motion's `duration`). */
|
|
1900
|
+
readonly duration: () => number
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
/** A mounted element's spine. */
|
|
1904
|
+
export interface ElementSpine {
|
|
1905
|
+
/** A Retarget toward `resolution`: called by the binding on each change of its target, untracked. */
|
|
1906
|
+
readonly retarget: (resolution: Resolution) => void
|
|
1907
|
+
/** Follow the element's Live values in `style` (#130), from its first routed key on. */
|
|
1908
|
+
readonly follow: (now: FollowNow) => void
|
|
1909
|
+
/** The element's teardown: following stops, and the legs end. */
|
|
1910
|
+
readonly dispose: () => void
|
|
1911
|
+
/**
|
|
1912
|
+
* Start where a leaver is drawn (#286, ADR-0048), before the first Retarget: each key's value and
|
|
1913
|
+
* velocity, drawn now. A key no entry sets returns to its Base value from there, as one a Retarget
|
|
1914
|
+
* drove does.
|
|
1915
|
+
*/
|
|
1916
|
+
readonly seed: (from: PresenceSeed) => void
|
|
1917
|
+
/**
|
|
1918
|
+
* An **Imperative animation** of the element (#433): it takes `target`'s keys from whatever runs
|
|
1919
|
+
* them, each noted with the target the Override stack has for it now, so a Retarget leaves it where
|
|
1920
|
+
* the animation leaves it until that target changes (Motion's latest-wins). Its legs start where
|
|
1921
|
+
* the keys are drawn, with their velocity, on `transition` (a key with none of its own on its
|
|
1922
|
+
* Default transition), reduced motion as the provider says, and run through `head` once
|
|
1923
|
+
* {@link ImperativeTrack.run} is called.
|
|
1924
|
+
*/
|
|
1925
|
+
readonly animate: (target: Target, transition: WardicTransition | undefined, head: Playback.Playhead, hooks: ImperativeHooks) => ImperativeTrack
|
|
1926
|
+
/** End every running leg where it is (#433, **Animation controls**' `stop`): its keys stay there, until the next Retarget. */
|
|
1927
|
+
readonly stop: () => void
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
/**
|
|
1931
|
+
* A mounted element's spine: its Retarget, the returns to its Base values, the follower of its
|
|
1932
|
+
* Live values, and its legs' lifetime. Every leg is a fiber of its provider's runtime, and joins
|
|
1933
|
+
* its Retarget's token: a Retarget ends the token before it starts its own legs, and {@link
|
|
1934
|
+
* ElementSpine.dispose} ends every leg's (#542). Both end the legs in the calling task: each leg's
|
|
1935
|
+
* run leaves the loop at once, and its fiber ends Superseded, inline (ADR-0027, decision 5,
|
|
1936
|
+
* *Amendment (#200)*). A leg the token didn't reach is interrupted (`endLeg`). The runtime's own
|
|
1937
|
+
* fiber Scope interrupts what's left when the provider ends.
|
|
1938
|
+
*/
|
|
1939
|
+
export const makeElementSpine = (options: ElementSpineOptions): ElementSpine => {
|
|
1940
|
+
const { animation, byName, canStart, config, defaultTransition, element, extraDelay, gate, id, links, live, onFrame, parent, positionalKeys, presence, runtime, snapshot, start, styleNow, tag } = options
|
|
1941
|
+
const { initialLink, initialValues, startedLink } = start
|
|
1942
|
+
const { linksFor, ownValue, startLinkOf } = links
|
|
1943
|
+
let handoff = options.handoff
|
|
1944
|
+
// Its mount tells nothing when it starts at its `animate` (#448): on until its first Retarget.
|
|
1945
|
+
let quiet = start.quiet
|
|
1946
|
+
// Its Variable values (#55, ADR-0035 *Amendment (#55)*): the Key rule bound to this element,
|
|
1947
|
+
// added to each leg it starts, so a `var()` end blends what it resolves to here.
|
|
1948
|
+
const elementRules = [Variables.values(element)]
|
|
1949
|
+
const recordFrame = recordFrameOf(presence)
|
|
1950
|
+
// Whether its animations are skipped (#468): never, with nothing given.
|
|
1951
|
+
const skipAnimations = UndefinedOr.match(options.skipAnimations, { onUndefined: () => constFalse, onDefined: identity })
|
|
1952
|
+
|
|
1953
|
+
// A routed key a return handed back: written by the follower from now on (#130). Set by
|
|
1954
|
+
// `follow`; a return settles a tick after it runs at the earliest.
|
|
1955
|
+
let handBack: (key: string) => void = constVoid
|
|
1956
|
+
|
|
1957
|
+
// The keys a Retarget drove, each with its last target value (its shape), and the
|
|
1958
|
+
// ones returning to their Base value until that return settles (ADR-0022). Both are
|
|
1959
|
+
// the spine's: a Retarget mid-return carries them again, so a return is never
|
|
1960
|
+
// frozen half-way, and a key that settled has left them, so a Retarget of the same
|
|
1961
|
+
// target (a `when` gate reopening, ADR-0007) leaves it be. On a drag element, the keys
|
|
1962
|
+
// the drag moves leave the return while it runs, so they stay where it dropped them (C9)
|
|
1963
|
+
// until an entry sets them again; a projected element's drag moves none, its projection
|
|
1964
|
+
// target does (ADR-0016), and its own translate returns as any key does (#363).
|
|
1965
|
+
const driven = new Map<string, number | string>()
|
|
1966
|
+
let returning = new Map<string, BaseValue.BaseSource>()
|
|
1967
|
+
// Read as a drag holds them: `projected` is a getter while the track waits for `drag` (#551), whose
|
|
1968
|
+
// track may drive a layout node that arrived with it (#582).
|
|
1969
|
+
const dragMoves = (): ReadonlyArray<string> => {
|
|
1970
|
+
if (options.projected) return NO_KEYS
|
|
1971
|
+
return UndefinedOr.match(options.dragAxis, { onUndefined: () => NO_KEYS, onDefined: dragMovesOf })
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
// A Retarget's returns have settled (ADR-0022, D4). A key back at its `style` value is
|
|
1975
|
+
// handed back to the element's style, so a later style change shows at rest and the
|
|
1976
|
+
// next gesture starts from it; any other key rests inline at its Base value and leaves
|
|
1977
|
+
// the target. A key a later Retarget took over is left alone. A routed key (#130) stays
|
|
1978
|
+
// in the gate, handed to the follower, which writes its value from now on.
|
|
1979
|
+
const settleReturns = (returns: ReadonlyArray<readonly [string, BaseValue.BaseSource]>): void => {
|
|
1980
|
+
// The returns still this Retarget's: a later one took over the rest.
|
|
1981
|
+
const settled = Arr.filter(returns, ([key, source]) => returning.get(key) === source)
|
|
1982
|
+
Arr.forEach(settled, ([key]) => {
|
|
1983
|
+
returning.delete(key)
|
|
1984
|
+
driven.delete(key)
|
|
1985
|
+
owners.set(key, undefined)
|
|
1986
|
+
})
|
|
1987
|
+
const toStyle = Arr.map(Arr.filter(settled, ([, source]) => source === "style"), ([key]) => key)
|
|
1988
|
+
const routedNow = (key: string): boolean => UndefinedOr.match(live, { onUndefined: constFalse, onDefined: (routes) => routes.routes(key) })
|
|
1989
|
+
Arr.forEach(Arr.filter(toStyle, routedNow), (key) => handBack(key))
|
|
1990
|
+
const handedBack = Arr.filter(toStyle, Predicate.not(routedNow))
|
|
1991
|
+
if (Arr.isReadonlyArrayEmpty(handedBack)) return
|
|
1992
|
+
// The gate writes the user's current value back, as the element's `style` would
|
|
1993
|
+
// (ADR-0034). A key gone from the user's style by now is cleared, not restored stale.
|
|
1994
|
+
const restore = UndefinedOr.map(styleNow, (now) => {
|
|
1995
|
+
const style = now()
|
|
1996
|
+
return Object.fromEntries(
|
|
1997
|
+
Arr.map(handedBack, (key) => [key, Option.match(OwnValues.styleValueOf(style, key), { onNone: () => "", onSome: (value) => String(value) })]),
|
|
1998
|
+
) as Record<string, string>
|
|
1999
|
+
})
|
|
2000
|
+
gate.release(id, handedBack, restore)
|
|
2001
|
+
Arr.forEach(handedBack, (key) => {
|
|
2002
|
+
delete snapshot.values[key]
|
|
2003
|
+
delete snapshot.velocities[key]
|
|
2004
|
+
if (Predicate.isNotUndefined(presence)) delete presence.snapshot.current[key]
|
|
2005
|
+
})
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// Cold path: once per Retarget that started a key from its Base value or returned one,
|
|
2009
|
+
// run at the head of that Retarget's first leg (no fiber of its own: forking one per
|
|
2010
|
+
// element cost 10-20 µs each at a 1000-element mount).
|
|
2011
|
+
const baseLog = (started: Record<string, string>, returns: ReadonlyArray<readonly [string, BaseValue.BaseSource]>): Effect.Effect<void> =>
|
|
2012
|
+
Effect.logDebug("wardic.base: a key with no value started from, or returned to, its Base value (ADR-0022)").pipe(
|
|
2013
|
+
Effect.annotateLogs({ id, started, returned: Object.fromEntries(returns) }),
|
|
2014
|
+
)
|
|
2015
|
+
|
|
2016
|
+
// Each key's leg (#325, Motion's per-key rule): the running leg that draws it, or the last one that
|
|
2017
|
+
// did, landed. A Retarget leaves a key its running leg when it resolves it to the same target, starts
|
|
2018
|
+
// none for a key whose last leg landed there, and starts every other key afresh. Each leg has its
|
|
2019
|
+
// own token (#200, ADR-0027 *Amendment (#200)*), ended once it has no key left: Superseded, not
|
|
2020
|
+
// interrupted, so off the loop at once and its fiber ended normally, at about a sixth of an
|
|
2021
|
+
// interrupt's cost.
|
|
2022
|
+
const owners: Owners = new Map()
|
|
2023
|
+
// The keys an Imperative animation took (#433), each with the target the Override stack had for it
|
|
2024
|
+
// then: a Retarget leaves each where the animation leaves it while its target is still that one, and
|
|
2025
|
+
// takes it back once it isn't, as a newer animation of Motion's value takes it (latest-wins).
|
|
2026
|
+
// Every element reads it, nearly none sets it (#460): the shared empty one until the first key taken.
|
|
2027
|
+
let taken: Map<string, number | string | undefined> = NONE_TAKEN
|
|
2028
|
+
// The legs that gave up a key in this Retarget: what each still draws is worked out at its end.
|
|
2029
|
+
const losing: Array<Running> = []
|
|
2030
|
+
// The landing every running leg counts toward: the last Retarget's.
|
|
2031
|
+
let current: Landing | undefined
|
|
2032
|
+
// A gated Retarget's (ADR-0007 `when`, #325): made at the first.
|
|
2033
|
+
let gated: Landing | undefined
|
|
2034
|
+
// The variant name the last started Retarget resolved, for its successor's `definition` (#257).
|
|
2035
|
+
let lastResolution: Resolution | undefined
|
|
2036
|
+
// The Controls change (#433) it last joined, whether it's still counted there (until a landing),
|
|
2037
|
+
// and the one its last started Retarget ran for: only the first runs on its override.
|
|
2038
|
+
let joined: ControlsChange | undefined
|
|
2039
|
+
let counted = false
|
|
2040
|
+
let startedChange: ControlsChange | undefined
|
|
2041
|
+
/** Its Retargets' change is `change` now: it leaves the last, and joins this one, held or not. */
|
|
2042
|
+
const joinChange = (change: ControlsChange | undefined): void => {
|
|
2043
|
+
if (counted) leave(joined as ControlsChange)
|
|
2044
|
+
joined = change
|
|
2045
|
+
counted = Predicate.isNotUndefined(change)
|
|
2046
|
+
if (counted) join(change as ControlsChange)
|
|
2047
|
+
}
|
|
2048
|
+
/** It landed, or can't (a leg failed, its unmount): it leaves its change. */
|
|
2049
|
+
const leaveChange = (): void => {
|
|
2050
|
+
if (!counted) return
|
|
2051
|
+
counted = false
|
|
2052
|
+
leave(joined as ControlsChange)
|
|
2053
|
+
}
|
|
2054
|
+
/** It landed: it leaves its change once the gate has flushed its last frame, unless it joined another meanwhile. */
|
|
2055
|
+
const leaveAfterFlush = (): void => {
|
|
2056
|
+
if (!counted) return
|
|
2057
|
+
const change = joined
|
|
2058
|
+
gate.afterFlush(() => {
|
|
2059
|
+
if (joined === change) leaveChange()
|
|
2060
|
+
})
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// The element's waiting Transition ends (#508), made at its first: most elements never have one.
|
|
2064
|
+
let waiting: WaitingEnds | undefined
|
|
2065
|
+
|
|
2066
|
+
// A leg with no key left ends. Its fiber ends inline (ADR-0027, *Amendment (#200)*); one its token
|
|
2067
|
+
// didn't reach, none of its runs on the loop, is interrupted. A Transition end it is (#508) is
|
|
2068
|
+
// dropped, written never, as its leg was.
|
|
2069
|
+
const endLeg = (running: Running): void => {
|
|
2070
|
+
Supersede.end(running.supersede)
|
|
2071
|
+
if (Predicate.isNotUndefined(waiting)) waiting.drop(running)
|
|
2072
|
+
const fiber = running.fiber
|
|
2073
|
+
if (Predicate.isUndefined(fiber)) return
|
|
2074
|
+
if (Predicate.isNotUndefined(fiber.pollUnsafe())) return
|
|
2075
|
+
// Interrupted here, nothing waiting on its end, as `runCallback`'s interruptor does: a forked
|
|
2076
|
+
// `Fiber.interrupt` was a fiber of its own per ended leg (#618, L2).
|
|
2077
|
+
fiber.interruptUnsafe()
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
// A leg takes its keys (#325): each key's owner from now on. One callback a spine, not one a leg.
|
|
2081
|
+
let owning: Running | undefined
|
|
2082
|
+
const ownKey = (key: string): void => {
|
|
2083
|
+
owners.set(key, owning)
|
|
2084
|
+
}
|
|
2085
|
+
const own = (running: Running): void => {
|
|
2086
|
+
const keys = Rec.keys(running.targets)
|
|
2087
|
+
running.count = keys.length
|
|
2088
|
+
owning = running
|
|
2089
|
+
Arr.forEach(keys, ownKey)
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
// The legs drawing each key: a running leg's velocity is read from it (#464).
|
|
2093
|
+
snapshot.owners = owners
|
|
2094
|
+
// Its time, its provider's Clock's (#460, Q7).
|
|
2095
|
+
snapshot.now = clockNowOf(runtime)
|
|
2096
|
+
// Each key's velocity, at the time the Retarget or Imperative animation reading it read (#464).
|
|
2097
|
+
let readAt = 0
|
|
2098
|
+
const readVelocity: Velocities = (key) => velocityAt(snapshot, owners.get(key), key, readAt)
|
|
2099
|
+
|
|
2100
|
+
// `key`'s leg gives it up: a leg ends with its last key, and draws only the rest before.
|
|
2101
|
+
const giveUp = (key: string, running: Running): void => {
|
|
2102
|
+
letGo(snapshot, key, running)
|
|
2103
|
+
owners.set(key, undefined)
|
|
2104
|
+
if (running.done) return
|
|
2105
|
+
running.count--
|
|
2106
|
+
if (running.count === 0) return endLeg(running)
|
|
2107
|
+
if (running.losing) return
|
|
2108
|
+
running.losing = true
|
|
2109
|
+
losing.push(running)
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
// The keys the Retarget being worked out resolves, for `giveUpUnresolved`: one callback a spine,
|
|
2113
|
+
// not one a Retarget.
|
|
2114
|
+
let resolvedNow: Values.Values = NO_VALUES
|
|
2115
|
+
// Whether `running` is whole: it still draws every key it was started for, each held now, resolved
|
|
2116
|
+
// by the Retarget being worked out or taken by its Imperative animation. A key its Retarget didn't
|
|
2117
|
+
// resolve, a Path's turn or the axis its target leaves out (#438), is the leg's own: not counted.
|
|
2118
|
+
const holdsWhole = (running: Running): boolean =>
|
|
2119
|
+
Arr.every(Rec.keys(running.targets), (key) => {
|
|
2120
|
+
if (taken.has(key)) return owners.get(key) === running
|
|
2121
|
+
if (Bool.not(Object.hasOwn(running.resolution.resolved, key))) return true
|
|
2122
|
+
return Bool.and(owners.get(key) === running, Object.hasOwn(resolvedNow, key))
|
|
2123
|
+
})
|
|
2124
|
+
const giveUpUnresolved = (owner: Running | undefined, key: string): void => {
|
|
2125
|
+
if (Predicate.isUndefined(owner)) return
|
|
2126
|
+
// An Imperative animation's key (#433) is its own while taken.
|
|
2127
|
+
if (taken.has(key)) return
|
|
2128
|
+
if (Object.hasOwn(resolvedNow, key)) return
|
|
2129
|
+
if (Object.hasOwn(owner.resolution.resolved, key)) return giveUp(key, owner)
|
|
2130
|
+
// A key its leg draws though its own Retarget didn't resolve it rides on with that leg while the
|
|
2131
|
+
// leg is whole (#460, ADR-0065): a Retarget of another key leaves a Path as it was. Once the leg
|
|
2132
|
+
// loses a key, its turn goes back to 0 (`turnBack`) and an axis its target left out stays.
|
|
2133
|
+
if (holdsWhole(owner)) return
|
|
2134
|
+
giveUp(key, owner)
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
// A Path's turn goes back to 0 once no leg draws it (#460, ADR-0065 decision 3): after a Retarget
|
|
2138
|
+
// whose legs don't turn, a stop, a cancel. Most elements never turn: a guard.
|
|
2139
|
+
const turnBack = (): void => {
|
|
2140
|
+
const turn = snapshot.values[PATH_ROTATION]
|
|
2141
|
+
if (Bool.or(Predicate.isUndefined(turn), turn === 0)) return
|
|
2142
|
+
if (Predicate.isNotUndefined(owners.get(PATH_ROTATION))) return
|
|
2143
|
+
snapshot.values = { ...snapshot.values, ...TURNED_BACK }
|
|
2144
|
+
snapshot.velocities = { ...snapshot.velocities, [PATH_ROTATION]: 0 }
|
|
2145
|
+
snapshot.drawnAt[PATH_ROTATION] = snapshot.now()
|
|
2146
|
+
gate.write(id, TURNED_BACK)
|
|
2147
|
+
recordFrame(TURNED_BACK)
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// Each leg that gave up keys draws the ones it kept from now on: once per such leg, a Retarget.
|
|
2151
|
+
const keepTheRest = (): void => {
|
|
2152
|
+
if (losing.length === 0) return
|
|
2153
|
+
Arr.forEach(losing, (running) => {
|
|
2154
|
+
running.losing = false
|
|
2155
|
+
if (running.count === 0) return
|
|
2156
|
+
const kept = Arr.filter(Rec.keys(running.targets), (key) => owners.get(key) === running)
|
|
2157
|
+
running.values = keepingOnly(kept)
|
|
2158
|
+
running.velocities = keepingOnly(kept)
|
|
2159
|
+
})
|
|
2160
|
+
losing.length = 0
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
/**
|
|
2164
|
+
* Where `key` starts toward `like` (ADR-0024, D1; ADR-0022, D3): its Snapshot value, coerced to its
|
|
2165
|
+
* target's shape; one of another type on a positional key as it is, a Measured pair (#430); else
|
|
2166
|
+
* its Base value's start link, read at its first need, noted in `started`; with none, at its target
|
|
2167
|
+
* (no motion). Guards, not a match: per key, per Retarget, and a key with a value builds nothing.
|
|
2168
|
+
*/
|
|
2169
|
+
const startKey = (key: string, like: number | string, started: Record<string, string>): void => {
|
|
2170
|
+
const coerced = OwnValues.valueLike(snapshot.values[key], like)
|
|
2171
|
+
if (Option.isSome(coerced)) snapshot.values[key] = coerced.value
|
|
2172
|
+
// A start of another type than its target's, on a positional key, stays as it is: a Measured
|
|
2173
|
+
// pair (#430), measured at the end of the task.
|
|
2174
|
+
if (Option.isSome(coerced)) return
|
|
2175
|
+
if (keepsStart(key, snapshot.values[key], like)) return
|
|
2176
|
+
const link = startLinkOf(key, like)
|
|
2177
|
+
if (Option.isSome(link) && isSameKind(link.value.value, like)) {
|
|
2178
|
+
snapshot.values[key] = link.value.value
|
|
2179
|
+
started[key] = link.value.source
|
|
2180
|
+
return
|
|
2181
|
+
}
|
|
2182
|
+
snapshot.values[key] = like
|
|
2183
|
+
started[key] = "target"
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
// A key's leg ended by a failure (#257 item 4): nothing draws its keys, so a later Retarget starts them afresh.
|
|
2187
|
+
const forget = (running: Running): void =>
|
|
2188
|
+
Arr.forEach(Rec.keys(running.targets), (key) => {
|
|
2189
|
+
if (owners.get(key) !== running) return
|
|
2190
|
+
letGo(snapshot, key, running)
|
|
2191
|
+
owners.set(key, undefined)
|
|
2192
|
+
})
|
|
2193
|
+
|
|
2194
|
+
/**
|
|
2195
|
+
* A leg's run into its sink: one dispatcher, each sink built for the leg that needs it, a
|
|
2196
|
+
* Retarget's and an Imperative animation's (#433). A blend's machine is built under the element's
|
|
2197
|
+
* Key rules when its change holds a Variable value (`variables`, #542).
|
|
2198
|
+
*/
|
|
2199
|
+
const legProgram = (leg: Leg, running: Running, variables: boolean): Effect.Effect<unknown, never, WardicServices> => {
|
|
2200
|
+
if (leg._tag === "Follow") {
|
|
2201
|
+
const key = leg.key
|
|
2202
|
+
const out: Record<string, number> = { [key]: 0 }
|
|
2203
|
+
// Per leg (#460): Machine's calls data-first; per frame, statements.
|
|
2204
|
+
return Effect.suspend(() =>
|
|
2205
|
+
Machine.run(leg.machine(), (frame) => {
|
|
2206
|
+
out[key] = frame.value
|
|
2207
|
+
snapshot.values[key] = frame.value
|
|
2208
|
+
let velocity = frame.velocity
|
|
2209
|
+
if (Predicate.isUndefined(velocity)) velocity = 0
|
|
2210
|
+
snapshot.velocities[key] = velocity
|
|
2211
|
+
gate.write(id, out)
|
|
2212
|
+
recordFrame(out)
|
|
2213
|
+
drew(running, frame.done, snapshot.now())
|
|
2214
|
+
if (frame.done) landEndsOf(waiting)
|
|
2215
|
+
return undefined
|
|
2216
|
+
}, running)
|
|
2217
|
+
)
|
|
2218
|
+
}
|
|
2219
|
+
if (leg._tag === "Numbers") {
|
|
2220
|
+
return Effect.suspend(() =>
|
|
2221
|
+
Machine.run(leg.machine(), (frame) => {
|
|
2222
|
+
// MULTI-OWNER MERGE (ADR-0004): merge the spring's keys into the snapshot
|
|
2223
|
+
// rather than replacing it, so the drag's direct-set x/y survive alongside the
|
|
2224
|
+
// spring's keys (drag owns x/y, spring owns the rest) and the post-drag
|
|
2225
|
+
// handoff continues from the rested position. Only the keys it still draws (#325).
|
|
2226
|
+
const value = running.values(frame.value)
|
|
2227
|
+
snapshot.values = { ...snapshot.values, ...value }
|
|
2228
|
+
snapshot.velocities = { ...snapshot.velocities, ...running.velocities(frame.velocity) }
|
|
2229
|
+
gate.write(id, value)
|
|
2230
|
+
recordFrame(value)
|
|
2231
|
+
drew(running, frame.done, snapshot.now())
|
|
2232
|
+
if (frame.done) landEndsOf(waiting)
|
|
2233
|
+
return undefined
|
|
2234
|
+
}, running)
|
|
2235
|
+
)
|
|
2236
|
+
}
|
|
2237
|
+
// A snap's or a blend's frames: merged into the Snapshot (ADR-0004), as the spring's are. A blend's
|
|
2238
|
+
// last two are kept, its keys' velocities read from them (#464): the frame's own values, which a
|
|
2239
|
+
// blend makes anew each frame (`values` writes one object for a leg that lost keys).
|
|
2240
|
+
const sink = (frame: Frame.Frame<Values.Values>): undefined => {
|
|
2241
|
+
const value = running.values(frame.value)
|
|
2242
|
+
let before = running.last
|
|
2243
|
+
let beforeAt = running.lastAt
|
|
2244
|
+
// Its first frame: from the Snapshot's values, a nominal frame before it.
|
|
2245
|
+
if (Predicate.isUndefined(before)) {
|
|
2246
|
+
before = snapshot.values
|
|
2247
|
+
beforeAt = snapshot.now() - NOMINAL_FRAME
|
|
2248
|
+
}
|
|
2249
|
+
running.before = before
|
|
2250
|
+
running.beforeAt = beforeAt
|
|
2251
|
+
running.last = frame.value
|
|
2252
|
+
snapshot.values = { ...snapshot.values, ...value }
|
|
2253
|
+
gate.write(id, value)
|
|
2254
|
+
recordFrame(value)
|
|
2255
|
+
drew(running, frame.done, snapshot.now())
|
|
2256
|
+
if (frame.done) landEndsOf(waiting)
|
|
2257
|
+
return undefined
|
|
2258
|
+
}
|
|
2259
|
+
if (leg._tag === "Snap") {
|
|
2260
|
+
// A snapped key comes to rest, so a later spring starts it at rest.
|
|
2261
|
+
snapshot.velocities = { ...snapshot.velocities, ...Rec.fromEntries(Arr.map(leg.keys, (key) => [key, 0] as const)) }
|
|
2262
|
+
return Effect.suspend(() => Machine.run(leg.machine(), sink, running))
|
|
2263
|
+
}
|
|
2264
|
+
// Per leg (#460): a statement, Effect's calls data-first.
|
|
2265
|
+
let built = Effect.suspend(leg.machine)
|
|
2266
|
+
if (variables) built = InterpolatorRegistry.withKeyRules(built, elementRules)
|
|
2267
|
+
return Effect.flatMap(built, (machine) => Machine.run(machine, sink, running))
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
// A leg's end, told its landing: a Retarget's legs' and an Imperative animation's (#433).
|
|
2271
|
+
const legEnded = (running: Running) => (exit: Exit.Exit<unknown>): Effect.Effect<void> => {
|
|
2272
|
+
// Its fiber has ended, and is let go: a leg kept for its keys' velocity (#464) keeps no fiber alive.
|
|
2273
|
+
running.fiber = undefined
|
|
2274
|
+
// Per leg (#460): statements.
|
|
2275
|
+
if (Exit.isSuccess(exit)) {
|
|
2276
|
+
// A Superseded leg (#200) counts toward nothing, nor tells a name: the next Retarget's legs
|
|
2277
|
+
// land. Nothing made for it: a Retarget ends its element's legs inline.
|
|
2278
|
+
const end = exit.value
|
|
2279
|
+
if (Supersede.isSuperseded(end)) return Effect.void
|
|
2280
|
+
return Effect.sync(() => {
|
|
2281
|
+
running.landing.settled()
|
|
2282
|
+
const tell = running.names
|
|
2283
|
+
if (Predicate.isNotUndefined(tell)) tell(end)
|
|
2284
|
+
})
|
|
2285
|
+
}
|
|
2286
|
+
// A leg its token didn't reach is interrupted: its keys are given up already, and it isn't a failure.
|
|
2287
|
+
const cause = exit.cause
|
|
2288
|
+
if (Cause.hasInterruptsOnly(cause)) return Effect.void
|
|
2289
|
+
return Report.failure("wardic.spine: a leg failed", cause).pipe(
|
|
2290
|
+
Effect.annotateLogs({ id }),
|
|
2291
|
+
Effect.andThen(Effect.sync(() => {
|
|
2292
|
+
forget(running)
|
|
2293
|
+
running.landing.failed(cause)
|
|
2294
|
+
})),
|
|
2295
|
+
)
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
// A gated Retarget (ADR-0007 `when`, #325): a running key it resolves to its leg's target runs on,
|
|
2299
|
+
// the element reported unsettled meanwhile, when an earlier change started that leg; every other
|
|
2300
|
+
// running key freezes where it is. The change the gate holds may have started legs just before
|
|
2301
|
+
// it shut (a child Retargets before its provider's effect resets its `beforeChildren` wait; an
|
|
2302
|
+
// `afterChildren` provider starts before its children report): those freeze too, as before.
|
|
2303
|
+
const hold = (resolution: Resolution): void => {
|
|
2304
|
+
if (Predicate.isUndefined(gated)) gated = gatedLandingOf(onFrame)
|
|
2305
|
+
const landing = gated
|
|
2306
|
+
const resolved = resolution.resolved
|
|
2307
|
+
current = landing
|
|
2308
|
+
owners.forEach((running, key) => {
|
|
2309
|
+
if (Predicate.isUndefined(running)) return
|
|
2310
|
+
if (running.done) return
|
|
2311
|
+
// An Imperative animation's key (#433) is its own while taken.
|
|
2312
|
+
if (taken.has(key)) return
|
|
2313
|
+
if (Object.hasOwn(resolved, key) && running.targets[key] === resolved[key] && !sameResolution(running.resolution, resolution)) {
|
|
2314
|
+
running.landing = landing
|
|
2315
|
+
running.names = undefined
|
|
2316
|
+
return
|
|
2317
|
+
}
|
|
2318
|
+
giveUp(key, running)
|
|
2319
|
+
})
|
|
2320
|
+
keepTheRest()
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
// The translate targets of the last Retarget that ran, held or not: what a takeover of the drag's
|
|
2324
|
+
// release looks for a change in (#340).
|
|
2325
|
+
let lastX: number | string | undefined = undefined
|
|
2326
|
+
let lastY: number | string | undefined = undefined
|
|
2327
|
+
// A Retarget took `x`/`y` from the release, and the hand-back of `dragging` hasn't come yet.
|
|
2328
|
+
let tookRelease = false
|
|
2329
|
+
/**
|
|
2330
|
+
* Whether the drag holds `x`/`y` for this Retarget (ADR-0004): while its pointer is down and
|
|
2331
|
+
* through its release, unless this Retarget gives the release's axis a new target. Then it takes
|
|
2332
|
+
* them from the release (#340, ADR-0016 *Amendment (#340)*), as a newer animation of Motion's value
|
|
2333
|
+
* stops the one running, and they go on from where the release has them, at its velocity. A key a
|
|
2334
|
+
* entry stops setting is no new target: it stays where the drag dropped it (ADR-0022).
|
|
2335
|
+
*/
|
|
2336
|
+
const dragHolds = (resolution: Resolution): boolean => {
|
|
2337
|
+
const { x, y } = resolution.resolved
|
|
2338
|
+
// Per Retarget (#460, compose once): statements.
|
|
2339
|
+
const newX = Predicate.isNotUndefined(x) && x !== lastX
|
|
2340
|
+
const newY = Predicate.isNotUndefined(y) && y !== lastY
|
|
2341
|
+
lastX = x
|
|
2342
|
+
lastY = y
|
|
2343
|
+
if (!resolution.dragging) return false
|
|
2344
|
+
// From a takeover to the hand-back of `dragging` at the end of its task, the drag holds nothing.
|
|
2345
|
+
if (tookRelease) return false
|
|
2346
|
+
const drag = options.drag
|
|
2347
|
+
if (Predicate.isUndefined(drag)) return true
|
|
2348
|
+
if (!newX && !newY) return true
|
|
2349
|
+
tookRelease = drag.takeRelease({ x: newX, y: newY })
|
|
2350
|
+
return !tookRelease
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
const retarget = (given: Resolution): void => {
|
|
2354
|
+
// A Controls change (#433): the element is in it from the Retarget it makes it run, held or not.
|
|
2355
|
+
if (given.change !== joined) joinChange(given.change)
|
|
2356
|
+
|
|
2357
|
+
// `when` start-gate (ADR-0007): when canStart() is false, the keys whose target changed freeze
|
|
2358
|
+
// where they are (#325: the rest run on), and the element waits; the binding retargets once
|
|
2359
|
+
// the gate flips true.
|
|
2360
|
+
if (Predicate.isNotUndefined(canStart) && !canStart()) {
|
|
2361
|
+
hold(given)
|
|
2362
|
+
// Report NOT settled: a gated child sits at its snapshot, still
|
|
2363
|
+
// owing an animation, so it must not count as settled (afterChildren).
|
|
2364
|
+
if (Predicate.isNotUndefined(onFrame)) onFrame(false)
|
|
2365
|
+
return
|
|
2366
|
+
}
|
|
2367
|
+
// The first Retarget a Controls change starts here runs on its override (#433); a later one, a
|
|
2368
|
+
// gesture's, on the targets' own.
|
|
2369
|
+
let resolution = given
|
|
2370
|
+
// On an override, every key starts again on it, a running leg toward the same target too: `set`
|
|
2371
|
+
// lands it at once, as Motion's.
|
|
2372
|
+
let restarting = false
|
|
2373
|
+
if (given.change !== startedChange) {
|
|
2374
|
+
startedChange = given.change
|
|
2375
|
+
const override = UndefinedOr.map(given.change, (change) => change.override)
|
|
2376
|
+
if (Predicate.isNotUndefined(override)) {
|
|
2377
|
+
resolution = overriddenOf(given, override)
|
|
2378
|
+
restarting = true
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
const { resolved, base } = resolution
|
|
2382
|
+
// The hand-back of `dragging` after a takeover (#340): the last Retarget already ran with `x`/`y`
|
|
2383
|
+
// its own, so one that changes nothing else is no new animation, as Motion's has none. It goes
|
|
2384
|
+
// on, and lands and is told as itself.
|
|
2385
|
+
if (tookRelease && !resolution.dragging) {
|
|
2386
|
+
tookRelease = false
|
|
2387
|
+
if (handsBackOnly(resolution, lastResolution)) {
|
|
2388
|
+
lastResolution = resolution
|
|
2389
|
+
return
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
const held = dragHolds(resolution)
|
|
2393
|
+
// The keys an Imperative animation took (#433): one whose target here isn't the one it had when
|
|
2394
|
+
// taken is the Override stack's again.
|
|
2395
|
+
if (taken.size > 0) Iter.forEach(taken, ([key, was]) => {
|
|
2396
|
+
if (resolved[key] !== was) taken.delete(key)
|
|
2397
|
+
})
|
|
2398
|
+
|
|
2399
|
+
// A key a Retarget drove and no entry sets now returns to its Base value
|
|
2400
|
+
// (ADR-0022); with none, it stays where it is (as before #47).
|
|
2401
|
+
const t: Values.Values = { ...resolved }
|
|
2402
|
+
Arr.forEach(Rec.keys(resolved), (key) => {
|
|
2403
|
+
driven.set(key, resolved[key])
|
|
2404
|
+
})
|
|
2405
|
+
if (held) Arr.forEach(dragMoves(), (key) => driven.delete(key))
|
|
2406
|
+
returning = new Map()
|
|
2407
|
+
// Over the Map itself: copying it into an array first costs ~20 times the walk, per element
|
|
2408
|
+
// per Retarget (#69's PR 4). Deleting the entry being visited is safe in a Map's iteration.
|
|
2409
|
+
Iter.forEach(driven, ([key, like]) => {
|
|
2410
|
+
// A translate key the strip below holds back returns once the drag lets go.
|
|
2411
|
+
if (Object.hasOwn(resolved, key) || (held && TRANSLATE_KEYS.has(key))) return
|
|
2412
|
+
// One an Imperative animation holds (#433) stays where it leaves it.
|
|
2413
|
+
if (taken.has(key)) return
|
|
2414
|
+
const back = BaseValue.of(key, { base, initial: initialLink, started: startedLink, ...linksFor(like) })
|
|
2415
|
+
if (Option.isNone(back)) {
|
|
2416
|
+
driven.delete(key)
|
|
2417
|
+
return
|
|
2418
|
+
}
|
|
2419
|
+
t[key] = back.value.value
|
|
2420
|
+
returning.set(key, back.value.source)
|
|
2421
|
+
})
|
|
2422
|
+
// MULTI-OWNER strip (ADR-0004): while dragging, x/y are DIRECT-SET by the
|
|
2423
|
+
// drag track, so strip the translate keys from the spring's target — the
|
|
2424
|
+
// spring must never fight the direct-set. Once dragging is false the
|
|
2425
|
+
// keys re-enter normally (post-drag handoff: the spring continues x/y only
|
|
2426
|
+
// if `animate` sets them, from the snapshot's rested position).
|
|
2427
|
+
if (held) Arr.forEach(TRANSLATE_KEY_LIST, (key) => delete t[key])
|
|
2428
|
+
// Every key an Imperative animation holds stays where it leaves it (#433).
|
|
2429
|
+
if (taken.size > 0) Iter.forEach(taken, ([key]) => {
|
|
2430
|
+
delete t[key]
|
|
2431
|
+
})
|
|
2432
|
+
// The keys this Retarget returns; once it lands, a key back at its `style` value is
|
|
2433
|
+
// handed back to the element's style (D4) and the rest rest inline.
|
|
2434
|
+
const returns = [...returning]
|
|
2435
|
+
// The transition, read at Retarget time so a change applies to the next Retarget. A child
|
|
2436
|
+
// with no local transition inherits the parent context's (ADR-0007).
|
|
2437
|
+
const c = config()
|
|
2438
|
+
|
|
2439
|
+
// This Retarget's landing (#257, #325): it lands when every leg drawing a key it resolves has
|
|
2440
|
+
// settled, its own and the ones it carries. A leg's end, told apart (ADR-0027, decision 7): a
|
|
2441
|
+
// settle counts; a Superseded end (a Retarget, an unmount, #200) or an interruption (a leg its
|
|
2442
|
+
// token didn't reach) is nothing; anything else (a step or a sink that threw, the Frame loop's
|
|
2443
|
+
// own failure) is logged and reported (ADR-0030, decision 4), and doesn't count.
|
|
2444
|
+
let legs = 0
|
|
2445
|
+
// The variant the Retarget before this one resolved, set once this one starts (#257).
|
|
2446
|
+
let previous: Resolution | undefined
|
|
2447
|
+
const legDone = (): void => {
|
|
2448
|
+
if (current !== landing) return
|
|
2449
|
+
if (--legs !== 0) return
|
|
2450
|
+
settleReturns(returns)
|
|
2451
|
+
// The Retarget settled with its last leg (#257): told once the gate has flushed its last frame,
|
|
2452
|
+
// so the element shows where it landed. Its Controls change hears it then too (#433).
|
|
2453
|
+
if (Predicate.isNotUndefined(animation)) gate.afterFlush(() => animation.settled(resolution, previous, c))
|
|
2454
|
+
leaveAfterFlush()
|
|
2455
|
+
}
|
|
2456
|
+
// A leg's failure (#257 item 4): the Retarget can't land, so a binding waiting on it is told,
|
|
2457
|
+
// once, at once: a leg that fails before it writes leaves no flush to wait for. A leg failing
|
|
2458
|
+
// as it's built, before `started` is told, is told right after it.
|
|
2459
|
+
let failedTold = false
|
|
2460
|
+
let startedTold = false
|
|
2461
|
+
let failedEarly: { readonly error: unknown } | undefined
|
|
2462
|
+
const tellFailed = (error: unknown): void => {
|
|
2463
|
+
const hooks = animation as AnimationHooks
|
|
2464
|
+
// Called on its hooks, which may be a class's: not taken off them.
|
|
2465
|
+
if (Predicate.isNotUndefined(hooks.failed)) hooks.failed(resolution, error)
|
|
2466
|
+
}
|
|
2467
|
+
const legFailed = (cause: Cause.Cause<unknown>): void => {
|
|
2468
|
+
// It can't land: its Controls change waits for it no longer (#433).
|
|
2469
|
+
if (current === landing) leaveChange()
|
|
2470
|
+
if (failedTold || Predicate.isUndefined(animation)) return
|
|
2471
|
+
failedTold = true
|
|
2472
|
+
const error = Cause.squash(cause)
|
|
2473
|
+
if (startedTold) return tellFailed(error)
|
|
2474
|
+
failedEarly = { error }
|
|
2475
|
+
}
|
|
2476
|
+
// The element settles with its last leg (ADR-0024, D5): a string leg can outlast the
|
|
2477
|
+
// numeric one, so each leg's frames report, and only the last leg's settle reports done.
|
|
2478
|
+
let reporting = 0
|
|
2479
|
+
let settled = 0
|
|
2480
|
+
const report = (done: boolean): void => {
|
|
2481
|
+
if (done) settled++
|
|
2482
|
+
if (Predicate.isNotUndefined(onFrame)) onFrame(settled === reporting)
|
|
2483
|
+
}
|
|
2484
|
+
const landing: Landing = { report, settled: legDone, failed: legFailed }
|
|
2485
|
+
current = landing
|
|
2486
|
+
|
|
2487
|
+
// The Retarget's transitions, read before its keys: a key already there asks its own for a
|
|
2488
|
+
// velocity (#448).
|
|
2489
|
+
const elementTransition = ownOrInherited(c.transition, parent)
|
|
2490
|
+
// The base target's own transition (#277): the keys its entry drives animate on it.
|
|
2491
|
+
const baseTransition = transitionOfTarget(resolution.transition, elementTransition)
|
|
2492
|
+
// Fold the per-child stagger delay (ADR-0007) into the effective
|
|
2493
|
+
// transition.delay = base.delay + computedDelay. extraDelay() is read
|
|
2494
|
+
// at Retarget time so a re-snapshot before this start is reflected. Each name's keys take that
|
|
2495
|
+
// name's delay (#297): the base's, its first name's.
|
|
2496
|
+
const layers = resolution.layers
|
|
2497
|
+
const extra = extraOf(extraDelay, layers[0])
|
|
2498
|
+
const transition = staggeredOf(baseTransition, extra, options.reuse)
|
|
2499
|
+
// With an entry's own transition (#277, #291), each key animates on its entry's: the base's keys
|
|
2500
|
+
// on the base target's, each active gesture's on its target's, else the element's, as Motion's
|
|
2501
|
+
// each animation type runs on its own.
|
|
2502
|
+
// A list's names (#297) split them too, each name's at its own delay; a list's returns run on the
|
|
2503
|
+
// element's transition, in a group of their own, last.
|
|
2504
|
+
// Guards, not matches: per Retarget, per element. A list's work (#297) is done apart, for a list:
|
|
2505
|
+
// the Retarget's other calls keep their inlining (docs/agents/effect-first.md).
|
|
2506
|
+
const listed = Arr.isArray(resolution.variant)
|
|
2507
|
+
// A list's names each wait for their own `when` (#297's PR 2): the entries whose gate is shut now.
|
|
2508
|
+
let shut: ReadonlyArray<boolean> | undefined
|
|
2509
|
+
if (listed) shut = shutOf(layers, byName)
|
|
2510
|
+
// Every key the Retarget resolves goes in its entry's group, the ones that stay too (#325): a
|
|
2511
|
+
// name's settle waits for them.
|
|
2512
|
+
const groups = keysByLayerOf(t, layers, extraDelay, extra, listed, shut)
|
|
2513
|
+
// Per key (#325, Motion's): a key whose running leg heads where this Retarget resolves it runs
|
|
2514
|
+
// on, counted toward this landing; one whose last leg landed there stays; every other's leg
|
|
2515
|
+
// gives it up, and it starts from its Snapshot value, coerced to its target's shape (ADR-0024,
|
|
2516
|
+
// D1). A key with no value yet, or one whose value can't take that shape, starts from its Base
|
|
2517
|
+
// value's start links, read here at its first need (ADR-0022, D3); with none, it starts at its
|
|
2518
|
+
// target (no motion).
|
|
2519
|
+
//
|
|
2520
|
+
// Every key's Own value is read first, at its first need, before anything writes it, so a return
|
|
2521
|
+
// later finds the element's value, not Wardic's (ADR-0022, D3). The read is kept: a later need is
|
|
2522
|
+
// a lookup. A transform key reads nothing.
|
|
2523
|
+
let stays: Set<string> | undefined
|
|
2524
|
+
let carried: Array<Running> | undefined
|
|
2525
|
+
let there: Record<string, number | string> | undefined
|
|
2526
|
+
const started: Record<string, string> = {}
|
|
2527
|
+
// Each key's Keyframes (#429), when any entry gives some: a key's carries on only toward the same.
|
|
2528
|
+
const keyframesNow = resolution.keyframes
|
|
2529
|
+
// The keys whose two ends may be a Measured pair (#430), found in the pass below.
|
|
2530
|
+
let measuring: Array<string> | undefined
|
|
2531
|
+
// Each key's velocity, read now (#464), for a key a spring starts or one already there.
|
|
2532
|
+
readAt = snapshot.now()
|
|
2533
|
+
// Where a Path's axis this target leaves out rests (#574), before the pass below gives up its leg.
|
|
2534
|
+
const rests = restsOf(owners, t)
|
|
2535
|
+
Arr.forEach(Rec.keys(t), (key) => {
|
|
2536
|
+
if (!Object.hasOwn(initialValues, key)) ownValue(key)
|
|
2537
|
+
const like = t[key]
|
|
2538
|
+
const owner = owners.get(key)
|
|
2539
|
+
const stops = keyframesAtOf(keyframesNow, key)
|
|
2540
|
+
// A leg drawing it toward another target: it's moving, so it's never already there (#448).
|
|
2541
|
+
let drawn = false
|
|
2542
|
+
if (Predicate.isNotUndefined(owner)) {
|
|
2543
|
+
drawn = !owner.done
|
|
2544
|
+
// A key whose Transition end changed starts again (#470), as Motion's value does, its end in
|
|
2545
|
+
// its resolved values.
|
|
2546
|
+
if (owner.targets[key] === like && !restarting && sameKeyframesAt(keyframesAtOf(owner.resolution.keyframes, key), stops) && !endMoved(lastResolution, resolution, key)) {
|
|
2547
|
+
if (Predicate.isUndefined(stays)) stays = new Set()
|
|
2548
|
+
stays.add(key)
|
|
2549
|
+
if (owner.done || owner.landing === landing) return
|
|
2550
|
+
owner.landing = landing
|
|
2551
|
+
owner.names = undefined
|
|
2552
|
+
legs++
|
|
2553
|
+
reporting++
|
|
2554
|
+
if (Predicate.isUndefined(carried)) carried = []
|
|
2555
|
+
carried.push(owner)
|
|
2556
|
+
return
|
|
2557
|
+
}
|
|
2558
|
+
giveUp(key, owner)
|
|
2559
|
+
}
|
|
2560
|
+
startKey(key, like, started)
|
|
2561
|
+
// A Measured pair (#430): its two ends of different types, found in this pass, so a Retarget
|
|
2562
|
+
// with none builds nothing for them.
|
|
2563
|
+
if (MeasuredPair.needs(key, UndefinedOr.getOrThrow(snapshot.values[key]), like)) {
|
|
2564
|
+
if (Predicate.isUndefined(measuring)) measuring = []
|
|
2565
|
+
measuring.push(key)
|
|
2566
|
+
}
|
|
2567
|
+
// Already there (#448, Motion's `visual-element-target.ts`): a key at its target, coerced to its
|
|
2568
|
+
// shape, with no leg drawing it, at rest and given no velocity by its transition, starts none.
|
|
2569
|
+
// It stays, as a carried key does, and is written once more (below), as Motion sets it on the
|
|
2570
|
+
// next frame. One that started at its target had no value to show, so its leg writes it, as
|
|
2571
|
+
// Motion's does for a value it has none of. Keyframes are never there (#429): they play.
|
|
2572
|
+
// Its velocity read last, for a key there alone (#464): per key, per Retarget.
|
|
2573
|
+
let still = !drawn && started[key] !== "target"
|
|
2574
|
+
if (still) still = snapshot.values[key] === like
|
|
2575
|
+
if (still) still = readVelocity(key) === 0
|
|
2576
|
+
// Its transition asked last, for a key already there alone: per key, per Retarget. A quiet
|
|
2577
|
+
// mount's never asks (#475): Motion animates nothing at a first render that starts at its
|
|
2578
|
+
// `animate`, whatever velocity its transition gives (`animation-state.ts`, `shouldAnimate`).
|
|
2579
|
+
if (still && Predicate.isUndefined(stops) && (quiet || !givesVelocity(keyTransitionOf(resolution, groups, key, transition, elementTransition), key))) {
|
|
2580
|
+
if (Predicate.isUndefined(stays)) stays = new Set()
|
|
2581
|
+
stays.add(key)
|
|
2582
|
+
if (Predicate.isUndefined(there)) there = {}
|
|
2583
|
+
there[key] = like
|
|
2584
|
+
}
|
|
2585
|
+
// A quiet mount's Keyframes (#429): Motion animates nothing at a first render that starts at its
|
|
2586
|
+
// `animate`, so they stay where they start, `animate`'s last stop with `initial={false}`.
|
|
2587
|
+
if (quiet && Predicate.isNotUndefined(stops)) {
|
|
2588
|
+
if (Predicate.isUndefined(stays)) stays = new Set()
|
|
2589
|
+
stays.add(key)
|
|
2590
|
+
if (Predicate.isUndefined(there)) there = {}
|
|
2591
|
+
there[key] = UndefinedOr.getOrThrow(snapshot.values[key])
|
|
2592
|
+
}
|
|
2593
|
+
})
|
|
2594
|
+
// A key no longer resolved (one the drag holds, or one with no Base value) is given up: it stays
|
|
2595
|
+
// where it is.
|
|
2596
|
+
resolvedNow = t
|
|
2597
|
+
owners.forEach(giveUpUnresolved)
|
|
2598
|
+
keepTheRest()
|
|
2599
|
+
// The keys this Retarget starts legs for: all of them, unless some stay.
|
|
2600
|
+
let fresh: Values.Values = t
|
|
2601
|
+
if (Predicate.isNotUndefined(stays)) {
|
|
2602
|
+
const kept = stays
|
|
2603
|
+
fresh = Rec.filter(t, (_, key) => !kept.has(key))
|
|
2604
|
+
// A Path couples `x` and `y` (#438): either retargeted, the other goes along it to its target.
|
|
2605
|
+
fresh = withPathAxes(fresh, t, transition)
|
|
2606
|
+
}
|
|
2607
|
+
// A mount that starts at its `animate`, with nothing to animate, tells no one (#448, Motion's):
|
|
2608
|
+
// no start, no landing, and no list's names. It starts no leg, so it lands at once.
|
|
2609
|
+
const silent = quiet && Rec.isEmptyRecord(fresh) && Predicate.isUndefined(carried)
|
|
2610
|
+
quiet = false
|
|
2611
|
+
// A key only a Transition end sets (#428) starts no leg of its own: its entry's end leg holds it
|
|
2612
|
+
// where it is, and sets it once the entry's animated keys land.
|
|
2613
|
+
const ends = resolution.ends
|
|
2614
|
+
let freshEnds: Values.Values = NO_VALUES
|
|
2615
|
+
if (Predicate.isNotUndefined(ends)) {
|
|
2616
|
+
const endOnly = ends.endOnly
|
|
2617
|
+
freshEnds = Rec.filter(fresh, (_, key) => endOnly.has(key))
|
|
2618
|
+
if (hasKeys(freshEnds)) fresh = Rec.filter(fresh, (_, key) => !endOnly.has(key))
|
|
2619
|
+
}
|
|
2620
|
+
let from: Values.Values = Rec.map(fresh, (_, key) => UndefinedOr.getOrThrow(snapshot.values[key]))
|
|
2621
|
+
// Keyframes (#429) start at their first stop, a `null` one where the key is; those of more than
|
|
2622
|
+
// two stops run through them.
|
|
2623
|
+
let manyStops: StopsByKey | undefined
|
|
2624
|
+
if (Predicate.isNotUndefined(keyframesNow)) {
|
|
2625
|
+
const begun = keyframesFrom(from, fresh, Rec.map(keyframesNow, (at) => at.stops))
|
|
2626
|
+
from = begun.from
|
|
2627
|
+
manyStops = begun.stops
|
|
2628
|
+
}
|
|
2629
|
+
// Each leg: the base-value log at the head of the first, and, once the leg's run
|
|
2630
|
+
// settles, a count toward this landing, so the per-frame handlers stay as they were.
|
|
2631
|
+
let pendingLog: Effect.Effect<void> | undefined
|
|
2632
|
+
if (returns.length > 0 || !Rec.isEmptyRecord(started)) pendingLog = baseLog(started, returns)
|
|
2633
|
+
// A list's names told settled one by one (#297, Solid 1's callbacks): each as the legs of the keys
|
|
2634
|
+
// it drives settle. Made only for a list with a binding that's told so.
|
|
2635
|
+
let names: NameSettles | undefined
|
|
2636
|
+
const leg = <R,>(running: Running, program: Effect.Effect<unknown, never, R>): Effect.Effect<void, never, R> => {
|
|
2637
|
+
legs++
|
|
2638
|
+
const log = pendingLog
|
|
2639
|
+
pendingLog = undefined
|
|
2640
|
+
// Per leg (#460): statements, Effect's calls data-first.
|
|
2641
|
+
let run: Effect.Effect<unknown, never, R> = program
|
|
2642
|
+
if (Predicate.isNotUndefined(log)) run = Effect.andThen(log, program)
|
|
2643
|
+
if (Predicate.isNotUndefined(names)) running.names = names.leg()
|
|
2644
|
+
return Effect.asVoid(Effect.onExit(run, legEnded(running)))
|
|
2645
|
+
}
|
|
2646
|
+
// Start a leg: forked on the provider's runtime, which runs it inline up to its add on the
|
|
2647
|
+
// loop, so every leg started in a task is on the loop by the end of that task and takes its
|
|
2648
|
+
// first step at the next tick (ADR-0027 decision 5, ADR-0033). Forked from a `runSync`, a leg
|
|
2649
|
+
// would inherit that call's scheduler (ADR-0027, decision 6). The runtime keeps it in its own
|
|
2650
|
+
// fiber Scope, which interrupts it if it's still running when the provider ends (#542).
|
|
2651
|
+
// The name the leg being started waits for (#297's PR 2), set as each is started; none for most.
|
|
2652
|
+
let gateNow: string | undefined
|
|
2653
|
+
const runLeg = <R extends WardicServices>(running: Running, program: Effect.Effect<unknown, never, R>): void => {
|
|
2654
|
+
let run: Effect.Effect<unknown, never, R | FrameLoop.FrameLoop> = program
|
|
2655
|
+
if (Predicate.isNotUndefined(gateNow)) run = gatedBy(gateNow, byName, running, program)
|
|
2656
|
+
running.fiber = runtime.runFork(leg(running, run))
|
|
2657
|
+
}
|
|
2658
|
+
// The legs (#48, ADR-0044 decision 8): one pure builder, which a server render's Appear
|
|
2659
|
+
// animation samples too, so the two can't drift. Built here; each leg's machine is built
|
|
2660
|
+
// in its own fiber, so a transition core rejects (#66, ADR-0025 *Amendment (#66)*) fails
|
|
2661
|
+
// that leg alone, reported as any leg's failure is, while the element's other legs run on.
|
|
2662
|
+
// The returns to a moving Live value (#130, ADR-0043 decision 6): a number's follows it,
|
|
2663
|
+
// in a leg of its own, and lands on it; the rest return to where it is now. Looked for
|
|
2664
|
+
// only when this Retarget returns a key.
|
|
2665
|
+
let follow: Follows | undefined
|
|
2666
|
+
if (returning.size > 0 && Predicate.isNotUndefined(live)) {
|
|
2667
|
+
const routes = live
|
|
2668
|
+
follow = { keysOf: (rest) => followingOf(routes, rest, returning), targetOf: (key, start) => liveTargetOf(routes, key, start) }
|
|
2669
|
+
}
|
|
2670
|
+
const reduce = options.reducedMotion()
|
|
2671
|
+
const skipped = skipAnimations()
|
|
2672
|
+
// Measured pairs (#430), asked of the end-of-task batch, unless reduced motion snaps them or the
|
|
2673
|
+
// animation is skipped (#468): the keys found above, and those Keyframes of two stops start from their first.
|
|
2674
|
+
let measured: MeasuredPairs | undefined
|
|
2675
|
+
if (!reduce && !skipped && (Predicate.isNotUndefined(measuring) || Predicate.isNotUndefined(keyframesNow))) {
|
|
2676
|
+
measured = measuredOf(element, measuringOf(measuring, keyframesNow), from, fresh, snapshot.values, manyStops)
|
|
2677
|
+
}
|
|
2678
|
+
let told: ToldNames | undefined
|
|
2679
|
+
if (!silent) told = namesToldOf(animation, resolution, lastResolution)
|
|
2680
|
+
// Every key on one transition when they aren't split, as before; else each group's legs, tagged.
|
|
2681
|
+
// A key that stays starts none, and no keys make no leg (#325).
|
|
2682
|
+
let retargetLegs: ReadonlyArray<Leg> = NO_LEGS
|
|
2683
|
+
let legGroups: ReadonlyArray<number> | undefined
|
|
2684
|
+
if (Predicate.isUndefined(groups) && hasKeys(fresh)) {
|
|
2685
|
+
// Where every key is now: a Path's axis the target leaves out starts there (#438), and ends where
|
|
2686
|
+
// it rests (#574).
|
|
2687
|
+
retargetLegs = legsOf(from, fresh, transition, reduce, skipped, positionalKeys, defaultTransition, readVelocity, follow, manyStops, measured, snapshot.values, rests)
|
|
2688
|
+
}
|
|
2689
|
+
if (Predicate.isNotUndefined(groups)) {
|
|
2690
|
+
const split = splitLegsOf(fresh, from, groups, resolution, listed, transition, elementTransition, extraDelay, options.reuse, reduce, skipped, positionalKeys, defaultTransition, readVelocity, follow, manyStops, measured, snapshot.values, rests)
|
|
2691
|
+
retargetLegs = split.legs
|
|
2692
|
+
legGroups = split.tags
|
|
2693
|
+
}
|
|
2694
|
+
// The Handoff (#48, ADR-0044 decision 6): the first Retarget that starts picks the Appear
|
|
2695
|
+
// animation up as far in as its CSS has got, read at the legs' first tick.
|
|
2696
|
+
if (Predicate.isNotUndefined(handoff)) {
|
|
2697
|
+
const taken = handoff
|
|
2698
|
+
handoff = undefined
|
|
2699
|
+
checkHandoff(runtime, id, taken, { tag, from, to: fresh, transition: baseTransition, reduced: reduce, layout: isLayoutNode(c), stops: manyStops, ...skippedField(skipped), ...templateField(c.transformTemplate) })
|
|
2700
|
+
const at = startAtOf(taken, baseTransition, delayOf(transition))
|
|
2701
|
+
retargetLegs = Arr.map(retargetLegs, (one) => startingAt(one, at))
|
|
2702
|
+
}
|
|
2703
|
+
reporting += retargetLegs.length
|
|
2704
|
+
// A list's names (#297): told settled with the legs of the keys each drives, the ones a key that
|
|
2705
|
+
// stays carries too (#325), to the binding's callbacks and its orchestration, and each shut
|
|
2706
|
+
// one's legs waiting on its gate (PR 2).
|
|
2707
|
+
let legGates: ReadonlyArray<string | undefined> | undefined
|
|
2708
|
+
if (listed || Predicate.isNotUndefined(told)) {
|
|
2709
|
+
const carriedLegs = UndefinedOr.match(carried, { onUndefined: (): ReadonlyArray<Running> => NO_RUNNING, onDefined: identity })
|
|
2710
|
+
const listing = listingOf({
|
|
2711
|
+
listed,
|
|
2712
|
+
told,
|
|
2713
|
+
byName,
|
|
2714
|
+
resolution,
|
|
2715
|
+
groups,
|
|
2716
|
+
legGroups,
|
|
2717
|
+
legCount: retargetLegs.length,
|
|
2718
|
+
carriedGroups: Arr.map(carriedLegs, (running) => groupsOfLeg(running, groups, owners)),
|
|
2719
|
+
shut,
|
|
2720
|
+
hold: (g) => {
|
|
2721
|
+
const kept = pickKeys(from, groups, g)
|
|
2722
|
+
if (Bool.not(Rec.isEmptyRecord(kept))) gate.write(id, kept)
|
|
2723
|
+
},
|
|
2724
|
+
afterFlush: (fn) => gate.afterFlush(fn),
|
|
2725
|
+
config: c,
|
|
2726
|
+
})
|
|
2727
|
+
names = listing.names
|
|
2728
|
+
legGates = listing.legGates
|
|
2729
|
+
UndefinedOr.map(names, (settles) =>
|
|
2730
|
+
Arr.forEach(carriedLegs, (running, i) => {
|
|
2731
|
+
running.names = settles.carried(i)
|
|
2732
|
+
}))
|
|
2733
|
+
}
|
|
2734
|
+
// Its `style`'s will-change value turns on as a leg starts a key Motion's would (#443).
|
|
2735
|
+
turnWillChangeOn(live, retargetLegs)
|
|
2736
|
+
// The element's Key rules only for a change a Variable value is in (#542).
|
|
2737
|
+
const variables = holdsVariable(from, fresh, manyStops)
|
|
2738
|
+
// Each leg into its sink, in the builder's order (#69's PR 2: a Retarget is per element). Each
|
|
2739
|
+
// leg draws the keys it animates (#325) until a later Retarget takes them.
|
|
2740
|
+
Arr.forEach(retargetLegs, (leg: Leg, i): void => {
|
|
2741
|
+
if (Predicate.isNotUndefined(legGates)) gateNow = legGates[i]
|
|
2742
|
+
const running = runningOf(leg.to, resolution, landing, leg._tag)
|
|
2743
|
+
own(running)
|
|
2744
|
+
runLeg(running, legProgram(leg, running, variables))
|
|
2745
|
+
})
|
|
2746
|
+
// Each entry's Transition end (#428): a leg holding the keys only its end sets where they are,
|
|
2747
|
+
// waiting for the keys the entry animates to land (the legs drawing them now, this Retarget's or
|
|
2748
|
+
// carried), then setting its end values, the keys it also animates among them. It counts toward
|
|
2749
|
+
// this landing, so the landing includes it. A later Retarget that resolves its keys to the same
|
|
2750
|
+
// values carries it (#325); one that doesn't gives them up, and it never sets them.
|
|
2751
|
+
if (Predicate.isNotUndefined(ends)) {
|
|
2752
|
+
gateNow = undefined
|
|
2753
|
+
Arr.forEach(ends.byLayer, (writes, at) => {
|
|
2754
|
+
if (Predicate.isUndefined(writes)) return
|
|
2755
|
+
const owned = Rec.filter(freshEnds, (_, key) => ends.endOnly.get(key) === at)
|
|
2756
|
+
// Its end starts again when a key it also animates starts again, or its end for one changed
|
|
2757
|
+
// (#470, one already there starts none), or a key only it sets does.
|
|
2758
|
+
const anew = Bool.or(
|
|
2759
|
+
hasKeys(owned),
|
|
2760
|
+
Arr.some(Rec.keys(writes), (key) => Bool.and(Bool.not(ends.endOnly.has(key)), Bool.or(Object.hasOwn(fresh, key), endMoved(lastResolution, resolution, key)))),
|
|
2761
|
+
)
|
|
2762
|
+
if (Bool.not(anew)) return
|
|
2763
|
+
const animated = Arr.filter(Rec.keys(resolved), (key) => Bool.and(Bool.not(ends.endOnly.has(key)), driverOf(layers, key) === at))
|
|
2764
|
+
// Its keys set at their end, at once: no motion, so no velocity (#464).
|
|
2765
|
+
const running = runningOf(owned, resolution, landing, "Snap")
|
|
2766
|
+
own(running)
|
|
2767
|
+
// Held where they are: a key with nothing to start from (it would start at its target, no
|
|
2768
|
+
// motion) is left unset until its end, not set to it now.
|
|
2769
|
+
const holding = Rec.map(Rec.filter(owned, (_, key) => started[key] !== "target"), (_, key) => UndefinedOr.getOrThrow(snapshot.values[key]))
|
|
2770
|
+
if (hasKeys(holding)) gate.write(id, holding)
|
|
2771
|
+
snapshot.velocities = { ...snapshot.velocities, ...Rec.map(owned, () => 0) }
|
|
2772
|
+
// Counted toward this landing as a leg is (#508), with no fiber of its own: the landing of its
|
|
2773
|
+
// keys' last leg writes it, or the next flush when they've landed already.
|
|
2774
|
+
reporting++
|
|
2775
|
+
legs++
|
|
2776
|
+
const log = pendingLog
|
|
2777
|
+
pendingLog = undefined
|
|
2778
|
+
// Forked only where a Debug log is kept (#618); a leg's runs inside the leg's own fiber.
|
|
2779
|
+
if (Predicate.isNotUndefined(log)) {
|
|
2780
|
+
if (logsDebugOn(runtime)) runtime.runFork(log)
|
|
2781
|
+
}
|
|
2782
|
+
if (Predicate.isNotUndefined(names)) running.names = names.leg()
|
|
2783
|
+
// One that owns no key records the legs it waits for: their interrupt drops it (ADR-0057 Q1).
|
|
2784
|
+
let waitsOn: ReadonlyArray<Running | undefined> | undefined
|
|
2785
|
+
if (Bool.not(hasKeys(owned))) waitsOn = Arr.map(animated, (key) => owners.get(key))
|
|
2786
|
+
if (Predicate.isUndefined(waiting)) waiting = waitingEndsOf(owners, snapshot, gate, id, recordFrame)
|
|
2787
|
+
waiting.add({ running, animated, writes, legs: waitsOn, state: "waiting" })
|
|
2788
|
+
})
|
|
2789
|
+
}
|
|
2790
|
+
// A turn none of its legs draws now goes back (#460).
|
|
2791
|
+
turnBack()
|
|
2792
|
+
// The keys already there (#448), written with each key the Retarget moves at where it starts, as
|
|
2793
|
+
// its leg's first frame writes it: the element's first commit, which takes its Starting style off,
|
|
2794
|
+
// holds every key, whatever frame the legs first draw in (a gated name's draws later).
|
|
2795
|
+
if (Predicate.isNotUndefined(there)) {
|
|
2796
|
+
const written = { ...from, ...there }
|
|
2797
|
+
gate.write(id, written)
|
|
2798
|
+
recordFrame(written)
|
|
2799
|
+
}
|
|
2800
|
+
// The Retarget started, its legs on their way (#257); with none, its own or carried, it landed
|
|
2801
|
+
// at once (#325): its returns settle, and the element reports settled at the gate's next flush,
|
|
2802
|
+
// after the effects that start its orchestration's wait (ADR-0007).
|
|
2803
|
+
previous = lastResolution
|
|
2804
|
+
lastResolution = resolution
|
|
2805
|
+
const landed = legs === 0
|
|
2806
|
+
if (landed) {
|
|
2807
|
+
settleReturns(returns)
|
|
2808
|
+
leaveAfterFlush()
|
|
2809
|
+
if (Predicate.isNotUndefined(pendingLog)) {
|
|
2810
|
+
if (logsDebugOn(runtime)) runtime.runFork(pendingLog)
|
|
2811
|
+
}
|
|
2812
|
+
if (Predicate.isNotUndefined(onFrame)) {
|
|
2813
|
+
const tell = onFrame
|
|
2814
|
+
gate.afterFlush(() => {
|
|
2815
|
+
if (current === landing) tell(true)
|
|
2816
|
+
})
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
if (Predicate.isNotUndefined(animation) && !silent) {
|
|
2820
|
+
const hooks = animation as AnimationHooks
|
|
2821
|
+
hooks.started(resolution, previous, c)
|
|
2822
|
+
startedTold = true
|
|
2823
|
+
if (landed) hooks.settled(resolution, previous, c)
|
|
2824
|
+
if (Predicate.isNotUndefined(failedEarly)) tellFailed(failedEarly.error)
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// --- Live values in `style` (#130, ADR-0043) ------------------------------
|
|
2829
|
+
// A routed key (a Live value, or a transform key) is its `style` link, live: while no entry of
|
|
2830
|
+
// the Override stack sets it, no return is running to it and no drag moves it, the element
|
|
2831
|
+
// follows it, written through the gate on every change. A return hands it back here when it
|
|
2832
|
+
// settles (`settleReturns`). The Starting style showed each one's value at render; from mount
|
|
2833
|
+
// the gate holds them.
|
|
2834
|
+
const follow = (now: FollowNow): void => {
|
|
2835
|
+
if (Predicate.isUndefined(live)) return
|
|
2836
|
+
const routes = live
|
|
2837
|
+
// Built at the first routed key, at mount or later: an element whose `style` routes none builds nothing.
|
|
2838
|
+
routes.whenRouted(() => {
|
|
2839
|
+
// Read at each ask: `projected` is a getter while the track waits for `drag` (#551, #582).
|
|
2840
|
+
const dragHolds = (): boolean => Bool.and(Bool.not(options.projected), Predicate.isNotUndefined(options.dragAxis))
|
|
2841
|
+
// Whether something other than the follower moves `key` now.
|
|
2842
|
+
const owned = (key: string): boolean =>
|
|
2843
|
+
Bool.some([
|
|
2844
|
+
driven.has(key),
|
|
2845
|
+
Object.hasOwn(now.resolved(), key),
|
|
2846
|
+
// An Imperative animation's (#433).
|
|
2847
|
+
taken.has(key),
|
|
2848
|
+
// The drag direct-sets both translate keys while it, and its release, run (ADR-0004).
|
|
2849
|
+
Bool.and(dragHolds(), Bool.and(now.dragging(), Bool.or(key === "x", key === "y"))),
|
|
2850
|
+
])
|
|
2851
|
+
// Written as any frame is: into the gate, the Snapshot (at rest) and the presence record.
|
|
2852
|
+
const put = (values: Values.Values): void => {
|
|
2853
|
+
Arr.forEach(Rec.keys(values), (key) => {
|
|
2854
|
+
snapshot.values[key] = UndefinedOr.getOrThrow(values[key])
|
|
2855
|
+
delete snapshot.velocities[key]
|
|
2856
|
+
})
|
|
2857
|
+
gate.write(id, values)
|
|
2858
|
+
recordFrame(values)
|
|
2859
|
+
}
|
|
2860
|
+
handBack = (key) => {
|
|
2861
|
+
Option.match(Option.filter(routes.valueOf(key), isValue), {
|
|
2862
|
+
onNone: constVoid,
|
|
2863
|
+
onSome: (value) => put({ [key]: value }),
|
|
2864
|
+
})
|
|
2865
|
+
}
|
|
2866
|
+
// Every routed key nothing owns, with, before anything's committed, the values the Override
|
|
2867
|
+
// stack's keys start from, so a held start (a `when` gate) keeps showing them.
|
|
2868
|
+
const first = Rec.filter(routes.values(), (_, key) => Bool.not(owned(key)))
|
|
2869
|
+
if (hasKeys(first)) put({ ...Values.pick(snapshot.values, now.resolved()), ...first })
|
|
2870
|
+
return {
|
|
2871
|
+
change: (key, value) => {
|
|
2872
|
+
if (Bool.or(owned(key), Bool.not(isValue(value)))) return
|
|
2873
|
+
put({ [key]: value as number | string })
|
|
2874
|
+
},
|
|
2875
|
+
removed: (key) => {
|
|
2876
|
+
if (owned(key)) return
|
|
2877
|
+
delete snapshot.values[key]
|
|
2878
|
+
delete snapshot.velocities[key]
|
|
2879
|
+
if (Predicate.isNotUndefined(presence)) delete presence.snapshot.current[key]
|
|
2880
|
+
// Gone from `style`: the element's own value comes back, or the key's CSS value now.
|
|
2881
|
+
const restore = Option.match(OwnValues.styleValueOf(UndefinedOr.match(styleNow, { onUndefined: constUndefined, onDefined: (style) => style() }), key), {
|
|
2882
|
+
onNone: () => "",
|
|
2883
|
+
onSome: String,
|
|
2884
|
+
})
|
|
2885
|
+
gate.release(id, [key], { [key]: restore })
|
|
2886
|
+
},
|
|
2887
|
+
}
|
|
2888
|
+
})
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
const dispose = (): void => {
|
|
2892
|
+
// Unmounted, it lands in no Controls change (#433).
|
|
2893
|
+
leaveChange()
|
|
2894
|
+
if (Predicate.isNotUndefined(live)) live.stop()
|
|
2895
|
+
// The legs end normally first, so closing the Scope has nothing left to interrupt. A Transition
|
|
2896
|
+
// end waiting or due writes nothing (#508).
|
|
2897
|
+
if (Predicate.isNotUndefined(waiting)) waiting.dispose()
|
|
2898
|
+
// Each leg not landed ends as a Retarget ends one (#542): by its token, and interrupted if its
|
|
2899
|
+
// token didn't reach it. Nothing else ends them before the provider does.
|
|
2900
|
+
owners.forEach((running) => {
|
|
2901
|
+
if (Predicate.isUndefined(running)) return
|
|
2902
|
+
if (!running.done) endLeg(running)
|
|
2903
|
+
})
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
// A child back under its key (#286): its values and velocities from where the leaver is drawn, a
|
|
2907
|
+
// projected element's own translate among them (#363). Drawn at the end of the task, so nothing
|
|
2908
|
+
// paints the element at its `initial` first: not in the mount effect it's called in, where the
|
|
2909
|
+
// first commit's Starting-style switch would flush Solid 2 inside an effect.
|
|
2910
|
+
const seed = (from: PresenceSeed): void => {
|
|
2911
|
+
const values = from.values
|
|
2912
|
+
Object.assign(snapshot.values, values)
|
|
2913
|
+
snapshot.velocities = { ...snapshot.velocities, ...from.velocities }
|
|
2914
|
+
// Drawn now, by the leaver (#464).
|
|
2915
|
+
const now = snapshot.now()
|
|
2916
|
+
Arr.forEach(Rec.keys(from.velocities), (key) => {
|
|
2917
|
+
snapshot.drawnAt[key] = now
|
|
2918
|
+
})
|
|
2919
|
+
if (Predicate.isNotUndefined(presence)) Object.assign(presence.snapshot.current, values)
|
|
2920
|
+
Arr.forEach(Rec.keys(values), (key) => {
|
|
2921
|
+
driven.set(key, values[key])
|
|
2922
|
+
})
|
|
2923
|
+
queueMicrotask(() => gate.flushPass(() => gate.write(id, values)))
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
// --- Imperative animations (#433) -------------------------------------------
|
|
2927
|
+
/** A leg of an Imperative animation, forked as a Retarget's are; it lands on its track. */
|
|
2928
|
+
// Data-first: a pipe's curried forms are two closures more for every leg (#618, H1).
|
|
2929
|
+
const runImperative = (running: Running, program: Effect.Effect<unknown, never, WardicServices>): void => {
|
|
2930
|
+
running.fiber = runtime.runFork(Effect.asVoid(Effect.onExit(program, legEnded(running))))
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
const animate = (target: Target, given: WardicTransition | undefined, head: Playback.Playhead, hooks: ImperativeHooks): ImperativeTrack => {
|
|
2934
|
+
const to = target.values
|
|
2935
|
+
const before = UndefinedOr.match(lastResolution, { onUndefined: () => NO_VALUES, onDefined: (last) => last.resolved })
|
|
2936
|
+
const started: Record<string, string> = {}
|
|
2937
|
+
let measuring: Array<string> | undefined
|
|
2938
|
+
// Each key taken from whatever runs it, noted with its target now, and started where it's drawn.
|
|
2939
|
+
// Its velocity read now (#464), as its legs are built.
|
|
2940
|
+
readAt = snapshot.now()
|
|
2941
|
+
// Where a Path's axis this target leaves out rests (#574), before its keys are taken below.
|
|
2942
|
+
const rests = restsOf(owners, to)
|
|
2943
|
+
if (taken === NONE_TAKEN) taken = new Map()
|
|
2944
|
+
const takenNow = taken
|
|
2945
|
+
Arr.forEach(Rec.keys(to), (key) => {
|
|
2946
|
+
takenNow.set(key, before[key])
|
|
2947
|
+
returning.delete(key)
|
|
2948
|
+
const owner = owners.get(key)
|
|
2949
|
+
if (Predicate.isNotUndefined(owner)) giveUp(key, owner)
|
|
2950
|
+
if (!Object.hasOwn(initialValues, key)) ownValue(key)
|
|
2951
|
+
const like = UndefinedOr.getOrThrow(to[key])
|
|
2952
|
+
startKey(key, like, started)
|
|
2953
|
+
if (MeasuredPair.needs(key, UndefinedOr.getOrThrow(snapshot.values[key]), like)) {
|
|
2954
|
+
if (Predicate.isUndefined(measuring)) measuring = []
|
|
2955
|
+
measuring.push(key)
|
|
2956
|
+
}
|
|
2957
|
+
})
|
|
2958
|
+
keepTheRest()
|
|
2959
|
+
let from: Values.Values = Rec.map(to, (_, key) => UndefinedOr.getOrThrow(snapshot.values[key]))
|
|
2960
|
+
let stops: StopsByKey | undefined
|
|
2961
|
+
if (Predicate.isNotUndefined(target.keyframes)) {
|
|
2962
|
+
const begun = keyframesFrom(from, to, target.keyframes)
|
|
2963
|
+
from = begun.from
|
|
2964
|
+
stops = begun.stops
|
|
2965
|
+
}
|
|
2966
|
+
const reduce = options.reducedMotion()
|
|
2967
|
+
const skipped = skipAnimations()
|
|
2968
|
+
let measured: MeasuredPairs | undefined
|
|
2969
|
+
if (Bool.and(Bool.not(Bool.or(reduce, skipped)), Predicate.isNotUndefined(measuring))) measured = measuredOf(element, UndefinedOr.getOrThrow(measuring), from, to, snapshot.values, stops)
|
|
2970
|
+
// Built once: a seek or a replay runs the same legs, from the same start, with the same velocities.
|
|
2971
|
+
const built = legsOf(from, to, given, reduce, skipped, positionalKeys, defaultTransition, readVelocity, undefined, stops, measured, snapshot.values, rests)
|
|
2972
|
+
const variables = holdsVariable(from, to, stops)
|
|
2973
|
+
const legs = Arr.map(built, (leg) => mapMachine(leg, Playback.drive(head)))
|
|
2974
|
+
let runnings: ReadonlyArray<Running> = NO_RUNNING
|
|
2975
|
+
let left = 0
|
|
2976
|
+
const landing: Landing = {
|
|
2977
|
+
report: constVoid,
|
|
2978
|
+
settled: () => {
|
|
2979
|
+
left--
|
|
2980
|
+
if (left === 0) gate.afterFlush(hooks.settled)
|
|
2981
|
+
},
|
|
2982
|
+
failed: hooks.failed,
|
|
2983
|
+
}
|
|
2984
|
+
// Its legs ended where they are: the keys they draw are drawn by none.
|
|
2985
|
+
const end = (): void =>
|
|
2986
|
+
Arr.forEach(runnings, (running) => {
|
|
2987
|
+
forget(running)
|
|
2988
|
+
if (Bool.not(running.done)) endLeg(running)
|
|
2989
|
+
})
|
|
2990
|
+
const run = (): void => {
|
|
2991
|
+
end()
|
|
2992
|
+
left = legs.length
|
|
2993
|
+
turnWillChangeOn(live, legs)
|
|
2994
|
+
runnings = Arr.map(legs, (leg) => {
|
|
2995
|
+
const running = runningOf(leg.to, IMPERATIVE, landing, leg._tag)
|
|
2996
|
+
own(running)
|
|
2997
|
+
runImperative(running, legProgram(leg, running, variables))
|
|
2998
|
+
return running
|
|
2999
|
+
})
|
|
3000
|
+
// A target with no key lands at once.
|
|
3001
|
+
if (left === 0) gate.afterFlush(hooks.settled)
|
|
3002
|
+
}
|
|
3003
|
+
const cancel = (): void => {
|
|
3004
|
+
end()
|
|
3005
|
+
turnBack()
|
|
3006
|
+
snapshot.values = { ...snapshot.values, ...from }
|
|
3007
|
+
snapshot.velocities = { ...snapshot.velocities, ...Rec.map(from, () => 0) }
|
|
3008
|
+
// Put back at once: at rest, newer than the legs that let them go (#464).
|
|
3009
|
+
const now = snapshot.now()
|
|
3010
|
+
Arr.forEach(Rec.keys(from), (key) => {
|
|
3011
|
+
snapshot.drawnAt[key] = now
|
|
3012
|
+
})
|
|
3013
|
+
gate.write(id, from)
|
|
3014
|
+
recordFrame(from)
|
|
3015
|
+
}
|
|
3016
|
+
// Each leg's machine, run from its start to rest, apart from the Playhead, which it would move: a
|
|
3017
|
+
// blend's built with the element's Key rules.
|
|
3018
|
+
const duration = (): number =>
|
|
3019
|
+
Arr.reduce(built, 0, (longest, leg) => Math.max(longest, Machine.durationOf(machineOf(leg))))
|
|
3020
|
+
const machineOf = (leg: Leg): Machine.Machine<Frame.Frame<unknown>> => {
|
|
3021
|
+
if (leg._tag === "Blend") return runtime.runSync(Effect.suspend(leg.machine).pipe(InterpolatorRegistry.withKeyRules(elementRules)))
|
|
3022
|
+
return leg.machine() as Machine.Machine<Frame.Frame<unknown>>
|
|
3023
|
+
}
|
|
3024
|
+
const stop = (): void => {
|
|
3025
|
+
end()
|
|
3026
|
+
turnBack()
|
|
3027
|
+
}
|
|
3028
|
+
return { run, stop, cancel, duration }
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
// **Animation controls**' `stop` (#433): every running leg ends where it is, its keys given up, so
|
|
3032
|
+
// the next Retarget starts them from where they're drawn.
|
|
3033
|
+
const stop = (): void => {
|
|
3034
|
+
owners.forEach((running) => {
|
|
3035
|
+
if (Predicate.isUndefined(running)) return
|
|
3036
|
+
forget(running)
|
|
3037
|
+
if (Bool.not(running.done)) endLeg(running)
|
|
3038
|
+
})
|
|
3039
|
+
turnBack()
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
return { retarget, follow, dispose, seed, animate, stop }
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
|
|
3046
|
+
/**
|
|
3047
|
+
* `sink` with an `onCommit` that calls the config's current `onUpdate` (#233), when the config had
|
|
3048
|
+
* one at mount; `sink` as it is otherwise. `config` reads it untracked.
|
|
3049
|
+
*/
|
|
3050
|
+
export const withOnUpdate = (sink: FlushGate.Sink, atMount: WardConfig["onUpdate"], config: () => WardConfig): FlushGate.Sink => {
|
|
3051
|
+
// Per element at mount: most have none, and pay a check.
|
|
3052
|
+
if (Predicate.isUndefined(atMount)) return sink
|
|
3053
|
+
return {
|
|
3054
|
+
...sink,
|
|
3055
|
+
onCommit: (latest) => {
|
|
3056
|
+
const onUpdate = config().onUpdate
|
|
3057
|
+
if (Predicate.isNotUndefined(onUpdate)) onUpdate(latest as Readonly<Record<string, number | string>>)
|
|
3058
|
+
},
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
/**
|
|
3063
|
+
* `sink`, removing the element's Appear animation in its first commit (#48, ADR-0044 decision 6):
|
|
3064
|
+
* in the task that writes the engine's first frame, so no paint falls between. Without one, `sink`.
|
|
3065
|
+
*/
|
|
3066
|
+
export const releasingAppear = (sink: FlushGate.Sink, handoff: Handoff | undefined, element: HTMLElement): FlushGate.Sink => {
|
|
3067
|
+
if (Predicate.isUndefined(handoff)) return sink
|
|
3068
|
+
const before = sink.beforeFirstCommit
|
|
3069
|
+
return {
|
|
3070
|
+
...sink,
|
|
3071
|
+
beforeFirstCommit: () => {
|
|
3072
|
+
Appear.release(element)
|
|
3073
|
+
if (Predicate.isNotUndefined(before)) before()
|
|
3074
|
+
},
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
/**
|
|
3079
|
+
* A Handoff whose Appear animation the server sampled from another mount animation than the
|
|
3080
|
+
* client's: the app rendered differently on the server, or a function it gave has no easing name.
|
|
3081
|
+
* Noted once (cold path, `wardic.hydration`); the client's own animation goes on from the same time.
|
|
3082
|
+
*/
|
|
3083
|
+
const checkHandoff = (runtime: WardicRuntime, id: string, handoff: Handoff, mount: MountAnimation): void => {
|
|
3084
|
+
// A templated element's rule is named by its stops (#443), which the client doesn't sample to check.
|
|
3085
|
+
if (Predicate.isNotUndefined(mount.template)) return
|
|
3086
|
+
const same = Option.exists(keyOf(mount), (key) =>
|
|
3087
|
+
Appear.matches(handoff.animation, { key, tag: mount.tag, infinite: repeatsForever(mount.transition), projected: mount.layout }))
|
|
3088
|
+
if (same) return
|
|
3089
|
+
runtime.runFork(
|
|
3090
|
+
Effect.logDebug("wardic.hydration: the server's Appear animation was sampled from another mount animation than this element's; it goes on from the same time").pipe(
|
|
3091
|
+
Effect.annotateLogs({ id }),
|
|
3092
|
+
),
|
|
3093
|
+
)
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
/** The keys a Retarget returns to a Live value that's a number: each one's return follows it (#130). */
|
|
3097
|
+
const followingOf = (routes: LiveStyle, rest: Values.Values, returning: ReadonlyMap<string, BaseValue.BaseSource>): ReadonlyArray<string> =>
|
|
3098
|
+
Arr.filter(Rec.keys(rest), (key) => Bool.and(returning.get(key) === "style", Bool.and(routes.isLive(key), Predicate.isNumber(rest[key]))))
|
|
3099
|
+
|
|
3100
|
+
/** Where `key`'s Live value is now, a number; where it last was when it's none (#130). */
|
|
3101
|
+
const liveTargetOf = (routes: LiveStyle, key: string, start: number): (() => number) => {
|
|
3102
|
+
let last = start
|
|
3103
|
+
return () => {
|
|
3104
|
+
const now = Option.flatMap(routes.valueOf(key), (raw) => OwnValues.valueLike(raw, last))
|
|
3105
|
+
if (Option.isSome(now)) last = now.value as number
|
|
3106
|
+
return last
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
/** Whether a value is one an element can take: a number or a string. */
|
|
3111
|
+
const isValue = (value: unknown): value is number | string => Bool.or(Predicate.isNumber(value), Predicate.isString(value))
|
|
3112
|
+
|
|
3113
|
+
/**
|
|
3114
|
+
* Where `key` rests outside the gate (#130): its `initial`, else its `style` value (a Live value's
|
|
3115
|
+
* now), else a transform key's identity, else `undefined`.
|
|
3116
|
+
*/
|
|
3117
|
+
export const restingOf = (key: string, initial: Values.Values, style: object | string | undefined, read: (value: unknown) => unknown): unknown =>
|
|
3118
|
+
Option.getOrUndefined(
|
|
3119
|
+
Option.fromUndefinedOr(initial[key]).pipe(
|
|
3120
|
+
Option.orElse((): Option.Option<unknown> => Option.flatMap(OwnValues.styleValueOf(style, key), (raw) => Option.fromNullishOr(read(raw)))),
|
|
3121
|
+
Option.orElse((): Option.Option<unknown> => BaseValue.transformIdentity(key)),
|
|
3122
|
+
),
|
|
3123
|
+
)
|
|
3124
|
+
|
|
3125
|
+
/** A provider runtime's `AtomRegistry`, read on first need: an element with no Atom never reads it. */
|
|
3126
|
+
export const registryOf = (runtime: WardicRuntime): (() => AtomRegistry.AtomRegistry) => {
|
|
3127
|
+
let registry: AtomRegistry.AtomRegistry | undefined
|
|
3128
|
+
return () => {
|
|
3129
|
+
if (Predicate.isUndefined(registry)) registry = runtime.runSync(AtomRegistry.AtomRegistry)
|
|
3130
|
+
return registry
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
|
|
3134
|
+
/** Log, once per element, that a `transform` string gave way to the transform keys in its `style` (#130). */
|
|
3135
|
+
export const warnDroppedTransform = (runtime: WardicRuntime, id: string): void => {
|
|
3136
|
+
runtime.runFork(
|
|
3137
|
+
Effect.logWarning(
|
|
3138
|
+
"wardic.style: a `transform` string is dropped while `style` sets a transform key (x, scale, rotate…); " +
|
|
3139
|
+
"the keys win, as Motion's do. Give the transform as keys, or drop them",
|
|
3140
|
+
).pipe(Effect.annotateLogs({ id })),
|
|
3141
|
+
)
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
/**
|
|
3145
|
+
* Log, at debug level, the keys whose Starting style the Environment's Renderer projects
|
|
3146
|
+
* differently from the DOM's `project`, which rendered the server markup. Hydration keeps
|
|
3147
|
+
* the server's values (the Hydration hold); the Renderer's take over at the first frame.
|
|
3148
|
+
* Hydration only, once per element. Internal.
|
|
3149
|
+
*/
|
|
3150
|
+
export const flagStartingStyle = (runtime: WardicRuntime, id: string, server: Renderer.Patch, client: Renderer.Patch): void => {
|
|
3151
|
+
const keys = Arr.appendAll(
|
|
3152
|
+
Arr.filter(Rec.keys(server), (key) => String(server[key]) !== String(client[key])),
|
|
3153
|
+
Arr.filter(Rec.keys(client), (key) => Bool.not(key in server)),
|
|
3154
|
+
)
|
|
3155
|
+
if (Arr.isReadonlyArrayEmpty(keys)) return
|
|
3156
|
+
const pick = (patch: Renderer.Patch) => Object.fromEntries(keys.map((key) => [key, patch[key]]))
|
|
3157
|
+
runtime.runFork(
|
|
3158
|
+
Effect.logDebug(
|
|
3159
|
+
"wardic.hydration: the Environment's Renderer projects the Starting style differently from the DOM's; " +
|
|
3160
|
+
"hydration keeps the server's until the first frame, and an element that never animates keeps it",
|
|
3161
|
+
).pipe(
|
|
3162
|
+
Effect.annotateLogs({ id, keys, server: pick(server), client: pick(client) }),
|
|
3163
|
+
),
|
|
3164
|
+
)
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
/** An element's Appear animation on a server (#48, ADR-0044), as both bindings' server wards and component wraps use it. */
|
|
3168
|
+
export interface ServerAppearOf {
|
|
3169
|
+
/** Its inline `animation`, under the user's style: none when it plays none. */
|
|
3170
|
+
readonly style: { readonly animation: string } | undefined
|
|
3171
|
+
/** The context its children read: its variant node's, with its Appear group when it leads one. */
|
|
3172
|
+
readonly provided: WardicVariantContextValue | undefined
|
|
3173
|
+
/** Rules it out once it has rendered: its user style is read when `ward()` is called. */
|
|
3174
|
+
readonly veto: (reason: string) => void
|
|
3175
|
+
/**
|
|
3176
|
+
* Rendered after the shell (#245): in a late chunk, and its root when no late element is around
|
|
3177
|
+
* it, which puts the chunk's `<style>` after itself. `state` is the render's.
|
|
3178
|
+
*/
|
|
3179
|
+
readonly late: boolean
|
|
3180
|
+
readonly chunkRoot: boolean
|
|
3181
|
+
readonly state: ServerAppear | undefined
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
/** No Appear animation: a provider with `appear={false}`. */
|
|
3185
|
+
const NO_APPEAR_ENTRY = (provided: WardicVariantContextValue | undefined): ServerAppearOf => ({
|
|
3186
|
+
style: undefined,
|
|
3187
|
+
provided,
|
|
3188
|
+
veto: constVoid,
|
|
3189
|
+
late: false,
|
|
3190
|
+
chunkRoot: false,
|
|
3191
|
+
state: undefined,
|
|
3192
|
+
})
|
|
3193
|
+
|
|
3194
|
+
/**
|
|
3195
|
+
* An element's entry in the server render's Appear animations (#48, ADR-0044): its starting
|
|
3196
|
+
* values, its targets at mount (`animate`, or an inherited variant name against its `variants`),
|
|
3197
|
+
* its transition, its place in its parent's group, and its nearest `WardicConfig`'s reduced motion
|
|
3198
|
+
* (#434). `state` is the provider's, `undefined` with `appear={false}`: each binding reads it from its
|
|
3199
|
+
* own context.
|
|
3200
|
+
*/
|
|
3201
|
+
export const appearEntryOf = (
|
|
3202
|
+
state: ServerAppear | undefined,
|
|
3203
|
+
c: WardConfig,
|
|
3204
|
+
tag: string | undefined,
|
|
3205
|
+
initialValues: Values.Values,
|
|
3206
|
+
node: ServerVariantNode,
|
|
3207
|
+
quiet: boolean,
|
|
3208
|
+
reducedMotion: ReducedMotion.Config | undefined,
|
|
3209
|
+
skipAnimations = false,
|
|
3210
|
+
): ServerAppearOf =>
|
|
3211
|
+
UndefinedOr.match(state, {
|
|
3212
|
+
onUndefined: () => NO_APPEAR_ENTRY(node.provided),
|
|
3213
|
+
onDefined: (state) => {
|
|
3214
|
+
// The target's own transition (#277) drives its mount animation, and its orchestration.
|
|
3215
|
+
const target = serverTargetOf(c, node.inherited)
|
|
3216
|
+
const own = Option.getOrUndefined(Option.flatMap(target, (t) => Option.fromUndefinedOr(t.transition)))
|
|
3217
|
+
const entered = enterServer(state, {
|
|
3218
|
+
appear: c.appear,
|
|
3219
|
+
tag,
|
|
3220
|
+
initial: initialValues,
|
|
3221
|
+
to: Option.map(target, (t) => t.values),
|
|
3222
|
+
keyframes: Option.getOrUndefined(Option.flatMap(target, (t) => Option.fromUndefinedOr(t.keyframes))),
|
|
3223
|
+
quiet,
|
|
3224
|
+
transition: transitionOfTarget(own, ownOrInherited(c.transition, node.inherited)),
|
|
3225
|
+
layout: isLayoutNode(c),
|
|
3226
|
+
template: c.transformTemplate,
|
|
3227
|
+
group: Option.flatMap(Option.fromUndefinedOr(node.inherited), (inherited) => Option.fromUndefinedOr(inherited.appearGroup)),
|
|
3228
|
+
leads: hasOwnVariants(c),
|
|
3229
|
+
ownTransition: transitionOfTarget(own, c.transition),
|
|
3230
|
+
reducedMotion,
|
|
3231
|
+
skipAnimations,
|
|
3232
|
+
})
|
|
3233
|
+
return {
|
|
3234
|
+
style: Option.match(entered.animation, { onNone: constUndefined, onSome: (animation) => ({ animation }) }),
|
|
3235
|
+
provided: Option.match(entered.group, {
|
|
3236
|
+
onNone: () => node.provided,
|
|
3237
|
+
onSome: (appearGroup) => UndefinedOr.map(node.provided, (provided): WardicVariantContextValue => ({ ...provided, appearGroup })),
|
|
3238
|
+
}),
|
|
3239
|
+
veto: entered.veto,
|
|
3240
|
+
late: entered.late,
|
|
3241
|
+
chunkRoot: entered.chunkRoot,
|
|
3242
|
+
state,
|
|
3243
|
+
}
|
|
3244
|
+
},
|
|
3245
|
+
})
|
|
3246
|
+
|
|
3247
|
+
/**
|
|
3248
|
+
* An element's target at mount on a server: its `animate`, else an inherited variant name resolved
|
|
3249
|
+
* against its own `variants`, a dynamic one with its `custom` and no values yet (#277).
|
|
3250
|
+
*/
|
|
3251
|
+
export const serverTargetOf = (c: WardConfig, inherited: WardicVariantContextValue | undefined): Option.Option<Target> =>
|
|
3252
|
+
UndefinedOr.match(c.animate as Entry, {
|
|
3253
|
+
onDefined: (own) => Option.some(resolveTarget(own, c.variants, c.custom, NO_VARIANT_STATE)),
|
|
3254
|
+
onUndefined: () =>
|
|
3255
|
+
Option.map(
|
|
3256
|
+
Option.flatMap(Option.fromUndefinedOr(inherited), (context) => Option.fromUndefinedOr(context.variant())),
|
|
3257
|
+
(name) => resolveTarget(name, c.variants, c.custom, NO_VARIANT_STATE),
|
|
3258
|
+
),
|
|
3259
|
+
})
|
|
3260
|
+
|
|
3261
|
+
/**
|
|
3262
|
+
* A server's Starting style for an element whose `style` has routed keys (#130): the projected
|
|
3263
|
+
* `initial` with their values under it. With none, its Starting style as it was.
|
|
3264
|
+
*/
|
|
3265
|
+
export const serverStartingStyle = (
|
|
3266
|
+
tag: string | undefined,
|
|
3267
|
+
starting: Renderer.Patch,
|
|
3268
|
+
initialValues: Values.Values,
|
|
3269
|
+
routed: Readonly<Record<string, unknown>>,
|
|
3270
|
+
layout: boolean,
|
|
3271
|
+
untrack: Reactivity["untrack"],
|
|
3272
|
+
template: WardConfig["transformTemplate"],
|
|
3273
|
+
): Renderer.Patch =>
|
|
3274
|
+
Bool.match(Rec.isEmptyRecord(routed), {
|
|
3275
|
+
onTrue: () => starting,
|
|
3276
|
+
onFalse: () => Style.starting(tag, drawnAtRest({ ...valuesOf(routed, serverRegistryOf, untrack), ...initialValues }, layout, template)).style,
|
|
3277
|
+
})
|
|
3278
|
+
|
|
3279
|
+
/** Whether a config makes its element a layout node: `layout` (or a mode), or a `layoutId`. */
|
|
3280
|
+
export const isLayoutNode = (c: WardConfig): boolean =>
|
|
3281
|
+
Bool.or(Bool.and(Predicate.isNotUndefined(c.layout), c.layout !== false), Predicate.isNotUndefined(c.layoutId))
|
|
3282
|
+
|
|
3283
|
+
/**
|
|
3284
|
+
* The values a layout node's Starting style is drawn from (#245, ADR-0044 *Amendment (#245)*):
|
|
3285
|
+
* through its projection at rest, as its first commit draws it, its own translate on top (#363).
|
|
3286
|
+
* Any other element's, as they are. A templated element's, with its Transform template (#443).
|
|
3287
|
+
*/
|
|
3288
|
+
export const drawnAtRest = (values: Values.Values, layout: boolean, template: WardConfig["transformTemplate"]): Readonly<Record<string, unknown>> => {
|
|
3289
|
+
// Per mount (#460): guards, nothing built for an element neither a layout node nor templated.
|
|
3290
|
+
if (Bool.not(layout)) return withTemplate(values, template)
|
|
3291
|
+
return withTemplate({ ...values, [Projection.KEY]: Projection.AT_REST }, template)
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
/** `values` with `template` under the renderer's reserved key, as its projection rides (#443); as they are with none. */
|
|
3295
|
+
export const withTemplate = <V extends Readonly<Record<string, unknown>>>(values: V, template: WardConfig["transformTemplate"]): V => {
|
|
3296
|
+
if (Predicate.isUndefined(template)) return values
|
|
3297
|
+
return { ...values, [DomRenderer.TEMPLATE_KEY]: template }
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
/**
|
|
3301
|
+
* What an element registers with in the gate (#443): its Transform template at mount, under the
|
|
3302
|
+
* renderer's reserved key, drawn with its first write (a write of its own would take its Starting
|
|
3303
|
+
* style off early). Nothing with none.
|
|
3304
|
+
*/
|
|
3305
|
+
export const registeredValuesOf = (template: WardConfig["transformTemplate"]): Readonly<Record<string, unknown>> | undefined => {
|
|
3306
|
+
if (Predicate.isUndefined(template)) return undefined
|
|
3307
|
+
return { [DomRenderer.TEMPLATE_KEY]: template }
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
/**
|
|
3311
|
+
* What an element's projection node is held at for a Measure pass (#443, Motion's
|
|
3312
|
+
* `transformTemplate(values, "")`): its Transform template's, from its values now, when it had one
|
|
3313
|
+
* at mount. `undefined` otherwise, so a node without one pays a field read a pass.
|
|
3314
|
+
*/
|
|
3315
|
+
export const heldTransformFor = (
|
|
3316
|
+
snapshot: Snapshot,
|
|
3317
|
+
atMount: WardConfig["transformTemplate"],
|
|
3318
|
+
template: () => WardConfig["transformTemplate"],
|
|
3319
|
+
): (() => string) | undefined => {
|
|
3320
|
+
if (Predicate.isUndefined(atMount)) return undefined
|
|
3321
|
+
return () => DomRenderer.heldTransformOf(withTemplate(snapshot.values, template()))
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
/**
|
|
3325
|
+
* Follow an element's Transform template after mount (#443): `write` is called with each other one
|
|
3326
|
+
* its config gives, and draws it at the next frame. Only for an element with one at mount, as
|
|
3327
|
+
* `onUpdate` (#233): its caller asks only then, so another makes nothing (#460). Returns the stop.
|
|
3328
|
+
*/
|
|
3329
|
+
export const followTemplate = (
|
|
3330
|
+
atMount: DomRenderer.TransformTemplate,
|
|
3331
|
+
template: () => WardConfig["transformTemplate"],
|
|
3332
|
+
watch: Reactivity["watch"],
|
|
3333
|
+
write: (template: WardConfig["transformTemplate"]) => void,
|
|
3334
|
+
): (() => void) => {
|
|
3335
|
+
let last: WardConfig["transformTemplate"] = atMount
|
|
3336
|
+
return watch(template, (next) => {
|
|
3337
|
+
if (next === last) return
|
|
3338
|
+
last = next
|
|
3339
|
+
write(next)
|
|
3340
|
+
})
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
/** A mount animation's template field (#443): left out with none, as its key reads it. */
|
|
3344
|
+
export const templateField = (template: WardConfig["transformTemplate"]): { readonly template?: DomRenderer.TransformTemplate } =>
|
|
3345
|
+
UndefinedOr.match(template, { onUndefined: () => ({}), onDefined: (t) => ({ template: t }) })
|
|
3346
|
+
|
|
3347
|
+
/** What a `<Reorder>` item adds to an element's drag: its gesture's axis, read at each drag's start. */
|
|
3348
|
+
interface DragInternals {
|
|
3349
|
+
readonly dragAxis?: (() => boolean | "x" | "y") | undefined
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
/** A plain element's drag internals: none. */
|
|
3353
|
+
const NO_DRAG_INTERNALS: DragInternals = {}
|
|
3354
|
+
|
|
3355
|
+
/**
|
|
3356
|
+
* The drag axis a spine runs with: its static `drag` config, else `true` for a
|
|
3357
|
+
* `<Reorder>` item (its gesture's axis comes from `dragAxis`), else none.
|
|
3358
|
+
*/
|
|
3359
|
+
export const dragAxisOf = (config: WardConfig, internal: DragInternals = NO_DRAG_INTERNALS): boolean | "x" | "y" | undefined =>
|
|
3360
|
+
Option.getOrUndefined(
|
|
3361
|
+
Option.orElse(Option.filter(Option.fromUndefinedOr(config.drag), (drag) => drag !== false), () =>
|
|
3362
|
+
Option.map(Option.fromUndefinedOr(internal.dragAxis), (): true => true)),
|
|
3363
|
+
)
|
|
3364
|
+
|
|
3365
|
+
/**
|
|
3366
|
+
* A drag element's defaults (ADR-0019 *Amendment (Reorder)*): its axis's, unless its own presses don't
|
|
3367
|
+
* drag it (`dragListener={false}`, #431), as Motion's come with its listener.
|
|
3368
|
+
*/
|
|
3369
|
+
export const dragDefaultsOf = (config: WardConfig, internal: DragInternals = NO_DRAG_INTERNALS): Style.DragDefaults | undefined => {
|
|
3370
|
+
if (config.dragListener === false) return undefined
|
|
3371
|
+
return UndefinedOr.map(dragAxisOf(config, internal), Style.dragDefaults)
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
/**
|
|
3375
|
+
* Set each of a drag element's defaults its inline style doesn't already set, and
|
|
3376
|
+
* `draggable="false"` unless it has the attribute, at mount: for an element whose props don't carry
|
|
3377
|
+
* them, a directive's (`use:wardic`, `use:w`) and a `wardic.create(Component)` wrap's, whose
|
|
3378
|
+
* component may not pass them on.
|
|
3379
|
+
*/
|
|
3380
|
+
export const applyDragDefaults = (element: HTMLElement, defaults: Style.DragDefaults): void => {
|
|
3381
|
+
Arr.forEach(Rec.keys(defaults), (key) => {
|
|
3382
|
+
if (element.style.getPropertyValue(key) === "") element.style.setProperty(key, UndefinedOr.getOrThrow(defaults[key]))
|
|
3383
|
+
})
|
|
3384
|
+
if (!element.hasAttribute("draggable")) element.setAttribute("draggable", Style.DRAG_DEFAULT_ATTRIBUTES.draggable)
|
|
3385
|
+
}
|