@weasel-js/core 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -0
- package/README.md +9 -5
- package/dist/animation/useAnimator.js +20 -13
- package/dist/animation/useAnimator.js.map +1 -1
- package/dist/{builtins-BW32dOXy.d.ts → builtins-CQcvcLXh.d.ts} +105 -31
- package/dist/canvas/DrawCanvas.js +33 -33
- package/dist/canvas/DrawCanvas.js.map +1 -1
- package/dist/canvas/MinimapCanvas.js +29 -29
- package/dist/canvas/MinimapCanvas.js.map +1 -1
- package/dist/canvas/NodeShape.js +63 -54
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/SceneCanvas/features.js +26 -5
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +3 -2
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +526 -529
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SceneViewCanvas.js +35 -35
- package/dist/canvas/SceneViewCanvas.js.map +1 -1
- package/dist/canvas/defaultDrawOne.js +6 -1
- package/dist/canvas/defaultDrawOne.js.map +1 -1
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/useHitObserver.js +23 -0
- package/dist/canvas/useHitObserver.js.map +1 -0
- package/dist/canvas/useHostAnchor.js.map +1 -1
- package/dist/core/device/useDeviceProfile.js.map +1 -1
- package/dist/core/scene/derivedPath.js.map +1 -1
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/kitRegistry.js.map +1 -1
- package/dist/core/scene/scene.js +9 -0
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/scene/useScene.js.map +1 -1
- package/dist/core/selection/useSelection.js +27 -33
- package/dist/core/selection/useSelection.js.map +1 -1
- package/dist/core/stableStringify.js +8 -0
- package/dist/core/stableStringify.js.map +1 -0
- package/dist/core/viewport/fitViewToBounds.js +3 -3
- package/dist/core/viewport/fitViewToBounds.js.map +1 -1
- package/dist/core/viewport/screenSlop.js +8 -2
- package/dist/core/viewport/screenSlop.js.map +1 -1
- package/dist/features/minimap/actions.js.map +1 -1
- package/dist/features/paths/markerCommands.js +95 -63
- package/dist/features/paths/markerCommands.js.map +1 -1
- package/dist/features/paths/tessellate/dash.js +52 -0
- package/dist/features/paths/tessellate/dash.js.map +1 -0
- package/dist/features/paths/tessellate/insets.js +32 -0
- package/dist/features/paths/tessellate/insets.js.map +1 -0
- package/dist/features/paths/tessellate/metric.js +72 -0
- package/dist/features/paths/tessellate/metric.js.map +1 -0
- package/dist/features/paths/tessellate/stroke.js +74 -134
- package/dist/features/paths/tessellate/stroke.js.map +1 -1
- package/dist/features/paths/tessellate/vertexWidths.js +32 -0
- package/dist/features/paths/tessellate/vertexWidths.js.map +1 -0
- package/dist/features/pointer/PointerContext.js +2 -2
- package/dist/features/pointer/PointerContext.js.map +1 -1
- package/dist/features/poseRun/usePoseRun.js.map +1 -1
- package/dist/features/text/useTextEdit.js +86 -84
- package/dist/features/text/useTextEdit.js.map +1 -1
- package/dist/index.d.ts +84 -45
- package/dist/index.js +269 -268
- package/dist/interactions/actions/defaults/align.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/booleans.js.map +1 -1
- package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
- package/dist/interactions/actions/defaults/clearSelection.js.map +1 -1
- package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
- package/dist/interactions/actions/defaults/clone.js.map +1 -1
- package/dist/interactions/actions/defaults/createOutlines.js +1 -1
- package/dist/interactions/actions/defaults/createOutlines.js.map +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/distribute.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/editAnchors.js.map +1 -1
- package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/escape.js +1 -1
- package/dist/interactions/actions/defaults/escape.js.map +1 -1
- package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/flip.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insert.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
- package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/move.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
- package/dist/interactions/actions/defaults/reorder.js.map +1 -1
- package/dist/interactions/actions/defaults/requiresSelection.js.map +1 -1
- package/dist/interactions/actions/defaults/resize.js.map +1 -1
- package/dist/interactions/actions/defaults/rotate.js.map +1 -1
- package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
- package/dist/interactions/actions/defaults/slice.js.map +1 -1
- package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
- package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
- package/dist/interactions/actions/defaults/undoRedo.js +1 -1
- package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
- package/dist/interactions/actions/planeInput.js.map +1 -1
- package/dist/interactions/gestures/shared/screenTolerance.js.map +1 -1
- package/dist/{registerProgram-ateHDGjw.d.ts → registerProgram-BMZRDgrh.d.ts} +4 -0
- package/dist/renderer/cache/markerCommandCache.js +46 -42
- package/dist/renderer/cache/markerCommandCache.js.map +1 -1
- package/dist/renderer/cache/outlineStrokeMeshCache.js +30 -25
- package/dist/renderer/cache/outlineStrokeMeshCache.js.map +1 -1
- package/dist/renderer/cache/strokeMeshCache.js +42 -31
- package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
- package/dist/renderer/cullDrawCommands.js +32 -31
- package/dist/renderer/cullDrawCommands.js.map +1 -1
- package/dist/renderer/draw.js +315 -362
- package/dist/renderer/draw.js.map +1 -1
- package/dist/renderer/drawBatch.js.map +1 -1
- package/dist/renderer/glyphOutlineMesh.js +67 -0
- package/dist/renderer/glyphOutlineMesh.js.map +1 -0
- package/dist/renderer/math/mat3.js +8 -3
- package/dist/renderer/math/mat3.js.map +1 -1
- package/dist/renderer/shaders/batchFill.js +3 -2
- package/dist/renderer/shaders/batchFill.js.map +1 -1
- package/dist/renderer/strokeLengths.js +50 -0
- package/dist/renderer/strokeLengths.js.map +1 -0
- package/dist/renderer.d.ts +2 -2
- package/dist/{test-seams-fYf6m5pu.d.ts → test-seams-CuIxe0xY.d.ts} +1 -1
- package/dist/test-seams.d.ts +2 -2
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +1 -1
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/select/selectionContributions.js +43 -23
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +17 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,89 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c06cc26: The built-in actions and tools read their deps at the types `DepSchema` declares instead of casting each one. `gestureViewReader` and `gesturePlaneReader` take `Pick<ActionDeps, 'view'>`, so a `view` that is not a `ViewApi` is a type error rather than a runtime one.
|
|
8
|
+
- dc5bcfc: `@weasel-js/react` exports `assignRef(ref, value)`, which writes a value into a ref prop whether it is a callback ref or an object ref. `<DrawCanvas>`, `<SceneViewCanvas>`, `<MinimapCanvas>`, `<SceneCanvas>` and `ItemList` forward their refs through it.
|
|
9
|
+
|
|
10
|
+
A callback ref passed as `<SceneViewCanvas canvasRef>` or `<MinimapCanvas canvasRef>` is now called only when the canvas attaches or detaches, or when the ref itself changes. It used to be called with `null` and then the element again on every render.
|
|
11
|
+
- 0b8d6f8: Core depends on `@msb235/blits` 0.6.0, where it pinned 0.4.0. An app that depends on blits 0.6.0 itself, as klieg does, now installs one copy rather than two. Nothing core exports changes.
|
|
12
|
+
- 0004749: `<SceneCanvas features>` takes four finer presets. `select` mounts the select tool and `outline` draws the selection outline, so a canvas supplying its own select tool can name `outline` without the built-in one mounting beside it. `resize` and `rotate` each draw and bind one kind of handle, so a resize-only canvas no longer hides the rotation handle by hand. The existing presets keep their meaning as abbreviations: `pick` is `select` + `outline`, `transform` is `resize` + `rotate`, and `draw` is every base preset. `COMPOSITE_FEATURES` lists what each abbreviates, and the new `selectionResizeContribution` / `selectionRotateContribution` (with their `…Bindings` and ids) are the ambient entries the two handle presets install; `selectionTransformContribution` still carries both.
|
|
13
|
+
|
|
14
|
+
Additive for any canvas that names presets, but `FEATURE_ACTION_IDS` and the `BaseFeature` type are now keyed by the base presets — `select`, `outline`, `resize`, `rotate` in place of `pick` and `transform` — so code indexing that table by `pick` or `transform` has to expand through `COMPOSITE_FEATURES` first.
|
|
15
|
+
- 1b22863: `fitViewToBounds` takes `anchor: 'start'`, which shows the left or top edge of bounds that overflow the viewport instead of their middle. `DiagramView` passes it through, alongside a new `minScale` floor on its initial fit.
|
|
16
|
+
- ad0da6c: How big a mesh may be and still join a draw batch now depends on whether a batch is open when it arrives: up to 1,024 vertices when one is, and up to 96 when none is, where it used to be 256 either way. A mesh among rects or other batched fills draws with them far more often, and back-to-back meshes between 96 and 256 vertices draw on their own.
|
|
17
|
+
- 2b03077: The text edit overlay sets `text-rendering: geometricPrecision` for faces the kit lays out itself, so it keeps the canvas's fractional glyph advances on Linux Chromium. Before, Chromium there rounded every advance to a whole pixel and the overlay ran ahead of the canvas glyphs as a line got longer: 3px over "Small Caps" at 24px. A family served by the canvas-font tier keeps the browser's default rendering, which is what the canvas measures it with.
|
|
18
|
+
- 718769e: A `{ px }` stroke is now that many pixels wide in every direction under a non-uniform transform. It used to resolve through the mean of the two axis scales, so at a 4:1 view a 1px line painted 0.5px tall running across and 2px wide running down, and a rect outline, an ellipse or a diagonal came out uneven. The ribbon is built after the transform's stretch and mapped back to world, so it still batches and aligns like any other stroke. Marker heads on a `{ px }` stroke, and their outlines, are built the same way.
|
|
19
|
+
|
|
20
|
+
Dashes on a `{ px }` stroke are now screen pixels, as SVG's `non-scaling-stroke` (which a `{ px }` width serializes to) measures them; they used to be world units that grew and shrank with the zoom. A stroke with a world-unit width keeps world-unit dashes.
|
|
21
|
+
|
|
22
|
+
Picking and paint bounds follow. `NodeInk` gains `outsetPx` and `insetPx`, a reach measured on screen; the built-in painters report a `{ px }` stroke's reach there and leave `outset` and `inset` at 0 for it, so code that read a `{ px }` stroke's reach from `outset` must add `outsetPx`. `NodeInkCtx` gains `leastScale`, which `defaultPaintBounds` fills with the smaller axis scale so a node's paint box covers a `{ px }` stroke along the axis zoomed least, and culling measures the same way.
|
|
23
|
+
- 3088756: Every part of a stroke is now built in the units its own size is given in, so each stays exact under a non-uniform transform.
|
|
24
|
+
|
|
25
|
+
- A `{ px }` stroke on outlined text is that many pixels wide in every direction, as a path's is; at a 4:1 view a 1px outline used to paint 2px across and half a pixel down. A glyph stroke's dashes are in the width's units — world, or screen pixels on a `{ px }` stroke — where they used to be read in em, so a dash pattern on text came out solid.
|
|
26
|
+
- A `{ px }`-sized marker head on a world-width stroke is that many pixels in every direction. It used to resolve through the mean scale and come out stretched.
|
|
27
|
+
- On a `{ px }` stroke, a head with a world size and every `vertexWidths` entry are world lengths, stretched by the view like any world geometry. They used to be read as lengths in the stroke's screen space. A head with no size still takes the stroke width.
|
|
28
|
+
|
|
29
|
+
Picking and culling follow each head's units: `NodeInk.outset` and `inset` carry what is built in world, `outsetPx` and `insetPx` what is built on screen, so a world-sized head on a `{ px }` stroke now reaches in `outset` and a `{ px }` head on a world-width stroke in `outsetPx`.
|
|
30
|
+
- 63d0bf8: `<SceneCanvas onClick>` reports every click with the node under it, after the click's own behavior has run, including a click on a node that is already picked. `DiagramView` uses it, so `onSelect` fires on a re-click, and it now pans on a plain drag as well as the wheel. `diagramScene` throws on a repeated node id and drops a repeated edge with the same ends and label.
|
|
31
|
+
- 4146713: A concretely typed `Scene<MyData, MyLayer, MyPose>` is now assignable to `Scene<unknown, string, unknown>`, the type the `scene` dep and the built-in actions take, so passing one no longer needs `as unknown as`. The pose callbacks a scene carries — `clipFromPose`, `derivePath`, `derivePose`, on nodes, on `AddNodeSpec` and in `SceneRegistry` — are checked the way method parameters are, which is what had made every typed scene unassignable. A callback written against an unrelated pose type is still a type error where the scene is built; one that asks for a narrower pose than the scene's is no longer caught. The clip callback's type is exported as `ClipFromPoseFn`, beside `DerivePathFn` and `DerivePoseFn`.
|
|
32
|
+
- 24b2eaf: `SceneRegistry`, `DerivedDep`, `DerivePathFn` and `DerivePoseFn` take the scene's data and layer types after the pose (`SceneRegistry<TPose, TData, TLayer>`), defaulting to `unknown` and `string`. A `derivePath` or `derivePose` on a `Scene<MyData, MyLayer, MyPose>`, or in its registry, now reads its node's and its dependencies' `data` and `layer` typed instead of casting. `withKitRegistry` and `withDiagramRegistry` carry the same parameters through. Existing `SceneRegistry<TPose>` spellings are unchanged.
|
|
33
|
+
- Updated dependencies [c06cc26]
|
|
34
|
+
- Updated dependencies [dc5bcfc]
|
|
35
|
+
- Updated dependencies [718769e]
|
|
36
|
+
- Updated dependencies [63d0bf8]
|
|
37
|
+
- Updated dependencies [3088756]
|
|
38
|
+
- @weasel-js/routing@1.9.0
|
|
39
|
+
- @weasel-js/react@1.9.0
|
|
40
|
+
- @weasel-js/paint@1.9.0
|
|
41
|
+
- @weasel-js/text@1.9.0
|
|
42
|
+
- @weasel-js/cursor@1.9.0
|
|
43
|
+
- @weasel-js/font@1.9.0
|
|
44
|
+
- @weasel-js/geom@1.9.0
|
|
45
|
+
- @weasel-js/gestures@1.9.0
|
|
46
|
+
- @weasel-js/history@1.9.0
|
|
47
|
+
- @weasel-js/modes@1.9.0
|
|
48
|
+
- @weasel-js/quantity@1.9.0
|
|
49
|
+
- @weasel-js/registry@1.9.0
|
|
50
|
+
- @weasel-js/select@1.9.0
|
|
51
|
+
|
|
52
|
+
## 1.8.1
|
|
53
|
+
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- e07c4ca: An animation whose `onTick`, `onDone` or interpolator throws no longer stops the animator. The error is logged, that animation is removed, and every other animation keeps running — before, the rest of that frame's animations were skipped and no further frame was requested, while `isActive()` stayed true. `animator.watch` delivers a new `error` event carrying the thrown value, in place of `cancel`, or after `end` when `onDone` threw. A listener that switches exhaustively on `AnimatorEvent['type']` has one more case to handle.
|
|
57
|
+
- 934f195: The gesture dispatcher no longer takes a press that lands on a control inside its host — a button, field, link or anything with a control role. It used to open a pointer session that captured the pointer, so the browser delivered the click to the host instead, and a button laid over a canvas (labkit's instrument overlay, for one) could not be clicked with a real pointer. `isInControlWithin` and `CONTROL_SELECTOR`, which answer that question, move from weasel-ui's internals to `@weasel-js/gestures`, re-exported by routing and core.
|
|
58
|
+
- bf522cf: labkit: a lab can present one trial and nothing else — no lab chrome, no trial chrome, a transparent ground — for embedding it as a figure. `<Lab present>` or `?present` in the URL starts it that way, opened on `seed={{ instrument, config, state, view }}`; a stored lab then keeps its own records under `storageKey` + `':present'`, and a changed seed reopens the trial for returning visitors. `usePresentation()` gives `{ active, enter, exit }` inside any lab: `enter` presents the focused trial, Escape returns, and nothing remounts either way. While presenting, the page's root `color-scheme` is reset so an iframe embed stays transparent.
|
|
59
|
+
|
|
60
|
+
core: `stableStringify`, JSON with object keys sorted, moves here from forge, which re-exports it.
|
|
61
|
+
- c49c9e0: New package `@weasel-js/select`: selection as a value. `intentOf(modifiers, policy)` reads a press as `'replace'`, `'toggle'` or `'range'` under a policy naming which keys toggle and which range, and `select(state, id, intent, { order, eligible })` applies it to `{ ids, anchor }`. Ineligible ids — locked or disabled rows — are skipped by a range, dropped when a selection grows, and selected alone when pressed. `createSelectionStore` keeps ids outside a component, behind the `SelectionStore` contract, which core still exports under the same name.
|
|
62
|
+
|
|
63
|
+
core's `useSelection`, and `Tree` and `LayerList` in `@weasel-js/ui`, now select through it rather than each keeping its own rules. `SelectionMode` and `SelectionExtendKey` are declared in `@weasel-js/select` and still exported from `@weasel-js/routing` and `@weasel-js/core`. Two edges change: `LayerList`'s Shift+arrow with its anchor hidden in a collapsed branch now selects the row it reaches instead of doing nothing, and a Cmd/Ctrl-press in a multi-select `Tree` drops any disabled row from the selection it builds on.
|
|
64
|
+
- 2123049: A wheel the gesture dispatcher claims no longer reaches the element's other wheel listeners. It listens in the capture phase and stops the event there, so a labkit loupe over a canvas with its own camera controls — three.js `OrbitControls`, say — zooms the lens alone instead of the lens and the camera together. A wheel no binding claims still reaches them.
|
|
65
|
+
- Updated dependencies [934f195]
|
|
66
|
+
- Updated dependencies [7e72192]
|
|
67
|
+
- Updated dependencies [412e53a]
|
|
68
|
+
- Updated dependencies [6d800fb]
|
|
69
|
+
- Updated dependencies [2d97065]
|
|
70
|
+
- Updated dependencies [f9c137b]
|
|
71
|
+
- Updated dependencies [c49c9e0]
|
|
72
|
+
- Updated dependencies [2123049]
|
|
73
|
+
- @weasel-js/gestures@1.8.1
|
|
74
|
+
- @weasel-js/routing@1.8.1
|
|
75
|
+
- @weasel-js/geom@1.8.1
|
|
76
|
+
- @weasel-js/history@1.8.1
|
|
77
|
+
- @weasel-js/select@1.8.1
|
|
78
|
+
- @weasel-js/text@1.8.1
|
|
79
|
+
- @weasel-js/cursor@1.8.1
|
|
80
|
+
- @weasel-js/font@1.8.1
|
|
81
|
+
- @weasel-js/modes@1.8.1
|
|
82
|
+
- @weasel-js/paint@1.8.1
|
|
83
|
+
- @weasel-js/quantity@1.8.1
|
|
84
|
+
- @weasel-js/react@1.8.1
|
|
85
|
+
- @weasel-js/registry@1.8.1
|
|
86
|
+
|
|
3
87
|
## 1.8.0
|
|
4
88
|
|
|
5
89
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -40,14 +40,18 @@ A bare `<SceneCanvas>` only renders. It keeps a selection that nothing sets from
|
|
|
40
40
|
| Preset | Turns on |
|
|
41
41
|
|---|---|
|
|
42
42
|
| `view` | wheel pan and zoom, pinch, the zoom keys, the hand tool (H, or hold Space). Passing `viewport` implies it. |
|
|
43
|
-
| `
|
|
43
|
+
| `select` | the select tool, active from the start: click to pick, drag on empty to marquee, click on empty to clear |
|
|
44
|
+
| `outline` | the selection outline, whichever tool sets the selection |
|
|
45
|
+
| `pick` | `select` and `outline` |
|
|
44
46
|
| `move` | drag a body to move it, Alt-drag to clone — under any tool that leaves the drag unclaimed |
|
|
45
|
-
| `
|
|
47
|
+
| `resize` | resize handles |
|
|
48
|
+
| `rotate` | the rotation handle |
|
|
49
|
+
| `transform` | `resize` and `rotate` |
|
|
46
50
|
| `edit` | undo/redo, delete, duplicate, group/ungroup, nudge, select-all, Escape, cut/copy/paste, fill and stroke, and their keys |
|
|
47
51
|
| `arrange` | align, distribute, reorder, flip |
|
|
48
52
|
| `paths` | pathfinder operations, path editing and anchor editing |
|
|
49
53
|
| `ingest` | dropped and pasted content |
|
|
50
|
-
| `draw` |
|
|
54
|
+
| `draw` | every preset above |
|
|
51
55
|
|
|
52
56
|
`defaultTools` adds built-in tools on top, and each brings the actions it binds: `defaultTools={['rect']}` brings `insert`.
|
|
53
57
|
|
|
@@ -94,7 +98,7 @@ Lower-level surfaces take a narrow **adapter** instead of a scene — a few meth
|
|
|
94
98
|
|
|
95
99
|
An `Action` is a named operation — `delete`, `duplicate`, `group`, `insert`, `viewport.dragPan` — paired with the input that triggers it. `<ActionsProvider>` holds the registered descriptors, and the gesture dispatcher matches live input against each one's `defaultBinding` and the active tool's bindings. Keystrokes and pointer gestures take the same path, so a keyboard shortcut and a drag are two bindings on one action rather than two mechanisms.
|
|
96
100
|
|
|
97
|
-
`<SceneCanvas>` mounts a provider when none is above it and registers the kit-standard actions its `features` presets name — under `draw`, all of them: escape, select-all, delete, duplicate, group and ungroup, undo and redo, flip, nudge, reorder, align, distribute, the pathfinder booleans, path-anchor editing, fill and stroke, clipboard, and the pointer-driven move, resize, rotate, clone, area-select and lasso. `FEATURE_ACTION_IDS` lists which preset registers which. `insert` comes with the shape tools that bind it.
|
|
101
|
+
`<SceneCanvas>` mounts a provider when none is above it and registers the kit-standard actions its `features` presets name — under `draw`, all of them: escape, select-all, delete, duplicate, group and ungroup, undo and redo, flip, nudge, reorder, align, distribute, the pathfinder booleans, path-anchor editing, fill and stroke, clipboard, and the pointer-driven move, resize, rotate, clone, area-select and lasso. `FEATURE_ACTION_IDS` lists which preset registers which, and `COMPOSITE_FEATURES` what `pick`, `transform` and `draw` abbreviate. `insert` comes with the shape tools that bind it.
|
|
98
102
|
|
|
99
103
|
```tsx
|
|
100
104
|
<SceneCanvas
|
|
@@ -112,7 +116,7 @@ An `Action` is a named operation — `delete`, `duplicate`, `group`, `insert`, `
|
|
|
112
116
|
requires: ['selection'],
|
|
113
117
|
invoker: {
|
|
114
118
|
timing: 'immediate',
|
|
115
|
-
run: (deps) => publish(
|
|
119
|
+
run: (deps) => publish(deps.selection?.get() ?? []),
|
|
116
120
|
},
|
|
117
121
|
},
|
|
118
122
|
}}
|
|
@@ -63,30 +63,37 @@ function b(u = {}) {
|
|
|
63
63
|
type: "end",
|
|
64
64
|
animation: _(t)
|
|
65
65
|
}));
|
|
66
|
-
}, k = (e, t) => {
|
|
67
|
-
let
|
|
68
|
-
|
|
66
|
+
}, k = (e, t, n) => {
|
|
67
|
+
let r = x.current.get(e);
|
|
68
|
+
r && (r.onCancel?.(), g.stop(e), x.current.delete(e), p(e), !(!h.watched || r.ended || n) && h.emit(t ? {
|
|
69
69
|
type: "interrupt",
|
|
70
|
-
animation: _(
|
|
70
|
+
animation: _(r),
|
|
71
71
|
by: _(t)
|
|
72
72
|
} : {
|
|
73
73
|
type: "cancel",
|
|
74
|
-
animation: _(
|
|
74
|
+
animation: _(r)
|
|
75
75
|
}));
|
|
76
76
|
}, A = (e) => {
|
|
77
|
-
let t = [], n = w.current == null ? 0 : Math.max(0, e - w.current);
|
|
78
|
-
w.current = e, g.frame(
|
|
79
|
-
for (let
|
|
80
|
-
let
|
|
81
|
-
|
|
82
|
-
let
|
|
83
|
-
|
|
77
|
+
let t = [], n = [], r = w.current == null ? 0 : Math.max(0, e - w.current);
|
|
78
|
+
w.current = e, g.frame(r * (E.current ? 0 : T.current));
|
|
79
|
+
for (let r of x.current.values()) {
|
|
80
|
+
let i = r.lastRealNow == null ? 0 : Math.max(0, e - r.lastRealNow);
|
|
81
|
+
r.lastRealNow = e;
|
|
82
|
+
let a = E.current ? 0 : T.current * (r.paused ? 0 : r.timeScale);
|
|
83
|
+
r.virtualNow += i * a, C.current += 1;
|
|
84
84
|
try {
|
|
85
|
-
|
|
85
|
+
r.tick(r.virtualNow, a) && t.push(r);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
n.push([r, e]);
|
|
86
88
|
} finally {
|
|
87
89
|
--C.current;
|
|
88
90
|
}
|
|
89
91
|
}
|
|
92
|
+
for (let [e, t] of n) console.error("useAnimator: animation tick threw; retiring it", t), x.current.get(e.id) === e && (e.ended ? (x.current.delete(e.id), p(e.id)) : k(e.id, void 0, !0), h.watched && h.emit({
|
|
93
|
+
type: "error",
|
|
94
|
+
animation: _(e),
|
|
95
|
+
error: t
|
|
96
|
+
}));
|
|
90
97
|
for (let e of t) x.current.get(e.id) === e && (v(e.id), x.current.delete(e.id), p(e.id));
|
|
91
98
|
for (let e of O.current) try {
|
|
92
99
|
e();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAnimator.js","names":["resolveSpringConstants","preset","o","SPRING_PRESETS","useAnimator","optsRef","useLatest","opts","animations","useRef","nextId","tickDepth","codecFrameT","globalTimeScale","globalPaused","colorOverrides","ColorOverrideRegistry","tickSubscribers","tickAllRef","frameLoop","useVisibleRaf","useCallback","t","anim","frameLoopRef","mountedRef","trippedRef","tripwire","useMemo","now","staggerTimers","c","m","cb","ms","x","h","completionListeners","fireCompletion","ls","id","watchCompletion","cur","idx","hub","AnimatorEventHub","codec","createCodec","infoOf","a","info","reportEnd","retire","by","tickAll","finished","frameDt","realDt","scale","sub","err","ensureLoop","cancelByKey","ids","key","rateOf","register","seed","s","createSupervisor","onCancelCb","base","tween","fromAxes","axesOf","toAxes","axes","easing","resolveEasing","easeOut","factoryFn","lastValueEmitted","reader","virtualNow","nowMs","value","eased","physics","add","b","subtract","v","k","magnitude","kBase","restThreshold","target","velocity","lastTime","damping","mass","kind","sameShape","baseHandle","dt","next","stepSpring","velRested","posRested","checkShape","what","newTo","api","friction","handle","retired","createLoop","factory","loopOpts","createTweenLoop","tweenLoopOpts","createStagger","items","delay","staggerOpts","createTimeline","animation","path","sup","listener","rows","row","useInsertionEffect","useEffect","overrides"],"sources":["../../src/animation/useAnimator.ts"],"sourcesContent":["import { useCallback, useEffect, useInsertionEffect, useMemo, useRef } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport { useVisibleRaf } from '../scheduling/useVisibleRaf';\nimport { easeOut, SPRING_PRESETS, resolveEasing } from '@weasel-js/geom';\nimport { createLoop, createTweenLoop } from './loop';\nimport { stepSpring } from './engine/integrator';\nimport { createCodec, type SpringReader } from './engine/codec';\nimport { axesOf } from './engine/axes';\nimport { createStagger, type StaggerTimers } from './stagger';\nimport { createTimeline } from './timeline/createTimeline';\nimport { ColorOverrideRegistry } from './colorRegistry';\nimport { AnimatorEventHub } from './observe';\nimport type { Supervisor, SupervisorOptions, WatchCompletion } from './supervisor';\nimport type {\n AnimationHandle,\n AnimationInfo,\n AnimationKind,\n Animator,\n TweenOptions,\n SpringOptions,\n DecayOptions,\n PhysicsOptions,\n PhysicsHandle,\n UseAnimatorOptions,\n LiveAnimation,\n} from './types';\nimport type { TimelineOptions } from './timeline/types';\n\ninterface ActiveAnimation {\n id: number;\n kind: AnimationKind;\n cancelKey?: string;\n label?: string;\n /** Built the first time something reports on this entry, then shared. */\n info?: AnimationInfo;\n /** 0–1 where the animation has an end to measure against. */\n progress?(virtualNow: number): number;\n /** Its `end` is reported, though the entry stays on the table until the\n * frame's ticks are done. */\n ended?: boolean;\n paused: boolean;\n timeScale: number;\n virtualNow: number;\n lastRealNow: number | null;\n /** Returns true when finished. Called once per frame with the current\n * virtual-ms timestamp and the effective scale it advanced at this frame. */\n tick(virtualNow: number, scale: number): boolean;\n /** Called when the animation is cancelled. Skips onDone. */\n onCancel?(): void;\n}\n\nfunction resolveSpringConstants(o: { preset?: string; stiffness?: number; damping?: number; mass?: number }) {\n const preset = o.preset ? SPRING_PRESETS[o.preset as keyof typeof SPRING_PRESETS] : null;\n return {\n stiffness: o.stiffness ?? preset?.stiffness ?? 170,\n damping: o.damping ?? preset?.damping ?? 26,\n mass: o.mass ?? preset?.mass ?? 1,\n };\n}\n\n/** Create the animator for a canvas. One rAF loop drives every animation it\n * owns, and everything still running is cancelled on unmount. */\nexport function useAnimator(opts: UseAnimatorOptions = {}): Animator {\n const optsRef = useLatest(opts);\n const animations = useRef<Map<number, ActiveAnimation>>(new Map());\n const nextId = useRef(1);\n /**\n * Re-entrancy counter incremented around each animation tick in tickAll.\n * Exposed via `isTicking()`. > 0 ⇒ we're currently inside an animation's\n * onTick; an adapter wrapper (`animateOnSetPose`) seeing this flag should\n * write through to the base adapter rather than schedule a new tween.\n */\n const tickDepth = useRef(0);\n /** The timestamp the codec last advanced to. Null after the page was hidden and while nothing\n * runs, so neither gap is charged to the codec; a tween starting then seeds it from `now()`. */\n const codecFrameT = useRef<number | null>(null);\n const globalTimeScale = useRef(1);\n const globalPaused = useRef(false);\n const colorOverrides = useRef<ColorOverrideRegistry>(new ColorOverrideRegistry());\n // Per-frame tick subscribers. Notified after each batch of animation\n // ticks (i.e. after `colorOverrides` has settled to its latest values\n // but before the next RAF schedules). Used by `<SceneCanvas animator>`\n // to request a redraw on every active frame.\n const tickSubscribers = useRef<Set<() => void>>(new Set());\n\n // The loop runs behind the visibility gate. `tickAll` is built inside the\n // memo below, so the frame callback reaches it through a ref, published\n // once the memo's render commits.\n const tickAllRef = useRef<((t: number) => void) | null>(null);\n const frameLoop = useVisibleRaf(\n useCallback((t: number) => { tickAllRef.current?.(t); }, []),\n {\n requestFrame: opts.requestFrame,\n cancelFrame: opts.cancelFrame,\n // An hour spent hidden is not an hour the animation ran: dropping each\n // animation's last timestamp makes the resuming frame's `realDt` zero.\n onResume: () => {\n for (const anim of animations.current.values()) anim.lastRealNow = null;\n codecFrameT.current = null;\n },\n },\n );\n const frameLoopRef = useLatest(frameLoop);\n\n // Tripwire: dev-only flag that every tween/spring/decay's tick reads to\n // detect \"animation fired after the host component unmounted.\" This is a\n // symptom of cleanup not running (regression) — typically because someone\n // refactored useAnimator and forgot to wire the unmount effect. The flag\n // flips false on cleanup; ticks see it and log a one-time error so the\n // bug is loud rather than just visually-doubled animation.\n const mountedRef = useRef(true);\n const trippedRef = useRef(false);\n const tripwire = (): boolean => {\n if (mountedRef.current) return false;\n if (!trippedRef.current) {\n trippedRef.current = true;\n const isDev = typeof process !== 'undefined' ? process.env.NODE_ENV !== 'production' : true;\n if (isDev) {\n console.error(\n 'weasel useAnimator: animation tick fired after the host component unmounted. ' +\n 'This usually means cleanup logic was bypassed — check for refactors that ' +\n 'removed the unmount effect or replaced cancelAll with a custom path.',\n );\n }\n }\n return true;\n };\n\n const { api, tickAll } = useMemo(() => {\n // Default to performance.now() so the time origin matches the\n // requestAnimationFrame callback's DOMHighResTimeStamp argument.\n // Using Date.now() here would mix epoch-millis with page-relative-millis,\n // producing huge negative `elapsed` values that clamp tween `t` to 0\n // forever (never reaching completion).\n const now = (): number =>\n (optsRef.current.now ?? (typeof performance !== 'undefined' ? performance.now.bind(performance) : Date.now))();\n // Resolved timer pair, snapshotted lazily from optsRef on each call so a\n // test that updates the injection after mount still wins. Kept internal —\n // not exposed on the public Animator surface; passed directly into\n // `createStagger` at bind time below.\n const staggerTimers: StaggerTimers = {\n setTimer: (cb, ms) =>\n (optsRef.current.setTimer ?? ((c, m) => setTimeout(c, m)))(cb, ms),\n clearTimer: (h) =>\n (optsRef.current.clearTimer ??\n ((x) => clearTimeout(x as ReturnType<typeof setTimeout>)))(h),\n now,\n };\n\n // Per-id one-shot completion listeners. Used by stagger to detect when\n // each child has left the registry (natural completion OR cancel) so the\n // supervising composite can retire itself when the last child finishes.\n const completionListeners = new Map<number, (() => void)[]>();\n const fireCompletion = (id: number): void => {\n const ls = completionListeners.get(id);\n if (!ls) return;\n completionListeners.delete(id);\n for (const cb of ls) cb();\n };\n const watchCompletion: WatchCompletion = (id, cb) => {\n const ls = completionListeners.get(id) ?? [];\n ls.push(cb);\n completionListeners.set(id, ls);\n return () => {\n const cur = completionListeners.get(id);\n if (!cur) return;\n const idx = cur.indexOf(cb);\n if (idx >= 0) cur.splice(idx, 1);\n if (cur.length === 0) completionListeners.delete(id);\n };\n };\n\n const hub = new AnimatorEventHub();\n const codec = createCodec();\n const infoOf = (a: ActiveAnimation): AnimationInfo => {\n if (!a.info) {\n const info: { -readonly [K in keyof AnimationInfo]: AnimationInfo[K] } = { id: a.id, kind: a.kind };\n if (a.cancelKey != null) info.key = a.cancelKey;\n if (a.label != null) info.label = a.label;\n a.info = info;\n }\n return a.info;\n };\n\n /** Report `id` as finished, ahead of its `onDone`, so whatever that\n * handler starts is heard after it. Nothing revives a primitive; a\n * timeline, which can be revived, is reported as it leaves the table. */\n const reportEnd = (id: number): void => {\n const a = animations.current.get(id);\n if (!a || a.ended) return;\n a.ended = true;\n if (hub.watched) hub.emit({ type: 'end', animation: infoOf(a) });\n };\n\n /** Take `id` off the table as canceled — or, given `by`, as interrupted by\n * the animation claiming its key. */\n const retire = (id: number, by?: ActiveAnimation): void => {\n const a = animations.current.get(id);\n if (!a) return;\n a.onCancel?.();\n codec.stop(id);\n animations.current.delete(id);\n fireCompletion(id);\n if (!hub.watched || a.ended) return;\n hub.emit(by\n ? { type: 'interrupt', animation: infoOf(a), by: infoOf(by) }\n : { type: 'cancel', animation: infoOf(a) });\n };\n\n const tickAll = (t: number): void => {\n const finished: ActiveAnimation[] = [];\n const frameDt = codecFrameT.current == null ? 0 : Math.max(0, t - codecFrameT.current);\n codecFrameT.current = t;\n codec.frame(frameDt * (globalPaused.current ? 0 : globalTimeScale.current));\n for (const anim of animations.current.values()) {\n // `t` comes from the frame clock; `lastRealNow` is seeded at register()\n // from `now()`. The two share a time origin in a browser, where the rAF\n // timestamp and `performance.now()` are both page-relative — but not\n // everywhere: jsdom starts them ~600ms apart, which made the first\n // frame's delta hugely negative and left `virtualNow` climbing back\n // toward zero for dozens of frames before a tween advanced at all.\n // Time never runs backwards, so a negative sample is never real.\n const realDt = anim.lastRealNow == null ? 0 : Math.max(0, t - anim.lastRealNow);\n anim.lastRealNow = t;\n const scale = globalPaused.current\n ? 0\n : globalTimeScale.current * (anim.paused ? 0 : anim.timeScale);\n anim.virtualNow += realDt * scale;\n // Increment tick depth around each animation's tick so re-entrant\n // calls (e.g. `decay.onTick` → `adapter.setPose` →\n // `animateOnSetPose` checking `isTicking()`) see the flag and\n // skip scheduling a new wrap-tween that would fight the caller.\n tickDepth.current += 1;\n try {\n if (anim.tick(anim.virtualNow, scale)) finished.push(anim);\n } finally {\n tickDepth.current -= 1;\n }\n }\n for (const anim of finished) {\n // Gone already if its own tick canceled it; replaced if something\n // revived it under the same id since.\n if (animations.current.get(anim.id) !== anim) continue;\n reportEnd(anim.id);\n animations.current.delete(anim.id);\n fireCompletion(anim.id);\n }\n // Notify every onTick subscriber AFTER the tick batch so they\n // see the latest `colorOverrides` / pose values. Errors in one\n // subscriber don't suppress the others.\n for (const sub of tickSubscribers.current) {\n try { sub(); } catch (err) { console.error('useAnimator: onTick subscriber threw', err); }\n }\n if (animations.current.size > 0) frameLoopRef.current.request();\n else codecFrameT.current = null;\n };\n\n const ensureLoop = (): void => {\n if (animations.current.size === 0) return;\n frameLoopRef.current.request();\n };\n\n const cancelByKey = (key: string, by?: ActiveAnimation): void => {\n const ids: number[] = [];\n for (const anim of animations.current.values()) {\n if (anim.cancelKey === key) ids.push(anim.id);\n }\n for (const id of ids) retire(id, by);\n };\n\n type AnimationSeed =\n Omit<ActiveAnimation, 'paused' | 'timeScale' | 'virtualNow' | 'lastRealNow'>\n & {\n /** Registers under an existing `cancelKey` without cancelling whoever\n * holds it. For an animation re-registering itself — a revived\n * timeline — which is not a new claim on the key. */\n keepExisting?: boolean;\n };\n /** Gives a codec subject the rate its own pause and time scale now ask for. */\n const rateOf = (a: ActiveAnimation): void => {\n if (codec.has(a.id)) codec.rate(a.id, a.paused ? 0 : a.timeScale);\n };\n\n const register = (seed: AnimationSeed): AnimationHandle => {\n const anim = seed as ActiveAnimation;\n if (seed.cancelKey != null && !seed.keepExisting) cancelByKey(seed.cancelKey, anim);\n anim.paused = false;\n anim.timeScale = 1;\n anim.virtualNow = 0;\n // Seed lastRealNow at registration so the first frame's realDt reflects\n // the gap between register() and the first RAF callback: it places a\n // tween's end, and gives spring/decay a non-zero first dt sample.\n anim.lastRealNow = now();\n animations.current.set(anim.id, anim);\n if (hub.watched) hub.emit({ type: 'start', animation: infoOf(anim) });\n ensureLoop();\n return {\n id: anim.id,\n cancel: () => retire(anim.id),\n pause: () => { const a = animations.current.get(anim.id); if (a) { a.paused = true; rateOf(a); } },\n resume: () => { const a = animations.current.get(anim.id); if (a) { a.paused = false; rateOf(a); } },\n setTimeScale: (s) => { const a = animations.current.get(anim.id); if (a) { a.timeScale = s; rateOf(a); } },\n timeScale: () => animations.current.get(anim.id)?.timeScale ?? 1,\n isPaused: () => animations.current.get(anim.id)?.paused ?? false,\n };\n };\n\n // Supervisor: a registered animation whose tick is a no-op (never\n // finishes naturally) so loop/stagger can sit in the animator's id table\n // and benefit from `cancel`/`cancelKey`/`isActive`. The owning composite\n // (loop/stagger) installs an onCancel to actually tear down its children.\n const createSupervisor = ({ kind, cancelKey, label }: SupervisorOptions): Supervisor => {\n const id = nextId.current++;\n let onCancelCb: (() => void) | undefined;\n const base = register({\n id,\n kind,\n cancelKey,\n label,\n // tick is a no-op: the supervisor only ends when the owner calls\n // `cancel()` (either via animator.cancel, animator.cancelKey, or the\n // composite's natural-completion path which forwards to `cancel`).\n tick: () => false,\n onCancel: () => onCancelCb?.(),\n });\n return {\n id: base.id,\n cancel: base.cancel,\n pause: base.pause,\n resume: base.resume,\n setTimeScale: base.setTimeScale,\n timeScale: base.timeScale,\n isPaused: base.isPaused,\n setOnCancel: (cb) => { onCancelCb = cb; },\n finish: () => {\n if (!animations.current.has(id)) return;\n reportEnd(id);\n animations.current.delete(id);\n fireCompletion(id);\n },\n cancelKey,\n };\n };\n\n const tween = <T,>(o: TweenOptions<T>): AnimationHandle => {\n // blits blends in a straight line, which a caller's own function may not do.\n const fromAxes = o.interpolate || o.interpolator ? null : axesOf(o.from);\n const toAxes = fromAxes && axesOf(o.to);\n const axes = fromAxes && toAxes && toAxes.shape === fromAxes.shape ? fromAxes : null;\n // Refused here rather than on a tick, where a throw would stop every other animation.\n if (!axes && !o.interpolate && !o.interpolator) {\n throw new Error('tween: interpolate or interpolator is required for non-numeric T');\n }\n const id = nextId.current++;\n const easing = resolveEasing(o.easing ?? easeOut);\n // Precedence: factory > per-tick. Factory is built once at tween start so\n // expensive setup (color space conversion etc.) doesn't repeat per frame.\n const factoryFn = o.interpolator ? o.interpolator(o.from, o.to) : null;\n let lastValueEmitted = false;\n // Seeds the codec's clock where `register` seeds the call's, so both charge the same wait.\n if (codecFrameT.current == null) codecFrameT.current = now();\n // Started ahead of `register`, whose `start` listeners may already cancel it.\n const reader = codec.tween({\n id, ms: Math.max(o.ms, 1), ease: easing,\n from: axes ? axes.to(o.from) : [0],\n to: axes ? axes.to(o.to) : [1],\n });\n return register({\n id,\n kind: 'tween',\n cancelKey: o.cancelKey,\n label: o.label,\n progress: (virtualNow) => (o.ms <= 0 ? 1 : Math.min(1, virtualNow / o.ms)),\n // `nowMs` is the call's own virtual time, so it still decides when the tween ends; the codec\n // supplies only the value in between.\n tick(nowMs) {\n if (tripwire()) { codec.stop(id); return true; }\n const t = o.ms <= 0 ? 1 : Math.min(1, nowMs / o.ms);\n let value: T;\n if (axes) {\n value = t >= 1 ? o.to : axes.from(reader.cols, reader.at);\n } else {\n const eased = t >= 1 ? easing(1) : reader.cols[reader.at]!;\n value = factoryFn ? factoryFn(eased) : o.interpolate!(o.from, o.to, eased);\n }\n o.onTick(value);\n if (t >= 1 && !lastValueEmitted) {\n lastValueEmitted = true;\n codec.stop(id);\n // That last onTick may have cancelled us — deregistering the id.\n // A cancelled tween never completes, whenever the cancel landed.\n if (animations.current.has(id)) {\n reportEnd(id);\n o.onDone?.();\n }\n return true;\n }\n return false;\n },\n });\n };\n\n const physics = <T,>(o: PhysicsOptions<T>, kind: AnimationKind = 'physics'): PhysicsHandle<T> => {\n const id = nextId.current++;\n const isNumeric = typeof o.from === 'number';\n if (!isNumeric && (!o.add || !o.subtract || !o.scale || !o.magnitude)) {\n throw new Error('physics: add/subtract/scale/magnitude are required for non-numeric T');\n }\n const add = o.add ?? ((a: T, b: T) => ((a as unknown as number) + (b as unknown as number)) as unknown as T);\n const subtract = o.subtract ?? ((a: T, b: T) => ((a as unknown as number) - (b as unknown as number)) as unknown as T);\n const scale = o.scale ?? ((v: T, k: number) => ((v as unknown as number) * k) as unknown as T);\n const magnitude = o.magnitude ?? ((v: T) => Math.abs(v as unknown as number));\n const { stiffness: kBase, damping, mass } = resolveSpringConstants(o);\n const restThreshold = o.restThreshold ?? 0.01;\n\n let target: T | null = o.to ?? null;\n let value = o.from;\n // Default to zero velocity: assumes scale(_, 0) returns the zero vector of T. Pass an explicit velocity for unusual T where this isn't true.\n let velocity: T = o.velocity ?? scale(o.from, 0);\n let lastTime: number | null = null;\n\n // blits' closed forms need positive damping and mass, and a spring with a target needs\n // stiffness; decay's handle never takes a target.\n const solvable = damping > 0 && mass > 0 && (kBase > 0 || kind === 'decay');\n const fromAxes = solvable ? axesOf(o.from) : null;\n const sameShape = (v: T | null | undefined): boolean => v == null || axesOf(v)?.shape === fromAxes!.shape;\n const axes = fromAxes && sameShape(o.to) && sameShape(o.velocity) ? fromAxes : null;\n let reader: SpringReader | null = null;\n if (axes) {\n // Seeds the codec's clock where `register` seeds the call's, so both charge the same wait.\n if (codecFrameT.current == null) codecFrameT.current = now();\n // Started ahead of `register`, whose `start` listeners may already cancel it.\n reader = codec.spring({\n id, axes: axes.count,\n from: axes.to(o.from),\n to: o.to == null ? null : axes.to(o.to),\n velocity: o.velocity == null ? new Array<number>(axes.count).fill(0) : axes.to(o.velocity),\n stiffness: kBase, damping, mass,\n });\n }\n\n const baseHandle = register({\n id,\n kind,\n cancelKey: o.cancelKey,\n label: o.label,\n tick(nowMs) {\n if (tripwire()) { codec.stop(id); return true; }\n if (lastTime == null) {\n lastTime = nowMs;\n // Already-at-rest short-circuit: decay-mode (target == null)\n // with starting velocity below threshold should complete\n // immediately rather than emit a tick and wait a frame.\n if (target == null && magnitude(velocity) < restThreshold) {\n codec.stop(id);\n reportEnd(id);\n o.onDone?.();\n return true;\n }\n // The codec's voice has run since registration, so only Euler starts its clock here.\n if (!axes) {\n o.onTick(value);\n return false;\n }\n }\n if (axes) {\n const m = reader!.motion();\n value = axes.from(m.value);\n velocity = axes.from(m.velocity);\n } else {\n // Semi-implicit Euler, for a T blits cannot move as axes, or constants its closed forms can't solve.\n const dt = Math.min(0.064, (nowMs - lastTime) / 1000);\n const next = stepSpring({ add, subtract, scale }, value, velocity, target, kBase, damping, mass, dt);\n value = next.value;\n velocity = next.velocity;\n }\n lastTime = nowMs;\n o.onTick(value);\n const velRested = magnitude(velocity) < restThreshold;\n const posRested = target == null\n ? true\n : magnitude(subtract(value, target)) < restThreshold;\n if (velRested && posRested) {\n if (target != null) o.onTick(target);\n codec.stop(id);\n reportEnd(id);\n o.onDone?.();\n return true;\n }\n return false;\n },\n });\n\n const checkShape = (v: T, what: string): void => {\n if (!sameShape(v)) throw new Error(`physics: ${what} is not the same shape as \\`from\\``);\n };\n const handle: PhysicsHandle<T> = {\n ...baseHandle,\n setTarget: (newTo: T | null) => {\n if (axes && newTo != null) checkShape(newTo, 'setTarget');\n target = newTo;\n if (axes && codec.has(id)) codec.retarget(id, newTo == null ? null : axes.to(newTo));\n },\n setVelocity: (v: T) => {\n if (axes) checkShape(v, 'setVelocity');\n velocity = v;\n if (axes && codec.has(id)) codec.push(id, axes.to(v));\n },\n };\n return handle;\n };\n\n const spring = <T,>(o: SpringOptions<T>): AnimationHandle => physics<T>(o, 'spring');\n\n const decay = <T,>(o: DecayOptions<T>): AnimationHandle => {\n const friction = o.friction ?? 0.95;\n // Per-second friction v(t) = v0 * friction^t corresponds to\n // m*dv/dt = -c*v with c = -ln(friction), m = 1, k = 0.\n const damping = -Math.log(friction);\n return physics<T>({\n from: o.from,\n to: null,\n velocity: o.velocity,\n stiffness: 0,\n damping,\n mass: 1,\n restThreshold: o.threshold ?? 0.5,\n add: o.add,\n // DecayOptions doesn't carry subtract; derive from add+scale.\n subtract: (a, b) => o.add(a, o.scale(b, -1)),\n scale: o.scale,\n magnitude: o.magnitude,\n onTick: o.onTick,\n onDone: o.onDone,\n cancelKey: o.cancelKey,\n label: o.label,\n }, 'decay');\n };\n\n const cancelAll = (): void => {\n const retired: ActiveAnimation[] = [];\n for (const a of animations.current.values()) {\n a.onCancel?.();\n codec.stop(a.id);\n retired.push(a);\n }\n animations.current.clear();\n codecFrameT.current = null;\n for (const a of retired) fireCompletion(a.id);\n if (hub.watched) for (const a of retired) hub.emit({ type: 'cancel', animation: infoOf(a) });\n frameLoopRef.current.cancel();\n };\n const api: Animator = {\n tween,\n spring,\n decay,\n physics,\n cancel: (handle) => retire(handle.id),\n cancelKey: (key) => cancelByKey(key),\n cancelAll,\n isActive: (key) => {\n if (key == null) return animations.current.size > 0;\n for (const a of animations.current.values()) {\n if (a.cancelKey === key) return true;\n }\n return false;\n },\n isTicking: () => tickDepth.current > 0,\n pause: () => { globalPaused.current = true; },\n resume: () => { globalPaused.current = false; },\n isPaused: () => globalPaused.current,\n setTimeScale: (s) => { globalTimeScale.current = s; },\n timeScale: () => globalTimeScale.current,\n pauseKey: (key) => {\n for (const a of animations.current.values()) if (a.cancelKey === key) { a.paused = true; rateOf(a); }\n },\n resumeKey: (key) => {\n for (const a of animations.current.values()) if (a.cancelKey === key) { a.paused = false; rateOf(a); }\n },\n setTimeScaleByKey: (key, s) => {\n for (const a of animations.current.values()) if (a.cancelKey === key) { a.timeScale = s; rateOf(a); }\n },\n loop: (factory, loopOpts) => createLoop(createSupervisor, factory, loopOpts),\n tweenLoop: (tweenLoopOpts) =>\n createTweenLoop(api, createSupervisor, tweenLoopOpts),\n stagger: ((items, delay, factory, staggerOpts) =>\n createStagger(\n api,\n staggerTimers,\n createSupervisor,\n watchCompletion,\n items,\n delay,\n factory as never,\n staggerOpts,\n )) as Animator['stagger'],\n timeline: (o: TimelineOptions) => {\n const id = nextId.current++;\n const info = (): AnimationInfo | undefined => {\n const a = animations.current.get(id);\n return a && infoOf(a);\n };\n return createTimeline(register, id, o, {\n get watched() { return hub.watched; },\n fire: (track, path, event, lateBy) => {\n const animation = info();\n if (animation) hub.emit({ type: 'fire', animation, track, path: path.slice(), event, lateBy });\n },\n lap: (lap) => {\n const animation = info();\n if (animation) hub.emit({ type: 'lap', animation, lap });\n },\n });\n },\n colorOverrides: colorOverrides.current,\n onTick: (cb) => {\n tickSubscribers.current.add(cb);\n return () => { tickSubscribers.current.delete(cb); };\n },\n keepAlive: () => {\n const sup = createSupervisor({ kind: 'keepAlive' });\n return () => sup.cancel();\n },\n watch: (listener) => hub.add(listener),\n live: () => {\n const rows: LiveAnimation[] = [];\n for (const a of animations.current.values()) {\n const row: LiveAnimation = {\n ...infoOf(a),\n paused: a.paused,\n timeScale: a.timeScale,\n elapsed: a.virtualNow,\n ...(a.progress ? { progress: a.progress(a.virtualNow) } : {}),\n };\n rows.push(row);\n }\n return rows;\n },\n };\n return { api, tickAll };\n }, [frameLoopRef, optsRef]);\n\n useInsertionEffect(() => {\n tickAllRef.current = tickAll;\n }, [tickAll]);\n\n // StrictMode-safe cleanup: when the component unmounts (including the\n // dev-mode double-mount that StrictMode performs), cancel every running\n // animation and stop the RAF loop. Without this, the FIRST mount's\n // animator keeps ticking with stale callbacks pointing at the unmounted\n // adapter, while the SECOND mount creates its own animator on top —\n // visible to the user as every animation playing twice.\n useEffect(() => {\n const overrides = colorOverrides.current;\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n api.cancelAll();\n overrides.clearAll();\n };\n }, [api]);\n\n return api;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useAnimator.js","names":["resolveSpringConstants","preset","o","SPRING_PRESETS","useAnimator","optsRef","useLatest","opts","animations","useRef","nextId","tickDepth","codecFrameT","globalTimeScale","globalPaused","colorOverrides","ColorOverrideRegistry","tickSubscribers","tickAllRef","frameLoop","useVisibleRaf","useCallback","t","anim","frameLoopRef","mountedRef","trippedRef","tripwire","useMemo","now","staggerTimers","c","m","cb","ms","x","h","completionListeners","fireCompletion","ls","id","watchCompletion","cur","idx","hub","AnimatorEventHub","codec","createCodec","infoOf","a","info","reportEnd","retire","error","by","tickAll","finished","threw","frameDt","realDt","scale","sub","err","ensureLoop","cancelByKey","ids","key","rateOf","register","seed","s","createSupervisor","onCancelCb","base","tween","fromAxes","axesOf","toAxes","axes","easing","resolveEasing","easeOut","factoryFn","lastValueEmitted","reader","virtualNow","nowMs","value","eased","physics","add","b","subtract","v","k","magnitude","kBase","restThreshold","target","velocity","lastTime","damping","mass","kind","sameShape","baseHandle","dt","next","stepSpring","velRested","posRested","checkShape","what","newTo","api","friction","handle","retired","createLoop","factory","loopOpts","createTweenLoop","tweenLoopOpts","createStagger","items","delay","staggerOpts","createTimeline","animation","path","sup","listener","rows","row","useInsertionEffect","useEffect","overrides"],"sources":["../../src/animation/useAnimator.ts"],"sourcesContent":["import { useCallback, useEffect, useInsertionEffect, useMemo, useRef } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport { useVisibleRaf } from '../scheduling/useVisibleRaf';\nimport { easeOut, SPRING_PRESETS, resolveEasing } from '@weasel-js/geom';\nimport { createLoop, createTweenLoop } from './loop';\nimport { stepSpring } from './engine/integrator';\nimport { createCodec, type SpringReader } from './engine/codec';\nimport { axesOf } from './engine/axes';\nimport { createStagger, type StaggerTimers } from './stagger';\nimport { createTimeline } from './timeline/createTimeline';\nimport { ColorOverrideRegistry } from './colorRegistry';\nimport { AnimatorEventHub } from './observe';\nimport type { Supervisor, SupervisorOptions, WatchCompletion } from './supervisor';\nimport type {\n AnimationHandle,\n AnimationInfo,\n AnimationKind,\n Animator,\n TweenOptions,\n SpringOptions,\n DecayOptions,\n PhysicsOptions,\n PhysicsHandle,\n UseAnimatorOptions,\n LiveAnimation,\n} from './types';\nimport type { TimelineOptions } from './timeline/types';\n\ninterface ActiveAnimation {\n id: number;\n kind: AnimationKind;\n cancelKey?: string;\n label?: string;\n /** Built the first time something reports on this entry, then shared. */\n info?: AnimationInfo;\n /** 0–1 where the animation has an end to measure against. */\n progress?(virtualNow: number): number;\n /** Its `end` is reported, though the entry stays on the table until the\n * frame's ticks are done. */\n ended?: boolean;\n paused: boolean;\n timeScale: number;\n virtualNow: number;\n lastRealNow: number | null;\n /** Returns true when finished. Called once per frame with the current\n * virtual-ms timestamp and the effective scale it advanced at this frame. */\n tick(virtualNow: number, scale: number): boolean;\n /** Called when the animation is cancelled. Skips onDone. */\n onCancel?(): void;\n}\n\nfunction resolveSpringConstants(o: { preset?: string; stiffness?: number; damping?: number; mass?: number }) {\n const preset = o.preset ? SPRING_PRESETS[o.preset as keyof typeof SPRING_PRESETS] : null;\n return {\n stiffness: o.stiffness ?? preset?.stiffness ?? 170,\n damping: o.damping ?? preset?.damping ?? 26,\n mass: o.mass ?? preset?.mass ?? 1,\n };\n}\n\n/** Create the animator for a canvas. One rAF loop drives every animation it\n * owns, and everything still running is cancelled on unmount. */\nexport function useAnimator(opts: UseAnimatorOptions = {}): Animator {\n const optsRef = useLatest(opts);\n const animations = useRef<Map<number, ActiveAnimation>>(new Map());\n const nextId = useRef(1);\n /**\n * Re-entrancy counter incremented around each animation tick in tickAll.\n * Exposed via `isTicking()`. > 0 ⇒ we're currently inside an animation's\n * onTick; an adapter wrapper (`animateOnSetPose`) seeing this flag should\n * write through to the base adapter rather than schedule a new tween.\n */\n const tickDepth = useRef(0);\n /** The timestamp the codec last advanced to. Null after the page was hidden and while nothing\n * runs, so neither gap is charged to the codec; a tween starting then seeds it from `now()`. */\n const codecFrameT = useRef<number | null>(null);\n const globalTimeScale = useRef(1);\n const globalPaused = useRef(false);\n const colorOverrides = useRef<ColorOverrideRegistry>(new ColorOverrideRegistry());\n // Per-frame tick subscribers. Notified after each batch of animation\n // ticks (i.e. after `colorOverrides` has settled to its latest values\n // but before the next RAF schedules). Used by `<SceneCanvas animator>`\n // to request a redraw on every active frame.\n const tickSubscribers = useRef<Set<() => void>>(new Set());\n\n // The loop runs behind the visibility gate. `tickAll` is built inside the\n // memo below, so the frame callback reaches it through a ref, published\n // once the memo's render commits.\n const tickAllRef = useRef<((t: number) => void) | null>(null);\n const frameLoop = useVisibleRaf(\n useCallback((t: number) => { tickAllRef.current?.(t); }, []),\n {\n requestFrame: opts.requestFrame,\n cancelFrame: opts.cancelFrame,\n // An hour spent hidden is not an hour the animation ran: dropping each\n // animation's last timestamp makes the resuming frame's `realDt` zero.\n onResume: () => {\n for (const anim of animations.current.values()) anim.lastRealNow = null;\n codecFrameT.current = null;\n },\n },\n );\n const frameLoopRef = useLatest(frameLoop);\n\n // Tripwire: dev-only flag that every tween/spring/decay's tick reads to\n // detect \"animation fired after the host component unmounted.\" This is a\n // symptom of cleanup not running (regression) — typically because someone\n // refactored useAnimator and forgot to wire the unmount effect. The flag\n // flips false on cleanup; ticks see it and log a one-time error so the\n // bug is loud rather than just visually-doubled animation.\n const mountedRef = useRef(true);\n const trippedRef = useRef(false);\n const tripwire = (): boolean => {\n if (mountedRef.current) return false;\n if (!trippedRef.current) {\n trippedRef.current = true;\n const isDev = typeof process !== 'undefined' ? process.env.NODE_ENV !== 'production' : true;\n if (isDev) {\n console.error(\n 'weasel useAnimator: animation tick fired after the host component unmounted. ' +\n 'This usually means cleanup logic was bypassed — check for refactors that ' +\n 'removed the unmount effect or replaced cancelAll with a custom path.',\n );\n }\n }\n return true;\n };\n\n const { api, tickAll } = useMemo(() => {\n // Default to performance.now() so the time origin matches the\n // requestAnimationFrame callback's DOMHighResTimeStamp argument.\n // Using Date.now() here would mix epoch-millis with page-relative-millis,\n // producing huge negative `elapsed` values that clamp tween `t` to 0\n // forever (never reaching completion).\n const now = (): number =>\n (optsRef.current.now ?? (typeof performance !== 'undefined' ? performance.now.bind(performance) : Date.now))();\n // Resolved timer pair, snapshotted lazily from optsRef on each call so a\n // test that updates the injection after mount still wins. Kept internal —\n // not exposed on the public Animator surface; passed directly into\n // `createStagger` at bind time below.\n const staggerTimers: StaggerTimers = {\n setTimer: (cb, ms) =>\n (optsRef.current.setTimer ?? ((c, m) => setTimeout(c, m)))(cb, ms),\n clearTimer: (h) =>\n (optsRef.current.clearTimer ??\n ((x) => clearTimeout(x as ReturnType<typeof setTimeout>)))(h),\n now,\n };\n\n // Per-id one-shot completion listeners. Used by stagger to detect when\n // each child has left the registry (natural completion OR cancel) so the\n // supervising composite can retire itself when the last child finishes.\n const completionListeners = new Map<number, (() => void)[]>();\n const fireCompletion = (id: number): void => {\n const ls = completionListeners.get(id);\n if (!ls) return;\n completionListeners.delete(id);\n for (const cb of ls) cb();\n };\n const watchCompletion: WatchCompletion = (id, cb) => {\n const ls = completionListeners.get(id) ?? [];\n ls.push(cb);\n completionListeners.set(id, ls);\n return () => {\n const cur = completionListeners.get(id);\n if (!cur) return;\n const idx = cur.indexOf(cb);\n if (idx >= 0) cur.splice(idx, 1);\n if (cur.length === 0) completionListeners.delete(id);\n };\n };\n\n const hub = new AnimatorEventHub();\n const codec = createCodec();\n const infoOf = (a: ActiveAnimation): AnimationInfo => {\n if (!a.info) {\n const info: { -readonly [K in keyof AnimationInfo]: AnimationInfo[K] } = { id: a.id, kind: a.kind };\n if (a.cancelKey != null) info.key = a.cancelKey;\n if (a.label != null) info.label = a.label;\n a.info = info;\n }\n return a.info;\n };\n\n /** Report `id` as finished, ahead of its `onDone`, so whatever that\n * handler starts is heard after it. Nothing revives a primitive; a\n * timeline, which can be revived, is reported as it leaves the table. */\n const reportEnd = (id: number): void => {\n const a = animations.current.get(id);\n if (!a || a.ended) return;\n a.ended = true;\n if (hub.watched) hub.emit({ type: 'end', animation: infoOf(a) });\n };\n\n /** Take `id` off the table as canceled — or, given `by`, as interrupted by\n * the animation claiming its key; given `error`, the caller reports it. */\n const retire = (id: number, by?: ActiveAnimation, error?: true): void => {\n const a = animations.current.get(id);\n if (!a) return;\n a.onCancel?.();\n codec.stop(id);\n animations.current.delete(id);\n fireCompletion(id);\n if (!hub.watched || a.ended || error) return;\n hub.emit(by\n ? { type: 'interrupt', animation: infoOf(a), by: infoOf(by) }\n : { type: 'cancel', animation: infoOf(a) });\n };\n\n const tickAll = (t: number): void => {\n const finished: ActiveAnimation[] = [];\n const threw: [ActiveAnimation, unknown][] = [];\n const frameDt = codecFrameT.current == null ? 0 : Math.max(0, t - codecFrameT.current);\n codecFrameT.current = t;\n codec.frame(frameDt * (globalPaused.current ? 0 : globalTimeScale.current));\n for (const anim of animations.current.values()) {\n // `t` comes from the frame clock; `lastRealNow` is seeded at register()\n // from `now()`. The two share a time origin in a browser, where the rAF\n // timestamp and `performance.now()` are both page-relative — but not\n // everywhere: jsdom starts them ~600ms apart, which made the first\n // frame's delta hugely negative and left `virtualNow` climbing back\n // toward zero for dozens of frames before a tween advanced at all.\n // Time never runs backwards, so a negative sample is never real.\n const realDt = anim.lastRealNow == null ? 0 : Math.max(0, t - anim.lastRealNow);\n anim.lastRealNow = t;\n const scale = globalPaused.current\n ? 0\n : globalTimeScale.current * (anim.paused ? 0 : anim.timeScale);\n anim.virtualNow += realDt * scale;\n // Increment tick depth around each animation's tick so re-entrant\n // calls (e.g. `decay.onTick` → `adapter.setPose` →\n // `animateOnSetPose` checking `isTicking()`) see the flag and\n // skip scheduling a new wrap-tween that would fight the caller.\n tickDepth.current += 1;\n try {\n if (anim.tick(anim.virtualNow, scale)) finished.push(anim);\n } catch (error) {\n threw.push([anim, error]);\n } finally {\n tickDepth.current -= 1;\n }\n }\n for (const [anim, error] of threw) {\n console.error('useAnimator: animation tick threw; retiring it', error);\n if (animations.current.get(anim.id) !== anim) continue;\n // One that ended before throwing (from `onDone`) is finished, not canceled.\n if (anim.ended) {\n animations.current.delete(anim.id);\n fireCompletion(anim.id);\n } else {\n retire(anim.id, undefined, true);\n }\n if (hub.watched) hub.emit({ type: 'error', animation: infoOf(anim), error });\n }\n for (const anim of finished) {\n // Gone already if its own tick canceled it; replaced if something\n // revived it under the same id since.\n if (animations.current.get(anim.id) !== anim) continue;\n reportEnd(anim.id);\n animations.current.delete(anim.id);\n fireCompletion(anim.id);\n }\n // Notify every onTick subscriber AFTER the tick batch so they\n // see the latest `colorOverrides` / pose values. Errors in one\n // subscriber don't suppress the others.\n for (const sub of tickSubscribers.current) {\n try { sub(); } catch (err) { console.error('useAnimator: onTick subscriber threw', err); }\n }\n if (animations.current.size > 0) frameLoopRef.current.request();\n else codecFrameT.current = null;\n };\n\n const ensureLoop = (): void => {\n if (animations.current.size === 0) return;\n frameLoopRef.current.request();\n };\n\n const cancelByKey = (key: string, by?: ActiveAnimation): void => {\n const ids: number[] = [];\n for (const anim of animations.current.values()) {\n if (anim.cancelKey === key) ids.push(anim.id);\n }\n for (const id of ids) retire(id, by);\n };\n\n type AnimationSeed =\n Omit<ActiveAnimation, 'paused' | 'timeScale' | 'virtualNow' | 'lastRealNow'>\n & {\n /** Registers under an existing `cancelKey` without cancelling whoever\n * holds it. For an animation re-registering itself — a revived\n * timeline — which is not a new claim on the key. */\n keepExisting?: boolean;\n };\n /** Gives a codec subject the rate its own pause and time scale now ask for. */\n const rateOf = (a: ActiveAnimation): void => {\n if (codec.has(a.id)) codec.rate(a.id, a.paused ? 0 : a.timeScale);\n };\n\n const register = (seed: AnimationSeed): AnimationHandle => {\n const anim = seed as ActiveAnimation;\n if (seed.cancelKey != null && !seed.keepExisting) cancelByKey(seed.cancelKey, anim);\n anim.paused = false;\n anim.timeScale = 1;\n anim.virtualNow = 0;\n // Seed lastRealNow at registration so the first frame's realDt reflects\n // the gap between register() and the first RAF callback: it places a\n // tween's end, and gives spring/decay a non-zero first dt sample.\n anim.lastRealNow = now();\n animations.current.set(anim.id, anim);\n if (hub.watched) hub.emit({ type: 'start', animation: infoOf(anim) });\n ensureLoop();\n return {\n id: anim.id,\n cancel: () => retire(anim.id),\n pause: () => { const a = animations.current.get(anim.id); if (a) { a.paused = true; rateOf(a); } },\n resume: () => { const a = animations.current.get(anim.id); if (a) { a.paused = false; rateOf(a); } },\n setTimeScale: (s) => { const a = animations.current.get(anim.id); if (a) { a.timeScale = s; rateOf(a); } },\n timeScale: () => animations.current.get(anim.id)?.timeScale ?? 1,\n isPaused: () => animations.current.get(anim.id)?.paused ?? false,\n };\n };\n\n // Supervisor: a registered animation whose tick is a no-op (never\n // finishes naturally) so loop/stagger can sit in the animator's id table\n // and benefit from `cancel`/`cancelKey`/`isActive`. The owning composite\n // (loop/stagger) installs an onCancel to actually tear down its children.\n const createSupervisor = ({ kind, cancelKey, label }: SupervisorOptions): Supervisor => {\n const id = nextId.current++;\n let onCancelCb: (() => void) | undefined;\n const base = register({\n id,\n kind,\n cancelKey,\n label,\n // tick is a no-op: the supervisor only ends when the owner calls\n // `cancel()` (either via animator.cancel, animator.cancelKey, or the\n // composite's natural-completion path which forwards to `cancel`).\n tick: () => false,\n onCancel: () => onCancelCb?.(),\n });\n return {\n id: base.id,\n cancel: base.cancel,\n pause: base.pause,\n resume: base.resume,\n setTimeScale: base.setTimeScale,\n timeScale: base.timeScale,\n isPaused: base.isPaused,\n setOnCancel: (cb) => { onCancelCb = cb; },\n finish: () => {\n if (!animations.current.has(id)) return;\n reportEnd(id);\n animations.current.delete(id);\n fireCompletion(id);\n },\n cancelKey,\n };\n };\n\n const tween = <T,>(o: TweenOptions<T>): AnimationHandle => {\n // blits blends in a straight line, which a caller's own function may not do.\n const fromAxes = o.interpolate || o.interpolator ? null : axesOf(o.from);\n const toAxes = fromAxes && axesOf(o.to);\n const axes = fromAxes && toAxes && toAxes.shape === fromAxes.shape ? fromAxes : null;\n // Refused here rather than on a tick, where a throw would stop every other animation.\n if (!axes && !o.interpolate && !o.interpolator) {\n throw new Error('tween: interpolate or interpolator is required for non-numeric T');\n }\n const id = nextId.current++;\n const easing = resolveEasing(o.easing ?? easeOut);\n // Precedence: factory > per-tick. Factory is built once at tween start so\n // expensive setup (color space conversion etc.) doesn't repeat per frame.\n const factoryFn = o.interpolator ? o.interpolator(o.from, o.to) : null;\n let lastValueEmitted = false;\n // Seeds the codec's clock where `register` seeds the call's, so both charge the same wait.\n if (codecFrameT.current == null) codecFrameT.current = now();\n // Started ahead of `register`, whose `start` listeners may already cancel it.\n const reader = codec.tween({\n id, ms: Math.max(o.ms, 1), ease: easing,\n from: axes ? axes.to(o.from) : [0],\n to: axes ? axes.to(o.to) : [1],\n });\n return register({\n id,\n kind: 'tween',\n cancelKey: o.cancelKey,\n label: o.label,\n progress: (virtualNow) => (o.ms <= 0 ? 1 : Math.min(1, virtualNow / o.ms)),\n // `nowMs` is the call's own virtual time, so it still decides when the tween ends; the codec\n // supplies only the value in between.\n tick(nowMs) {\n if (tripwire()) { codec.stop(id); return true; }\n const t = o.ms <= 0 ? 1 : Math.min(1, nowMs / o.ms);\n let value: T;\n if (axes) {\n value = t >= 1 ? o.to : axes.from(reader.cols, reader.at);\n } else {\n const eased = t >= 1 ? easing(1) : reader.cols[reader.at]!;\n value = factoryFn ? factoryFn(eased) : o.interpolate!(o.from, o.to, eased);\n }\n o.onTick(value);\n if (t >= 1 && !lastValueEmitted) {\n lastValueEmitted = true;\n codec.stop(id);\n // That last onTick may have cancelled us — deregistering the id.\n // A cancelled tween never completes, whenever the cancel landed.\n if (animations.current.has(id)) {\n reportEnd(id);\n o.onDone?.();\n }\n return true;\n }\n return false;\n },\n });\n };\n\n const physics = <T,>(o: PhysicsOptions<T>, kind: AnimationKind = 'physics'): PhysicsHandle<T> => {\n const id = nextId.current++;\n const isNumeric = typeof o.from === 'number';\n if (!isNumeric && (!o.add || !o.subtract || !o.scale || !o.magnitude)) {\n throw new Error('physics: add/subtract/scale/magnitude are required for non-numeric T');\n }\n const add = o.add ?? ((a: T, b: T) => ((a as unknown as number) + (b as unknown as number)) as unknown as T);\n const subtract = o.subtract ?? ((a: T, b: T) => ((a as unknown as number) - (b as unknown as number)) as unknown as T);\n const scale = o.scale ?? ((v: T, k: number) => ((v as unknown as number) * k) as unknown as T);\n const magnitude = o.magnitude ?? ((v: T) => Math.abs(v as unknown as number));\n const { stiffness: kBase, damping, mass } = resolveSpringConstants(o);\n const restThreshold = o.restThreshold ?? 0.01;\n\n let target: T | null = o.to ?? null;\n let value = o.from;\n // Default to zero velocity: assumes scale(_, 0) returns the zero vector of T. Pass an explicit velocity for unusual T where this isn't true.\n let velocity: T = o.velocity ?? scale(o.from, 0);\n let lastTime: number | null = null;\n\n // blits' closed forms need positive damping and mass, and a spring with a target needs\n // stiffness; decay's handle never takes a target.\n const solvable = damping > 0 && mass > 0 && (kBase > 0 || kind === 'decay');\n const fromAxes = solvable ? axesOf(o.from) : null;\n const sameShape = (v: T | null | undefined): boolean => v == null || axesOf(v)?.shape === fromAxes!.shape;\n const axes = fromAxes && sameShape(o.to) && sameShape(o.velocity) ? fromAxes : null;\n let reader: SpringReader | null = null;\n if (axes) {\n // Seeds the codec's clock where `register` seeds the call's, so both charge the same wait.\n if (codecFrameT.current == null) codecFrameT.current = now();\n // Started ahead of `register`, whose `start` listeners may already cancel it.\n reader = codec.spring({\n id, axes: axes.count,\n from: axes.to(o.from),\n to: o.to == null ? null : axes.to(o.to),\n velocity: o.velocity == null ? new Array<number>(axes.count).fill(0) : axes.to(o.velocity),\n stiffness: kBase, damping, mass,\n });\n }\n\n const baseHandle = register({\n id,\n kind,\n cancelKey: o.cancelKey,\n label: o.label,\n tick(nowMs) {\n if (tripwire()) { codec.stop(id); return true; }\n if (lastTime == null) {\n lastTime = nowMs;\n // Already-at-rest short-circuit: decay-mode (target == null)\n // with starting velocity below threshold should complete\n // immediately rather than emit a tick and wait a frame.\n if (target == null && magnitude(velocity) < restThreshold) {\n codec.stop(id);\n reportEnd(id);\n o.onDone?.();\n return true;\n }\n // The codec's voice has run since registration, so only Euler starts its clock here.\n if (!axes) {\n o.onTick(value);\n return false;\n }\n }\n if (axes) {\n const m = reader!.motion();\n value = axes.from(m.value);\n velocity = axes.from(m.velocity);\n } else {\n // Semi-implicit Euler, for a T blits cannot move as axes, or constants its closed forms can't solve.\n const dt = Math.min(0.064, (nowMs - lastTime) / 1000);\n const next = stepSpring({ add, subtract, scale }, value, velocity, target, kBase, damping, mass, dt);\n value = next.value;\n velocity = next.velocity;\n }\n lastTime = nowMs;\n o.onTick(value);\n const velRested = magnitude(velocity) < restThreshold;\n const posRested = target == null\n ? true\n : magnitude(subtract(value, target)) < restThreshold;\n if (velRested && posRested) {\n if (target != null) o.onTick(target);\n codec.stop(id);\n reportEnd(id);\n o.onDone?.();\n return true;\n }\n return false;\n },\n });\n\n const checkShape = (v: T, what: string): void => {\n if (!sameShape(v)) throw new Error(`physics: ${what} is not the same shape as \\`from\\``);\n };\n const handle: PhysicsHandle<T> = {\n ...baseHandle,\n setTarget: (newTo: T | null) => {\n if (axes && newTo != null) checkShape(newTo, 'setTarget');\n target = newTo;\n if (axes && codec.has(id)) codec.retarget(id, newTo == null ? null : axes.to(newTo));\n },\n setVelocity: (v: T) => {\n if (axes) checkShape(v, 'setVelocity');\n velocity = v;\n if (axes && codec.has(id)) codec.push(id, axes.to(v));\n },\n };\n return handle;\n };\n\n const spring = <T,>(o: SpringOptions<T>): AnimationHandle => physics<T>(o, 'spring');\n\n const decay = <T,>(o: DecayOptions<T>): AnimationHandle => {\n const friction = o.friction ?? 0.95;\n // Per-second friction v(t) = v0 * friction^t corresponds to\n // m*dv/dt = -c*v with c = -ln(friction), m = 1, k = 0.\n const damping = -Math.log(friction);\n return physics<T>({\n from: o.from,\n to: null,\n velocity: o.velocity,\n stiffness: 0,\n damping,\n mass: 1,\n restThreshold: o.threshold ?? 0.5,\n add: o.add,\n // DecayOptions doesn't carry subtract; derive from add+scale.\n subtract: (a, b) => o.add(a, o.scale(b, -1)),\n scale: o.scale,\n magnitude: o.magnitude,\n onTick: o.onTick,\n onDone: o.onDone,\n cancelKey: o.cancelKey,\n label: o.label,\n }, 'decay');\n };\n\n const cancelAll = (): void => {\n const retired: ActiveAnimation[] = [];\n for (const a of animations.current.values()) {\n a.onCancel?.();\n codec.stop(a.id);\n retired.push(a);\n }\n animations.current.clear();\n codecFrameT.current = null;\n for (const a of retired) fireCompletion(a.id);\n if (hub.watched) for (const a of retired) hub.emit({ type: 'cancel', animation: infoOf(a) });\n frameLoopRef.current.cancel();\n };\n const api: Animator = {\n tween,\n spring,\n decay,\n physics,\n cancel: (handle) => retire(handle.id),\n cancelKey: (key) => cancelByKey(key),\n cancelAll,\n isActive: (key) => {\n if (key == null) return animations.current.size > 0;\n for (const a of animations.current.values()) {\n if (a.cancelKey === key) return true;\n }\n return false;\n },\n isTicking: () => tickDepth.current > 0,\n pause: () => { globalPaused.current = true; },\n resume: () => { globalPaused.current = false; },\n isPaused: () => globalPaused.current,\n setTimeScale: (s) => { globalTimeScale.current = s; },\n timeScale: () => globalTimeScale.current,\n pauseKey: (key) => {\n for (const a of animations.current.values()) if (a.cancelKey === key) { a.paused = true; rateOf(a); }\n },\n resumeKey: (key) => {\n for (const a of animations.current.values()) if (a.cancelKey === key) { a.paused = false; rateOf(a); }\n },\n setTimeScaleByKey: (key, s) => {\n for (const a of animations.current.values()) if (a.cancelKey === key) { a.timeScale = s; rateOf(a); }\n },\n loop: (factory, loopOpts) => createLoop(createSupervisor, factory, loopOpts),\n tweenLoop: (tweenLoopOpts) =>\n createTweenLoop(api, createSupervisor, tweenLoopOpts),\n stagger: ((items, delay, factory, staggerOpts) =>\n createStagger(\n api,\n staggerTimers,\n createSupervisor,\n watchCompletion,\n items,\n delay,\n factory as never,\n staggerOpts,\n )) as Animator['stagger'],\n timeline: (o: TimelineOptions) => {\n const id = nextId.current++;\n const info = (): AnimationInfo | undefined => {\n const a = animations.current.get(id);\n return a && infoOf(a);\n };\n return createTimeline(register, id, o, {\n get watched() { return hub.watched; },\n fire: (track, path, event, lateBy) => {\n const animation = info();\n if (animation) hub.emit({ type: 'fire', animation, track, path: path.slice(), event, lateBy });\n },\n lap: (lap) => {\n const animation = info();\n if (animation) hub.emit({ type: 'lap', animation, lap });\n },\n });\n },\n colorOverrides: colorOverrides.current,\n onTick: (cb) => {\n tickSubscribers.current.add(cb);\n return () => { tickSubscribers.current.delete(cb); };\n },\n keepAlive: () => {\n const sup = createSupervisor({ kind: 'keepAlive' });\n return () => sup.cancel();\n },\n watch: (listener) => hub.add(listener),\n live: () => {\n const rows: LiveAnimation[] = [];\n for (const a of animations.current.values()) {\n const row: LiveAnimation = {\n ...infoOf(a),\n paused: a.paused,\n timeScale: a.timeScale,\n elapsed: a.virtualNow,\n ...(a.progress ? { progress: a.progress(a.virtualNow) } : {}),\n };\n rows.push(row);\n }\n return rows;\n },\n };\n return { api, tickAll };\n }, [frameLoopRef, optsRef]);\n\n useInsertionEffect(() => {\n tickAllRef.current = tickAll;\n }, [tickAll]);\n\n // StrictMode-safe cleanup: when the component unmounts (including the\n // dev-mode double-mount that StrictMode performs), cancel every running\n // animation and stop the RAF loop. Without this, the FIRST mount's\n // animator keeps ticking with stale callbacks pointing at the unmounted\n // adapter, while the SECOND mount creates its own animator on top —\n // visible to the user as every animation playing twice.\n useEffect(() => {\n const overrides = colorOverrides.current;\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n api.cancelAll();\n overrides.clearAll();\n };\n }, [api]);\n\n return api;\n}\n"],"mappings":"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"}
|