@weasel-js/core 1.4.4 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +937 -2202
  2. package/README.md +118 -75
  3. package/dist/{autoPoseDescriptor-DF1SnnSx.d.ts → autoPoseDescriptor-CvjflWJK.d.ts} +29 -26
  4. package/dist/{chunk-2VXGHUVL.js → chunk-BDWAA634.js} +4 -22
  5. package/dist/chunk-BDWAA634.js.map +1 -0
  6. package/dist/{chunk-R3AWPTLZ.js → chunk-MG7OXCAI.js} +2760 -4491
  7. package/dist/chunk-MG7OXCAI.js.map +1 -0
  8. package/dist/{chunk-PRGBGMH3.js → chunk-MQI4PIX3.js} +3 -3
  9. package/dist/chunk-MQI4PIX3.js.map +1 -0
  10. package/dist/{chunk-WPM42WJP.js → chunk-UCPV7JXC.js} +201 -256
  11. package/dist/chunk-UCPV7JXC.js.map +1 -0
  12. package/dist/clipboard.d.ts +2 -3
  13. package/dist/clone.d.ts +3 -2
  14. package/dist/depSchema-nMqj_qTM.d.ts +3490 -0
  15. package/dist/{grid-0Pbn5B2C.d.ts → grid-BrIa38gG.d.ts} +7 -10
  16. package/dist/index.d.ts +1996 -1229
  17. package/dist/index.js +4 -5
  18. package/dist/insert.d.ts +4 -4
  19. package/dist/insert.js +1 -1
  20. package/dist/move.d.ts +5 -6
  21. package/dist/move.js +3 -6
  22. package/dist/move.js.map +1 -1
  23. package/dist/{options-DbYLImvq.d.ts → options-BDyCnrp8.d.ts} +3 -2
  24. package/dist/poseDescriptor-CGOgIgf8.d.ts +134 -0
  25. package/dist/renderer.d.ts +10 -4
  26. package/dist/renderer.js +4 -5
  27. package/dist/resize.d.ts +10 -12
  28. package/dist/resize.js +2 -2
  29. package/dist/routing.d.ts +1 -142
  30. package/dist/routing.js +1 -1
  31. package/dist/routing.js.map +1 -1
  32. package/dist/{types-ei3UMl9R.d.ts → types-DMyo7dnM.d.ts} +12 -41
  33. package/dist/{types-DEALFt5F.d.ts → types-DtjCJA5r.d.ts} +9 -3
  34. package/package.json +13 -10
  35. package/dist/DrawCommand-CD-ug3d9.d.ts +0 -332
  36. package/dist/builtins-BXFBXegF.d.ts +0 -840
  37. package/dist/chunk-2VXGHUVL.js.map +0 -1
  38. package/dist/chunk-BL65SHCX.js +0 -573
  39. package/dist/chunk-BL65SHCX.js.map +0 -1
  40. package/dist/chunk-PRGBGMH3.js.map +0 -1
  41. package/dist/chunk-R3AWPTLZ.js.map +0 -1
  42. package/dist/chunk-WPM42WJP.js.map +0 -1
  43. package/dist/geometry-6fCNhAux.d.ts +0 -114
  44. package/dist/path-JEV2c5If.d.ts +0 -48
  45. package/dist/registry-BY-wI9gm.d.ts +0 -4003
  46. package/dist/types-BHK2dkMu.d.ts +0 -172
  47. package/dist/types-bcc7jcUy.d.ts +0 -594
  48. package/dist/view-DSQgxBJB.d.ts +0 -63
