@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.
Files changed (43) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/dist/{DrawCommand-Ch8W63oQ.d.ts → DrawCommand-CD-ug3d9.d.ts} +45 -1
  3. package/dist/{stroke-Dq8nL4A2.d.ts → builtins-CtGORLCG.d.ts} +51 -2
  4. package/dist/{chunk-2KKYDDDD.js → chunk-4RJP2N2L.js} +43 -21
  5. package/dist/chunk-4RJP2N2L.js.map +1 -0
  6. package/dist/{chunk-MXFSHJOM.js → chunk-D4AAWR64.js} +4 -4
  7. package/dist/{chunk-MXFSHJOM.js.map → chunk-D4AAWR64.js.map} +1 -1
  8. package/dist/{chunk-ZB7UYJVG.js → chunk-DOBSZOPR.js} +3 -3
  9. package/dist/{chunk-ZB7UYJVG.js.map → chunk-DOBSZOPR.js.map} +1 -1
  10. package/dist/{chunk-TORKRNFY.js → chunk-LEURURX3.js} +68 -5
  11. package/dist/chunk-LEURURX3.js.map +1 -0
  12. package/dist/{chunk-Y5KS66G7.js → chunk-ORZNKVXM.js} +3 -3
  13. package/dist/{chunk-Y5KS66G7.js.map → chunk-ORZNKVXM.js.map} +1 -1
  14. package/dist/{chunk-67KE7SDP.js → chunk-T3UQ3F6R.js} +4 -4
  15. package/dist/{chunk-67KE7SDP.js.map → chunk-T3UQ3F6R.js.map} +1 -1
  16. package/dist/{chunk-KIVJXUYE.js → chunk-WYPSGIYS.js} +1058 -503
  17. package/dist/chunk-WYPSGIYS.js.map +1 -0
  18. package/dist/clipboard.d.ts +2 -2
  19. package/dist/clipboard.js +3 -3
  20. package/dist/clone.d.ts +2 -2
  21. package/dist/clone.js +3 -3
  22. package/dist/{geometry-Dtt_k6Dq.d.ts → geometry-CHR36Ub_.d.ts} +3 -3
  23. package/dist/{grid-nnXU4VjN.d.ts → grid-Bw8cSde-.d.ts} +1 -1
  24. package/dist/index.d.ts +117 -232
  25. package/dist/index.js +7 -7
  26. package/dist/insert.d.ts +3 -3
  27. package/dist/move.d.ts +4 -4
  28. package/dist/move.js +2 -2
  29. package/dist/{options-CdFl510T.d.ts → options-DYfUlZxk.d.ts} +1 -1
  30. package/dist/{pointSnapToGrid-Dmthv94u.d.ts → pointSnapToGrid-D1bRdq-j.d.ts} +2 -2
  31. package/dist/{registry-BwY_DxJM.d.ts → registry-DnoTWGED.d.ts} +98 -8
  32. package/dist/renderer.d.ts +32 -4
  33. package/dist/renderer.js +7 -7
  34. package/dist/resize.d.ts +4 -4
  35. package/dist/routing.d.ts +6 -6
  36. package/dist/routing.js +1 -1
  37. package/dist/{types-miXHGrZM.d.ts → types-BJqsTlXl.d.ts} +1 -1
  38. package/dist/{types-XBcDEp3Y.d.ts → types-CoVTbo_y.d.ts} +9 -0
  39. package/dist/{types-DIQAisSG.d.ts → types-H7o6MaPo.d.ts} +20 -1
  40. package/package.json +9 -9
  41. package/dist/chunk-2KKYDDDD.js.map +0 -1
  42. package/dist/chunk-KIVJXUYE.js.map +0 -1
  43. package/dist/chunk-TORKRNFY.js.map +0 -1
@@ -1,10 +1,10 @@
1
- import { ActionDisabledReason, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsProvider, useDepRegistry, reportRouteConflicts, buildDepsFromRequires, useDepSource, actionBindings } from './chunk-TORKRNFY.js';
2
- import { scratchKey, createTransformOp, snap, getScratch, createReparentOp, setScratch } from './chunk-Y5KS66G7.js';
1
+ import { ActionDisabledReason, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsScope, ActionsProvider, useDepRegistry, reportRouteConflicts, buildDepsFromRequires, useDepSource, actionBindings } from './chunk-LEURURX3.js';
2
+ import { scratchKey, createReparentOp, createTransformOp, snap, getScratch, setScratch } from './chunk-ORZNKVXM.js';
3
3
  import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-BLJNRMKB.js';
4
- import { AUTO_POSE_DESCRIPTOR, resolveUnit, polylineFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, polygonFromPoints, PATH_M, PATH_L, PATH_Z, RECT_POSE_DESCRIPTOR, pathPoseDescriptor, withinPxRadius, boundsOfPath, PATH_Q, PATH_C, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, PathBuilder, pointInPath, strokeHitTest, transformPath, translatePath, pxExtent, remapRotatedLeaf, withinPxBox, flattenCubicWithArcLen, flattenQuadraticWithArcLen, PATH_CMD_LENGTHS, pointToGridCell } from './chunk-SBFC6J3C.js';
5
- import { dispatchApplyBatch, dlog, dwarn, usePointerContext, PointerContextProvider, applyOpsTo, extractWeaselClipboardFromSvg, parseWeaselClipboardText, materializePaste, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, sniffWeaselClipboardText } from './chunk-67KE7SDP.js';
6
- import { createSetSelectionOp } from './chunk-ZB7UYJVG.js';
7
- import { registerOpFactory, createDeleteOp, createInsertOp, rebuildOp } from './chunk-2KKYDDDD.js';
4
+ import { AUTO_POSE_DESCRIPTOR, remapRotatedLeaf, PATH_Z, PATH_Q, PATH_C, PATH_L, PATH_M, resolveUnit, polylineFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, polygonFromPoints, RECT_POSE_DESCRIPTOR, PathBuilder, pathPoseDescriptor, withinPxRadius, boundsOfPath, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, pointInPath, strokeHitTest, transformPath, translatePath, pxExtent, withinPxBox, flattenCubicWithArcLen, flattenQuadraticWithArcLen, PATH_CMD_LENGTHS, pointToGridCell } from './chunk-SBFC6J3C.js';
5
+ import { dispatchApplyBatch, dlog, dwarn, usePointerContext, PointerContextProvider, extractWeaselClipboardFromSvg, parseWeaselClipboardText, materializePaste, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, sniffWeaselClipboardText, applyOpsTo } from './chunk-T3UQ3F6R.js';
6
+ import { createSetSelectionOp } from './chunk-DOBSZOPR.js';
7
+ import { registerOpFactory, createInsertOp, createDeleteOp, rebuildOp } from './chunk-4RJP2N2L.js';
8
8
  import { getTexture, chunks, dots, crosshatch, hatch } from './chunk-BJN5ZRIJ.js';
9
9
  import { createContext, createElement, forwardRef, useMemo, useRef, useCallback, useState, useEffect, useImperativeHandle, useSyncExternalStore, useLayoutEffect, useContext, useId, useReducer } from 'react';
10
10
  import { subscribeGlyphReady, DEFAULT_BAKE_BUDGET, TEXT_VERT_SRC, TEXT_FRAG_SRC, TEXT_SDF_UNIFORMS, TEXT_SDF_ATTRIBUTES, TEXT_FRAG_R8_SRC, markAllFontsNotUploaded, resetBakeBudget, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey } from '@weasel-js/font';
