@weasel-js/core 1.4.3 → 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.
Files changed (37) hide show
  1. package/CHANGELOG.md +367 -0
  2. package/dist/{pointSnapToGrid-D1bRdq-j.d.ts → autoPoseDescriptor-DF1SnnSx.d.ts} +18 -3
  3. package/dist/{builtins-CtGORLCG.d.ts → builtins-BXFBXegF.d.ts} +200 -100
  4. package/dist/{chunk-ORZNKVXM.js → chunk-2VXGHUVL.js} +3 -3
  5. package/dist/{chunk-ORZNKVXM.js.map → chunk-2VXGHUVL.js.map} +1 -1
  6. package/dist/{chunk-LEURURX3.js → chunk-BL65SHCX.js} +17 -5
  7. package/dist/chunk-BL65SHCX.js.map +1 -0
  8. package/dist/{chunk-BLJNRMKB.js → chunk-PRGBGMH3.js} +3 -3
  9. package/dist/{chunk-BLJNRMKB.js.map → chunk-PRGBGMH3.js.map} +1 -1
  10. package/dist/{chunk-WYPSGIYS.js → chunk-R3AWPTLZ.js} +1773 -1209
  11. package/dist/chunk-R3AWPTLZ.js.map +1 -0
  12. package/dist/{chunk-SBFC6J3C.js → chunk-WPM42WJP.js} +3 -3
  13. package/dist/{chunk-SBFC6J3C.js.map → chunk-WPM42WJP.js.map} +1 -1
  14. package/dist/clipboard.d.ts +2 -2
  15. package/dist/clone.d.ts +2 -2
  16. package/dist/{geometry-CHR36Ub_.d.ts → geometry-6fCNhAux.d.ts} +1 -1
  17. package/dist/{grid-Bw8cSde-.d.ts → grid-0Pbn5B2C.d.ts} +1 -14
  18. package/dist/index.d.ts +231 -74
  19. package/dist/index.js +5 -5
  20. package/dist/insert.d.ts +3 -3
  21. package/dist/insert.js +1 -1
  22. package/dist/move.d.ts +4 -4
  23. package/dist/move.js +2 -2
  24. package/dist/{options-DYfUlZxk.d.ts → options-DbYLImvq.d.ts} +1 -1
  25. package/dist/{registry-DnoTWGED.d.ts → registry-BY-wI9gm.d.ts} +4 -4
  26. package/dist/renderer.d.ts +2 -2
  27. package/dist/renderer.js +5 -5
  28. package/dist/resize.d.ts +5 -4
  29. package/dist/resize.js +2 -2
  30. package/dist/routing.d.ts +5 -5
  31. package/dist/routing.js +1 -1
  32. package/dist/{types-CoVTbo_y.d.ts → types-DEALFt5F.d.ts} +1 -6
  33. package/dist/{types-H7o6MaPo.d.ts → types-bcc7jcUy.d.ts} +96 -14
  34. package/dist/{types-BJqsTlXl.d.ts → types-ei3UMl9R.d.ts} +1 -1
  35. package/package.json +9 -9
  36. package/dist/chunk-LEURURX3.js.map +0 -1
  37. package/dist/chunk-WYPSGIYS.js.map +0 -1
@@ -218,6 +218,23 @@ declare class GLImageCache {
218
218
  private readonly gl;
219
219
  private readonly minification;
220
220
  private readonly map;
221
+ /**
222
+ * MAG_FILTER each texture currently carries, so a redundant write can be
223
+ * skipped.
224
+ *
225
+ * The batch sets this per flush — the same bitmap can be drawn at both
226
+ * filters in one frame, and the value has to be right at the draw rather than
227
+ * at upload. Filtering is state on the *texture object*, though, not on the
228
+ * unit, so re-asserting a value it already has is a write to a live texture
229
+ * for no reason. On a large sheet that is not free: a consumer's wall samples
230
+ * a 5652px-square atlas, 122MB resident, and measured `sampling: 'nearest'`
231
+ * costing up to 8x `'linear'` there — where the linear pass re-asserts the
232
+ * upload default and the nearest pass changes state on every draw.
233
+ *
234
+ * Whether that is the cause is unproven (see `docs/TODO.md`), but the write
235
+ * was redundant either way.
236
+ */
237
+ private readonly magFilters;
221
238
  /** `minification` selects the MIN_FILTER strategy for uploaded textures.
222
239
  * `'linear'` (default) is the screen path's existing behavior. `'mipmap'`
223
240
  * generates mipmaps and filters LINEAR_MIPMAP_LINEAR — required for
@@ -227,13 +244,21 @@ declare class GLImageCache {
227
244
  constructor(gl: WebGL2RenderingContext, minification?: ImageMinification);
228
245
  upload(key: object, source: TexSource, repetition?: PatternRepetition): WebGLTexture;
229
246
  bind(key: object, unit: number): void;
247
+ /** Set `key`'s MAG_FILTER, skipping the call when it already holds that
248
+ * value. Its texture must be bound to the active unit — callers pair this
249
+ * with `bind`. */
250
+ setMagFilter(key: object, filter: number): void;
230
251
  }
231
252
 
232
253
  /**
233
- * CPU gradient-ramp builder + GL 1×256 RGBA texture cache.
254
+ * CPU gradient-ramp builder + the GL texture every baked ramp lives in.
234
255
  *
235
- * Each unique stop list is uploaded once. Key = JSON.stringify(stops).
236
- * The cache is GL-context-bound; discard and recreate on context loss.
256
+ * Each unique stop list is baked once into a 256-texel strip and written to its
257
+ * own **row** of one RGBA texture, keyed by `JSON.stringify(stops)`. One
258
+ * texture rather than one per ramp is what lets a gradient take a batch texture
259
+ * slot the way a bitmap or a font atlas does: every gradient in a frame samples
260
+ * the same unit, so a run does not break per gradient. The atlas is
261
+ * GL-context-bound; discard and recreate on context loss.
237
262
  *
238
263
  * Output convention §2: texels are stored as straight RGBA. The fragment
239
264
  * shader applies premultiplication before writing outColor.
@@ -242,25 +267,66 @@ declare class GLImageCache {
242
267
  /** Bake gradient stops into a 256-entry RGBA lookup strip, which the shader
243
268
  * samples instead of evaluating stops per fragment. */
