@weasel-js/core 1.5.0 → 1.5.2

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 (42) hide show
  1. package/CHANGELOG.md +501 -0
  2. package/dist/autoPoseDescriptor-Dr6CwNZK.d.ts +26 -0
  3. package/dist/{chunk-BDWAA634.js → chunk-44EWMJV2.js} +3 -3
  4. package/dist/{chunk-BDWAA634.js.map → chunk-44EWMJV2.js.map} +1 -1
  5. package/dist/{chunk-MQI4PIX3.js → chunk-H6N2JSZ3.js} +3 -3
  6. package/dist/{chunk-MQI4PIX3.js.map → chunk-H6N2JSZ3.js.map} +1 -1
  7. package/dist/chunk-HPFJUUTF.js +167 -0
  8. package/dist/chunk-HPFJUUTF.js.map +1 -0
  9. package/dist/{chunk-UCPV7JXC.js → chunk-L5YNZYEG.js} +9 -169
  10. package/dist/chunk-L5YNZYEG.js.map +1 -0
  11. package/dist/chunk-PNNZSYRD.js +318 -0
  12. package/dist/chunk-PNNZSYRD.js.map +1 -0
  13. package/dist/{chunk-MG7OXCAI.js → chunk-QEZL53V6.js} +2493 -1580
  14. package/dist/chunk-QEZL53V6.js.map +1 -0
  15. package/dist/clipboard.d.ts +1 -1
  16. package/dist/clone.d.ts +2 -2
  17. package/dist/{depSchema-nMqj_qTM.d.ts → depSchema-FKQBEfg7.d.ts} +116 -125
  18. package/dist/{grid-BrIa38gG.d.ts → grid-CuxXjaQV.d.ts} +22 -6
  19. package/dist/index.d.ts +780 -408
  20. package/dist/index.js +6 -4
  21. package/dist/insert.d.ts +3 -3
  22. package/dist/insert.js +2 -1
  23. package/dist/insert.js.map +1 -1
  24. package/dist/math-CeGmGPy8.d.ts +267 -0
  25. package/dist/math.d.ts +5 -0
  26. package/dist/math.js +4 -0
  27. package/dist/math.js.map +1 -0
  28. package/dist/move.d.ts +4 -4
  29. package/dist/move.js +3 -2
  30. package/dist/move.js.map +1 -1
  31. package/dist/{options-BDyCnrp8.d.ts → options-BPcmwYk7.d.ts} +1 -1
  32. package/dist/{autoPoseDescriptor-CvjflWJK.d.ts → pointSnapToGrid-CgcK2R_I.d.ts} +2 -26
  33. package/dist/{poseDescriptor-CGOgIgf8.d.ts → poseDescriptor-PgfVKfa0.d.ts} +1 -1
  34. package/dist/renderer.d.ts +7 -8
  35. package/dist/renderer.js +6 -4
  36. package/dist/resize.d.ts +5 -4
  37. package/dist/resize.js +3 -2
  38. package/dist/{types-DMyo7dnM.d.ts → types-BHGdrOcu.d.ts} +1 -1
  39. package/dist/{types-DtjCJA5r.d.ts → types-BdaK9PcP.d.ts} +1 -1
  40. package/package.json +14 -10
  41. package/dist/chunk-MG7OXCAI.js.map +0 -1
  42. package/dist/chunk-UCPV7JXC.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,506 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.5.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 1c695cb: `derivePose` now notices a dependency that moved with nothing pushed behind it.
