@weasel-js/core 1.8.1 → 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.
Files changed (129) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +9 -5
  3. package/dist/{builtins-CMEh6-Oc.d.ts → builtins-CQcvcLXh.d.ts} +98 -31
  4. package/dist/canvas/DrawCanvas.js +33 -33
  5. package/dist/canvas/DrawCanvas.js.map +1 -1
  6. package/dist/canvas/MinimapCanvas.js +29 -29
  7. package/dist/canvas/MinimapCanvas.js.map +1 -1
  8. package/dist/canvas/NodeShape.js +63 -54
  9. package/dist/canvas/NodeShape.js.map +1 -1
  10. package/dist/canvas/SceneCanvas/features.js +26 -5
  11. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  12. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +3 -2
  13. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  14. package/dist/canvas/SceneCanvas.js +526 -529
  15. package/dist/canvas/SceneCanvas.js.map +1 -1
  16. package/dist/canvas/SceneViewCanvas.js +35 -35
  17. package/dist/canvas/SceneViewCanvas.js.map +1 -1
  18. package/dist/canvas/defaultDrawOne.js +6 -1
  19. package/dist/canvas/defaultDrawOne.js.map +1 -1
  20. package/dist/canvas/pickWalk.js.map +1 -1
  21. package/dist/canvas/sceneAdapter.js.map +1 -1
  22. package/dist/canvas/useHitObserver.js +23 -0
  23. package/dist/canvas/useHitObserver.js.map +1 -0
  24. package/dist/canvas/useHostAnchor.js.map +1 -1
  25. package/dist/core/device/useDeviceProfile.js.map +1 -1
  26. package/dist/core/scene/derivedPath.js.map +1 -1
  27. package/dist/core/scene/effectivePose.js.map +1 -1
  28. package/dist/core/scene/kitRegistry.js.map +1 -1
  29. package/dist/core/scene/scene.js.map +1 -1
  30. package/dist/core/scene/types.js.map +1 -1
  31. package/dist/core/scene/useScene.js.map +1 -1
  32. package/dist/core/viewport/fitViewToBounds.js +3 -3
  33. package/dist/core/viewport/fitViewToBounds.js.map +1 -1
  34. package/dist/core/viewport/screenSlop.js +8 -2
  35. package/dist/core/viewport/screenSlop.js.map +1 -1
  36. package/dist/features/minimap/actions.js.map +1 -1
  37. package/dist/features/paths/markerCommands.js +95 -63
  38. package/dist/features/paths/markerCommands.js.map +1 -1
  39. package/dist/features/paths/tessellate/dash.js +52 -0
  40. package/dist/features/paths/tessellate/dash.js.map +1 -0
  41. package/dist/features/paths/tessellate/insets.js +32 -0
  42. package/dist/features/paths/tessellate/insets.js.map +1 -0
  43. package/dist/features/paths/tessellate/metric.js +72 -0
  44. package/dist/features/paths/tessellate/metric.js.map +1 -0
  45. package/dist/features/paths/tessellate/stroke.js +74 -134
  46. package/dist/features/paths/tessellate/stroke.js.map +1 -1
  47. package/dist/features/paths/tessellate/vertexWidths.js +32 -0
  48. package/dist/features/paths/tessellate/vertexWidths.js.map +1 -0
  49. package/dist/features/pointer/PointerContext.js +2 -2
  50. package/dist/features/pointer/PointerContext.js.map +1 -1
  51. package/dist/features/poseRun/usePoseRun.js.map +1 -1
  52. package/dist/features/text/useTextEdit.js +86 -84
  53. package/dist/features/text/useTextEdit.js.map +1 -1
  54. package/dist/index.d.ts +78 -39
  55. package/dist/index.js +148 -148
  56. package/dist/interactions/actions/defaults/align.js.map +1 -1
  57. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  58. package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
  59. package/dist/interactions/actions/defaults/booleans.js.map +1 -1
  60. package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
  61. package/dist/interactions/actions/defaults/clearSelection.js.map +1 -1
  62. package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
  63. package/dist/interactions/actions/defaults/clone.js.map +1 -1
  64. package/dist/interactions/actions/defaults/createOutlines.js +1 -1
  65. package/dist/interactions/actions/defaults/createOutlines.js.map +1 -1
  66. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  67. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  68. package/dist/interactions/actions/defaults/distribute.js.map +1 -1
  69. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  70. package/dist/interactions/actions/defaults/editAnchors.js.map +1 -1
  71. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  72. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  73. package/dist/interactions/actions/defaults/escape.js +1 -1
  74. package/dist/interactions/actions/defaults/escape.js.map +1 -1
  75. package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
  76. package/dist/interactions/actions/defaults/flip.js.map +1 -1
  77. package/dist/interactions/actions/defaults/group.js.map +1 -1
  78. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  79. package/dist/interactions/actions/defaults/insert.js.map +1 -1
  80. package/dist/interactions/actions/defaults/insertPathAnchor.js +1 -1
  81. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  82. package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
  83. package/dist/interactions/actions/defaults/move.js.map +1 -1
  84. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  85. package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
  86. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  87. package/dist/interactions/actions/defaults/requiresSelection.js.map +1 -1
  88. package/dist/interactions/actions/defaults/resize.js.map +1 -1
  89. package/dist/interactions/actions/defaults/rotate.js.map +1 -1
  90. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  91. package/dist/interactions/actions/defaults/slice.js.map +1 -1
  92. package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
  93. package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
  94. package/dist/interactions/actions/defaults/undoRedo.js +1 -1
  95. package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
  96. package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
  97. package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
  98. package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
  99. package/dist/interactions/actions/planeInput.js.map +1 -1
  100. package/dist/interactions/gestures/shared/screenTolerance.js.map +1 -1
  101. package/dist/{registerProgram-ateHDGjw.d.ts → registerProgram-BMZRDgrh.d.ts} +4 -0
  102. package/dist/renderer/cache/markerCommandCache.js +46 -42
  103. package/dist/renderer/cache/markerCommandCache.js.map +1 -1
  104. package/dist/renderer/cache/outlineStrokeMeshCache.js +30 -25
  105. package/dist/renderer/cache/outlineStrokeMeshCache.js.map +1 -1
  106. package/dist/renderer/cache/strokeMeshCache.js +42 -31
  107. package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
  108. package/dist/renderer/cullDrawCommands.js +32 -31
  109. package/dist/renderer/cullDrawCommands.js.map +1 -1
  110. package/dist/renderer/draw.js +315 -362
  111. package/dist/renderer/draw.js.map +1 -1
  112. package/dist/renderer/glyphOutlineMesh.js +67 -0
  113. package/dist/renderer/glyphOutlineMesh.js.map +1 -0
  114. package/dist/renderer/math/mat3.js +8 -3
  115. package/dist/renderer/math/mat3.js.map +1 -1
  116. package/dist/renderer/strokeLengths.js +50 -0
  117. package/dist/renderer/strokeLengths.js.map +1 -0
  118. package/dist/renderer.d.ts +2 -2
  119. package/dist/{test-seams-fYf6m5pu.d.ts → test-seams-CuIxe0xY.d.ts} +1 -1
  120. package/dist/test-seams.d.ts +2 -2
  121. package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
  122. package/dist/tools/builtin/pen/usePenTool.js +1 -1
  123. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  124. package/dist/tools/builtin/select/selectionContributions.js +43 -23
  125. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  126. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  127. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  128. package/dist/version.js +1 -1
  129. package/package.json +17 -17