244
269
  declare function buildGradientRamp(stops: GradStop[]): Uint8ClampedArray;
245
- declare class GradientRampCache {
270
+ declare class GradientRampAtlas {
246
271
  private readonly gl;
247
- private readonly map;
272
+ /** Row per stop list, in least-recently-used order: a hit re-inserts, so the
273
+ * first entry is the row a full atlas recycles. */
274
+ private readonly rowByKey;
275
+ private texture;
276
+ private rows;
277
+ /** Mirror of the texture, so growth can respecify it without re-baking every
278
+ * ramp it already holds. */
279
+ private pixels;
248
280
  private totalQueries;
249
281
  private cacheHits;
250
282
  constructor(gl: WebGL2RenderingContext);
251
- upload(stops: GradStop[]): string;
252
- bind(key: string, unit: number): void;
283
+ /** Rows the atlas currently holds — its texture height. */
284
+ get height(): number;
285
+ /**
286
+ * Bake `stops` into a row and return it, reusing the row an identical stop
287
+ * list already holds.
288
+ *
289
+ * **A returned row outlives the frame only while the atlas has room.** Past
290
+ * `RAMP_ATLAS_MAX_ROWS` an upload recycles the least recently used row, which
291
+ * rewrites texels a row handed out earlier still names. Nothing that defers
292
+ * its draw past this call may hold a row across another `upload` without
293
+ * arranging to be flushed first.
294
+ */
295
+ upload(stops: GradStop[]): number;
296
+ /**
297
+ * Whether uploading `stops` would move where existing rows sit — by growing
298
+ * the atlas, which changes every row's `v`, or by recycling one, which
299
+ * rewrites its texels.
300
+ *
301
+ * Anything holding a row past this call asks first and gets itself out of
302
+ * the way, because neither can be undone once it has happened.
303
+ */
304
+ wouldReshape(stops: GradStop[]): boolean;
305
+ /**
306
+ * The `v` a row is sampled at — its center.
307
+ *
308
+ * The center is load-bearing: the atlas filters LINEAR, so a `v` anywhere
309
+ * else blends the ramp beside it into this one. At the center the neighbor's
310
+ * weight is exactly zero.
311
+ */
312
+ rowV(row: number): number;
313
+ bind(unit: number): void;
253
314
  hitRate(): number;
254
315
  resetStats(): void;
255
316
  /**
256
- * Delete every uploaded GL ramp texture and clear the map. Called by
257
- * `WeaselRenderer.dispose()`. Only the GL textures are owned resources —
258
- * `buildGradientRamp`'s CPU-side `Uint8ClampedArray` is transient per
259
- * `upload()` call and already gone by the time a ramp is cached. The
260
- * cache is unusable but refillable afterward (stats are left as-is; call
317
+ * Delete the atlas texture and forget every row in it. Called by
318
+ * `WeaselRenderer.dispose()`. The CPU mirror goes with it, so the atlas is
319
+ * unusable but refillable afterward (stats are left as-is; call
261
320
  * `resetStats()` separately if desired).
262
321
  */
263
322
  free(): void;
323
+ /** The row the next ramp is written to: a free one, a taller atlas, or the
324
+ * least recently used row of a full one. */
325
+ private claimRow;
326
+ /** Grow to `rows`, keeping every row at the index it already had — a row
327
+ * index is a stable name, and callers hold them. */
328
+ private resize;
329
+ private writeRow;
264
330
  }
265
331
 
266
332
  /** Row-major 4×5 color matrix identity. */
@@ -302,33 +368,72 @@ declare class GroupState {
302
368
  }
303
369
 
304
370
  /**
305
- * Growable vertex staging for consecutive solid-fill geometry.
371
+ * Growable vertex staging for a run of solid-fill geometry, image quads and
372
+ * glyphs.
306
373
  *
307
374
  * Geometry only: `draw.ts` owns when a run starts, what breaks it, and the
308
- * uniforms the flush draws under. Colors ride the vertices (the batch program
309
- * is `pathFillVColor` with `u_color` left at white) because shapes in a run
310
- * differ in color and a merged draw has one set of uniforms — and so does the
311
- * model transform, applied here rather than uploaded, so that shapes under
312
- * different transforms still share a draw.
375
+ * uniforms the flush draws under.
376
+ *
377
+ * **One batch for all three, because a page interleaves them.** A grid of
378
+ * thumbnails is a ground rect under an atlas quad under a caption, per cell,
379
+ * and while each kind staged separately every one had to drain the others
380
+ * before it could stage — so a shape that batches perfectly in any one half
381
+ * alone paid a flush per command. Everything a vertex needs to say which it is
382
+ * fits in the same vertex: solids carry the UV of a 1x1 white texel and are
383
+ * their own color, quads carry their atlas UV and a white color, glyphs carry
384
+ * a font atlas UV, their text color, and a paint mode saying the texel is a
385
+ * distance field rather than a color. Gradients join as a fourth off the ramp
386
+ * atlas — a linear one without a mode of its own, since (ramp position, row) is
387
+ * what the plain mode already samples, and a radial or conic one with a mode
388
+ * that says its UV is a gradient-space coordinate to take a `length` or an
389
+ * `atan` of. See `shaders/batchFill.ts`.
390
+ *
391
+ * Colors ride the vertices because shapes in a run differ in color and a merged
392
+ * draw has one set of uniforms — and so does the model transform, applied here
393
+ * rather than uploaded, so shapes under different transforms still share a
394
+ * draw. The texture rides them too, as a slot index into the units the flush
395
+ * binds: slot 0 is the white texel every solid samples, and `draw.ts` hands out
396
+ * the rest per bitmap. A run holds as many bitmaps as there are slots, so an
397
+ * atlas is what makes a wall of one sheet coalesce and a handful of loose
398
+ * bitmaps no longer breaks a run per command.
313
399
  */
314
400
 
315
- declare class SolidBatch {
401
+ /**
402
+ * A gradient vertex's `a_uv`, each channel affine in the coordinates the
403
+ * geometry arrives in: `u = ux*x + uy*y + u0`, and the same for `v`.
404
+ *
405
+ * One shape for all three gradients. A linear one's `v` row is the constant
406
+ * atlas row and its `u` row is the ramp position; a radial or conic one's two
407
+ * rows are the gradient-space coordinate, and the row rides `a_post` instead.
408
+ */
409
+ interface GradientUV {
410
+ ux: number;
411
+ uy: number;
412
+ u0: number;
413
+ vx: number;
414
+ vy: number;
415
+ v0: number;
416
+ }
417
+ declare class DrawBatch {
316
418
  private readonly gl;
317
419
  private readonly aPos;
318
420
  private readonly aColor;
319
- /** Cycled per flush, and created on first use so a renderer that flushes
320
- * rarely allocates as few slots as it flushes. */
321
- private readonly ring;
322
- private next;
323
- /** The same, for flushes past a slot's capacity; these sets grow to fit. */
421
+ private readonly aUv;
422
+ private readonly aPost;
423
+ private readonly aSlot;
424
+ /** One ring per tier, cycled per flush. Slots are created on first use, so a
425
+ * renderer that flushes rarely allocates as few as it flushes. */
426
+ private readonly rings;
427
+ private readonly nextInRing;
428
+ /** The same, for flushes past the largest tier; these sets grow to fit. */
324
429
  private readonly largeRing;
325
430
  private nextLarge;
326
431
  private verts;
327
432
  private idx;
328
433
  private nVerts;
329
434
  private nIdx;
330
- /** Whether the staged run is rects alone, so its indices are the canonical
331
- * quad pattern and a slot already holding that pattern needs no upload. */
435
+ /** Whether the staged run is quads alone, so its indices are the canonical
436
+ * pattern and a slot already holding that pattern needs no upload. */
332
437
  private pureRects;
333
438
  constructor(gl: WebGL2RenderingContext, prog: ShaderProgram);
334
439
  get length(): number;
@@ -340,6 +445,62 @@ declare class SolidBatch {
340
445
  * cover it and the batch draws at `u_model` identity.
341
446
  */
342
447
  pushRect(x: number, y: number, w: number, h: number, m: Mat3, r: number, g: number, b: number, a: number): void;
448
+ /**
449
+ * Append one image quad: the destination rect `(x, y, w, h)` mapped through
450
+ * `m`, sampling `(u0, v0)`-`(u1, v1)`, every corner carrying `post` as the
451
+ * after-the-color-matrix alpha factor.
452
+ *
453
+ * Corners wind top-left, top-right, bottom-right, bottom-left with UVs
454
+ * following — the same winding `pushRect` uses, which is what lets a run of
455
+ * mixed rects and quads keep the canonical index pattern. The flips a command
456
+ * asks for are already in the `u` / `v` the caller passes.
457
+ *
458
+ * `slot` is the texture unit the corners sample, which `draw.ts` assigns per
459
+ * bitmap within the run.
460
+ */
461
+ pushQuad(x: number, y: number, w: number, h: number, m: Mat3, u0: number, v0: number, u1: number, v1: number, post: number, slot: number): void;
462
+ /**
463
+ * Append one glyph quad: the box `(x0, y0)`-`(x1, y1)` mapped through `m`,
464
+ * sampling `(u0, v0)`-`(u1, v1)` of the font atlas at `slot`, painted in
465
+ * `rgba`.
466
+ *
467
+ * `mode` says which channels of that atlas carry the distance field, and
468
+ * rides the vertices packed into the slot — so a run off a baked MSDF atlas
469
+ * and one off the runtime canvas bake still share a draw. The synthetic-bold
470
+ * threshold does not: it is `u_synthBold`, and `draw.ts` breaks the run when
471
+ * it changes.
472
+ *
473
+ * **A synthetic oblique is sheared here rather than in the shader.** The old
474
+ * text program carried the baseline per vertex and skewed against
475
+ * `u_synthItalic`; the batch places its own corners, so the shear is one
476
+ * multiply while they are being placed, and `tanItalic` is 0 for an upright
477
+ * face. It has to happen before `m`, which is where the shader had it too.
478
+ *
479
+ * Corners wind top-left, top-right, bottom-right, bottom-left — `pushQuad`'s
480
+ * winding, which is what lets a run of mixed rects, image quads and glyphs
481
+ * keep the canonical index pattern.
482
+ */
483
+ pushGlyph(x0: number, y0: number, x1: number, y1: number, baselineY: number, tanItalic: number, m: Mat3, u0: number, v0: number, u1: number, v1: number, r: number, g: number, b: number, a: number, slot: number, mode: number): void;
484
+ /**
485
+ * Append one rect filled by a gradient: the corners of `(x, y, w, h)` mapped
486
+ * through `m`, sampling the ramp atlas at `slot`.
487
+ *
488
+ * `uv` gives each channel of `a_uv` as an affine function of the coordinates
489
+ * the corners arrive in, which is what lets a gradient ride the vertices at
490
+ * all: a linear one's ramp position is affine in position outright, and a
491
+ * radial or conic one's gradient-space *coordinate* is, even though the ramp
492
+ * position it yields is not. Either way the rasterizer's interpolation across
493
+ * a triangle is exact. `mode` says which of the two the fragment shader is
494
+ * looking at, and `post` carries the atlas row for the modes that read it —
495
+ * see `shaders/batchFill.ts`.
496
+ *
497
+ * Values outside 0..1 are the sampler's business; the atlas clamps to the
498
+ * edge texel, which is what the gradient shader's own `clamp` did.
499
+ */
500
+ pushGradientRect(x: number, y: number, w: number, h: number, m: Mat3, uv: GradientUV, post: number, slot: number, mode: number, r: number, g: number, b: number, a: number): void;
501
+ /** `pushMesh` for a mesh filled by a gradient — see `pushGradientRect` for
502
+ * what `uv`, `post` and `mode` carry. */
503
+ pushGradientMesh(mesh: Mesh, m: Mat3, uv: GradientUV, post: number, slot: number, mode: number, r: number, g: number, b: number, a: number): void;
343
504
  /**
344
505
  * Append a tessellated mesh through `m`, all vertices carrying `rgba`. The
345
506
  * mesh's own indices are rebased onto the staged vertices, which is why the
@@ -351,6 +512,9 @@ declare class SolidBatch {
351
512
  uploadAndBind(): number;
352
513
  reset(): void;
353
514
  dispose(): void;
515
+ private writeVertex;
516
+ /** Two triangles over the four corners just written. */
517
+ private pushQuadIndices;
354
518
  /** Grow the CPU arrays so `vertices` / `indices` more fit. */
355
519
  private reserve;
356
520
  private nextRingSlot;
@@ -359,67 +523,6 @@ declare class SolidBatch {
359
523
  private deleteSet;
360
524
  }
361
525
 
362
- /**
363
- * Growable vertex staging for consecutive image quads.
364
- *
365
- * Geometry only: `draw.ts` owns when a run starts, what breaks it, and the
366
- * uniforms the flush draws under. The counterpart to `SolidBatch`, and the same
367
- * two tricks — the model transform is applied here rather than uploaded, so
368
- * quads under different group transforms still share a draw, and opacity rides
369
- * the vertices, so quads under different opacities do too. What it cannot
370
- * absorb is the texture: a batch samples one, which is why an atlas is what
371
- * makes a large run coalesce at all.
372
- *
373
- * Every run is quads, so a slot's index buffer is written once at creation and
374
- * never again: the pattern for N quads is a prefix of the pattern for any
375
- * larger N, so the pattern for a slot's capacity serves every flush it takes.
376
- * `SolidBatch` carries meshes, whose indices are rebased per flush, and has to
377
- * re-upload; this does not.
378
- */
379
-
380
- declare class ImageBatch {
381
- private readonly gl;
382
- private readonly aPos;
383
- private readonly aUv;
384
- private readonly aOpacity;
385
- /** One ring per tier, cycled per flush. Slots are created on first use, so a
386
- * renderer that flushes rarely allocates as few as it flushes. */
387
- private readonly rings;
388
- private readonly nextInRing;
389
- /** For flushes past the largest tier; these sets grow to fit. */
390
- private readonly largeRing;
391
- private nextLarge;
392
- private verts;
393
- private nQuads;
394
- constructor(gl: WebGL2RenderingContext, prog: ShaderProgram);
395
- /** Indices staged — what a caller passes to `drawElements`. */
396
- get length(): number;
397
- get quads(): number;
398
- /** Whether staging one more quad would put the run past the per-flush cap. */
399
- wouldOverflow(): boolean;
400
- /**
401
- * Append one image quad: the destination rect `(x, y, w, h)` mapped through
402
- * `m`, sampling `(u0, v0)`-`(u1, v1)`, every corner carrying `opacity`.
403
- *
404
- * An affine maps a rect to a parallelogram, so two triangles still cover it
405
- * and the batch draws at `u_model` identity. Corners wind top-left,
406
- * top-right, bottom-right, bottom-left, with UVs following — the flips a
407
- * command asks for are already in the `u`/`v` the caller passes.
408
- */
409
- pushQuad(x: number, y: number, w: number, h: number, m: Mat3, u0: number, v0: number, u1: number, v1: number, opacity: number): void;
410
- /** Upload the staged geometry into the next set of buffers and bind its VAO.
411
- * Returns the index count for the caller's `drawElements`. */
412
- uploadAndBind(): number;
413
- reset(): void;
414
- dispose(): void;
415
- /** Grow the CPU arrays so one more quad fits. */
416
- private reserve;
417
- private nextRingSlot;
418
- private nextLargeSlot;
419
- private createSet;
420
- private deleteSet;
421
- }
422
-
423
526
  /** How to construct a `WeaselRenderer`: the GL context or canvas to draw
424
527
  * into, the output size, and the quality knobs that separate screen
425
528
  * rendering from print. */
@@ -486,21 +589,21 @@ declare class WeaselRenderer {
486
589
  private readonly gl;
487
590
  private pathFill;
488
591
  private pathFillVColor;
489
- private textSdf;
490
- private textSdfR8;
491
592
  private imageFill;
492
- private imageFillVOpacity;
593
+ private batchFill;
493
594
  private gradFill;
494
595
  private patternFill;
495
596
  private meshCache;
496
597
  private textureCache;
497
598
  private imageCache;
498
- private gradRampCache;
599
+ private gradRamps;
499
600
  private programRegistry;
500
601
  private quadVbo;
501
602
  private quadIbo;
502
- private solidBatch;
503
- private imageBatch;
603
+ private drawBatch;
604
+ /** 1x1 white, so a batch flush sampling no image still samples something —
605
+ * see `shaders/batchFill.ts`. */
606
+ private whiteTexture;
504
607
  private readonly groupState;
505
608
  private widthCss;
506
609
  private heightCss;
@@ -560,7 +663,7 @@ declare class WeaselRenderer {
560
663
  * Scope: built-in shader programs, any consumer-registered programs, the
561
664
  * shared quad/rect geometry, any in-flight transient meshes, and the
562
665
  * enumerable Map-keyed caches (`GLTextureCache` atlas/image textures,
563
- * `GradientRampCache` ramp textures) ARE freed.
666
+ * `GradientRampAtlas`'s ramp texture) ARE freed.
564
667
  *
565
668
  * NOT freed: `GLImageCache` (bitmap/pattern textures) and `GLMeshCache`'s
566
669
  * persistent per-Path mesh cache are keyed by `WeakMap`, not enumerable,
@@ -596,18 +699,15 @@ declare class WeaselRenderer {
596
699
  /** @internal */ _gl(): WebGL2RenderingContext;
597
700
  /** @internal */ _pathFill(): ShaderProgram;
598
701
  /** @internal */ _pathFillVColor(): ShaderProgram;
599
- /** @internal */ _solidBatch(): SolidBatch;
600
- /** @internal */ _imageBatch(): ImageBatch;
601
- /** @internal */ _textSdf(): ShaderProgram;
602
- /** @internal */ _textSdfR8(): ShaderProgram;
702
+ /** @internal */ _drawBatch(): DrawBatch;
603
703
  /** @internal */ _imageFill(): ShaderProgram;
604
- /** @internal */ _imageFillVOpacity(): ShaderProgram;
704
+ /** @internal */ _batchFill(): ShaderProgram;
605
705
  /** @internal */ _gradFill(): ShaderProgram;
606
706
  /** @internal */ _patternFill(): ShaderProgram;
607
707
  /** @internal */ _meshCache(): GLMeshCache;
608
708
  /** @internal */ _textureCache(): GLTextureCache;
609
709
  /** @internal */ _imageCache(): GLImageCache;
610
- /** @internal */ _gradRampCache(): GradientRampCache;
710
+ /** @internal */ _gradRamps(): GradientRampAtlas;
611
711
  /** @internal */ _groupState(): GroupState;
612
712
  /** @internal */ _widthCss(): number;
613
713
  /** @internal */ _heightCss(): number;
@@ -1,4 +1,4 @@
1
- import { RECT_ORIGIN_PROJECTION } from './chunk-SBFC6J3C.js';
1
+ import { RECT_ORIGIN_PROJECTION } from './chunk-WPM42WJP.js';
2
2
  import { registerOpFactory, captureSlot, slotFromIndex, resolveSlot } from './chunk-4RJP2N2L.js';
3
3
 
4
4
  // src/core/ops/transform.ts
@@ -142,5 +142,5 @@ function snap(strategy, opts = {}) {
142
142
  }
143
143
 
144
144
  export { createReparentOp, createTransformOp, deleteScratch, getScratch, scratchKey, setScratch, snap };
145
- //# sourceMappingURL=chunk-ORZNKVXM.js.map
146
- //# sourceMappingURL=chunk-ORZNKVXM.js.map
145
+ //# sourceMappingURL=chunk-2VXGHUVL.js.map
146
+ //# sourceMappingURL=chunk-2VXGHUVL.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/ops/transform.ts","../src/core/ops/reparent.ts","../src/interactions/scratchKey.ts","../src/interactions/gestures/shared/snap.ts"],"names":[],"mappings":"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","file":"chunk-ORZNKVXM.js","sourcesContent":["import type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface TransformAdapter<TPose> {\n setPose(id: string, pose: TPose): void;\n}\n\n/** @internal */\ninterface TransformArgs<TPose> {\n id: string;\n from: TPose;\n to: TPose;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Op: set an object's pose, inverting back to `from`. `coalesceKey` defaults to\n * `transform:${id}` so successive pose writes to the same node (drag-move,\n * resize, repeated nudges) merge into one undo entry within the history's\n * coalesce window. Pass an explicit key (or a unique one) to opt out. */\nexport function createTransformOp<TPose>(args: TransformArgs<TPose>): Op {\n const { id, from, to, label, coalesceKey = `transform:${id}` } = args;\n return {\n name: 'transform',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n // Always call setPose so consumers that inspect op behavior (tests,\n // overlays) see a consistent \"this op writes (id, to)\" signal. When\n // from and to are structurally identical, additionally report a\n // no-op to history so the entry can be skipped from the undo stack.\n // setPose(id, to) is idempotent in this branch — adapters write the\n // same pose they already had.\n (adapter as TransformAdapter<TPose>).setPose(id, to);\n if (poseShallowEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createTransformOp<TPose>({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nregisterOpFactory<TransformArgs<unknown>>('transform', (args) => createTransformOp(args));\n\nfunction poseShallowEqual<TPose>(a: TPose, b: TPose): boolean {\n if (a === b) return true;\n if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if ((a as Record<string, unknown>)[k] !== (b as Record<string, unknown>)[k]) return false;\n }\n return true;\n}\n","import type { Op } from './types';\nimport { registerOpFactory } from './registry';\nimport { captureSlot, resolveSlot, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface ReparentAdapter extends OrderedReader {\n setParent(id: string, parentId: string | null): void;\n /** Sibling-order write, paired with `getChildren`. Optional: an adapter\n * without both silently ignores slots and appends. */\n setChildOrder?(parentId: string | null, ids: string[]): void;\n}\n\n/** @internal */\ninterface ReparentArgs {\n id: string;\n fromParentId: string | null;\n toParentId: string | null;\n /** Sibling ordinal under the old parent. Sugar for `fromSlot: { index }`,\n * and only a seed: `apply` re-observes the node's full slot through the\n * adapter and that observation supersedes this. */\n fromIndex?: number;\n /** Sibling ordinal under the new parent. Sugar for `toSlot: { index }`.\n * Omit to append. */\n toIndex?: number;\n /** Slot under the old parent, anchor included. `invert()` forwards it as\n * the destination slot; without it undo re-parents with no position and\n * the node lands last, quietly changing paint order. */\n fromSlot?: Slot;\n /** Slot under the new parent, anchor included. Omit to append. */\n toSlot?: Slot;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Move `id` to `slot` among `parentId`'s children. No-op when the adapter\n * has no ordering seam, when `id` isn't there, or when it's already in\n * place. */\nfunction placeAt(\n a: ReparentAdapter,\n parentId: string | null,\n id: string,\n slot: Slot,\n): void {\n if (!a.getChildren || !a.setChildOrder) return;\n const current = a.getChildren(parentId);\n const from = current.indexOf(id);\n if (from < 0) return;\n const next = current.slice();\n next.splice(from, 1);\n const resolved = resolveSlot(next, slot);\n const to = resolved === undefined ? next.length : Math.max(0, Math.min(resolved, next.length));\n if (from === to) return;\n next.splice(to, 0, id);\n a.setChildOrder(parentId, next);\n}\n\n/** Present when the caller named a destination at all; `undefined` leaves\n * placement to the adapter. */\nfunction optionalSlot(slot: Slot | undefined, index: number | undefined): Slot | undefined {\n if (slot) return slot;\n return index === undefined ? undefined : slotFromIndex(index);\n}\n\n/**\n * Op: change `id`'s parent and sibling slot, inverting back to the prior\n * parent and slot.\n *\n * `coalesceKey` defaults to `reparent:${id}` so successive reparents of the\n * same id batch-merge cleanly.\n */\nexport function createReparentOp(args: ReparentArgs): Op {\n const {\n id, fromParentId, toParentId,\n label = 'Reparent', coalesceKey = `reparent:${id}`,\n } = args;\n let fromSlot = optionalSlot(args.fromSlot, args.fromIndex);\n const toSlot = optionalSlot(args.toSlot, args.toIndex);\n const argsForSerial: ReparentArgs = {\n id, fromParentId, toParentId, fromSlot, toSlot, label, coalesceKey,\n };\n return {\n name: 'reparent',\n args: argsForSerial,\n label,\n coalesceKey,\n apply(adapter) {\n const a = adapter as ReparentAdapter;\n const observed = captureSlot(a, fromParentId, id);\n if (observed) {\n fromSlot = observed;\n argsForSerial.fromSlot = observed;\n }\n a.setParent(id, toParentId);\n if (toSlot) placeAt(a, toParentId, id, toSlot);\n },\n invert() {\n return createReparentOp({\n id,\n fromParentId: toParentId,\n toParentId: fromParentId,\n fromSlot: toSlot,\n toSlot: fromSlot,\n label,\n coalesceKey,\n });\n },\n };\n}\n\nregisterOpFactory<ReparentArgs>('reparent', (args) => createReparentOp(args));\n","/**\n * Typed keys for the gesture/behavior scratch store.\n *\n * Gestures expose `ctx.scratch: Record<string, unknown>` as a per-gesture\n * mutable bag that behaviors use to stash state across `onDown` / `onMove`\n * / `onEnd` calls. The store is `unknown`-valued because behaviors are\n * tool-agnostic — a single behavior may run under different tools with\n * different scratch shapes. Without a typing convention every read site\n * has to `(ctx.scratch as <some shape>)[KEY]`, and a writer changing the\n * shape can silently break readers with no compile-time signal.\n *\n * `ScratchKey<T>` is a phantom-typed string that carries its payload type\n * via a branded interface. `getScratch` and `setScratch` use the brand to\n * narrow / constrain at the typing layer; at runtime the key is just its\n * underlying string and the store remains a plain `Record`.\n *\n * @example\n * ```ts\n * // Module-level (or behavior-level) constant, shared by writer + reader:\n * const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');\n *\n * // Writer (a tool's onMove):\n * setScratch(ctx.scratch, LASSO_VERTICES, vertices);\n *\n * // Reader (a behavior's onEnd):\n * const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];\n * // ^? readonly { x: number; y: number }[] | undefined\n * ```\n */\n\n/** Phantom-typed key for a scratch slot. The `__scratchKeyPayload` phantom\n * field carries the value type without occupying any runtime memory. */\nexport interface ScratchKey<T> {\n readonly name: string;\n /** Phantom — never read at runtime. Only present so TypeScript can\n * recover `T` at use sites. */\n readonly __scratchKeyPayload?: (_: T) => T;\n}\n\n/** Compatible scratch store shape. The kit's gesture context's `scratch`\n * field already satisfies this; consumers don't need to construct one. */\nexport type ScratchStore = Record<string, unknown>;\n\n/**\n * Make a typed scratch key. Use module-level constants so the same key\n * is shared by writer and reader.\n *\n * Namespace the name by behavior or feature to avoid collisions\n * (`'behavior.field'` is a good convention). Two keys with the same\n * `name` refer to the same slot at runtime — the type parameter is a\n * compile-time contract, not a uniqueness guarantee.\n */\nexport function scratchKey<T>(name: string): ScratchKey<T> {\n return { name };\n}\n\n/** Read a typed slot from the scratch store. Returns `undefined` if\n * nothing has been written under the key. */\nexport function getScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): T | undefined {\n return store[key.name] as T | undefined;\n}\n\n/** Write a value to a typed slot. Overwrites whatever was there. */\nexport function setScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n value: T,\n): void {\n store[key.name] = value;\n}\n\n/** Remove a slot from the scratch store. Returns `true` if it was set,\n * `false` if it wasn't. */\nexport function deleteScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): boolean {\n if (Object.prototype.hasOwnProperty.call(store, key.name)) {\n delete store[key.name];\n return true;\n }\n return false;\n}\n","import type { MoveBehavior, ModifierState, SnapStrategy } from '../types';\nimport {\n RECT_ORIGIN_PROJECTION,\n type OriginProjection,\n} from './strategies/grid';\n\ntype ModKey = keyof ModifierState;\n\n/**\n * Generic snap behavior factory: wraps a `SnapStrategy` and returns a\n * `MoveBehavior` whose `onMove` returns a uniform `GroupTransform`.\n *\n * Implementation:\n * 1. Compute the primary's proposed pose by applying the gesture's\n * `transform` (translate) to `origin.get(primaryId)`.\n * 2. Call `strategy.snap(proposed)`. If null, no-op.\n * 3. Derive the snapped delta via the `OriginProjection`:\n * dx = origin(snapped).x - origin(originPose).x\n * ...so the gesture applies the same delta to every dragged id.\n *\n * The pose-shape-aware delta extraction lives here — gestures stay pose-shape\n * agnostic. Default projection is `{x, y}`-bearing (rect / path / polygon).\n * Pass `origin` for exotic poses.\n */\nexport function snap<TPose>(\n strategy: SnapStrategy<TPose>,\n opts: { bypassKey?: ModKey; origin?: OriginProjection<TPose> } = {},\n): MoveBehavior<TPose> {\n const { bypassKey } = opts;\n const proj: OriginProjection<TPose> = opts.origin\n ?? (RECT_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n return {\n onMove(ctx, transform) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n // The new contract: behaviors see a GroupTransform, not a pose. But\n // strategies still operate on poses. Reconstruct the proposed pose\n // by applying the (translate) transform to the primary's origin.\n if (transform.kind !== 'translate') return;\n const primaryId = ctx.draggedIds[0];\n if (primaryId === undefined) return;\n const originPose = ctx.origin.get(primaryId);\n if (originPose === undefined) return;\n const proposed = proj.translate(originPose, transform.dx, transform.dy);\n const snapped = strategy.snap(proposed, ctx);\n if (snapped === null) return;\n const o0 = proj.getOrigin(originPose);\n const o1 = proj.getOrigin(snapped);\n return {\n transform: { kind: 'translate', dx: o1.x - o0.x, dy: o1.y - o0.y },\n };\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/core/ops/transform.ts","../src/core/ops/reparent.ts","../src/interactions/scratchKey.ts","../src/interactions/gestures/shared/snap.ts"],"names":[],"mappings":"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","file":"chunk-2VXGHUVL.js","sourcesContent":["import type { Op } from './types';\nimport { registerOpFactory } from './registry';\n\ninterface TransformAdapter<TPose> {\n setPose(id: string, pose: TPose): void;\n}\n\n/** @internal */\ninterface TransformArgs<TPose> {\n id: string;\n from: TPose;\n to: TPose;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Op: set an object's pose, inverting back to `from`. `coalesceKey` defaults to\n * `transform:${id}` so successive pose writes to the same node (drag-move,\n * resize, repeated nudges) merge into one undo entry within the history's\n * coalesce window. Pass an explicit key (or a unique one) to opt out. */\nexport function createTransformOp<TPose>(args: TransformArgs<TPose>): Op {\n const { id, from, to, label, coalesceKey = `transform:${id}` } = args;\n return {\n name: 'transform',\n args: { id, from, to, label, coalesceKey },\n label,\n coalesceKey,\n apply(adapter) {\n // Always call setPose so consumers that inspect op behavior (tests,\n // overlays) see a consistent \"this op writes (id, to)\" signal. When\n // from and to are structurally identical, additionally report a\n // no-op to history so the entry can be skipped from the undo stack.\n // setPose(id, to) is idempotent in this branch — adapters write the\n // same pose they already had.\n (adapter as TransformAdapter<TPose>).setPose(id, to);\n if (poseShallowEqual(from, to)) return false;\n return undefined;\n },\n invert() {\n return createTransformOp<TPose>({ id, from: to, to: from, label, coalesceKey });\n },\n };\n}\n\nregisterOpFactory<TransformArgs<unknown>>('transform', (args) => createTransformOp(args));\n\nfunction poseShallowEqual<TPose>(a: TPose, b: TPose): boolean {\n if (a === b) return true;\n if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if ((a as Record<string, unknown>)[k] !== (b as Record<string, unknown>)[k]) return false;\n }\n return true;\n}\n","import type { Op } from './types';\nimport { registerOpFactory } from './registry';\nimport { captureSlot, resolveSlot, slotFromIndex, type OrderedReader, type Slot } from './slot';\n\ninterface ReparentAdapter extends OrderedReader {\n setParent(id: string, parentId: string | null): void;\n /** Sibling-order write, paired with `getChildren`. Optional: an adapter\n * without both silently ignores slots and appends. */\n setChildOrder?(parentId: string | null, ids: string[]): void;\n}\n\n/** @internal */\ninterface ReparentArgs {\n id: string;\n fromParentId: string | null;\n toParentId: string | null;\n /** Sibling ordinal under the old parent. Sugar for `fromSlot: { index }`,\n * and only a seed: `apply` re-observes the node's full slot through the\n * adapter and that observation supersedes this. */\n fromIndex?: number;\n /** Sibling ordinal under the new parent. Sugar for `toSlot: { index }`.\n * Omit to append. */\n toIndex?: number;\n /** Slot under the old parent, anchor included. `invert()` forwards it as\n * the destination slot; without it undo re-parents with no position and\n * the node lands last, quietly changing paint order. */\n fromSlot?: Slot;\n /** Slot under the new parent, anchor included. Omit to append. */\n toSlot?: Slot;\n label?: string;\n coalesceKey?: string;\n}\n\n/** Move `id` to `slot` among `parentId`'s children. No-op when the adapter\n * has no ordering seam, when `id` isn't there, or when it's already in\n * place. */\nfunction placeAt(\n a: ReparentAdapter,\n parentId: string | null,\n id: string,\n slot: Slot,\n): void {\n if (!a.getChildren || !a.setChildOrder) return;\n const current = a.getChildren(parentId);\n const from = current.indexOf(id);\n if (from < 0) return;\n const next = current.slice();\n next.splice(from, 1);\n const resolved = resolveSlot(next, slot);\n const to = resolved === undefined ? next.length : Math.max(0, Math.min(resolved, next.length));\n if (from === to) return;\n next.splice(to, 0, id);\n a.setChildOrder(parentId, next);\n}\n\n/** Present when the caller named a destination at all; `undefined` leaves\n * placement to the adapter. */\nfunction optionalSlot(slot: Slot | undefined, index: number | undefined): Slot | undefined {\n if (slot) return slot;\n return index === undefined ? undefined : slotFromIndex(index);\n}\n\n/**\n * Op: change `id`'s parent and sibling slot, inverting back to the prior\n * parent and slot.\n *\n * `coalesceKey` defaults to `reparent:${id}` so successive reparents of the\n * same id batch-merge cleanly.\n */\nexport function createReparentOp(args: ReparentArgs): Op {\n const {\n id, fromParentId, toParentId,\n label = 'Reparent', coalesceKey = `reparent:${id}`,\n } = args;\n let fromSlot = optionalSlot(args.fromSlot, args.fromIndex);\n const toSlot = optionalSlot(args.toSlot, args.toIndex);\n const argsForSerial: ReparentArgs = {\n id, fromParentId, toParentId, fromSlot, toSlot, label, coalesceKey,\n };\n return {\n name: 'reparent',\n args: argsForSerial,\n label,\n coalesceKey,\n apply(adapter) {\n const a = adapter as ReparentAdapter;\n const observed = captureSlot(a, fromParentId, id);\n if (observed) {\n fromSlot = observed;\n argsForSerial.fromSlot = observed;\n }\n a.setParent(id, toParentId);\n if (toSlot) placeAt(a, toParentId, id, toSlot);\n },\n invert() {\n return createReparentOp({\n id,\n fromParentId: toParentId,\n toParentId: fromParentId,\n fromSlot: toSlot,\n toSlot: fromSlot,\n label,\n coalesceKey,\n });\n },\n };\n}\n\nregisterOpFactory<ReparentArgs>('reparent', (args) => createReparentOp(args));\n","/**\n * Typed keys for the gesture/behavior scratch store.\n *\n * Gestures expose `ctx.scratch: Record<string, unknown>` as a per-gesture\n * mutable bag that behaviors use to stash state across `onDown` / `onMove`\n * / `onEnd` calls. The store is `unknown`-valued because behaviors are\n * tool-agnostic — a single behavior may run under different tools with\n * different scratch shapes. Without a typing convention every read site\n * has to `(ctx.scratch as <some shape>)[KEY]`, and a writer changing the\n * shape can silently break readers with no compile-time signal.\n *\n * `ScratchKey<T>` is a phantom-typed string that carries its payload type\n * via a branded interface. `getScratch` and `setScratch` use the brand to\n * narrow / constrain at the typing layer; at runtime the key is just its\n * underlying string and the store remains a plain `Record`.\n *\n * @example\n * ```ts\n * // Module-level (or behavior-level) constant, shared by writer + reader:\n * const LASSO_VERTICES = scratchKey<readonly { x: number; y: number }[]>('lasso.vertices');\n *\n * // Writer (a tool's onMove):\n * setScratch(ctx.scratch, LASSO_VERTICES, vertices);\n *\n * // Reader (a behavior's onEnd):\n * const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];\n * // ^? readonly { x: number; y: number }[] | undefined\n * ```\n */\n\n/** Phantom-typed key for a scratch slot. The `__scratchKeyPayload` phantom\n * field carries the value type without occupying any runtime memory. */\nexport interface ScratchKey<T> {\n readonly name: string;\n /** Phantom — never read at runtime. Only present so TypeScript can\n * recover `T` at use sites. */\n readonly __scratchKeyPayload?: (_: T) => T;\n}\n\n/** Compatible scratch store shape. The kit's gesture context's `scratch`\n * field already satisfies this; consumers don't need to construct one. */\nexport type ScratchStore = Record<string, unknown>;\n\n/**\n * Make a typed scratch key. Use module-level constants so the same key\n * is shared by writer and reader.\n *\n * Namespace the name by behavior or feature to avoid collisions\n * (`'behavior.field'` is a good convention). Two keys with the same\n * `name` refer to the same slot at runtime — the type parameter is a\n * compile-time contract, not a uniqueness guarantee.\n */\nexport function scratchKey<T>(name: string): ScratchKey<T> {\n return { name };\n}\n\n/** Read a typed slot from the scratch store. Returns `undefined` if\n * nothing has been written under the key. */\nexport function getScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): T | undefined {\n return store[key.name] as T | undefined;\n}\n\n/** Write a value to a typed slot. Overwrites whatever was there. */\nexport function setScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n value: T,\n): void {\n store[key.name] = value;\n}\n\n/** Remove a slot from the scratch store. Returns `true` if it was set,\n * `false` if it wasn't. */\nexport function deleteScratch<T>(\n store: ScratchStore,\n key: ScratchKey<T>,\n): boolean {\n if (Object.prototype.hasOwnProperty.call(store, key.name)) {\n delete store[key.name];\n return true;\n }\n return false;\n}\n","import type { MoveBehavior, ModifierState, SnapStrategy } from '../types';\nimport {\n RECT_ORIGIN_PROJECTION,\n type OriginProjection,\n} from './strategies/grid';\n\ntype ModKey = keyof ModifierState;\n\n/**\n * Generic snap behavior factory: wraps a `SnapStrategy` and returns a\n * `MoveBehavior` whose `onMove` returns a uniform `GroupTransform`.\n *\n * Implementation:\n * 1. Compute the primary's proposed pose by applying the gesture's\n * `transform` (translate) to `origin.get(primaryId)`.\n * 2. Call `strategy.snap(proposed)`. If null, no-op.\n * 3. Derive the snapped delta via the `OriginProjection`:\n * dx = origin(snapped).x - origin(originPose).x\n * ...so the gesture applies the same delta to every dragged id.\n *\n * The pose-shape-aware delta extraction lives here — gestures stay pose-shape\n * agnostic. Default projection is `{x, y}`-bearing (rect / path / polygon).\n * Pass `origin` for exotic poses.\n */\nexport function snap<TPose>(\n strategy: SnapStrategy<TPose>,\n opts: { bypassKey?: ModKey; origin?: OriginProjection<TPose> } = {},\n): MoveBehavior<TPose> {\n const { bypassKey } = opts;\n const proj: OriginProjection<TPose> = opts.origin\n ?? (RECT_ORIGIN_PROJECTION as unknown as OriginProjection<TPose>);\n return {\n onMove(ctx, transform) {\n if (bypassKey && ctx.modifiers[bypassKey]) return;\n // The new contract: behaviors see a GroupTransform, not a pose. But\n // strategies still operate on poses. Reconstruct the proposed pose\n // by applying the (translate) transform to the primary's origin.\n if (transform.kind !== 'translate') return;\n const primaryId = ctx.draggedIds[0];\n if (primaryId === undefined) return;\n const originPose = ctx.origin.get(primaryId);\n if (originPose === undefined) return;\n const proposed = proj.translate(originPose, transform.dx, transform.dy);\n const snapped = strategy.snap(proposed, ctx);\n if (snapped === null) return;\n const o0 = proj.getOrigin(originPose);\n const o1 = proj.getOrigin(snapped);\n return {\n transform: { kind: 'translate', dx: o1.x - o0.x, dy: o1.y - o0.y },\n };\n },\n };\n}\n"]}
@@ -9,12 +9,24 @@ function DepRegistryProvider({ children }) {
9
9
  const sourcesRef = useRef(/* @__PURE__ */ new Map());
10
10
  const registry = useMemo(() => ({
11
11
  register: (name, source) => {
12
- sourcesRef.current.set(name, source);
12
+ const key = name;
13
+ const entry = source;
14
+ const stack = sourcesRef.current.get(key);
15
+ if (stack) stack.push(entry);
16
+ else sourcesRef.current.set(key, [entry]);
17
+ let released = false;
13
18
  return () => {
14
- sourcesRef.current.delete(name);
19
+ if (released) return;
20
+ released = true;
21
+ const cur = sourcesRef.current.get(key);
22
+ if (!cur) return;
23
+ const i = cur.lastIndexOf(entry);
24
+ if (i === -1) return;
25
+ cur.splice(i, 1);
26
+ if (cur.length === 0) sourcesRef.current.delete(key);
15
27
  };
16
28
  },
17
- get: (name) => sourcesRef.current.get(name)?.()
29
+ get: (name) => sourcesRef.current.get(name)?.at(-1)?.()
18
30
  }), []);
19
31
  return /* @__PURE__ */ jsx(DepRegistryContext.Provider, { value: registry, children });
20
32
  }
@@ -557,5 +569,5 @@ function reportRouteConflicts(scopes, warn = (m) => console.warn(m)) {
557
569
  }
558
570
 
559
571
  export { ActionDisabledReason, ActionsProvider, ActionsScope, DepRegistryProvider, PREDICATE_TARGET, actionBindings, buildDepsFromRequires, buildRouteRegistry, evaluateEnabled, findConflicts, reportRouteConflicts, routeGestureForSpecKind, routesForSpec, useAction, useActionsRegistry, useDepRegistry, useDepSource, useOptionalDepRegistry };
560
- //# sourceMappingURL=chunk-LEURURX3.js.map
561
- //# sourceMappingURL=chunk-LEURURX3.js.map
572
+ //# sourceMappingURL=chunk-BL65SHCX.js.map
573
+ //# sourceMappingURL=chunk-BL65SHCX.js.map