8
+ The memo is keyed on the deriving node's own authored pose, which only changes
9
+ when someone pushes an invalidation — so an ancestor's frame moving, a removal,
10
+ a dependency appearing, or a lookup answering poses of its own all left the
11
+ derived pose stale. It now records what it derived from and re-checks it by
12
+ value on a memo hit, the way `resolveDerivedPath` already did. The compare both
13
+ paths use is shared in `depMemo.ts`.
14
+ - 24a2dae: Close the places where two tiers spelled one concept differently.
15
+
16
+ **A fixed pan bug.** `viewport.dragPan` fell back from `drag.screenDelta` to
17
+ the world `drag.delta` and then divided by the zoom anyway, panning at
18
+ 1/scale² for any event source that supplies no `clientX`/`clientY` — which is
19
+ every synthesized `InputEvent`, since those fields are optional. It now
20
+ reconstructs the client delta exactly, by undoing each end of the world delta
21
+ against the view that produced it.
22
+
23
+ **Breaking, renames.** `ClickEvent`, `DoubleClickEvent` and `ContextMenuEvent`
24
+ carry their world point as `x`/`y`, matching every other kind in `InputEvent`;
25
+ `worldX`/`worldY` are gone, and a consumer who set `x`/`y` no longer silently
26
+ lands at the origin. All three now also carry `clientX`/`clientY`, so a
27
+ context-menu action can finally read `ctx.screen` — the case that surface was
28
+ added for. The renderer's `Mat3` is `GlMat3`, freeing `Mat3` to mean geom's
29
+ affine in a file that imports from both. `translatePolygonInPlace` is
30
+ gone: it was the one sanctioned writer into a committed path's coord buffer,
31
+ documented as overlay-only, and nothing called it. `@weasel-js/font` exports `FontStyle`
32
+ in place of `OutlineFontStyle`. `@weasel-js/labkit` no longer exports
33
+ `useOrbit`, `OrbitView`, `Vec3` or their helpers: `@weasel-js/kernel3d` owns
34
+ the orbit camera and `@weasel-js/geom/3d` owns `Vec3`. `ToolCtx.screenPoint`
35
+ was declared and never written by anything; it is gone.
36
+
37
+ **Breaking, types narrowed.** geom's `Mat3` and `Box` are readonly tuples,
38
+ matching the reason `geom/3d` already gives for its own. `History.entries()`
39
+ returns `readonly` arrays, which is what its docstring always asked callers to
40
+ assume.
41
+
42
+ **One type where there were two.** `@weasel-js/svg`'s `Matrix` is geom's
43
+ `Mat3`, and its duplicate `multiply` is geom's; `SvgStroke.width` is
44
+ `ScreenLength` rather than that union written out again. `kernel3d`'s
45
+ `ViewportRect` is `ScreenBox` — one rectangle spelling instead of `w`/`h`
46
+ beside `width`/`height` eight lines apart. The renderer's `View` is routing's.
47
+ Core's `Vec2` is routing's `Point2`, and `Pt` is gone from the barrel.
48
+
49
+ **Additions.** `oklchDegToHex` / `hexToOklchDeg` / `OklchDeg` in
50
+ `@weasel-js/paint` — the degrees-and-hex form `@weasel-js/ui` and
51
+ `@weasel-js/theme` had each built for themselves. `srgbFloatToOklab`, for
52
+ callers holding 0..1 floats; feeding those to `srgbU8ToOklab` truncated where
53
+ paint's own internal conversion rounds. `mat3.toAffine` / `mat3.fromAffine`
54
+ name the repack between the GL layout and geom's.
55
+
56
+ **Corrections.** `RECT_POSE_DESCRIPTOR` implements `getRotation`, so a pose it
57
+ rotated no longer reports itself unrotated to `useResize` and to diagram's port
58
+ placement. `ToolDef.capabilities` is documented as reaching
59
+ `Tool.eligibility.capabilities`, which is where it actually goes — following
60
+ the old text gave `undefined`, and `eligibleForMode` turns that into a tool
61
+ that vanishes from every mode. `MultitouchEvent.centroid` is documented as
62
+ canvas-local, which is what the dispatcher hands over. `drag.points` is a
63
+ snapshot on `onEnd` rather than the dispatcher's live accumulator.
64
+
65
+ `tsconfig.json` now typechecks `packages/routing`, `cursor`, `bidi` and
66
+ `loupe`, which it had never included.
67
+ - 564deb4: Take typed units past linear factors.
68
+
69
+ A unit table entry is now `number | { factor, offset }`, so a scale that
70
+ disagrees with the base about where zero sits — degC against K — is
71
+ expressible; a bare number stays the shorthand for a pure factor, so every
72
+ existing table reads as it did. `unitScale(system, unit)` is the one reader
73
+ that widens the shorthand and defaults the offset, and `resolveUnit` /
74
+ `formatUnit` both go through it.
75
+
76
+ `parseNumber` reads a compound value: `5ft 3in` is 63in, a leading sign carries
77
+ across every term, and a term with no unit (or a unit the field does not
78
+ accept) leaves the value unreadable rather than half-read. Offsets have no
79
+ meaning in a sum, so a compound value takes pure scales only.
80
+
81
+ `prefUnit` converts through offsets in both directions and now returns a
82
+ `format`, which is what the SelectionPanel slider readout draws — `formatUnit`
83
+ had no callers before this.
84
+ - 8ffd746: State the port-curve rule once, in `@weasel-js/geom`.
85
+
86
+ A routed edge leaves a port along its normal and arrives at the next against
87
+ that port's normal, with the controls reaching 0.4 of the straight-line
88
+ distance. That rule lived inside `@weasel-js/diagram`'s `bezier` router with
89
+ its reach constant module-private, so a 3D consumer had no way to share even
90
+ the number.
91
+
92
+ It is now `portControls` / `portCurvePoints` / `PORT_REACH`, exported from
93
+ both `@weasel-js/geom` and `@weasel-js/geom/3d`. One implementation over loose
94
+ components sits behind both, so the two dimensions cannot disagree: every
95
+ operation in it is closed on the plane z = 0, and a planar problem answered
96
+ through the 3D entry returns the same numbers, not an approximation. Each
97
+ barrel wraps it in its own tier's currency — scalars for 2D, matching
98
+ `cubicEvalAt`, and `Vec3` for 3D.
99
+
100
+ `bezier` is unchanged in behavior; it calls through. `@weasel-js/diagram` now
101
+ declares the `@weasel-js/geom` peer it had been importing without.
102
+
103
+ Also moves `Vec2`'s declaration out of `core/geometry/polygonHitTestRect.ts`,
104
+ a polygon-versus-rect hit-testing helper it had been an incidental local in,
105
+ into `core/geometry/vec2.ts`. No API change — the barrel exports the same
106
+ type from a place you would look for it.
107
+ - 37e8105: Eight character-styling glyphs, and a text tool that declares its own options.
108
+
109
+ `bold`, `italic`, `underline`, `strikethrough`, `overline`, `superscript`,
110
+ `subscript` and `code` join the icon set. The five flags are letterforms
111
+ wearing the treatment they apply, because that is what the controls they label
112
+ replaced.
113
+
114
+ `useTextTool.options` declares the character half of text styling as a
115
+ `ToolPrefGroup`, keyed by `StyledRun` field, so a host reads a `RangeStyle`
116
+ into it and writes a `RunStylePatch` back with no name mapping. `short` moves
117
+ from `ToolPrefBoolean` to every leaf: it is the label any surface too narrow
118
+ for `name` shows, and `name` stays the accessible name.
119
+
120
+ `ToolOptionsBar` labels a bare value box — a number or a string — and leaves
121
+ every other control to say what it is, and it sizes fields and opacity tracks
122
+ for one line rather than for a panel column.
123
+ - 6d79849: Compact a tile grid's resting arrangement in current cell order rather than by
124
+ sorted id. Drops, swaps and occupancy already went by where a child's pose sits,
125
+ so the reflow that ran when a child left the grid re-sorted the survivors and
126
+ undid any earlier swap among them. Children sitting in no cell still sort last,
127
+ by id, so a grid nobody has arranged yet fills the same way it did.
128
+ - Updated dependencies [24a2dae]
129
+ - Updated dependencies [15b798e]
130
+ - Updated dependencies [8ffd746]
131
+ - Updated dependencies [ad6c351]
132
+ - @weasel-js/routing@1.5.2
133
+ - @weasel-js/gestures@1.5.2
134
+ - @weasel-js/geom@1.5.2
135
+ - @weasel-js/paint@1.5.2
136
+ - @weasel-js/font@1.5.2
137
+ - @weasel-js/text@1.5.2
138
+ - @weasel-js/history@1.5.2
139
+ - @weasel-js/cursor@1.5.2
140
+
141
+ ## 1.5.1
142
+
143
+ ### Patch Changes
144
+
145
+ - 5769e02: `<Canvas>` and `<SceneCanvas>` take a `flattenTolerance` prop, forwarded to
146
+ `WeaselRendererOptions.flattenTolerance`. A scene whose world unit is not a
147
+ pixel — feet, inches, millimeters — was pinned to the path-local
148
+ `DEFAULT_FLATTEN_TOLERANCE` of 0.5, which flattens a sub-unit curve to a few
149
+ vertices; only the headless `renderSceneToPixels` path could set it.
150
+
151
+ `WeaselRenderer.setFlattenTolerance` is new, so a change to the prop reaches
152
+ the renderer the canvas already built rather than being read once at
153
+ construction.
154
+ - f644eac: The sRGB ↔ OKLab/OKLCH conversions (`srgbU8ToOklab`, `oklabToOklch`, `lerpOklch` and the rest) now live in `@weasel-js/paint`. `@weasel-js/core` still exports every one of them, so no import changes.
155
+ - 9becb93: `features/selection` gets a barrel, and core's main barrel reaches its overlay
156
+ layers and ambient context only through it. No export changes name or shape.
157
+
158
+ The entry had been held back as needing a design pass, on the grounds that
159
+ selection is protocol-shaped. It is — but the protocol (`SelectionApi`,
160
+ `useSelection`, `ChromeState`, `MULTI_RESIZE_TARGET_ID`) lives in
161
+ `core/selection`, below this directory, because affordances and tools both read
162
+ it and the layering runs `core` → `affordances` → `tools` → `features`. So the
163
+ barrel covers what is built *on* the protocol and says so, rather than moving
164
+ the protocol up to join it. `docs/taxonomy.md` said the protocol lived under
165
+ `features/`; corrected.
166
+ - b984947: A `<SceneCanvas>` scene slot with its own `toPose` now derives paths from the poses that `toPose` paints dependencies at. Before, `derivePath` was handed the scene's poses while the nodes it connects were painted through `toPose`, so a derived edge could miss its endpoints. The clip a derived container imposes follows the same rule. No API change.
167
+ - 7e9a230: `resolveDerivedPath` now notices a dependency that moved on its own. On a memo hit it re-resolves the dependencies and compares their poses **by value** against the ones the cached path was drawn from, rather than serving the cached path until the scene pushes an invalidation. That covers the three misses the pushed triggers have had to be widened for — an ancestor's frame moving, a dependency going away, a dependency appearing — and covers a lookup the scene cannot know about at all, such as the `toPose` a scene slot paints through. By value, not by reference: a pose override mutates its buffer in place.
168
+
169
+ The pull covers poses. A derivation is handed each dependency's whole node, so one reading `data` or `layer` still rides on the scene's pushed invalidation, which is unchanged.
170
+
171
+ It costs a resolve and a compare per dependency per frame. Measured with `tests/perf/bench/derived-path.bench.ts` on one machine, taking the minimum of 200 iterations, a steady-state frame in which nothing moved went from 0.0037ms to 0.0112ms at 30 edges, 0.0212ms to 0.0590ms at 150, and 0.0863ms to 0.2757ms at 750 — about 3x the memo-hit frame, and under 2% of a 16.7ms frame at the largest of those. Two changes keep it there: `samePoseValue` walks an opaque pose without allocating, and a dependency now reaches a derivation as a class instance rather than an object literal with an own `get path()`, which alone was a third of the added cost.
172
+ - 72fde09: `documentPose` now ignores pose overrides along a derived node's whole dependency chain, not just on the node itself. Before, a derived node's document pose moved while something it derives from was being dragged, which let the minimap's framing follow a drag in any scene with derived poses. `effectivePose` is unchanged. Behavior change, no API change.
173
+ - e9051ac: New `effectiveRangeStyle(range, style, paint?)` reports what is actually rendering across a text range: the range's styling (from `styleAtRange` or `useTextEdit`'s `rangeStyle`) resolved against the node's own `TextStyle` and paint, the way the canvas resolves runs. A node-level flag reads as on across the whole range, a run's override wins over the node's value, and with a `null` range it reports the node alone. Additive: `styleAtRange` and `rangeStyle` still report the runs alone, unchanged.
174
+ - 626bace: A gradient now names the space its stops blend through. `interpolate` on any of
175
+ the three gradient kinds takes `'rgb'` (the default, and what every other vector
176
+ format means by a gradient), `'oklab'`, or `'oklch'` — which travels around the
177
+ hue wheel, so red to blue stays saturated instead of passing through a muddy
178
+ purple. Alpha is linear in every space.
179
+
180
+ The blend is paid for once, in the 256-texel ramp the shader samples, so a
181
+ perceptual gradient costs a batched frame nothing over an sRGB one. The space is
182
+ part of the ramp atlas key: the same stops under two spaces take two rows.
183
+
184
+ `sampleGradientStops` and `sampleResolvedStops` take the space as a third
185
+ argument, `bindRamp` as an optional third, and `GradientEditor` grows an
186
+ sRGB / OKLab / OKLCh switch (`spaceSwitch={false}` hides it). `@weasel-js/svg`
187
+ writes `wzl:interpolate` on the gradient's own element and reads it back; a
188
+ renderer that ignores it still paints the gradient, in sRGB.
189
+ - f4049be: Poll which keys are down. `useKeyState()` (or `createKeyState()` plus
190
+ `attach(target)` outside React) tracks physical keys by `KeyboardEvent.code`,
191
+ independently of which bindings claim them: `isDown(code)`, `isKeyDown(key)`,
192
+ `codes()`, `modifiers()`, and `take(code)`, which hands out each fresh press
193
+ once — autorepeat is not a press. Everything is released when the keyups can no
194
+ longer arrive: the window blurs, the document hides, focus leaves an element
195
+ target, or Meta is released (macOS sends no keyup for a key let go while Cmd is
196
+ held). Modifier keys are reconciled against the flags every key event carries,
197
+ so a modifier released in another window does not stick. `preventDefault`
198
+ names the codes whose browser default is blocked, so a game's Space and arrows
199
+ stop scrolling the page.
200
+
201
+ This adds API and changes nothing existing.
202
+ - 432b143: A `LayerGroup` member name now claims that layer and every layer under it in the `:` namespace, so `layers: ['scene']` covers the `scene:<layerId>` layers a scene with declared layers draws as — the same set `before`/`after: 'scene'` anchors against. The separator is required, so `'scene'` does not reach `scenery`. A member matching no layer being drawn is warned about rather than silently ignored; one group naming a layer both directly and through its namespace is treated as redundant, not as a conflict.
203
+
204
+ Additive for a group whose members all name layers exactly. A group that named a namespace prefix and relied on it grouping nothing now groups those layers, and a member that matched nothing now logs a warning where it previously stayed quiet.
205
+ - 4f9fd3b: labkit's loupe routes its peek key and its wheel through the gesture dispatcher, as the `loupe.peek` and `loupe.magnify` actions, instead of attaching `keydown`/`keyup`/`blur` on the window and a capture-phase `wheel` on the host. Taking the wheel from a lab's pan/zoom is now the dispatcher's ordinary rule — `loupe.magnify`'s `enabled` declines while the lens is down, so the event goes unhandled and falls through, and while the lens is up the dispatcher stops propagation before React's root listener runs. Aiming the lens stays a plain `pointermove` listener: the gesture grammar names no hover.
206
+
207
+ `useGestureDispatcher` takes `channels`, switching off any of the four listener groups it attaches to its element — `pointer`, `wheel`, `contextMenu`, `ingest`. Every one defaults on, so nothing changes for a caller that omits it. A mount that wants one gesture should not also have to take the rest of the pipeline's side effects: `contextMenu` suppresses the native menu unconditionally, and `ingest` makes the element a file-drop target. The loupe mounts with three of the four off, which is what keeps right-click and drops working on a lab that turns a magnifier on.
208
+
209
+ `<LoupeGestures>`, `createLoupeActions` and `LoupeInputApi` are new on `@weasel-js/labkit/loupe`; `useLoupe`'s returned state carries a new `input` member that `<LoupeGestures>` drives the lens through.
210
+ - 91973a7: Export the marker path builders — `circlePath`, `squarePath` and
211
+ `rectMarkerPath` — from `@weasel-js/core`, and add `roundRectPath` beside them.
212
+ They are the shared math for selection chrome, anchor markers and rubber-band
213
+ rects; until now a consumer building overlay chrome had to reimplement the
214
+ circle sample loop.
215
+
216
+ `markers.ts` also carried a `linePath(ax, ay, bx, by)` that built the identical
217
+ open two-vertex `PolygonPath` as the already-public `linePath(a, b)` from
218
+ `builder.ts`. Rather than rename one to clear the collision on the barrel, the
219
+ duplicate is deleted and its callers now use the point-taking public one — two
220
+ spellings of one function is the thing worth removing, not the name clash.
221
+ - 86be3eb: A sixth paint kind: `mesh-gradient`, PDF's shading types 6 and 7. A mesh is a
222
+ set of curved quadrilateral patches, each carrying a color at every corner, so
223
+ its color field bends where the three gradients can only run straight. Twelve
224
+ control points make a Coons patch and sixteen a tensor patch, which is the only
225
+ difference between them.
226
+
227
+ It is registered through `registerPaintKind` rather than built into the
228
+ renderer, so every slot it uses is one a consumer's own kind can use. The paint
229
+ is rasterized once into a 256-texel bake the shader samples — forward, the way
230
+ every renderer that draws these works, because a paint has to answer "what color
231
+ is this fragment" and inverting a bicubic per fragment does not. Corner colors
232
+ blend through `interpolate`, as a gradient's stops do.
233
+
234
+ `@weasel-js/svg` writes it as `<wzl:meshGradient>` with every patch's points in
235
+ full — not SVG's abandoned `<meshgradient>`, whose implicit edge sharing gives a
236
+ reader a way to be quietly wrong — and reads it back. A renderer that skips the
237
+ def paints the fallback color beside the reference.
238
+
239
+ `MeshEditor` in `@weasel-js/ui` edits the corner colors and the blend space, and
240
+ `PaintInput` renders it: before this, a mesh in that control fell through to the
241
+ color field, which wrote a solid back over it.
242
+ - 51372f1: Dragging a container that holds a derived-pose child no longer shows that child jumping to its authored placeholder for the length of the drag. The move action now captures each dragged node and descendant at the pose it is painted at (`effectivePose`), and commits the authored pose translated by the drag, so undo restores the placeholder exactly. No API change.
243
+ - 2a63f31: Alt+clicking a segment of the path being edited now inserts an anchor where you clicked, and the pen cursor shows while Alt is held over a segment. A straight segment stays straight; a curve is split without changing its shape. The closing edge of a closed path can be split too, the new anchor becomes the selected one, and the click has to land within 8 screen pixels of the path — so the reach no longer changes with zoom. Before this, the split only worked on curves: a straight edge came back as a curve, and the closing edge could not be split at all.
244
+
245
+ Every anchor edit (drag, nudge, delete, cut, insert) can now be undone. Before, `SceneCanvas` recorded these edits as operations with no inverse, and undoing one threw an error.
246
+
247
+ Additive: `Action.enabled` gets a second, optional argument — the world point of the click or press being routed. When it returns disabled for that point, the dispatcher tries the next binding, and the hover cursor is not shown there. The hover cursor now also comes from the action a click would run, when the action a drag would run has no cursor. `nearestSegmentT` gets an optional `closed` argument and returns an exact parameter instead of the nearest of 32 samples. `segmentAt` is new. The `SceneCanvas` adapter gains `setData`.
248
+ - 66e0e10: The pen tool snaps to existing anchors. An anchor placed within
249
+ `anchorSnapRadius` screen pixels (default 8) of any existing path's anchor
250
+ lands exactly on it, which makes stitching paths end to end precise. Anchor
251
+ snapping takes precedence over `snapPoint`, which still applies away from
252
+ anchors; `anchorSnapRadius: 0` turns it off.
253
+
254
+ This is additive: `usePenTool` gains the `anchorSnapRadius` option.
255
+ - 8b79c20: The pen tool continues an existing open path. With nothing drawn, pressing an
256
+ open path's first or last anchor picks that path up; the clicks that follow
257
+ extend it from that end (a first-anchor pick-up prepends and keeps the path's
258
+ direction), and finishing — Enter, ⌘-click, double-click, or clicking the far
259
+ end to close it — writes the result back to the same node as one undoable
260
+ edit rather than making a new node. Dragging from the endpoint pulls that
261
+ anchor's own handle. The pick-up radius is `closeHitRadius`, in screen pixels.
262
+
263
+ This is additive. The pen now reads existing paths through the `areaSelect`
264
+ and `editAnchors` deps, which `<SceneCanvas>` already publishes; a host
265
+ without them gets the old behavior. `PenScratch` gains a `continuing` field.
266
+ - b6a5eed: Inserting a path anchor gets its own cursor. Alt over a segment in path-edit
267
+ mode drew the pen tool's own glyph, so the pointer said nothing about what the
268
+ click would do.
269
+
270
+ The new `penPlus` glyph is the pen nib with a filled plus badge in the
271
+ lower-right. The nib runs along the anti-diagonal and left no room on the
272
+ diagonal for a badge, so it is shifted two units up and left — the same trade
273
+ the shape cursors make with a short-armed crosshair. Proofed at 11x over the
274
+ three grounds and on the pixel grid at 16 and 24 CSS px, 1x and 2x.
275
+ - 98ad39c: The platformer demo's eleven bones are a parented chain in the scene tree, composed by `RIGID_POSE_COMPOSITION`, instead of eleven parentless nodes re-derived from world matrices every frame. `syncScene` writes local poses and resolves nothing; the joint hierarchy and the node hierarchy are now the same tree, which is what the scene-as-frame work was for.
276
+
277
+ The rig is rigid — every joint's `scaleX`/`scaleY` is 1 at bind and no clip touches either — so nothing had to stay flattened on the grounds the design doc excludes. The one term `RectPose` could not carry is the facing mirror, which was a `scaleX: -1` above the whole rotated chain. Mirroring a rigid chain is equivalent to negating every local rotation and every local x offset, so it moves into the pose data and stays exact.
278
+
279
+ A bone with children has to be a container, and a container with no `clipFromPose` clips its descendants to its own silhouette — a box a few units wide, here. The bone containers return `null`, which is how a frame says it groups without clipping.
280
+ - 67f3867: Project a `'polyline'` overlay's vertices to screen coordinates, the way
281
+ `useDispatcherOverlayLayer` already handles marquee, lasso and insert previews.
282
+ A cut line and a connector were wrapped in a `viewToMat3(view)` group instead,
283
+ so their stroke thickened with the zoom while the chrome beside them held its
284
+ CSS-pixel weight. That came across unchanged when the layer took over painting
285
+ from the actions; it is now the same rule for every overlay.
286
+ - f663199: A `{ px }` stroke width no longer re-tessellates its ribbon on every frame of a
287
+ zoom. The width is now resolved against the zoom snapped to a fine grid, so
288
+ consecutive frames share a cached ribbon and a zoom no longer evicts the path's
289
+ other cached stroke styles. The drawn width stays within 1/8 of a screen pixel
290
+ of the width asked for. No API change.
291
+ - a80e8db: `@weasel-js/diagram/layout` and `@weasel-js/core/math` are new subpaths that a
292
+ Node process can import: measuring a box from its rows, ranking a graph,
293
+ relaxing one under forces, and finding where on a box's perimeter an edge
294
+ should leave, with no React in the module graph. A server rendering a diagram
295
+ needed all of that and could not have it — the diagram barrel reaches `live`
296
+ (a hook) and `connect` (an interaction), and core's reaches the canvas.
297
+
298
+ `layout` exports `measureBody`/`sizeToBody`, `layered`/`ranksOf`/`backEdges`,
299
+ `force`, `tree`, `COMPASS` and the rest of `ports`, `outline` and `onOutline`.
300
+ A caller with its own nodes and edges implements `Graph` — an interface, not a
301
+ class — over what it already has, so `buildGraph`, which reads one out of a
302
+ scene, is not needed and is not there. The routers stay behind: they live with
303
+ the scene registry, and they are typed in `Vec2`, so a consumer routing in
304
+ three dimensions cannot call them regardless.
305
+
306
+ The seven modules behind it now import `@weasel-js/core/math` rather than the
307
+ core barrel. Every symbol they took is in the subpath, so this narrows what
308
+ they ask for rather than moving anything.
309
+
310
+ `scripts/check-react-free.mjs` (`npm run check:react-free`) walks each entry's
311
+ **built** closure, through sibling packages' `exports` maps, and fails on a
312
+ React specifier. Sources cannot answer this question: `core/math` re-exports
313
+ nineteen leaf modules that each import only numbers, and its first build still
314
+ pulled a megabyte of canvas — one leaf reached core's own barrel, and
315
+ `splitting: true` put the result in a chunk the entry imported.
316
+ - a7519a1: Remove the `pointer` dep. **Breaking:** `DepSchema` no longer has a `pointer`
317
+ entry, `useStandardActions` no longer takes a `pointer` option, and the fixed
318
+ deps bag handed to an action that declares no `requires` no longer carries it.
319
+
320
+ Nothing in the kit declared or read it, and `<SceneCanvas>` never supplied a
321
+ value, so an action reading `deps.pointer` was already getting `undefined`. An
322
+ action that wants the pointer reads it from its invocation context
323
+ (`ctx.world`), and code outside an action can still use `usePointerContext()`,
324
+ which is unchanged.
325
+ - 187593e: Remove `useRotateTool` and the `'rotate'` built-in tool id. **Breaking:**
326
+ `'rotate'` is no longer a `BuiltinToolId`, so `defaultTools={['rotate', …]}`
327
+ and `tools={{ rotate: true }}` no longer typecheck, and the `standard` bundle
328
+ and the default tier no longer list it.
329
+
330
+ Rotation itself is unchanged. It has run through `rotateAction`, bound by the
331
+ select tool on the rotation handle, since the affordance hit-test was
332
+ consolidated; the tool contributed no bindings and its overlay painted
333
+ nothing. Drop the id from any tool list, and delete a `useRotateTool` mount
334
+ outright. `selectTool={{ rotate: false }}` still turns rotation off.
335
+ - 08a3aec: The renderer's `mat3.translate` and `mat3.scale` are renamed `mat3.translated`
336
+ and `mat3.scaled`. They compose onto the matrix you pass (`m · T`, `m · S`),
337
+ where `@weasel-js/geom`'s `translate` and `scale` build a fresh matrix, so the
338
+ shared names let code moved between the two compile and then misbehave.
339
+
340
+ This is a breaking change for anyone calling `mat3.translate` or `mat3.scale`
341
+ from `@weasel-js/core`: rename the call; the behavior is unchanged.
342
+ - d963d14: `EligibilityState.heldTriggers` is gone. Nothing populated it: a declared
343
+ `Eligibility.offhand` already reaches the hotkey tier by id, because the
344
+ `tool.offhand` action the declaration registers pushes the tool's id onto the
345
+ active-tool context's hotkey stack, and `engagedIds` is what `liveScope`
346
+ reads. Populating the set instead would have given the same tier a second
347
+ source of truth — raw key state tracked beside the gesture that already owns
348
+ the hold — with release order to reconcile between them.
349
+
350
+ `offhand` is untouched. Construct `EligibilityState` without the field; a
351
+ consumer reading it has to read `engagedIds` instead.
352
+ - edb825a: `Scene.nodesOnLayer(layer)` returns one layer's nodes in render order, cached
353
+ beside `renderOrderNodes()` until the next structural edit — so a pose tween,
354
+ which fires per frame, does not throw the walk away.
355
+
356
+ `d3Bind(...).join()` uses it. The diff used to scan every node in the scene on
357
+ every call to find the leaves on its own layer; it now classifies enter and
358
+ update with `scene.get` and scans only the target layer for exits. The
359
+ semantics are unchanged: a leaf on the target layer whose key is absent from
360
+ the data still exits, and nodes on other layers and containers are still left
361
+ alone.
362
+ - 229a16a: Add `scene.untracked(fn)`, which applies scene mutations without recording them, for writes that are not edits, such as a simulation stepping poses each frame. It notifies once, reverts on throw, and refuses `applyBatch` and `history.apply` / `applyOps`. Undo never restores an untracked write; undoing the next recorded change lands on the pose the untracked writes left.
363
+
364
+ Add `History.seal()`, which ends the current coalescing run so the next entry is pushed fresh. This adds a member to the `History` interface, so a hand-written implementation of it must add `seal`.
365
+ - f9feecc: The scene slot can skip what the view cannot see. Pass
366
+ `layers={{ scene: { drawOne: defaultDrawOne, cull: true } }}` to
367
+ `<SceneCanvas>` (or `cull: true` on a `<Canvas>` scene slot) and commands that
368
+ cannot reach the view are dropped before they reach the renderer, so an
369
+ off-screen node no longer costs tessellation, upload or a draw.
370
+
371
+ The cull is conservative: paths are bounded by their control points plus the
372
+ farthest their stroke can reach, rotated content by the box around its
373
+ corners, and text, custom shaders and anything under an effect are always
374
+ kept. It is off by default because it makes the scene layer's output depend on
375
+ the view, which matters only if you cache or re-display that output under a
376
+ different camera.
377
+
378
+ The same pass is exported as `cullDrawCommands(cmds, transform, rect)` for
379
+ custom layers. This adds API; nothing existing changes.
380
+ - b981856: Accept `{ px }` screen-pixel sizes for `fontSize` and `letterSpacing`
381
+
382
+ `TextStyle.fontSize`, `TextStyle.letterSpacing` and their `StyledRun`
383
+ counterparts now take `number | { px: number }`, the spelling `Stroke.width`
384
+ and `MarkerRef.size` already had. A `{ px }` size holds its on-screen size as
385
+ the view zooms, so a label no longer divides by the view scale at the call
386
+ site.
387
+
388
+ The unit is one type and one resolver now: `ScreenLength` and
389
+ `resolveScreenLength` live in `@weasel-js/paint`, which both core and text
390
+ already depend on, and `resolveStrokeWidth` delegates to it.
391
+
392
+ Resolution happens at the entry to layout, not at draw time. A screen-pixel
393
+ size changes the glyph advances and so the wrap points and the measured
394
+ bounds, so `resolveTextStyle`, `resolveRuns`, `textPoseLayoutInput`,
395
+ `layoutTextPose`, `measureTextBounds` and the three command builders
396
+ (`textCommand`, `textCommandFromRuns`, `textCommandFromPose`) each take the
397
+ view scale, defaulting to 1. `ResolvedTextStyle` and `ResolvedRun` keep plain
398
+ world numbers, so everything downstream is unchanged.
399
+
400
+ `createTextLayer` passes the mean of `view.scale.x` and `view.scale.y`, so
401
+ non-scene text gets this with no consumer change. The `kit:text` node painter
402
+ deliberately does not: it memoizes on `(data, pose)` to keep the renderer's
403
+ layout cache hitting across frames, and keying that on the live camera would
404
+ miss on every zoom frame.
405
+
406
+ SVG serialization writes a `{ px }` size as that many user units — SVG user
407
+ space has no camera — and the fit clamp on import leaves one alone, since a
408
+ screen-pinned size is not the file's to scale.
409
+ - 0662a2d: Add a slew limiter: `slewToward(value, target, dt, { rise, fall })` moves a value toward a target by at most a per-second rate in each direction, and `createSlew` keeps that state between steps. A fast rise with a slow fall is an attack/release envelope; a direction with no rate jumps.
410
+ - c0fa540: `usePointerStylus` no longer drops the first pointer move when it arrives within
411
+ `1000 / maxFps` ms of the page's time origin. The throttle measured that first
412
+ move against a last-commit time of zero rather than "never". A bug fix; nothing
413
+ else about the throttle changes.
414
+ - 21ce23e: The text-edit overlay now honors `verticalAlign`. `TextEditScreenPose` gains an optional `verticalAlign`, and `useSceneTextEdit` fills it from the node (`data.verticalAlign`, or `getVerticalAlign`), so editing a center- or bottom-aligned text node keeps its text where the canvas drew it instead of jumping to the top of the box. The overlay re-measures its own height as you type, so added lines grow a bottom-aligned node upward. Additive: a pose without `verticalAlign` places exactly as before.
415
+ - ff17dd7: `tileGrid` no longer lets two children land in one cell. A drop from outside the container is offered only the free cells, so it lands in the nearest free one, a multi-select drop fills distinct cells, and a full grid rejects the drop. A drag within the container still swaps with the child in the cell it lands on, and the swap now finds that child by where it sits rather than by id order, so it keeps working after an earlier swap. The new optional `centerOf` option says which point of a pose decides its cell; the default suits rect and point poses. Behavior change, additive API.
416
+ - f2b8d57: Add `createTiledLayer` — a periodic lattice over any RenderLayer
417
+
418
+ Content is authored once, in the cell `[0, period)`, and the wrapper draws
419
+ every copy the view can see. Panning keeps producing copies in either
420
+ direction, so a backdrop loops seamlessly with no wrap-around bookkeeping
421
+ anywhere in the source. `tiledProject(visStart, visEnd, period, bleed?)` is
422
+ the public helper underneath: the inclusive range of copy indices a visible
423
+ span touches.
424
+
425
+ Each copy draws the source through a view shifted by `-k * period`, so a
426
+ source that culls against the view it is handed culls per copy and never
427
+ learns it is being tiled; the copy's commands are then translated by
428
+ `+k * period`.
429
+
430
+ Three open questions from the backlog entry, settled:
431
+
432
+ **2D wrap, and the period names the axes.** `period` is `number | { x?, y? }`:
433
+ a bare number wraps x only — the side-scrolling case — and `{ x, y }` wraps
434
+ both. There is no separate `axis` option, because two ways to say which axes
435
+ wrap is one way for them to disagree.
436
+
437
+ **The period may be a function of the view and dims**, read per draw. A
438
+ constant is the common case, but "one screen wide" is a period too, and a
439
+ thunk is the shape every other view-dependent kit option takes.
440
+
441
+ **The lattice is per layer, not per shape.** Content on two periods is two
442
+ tiled layers stacked. A per-shape period would move the lattice into the
443
+ source and leave the wrapper unable to cull.
444
+
445
+ The plane is `space: 'world'`, which is what lets it be a `source` of
446
+ `createParallaxLayer`: the parallax plane hands it the derived inner view, the
447
+ lattice resolves against that, and the copies ride the plane. A
448
+ `space: 'screen'` source is drawn once, untiled — it ignores the view by
449
+ definition, so every copy would land on the last.
450
+
451
+ `bleed` declares how far the content reaches outside its own cell, in world
452
+ units, so a shape that overhangs still scrolls in rather than popping in at
453
+ the edge of the view.
454
+
455
+ `apps/site/demos/ParallaxDemo.tsx` is rebuilt on it; its local `tiledProject`
456
+ helper and the per-painter period threading are gone.
457
+ - 29f6ed0: Bind a rig to scene nodes. `useRig({ scene, skeleton, bindings })` (or
458
+ `bindRig` outside React) maps joint names to the node or nodes that ride them;
459
+ `rig.pose(pose, root?)` resolves the skeleton and moves every bound node
460
+ through the scene's pose overrides, so a per-frame write records no history and
461
+ bumps no version. Each node keeps the offset from its joint that it has at the
462
+ bind pose, so nodes are authored in place over the rest skeleton. `apply` is
463
+ the one part that knows the pose shape: the default, `rigidRigApply(descriptor)`,
464
+ carries a node's center and rotation with its joint through any pose
465
+ descriptor, mirrored roots included; supply your own to size nodes from joint
466
+ scale or to treat some nodes differently. `rig.bake()` writes the current frame
467
+ into the document as one undo entry, `rig.release()` drops the overrides, and
468
+ `rig.world()` returns the last resolved joint transforms.
469
+
470
+ This adds API and changes nothing existing.
471
+ - fb6d8e5: A registered shader program supplying its own vertex shader now gets locations
472
+ for the uniforms it declares there. `WeaselRenderer.registerProgram` scanned
473
+ only the fragment source, so every vertex uniform was written through a `null`
474
+ location — which GL accepts in silence, leaving the uniform at its zero
475
+ default. A zeroed `u_model` collapses every vertex to a point, so the program
476
+ bound, drew, and painted nothing at all, with no error anywhere.
477
+
478
+ This is what the `mesh-gradient` paint hit: it is the first thing in the kit to
479
+ reach the custom-program path with a vertex stage of its own.
480
+ - ca7c737: Add `useViewAnimationOn(view, animator)`: `useViewAnimation` on an animator the
481
+ caller owns, without the idle fallback animator `useViewAnimation` has to build
482
+ because a hook cannot be called conditionally. `<SceneCanvas>` now uses it, so
483
+ each canvas constructs one camera animator instead of two. Additive;
484
+ `useViewAnimation` is unchanged.
485
+ - Updated dependencies [f644eac]
486
+ - Updated dependencies [626bace]
487
+ - Updated dependencies [4f9fd3b]
488
+ - Updated dependencies [ed05c54]
489
+ - Updated dependencies [2a63f31]
490
+ - Updated dependencies [b6a5eed]
491
+ - Updated dependencies [a7519a1]
492
+ - Updated dependencies [d963d14]
493
+ - Updated dependencies [229a16a]
494
+ - Updated dependencies [b981856]
495
+ - @weasel-js/paint@1.5.1
496
+ - @weasel-js/routing@1.5.1
497
+ - @weasel-js/cursor@1.5.1
498
+ - @weasel-js/history@1.5.1
499
+ - @weasel-js/text@1.5.1
500
+ - @weasel-js/font@1.5.1
501
+ - @weasel-js/geom@1.5.1
502
+ - @weasel-js/gestures@1.5.1
503
+
3
504
  ## 1.5.0
