@weasel-js/core 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +742 -0
- package/dist/{DrawCommand-Ch8W63oQ.d.ts → DrawCommand-CD-ug3d9.d.ts} +45 -1
- package/dist/{pointSnapToGrid-Dmthv94u.d.ts → autoPoseDescriptor-DF1SnnSx.d.ts} +18 -3
- package/dist/{stroke-Dq8nL4A2.d.ts → builtins-BXFBXegF.d.ts} +251 -102
- package/dist/{chunk-Y5KS66G7.js → chunk-2VXGHUVL.js} +4 -4
- package/dist/{chunk-Y5KS66G7.js.map → chunk-2VXGHUVL.js.map} +1 -1
- package/dist/{chunk-2KKYDDDD.js → chunk-4RJP2N2L.js} +43 -21
- package/dist/chunk-4RJP2N2L.js.map +1 -0
- package/dist/{chunk-TORKRNFY.js → chunk-BL65SHCX.js} +83 -8
- package/dist/chunk-BL65SHCX.js.map +1 -0
- package/dist/{chunk-MXFSHJOM.js → chunk-D4AAWR64.js} +4 -4
- package/dist/{chunk-MXFSHJOM.js.map → chunk-D4AAWR64.js.map} +1 -1
- package/dist/{chunk-ZB7UYJVG.js → chunk-DOBSZOPR.js} +3 -3
- package/dist/{chunk-ZB7UYJVG.js.map → chunk-DOBSZOPR.js.map} +1 -1
- package/dist/{chunk-BLJNRMKB.js → chunk-PRGBGMH3.js} +3 -3
- package/dist/{chunk-BLJNRMKB.js.map → chunk-PRGBGMH3.js.map} +1 -1
- package/dist/{chunk-KIVJXUYE.js → chunk-R3AWPTLZ.js} +2779 -1660
- package/dist/chunk-R3AWPTLZ.js.map +1 -0
- package/dist/{chunk-67KE7SDP.js → chunk-T3UQ3F6R.js} +4 -4
- package/dist/{chunk-67KE7SDP.js.map → chunk-T3UQ3F6R.js.map} +1 -1
- package/dist/{chunk-SBFC6J3C.js → chunk-WPM42WJP.js} +3 -3
- package/dist/{chunk-SBFC6J3C.js.map → chunk-WPM42WJP.js.map} +1 -1
- package/dist/clipboard.d.ts +2 -2
- package/dist/clipboard.js +3 -3
- package/dist/clone.d.ts +2 -2
- package/dist/clone.js +3 -3
- package/dist/{geometry-Dtt_k6Dq.d.ts → geometry-6fCNhAux.d.ts} +3 -3
- package/dist/{grid-nnXU4VjN.d.ts → grid-0Pbn5B2C.d.ts} +1 -14
- package/dist/index.d.ts +330 -288
- package/dist/index.js +9 -9
- package/dist/insert.d.ts +3 -3
- package/dist/insert.js +1 -1
- package/dist/move.d.ts +4 -4
- package/dist/move.js +3 -3
- package/dist/{options-CdFl510T.d.ts → options-DbYLImvq.d.ts} +1 -1
- package/dist/{registry-BwY_DxJM.d.ts → registry-BY-wI9gm.d.ts} +98 -8
- package/dist/renderer.d.ts +32 -4
- package/dist/renderer.js +9 -9
- package/dist/resize.d.ts +5 -4
- package/dist/resize.js +2 -2
- package/dist/routing.d.ts +6 -6
- package/dist/routing.js +1 -1
- package/dist/{types-XBcDEp3Y.d.ts → types-DEALFt5F.d.ts} +10 -6
- package/dist/{types-DIQAisSG.d.ts → types-bcc7jcUy.d.ts} +116 -15
- package/dist/{types-miXHGrZM.d.ts → types-ei3UMl9R.d.ts} +1 -1
- package/package.json +9 -9
- package/dist/chunk-2KKYDDDD.js.map +0 -1
- package/dist/chunk-KIVJXUYE.js.map +0 -1
- package/dist/chunk-TORKRNFY.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,747 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.4.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9ce6f00: Fix solid fills coming out multiplied by whatever bitmap shared their batch.
|
|
8
|
+
A solid vertex carried the UV of a 1x1 white texel, but the flush bound the
|
|
9
|
+
run's *image*, so any solid staged alongside an image quad sampled that bitmap
|
|
10
|
+
at its middle texel instead. A wall of thumbnails is a ground rect under an
|
|
11
|
+
atlas quad per cell — the shape the merged batch exists for — so its grounds
|
|
12
|
+
came out tinted by the atlas: white drew olive. Reported from outside the repo
|
|
13
|
+
against a canvas2d reference; no visual baseline caught it, because in every
|
|
14
|
+
demo the quad covers its ground.
|
|
15
|
+
|
|
16
|
+
Every batch vertex now names the texture slot it samples. Slot 0 is always the
|
|
17
|
+
white texel, so a solid's `texture() * a_vertexColor` is the vertex color
|
|
18
|
+
exactly whatever else joins its run, and bitmaps take the slots above it.
|
|
19
|
+
|
|
20
|
+
That also lets one run hold up to seven distinct bitmaps. A document with a
|
|
21
|
+
handful of loose images used to break its run on every change of bitmap; now it
|
|
22
|
+
breaks only when the slots run out, or when one bitmap is drawn at two
|
|
23
|
+
MAG_FILTERs, which is state on the texture object and cannot be had both ways
|
|
24
|
+
in one draw.
|
|
25
|
+
|
|
26
|
+
`tests/visual/batch-pixels.spec.ts` reads the framebuffer channel by channel
|
|
27
|
+
rather than diffing a screenshot, which is what it takes to see a run's
|
|
28
|
+
composition at all.
|
|
29
|
+
- 6f876a7: Two canvases under one `DepRegistryProvider` no longer take each other's deps
|
|
30
|
+
down.
|
|
31
|
+
|
|
32
|
+
`register` set one source per name and its release did a bare `delete`. Mount a
|
|
33
|
+
second canvas under a shared provider and it displaced the first's `view` /
|
|
34
|
+
`scene` / `selection`; unmount either one and the name went dark for the canvas
|
|
35
|
+
still on screen.
|
|
36
|
+
|
|
37
|
+
Sources now stack per name, newest live, and a release removes its own entry
|
|
38
|
+
wherever it sits — so a displaced source comes back when the one above it
|
|
39
|
+
leaves, and a displaced source leaving disturbs nothing. Same shape as the
|
|
40
|
+
`ActionsProvider` registrant stack, one layer down.
|
|
41
|
+
- ed400a3: A derivation now receives its dependencies as `{ node, pose }` rather than as
|
|
42
|
+
bare poses. Breaking for anything with a `derivePath` or `derivePose`: read
|
|
43
|
+
`deps[0]?.pose` where it read `deps[0]`.
|
|
44
|
+
|
|
45
|
+
A connector legitimately reads more than a box — one that thickens with its
|
|
46
|
+
endpoint's weight, or routes only to nodes on a given layer, is answering off
|
|
47
|
+
`data` and `layer`. The scene has invalidated dependents on `kit:setData` and
|
|
48
|
+
`kit:setLayer` since those landed, and `scene.ts` said in a comment that a
|
|
49
|
+
derivation "is handed its dependencies' nodes, not only their poses". It was
|
|
50
|
+
not: the invalidation was paying for a read the signature could not perform.
|
|
51
|
+
|
|
52
|
+
`scenePoseLookup` is now `sceneDepLookup` and answers `DerivedDep` for the same
|
|
53
|
+
reason. `DerivedDep` is exported.
|
|
54
|
+
- fc00dae: Hand a derivation its dependencies' paths, and label an edge with one.
|
|
55
|
+
|
|
56
|
+
`DerivedDep` is now `{ node, pose, path }`. The path resolves on first read and
|
|
57
|
+
memoizes, so a route costs the same whether one node reads it or five, and a
|
|
58
|
+
dependency nobody asks about costs nothing. `resolveDerivedPath` moves beside
|
|
59
|
+
`derivedPose` in `core/scene` — a pose can now derive from a dependency's path —
|
|
60
|
+
and picks up the cycle guard the pose side already had.
|
|
61
|
+
|
|
62
|
+
`pointAlongPath(path, t)` is the new geometry primitive underneath: where a path
|
|
63
|
+
is at a fraction of its length, and which way it heads there, measured along the
|
|
64
|
+
flattened arc.
|
|
65
|
+
|
|
66
|
+
In `@weasel-js/diagram`, an edge label is an ordinary leaf node with
|
|
67
|
+
`dependsOn: [edge]` and `LABEL_DERIVE_POSE`. Its trait says where it sits —
|
|
68
|
+
`at: 'start' | 'mid' | 'end'` or a fraction, plus an `offset` perpendicular to
|
|
69
|
+
the route — and it reads the edge's resolved path rather than routing again, so
|
|
70
|
+
a label and its arrowhead can never disagree about where the edge went.
|
|
71
|
+
- 730da55: A derived node now recomputes when a dependency's `data` or `layer` changes.
|
|
72
|
+
|
|
73
|
+
`derivePath` is handed its dependencies' nodes, not only their poses — so a
|
|
74
|
+
connector that thickens with a node's weight, or draws only for nodes on a given
|
|
75
|
+
layer, is answering off `data` and `layer`. `kit:setPose` and `kit:move`
|
|
76
|
+
invalidated the dependent's memo; `kit:setData` and `kit:setLayer` did not, so
|
|
77
|
+
the derived geometry kept the old answer with nothing on screen to show it was
|
|
78
|
+
stale. Undo and redo were wrong the same way.
|
|
79
|
+
|
|
80
|
+
Both ops now invalidate dependents on `apply` and on `revert`, matching the two
|
|
81
|
+
that already did.
|
|
82
|
+
- 60ba9d9: A stroke marker on a derived path is drawn.
|
|
83
|
+
|
|
84
|
+
`markerStart` / `markerMid` / `markerEnd` reached the `kit:derived` painter
|
|
85
|
+
intact and were then dropped: the painter emitted its stroke command and
|
|
86
|
+
returned, where `kit:path` follows with a marker pass. So an arrowhead on a
|
|
87
|
+
diagram edge — the whole reason markers and derived geometry landed in the same
|
|
88
|
+
release — silently drew nothing. Its own `ink` had been reserving the hit-test
|
|
89
|
+
reach for the marker all along, which is the shape of the bug: the pointer could
|
|
90
|
+
already grab past the end of a line with no head on it.
|
|
91
|
+
|
|
92
|
+
A connect-authored edge now carries `markerEnd: 'arrow'` by default. An edge
|
|
93
|
+
runs *from* one node *to* another and a plain line does not say so;
|
|
94
|
+
`DEFAULT_EDGE_STROKE` is exported for a consumer overriding `commit` who wants
|
|
95
|
+
the rest of it.
|
|
96
|
+
- 5732951: The minimap's scene fit and the scene text editor read a derived pose rather
|
|
97
|
+
than the placeholder a derived node authors. `computeFitView`'s `'scene'` fit
|
|
98
|
+
frames on `documentPose`, so a derived node is framed where it actually is
|
|
99
|
+
while a drag still leaves the framing alone; `useSceneTextEdit` resolves both
|
|
100
|
+
its double-click hit test and the overlay's own projection through
|
|
101
|
+
`effectivePose`, so double-clicking a derived label opens the editor on it and
|
|
102
|
+
the box lands on the text.
|
|
103
|
+
|
|
104
|
+
Removes `UseMoveOptions.cascadeWorldPose`. Nothing has read it since the move
|
|
105
|
+
action started walking `scene.childrenOf` for its own cascade — it was a
|
|
106
|
+
documented option that silently did nothing, and two doc comments described the
|
|
107
|
+
behavior it used to drive.
|
|
108
|
+
- 2ff4824: A scene node can now derive its **pose** from its dependencies, the way it
|
|
109
|
+
already derived its path. New API; a group's bounds change behavior.
|
|
110
|
+
|
|
111
|
+
`Node.derivePose` takes the same `dependsOn` list as `derivePath`, serializes
|
|
112
|
+
through `SceneRegistry.derivePose` by key, and rides the same push
|
|
113
|
+
invalidation. Where a derived path is resolved at paint time and reaches only
|
|
114
|
+
the painter, a derived pose is what the node *is* at — it feeds bounds,
|
|
115
|
+
hit-testing, selection chrome, snapping and layout.
|
|
116
|
+
|
|
117
|
+
`dependsOn` gains a second form, `'children'`: "my own children, in child
|
|
118
|
+
order", which a fixed id list cannot express because reparenting would have to
|
|
119
|
+
maintain it. The two forms differ in lifetime as well as membership — deleting
|
|
120
|
+
a node still deletes everything that names it in `dependsOn`, but a container
|
|
121
|
+
outlives the children it derives from, because an emptied group is still a
|
|
122
|
+
group.
|
|
123
|
+
|
|
124
|
+
`groupAction` uses it, which fixes the group-bounds defect: a container's union
|
|
125
|
+
AABB was computed once at creation and never re-derived, so moving a member
|
|
126
|
+
left the group's bounds, selection chrome and hit area behind. The kit
|
|
127
|
+
registers the union function under `kit:unionOfChildren` and merges its own
|
|
128
|
+
registry entries under the consumer's, so a grouped document round-trips
|
|
129
|
+
through `toJSON` in any scene.
|
|
130
|
+
|
|
131
|
+
`effectivePose(scene, node)` is the one rule — override, else derived, else
|
|
132
|
+
authored — and now takes the scene rather than the override table alone. The
|
|
133
|
+
three render walks, the pick walk, and the scene, commit and gesture adapters
|
|
134
|
+
all resolve through it or through `documentPose`, the same answer minus the
|
|
135
|
+
override step for a reader that must not see an in-flight gesture. Both are
|
|
136
|
+
exported.
|
|
137
|
+
|
|
138
|
+
`clipFromPose`, `derivePath` and `derivePose` now share one table-driven
|
|
139
|
+
serialization path (`core/scene/nodeFnFields.ts`) instead of a copy per field.
|
|
140
|
+
- 3d89141: Layout: `layered`, `tree` and `force`, and the action that runs one.
|
|
141
|
+
|
|
142
|
+
A layout is a plain function of the graph — no scene, no ops, no history. It
|
|
143
|
+
hands back the new top-left for every node that **moves**, and a node already
|
|
144
|
+
standing where the layout wants it is absent from the answer, so re-running a
|
|
145
|
+
layout on an arrangement it produced writes nothing and pushes no undo entry.
|
|
146
|
+
|
|
147
|
+
Three rules keep a re-layout from scrambling a diagram someone has arranged.
|
|
148
|
+
There is no RNG anywhere in the path, so the same graph always lays out the same
|
|
149
|
+
way. Within-rank order is seeded from where the nodes already sit on the cross
|
|
150
|
+
axis rather than from crossing-minimization, so two branches an author dragged
|
|
151
|
+
into an order come back in it. And a node carrying `pinned: true` never moves,
|
|
152
|
+
with the rest of the layout translated to sit around it — with no pin, the
|
|
153
|
+
layout lands on the diagram's own bounding box rather than at the origin.
|
|
154
|
+
|
|
155
|
+
`layered` ranks by longest path, breaking cycles with a depth-first walk in node
|
|
156
|
+
order so a loop draws as an edge running back up the page. `tree` centers a
|
|
157
|
+
parent over its children's block; a graph that is not a tree still lays out,
|
|
158
|
+
since roots are the nodes nothing points at and anything the walk cannot reach
|
|
159
|
+
becomes a root of its own. `force` is an iterative relaxation seeded from the
|
|
160
|
+
current positions — **the one layout that is not idempotent**, since re-running
|
|
161
|
+
it keeps relaxing.
|
|
162
|
+
|
|
163
|
+
`buildGraph` reads the adjacency index from the same participant source the port
|
|
164
|
+
affordance takes, per invocation rather than maintaining one. `createLayoutAction`
|
|
165
|
+
rebuilds it on each press and writes the whole move as a single `scene.batch`,
|
|
166
|
+
carrying a container's whole subtree — `setPose` does not cascade, and a built
|
|
167
|
+
body would otherwise walk out from under its own label rows.
|
|
168
|
+
|
|
169
|
+
In core, `createSimulation` is the velocity-Verlet integrator with no clock
|
|
170
|
+
attached: `tick()` is the only thing that moves a node, so a pure function can
|
|
171
|
+
run a whole relaxation and read the result. `useSimulation` is now that
|
|
172
|
+
integrator on a frame loop and is otherwise unchanged. `SimulationCore` and
|
|
173
|
+
`SimulationOptions` name the halves, and forces can be handed a seeded `random`
|
|
174
|
+
in place of `Math.random`.
|
|
175
|
+
- 4a128c4: New package `@weasel-js/diagram` — the skeleton of weasel's node-link
|
|
176
|
+
diagramming: flowcharts, pipelines, code-flow diagrams, simple visual
|
|
177
|
+
programming. Arc 3 of the diagram design; edges, routing, the connect gesture
|
|
178
|
+
and layout are still to come.
|
|
179
|
+
|
|
180
|
+
**`DiagramNode` is a trait on an existing scene node, not a node type the
|
|
181
|
+
package mints.** A text block, an image, a path, a group or a plain rect all
|
|
182
|
+
become participants by carrying it, and nothing has to be authored through this
|
|
183
|
+
package to take part. Two ways to attach it, both answered by the default
|
|
184
|
+
reader: on the node's own `data.diagram`, or by kind — `createDiagramNodes`
|
|
185
|
+
takes predicates over `data` the way `createNodeRouting` does, so a class of
|
|
186
|
+
node takes part without being stamped one at a time.
|
|
187
|
+
|
|
188
|
+
**Ports default to the perimeter, so a node needs to say nothing to be
|
|
189
|
+
connectable.** `portsOf(node, pose)` resolves every port to a world point and
|
|
190
|
+
an outward normal, reading bounds through the pose descriptor and carrying the
|
|
191
|
+
node's rotation. Anchors are normalized against the bounds — `{ u, v }` from
|
|
192
|
+
the top-left — so a port stays where it was put when the node is resized.
|
|
193
|
+
|
|
194
|
+
**The body builder is optional**, for nodes that should look like a flowchart
|
|
195
|
+
box: `rect`, `diamond`, `stadium` and `parallelogram` outlines plus a `Row[]`
|
|
196
|
+
body of labels, fields, port rows and slots. Rows measure a floor, and the
|
|
197
|
+
authored pose is maxed against it rather than set to it — adding a row can grow
|
|
198
|
+
a node, nothing shrinks one back — which is what keeps resize, align,
|
|
199
|
+
distribute, guides, snapping and undo free of a special case. Text measurement
|
|
200
|
+
is a seam rather than an import; `canvasMeasure` adapts a 2D context.
|
|
201
|
+
|
|
202
|
+
Rows lay out in the shape's **content box**, not its bounding box.
|
|
203
|
+
`contentBox` reports the largest axis-aligned box inside an outline — a
|
|
204
|
+
diamond's inscribed rect, a parallelogram minus its lean, the flat span between
|
|
205
|
+
a stadium's ends — and `boxForContent` inverts it so the floor grows to suit.
|
|
206
|
+
Without it a diamond's label is placed against the bounding box, lands outside
|
|
207
|
+
the diamond, and the silhouette clip removes it: the label simply vanishes.
|
|
208
|
+
|
|
209
|
+
`registerDiagramShape` paints a node whose trait names an outline, and reports
|
|
210
|
+
the outline as its silhouette so picking and clipping follow the diamond rather
|
|
211
|
+
than its box. Rows are not painted there — a built body's rows are ordinary
|
|
212
|
+
scene nodes, so the kit's own text painter draws them and text editing,
|
|
213
|
+
selection and styling work on them unchanged.
|
|
214
|
+
|
|
215
|
+
Core exports `AUTO_POSE_DESCRIPTOR` and `isPathLike`, which were already
|
|
216
|
+
general-purpose but reachable only from inside the package. A peer package
|
|
217
|
+
computing a node's bounds needs the kit's own default descriptor rather than a
|
|
218
|
+
second copy of it.
|
|
219
|
+
- aee9d92: `createPoseOverrides` is public.
|
|
220
|
+
|
|
221
|
+
`Scene` is public and its `overrides: PoseOverrides<TPose>` is mandatory — and
|
|
222
|
+
load-bearing, since every ongoing gesture writes a frame to it. The factory that
|
|
223
|
+
builds one was internal, so a consumer assembling a scene-like object by hand
|
|
224
|
+
had to reimplement the table from its type. It is now exported alongside
|
|
225
|
+
`createScene`.
|
|
226
|
+
- c067221: Put every baked gradient ramp in one texture, a row each, rather than a texture
|
|
227
|
+
each. `GradientRampAtlas` replaces `GradientRampCache`: a stop list is baked
|
|
228
|
+
once into a 256-texel strip and written to a row, and the fragment shader picks
|
|
229
|
+
its row with `u_rampV`. Every gradient in a frame now samples the same texture
|
|
230
|
+
unit, which is what a gradient needs before it can take a batch texture slot
|
|
231
|
+
the way a bitmap or a font atlas already does.
|
|
232
|
+
|
|
233
|
+
The atlas doubles from 16 rows and stops at 1024, recycling the least recently
|
|
234
|
+
used row past that. An animating gradient mints a new stop list every frame, so
|
|
235
|
+
the old cache grew a GL texture per frame and freed none of them; the cap is
|
|
236
|
+
what bounds that.
|
|
237
|
+
|
|
238
|
+
`PaintBindContext.bindRamp` now returns the `v` its ramp sits at. A registered
|
|
239
|
+
paint kind that samples the ramp at a constant `v` reads whatever gradient
|
|
240
|
+
happens to own that row, so it must sample at the returned value.
|
|
241
|
+
- 26d40bf: Fold gradients into the shared batch. A gradient fill used to bind its own
|
|
242
|
+
program and break the run of solid geometry, image quads and glyphs around it;
|
|
243
|
+
now it stages alongside them, so a page of gradient-filled shapes is one draw
|
|
244
|
+
rather than one per shape.
|
|
245
|
+
|
|
246
|
+
All three kinds go, and for the same reason — not that the ramp position is
|
|
247
|
+
affine in position, which is true only of a linear gradient, but that the
|
|
248
|
+
*coordinate* the ramp position is computed from is affine in all three. So a
|
|
249
|
+
vertex carries that and the rasterizer's interpolation across a triangle is
|
|
250
|
+
exact. A linear gradient's coordinate is the ramp position itself, which is why
|
|
251
|
+
it needs no paint mode of its own; a radial or conic one carries a
|
|
252
|
+
gradient-space point, with its atlas row where a plain vertex keeps its alpha,
|
|
253
|
+
and the shader takes a `length` or an `atan` of it behind a branch on the flat
|
|
254
|
+
paint mode. Fill opacity and group alpha ride the vertices the way a solid's
|
|
255
|
+
already did, and fills, stroke ribbons and glyph-outline meshes all take the
|
|
256
|
+
route.
|
|
257
|
+
|
|
258
|
+
Per-vertex-colored and even-odd fills still take their own draw, as do patterns
|
|
259
|
+
and shaders.
|
|
260
|
+
|
|
261
|
+
The ramp atlas is what makes the slot arithmetic work: every gradient in a run
|
|
262
|
+
shares one texture slot, so a document full of them costs the same one slot a
|
|
263
|
+
single gradient does. Growing the atlas moves every row, so a bake that would
|
|
264
|
+
grow it — or recycle a row — flushes the run first.
|
|
265
|
+
|
|
266
|
+
Gradient fills now apply the group's color matrix. `gradFill` was the only
|
|
267
|
+
paint program that did not, and since the batch program applies it to
|
|
268
|
+
everything in a run, leaving it out would have made a linear gradient and a
|
|
269
|
+
radial one under the same group paint differently.
|
|
270
|
+
- b8d2940: Layout you can watch, and push against.
|
|
271
|
+
|
|
272
|
+
`usePoseRun` is the transport: each frame it asks a producer for poses,
|
|
273
|
+
publishes them to the scene's ephemeral override channel — the one a drag
|
|
274
|
+
already writes to, which `effectivePose`, derived geometry and the pick source
|
|
275
|
+
read — and commits the lot as one batch when the producer says it is done or
|
|
276
|
+
the consumer stops it. Cancel drops the frames and the document is untouched.
|
|
277
|
+
It runs behind `useVisibleRaf`, and it knows nothing about layout.
|
|
278
|
+
|
|
279
|
+
A node carrying an override the run did not publish belongs to another gesture:
|
|
280
|
+
the run never writes it, never commits it, and reports it to the producer as
|
|
281
|
+
pinned. Dragging a box mid-run is therefore the consumer's ordinary move tool,
|
|
282
|
+
with no gesture contributed by the diagram package.
|
|
283
|
+
|
|
284
|
+
`useLiveLayout` in `@weasel-js/diagram` drives it. `force` relaxes one tick a
|
|
285
|
+
frame off the same force list the one-shot `force` runs, holding a pinned node
|
|
286
|
+
with `fx`/`fy` while its neighbors answer; `layered` and `tree` ease into a
|
|
287
|
+
target computed once. A node or edge appearing or disappearing re-heats the run.
|
|
288
|
+
|
|
289
|
+
`SceneNode.pickable: false` makes a node transparent to the hit-test walk, so a
|
|
290
|
+
press lands on what is behind it. Without it the innermost hit wins and dragging
|
|
291
|
+
a labeled box pulls the label out of the box.
|
|
292
|
+
- b5e2cd9: Solid geometry and image quads now share one batch, so a wall of thumbnails —
|
|
293
|
+
a ground rect under an atlas quad, per cell — draws in one call rather than
|
|
294
|
+
one per command. The two batches used to be exclusive: staging a solid drained
|
|
295
|
+
the image run and staging an image drained the solid one, so a shape that
|
|
296
|
+
batches perfectly in either half alone paid a flush per command. Solid vertices
|
|
297
|
+
carry the UV of a 1x1 white texel, which makes `texture() * a_vertexColor` the
|
|
298
|
+
vertex color exactly, so the merge is pixel-identical rather than close.
|
|
299
|
+
|
|
300
|
+
Measured over a viewport-filling grid of those cells on an M2 Max via ANGLE
|
|
301
|
+
(`tests/perf/atlas-wall.spec.ts`): 600 draw commands 2.83 -> 0.10 ms, 1,650
|
|
302
|
+
11.37 -> 0.20, 5,400 40.50 -> 0.58, 15,000 126.15 -> 1.50. A run still breaks
|
|
303
|
+
on what a run cannot carry — a second bitmap, a different MAG_FILTER, a clip
|
|
304
|
+
depth, a color matrix.
|
|
305
|
+
- 89276ee: `usePoseRun` and `useSimulation` hand their injected clock straight to
|
|
306
|
+
`useVisibleRaf` instead of defaulting it themselves. The gate already falls
|
|
307
|
+
back to `requestAnimationFrame`, so both were defaulting it twice — and the
|
|
308
|
+
copy in `usePoseRun` was a bare `requestAnimationFrame` in kit source, which
|
|
309
|
+
`check:frame-loops` fails the build on. The allowlist is back down to the gate
|
|
310
|
+
itself.
|
|
311
|
+
- 36950d8: `scene.setDependsOn(id, dependsOn)` retargets a node's dependencies as one
|
|
312
|
+
undoable step, so dragging an existing edge's end onto a different node no
|
|
313
|
+
longer means removing the edge and adding another one. Switching a container
|
|
314
|
+
between an id list and `'children'` goes through the same call.
|
|
315
|
+
|
|
316
|
+
Both indices move with it — the reverse dependents index that drives cascade
|
|
317
|
+
delete, and the `'children'` set that drives the ancestor walk — and the
|
|
318
|
+
retargeted node's own memo is dropped alongside its dependents', since its
|
|
319
|
+
derivation now reads different sources. Order is significant, because a
|
|
320
|
+
derivation reads its dependencies positionally; declaring what a node already
|
|
321
|
+
declares records no history entry.
|
|
322
|
+
- 4f8c6b2: Text no longer breaks a batched run. Glyphs, the rules under underlined words,
|
|
323
|
+
and tessellated glyph outlines all stage into the same draw as the solid
|
|
324
|
+
geometry and image quads around them, so a wall of captioned thumbnails is one
|
|
325
|
+
draw where every label used to cost two.
|
|
326
|
+
|
|
327
|
+
The batch shader carries the glyph math behind a paint mode, which packs into
|
|
328
|
+
the texture-slot attribute the vertex already had, so the vertex does not grow
|
|
329
|
+
and a wall of thumbnails costs what it did before. It runs that math on every
|
|
330
|
+
fragment, glyph or not, because `fwidth` in non-uniform control flow is
|
|
331
|
+
undefined and the derivative has to be taken before anything selects on the
|
|
332
|
+
mode — priced at about 1.4% of a fragment that is not a glyph. A synthetic
|
|
333
|
+
oblique now shears on the CPU as the batch places its corners, rather than in a
|
|
334
|
+
vertex shader that read the baseline from a vertex attribute.
|
|
335
|
+
|
|
336
|
+
Three things a run used to break on are gone: a second text color in the same
|
|
337
|
+
paragraph, a decoration whose fill differs from the glyphs it sits under, and
|
|
338
|
+
the difference between a baked MSDF atlas and a runtime canvas bake. What still
|
|
339
|
+
breaks a run is a change of synthetic-bold threshold, which is a uniform — that
|
|
340
|
+
one is a fallback path, since a registered bold face never sets it.
|
|
341
|
+
|
|
342
|
+
**Breaking for anyone importing the text shader sources.** `TEXT_VERT_SRC`,
|
|
343
|
+
`TEXT_FRAG_SRC`, `TEXT_FRAG_R8_SRC`, `TEXT_SDF_UNIFORMS` and
|
|
344
|
+
`TEXT_SDF_ATTRIBUTES` are removed from `@weasel-js/font`: text has no program of
|
|
345
|
+
its own any more. What replaces them is `GLYPH_COVERAGE_GLSL`, the snippet a
|
|
346
|
+
program pastes in to turn an atlas sample into coverage, alongside
|
|
347
|
+
`GLYPH_MODE_MSDF` and `GLYPH_MODE_R8` naming the two channel layouts.
|
|
348
|
+
- 1240956: `kit:text` nodes take a `verticalAlign`, and picking follows it.
|
|
349
|
+
|
|
350
|
+
Centering a glyph in its box meant nudging `pose.y` by hand and re-deriving the
|
|
351
|
+
nudge whenever the font size changed. The painter forwarded the pose's height
|
|
352
|
+
but never an alignment, so the box the renderer aligned within was always
|
|
353
|
+
resolved as `'top'`.
|
|
354
|
+
|
|
355
|
+
`data.verticalAlign` — `'top' | 'center' | 'bottom'`, the same spelling the draw
|
|
356
|
+
command already took — now reaches both halves: the paint command and the
|
|
357
|
+
silhouette `textLineBoxes` builds, so a centered block is grabbable where it
|
|
358
|
+
draws rather than where a top-aligned one would have. A node that names none
|
|
359
|
+
paints exactly where it did before.
|
|
360
|
+
- Updated dependencies [4f8c6b2]
|
|
361
|
+
- @weasel-js/font@1.4.4
|
|
362
|
+
- @weasel-js/text@1.4.4
|
|
363
|
+
- @weasel-js/cursor@1.4.4
|
|
364
|
+
- @weasel-js/geom@1.4.4
|
|
365
|
+
- @weasel-js/gestures@1.4.4
|
|
366
|
+
- @weasel-js/history@1.4.4
|
|
367
|
+
- @weasel-js/modes@1.4.4
|
|
368
|
+
- @weasel-js/paint@1.4.4
|
|
369
|
+
|
|
370
|
+
## 1.4.3
|
|
371
|
+
|
|
372
|
+
### Patch Changes
|
|
373
|
+
|
|
374
|
+
- 2de5a37: A canvas opting out of an action no longer takes it away from its siblings.
|
|
375
|
+
|
|
376
|
+
`useViewportActions` answered `pinchZoom: false` with
|
|
377
|
+
`reg.unregister('viewport.pinchZoom')`, which drops *every* registrant of that
|
|
378
|
+
id — so one canvas opting out killed pinch-zoom on a sibling that asked for it,
|
|
379
|
+
and nothing put it back when the opting-out canvas unmounted.
|
|
380
|
+
`actions={{ id: null }}` went through the same door. Registration has been
|
|
381
|
+
per-registrant since the registrant stack landed; suppression was not, and a
|
|
382
|
+
shared registry had nowhere to hang "not for me".
|
|
383
|
+
|
|
384
|
+
`ActionsScope` is that place. It is a view of the registry in scope with its own
|
|
385
|
+
mute set: `register`, `unregister` and the dispatcher / dep-registry slots pass
|
|
386
|
+
straight through to the shared store, so cross-canvas sharing is untouched,
|
|
387
|
+
while `list`, `trigger` and `begin` skip what this scope muted. Scopes nest.
|
|
388
|
+
`<ActionsProvider>` is itself a scope, so a lone canvas needs no wrapper, and a
|
|
389
|
+
`<SceneCanvas>` deferring to a host provider now mounts one.
|
|
390
|
+
|
|
391
|
+
`ActionsRegistry.mute(id)` returns a release; a scope drops everything it muted
|
|
392
|
+
when it unmounts. `unregister` keeps its old meaning — "this action should not
|
|
393
|
+
exist" — and is unchanged.
|
|
394
|
+
|
|
395
|
+
Breaking for anyone implementing `ActionsRegistry` themselves: `mute` is a new
|
|
396
|
+
required method.
|
|
397
|
+
- 10e1ab6: A bare `<Canvas>` now repaints when its selection changes, without a wrapper
|
|
398
|
+
asking it to.
|
|
399
|
+
|
|
400
|
+
The redraw tripwire — the layout effect whose dep array is meant to name every
|
|
401
|
+
paint input that arrives on a render — did not name the overlay-aware state the
|
|
402
|
+
layer helpers expose. Selection, preview poses and the chrome derived from them
|
|
403
|
+
were written during render into a ref, so changing the selection prop repainted
|
|
404
|
+
nothing. It looked correct only because `<SceneCanvas>` calls `requestRedraw()`
|
|
405
|
+
by hand for its own data sources.
|
|
406
|
+
|
|
407
|
+
The tripwire now carries the memoized chrome state, which re-derives on exactly
|
|
408
|
+
the selection, bounds and preview inputs the helpers read, plus the chrome-caps
|
|
409
|
+
predicate. A render that changed none of them still paints nothing.
|
|
410
|
+
- eb0d6ce: Undo of a Delete now brings back everything the delete cascaded — a node's
|
|
411
|
+
dependents, and their own subtrees — not just the node that was named.
|
|
412
|
+
|
|
413
|
+
Deleting a node takes everything deriving from it, so deleting a box takes the
|
|
414
|
+
edges drawn from it. The delete op only ever snapshotted the subtree, so undo
|
|
415
|
+
re-inserted the box alone and the edges stayed gone. The op now snapshots the
|
|
416
|
+
whole set before removing, and re-inserts it parent-first, each node at the slot
|
|
417
|
+
it held.
|
|
418
|
+
|
|
419
|
+
New public read: `scene.removalClosure(ids)` answers what `removeMany(ids)`
|
|
420
|
+
would take, without taking it. The scene owns the cascade relations, so a caller
|
|
421
|
+
that rebuilds the walk from `dependsOn` and `children` goes stale the moment a
|
|
422
|
+
relation is added — `buildDeleteOps` asked its own copy of that question and is
|
|
423
|
+
now on this one.
|
|
424
|
+
|
|
425
|
+
The delete op reads it through an optional `getRemovalClosure(ids)` on the
|
|
426
|
+
adapter, alongside `getChildren`. An adapter that cascades along nothing but the
|
|
427
|
+
subtree can leave it out and behaves as before; one that cascades further has to
|
|
428
|
+
answer, or the nodes it takes are absent from the snapshot. The scene-backed
|
|
429
|
+
adapters answer it.
|
|
430
|
+
|
|
431
|
+
`insertNode` on the scene-backed adapters now forwards the function-valued
|
|
432
|
+
fields — `dependsOn`, `derivePath` and `clipFromPose` — so a restored edge
|
|
433
|
+
derives again instead of coming back as a static path, and a restored container
|
|
434
|
+
still clips. The delete op's serialized `descendants` argument is now
|
|
435
|
+
`cascaded`, and carries the node itself alongside what went with it.
|
|
436
|
+
- 75969f6: Full-screen effect passes: a group's children render into a texture, shader
|
|
437
|
+
passes run over it, and the result composites back where the group sits.
|
|
438
|
+
|
|
439
|
+
Until now the renderer could draw over the frame but never transform it —
|
|
440
|
+
nothing sampled what had already been drawn, so blur, bloom and distortion were
|
|
441
|
+
unreachable and the only recourse was a CSS `filter` on the `<canvas>`, which is
|
|
442
|
+
the browser compositing on the kit's behalf.
|
|
443
|
+
|
|
444
|
+
`GroupDrawCommand.effects` is the primitive. It is the one field there that does
|
|
445
|
+
not accumulate down the group stack: it is a render-target boundary. The
|
|
446
|
+
children draw into a buffer with a stencil of its own, each effect reads the
|
|
447
|
+
previous one's output, and the composite applies the group's `transform`,
|
|
448
|
+
`alpha` and `colorMatrix` plus whatever clip encloses it — so the enclosing clip
|
|
449
|
+
clips the result rather than the pixels an effect reads, and nested clips inside
|
|
450
|
+
start from a fresh depth budget.
|
|
451
|
+
|
|
452
|
+
`RenderLayer.effects` is that field folded in at `drawOneLayer`, which is where
|
|
453
|
+
every layer already gets wrapped in a group. A blur there blurs one layer and
|
|
454
|
+
leaves the chrome drawn above it sharp, which is the thing the CSS filter cannot
|
|
455
|
+
do.
|
|
456
|
+
|
|
457
|
+
`blur({ radius })` and `vignette({ amount })` ship as built-ins; both return
|
|
458
|
+
`Effect[]` because a separable blur is honestly two passes.
|
|
459
|
+
`registerEffect(id, frag)` registers your own — it is `registerProgram` with the
|
|
460
|
+
effect vertex shader, and using the custom-shader one instead compiles, runs,
|
|
461
|
+
and samples the source upside down.
|
|
462
|
+
|
|
463
|
+
Nothing is allocated until a group declares an effect, so a canvas without them
|
|
464
|
+
carries no offscreen buffer. Buffers are drawing-buffer sized, pooled per
|
|
465
|
+
renderer, and dropped on resize, dispose and context loss.
|
|
466
|
+
|
|
467
|
+
Demo: "Full-screen effect passes", with visual baselines blurred and sharp.
|
|
468
|
+
- 0d40f94: A layer whose `draw` throws now paints nothing and names itself in the console,
|
|
469
|
+
instead of taking the frame down with it.
|
|
470
|
+
|
|
471
|
+
The paint runs on the frame loop, so a throw out of `draw` surfaced as an
|
|
472
|
+
uncaught `requestAnimationFrame` error on the window: the canvas went blank and
|
|
473
|
+
stayed stale until something unrelated asked for a redraw. `drawOneLayer` — the
|
|
474
|
+
one path both the canvas and a viewport's inner pass go through — catches it,
|
|
475
|
+
drops that layer for the frame, and paints the rest.
|
|
476
|
+
|
|
477
|
+
`drawLayers` and `drawOneLayer` take an `onLayerError` callback for a consumer
|
|
478
|
+
that wants to route the failure somewhere of its own; the default reports the
|
|
479
|
+
layer id and the error to `console.error`. The layer's cached commands are
|
|
480
|
+
dropped with it, so the next frame is a real re-attempt rather than a stale tree
|
|
481
|
+
served under fresh deps.
|
|
482
|
+
- 713f98a: A layout strategy decides what happens to a child released outside every
|
|
483
|
+
container.
|
|
484
|
+
|
|
485
|
+
When no container accepted a drag, the move action committed the child wherever
|
|
486
|
+
the pointer stopped, and the source layout had no say — a grid could not close
|
|
487
|
+
the gap, and a container that only means to arrange its own children could not
|
|
488
|
+
take one back.
|
|
489
|
+
|
|
490
|
+
`LayoutStrategy.releaseDrop` is that say. It sees the source container, its
|
|
491
|
+
remaining children and the dragged child in world coordinates, and returns ops
|
|
492
|
+
that replace the free-space commit. An empty array leaves the container alone,
|
|
493
|
+
which snaps the child home, since the drag only ever wrote previews. `null` —
|
|
494
|
+
and a strategy that does not implement the method — leaves the drop where the
|
|
495
|
+
pointer left it, exactly as before.
|
|
496
|
+
- 85f4a21: Dragging several selected nodes into a layout container now runs the layout,
|
|
497
|
+
instead of falling through to a plain translate. Three nodes dropped on a grid
|
|
498
|
+
fill three cells.
|
|
499
|
+
|
|
500
|
+
The selection lands as a unit. One container is chosen, from the center of the
|
|
501
|
+
whole dragged group rather than each child's own — hit-testing per child would
|
|
502
|
+
scatter a selection straddling two containers — and it has to accept every
|
|
503
|
+
member: `acceptsDrop` is asked once per dragged child, and one refusal takes the
|
|
504
|
+
container out of the running. The children are then placed one at a time, in
|
|
505
|
+
selection order, and each placement sees the container state the previous one
|
|
506
|
+
produced. A strategy that packs, stacks or displaces therefore sees the group
|
|
507
|
+
arrive the same way it would see three separate drags. If any child has no
|
|
508
|
+
target, the whole drop is refused rather than split between two homes.
|
|
509
|
+
|
|
510
|
+
Each child is snapped at its own position, displaced by however far the pointer
|
|
511
|
+
sits from the selection's center, so the group keeps its shape as it lands. A
|
|
512
|
+
single-node drag still probes at the pointer exactly as before.
|
|
513
|
+
|
|
514
|
+
The rest follows the selection: the live preview reflows the destination and
|
|
515
|
+
every source container the selection left, one pass per container with all of
|
|
516
|
+
its departing children withdrawn at once; and the commit emits a reparent op per
|
|
517
|
+
child that changed parent, every reparent before every drop.
|
|
518
|
+
|
|
519
|
+
`releaseDrop` — a container's say over its own child released into open space —
|
|
520
|
+
is now per source container too, so a mixed selection resolves per child: the
|
|
521
|
+
grid's own child goes home, and a free node in the same selection keeps the
|
|
522
|
+
translate it would have had on its own.
|
|
523
|
+
- 4bb0341: A parallax plane draws its source layers through `drawOneLayer`, so their
|
|
524
|
+
`space` means something.
|
|
525
|
+
|
|
526
|
+
`createParallaxLayer` called `layer.draw(...)` directly where every other path
|
|
527
|
+
through a view — the canvas itself, a viewport node's inner pass — goes through
|
|
528
|
+
`drawOneLayer`. A `space: 'world'` source therefore came out unprojected, and the
|
|
529
|
+
only way to see anything was for the source to pre-project by hand while
|
|
530
|
+
declaring a space it did not draw in. ParallaxDemo's four layers did exactly
|
|
531
|
+
that, and their labels were lies.
|
|
532
|
+
|
|
533
|
+
Now a world-space source is wrapped in the plane's inner view and a screen-space
|
|
534
|
+
one is passed through, the same rule that holds everywhere else. The plane
|
|
535
|
+
itself stays `space: 'screen'` — its children carry whatever transform they
|
|
536
|
+
need, and the outer canvas must add none.
|
|
537
|
+
|
|
538
|
+
The demo drops its `project` helper and emits world coordinates; the committed
|
|
539
|
+
`parallax` visual baseline passes unchanged.
|
|
540
|
+
- e0d5580: `pathHitTest` reads curves and holes instead of throwing on one and ignoring
|
|
541
|
+
the other.
|
|
542
|
+
|
|
543
|
+
Its vertex extractor walked `M` and `L` only and threw on any bezier command,
|
|
544
|
+
and it stopped at the first `Z`, so a path's second contour was never
|
|
545
|
+
considered. The throw was reachable in ordinary use: `sceneAdapter`'s
|
|
546
|
+
`nodeBoundsPassClips` calls `pathIntersectsRect` on every ancestor clip, so a
|
|
547
|
+
container with a curved `clipFromPose` crashed the hit-test walk.
|
|
548
|
+
|
|
549
|
+
`pathContainsRect`, `pathIntersectsRect`, `pathContainsPolygon` and
|
|
550
|
+
`pathIntersectsPolygon` now treat a `PolygonPath` as its filled region. Whether
|
|
551
|
+
a point is inside comes from `pointInPath`, so beziers flatten and `fillRule`
|
|
552
|
+
decides — a donut's hole is outside the shape under `evenodd` and inside it
|
|
553
|
+
under `nonzero`, and the four predicates agree with `pathContainsPoint` on
|
|
554
|
+
which. Each takes the same optional flattening tolerance `pointInPath` does.
|
|
555
|
+
|
|
556
|
+
Two answers change for paths that already worked. A rect sitting in a hole is
|
|
557
|
+
no longer reported as contained or intersecting, and containment now fails when
|
|
558
|
+
a contour reaches into the rect at all rather than only when it crosses an
|
|
559
|
+
edge.
|
|
560
|
+
- edf99d5: Undoing a removal from a persisted history brings `derivePath` and
|
|
561
|
+
`clipFromPose` back.
|
|
562
|
+
|
|
563
|
+
`kit:remove`'s snapshot cloned each node with its function-valued fields
|
|
564
|
+
attached, which works in-session and disappears the moment the history is
|
|
565
|
+
serialized. `dependsOn` survived the round-trip and repopulated the dependency
|
|
566
|
+
index, so a restored derived node looked wired up and never painted; a
|
|
567
|
+
container came back unclipped the same way.
|
|
568
|
+
|
|
569
|
+
The snapshot now carries the registry keys beside the nodes, and revert
|
|
570
|
+
re-resolves them exactly as `kit:add` does — warning, not throwing, when a key
|
|
571
|
+
is absent from the scene's registry.
|
|
572
|
+
- 2723cc7: `selectAll` no longer selects nodes on a hidden layer, so Cmd+A then Delete
|
|
573
|
+
cannot take content the user cannot see.
|
|
574
|
+
|
|
575
|
+
It walked `renderOrder()`, which is every node in the scene regardless of what
|
|
576
|
+
its layer's `visible` flag says. It now walks `renderOrderNodes()` when any
|
|
577
|
+
layer is hidden — the same sequence, carrying the layer each node sits on — and
|
|
578
|
+
keeps the cheaper id walk when every layer is visible.
|
|
579
|
+
|
|
580
|
+
A `scene` dep that answers neither `layers` nor `renderOrderNodes` behaves
|
|
581
|
+
exactly as before.
|
|
582
|
+
- 0ca0aca: Layer groups: several consecutive layers render into one buffer and share one
|
|
583
|
+
effect pass.
|
|
584
|
+
|
|
585
|
+
`RenderLayer.effects` runs over a single layer, which is the wrong picture as
|
|
586
|
+
soon as a pass reads neighboring pixels — `blur(A over B)` is not `blur(A) over
|
|
587
|
+
blur(B)` — and it costs a buffer and a pass chain per layer. A consumer wanting
|
|
588
|
+
the world blurred and the HUD sharp had no way to say that the world was more
|
|
589
|
+
than one layer.
|
|
590
|
+
|
|
591
|
+
`LayerGroup` is that surface: `{ id, layers, effects?, alpha?, colorMatrix? }`,
|
|
592
|
+
passed to `<Canvas>` / `<SceneCanvas>` as `layerGroups`. Members are named by
|
|
593
|
+
`RenderLayer.id`, the same names `layerOrder` and `layerVisibility` use, so a
|
|
594
|
+
group can take in kit-built layers as readily as consumer ones. `drawLayers`
|
|
595
|
+
brackets each run of consecutive members in one `kind: 'group'` command; the
|
|
596
|
+
renderer's existing offscreen path does the rest.
|
|
597
|
+
|
|
598
|
+
Only consecutive members share a buffer, because anything drawn between two of
|
|
599
|
+
them has to land between them. A group split across the render order is drawn as
|
|
600
|
+
one bracket per run, with a warning — the picture is right, the declaration
|
|
601
|
+
probably is not. A hidden member, and a member that paints nothing this frame,
|
|
602
|
+
break no run.
|
|
603
|
+
|
|
604
|
+
`effects` also accepts a thunk, re-read on every frame the canvas paints, so an
|
|
605
|
+
animating radius costs no React render. A group that declares no effects, alpha
|
|
606
|
+
or color matrix emits no wrapper at all, and a frame with no groups allocates
|
|
607
|
+
nothing.
|
|
608
|
+
|
|
609
|
+
The side-scroller load test now blurs its six world layers on a head knock and
|
|
610
|
+
leaves its HUD, callouts and ending card sharp; the CSS `filter` it used to
|
|
611
|
+
reach for is gone.
|
|
612
|
+
- 3583ca3: `useStandardActions` takes an `exclude` list, so a consumer can bind its own.
|
|
613
|
+
|
|
614
|
+
The hook registered a fixed descriptor list, which left no way to suppress an
|
|
615
|
+
individual kit action. A consumer wanting its own align or distribute
|
|
616
|
+
keybindings got the kit's as well, and the two competed for the same keys.
|
|
617
|
+
|
|
618
|
+
`exclude` names ids to leave unregistered; an id naming no kit action is
|
|
619
|
+
ignored, and changing the list re-registers. `KIT_STANDARD_ACTION_IDS` is the
|
|
620
|
+
full list in registration order, so the ids are discoverable rather than
|
|
621
|
+
something to read out of the source.
|
|
622
|
+
- fc16cac: `Stroke.paint` is optional, and a stroke without one paints nothing everywhere
|
|
623
|
+
rather than throwing.
|
|
624
|
+
|
|
625
|
+
Such a stroke is real: a property panel that writes one field onto a node with
|
|
626
|
+
no stroke — a width, a cap — materializes a whole stroke around it, and
|
|
627
|
+
documents written before that was fixed still hold them. The painters already
|
|
628
|
+
read one as no stroke. Every other reader dereferenced `paint` unguarded, so a
|
|
629
|
+
document holding one threw on SVG export, on copy, and out of any consumer
|
|
630
|
+
painter or overlay whose command reached the renderer directly.
|
|
631
|
+
|
|
632
|
+
The type says so now, which is what stops the next reader from assuming
|
|
633
|
+
otherwise. What each one does with an unpainted stroke:
|
|
634
|
+
|
|
635
|
+
- The renderer skips the stroke pass and paints the fill.
|
|
636
|
+
- The SVG serializer emits no `stroke` attributes at all, the way it already
|
|
637
|
+
does for an absent or zero-width stroke.
|
|
638
|
+
- Text layout keys it as no stroke, so an unpainted run groups with unstroked
|
|
639
|
+
ones instead of splitting a draw call, and does not get pulled onto the
|
|
640
|
+
outline tier to stroke nothing.
|
|
641
|
+
- `setStrokeOpacity` seeds the default stroke color to have something to set an
|
|
642
|
+
opacity on, keeping the width and joins already there.
|
|
643
|
+
- 6d4bbeb: WeaselDraw's SVG export drops what a hidden layer holds, matching what the
|
|
644
|
+
pixel path draws. It walked the whole container tree with no visibility gate,
|
|
645
|
+
so hiding a layer and exporting produced a file with the hidden content in it.
|
|
646
|
+
|
|
647
|
+
`SceneSource` gains an optional `isPainted(id)`; a node it refuses is skipped
|
|
648
|
+
along with everything under it, under an explicit `roots` override too — a
|
|
649
|
+
selection naming a hidden node still must not export it. A source that omits
|
|
650
|
+
the predicate emits everything, exactly as before.
|
|
651
|
+
- 995fde2: A text node's `data.fill: null` is now an explicit no-fill, so stroked-but-
|
|
652
|
+
unfilled text — outline-only display type — renders as such.
|
|
653
|
+
|
|
654
|
+
Every other node kind already read `null` that way. Text resolved it back to the
|
|
655
|
+
default black, because a `ResolvedRun` had to name a concrete `FillStyle` and
|
|
656
|
+
nothing downstream could skip the fill pass. `ResolvedRun.fill`,
|
|
657
|
+
`ResolvedTextStyle.fill` and `LaidOutGroup.fill` are now `FillStyle | null`, and
|
|
658
|
+
`TextPaint.fill: null` carries through to all three. Absent still means the
|
|
659
|
+
default black.
|
|
660
|
+
|
|
661
|
+
An unfilled run paints through its stroke alone, which only the outline tier can
|
|
662
|
+
lay down, so layout emits no atlas quads for one and the renderer skips the
|
|
663
|
+
glyph-fill mesh — an unfilled, unstroked run emits nothing at all, not even its
|
|
664
|
+
outline geometry. Underline, strikethrough and overline follow the fill: a rule
|
|
665
|
+
is a solid rect with no stroked counterpart, so an unfilled run draws none.
|
|
666
|
+
Nothing changes for text that has a fill.
|
|
667
|
+
|
|
668
|
+
Picking deliberately does not follow. `kit:text` still reports `filled: true`
|
|
669
|
+
for `fill: null`, because a text node's silhouette is its line boxes rather than
|
|
670
|
+
its glyph ink — reporting it unfilled would leave a word grabbable within a
|
|
671
|
+
stroke width of a box edge and nowhere near the letters.
|
|
672
|
+
|
|
673
|
+
`@weasel-js/svg` reads and writes SVG's own spelling of this: `<text
|
|
674
|
+
fill="none">` parses to `fill: null` instead of being dropped as absent, and a
|
|
675
|
+
text node with `fill: null` serializes as `fill="none"` rather than as SVG's
|
|
676
|
+
default black. `SvgTextNode.fill` widens to `FillStyle | null`.
|
|
677
|
+
|
|
678
|
+
The "Text outlines" demo has a Fill checkbox alongside its Stroke one; the two
|
|
679
|
+
off together is a node with no glyph paint at all.
|
|
680
|
+
- 6e4fb4d: A user layer survives `toJSON()` — its name, and the fact that it is a user
|
|
681
|
+
layer at all.
|
|
682
|
+
|
|
683
|
+
Every layer was written to a snapshot as `{ id, visible, locked }` and read back
|
|
684
|
+
as `kind: 'system'`, so reloading a document renamed nothing, showed nothing in
|
|
685
|
+
a layer list, and made `renameLayer` throw "cannot rename system layer" on a
|
|
686
|
+
layer the user had just created.
|
|
687
|
+
|
|
688
|
+
`SerializedLayer` is the snapshot's layer shape: the fields it always had, plus
|
|
689
|
+
an optional `kind` and `name`. A snapshot written before this carries neither
|
|
690
|
+
and loads as system layers, which is what every layer in it was.
|
|
691
|
+
|
|
692
|
+
`sceneFromJSON` now builds an empty scene and calls `loadState`, so the layer
|
|
693
|
+
stack is rebuilt by the one reader that knows how instead of by `createScene`,
|
|
694
|
+
which mints system layers only.
|
|
695
|
+
- b0fba6a: One wheel convention across the kit. Breaking: `computeWheelAction` changes
|
|
696
|
+
shape, and `useZoom` / `usePinchZoomTool` are gone.
|
|
697
|
+
|
|
698
|
+
Three public entry points answered the wheel and disagreed with each other.
|
|
699
|
+
`viewport.wheelPan` + `viewport.zoom` panned on a bare wheel and zoomed on
|
|
700
|
+
Cmd/Ctrl+wheel; `computeWheelAction` did the opposite, zooming on a bare wheel
|
|
701
|
+
and treating Cmd+wheel as a vertical scroll; `useZoom` zoomed on a bare wheel
|
|
702
|
+
and never panned. The two reducers also panned in screen pixels against a
|
|
703
|
+
`{ zoom, panX, panY }` state that is not a `View` and cannot be handed to
|
|
704
|
+
`view.set`.
|
|
705
|
+
|
|
706
|
+
The surviving convention is the one the dispatcher already ships: bare wheel
|
|
707
|
+
pans, shift+wheel pans horizontally, Cmd/Ctrl+wheel zooms under the pointer,
|
|
708
|
+
and a trackpad pinch (which browsers deliver as ctrl+wheel) zooms. Coordinates
|
|
709
|
+
are `View` throughout — a pan delta arrives in screen pixels and is divided by
|
|
710
|
+
`View.scale` before it lands, and a zoom anchor is canvas-local.
|
|
711
|
+
|
|
712
|
+
`computeWheelAction(view, input, clamp?)` now takes and returns a `View`. Its
|
|
713
|
+
halves, `wheelPan` and `wheelZoom`, are exported alongside `wheelZoomFactor`,
|
|
714
|
+
and `viewport.wheelPan` / `viewport.zoom` call them rather than restating the
|
|
715
|
+
math — so the wired path and the pure one cannot drift again. `WheelState` and
|
|
716
|
+
`ZoomBounds` are removed; `WheelInput` names its anchor `x`/`y` instead of
|
|
717
|
+
`mouseX`/`mouseY` and reads `ctrlKey`. Zoom is now `1.1^(-deltaY/100)` on
|
|
718
|
+
every path, which is reciprocal: scrolling a distance and back returns to the
|
|
719
|
+
scale you started from, where the old `1.1`/`0.9` pair did not.
|
|
720
|
+
|
|
721
|
+
A pinch anchors under the fingers in canvas-local coordinates. The dispatcher
|
|
722
|
+
converts the multitouch centroid the same way it already converted the wheel
|
|
723
|
+
anchor; on a canvas offset from the viewport top-left, `viewport.pinchZoom`
|
|
724
|
+
was anchoring on raw client coordinates and drifting by that offset.
|
|
725
|
+
|
|
726
|
+
`useZoom`, `UseZoomOptions` and `UseZoomReturn` are removed. They were
|
|
727
|
+
deprecated, had no consumer, and were the third convention.
|
|
728
|
+
|
|
729
|
+
`usePinchZoomTool` and `PinchZoomToolOpts` are removed, with the `viewport`
|
|
730
|
+
prop on the unexported `<Canvas>` primitive and the `ViewportConfig` type that
|
|
731
|
+
typed it. `viewport.pinchZoom` on `<SceneCanvas>` is unaffected — it is the
|
|
732
|
+
action, and it is now the kit's only pinch path. `usePinchGesture`, the raw
|
|
733
|
+
two-finger listener underneath, stays.
|
|
734
|
+
- Updated dependencies [fc16cac]
|
|
735
|
+
- Updated dependencies [995fde2]
|
|
736
|
+
- @weasel-js/paint@1.4.3
|
|
737
|
+
- @weasel-js/text@1.4.3
|
|
738
|
+
- @weasel-js/cursor@1.4.3
|
|
739
|
+
- @weasel-js/font@1.4.3
|
|
740
|
+
- @weasel-js/geom@1.4.3
|
|
741
|
+
- @weasel-js/gestures@1.4.3
|
|
742
|
+
- @weasel-js/history@1.4.3
|
|
743
|
+
- @weasel-js/modes@1.4.3
|
|
744
|
+
|
|
3
745
|
## 1.4.2
|
|
4
746
|
|
|
5
747
|
### Patch Changes
|