package/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
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
+
3
52
  ## 1.8.1
4
53
 
5
54
  ### Patch 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
- | `pick` | the select tool, active from the start: click to pick, drag on empty to marquee, click on empty to clear; the selection outline |
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
- | `transform` | resize and rotation handles |
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` | all of the above |
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((deps.selection as SelectionApi).get()),
119
+ run: (deps) => publish(deps.selection?.get() ?? []),
116
120
  },
117
121
  },
118
122
  }}
@@ -1,4 +1,4 @@
1
- import { G as GlMat3, a as ShaderProgramHandle, b as ShaderUniform, c as ShaderProgram } from './registerProgram-ateHDGjw.js';
1
+ import { G as GlMat3, a as ShaderProgramHandle, b as ShaderUniform, c as ShaderProgram } from './registerProgram-BMZRDgrh.js';
2
2
  import { Mesh } from '@weasel-js/geom/tessellate';
3
3
  import { GradStop, ColorSpace, FillStyle, Stroke, ScreenLength } from '@weasel-js/paint';
4
4
  import { NodeId, Bounds, View, ModifierState, SelectionApi, ActionDeps, IngestItem, DragSample, DebugSink, Point2, LongPressState, AnyToolOf, Contribution as Contribution$1, ContributionChrome as ContributionChrome$1, Tool as Tool$1, ToolDef as ToolDef$1 } from '@weasel-js/routing';
@@ -112,8 +112,8 @@ declare const asNodeId: (s: string) => NodeId;
112
112
  * so handing over only the pose made the invalidation pay for a read nothing
113
113
  * could perform.
114
114
  */
115
- interface DerivedDep<TPose> {
116
- node: Node<unknown, string, TPose>;
115
+ interface DerivedDep<TPose, TData = unknown, TLayer extends string = string> {
116
+ node: Node<TData, TLayer, TPose>;
117
117
  /** Its override when it has one, else its own derived pose, else the pose
118
118
  * the document stores — `effectivePose`, the same answer the renderer uses. */
119
119
  pose: TPose;
@@ -126,12 +126,21 @@ interface DerivedDep<TPose> {
126
126
  * about costs nothing. */
127
127
  readonly path: Path | null;
128
128
  }
129
+ /** `F` with its parameters checked bivariantly, as a method's are, so that a
130
+ * `Scene<D, L, MyPose>` is a `Scene<unknown, string, unknown>`. A callback for
131
+ * an unrelated pose is still rejected; one for a narrower pose is not. */
132
+ type Bivariant<F extends (...args: never[]) => unknown> = {
133
+ method(...args: Parameters<F>): ReturnType<F>;
134
+ }['method'];
129
135
  /** Computes a node's path from its dependencies: the type of a node's
130
136
  * `derivePath` and of each entry in `SceneRegistry.derivePath`. */
131
- type DerivePathFn<TPose> = (node: Node<unknown, string, TPose>, deps: readonly (DerivedDep<TPose> | undefined)[]) => Path | null;
137
+ type DerivePathFn<TPose, TData = unknown, TLayer extends string = string> = Bivariant<(node: Node<TData, TLayer, TPose>, deps: readonly (DerivedDep<TPose, TData, TLayer> | undefined)[]) => Path | null>;
132
138
  /** Computes a node's pose from its dependencies: the type of a node's
133
139
  * `derivePose` and of each entry in `SceneRegistry.derivePose`. */
134
- type DerivePoseFn<TPose> = (node: Node<unknown, string, TPose>, deps: readonly (DerivedDep<TPose> | undefined)[]) => TPose | null;
140
+ type DerivePoseFn<TPose, TData = unknown, TLayer extends string = string> = Bivariant<(node: Node<TData, TLayer, TPose>, deps: readonly (DerivedDep<TPose, TData, TLayer> | undefined)[]) => TPose | null>;
141
+ /** A container's clip, from its pose: the type of `ContainerNode.clipFromPose`
142
+ * and of each entry in `SceneRegistry.clipFromPose`. */
143
+ type ClipFromPoseFn<TPose> = Bivariant<(pose: TPose) => Path | null>;
135
144
  interface NodeBase<TData, TLayer extends string, TPose> {
136
145
  id: NodeId;
137
146
  layer: TLayer;
@@ -165,14 +174,11 @@ interface NodeBase<TData, TLayer extends string, TPose> {
165
174
  * Returning `null` means "nothing to draw right now". Re-evaluated when a
166
175
  * dependency's world pose changes, never authored. Absolute-pose `Scene`
167
176
  * makes that the dependency's own pose, and an ancestor's move reaches it as
168
- * a `setPose` of its own from the container cascade.
169
- * `node` is deliberately widened: naming `TData`/`TLayer` here puts them in
170
- * a contravariant position, making `Scene` invariant in both and breaking
171
- * assignment kit-wide. The cost is that a `derivePath` casts to read `node.data`. */
172
- derivePath?: DerivePathFn<TPose>;
177
+ * a `setPose` of its own from the container cascade. */
178
+ derivePath?: DerivePathFn<TPose, TData, TLayer>;
173
179
  /** Computes this node's pose from its dependencies' poses, the same way
174
- * `derivePath` computes its path — same `dependsOn` list, same widened
175
- * `node`, same registry-keyed serialization.
180
+ * `derivePath` computes its path — same `dependsOn` list, same
181
+ * registry-keyed serialization.
176
182
  *
177
183
  * Where a derived path is resolved at paint time and reaches only the
178
184
  * painter, a derived pose is what the node *is* at: it feeds bounds,
@@ -183,7 +189,7 @@ interface NodeBase<TData, TLayer extends string, TPose> {
183
189
  * Returning `null` means "I have nothing to derive from right now", and
184
190
  * the node falls back to its authored `pose`. `setPose` on a derived node
185
191
  * still writes that authored pose; it is simply not what anything reads. */
186
- derivePose?: DerivePoseFn<TPose>;
192
+ derivePose?: DerivePoseFn<TPose, TData, TLayer>;
187
193
  }
188
194
  /** A node with no children — a shape, a label, an image. */
189
195
  interface LeafNode<TData, TLayer extends string, TPose = RectPose> extends NodeBase<TData, TLayer, TPose> {
@@ -200,7 +206,7 @@ interface ContainerNode<TData, TLayer extends string, TPose = RectPose> extends
200
206
  * means "clip everything out" (children render nowhere). When set, the
201
207
  * renderer rasterizes the returned path into the stencil buffer and
202
208
  * paints descendants only where it covers. */
203
- clipFromPose?: (pose: TPose) => Path | null;
209
+ clipFromPose?: ClipFromPoseFn<TPose>;
204
210
  /** How this container arranges its children. Whenever the container's
205
211
  * child set or order changes, or its bounds change size, the scene hands
206
212
  * the change to the strategy — `depart` for children that left it,
@@ -270,14 +276,14 @@ interface AddNodeSpec<TData, TLayer extends string, TPose = RectPose> {
270
276
  pickable?: boolean;
271
277
  /** Only meaningful when `kind === 'container'`. Attach a clip-path function
272
278
  * to the node; ignored for leaves. Mirrors `ContainerNode.clipFromPose`. */
273
- clipFromPose?: (pose: TPose) => Path | null;
279
+ clipFromPose?: ClipFromPoseFn<TPose>;
274
280
  /** Mirrors `SceneNode.dependsOn`. */
275
281
  dependsOn?: readonly NodeId[] | 'children';
276
282
  /** Mirrors `SceneNode.derivePath`. Taken as a live function; its registry key is
277
283
  * looked up from it, never passed in. */
278
- derivePath?: DerivePathFn<TPose>;
284
+ derivePath?: DerivePathFn<TPose, TData, TLayer>;
279
285
  /** Mirrors `SceneNode.derivePose`, on the same terms as `derivePath`. */
280
- derivePose?: DerivePoseFn<TPose>;
286
+ derivePose?: DerivePoseFn<TPose, TData, TLayer>;
281
287
  /** Mirrors `ContainerNode.layout`; ignored for leaves. */
282
288
  layout?: LayoutStrategy<TPose>;
283
289
  }
@@ -356,13 +362,13 @@ interface SerializedNode<TData, TLayer extends string, TPose> {
356
362
  /** Per-scene registry mapping string keys to live function references.
357
363
  * Passed to `createScene({ ..., registry })` and `sceneFromJSON(json, { registry })`.
358
364
  * Each function-field type has its own keyed map. */
359
- interface SceneRegistry<TPose> {
365
+ interface SceneRegistry<TPose, TData = unknown, TLayer extends string = string> {
360
366
  /** Maps registry keys to `clipFromPose` factory functions for container nodes. */
361
- clipFromPose?: Readonly<Record<string, (pose: TPose) => Path | null>>;
367
+ clipFromPose?: Readonly<Record<string, ClipFromPoseFn<TPose>>>;
362
368
  /** Maps registry keys to `derivePath` functions for nodes with `dependsOn`. */
363
- derivePath?: Readonly<Record<string, DerivePathFn<TPose>>>;
369
+ derivePath?: Readonly<Record<string, DerivePathFn<TPose, TData, TLayer>>>;
364
370
  /** Maps registry keys to `derivePose` functions for nodes with `dependsOn`. */
365
- derivePose?: Readonly<Record<string, DerivePoseFn<TPose>>>;
371
+ derivePose?: Readonly<Record<string, DerivePoseFn<TPose, TData, TLayer>>>;
366
372
  /** Maps registry keys to container layout strategies. */
367
373
  layout?: Readonly<Record<string, LayoutStrategy<TPose>>>;
368
374
  }
@@ -405,7 +411,7 @@ interface UseSceneOptions<TData, TLayer extends string, TPose = RectPose> {
405
411
  generateId?: () => NodeId;
406
412
  /** Per-scene registry for non-serializable function fields (clipFromPose, etc.).
407
413
  * Required only when serializing/deserializing scenes that use function fields. */
408
- registry?: SceneRegistry<TPose>;
414
+ registry?: SceneRegistry<TPose, TData, TLayer>;
409
415
  /** How laid-out containers are measured. See {@link LayoutFrame}. */
410
416
  layoutFrame?: LayoutFrame<TPose>;
411
417
  /** When supplied, `scene.applyOps(ops, label)` consults this on every call.
@@ -501,7 +507,7 @@ interface Scene<TData, TLayer extends string, TPose = RectPose> {
501
507
  readonly layers: readonly LayerRecord<TLayer>[];
502
508
  /** The registry this scene resolves node functions against — the consumer's
503
509
  * entries over the kit's. */
504
- readonly registry: SceneRegistry<TPose>;
510
+ readonly registry: SceneRegistry<TPose, TData, TLayer>;
505
511
  get(id: NodeId): Node<TData, TLayer, TPose> | undefined;
506
512
  /** A token for the node `id` names right now, for telling one node from a
507
513
  * later one that reuses its id. It holds for as long as the node stays in
@@ -791,6 +797,13 @@ interface FitViewToBoundsOptions {
791
797
  * - `'stretch'`: per-axis scale. Bounds match viewport exactly; scale is non-uniform.
792
798
  */
793
799
  mode?: 'contain' | 'fill' | 'stretch';
800
+ /**
801
+ * Where bounds that overflow the viewport on an axis sit, which happens when
802
+ * `minScale` stops the fit or `mode` is `'fill'`. `'center'` (default) shows
803
+ * their middle; `'start'` shows their left or top edge, inset by `padding`.
804
+ * An axis that fits is centered either way.
805
+ */
806
+ anchor?: 'center' | 'start';
794
807
  }
795
808
  /**
796
809
  * Compute the target {@link View} that fits `bounds` (world space) inside
@@ -4154,7 +4167,8 @@ declare module '@weasel-js/routing' {
4154
4167
  * Scene tree — structural reads + undoable mutations.
4155
4168
  *
4156
4169
  * The entry uses the fully-erased form `Scene<unknown, string, unknown>`
4157
- * because `DepSchema` must be concrete. Actions that need a typed scene
4170
+ * because `DepSchema` must be concrete; any `Scene<MyData, MyLayer,
4171
+ * MyPose>` is assignable to it. Actions that need a typed scene back
4158
4172
  * should cast: `deps.scene as Scene<MyData, MyLayer, MyPose>`.
4159
4173
  */
4160
4174
  scene: Scene<unknown, string, unknown>;
@@ -4383,17 +4397,70 @@ type UseToolsOptions = UseToolsOptions$1<Overlay>;
4383
4397
  /** Routing's `useTools`, typed for `RenderLayer` overlays. */
4384
4398
  declare const useTools: (opts: UseToolsOptions) => ToolsApi;
4385
4399
 
4400
+ /**
4401
+ * The space a screen-pixel stroke is built in.
4402
+ *
4403
+ * A `{ px }` stroke is exact on screen only when its ribbon is offset in a
4404
+ * space that measures lengths the way the screen does. The transform's linear
4405
+ * part is `scale · R · P`: a uniform scale, a rotation (or reflection) that
4406
+ * moves no lengths, and `P`, the symmetric unit-determinant stretch left over.
4407
+ * Offsetting the ribbon after `P` and mapping it back through `P⁻¹` gives a
4408
+ * world-space mesh that the full transform turns into a ribbon of exactly
4409
+ * `width · scale` pixels in every direction — and keeps the mesh drawable by
4410
+ * every path that draws a world-space ribbon.
4411
+ *
4412
+ * `P` depends only on the lengths the transform induces (`LᵀL`), not on any
4413
+ * rotation applied after it, so a view that only pans or zooms uniformly keeps
4414
+ * the same metric and the ribbon cache keeps hitting.
4415
+ */
4416
+
4417
+ /** `P` = [[p, q], [q, r]], with `pr − q² = 1`. */
4418
+ interface StrokeMetric {
4419
+ readonly p: number;
4420
+ readonly q: number;
4421
+ readonly r: number;
4422
+ }
4423
+
4424
+ /**
4425
+ * Trimming a stroke's ends where each inset is measured.
4426
+ *
4427
+ * A head stops the line by a length in the head's own space, which need not be
4428
+ * the ribbon's: a world-sized head on a `{ px }` stroke stops it a world
4429
+ * distance short, a `{ px }` head on a world-width stroke a screen distance.
4430
+ * Under a stretching view one length means different distances in the two
4431
+ * spaces depending on which way the line runs, so each end is trimmed in its
4432
+ * own space rather than converted.
4433
+ */
4434
+
4435
+ /** A length along the line measured in `metric`'s space, or in world where
4436
+ * `metric` is `null`. */
4437
+ interface MeasuredLength {
4438
+ readonly length: number;
4439
+ readonly metric: StrokeMetric | null;
4440
+ }
4441
+ /** A plain number is a length in the ribbon's own space. */
4442
+ type InsetLength = number | MeasuredLength;
4443
+
4386
4444
  /** Options for stroke tessellation. */
4387
4445
  interface StrokeOptions {
4388
4446
  flattenTolerance?: number;
4389
4447
  /**
4390
- * Shorten each open subpath by this much at its start / end, in the same
4391
- * world units as the path. Stroke markers use this so a filled head is not
4392
- * speared by its own line; the caller resolves the distance, because this
4393
- * layer knows nothing about the marker registry.
4448
+ * Shorten each open subpath by this much at its start / end: a length in
4449
+ * the ribbon's space, or one measured in a space of its own. Stroke markers
4450
+ * use this so a filled head is not speared by its own line; the caller
4451
+ * resolves the distance, because this layer knows nothing about the marker
4452
+ * registry.
4453
+ */
4454
+ startInset?: InsetLength;
4455
+ endInset?: InsetLength;
4456
+ /**
4457
+ * Build the ribbon in this metric's space and map it back out, so the
4458
+ * transform it was derived from draws it at an exact screen width — see
4459
+ * `./metric`. Width and dash are then lengths in that space; `vertexWidths`
4460
+ * stay world widths. The path is flattened in world first, so the ribbon
4461
+ * follows the same vertices its fill does.
4394
4462
  */
4395
- startInset?: number;
4396
- endInset?: number;
4463
+ metric?: StrokeMetric;
4397
4464
  }
4398
4465
  /** Resolve a stroke width to world units. A number is already world units;
4399
4466
  * `{ px }` is screen pixels divided by the accumulated scale, so it holds its
@@ -4442,4 +4509,4 @@ declare function vignette({ amount, feather }: {
4442
4509
  feather?: number;
4443
4510
  }): Effect[];
4444
4511
 
4445
- export { type SvgIngestOptions as $, type PoseComposition as A, type PoseAdapter as B, type CullRect as C, type DrawCommand as D, type Effect as E, type GestureLifecycleOptions as F, type GroupDrawCommand as G, type SliceDep as H, IDENTITY_COLOR_MATRIX as I, type ToolsApi as J, type Tool as K, type Contribution as L, type LayerHit as M, type Node as N, type ChromeState as O, type PathDrawCommand as P, type VertexColorChannel as Q, type RenderOptions as R, SPRITE_STRIDE as S, type TextDrawCommand as T, type LayerGroup as U, type ViewAnimationOptions as V, WeaselRenderer as W, type InsertExtras as X, type SerializedScene as Y, type GeometryProjection as Z, type ContentHandlerEntry as _, type ImageDrawCommand as a, type LayoutMove as a$, type AnyTool as a0, type PanBounds as a1, type Animator as a2, ColorOverrideRegistry as a3, type UseSceneOptions as a4, type SceneRegistry as a5, type RegisteredOp as a6, type PoseOverrides as a7, type DerivedDep as a8, type ViewportDims as a9, type ContainerNode as aA, type ContributionChrome as aB, type CreateOutlinesResult as aC, type CustomPaintContext as aD, type DecayLoopConfig as aE, type DecayOptions as aF, type DerivePathFn as aG, type EditAnchorsDep as aH, type EventBooking as aI, type EventBookingHandle as aJ, type EventTrack as aK, type FitViewToBoundsOptions as aL, type HitTestView as aM, IDENTITY_POSE_COMPOSITION as aN, type IngestCtx as aO, type IngestionDep as aP, type InsertDep as aQ, type Interpolate as aR, type InterpolatorFactory as aS, type Keyframe as aT, type LassoSelectDep as aU, type LayerCommandCache as aV, type LayerDrawFailure as aW, type LayerDrawSpan as aX, type LayerRecord as aY, type LayoutDep as aZ, type LayoutFrame as a_, type ViewApi as aa, type Affordance as ab, type AffordanceRegion as ac, type AffordanceBinding as ad, type CommonAffordanceScratch as ae, type DerivePoseFn as af, type BooleansAdapter as ag, type CreateOutlinesAdapter as ah, type UseAnimatorOptions as ai, type AnimationHandle as aj, type SampledTrack as ak, type SceneArrivalHandler as al, type AddLayerSpec as am, type AddNodeSpec as an, type AnimateToBoundsOptions as ao, type AnimationInfo as ap, type AnimationKind as aq, type AnimatorEvent as ar, type AnimatorListener as as, type AreaSelectDep as at, type BooleanOp as au, type BooleanOpResult as av, type ClipboardDep as aw, type ClipboardIngestCtx as ax, type ColorOverride as ay, type ColorOverrideFn as az, type ImageMinification as b, loadTextOutlines as b$, type LeafNode as b0, type LiveAnimation as b1, type LoopFactory as b2, type LoopOptions as b3, type NestedTimeline as b4, type NodeAtPointDep as b5, type OutlinePathSpec as b6, type Overlay as b7, type PathInWorldPose as b8, type PhysicsHandle as b9, type TextPaintPath as bA, type TextToPathOptions as bB, type TimelineClock as bC, type TimelineEvent as bD, type TimelineHandle as bE, type TimelineOptions as bF, type TimelineTrack as bG, type ToolDef as bH, type Track as bI, type TweenLoopOptions as bJ, type TweenOptions as bK, type UseToolsOptions as bL, type UserLayerRecord as bM, VIEW_ANIMATION_KEY as bN, type ViewAnimationApi as bO, type ViewChannel as bP, applyBooleanOp as bQ, applyCreateOutlines as bR, asNodeId as bS, composeWorldPose as bT, createPointerStore as bU, depsUnchanged as bV, drawLayers as bW, drawOneLayer as bX, fitViewToBounds as bY, isLayerPainted as bZ, isLayerVisible as b_, type PhysicsOptions as ba, PointerContextProvider as bb, type PointerContextValue as bc, type PointerWorldPos as bd, type PoseClosure as be, type PoseOverride as bf, type ResizePolicy as bg, type SerializedLayer as bh, type SerializedNode as bi, type SnapDep as bj, type SpringOptions as bk, type StaggerBuilder as bl, type StaggerDelay as bm, type StaggerFactory as bn, type StaggerOptions as bo, type StaggerPerItem as bp, type StaggerSpringPoseOptions as bq, type StaggerTweenOptions as br, type SvgUnpacker as bs, type SystemLayerRecord as bt, type SystemLayerSpec as bu, type TextEditDep as bv, type TextNodeSource as bw, type TextOutlineSource as bx, TextOutlinesError as by, type TextOutlinesFailure as bz, type RenderSpan as c, pathInPoseFrame as c0, pathInWorld as c1, rebaseLocalPose as c2, registerContentHandler as c3, resolveStrokeWidth as c4, subscribeToSources as c5, textToPath as c6, textToPathsByPaint as c7, useDecayLoop as c8, usePointerContext as c9, usePointerPosition as ca, useTools as cb, useViewAnimation as cc, useViewAnimationOn as cd, worldEditToStorage as ce, type RenderSpanStats as d, type RenderStats as e, type RenderTarget as f, type ShaderDrawCommand as g, type SolidPaint as h, type SpriteSheet as i, type SpritesDrawCommand as j, type StrokeOptions as k, type WeaselRendererOptions as l, blur as m, buildGradientRamp as n, cullDrawCommands as o, frameRect as p, vignette as q, registerEffect as r, type RenderLayer as s, tessellateStroke as t, type Dims as u, viewToMat3 as v, type RedrawSource as w, type Scene as x, type RectPose as y, type InertiaConfig as z };
4512
+ export { type SvgIngestOptions as $, type PoseComposition as A, type PoseAdapter as B, type CullRect as C, type DrawCommand as D, type Effect as E, type GestureLifecycleOptions as F, type GroupDrawCommand as G, type SliceDep as H, IDENTITY_COLOR_MATRIX as I, type ToolsApi as J, type Tool as K, type Contribution as L, type LayerHit as M, type Node as N, type ChromeState as O, type PathDrawCommand as P, type VertexColorChannel as Q, type RenderOptions as R, SPRITE_STRIDE as S, type TextDrawCommand as T, type LayerGroup as U, type ViewAnimationOptions as V, WeaselRenderer as W, type InsertExtras as X, type SerializedScene as Y, type GeometryProjection as Z, type ContentHandlerEntry as _, type ImageDrawCommand as a, type LayoutFrame as a$, type AnyTool as a0, type PanBounds as a1, type Animator as a2, ColorOverrideRegistry as a3, type UseSceneOptions as a4, type SceneRegistry as a5, type RegisteredOp as a6, type PoseOverrides as a7, type DerivePoseFn as a8, type DerivePathFn as a9, type ColorOverride as aA, type ColorOverrideFn as aB, type ContainerNode as aC, type ContributionChrome as aD, type CreateOutlinesResult as aE, type CustomPaintContext as aF, type DecayLoopConfig as aG, type DecayOptions as aH, type EditAnchorsDep as aI, type EventBooking as aJ, type EventBookingHandle as aK, type EventTrack as aL, type FitViewToBoundsOptions as aM, type HitTestView as aN, IDENTITY_POSE_COMPOSITION as aO, type IngestCtx as aP, type IngestionDep as aQ, type InsertDep as aR, type Interpolate as aS, type InterpolatorFactory as aT, type Keyframe as aU, type LassoSelectDep as aV, type LayerCommandCache as aW, type LayerDrawFailure as aX, type LayerDrawSpan as aY, type LayerRecord as aZ, type LayoutDep as a_, type DerivedDep as aa, type ViewportDims as ab, type ViewApi as ac, type Affordance as ad, type AffordanceRegion as ae, type AffordanceBinding as af, type CommonAffordanceScratch as ag, type BooleansAdapter as ah, type CreateOutlinesAdapter as ai, type UseAnimatorOptions as aj, type AnimationHandle as ak, type SampledTrack as al, type SceneArrivalHandler as am, type AddLayerSpec as an, type AddNodeSpec as ao, type AnimateToBoundsOptions as ap, type AnimationInfo as aq, type AnimationKind as ar, type AnimatorEvent as as, type AnimatorListener as at, type AreaSelectDep as au, type BooleanOp as av, type BooleanOpResult as aw, type ClipFromPoseFn as ax, type ClipboardDep as ay, type ClipboardIngestCtx as az, type ImageMinification as b, isLayerVisible as b$, type LayoutMove as b0, type LeafNode as b1, type LiveAnimation as b2, type LoopFactory as b3, type LoopOptions as b4, type NestedTimeline as b5, type NodeAtPointDep as b6, type OutlinePathSpec as b7, type Overlay as b8, type PathInWorldPose as b9, type TextOutlinesFailure as bA, type TextPaintPath as bB, type TextToPathOptions as bC, type TimelineClock as bD, type TimelineEvent as bE, type TimelineHandle as bF, type TimelineOptions as bG, type TimelineTrack as bH, type ToolDef as bI, type Track as bJ, type TweenLoopOptions as bK, type TweenOptions as bL, type UseToolsOptions as bM, type UserLayerRecord as bN, VIEW_ANIMATION_KEY as bO, type ViewAnimationApi as bP, type ViewChannel as bQ, applyBooleanOp as bR, applyCreateOutlines as bS, asNodeId as bT, composeWorldPose as bU, createPointerStore as bV, depsUnchanged as bW, drawLayers as bX, drawOneLayer as bY, fitViewToBounds as bZ, isLayerPainted as b_, type PhysicsHandle as ba, type PhysicsOptions as bb, PointerContextProvider as bc, type PointerContextValue as bd, type PointerWorldPos as be, type PoseClosure as bf, type PoseOverride as bg, type ResizePolicy as bh, type SerializedLayer as bi, type SerializedNode as bj, type SnapDep as bk, type SpringOptions as bl, type StaggerBuilder as bm, type StaggerDelay as bn, type StaggerFactory as bo, type StaggerOptions as bp, type StaggerPerItem as bq, type StaggerSpringPoseOptions as br, type StaggerTweenOptions as bs, type SvgUnpacker as bt, type SystemLayerRecord as bu, type SystemLayerSpec as bv, type TextEditDep as bw, type TextNodeSource as bx, type TextOutlineSource as by, TextOutlinesError as bz, type RenderSpan as c, loadTextOutlines as c0, pathInPoseFrame as c1, pathInWorld as c2, rebaseLocalPose as c3, registerContentHandler as c4, resolveStrokeWidth as c5, subscribeToSources as c6, textToPath as c7, textToPathsByPaint as c8, useDecayLoop as c9, usePointerContext as ca, usePointerPosition as cb, useTools as cc, useViewAnimation as cd, useViewAnimationOn as ce, worldEditToStorage as cf, type RenderSpanStats as d, type RenderStats as e, type RenderTarget as f, type ShaderDrawCommand as g, type SolidPaint as h, type SpriteSheet as i, type SpritesDrawCommand as j, type StrokeOptions as k, type WeaselRendererOptions as l, blur as m, buildGradientRamp as n, cullDrawCommands as o, frameRect as p, vignette as q, registerEffect as r, type RenderLayer as s, tessellateStroke as t, type Dims as u, viewToMat3 as v, type RedrawSource as w, type Scene as x, type RectPose as y, type InertiaConfig as z };
@@ -5,10 +5,10 @@ import { useFrameLoop as r } from "./useFrameLoop.js";
5
5
  import { useRedrawOn as i } from "./useRedrawOn.js";
6
6
  import { useCanvasRenderer as a } from "./useCanvasRenderer.js";
7
7
  import { useCallback as o, useEffect as s, useLayoutEffect as c, useRef as l } from "react";
8
- import { useLatest as u } from "@weasel-js/react";
9
- import { jsx as d } from "react/jsx-runtime";
8
+ import { assignRef as u, useLatest as d } from "@weasel-js/react";
9
+ import { jsx as f } from "react/jsx-runtime";
10
10
  //#region src/canvas/DrawCanvas.tsx
11
- var f = {
11
+ var p = {
12
12
  x: 0,
13
13
  y: 0,
14
14
  scale: {
@@ -16,52 +16,52 @@ var f = {
16
16
  y: 1
17
17
  }
18
18
  };
19
- function p(p) {
20
- let { width: m, height: h, draw: g, background: _, redrawOn: v, dpr: y, className: b, canvasRef: x } = p, S = p.view ? t(p.view) : f, C = l(null), w = o((e) => {
21
- C.current = e, typeof x == "function" ? x(e) : x && (x.current = e);
22
- }, [x]), { paint: T } = a(), E = u(() => {
23
- let t = C.current;
19
+ function m(m) {
20
+ let { width: h, height: g, draw: _, background: v, redrawOn: y, dpr: b, className: x, canvasRef: S } = m, C = m.view ? t(m.view) : p, w = l(null), T = o((e) => {
21
+ w.current = e, u(S, e);
22
+ }, [S]), { paint: E } = a(), D = d(() => {
23
+ let t = w.current;
24
24
  if (!t) return !1;
25
- let n = typeof g == "function" ? g({
26
- width: m,
27
- height: h
28
- }) : g, r = [];
29
- return _ && r.push({
25
+ let n = typeof _ == "function" ? _({
26
+ width: h,
27
+ height: g
28
+ }) : _, r = [];
29
+ return v && r.push({
30
30
  kind: "path",
31
31
  path: {
32
32
  kind: "rect",
33
33
  x: 0,
34
34
  y: 0,
35
- width: m,
36
- height: h
35
+ width: h,
36
+ height: g
37
37
  },
38
- fill: _
38
+ fill: v
39
39
  }), r.push({
40
40
  kind: "group",
41
- transform: e(S),
41
+ transform: e(C),
42
42
  children: [...n]
43
- }), T(t, r, S, {
44
- width: m,
45
- height: h,
46
- dpr: y
43
+ }), E(t, r, C, {
44
+ width: h,
45
+ height: g,
46
+ dpr: b
47
47
  });
48
- }), { requestRedraw: D } = r(o(() => E.current(), [E]), { target: C }), O = l(!1);
48
+ }), { requestRedraw: O } = r(o(() => D.current(), [D]), { target: w }), k = l(!1);
49
49
  return c(() => {
50
- if (O.current) {
51
- D();
50
+ if (k.current) {
51
+ O();
52
52
  return;
53
53
  }
54
- O.current = E.current(), O.current || D();
55
- }), n(D), i(v, D), s(() => () => {
56
- O.current = !1;
57
- }, []), /* @__PURE__ */ d("canvas", {
58
- ref: w,
59
- width: m,
60
- height: h,
61
- className: b
54
+ k.current = D.current(), k.current || O();
55
+ }), n(O), i(y, O), s(() => () => {
56
+ k.current = !1;
57
+ }, []), /* @__PURE__ */ f("canvas", {
58
+ ref: T,
59
+ width: h,
60
+ height: g,
61
+ className: x
62
62
  });
63
63
  }
64
64
  //#endregion
65
- export { p as DrawCanvas };
65
+ export { m as DrawCanvas };
66
66
 
67
67
  //# sourceMappingURL=DrawCanvas.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DrawCanvas.js","names":["IDENTITY_VIEW","DrawCanvas","props","view","normalizeView","canvasElRef","useRef","setRefs","useCallback","el","canvasRef","paintCanvas","useCanvasRenderer","paintRef","useLatest","content","draw","commands","background","viewToMat3","useFrameLoop","paintedRef","useLayoutEffect","requestRedraw","useLateContentRedraw","useRedrawOn","redrawOn","useEffect"],"sources":["../../src/canvas/DrawCanvas.tsx"],"sourcesContent":["/**\n * `<DrawCanvas>` — a sized, DPR-correct WebGL2 surface that paints a list of\n * draw commands, with no scene, tools or dispatcher behind it.\n *\n * It paints through the same per-canvas renderer as `<SceneViewCanvas>`\n * (`useCanvasRenderer`), on a `useFrameLoop` that stops while the canvas is off\n * screen or the tab is hidden. Like `<SceneViewCanvas>` it attaches no event\n * listeners and emits no inline styles: size the element with `className`.\n */\nimport { useCallback, useEffect, useLayoutEffect, useRef } from 'react';\nimport type { Ref } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport type { FillStyle } from '@weasel-js/paint';\nimport { useFrameLoop } from './useFrameLoop';\nimport { useLateContentRedraw } from './useLateContentRedraw';\nimport { useRedrawOn } from './useRedrawOn';\nimport { useCanvasRenderer } from './useCanvasRenderer';\nimport { viewToMat3 } from '../renderer/math/viewToMat3';\nimport type { DrawCommand } from '../renderer/DrawCommand';\nimport type { RedrawSource } from '../core/layers/render';\nimport { normalizeView, type View } from '../core/viewport/view';\n\n/** What a `<DrawCanvas>` paints: a command list, or a function returning one\n * that is called on every paint with the surface's CSS-pixel size. */\nexport type DrawCanvasDraw =\n | readonly DrawCommand[]\n | ((size: { width: number; height: number }) => readonly DrawCommand[]);\n\n/** Props for `<DrawCanvas>`. */\nexport interface DrawCanvasProps {\n /** CSS-pixel width. The drawing buffer is `width × dpr`. */\n width: number;\n /** CSS-pixel height. */\n height: number;\n /** The commands to paint, in the coordinate space `view` maps to the\n * screen — CSS pixels when `view` is omitted. A new array repaints on the\n * next frame. A function is read at paint time, so state it reads outside\n * React repaints when one of `redrawOn`'s sources notifies. */\n draw: DrawCanvasDraw;\n /** Camera over `draw`'s commands. Defaults to the identity view. */\n view?: View;\n /** Fills the whole surface beneath `draw`, in screen space. The canvas is\n * transparent without it. */\n background?: FillStyle;\n /** External state a `draw` function reads. Each source is subscribed while\n * mounted and repaints the canvas when it notifies. */\n redrawOn?: readonly RedrawSource[];\n /** Device-pixel ratio. Defaults to `window.devicePixelRatio`, read per\n * paint. */\n dpr?: number;\n /** CSS class for the `<canvas>` element. */\n className?: string;\n /** Receives the underlying `<canvas>`, for pointer listeners or `toBlob`. */\n canvasRef?: Ref<HTMLCanvasElement>;\n}\n\nconst IDENTITY_VIEW: View = { x: 0, y: 0, scale: { x: 1, y: 1 } };\n\n/**\n * Paint draw commands onto a canvas of its own. For a surface that only draws —\n * a package demo, a swatch, a preview — where a `<SceneCanvas>` would bring a\n * scene, tools and an input dispatcher that nothing uses.\n */\nexport function DrawCanvas(props: DrawCanvasProps) {\n const { width, height, draw, background, redrawOn, dpr, className, canvasRef } = props;\n const view = props.view ? normalizeView(props.view) : IDENTITY_VIEW;\n\n const canvasElRef = useRef<HTMLCanvasElement | null>(null);\n const setRefs = useCallback((el: HTMLCanvasElement | null) => {\n canvasElRef.current = el;\n if (typeof canvasRef === 'function') canvasRef(el);\n else if (canvasRef) (canvasRef as { current: HTMLCanvasElement | null }).current = el;\n }, [canvasRef]);\n\n const { paint: paintCanvas } = useCanvasRenderer();\n const paint = (): boolean => {\n const el = canvasElRef.current;\n if (!el) return false;\n const content = typeof draw === 'function' ? draw({ width, height }) : draw;\n const commands: DrawCommand[] = [];\n if (background) {\n commands.push({ kind: 'path', path: { kind: 'rect', x: 0, y: 0, width, height }, fill: background });\n }\n commands.push({ kind: 'group', transform: viewToMat3(view), children: [...content] });\n return paintCanvas(el, commands, view, { width, height, dpr });\n };\n const paintRef = useLatest(paint);\n const { requestRedraw } = useFrameLoop(\n useCallback(() => paintRef.current(), [paintRef]),\n { target: canvasElRef },\n );\n\n // Every committed render marks the surface dirty; the first paints in place\n // so the first frame shown is the content rather than a blank canvas.\n const paintedRef = useRef(false);\n useLayoutEffect(() => {\n if (paintedRef.current) { requestRedraw(); return; }\n paintedRef.current = paintRef.current();\n if (!paintedRef.current) requestRedraw();\n });\n\n useLateContentRedraw(requestRedraw);\n useRedrawOn(redrawOn, requestRedraw);\n\n // `useCanvasRenderer` frees the renderer on unmount; a remount (StrictMode)\n // paints its first frame in place again.\n useEffect(() => () => { paintedRef.current = false; }, []);\n\n return <canvas ref={setRefs} width={width} height={height} className={className} />;\n}\n"],"mappings":";;;;;;;;;;AAwDA,IAAMA,IAAsB;CAAE,GAAG;CAAG,GAAG;CAAG,OAAO;EAAE,GAAG;EAAG,GAAG;CAAE;AAAE;AAOhE,SAAgBC,EAAW,GAAwB;CACjD,IAAM,EAAE,UAAO,WAAQ,SAAM,eAAY,aAAU,QAAK,cAAW,iBAAcC,GAC3EC,IAAOD,EAAM,OAAOE,EAAcF,EAAM,IAAI,IAAIF,GAEhDK,IAAcC,EAAiC,IAAI,GACnDC,IAAUC,GAAa,MAAiC;EAE5D,AADA,EAAY,UAAUC,GAClB,OAAOC,KAAc,aAAYA,EAAUD,CAAE,IACxCC,MAAW,EAAqD,UAAUD;CACrF,GAAG,CAACC,CAAS,CAAC,GAER,EAAE,OAAOC,MAAgBC,EAAkB,GAY3CC,IAAWC,QAXY;EAC3B,IAAML,IAAKJ,EAAY;EACvB,IAAI,CAACI,GAAI,OAAO;EAChB,IAAMM,IAAU,OAAOC,KAAS,aAAaA,EAAK;GAAE;GAAO;EAAO,CAAC,IAAIA,GACjEC,IAA0B,CAAC;EAKjC,OAJIC,KACFD,EAAS,KAAK;GAAE,MAAM;GAAQ,MAAM;IAAE,MAAM;IAAQ,GAAG;IAAG,GAAG;IAAG;IAAO;GAAO;GAAG,MAAMC;EAAW,CAAC,GAErGD,EAAS,KAAK;GAAE,MAAM;GAAS,WAAWE,EAAWhB,CAAI;GAAG,UAAU,CAAC,GAAGY,CAAO;EAAE,CAAC,GAC7EJ,EAAYF,GAAIQ,GAAUd,GAAM;GAAE;GAAO;GAAQ;EAAI,CAAC;CAC/D,CACgC,GAC1B,EAAE,qBAAkBiB,EACxBZ,QAAkBK,EAAS,QAAQ,GAAG,CAACA,CAAQ,CAAC,GAChD,EAAE,QAAQR,EAAY,CACxB,GAIMgB,IAAaf,EAAO,EAAK;CAc/B,OAbAgB,QAAsB;EACpB,IAAID,EAAW,SAAS;GAAE,EAAc;GAAG;EAAQ;EAEnD,AADA,EAAW,UAAUR,EAAS,QAAQ,GACjCQ,EAAW,WAASE,EAAc;CACzC,CAAC,GAEDC,EAAqBD,CAAa,GAClCE,EAAYC,GAAUH,CAAa,GAInCI,cAAsB;EAAE,EAAW,UAAU;CAAO,GAAG,CAAC,CAAC,GAElD,kBAAC,UAAD;EAAQ,KAAKpB;EAAgB;EAAe;EAAmB;CAAY,CAAA;AACpF"}
1
+ {"version":3,"file":"DrawCanvas.js","names":["IDENTITY_VIEW","DrawCanvas","props","view","normalizeView","canvasElRef","useRef","setRefs","useCallback","el","assignRef","canvasRef","paintCanvas","useCanvasRenderer","paintRef","useLatest","content","draw","commands","background","viewToMat3","useFrameLoop","paintedRef","useLayoutEffect","requestRedraw","useLateContentRedraw","useRedrawOn","redrawOn","useEffect"],"sources":["../../src/canvas/DrawCanvas.tsx"],"sourcesContent":["/**\n * `<DrawCanvas>` — a sized, DPR-correct WebGL2 surface that paints a list of\n * draw commands, with no scene, tools or dispatcher behind it.\n *\n * It paints through the same per-canvas renderer as `<SceneViewCanvas>`\n * (`useCanvasRenderer`), on a `useFrameLoop` that stops while the canvas is off\n * screen or the tab is hidden. Like `<SceneViewCanvas>` it attaches no event\n * listeners and emits no inline styles: size the element with `className`.\n */\nimport { useCallback, useEffect, useLayoutEffect, useRef } from 'react';\nimport type { Ref } from 'react';\nimport { assignRef, useLatest } from '@weasel-js/react';\nimport type { FillStyle } from '@weasel-js/paint';\nimport { useFrameLoop } from './useFrameLoop';\nimport { useLateContentRedraw } from './useLateContentRedraw';\nimport { useRedrawOn } from './useRedrawOn';\nimport { useCanvasRenderer } from './useCanvasRenderer';\nimport { viewToMat3 } from '../renderer/math/viewToMat3';\nimport type { DrawCommand } from '../renderer/DrawCommand';\nimport type { RedrawSource } from '../core/layers/render';\nimport { normalizeView, type View } from '../core/viewport/view';\n\n/** What a `<DrawCanvas>` paints: a command list, or a function returning one\n * that is called on every paint with the surface's CSS-pixel size. */\nexport type DrawCanvasDraw =\n | readonly DrawCommand[]\n | ((size: { width: number; height: number }) => readonly DrawCommand[]);\n\n/** Props for `<DrawCanvas>`. */\nexport interface DrawCanvasProps {\n /** CSS-pixel width. The drawing buffer is `width × dpr`. */\n width: number;\n /** CSS-pixel height. */\n height: number;\n /** The commands to paint, in the coordinate space `view` maps to the\n * screen — CSS pixels when `view` is omitted. A new array repaints on the\n * next frame. A function is read at paint time, so state it reads outside\n * React repaints when one of `redrawOn`'s sources notifies. */\n draw: DrawCanvasDraw;\n /** Camera over `draw`'s commands. Defaults to the identity view. */\n view?: View;\n /** Fills the whole surface beneath `draw`, in screen space. The canvas is\n * transparent without it. */\n background?: FillStyle;\n /** External state a `draw` function reads. Each source is subscribed while\n * mounted and repaints the canvas when it notifies. */\n redrawOn?: readonly RedrawSource[];\n /** Device-pixel ratio. Defaults to `window.devicePixelRatio`, read per\n * paint. */\n dpr?: number;\n /** CSS class for the `<canvas>` element. */\n className?: string;\n /** Receives the underlying `<canvas>`, for pointer listeners or `toBlob`. */\n canvasRef?: Ref<HTMLCanvasElement>;\n}\n\nconst IDENTITY_VIEW: View = { x: 0, y: 0, scale: { x: 1, y: 1 } };\n\n/**\n * Paint draw commands onto a canvas of its own. For a surface that only draws —\n * a package demo, a swatch, a preview — where a `<SceneCanvas>` would bring a\n * scene, tools and an input dispatcher that nothing uses.\n */\nexport function DrawCanvas(props: DrawCanvasProps) {\n const { width, height, draw, background, redrawOn, dpr, className, canvasRef } = props;\n const view = props.view ? normalizeView(props.view) : IDENTITY_VIEW;\n\n const canvasElRef = useRef<HTMLCanvasElement | null>(null);\n const setRefs = useCallback((el: HTMLCanvasElement | null) => {\n canvasElRef.current = el;\n assignRef(canvasRef, el);\n }, [canvasRef]);\n\n const { paint: paintCanvas } = useCanvasRenderer();\n const paint = (): boolean => {\n const el = canvasElRef.current;\n if (!el) return false;\n const content = typeof draw === 'function' ? draw({ width, height }) : draw;\n const commands: DrawCommand[] = [];\n if (background) {\n commands.push({ kind: 'path', path: { kind: 'rect', x: 0, y: 0, width, height }, fill: background });\n }\n commands.push({ kind: 'group', transform: viewToMat3(view), children: [...content] });\n return paintCanvas(el, commands, view, { width, height, dpr });\n };\n const paintRef = useLatest(paint);\n const { requestRedraw } = useFrameLoop(\n useCallback(() => paintRef.current(), [paintRef]),\n { target: canvasElRef },\n );\n\n // Every committed render marks the surface dirty; the first paints in place\n // so the first frame shown is the content rather than a blank canvas.\n const paintedRef = useRef(false);\n useLayoutEffect(() => {\n if (paintedRef.current) { requestRedraw(); return; }\n paintedRef.current = paintRef.current();\n if (!paintedRef.current) requestRedraw();\n });\n\n useLateContentRedraw(requestRedraw);\n useRedrawOn(redrawOn, requestRedraw);\n\n // `useCanvasRenderer` frees the renderer on unmount; a remount (StrictMode)\n // paints its first frame in place again.\n useEffect(() => () => { paintedRef.current = false; }, []);\n\n return <canvas ref={setRefs} width={width} height={height} className={className} />;\n}\n"],"mappings":";;;;;;;;;;AAwDA,IAAMA,IAAsB;CAAE,GAAG;CAAG,GAAG;CAAG,OAAO;EAAE,GAAG;EAAG,GAAG;CAAE;AAAE;AAOhE,SAAgBC,EAAW,GAAwB;CACjD,IAAM,EAAE,UAAO,WAAQ,SAAM,eAAY,aAAU,QAAK,cAAW,iBAAcC,GAC3EC,IAAOD,EAAM,OAAOE,EAAcF,EAAM,IAAI,IAAIF,GAEhDK,IAAcC,EAAiC,IAAI,GACnDC,IAAUC,GAAa,MAAiC;EAE5D,AADA,EAAY,UAAUC,GACtBC,EAAUC,GAAWF,CAAE;CACzB,GAAG,CAACE,CAAS,CAAC,GAER,EAAE,OAAOC,MAAgBC,EAAkB,GAY3CC,IAAWC,QAXY;EAC3B,IAAMN,IAAKJ,EAAY;EACvB,IAAI,CAACI,GAAI,OAAO;EAChB,IAAMO,IAAU,OAAOC,KAAS,aAAaA,EAAK;GAAE;GAAO;EAAO,CAAC,IAAIA,GACjEC,IAA0B,CAAC;EAKjC,OAJIC,KACFD,EAAS,KAAK;GAAE,MAAM;GAAQ,MAAM;IAAE,MAAM;IAAQ,GAAG;IAAG,GAAG;IAAG;IAAO;GAAO;GAAG,MAAMC;EAAW,CAAC,GAErGD,EAAS,KAAK;GAAE,MAAM;GAAS,WAAWE,EAAWjB,CAAI;GAAG,UAAU,CAAC,GAAGa,CAAO;EAAE,CAAC,GAC7EJ,EAAYH,GAAIS,GAAUf,GAAM;GAAE;GAAO;GAAQ;EAAI,CAAC;CAC/D,CACgC,GAC1B,EAAE,qBAAkBkB,EACxBb,QAAkBM,EAAS,QAAQ,GAAG,CAACA,CAAQ,CAAC,GAChD,EAAE,QAAQT,EAAY,CACxB,GAIMiB,IAAahB,EAAO,EAAK;CAc/B,OAbAiB,QAAsB;EACpB,IAAID,EAAW,SAAS;GAAE,EAAc;GAAG;EAAQ;EAEnD,AADA,EAAW,UAAUR,EAAS,QAAQ,GACjCQ,EAAW,WAASE,EAAc;CACzC,CAAC,GAEDC,EAAqBD,CAAa,GAClCE,EAAYC,GAAUH,CAAa,GAInCI,cAAsB;EAAE,EAAW,UAAU;CAAO,GAAG,CAAC,CAAC,GAElD,kBAAC,UAAD;EAAQ,KAAKrB;EAAgB;EAAe;EAAmB;CAAY,CAAA;AACpF"}