4
505
 
5
506
  ### Patch Changes
@@ -0,0 +1,26 @@
1
+ import { P as PoseDescriptor, a as Path } from './poseDescriptor-PgfVKfa0.js';
2
+
3
+ /** True for Path-shaped poses (`{kind: 'polygon' | 'rect'}`). Useful for
4
+ * callers that need to fork between `pathPoseDescriptor` and
5
+ * `RECT_POSE_DESCRIPTOR` without forcing the consumer to wire
6
+ * `poseDescriptor` explicitly. */
7
+ declare function isPathLike(p: unknown): p is Path;
8
+ /** True for a pose with numeric top-level `x`/`y`/`width`/`height` — the only
9
+ * shape the rect descriptor and the kit's built-in painters can read. */
10
+ declare function isRectPose(p: unknown): p is {
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ rotation?: number;
16
+ };
17
+ /** Per-call dispatch: if the pose looks like a Path, route to
18
+ * `pathPoseDescriptor`; otherwise treat as a plain rect pose. Avoids forcing
19
+ * demos with Path TPose to wire `poseDescriptor={pathPoseDescriptor}`
20
+ * explicitly. `getRotation` surfaces a `pose.rotation` field on non-Path poses
21
+ * so demos using rect-with-rotation shapes (e.g. `RotatedPose`) don't have to
22
+ * wire `poseDescriptor={ROTATED_POSE_DESCRIPTOR}` just to get rotated
23
+ * selection chrome and rotation-aware corner hit-tests. */
24
+ declare const AUTO_POSE_DESCRIPTOR: PoseDescriptor<unknown>;
25
+
26
+ export { AUTO_POSE_DESCRIPTOR as A, isRectPose as a, isPathLike as i };
@@ -1,4 +1,4 @@
1
- import { RECT_ORIGIN_PROJECTION } from './chunk-UCPV7JXC.js';
1
+ import { RECT_ORIGIN_PROJECTION } from './chunk-HPFJUUTF.js';
2
2
  import { registerOpFactory, captureSlot, slotFromIndex, resolveSlot } from './chunk-4RJP2N2L.js';
