@weasel-js/core 1.4.2 → 1.4.3
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 +375 -0
- package/dist/{DrawCommand-Ch8W63oQ.d.ts → DrawCommand-CD-ug3d9.d.ts} +45 -1
- package/dist/{stroke-Dq8nL4A2.d.ts → builtins-CtGORLCG.d.ts} +51 -2
- package/dist/{chunk-2KKYDDDD.js → chunk-4RJP2N2L.js} +43 -21
- package/dist/chunk-4RJP2N2L.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-TORKRNFY.js → chunk-LEURURX3.js} +68 -5
- package/dist/chunk-LEURURX3.js.map +1 -0
- package/dist/{chunk-Y5KS66G7.js → chunk-ORZNKVXM.js} +3 -3
- package/dist/{chunk-Y5KS66G7.js.map → chunk-ORZNKVXM.js.map} +1 -1
- 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-KIVJXUYE.js → chunk-WYPSGIYS.js} +1058 -503
- package/dist/chunk-WYPSGIYS.js.map +1 -0
- 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-CHR36Ub_.d.ts} +3 -3
- package/dist/{grid-nnXU4VjN.d.ts → grid-Bw8cSde-.d.ts} +1 -1
- package/dist/index.d.ts +117 -232
- package/dist/index.js +7 -7
- package/dist/insert.d.ts +3 -3
- package/dist/move.d.ts +4 -4
- package/dist/move.js +2 -2
- package/dist/{options-CdFl510T.d.ts → options-DYfUlZxk.d.ts} +1 -1
- package/dist/{pointSnapToGrid-Dmthv94u.d.ts → pointSnapToGrid-D1bRdq-j.d.ts} +2 -2
- package/dist/{registry-BwY_DxJM.d.ts → registry-DnoTWGED.d.ts} +98 -8
- package/dist/renderer.d.ts +32 -4
- package/dist/renderer.js +7 -7
- package/dist/resize.d.ts +4 -4
- package/dist/routing.d.ts +6 -6
- package/dist/routing.js +1 -1
- package/dist/{types-miXHGrZM.d.ts → types-BJqsTlXl.d.ts} +1 -1
- package/dist/{types-XBcDEp3Y.d.ts → types-CoVTbo_y.d.ts} +9 -0
- package/dist/{types-DIQAisSG.d.ts → types-H7o6MaPo.d.ts} +20 -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,380 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.4.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2de5a37: A canvas opting out of an action no longer takes it away from its siblings.
|
|
8
|
+
|
|
9
|
+
`useViewportActions` answered `pinchZoom: false` with
|
|
10
|
+
`reg.unregister('viewport.pinchZoom')`, which drops *every* registrant of that
|
|
11
|
+
id — so one canvas opting out killed pinch-zoom on a sibling that asked for it,
|
|
12
|
+
and nothing put it back when the opting-out canvas unmounted.
|
|
13
|
+
`actions={{ id: null }}` went through the same door. Registration has been
|
|
14
|
+
per-registrant since the registrant stack landed; suppression was not, and a
|
|
15
|
+
shared registry had nowhere to hang "not for me".
|
|
16
|
+
|
|
17
|
+
`ActionsScope` is that place. It is a view of the registry in scope with its own
|
|
18
|
+
mute set: `register`, `unregister` and the dispatcher / dep-registry slots pass
|
|
19
|
+
straight through to the shared store, so cross-canvas sharing is untouched,
|
|
20
|
+
while `list`, `trigger` and `begin` skip what this scope muted. Scopes nest.
|
|
21
|
+
`<ActionsProvider>` is itself a scope, so a lone canvas needs no wrapper, and a
|
|
22
|
+
`<SceneCanvas>` deferring to a host provider now mounts one.
|
|
23
|
+
|
|
24
|
+
`ActionsRegistry.mute(id)` returns a release; a scope drops everything it muted
|
|
25
|
+
when it unmounts. `unregister` keeps its old meaning — "this action should not
|
|
26
|
+
exist" — and is unchanged.
|
|
27
|
+
|
|
28
|
+
Breaking for anyone implementing `ActionsRegistry` themselves: `mute` is a new
|
|
29
|
+
required method.
|
|
30
|
+
- 10e1ab6: A bare `<Canvas>` now repaints when its selection changes, without a wrapper
|
|
31
|
+
asking it to.
|
|
32
|
+
|
|
33
|
+
The redraw tripwire — the layout effect whose dep array is meant to name every
|
|
34
|
+
paint input that arrives on a render — did not name the overlay-aware state the
|
|
35
|
+
layer helpers expose. Selection, preview poses and the chrome derived from them
|
|
36
|
+
were written during render into a ref, so changing the selection prop repainted
|
|
37
|
+
nothing. It looked correct only because `<SceneCanvas>` calls `requestRedraw()`
|
|
38
|
+
by hand for its own data sources.
|
|
39
|
+
|
|
40
|
+
The tripwire now carries the memoized chrome state, which re-derives on exactly
|
|
41
|
+
the selection, bounds and preview inputs the helpers read, plus the chrome-caps
|
|
42
|
+
predicate. A render that changed none of them still paints nothing.
|
|
43
|
+
- eb0d6ce: Undo of a Delete now brings back everything the delete cascaded — a node's
|
|
44
|
+
dependents, and their own subtrees — not just the node that was named.
|
|
45
|
+
|
|
46
|
+
Deleting a node takes everything deriving from it, so deleting a box takes the
|
|
47
|
+
edges drawn from it. The delete op only ever snapshotted the subtree, so undo
|
|
48
|
+
re-inserted the box alone and the edges stayed gone. The op now snapshots the
|
|
49
|
+
whole set before removing, and re-inserts it parent-first, each node at the slot
|
|
50
|
+
it held.
|
|
51
|
+
|
|
52
|
+
New public read: `scene.removalClosure(ids)` answers what `removeMany(ids)`
|
|
53
|
+
would take, without taking it. The scene owns the cascade relations, so a caller
|
|
54
|
+
that rebuilds the walk from `dependsOn` and `children` goes stale the moment a
|
|
55
|
+
relation is added — `buildDeleteOps` asked its own copy of that question and is
|
|
56
|
+
now on this one.
|
|
57
|
+
|
|
58
|
+
The delete op reads it through an optional `getRemovalClosure(ids)` on the
|
|
59
|
+
adapter, alongside `getChildren`. An adapter that cascades along nothing but the
|
|
60
|
+
subtree can leave it out and behaves as before; one that cascades further has to
|
|
61
|
+
answer, or the nodes it takes are absent from the snapshot. The scene-backed
|
|
62
|
+
adapters answer it.
|
|
63
|
+
|
|
64
|
+
`insertNode` on the scene-backed adapters now forwards the function-valued
|
|
65
|
+
fields — `dependsOn`, `derivePath` and `clipFromPose` — so a restored edge
|
|
66
|
+
derives again instead of coming back as a static path, and a restored container
|
|
67
|
+
still clips. The delete op's serialized `descendants` argument is now
|
|
68
|
+
`cascaded`, and carries the node itself alongside what went with it.
|
|
69
|
+
- 75969f6: Full-screen effect passes: a group's children render into a texture, shader
|
|
70
|
+
passes run over it, and the result composites back where the group sits.
|
|
71
|
+
|
|
72
|
+
Until now the renderer could draw over the frame but never transform it —
|
|
73
|
+
nothing sampled what had already been drawn, so blur, bloom and distortion were
|
|
74
|
+
unreachable and the only recourse was a CSS `filter` on the `<canvas>`, which is
|
|
75
|
+
the browser compositing on the kit's behalf.
|
|
76
|
+
|
|
77
|
+
`GroupDrawCommand.effects` is the primitive. It is the one field there that does
|
|
78
|
+
not accumulate down the group stack: it is a render-target boundary. The
|
|
79
|
+
children draw into a buffer with a stencil of its own, each effect reads the
|
|
80
|
+
previous one's output, and the composite applies the group's `transform`,
|
|
81
|
+
`alpha` and `colorMatrix` plus whatever clip encloses it — so the enclosing clip
|
|
82
|
+
clips the result rather than the pixels an effect reads, and nested clips inside
|
|
83
|
+
start from a fresh depth budget.
|
|
84
|
+
|
|
85
|
+
`RenderLayer.effects` is that field folded in at `drawOneLayer`, which is where
|
|
86
|
+
every layer already gets wrapped in a group. A blur there blurs one layer and
|
|
87
|
+
leaves the chrome drawn above it sharp, which is the thing the CSS filter cannot
|
|
88
|
+
do.
|
|
89
|
+
|
|
90
|
+
`blur({ radius })` and `vignette({ amount })` ship as built-ins; both return
|
|
91
|
+
`Effect[]` because a separable blur is honestly two passes.
|
|
92
|
+
`registerEffect(id, frag)` registers your own — it is `registerProgram` with the
|
|
93
|
+
effect vertex shader, and using the custom-shader one instead compiles, runs,
|
|
94
|
+
and samples the source upside down.
|
|
95
|
+
|
|
96
|
+
Nothing is allocated until a group declares an effect, so a canvas without them
|
|
97
|
+
carries no offscreen buffer. Buffers are drawing-buffer sized, pooled per
|
|
98
|
+
renderer, and dropped on resize, dispose and context loss.
|
|
99
|
+
|
|
100
|
+
Demo: "Full-screen effect passes", with visual baselines blurred and sharp.
|
|
101
|
+
- 0d40f94: A layer whose `draw` throws now paints nothing and names itself in the console,
|
|
102
|
+
instead of taking the frame down with it.
|
|
103
|
+
|
|
104
|
+
The paint runs on the frame loop, so a throw out of `draw` surfaced as an
|
|
105
|
+
uncaught `requestAnimationFrame` error on the window: the canvas went blank and
|
|
106
|
+
stayed stale until something unrelated asked for a redraw. `drawOneLayer` — the
|
|
107
|
+
one path both the canvas and a viewport's inner pass go through — catches it,
|
|
108
|
+
drops that layer for the frame, and paints the rest.
|
|
109
|
+
|
|
110
|
+
`drawLayers` and `drawOneLayer` take an `onLayerError` callback for a consumer
|
|
111
|
+
that wants to route the failure somewhere of its own; the default reports the
|
|
112
|
+
layer id and the error to `console.error`. The layer's cached commands are
|
|
113
|
+
dropped with it, so the next frame is a real re-attempt rather than a stale tree
|
|
114
|
+
served under fresh deps.
|
|
115
|
+
- 713f98a: A layout strategy decides what happens to a child released outside every
|
|
116
|
+
container.
|
|
117
|
+
|
|
118
|
+
When no container accepted a drag, the move action committed the child wherever
|
|
119
|
+
the pointer stopped, and the source layout had no say — a grid could not close
|
|
120
|
+
the gap, and a container that only means to arrange its own children could not
|
|
121
|
+
take one back.
|
|
122
|
+
|
|
123
|
+
`LayoutStrategy.releaseDrop` is that say. It sees the source container, its
|
|
124
|
+
remaining children and the dragged child in world coordinates, and returns ops
|
|
125
|
+
that replace the free-space commit. An empty array leaves the container alone,
|
|
126
|
+
which snaps the child home, since the drag only ever wrote previews. `null` —
|
|
127
|
+
and a strategy that does not implement the method — leaves the drop where the
|
|
128
|
+
pointer left it, exactly as before.
|
|
129
|
+
- 85f4a21: Dragging several selected nodes into a layout container now runs the layout,
|
|
130
|
+
instead of falling through to a plain translate. Three nodes dropped on a grid
|
|
131
|
+
fill three cells.
|
|
132
|
+
|
|
133
|
+
The selection lands as a unit. One container is chosen, from the center of the
|
|
134
|
+
whole dragged group rather than each child's own — hit-testing per child would
|
|
135
|
+
scatter a selection straddling two containers — and it has to accept every
|
|
136
|
+
member: `acceptsDrop` is asked once per dragged child, and one refusal takes the
|
|
137
|
+
container out of the running. The children are then placed one at a time, in
|
|
138
|
+
selection order, and each placement sees the container state the previous one
|
|
139
|
+
produced. A strategy that packs, stacks or displaces therefore sees the group
|
|
140
|
+
arrive the same way it would see three separate drags. If any child has no
|
|
141
|
+
target, the whole drop is refused rather than split between two homes.
|
|
142
|
+
|
|
143
|
+
Each child is snapped at its own position, displaced by however far the pointer
|
|
144
|
+
sits from the selection's center, so the group keeps its shape as it lands. A
|
|
145
|
+
single-node drag still probes at the pointer exactly as before.
|
|
146
|
+
|
|
147
|
+
The rest follows the selection: the live preview reflows the destination and
|
|
148
|
+
every source container the selection left, one pass per container with all of
|
|
149
|
+
its departing children withdrawn at once; and the commit emits a reparent op per
|
|
150
|
+
child that changed parent, every reparent before every drop.
|
|
151
|
+
|
|
152
|
+
`releaseDrop` — a container's say over its own child released into open space —
|
|
153
|
+
is now per source container too, so a mixed selection resolves per child: the
|
|
154
|
+
grid's own child goes home, and a free node in the same selection keeps the
|
|
155
|
+
translate it would have had on its own.
|
|
156
|
+
- 4bb0341: A parallax plane draws its source layers through `drawOneLayer`, so their
|
|
157
|
+
`space` means something.
|
|
158
|
+
|
|
159
|
+
`createParallaxLayer` called `layer.draw(...)` directly where every other path
|
|
160
|
+
through a view — the canvas itself, a viewport node's inner pass — goes through
|
|
161
|
+
`drawOneLayer`. A `space: 'world'` source therefore came out unprojected, and the
|
|
162
|
+
only way to see anything was for the source to pre-project by hand while
|
|
163
|
+
declaring a space it did not draw in. ParallaxDemo's four layers did exactly
|
|
164
|
+
that, and their labels were lies.
|
|
165
|
+
|
|
166
|
+
Now a world-space source is wrapped in the plane's inner view and a screen-space
|
|
167
|
+
one is passed through, the same rule that holds everywhere else. The plane
|
|
168
|
+
itself stays `space: 'screen'` — its children carry whatever transform they
|
|
169
|
+
need, and the outer canvas must add none.
|
|
170
|
+
|
|
171
|
+
The demo drops its `project` helper and emits world coordinates; the committed
|
|
172
|
+
`parallax` visual baseline passes unchanged.
|
|
173
|
+
- e0d5580: `pathHitTest` reads curves and holes instead of throwing on one and ignoring
|
|
174
|
+
the other.
|
|
175
|
+
|
|
176
|
+
Its vertex extractor walked `M` and `L` only and threw on any bezier command,
|
|
177
|
+
and it stopped at the first `Z`, so a path's second contour was never
|
|
178
|
+
considered. The throw was reachable in ordinary use: `sceneAdapter`'s
|
|
179
|
+
`nodeBoundsPassClips` calls `pathIntersectsRect` on every ancestor clip, so a
|
|
180
|
+
container with a curved `clipFromPose` crashed the hit-test walk.
|
|
181
|
+
|
|
182
|
+
`pathContainsRect`, `pathIntersectsRect`, `pathContainsPolygon` and
|
|
183
|
+
`pathIntersectsPolygon` now treat a `PolygonPath` as its filled region. Whether
|
|
184
|
+
a point is inside comes from `pointInPath`, so beziers flatten and `fillRule`
|
|
185
|
+
decides — a donut's hole is outside the shape under `evenodd` and inside it
|
|
186
|
+
under `nonzero`, and the four predicates agree with `pathContainsPoint` on
|
|
187
|
+
which. Each takes the same optional flattening tolerance `pointInPath` does.
|
|
188
|
+
|
|
189
|
+
Two answers change for paths that already worked. A rect sitting in a hole is
|
|
190
|
+
no longer reported as contained or intersecting, and containment now fails when
|
|
191
|
+
a contour reaches into the rect at all rather than only when it crosses an
|
|
192
|
+
edge.
|
|
193
|
+
- edf99d5: Undoing a removal from a persisted history brings `derivePath` and
|
|
194
|
+
`clipFromPose` back.
|
|
195
|
+
|
|
196
|
+
`kit:remove`'s snapshot cloned each node with its function-valued fields
|
|
197
|
+
attached, which works in-session and disappears the moment the history is
|
|
198
|
+
serialized. `dependsOn` survived the round-trip and repopulated the dependency
|
|
199
|
+
index, so a restored derived node looked wired up and never painted; a
|
|
200
|
+
container came back unclipped the same way.
|
|
201
|
+
|
|
202
|
+
The snapshot now carries the registry keys beside the nodes, and revert
|
|
203
|
+
re-resolves them exactly as `kit:add` does — warning, not throwing, when a key
|
|
204
|
+
is absent from the scene's registry.
|
|
205
|
+
- 2723cc7: `selectAll` no longer selects nodes on a hidden layer, so Cmd+A then Delete
|
|
206
|
+
cannot take content the user cannot see.
|
|
207
|
+
|
|
208
|
+
It walked `renderOrder()`, which is every node in the scene regardless of what
|
|
209
|
+
its layer's `visible` flag says. It now walks `renderOrderNodes()` when any
|
|
210
|
+
layer is hidden — the same sequence, carrying the layer each node sits on — and
|
|
211
|
+
keeps the cheaper id walk when every layer is visible.
|
|
212
|
+
|
|
213
|
+
A `scene` dep that answers neither `layers` nor `renderOrderNodes` behaves
|
|
214
|
+
exactly as before.
|
|
215
|
+
- 0ca0aca: Layer groups: several consecutive layers render into one buffer and share one
|
|
216
|
+
effect pass.
|
|
217
|
+
|
|
218
|
+
`RenderLayer.effects` runs over a single layer, which is the wrong picture as
|
|
219
|
+
soon as a pass reads neighboring pixels — `blur(A over B)` is not `blur(A) over
|
|
220
|
+
blur(B)` — and it costs a buffer and a pass chain per layer. A consumer wanting
|
|
221
|
+
the world blurred and the HUD sharp had no way to say that the world was more
|
|
222
|
+
than one layer.
|
|
223
|
+
|
|
224
|
+
`LayerGroup` is that surface: `{ id, layers, effects?, alpha?, colorMatrix? }`,
|
|
225
|
+
passed to `<Canvas>` / `<SceneCanvas>` as `layerGroups`. Members are named by
|
|
226
|
+
`RenderLayer.id`, the same names `layerOrder` and `layerVisibility` use, so a
|
|
227
|
+
group can take in kit-built layers as readily as consumer ones. `drawLayers`
|
|
228
|
+
brackets each run of consecutive members in one `kind: 'group'` command; the
|
|
229
|
+
renderer's existing offscreen path does the rest.
|
|
230
|
+
|
|
231
|
+
Only consecutive members share a buffer, because anything drawn between two of
|
|
232
|
+
them has to land between them. A group split across the render order is drawn as
|
|
233
|
+
one bracket per run, with a warning — the picture is right, the declaration
|
|
234
|
+
probably is not. A hidden member, and a member that paints nothing this frame,
|
|
235
|
+
break no run.
|
|
236
|
+
|
|
237
|
+
`effects` also accepts a thunk, re-read on every frame the canvas paints, so an
|
|
238
|
+
animating radius costs no React render. A group that declares no effects, alpha
|
|
239
|
+
or color matrix emits no wrapper at all, and a frame with no groups allocates
|
|
240
|
+
nothing.
|
|
241
|
+
|
|
242
|
+
The side-scroller load test now blurs its six world layers on a head knock and
|
|
243
|
+
leaves its HUD, callouts and ending card sharp; the CSS `filter` it used to
|
|
244
|
+
reach for is gone.
|
|
245
|
+
- 3583ca3: `useStandardActions` takes an `exclude` list, so a consumer can bind its own.
|
|
246
|
+
|
|
247
|
+
The hook registered a fixed descriptor list, which left no way to suppress an
|
|
248
|
+
individual kit action. A consumer wanting its own align or distribute
|
|
249
|
+
keybindings got the kit's as well, and the two competed for the same keys.
|
|
250
|
+
|
|
251
|
+
`exclude` names ids to leave unregistered; an id naming no kit action is
|
|
252
|
+
ignored, and changing the list re-registers. `KIT_STANDARD_ACTION_IDS` is the
|
|
253
|
+
full list in registration order, so the ids are discoverable rather than
|
|
254
|
+
something to read out of the source.
|
|
255
|
+
- fc16cac: `Stroke.paint` is optional, and a stroke without one paints nothing everywhere
|
|
256
|
+
rather than throwing.
|
|
257
|
+
|
|
258
|
+
Such a stroke is real: a property panel that writes one field onto a node with
|
|
259
|
+
no stroke — a width, a cap — materializes a whole stroke around it, and
|
|
260
|
+
documents written before that was fixed still hold them. The painters already
|
|
261
|
+
read one as no stroke. Every other reader dereferenced `paint` unguarded, so a
|
|
262
|
+
document holding one threw on SVG export, on copy, and out of any consumer
|
|
263
|
+
painter or overlay whose command reached the renderer directly.
|
|
264
|
+
|
|
265
|
+
The type says so now, which is what stops the next reader from assuming
|
|
266
|
+
otherwise. What each one does with an unpainted stroke:
|
|
267
|
+
|
|
268
|
+
- The renderer skips the stroke pass and paints the fill.
|
|
269
|
+
- The SVG serializer emits no `stroke` attributes at all, the way it already
|
|
270
|
+
does for an absent or zero-width stroke.
|
|
271
|
+
- Text layout keys it as no stroke, so an unpainted run groups with unstroked
|
|
272
|
+
ones instead of splitting a draw call, and does not get pulled onto the
|
|
273
|
+
outline tier to stroke nothing.
|
|
274
|
+
- `setStrokeOpacity` seeds the default stroke color to have something to set an
|
|
275
|
+
opacity on, keeping the width and joins already there.
|
|
276
|
+
- 6d4bbeb: WeaselDraw's SVG export drops what a hidden layer holds, matching what the
|
|
277
|
+
pixel path draws. It walked the whole container tree with no visibility gate,
|
|
278
|
+
so hiding a layer and exporting produced a file with the hidden content in it.
|
|
279
|
+
|
|
280
|
+
`SceneSource` gains an optional `isPainted(id)`; a node it refuses is skipped
|
|
281
|
+
along with everything under it, under an explicit `roots` override too — a
|
|
282
|
+
selection naming a hidden node still must not export it. A source that omits
|
|
283
|
+
the predicate emits everything, exactly as before.
|
|
284
|
+
- 995fde2: A text node's `data.fill: null` is now an explicit no-fill, so stroked-but-
|
|
285
|
+
unfilled text — outline-only display type — renders as such.
|
|
286
|
+
|
|
287
|
+
Every other node kind already read `null` that way. Text resolved it back to the
|
|
288
|
+
default black, because a `ResolvedRun` had to name a concrete `FillStyle` and
|
|
289
|
+
nothing downstream could skip the fill pass. `ResolvedRun.fill`,
|
|
290
|
+
`ResolvedTextStyle.fill` and `LaidOutGroup.fill` are now `FillStyle | null`, and
|
|
291
|
+
`TextPaint.fill: null` carries through to all three. Absent still means the
|
|
292
|
+
default black.
|
|
293
|
+
|
|
294
|
+
An unfilled run paints through its stroke alone, which only the outline tier can
|
|
295
|
+
lay down, so layout emits no atlas quads for one and the renderer skips the
|
|
296
|
+
glyph-fill mesh — an unfilled, unstroked run emits nothing at all, not even its
|
|
297
|
+
outline geometry. Underline, strikethrough and overline follow the fill: a rule
|
|
298
|
+
is a solid rect with no stroked counterpart, so an unfilled run draws none.
|
|
299
|
+
Nothing changes for text that has a fill.
|
|
300
|
+
|
|
301
|
+
Picking deliberately does not follow. `kit:text` still reports `filled: true`
|
|
302
|
+
for `fill: null`, because a text node's silhouette is its line boxes rather than
|
|
303
|
+
its glyph ink — reporting it unfilled would leave a word grabbable within a
|
|
304
|
+
stroke width of a box edge and nowhere near the letters.
|
|
305
|
+
|
|
306
|
+
`@weasel-js/svg` reads and writes SVG's own spelling of this: `<text
|
|
307
|
+
fill="none">` parses to `fill: null` instead of being dropped as absent, and a
|
|
308
|
+
text node with `fill: null` serializes as `fill="none"` rather than as SVG's
|
|
309
|
+
default black. `SvgTextNode.fill` widens to `FillStyle | null`.
|
|
310
|
+
|
|
311
|
+
The "Text outlines" demo has a Fill checkbox alongside its Stroke one; the two
|
|
312
|
+
off together is a node with no glyph paint at all.
|
|
313
|
+
- 6e4fb4d: A user layer survives `toJSON()` — its name, and the fact that it is a user
|
|
314
|
+
layer at all.
|
|
315
|
+
|
|
316
|
+
Every layer was written to a snapshot as `{ id, visible, locked }` and read back
|
|
317
|
+
as `kind: 'system'`, so reloading a document renamed nothing, showed nothing in
|
|
318
|
+
a layer list, and made `renameLayer` throw "cannot rename system layer" on a
|
|
319
|
+
layer the user had just created.
|
|
320
|
+
|
|
321
|
+
`SerializedLayer` is the snapshot's layer shape: the fields it always had, plus
|
|
322
|
+
an optional `kind` and `name`. A snapshot written before this carries neither
|
|
323
|
+
and loads as system layers, which is what every layer in it was.
|
|
324
|
+
|
|
325
|
+
`sceneFromJSON` now builds an empty scene and calls `loadState`, so the layer
|
|
326
|
+
stack is rebuilt by the one reader that knows how instead of by `createScene`,
|
|
327
|
+
which mints system layers only.
|
|
328
|
+
- b0fba6a: One wheel convention across the kit. Breaking: `computeWheelAction` changes
|
|
329
|
+
shape, and `useZoom` / `usePinchZoomTool` are gone.
|
|
330
|
+
|
|
331
|
+
Three public entry points answered the wheel and disagreed with each other.
|
|
332
|
+
`viewport.wheelPan` + `viewport.zoom` panned on a bare wheel and zoomed on
|
|
333
|
+
Cmd/Ctrl+wheel; `computeWheelAction` did the opposite, zooming on a bare wheel
|
|
334
|
+
and treating Cmd+wheel as a vertical scroll; `useZoom` zoomed on a bare wheel
|
|
335
|
+
and never panned. The two reducers also panned in screen pixels against a
|
|
336
|
+
`{ zoom, panX, panY }` state that is not a `View` and cannot be handed to
|
|
337
|
+
`view.set`.
|
|
338
|
+
|
|
339
|
+
The surviving convention is the one the dispatcher already ships: bare wheel
|
|
340
|
+
pans, shift+wheel pans horizontally, Cmd/Ctrl+wheel zooms under the pointer,
|
|
341
|
+
and a trackpad pinch (which browsers deliver as ctrl+wheel) zooms. Coordinates
|
|
342
|
+
are `View` throughout — a pan delta arrives in screen pixels and is divided by
|
|
343
|
+
`View.scale` before it lands, and a zoom anchor is canvas-local.
|
|
344
|
+
|
|
345
|
+
`computeWheelAction(view, input, clamp?)` now takes and returns a `View`. Its
|
|
346
|
+
halves, `wheelPan` and `wheelZoom`, are exported alongside `wheelZoomFactor`,
|
|
347
|
+
and `viewport.wheelPan` / `viewport.zoom` call them rather than restating the
|
|
348
|
+
math — so the wired path and the pure one cannot drift again. `WheelState` and
|
|
349
|
+
`ZoomBounds` are removed; `WheelInput` names its anchor `x`/`y` instead of
|
|
350
|
+
`mouseX`/`mouseY` and reads `ctrlKey`. Zoom is now `1.1^(-deltaY/100)` on
|
|
351
|
+
every path, which is reciprocal: scrolling a distance and back returns to the
|
|
352
|
+
scale you started from, where the old `1.1`/`0.9` pair did not.
|
|
353
|
+
|
|
354
|
+
A pinch anchors under the fingers in canvas-local coordinates. The dispatcher
|
|
355
|
+
converts the multitouch centroid the same way it already converted the wheel
|
|
356
|
+
anchor; on a canvas offset from the viewport top-left, `viewport.pinchZoom`
|
|
357
|
+
was anchoring on raw client coordinates and drifting by that offset.
|
|
358
|
+
|
|
359
|
+
`useZoom`, `UseZoomOptions` and `UseZoomReturn` are removed. They were
|
|
360
|
+
deprecated, had no consumer, and were the third convention.
|
|
361
|
+
|
|
362
|
+
`usePinchZoomTool` and `PinchZoomToolOpts` are removed, with the `viewport`
|
|
363
|
+
prop on the unexported `<Canvas>` primitive and the `ViewportConfig` type that
|
|
364
|
+
typed it. `viewport.pinchZoom` on `<SceneCanvas>` is unaffected — it is the
|
|
365
|
+
action, and it is now the kit's only pinch path. `usePinchGesture`, the raw
|
|
366
|
+
two-finger listener underneath, stays.
|
|
367
|
+
- Updated dependencies [fc16cac]
|
|
368
|
+
- Updated dependencies [995fde2]
|
|
369
|
+
- @weasel-js/paint@1.4.3
|
|
370
|
+
- @weasel-js/text@1.4.3
|
|
371
|
+
- @weasel-js/cursor@1.4.3
|
|
372
|
+
- @weasel-js/font@1.4.3
|
|
373
|
+
- @weasel-js/geom@1.4.3
|
|
374
|
+
- @weasel-js/gestures@1.4.3
|
|
375
|
+
- @weasel-js/history@1.4.3
|
|
376
|
+
- @weasel-js/modes@1.4.3
|
|
377
|
+
|
|
3
378
|
## 1.4.2
|
|
4
379
|
|
|
5
380
|
### Patch Changes
|
|
@@ -116,6 +116,35 @@ type ShaderUniform = number | [number, number] | [number, number, number] | [num
|
|
|
116
116
|
*/
|
|
117
117
|
declare function registerProgram(id: string, vert: string, frag: string): ShaderProgramHandle;
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* One full-screen pass over what a group has already drawn.
|
|
121
|
+
*
|
|
122
|
+
* The renderer runs a group's effects in order, each reading the previous
|
|
123
|
+
* one's output through `u_source` and writing a whole new buffer — so an
|
|
124
|
+
* effect is free to read neighbouring pixels, which is the entire point and
|
|
125
|
+
* the one thing `colorMatrix` can never do.
|
|
126
|
+
*
|
|
127
|
+
* `uniforms` are the effect's own; `u_source`, `u_resolution` and `u_texel`
|
|
128
|
+
* come from the renderer and must not be passed here.
|
|
129
|
+
*/
|
|
130
|
+
interface Effect {
|
|
131
|
+
program: ShaderProgramHandle;
|
|
132
|
+
uniforms?: Record<string, ShaderUniform>;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Register a fragment shader as an effect. Sugar over `registerProgram` with
|
|
136
|
+
* the effect vertex shader, and the reason a consumer never imports the
|
|
137
|
+
* prelude: an effect that registers with the *custom-shader* vertex shader
|
|
138
|
+
* compiles, runs, and samples its source upside down.
|
|
139
|
+
*
|
|
140
|
+
* The fragment shader reads `v_uv` and `u_source`, and may declare
|
|
141
|
+
* `u_resolution` / `u_texel`. See `effectPrelude.ts` for the full contract,
|
|
142
|
+
* including the premultiplied-alpha requirement.
|
|
143
|
+
*
|
|
144
|
+
* @experimental
|
|
145
|
+
*/
|
|
146
|
+
declare function registerEffect(id: string, frag: string): ShaderProgramHandle;
|
|
147
|
+
|
|
119
148
|
/**
|
|
120
149
|
* Solid-fill paint variant (subset of the full `FillStyle` union from
|
|
121
150
|
* `@weasel-js/core`). Kept for back-compat with step-1/2 consumers and
|
|
@@ -172,6 +201,21 @@ interface GroupDrawCommand {
|
|
|
172
201
|
* cannot escape an ancestor's clip. Max 7 nesting levels; the renderer
|
|
173
202
|
* throws if exceeded. */
|
|
174
203
|
clip?: Path;
|
|
204
|
+
/**
|
|
205
|
+
* Full-screen passes run over this group's own pixels, in order, before it
|
|
206
|
+
* is composited into its parent.
|
|
207
|
+
*
|
|
208
|
+
* Unlike every other field here, this does not accumulate down the group
|
|
209
|
+
* stack — it is a render-target boundary. The children draw into a buffer of
|
|
210
|
+
* their own, each effect reads the previous one's output, and the result is
|
|
211
|
+
* composited back under this group's `transform`, `alpha`, `colorMatrix` and
|
|
212
|
+
* whatever clip encloses it. So a blur here blurs this group and nothing
|
|
213
|
+
* around it, which is what a CSS `filter` on the canvas cannot do.
|
|
214
|
+
*
|
|
215
|
+
* An empty or absent list costs nothing: no buffer is allocated until a
|
|
216
|
+
* group asks for one.
|
|
217
|
+
*/
|
|
218
|
+
effects?: readonly Effect[];
|
|
175
219
|
children: DrawCommand[];
|
|
176
220
|
}
|
|
177
221
|
/**
|
|
@@ -285,4 +329,4 @@ interface ShaderDrawCommand {
|
|
|
285
329
|
};
|
|
286
330
|
}
|
|
287
331
|
|
|
288
|
-
export { type DrawCommand as D, type GroupDrawCommand as G, type ImageDrawCommand as I, type Mat3 as M, type PathDrawCommand as P, SPRITE_STRIDE as S, type TextDrawCommand as T, type ShaderDrawCommand as a, type ShaderProgramHandle as b, type ShaderUniform as c, type SolidPaint as d, type SpritesDrawCommand as e, mat3 as m,
|
|
332
|
+
export { type DrawCommand as D, type Effect as E, type GroupDrawCommand as G, type ImageDrawCommand as I, type Mat3 as M, type PathDrawCommand as P, SPRITE_STRIDE as S, type TextDrawCommand as T, type ShaderDrawCommand as a, type ShaderProgramHandle as b, type ShaderUniform as c, type SolidPaint as d, type SpritesDrawCommand as e, registerProgram as f, mat3 as m, registerEffect as r };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GradStop, Stroke } from '@weasel-js/paint';
|
|
2
|
-
import { M as Mat3, b as ShaderProgramHandle, D as DrawCommand } from './DrawCommand-
|
|
2
|
+
import { M as Mat3, b as ShaderProgramHandle, D as DrawCommand, E as Effect } from './DrawCommand-CD-ug3d9.js';
|
|
3
3
|
import { P as Path } from './path-JEV2c5If.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -279,6 +279,22 @@ declare class GroupState {
|
|
|
279
279
|
get alpha(): number;
|
|
280
280
|
get colorMatrix(): Float32Array;
|
|
281
281
|
push(frame: GroupFrame): void;
|
|
282
|
+
/**
|
|
283
|
+
* Push a frame whose children paint into a surface of their own.
|
|
284
|
+
*
|
|
285
|
+
* The transform still accumulates — the children draw where they would have
|
|
286
|
+
* drawn. Alpha and colour do not: they describe how the finished surface
|
|
287
|
+
* joins the frame, and applying them on the way in as well would fade the
|
|
288
|
+
* pixels an effect is about to read, then fade them again on the way out.
|
|
289
|
+
*
|
|
290
|
+
* Returns the pair the caller must apply at composite time — what `push`
|
|
291
|
+
* would have left on the stack — so the composition rules live here and not
|
|
292
|
+
* in the dispatcher.
|
|
293
|
+
*/
|
|
294
|
+
pushIsolated(frame: GroupFrame): {
|
|
295
|
+
alpha: number;
|
|
296
|
+
colorMatrix: Float32Array;
|
|
297
|
+
};
|
|
282
298
|
/** Drop every pushed frame, leaving the root. A frame that throws part-way
|
|
283
299
|
* down the tree never pops, and the next frame would draw under leftovers. */
|
|
284
300
|
reset(): void;
|
|
@@ -491,6 +507,9 @@ declare class WeaselRenderer {
|
|
|
491
507
|
private dpr;
|
|
492
508
|
private canvas;
|
|
493
509
|
private target;
|
|
510
|
+
/** Offscreen buffers for group effects. Allocates nothing until a group
|
|
511
|
+
* with effects asks, so a canvas without them pays no memory. */
|
|
512
|
+
private readonly effectTargets;
|
|
494
513
|
private readonly imageMinification;
|
|
495
514
|
private readonly flattenTolerance?;
|
|
496
515
|
private readonly bakeBudget;
|
|
@@ -688,4 +707,34 @@ declare function resolveStrokeWidth(width: number | {
|
|
|
688
707
|
*/
|
|
689
708
|
declare function tessellateStroke(path: Path, stroke: Stroke, opts?: StrokeOptions): Mesh;
|
|
690
709
|
|
|
691
|
-
|
|
710
|
+
/**
|
|
711
|
+
* Effects that ship with the kit.
|
|
712
|
+
*
|
|
713
|
+
* Each is a function returning `Effect[]`, not a single `Effect`, because a
|
|
714
|
+
* separable kernel is genuinely two passes and hiding that behind one entry
|
|
715
|
+
* would make the cost invisible at the callsite:
|
|
716
|
+
*
|
|
717
|
+
* effects={[...blur({ radius: 4 }), ...vignette({ amount: 0.6 })]}
|
|
718
|
+
*/
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Gaussian blur, as a horizontal pass followed by a vertical one.
|
|
722
|
+
*
|
|
723
|
+
* `radius` is in device pixels and is the distance of the outermost tap, not a
|
|
724
|
+
* standard deviation — 0 is a copy, and the falloff is the same nine-tap
|
|
725
|
+
* kernel at every radius, so a large one is a wide blur rather than a better
|
|
726
|
+
* one. Two passes at O(9) beat one at O(81) and look the same.
|
|
727
|
+
*/
|
|
728
|
+
declare function blur({ radius }: {
|
|
729
|
+
radius: number;
|
|
730
|
+
}): Effect[];
|
|
731
|
+
/**
|
|
732
|
+
* Darken toward the corners. `amount` is how dark the corner gets (0..1) and
|
|
733
|
+
* `feather` how much of the radius the falloff occupies.
|
|
734
|
+
*/
|
|
735
|
+
declare function vignette({ amount, feather }: {
|
|
736
|
+
amount: number;
|
|
737
|
+
feather?: number;
|
|
738
|
+
}): Effect[];
|
|
739
|
+
|
|
740
|
+
export { IDENTITY_COLOR_MATRIX as I, type Mesh as M, type RenderTarget as R, ShaderCompileError as S, type View as V, WeaselRenderer as W, type ImageMinification as a, type SpriteSheet as b, type StrokeOptions as c, type WeaselRendererOptions as d, blur as e, buildGradientRamp as f, frameRect as g, vignette as h, ShaderProgram as i, resolveStrokeWidth as r, tessellateStroke as t, viewToMat3 as v };
|
|
@@ -55,27 +55,49 @@ function createInsertOp(args) {
|
|
|
55
55
|
registerOpFactory("insert", (args) => createInsertOp(args));
|
|
56
56
|
|
|
57
57
|
// src/core/ops/delete.ts
|
|
58
|
-
function
|
|
58
|
+
function indexIn(a, parentId, id) {
|
|
59
|
+
return a.getChildren?.(parentId)?.indexOf(id) ?? -1;
|
|
60
|
+
}
|
|
61
|
+
function captureCascade(a, rootId) {
|
|
59
62
|
const { getChildren, getNode } = a;
|
|
60
63
|
if (!getChildren || !getNode) return null;
|
|
64
|
+
const closure = a.getRemovalClosure?.([rootId]);
|
|
65
|
+
const taken = /* @__PURE__ */ new Map();
|
|
66
|
+
if (closure) {
|
|
67
|
+
for (const id of closure) {
|
|
68
|
+
const node = getNode.call(a, id);
|
|
69
|
+
if (node) taken.set(id, node);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
const walk = (id) => {
|
|
73
|
+
const node = getNode.call(a, id);
|
|
74
|
+
if (!node) return;
|
|
75
|
+
taken.set(id, node);
|
|
76
|
+
for (const kid of getChildren.call(a, id)) walk(kid);
|
|
77
|
+
};
|
|
78
|
+
walk(rootId);
|
|
79
|
+
}
|
|
80
|
+
const detachRoots = [...taken.values()].filter((n) => {
|
|
81
|
+
const p = parentOf(n);
|
|
82
|
+
return p === null || !taken.has(p);
|
|
83
|
+
}).map((n) => ({ node: n, index: indexIn(a, parentOf(n), n.id) })).sort((l, r) => l.index - r.index);
|
|
61
84
|
const out = [];
|
|
62
|
-
const
|
|
63
|
-
|
|
85
|
+
const emitSubtree = (entry) => {
|
|
86
|
+
out.push(entry);
|
|
87
|
+
const kids = getChildren.call(a, entry.node.id);
|
|
64
88
|
for (let i = 0; i < kids.length; i++) {
|
|
65
|
-
const child =
|
|
66
|
-
if (
|
|
67
|
-
out.push({ node: child, index: i });
|
|
68
|
-
walk(kids[i]);
|
|
89
|
+
const child = taken.get(kids[i]);
|
|
90
|
+
if (child) emitSubtree({ node: child, index: i });
|
|
69
91
|
}
|
|
70
92
|
};
|
|
71
|
-
|
|
93
|
+
for (const entry of detachRoots) emitSubtree(entry);
|
|
72
94
|
return out;
|
|
73
95
|
}
|
|
74
96
|
function createDeleteOp(args) {
|
|
75
97
|
const { node, label } = args;
|
|
76
98
|
let slot = args.slot ?? slotFromIndex(args.index);
|
|
77
|
-
const argsForSerial = { node, label, slot,
|
|
78
|
-
let captured = args.
|
|
99
|
+
const argsForSerial = { node, label, slot, cascaded: args.cascaded };
|
|
100
|
+
let captured = args.cascaded ?? [];
|
|
79
101
|
return {
|
|
80
102
|
name: "delete",
|
|
81
103
|
args: argsForSerial,
|
|
@@ -87,29 +109,29 @@ function createDeleteOp(args) {
|
|
|
87
109
|
slot = observed;
|
|
88
110
|
argsForSerial.slot = observed;
|
|
89
111
|
}
|
|
90
|
-
const snapshot =
|
|
112
|
+
const snapshot = captureCascade(a, node.id);
|
|
91
113
|
if (snapshot) {
|
|
92
114
|
captured = snapshot;
|
|
93
|
-
argsForSerial.
|
|
115
|
+
argsForSerial.cascaded = snapshot;
|
|
94
116
|
}
|
|
95
117
|
a.removeNode(node.id);
|
|
96
118
|
},
|
|
97
119
|
invert() {
|
|
98
|
-
const
|
|
120
|
+
const cascaded = captured;
|
|
99
121
|
const restoredSlot = slot;
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
122
|
+
const reinsertRoot = createInsertOp({ node, label, slot: restoredSlot });
|
|
123
|
+
if (cascaded.length === 0) return reinsertRoot;
|
|
102
124
|
return {
|
|
103
125
|
label,
|
|
104
126
|
apply(adapter) {
|
|
105
|
-
reinsert.apply(adapter);
|
|
106
127
|
const a = adapter;
|
|
107
|
-
for (const entry of
|
|
128
|
+
for (const entry of cascaded) {
|
|
108
129
|
if (a.getNode?.(entry.node.id)) continue;
|
|
109
|
-
|
|
130
|
+
if (entry.node.id === node.id) reinsertRoot.apply(adapter);
|
|
131
|
+
else a.insertNode(entry.node, entry.index);
|
|
110
132
|
}
|
|
111
133
|
},
|
|
112
|
-
invert: () => createDeleteOp({ node, label, slot: restoredSlot,
|
|
134
|
+
invert: () => createDeleteOp({ node, label, slot: restoredSlot, cascaded })
|
|
113
135
|
};
|
|
114
136
|
}
|
|
115
137
|
};
|
|
@@ -117,5 +139,5 @@ function createDeleteOp(args) {
|
|
|
117
139
|
registerOpFactory("delete", (args) => createDeleteOp(args));
|
|
118
140
|
|
|
119
141
|
export { captureSlot, createDeleteOp, createInsertOp, rebuildOp, registerOpFactory, registeredOpNames, resolveSlot, slotFromIndex };
|
|
120
|
-
//# sourceMappingURL=chunk-
|
|
121
|
-
//# sourceMappingURL=chunk-
|
|
142
|
+
//# sourceMappingURL=chunk-4RJP2N2L.js.map
|
|
143
|
+
//# sourceMappingURL=chunk-4RJP2N2L.js.map
|