@@ -1,594 +0,0 @@
1
- import * as _weasel_js_history from '@weasel-js/history';
2
- import { Op, SerializedHistory } from '@weasel-js/history';
3
- import { P as Path } from './path-JEV2c5If.js';
4
-
5
- /**
6
- * Axis-aligned rectangle pose with optional rotation. The canonical pose
7
- * shape used by `composeRectPose` and the `unionBounds` helper. Rotation is
8
- * in radians, pivoted on the unrotated AABB center; absent === 0. Kit-side
9
- * code that consumes rotation already reads `pose.rotation ?? 0`
10
- * (`wrapNodeOutput`, `rotate/handle.ts`, `pathInWorld.ts`), so
11
- * the slot exists on every default scene whether or not the consumer
12
- * populates it.
13
- */
14
- interface RectPose {
15
- x: number;
16
- y: number;
17
- width: number;
18
- height: number;
19
- /** Rotation in radians around the unrotated AABB center. Absent === 0. */
20
- rotation?: number;
21
- }
22
- /**
23
- * # SceneNode — the thing in the scene
24
- *
25
- * A `SceneNode` is the single canonical unit of a weasel scene. Everything
26
- * the user sees on the canvas — a shape, a group, an annotation, a tile —
27
- * is one of these. Containers and leaves are both nodes; the kit has no
28
- * other concept of "scene element."
29
- *
30
- * ## Three orthogonal slots
31
- *
32
- * Every node carries three independent slots, plus its tree position:
33
- *
34
- * - **`data: TData`** — app-defined payload. The kit never inspects it.
35
- * Color, label, kind, glyph, sample-rate, whatever the app's domain
36
- * calls for. Mutated via `Scene.update(id, { data })`.
37
- *
38
- * - **`pose: TPose`** — local transform, relative to the node's direct
39
- * parent (or world, for root nodes). Default `RectPose` is
40
- * `{ x, y, width, height }`, but `TPose` is fully generic so apps can
41
- * use rotated rects, paths, ellipses, etc. The kit composes world
42
- * poses via `composeWorldPose` when rendering / hit-testing / snapping.
43
- *
44
- * - **`layer: TLayer`** — a string tag associating the node with a
45
- * visual `RenderLayer` at draw time. Separate from `LayerRecord` (the
46
- * per-layer visible/locked metadata held by the `Scene`).
47
- *
48
- * Tree position lives on the node itself: every node has a `parent` (or
49
- * `null` for roots), and `ContainerNode` adds an ordered `children: NodeId[]`.
50
- *
51
- * ## Identity is by `NodeId`, not by reference
52
- *
53
- * Nodes are addressed by `NodeId` everywhere outside the scene tree:
54
- * selection is `NodeId[]`, ops reference `NodeId`s, adapter methods accept
55
- * `string` ids and look up the node on demand. The `Node` object itself is
56
- * a snapshot of current state — don't hold references to it across scene
57
- * updates; look up by id when you need the latest.
58
- *
59
- * Picking helpers (`pickBest`, `pickEvery`) likewise return ids, not nodes —
60
- * they're hit-testing primitives that stay ignorant of node payload shape.
61
- *
62
- * ## Vocabulary
63
- *
64
- * The kit-internal name is `Node`; the public re-export is `SceneNode`
65
- * (avoids collision with DOM `Node` at call sites). Adapter methods speak
66
- * the same vocabulary: `getNode`, `getNodes`, `insertNode`, `removeNode`,
67
- * `cloneNode`, `addNode`. Older code, demos, and comments may still say
68
- * "object" or "item" — those are historical aliases for the same concept.
69
- */
70
- /** Opaque branded id. Treat as opaque outside the kit. */
71
- type NodeId = string & {
72
- readonly __brand: 'NodeId';
73
- };
74
- /** Brand a string as a NodeId. */
75
- declare const asNodeId: (s: string) => NodeId;
76
- /**
77
- * One dependency, as a derivation reads it: the node itself and the pose it is
78
- * painted at.
79
- *
80
- * The node comes along because a connector legitimately reads more than a box
81
- * — an edge that thickens with its endpoint's weight, or routes only to nodes
82
- * on a given layer, is answering off `data` and `layer`. The scene already
83
- * invalidates dependents on `kit:setData` and `kit:setLayer` for exactly that,
84
- * so handing over only the pose made the invalidation pay for a read nothing
85
- * could perform.
86
- */
87
- interface DerivedDep<TPose> {
88
- node: Node<unknown, string, TPose>;
89
- /** Its override when it has one, else its own derived pose, else the pose
90
- * the document stores — `effectivePose`, the same answer the renderer uses. */
91
- pose: TPose;
92
- /** The path it derives, or `null` when it derives none. Read it to place
93
- * something *along* a dependency rather than beside it — a label on a
94
- * routed edge, a tick on a curve.
95
- *
96
- * Resolved on first read and memoized on the dependency, so a route costs
97
- * the same whether one node reads it or five, and a dependency nobody asks
98
- * about costs nothing. */
99
- readonly path: Path | null;
100
- }
101
- interface NodeBase<TData, TLayer extends string, TPose> {
102
- id: NodeId;
103
- layer: TLayer;
104
- pose: TPose;
105
- data: TData;
106
- parent: NodeId | null;
107
- /** Nodes whose poses this node's geometry is computed from. Fixed at add
108
- * time. Absent or empty means the node's geometry is authored, which is the
109
- * normal case.
110
- *
111
- * `'children'` means "my own children, in child order" — a container that
112
- * hugs its contents, which a fixed id list cannot express because
113
- * reparenting would have to maintain it. The two forms differ in lifetime
114
- * as well as in membership: deleting a node deletes everything that names
115
- * it in `dependsOn`, but a container outlives the children it derives
116
- * from — an emptied group is still a group. */
117
- dependsOn?: readonly NodeId[] | 'children';
118
- /** Whether the hit-test walk can return this node. Default `true`; `false`
119
- * makes it transparent to picking, so a click on it lands on whatever is
120
- * behind — normally its own container.
121
- *
122
- * For content a container owns rather than content the user selects on its
123
- * own: the label inside a box, a body's rows. Without it the innermost hit
124
- * wins, and dragging a labeled box moves the label out of the box. It does
125
- * not hide the node from anything else — it still paints, still clips, and
126
- * a consumer can still select it by id. */
127
- pickable?: boolean;
128
- /** Computes this node's path from its dependencies, in `dependsOn` order —
129
- * each one the node and the pose it is painted at.
130
- * A dependency that has been removed arrives as `undefined`.
131
- * Returning `null` means "nothing to draw right now". Re-evaluated when a
132
- * dependency's world pose changes, never authored. Absolute-pose `Scene`
133
- * makes that the dependency's own pose, and an ancestor's move reaches it as
134
- * a `setPose` of its own from the container cascade.
135
- * `node` is deliberately widened: naming `TData`/`TLayer` here puts them in
136
- * a contravariant position, making `Scene` invariant in both and breaking
137
- * assignment kit-wide. The cost is that a `derivePath` casts to read `node.data`. */
138
- derivePath?: (node: Node<unknown, string, TPose>, deps: readonly (DerivedDep<TPose> | undefined)[]) => Path | null;
139
- /** Computes this node's pose from its dependencies' poses, the same way
140
- * `derivePath` computes its path — same `dependsOn` list, same widened
141
- * `node`, same registry-keyed serialization.
142
- *
143
- * Where a derived path is resolved at paint time and reaches only the
144
- * painter, a derived pose is what the node *is* at: it feeds bounds,
145
- * hit-testing, selection chrome, snapping and layout, and every reader
146
- * gets it through `effectivePose`. A pose override still wins over it —
147
- * that is what lets a drag preview a node the document says is elsewhere.
148
- *
149
- * Returning `null` means "I have nothing to derive from right now", and
150
- * the node falls back to its authored `pose`. `setPose` on a derived node
151
- * still writes that authored pose; it is simply not what anything reads. */
152
- derivePose?: (node: Node<unknown, string, TPose>, deps: readonly (DerivedDep<TPose> | undefined)[]) => TPose | null;
153
- }
154
- /** A node with no children — a shape, a label, an image. */
155
- interface LeafNode<TData, TLayer extends string, TPose = RectPose> extends NodeBase<TData, TLayer, TPose> {
156
- kind: 'leaf';
157
- }
158
- /** A node with an ordered list of children. This is the real group: what
159
- * Cmd+G creates, what SVG `<g>` round-trips to. A container has its own pose,
160
- * which its children's poses are relative to, and may optionally clip them. */
161
- interface ContainerNode<TData, TLayer extends string, TPose = RectPose> extends NodeBase<TData, TLayer, TPose> {
162
- kind: 'container';
163
- children: NodeId[];
164
- /** Optional clip-path source. Re-evaluated each render. Returning `null`
165
- * means "no clip for this container right now"; an empty / zero-area path
166
- * means "clip everything out" (children render nowhere). When set, the
167
- * renderer rasterizes the returned path into the stencil buffer and
168
- * paints descendants only where it covers. */
169
- clipFromPose?: (pose: TPose) => Path | null;
170
- }
171
- /** A node in the scene tree: either a leaf or a container. Re-exported
172
- * publicly as `SceneNode`, to avoid colliding with the DOM's `Node`. */
173
- type Node<TData, TLayer extends string, TPose = RectPose> = LeafNode<TData, TLayer, TPose> | ContainerNode<TData, TLayer, TPose>;
174
- interface LayerRecordBase<TLayer extends string> {
175
- id: TLayer;
176
- visible: boolean;
177
- locked: boolean;
178
- }
179
- /** A layer declared when the scene was created. Fixed set, no display name —
180
- * these are the kit's own render bands, not something a user manages. */
181
- interface SystemLayerRecord<TLayer extends string> extends LayerRecordBase<TLayer> {
182
- kind: 'system';
183
- }
184
- /** A layer the user created and can rename, reorder or delete. */
185
- interface UserLayerRecord<TLayer extends string> extends LayerRecordBase<TLayer> {
186
- kind: 'user';
187
- name: string;
188
- }
189
- /** Per-layer metadata held by the scene: whether it is visible and locked,
190
- * and where it sits in the render stack. Distinct from a node's `layer` tag,
191
- * which merely names one of these. */
192
- type LayerRecord<TLayer extends string> = SystemLayerRecord<TLayer> | UserLayerRecord<TLayer>;
193
- /** What `Scene.add` needs to mint a node. Everything except the id is
194
- * required; the id is generated unless one is supplied. */
195
- interface AddNodeSpec<TData, TLayer extends string, TPose = RectPose> {
196
- kind: 'leaf' | 'container';
197
- layer: TLayer;
198
- pose: TPose;
199
- data: TData;
200
- parent?: NodeId | null;
201
- index?: number;
202
- /** Explicit id wins over the Scene's `generateId` and the kit default. */
203
- id?: NodeId;
204
- /** Mirrors `SceneNode.pickable`. Omit for the default, which is pickable. */
205
- pickable?: boolean;
206
- /** Only meaningful when `kind === 'container'`. Attach a clip-path function
207
- * to the node; ignored for leaves. Mirrors `ContainerNode.clipFromPose`. */
208
- clipFromPose?: (pose: TPose) => Path | null;
209
- /** Mirrors `SceneNode.dependsOn`. */
210
- dependsOn?: readonly NodeId[] | 'children';
211
- /** Mirrors `SceneNode.derivePath`. Taken as a live function; its registry key is
212
- * looked up from it, never passed in. */
213
- derivePath?: (node: Node<unknown, string, TPose>, deps: readonly (DerivedDep<TPose> | undefined)[]) => Path | null;
214
- /** Mirrors `SceneNode.derivePose`, on the same terms as `derivePath`. */
215
- derivePose?: (node: Node<unknown, string, TPose>, deps: readonly (DerivedDep<TPose> | undefined)[]) => TPose | null;
216
- }
217
- /** A custom scene mutation registered with `Scene.registerOp`: how to apply
218
- * it and how to undo it. The pair is what makes it participate in history. */
219
- interface RegisteredOp<P> {
220
- apply: (payload: P) => void;
221
- revert: (payload: P) => void;
222
- }
223
- /** One of the layers a scene is created with. */
224
- interface SystemLayerSpec<TLayer extends string> {
225
- id: TLayer;
226
- visible?: boolean;
227
- locked?: boolean;
228
- }
229
- /** Argument to `Scene.addLayer`. Always produces a `UserLayerRecord`
230
- * (`kind: 'user'`). */
231
- interface AddLayerSpec<TLayer extends string> {
232
- id: TLayer;
233
- name: string;
234
- /** Default `true`. */
235
- visible?: boolean;
236
- /** Default `false`. */
237
- locked?: boolean;
238
- /** Render-stack position. Default: top of stack (highest render index). */
239
- index?: number;
240
- }
241
- /** One layer as it appears in a snapshot. A snapshot written before layer
242
- * kind was serialized carries neither field, and loads as a system layer —
243
- * which is what every layer in such a snapshot was. */
244
- interface SerializedLayer<TLayer extends string> extends SystemLayerSpec<TLayer> {
245
- kind?: 'system' | 'user';
246
- /** Present on a user layer, absent on a system one. */
247
- name?: string;
248
- }
249
- /** JSON-serializable shape of a Scene's current state. Produced by
250
- * `scene.toJSON()`; consumed by `sceneFromJSON()`. Function fields
251
- * (e.g., `clipFromPose`) appear as string keys (`clipFromPoseKey`) and
252
- * are resolved through `SceneRegistry` at load time. */
253
- interface SerializedScene<TData, TLayer extends string, TPose> {
254
- version: 1;
255
- /** The whole layer stack, system and user alike — the name predates user
256
- * layers and is kept so existing snapshots still parse. */
257
- systemLayers: readonly SerializedLayer<TLayer>[];
258
- nodes: readonly SerializedNode<TData, TLayer, TPose>[];
259
- }
260
- /** JSON-serializable shape of a single node. Mirrors `AddNodeSpec` but
261
- * with function fields replaced by registry keys. */
262
- interface SerializedNode<TData, TLayer extends string, TPose> {
263
- id: string;
264
- kind: 'leaf' | 'container';
265
- layer: TLayer;
266
- pose: TPose;
267
- data: TData;
268
- /** Parent id; omitted for roots. */
269
- parent?: string;
270
- /** Registry key for the container's clip-path factory.
271
- * Containers only; omitted when the container has no clip. */
272
- clipFromPoseKey?: string;
273
- /** Ids this node's geometry derives from, or `'children'`. Omitted when it
274
- * derives from nothing. */
275
- dependsOn?: readonly string[] | 'children';
276
- /** Mirrors `SceneNode.pickable`. Omitted when the node is pickable. */
277
- pickable?: boolean;
278
- /** Registry key for the node's `derivePath` function. Omitted when it has none. */
279
- derivePathKey?: string;
280
- /** Registry key for the node's `derivePose` function. Omitted when it has none. */
281
- derivePoseKey?: string;
282
- }
283
- /** Per-scene registry mapping string keys to live function references.
284
- * Passed to `createScene({ ..., registry })` and `sceneFromJSON(json, { registry })`.
285
- * Each function-field type has its own keyed map. */
286
- interface SceneRegistry<TPose> {
287
- /** Maps registry keys to `clipFromPose` factory functions for container nodes. */
288
- clipFromPose?: Readonly<Record<string, (pose: TPose) => Path | null>>;
289
- /** Maps registry keys to `derivePath` functions for nodes with `dependsOn`. */
290
- derivePath?: Readonly<Record<string, (node: Node<unknown, string, TPose>, deps: readonly (DerivedDep<TPose> | undefined)[]) => Path | null>>;
291
- /** Maps registry keys to `derivePose` functions for nodes with `dependsOn`. */
292
- derivePose?: Readonly<Record<string, (node: Node<unknown, string, TPose>, deps: readonly (DerivedDep<TPose> | undefined)[]) => TPose | null>>;
293
- }
294
- /** Options for `useScene` — the layers the scene has, what it starts out
295
- * holding, and how its history behaves. */
296
- interface UseSceneOptions<TData, TLayer extends string, TPose = RectPose> {
297
- systemLayers: readonly SystemLayerSpec<TLayer>[];
298
- initial?: readonly AddNodeSpec<TData, TLayer, TPose>[];
299
- ops?: Readonly<Record<string, RegisteredOp<unknown>>>;
300
- historyLimit?: number;
301
- /** Window (ms) within which consecutive same-shaped mutations merge into
302
- * the previous undo entry (matching per-op coalesce keys — e.g. repeated
303
- * `setPose` on the same node, or repeated `applyBatch` calls whose ops
304
- * carry matching `coalesceKey` multisets). `0` (default) disables
305
- * coalescing: every mutation is a discrete undo entry. Undo of a
306
- * coalesced entry returns to the state before the first merged mutation;
307
- * redo restores the latest. `scene.batch` entries never coalesce. */
308
- coalesceWindowMs?: number;
309
- generateId?: () => NodeId;
310
- /** Per-scene registry for non-serializable function fields (clipFromPose, etc.).
311
- * Required only when serializing/deserializing scenes that use function fields. */
312
- registry?: SceneRegistry<TPose>;
313
- /** When supplied, `scene.applyOps(ops, label)` consults this on every call.
314
- * If it returns a non-null `Journal`, ops are routed to the journal's
315
- * `applyBatch` instead of recording a new parent-history entry. The journal
316
- * drives adapter mutation internally (via `op.apply(adapter)`), so the
317
- * scene state changes as normal; only the history tracking differs.
318
- *
319
- * The accessor is called on every `applyOps` invocation so the caller can
320
- * swap the active journal in and out by updating the closure's reference
321
- * (e.g., an app's mode machine holds `let activeJournal: Journal | null`
322
- * and the accessor reads that variable).
323
- *
324
- * When the accessor isn't known at scene-construction time (typical for
325
- * mode machines that depend on `scene.history`), pass nothing here and
326
- * wire it after construction via `scene.setActiveJournalAccessor(fn)`. */
327
- getActiveJournal?: () => _weasel_js_history.Journal | null;
328
- /** Re-render the host on every scene mutation. Default `true`. Set `false`
329
- * when the scene is read by a frame loop rather than by a render — a game
330
- * loop, a simulation — and nothing in the host's DOM derives from it.
331
- * Read by `useScene`; `createScene` ignores it. */
332
- subscribe?: boolean;
333
- }
334
- /**
335
- * One node's ephemeral presentation override — what a frame loop wants to say
336
- * about a node without saying it about the document.
337
- *
338
- * Not document content: never recorded in history, never in `toJSON`, and
339
- * writing one does not bump `Scene.getVersion()`. Hoist one entry per node and
340
- * mutate it in place on a frame loop; `PoseOverrides.commit()` is what makes a
341
- * mutation visible.
342
- */
343
- interface PoseOverride<TPose> {
344
- /** Replaces the node's document pose everywhere the render and hit-test
345
- * paths read one, including the clip a container derives from its pose, and
346
- * winning over a `derivePose`. Resolved by `effectivePose` — reading
347
- * `node.pose` directly is how those paths came to disagree about where a
348
- * node is. */
349
- pose?: TPose;
350
- /** Multiplied into the node's painted alpha, on top of any `alphaFor`. */
351
- alpha?: number;
352
- }
353
- /**
354
- * The scene's ephemeral per-node overrides — see {@link PoseOverride}.
355
- *
356
- * The intended shape of a frame is: `set` each node once, mutate the entries
357
- * in place per frame, `commit()` once. `commit` is not optional bookkeeping —
358
- * the painter memo keys on pose *reference*, so a mutation without a commit
359
- * paints the previous frame with no error.
360
- *
361
- * To promote a frame to document state (dropping a drag, baking an animation),
362
- * write it once through `Scene.setPose` and `clear` the override.
363
- */
364
- interface PoseOverrides<TPose> {
365
- /** Store `entry` for `id` **by reference**; the caller keeps mutating it. */
366
- set(id: NodeId, entry: PoseOverride<TPose>): void;
367
- get(id: NodeId): PoseOverride<TPose> | undefined;
368
- has(id: NodeId): boolean;
369
- /** The overridden ids, as a snapshot array. */
370
- ids(): readonly NodeId[];
371
- clear(id: NodeId): void;
372
- clearAll(): void;
373
- /** Publish this frame's in-place mutations: invalidate the painter memo for
374
- * every overridden node, then notify subscribers. */
375
- commit(): void;
376
- /** Notified after every write. The canvas uses this to repaint without a
377
- * scene version bump. */
378
- subscribe(fn: () => void): () => void;
379
- /** Monotonic write counter. A snapshot for observers that poll. */
380
- getGeneration(): number;
381
- }
382
- /**
383
- * The kit-owned scene tree: nodes, layers, and the undo history over both.
384
- *
385
- * A scene is logical, not visual — it says what exists and where, and nothing
386
- * about how it is painted. Every mutating method is undoable, and reads are
387
- * snapshots rather than live views. Nodes are addressed by `NodeId`; hold ids
388
- * across mutations, not node objects.
389
- *
390
- * Three type parameters keep it domain-agnostic: `TData` is the app's payload,
391
- * which the kit never inspects; `TPose` is the transform shape, `RectPose` by
392
- * default; `TLayer` is the union of layer names.
393
- */
394
- interface Scene<TData, TLayer extends string, TPose = RectPose> {
395
- readonly nodes: ReadonlyMap<NodeId, Node<TData, TLayer, TPose>>;
396
- readonly roots: readonly NodeId[];
397
- readonly layers: readonly LayerRecord<TLayer>[];
398
- get(id: NodeId): Node<TData, TLayer, TPose> | undefined;
399
- childrenOf(id: NodeId): readonly NodeId[];
400
- ancestorsOf(id: NodeId): readonly NodeId[];
401
- renderOrder(): Iterable<NodeId>;
402
- /** The same layer-major sequence as {@link Scene.renderOrder}, as the nodes
403
- * themselves. Prefer this wherever the ids are only going to be resolved
404
- * back to nodes: the traversal already holds them, and re-looking each one
405
- * up was ~40% of the area hit-test's per-node cost. Cached until a
406
- * structural edit, so repeat calls hand back the same array — a snapshot,
407
- * not a live view, and not yours to mutate. */
408
- renderOrderNodes(): readonly Node<TData, TLayer, TPose>[];
409
- add(spec: AddNodeSpec<TData, TLayer, TPose>): NodeId;
410
- /** Delete `id`, its **entire subtree**, and **everything that derives from**
411
- * any of those nodes — a node listing one of them in `dependsOn` goes too,
412
- * along with its own subtree, transitively. A dependent can live anywhere in
413
- * the tree, so this deletes nodes the caller never named and may unlink
414
- * several disjoint subtrees at once. Recorded as one undoable step; `undo()`
415
- * restores every one of them where it was, child order intact. */
416
- remove(id: NodeId): void;
417
- /** {@link remove} over several roots at once, as a **single** undoable step.
418
- * Ids resolve against the tree as it stands at the call, so an id that
419
- * another one would cascade away is absorbed rather than removed twice —
420
- * which is what makes it safe to pass a whole selection. Throws if any id is
421
- * not in the scene; an empty list does nothing and records no step. */
422
- removeMany(ids: readonly NodeId[]): void;
423
- /** Every node {@link removeMany} would take if given `ids`: each id, its
424
- * subtree, everything deriving from any of those, and those nodes' subtrees
425
- * in turn. The authoritative answer — the cascade relations live here, and
426
- * a caller reconstructing them from `dependsOn` and `children` drifts the
427
- * moment a third one is added. Ids not in the scene come back unchanged.
428
- *
429
- * A set, not a restore order: a dependent can be reached before the parent
430
- * it sits under, which is also in the closure. */
431
- removalClosure(ids: readonly NodeId[]): readonly NodeId[];
432
- update(id: NodeId, patch: {
433
- data: TData;
434
- }): void;
435
- setPose(id: NodeId, pose: TPose): void;
436
- /** Retag `id` to `layer`. On a **container this cascades**: every descendant
437
- * is moved to the same layer, recorded as a **single** undo step.
438
- *
439
- * Invariants:
440
- * - **Layer floor** — a child may not render below its parent, so retagging
441
- * to a layer *below* the node's parent throws. Retagging to the parent's
442
- * layer or any higher one is allowed; a node with no parent is
443
- * unconstrained.
444
- * - **No-op elision** — setting the layer a node already has does nothing
445
- * and pushes **no** history entry. */
446
- setLayer(id: NodeId, layer: TLayer): void;
447
- /** Point `id` at a different set of dependencies — retargeting an edge's end
448
- * onto another node, or switching a container between an id list and
449
- * `'children'`. Recorded as one undoable step.
450
- *
451
- * Order is significant: a derivation reads its dependencies positionally,
452
- * so `[b, a]` is not `[a, b]`. Ids need not be in the scene — a declared
453
- * dependency that appears later invalidates the node when it does, the same
454
- * as one declared at `add`. Passing `undefined` drops the declaration, after
455
- * which nothing cascades the node away.
456
- *
457
- * **No-op elision** — declaring what the node already declares does nothing
458
- * and pushes no history entry. */
459
- setDependsOn(id: NodeId, dependsOn: readonly NodeId[] | 'children' | undefined): void;
460
- /** Reparent `id` under `parent` (or to a root when `parent` is `null`) at
461
- * `index` within the new sibling list, appending when `index` is omitted.
462
- * Siblings are reindexed. Recorded as one undoable step.
463
- *
464
- * Rejected (throws) when:
465
- * - `parent` exists but is a **leaf**, not a container;
466
- * - the move would form a **cycle** — `parent` is `id` itself or one of
467
- * `id`'s own descendants;
468
- * - it would drop `id` **below its new parent's layer** (child may not
469
- * render below its parent).
470
- *
471
- * `move(id, null)` — detaching to a root — is always allowed regardless of
472
- * layer, since a root has no parent to render beneath. */
473
- move(id: NodeId, parent: NodeId | null, index?: number): void;
474
- /** Shift `id` to `index` within its **current** parent's child list. Unlike
475
- * {@link move}, the parent never changes — only sibling order. */
476
- reorder(id: NodeId, index: number): void;
477
- setLayerVisible(layer: TLayer, visible: boolean): void;
478
- setLayerLocked(layer: TLayer, locked: boolean): void;
479
- addLayer(spec: AddLayerSpec<TLayer>): void;
480
- /** Drop a user layer and every node tagged to it, as one undoable step.
481
- * Removal cascades, so this also deletes nodes **on other layers** that
482
- * derive from a node on this one. */
483
- removeLayer(layer: TLayer): void;
484
- renameLayer(layer: TLayer, name: string): void;
485
- moveLayer(layer: TLayer, index: number): void;
486
- registerOp<P>(kind: string, handler: RegisteredOp<P>): void;
487
- recordOp<P>(op: {
488
- kind: string;
489
- payload: P;
490
- }): void;
491
- /** Install (or clear) the active-journal accessor after scene construction.
492
- * Useful when the journal source (typically a mode machine) is built
493
- * with `scene.history` as a dependency — a chicken-and-egg situation
494
- * where the accessor can't be passed in via `UseSceneOptions`.
495
- *
496
- * Pass `null` to detach. Overrides any `getActiveJournal` set in
497
- * `UseSceneOptions`. */
498
- setActiveJournalAccessor(fn: (() => _weasel_js_history.Journal | null) | null): void;
499
- /** Apply a batch of ops with journal-aware routing.
500
- *
501
- * - **Without active journal** (or no `getActiveJournal` in options):
502
- * the ops themselves are recorded as one undo entry on the scene's own
503
- * history, rebound to `adapter` — undo replays each op's `invert()`
504
- * against that same adapter. Consecutive `applyBatch` entries can
505
- * coalesce via matching op `coalesceKey`s when the scene opts into
506
- * `coalesceWindowMs`.
507
- * - **With active journal**: routes ops to `journal.applyBatch(ops, label)`.
508
- * The scene's history recording is suppressed for the duration so the
509
- * journal's inner history — not the scene's undo stack — tracks the batch.
510
- * Mutations still happen on `adapter` / scene state.
511
- *
512
- * `adapter` must be the same adapter the ops expect (typically a
513
- * `SceneCanvasAdapter`). Pass `this` from `sceneToAdapter` or a compatible
514
- * adapter. */
515
- applyBatch(ops: Op[], label: string, adapter: unknown): void;
516
- /** The transient set of active ids — "operate on these N as a unit".
517
- * Shared by every view over this scene unless a view supplies its own
518
- * (see `CanvasView.selection`). Not document content: it never appears
519
- * in `toJSON`. It does ride on history entries, so undo and redo put
520
- * back the selection an edit was made under; changing it is never an
521
- * undo step of its own. */
522
- getSelection(): readonly NodeId[];
523
- setSelection(ids: readonly NodeId[]): void;
524
- /** Per-node pose / alpha overrides the render and hit-test paths read
525
- * through. Like {@link Scene.getSelection} this is not document content:
526
- * writes are never recorded, never serialized, and do not bump
527
- * {@link Scene.getVersion}. See {@link PoseOverrides}. */
528
- readonly overrides: PoseOverrides<TPose>;
529
- undo(): boolean;
530
- redo(): boolean;
531
- canUndo(): boolean;
532
- canRedo(): boolean;
533
- batch<T>(label: string, fn: () => T): T;
534
- /** Read-only snapshot of every history entry currently reachable from
535
- * the present state. Oldest applied first, then redoable entries in
536
- * the order they'd be re-applied. Each entry id is stable. */
537
- historyEntries(): readonly {
538
- id: string;
539
- label: string;
540
- }[];
541
- /** Index of the "current state". Equals the count of applied entries;
542
- * `0` means "nothing applied" (initial). */
543
- historyIndex(): number;
544
- /** Jump to the given history index by calling undo/redo repeatedly.
545
- * Clamps to [0, total]. Returns true if any movement occurred. */
546
- jumpToHistoryIndex(index: number): boolean;
547
- /** Snapshot the undo/redo history in a JSON-serializable form (the
548
- * engine's `SerializedHistory`). Entries containing any nameless op are
549
- * dropped (hand-rolled anonymous ops passed to `applyBatch`); kit and
550
- * consumer-registered ops always carry names. Payload JSON-safety
551
- * (e.g. typed arrays inside poses) is the caller's concern. Do not call
552
- * mid-`batch` — the open batch's ops are not yet recorded. */
553
- serializeHistory(): SerializedHistory;
554
- /** Replace the undo/redo history from a `serializeHistory()` snapshot.
555
- * Call on a scene whose node/layer state already matches the snapshot's
556
- * head state (i.e. right after `loadState` from the paired scene
557
- * snapshot); node state is NOT mutated. Ops re-registered via
558
- * `registerOp` before this call round-trip; unknown kinds become no-op
559
- * placeholders; external ops rebuild via the global op-factory registry
560
- * and replay against the `setHistoryAdapter` accessor. Restored entries
561
- * never coalesce with new ones. Notifies once. Do not call mid-`batch`:
562
- * the stacks are replaced underneath the open batch, whose eventual
563
- * flush would graft onto (and evict against) the restored stacks. */
564
- restoreHistory(snapshot: SerializedHistory): void;
565
- /** Install (or clear with `null`) the accessor for the adapter that
566
- * RESTORED external ops (recorded via `applyBatch`, rebuilt from a
567
- * `restoreHistory` snapshot) apply against on undo/redo. Resolved lazily
568
- * at each apply, so wiring order relative to `restoreHistory` doesn't
569
- * matter. Live `applyBatch` entries are unaffected (they bind their
570
- * call-site adapter). If unset when a restored op applies, the op is a
571
- * debug-warned no-op. */
572
- setHistoryAdapter(fn: (() => unknown) | null): void;
573
- /** Snapshot the current scene state to a JSON-serializable shape.
574
- * History (undo/redo stacks) is NOT captured. Function fields like
575
- * `ContainerNode.clipFromPose` are translated to string keys via the
576
- * scene's registry; throws if any function field has no matching key. */
577
- toJSON(): SerializedScene<TData, TLayer, TPose>;
578
- /** Replace this scene's entire node + layer state in place from a snapshot
579
- * produced by `toJSON()`. Unlike `sceneFromJSON`, the existing Scene
580
- * instance is preserved — holders such as `<SceneCanvas>` keep their
581
- * reference. History (undo/redo) is cleared, matching `sceneFromJSON`.
582
- * Bumps `getVersion()` and notifies subscribers exactly once.
583
- *
584
- * Throws on an unsupported version or unknown registry/layer ids; on a
585
- * malformed snapshot the scene is left empty or partially populated (callers should treat a
586
- * `loadState` throw as fatal and reload). Snapshots from `toJSON()` are
587
- * always well-formed. */
588
- loadState(json: SerializedScene<TData, TLayer, TPose>): void;
589
- subscribe(listener: () => void): () => void;
590
- /** Monotonically increasing version. Snapshot for `useSyncExternalStore`. */
591
- getVersion(): number;
592
- }
593
-
594
- export { type AddLayerSpec as A, type ContainerNode as C, type DerivedDep as D, type LayerRecord as L, type NodeId as N, type PoseOverrides as P, type RectPose as R, type Scene as S, type UseSceneOptions as U, type Node as a, type SerializedScene as b, type SceneRegistry as c, type RegisteredOp as d, type AddNodeSpec as e, type LeafNode as f, type PoseOverride as g, type SerializedNode as h, type SystemLayerRecord as i, type SystemLayerSpec as j, type UserLayerRecord as k, asNodeId as l };
@@ -1,63 +0,0 @@
1
- /** Pan offset (in pixels) plus per-axis zoom (pixels per content unit). */
2
- interface ViewTransform {
3
- panX: number;
4
- panY: number;
5
- zoom: {
6
- x: number;
7
- y: number;
8
- };
9
- }
10
- /** Project a world-space point to screen-space pixels through a `ViewTransform`. */
11
- declare function worldToScreen(worldX: number, worldY: number, view: ViewTransform): [number, number];
12
- /** Inverse of `worldToScreen` — recover the world-space point under a screen-space pixel. */
13
- declare function screenToWorld(screenX: number, screenY: number, view: ViewTransform): [number, number];
14
-
15
- /**
16
- * Viewport state. `(view.x, view.y)` is the **world point currently
17
- * rendered at the canvas top-left**; `view.scale.x` / `view.scale.y` is
18
- * pixels per world unit on each axis (default `{ x: 1, y: 1 }`). So:
19
- *
20
- * screenX = (worldX - view.x) * view.scale.x
21
- * screenY = (worldY - view.y) * view.scale.y
22
- * worldX = screenX / view.scale.x + view.x
23
- * worldY = screenY / view.scale.y + view.y
24
- *
25
- * `scale` is always a 2-vector. Input convenience types
26
- * {@link ZoomFactor} and {@link ZoomBound} let callers pass a scalar
27
- * when they want both axes treated the same.
28
- */
29
- interface View {
30
- x: number;
31
- y: number;
32
- scale: {
33
- x: number;
34
- y: number;
35
- };
36
- }
37
- /**
38
- * Input convenience for zoom primitives. A `number` is treated as a
39
- * uniform factor applied to both axes; a `{x, y}` vector applies
40
- * per-axis factors.
41
- */
42
- type ZoomFactor = number | {
43
- x: number;
44
- y: number;
45
- };
46
- /**
47
- * Input convenience for zoom-clamp ranges. A `number` is applied as the
48
- * same bound on both axes; a `{x, y}` vector applies per-axis bounds.
49
- */
50
- type ZoomBound = number | {
51
- x: number;
52
- y: number;
53
- };
54
- /**
55
- * Bridge `View` into the legacy `ViewTransform` shape so chrome can keep
56
- * calling `worldToScreen` / `screenToWorld`. `View` and `ViewTransform`
57
- * use opposite sign conventions for the translation half (`view.x` is
58
- * camera position; `panX` is canvas translation), so the adapter flips
59
- * the sign and multiplies by per-axis scale.
60
- */
61
- declare function viewToTransform(view: View): ViewTransform;
62
-
63
- export { type View as V, type ZoomBound as Z, type ViewTransform as a, type ZoomFactor as b, screenToWorld as s, viewToTransform as v, worldToScreen as w };