3
3
 
4
4
  // src/core/ops/transform.ts
@@ -124,5 +124,5 @@ function snap(strategy, opts = {}) {
124
124
  }
125
125
 
126
126
  export { createReparentOp, createTransformOp, snap };
127
- //# sourceMappingURL=chunk-BDWAA634.js.map
128
- //# sourceMappingURL=chunk-BDWAA634.js.map
127
+ //# sourceMappingURL=chunk-44EWMJV2.js.map
128
+ //# sourceMappingURL=chunk-44EWMJV2.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/ops/transform.ts","../src/core/ops/reparent.ts","../src/interactions/gestures/shared/snap.ts"],"names":[],"mappings":"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","file":"chunk-BDWAA634.js","sourcesContent":["import type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface TransformAdapter<TPose> {\n setPose(id: string, pose: TPose): void;\n}\n\n/** @internal */\ninterface TransformArgs<TPose> {\n id: string;\n from: TPose;\n to: TPose;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Op: set an object's pose, inverting back to `from`. `coalesceKey` defaults to\n * `transform:${id}` so successive pose writes to the same node (drag-move,\n * resize, repeated nudges) merge into one undo entry within the history's\n * coalesce window. Pass an explicit key (or a unique one) to opt out. */\nexport function createTransformOp<TPose>(args: TransformArgs<TPose>): Op {\n const { id, from, to, label, coalesceKey = `transform:${id}` } = args;\n return {\n name: 'transform',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n // Always call setPose so consumers that inspect op behavior (tests,\n // overlays) see a consistent \"this op writes (id, to)\" signal. When\n // from and to are structurally identical, additionally report a\n // no-op to history so the entry can be skipped from the undo stack.\n // setPose(id, to) is idempotent in this branch — adapters write the\n // same pose they already had.\n (adapter as TransformAdapter<TPose>).setPose(id, to);\n if (poseShallowEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createTransformOp<TPose>({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nregisterOpFactory<TransformArgs<unknown>>('transform', (args) => createTransformOp(args));\n\nfunction poseShallowEqual<TPose>(a: TPose, b: TPose): boolean {\n if (a === b) return true;\n if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if ((a as Record<string, unknown>)[k] !== (b as Record<string, unknown>)[k]) return false;\n }\n return true;\n}\n","import type { Op } from './types';\nimport { registerOpFactory } from './registry';\nimport { captureSlot, resolveSlot, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface ReparentAdapter extends OrderedReader {\n setParent(id: string, parentId: string | null): void;\n /** Sibling-order write, paired with `getChildren`. Optional: an adapter\n * without both silently ignores slots and appends. */\n setChildOrder?(parentId: string | null, ids: string[]): void;\n}\n\n/** @internal */\ninterface ReparentArgs {\n id: string;\n fromParentId: string | null;\n toParentId: string | null;\n /** Sibling ordinal under the old parent. Sugar for `fromSlot: { index }`,\n * and only a seed: `apply` re-observes the node's full slot through the\n * adapter and that observation supersedes this. */\n fromIndex?: number;\n /** Sibling ordinal under the new parent. Sugar for `toSlot: { index }`.\n * Omit to append. */\n toIndex?: number;\n /** Slot under the old parent, anchor included. `invert()` forwards it as\n * the destination slot; without it undo re-parents with no position and\n * the node lands last, quietly changing paint order. */\n fromSlot?: Slot;\n /** Slot under the new parent, anchor included. Omit to append. */\n toSlot?: Slot;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Move `id` to `slot` among `parentId`'s children. No-op when the adapter\n * has no ordering seam, when `id` isn't there, or when it's already in\n * place. */\nfunction placeAt(\n a: ReparentAdapter,\n parentId: string | null,\n id: string,\n slot: Slot,\n): void {\n if (!a.getChildren || !a.setChildOrder) return;\n const current = a.getChildren(parentId);\n const from = current.indexOf(id);\n if (from < 0) return;\n const next = current.slice();\n next.splice(from, 1);\n const resolved = resolveSlot(next, slot);\n const to = resolved === undefined ? next.length : Math.max(0, Math.min(resolved, next.length));\n if (from === to) return;\n next.splice(to, 0, id);\n a.setChildOrder(parentId, next);\n}\n\n/** Present when the caller named a destination at all; `undefined` leaves\n * placement to the adapter. */\nfunction optionalSlot(slot: Slot | undefined, index: number | undefined): Slot | undefined {\n if (slot) return slot;\n return index === undefined ? undefined : slotFromIndex(index);\n}\n\n/**\n * Op: change `id`'s parent and sibling slot, inverting back to the prior\n * parent and slot.\n *\n * `coalesceKey` defaults to `reparent:${id}` so successive reparents of the\n * same id batch-merge cleanly.\n */\nexport function createReparentOp(args: ReparentArgs): Op {\n const {\n id, fromParentId, toParentId,\n label = 'Reparent', coalesceKey = `reparent:${id}`,\n } = args;\n let fromSlot = optionalSlot(args.fromSlot, args.fromIndex);\n const toSlot = optionalSlot(args.toSlot, args.toIndex);\n const argsForSerial: ReparentArgs = {\n id, fromParentId, toParentId, fromSlot, toSlot, label, coalesceKey,\n };\n return {\n name: 'reparent',\n args: argsForSerial,\n label,\n coalesceKey,\n apply(adapter) {\n const a = adapter as ReparentAdapter;\n const observed = captureSlot(a, fromParentId, id);\n if (observed) {\n fromSlot = observed;\n argsForSerial.fromSlot = observed;\n }\n a.setParent(id, toParentId);\n if (toSlot) placeAt(a, toParentId, id, toSlot);\n },\n invert() {\n return createReparentOp({\n id,\n fromParentId: toParentId,\n toParentId: fromParentId,\n fromSlot: toSlot,\n toSlot: fromSlot,\n label,\n coalesceKey,\n });\n },\n };\n}\n\nregisterOpFactory<ReparentArgs>('reparent', (args) => createReparentOp(args));\n","import type { MoveBehavior, ModifierState, SnapStrategy } from '../types';\nimport {\n RECT_ORIGIN_PROJECTION,\n type OriginProjection,\n} from './strategies/grid';\n\ntype ModKey = keyof ModifierState;\n\n/**\n * Generic snap behavior factory: wraps a `SnapStrategy` and returns a\n * `MoveBehavior` whose `onMove` returns a uniform `GroupTransform`.\n *\n * Implementation:\n * 1. Compute the primary's proposed pose by applying the gesture's\n * `transform` (translate) to `origin.get(primaryId)`.\n * 2. Call `strategy.snap(proposed)`. If null, no-op.\n * 3. Derive the snapped delta via the `OriginProjection`:\n * dx = origin(snapped).x - origin(originPose).x\n * ...so the gesture applies the same delta to every dragged id.\n *\n * The pose-shape-aware delta extraction lives here — gestures stay pose-shape\n * agnostic. Default projection is `{x, y}`-bearing (rect / path / polygon).\n * Pass `origin` for exotic poses.\n */\nexport function snap<TPose>(\n strategy: SnapStrategy<TPose>,\n opts: { bypassKey?: ModKey; origin?: OriginProjection<TPose> } = {},\n): MoveBehavior<TPose> {\n const { bypassKey } = opts;\n const proj: OriginProjection<TPose> = opts.origin\n ?? (RECT_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n return {\n onMove(ctx, transform) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n // The new contract: behaviors see a GroupTransform, not a pose. But\n // strategies still operate on poses. Reconstruct the proposed pose\n // by applying the (translate) transform to the primary's origin.\n if (transform.kind !== 'translate') return;\n const primaryId = ctx.draggedIds[0];\n if (primaryId === undefined) return;\n const originPose = ctx.origin.get(primaryId);\n if (originPose === undefined) return;\n const proposed = proj.translate(originPose, transform.dx, transform.dy);\n const snapped = strategy.snap(proposed, ctx);\n if (snapped === null) return;\n const o0 = proj.getOrigin(originPose);\n const o1 = proj.getOrigin(snapped);\n return {\n transform: { kind: 'translate', dx: o1.x - o0.x, dy: o1.y - o0.y },\n };\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/core/ops/transform.ts","../src/core/ops/reparent.ts","../src/interactions/gestures/shared/snap.ts"],"names":[],"mappings":"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","file":"chunk-44EWMJV2.js","sourcesContent":["import type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface TransformAdapter<TPose> {\n setPose(id: string, pose: TPose): void;\n}\n\n/** @internal */\ninterface TransformArgs<TPose> {\n id: string;\n from: TPose;\n to: TPose;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Op: set an object's pose, inverting back to `from`. `coalesceKey` defaults to\n * `transform:${id}` so successive pose writes to the same node (drag-move,\n * resize, repeated nudges) merge into one undo entry within the history's\n * coalesce window. Pass an explicit key (or a unique one) to opt out. */\nexport function createTransformOp<TPose>(args: TransformArgs<TPose>): Op {\n const { id, from, to, label, coalesceKey = `transform:${id}` } = args;\n return {\n name: 'transform',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n // Always call setPose so consumers that inspect op behavior (tests,\n // overlays) see a consistent \"this op writes (id, to)\" signal. When\n // from and to are structurally identical, additionally report a\n // no-op to history so the entry can be skipped from the undo stack.\n // setPose(id, to) is idempotent in this branch — adapters write the\n // same pose they already had.\n (adapter as TransformAdapter<TPose>).setPose(id, to);\n if (poseShallowEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createTransformOp<TPose>({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nregisterOpFactory<TransformArgs<unknown>>('transform', (args) => createTransformOp(args));\n\nfunction poseShallowEqual<TPose>(a: TPose, b: TPose): boolean {\n if (a === b) return true;\n if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if ((a as Record<string, unknown>)[k] !== (b as Record<string, unknown>)[k]) return false;\n }\n return true;\n}\n","import type { Op } from './types';\nimport { registerOpFactory } from './registry';\nimport { captureSlot, resolveSlot, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface ReparentAdapter extends OrderedReader {\n setParent(id: string, parentId: string | null): void;\n /** Sibling-order write, paired with `getChildren`. Optional: an adapter\n * without both silently ignores slots and appends. */\n setChildOrder?(parentId: string | null, ids: string[]): void;\n}\n\n/** @internal */\ninterface ReparentArgs {\n id: string;\n fromParentId: string | null;\n toParentId: string | null;\n /** Sibling ordinal under the old parent. Sugar for `fromSlot: { index }`,\n * and only a seed: `apply` re-observes the node's full slot through the\n * adapter and that observation supersedes this. */\n fromIndex?: number;\n /** Sibling ordinal under the new parent. Sugar for `toSlot: { index }`.\n * Omit to append. */\n toIndex?: number;\n /** Slot under the old parent, anchor included. `invert()` forwards it as\n * the destination slot; without it undo re-parents with no position and\n * the node lands last, quietly changing paint order. */\n fromSlot?: Slot;\n /** Slot under the new parent, anchor included. Omit to append. */\n toSlot?: Slot;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Move `id` to `slot` among `parentId`'s children. No-op when the adapter\n * has no ordering seam, when `id` isn't there, or when it's already in\n * place. */\nfunction placeAt(\n a: ReparentAdapter,\n parentId: string | null,\n id: string,\n slot: Slot,\n): void {\n if (!a.getChildren || !a.setChildOrder) return;\n const current = a.getChildren(parentId);\n const from = current.indexOf(id);\n if (from < 0) return;\n const next = current.slice();\n next.splice(from, 1);\n const resolved = resolveSlot(next, slot);\n const to = resolved === undefined ? next.length : Math.max(0, Math.min(resolved, next.length));\n if (from === to) return;\n next.splice(to, 0, id);\n a.setChildOrder(parentId, next);\n}\n\n/** Present when the caller named a destination at all; `undefined` leaves\n * placement to the adapter. */\nfunction optionalSlot(slot: Slot | undefined, index: number | undefined): Slot | undefined {\n if (slot) return slot;\n return index === undefined ? undefined : slotFromIndex(index);\n}\n\n/**\n * Op: change `id`'s parent and sibling slot, inverting back to the prior\n * parent and slot.\n *\n * `coalesceKey` defaults to `reparent:${id}` so successive reparents of the\n * same id batch-merge cleanly.\n */\nexport function createReparentOp(args: ReparentArgs): Op {\n const {\n id, fromParentId, toParentId,\n label = 'Reparent', coalesceKey = `reparent:${id}`,\n } = args;\n let fromSlot = optionalSlot(args.fromSlot, args.fromIndex);\n const toSlot = optionalSlot(args.toSlot, args.toIndex);\n const argsForSerial: ReparentArgs = {\n id, fromParentId, toParentId, fromSlot, toSlot, label, coalesceKey,\n };\n return {\n name: 'reparent',\n args: argsForSerial,\n label,\n coalesceKey,\n apply(adapter) {\n const a = adapter as ReparentAdapter;\n const observed = captureSlot(a, fromParentId, id);\n if (observed) {\n fromSlot = observed;\n argsForSerial.fromSlot = observed;\n }\n a.setParent(id, toParentId);\n if (toSlot) placeAt(a, toParentId, id, toSlot);\n },\n invert() {\n return createReparentOp({\n id,\n fromParentId: toParentId,\n toParentId: fromParentId,\n fromSlot: toSlot,\n toSlot: fromSlot,\n label,\n coalesceKey,\n });\n },\n };\n}\n\nregisterOpFactory<ReparentArgs>('reparent', (args) => createReparentOp(args));\n","import type { MoveBehavior, ModifierState, SnapStrategy } from '../types';\nimport {\n RECT_ORIGIN_PROJECTION,\n type OriginProjection,\n} from './strategies/grid';\n\ntype ModKey = keyof ModifierState;\n\n/**\n * Generic snap behavior factory: wraps a `SnapStrategy` and returns a\n * `MoveBehavior` whose `onMove` returns a uniform `GroupTransform`.\n *\n * Implementation:\n * 1. Compute the primary's proposed pose by applying the gesture's\n * `transform` (translate) to `origin.get(primaryId)`.\n * 2. Call `strategy.snap(proposed)`. If null, no-op.\n * 3. Derive the snapped delta via the `OriginProjection`:\n * dx = origin(snapped).x - origin(originPose).x\n * ...so the gesture applies the same delta to every dragged id.\n *\n * The pose-shape-aware delta extraction lives here — gestures stay pose-shape\n * agnostic. Default projection is `{x, y}`-bearing (rect / path / polygon).\n * Pass `origin` for exotic poses.\n */\nexport function snap<TPose>(\n strategy: SnapStrategy<TPose>,\n opts: { bypassKey?: ModKey; origin?: OriginProjection<TPose> } = {},\n): MoveBehavior<TPose> {\n const { bypassKey } = opts;\n const proj: OriginProjection<TPose> = opts.origin\n ?? (RECT_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n return {\n onMove(ctx, transform) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n // The new contract: behaviors see a GroupTransform, not a pose. But\n // strategies still operate on poses. Reconstruct the proposed pose\n // by applying the (translate) transform to the primary's origin.\n if (transform.kind !== 'translate') return;\n const primaryId = ctx.draggedIds[0];\n if (primaryId === undefined) return;\n const originPose = ctx.origin.get(primaryId);\n if (originPose === undefined) return;\n const proposed = proj.translate(originPose, transform.dx, transform.dy);\n const snapped = strategy.snap(proposed, ctx);\n if (snapped === null) return;\n const o0 = proj.getOrigin(originPose);\n const o1 = proj.getOrigin(snapped);\n return {\n transform: { kind: 'translate', dx: o1.x - o0.x, dy: o1.y - o0.y },\n };\n },\n };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { DEFAULT_GUIDE_TOLERANCE_PX, pxExtent } from './chunk-UCPV7JXC.js';
1
+ import { DEFAULT_GUIDE_TOLERANCE_PX, pxExtent } from './chunk-HPFJUUTF.js';
2
2
 
3
3
  // src/interactions/actions/resize/behaviors/clampMinSize.ts
4
4
  function clampMinSize(args) {
@@ -244,5 +244,5 @@ function fixedCornerOf(bounds, anchor) {
244
244
  }
245
245
 
246
246
  export { CORNER_ANCHORS, DEFAULT_RESIZE_BEHAVIORS, clampMinSize, cornerPoint, cornerResizeHandles, fixedCornerOf, hitCornerHandle, lockAspectWithModifier, pointSnapToGrid, snapToGrid, snapToGuides };
247
- //# sourceMappingURL=chunk-MQI4PIX3.js.map
248
- //# sourceMappingURL=chunk-MQI4PIX3.js.map
247
+ //# sourceMappingURL=chunk-H6N2JSZ3.js.map
248
+ //# sourceMappingURL=chunk-H6N2JSZ3.js.map