@@ -14,10 +14,10 @@ export { DEFAULT_TEXT_STYLE, MARKDOWN_RUN_GRAMMAR, SCRIPT_METRICS, cachedLayoutR
14
14
  import earcut from 'earcut';
15
15
  import { dashForStrokeStyle, strokeDashStyleOf, alignedStrokeRect } from '@weasel-js/paint';
16
16
  export { STROKE_DASH_RATIOS, alignedStrokeRect, dashForStrokeStyle, strokeDashStyleOf } from '@weasel-js/paint';
17
- import { clampRectWithin, applyToPoint, rotateAboutPoint, boxToBox, pointInPolygon, segmentsCross, forEachSegment, pathCommandCoordCount, multiply as multiply$1, invert as invert$1, pointSegmentDist2 } from '@weasel-js/geom';
17
+ import { boxToBox, clampRectWithin, applyToPoint, rotateAboutPoint, pointInPolygon, segmentsCross, forEachSegment, pathCommandCoordCount, multiply as multiply$1, invert as invert$1, pointSegmentDist2 } from '@weasel-js/geom';
18
18
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
19
19
  import polygonClipping from 'polygon-clipping';
20
- import { matchSpec, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES, parseTargetSpec, mimeMatchesGlob } from '@weasel-js/gestures';
20
+ import { mimeMatchesGlob, matchSpec, RESERVED_ID_PREFIXES, RESERVED_ID_NAMES, parseTargetSpec } from '@weasel-js/gestures';
21
21
  import { NORMAL, IMPLICIT_TAGS } from '@weasel-js/modes';
22
22
  import { GLYPHS, cursorWorldSize, cursorPaintOps, chromeLineWidthScale, cursorPaintMatrix, resolveCursorTier, cursorFor, resolveCursor } from '@weasel-js/cursor';
23
23
  export { bakeCursor, cursorFor, resolveCursor, resolveCursorTier } from '@weasel-js/cursor';
@@ -25,7 +25,7 @@ import { createHistory } from '@weasel-js/history';
25
25
  export * from '@weasel-js/history';
26
26
 
27
27
  // src/version.ts
28
- var VERSION = "1.4.2" ;
28
+ var VERSION = "1.4.3" ;
29
29
 
30
30
  // src/features/grid/roundToCell.ts
31
31
  function roundToCell(value, cellSize) {
@@ -1431,6 +1431,28 @@ var GroupState = class {
1431
1431
  this.alphaStack.push(nextAlpha);
1432
1432
  this.colorMatrixStack.push(nextCM);
1433
1433
  }
1434
+ /**
1435
+ * Push a frame whose children paint into a surface of their own.
1436
+ *
1437
+ * The transform still accumulates — the children draw where they would have
1438
+ * drawn. Alpha and colour do not: they describe how the finished surface
1439
+ * joins the frame, and applying them on the way in as well would fade the
1440
+ * pixels an effect is about to read, then fade them again on the way out.
1441
+ *
1442
+ * Returns the pair the caller must apply at composite time — what `push`
1443
+ * would have left on the stack — so the composition rules live here and not
1444
+ * in the dispatcher.
1445
+ */
1446
+ pushIsolated(frame) {
1447
+ this.push(frame);
1448
+ const composited = { alpha: this.alpha, colorMatrix: this.colorMatrix };
1449
+ this.pop();
1450
+ const current = this.transform;
1451
+ this.transformStack.push(frame.transform ? mat3.multiply(current, frame.transform) : current);
1452
+ this.alphaStack.push(1);
1453
+ this.colorMatrixStack.push(IDENTITY_COLOR_MATRIX);
1454
+ return composited;
1455
+ }
1434
1456
  /** Drop every pushed frame, leaving the root. A frame that throws part-way
1435
1457
  * down the tree never pops, and the next frame would draw under leftovers. */
1436
1458
  reset() {
@@ -3495,6 +3517,63 @@ var ImageBatch = class {
3495
3517
  }
3496
3518
  };
3497
3519
 
3520
+ // src/renderer/shaders/registerProgram.ts
3521
+ var registry = /* @__PURE__ */ new Map();
3522
+ function getProgramSource(id) {
3523
+ return registry.get(id) ?? null;
3524
+ }
3525
+ var isDev = () => typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
3526
+ function registerProgram(id, vert, frag) {
3527
+ if (registry.has(id) && !isDev()) {
3528
+ throw new Error(`weasel registerProgram: duplicate program id "${id}". In production, re-registration is not allowed. Pass a unique id or call in dev mode.`);
3529
+ }
3530
+ registry.set(id, { vert, frag });
3531
+ return { id };
3532
+ }
3533
+
3534
+ // src/renderer/effects/effectPrelude.ts
3535
+ var EFFECT_VERT_SRC = (
3536
+ /* glsl */
3537
+ `#version 300 es
3538
+ in vec2 a_position;
3539
+ in vec2 a_uv;
3540
+ out vec2 v_uv;
3541
+
3542
+ void main() {
3543
+ v_uv = a_uv;
3544
+ gl_Position = vec4(a_position, 0.0, 1.0);
3545
+ }
3546
+ `
3547
+ );
3548
+ var EFFECT_KIT_UNIFORMS = ["u_source", "u_resolution", "u_texel"];
3549
+ var COMPOSITE_FRAG_SRC = (
3550
+ /* glsl */
3551
+ `#version 300 es
3552
+ precision highp float;
3553
+ in vec2 v_uv;
3554
+ uniform sampler2D u_source;
3555
+ uniform float u_alpha;
3556
+ uniform mat4 u_colorMatrix;
3557
+ uniform vec4 u_colorBias;
3558
+ out vec4 outColor;
3559
+
3560
+ void main() {
3561
+ vec4 src = texture(u_source, v_uv);
3562
+ // Undo premultiplication before the colour matrix, which is defined on
3563
+ // straight alpha, then put it back for the blend.
3564
+ float a = src.a;
3565
+ vec3 rgb = a > 0.0 ? src.rgb / a : vec3(0.0);
3566
+ vec4 c = u_colorMatrix * vec4(rgb, a) + u_colorBias;
3567
+ c.a *= u_alpha;
3568
+ outColor = vec4(clamp(c.rgb, 0.0, 1.0) * c.a, c.a);
3569
+ }
3570
+ `
3571
+ );
3572
+
3573
+ // src/renderer/effects/composite.ts
3574
+ var COMPOSITE_PROGRAM_ID = "weasel:effect-composite";
3575
+ registerProgram(COMPOSITE_PROGRAM_ID, EFFECT_VERT_SRC, COMPOSITE_FRAG_SRC);
3576
+
3498
3577
  // src/renderer/draw.ts
3499
3578
  var FRAME_UPLOADS = /* @__PURE__ */ new WeakMap();
3500
3579
  var FRAME_PROJ = /* @__PURE__ */ new WeakMap();
@@ -3711,7 +3790,118 @@ function drawShader(ctx, cmd) {
3711
3790
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
3712
3791
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
3713
3792
  }
3793
+ function drawGroupWithEffects(ctx, cmd, targets, width, height) {
3794
+ const gl = ctx.gl;
3795
+ flushBatches(ctx);
3796
+ const parentTarget = ctx.renderTarget ?? null;
3797
+ const parentClipDepth = ctx.clipDepth;
3798
+ const scissorWasOn = gl.isEnabled(gl.SCISSOR_TEST);
3799
+ let front = targets.acquire(width, height);
3800
+ const composited = ctx.state.pushIsolated({ transform: cmd.transform });
3801
+ ctx.clipDepth = 0;
3802
+ gl.bindFramebuffer(gl.FRAMEBUFFER, front.fbo);
3803
+ ctx.renderTarget = front;
3804
+ gl.viewport(0, 0, width, height);
3805
+ gl.disable(gl.SCISSOR_TEST);
3806
+ gl.stencilMask(255);
3807
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
3808
+ for (const child of cmd.children) dispatch(ctx, child);
3809
+ flushBatches(ctx);
3810
+ gl.disable(gl.BLEND);
3811
+ for (const effect of cmd.effects) {
3812
+ const program = ctx.ensureProgram?.(effect.program.id) ?? null;
3813
+ if (!program) {
3814
+ warnOnceUniform(effect.program.id, "(program)");
3815
+ continue;
3816
+ }
3817
+ const back = targets.acquire(width, height);
3818
+ gl.bindFramebuffer(gl.FRAMEBUFFER, back.fbo);
3819
+ gl.viewport(0, 0, width, height);
3820
+ gl.clear(gl.COLOR_BUFFER_BIT);
3821
+ drawFullscreenQuad(
3822
+ ctx,
3823
+ program,
3824
+ front.texture,
3825
+ width,
3826
+ height,
3827
+ effect.uniforms,
3828
+ void 0,
3829
+ effect.program.id
3830
+ );
3831
+ targets.release(front);
3832
+ front = back;
3833
+ }
3834
+ gl.enable(gl.BLEND);
3835
+ ctx.state.pop();
3836
+ ctx.clipDepth = parentClipDepth;
3837
+ ctx.renderTarget = parentTarget;
3838
+ gl.bindFramebuffer(gl.FRAMEBUFFER, parentTarget ? parentTarget.fbo : null);
3839
+ if (parentTarget) {
3840
+ gl.viewport(0, 0, width, height);
3841
+ } else {
3842
+ ctx.restoreTargetRect?.();
3843
+ if (scissorWasOn) gl.enable(gl.SCISSOR_TEST);
3844
+ }
3845
+ const composite = ctx.ensureProgram?.(COMPOSITE_PROGRAM_ID) ?? null;
3846
+ if (composite) {
3847
+ applyClipTest(ctx);
3848
+ drawFullscreenQuad(ctx, composite, front.texture, width, height, void 0, {
3849
+ alpha: composited.alpha,
3850
+ colorMatrix: composited.colorMatrix
3851
+ });
3852
+ }
3853
+ targets.release(front);
3854
+ }
3855
+ function drawFullscreenQuad(ctx, program, texture, width, height, uniforms, composite, programId) {
3856
+ const gl = ctx.gl;
3857
+ gl.useProgram(program.handle);
3858
+ const aPos = program.attribute("a_position");
3859
+ const aUv = program.attribute("a_uv");
3860
+ gl.bindBuffer(gl.ARRAY_BUFFER, ctx.quadVbo);
3861
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ctx.quadIbo);
3862
+ if (aPos !== void 0 && aPos >= 0) {
3863
+ gl.enableVertexAttribArray(aPos);
3864
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0);
3865
+ }
3866
+ if (aUv !== void 0 && aUv >= 0) {
3867
+ gl.enableVertexAttribArray(aUv);
3868
+ gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 16, 8);
3869
+ }
3870
+ gl.activeTexture(gl.TEXTURE0);
3871
+ gl.bindTexture(gl.TEXTURE_2D, texture);
3872
+ const source = program.uniform("u_source");
3873
+ if (source !== void 0) gl.uniform1i(source, 0);
3874
+ const resolution = program.uniform("u_resolution");
3875
+ if (resolution !== void 0) gl.uniform2f(resolution, width, height);
3876
+ const texel = program.uniform("u_texel");
3877
+ if (texel !== void 0) gl.uniform2f(texel, 1 / width, 1 / height);
3878
+ if (composite) {
3879
+ const alpha = program.uniform("u_alpha");
3880
+ if (alpha !== void 0) gl.uniform1f(alpha, composite.alpha);
3881
+ setColorMatrixUniforms(ctx, program, composite.colorMatrix);
3882
+ }
3883
+ if (uniforms) {
3884
+ const nextTexUnit = { value: 1 };
3885
+ for (const [name, value] of Object.entries(uniforms)) {
3886
+ const loc = program.uniform(name);
3887
+ if (loc === void 0) {
3888
+ warnOnceUniform(programId ?? "effect", name);
3889
+ continue;
3890
+ }
3891
+ setUniform(gl, loc, value, ctx.textureCache, nextTexUnit);
3892
+ }
3893
+ }
3894
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
3895
+ if (aPos !== void 0 && aPos >= 0) gl.disableVertexAttribArray(aPos);
3896
+ if (aUv !== void 0 && aUv >= 0) gl.disableVertexAttribArray(aUv);
3897
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
3898
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
3899
+ }
3714
3900
  function drawGroup(ctx, cmd) {
3901
+ if (cmd.effects && cmd.effects.length > 0 && ctx.effectTargets && ctx.deviceWidth && ctx.deviceHeight) {
3902
+ drawGroupWithEffects(ctx, cmd, ctx.effectTargets, ctx.deviceWidth, ctx.deviceHeight);
3903
+ return;
3904
+ }
3715
3905
  ctx.state.push({
3716
3906
  transform: cmd.transform,
3717
3907
  alpha: cmd.alpha,
@@ -3737,7 +3927,7 @@ function drawGroup(ctx, cmd) {
3737
3927
  }
3738
3928
  function drawPath(ctx, cmd) {
3739
3929
  if (cmd.fill) drawPathFill(ctx, cmd);
3740
- if (cmd.stroke) drawPathStroke(ctx, cmd);
3930
+ if (cmd.stroke?.paint) drawPathStroke(ctx, cmd);
3741
3931
  }
3742
3932
  function drawPathFill(ctx, cmd) {
3743
3933
  const fill = cmd.fill;
@@ -4329,12 +4519,16 @@ function drawText(ctx, cmd) {
4329
4519
  }
4330
4520
  var SYNTHETIC_ITALIC_RADIANS = 0.2094;
4331
4521
  function drawTextOutlineGroup(ctx, group, dx, dy) {
4332
- const mesh = outlineGroupMesh(group, dx, dy);
4333
- if (mesh) drawPathFillByKind(ctx, group.fill, ctx.meshCache.uploadTransient(mesh));
4334
- if (!group.stroke) return;
4522
+ const fill = group.fill;
4523
+ if (fill !== null) {
4524
+ const mesh = outlineGroupMesh(group, dx, dy);
4525
+ if (mesh) drawPathFillByKind(ctx, fill, ctx.meshCache.uploadTransient(mesh));
4526
+ }
4527
+ const strokePaint = group.stroke?.paint;
4528
+ if (!strokePaint) return;
4335
4529
  const ribbon = outlineGroupStrokeMesh(group, dx, dy, mat3.meanScaleOf(ctx.state.transform));
4336
4530
  if (ribbon) {
4337
- drawPathFillByKind(ctx, group.stroke.paint, ctx.meshCache.uploadTransient(ribbon));
4531
+ drawPathFillByKind(ctx, strokePaint, ctx.meshCache.uploadTransient(ribbon));
4338
4532
  }
4339
4533
  }
4340
4534
  function outlineGroupMesh(group, dx, dy) {
@@ -4485,7 +4679,7 @@ function drawTextGroup(ctx, group, prog, dx, dy) {
4485
4679
  ensureQuadIndices(gl, ring, group.quads.length);
4486
4680
  bindTextQuadSlot(gl, ring, vertices.subarray(0, floats));
4487
4681
  let r = 0, g = 0, b = 0, a = 1;
4488
- if ("color" in group.fill) {
4682
+ if (group.fill !== null && "color" in group.fill) {
4489
4683
  [r, g, b, a] = resolveColor(group.fill.color);
4490
4684
  }
4491
4685
  setColorUniform(ctx, prog, r, g, b, a);
@@ -4699,19 +4893,79 @@ var QUAD_VERTICES = new Float32Array([
4699
4893
  ]);
4700
4894
  var QUAD_INDICES = new Uint16Array([0, 1, 2, 0, 2, 3]);
4701
4895
 
4702
- // src/renderer/shaders/registerProgram.ts
4703
- var registry = /* @__PURE__ */ new Map();
4704
- function getProgramSource(id) {
4705
- return registry.get(id) ?? null;
4706
- }
4707
- var isDev = () => typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
4708
- function registerProgram(id, vert, frag) {
4709
- if (registry.has(id) && !isDev()) {
4710
- throw new Error(`weasel registerProgram: duplicate program id "${id}". In production, re-registration is not allowed. Pass a unique id or call in dev mode.`);
4896
+ // src/renderer/effects/EffectTargets.ts
4897
+ var EffectTargets = class {
4898
+ gl;
4899
+ all = [];
4900
+ free = [];
4901
+ width = 0;
4902
+ height = 0;
4903
+ constructor(gl) {
4904
+ this.gl = gl;
4711
4905
  }
4712
- registry.set(id, { vert, frag });
4713
- return { id };
4714
- }
4906
+ /** True once anything has been allocated — what a test asks instead of
4907
+ * reaching for the private arrays. */
4908
+ get allocated() {
4909
+ return this.all.length > 0;
4910
+ }
4911
+ /**
4912
+ * Borrow a target sized `width` × `height` device pixels. A size change
4913
+ * drops every pooled buffer rather than resizing in place: a borrowed one
4914
+ * cannot be resized under its holder, and a resize is not a per-frame event.
4915
+ */
4916
+ acquire(width, height) {
4917
+ if (width !== this.width || height !== this.height) {
4918
+ this.free = [];
4919
+ this.releaseAll();
4920
+ this.width = width;
4921
+ this.height = height;
4922
+ }
4923
+ const pooled = this.free.pop();
4924
+ if (pooled) return pooled;
4925
+ const made = this.create(width, height);
4926
+ this.all.push(made);
4927
+ return made;
4928
+ }
4929
+ /** Hand one back. Order does not matter — every target is interchangeable. */
4930
+ release(target) {
4931
+ this.free.push(target);
4932
+ }
4933
+ /** Drop every buffer. Called on dispose and on context loss, where the
4934
+ * handles are already invalid and only the bookkeeping has to go. */
4935
+ releaseAll() {
4936
+ const gl = this.gl;
4937
+ for (const t of this.all) {
4938
+ gl.deleteFramebuffer(t.fbo);
4939
+ gl.deleteTexture(t.texture);
4940
+ gl.deleteRenderbuffer(t.stencil);
4941
+ }
4942
+ this.all.length = 0;
4943
+ this.free = [];
4944
+ this.width = 0;
4945
+ this.height = 0;
4946
+ }
4947
+ create(width, height) {
4948
+ const gl = this.gl;
4949
+ const texture = gl.createTexture();
4950
+ gl.bindTexture(gl.TEXTURE_2D, texture);
4951
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
4952
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
4953
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
4954
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
4955
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
4956
+ gl.bindTexture(gl.TEXTURE_2D, null);
4957
+ const stencil = gl.createRenderbuffer();
4958
+ gl.bindRenderbuffer(gl.RENDERBUFFER, stencil);
4959
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, width, height);
4960
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
4961
+ const fbo = gl.createFramebuffer();
4962
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
4963
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
4964
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, stencil);
4965
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
4966
+ return { fbo, texture, stencil };
4967
+ }
4968
+ };
4715
4969
 
4716
4970
  // src/renderer/WeaselRenderer.ts
4717
4971
  var UNIFORM_QUALIFIER = /^(?:lowp|mediump|highp|precise|invariant|centroid|flat|smooth|noperspective)$/;
@@ -4764,6 +5018,9 @@ var WeaselRenderer = class {
4764
5018
  dpr;
4765
5019
  canvas = null;
4766
5020
  target = null;
5021
+ /** Offscreen buffers for group effects. Allocates nothing until a group
5022
+ * with effects asks, so a canvas without them pays no memory. */
5023
+ effectTargets;
4767
5024
  imageMinification;
4768
5025
  flattenTolerance;
4769
5026
  bakeBudget;
@@ -4781,6 +5038,7 @@ var WeaselRenderer = class {
4781
5038
  const gl = opts.gl ?? opts.canvas.getContext("webgl2", { stencil: true });
4782
5039
  if (!gl) throw new Error("WeaselRenderer: WebGL2 not available");
4783
5040
  this.gl = gl;
5041
+ this.effectTargets = new EffectTargets(this.gl);
4784
5042
  const attrs = typeof this.gl.getContextAttributes === "function" ? this.gl.getContextAttributes() : null;
4785
5043
  if (attrs && attrs.stencil === false) {
4786
5044
  throw new Error(
@@ -4952,6 +5210,7 @@ var WeaselRenderer = class {
4952
5210
  this.contextLost = false;
4953
5211
  this.applyGlState();
4954
5212
  this.applyTarget();
5213
+ this.effectTargets.releaseAll();
4955
5214
  this.pathFill = new ShaderProgram(this.gl, VERT_SRC, FRAG_SRC);
4956
5215
  this.pathFill.lookupUniforms(PATH_FILL_UNIFORMS);
4957
5216
  this.pathFill.lookupAttributes(PATH_FILL_ATTRIBUTES);
@@ -5052,6 +5311,7 @@ var WeaselRenderer = class {
5052
5311
  for (const prog of [this.textSdf, this.textSdfR8, this.pathFill]) disposeTextQuads(gl, prog);
5053
5312
  this.solidBatch.dispose();
5054
5313
  this.imageBatch.dispose();
5314
+ this.effectTargets.releaseAll();
5055
5315
  }
5056
5316
  /**
5057
5317
  * Draw one frame.
@@ -5099,7 +5359,12 @@ var WeaselRenderer = class {
5099
5359
  clipDepth: 0,
5100
5360
  flattenTolerance: this.flattenTolerance,
5101
5361
  textOutlineMinScreenSize: this.textOutlineMinScreenSize,
5102
- viewMatrix: viewMatrix2
5362
+ viewMatrix: viewMatrix2,
5363
+ effectTargets: this.effectTargets,
5364
+ renderTarget: null,
5365
+ deviceWidth: Math.round(this.widthCss * this.dpr),
5366
+ deviceHeight: Math.round(this.heightCss * this.dpr),
5367
+ restoreTargetRect: () => this.applyTarget()
5103
5368
  };
5104
5369
  for (const cmd of commands) dispatch(ctx, cmd);
5105
5370
  flushBatches(ctx);
@@ -5231,6 +5496,71 @@ function viewToMat3(view) {
5231
5496
  ]);
5232
5497
  }
5233
5498
 
5499
+ // src/renderer/effects/types.ts
5500
+ function registerEffect(id, frag) {
5501
+ return registerProgram(id, EFFECT_VERT_SRC, frag);
5502
+ }
5503
+
5504
+ // src/renderer/effects/builtins.ts
5505
+ var BLUR_FRAG = (
5506
+ /* glsl */
5507
+ `#version 300 es
5508
+ precision highp float;
5509
+ in vec2 v_uv;
5510
+ uniform sampler2D u_source;
5511
+ uniform vec2 u_texel;
5512
+ uniform vec2 u_direction;
5513
+ uniform float u_radius;
5514
+ out vec4 outColor;
5515
+
5516
+ void main() {
5517
+ // Nine taps on a Gaussian, spread across the requested radius. The weights
5518
+ // are a normalized binomial row, so a radius of 0 collapses to a copy.
5519
+ const float W[5] = float[5](0.2270270, 0.1945946, 0.1216216, 0.0540541, 0.0162162);
5520
+ vec2 step = u_direction * u_texel * (u_radius / 4.0);
5521
+ vec4 sum = texture(u_source, v_uv) * W[0];
5522
+ for (int i = 1; i < 5; i++) {
5523
+ vec2 off = step * float(i);
5524
+ sum += texture(u_source, v_uv + off) * W[i];
5525
+ sum += texture(u_source, v_uv - off) * W[i];
5526
+ }
5527
+ outColor = sum;
5528
+ }
5529
+ `
5530
+ );
5531
+ var VIGNETTE_FRAG = (
5532
+ /* glsl */
5533
+ `#version 300 es
5534
+ precision highp float;
5535
+ in vec2 v_uv;
5536
+ uniform sampler2D u_source;
5537
+ uniform float u_amount;
5538
+ uniform float u_feather;
5539
+ out vec4 outColor;
5540
+
5541
+ void main() {
5542
+ vec4 src = texture(u_source, v_uv);
5543
+ float d = distance(v_uv, vec2(0.5)) * 1.41421356;
5544
+ float shade = 1.0 - u_amount * smoothstep(1.0 - u_feather, 1.0, d);
5545
+ // Premultiplied in, premultiplied out: scaling all four channels darkens
5546
+ // toward transparent black, which is what a vignette over a transparent
5547
+ // canvas should do.
5548
+ outColor = src * shade;
5549
+ }
5550
+ `
5551
+ );
5552
+ var BLUR = registerEffect("weasel:blur", BLUR_FRAG);
5553
+ var VIGNETTE = registerEffect("weasel:vignette", VIGNETTE_FRAG);
5554
+ function blur({ radius }) {
5555
+ return [
5556
+ { program: BLUR, uniforms: { u_direction: [1, 0], u_radius: radius } },
5557
+ { program: BLUR, uniforms: { u_direction: [0, 1], u_radius: radius } }
5558
+ ];
5559
+ }
5560
+ function vignette({ amount, feather = 0.6 }) {
5561
+ return [{ program: VIGNETTE, uniforms: { u_amount: amount, u_feather: feather } }];
5562
+ }
5563
+
5234
5564
  // src/core/viewport/meanScale.ts
5235
5565
  function meanScale(s) {
5236
5566
  return Math.sqrt(s.x * s.y);
@@ -5348,7 +5678,13 @@ function createCellHighlightLayer(opts) {
5348
5678
 
5349
5679
  // src/core/layers/render.ts
5350
5680
  var IDENTITY_VIEW = { x: 0, y: 0, scale: { x: 1, y: 1 } };
5351
- function drawLayers(layers, data, visibility, order, view, dims, cache7) {
5681
+ function reportLayerFailure({ layerId, error }) {
5682
+ console.error(
5683
+ `[weasel] layer "${layerId}" threw while drawing; it painted nothing this frame.`,
5684
+ error
5685
+ );
5686
+ }
5687
+ function drawLayers(layers, data, visibility, order, view, dims, cache7, onLayerError = reportLayerFailure, groups) {
5352
5688
  const layerById = new Map(layers.map((l) => [l.id, l]));
5353
5689
  const sequence = order ? order.map((id) => layerById.get(id)).filter((l) => l !== void 0) : layers;
5354
5690
  const v = view ?? IDENTITY_VIEW;
@@ -5358,12 +5694,67 @@ function drawLayers(layers, data, visibility, order, view, dims, cache7) {
5358
5694
  if (!layerById.has(id)) cache7.delete(id);
5359
5695
  }
5360
5696
  }
5697
+ const groupOf = groupMembership(groups);
5698
+ const bracketed = /* @__PURE__ */ new Set();
5699
+ let run = null;
5700
+ const closeRun = () => {
5701
+ if (!run) return;
5702
+ for (const c of wrapGroup(run.group, run.children, v, dims)) out.push(c);
5703
+ run = null;
5704
+ };
5361
5705
  for (const layer of sequence) {
5362
5706
  if (!isLayerVisible(layer, visibility)) continue;
5363
- for (const c of drawOneLayer(layer, data, v, dims, cache7)) out.push(c);
5707
+ const cmds = drawOneLayer(layer, data, v, dims, cache7, onLayerError);
5708
+ if (cmds.length === 0) continue;
5709
+ const group = groupOf?.get(layer.id);
5710
+ if (run && run.group !== group) closeRun();
5711
+ if (!group) {
5712
+ for (const c of cmds) out.push(c);
5713
+ continue;
5714
+ }
5715
+ if (!run) {
5716
+ if (bracketed.has(group)) {
5717
+ console.warn(
5718
+ `[weasel] layer group "${group.id}" is not one run \u2014 another layer is drawn between its members, so it composites as more than one pass over more than one buffer. Move its members together in the layer order.`
5719
+ );
5720
+ }
5721
+ bracketed.add(group);
5722
+ run = { group, children: [] };
5723
+ }
5724
+ for (const c of cmds) run.children.push(c);
5364
5725
  }
5726
+ closeRun();
5365
5727
  return out;
5366
5728
  }
5729
+ function groupMembership(groups) {
5730
+ if (!groups || groups.length === 0) return null;
5731
+ const byLayer = /* @__PURE__ */ new Map();
5732
+ for (const group of groups) {
5733
+ for (const id of group.layers) {
5734
+ const claimed = byLayer.get(id);
5735
+ if (claimed) {
5736
+ console.warn(
5737
+ `[weasel] layer "${id}" is claimed by layer groups "${claimed.id}" and "${group.id}"; it stays in "${claimed.id}".`
5738
+ );
5739
+ continue;
5740
+ }
5741
+ byLayer.set(id, group);
5742
+ }
5743
+ }
5744
+ return byLayer;
5745
+ }
5746
+ function wrapGroup(group, children, view, dims) {
5747
+ const declared = typeof group.effects === "function" ? group.effects(view, dims) : group.effects;
5748
+ const effects = declared && declared.length > 0 ? declared : void 0;
5749
+ if (!effects && group.alpha === void 0 && group.colorMatrix === void 0) return children;
5750
+ return [{
5751
+ kind: "group",
5752
+ ...effects ? { effects } : {},
5753
+ ...group.alpha !== void 0 ? { alpha: group.alpha } : {},
5754
+ ...group.colorMatrix !== void 0 ? { colorMatrix: group.colorMatrix } : {},
5755
+ children
5756
+ }];
5757
+ }
5367
5758
  function isLayerVisible(layer, visibility) {
5368
5759
  if (layer.alwaysOn) return true;
5369
5760
  if (layer.id in visibility) return visibility[layer.id];
@@ -5373,11 +5764,21 @@ function isLayerPainted(layer, visibility, order) {
5373
5764
  if (order && !order.includes(layer.id)) return false;
5374
5765
  return isLayerVisible(layer, visibility);
5375
5766
  }
5376
- function drawOneLayer(layer, data, view, dims, cache7) {
5377
- const cmds = layerCommands(layer, data, view, dims, cache7);
5767
+ function drawOneLayer(layer, data, view, dims, cache7, onLayerError = reportLayerFailure) {
5768
+ let cmds;
5769
+ try {
5770
+ cmds = layerCommands(layer, data, view, dims, cache7);
5771
+ } catch (error) {
5772
+ cache7?.delete(layer.id);
5773
+ onLayerError({ layerId: layer.id, error });
5774
+ return [];
5775
+ }
5378
5776
  if (cmds.length === 0) return [];
5379
- if ((layer.space ?? "world") === "screen") return cmds;
5380
- return [{ kind: "group", transform: viewToMat3(view), children: cmds }];
5777
+ const effects = layer.effects && layer.effects.length > 0 ? layer.effects : void 0;
5778
+ if ((layer.space ?? "world") === "screen") {
5779
+ return effects ? [{ kind: "group", effects, children: cmds }] : cmds;
5780
+ }
5781
+ return [{ kind: "group", transform: viewToMat3(view), ...effects ? { effects } : {}, children: cmds }];
5381
5782
  }
5382
5783
  function layerCommands(layer, data, view, dims, cache7) {
5383
5784
  if (!cache7 || !layer.deps) return layer.draw(data, view, dims);
@@ -5535,7 +5936,7 @@ function createParallaxLayer(opts) {
5535
5936
  space: "screen",
5536
5937
  draw: (data, outer, dims) => {
5537
5938
  const inner = deriveParallaxView(getOuterView?.() ?? outer, { pan, zoom, anchor });
5538
- return source.flatMap((layer) => layer.draw(data, inner, dims));
5939
+ return source.flatMap((layer) => drawOneLayer(layer, data, inner, dims));
5539
5940
  }
5540
5941
  };
5541
5942
  }
@@ -6548,16 +6949,16 @@ function targetSizesPx(targetScale = 1) {
6548
6949
  }
6549
6950
 
6550
6951
  // src/core/viewport/fitToBounds.ts
6551
- function fitZoom(availW, availH, contentW, contentH, clamp2) {
6952
+ function fitZoom(availW, availH, contentW, contentH, clamp) {
6552
6953
  const raw = Math.min(availW / contentW, availH / contentH);
6553
- const min = clamp2?.min ?? -Infinity;
6554
- const max = clamp2?.max ?? Infinity;
6954
+ const min = clamp?.min ?? -Infinity;
6955
+ const max = clamp?.max ?? Infinity;
6555
6956
  return Math.min(max, Math.max(min, raw));
6556
6957
  }
6557
- function fitToBounds(viewportW, viewportH, contentW, contentH, paddingPx = 0, clamp2) {
6958
+ function fitToBounds(viewportW, viewportH, contentW, contentH, paddingPx = 0, clamp) {
6558
6959
  const availW = Math.max(1, viewportW - paddingPx * 2);
6559
6960
  const availH = Math.max(1, viewportH - paddingPx * 2);
6560
- const zoom = fitZoom(availW, availH, contentW, contentH, clamp2);
6961
+ const zoom = fitZoom(availW, availH, contentW, contentH, clamp);
6561
6962
  const contentPxW = contentW * zoom;
6562
6963
  const contentPxH = contentH * zoom;
6563
6964
  return {
@@ -6680,128 +7081,6 @@ function sceneNodeClientRect(opts) {
6680
7081
  height: bounds.height * opts.view.scale.y
6681
7082
  };
6682
7083
  }
6683
- var clamp = (z, min, max) => Math.min(max, Math.max(min, z));
6684
- function isEditableTarget(t) {
6685
- if (!t) return false;
6686
- const el = t;
6687
- if (el.isContentEditable) return true;
6688
- const tag = (el.tagName ?? "").toUpperCase();
6689
- return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
6690
- }
6691
- function useZoom(opts) {
6692
- const min = opts.min ?? DEFAULT_MIN_ZOOM;
6693
- const max = opts.max ?? DEFAULT_MAX_ZOOM;
6694
- const wheelStep = opts.wheelStep ?? 1.1;
6695
- const keyStep = opts.keyStep ?? 1.25;
6696
- const sources = useMemo(
6697
- () => ({
6698
- wheel: opts.sources?.wheel ?? true,
6699
- keys: opts.sources?.keys ?? true,
6700
- doubleClick: opts.sources?.doubleClick ?? false,
6701
- pinch: opts.sources?.pinch ?? true
6702
- }),
6703
- [opts.sources?.wheel, opts.sources?.keys, opts.sources?.doubleClick, opts.sources?.pinch]
6704
- );
6705
- const applyZoom = useCallback(
6706
- (nextZoom, focal) => {
6707
- const oldZoom = opts.zoom;
6708
- const newZoom = clamp(nextZoom, min, max);
6709
- const k = newZoom / oldZoom;
6710
- const newPan = {
6711
- x: focal.x - (focal.x - opts.pan.x) * k,
6712
- y: focal.y - (focal.y - opts.pan.y) * k
6713
- };
6714
- opts.setZoom(newZoom);
6715
- opts.setPan(newPan);
6716
- },
6717
- [opts, min, max]
6718
- );
6719
- const viewportCenter = useCallback(() => {
6720
- if (!opts.viewport) {
6721
- throw new Error(
6722
- "useZoom: viewport option is required for keyboard zoom and zoomTo without focal"
6723
- );
6724
- }
6725
- return { x: opts.viewport.width / 2, y: opts.viewport.height / 2 };
6726
- }, [opts.viewport]);
6727
- const zoomTo = useCallback(
6728
- (level, focal) => {
6729
- applyZoom(level, focal ?? viewportCenter());
6730
- },
6731
- [applyZoom, viewportCenter]
6732
- );
6733
- const zoomBy = useCallback(
6734
- (factor, focal) => {
6735
- applyZoom(opts.zoom * factor, focal ?? viewportCenter());
6736
- },
6737
- [applyZoom, opts.zoom, viewportCenter]
6738
- );
6739
- const reset = useCallback(() => {
6740
- opts.setZoom(1);
6741
- opts.setPan({ x: 0, y: 0 });
6742
- }, [opts]);
6743
- const onWheel = useCallback(
6744
- (e) => {
6745
- const evt = e;
6746
- const isPinch = evt.ctrlKey === true;
6747
- if (isPinch) {
6748
- if (!sources.pinch) return;
6749
- evt.preventDefault?.();
6750
- } else {
6751
- if (!sources.wheel) return;
6752
- if (opts.wheelRequiresModifier && !evt.metaKey) return;
6753
- }
6754
- const target = evt.currentTarget;
6755
- const rect = target?.getBoundingClientRect?.();
6756
- const focal = {
6757
- x: evt.clientX - (rect?.left ?? 0),
6758
- y: evt.clientY - (rect?.top ?? 0)
6759
- };
6760
- const factor = evt.deltaY < 0 ? wheelStep : 1 / wheelStep;
6761
- applyZoom(opts.zoom * factor, focal);
6762
- },
6763
- [applyZoom, opts, sources, wheelStep]
6764
- );
6765
- const onKeyDown = useCallback(
6766
- (e) => {
6767
- if (!sources.keys) return;
6768
- const evt = e;
6769
- if (isEditableTarget(evt.target)) return;
6770
- if (evt.key === "0" && (evt.metaKey || evt.ctrlKey)) {
6771
- opts.setZoom(1);
6772
- opts.setPan({ x: 0, y: 0 });
6773
- return;
6774
- }
6775
- let factor = 0;
6776
- if (evt.key === "+" || evt.key === "=") factor = keyStep;
6777
- else if (evt.key === "-" || evt.key === "_") factor = 1 / keyStep;
6778
- if (factor === 0) return;
6779
- applyZoom(opts.zoom * factor, viewportCenter());
6780
- },
6781
- [applyZoom, opts, sources, keyStep, viewportCenter]
6782
- );
6783
- const onDoubleClick = useCallback(
6784
- (e) => {
6785
- if (!sources.doubleClick) return;
6786
- const evt = e;
6787
- if (evt.altKey) {
6788
- opts.setZoom(1);
6789
- opts.setPan({ x: 0, y: 0 });
6790
- return;
6791
- }
6792
- const target = evt.currentTarget;
6793
- const rect = target?.getBoundingClientRect?.();
6794
- const focal = {
6795
- x: evt.clientX - (rect?.left ?? 0),
6796
- y: evt.clientY - (rect?.top ?? 0)
6797
- };
6798
- const factor = evt.shiftKey ? 1 / keyStep : keyStep;
6799
- applyZoom(opts.zoom * factor, focal);
6800
- },
6801
- [applyZoom, opts, sources, keyStep]
6802
- );
6803
- return { onWheel, onKeyDown, onDoubleClick, zoomTo, zoomBy, reset };
6804
- }
6805
7084
  function computeFitViewport(viewportW, viewportH, contentW, contentH, padRatio = 0.85) {
6806
7085
  const zoom = fitZoom(viewportW * padRatio, viewportH * padRatio, contentW, contentH);
6807
7086
  const cw = contentW * zoom;
@@ -6821,7 +7100,7 @@ function useAutoCenter(width, height, contentW, contentH, setZoom, setPan) {
6821
7100
  }
6822
7101
 
6823
7102
  // src/interactions/keyHelpers.ts
6824
- function isEditableTarget2(target) {
7103
+ function isEditableTarget(target) {
6825
7104
  if (!target || !(target instanceof Element)) return false;
6826
7105
  const tag = target.tagName;
6827
7106
  if (tag === "INPUT" || tag === "TEXTAREA") return true;
@@ -6959,7 +7238,10 @@ function moveGestureAdapter(scene) {
6959
7238
  data: node.data,
6960
7239
  id: node.id,
6961
7240
  ...index !== void 0 ? { index } : {},
6962
- ...node.parent !== null ? { parent: node.parent } : {}
7241
+ ...node.parent !== null ? { parent: node.parent } : {},
7242
+ ...node.dependsOn !== void 0 ? { dependsOn: node.dependsOn } : {},
7243
+ ...node.derivePath !== void 0 ? { derivePath: node.derivePath } : {},
7244
+ ...node.kind === "container" && node.clipFromPose !== void 0 ? { clipFromPose: node.clipFromPose } : {}
6963
7245
  }),
6964
7246
  getChildren: (parentId) => parentId === null ? [...scene.roots] : [...scene.childrenOf(asNodeId(parentId))],
6965
7247
  setChildOrder: (parentId, ids) => {
@@ -6984,31 +7266,51 @@ function runLayoutPass(scratch, moveCtx) {
6984
7266
  if (!layoutDep || !moveCtx.drag) return;
6985
7267
  const scene = scratch.scene;
6986
7268
  const pc = scratch.pc;
6987
- const draggedId = scratch.ids[0];
6988
7269
  const poseAdapter = scenePoseAdapter(scene);
6989
- if (!scene.get(draggedId)) return;
6990
- const sourceContainerId = scene.get(draggedId)?.parent ?? null;
6991
7270
  const { dx, dy } = scratch.currentDelta;
6992
- const startWorld = composeWorldPose(poseAdapter, draggedId, pc.compose);
6993
- const draggedWorld = { ...startWorld, x: startWorld.x + dx, y: startWorld.y + dy };
6994
- const draggedCenter = {
6995
- x: draggedWorld.x + (draggedWorld.width ?? 0) / 2,
6996
- y: draggedWorld.y + (draggedWorld.height ?? 0) / 2
7271
+ const dragged = [];
7272
+ for (const id of scratch.ids) {
7273
+ const node = scene.get(id);
7274
+ if (!node) continue;
7275
+ const startWorld = composeWorldPose(poseAdapter, id, pc.compose);
7276
+ const world = { ...startWorld, x: startWorld.x + dx, y: startWorld.y + dy };
7277
+ dragged.push({
7278
+ id,
7279
+ arg: {
7280
+ id,
7281
+ originPose: startWorld,
7282
+ pose: world,
7283
+ sourceContainerId: node.parent ?? null
7284
+ },
7285
+ center: { x: world.x + (world.width ?? 0) / 2, y: world.y + (world.height ?? 0) / 2 }
7286
+ });
7287
+ }
7288
+ if (dragged.length === 0) return;
7289
+ const draggedIds = new Set(dragged.map((d) => d.id));
7290
+ let minX = Infinity;
7291
+ let minY = Infinity;
7292
+ let maxX = -Infinity;
7293
+ let maxY = -Infinity;
7294
+ for (const d of dragged) {
7295
+ const p = d.arg.pose;
7296
+ minX = Math.min(minX, p.x);
7297
+ minY = Math.min(minY, p.y);
7298
+ maxX = Math.max(maxX, p.x + (p.width ?? 0));
7299
+ maxY = Math.max(maxY, p.y + (p.height ?? 0));
7300
+ }
7301
+ const selectionCenter = { x: (minX + maxX) / 2, y: (minY + maxY) / 2 };
7302
+ const probeOffset = {
7303
+ x: moveCtx.drag.current.x - selectionCenter.x,
7304
+ y: moveCtx.drag.current.y - selectionCenter.y
6997
7305
  };
6998
7306
  const candidates = [];
6999
7307
  const boundsOf2 = scratch.projection?.getBounds ?? AUTO_POSE_DESCRIPTOR.getBounds;
7000
7308
  const testInside = (cPose, aabb, layout2) => {
7001
- if (layout2.contains) return layout2.contains(cPose, draggedCenter);
7002
- return draggedCenter.x >= aabb.x && draggedCenter.x < aabb.x + aabb.width && draggedCenter.y >= aabb.y && draggedCenter.y < aabb.y + aabb.height;
7003
- };
7004
- const draggedArg = {
7005
- id: draggedId,
7006
- originPose: startWorld,
7007
- pose: draggedWorld,
7008
- sourceContainerId
7309
+ if (layout2.contains) return layout2.contains(cPose, selectionCenter);
7310
+ return selectionCenter.x >= aabb.x && selectionCenter.x < aabb.x + aabb.width && selectionCenter.y >= aabb.y && selectionCenter.y < aabb.y + aabb.height;
7009
7311
  };
7010
7312
  const consider = (id, zPath) => {
7011
- if (id === draggedId) return;
7313
+ if (draggedIds.has(id)) return;
7012
7314
  const layout2 = layoutDep.getLayout(id);
7013
7315
  if (!layout2) return;
7014
7316
  const node = scene.get(id);
@@ -7016,7 +7318,10 @@ function runLayoutPass(scratch, moveCtx) {
7016
7318
  const worldPose = composeWorldPose(poseAdapter, id, pc.compose);
7017
7319
  const worldAABB = boundsOf2(worldPose);
7018
7320
  if (!testInside(worldPose, worldAABB, layout2)) return;
7019
- if (layout2.acceptsDrop && !layout2.acceptsDrop({ id, bounds: worldAABB }, draggedArg)) return;
7321
+ if (layout2.acceptsDrop) {
7322
+ const arg = { id, bounds: worldAABB };
7323
+ for (const d of dragged) if (!layout2.acceptsDrop(arg, d.arg)) return;
7324
+ }
7020
7325
  candidates.push({
7021
7326
  id,
7022
7327
  bounds: worldAABB,
@@ -7061,47 +7366,126 @@ function runLayoutPass(scratch, moveCtx) {
7061
7366
  if (!dest) return;
7062
7367
  const layout = dest.layout;
7063
7368
  const container = { id: dest.id, bounds: dest.bounds };
7064
- const children = scene.childrenOf(dest.id).filter((cid) => cid !== draggedId || sourceContainerId === dest.id).map((cid) => ({
7369
+ const working = scene.childrenOf(dest.id).map((cid) => ({
7065
7370
  id: cid,
7066
7371
  pose: composeWorldPose(poseAdapter, cid, pc.compose)
7067
7372
  }));
7068
- const targets = layout.getDropTargets(container, children, draggedArg);
7069
- const target = layout.snap.pickTarget(targets, { x: moveCtx.drag.current.x, y: moveCtx.drag.current.y });
7070
- if (!target) return;
7373
+ const setWorking = (id, pose) => {
7374
+ const at = working.findIndex((c) => c.id === id);
7375
+ if (at >= 0) working[at] = { id, pose };
7376
+ else working.push({ id, pose });
7377
+ };
7378
+ const placements = [];
7379
+ const destReflow = /* @__PURE__ */ new Map();
7380
+ for (const d of dragged) {
7381
+ const children = working.map((c) => ({ ...c }));
7382
+ const targets = layout.getDropTargets(container, children, d.arg);
7383
+ const target = layout.snap.pickTarget(targets, {
7384
+ x: d.center.x + probeOffset.x,
7385
+ y: d.center.y + probeOffset.y
7386
+ });
7387
+ if (!target) return;
7388
+ placements.push({ dragged: d.arg, children, target });
7389
+ const poses = layout.reflowPoses(container, children, d.arg, target);
7390
+ for (const [cid, pose] of poses) {
7391
+ setWorking(cid, pose);
7392
+ if (!draggedIds.has(asNodeId(cid))) destReflow.set(asNodeId(cid), pose);
7393
+ }
7394
+ setWorking(d.arg.id, poses.get(d.arg.id) ?? target.pose);
7395
+ }
7071
7396
  const reflowIds = /* @__PURE__ */ new Set();
7072
- for (const [cid, pose] of layout.reflowPoses(container, children, draggedArg, target)) {
7073
- if (asNodeId(cid) === draggedId) continue;
7074
- const parent = scene.get(asNodeId(cid))?.parent ?? null;
7075
- const local = rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose);
7076
- scratch.previews.set(asNodeId(cid), local);
7077
- reflowIds.add(asNodeId(cid));
7397
+ for (const [cid, pose] of destReflow) {
7398
+ const parent = scene.get(cid)?.parent ?? null;
7399
+ scratch.previews.set(cid, rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose));
7400
+ reflowIds.add(cid);
7078
7401
  }
7079
7402
  const sourceReflow = /* @__PURE__ */ new Map();
7080
- if (sourceContainerId && sourceContainerId !== dest.id) {
7081
- const srcLayout = layoutDep.getLayout(sourceContainerId);
7082
- const srcNode = scene.get(asNodeId(sourceContainerId));
7083
- if (srcLayout && srcNode) {
7084
- const srcContainer = {
7085
- id: sourceContainerId,
7086
- bounds: composeWorldPose(poseAdapter, sourceContainerId, pc.compose)
7087
- };
7088
- const srcChildren = scene.childrenOf(asNodeId(sourceContainerId)).filter((cid) => cid !== draggedId).map((cid) => ({
7089
- id: cid,
7090
- pose: composeWorldPose(poseAdapter, cid, pc.compose)
7091
- }));
7092
- for (const [cid, pose] of srcLayout.childPoses(srcContainer, srcChildren)) {
7093
- const cur = composeWorldPose(poseAdapter, cid, pc.compose);
7094
- const next = pose;
7095
- const same2 = cur.x === next.x && cur.y === next.y && cur.width === next.width && cur.height === next.height;
7096
- if (same2) continue;
7097
- sourceReflow.set(cid, pose);
7098
- const parent = scene.get(asNodeId(cid))?.parent ?? null;
7099
- scratch.previews.set(asNodeId(cid), rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose));
7100
- reflowIds.add(asNodeId(cid));
7403
+ for (const [srcId, leaving] of groupBySourceContainer(dragged.map((d) => ({
7404
+ id: d.id,
7405
+ sourceContainerId: d.arg.sourceContainerId
7406
+ })), container.id)) {
7407
+ const srcLayout = layoutDep.getLayout(srcId);
7408
+ const srcNode = scene.get(asNodeId(srcId));
7409
+ if (!srcLayout || !srcNode) continue;
7410
+ const srcContainer = {
7411
+ id: srcId,
7412
+ bounds: composeWorldPose(poseAdapter, srcId, pc.compose)
7413
+ };
7414
+ const srcChildren = scene.childrenOf(asNodeId(srcId)).filter((cid) => !leaving.has(cid)).map((cid) => ({
7415
+ id: cid,
7416
+ pose: composeWorldPose(poseAdapter, cid, pc.compose)
7417
+ }));
7418
+ for (const [cid, pose] of srcLayout.childPoses(srcContainer, srcChildren)) {
7419
+ const cur = composeWorldPose(poseAdapter, cid, pc.compose);
7420
+ const next = pose;
7421
+ const same2 = cur.x === next.x && cur.y === next.y && cur.width === next.width && cur.height === next.height;
7422
+ if (same2) continue;
7423
+ sourceReflow.set(cid, pose);
7424
+ const parent = scene.get(asNodeId(cid))?.parent ?? null;
7425
+ scratch.previews.set(asNodeId(cid), rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose));
7426
+ reflowIds.add(asNodeId(cid));
7427
+ }
7428
+ }
7429
+ scratch.layoutPass = { layout, container, placements, sourceReflow, reflowIds };
7430
+ }
7431
+ function groupBySourceContainer(dragged, exclude) {
7432
+ const out = /* @__PURE__ */ new Map();
7433
+ for (const d of dragged) {
7434
+ const src = d.sourceContainerId;
7435
+ if (!src || src === exclude) continue;
7436
+ let set = out.get(src);
7437
+ if (!set) {
7438
+ set = /* @__PURE__ */ new Set();
7439
+ out.set(src, set);
7440
+ }
7441
+ set.add(d.id);
7442
+ }
7443
+ return out;
7444
+ }
7445
+ function translateCommitOps(scratch, ids, dx, dy) {
7446
+ const m = [1, 0, 0, 1, dx, dy];
7447
+ const ops = [];
7448
+ for (const id of ids) {
7449
+ const origin = scratch.startPoses.get(id);
7450
+ if (origin === void 0) continue;
7451
+ ops.push(createTransformOp({
7452
+ id,
7453
+ from: origin,
7454
+ to: translatePoseGeneric(origin, dx, dy, scratch.projection),
7455
+ label: "Move"
7456
+ }));
7457
+ if (scratch.geometryProjection) {
7458
+ const node = scratch.scene.get(id);
7459
+ if (node) {
7460
+ const dataOp = geometryDataOp(
7461
+ scratch.geometryProjection,
7462
+ { id, data: node.data, pose: origin },
7463
+ m,
7464
+ "Move"
7465
+ );
7466
+ if (dataOp) ops.push(dataOp);
7101
7467
  }
7102
7468
  }
7103
7469
  }
7104
- scratch.layoutPass = { layout, container, children, target, sourceReflow, reflowIds };
7470
+ return ops;
7471
+ }
7472
+ function unreleasedIds(scratch, released) {
7473
+ const selected = new Set(scratch.ids);
7474
+ const out = scratch.ids.filter((id) => !released.has(id));
7475
+ for (const id of scratch.cascadeIds) {
7476
+ let cur = scratch.scene.get(id)?.parent ?? null;
7477
+ let claimed = false;
7478
+ while (cur) {
7479
+ if (released.has(cur)) {
7480
+ claimed = true;
7481
+ break;
7482
+ }
7483
+ if (selected.has(cur)) break;
7484
+ cur = scratch.scene.get(cur)?.parent ?? null;
7485
+ }
7486
+ if (!claimed) out.push(id);
7487
+ }
7488
+ return out;
7105
7489
  }
7106
7490
  function scenePoseAdapter(scene) {
7107
7491
  return {
@@ -7286,7 +7670,7 @@ var moveAction = {
7286
7670
  scratch.previews.set(id, translatePoseGeneric(ori, dx, dy, scratch.projection));
7287
7671
  }
7288
7672
  scratch.layoutPass = null;
7289
- if (scratch.ids.length === 1) runLayoutPass(scratch, moveCtx);
7673
+ runLayoutPass(scratch, moveCtx);
7290
7674
  syncPreviewOverrides(scratch);
7291
7675
  },
7292
7676
  onEnd(endCtx, reason) {
@@ -7321,25 +7705,17 @@ var moveAction = {
7321
7705
  scratch.previews.clear();
7322
7706
  return;
7323
7707
  }
7324
- if (scratch.layout && scratch.ids.length === 1 && scratch.layoutPass) {
7708
+ if (scratch.layout && scratch.layoutPass) {
7325
7709
  const lp = scratch.layoutPass;
7326
7710
  const pc2 = scratch.pc;
7327
- const draggedId = scratch.ids[0];
7328
7711
  const commitAdapter = scenePoseAdapter(scratch.scene);
7329
- const startWorld = composeWorldPose(commitAdapter, draggedId, pc2.compose);
7330
- const sourceContainerId = scratch.scene.get(draggedId)?.parent ?? null;
7331
- const draggedArg = {
7332
- id: draggedId,
7333
- originPose: startWorld,
7334
- pose: { ...startWorld, x: startWorld.x + dx, y: startWorld.y + dy },
7335
- sourceContainerId
7336
- };
7337
7712
  const destId = lp.container.id;
7713
+ const draggedIds = new Set(scratch.ids.map((id) => id));
7338
7714
  const rebaseOpToLocal = (op) => {
7339
7715
  if (op.name !== "transform") return op;
7340
7716
  const a = op.args;
7341
7717
  const curParent = scratch.scene.get(asNodeId(a.id))?.parent ?? null;
7342
- const toParent = a.id === draggedId ? destId : curParent;
7718
+ const toParent = draggedIds.has(a.id) ? destId : curParent;
7343
7719
  return createTransformOp({
7344
7720
  id: a.id,
7345
7721
  from: rebaseLocalPose(commitAdapter, a.from, curParent, pc2.compose, pc2.decompose),
@@ -7348,15 +7724,24 @@ var moveAction = {
7348
7724
  coalesceKey: a.coalesceKey
7349
7725
  });
7350
7726
  };
7351
- const dropOps = lp.layout.commitDrop(lp.container, lp.children, draggedArg, lp.target).map(rebaseOpToLocal);
7352
7727
  const reparentOps = [];
7353
- if (draggedArg.sourceContainerId !== destId) {
7354
- reparentOps.push(createReparentOp({
7355
- id: draggedId,
7356
- fromParentId: draggedArg.sourceContainerId,
7357
- toParentId: destId,
7358
- label: "Reparent"
7359
- }));
7728
+ const dropOps = [];
7729
+ for (const placement of lp.placements) {
7730
+ if (placement.dragged.sourceContainerId !== destId) {
7731
+ reparentOps.push(createReparentOp({
7732
+ id: placement.dragged.id,
7733
+ fromParentId: placement.dragged.sourceContainerId,
7734
+ toParentId: destId,
7735
+ label: "Reparent"
7736
+ }));
7737
+ }
7738
+ const ops2 = lp.layout.commitDrop(
7739
+ lp.container,
7740
+ placement.children,
7741
+ placement.dragged,
7742
+ placement.target
7743
+ );
7744
+ for (const op of ops2) dropOps.push(rebaseOpToLocal(op));
7360
7745
  }
7361
7746
  const reflowOps = [];
7362
7747
  for (const [cid, worldPose] of lp.sourceReflow) {
@@ -7375,38 +7760,82 @@ var moveAction = {
7375
7760
  scratch.previews.clear();
7376
7761
  return;
7377
7762
  }
7763
+ const released = /* @__PURE__ */ new Set();
7764
+ const releasedOps = [];
7765
+ if (scratch.layout && !scratch.layoutPass) {
7766
+ const pc2 = scratch.pc;
7767
+ const commitAdapter = scenePoseAdapter(scratch.scene);
7768
+ const boundsOf2 = scratch.projection?.getBounds ?? AUTO_POSE_DESCRIPTOR.getBounds;
7769
+ const rebaseReleased = (op) => {
7770
+ if (op.name !== "transform") return op;
7771
+ const a = op.args;
7772
+ const parent = scratch.scene.get(asNodeId(a.id))?.parent ?? null;
7773
+ return createTransformOp({
7774
+ id: a.id,
7775
+ from: rebaseLocalPose(commitAdapter, a.from, parent, pc2.compose, pc2.decompose),
7776
+ to: rebaseLocalPose(commitAdapter, a.to, parent, pc2.compose, pc2.decompose),
7777
+ label: a.label,
7778
+ coalesceKey: a.coalesceKey
7779
+ });
7780
+ };
7781
+ const sources = groupBySourceContainer(
7782
+ scratch.ids.map((id) => ({
7783
+ id,
7784
+ sourceContainerId: scratch.scene.get(id)?.parent ?? null
7785
+ })),
7786
+ null
7787
+ );
7788
+ for (const [srcId, leaving] of sources) {
7789
+ const srcLayout = scratch.layout.getLayout(srcId);
7790
+ if (!srcLayout?.releaseDrop) continue;
7791
+ const srcContainer = {
7792
+ id: srcId,
7793
+ bounds: boundsOf2(
7794
+ composeWorldPose(commitAdapter, srcId, pc2.compose)
7795
+ )
7796
+ };
7797
+ let srcChildren = scratch.scene.childrenOf(asNodeId(srcId)).filter((cid) => !leaving.has(cid)).map((cid) => ({
7798
+ id: cid,
7799
+ pose: composeWorldPose(commitAdapter, cid, pc2.compose)
7800
+ }));
7801
+ for (const id of leaving) {
7802
+ const startWorld = composeWorldPose(commitAdapter, id, pc2.compose);
7803
+ const ops = srcLayout.releaseDrop(srcContainer, srcChildren, {
7804
+ id,
7805
+ originPose: startWorld,
7806
+ pose: { ...startWorld, x: startWorld.x + dx, y: startWorld.y + dy },
7807
+ sourceContainerId: srcId
7808
+ });
7809
+ if (ops === null) continue;
7810
+ released.add(id);
7811
+ let landed = startWorld;
7812
+ for (const op of ops) {
7813
+ const a = op.args;
7814
+ if (op.name === "transform" && a.id === id) landed = a.to;
7815
+ releasedOps.push(rebaseReleased(op));
7816
+ }
7817
+ srcChildren = [...srcChildren, { id, pose: landed }];
7818
+ }
7819
+ }
7820
+ }
7821
+ if (released.size > 0) {
7822
+ const ops = [
7823
+ ...releasedOps,
7824
+ ...translateCommitOps(scratch, unreleasedIds(scratch, released), dx, dy)
7825
+ ];
7826
+ if (ops.length > 0) commitOps(ops, ops[0].label ?? "Move");
7827
+ scratch.previews.clear();
7828
+ return;
7829
+ }
7378
7830
  const resolved = resolveParams(opts?.params);
7379
7831
  const reparentMode = resolved?.["reparentOnDrop"] ?? "off";
7380
7832
  const dropTarget = reparentMode !== "off" ? resolveDropTarget(endCtx, scratch) : null;
7381
7833
  if (dropTarget) {
7382
7834
  scratch.scene.batch("Move", () => {
7383
7835
  applyReparent(scratch, dropTarget, reparentMode, dx, dy, scratch.pc);
7384
- });
7385
- } else {
7386
- const m = [1, 0, 0, 1, dx, dy];
7387
- const ops = [];
7388
- for (const id of [...scratch.ids, ...scratch.cascadeIds]) {
7389
- const origin2 = scratch.startPoses.get(id);
7390
- if (origin2 === void 0) continue;
7391
- ops.push(createTransformOp({
7392
- id,
7393
- from: origin2,
7394
- to: translatePoseGeneric(origin2, dx, dy, scratch.projection),
7395
- label: "Move"
7396
- }));
7397
- if (scratch.geometryProjection) {
7398
- const node = scratch.scene.get(id);
7399
- if (node) {
7400
- const dataOp = geometryDataOp(
7401
- scratch.geometryProjection,
7402
- { id, data: node.data, pose: origin2 },
7403
- m,
7404
- "Move"
7405
- );
7406
- if (dataOp) ops.push(dataOp);
7407
- }
7408
- }
7409
- }
7836
+ });
7837
+ } else {
7838
+ const ops = translateCommitOps(scratch, [...scratch.ids, ...scratch.cascadeIds], dx, dy);
7410
7839
  if (ops.length > 0) commitOps(ops, "Move");
7411
7840
  }
7412
7841
  dropPreviewOverrides(scratch);
@@ -7477,6 +7906,7 @@ function defaultCommitAdapter(scene) {
7477
7906
  getSelection: () => [...scene.getSelection()],
7478
7907
  setSelection: (ids) => scene.setSelection(ids.map(asNodeId)),
7479
7908
  getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,
7909
+ getRemovalClosure: (ids) => [...scene.removalClosure(ids.map(asNodeId))],
7480
7910
  getChildren: (parentId) => parentId === null ? [...scene.roots] : [...scene.childrenOf(asNodeId(parentId))],
7481
7911
  // Re-stamp each slot whose occupant changed. `scene.reorder` mutates the
7482
7912
  // live sibling list in place, so re-read it each iteration and skip slots
@@ -7505,7 +7935,13 @@ function defaultCommitAdapter(scene) {
7505
7935
  // Sibling-index passthrough: undo of a delete (or a boolean op's
7506
7936
  // source removal) re-inserts at the recorded z-order slot instead
7507
7937
  // of appending at the end.
7508
- ...index !== void 0 ? { index } : {}
7938
+ ...index !== void 0 ? { index } : {},
7939
+ // The function-valued fields travel with the node, or undo of a delete
7940
+ // brings an edge back as a static path that never follows its endpoints
7941
+ // again, and a clipped container back with nothing clipped.
7942
+ ...node.dependsOn !== void 0 ? { dependsOn: node.dependsOn } : {},
7943
+ ...node.derivePath !== void 0 ? { derivePath: node.derivePath } : {},
7944
+ ...node.kind === "container" && node.clipFromPose !== void 0 ? { clipFromPose: node.clipFromPose } : {}
7509
7945
  })
7510
7946
  };
7511
7947
  }
@@ -8681,15 +9117,40 @@ var viewportDragPanAction = {
8681
9117
  enabled: () => true
8682
9118
  };
8683
9119
 
9120
+ // src/core/viewport/wheelHandler.ts
9121
+ var WHEEL_ZOOM_STEP = 1.1;
9122
+ function wheelZoomFactor(deltaY) {
9123
+ return Math.pow(WHEEL_ZOOM_STEP, -deltaY / 100);
9124
+ }
9125
+ function wheelZoom(view, anchor, deltaY, clamp) {
9126
+ return zoomAt(view, anchor, wheelZoomFactor(deltaY), clamp);
9127
+ }
9128
+ function wheelPan(view, delta3, opts = {}) {
9129
+ const axis = opts.axis ?? "both";
9130
+ const swapped = opts.swapAxis === true;
9131
+ const screenX = swapped ? delta3.deltaX !== 0 ? delta3.deltaX : delta3.deltaY : delta3.deltaX;
9132
+ const screenY = swapped ? 0 : delta3.deltaY;
9133
+ return {
9134
+ scale: view.scale,
9135
+ x: view.x + (axis === "y" ? 0 : screenX / view.scale.x),
9136
+ y: view.y + (axis === "x" ? 0 : screenY / view.scale.y)
9137
+ };
9138
+ }
9139
+ function computeWheelAction(view, input, clamp, opts = {}) {
9140
+ if (input.ctrlKey || input.metaKey) {
9141
+ return wheelZoom(view, { x: input.x, y: input.y }, input.deltaY, clamp);
9142
+ }
9143
+ return wheelPan(view, input, { axis: opts.axis, swapAxis: input.shiftKey === true });
9144
+ }
9145
+
8684
9146
  // src/interactions/actions/defaults/viewportZoom.ts
8685
9147
  var KEY_STEP = 1.25;
8686
9148
  function keyAnchor(view) {
8687
9149
  const size = view.hostSize?.();
8688
9150
  return size ? { x: size.width / 2, y: size.height / 2 } : { x: 0, y: 0 };
8689
9151
  }
8690
- var WHEEL_STEP = 1.1;
8691
9152
  function makeViewportZoomAction(opts = {}) {
8692
- const clamp2 = { min: opts.min ?? DEFAULT_MIN_ZOOM, max: opts.max ?? DEFAULT_MAX_ZOOM };
9153
+ const clamp = { min: opts.min ?? DEFAULT_MIN_ZOOM, max: opts.max ?? DEFAULT_MAX_ZOOM };
8693
9154
  const wheelSpec = opts.wheel === "plain" ? { kind: "wheel" } : { kind: "wheel", mods: { mod: true } };
8694
9155
  const rawAnimate = opts.animate === true ? {} : opts.animate || null;
8695
9156
  const { resetMs, ...animateRest } = rawAnimate ?? {};
@@ -8749,15 +9210,14 @@ function makeViewportZoomAction(opts = {}) {
8749
9210
  const deltaY = params?.deltaY ?? 0;
8750
9211
  const clientX = params?.clientX ?? 0;
8751
9212
  const clientY = params?.clientY ?? 0;
8752
- const factor = Math.pow(WHEEL_STEP, -deltaY / 100);
8753
- view.set(zoomAt(current, { x: clientX, y: clientY }, factor, clamp2));
9213
+ view.set(wheelZoom(current, { x: clientX, y: clientY }, deltaY, clamp));
8754
9214
  break;
8755
9215
  }
8756
9216
  case "in":
8757
- stepTo(zoomAt(stepFrom(), keyAnchor(view), KEY_STEP, clamp2));
9217
+ stepTo(zoomAt(stepFrom(), keyAnchor(view), KEY_STEP, clamp));
8758
9218
  break;
8759
9219
  case "out":
8760
- stepTo(zoomAt(stepFrom(), keyAnchor(view), 1 / KEY_STEP, clamp2));
9220
+ stepTo(zoomAt(stepFrom(), keyAnchor(view), 1 / KEY_STEP, clamp));
8761
9221
  break;
8762
9222
  case "reset": {
8763
9223
  const target = view.recenter ? view.recenter() : { x: 0, y: 0, scale: { x: 1, y: 1 } };
@@ -8766,7 +9226,7 @@ function makeViewportZoomAction(opts = {}) {
8766
9226
  }
8767
9227
  default:
8768
9228
  if (params === void 0) {
8769
- view.set(zoomAt(current, { x: 0, y: 0 }, KEY_STEP, clamp2));
9229
+ view.set(zoomAt(current, { x: 0, y: 0 }, KEY_STEP, clamp));
8770
9230
  }
8771
9231
  break;
8772
9232
  }
@@ -8801,15 +9261,9 @@ var viewportWheelPanAction = {
8801
9261
  const axis = params?.axis ?? "both";
8802
9262
  const deltaX = params?.deltaX ?? 0;
8803
9263
  const deltaY = params?.deltaY ?? 0;
8804
- const current = view.get();
8805
- const swap = params?.swapAxis === true;
8806
- const dx = swap ? (deltaX !== 0 ? deltaX : deltaY) / current.scale.x : deltaX / current.scale.x;
8807
- const dy = swap ? 0 : deltaY / current.scale.y;
8808
- view.set({
8809
- ...current,
8810
- x: current.x + (axis === "y" ? 0 : dx),
8811
- y: current.y + (axis === "x" ? 0 : dy)
8812
- });
9264
+ view.set(
9265
+ wheelPan(view.get(), { deltaX, deltaY }, { axis, swapAxis: params?.swapAxis === true })
9266
+ );
8813
9267
  }
8814
9268
  },
8815
9269
  enabled: () => true
@@ -9457,7 +9911,7 @@ var sliceAction = {
9457
9911
 
9458
9912
  // src/interactions/actions/defaults/pinchZoom.ts
9459
9913
  function makePinchZoomAction(opts = {}) {
9460
- const clamp2 = { min: opts.min ?? DEFAULT_MIN_ZOOM, max: opts.max ?? DEFAULT_MAX_ZOOM };
9914
+ const clamp = { min: opts.min ?? DEFAULT_MIN_ZOOM, max: opts.max ?? DEFAULT_MAX_ZOOM };
9461
9915
  return {
9462
9916
  id: "viewport.pinchZoom",
9463
9917
  label: "Pinch Zoom",
@@ -9485,7 +9939,7 @@ function makePinchZoomAction(opts = {}) {
9485
9939
  const prev = scratch.centroid;
9486
9940
  scratch.centroid = centroid;
9487
9941
  const current = scratch.view.get();
9488
- const zoomed = zoomAt(current, prev, factor, clamp2);
9942
+ const zoomed = zoomAt(current, prev, factor, clamp);
9489
9943
  scratch.view.set({
9490
9944
  scale: zoomed.scale,
9491
9945
  x: zoomed.x - (centroid.x - prev.x) / zoomed.scale.x,
@@ -9507,31 +9961,14 @@ function hostIndex(scene, id) {
9507
9961
  const siblings = node?.parent == null ? scene.roots : scene.childrenOf(node.parent);
9508
9962
  return siblings.indexOf(id);
9509
9963
  }
9510
- function coveredByEmitted(scene, id, emitted) {
9511
- const seen = /* @__PURE__ */ new Set([id]);
9512
- const stack = [id];
9513
- while (stack.length > 0) {
9514
- const node = scene.get(stack.pop());
9515
- if (!node) continue;
9516
- const up = node.parent == null ? node.dependsOn ?? [] : [node.parent, ...node.dependsOn ?? []];
9517
- for (const next of up) {
9518
- if (seen.has(next)) continue;
9519
- if (emitted.has(next)) return true;
9520
- seen.add(next);
9521
- stack.push(next);
9522
- }
9523
- }
9524
- return false;
9525
- }
9526
9964
  function buildDeleteOps(scene, ids, label) {
9527
- const emitted = /* @__PURE__ */ new Set();
9965
+ const covered = /* @__PURE__ */ new Set();
9528
9966
  const ops = [];
9529
9967
  for (const id of ids) {
9530
9968
  const node = scene.get(id);
9531
9969
  if (!node) continue;
9532
- if (emitted.has(id)) continue;
9533
- if (coveredByEmitted(scene, id, emitted)) continue;
9534
- emitted.add(id);
9970
+ if (covered.has(id)) continue;
9971
+ for (const taken of scene.removalClosure([id])) covered.add(taken);
9535
9972
  ops.push(createDeleteOp({
9536
9973
  node,
9537
9974
  index: hostIndex(scene, id),
@@ -9755,7 +10192,10 @@ var selectAllAction = {
9755
10192
  timing: "immediate",
9756
10193
  run: (deps) => {
9757
10194
  const scene = deps.scene;
9758
- const all = scene?.renderOrder ? [...scene.renderOrder()] : [];
10195
+ const hidden = new Set(
10196
+ (scene?.layers ?? []).filter((l) => !l.visible).map((l) => l.id)
10197
+ );
10198
+ const all = hidden.size === 0 || !scene?.renderOrderNodes ? scene?.renderOrder ? [...scene.renderOrder()] : [] : scene.renderOrderNodes().filter((n) => !hidden.has(n.layer)).map((n) => n.id);
9759
10199
  if (all.length === 0) return;
9760
10200
  deps.selection?.set(all);
9761
10201
  }
@@ -11773,7 +12213,7 @@ var setStrokeOpacityAction = createPaintAction({
11773
12213
  readParams: (_prev, params) => params?.alpha01 ?? null,
11774
12214
  merge: (prev, alpha01) => {
11775
12215
  const base = prev ?? strokeOf(DEFAULT_STROKE_COLOR);
11776
- return { ...base, paint: paintWithAlpha(base.paint, alpha01) };
12216
+ return { ...base, paint: paintWithAlpha(base.paint ?? solid(DEFAULT_STROKE_COLOR), alpha01) };
11777
12217
  }
11778
12218
  });
11779
12219
 
@@ -12243,6 +12683,7 @@ var KIT_STANDARD_DESCRIPTORS = [
12243
12683
  clipboardCopyAction,
12244
12684
  clipboardCutAction
12245
12685
  ];
12686
+ var KIT_STANDARD_ACTION_IDS = KIT_STANDARD_DESCRIPTORS.map((a) => a.id);
12246
12687
  function useStandardActions(opts) {
12247
12688
  const optsRef = useRef(opts);
12248
12689
  optsRef.current = opts;
@@ -12275,13 +12716,15 @@ function useStandardActions(opts) {
12275
12716
  const reg = useActionsRegistry();
12276
12717
  const regRef = useRef(reg);
12277
12718
  regRef.current = reg;
12719
+ const excludeKey = opts.exclude ? [...opts.exclude].join("\0") : "";
12278
12720
  useEffect(() => {
12279
12721
  if (!reg || !depReg) return;
12280
- const unregisters = KIT_STANDARD_DESCRIPTORS.map((a) => reg.register(a));
12722
+ const excluded = new Set(excludeKey ? excludeKey.split("\0") : []);
12723
+ const unregisters = KIT_STANDARD_DESCRIPTORS.filter((a) => !excluded.has(a.id)).map((a) => reg.register(a));
12281
12724
  return () => {
12282
12725
  for (const u of unregisters) u();
12283
12726
  };
12284
- }, [reg, depReg]);
12727
+ }, [reg, depReg, excludeKey]);
12285
12728
  }
12286
12729
  var IDENTITY_EXPAND2 = (ids) => ids;
12287
12730
  var EMPTY = Object.freeze([]);
@@ -13599,9 +14042,18 @@ function useGestureDispatcher(opts) {
13599
14042
  };
13600
14043
  const heldKeys = /* @__PURE__ */ new Set();
13601
14044
  const held = /* @__PURE__ */ new Map();
13602
- const heldGeometry = () => computeMultiTouchGeometry(
13603
- [...held.values()].map((p) => p.pos)
13604
- );
14045
+ const toCanvasLocal = (p) => {
14046
+ const rect = canvas?.getBoundingClientRect();
14047
+ if (!rect) return p;
14048
+ const [x, y] = clientToCanvasRect(rect, p.x, p.y);
14049
+ return { x, y };
14050
+ };
14051
+ const heldGeometry = () => {
14052
+ const { centroid, spread } = computeMultiTouchGeometry(
14053
+ [...held.values()].map((p) => p.pos)
14054
+ );
14055
+ return { centroid: toCanvasLocal(centroid), spread };
14056
+ };
13605
14057
  let multiTouchStart = null;
13606
14058
  const TAP_THRESHOLD_PX = 8;
13607
14059
  const DRAG_THRESHOLD_PX = 4;
@@ -13651,7 +14103,7 @@ function useGestureDispatcher(opts) {
13651
14103
  const DOUBLE_CLICK_MAX_MS = 600;
13652
14104
  const DOUBLE_CLICK_MAX_PX = 8;
13653
14105
  const onKeyDown = (e) => {
13654
- if (isEditableTarget2(e.target)) return;
14106
+ if (isEditableTarget(e.target)) return;
13655
14107
  if (e.repeat && heldKeys.has(e.key)) {
13656
14108
  e.preventDefault();
13657
14109
  return;
@@ -13989,10 +14441,12 @@ function useGestureDispatcher(opts) {
13989
14441
  }
13990
14442
  const start = multiTouchStart;
13991
14443
  if (start) {
13992
- const { centroid } = computeMultiTouchGeometry([
13993
- ...[...held.values()].map((p) => p.pos),
13994
- { x: e.clientX, y: e.clientY }
13995
- ]);
14444
+ const centroid = toCanvasLocal(
14445
+ computeMultiTouchGeometry([
14446
+ ...[...held.values()].map((p) => p.pos),
14447
+ { x: e.clientX, y: e.clientY }
14448
+ ]).centroid
14449
+ );
13996
14450
  const dx = centroid.x - start.centroid.x;
13997
14451
  const dy = centroid.y - start.centroid.y;
13998
14452
  if (Math.hypot(dx, dy) <= TAP_THRESHOLD_PX) {
@@ -14161,7 +14615,7 @@ function useGestureDispatcher(opts) {
14161
14615
  });
14162
14616
  };
14163
14617
  const onPaste = (e) => {
14164
- if (isEditableTarget2(e.target)) return;
14618
+ if (isEditableTarget(e.target)) return;
14165
14619
  const cd = e.clipboardData;
14166
14620
  if (!cd) return;
14167
14621
  const items = itemsFromClipboardData(cd);
@@ -14222,33 +14676,6 @@ function useGestureDispatcher(opts) {
14222
14676
  };
14223
14677
  }, [enabled, keyboard, canvasRef]);
14224
14678
  }
14225
-
14226
- // src/core/viewport/wheelHandler.ts
14227
- function computeWheelAction(state, input, bounds = { min: DEFAULT_MIN_ZOOM, max: DEFAULT_MAX_ZOOM }) {
14228
- if (input.shiftKey) {
14229
- return {
14230
- zoom: state.zoom,
14231
- panX: state.panX - input.deltaY,
14232
- panY: state.panY
14233
- };
14234
- }
14235
- if (input.metaKey) {
14236
- return {
14237
- zoom: state.zoom,
14238
- panX: state.panX,
14239
- panY: state.panY - input.deltaY
14240
- };
14241
- }
14242
- const factor = input.deltaY < 0 ? 1.1 : 0.9;
14243
- const newZoom = Math.min(bounds.max, Math.max(bounds.min, state.zoom * factor));
14244
- const worldX = (input.mouseX - state.panX) / state.zoom;
14245
- const worldY = (input.mouseY - state.panY) / state.zoom;
14246
- return {
14247
- zoom: newZoom,
14248
- panX: input.mouseX - worldX * newZoom,
14249
- panY: input.mouseY - worldY * newZoom
14250
- };
14251
- }
14252
14679
  function useVelocityTracker() {
14253
14680
  return useMemo(() => createVelocityTracker(), []);
14254
14681
  }
@@ -16373,85 +16800,64 @@ function UnknownIcon({ className, size = 20 }) {
16373
16800
  }
16374
16801
  );
16375
16802
  }
16376
- function flattenVerts(poly2) {
16377
- const flat = new Array(poly2.length * 2);
16378
- for (let i = 0; i < poly2.length; i++) {
16379
- flat[i * 2] = poly2[i].x;
16380
- flat[i * 2 + 1] = poly2[i].y;
16381
- }
16382
- return flat;
16383
- }
16384
- function polygonContainsRectCenter(poly2, rect) {
16385
- return pointInPolygon(flattenVerts(poly2), rect.x + rect.width / 2, rect.y + rect.height / 2);
16386
- }
16387
- function polygonContainsRect(poly2, rect) {
16388
- if (poly2.length < 3) return false;
16389
- const flat = flattenVerts(poly2);
16390
- if (!pointInPolygon(flat, rect.x, rect.y)) return false;
16391
- if (!pointInPolygon(flat, rect.x + rect.width, rect.y)) return false;
16392
- if (!pointInPolygon(flat, rect.x + rect.width, rect.y + rect.height)) return false;
16393
- if (!pointInPolygon(flat, rect.x, rect.y + rect.height)) return false;
16394
- return !anyPolyEdgeCrossesRect(poly2, rect);
16395
- }
16396
- function polygonIntersectsRect(poly2, rect) {
16397
- if (poly2.length < 3) return false;
16398
- const ab = polygonAabb(poly2);
16399
- if (ab.x + ab.width < rect.x || ab.x > rect.x + rect.width) return false;
16400
- if (ab.y + ab.height < rect.y || ab.y > rect.y + rect.height) return false;
16401
- for (const v of poly2) {
16402
- if (v.x >= rect.x && v.x <= rect.x + rect.width && v.y >= rect.y && v.y <= rect.y + rect.height) return true;
16403
- }
16404
- const flat = flattenVerts(poly2);
16405
- if (pointInPolygon(flat, rect.x, rect.y)) return true;
16406
- if (pointInPolygon(flat, rect.x + rect.width, rect.y)) return true;
16407
- if (pointInPolygon(flat, rect.x + rect.width, rect.y + rect.height)) return true;
16408
- if (pointInPolygon(flat, rect.x, rect.y + rect.height)) return true;
16409
- return anyPolyEdgeCrossesRect(poly2, rect);
16410
- }
16411
- function polygonAabb(poly2) {
16412
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
16413
- for (const v of poly2) {
16414
- if (v.x < minX) minX = v.x;
16415
- if (v.y < minY) minY = v.y;
16416
- if (v.x > maxX) maxX = v.x;
16417
- if (v.y > maxY) maxY = v.y;
16418
- }
16419
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
16420
- }
16421
- function anyPolyEdgeCrossesRect(poly2, rect) {
16422
- const r0x = rect.x, r0y = rect.y;
16423
- const r1x = rect.x + rect.width, r1y = rect.y;
16424
- const r2x = rect.x + rect.width, r2y = rect.y + rect.height;
16425
- const r3x = rect.x, r3y = rect.y + rect.height;
16426
- for (let i = 0, j = poly2.length - 1; i < poly2.length; j = i++) {
16427
- const a = poly2[j], b = poly2[i];
16428
- if (segmentsCross(a.x, a.y, b.x, b.y, r0x, r0y, r1x, r1y)) return true;
16429
- if (segmentsCross(a.x, a.y, b.x, b.y, r1x, r1y, r2x, r2y)) return true;
16430
- if (segmentsCross(a.x, a.y, b.x, b.y, r2x, r2y, r3x, r3y)) return true;
16431
- if (segmentsCross(a.x, a.y, b.x, b.y, r3x, r3y, r0x, r0y)) return true;
16432
- }
16433
- return false;
16434
- }
16435
-
16436
- // src/features/paths/pathHitTest.ts
16437
- function extractVertices(path) {
16803
+ function closedSubpaths(path, tolerance) {
16438
16804
  const { commands, coords } = path;
16439
- const verts = [];
16805
+ const out = [];
16806
+ let sub2 = [];
16807
+ let subStartX = 0, subStartY = 0;
16808
+ let curX = 0, curY = 0;
16440
16809
  let ci = 0;
16441
16810
  for (let i = 0; i < commands.length; i++) {
16442
16811
  const cmd = commands[i];
16443
- if (cmd === PATH_M || cmd === PATH_L) {
16444
- verts.push({ x: coords[ci], y: coords[ci + 1] });
16445
- ci += 2;
16446
- } else if (cmd === PATH_Z) {
16447
- break;
16448
- } else {
16449
- throw new Error(
16450
- `pathHitTest: bezier command ${cmd} not supported \u2014 promote path to use pointInPath for bezier paths`
16451
- );
16812
+ switch (cmd) {
16813
+ case PATH_M: {
16814
+ sub2 = [];
16815
+ subStartX = coords[ci];
16816
+ subStartY = coords[ci + 1];
16817
+ curX = subStartX;
16818
+ curY = subStartY;
16819
+ sub2.push(curX, curY);
16820
+ ci += 2;
16821
+ break;
16822
+ }
16823
+ case PATH_L: {
16824
+ curX = coords[ci];
16825
+ curY = coords[ci + 1];
16826
+ sub2.push(curX, curY);
16827
+ ci += 2;
16828
+ break;
16829
+ }
16830
+ case PATH_C: {
16831
+ const x1 = coords[ci], y1 = coords[ci + 1];
16832
+ const x2 = coords[ci + 2], y2 = coords[ci + 3];
16833
+ const x3 = coords[ci + 4], y3 = coords[ci + 5];
16834
+ flattenCubic(curX, curY, x1, y1, x2, y2, x3, y3, tolerance, sub2);
16835
+ curX = x3;
16836
+ curY = y3;
16837
+ ci += 6;
16838
+ break;
16839
+ }
16840
+ case PATH_Q: {
16841
+ const x1 = coords[ci], y1 = coords[ci + 1];
16842
+ const x2 = coords[ci + 2], y2 = coords[ci + 3];
16843
+ flattenQuadratic(curX, curY, x1, y1, x2, y2, tolerance, sub2);
16844
+ curX = x2;
16845
+ curY = y2;
16846
+ ci += 4;
16847
+ break;
16848
+ }
16849
+ case PATH_Z: {
16850
+ if (sub2.length >= 6) out.push(sub2);
16851
+ sub2 = [];
16852
+ curX = subStartX;
16853
+ curY = subStartY;
16854
+ break;
16855
+ }
16856
+ default:
16857
+ throw new Error(`pathHitTest: unknown command ${cmd}`);
16452
16858
  }
16453
16859
  }
16454
- return verts;
16860
+ return out;
16455
16861
  }
16456
16862
  function rectToVerts(r) {
16457
16863
  return [
@@ -16461,7 +16867,7 @@ function rectToVerts(r) {
16461
16867
  { x: r.x, y: r.y + r.height }
16462
16868
  ];
16463
16869
  }
16464
- function flattenVerts2(poly2) {
16870
+ function flattenVerts(poly2) {
16465
16871
  const flat = new Array(poly2.length * 2);
16466
16872
  for (let i = 0; i < poly2.length; i++) {
16467
16873
  flat[i * 2] = poly2[i].x;
@@ -16469,15 +16875,10 @@ function flattenVerts2(poly2) {
16469
16875
  }
16470
16876
  return flat;
16471
16877
  }
16472
- function polygonContainsPolygon(outer, inner) {
16473
- if (inner.length === 0) return false;
16474
- const flatOuter = flattenVerts2(outer);
16475
- return inner.every((p) => pointInPolygon(flatOuter, p.x, p.y));
16476
- }
16477
16878
  function polygonsIntersect(a, b) {
16478
16879
  if (a.length === 0 || b.length === 0) return false;
16479
- if (pointInPolygon(flattenVerts2(a), b[0].x, b[0].y)) return true;
16480
- if (pointInPolygon(flattenVerts2(b), a[0].x, a[0].y)) return true;
16880
+ if (pointInPolygon(flattenVerts(a), b[0].x, b[0].y)) return true;
16881
+ if (pointInPolygon(flattenVerts(b), a[0].x, a[0].y)) return true;
16481
16882
  for (let i = 0; i < a.length; i++) {
16482
16883
  const a0 = a[i], a1 = a[(i + 1) % a.length];
16483
16884
  for (let j = 0; j < b.length; j++) {
@@ -16487,35 +16888,105 @@ function polygonsIntersect(a, b) {
16487
16888
  }
16488
16889
  return false;
16489
16890
  }
16490
- function pathContainsPoint(path, x, y) {
16491
- return pointInPath(path, x, y);
16891
+ function anyEdge(subpaths, visit) {
16892
+ for (const sub2 of subpaths) {
16893
+ const n = sub2.length;
16894
+ for (let i = 0; i < n; i += 2) {
16895
+ const j = (i + 2) % n;
16896
+ if (visit(sub2[i], sub2[i + 1], sub2[j], sub2[j + 1])) return true;
16897
+ }
16898
+ }
16899
+ return false;
16900
+ }
16901
+ function anyEdgeCrossesRect(subpaths, r) {
16902
+ const x0 = r.x, y0 = r.y, x1 = r.x + r.width, y1 = r.y + r.height;
16903
+ return anyEdge(
16904
+ subpaths,
16905
+ (ax, ay, bx, by) => segmentsCross(ax, ay, bx, by, x0, y0, x1, y0) || segmentsCross(ax, ay, bx, by, x1, y0, x1, y1) || segmentsCross(ax, ay, bx, by, x1, y1, x0, y1) || segmentsCross(ax, ay, bx, by, x0, y1, x0, y0)
16906
+ );
16907
+ }
16908
+ function anyEdgeCrossesPolygon(subpaths, poly2) {
16909
+ if (poly2.length === 0) return false;
16910
+ return anyEdge(subpaths, (ax, ay, bx, by) => {
16911
+ for (let i = 0; i < poly2.length; i++) {
16912
+ const p0 = poly2[i], p1 = poly2[(i + 1) % poly2.length];
16913
+ if (segmentsCross(ax, ay, bx, by, p0.x, p0.y, p1.x, p1.y)) return true;
16914
+ }
16915
+ return false;
16916
+ });
16917
+ }
16918
+ function anyVertexInRect(subpaths, r) {
16919
+ for (const sub2 of subpaths) {
16920
+ for (let i = 0; i < sub2.length; i += 2) {
16921
+ const x = sub2[i], y = sub2[i + 1];
16922
+ if (x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height) return true;
16923
+ }
16924
+ }
16925
+ return false;
16926
+ }
16927
+ function anyVertexInPolygon(subpaths, flatPoly) {
16928
+ for (const sub2 of subpaths) {
16929
+ for (let i = 0; i < sub2.length; i += 2) {
16930
+ if (pointInPolygon(flatPoly, sub2[i], sub2[i + 1])) return true;
16931
+ }
16932
+ }
16933
+ return false;
16934
+ }
16935
+ function subpathsOf(path, opts) {
16936
+ return closedSubpaths(path, opts.tolerance ?? DEFAULT_FLATTEN_TOLERANCE);
16492
16937
  }
16493
- function pathContainsRect(path, rect) {
16938
+ function pathContainsPoint(path, x, y, opts = {}) {
16939
+ return pointInPath(path, x, y, opts);
16940
+ }
16941
+ function pathContainsRect(path, rect, opts = {}) {
16494
16942
  if (path.kind === "rect") {
16495
16943
  return rect.x >= path.x && rect.y >= path.y && rect.x + rect.width <= path.x + path.width && rect.y + rect.height <= path.y + path.height;
16496
16944
  }
16497
- return polygonContainsRect(extractVertices(path), rect);
16945
+ const subpaths = subpathsOf(path, opts);
16946
+ if (subpaths.length === 0) return false;
16947
+ for (const c of rectToVerts(rect)) {
16948
+ if (!pointInPath(path, c.x, c.y, opts)) return false;
16949
+ }
16950
+ return !anyVertexInRect(subpaths, rect) && !anyEdgeCrossesRect(subpaths, rect);
16498
16951
  }
16499
- function pathIntersectsRect(path, rect) {
16952
+ function pathIntersectsRect(path, rect, opts = {}) {
16500
16953
  if (path.kind === "rect") {
16501
16954
  return rect.x < path.x + path.width && rect.x + rect.width > path.x && rect.y < path.y + path.height && rect.y + rect.height > path.y;
16502
16955
  }
16503
- return polygonIntersectsRect(extractVertices(path), rect);
16956
+ const subpaths = subpathsOf(path, opts);
16957
+ if (subpaths.length === 0) return false;
16958
+ for (const c of rectToVerts(rect)) {
16959
+ if (pointInPath(path, c.x, c.y, opts)) return true;
16960
+ }
16961
+ if (anyVertexInRect(subpaths, rect)) return true;
16962
+ return anyEdgeCrossesRect(subpaths, rect);
16504
16963
  }
16505
- function pathContainsPolygon(path, polygon) {
16964
+ function pathContainsPolygon(path, polygon, opts = {}) {
16506
16965
  if (polygon.length === 0) return false;
16507
16966
  if (path.kind === "rect") {
16508
16967
  return polygon.every(
16509
16968
  (p) => p.x >= path.x && p.x <= path.x + path.width && p.y >= path.y && p.y <= path.y + path.height
16510
16969
  );
16511
16970
  }
16512
- return polygonContainsPolygon(extractVertices(path), polygon);
16971
+ const subpaths = subpathsOf(path, opts);
16972
+ if (subpaths.length === 0) return false;
16973
+ for (const p of polygon) {
16974
+ if (!pointInPath(path, p.x, p.y, opts)) return false;
16975
+ }
16976
+ return !anyVertexInPolygon(subpaths, flattenVerts(polygon)) && !anyEdgeCrossesPolygon(subpaths, polygon);
16513
16977
  }
16514
- function pathIntersectsPolygon(path, polygon) {
16978
+ function pathIntersectsPolygon(path, polygon, opts = {}) {
16979
+ if (polygon.length === 0) return false;
16515
16980
  if (path.kind === "rect") {
16516
16981
  return polygonsIntersect(rectToVerts(path), polygon);
16517
16982
  }
16518
- return polygonsIntersect(extractVertices(path), polygon);
16983
+ const subpaths = subpathsOf(path, opts);
16984
+ if (subpaths.length === 0) return false;
16985
+ for (const p of polygon) {
16986
+ if (pointInPath(path, p.x, p.y, opts)) return true;
16987
+ }
16988
+ if (anyVertexInPolygon(subpaths, flattenVerts(polygon))) return true;
16989
+ return anyEdgeCrossesPolygon(subpaths, polygon);
16519
16990
  }
16520
16991
 
16521
16992
  // src/features/paths/markerSites.ts
@@ -16908,10 +17379,11 @@ var TEXT_PAINTER = {
16908
17379
  if (boxes.length === 0) return null;
16909
17380
  return rectsToPath(boxes);
16910
17381
  },
16911
- // `filled` is unconditional: a text node's `data.fill: null` still resolves
16912
- // to the default in `ResolvedRun`, so there is no outline-only text to
16913
- // report. The reach is the point — a heavily outlined glyph was unpickable
16914
- // across the width of its own outline.
17382
+ // `filled` is unconditional, `data.fill: null` included: the silhouette
17383
+ // above is line boxes, not glyph ink, so reporting outline-only text as
17384
+ // unfilled would leave it grabbable within a stroke width of a box edge and
17385
+ // nowhere near the letters. The reach still matters — a heavily outlined
17386
+ // glyph was unpickable across the width of its own outline.
16915
17387
  ink: (node, _pose, ctx) => {
16916
17388
  const d = node.data;
16917
17389
  return { filled: true, ...inkReach(resolveNodeStroke(d.stroke), ctx?.scale) };
@@ -16947,7 +17419,7 @@ function resolveNodeFill(fill, fallback) {
16947
17419
  return fill === void 0 ? fallback : fill;
16948
17420
  }
16949
17421
  function resolveNodeStroke(stroke) {
16950
- if (stroke == null || stroke.paint == null) return null;
17422
+ if (stroke?.paint == null) return null;
16951
17423
  return stroke;
16952
17424
  }
16953
17425
  function strokeInPoseFrame(stroke, box) {
@@ -18069,6 +18541,7 @@ function worldOf(region, xf, view) {
18069
18541
  }
18070
18542
 
18071
18543
  // src/affordances/rotationHandle.ts
18544
+ var isAnnulusForm = (paint) => "fill" in paint || "stroke" in paint || "insetPx" in paint;
18072
18545
  var DEFAULT_PAINT = {
18073
18546
  fill: { fill: "solid", color: "rgba(26, 19, 13, 0.12)" },
18074
18547
  insetPx: 10
@@ -18077,7 +18550,7 @@ function createRotationAffordance(opts = {}) {
18077
18550
  const { bandPx = 24, paint = DEFAULT_PAINT, cursor } = opts;
18078
18551
  const annulusPaint = (() => {
18079
18552
  if (!paint) return null;
18080
- if ("paint" in paint) {
18553
+ if (!isAnnulusForm(paint)) {
18081
18554
  return { kind: "annulus", stroke: paint };
18082
18555
  }
18083
18556
  return {
@@ -18273,29 +18746,6 @@ function useTextTool() {
18273
18746
  []
18274
18747
  );
18275
18748
  }
18276
- function usePinchZoomTool(canvasRef, getView, setView, opts = {}) {
18277
- const getViewRef = useRef(getView);
18278
- getViewRef.current = getView;
18279
- const setViewRef = useRef(setView);
18280
- setViewRef.current = setView;
18281
- const { min = DEFAULT_MIN_ZOOM, max = DEFAULT_MAX_ZOOM, enabled = true } = opts;
18282
- const resolveTargetRef = useRef(opts.resolveTarget);
18283
- resolveTargetRef.current = opts.resolveTarget;
18284
- usePinchGesture(canvasRef, (clientAnchor, scaleFactor) => {
18285
- const el = canvasRef.current;
18286
- if (!el) return;
18287
- const target = resolveTargetRef.current?.(clientAnchor.x, clientAnchor.y) ?? null;
18288
- let anchor;
18289
- if (target) {
18290
- anchor = { x: clientAnchor.x - target.origin.left, y: clientAnchor.y - target.origin.top };
18291
- } else {
18292
- const [cx, cy] = clientToCanvas(el, clientAnchor.x, clientAnchor.y);
18293
- anchor = { x: cx, y: cy };
18294
- }
18295
- const newView = zoomAt(target?.view ?? getViewRef.current(), anchor, scaleFactor, { min, max });
18296
- (target ? target.setView : setViewRef.current)(newView);
18297
- }, enabled);
18298
- }
18299
18749
 
18300
18750
  // src/util/constrainTo45.ts
18301
18751
  function constrainTo45(dx, dy) {
@@ -20611,7 +21061,6 @@ function CanvasInner(props, ref) {
20611
21061
  previewIdsExtra,
20612
21062
  previewPoseExtra,
20613
21063
  gestureSource,
20614
- viewport,
20615
21064
  backgroundFill,
20616
21065
  cursorCoordsHud,
20617
21066
  pickHud,
@@ -20620,7 +21069,8 @@ function CanvasInner(props, ref) {
20620
21069
  decorationLayer,
20621
21070
  getIsVisible,
20622
21071
  layerVisibility,
20623
- layerOrder
21072
+ layerOrder,
21073
+ layerGroups
20624
21074
  } = props;
20625
21075
  const resolvedDebugConfig = useMemo(() => {
20626
21076
  if (debugProp === false) return null;
@@ -20773,25 +21223,7 @@ function CanvasInner(props, ref) {
20773
21223
  const glRendererRef = useRef(null);
20774
21224
  const layerCacheRef = useRef(/* @__PURE__ */ new Map());
20775
21225
  const lastResizeRef = useRef(null);
20776
- const pinchConfig = viewport?.pinchZoom === true ? {} : viewport?.pinchZoom || null;
20777
21226
  const viewRegistry = useOptionalViewRegistry();
20778
- const viewRegistryRef = useRef(viewRegistry);
20779
- viewRegistryRef.current = viewRegistry;
20780
- const resolvePinchTarget = useCallback((clientX, clientY) => {
20781
- const reg = viewRegistryRef.current;
20782
- if (!reg) return null;
20783
- const target = reg.resolver.at(null, clientX, clientY);
20784
- if (target.id === null) return null;
20785
- const api = reg.list().find((r) => r.id === target.id)?.target.deps?.().view;
20786
- if (!api) return null;
20787
- return { view: api.get(), setView: api.set, origin: target.origin };
20788
- }, []);
20789
- usePinchZoomTool(
20790
- canvasRef,
20791
- getView,
20792
- setView,
20793
- { ...pinchConfig ?? {}, enabled: pinchConfig !== null, resolveTarget: resolvePinchTarget }
20794
- );
20795
21227
  const noopAdapter = useMemo(
20796
21228
  () => ({
20797
21229
  getPose: () => ({}),
@@ -20883,6 +21315,7 @@ function CanvasInner(props, ref) {
20883
21315
  const selectedIdsForWiring = effectiveSelection.current;
20884
21316
  const {
20885
21317
  helpers: viewHelpers,
21318
+ chromeState: viewChromeState,
20886
21319
  effectiveBoundsOf,
20887
21320
  previewExtraRef
20888
21321
  } = useViewHelpers({
@@ -21051,6 +21484,7 @@ function CanvasInner(props, ref) {
21051
21484
  dpr: dprProp,
21052
21485
  layerVisibility,
21053
21486
  layerOrder,
21487
+ layerGroups,
21054
21488
  shaders
21055
21489
  });
21056
21490
  paintInputsRef.current = {
@@ -21061,6 +21495,7 @@ function CanvasInner(props, ref) {
21061
21495
  dpr: dprProp,
21062
21496
  layerVisibility,
21063
21497
  layerOrder,
21498
+ layerGroups,
21064
21499
  shaders
21065
21500
  };
21066
21501
  const paint = useCallback(() => {
@@ -21075,6 +21510,7 @@ function CanvasInner(props, ref) {
21075
21510
  dpr: dprIn,
21076
21511
  layerVisibility: vis,
21077
21512
  layerOrder: order,
21513
+ layerGroups: groups,
21078
21514
  shaders: paintShaders
21079
21515
  } = paintInputsRef.current;
21080
21516
  sink?.beginFrame();
@@ -21125,7 +21561,9 @@ function CanvasInner(props, ref) {
21125
21561
  order,
21126
21562
  view,
21127
21563
  { width: w, height: h },
21128
- layerCacheRef.current
21564
+ layerCacheRef.current,
21565
+ void 0,
21566
+ groups
21129
21567
  );
21130
21568
  renderer.render(commands, viewToMat3(view));
21131
21569
  paintedVersionRef.current = contentVersionRef.current?.() ?? 0;
@@ -21143,8 +21581,11 @@ function CanvasInner(props, ref) {
21143
21581
  dprProp,
21144
21582
  layerVisibility,
21145
21583
  layerOrder,
21584
+ layerGroups,
21146
21585
  shaderIdKey,
21147
21586
  syncPaint,
21587
+ viewChromeState,
21588
+ getIsVisible,
21148
21589
  requestRedraw
21149
21590
  ]);
21150
21591
  useEffect(() => {
@@ -21355,6 +21796,14 @@ function createDependentsIndex() {
21355
21796
  // src/core/scene/scene.ts
21356
21797
  var defaultIdCounter = 0;
21357
21798
  var defaultGenerateId = () => asNodeId(`n${(defaultIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`);
21799
+ function layerFromSerialized(spec) {
21800
+ const base = {
21801
+ id: spec.id,
21802
+ visible: spec.visible ?? true,
21803
+ locked: spec.locked ?? false
21804
+ };
21805
+ return spec.kind === "user" ? { ...base, kind: "user", name: spec.name ?? spec.id } : { ...base, kind: "system" };
21806
+ }
21358
21807
  function createScene(options) {
21359
21808
  if (!options.systemLayers || options.systemLayers.length === 0) {
21360
21809
  throw new Error("useScene: systemLayers must declare at least one layer");
@@ -21578,6 +22027,28 @@ function createScene(options) {
21578
22027
  state.nodes.get(id).derivePath = spec.derivePath;
21579
22028
  if (cacheForRedo) pendingDerivePathPatches.set(id, spec.derivePath);
21580
22029
  }
22030
+ function restoreNodeFunctions(node, keys) {
22031
+ if (!keys) return;
22032
+ if (node.derivePath === void 0 && keys.derivePathKey !== void 0) {
22033
+ const fn = registry2.derivePath?.[keys.derivePathKey];
22034
+ if (fn) {
22035
+ node.derivePath = fn;
22036
+ pendingDerivePathPatches.set(node.id, fn);
22037
+ } else {
22038
+ dwarn("scene", `kit:remove: derivePathKey "${keys.derivePathKey}" not in this scene's registry \u2014 node "${node.id}" restored without derived geometry. Register a function with this key in the registry option to restore it.`);
22039
+ }
22040
+ }
22041
+ if (node.kind !== "container" || keys.clipKey === void 0) return;
22042
+ const container = node;
22043
+ if (container.clipFromPose !== void 0) return;
22044
+ const clip = registry2.clipFromPose?.[keys.clipKey];
22045
+ if (clip) {
22046
+ container.clipFromPose = clip;
22047
+ pendingClipPatches.set(node.id, clip);
22048
+ } else {
22049
+ dwarn("scene", `kit:remove: clipKey "${keys.clipKey}" not in this scene's registry \u2014 container "${node.id}" restored without clip. Register a function with this key in the registry option to restore the clip.`);
22050
+ }
22051
+ }
21581
22052
  function registerKitOp(kind, handler) {
21582
22053
  registered.set(kind, handler);
21583
22054
  }
@@ -21635,8 +22106,10 @@ function createScene(options) {
21635
22106
  }
21636
22107
  },
21637
22108
  revert: (p) => {
22109
+ const keys = new Map((p.fnKeys ?? []).map((k) => [k.id, k]));
21638
22110
  for (const n of p.nodes) {
21639
22111
  const clone = n.kind === "container" ? { ...n, children: [...n.children] } : { ...n };
22112
+ restoreNodeFunctions(clone, keys.get(n.id));
21640
22113
  state.nodes.set(n.id, clone);
21641
22114
  dependents.add(n.id, clone.dependsOn ?? []);
21642
22115
  }
@@ -21974,6 +22447,9 @@ function createScene(options) {
21974
22447
  remove(id) {
21975
22448
  scene.removeMany([id]);
21976
22449
  },
22450
+ removalClosure(rootIds) {
22451
+ return removalClosure(rootIds).ids;
22452
+ },
21977
22453
  removeMany(rootIds) {
21978
22454
  if (rootIds.length === 0) return;
21979
22455
  const { ids, removing } = removalClosure(rootIds);
@@ -21983,7 +22459,20 @@ function createScene(options) {
21983
22459
  });
21984
22460
  const detached = snapshot.filter((n) => n.parent === null || !removing.has(n.parent)).map((n) => ({ id: n.id, parent: n.parent, index: siblingsOf(n.parent).indexOf(n.id) })).sort((l, r) => l.index - r.index);
21985
22461
  for (const nid of ids) overrides.clear(nid);
21986
- const payload = { nodes: snapshot, detached };
22462
+ const fnKeys = snapshot.flatMap((n) => {
22463
+ const derivePathKey = n.derivePath !== void 0 ? reverseDerivePath.get(n.derivePath) : void 0;
22464
+ const clipKey = n.kind === "container" && n.clipFromPose !== void 0 ? reverseClipFromPose.get(n.clipFromPose) : void 0;
22465
+ return derivePathKey === void 0 && clipKey === void 0 ? [] : [{
22466
+ id: n.id,
22467
+ ...derivePathKey !== void 0 ? { derivePathKey } : {},
22468
+ ...clipKey !== void 0 ? { clipKey } : {}
22469
+ }];
22470
+ });
22471
+ const payload = {
22472
+ nodes: snapshot,
22473
+ detached,
22474
+ ...fnKeys.length > 0 ? { fnKeys } : {}
22475
+ };
21987
22476
  executeAndLog("kit:remove", payload, "remove");
21988
22477
  },
21989
22478
  update(id, patch) {
@@ -22280,6 +22769,10 @@ function createScene(options) {
22280
22769
  const layer = { id: l.id };
22281
22770
  if (l.visible === false) layer.visible = false;
22282
22771
  if (l.locked === true) layer.locked = true;
22772
+ if (l.kind === "user") {
22773
+ layer.kind = "user";
22774
+ layer.name = l.name;
22775
+ }
22283
22776
  return layer;
22284
22777
  });
22285
22778
  return { version: 1, systemLayers, nodes };
@@ -22297,12 +22790,7 @@ function createScene(options) {
22297
22790
  if (state.layerIndex.has(spec.id)) {
22298
22791
  throw new Error(`Scene.loadState: duplicate system layer id "${spec.id}"`);
22299
22792
  }
22300
- state.layers.push({
22301
- kind: "system",
22302
- id: spec.id,
22303
- visible: spec.visible ?? true,
22304
- locked: spec.locked ?? false
22305
- });
22793
+ state.layers.push(layerFromSerialized(spec));
22306
22794
  state.layerIndex.set(spec.id, i);
22307
22795
  }
22308
22796
  history.clear();
@@ -22402,16 +22890,16 @@ function sceneSelectionStore(scene) {
22402
22890
  }
22403
22891
  function sceneFromJSON(json, options) {
22404
22892
  const registry2 = options.registry ?? {};
22405
- const initial = specsFromSerialized(json, registry2);
22406
- return createScene({
22407
- systemLayers: json.systemLayers,
22408
- initial,
22893
+ const scene = createScene({
22894
+ systemLayers: json.systemLayers.map((l) => ({ id: l.id })),
22409
22895
  registry: registry2,
22410
22896
  ...options.historyLimit !== void 0 ? { historyLimit: options.historyLimit } : {},
22411
22897
  ...options.coalesceWindowMs !== void 0 ? { coalesceWindowMs: options.coalesceWindowMs } : {},
22412
22898
  ...options.generateId !== void 0 ? { generateId: options.generateId } : {},
22413
22899
  ...options.ops !== void 0 ? { ops: options.ops } : {}
22414
22900
  });
22901
+ scene.loadState(json);
22902
+ return scene;
22415
22903
  }
22416
22904
  var SelectionContext = createContext(null);
22417
22905
  function SelectionContextProvider({ children }) {
@@ -23154,7 +23642,7 @@ function CanvasView(props) {
23154
23642
  }
23155
23643
  function ActionsProviderIfRoot({ children }) {
23156
23644
  const parent = useActionsRegistry();
23157
- if (parent) return /* @__PURE__ */ jsx(Fragment, { children });
23645
+ if (parent) return /* @__PURE__ */ jsx(ActionsScope, { children });
23158
23646
  return /* @__PURE__ */ jsx(ActionsProvider, { children });
23159
23647
  }
23160
23648
  function useToolActions(tools) {
@@ -23221,6 +23709,65 @@ function PointerPublisher({
23221
23709
  }, [ctx, canvasRef, registry2]);
23222
23710
  return null;
23223
23711
  }
23712
+ function flattenVerts2(poly2) {
23713
+ const flat = new Array(poly2.length * 2);
23714
+ for (let i = 0; i < poly2.length; i++) {
23715
+ flat[i * 2] = poly2[i].x;
23716
+ flat[i * 2 + 1] = poly2[i].y;
23717
+ }
23718
+ return flat;
23719
+ }
23720
+ function polygonContainsRectCenter(poly2, rect) {
23721
+ return pointInPolygon(flattenVerts2(poly2), rect.x + rect.width / 2, rect.y + rect.height / 2);
23722
+ }
23723
+ function polygonContainsRect(poly2, rect) {
23724
+ if (poly2.length < 3) return false;
23725
+ const flat = flattenVerts2(poly2);
23726
+ if (!pointInPolygon(flat, rect.x, rect.y)) return false;
23727
+ if (!pointInPolygon(flat, rect.x + rect.width, rect.y)) return false;
23728
+ if (!pointInPolygon(flat, rect.x + rect.width, rect.y + rect.height)) return false;
23729
+ if (!pointInPolygon(flat, rect.x, rect.y + rect.height)) return false;
23730
+ return !anyPolyEdgeCrossesRect(poly2, rect);
23731
+ }
23732
+ function polygonIntersectsRect(poly2, rect) {
23733
+ if (poly2.length < 3) return false;
23734
+ const ab = polygonAabb(poly2);
23735
+ if (ab.x + ab.width < rect.x || ab.x > rect.x + rect.width) return false;
23736
+ if (ab.y + ab.height < rect.y || ab.y > rect.y + rect.height) return false;
23737
+ for (const v of poly2) {
23738
+ if (v.x >= rect.x && v.x <= rect.x + rect.width && v.y >= rect.y && v.y <= rect.y + rect.height) return true;
23739
+ }
23740
+ const flat = flattenVerts2(poly2);
23741
+ if (pointInPolygon(flat, rect.x, rect.y)) return true;
23742
+ if (pointInPolygon(flat, rect.x + rect.width, rect.y)) return true;
23743
+ if (pointInPolygon(flat, rect.x + rect.width, rect.y + rect.height)) return true;
23744
+ if (pointInPolygon(flat, rect.x, rect.y + rect.height)) return true;
23745
+ return anyPolyEdgeCrossesRect(poly2, rect);
23746
+ }
23747
+ function polygonAabb(poly2) {
23748
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
23749
+ for (const v of poly2) {
23750
+ if (v.x < minX) minX = v.x;
23751
+ if (v.y < minY) minY = v.y;
23752
+ if (v.x > maxX) maxX = v.x;
23753
+ if (v.y > maxY) maxY = v.y;
23754
+ }
23755
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
23756
+ }
23757
+ function anyPolyEdgeCrossesRect(poly2, rect) {
23758
+ const r0x = rect.x, r0y = rect.y;
23759
+ const r1x = rect.x + rect.width, r1y = rect.y;
23760
+ const r2x = rect.x + rect.width, r2y = rect.y + rect.height;
23761
+ const r3x = rect.x, r3y = rect.y + rect.height;
23762
+ for (let i = 0, j = poly2.length - 1; i < poly2.length; j = i++) {
23763
+ const a = poly2[j], b = poly2[i];
23764
+ if (segmentsCross(a.x, a.y, b.x, b.y, r0x, r0y, r1x, r1y)) return true;
23765
+ if (segmentsCross(a.x, a.y, b.x, b.y, r1x, r1y, r2x, r2y)) return true;
23766
+ if (segmentsCross(a.x, a.y, b.x, b.y, r2x, r2y, r3x, r3y)) return true;
23767
+ if (segmentsCross(a.x, a.y, b.x, b.y, r3x, r3y, r0x, r0y)) return true;
23768
+ }
23769
+ return false;
23770
+ }
23224
23771
  function isPathLike(p) {
23225
23772
  if (!p || typeof p !== "object") return false;
23226
23773
  const k = p.kind;
@@ -23402,12 +23949,19 @@ function sceneToAdapter(scene, options = {}) {
23402
23949
  id: node.id,
23403
23950
  ...index !== void 0 ? { index } : {},
23404
23951
  ...node.parent !== null ? { parent: node.parent } : {},
23405
- ...node.kind === "container" && node.clipFromPose ? { clipFromPose: node.clipFromPose } : {}
23952
+ ...node.kind === "container" && node.clipFromPose ? { clipFromPose: node.clipFromPose } : {},
23953
+ // Derivation travels with the node, or undo of a delete brings an
23954
+ // edge back as a static path that never follows its endpoints again.
23955
+ ...node.dependsOn !== void 0 ? { dependsOn: node.dependsOn } : {},
23956
+ ...node.derivePath !== void 0 ? { derivePath: node.derivePath } : {}
23406
23957
  });
23407
23958
  },
23408
23959
  removeNode(id) {
23409
23960
  scene.remove(asNodeId(id));
23410
23961
  },
23962
+ getRemovalClosure(ids) {
23963
+ return [...scene.removalClosure(ids.map(asNodeId))];
23964
+ },
23411
23965
  // AreaSelectAdapter surface — included unconditionally so plain
23412
23966
  // `useSelectTool(sceneToAdapter(scene, { selection }))` Just Works for the
23413
23967
  // marquee gesture. `applyOps` (above) dispatches transiently when called
@@ -25333,17 +25887,18 @@ function useActionsPropResolver(actions) {
25333
25887
  useEffect(() => {
25334
25888
  if (!reg) return;
25335
25889
  const prop = actionsRef.current;
25890
+ const unregisters = [];
25336
25891
  if (prop === null) {
25337
- const ids = reg.list().map((a) => a.id);
25338
- for (const id of ids) reg.unregister(id);
25339
- return;
25892
+ for (const id of reg.list().map((a) => a.id)) unregisters.push(reg.mute(id));
25893
+ return () => {
25894
+ for (const u of unregisters) u();
25895
+ };
25340
25896
  }
25341
25897
  if (!prop) return;
25342
- const unregisters = [];
25343
25898
  const warnedIds = /* @__PURE__ */ new Set();
25344
25899
  for (const [slotId, entry] of Object.entries(prop)) {
25345
25900
  if (entry === null) {
25346
- reg.unregister(slotId);
25901
+ unregisters.push(reg.mute(slotId));
25347
25902
  continue;
25348
25903
  }
25349
25904
  const isFull = (e) => typeof e.id === "string" && typeof e.label === "string" && !!e.invoker;
@@ -25400,7 +25955,7 @@ function useViewportActions(args) {
25400
25955
  r.register(makePinchZoomAction(typeof pinchZoom === "object" ? pinchZoom : {}))
25401
25956
  );
25402
25957
  } else {
25403
- r.unregister("viewport.pinchZoom");
25958
+ unregisters.push(r.mute("viewport.pinchZoom"));
25404
25959
  }
25405
25960
  return () => {
25406
25961
  for (const u of unregisters) u();
@@ -28291,7 +28846,7 @@ function applyOverlayStyle(el, style) {
28291
28846
  el.style.border = "0";
28292
28847
  el.style.outline = "none";
28293
28848
  el.style.background = "transparent";
28294
- el.style.color = "color" in style.fill ? style.fill.color : "#000";
28849
+ el.style.color = style.fill !== null && "color" in style.fill ? style.fill.color : "#000";
28295
28850
  el.style.caretColor = style.caretColor;
28296
28851
  el.style.font = fontString(style);
28297
28852
  el.style.lineHeight = String(style.lineHeight);
@@ -31259,6 +31814,6 @@ function mergeContributions(...bundles) {
31259
31814
  return out;
31260
31815
  }
31261
31816
 
31262
- export { ALWAYS, ANCHOR_HIT_BASE_PX, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, ArrowIcon, BUILTIN_MARKERS, BUNDLE_TOOLS, COARSE_TARGET_SCALE, CURVE_REPS, CanvasView, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_STROKES, DEFAULT_DEBUG_THEME, DEFAULT_DEVICE_PROFILE, DEFAULT_FILL_COLOR, DEFAULT_HANDLE_SIZE2 as DEFAULT_HANDLE_SIZE, DEFAULT_MAX_ZOOM, DEFAULT_MIN_ZOOM, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_SHAPE_FILL, DEFAULT_STROKE_COLOR, DEFAULT_VELOCITY_DECAY, DepRegistryProviderIfRoot, DeviceProfileProvider, DivideIcon, EASINGS, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, HANDLE_BASE_PX, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_JOINT, IDENTITY_POSE_COMPOSITION, ImageIcon, IntersectIcon, KIT_SHAPE_KINDS, LassoIcon, LineIcon, MIXED, MOVE_ANCHORS, MinimapCanvas, NEVER, OUTLINE_MIN_SCREEN_PX, PAINTED_CURSOR_LAYER_ID, PATH_ANCHOR_CHROME_ID, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_ALIGN_PROJECTION, ROTATION_HANDLE_BASE_PX, RectIcon, SPRING_PRESETS, SPRITE_STRIDE, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, ShaderCompileError, StarIcon, SubtractIcon, TOOL_PREF_KINDS, TextIcon, UnionIcon, UnknownIcon, VERSION, VIEW_ANIMATION_KEY, WeaselProvider, WeaselRenderer, _resetMarkersForTests, _resetPaintKindsForTests, aabbCenter, actionIs, actionShortcuts, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, asPaint, axisAlignedBounds, bezierCubic, bezierQuadratic, blendPoses, buildChromeCtx, buildGradientRamp, buildRuleCtx, buildSceneViewCommands, canBringForward, canHover, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, clipboardCopyAction, clipboardCutAction, cloneAction, coarsePointer, composeAffordanceLayer, composePath, composeRectPose, composeSelectionPose, composeWorldPose, computeFitView, computeFitViewport, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDispatcher, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createMoveToIndexOp, createNodeProperties, createNodeRouting, createPaintedCursorLayer, createPaintedCursorState, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createTextLayer, createVelocityTracker, createViewResolver, createViewportLayer, cubicBezierEasing, cycleVertexColors, decomposePath, decomposeRectPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, defineTool, defineViewportTool, deriveAlignmentGuides, deriveParallaxView, describeRule, domPositionToCharOffset, domToRuns, drawLayers, drawOneLayer, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, enterTextEditAction, enumerateAnchors, evaluate, fillInPoseFrame, fillToBoundsFrame, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, forEachCoalesced, frameRect, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getMarker, getNodeShapes, getPaintKind, getStylusData, gradientForBounds, gradientGeometry, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hostAnchorCss, hostAnchorRect, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, interpolateView, isBuiltinToolPref, isEditableTarget2 as isEditableTarget, isGradientFill, isLayerPainted, isLayerVisible, isPatternSpec, keySpecShortcut, lassoSelectAction, lerpColorArray, lerpOklab, lerpOklch, linear, listMarkers, listPaintKinds, liveScope, makePinchZoomAction, makeViewportWheelPanAction, makeViewportZoomAction, markerDrawCommands, markerInset, markerKeyOf, markerSites, mat3, matchAlignment, matchesKeyBinding, meanScale, mergeAlphaFromPrev, mergeContributions, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, nodeHasFlag, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, openPointerSession, or, paintAlpha, paintKindOf, paintWithAlpha, paintWithColor, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInRotatedRect, pointInTextPose, poseRotationOf, pressureToWidth, rainbowVertexColors, rebaseLocalPose, rectCorners, registerContentHandler, registerMarker, registerNodeShape, registerPaintKind, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveDeviceProfile, resolveEasing, resolveFillPattern, resolveMarkerSize, resolveParams, resolvePatternSpec, resolveSkeleton, resolveStrokeWidth, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, sampleGradientStops, sampleTrack, sceneFromJSON, sceneNodeClientRect, sceneSelectionStore, sceneToAdapter, scopeBindings, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, setFlagOverRange, shapeCoversPoint, sliceAction, snapPoint, solid, solidVertexColors, specificity, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, startThresholdDrag, strokeInsets, strokeOf, strokeWith, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, textCommandFromRuns, tileGrid, toHex8, translatePoseViaDescriptor, translateRectPose, trimPolyline, tweenPose, tweenVertexColors, unionAABB, unionBounds, unionBoundsPath, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDeviceProfile, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHostAnchor, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useNodeOverlayFrame, useOptionalActiveToolContext, usePenTool, usePencilTool, usePinchGesture, usePinchZoomTool, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useVisibleRaf, useZoom, viewToMat3, viewToTransform, viewportDragPanAction, viewportWheelPanAction, viewportZoomAction, viewportsAt, visualBoundsViaDescriptor, when, withAlpha01, withCoord, withGradientKind, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
31263
- //# sourceMappingURL=chunk-KIVJXUYE.js.map
31264
- //# sourceMappingURL=chunk-KIVJXUYE.js.map
31817
+ export { ALWAYS, ANCHOR_HIT_BASE_PX, ActionsProviderIfRoot, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, ArrowIcon, BUILTIN_MARKERS, BUNDLE_TOOLS, COARSE_TARGET_SCALE, CURVE_REPS, CanvasView, ColorOverrideRegistry, CropIcon, CursorCoordsHud, DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_DEBUG_STROKES, DEFAULT_DEBUG_THEME, DEFAULT_DEVICE_PROFILE, DEFAULT_FILL_COLOR, DEFAULT_HANDLE_SIZE2 as DEFAULT_HANDLE_SIZE, DEFAULT_MAX_ZOOM, DEFAULT_MIN_ZOOM, DEFAULT_PALETTE, DEFAULT_ROTATION_HANDLE_DISTANCE, DEFAULT_SHAPE_FILL, DEFAULT_STROKE_COLOR, DEFAULT_VELOCITY_DECAY, DepRegistryProviderIfRoot, DeviceProfileProvider, DivideIcon, EASINGS, EFFECT_KIT_UNIFORMS, EFFECT_VERT_SRC, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, GHOST_STROKE, HANDLE_BASE_PX, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_JOINT, IDENTITY_POSE_COMPOSITION, ImageIcon, IntersectIcon, KIT_SHAPE_KINDS, KIT_STANDARD_ACTION_IDS, LassoIcon, LineIcon, MIXED, MOVE_ANCHORS, MinimapCanvas, NEVER, OUTLINE_MIN_SCREEN_PX, PAINTED_CURSOR_LAYER_ID, PATH_ANCHOR_CHROME_ID, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_ALIGN_PROJECTION, ROTATION_HANDLE_BASE_PX, RectIcon, SPRING_PRESETS, SPRITE_STRIDE, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, ShaderCompileError, StarIcon, SubtractIcon, TOOL_PREF_KINDS, TextIcon, UnionIcon, UnknownIcon, VERSION, VIEW_ANIMATION_KEY, WHEEL_ZOOM_STEP, WeaselProvider, WeaselRenderer, _resetMarkersForTests, _resetPaintKindsForTests, aabbCenter, actionIs, actionShortcuts, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, asPaint, axisAlignedBounds, bezierCubic, bezierQuadratic, blendPoses, blur, buildChromeCtx, buildGradientRamp, buildRuleCtx, buildSceneViewCommands, canBringForward, canHover, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, charOffsetToDomPosition, clampView, clearSelectionAction, clientToCanvas, clipboardCopyAction, clipboardCutAction, cloneAction, coarsePointer, composeAffordanceLayer, composePath, composeRectPose, composeSelectionPose, composeWorldPose, computeFitView, computeFitViewport, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createDispatcher, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createMoveToIndexOp, createNodeProperties, createNodeRouting, createPaintedCursorLayer, createPaintedCursorState, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createTextLayer, createVelocityTracker, createViewResolver, createViewportLayer, cubicBezierEasing, cycleVertexColors, decomposePath, decomposeRectPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, defineTool, defineViewportTool, deriveAlignmentGuides, deriveParallaxView, describeRule, domPositionToCharOffset, domToRuns, drawLayers, drawOneLayer, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorsAction, enterTextEditAction, enumerateAnchors, evaluate, fillInPoseFrame, fillToBoundsFrame, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, focused, forEachCoalesced, frameRect, freeform, gateLayer, gesturing, getAlpha01, getImageBitmap, getMarker, getNodeShapes, getPaintKind, getStylusData, gradientForBounds, gradientGeometry, hexToRgba, hitAffordanceRegions, hitAnchor, hitRotationHandle, hostAnchorCss, hostAnchorRect, hovering, hoveringSelected, imageStatus, inferredNodeProperties, inferredNodeRouting, insertAction, interpolateView, isBuiltinToolPref, isEditableTarget, isGradientFill, isLayerPainted, isLayerVisible, isPatternSpec, keySpecShortcut, lassoSelectAction, lerpColorArray, lerpOklab, lerpOklch, linear, listMarkers, listPaintKinds, liveScope, makePinchZoomAction, makeViewportWheelPanAction, makeViewportZoomAction, markerDrawCommands, markerInset, markerKeyOf, markerSites, mat3, matchAlignment, matchesKeyBinding, meanScale, mergeAlphaFromPrev, mergeContributions, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, nodeHasFlag, none, normalizeHex, not, nurbs, oklabToOklch, oklabToSrgbU8, oklchToOklab, openFilePicker, openPointerSession, or, paintAlpha, paintKindOf, paintWithAlpha, paintWithColor, parseColor, parseColorToRgba255, parseDebugFlags, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInRotatedRect, pointInTextPose, poseRotationOf, pressureToWidth, rainbowVertexColors, rebaseLocalPose, rectCorners, registerContentHandler, registerEffect, registerMarker, registerNodeShape, registerPaintKind, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveDeviceProfile, resolveEasing, resolveFillPattern, resolveMarkerSize, resolveParams, resolvePatternSpec, resolveSkeleton, resolveStrokeWidth, resolveVisibility, rgbaToHex, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotatePoint, rotatedRectCorners, rotationDegreesUnit, rotationHandle, roundToCell, runsToDom, sampleGradientStops, sampleTrack, sceneFromJSON, sceneNodeClientRect, sceneSelectionStore, sceneToAdapter, scopeBindings, screenToWorld, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, setFlagOverRange, shapeCoversPoint, sliceAction, snapPoint, solid, solidVertexColors, specificity, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, srgbU8ToOklab, staggerVertexColors, startThresholdDrag, strokeInsets, strokeOf, strokeWith, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, textCommandFromRuns, tileGrid, toHex8, translatePoseViaDescriptor, translateRectPose, trimPolyline, tweenPose, tweenVertexColors, unionAABB, unionBounds, unionBoundsPath, useActiveToolContext, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDeviceProfile, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGestureDispatcher, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHostAnchor, useHoverTracking, useImageTool, useKeybindings, useLassoTool, useLineTool, useNodeOverlayFrame, useOptionalActiveToolContext, usePenTool, usePencilTool, usePinchGesture, usePointerStylus, usePolygonTool, usePublishSelection, useRectTool, useResizePolicy, useRotateTool, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useVisibleRaf, viewToMat3, viewToTransform, viewportDragPanAction, viewportWheelPanAction, viewportZoomAction, viewportsAt, vignette, visualBoundsViaDescriptor, wheelPan, wheelZoom, wheelZoomFactor, when, withAlpha01, withCoord, withGradientKind, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
31818
+ //# sourceMappingURL=chunk-WYPSGIYS.js.map
31819
+ //# sourceMappingURL=chunk-WYPSGIYS.js.map