@weasel-js/core 1.4.1 → 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 +428 -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-NU7D64RL.js → chunk-WYPSGIYS.js} +1142 -595
  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 +128 -237
  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-NU7D64RL.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.1" ;
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
  }
@@ -6204,7 +6605,7 @@ function clientToCanvasRect(rect, clientX, clientY) {
6204
6605
  return [clientX - rect.left, clientY - rect.top];
6205
6606
  }
6206
6607
 
6207
- // src/interactions/gestures/pointerSession/recovery.ts
6608
+ // src/interactions/gestures/pointerSession/pointerSession.ts
6208
6609
  var LOST_CAPTURE_EVENT = "lostpointercapture";
6209
6610
  function reportsButtons(down) {
6210
6611
  return !!down.buttons;
@@ -6212,8 +6613,6 @@ function reportsButtons(down) {
6212
6613
  function isMissedRelease(move) {
6213
6614
  return move.buttons === 0;
6214
6615
  }
6215
-
6216
- // src/interactions/gestures/pointerSession/pointerSession.ts
6217
6616
  function openPointerSession(origin, down, cb, opts = {}) {
6218
6617
  const pointerId = down.pointerId;
6219
6618
  const doc = origin.ownerDocument ?? document;
@@ -6262,7 +6661,8 @@ function openPointerSession(origin, down, cb, opts = {}) {
6262
6661
  if (mine(e)) abort("superseded");
6263
6662
  }
6264
6663
  function onLostCapture(e) {
6265
- if (e.pointerId === pointerId) abort("lostcapture");
6664
+ if (e.pointerId !== pointerId) return;
6665
+ if (!origin.isConnected) abort("lostcapture");
6266
6666
  }
6267
6667
  if (opts.capture !== false) {
6268
6668
  try {
@@ -6510,14 +6910,14 @@ function startThresholdDrag(e, opts) {
6510
6910
  activated = true;
6511
6911
  opts.onActivate?.(ev);
6512
6912
  };
6513
- const session = openPointerSession(e.currentTarget, e, {
6913
+ const session = openPointerSession(opts.origin ?? e.currentTarget, e, {
6514
6914
  onMove: (ev) => {
6515
6915
  maybeActivate(ev);
6516
6916
  if (activated) opts.onMove(ev);
6517
6917
  },
6518
6918
  onEnd: (ev) => {
6519
6919
  if (activated) opts.onCommit(ev);
6520
- else opts.onCancel?.();
6920
+ else opts.onClick?.(ev);
6521
6921
  },
6522
6922
  onCancel: () => {
6523
6923
  opts.onCancel?.();
@@ -6549,16 +6949,16 @@ function targetSizesPx(targetScale = 1) {
6549
6949
  }
6550
6950
 
6551
6951
  // src/core/viewport/fitToBounds.ts
6552
- function fitZoom(availW, availH, contentW, contentH, clamp2) {
6952
+ function fitZoom(availW, availH, contentW, contentH, clamp) {
6553
6953
  const raw = Math.min(availW / contentW, availH / contentH);
6554
- const min = clamp2?.min ?? -Infinity;
6555
- const max = clamp2?.max ?? Infinity;
6954
+ const min = clamp?.min ?? -Infinity;
6955
+ const max = clamp?.max ?? Infinity;
6556
6956
  return Math.min(max, Math.max(min, raw));
6557
6957
  }
6558
- function fitToBounds(viewportW, viewportH, contentW, contentH, paddingPx = 0, clamp2) {
6958
+ function fitToBounds(viewportW, viewportH, contentW, contentH, paddingPx = 0, clamp) {
6559
6959
  const availW = Math.max(1, viewportW - paddingPx * 2);
6560
6960
  const availH = Math.max(1, viewportH - paddingPx * 2);
6561
- const zoom = fitZoom(availW, availH, contentW, contentH, clamp2);
6961
+ const zoom = fitZoom(availW, availH, contentW, contentH, clamp);
6562
6962
  const contentPxW = contentW * zoom;
6563
6963
  const contentPxH = contentH * zoom;
6564
6964
  return {
@@ -6681,128 +7081,6 @@ function sceneNodeClientRect(opts) {
6681
7081
  height: bounds.height * opts.view.scale.y
6682
7082
  };
6683
7083
  }
6684
- var clamp = (z, min, max) => Math.min(max, Math.max(min, z));
6685
- function isEditableTarget(t) {
6686
- if (!t) return false;
6687
- const el = t;
6688
- if (el.isContentEditable) return true;
6689
- const tag = (el.tagName ?? "").toUpperCase();
6690
- return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
6691
- }
6692
- function useZoom(opts) {
6693
- const min = opts.min ?? DEFAULT_MIN_ZOOM;
6694
- const max = opts.max ?? DEFAULT_MAX_ZOOM;
6695
- const wheelStep = opts.wheelStep ?? 1.1;
6696
- const keyStep = opts.keyStep ?? 1.25;
6697
- const sources = useMemo(
6698
- () => ({
6699
- wheel: opts.sources?.wheel ?? true,
6700
- keys: opts.sources?.keys ?? true,
6701
- doubleClick: opts.sources?.doubleClick ?? false,
6702
- pinch: opts.sources?.pinch ?? true
6703
- }),
6704
- [opts.sources?.wheel, opts.sources?.keys, opts.sources?.doubleClick, opts.sources?.pinch]
6705
- );
6706
- const applyZoom = useCallback(
6707
- (nextZoom, focal) => {
6708
- const oldZoom = opts.zoom;
6709
- const newZoom = clamp(nextZoom, min, max);
6710
- const k = newZoom / oldZoom;
6711
- const newPan = {
6712
- x: focal.x - (focal.x - opts.pan.x) * k,
6713
- y: focal.y - (focal.y - opts.pan.y) * k
6714
- };
6715
- opts.setZoom(newZoom);
6716
- opts.setPan(newPan);
6717
- },
6718
- [opts, min, max]
6719
- );
6720
- const viewportCenter = useCallback(() => {
6721
- if (!opts.viewport) {
6722
- throw new Error(
6723
- "useZoom: viewport option is required for keyboard zoom and zoomTo without focal"
6724
- );
6725
- }
6726
- return { x: opts.viewport.width / 2, y: opts.viewport.height / 2 };
6727
- }, [opts.viewport]);
6728
- const zoomTo = useCallback(
6729
- (level, focal) => {
6730
- applyZoom(level, focal ?? viewportCenter());
6731
- },
6732
- [applyZoom, viewportCenter]
6733
- );
6734
- const zoomBy = useCallback(
6735
- (factor, focal) => {
6736
- applyZoom(opts.zoom * factor, focal ?? viewportCenter());
6737
- },
6738
- [applyZoom, opts.zoom, viewportCenter]
6739
- );
6740
- const reset = useCallback(() => {
6741
- opts.setZoom(1);
6742
- opts.setPan({ x: 0, y: 0 });
6743
- }, [opts]);
6744
- const onWheel = useCallback(
6745
- (e) => {
6746
- const evt = e;
6747
- const isPinch = evt.ctrlKey === true;
6748
- if (isPinch) {
6749
- if (!sources.pinch) return;
6750
- evt.preventDefault?.();
6751
- } else {
6752
- if (!sources.wheel) return;
6753
- if (opts.wheelRequiresModifier && !evt.metaKey) return;
6754
- }
6755
- const target = evt.currentTarget;
6756
- const rect = target?.getBoundingClientRect?.();
6757
- const focal = {
6758
- x: evt.clientX - (rect?.left ?? 0),
6759
- y: evt.clientY - (rect?.top ?? 0)
6760
- };
6761
- const factor = evt.deltaY < 0 ? wheelStep : 1 / wheelStep;
6762
- applyZoom(opts.zoom * factor, focal);
6763
- },
6764
- [applyZoom, opts, sources, wheelStep]
6765
- );
6766
- const onKeyDown = useCallback(
6767
- (e) => {
6768
- if (!sources.keys) return;
6769
- const evt = e;
6770
- if (isEditableTarget(evt.target)) return;
6771
- if (evt.key === "0" && (evt.metaKey || evt.ctrlKey)) {
6772
- opts.setZoom(1);
6773
- opts.setPan({ x: 0, y: 0 });
6774
- return;
6775
- }
6776
- let factor = 0;
6777
- if (evt.key === "+" || evt.key === "=") factor = keyStep;
6778
- else if (evt.key === "-" || evt.key === "_") factor = 1 / keyStep;
6779
- if (factor === 0) return;
6780
- applyZoom(opts.zoom * factor, viewportCenter());
6781
- },
6782
- [applyZoom, opts, sources, keyStep, viewportCenter]
6783
- );
6784
- const onDoubleClick = useCallback(
6785
- (e) => {
6786
- if (!sources.doubleClick) return;
6787
- const evt = e;
6788
- if (evt.altKey) {
6789
- opts.setZoom(1);
6790
- opts.setPan({ x: 0, y: 0 });
6791
- return;
6792
- }
6793
- const target = evt.currentTarget;
6794
- const rect = target?.getBoundingClientRect?.();
6795
- const focal = {
6796
- x: evt.clientX - (rect?.left ?? 0),
6797
- y: evt.clientY - (rect?.top ?? 0)
6798
- };
6799
- const factor = evt.shiftKey ? 1 / keyStep : keyStep;
6800
- applyZoom(opts.zoom * factor, focal);
6801
- },
6802
- [applyZoom, opts, sources, keyStep]
6803
- );
6804
- return { onWheel, onKeyDown, onDoubleClick, zoomTo, zoomBy, reset };
6805
- }
6806
7084
  function computeFitViewport(viewportW, viewportH, contentW, contentH, padRatio = 0.85) {
6807
7085
  const zoom = fitZoom(viewportW * padRatio, viewportH * padRatio, contentW, contentH);
6808
7086
  const cw = contentW * zoom;
@@ -6822,7 +7100,7 @@ function useAutoCenter(width, height, contentW, contentH, setZoom, setPan) {
6822
7100
  }
6823
7101
 
6824
7102
  // src/interactions/keyHelpers.ts
6825
- function isEditableTarget2(target) {
7103
+ function isEditableTarget(target) {
6826
7104
  if (!target || !(target instanceof Element)) return false;
6827
7105
  const tag = target.tagName;
6828
7106
  if (tag === "INPUT" || tag === "TEXTAREA") return true;
@@ -6960,7 +7238,10 @@ function moveGestureAdapter(scene) {
6960
7238
  data: node.data,
6961
7239
  id: node.id,
6962
7240
  ...index !== void 0 ? { index } : {},
6963
- ...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 } : {}
6964
7245
  }),
6965
7246
  getChildren: (parentId) => parentId === null ? [...scene.roots] : [...scene.childrenOf(asNodeId(parentId))],
6966
7247
  setChildOrder: (parentId, ids) => {
@@ -6985,31 +7266,51 @@ function runLayoutPass(scratch, moveCtx) {
6985
7266
  if (!layoutDep || !moveCtx.drag) return;
6986
7267
  const scene = scratch.scene;
6987
7268
  const pc = scratch.pc;
6988
- const draggedId = scratch.ids[0];
6989
7269
  const poseAdapter = scenePoseAdapter(scene);
6990
- if (!scene.get(draggedId)) return;
6991
- const sourceContainerId = scene.get(draggedId)?.parent ?? null;
6992
7270
  const { dx, dy } = scratch.currentDelta;
6993
- const startWorld = composeWorldPose(poseAdapter, draggedId, pc.compose);
6994
- const draggedWorld = { ...startWorld, x: startWorld.x + dx, y: startWorld.y + dy };
6995
- const draggedCenter = {
6996
- x: draggedWorld.x + (draggedWorld.width ?? 0) / 2,
6997
- 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
6998
7305
  };
6999
7306
  const candidates = [];
7000
7307
  const boundsOf2 = scratch.projection?.getBounds ?? AUTO_POSE_DESCRIPTOR.getBounds;
7001
7308
  const testInside = (cPose, aabb, layout2) => {
7002
- if (layout2.contains) return layout2.contains(cPose, draggedCenter);
7003
- return draggedCenter.x >= aabb.x && draggedCenter.x < aabb.x + aabb.width && draggedCenter.y >= aabb.y && draggedCenter.y < aabb.y + aabb.height;
7004
- };
7005
- const draggedArg = {
7006
- id: draggedId,
7007
- originPose: startWorld,
7008
- pose: draggedWorld,
7009
- 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;
7010
7311
  };
7011
7312
  const consider = (id, zPath) => {
7012
- if (id === draggedId) return;
7313
+ if (draggedIds.has(id)) return;
7013
7314
  const layout2 = layoutDep.getLayout(id);
7014
7315
  if (!layout2) return;
7015
7316
  const node = scene.get(id);
@@ -7017,7 +7318,10 @@ function runLayoutPass(scratch, moveCtx) {
7017
7318
  const worldPose = composeWorldPose(poseAdapter, id, pc.compose);
7018
7319
  const worldAABB = boundsOf2(worldPose);
7019
7320
  if (!testInside(worldPose, worldAABB, layout2)) return;
7020
- 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
+ }
7021
7325
  candidates.push({
7022
7326
  id,
7023
7327
  bounds: worldAABB,
@@ -7062,47 +7366,126 @@ function runLayoutPass(scratch, moveCtx) {
7062
7366
  if (!dest) return;
7063
7367
  const layout = dest.layout;
7064
7368
  const container = { id: dest.id, bounds: dest.bounds };
7065
- const children = scene.childrenOf(dest.id).filter((cid) => cid !== draggedId || sourceContainerId === dest.id).map((cid) => ({
7369
+ const working = scene.childrenOf(dest.id).map((cid) => ({
7066
7370
  id: cid,
7067
7371
  pose: composeWorldPose(poseAdapter, cid, pc.compose)
7068
7372
  }));
7069
- const targets = layout.getDropTargets(container, children, draggedArg);
7070
- const target = layout.snap.pickTarget(targets, { x: moveCtx.drag.current.x, y: moveCtx.drag.current.y });
7071
- 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
+ }
7072
7396
  const reflowIds = /* @__PURE__ */ new Set();
7073
- for (const [cid, pose] of layout.reflowPoses(container, children, draggedArg, target)) {
7074
- if (asNodeId(cid) === draggedId) continue;
7075
- const parent = scene.get(asNodeId(cid))?.parent ?? null;
7076
- const local = rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose);
7077
- scratch.previews.set(asNodeId(cid), local);
7078
- 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);
7079
7401
  }
7080
7402
  const sourceReflow = /* @__PURE__ */ new Map();
7081
- if (sourceContainerId && sourceContainerId !== dest.id) {
7082
- const srcLayout = layoutDep.getLayout(sourceContainerId);
7083
- const srcNode = scene.get(asNodeId(sourceContainerId));
7084
- if (srcLayout && srcNode) {
7085
- const srcContainer = {
7086
- id: sourceContainerId,
7087
- bounds: composeWorldPose(poseAdapter, sourceContainerId, pc.compose)
7088
- };
7089
- const srcChildren = scene.childrenOf(asNodeId(sourceContainerId)).filter((cid) => cid !== draggedId).map((cid) => ({
7090
- id: cid,
7091
- pose: composeWorldPose(poseAdapter, cid, pc.compose)
7092
- }));
7093
- for (const [cid, pose] of srcLayout.childPoses(srcContainer, srcChildren)) {
7094
- const cur = composeWorldPose(poseAdapter, cid, pc.compose);
7095
- const next = pose;
7096
- const same2 = cur.x === next.x && cur.y === next.y && cur.width === next.width && cur.height === next.height;
7097
- if (same2) continue;
7098
- sourceReflow.set(cid, pose);
7099
- const parent = scene.get(asNodeId(cid))?.parent ?? null;
7100
- scratch.previews.set(asNodeId(cid), rebaseLocalPose(poseAdapter, pose, parent, pc.compose, pc.decompose));
7101
- 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);
7102
7467
  }
7103
7468
  }
7104
7469
  }
7105
- 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;
7106
7489
  }
7107
7490
  function scenePoseAdapter(scene) {
7108
7491
  return {
@@ -7287,7 +7670,7 @@ var moveAction = {
7287
7670
  scratch.previews.set(id, translatePoseGeneric(ori, dx, dy, scratch.projection));
7288
7671
  }
7289
7672
  scratch.layoutPass = null;
7290
- if (scratch.ids.length === 1) runLayoutPass(scratch, moveCtx);
7673
+ runLayoutPass(scratch, moveCtx);
7291
7674
  syncPreviewOverrides(scratch);
7292
7675
  },
7293
7676
  onEnd(endCtx, reason) {
@@ -7322,25 +7705,17 @@ var moveAction = {
7322
7705
  scratch.previews.clear();
7323
7706
  return;
7324
7707
  }
7325
- if (scratch.layout && scratch.ids.length === 1 && scratch.layoutPass) {
7708
+ if (scratch.layout && scratch.layoutPass) {
7326
7709
  const lp = scratch.layoutPass;
7327
7710
  const pc2 = scratch.pc;
7328
- const draggedId = scratch.ids[0];
7329
7711
  const commitAdapter = scenePoseAdapter(scratch.scene);
7330
- const startWorld = composeWorldPose(commitAdapter, draggedId, pc2.compose);
7331
- const sourceContainerId = scratch.scene.get(draggedId)?.parent ?? null;
7332
- const draggedArg = {
7333
- id: draggedId,
7334
- originPose: startWorld,
7335
- pose: { ...startWorld, x: startWorld.x + dx, y: startWorld.y + dy },
7336
- sourceContainerId
7337
- };
7338
7712
  const destId = lp.container.id;
7713
+ const draggedIds = new Set(scratch.ids.map((id) => id));
7339
7714
  const rebaseOpToLocal = (op) => {
7340
7715
  if (op.name !== "transform") return op;
7341
7716
  const a = op.args;
7342
7717
  const curParent = scratch.scene.get(asNodeId(a.id))?.parent ?? null;
7343
- const toParent = a.id === draggedId ? destId : curParent;
7718
+ const toParent = draggedIds.has(a.id) ? destId : curParent;
7344
7719
  return createTransformOp({
7345
7720
  id: a.id,
7346
7721
  from: rebaseLocalPose(commitAdapter, a.from, curParent, pc2.compose, pc2.decompose),
@@ -7349,15 +7724,24 @@ var moveAction = {
7349
7724
  coalesceKey: a.coalesceKey
7350
7725
  });
7351
7726
  };
7352
- const dropOps = lp.layout.commitDrop(lp.container, lp.children, draggedArg, lp.target).map(rebaseOpToLocal);
7353
7727
  const reparentOps = [];
7354
- if (draggedArg.sourceContainerId !== destId) {
7355
- reparentOps.push(createReparentOp({
7356
- id: draggedId,
7357
- fromParentId: draggedArg.sourceContainerId,
7358
- toParentId: destId,
7359
- label: "Reparent"
7360
- }));
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));
7361
7745
  }
7362
7746
  const reflowOps = [];
7363
7747
  for (const [cid, worldPose] of lp.sourceReflow) {
@@ -7376,6 +7760,73 @@ var moveAction = {
7376
7760
  scratch.previews.clear();
7377
7761
  return;
7378
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
+ }
7379
7830
  const resolved = resolveParams(opts?.params);
7380
7831
  const reparentMode = resolved?.["reparentOnDrop"] ?? "off";
7381
7832
  const dropTarget = reparentMode !== "off" ? resolveDropTarget(endCtx, scratch) : null;
@@ -7384,30 +7835,7 @@ var moveAction = {
7384
7835
  applyReparent(scratch, dropTarget, reparentMode, dx, dy, scratch.pc);
7385
7836
  });
7386
7837
  } else {
7387
- const m = [1, 0, 0, 1, dx, dy];
7388
- const ops = [];
7389
- for (const id of [...scratch.ids, ...scratch.cascadeIds]) {
7390
- const origin2 = scratch.startPoses.get(id);
7391
- if (origin2 === void 0) continue;
7392
- ops.push(createTransformOp({
7393
- id,
7394
- from: origin2,
7395
- to: translatePoseGeneric(origin2, dx, dy, scratch.projection),
7396
- label: "Move"
7397
- }));
7398
- if (scratch.geometryProjection) {
7399
- const node = scratch.scene.get(id);
7400
- if (node) {
7401
- const dataOp = geometryDataOp(
7402
- scratch.geometryProjection,
7403
- { id, data: node.data, pose: origin2 },
7404
- m,
7405
- "Move"
7406
- );
7407
- if (dataOp) ops.push(dataOp);
7408
- }
7409
- }
7410
- }
7838
+ const ops = translateCommitOps(scratch, [...scratch.ids, ...scratch.cascadeIds], dx, dy);
7411
7839
  if (ops.length > 0) commitOps(ops, "Move");
7412
7840
  }
7413
7841
  dropPreviewOverrides(scratch);
@@ -7478,6 +7906,7 @@ function defaultCommitAdapter(scene) {
7478
7906
  getSelection: () => [...scene.getSelection()],
7479
7907
  setSelection: (ids) => scene.setSelection(ids.map(asNodeId)),
7480
7908
  getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,
7909
+ getRemovalClosure: (ids) => [...scene.removalClosure(ids.map(asNodeId))],
7481
7910
  getChildren: (parentId) => parentId === null ? [...scene.roots] : [...scene.childrenOf(asNodeId(parentId))],
7482
7911
  // Re-stamp each slot whose occupant changed. `scene.reorder` mutates the
7483
7912
  // live sibling list in place, so re-read it each iteration and skip slots
@@ -7506,7 +7935,13 @@ function defaultCommitAdapter(scene) {
7506
7935
  // Sibling-index passthrough: undo of a delete (or a boolean op's
7507
7936
  // source removal) re-inserts at the recorded z-order slot instead
7508
7937
  // of appending at the end.
7509
- ...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 } : {}
7510
7945
  })
7511
7946
  };
7512
7947
  }
@@ -8682,15 +9117,40 @@ var viewportDragPanAction = {
8682
9117
  enabled: () => true
8683
9118
  };
8684
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
+
8685
9146
  // src/interactions/actions/defaults/viewportZoom.ts
8686
9147
  var KEY_STEP = 1.25;
8687
9148
  function keyAnchor(view) {
8688
9149
  const size = view.hostSize?.();
8689
9150
  return size ? { x: size.width / 2, y: size.height / 2 } : { x: 0, y: 0 };
8690
9151
  }
8691
- var WHEEL_STEP = 1.1;
8692
9152
  function makeViewportZoomAction(opts = {}) {
8693
- 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 };
8694
9154
  const wheelSpec = opts.wheel === "plain" ? { kind: "wheel" } : { kind: "wheel", mods: { mod: true } };
8695
9155
  const rawAnimate = opts.animate === true ? {} : opts.animate || null;
8696
9156
  const { resetMs, ...animateRest } = rawAnimate ?? {};
@@ -8750,15 +9210,14 @@ function makeViewportZoomAction(opts = {}) {
8750
9210
  const deltaY = params?.deltaY ?? 0;
8751
9211
  const clientX = params?.clientX ?? 0;
8752
9212
  const clientY = params?.clientY ?? 0;
8753
- const factor = Math.pow(WHEEL_STEP, -deltaY / 100);
8754
- view.set(zoomAt(current, { x: clientX, y: clientY }, factor, clamp2));
9213
+ view.set(wheelZoom(current, { x: clientX, y: clientY }, deltaY, clamp));
8755
9214
  break;
8756
9215
  }
8757
9216
  case "in":
8758
- stepTo(zoomAt(stepFrom(), keyAnchor(view), KEY_STEP, clamp2));
9217
+ stepTo(zoomAt(stepFrom(), keyAnchor(view), KEY_STEP, clamp));
8759
9218
  break;
8760
9219
  case "out":
8761
- stepTo(zoomAt(stepFrom(), keyAnchor(view), 1 / KEY_STEP, clamp2));
9220
+ stepTo(zoomAt(stepFrom(), keyAnchor(view), 1 / KEY_STEP, clamp));
8762
9221
  break;
8763
9222
  case "reset": {
8764
9223
  const target = view.recenter ? view.recenter() : { x: 0, y: 0, scale: { x: 1, y: 1 } };
@@ -8767,7 +9226,7 @@ function makeViewportZoomAction(opts = {}) {
8767
9226
  }
8768
9227
  default:
8769
9228
  if (params === void 0) {
8770
- view.set(zoomAt(current, { x: 0, y: 0 }, KEY_STEP, clamp2));
9229
+ view.set(zoomAt(current, { x: 0, y: 0 }, KEY_STEP, clamp));
8771
9230
  }
8772
9231
  break;
8773
9232
  }
@@ -8802,15 +9261,9 @@ var viewportWheelPanAction = {
8802
9261
  const axis = params?.axis ?? "both";
8803
9262
  const deltaX = params?.deltaX ?? 0;
8804
9263
  const deltaY = params?.deltaY ?? 0;
8805
- const current = view.get();
8806
- const swap = params?.swapAxis === true;
8807
- const dx = swap ? (deltaX !== 0 ? deltaX : deltaY) / current.scale.x : deltaX / current.scale.x;
8808
- const dy = swap ? 0 : deltaY / current.scale.y;
8809
- view.set({
8810
- ...current,
8811
- x: current.x + (axis === "y" ? 0 : dx),
8812
- y: current.y + (axis === "x" ? 0 : dy)
8813
- });
9264
+ view.set(
9265
+ wheelPan(view.get(), { deltaX, deltaY }, { axis, swapAxis: params?.swapAxis === true })
9266
+ );
8814
9267
  }
8815
9268
  },
8816
9269
  enabled: () => true
@@ -9458,7 +9911,7 @@ var sliceAction = {
9458
9911
 
9459
9912
  // src/interactions/actions/defaults/pinchZoom.ts
9460
9913
  function makePinchZoomAction(opts = {}) {
9461
- 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 };
9462
9915
  return {
9463
9916
  id: "viewport.pinchZoom",
9464
9917
  label: "Pinch Zoom",
@@ -9486,7 +9939,7 @@ function makePinchZoomAction(opts = {}) {
9486
9939
  const prev = scratch.centroid;
9487
9940
  scratch.centroid = centroid;
9488
9941
  const current = scratch.view.get();
9489
- const zoomed = zoomAt(current, prev, factor, clamp2);
9942
+ const zoomed = zoomAt(current, prev, factor, clamp);
9490
9943
  scratch.view.set({
9491
9944
  scale: zoomed.scale,
9492
9945
  x: zoomed.x - (centroid.x - prev.x) / zoomed.scale.x,
@@ -9508,31 +9961,14 @@ function hostIndex(scene, id) {
9508
9961
  const siblings = node?.parent == null ? scene.roots : scene.childrenOf(node.parent);
9509
9962
  return siblings.indexOf(id);
9510
9963
  }
9511
- function coveredByEmitted(scene, id, emitted) {
9512
- const seen = /* @__PURE__ */ new Set([id]);
9513
- const stack = [id];
9514
- while (stack.length > 0) {
9515
- const node = scene.get(stack.pop());
9516
- if (!node) continue;
9517
- const up = node.parent == null ? node.dependsOn ?? [] : [node.parent, ...node.dependsOn ?? []];
9518
- for (const next of up) {
9519
- if (seen.has(next)) continue;
9520
- if (emitted.has(next)) return true;
9521
- seen.add(next);
9522
- stack.push(next);
9523
- }
9524
- }
9525
- return false;
9526
- }
9527
9964
  function buildDeleteOps(scene, ids, label) {
9528
- const emitted = /* @__PURE__ */ new Set();
9965
+ const covered = /* @__PURE__ */ new Set();
9529
9966
  const ops = [];
9530
9967
  for (const id of ids) {
9531
9968
  const node = scene.get(id);
9532
9969
  if (!node) continue;
9533
- if (emitted.has(id)) continue;
9534
- if (coveredByEmitted(scene, id, emitted)) continue;
9535
- emitted.add(id);
9970
+ if (covered.has(id)) continue;
9971
+ for (const taken of scene.removalClosure([id])) covered.add(taken);
9536
9972
  ops.push(createDeleteOp({
9537
9973
  node,
9538
9974
  index: hostIndex(scene, id),
@@ -9756,7 +10192,10 @@ var selectAllAction = {
9756
10192
  timing: "immediate",
9757
10193
  run: (deps) => {
9758
10194
  const scene = deps.scene;
9759
- 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);
9760
10199
  if (all.length === 0) return;
9761
10200
  deps.selection?.set(all);
9762
10201
  }
@@ -11774,7 +12213,7 @@ var setStrokeOpacityAction = createPaintAction({
11774
12213
  readParams: (_prev, params) => params?.alpha01 ?? null,
11775
12214
  merge: (prev, alpha01) => {
11776
12215
  const base = prev ?? strokeOf(DEFAULT_STROKE_COLOR);
11777
- return { ...base, paint: paintWithAlpha(base.paint, alpha01) };
12216
+ return { ...base, paint: paintWithAlpha(base.paint ?? solid(DEFAULT_STROKE_COLOR), alpha01) };
11778
12217
  }
11779
12218
  });
11780
12219
 
@@ -12244,6 +12683,7 @@ var KIT_STANDARD_DESCRIPTORS = [
12244
12683
  clipboardCopyAction,
12245
12684
  clipboardCutAction
12246
12685
  ];
12686
+ var KIT_STANDARD_ACTION_IDS = KIT_STANDARD_DESCRIPTORS.map((a) => a.id);
12247
12687
  function useStandardActions(opts) {
12248
12688
  const optsRef = useRef(opts);
12249
12689
  optsRef.current = opts;
@@ -12276,13 +12716,15 @@ function useStandardActions(opts) {
12276
12716
  const reg = useActionsRegistry();
12277
12717
  const regRef = useRef(reg);
12278
12718
  regRef.current = reg;
12719
+ const excludeKey = opts.exclude ? [...opts.exclude].join("\0") : "";
12279
12720
  useEffect(() => {
12280
12721
  if (!reg || !depReg) return;
12281
- 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));
12282
12724
  return () => {
12283
12725
  for (const u of unregisters) u();
12284
12726
  };
12285
- }, [reg, depReg]);
12727
+ }, [reg, depReg, excludeKey]);
12286
12728
  }
12287
12729
  var IDENTITY_EXPAND2 = (ids) => ids;
12288
12730
  var EMPTY = Object.freeze([]);
@@ -13472,7 +13914,7 @@ function withViewDeps(base, overlay) {
13472
13914
  };
13473
13915
  }
13474
13916
  function computeMultiTouchGeometry(positions) {
13475
- const pts = [...positions.values()];
13917
+ const pts = [...positions];
13476
13918
  if (pts.length < 2) {
13477
13919
  return { centroid: { x: 0, y: 0 }, spread: 0 };
13478
13920
  }
@@ -13599,14 +14041,22 @@ function useGestureDispatcher(opts) {
13599
14041
  return result;
13600
14042
  };
13601
14043
  const heldKeys = /* @__PURE__ */ new Set();
13602
- const activePointers = /* @__PURE__ */ new Set();
13603
- const buttonsReportedFor = /* @__PURE__ */ new Set();
13604
- const pointerPositions = /* @__PURE__ */ new Map();
14044
+ const held = /* @__PURE__ */ new Map();
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;
13608
- const bufferedDown = /* @__PURE__ */ new Map();
13609
- const lastPointerDown = /* @__PURE__ */ new Map();
13610
14060
  const LONG_PRESS_MS = 500;
13611
14061
  const longPressTimers = /* @__PURE__ */ new Map();
13612
14062
  const cancelLongPress = (pointerId) => {
@@ -13621,7 +14071,7 @@ function useGestureDispatcher(opts) {
13621
14071
  longPressTimers.clear();
13622
14072
  };
13623
14073
  const fireLongPress = (pointerId) => {
13624
- const down = lastPointerDown.get(pointerId);
14074
+ const down = held.get(pointerId)?.down;
13625
14075
  if (!down) return;
13626
14076
  const shared = {
13627
14077
  target: down.target,
@@ -13653,7 +14103,7 @@ function useGestureDispatcher(opts) {
13653
14103
  const DOUBLE_CLICK_MAX_MS = 600;
13654
14104
  const DOUBLE_CLICK_MAX_PX = 8;
13655
14105
  const onKeyDown = (e) => {
13656
- if (isEditableTarget2(e.target)) return;
14106
+ if (isEditableTarget(e.target)) return;
13657
14107
  if (e.repeat && heldKeys.has(e.key)) {
13658
14108
  e.preventDefault();
13659
14109
  return;
@@ -13743,15 +14193,8 @@ function useGestureDispatcher(opts) {
13743
14193
  };
13744
14194
  const onPointerDown = (e) => {
13745
14195
  if ((e.button ?? 0) !== 0) return;
13746
- if (activePointers.has(e.pointerId)) onPointerUp(e);
14196
+ if (!canvas) return;
13747
14197
  routeDown(e.pointerId, e.clientX, e.clientY);
13748
- activePointers.add(e.pointerId);
13749
- if (reportsButtons(e)) buttonsReportedFor.add(e.pointerId);
13750
- pointerPositions.set(e.pointerId, { x: e.clientX, y: e.clientY });
13751
- try {
13752
- canvas?.setPointerCapture?.(e.pointerId);
13753
- } catch {
13754
- }
13755
14198
  const worldPoint = { x: e.clientX, y: e.clientY };
13756
14199
  const affordance = target().affordanceAt?.(worldPoint) ?? void 0;
13757
14200
  const body = target().classifyTarget?.(worldPoint);
@@ -13773,25 +14216,37 @@ function useGestureDispatcher(opts) {
13773
14216
  ...stylusOf(e),
13774
14217
  ...affordance !== void 0 ? { affordance } : {},
13775
14218
  ...bodyTarget !== void 0 ? { bodyTarget } : {},
13776
- ...bodyKind !== void 0 ? { bodyKind } : {}
13777
- };
13778
- bufferedDown.set(e.pointerId, { ev, clientX: e.clientX, clientY: e.clientY });
13779
- dispatch2({ ...ev, stage: "press" });
13780
- lastPointerDown.set(e.pointerId, {
13781
- target: e.target,
13782
- clientX: e.clientX,
13783
- clientY: e.clientY,
13784
- worldX: w.x,
13785
- worldY: w.y,
13786
- affordance,
13787
- bodyTarget,
13788
- bodyKind,
13789
- altKey: e.altKey,
13790
- ctrlKey: e.ctrlKey,
13791
- metaKey: e.metaKey,
13792
- shiftKey: e.shiftKey
14219
+ ...bodyKind !== void 0 ? { bodyKind } : {}
14220
+ };
14221
+ held.set(e.pointerId, {
14222
+ session: openPointerSession(canvas, e, {
14223
+ onMove: onPointerMove,
14224
+ onEnd: onPointerUp,
14225
+ onCancel: () => {
14226
+ onPointerCancel(e.pointerId);
14227
+ }
14228
+ }),
14229
+ pos: { x: e.clientX, y: e.clientY },
14230
+ // Deferred until the first past-threshold pointermove: a bare
14231
+ // press-and-release should fire `click`, not start a drag action.
14232
+ buffered: { ev, clientX: e.clientX, clientY: e.clientY },
14233
+ down: {
14234
+ target: e.target,
14235
+ clientX: e.clientX,
14236
+ clientY: e.clientY,
14237
+ worldX: w.x,
14238
+ worldY: w.y,
14239
+ affordance,
14240
+ bodyTarget,
14241
+ bodyKind,
14242
+ altKey: e.altKey,
14243
+ ctrlKey: e.ctrlKey,
14244
+ metaKey: e.metaKey,
14245
+ shiftKey: e.shiftKey
14246
+ }
13793
14247
  });
13794
- if ((e.pointerType === "touch" || e.pointerType === "pen") && activePointers.size === 1) {
14248
+ dispatch2({ ...ev, stage: "press" });
14249
+ if ((e.pointerType === "touch" || e.pointerType === "pen") && held.size === 1) {
13795
14250
  cancelLongPress(e.pointerId);
13796
14251
  longPressTimers.set(
13797
14252
  e.pointerId,
@@ -13802,18 +14257,18 @@ function useGestureDispatcher(opts) {
13802
14257
  }, LONG_PRESS_MS)
13803
14258
  );
13804
14259
  }
13805
- if (activePointers.size >= 2) {
14260
+ if (held.size >= 2) {
13806
14261
  cancelAllLongPress();
13807
- for (const id of activePointers) bufferedDown.delete(id);
13808
- const { centroid, spread } = computeMultiTouchGeometry(pointerPositions);
14262
+ for (const p of held.values()) p.buffered = null;
14263
+ const { centroid, spread } = heldGeometry();
13809
14264
  if (!multiTouchStart) {
13810
- multiTouchStart = { fingers: activePointers.size, centroid: { ...centroid } };
14265
+ multiTouchStart = { fingers: held.size, centroid: { ...centroid } };
13811
14266
  } else {
13812
- multiTouchStart.fingers = activePointers.size;
14267
+ multiTouchStart.fingers = held.size;
13813
14268
  }
13814
14269
  const mt = {
13815
14270
  kind: "multitouch",
13816
- fingers: activePointers.size,
14271
+ fingers: held.size,
13817
14272
  altKey: e.altKey,
13818
14273
  ctrlKey: e.ctrlKey,
13819
14274
  metaKey: e.metaKey,
@@ -13825,30 +14280,25 @@ function useGestureDispatcher(opts) {
13825
14280
  }
13826
14281
  };
13827
14282
  const onPointerMove = (e) => {
13828
- if (activePointers.has(e.pointerId) && buttonsReportedFor.has(e.pointerId) && isMissedRelease(e)) {
13829
- onPointerUp(e);
13830
- return;
13831
- }
13832
14283
  routeAt(e.pointerId, e.clientX, e.clientY);
13833
- if (activePointers.has(e.pointerId)) {
13834
- pointerPositions.set(e.pointerId, { x: e.clientX, y: e.clientY });
13835
- }
13836
- const buffered = bufferedDown.get(e.pointerId);
13837
- if (buffered) {
14284
+ const rec = held.get(e.pointerId);
14285
+ if (rec) rec.pos = { x: e.clientX, y: e.clientY };
14286
+ const buffered = rec?.buffered;
14287
+ if (rec && buffered) {
13838
14288
  const dx = e.clientX - buffered.clientX;
13839
14289
  const dy = e.clientY - buffered.clientY;
13840
14290
  if (Math.hypot(dx, dy) >= DRAG_THRESHOLD_PX) {
13841
- bufferedDown.delete(e.pointerId);
14291
+ rec.buffered = null;
13842
14292
  cancelLongPress(e.pointerId);
13843
14293
  dispatch2(buffered.ev);
13844
14294
  }
13845
14295
  }
13846
- const mtGestureId = `multitouch-${activePointers.size}`;
13847
- if (activePointers.size >= 2 && dispatcherNow().inFlight().has(mtGestureId)) {
13848
- const { centroid, spread } = computeMultiTouchGeometry(pointerPositions);
14296
+ const mtGestureId = `multitouch-${held.size}`;
14297
+ if (held.size >= 2 && dispatcherNow().inFlight().has(mtGestureId)) {
14298
+ const { centroid, spread } = heldGeometry();
13849
14299
  const mtEv = {
13850
14300
  kind: "multitouch",
13851
- fingers: activePointers.size,
14301
+ fingers: held.size,
13852
14302
  altKey: e.altKey,
13853
14303
  ctrlKey: e.ctrlKey,
13854
14304
  metaKey: e.metaKey,
@@ -13920,7 +14370,7 @@ function useGestureDispatcher(opts) {
13920
14370
  }
13921
14371
  function resolveHoverCursor() {
13922
14372
  if (!canvas || !lastHover) return;
13923
- if (activePointers.size > 0 || dispatcherNow().inFlight().size > 0) {
14373
+ if (held.size > 0 || dispatcherNow().inFlight().size > 0) {
13924
14374
  applyHoverCursor(dispatcherNow().inFlightCursor());
13925
14375
  return;
13926
14376
  }
@@ -13968,6 +14418,10 @@ function useGestureDispatcher(opts) {
13968
14418
  if (!disposed) refreshHoverCursor();
13969
14419
  }, 0);
13970
14420
  };
14421
+ const onHoverMove = (e) => {
14422
+ if (held.has(e.pointerId)) return;
14423
+ onPointerMove(e);
14424
+ };
13971
14425
  const onHoverPointerLeave = () => {
13972
14426
  lastHover = null;
13973
14427
  paintedCursorRef.current?.()?.clearPointer();
@@ -13976,21 +14430,23 @@ function useGestureDispatcher(opts) {
13976
14430
  const onPointerUp = (e) => {
13977
14431
  routeAt(e.pointerId, e.clientX, e.clientY);
13978
14432
  cancelLongPress(e.pointerId);
13979
- const prevSize = activePointers.size;
13980
- activePointers.delete(e.pointerId);
13981
- buttonsReportedFor.delete(e.pointerId);
13982
- pointerPositions.delete(e.pointerId);
13983
- const staysUnderThreshold = bufferedDown.delete(e.pointerId);
13984
- if (prevSize >= 2 && activePointers.size < 2) {
14433
+ const prevSize = held.size;
14434
+ const rec = held.get(e.pointerId);
14435
+ held.delete(e.pointerId);
14436
+ const staysUnderThreshold = rec?.buffered != null;
14437
+ if (prevSize >= 2 && held.size < 2) {
13985
14438
  const mtGestureId = `multitouch-${prevSize}`;
13986
14439
  if (dispatcherNow().inFlight().has(mtGestureId)) {
13987
14440
  dispatcherNow().cancelAll("commit");
13988
14441
  }
13989
14442
  const start = multiTouchStart;
13990
14443
  if (start) {
13991
- const finalPositions = new Map(pointerPositions);
13992
- finalPositions.set(e.pointerId, { x: e.clientX, y: e.clientY });
13993
- const { centroid } = computeMultiTouchGeometry(finalPositions);
14444
+ const centroid = toCanvasLocal(
14445
+ computeMultiTouchGeometry([
14446
+ ...[...held.values()].map((p) => p.pos),
14447
+ { x: e.clientX, y: e.clientY }
14448
+ ]).centroid
14449
+ );
13994
14450
  const dx = centroid.x - start.centroid.x;
13995
14451
  const dy = centroid.y - start.centroid.y;
13996
14452
  if (Math.hypot(dx, dy) <= TAP_THRESHOLD_PX) {
@@ -14007,8 +14463,7 @@ function useGestureDispatcher(opts) {
14007
14463
  multiTouchStart = null;
14008
14464
  }
14009
14465
  }
14010
- const downForClick = lastPointerDown.get(e.pointerId);
14011
- const movedDuringDrag = downForClick ? downForClick.clientX !== e.clientX || downForClick.clientY !== e.clientY : true;
14466
+ const movedDuringDrag = rec ? rec.down.clientX !== e.clientX || rec.down.clientY !== e.clientY : true;
14012
14467
  const hadDragInFlight = dispatcherNow().inFlight().has(pointerGestureId(e.pointerId)) && movedDuringDrag;
14013
14468
  const wUp = toWorld2(e.clientX, e.clientY);
14014
14469
  const ev = {
@@ -14024,8 +14479,7 @@ function useGestureDispatcher(opts) {
14024
14479
  shiftKey: e.shiftKey
14025
14480
  };
14026
14481
  dispatch2(ev);
14027
- const down = lastPointerDown.get(e.pointerId);
14028
- lastPointerDown.delete(e.pointerId);
14482
+ const down = rec?.down;
14029
14483
  if (!hadDragInFlight && staysUnderThreshold && down) {
14030
14484
  const wClick = toWorld2(e.clientX, e.clientY);
14031
14485
  const clickEv = {
@@ -14082,30 +14536,24 @@ function useGestureDispatcher(opts) {
14082
14536
  if (!disposed) refreshHoverCursor();
14083
14537
  }, 0);
14084
14538
  };
14085
- const onPointerCancel = (e) => {
14086
- routeAt(e.pointerId, e.clientX, e.clientY);
14087
- cancelLongPress(e.pointerId);
14088
- activePointers.delete(e.pointerId);
14089
- buttonsReportedFor.delete(e.pointerId);
14090
- pointerPositions.delete(e.pointerId);
14091
- lastPointerDown.delete(e.pointerId);
14092
- bufferedDown.delete(e.pointerId);
14539
+ const onPointerCancel = (pointerId) => {
14540
+ if (disposed) return;
14541
+ const rec = held.get(pointerId);
14542
+ if (!rec) return;
14543
+ held.delete(pointerId);
14544
+ routeAt(pointerId, rec.pos.x, rec.pos.y);
14545
+ cancelLongPress(pointerId);
14093
14546
  multiTouchStart = null;
14094
14547
  const ev = {
14095
14548
  kind: "pointercancel",
14096
- pointerId: e.pointerId,
14097
- altKey: e.altKey,
14098
- ctrlKey: e.ctrlKey,
14099
- metaKey: e.metaKey,
14100
- shiftKey: e.shiftKey
14549
+ pointerId,
14550
+ altKey: rec.down.altKey,
14551
+ ctrlKey: rec.down.ctrlKey,
14552
+ metaKey: rec.down.metaKey,
14553
+ shiftKey: rec.down.shiftKey
14101
14554
  };
14102
14555
  dispatch2(ev);
14103
- routeRelease(e.pointerId);
14104
- };
14105
- const onLostCapture = (e) => {
14106
- const pe = e;
14107
- if (!activePointers.has(pe.pointerId)) return;
14108
- onPointerCancel(pe);
14556
+ routeRelease(pointerId);
14109
14557
  };
14110
14558
  const onContextMenu = (e) => {
14111
14559
  e.preventDefault();
@@ -14167,7 +14615,7 @@ function useGestureDispatcher(opts) {
14167
14615
  });
14168
14616
  };
14169
14617
  const onPaste = (e) => {
14170
- if (isEditableTarget2(e.target)) return;
14618
+ if (isEditableTarget(e.target)) return;
14171
14619
  const cd = e.clipboardData;
14172
14620
  if (!cd) return;
14173
14621
  const items = itemsFromClipboardData(cd);
@@ -14193,10 +14641,7 @@ function useGestureDispatcher(opts) {
14193
14641
  canvas?.addEventListener("pointerleave", onHoverPointerLeave);
14194
14642
  canvas?.addEventListener("wheel", onWheel, { passive: false });
14195
14643
  canvas?.addEventListener("pointerdown", onPointerDown);
14196
- canvas?.addEventListener("pointermove", onPointerMove);
14197
- canvas?.addEventListener("pointerup", onPointerUp);
14198
- canvas?.addEventListener("pointercancel", onPointerCancel);
14199
- canvas?.addEventListener(LOST_CAPTURE_EVENT, onLostCapture);
14644
+ canvas?.addEventListener("pointermove", onHoverMove);
14200
14645
  canvas?.addEventListener("contextmenu", onContextMenu);
14201
14646
  canvas?.addEventListener("dragenter", onDragOver);
14202
14647
  canvas?.addEventListener("dragover", onDragOver);
@@ -14214,10 +14659,7 @@ function useGestureDispatcher(opts) {
14214
14659
  canvas?.removeEventListener("pointerleave", onHoverPointerLeave);
14215
14660
  canvas?.removeEventListener("wheel", onWheel);
14216
14661
  canvas?.removeEventListener("pointerdown", onPointerDown);
14217
- canvas?.removeEventListener("pointermove", onPointerMove);
14218
- canvas?.removeEventListener("pointerup", onPointerUp);
14219
- canvas?.removeEventListener("pointercancel", onPointerCancel);
14220
- canvas?.removeEventListener(LOST_CAPTURE_EVENT, onLostCapture);
14662
+ canvas?.removeEventListener("pointermove", onHoverMove);
14221
14663
  canvas?.removeEventListener("contextmenu", onContextMenu);
14222
14664
  canvas?.removeEventListener("dragenter", onDragOver);
14223
14665
  canvas?.removeEventListener("dragover", onDragOver);
@@ -14228,37 +14670,12 @@ function useGestureDispatcher(opts) {
14228
14670
  clearHoverCursor();
14229
14671
  cancelAllLongPress();
14230
14672
  disposed = true;
14673
+ for (const p of held.values()) p.session.cancel();
14674
+ held.clear();
14231
14675
  for (const d of allDispatchers()) d.cancelAll("cancel");
14232
14676
  };
14233
14677
  }, [enabled, keyboard, canvasRef]);
14234
14678
  }
14235
-
14236
- // src/core/viewport/wheelHandler.ts
14237
- function computeWheelAction(state, input, bounds = { min: DEFAULT_MIN_ZOOM, max: DEFAULT_MAX_ZOOM }) {
14238
- if (input.shiftKey) {
14239
- return {
14240
- zoom: state.zoom,
14241
- panX: state.panX - input.deltaY,
14242
- panY: state.panY
14243
- };
14244
- }
14245
- if (input.metaKey) {
14246
- return {
14247
- zoom: state.zoom,
14248
- panX: state.panX,
14249
- panY: state.panY - input.deltaY
14250
- };
14251
- }
14252
- const factor = input.deltaY < 0 ? 1.1 : 0.9;
14253
- const newZoom = Math.min(bounds.max, Math.max(bounds.min, state.zoom * factor));
14254
- const worldX = (input.mouseX - state.panX) / state.zoom;
14255
- const worldY = (input.mouseY - state.panY) / state.zoom;
14256
- return {
14257
- zoom: newZoom,
14258
- panX: input.mouseX - worldX * newZoom,
14259
- panY: input.mouseY - worldY * newZoom
14260
- };
14261
- }
14262
14679
  function useVelocityTracker() {
14263
14680
  return useMemo(() => createVelocityTracker(), []);
14264
14681
  }
@@ -16383,85 +16800,64 @@ function UnknownIcon({ className, size = 20 }) {
16383
16800
  }
16384
16801
  );
16385
16802
  }
16386
- function flattenVerts(poly2) {
16387
- const flat = new Array(poly2.length * 2);
16388
- for (let i = 0; i < poly2.length; i++) {
16389
- flat[i * 2] = poly2[i].x;
16390
- flat[i * 2 + 1] = poly2[i].y;
16391
- }
16392
- return flat;
16393
- }
16394
- function polygonContainsRectCenter(poly2, rect) {
16395
- return pointInPolygon(flattenVerts(poly2), rect.x + rect.width / 2, rect.y + rect.height / 2);
16396
- }
16397
- function polygonContainsRect(poly2, rect) {
16398
- if (poly2.length < 3) return false;
16399
- const flat = flattenVerts(poly2);
16400
- if (!pointInPolygon(flat, rect.x, rect.y)) return false;
16401
- if (!pointInPolygon(flat, rect.x + rect.width, rect.y)) return false;
16402
- if (!pointInPolygon(flat, rect.x + rect.width, rect.y + rect.height)) return false;
16403
- if (!pointInPolygon(flat, rect.x, rect.y + rect.height)) return false;
16404
- return !anyPolyEdgeCrossesRect(poly2, rect);
16405
- }
16406
- function polygonIntersectsRect(poly2, rect) {
16407
- if (poly2.length < 3) return false;
16408
- const ab = polygonAabb(poly2);
16409
- if (ab.x + ab.width < rect.x || ab.x > rect.x + rect.width) return false;
16410
- if (ab.y + ab.height < rect.y || ab.y > rect.y + rect.height) return false;
16411
- for (const v of poly2) {
16412
- if (v.x >= rect.x && v.x <= rect.x + rect.width && v.y >= rect.y && v.y <= rect.y + rect.height) return true;
16413
- }
16414
- const flat = flattenVerts(poly2);
16415
- if (pointInPolygon(flat, rect.x, rect.y)) return true;
16416
- if (pointInPolygon(flat, rect.x + rect.width, rect.y)) return true;
16417
- if (pointInPolygon(flat, rect.x + rect.width, rect.y + rect.height)) return true;
16418
- if (pointInPolygon(flat, rect.x, rect.y + rect.height)) return true;
16419
- return anyPolyEdgeCrossesRect(poly2, rect);
16420
- }
16421
- function polygonAabb(poly2) {
16422
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
16423
- for (const v of poly2) {
16424
- if (v.x < minX) minX = v.x;
16425
- if (v.y < minY) minY = v.y;
16426
- if (v.x > maxX) maxX = v.x;
16427
- if (v.y > maxY) maxY = v.y;
16428
- }
16429
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
16430
- }
16431
- function anyPolyEdgeCrossesRect(poly2, rect) {
16432
- const r0x = rect.x, r0y = rect.y;
16433
- const r1x = rect.x + rect.width, r1y = rect.y;
16434
- const r2x = rect.x + rect.width, r2y = rect.y + rect.height;
16435
- const r3x = rect.x, r3y = rect.y + rect.height;
16436
- for (let i = 0, j = poly2.length - 1; i < poly2.length; j = i++) {
16437
- const a = poly2[j], b = poly2[i];
16438
- if (segmentsCross(a.x, a.y, b.x, b.y, r0x, r0y, r1x, r1y)) return true;
16439
- if (segmentsCross(a.x, a.y, b.x, b.y, r1x, r1y, r2x, r2y)) return true;
16440
- if (segmentsCross(a.x, a.y, b.x, b.y, r2x, r2y, r3x, r3y)) return true;
16441
- if (segmentsCross(a.x, a.y, b.x, b.y, r3x, r3y, r0x, r0y)) return true;
16442
- }
16443
- return false;
16444
- }
16445
-
16446
- // src/features/paths/pathHitTest.ts
16447
- function extractVertices(path) {
16803
+ function closedSubpaths(path, tolerance) {
16448
16804
  const { commands, coords } = path;
16449
- const verts = [];
16805
+ const out = [];
16806
+ let sub2 = [];
16807
+ let subStartX = 0, subStartY = 0;
16808
+ let curX = 0, curY = 0;
16450
16809
  let ci = 0;
16451
16810
  for (let i = 0; i < commands.length; i++) {
16452
16811
  const cmd = commands[i];
16453
- if (cmd === PATH_M || cmd === PATH_L) {
16454
- verts.push({ x: coords[ci], y: coords[ci + 1] });
16455
- ci += 2;
16456
- } else if (cmd === PATH_Z) {
16457
- break;
16458
- } else {
16459
- throw new Error(
16460
- `pathHitTest: bezier command ${cmd} not supported \u2014 promote path to use pointInPath for bezier paths`
16461
- );
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}`);
16462
16858
  }
16463
16859
  }
16464
- return verts;
16860
+ return out;
16465
16861
  }
16466
16862
  function rectToVerts(r) {
16467
16863
  return [
@@ -16471,7 +16867,7 @@ function rectToVerts(r) {
16471
16867
  { x: r.x, y: r.y + r.height }
16472
16868
  ];
16473
16869
  }
16474
- function flattenVerts2(poly2) {
16870
+ function flattenVerts(poly2) {
16475
16871
  const flat = new Array(poly2.length * 2);
16476
16872
  for (let i = 0; i < poly2.length; i++) {
16477
16873
  flat[i * 2] = poly2[i].x;
@@ -16479,15 +16875,10 @@ function flattenVerts2(poly2) {
16479
16875
  }
16480
16876
  return flat;
16481
16877
  }
16482
- function polygonContainsPolygon(outer, inner) {
16483
- if (inner.length === 0) return false;
16484
- const flatOuter = flattenVerts2(outer);
16485
- return inner.every((p) => pointInPolygon(flatOuter, p.x, p.y));
16486
- }
16487
16878
  function polygonsIntersect(a, b) {
16488
16879
  if (a.length === 0 || b.length === 0) return false;
16489
- if (pointInPolygon(flattenVerts2(a), b[0].x, b[0].y)) return true;
16490
- 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;
16491
16882
  for (let i = 0; i < a.length; i++) {
16492
16883
  const a0 = a[i], a1 = a[(i + 1) % a.length];
16493
16884
  for (let j = 0; j < b.length; j++) {
@@ -16497,35 +16888,105 @@ function polygonsIntersect(a, b) {
16497
16888
  }
16498
16889
  return false;
16499
16890
  }
16500
- function pathContainsPoint(path, x, y) {
16501
- 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);
16937
+ }
16938
+ function pathContainsPoint(path, x, y, opts = {}) {
16939
+ return pointInPath(path, x, y, opts);
16502
16940
  }
16503
- function pathContainsRect(path, rect) {
16941
+ function pathContainsRect(path, rect, opts = {}) {
16504
16942
  if (path.kind === "rect") {
16505
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;
16506
16944
  }
16507
- 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);
16508
16951
  }
16509
- function pathIntersectsRect(path, rect) {
16952
+ function pathIntersectsRect(path, rect, opts = {}) {
16510
16953
  if (path.kind === "rect") {
16511
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;
16512
16955
  }
16513
- 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);
16514
16963
  }
16515
- function pathContainsPolygon(path, polygon) {
16964
+ function pathContainsPolygon(path, polygon, opts = {}) {
16516
16965
  if (polygon.length === 0) return false;
16517
16966
  if (path.kind === "rect") {
16518
16967
  return polygon.every(
16519
16968
  (p) => p.x >= path.x && p.x <= path.x + path.width && p.y >= path.y && p.y <= path.y + path.height
16520
16969
  );
16521
16970
  }
16522
- 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);
16523
16977
  }
16524
- function pathIntersectsPolygon(path, polygon) {
16978
+ function pathIntersectsPolygon(path, polygon, opts = {}) {
16979
+ if (polygon.length === 0) return false;
16525
16980
  if (path.kind === "rect") {
16526
16981
  return polygonsIntersect(rectToVerts(path), polygon);
16527
16982
  }
16528
- 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);
16529
16990
  }
16530
16991
 
16531
16992
  // src/features/paths/markerSites.ts
@@ -16918,10 +17379,11 @@ var TEXT_PAINTER = {
16918
17379
  if (boxes.length === 0) return null;
16919
17380
  return rectsToPath(boxes);
16920
17381
  },
16921
- // `filled` is unconditional: a text node's `data.fill: null` still resolves
16922
- // to the default in `ResolvedRun`, so there is no outline-only text to
16923
- // report. The reach is the point — a heavily outlined glyph was unpickable
16924
- // 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.
16925
17387
  ink: (node, _pose, ctx) => {
16926
17388
  const d = node.data;
16927
17389
  return { filled: true, ...inkReach(resolveNodeStroke(d.stroke), ctx?.scale) };
@@ -16957,7 +17419,7 @@ function resolveNodeFill(fill, fallback) {
16957
17419
  return fill === void 0 ? fallback : fill;
16958
17420
  }
16959
17421
  function resolveNodeStroke(stroke) {
16960
- if (stroke == null || stroke.paint == null) return null;
17422
+ if (stroke?.paint == null) return null;
16961
17423
  return stroke;
16962
17424
  }
16963
17425
  function strokeInPoseFrame(stroke, box) {
@@ -18079,6 +18541,7 @@ function worldOf(region, xf, view) {
18079
18541
  }
18080
18542
 
18081
18543
  // src/affordances/rotationHandle.ts
18544
+ var isAnnulusForm = (paint) => "fill" in paint || "stroke" in paint || "insetPx" in paint;
18082
18545
  var DEFAULT_PAINT = {
18083
18546
  fill: { fill: "solid", color: "rgba(26, 19, 13, 0.12)" },
18084
18547
  insetPx: 10
@@ -18087,7 +18550,7 @@ function createRotationAffordance(opts = {}) {
18087
18550
  const { bandPx = 24, paint = DEFAULT_PAINT, cursor } = opts;
18088
18551
  const annulusPaint = (() => {
18089
18552
  if (!paint) return null;
18090
- if ("paint" in paint) {
18553
+ if (!isAnnulusForm(paint)) {
18091
18554
  return { kind: "annulus", stroke: paint };
18092
18555
  }
18093
18556
  return {
@@ -18283,29 +18746,6 @@ function useTextTool() {
18283
18746
  []
18284
18747
  );
18285
18748
  }
18286
- function usePinchZoomTool(canvasRef, getView, setView, opts = {}) {
18287
- const getViewRef = useRef(getView);
18288
- getViewRef.current = getView;
18289
- const setViewRef = useRef(setView);
18290
- setViewRef.current = setView;
18291
- const { min = DEFAULT_MIN_ZOOM, max = DEFAULT_MAX_ZOOM, enabled = true } = opts;
18292
- const resolveTargetRef = useRef(opts.resolveTarget);
18293
- resolveTargetRef.current = opts.resolveTarget;
18294
- usePinchGesture(canvasRef, (clientAnchor, scaleFactor) => {
18295
- const el = canvasRef.current;
18296
- if (!el) return;
18297
- const target = resolveTargetRef.current?.(clientAnchor.x, clientAnchor.y) ?? null;
18298
- let anchor;
18299
- if (target) {
18300
- anchor = { x: clientAnchor.x - target.origin.left, y: clientAnchor.y - target.origin.top };
18301
- } else {
18302
- const [cx, cy] = clientToCanvas(el, clientAnchor.x, clientAnchor.y);
18303
- anchor = { x: cx, y: cy };
18304
- }
18305
- const newView = zoomAt(target?.view ?? getViewRef.current(), anchor, scaleFactor, { min, max });
18306
- (target ? target.setView : setViewRef.current)(newView);
18307
- }, enabled);
18308
- }
18309
18749
 
18310
18750
  // src/util/constrainTo45.ts
18311
18751
  function constrainTo45(dx, dy) {
@@ -20621,7 +21061,6 @@ function CanvasInner(props, ref) {
20621
21061
  previewIdsExtra,
20622
21062
  previewPoseExtra,
20623
21063
  gestureSource,
20624
- viewport,
20625
21064
  backgroundFill,
20626
21065
  cursorCoordsHud,
20627
21066
  pickHud,
@@ -20630,7 +21069,8 @@ function CanvasInner(props, ref) {
20630
21069
  decorationLayer,
20631
21070
  getIsVisible,
20632
21071
  layerVisibility,
20633
- layerOrder
21072
+ layerOrder,
21073
+ layerGroups
20634
21074
  } = props;
20635
21075
  const resolvedDebugConfig = useMemo(() => {
20636
21076
  if (debugProp === false) return null;
@@ -20783,25 +21223,7 @@ function CanvasInner(props, ref) {
20783
21223
  const glRendererRef = useRef(null);
20784
21224
  const layerCacheRef = useRef(/* @__PURE__ */ new Map());
20785
21225
  const lastResizeRef = useRef(null);
20786
- const pinchConfig = viewport?.pinchZoom === true ? {} : viewport?.pinchZoom || null;
20787
21226
  const viewRegistry = useOptionalViewRegistry();
20788
- const viewRegistryRef = useRef(viewRegistry);
20789
- viewRegistryRef.current = viewRegistry;
20790
- const resolvePinchTarget = useCallback((clientX, clientY) => {
20791
- const reg = viewRegistryRef.current;
20792
- if (!reg) return null;
20793
- const target = reg.resolver.at(null, clientX, clientY);
20794
- if (target.id === null) return null;
20795
- const api = reg.list().find((r) => r.id === target.id)?.target.deps?.().view;
20796
- if (!api) return null;
20797
- return { view: api.get(), setView: api.set, origin: target.origin };
20798
- }, []);
20799
- usePinchZoomTool(
20800
- canvasRef,
20801
- getView,
20802
- setView,
20803
- { ...pinchConfig ?? {}, enabled: pinchConfig !== null, resolveTarget: resolvePinchTarget }
20804
- );
20805
21227
  const noopAdapter = useMemo(
20806
21228
  () => ({
20807
21229
  getPose: () => ({}),
@@ -20893,6 +21315,7 @@ function CanvasInner(props, ref) {
20893
21315
  const selectedIdsForWiring = effectiveSelection.current;
20894
21316
  const {
20895
21317
  helpers: viewHelpers,
21318
+ chromeState: viewChromeState,
20896
21319
  effectiveBoundsOf,
20897
21320
  previewExtraRef
20898
21321
  } = useViewHelpers({
@@ -21061,6 +21484,7 @@ function CanvasInner(props, ref) {
21061
21484
  dpr: dprProp,
21062
21485
  layerVisibility,
21063
21486
  layerOrder,
21487
+ layerGroups,
21064
21488
  shaders
21065
21489
  });
21066
21490
  paintInputsRef.current = {
@@ -21071,6 +21495,7 @@ function CanvasInner(props, ref) {
21071
21495
  dpr: dprProp,
21072
21496
  layerVisibility,
21073
21497
  layerOrder,
21498
+ layerGroups,
21074
21499
  shaders
21075
21500
  };
21076
21501
  const paint = useCallback(() => {
@@ -21085,6 +21510,7 @@ function CanvasInner(props, ref) {
21085
21510
  dpr: dprIn,
21086
21511
  layerVisibility: vis,
21087
21512
  layerOrder: order,
21513
+ layerGroups: groups,
21088
21514
  shaders: paintShaders
21089
21515
  } = paintInputsRef.current;
21090
21516
  sink?.beginFrame();
@@ -21135,7 +21561,9 @@ function CanvasInner(props, ref) {
21135
21561
  order,
21136
21562
  view,
21137
21563
  { width: w, height: h },
21138
- layerCacheRef.current
21564
+ layerCacheRef.current,
21565
+ void 0,
21566
+ groups
21139
21567
  );
21140
21568
  renderer.render(commands, viewToMat3(view));
21141
21569
  paintedVersionRef.current = contentVersionRef.current?.() ?? 0;
@@ -21153,8 +21581,11 @@ function CanvasInner(props, ref) {
21153
21581
  dprProp,
21154
21582
  layerVisibility,
21155
21583
  layerOrder,
21584
+ layerGroups,
21156
21585
  shaderIdKey,
21157
21586
  syncPaint,
21587
+ viewChromeState,
21588
+ getIsVisible,
21158
21589
  requestRedraw
21159
21590
  ]);
21160
21591
  useEffect(() => {
@@ -21365,6 +21796,14 @@ function createDependentsIndex() {
21365
21796
  // src/core/scene/scene.ts
21366
21797
  var defaultIdCounter = 0;
21367
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
+ }
21368
21807
  function createScene(options) {
21369
21808
  if (!options.systemLayers || options.systemLayers.length === 0) {
21370
21809
  throw new Error("useScene: systemLayers must declare at least one layer");
@@ -21588,6 +22027,28 @@ function createScene(options) {
21588
22027
  state.nodes.get(id).derivePath = spec.derivePath;
21589
22028
  if (cacheForRedo) pendingDerivePathPatches.set(id, spec.derivePath);
21590
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
+ }
21591
22052
  function registerKitOp(kind, handler) {
21592
22053
  registered.set(kind, handler);
21593
22054
  }
@@ -21645,8 +22106,10 @@ function createScene(options) {
21645
22106
  }
21646
22107
  },
21647
22108
  revert: (p) => {
22109
+ const keys = new Map((p.fnKeys ?? []).map((k) => [k.id, k]));
21648
22110
  for (const n of p.nodes) {
21649
22111
  const clone = n.kind === "container" ? { ...n, children: [...n.children] } : { ...n };
22112
+ restoreNodeFunctions(clone, keys.get(n.id));
21650
22113
  state.nodes.set(n.id, clone);
21651
22114
  dependents.add(n.id, clone.dependsOn ?? []);
21652
22115
  }
@@ -21984,6 +22447,9 @@ function createScene(options) {
21984
22447
  remove(id) {
21985
22448
  scene.removeMany([id]);
21986
22449
  },
22450
+ removalClosure(rootIds) {
22451
+ return removalClosure(rootIds).ids;
22452
+ },
21987
22453
  removeMany(rootIds) {
21988
22454
  if (rootIds.length === 0) return;
21989
22455
  const { ids, removing } = removalClosure(rootIds);
@@ -21993,7 +22459,20 @@ function createScene(options) {
21993
22459
  });
21994
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);
21995
22461
  for (const nid of ids) overrides.clear(nid);
21996
- 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
+ };
21997
22476
  executeAndLog("kit:remove", payload, "remove");
21998
22477
  },
21999
22478
  update(id, patch) {
@@ -22290,6 +22769,10 @@ function createScene(options) {
22290
22769
  const layer = { id: l.id };
22291
22770
  if (l.visible === false) layer.visible = false;
22292
22771
  if (l.locked === true) layer.locked = true;
22772
+ if (l.kind === "user") {
22773
+ layer.kind = "user";
22774
+ layer.name = l.name;
22775
+ }
22293
22776
  return layer;
22294
22777
  });
22295
22778
  return { version: 1, systemLayers, nodes };
@@ -22307,12 +22790,7 @@ function createScene(options) {
22307
22790
  if (state.layerIndex.has(spec.id)) {
22308
22791
  throw new Error(`Scene.loadState: duplicate system layer id "${spec.id}"`);
22309
22792
  }
22310
- state.layers.push({
22311
- kind: "system",
22312
- id: spec.id,
22313
- visible: spec.visible ?? true,
22314
- locked: spec.locked ?? false
22315
- });
22793
+ state.layers.push(layerFromSerialized(spec));
22316
22794
  state.layerIndex.set(spec.id, i);
22317
22795
  }
22318
22796
  history.clear();
@@ -22412,16 +22890,16 @@ function sceneSelectionStore(scene) {
22412
22890
  }
22413
22891
  function sceneFromJSON(json, options) {
22414
22892
  const registry2 = options.registry ?? {};
22415
- const initial = specsFromSerialized(json, registry2);
22416
- return createScene({
22417
- systemLayers: json.systemLayers,
22418
- initial,
22893
+ const scene = createScene({
22894
+ systemLayers: json.systemLayers.map((l) => ({ id: l.id })),
22419
22895
  registry: registry2,
22420
22896
  ...options.historyLimit !== void 0 ? { historyLimit: options.historyLimit } : {},
22421
22897
  ...options.coalesceWindowMs !== void 0 ? { coalesceWindowMs: options.coalesceWindowMs } : {},
22422
22898
  ...options.generateId !== void 0 ? { generateId: options.generateId } : {},
22423
22899
  ...options.ops !== void 0 ? { ops: options.ops } : {}
22424
22900
  });
22901
+ scene.loadState(json);
22902
+ return scene;
22425
22903
  }
22426
22904
  var SelectionContext = createContext(null);
22427
22905
  function SelectionContextProvider({ children }) {
@@ -23164,7 +23642,7 @@ function CanvasView(props) {
23164
23642
  }
23165
23643
  function ActionsProviderIfRoot({ children }) {
23166
23644
  const parent = useActionsRegistry();
23167
- if (parent) return /* @__PURE__ */ jsx(Fragment, { children });
23645
+ if (parent) return /* @__PURE__ */ jsx(ActionsScope, { children });
23168
23646
  return /* @__PURE__ */ jsx(ActionsProvider, { children });
23169
23647
  }
23170
23648
  function useToolActions(tools) {
@@ -23231,6 +23709,65 @@ function PointerPublisher({
23231
23709
  }, [ctx, canvasRef, registry2]);
23232
23710
  return null;
23233
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
+ }
23234
23771
  function isPathLike(p) {
23235
23772
  if (!p || typeof p !== "object") return false;
23236
23773
  const k = p.kind;
@@ -23412,12 +23949,19 @@ function sceneToAdapter(scene, options = {}) {
23412
23949
  id: node.id,
23413
23950
  ...index !== void 0 ? { index } : {},
23414
23951
  ...node.parent !== null ? { parent: node.parent } : {},
23415
- ...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 } : {}
23416
23957
  });
23417
23958
  },
23418
23959
  removeNode(id) {
23419
23960
  scene.remove(asNodeId(id));
23420
23961
  },
23962
+ getRemovalClosure(ids) {
23963
+ return [...scene.removalClosure(ids.map(asNodeId))];
23964
+ },
23421
23965
  // AreaSelectAdapter surface — included unconditionally so plain
23422
23966
  // `useSelectTool(sceneToAdapter(scene, { selection }))` Just Works for the
23423
23967
  // marquee gesture. `applyOps` (above) dispatches transiently when called
@@ -25343,17 +25887,18 @@ function useActionsPropResolver(actions) {
25343
25887
  useEffect(() => {
25344
25888
  if (!reg) return;
25345
25889
  const prop = actionsRef.current;
25890
+ const unregisters = [];
25346
25891
  if (prop === null) {
25347
- const ids = reg.list().map((a) => a.id);
25348
- for (const id of ids) reg.unregister(id);
25349
- 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
+ };
25350
25896
  }
25351
25897
  if (!prop) return;
25352
- const unregisters = [];
25353
25898
  const warnedIds = /* @__PURE__ */ new Set();
25354
25899
  for (const [slotId, entry] of Object.entries(prop)) {
25355
25900
  if (entry === null) {
25356
- reg.unregister(slotId);
25901
+ unregisters.push(reg.mute(slotId));
25357
25902
  continue;
25358
25903
  }
25359
25904
  const isFull = (e) => typeof e.id === "string" && typeof e.label === "string" && !!e.invoker;
@@ -25410,7 +25955,7 @@ function useViewportActions(args) {
25410
25955
  r.register(makePinchZoomAction(typeof pinchZoom === "object" ? pinchZoom : {}))
25411
25956
  );
25412
25957
  } else {
25413
- r.unregister("viewport.pinchZoom");
25958
+ unregisters.push(r.mute("viewport.pinchZoom"));
25414
25959
  }
25415
25960
  return () => {
25416
25961
  for (const u of unregisters) u();
@@ -25639,7 +26184,6 @@ function SceneCanvasInner(props, ref) {
25639
26184
  () => isSerialized(sceneInput) ? sceneFromJSON(sceneInput, {}) : null
25640
26185
  );
25641
26186
  const scene = bakedScene ?? sceneInput;
25642
- useSyncExternalStore(scene.subscribe, scene.getVersion, scene.getVersion);
25643
26187
  const { view: viewProp, onViewChange: onViewChangeProp, defaultView, ...restProps } = rest;
25644
26188
  const internalCanvasRef = useRef(null);
25645
26189
  const canvasApiRef = useRef(null);
@@ -25670,6 +26214,9 @@ function SceneCanvasInner(props, ref) {
25670
26214
  useEffect(() => scene.overrides.subscribe(() => {
25671
26215
  canvasApiRef.current?.requestRedraw?.();
25672
26216
  }), [scene]);
26217
+ useEffect(() => scene.subscribe(() => {
26218
+ canvasApiRef.current?.requestRedraw?.();
26219
+ }), [scene]);
25673
26220
  const currentViewRef = useRef(
25674
26221
  viewProp ?? defaultView ?? { x: 0, y: 0, scale: { x: 1, y: 1 } }
25675
26222
  );
@@ -28299,7 +28846,7 @@ function applyOverlayStyle(el, style) {
28299
28846
  el.style.border = "0";
28300
28847
  el.style.outline = "none";
28301
28848
  el.style.background = "transparent";
28302
- 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";
28303
28850
  el.style.caretColor = style.caretColor;
28304
28851
  el.style.font = fontString(style);
28305
28852
  el.style.lineHeight = String(style.lineHeight);
@@ -31267,6 +31814,6 @@ function mergeContributions(...bundles) {
31267
31814
  return out;
31268
31815
  }
31269
31816
 
31270
- 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 };
31271
- //# sourceMappingURL=chunk-NU7D64RL.js.map
31272
- //# sourceMappingURL=chunk-NU7D64RL.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