@weasel-js/core 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +242 -0
  2. package/README.md +24 -4
  3. package/dist/{builtins-HWyuLzE-.d.ts → builtins-DLn2lCr_.d.ts} +578 -10
  4. package/dist/{chunk-BJN5ZRIJ.js → chunk-3TY6FHAF.js} +7 -4
  5. package/dist/chunk-3TY6FHAF.js.map +1 -0
  6. package/dist/{chunk-H6N2JSZ3.js → chunk-DGWNNKGD.js} +3 -3
  7. package/dist/{chunk-H6N2JSZ3.js.map → chunk-DGWNNKGD.js.map} +1 -1
  8. package/dist/{chunk-MZAIDI6D.js → chunk-G4ZGQMY7.js} +2102 -1222
  9. package/dist/chunk-G4ZGQMY7.js.map +1 -0
  10. package/dist/{chunk-HPFJUUTF.js → chunk-LU4IJ52W.js} +5 -61
  11. package/dist/chunk-LU4IJ52W.js.map +1 -0
  12. package/dist/{chunk-D4AAWR64.js → chunk-M24CLARN.js} +4 -4
  13. package/dist/{chunk-D4AAWR64.js.map → chunk-M24CLARN.js.map} +1 -1
  14. package/dist/{chunk-4RJP2N2L.js → chunk-NE27EOLP.js} +8 -5
  15. package/dist/chunk-NE27EOLP.js.map +1 -0
  16. package/dist/{chunk-DOBSZOPR.js → chunk-UPZY4ART.js} +3 -3
  17. package/dist/{chunk-DOBSZOPR.js.map → chunk-UPZY4ART.js.map} +1 -1
  18. package/dist/{chunk-RMR4QMM6.js → chunk-WPNA6L5O.js} +4 -4
  19. package/dist/{chunk-RMR4QMM6.js.map → chunk-WPNA6L5O.js.map} +1 -1
  20. package/dist/{chunk-44EWMJV2.js → chunk-XMAWPGTK.js} +5 -5
  21. package/dist/{chunk-44EWMJV2.js.map → chunk-XMAWPGTK.js.map} +1 -1
  22. package/dist/clipboard.d.ts +1 -1
  23. package/dist/clipboard.js +3 -3
  24. package/dist/clone.d.ts +2 -2
  25. package/dist/clone.js +3 -3
  26. package/dist/grid-CY5UKlZh.d.ts +98 -0
  27. package/dist/index.css +11 -35
  28. package/dist/index.css.map +1 -1
  29. package/dist/index.d.ts +460 -784
  30. package/dist/index.js +9 -9
  31. package/dist/insert.d.ts +2 -3
  32. package/dist/insert.js +1 -1
  33. package/dist/move.d.ts +5 -4
  34. package/dist/move.js +4 -4
  35. package/dist/move.js.map +1 -1
  36. package/dist/patterns-builtin.js +1 -1
  37. package/dist/{pointSnapToGrid-7mCcm6qG.d.ts → pointSnapToGrid-Dp2IxLGb.d.ts} +53 -60
  38. package/dist/renderer.css +11 -35
  39. package/dist/renderer.css.map +1 -1
  40. package/dist/renderer.d.ts +16 -8
  41. package/dist/renderer.js +9 -9
  42. package/dist/resize.d.ts +3 -3
  43. package/dist/resize.js +2 -2
  44. package/dist/routing.d.ts +1 -1
  45. package/dist/routing.js +1 -1
  46. package/dist/{types-CxC8P6W0.d.ts → types-D-9QWBy5.d.ts} +9 -6
  47. package/dist/{types-D_74YlWs.d.ts → types-qYpyTAiZ.d.ts} +4 -7
  48. package/package.json +15 -12
  49. package/dist/chunk-4RJP2N2L.js.map +0 -1
  50. package/dist/chunk-BJN5ZRIJ.js.map +0 -1
  51. package/dist/chunk-HPFJUUTF.js.map +0 -1
  52. package/dist/chunk-MZAIDI6D.js.map +0 -1
  53. package/dist/grid-BXdz8GF2.d.ts +0 -158
  54. package/dist/options-CzjxFoYZ.d.ts +0 -67
@@ -1,25 +1,29 @@
1
1
  import { rotatePoint, aabbCenter, extractPolylines, createSimulation } from './chunk-PNNZSYRD.js';
2
- import { createReparentOp, createTransformOp, snap } from './chunk-44EWMJV2.js';
3
- import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-H6N2JSZ3.js';
4
- import { unitScale, formatUnit, resolveUnit, ANGLE_RADIANS, withinPxRadius, scaleDelta, pxExtent, withinPxBox, pointToGridCell } from './chunk-HPFJUUTF.js';
5
- import { unionBounds, visualBoundsViaDescriptor, translatePoseViaDescriptor, poseDescriptorForNode, unionAABB, AUTO_POSE_DESCRIPTOR, remapRotatedLeaf, PATH_Z, PATH_Q, PATH_C, PATH_L, PATH_M, polylineFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, polygonFromPoints, PathBuilder, boundsOfPath, pointInPath, isPathLike, strokeHitTest, isRectPose, transformPath, translatePath, DEFAULT_FLATTEN_TOLERANCE, flattenQuadratic, flattenCubic, flattenCubicWithArcLen, flattenQuadraticWithArcLen, RECT_POSE_DESCRIPTOR, aabbIntersectsRect } from './chunk-L5YNZYEG.js';
6
- import { dispatchApplyBatch, dlog, dwarn, usePointerContext, PointerContextProvider, extractWeaselClipboardFromSvg, parseWeaselClipboardText, materializePaste, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, sniffWeaselClipboardText, applyOpsTo, usePointerPosition } from './chunk-RMR4QMM6.js';
7
- import { createSetSelectionOp } from './chunk-DOBSZOPR.js';
8
- import { registerOpFactory, createInsertOp, createDeleteOp, rebuildOp } from './chunk-4RJP2N2L.js';
9
- import { getTexture, chunks, dots, crosshatch, hatch } from './chunk-BJN5ZRIJ.js';
2
+ import { createReparentOp, createTransformOp, snap } from './chunk-XMAWPGTK.js';
3
+ import { fixedCornerOf, DEFAULT_RESIZE_BEHAVIORS, CORNER_ANCHORS, cornerPoint } from './chunk-DGWNNKGD.js';
4
+ import { withinPxRadius, scaleDelta, pxExtent, withinPxBox, pointToGridCell } from './chunk-LU4IJ52W.js';
5
+ import { unionBounds, visualBoundsViaDescriptor, translatePoseViaDescriptor, poseDescriptorForNode, unionAABB, AUTO_POSE_DESCRIPTOR, remapRotatedLeaf, PATH_Z, PATH_Q, PATH_C, PATH_L, PATH_M, polylineFromPoints, starPath, regularPolygonPath, linePath, ellipsePath, rectPath, polygonFromPoints, PathBuilder, boundsOfPath, pointInPath, isPathLike, strokeHitTest, isRectPose, transformPath, translatePath, DEFAULT_FLATTEN_TOLERANCE, flattenCubicWithArcLen, flattenQuadraticWithArcLen, flattenQuadratic, flattenCubic, RECT_POSE_DESCRIPTOR, aabbIntersectsRect } from './chunk-L5YNZYEG.js';
6
+ import { dispatchApplyBatch, dlog, dwarn, usePointerContext, PointerContextProvider, applyOpsTo, extractWeaselClipboardFromSvg, parseWeaselClipboardText, materializePaste, WEASEL_CLIPBOARD_MIME, WEASEL_CLIPBOARD_MIME_WEB, sniffWeaselClipboardText, usePointerPosition } from './chunk-WPNA6L5O.js';
7
+ import { createSetSelectionOp } from './chunk-UPZY4ART.js';
8
+ import { registerOpFactory, createInsertOp, createDeleteOp, rebuildOp } from './chunk-NE27EOLP.js';
9
+ import { getTexture, chunks, dots, crosshatch, hatch } from './chunk-3TY6FHAF.js';
10
10
  import { createContext, createElement, forwardRef, useMemo, useRef, useCallback, useState, useLayoutEffect, useEffect, useImperativeHandle, useSyncExternalStore, useContext, useId, useReducer } from 'react';
11
11
  import { GLYPH_COVERAGE_GLSL, subscribeGlyphReady, DEFAULT_BAKE_BUDGET, markAllFontsNotUploaded, resetBakeBudget, syncDynamicPageTexture, ensureFontTexture, dynamicPageTextureId, textureCacheKey, GLYPH_MODE_R8, GLYPH_MODE_MSDF } from '@weasel-js/font';
12
- export { canQueryLocalFonts, canQueryLocalFonts as canQueryLocalFonts2, enableLocalFontOutlines, enableLocalFontOutlines as enableLocalFontOutlines2, hasFontOutlines, hasFontOutlines as hasFontOutlines2, isCanvasFont, isCanvasFont as isCanvasFont2, listFontOutlines, listFontOutlines as listFontOutlines2, outlineStatus, outlineStatus as outlineStatus2, registerCanvasFont, registerCanvasFont as registerCanvasFont2, registerFont, registerFont as registerFont2, registerFontOutlines, registerFontOutlines as registerFontOutlines2, subscribeGlyphReady, subscribeGlyphReady as subscribeGlyphReady2, unregisterCanvasFont, unregisterCanvasFont as unregisterCanvasFont2, unregisterFontOutlines, unregisterFontOutlines as unregisterFontOutlines2 } from '@weasel-js/font';
12
+ export { canQueryLocalFonts, canQueryLocalFonts as canQueryLocalFonts2, enableLocalFontOutlines, enableLocalFontOutlines as enableLocalFontOutlines2, fontOutlineRegistry, fontOutlineRegistry as fontOutlineRegistry2, fontRegistry, fontRegistry as fontRegistry2, hasFontOutlines, hasFontOutlines as hasFontOutlines2, isCanvasFont, isCanvasFont as isCanvasFont2, listFontOutlines, listFontOutlines as listFontOutlines2, outlineStatus, outlineStatus as outlineStatus2, registerCanvasFont, registerCanvasFont as registerCanvasFont2, registerFont, registerFont as registerFont2, registerFontOutlines, registerFontOutlines as registerFontOutlines2, subscribeGlyphReady, subscribeGlyphReady as subscribeGlyphReady2, unregisterCanvasFont, unregisterCanvasFont as unregisterCanvasFont2, unregisterFontOutlines, unregisterFontOutlines as unregisterFontOutlines2 } from '@weasel-js/font';
13
13
  import { hexToColorLiteral, resolvePaletteColor, colorLiteralToHex, dashForStrokeStyle, strokeDashStyleOf, resolveScreenLength, alignedStrokeRect, lerpColorArray, hslToSrgb, srgbFloatToOklab, oklabToOklch, oklchToOklab, oklabToSrgbU8 } from '@weasel-js/paint';
14
14
  export { STROKE_DASH_RATIOS, alignedStrokeRect, contrastLineColor, dashForStrokeStyle, hexToOklchDeg, hslToSrgb, interpolateSrgb, lerpColorArray, lerpOklab, lerpOklch, linearSrgbToOklab, linearToSrgb, oklabToLinearSrgb, oklabToOklch, oklabToSrgbU8, oklchDegToHex, oklchToOklab, resolveScreenLength, srgbFloatToOklab, srgbToHsl, srgbToLinear, srgbU8ToOklab, strokeDashStyleOf } from '@weasel-js/paint';
15
- import { boxToBox, forEachSegment, multiply as multiply$1, rotate, translate as translate$1, rectToContour, invert as invert$1, clampRectWithin, applyToPoint, rotateAboutPoint, cubicEvalAt, pointInPolygon, segmentsCross, pathCommandCoordCount, pointSegmentDist2, elevateQuadraticToCubic } from '@weasel-js/geom';
16
- import { resolveTextStyle, resolveAlign, resolveRuns, cachedLayoutRuns, verticalAlignOffset, textPoseLayoutInput, runsToPlainText, layoutTextPose, fontString, textLineBoxes } from '@weasel-js/text';
15
+ import { boxToBox, forEachSegment, multiply as multiply$1, rotate, translate as translate$1, rectToContour, splitCubicAt, invert as invert$1, clampRectWithin, applyToPoint, rotateAboutPoint, nearestOnLine, nearestOnCubic, cubicEvalAt, pathCommandCoordCount, pointInPolygon, segmentsCross, nearestOnPath, splitQuadraticAt } from '@weasel-js/geom';
16
+ import { createReflectable } from '@weasel-js/registry';
17
+ export { createReflectable } from '@weasel-js/registry';
18
+ import { resolveAlign, resolveTextStyle, resolveRuns, cachedLayoutRuns, verticalAlignOffset, textPoseLayoutInput, runsToPlainText, layoutTextPose, fontString, textLineBoxes, SCRIPT_METRICS } from '@weasel-js/text';
17
19
  export { DEFAULT_TEXT_STYLE, MARKDOWN_RUN_GRAMMAR, SCRIPT_METRICS, cachedLayoutRuns, createMarkdownRenderer, fontString, layoutMarkdown, layoutRuns, layoutTextPose, markdownToRuns, measureText, measureTextBounds, measuredWidth, resolveAlign, resolveRuns, resolveTextStyle, runsToMarkdown, runsToPlainText, textLineBoxes, textPoseLayoutInput, toRuns, transformRunTexts, verticalAlignOffset } from '@weasel-js/text';
18
20
  import earcut from 'earcut';
19
- import { isAnchorOrControl, isBody, ActionDisabledReason, scratchKey, resolveParams, createDispatcher, DEFAULT_ALLOWED_CAPABILITIES, itemsFromFiles, evaluate, resolveDeviceProfile, isResizeHandle, isRotateHandle, defineViewportTool as defineViewportTool$1, NEVER, recordModeSwitch, DEFAULT_DEVICE_PROFILE, defineTool as defineTool$1, getScratch, openPointerSession, actionBindings, ALWAYS, clientToCanvas, setScratch, mergeContributions as mergeContributions$1, clientToCanvasRect, isEditableTarget } from '@weasel-js/routing';
20
- export { ALWAYS, ActionDisabledReason, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, NEVER, actionBindings, actionItems, buildDepsFromRequires, buildRuleCtx, clientToCanvas, createDispatcher, deleteScratch, describeRule, evaluate, evaluateEnabled, getScratch, isEditableTarget, liveScope, matchesKeyBinding, openPointerSession, resolveDeviceProfile, resolveParams, scopeBindings, scratchKey, setScratch, specificity } from '@weasel-js/routing';
21
- import { ActiveToolContextProviderIfRoot, useOptionalActiveToolContext, useTools as useTools$1, useOptionalDepRegistry, DepRegistryProvider, useActionsRegistry, ActionsScope, ActionsProvider, useDepRegistry, useDepSource, useGestureDispatcher, ActiveToolContextProvider } from '@weasel-js/routing/react';
22
- export { ActionsProvider, ActionsScope, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, DepRegistryProvider, useAction, useActionsRegistry, useActiveToolContext, useDepRegistry, useDepSource, useGestureDispatcher, useOngoingAction, useOptionalActiveToolContext, useOptionalDepRegistry } from '@weasel-js/routing/react';
21
+ import { unitScale, formatUnit, resolveUnit, ANGLE_RADIANS } from '@weasel-js/quantity';
22
+ export { ANGLE_RADIANS, IMPERIAL_INCHES, METRIC_MM, PIXELS, formatUnit, resolveUnit, unitScale } from '@weasel-js/quantity';
23
+ import { scratchKey, isAnchorOrControl, isBody, ActionDisabledReason, resolveParams, DRAG_THRESHOLD_PX, pastDragThreshold, setScratch, createDispatcher, DEFAULT_ALLOWED_CAPABILITIES, itemsFromFiles, evaluate, resolveDeviceProfile, defineTool, isResizeHandle, isRotateHandle, NEVER, recordModeSwitch, DEFAULT_DEVICE_PROFILE, openPointerSession, actionBindings, getScratch, ALWAYS, clientToCanvas, mergeContributions as mergeContributions$1, clientToCanvasRect, isEditableTarget } from '@weasel-js/routing';
24
+ export { ALWAYS, ActionDisabledReason, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, DRAG_THRESHOLD_PX, NEVER, actionBindings, actionItems, buildDepsFromRequires, buildRuleCtx, clientToCanvas, createDepRegistry, createDispatcher, defineTool, deleteScratch, describeRule, evaluate, evaluateEnabled, getScratch, isEditableTarget, liveScope, matchesKeyBinding, modeShortcutSpec, modeShortcuts, openPointerSession, pastDragThreshold, resolveDeviceProfile, resolveParams, scopeBindings, scratchKey, setScratch, specificity } from '@weasel-js/routing';
25
+ import { InputScope, useOptionalActiveToolContext, useTools as useTools$1, useActionsRegistry, useDepSource, useDepRegistry, useGestureDispatcher, useOptionalDepRegistry, useOffhandAction, DepRegistryProvider, ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot } from '@weasel-js/routing/react';
26
+ export { ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, DepRegistryProvider, InputScope, Yoke, useAction, useActionsRegistry, useActiveToolContext, useDepRegistry, useDepSource, useGestureDispatcher, useOngoingAction, useOptionalActiveToolContext, useOptionalDepRegistry, useYoke } from '@weasel-js/routing/react';
23
27
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
24
28
  import { pathCrop as pathCrop$1, pathDivide as pathDivide$1, pathUnion as pathUnion$1, pathSubtract as pathSubtract$1, pathExclude as pathExclude$1, pathIntersect as pathIntersect$1 } from '@weasel-js/geom/booleans';
25
29
  import { mimeMatchesGlob } from '@weasel-js/gestures';
@@ -27,9 +31,10 @@ import { createPaintedCursorState, GLYPHS, cursorWorldSize, cursorPaintOps, chro
27
31
  export { bakeCursor, createPaintedCursorState, cursorFor, resolveCursor, resolveCursorTier } from '@weasel-js/cursor';
28
32
  import { createHistory } from '@weasel-js/history';
29
33
  export * from '@weasel-js/history';
34
+ import { getActiveModeFor } from '@weasel-js/modes';
30
35
 
31
36
  // src/version.ts
32
- var VERSION = "1.6.1" ;
37
+ var VERSION = "1.7.0" ;
33
38
 
34
39
  // src/features/grid/roundToCell.ts
35
40
  function roundToCell(value, cellSize) {
@@ -1719,8 +1724,6 @@ function dropPoseKeyedMemoSlots(node) {
1719
1724
  if (entry.pose !== void 0) record.slots.delete(slot);
1720
1725
  }
1721
1726
  }
1722
-
1723
- // src/core/paintKinds.ts
1724
1727
  var BUILTINS = [
1725
1728
  {
1726
1729
  id: "solid",
@@ -1751,7 +1754,9 @@ function gradientKind(id, label, icon) {
1751
1754
  icon,
1752
1755
  // `bounds` units make the seed independent of the node's actual size.
1753
1756
  seed: (color) => gradientForBounds(id, UNIT_BOX, twoStopRamp(color), "bounds"),
1754
- colorOf: (paint) => paint.stops?.[0]?.color
1757
+ colorOf: (paint) => paint.stops?.[0]?.color,
1758
+ stopsOf: (paint) => paint.stops,
1759
+ fromStops: (stops) => gradientForBounds(id, UNIT_BOX, stops, "bounds")
1755
1760
  };
1756
1761
  }
1757
1762
  var UNIT_BOX = { x: 0, y: 0, width: 1, height: 1 };
@@ -1759,44 +1764,61 @@ function twoStopRamp(color) {
1759
1764
  const isWhite = color.slice(0, 7).toLowerCase() === "#ffffff";
1760
1765
  return [{ offset: 0, color }, { offset: 1, color: isWhite ? "#000000ff" : "#ffffffff" }];
1761
1766
  }
1762
- var KINDS = /* @__PURE__ */ new Map();
1767
+ var KINDS = createReflectable();
1763
1768
  function seedBuiltins() {
1764
- for (const entry of BUILTINS) KINDS.set(entry.id, entry);
1769
+ for (const entry of BUILTINS) KINDS.push(entry.id, entry, { source: "kit" });
1765
1770
  }
1766
1771
  seedBuiltins();
1772
+ KINDS.subscribe(bumpNodeMemoGeneration);
1773
+ var paintKindRegistry = KINDS.reflection;
1767
1774
  function asPaint(paint) {
1768
1775
  return paint;
1769
1776
  }
1770
1777
  function registerPaintKind(entry) {
1778
+ if (entry.stopsOf === void 0 !== (entry.fromStops === void 0)) {
1779
+ const missing = entry.stopsOf === void 0 ? "stopsOf" : "fromStops";
1780
+ throw new Error(
1781
+ `weasel registerPaintKind: kind "${entry.id}" is missing ${missing}. A gradient kind reads as a stop list and builds from one; supply both or neither.`
1782
+ );
1783
+ }
1771
1784
  if (entry.inPoseFrame === void 0 !== (entry.toBoundsFrame === void 0)) {
1772
1785
  const missing = entry.inPoseFrame === void 0 ? "inPoseFrame" : "toBoundsFrame";
1773
1786
  throw new Error(
1774
1787
  `weasel registerPaintKind: kind "${entry.id}" is missing ${missing}. A kind that converts one frame direction and not the other drifts on the next resize; supply both or neither.`
1775
1788
  );
1776
1789
  }
1777
- const displaced = KINDS.get(entry.id);
1778
- KINDS.set(entry.id, entry);
1779
- bumpNodeMemoGeneration();
1780
- return () => {
1781
- if (KINDS.get(entry.id) !== entry) return;
1782
- if (displaced) KINDS.set(entry.id, displaced);
1783
- else KINDS.delete(entry.id);
1784
- bumpNodeMemoGeneration();
1785
- };
1790
+ return KINDS.push(entry.id, entry);
1786
1791
  }
1787
1792
  function getPaintKind(kind) {
1788
1793
  return KINDS.get(kind ?? "solid");
1789
1794
  }
1790
1795
  function listPaintKinds() {
1791
- return [...KINDS.values()];
1796
+ return KINDS.entries().map((e) => e.value);
1797
+ }
1798
+ function listGradientKinds() {
1799
+ return listPaintKinds().filter((entry) => entry.stopsOf && entry.fromStops);
1800
+ }
1801
+ function switchGradientKind(paint, kind) {
1802
+ const from = paintKindOf(paint);
1803
+ const to = getPaintKind(kind);
1804
+ if (!from?.stopsOf || !to?.fromStops) return void 0;
1805
+ if (from.id === to.id) return paint;
1806
+ if (isGradientFill(paint) && isGradientFill({ fill: kind })) {
1807
+ return withGradientKind(paint, kind);
1808
+ }
1809
+ const { interpolate, opacity } = paint;
1810
+ return {
1811
+ ...to.fromStops(from.stopsOf(paint)),
1812
+ ...interpolate !== void 0 ? { interpolate } : {},
1813
+ ...opacity !== void 0 ? { opacity } : {}
1814
+ };
1792
1815
  }
1793
1816
  function paintKindOf(fill) {
1794
1817
  return getPaintKind(fill.fill ?? "solid");
1795
1818
  }
1796
1819
  function _resetPaintKindsForTests() {
1797
- KINDS = /* @__PURE__ */ new Map();
1820
+ KINDS.clear();
1798
1821
  seedBuiltins();
1799
- bumpNodeMemoGeneration();
1800
1822
  }
1801
1823
 
1802
1824
  // src/renderer/DrawCommand.ts
@@ -2970,11 +2992,13 @@ function circle(cx, r, size, segments = 32) {
2970
2992
  var BUILTIN_MARKERS = [
2971
2993
  {
2972
2994
  id: "arrow",
2995
+ reads: [],
2973
2996
  inset: 3,
2974
2997
  path: ({ size }) => poly([0, 0, -3, -1.5, -3, 1.5], size)
2975
2998
  },
2976
2999
  {
2977
3000
  id: "arrow-open",
3001
+ reads: [],
2978
3002
  inset: 0,
2979
3003
  fill: "none",
2980
3004
  outline: { width: 1 },
@@ -2986,16 +3010,19 @@ var BUILTIN_MARKERS = [
2986
3010
  },
2987
3011
  {
2988
3012
  id: "arrow-concave",
3013
+ reads: [],
2989
3014
  inset: 3,
2990
3015
  path: ({ size }) => poly([0, 0, -3, -1.5, -2, 0, -3, 1.5], size)
2991
3016
  },
2992
3017
  {
2993
3018
  id: "diamond",
3019
+ reads: [],
2994
3020
  inset: 4,
2995
3021
  path: ({ size }) => poly([0, 0, -2, -1.2, -4, 0, -2, 1.2], size)
2996
3022
  },
2997
3023
  {
2998
3024
  id: "diamond-hollow",
3025
+ reads: [],
2999
3026
  inset: 4,
3000
3027
  fill: "none",
3001
3028
  outline: { width: 0.5 },
@@ -3003,16 +3030,19 @@ var BUILTIN_MARKERS = [
3003
3030
  },
3004
3031
  {
3005
3032
  id: "circle",
3033
+ reads: [],
3006
3034
  inset: 2,
3007
3035
  path: ({ size }) => circle(-1, 1, size)
3008
3036
  },
3009
3037
  {
3010
3038
  id: "square",
3039
+ reads: [],
3011
3040
  inset: 2,
3012
3041
  path: ({ size }) => poly([0, -1, 0, 1, -2, 1, -2, -1], size)
3013
3042
  },
3014
3043
  {
3015
3044
  id: "bar",
3045
+ reads: [],
3016
3046
  inset: 0,
3017
3047
  fill: "none",
3018
3048
  outline: { width: 1 },
@@ -3021,48 +3051,42 @@ var BUILTIN_MARKERS = [
3021
3051
  ];
3022
3052
 
3023
3053
  // src/core/strokeMarkers.ts
3024
- var MARKERS = /* @__PURE__ */ new Map();
3054
+ var MARKERS = createReflectable();
3025
3055
  function seedBuiltins2() {
3026
- for (const entry of BUILTIN_MARKERS) MARKERS.set(entry.id, entry);
3056
+ for (const entry of BUILTIN_MARKERS) MARKERS.push(entry.id, entry, { source: "kit" });
3027
3057
  }
3028
3058
  seedBuiltins2();
3059
+ var markerRegistry = MARKERS.reflection;
3060
+ function markerGeneration() {
3061
+ return MARKERS.getVersion();
3062
+ }
3029
3063
  function registerMarker(entry) {
3030
- const displaced = MARKERS.get(entry.id);
3031
- MARKERS.set(entry.id, entry);
3032
- bumpNodeMemoGeneration();
3033
- return () => {
3034
- if (MARKERS.get(entry.id) !== entry) return;
3035
- if (displaced) MARKERS.set(entry.id, displaced);
3036
- else MARKERS.delete(entry.id);
3037
- bumpNodeMemoGeneration();
3038
- };
3064
+ return MARKERS.push(entry.id, entry);
3039
3065
  }
3040
3066
  function getMarker(key) {
3041
3067
  return key === void 0 ? void 0 : MARKERS.get(key);
3042
3068
  }
3043
3069
  function listMarkers() {
3044
- return [...MARKERS.values()];
3070
+ return MARKERS.entries().map((e) => e.value);
3045
3071
  }
3046
3072
  function _resetMarkersForTests() {
3047
- MARKERS = /* @__PURE__ */ new Map();
3073
+ MARKERS.clear();
3048
3074
  seedBuiltins2();
3049
- bumpNodeMemoGeneration();
3050
3075
  }
3051
3076
 
3052
3077
  // src/core/markerInset.ts
3053
- function resolveMarkerSize(ref, strokeWidth) {
3078
+ function resolveMarkerSize(ref, strokeWidth, scale = 1) {
3054
3079
  if (typeof ref === "string" || ref.size === void 0) return strokeWidth;
3055
- const { size } = ref;
3056
- return typeof size === "number" ? size : size.px;
3080
+ return resolveScreenLength(ref.size, scale);
3057
3081
  }
3058
3082
  function markerKeyOf(ref) {
3059
3083
  return typeof ref === "string" ? ref : ref.key;
3060
3084
  }
3061
- function markerInset(ref, strokeWidth) {
3085
+ function markerInset(ref, strokeWidth, scale = 1) {
3062
3086
  if (ref === void 0) return 0;
3063
3087
  const entry = getMarker(markerKeyOf(ref));
3064
3088
  if (entry === void 0) return 0;
3065
- return (entry.inset ?? 0) * resolveMarkerSize(ref, strokeWidth);
3089
+ return (entry.inset ?? 0) * resolveMarkerSize(ref, strokeWidth, scale);
3066
3090
  }
3067
3091
  function strokeInsets(stroke, strokeWidth) {
3068
3092
  return {
@@ -3119,6 +3143,220 @@ function strokeMesh(path, stroke, flattenTolerance) {
3119
3143
  return mesh;
3120
3144
  }
3121
3145
 
3146
+ // src/features/paths/markerSites.ts
3147
+ var EPS2 = 1e-9;
3148
+ function dir(pl, i, j) {
3149
+ const dx = pl.points[j * 2] - pl.points[i * 2];
3150
+ const dy = pl.points[j * 2 + 1] - pl.points[i * 2 + 1];
3151
+ const len = Math.hypot(dx, dy);
3152
+ if (len < EPS2) return null;
3153
+ return { x: dx / len, y: dy / len };
3154
+ }
3155
+ function nextDistinct(pl, i, n) {
3156
+ for (let k = i + 1; k < n; k++) if (dir(pl, i, k)) return k;
3157
+ return -1;
3158
+ }
3159
+ function prevDistinct(pl, i) {
3160
+ for (let k = i - 1; k >= 0; k--) if (dir(pl, k, i)) return k;
3161
+ return -1;
3162
+ }
3163
+ function markerSites(pl, want) {
3164
+ const out = [];
3165
+ const n = pl.points.length / 2;
3166
+ if (n < 2) return out;
3167
+ if (want.start && !pl.closed) {
3168
+ const j = nextDistinct(pl, 0, n);
3169
+ const d = j >= 0 ? dir(pl, 0, j) : null;
3170
+ if (d) {
3171
+ out.push({ x: pl.points[0], y: pl.points[1], angle: Math.atan2(-d.y, -d.x), role: "start" });
3172
+ }
3173
+ }
3174
+ if (want.mid) {
3175
+ const A = pl.anchorA, B = pl.anchorB, T = pl.anchorT;
3176
+ if (A && B && T) {
3177
+ for (let i = 1; i < n - 1; i++) {
3178
+ if (A[i] !== B[i] || T[i] !== 0) continue;
3179
+ const p = prevDistinct(pl, i);
3180
+ const q = nextDistinct(pl, i, n);
3181
+ if (p < 0 || q < 0) continue;
3182
+ const inDir = dir(pl, p, i);
3183
+ const outDir = dir(pl, i, q);
3184
+ let bx = inDir.x + outDir.x;
3185
+ let by = inDir.y + outDir.y;
3186
+ if (Math.hypot(bx, by) < EPS2) {
3187
+ bx = inDir.x;
3188
+ by = inDir.y;
3189
+ }
3190
+ out.push({ x: pl.points[i * 2], y: pl.points[i * 2 + 1], angle: Math.atan2(by, bx), role: "mid" });
3191
+ }
3192
+ }
3193
+ }
3194
+ if (want.end && !pl.closed) {
3195
+ const last = n - 1;
3196
+ const p = prevDistinct(pl, last);
3197
+ const d = p >= 0 ? dir(pl, p, last) : null;
3198
+ if (d) {
3199
+ out.push({ x: pl.points[last * 2], y: pl.points[last * 2 + 1], angle: Math.atan2(d.y, d.x), role: "end" });
3200
+ }
3201
+ }
3202
+ return out;
3203
+ }
3204
+
3205
+ // src/features/paths/markerCommands.ts
3206
+ function placed(path, site) {
3207
+ const src = path;
3208
+ const cos = Math.cos(site.angle);
3209
+ const sin = Math.sin(site.angle);
3210
+ const coords = new Float32Array(src.coords.length);
3211
+ for (let i = 0; i < src.coords.length; i += 2) {
3212
+ const x = src.coords[i], y = src.coords[i + 1];
3213
+ coords[i] = site.x + x * cos - y * sin;
3214
+ coords[i + 1] = site.y + x * sin + y * cos;
3215
+ }
3216
+ return { kind: "polygon", commands: src.commands, coords, fillRule: src.fillRule };
3217
+ }
3218
+ function resolvePaint(p, stroke, fallback) {
3219
+ const v = p ?? fallback;
3220
+ if (v === "none") return void 0;
3221
+ return v === "line" ? stroke.paint : v;
3222
+ }
3223
+ function markerHeads(path, stroke, strokeWidth, flattenTolerance) {
3224
+ const want = {
3225
+ start: stroke.markerStart !== void 0,
3226
+ mid: stroke.markerMid !== void 0,
3227
+ end: stroke.markerEnd !== void 0
3228
+ };
3229
+ if (!want.start && !want.mid && !want.end) return [];
3230
+ const refFor = (role) => role === "start" ? stroke.markerStart : role === "mid" ? stroke.markerMid : stroke.markerEnd;
3231
+ const out = [];
3232
+ for (const pl of extractPolylines(path, { flattenTolerance })) {
3233
+ for (const site of markerSites(pl, want)) {
3234
+ const ref = refFor(site.role);
3235
+ if (ref === void 0) continue;
3236
+ const entry = getMarker(markerKeyOf(ref));
3237
+ if (entry === void 0) continue;
3238
+ const size = resolveMarkerSize(ref, strokeWidth);
3239
+ const angle = entry.orient === void 0 || entry.orient === "auto" ? site.angle : entry.orient;
3240
+ out.push({ entry, size, path: placed(entry.path({ size, stroke }), { ...site, angle }) });
3241
+ }
3242
+ }
3243
+ return out;
3244
+ }
3245
+ function headCommands(heads, stroke) {
3246
+ const out = [];
3247
+ for (const { entry, path, size } of heads) {
3248
+ const fill = resolvePaint(entry.fill, stroke, "line");
3249
+ const outline = entry.outline ? { paint: resolvePaint(entry.outline.paint, stroke, "line"), width: entry.outline.width * size } : null;
3250
+ if (fill === void 0 && (outline === null || outline.paint === void 0)) continue;
3251
+ out.push({
3252
+ kind: "path",
3253
+ path,
3254
+ ...fill ? { fill } : {},
3255
+ ...outline && outline.paint ? { stroke: { paint: outline.paint, width: outline.width, cap: "round", join: "round" } } : {}
3256
+ });
3257
+ }
3258
+ return out;
3259
+ }
3260
+ function markerReadsKey(entry, stroke) {
3261
+ if (entry.reads === void 0) return null;
3262
+ let key = "";
3263
+ for (const field of entry.reads) key += `${JSON.stringify(stroke[field]) ?? ""}|`;
3264
+ return key;
3265
+ }
3266
+ var READINGS_PER_ENTRY = 64;
3267
+ var REACH_BY_READING = /* @__PURE__ */ new WeakMap();
3268
+ var REACH_BY_STROKE = /* @__PURE__ */ new WeakMap();
3269
+ function measureUnitReach(entry, stroke) {
3270
+ const b = boundsOfPath(entry.path({ size: 1, stroke }));
3271
+ return Math.max(Math.abs(b.x), Math.abs(b.x + b.width), Math.abs(b.y), Math.abs(b.y + b.height)) + (entry.outline ? entry.outline.width / 2 : 0);
3272
+ }
3273
+ function unitReach(entry, stroke) {
3274
+ const key = markerReadsKey(entry, stroke);
3275
+ if (key === null) {
3276
+ let byStroke2 = REACH_BY_STROKE.get(entry);
3277
+ if (byStroke2 === void 0) REACH_BY_STROKE.set(entry, byStroke2 = /* @__PURE__ */ new WeakMap());
3278
+ let reach2 = byStroke2.get(stroke);
3279
+ if (reach2 === void 0) byStroke2.set(stroke, reach2 = measureUnitReach(entry, stroke));
3280
+ return reach2;
3281
+ }
3282
+ let byReading = REACH_BY_READING.get(entry);
3283
+ if (byReading === void 0) REACH_BY_READING.set(entry, byReading = /* @__PURE__ */ new Map());
3284
+ let reach = byReading.get(key);
3285
+ if (reach === void 0) {
3286
+ if (byReading.size >= READINGS_PER_ENTRY) byReading.clear();
3287
+ byReading.set(key, reach = measureUnitReach(entry, stroke));
3288
+ }
3289
+ return reach;
3290
+ }
3291
+ function markerReach(stroke, strokeWidth, scale = 1) {
3292
+ let reach = 0;
3293
+ for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {
3294
+ if (ref === void 0) continue;
3295
+ const entry = getMarker(markerKeyOf(ref));
3296
+ if (entry === void 0) continue;
3297
+ reach = Math.max(reach, unitReach(entry, stroke) * resolveMarkerSize(ref, strokeWidth, scale));
3298
+ }
3299
+ return reach;
3300
+ }
3301
+
3302
+ // src/renderer/cache/markerCommandCache.ts
3303
+ var byValue = /* @__PURE__ */ new WeakMap();
3304
+ var byStroke = /* @__PURE__ */ new WeakMap();
3305
+ var refKey = (ref) => ref === void 0 ? "" : typeof ref === "string" ? ref : `${ref.key}@${String(ref.size ?? "")}`;
3306
+ function readsKey(stroke) {
3307
+ let key = "";
3308
+ for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {
3309
+ const entry = ref === void 0 ? void 0 : getMarker(markerKeyOf(ref));
3310
+ if (entry === void 0) {
3311
+ key += "/";
3312
+ continue;
3313
+ }
3314
+ const reads = markerReadsKey(entry, stroke);
3315
+ if (reads === null) return null;
3316
+ key += `${reads}/`;
3317
+ }
3318
+ return key;
3319
+ }
3320
+ function mapFor(path, source, reads) {
3321
+ if (reads !== null) {
3322
+ let map2 = byValue.get(path);
3323
+ if (map2 === void 0) byValue.set(path, map2 = /* @__PURE__ */ new Map());
3324
+ return map2;
3325
+ }
3326
+ let strokes = byStroke.get(path);
3327
+ if (strokes === void 0) byStroke.set(path, strokes = /* @__PURE__ */ new WeakMap());
3328
+ let map = strokes.get(source);
3329
+ if (map === void 0) strokes.set(source, map = /* @__PURE__ */ new Map());
3330
+ return map;
3331
+ }
3332
+ function cachedMarkerCommands(path, source, stroke, strokeWidth, flattenTolerance) {
3333
+ if (stroke.markerStart === void 0 && stroke.markerMid === void 0 && stroke.markerEnd === void 0) {
3334
+ return [];
3335
+ }
3336
+ const reads = readsKey(stroke);
3337
+ const map = mapFor(path, source, reads);
3338
+ const key = [
3339
+ strokeWidth,
3340
+ flattenTolerance ?? "",
3341
+ markerGeneration(),
3342
+ refKey(stroke.markerStart),
3343
+ refKey(stroke.markerMid),
3344
+ refKey(stroke.markerEnd),
3345
+ reads ?? ""
3346
+ ].join("|");
3347
+ let entry = map.get(key);
3348
+ if (entry === void 0) {
3349
+ const heads = markerHeads(path, stroke, strokeWidth, flattenTolerance);
3350
+ entry = { heads, paint: stroke.paint, cmds: headCommands(heads, stroke) };
3351
+ if (map.size >= STROKE_CONFIGS_PER_PATH) map.clear();
3352
+ map.set(key, entry);
3353
+ } else if (entry.paint !== stroke.paint) {
3354
+ entry.paint = stroke.paint;
3355
+ entry.cmds = headCommands(entry.heads, stroke);
3356
+ }
3357
+ return entry.cmds;
3358
+ }
3359
+
3122
3360
  // src/renderer/drawBatch.ts
3123
3361
  var MAX_VERTICES_PER_BATCH = 32768;
3124
3362
  var FLOATS_PER_VERTEX = 10;
@@ -3550,9 +3788,8 @@ var DrawBatch = class {
3550
3788
  gl.deleteVertexArray(set.vao);
3551
3789
  }
3552
3790
  };
3553
-
3554
- // src/renderer/shaders/registerProgram.ts
3555
- var registry = /* @__PURE__ */ new Map();
3791
+ var registry = createReflectable();
3792
+ var programSourceRegistry = registry.reflection;
3556
3793
  function getProgramSource(id) {
3557
3794
  return registry.get(id) ?? null;
3558
3795
  }
@@ -4532,23 +4769,42 @@ function drawPathFillStencil(ctx, fill, handle) {
4532
4769
  gl.disable(gl.STENCIL_TEST);
4533
4770
  gl.bindVertexArray(null);
4534
4771
  }
4535
- function withResolvedStrokeWidth(ctx, cmd) {
4772
+ function withResolvedStrokeLengths(ctx, cmd) {
4536
4773
  const stroke = cmd.stroke;
4537
- if (typeof stroke.width !== "object") return cmd;
4538
- const scale = quantizeStrokeScale(stroke.width.px, mat3.meanScaleOf(ctx.state.transform));
4539
- const width = resolveStrokeWidth(stroke.width, scale);
4540
- return { ...cmd, stroke: { ...stroke, width } };
4774
+ const { markerStart, markerMid, markerEnd } = stroke;
4775
+ if (typeof stroke.width !== "object" && !isPxMarker(markerStart) && !isPxMarker(markerMid) && !isPxMarker(markerEnd)) {
4776
+ return cmd;
4777
+ }
4778
+ const scale = mat3.meanScaleOf(ctx.state.transform);
4779
+ const resolved = { ...stroke };
4780
+ if (typeof stroke.width === "object") {
4781
+ resolved.width = resolveStrokeWidth(stroke.width, quantizeStrokeScale(stroke.width.px, scale));
4782
+ }
4783
+ if (isPxMarker(markerStart)) resolved.markerStart = resolvedMarker(markerStart, scale);
4784
+ if (isPxMarker(markerMid)) resolved.markerMid = resolvedMarker(markerMid, scale);
4785
+ if (isPxMarker(markerEnd)) resolved.markerEnd = resolvedMarker(markerEnd, scale);
4786
+ return { ...cmd, stroke: resolved };
4787
+ }
4788
+ function isPxMarker(ref) {
4789
+ return typeof ref === "object" && typeof ref.size === "object";
4790
+ }
4791
+ function resolvedMarker(ref, scale) {
4792
+ return { ...ref, size: resolveMarkerSize(ref, 0, quantizeStrokeScale(ref.size.px, scale)) };
4541
4793
  }
4542
4794
  function drawPathStroke(ctx, rawCmd) {
4543
- const cmd = withResolvedStrokeWidth(ctx, rawCmd);
4795
+ const cmd = withResolvedStrokeLengths(ctx, rawCmd);
4544
4796
  const stroke = cmd.stroke;
4545
4797
  const align = stroke.align ?? "center";
4546
4798
  if (cmd.path.kind === "polygon" && align !== "center") {
4547
4799
  flushBatch(ctx);
4548
4800
  drawPathStrokeStenciled(ctx, cmd, align);
4549
- return;
4801
+ } else {
4802
+ drawPathStrokeUnclipped(ctx, cmd);
4803
+ }
4804
+ const width = resolveStrokeWidth(stroke.width ?? 1, 1);
4805
+ for (const marker of cachedMarkerCommands(cmd.path, rawCmd.stroke, stroke, width, ctx.flattenTolerance)) {
4806
+ drawPath(ctx, marker);
4550
4807
  }
4551
- drawPathStrokeUnclipped(ctx, cmd);
4552
4808
  }
4553
4809
  function drawPathStrokeUnclipped(ctx, cmd) {
4554
4810
  const stroke = cmd.stroke;
@@ -5810,13 +6066,14 @@ function pathMisses(path, stroke, m, box) {
5810
6066
  return quadMisses(minX - reach, minY - reach, maxX + reach, maxY + reach, m, box);
5811
6067
  }
5812
6068
  function strokeReach(stroke, m) {
5813
- const w = stroke.width ?? 1;
5814
- let width = typeof w === "number" ? w : w.px / (mat3.meanScaleOf(m) || 1);
6069
+ const scale = mat3.meanScaleOf(m);
6070
+ const base = resolveStrokeWidth(stroke.width ?? 1, scale);
6071
+ let width = base;
5815
6072
  if (stroke.vertexWidths) {
5816
6073
  for (const v of stroke.vertexWidths) if (v > width) width = v;
5817
6074
  }
5818
6075
  const spike = Math.max(stroke.miterLimit ?? DEFAULT_MITER_LIMIT2, Math.SQRT2);
5819
- return Math.abs(width) * spike;
6076
+ return Math.max(Math.abs(width) * spike, markerReach(stroke, base, scale));
5820
6077
  }
5821
6078
  function spritesMiss(sprites, m, box) {
5822
6079
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
@@ -6027,6 +6284,15 @@ function reportLayerFailure({ layerId, error }) {
6027
6284
  error
6028
6285
  );
6029
6286
  }
6287
+ function subscribeToSources(sources) {
6288
+ return (listener) => {
6289
+ const list2 = typeof sources === "function" ? sources() : sources;
6290
+ const offs = list2.flatMap((l) => l.subscribe ? [l.subscribe(listener)] : []);
6291
+ return () => {
6292
+ for (const off of offs) off();
6293
+ };
6294
+ };
6295
+ }
6030
6296
  function drawLayers(layers, data, visibility, order, view, dims, cache7, onLayerError = reportLayerFailure, groups) {
6031
6297
  const layerById = new Map(layers.map((l) => [l.id, l]));
6032
6298
  const sequence = order ? order.map((id) => layerById.get(id)).filter((l) => l !== void 0) : layers;
@@ -6216,6 +6482,7 @@ function createViewportLayer(opts) {
6216
6482
  id,
6217
6483
  label,
6218
6484
  space: "screen",
6485
+ subscribe: subscribeToSources(sourceAt),
6219
6486
  resolvable(outer, dims) {
6220
6487
  return { id, view: viewAt(outer, dims), rect: bounds(outer, dims) };
6221
6488
  },
@@ -6346,6 +6613,7 @@ function createParallaxLayer(opts) {
6346
6613
  id,
6347
6614
  label,
6348
6615
  space: "screen",
6616
+ subscribe: subscribeToSources(source),
6349
6617
  draw: (data, outer, dims) => {
6350
6618
  const inner = deriveParallaxView(getOuterView?.() ?? outer, { pan, zoom, anchor });
6351
6619
  return source.flatMap((layer) => drawOneLayer(layer, data, inner, dims));
@@ -6377,6 +6645,7 @@ function createTiledLayer(opts) {
6377
6645
  id,
6378
6646
  label,
6379
6647
  space: "world",
6648
+ subscribe: subscribeToSources(source),
6380
6649
  draw: (data, view, dims) => {
6381
6650
  const p = axes(typeof period === "function" ? period(view, dims) : period, Infinity);
6382
6651
  const b = axes(bleed, 0);
@@ -6958,13 +7227,16 @@ function worldPoseLookup(adapter, compose2) {
6958
7227
  };
6959
7228
  }
6960
7229
 
7230
+ // src/core/isPlainObject.ts
7231
+ function isPlainPrototype(proto) {
7232
+ return proto === Object.prototype || proto === null || Object.getPrototypeOf(proto) === null;
7233
+ }
7234
+ function isPlainObject(value) {
7235
+ return typeof value === "object" && value !== null && isPlainPrototype(Object.getPrototypeOf(value));
7236
+ }
7237
+
6961
7238
  // src/core/scene/poseSnapshot.ts
6962
7239
  var DEPTH = 4;
6963
- function isPlainObject(v) {
6964
- if (typeof v !== "object" || v === null) return false;
6965
- const proto = Object.getPrototypeOf(v);
6966
- return proto === Object.prototype || proto === null;
6967
- }
6968
7240
  function isTypedArray(v) {
6969
7241
  return ArrayBuffer.isView(v) && !(v instanceof DataView);
6970
7242
  }
@@ -6989,9 +7261,8 @@ function samePoseValue(snapshot, value, depth = DEPTH) {
6989
7261
  if (typeof snapshot !== "object" || snapshot === null || typeof value !== "object" || value === null) return Object.is(snapshot, value);
6990
7262
  if (depth <= 0) return false;
6991
7263
  const proto = Object.getPrototypeOf(snapshot);
6992
- if (proto === Object.prototype || proto === null) {
6993
- const other = Object.getPrototypeOf(value);
6994
- if (other !== Object.prototype && other !== null) return false;
7264
+ if (isPlainPrototype(proto)) {
7265
+ if (!isPlainPrototype(Object.getPrototypeOf(value))) return false;
6995
7266
  const a = snapshot;
6996
7267
  const b = value;
6997
7268
  let count = 0;
@@ -7580,7 +7851,6 @@ function beginPointerDrag(payload, source, startX, startY, ghostFn) {
7580
7851
  }
7581
7852
  };
7582
7853
  }
7583
- var DRAG_THRESHOLD_PX_SQ = 25;
7584
7854
  function useDragHandle(getPayload, options) {
7585
7855
  const optsRef = useRef(options);
7586
7856
  optsRef.current = options;
@@ -7592,8 +7862,7 @@ function useDragHandle(getPayload, options) {
7592
7862
  if (e.pointerType === "mouse" && e.button !== 0) return;
7593
7863
  const target = e.currentTarget;
7594
7864
  if (e.target instanceof HTMLElement && e.target.closest("input, button, select, textarea")) return;
7595
- const startX = e.clientX;
7596
- const startY = e.clientY;
7865
+ const start = { clientX: e.clientX, clientY: e.clientY };
7597
7866
  let ghost = null;
7598
7867
  let past = false;
7599
7868
  live2.current = openPointerSession(target, e, {
@@ -7603,9 +7872,7 @@ function useDragHandle(getPayload, options) {
7603
7872
  return;
7604
7873
  }
7605
7874
  if (past) return;
7606
- const dx = ev.clientX - startX;
7607
- const dy = ev.clientY - startY;
7608
- if (dx * dx + dy * dy <= DRAG_THRESHOLD_PX_SQ) return;
7875
+ if (!pastDragThreshold(start, ev)) return;
7609
7876
  past = true;
7610
7877
  const payload = getPayload();
7611
7878
  if (!payload) return;
@@ -7655,15 +7922,11 @@ function useDropZone(opts) {
7655
7922
  }, []);
7656
7923
  }
7657
7924
  function startThresholdDrag(e, opts) {
7658
- const startX = e.clientX;
7659
- const startY = e.clientY;
7660
- const threshold = opts.threshold ?? 4;
7925
+ const start = { clientX: e.clientX, clientY: e.clientY };
7661
7926
  let activated = false;
7662
7927
  const maybeActivate = (ev) => {
7663
7928
  if (activated) return;
7664
- const dx = ev.clientX - startX;
7665
- const dy = ev.clientY - startY;
7666
- if (dx * dx + dy * dy < threshold * threshold) return;
7929
+ if (!pastDragThreshold(start, ev, opts.threshold)) return;
7667
7930
  activated = true;
7668
7931
  opts.onActivate?.(ev);
7669
7932
  };
@@ -8012,6 +8275,47 @@ function paramsMatch(bound, wanted) {
8012
8275
  return Object.entries(wanted).every(([k, v]) => bound[k] === v);
8013
8276
  }
8014
8277
 
8278
+ // src/interactions/actions/gestureLifecycle.ts
8279
+ function gestureLifecycleParams(o) {
8280
+ const out = {};
8281
+ if (o.label !== void 0) out.label = o.label;
8282
+ if (o.transient !== void 0) out.transient = o.transient;
8283
+ if (o.onGestureStart) out.onGestureStart = o.onGestureStart;
8284
+ if (o.onGestureEnd) out.onGestureEnd = o.onGestureEnd;
8285
+ return out;
8286
+ }
8287
+ function readGestureLifecycle(source, defaultLabel, behaviors = []) {
8288
+ const o = source ?? {};
8289
+ let started = false;
8290
+ let ended = false;
8291
+ return {
8292
+ label: o.label ?? defaultLabel,
8293
+ labelFor: (ops) => o.label ?? ops[0]?.label ?? defaultLabel,
8294
+ transient: o.transient ?? behaviors.some((b) => b.defaultTransient === true),
8295
+ start(ids) {
8296
+ if (started) return;
8297
+ started = true;
8298
+ o.onGestureStart?.([...ids]);
8299
+ },
8300
+ end(committed) {
8301
+ if (!started || ended) return;
8302
+ ended = true;
8303
+ o.onGestureEnd?.(committed);
8304
+ }
8305
+ };
8306
+ }
8307
+ function commitGestureOps(target, lifecycle, ops) {
8308
+ if (lifecycle.transient) {
8309
+ target.scene.untracked(() => {
8310
+ for (const op of ops) op.apply(target.adapter);
8311
+ });
8312
+ return;
8313
+ }
8314
+ const label = lifecycle.labelFor(ops);
8315
+ if (target.applyOps) target.applyOps(ops, label);
8316
+ else target.scene.applyBatch(ops, label, target.adapter);
8317
+ }
8318
+
8015
8319
  // src/core/scene/types.ts
8016
8320
  var asNodeId = (s) => s;
8017
8321
 
@@ -8373,14 +8677,14 @@ function translateCommitOps(scratch, ids, dx, dy) {
8373
8677
  id,
8374
8678
  from: authored,
8375
8679
  to: translatePoseViaDescriptor(authored, l.dx, l.dy, scratch.projection),
8376
- label: "Move"
8680
+ label: scratch.lifecycle.label
8377
8681
  }));
8378
8682
  if (scratch.geometryProjection) {
8379
8683
  const dataOp = geometryDataOp(
8380
8684
  scratch.geometryProjection,
8381
8685
  { id, data: node.data, pose: authored },
8382
8686
  m,
8383
- "Move"
8687
+ scratch.lifecycle.label
8384
8688
  );
8385
8689
  if (dataOp) ops.push(dataOp);
8386
8690
  }
@@ -8502,7 +8806,10 @@ var moveAction = {
8502
8806
  const pc = ctx.deps.poseComposition ?? IDENTITY_POSE_COMPOSITION;
8503
8807
  if (!selection || !scene) return {};
8504
8808
  if (isAnchorOrControl(ctx.drag?.affordance)) return {};
8505
- const ids = selection.get();
8809
+ const params = resolveParams(opts?.params);
8810
+ const expandIds = params?.["expandIds"];
8811
+ const selected = [...selection.get()];
8812
+ const ids = expandIds ? expandIds(selected) : selected;
8506
8813
  if (ids.length === 0) return {};
8507
8814
  const startPoses = /* @__PURE__ */ new Map();
8508
8815
  const cascadeIds = [];
@@ -8545,6 +8852,9 @@ var moveAction = {
8545
8852
  }
8546
8853
  }
8547
8854
  const behaviors = opts?.behaviors ?? [];
8855
+ const dragThresholdPx = params?.["dragThresholdPx"] ?? DRAG_THRESHOLD_PX;
8856
+ const lifecycle = readGestureLifecycle(params, "Move", behaviors);
8857
+ let engaged = false;
8548
8858
  const adapter = moveGestureAdapter(scene);
8549
8859
  const origin = /* @__PURE__ */ new Map();
8550
8860
  for (const [id, pose] of startPoses) origin.set(id, pose);
@@ -8577,12 +8887,170 @@ var moveAction = {
8577
8887
  layoutPass: null,
8578
8888
  pc,
8579
8889
  applyOps,
8580
- geometryProjection
8890
+ geometryProjection,
8891
+ lifecycle
8892
+ };
8893
+ const commitMove = (endCtx) => {
8894
+ const { dx, dy } = scratch.currentDelta;
8895
+ let committed = false;
8896
+ const commitOps = (ops) => {
8897
+ commitGestureOps(scratch, lifecycle, ops);
8898
+ committed = true;
8899
+ };
8900
+ if (scratch.behaviors.length > 0) {
8901
+ const gctx = scratch.gestureCtx;
8902
+ for (const [id, ori] of scratch.startPoses) {
8903
+ gctx.current.set(id, translatePoseViaDescriptor(ori, dx, dy, scratch.projection));
8904
+ }
8905
+ for (const b of scratch.behaviors) {
8906
+ const r = b.onEnd?.(gctx);
8907
+ if (r === void 0) continue;
8908
+ if (r === null) return false;
8909
+ commitOps(r);
8910
+ return true;
8911
+ }
8912
+ }
8913
+ if (dx === 0 && dy === 0) return false;
8914
+ if (scratch.layout && scratch.layoutPass) {
8915
+ const lp = scratch.layoutPass;
8916
+ const pc2 = scratch.pc;
8917
+ const commitAdapter = scenePoseAdapter(scratch.scene);
8918
+ const destId = lp.container.id;
8919
+ const draggedIds = new Set(scratch.ids.map((id) => id));
8920
+ const rebaseOpToLocal = (op) => {
8921
+ if (op.name !== "transform") return op;
8922
+ const a = op.args;
8923
+ const curParent = scratch.scene.get(asNodeId(a.id))?.parent ?? null;
8924
+ const toParent = draggedIds.has(a.id) ? destId : curParent;
8925
+ return createTransformOp({
8926
+ id: a.id,
8927
+ from: rebaseLocalPose(commitAdapter, a.from, curParent, pc2.compose, pc2.decompose),
8928
+ to: rebaseLocalPose(commitAdapter, a.to, toParent, pc2.compose, pc2.decompose),
8929
+ label: a.label,
8930
+ coalesceKey: a.coalesceKey
8931
+ });
8932
+ };
8933
+ const reparentOps = [];
8934
+ const dropOps = [];
8935
+ for (const placement of lp.placements) {
8936
+ if (placement.dragged.sourceContainerId !== destId) {
8937
+ reparentOps.push(createReparentOp({
8938
+ id: placement.dragged.id,
8939
+ fromParentId: placement.dragged.sourceContainerId,
8940
+ toParentId: destId,
8941
+ label: "Reparent"
8942
+ }));
8943
+ }
8944
+ const ops2 = lp.layout.commitDrop(
8945
+ lp.container,
8946
+ placement.children,
8947
+ placement.dragged,
8948
+ placement.target
8949
+ );
8950
+ for (const op of ops2) dropOps.push(rebaseOpToLocal(op));
8951
+ }
8952
+ const reflowOps = [];
8953
+ for (const [cid, worldPose] of lp.sourceReflow) {
8954
+ const parent = scratch.scene.get(asNodeId(cid))?.parent ?? null;
8955
+ reflowOps.push(createTransformOp({
8956
+ id: cid,
8957
+ from: scratch.scene.get(asNodeId(cid)).pose,
8958
+ to: rebaseLocalPose(commitAdapter, worldPose, parent, pc2.compose, pc2.decompose),
8959
+ label: "Source reflow"
8960
+ }));
8961
+ }
8962
+ const ops = [...reparentOps, ...dropOps, ...reflowOps];
8963
+ if (ops.length > 0) commitOps(ops);
8964
+ return committed;
8965
+ }
8966
+ const released = /* @__PURE__ */ new Set();
8967
+ const releasedOps = [];
8968
+ if (scratch.layout && !scratch.layoutPass) {
8969
+ const pc2 = scratch.pc;
8970
+ const commitAdapter = scenePoseAdapter(scratch.scene);
8971
+ const rebaseReleased = (op) => {
8972
+ if (op.name !== "transform") return op;
8973
+ const a = op.args;
8974
+ const parent = scratch.scene.get(asNodeId(a.id))?.parent ?? null;
8975
+ return createTransformOp({
8976
+ id: a.id,
8977
+ from: rebaseLocalPose(commitAdapter, a.from, parent, pc2.compose, pc2.decompose),
8978
+ to: rebaseLocalPose(commitAdapter, a.to, parent, pc2.compose, pc2.decompose),
8979
+ label: a.label,
8980
+ coalesceKey: a.coalesceKey
8981
+ });
8982
+ };
8983
+ const sources = groupBySourceContainer(
8984
+ scratch.ids.map((id) => ({
8985
+ id,
8986
+ sourceContainerId: scratch.scene.get(id)?.parent ?? null
8987
+ })),
8988
+ null
8989
+ );
8990
+ for (const [srcId, leaving] of sources) {
8991
+ const srcLayout = scratch.layout.getLayout(srcId);
8992
+ if (!srcLayout?.releaseDrop) continue;
8993
+ const srcContainer = {
8994
+ id: srcId,
8995
+ bounds: scratch.projection.getBounds(
8996
+ composeWorldPose(commitAdapter, srcId, pc2.compose)
8997
+ )
8998
+ };
8999
+ let srcChildren = scratch.scene.childrenOf(asNodeId(srcId)).filter((cid) => !leaving.has(cid)).map((cid) => ({
9000
+ id: cid,
9001
+ pose: composeWorldPose(commitAdapter, cid, pc2.compose)
9002
+ }));
9003
+ for (const id of leaving) {
9004
+ const startWorld = composeWorldPose(commitAdapter, id, pc2.compose);
9005
+ const ops = srcLayout.releaseDrop(srcContainer, srcChildren, {
9006
+ id,
9007
+ originPose: startWorld,
9008
+ pose: translatePoseViaDescriptor(startWorld, dx, dy, scratch.projection),
9009
+ sourceContainerId: srcId
9010
+ });
9011
+ if (ops === null) continue;
9012
+ released.add(id);
9013
+ let landed = startWorld;
9014
+ for (const op of ops) {
9015
+ const a = op.args;
9016
+ if (op.name === "transform" && a.id === id) landed = a.to;
9017
+ releasedOps.push(rebaseReleased(op));
9018
+ }
9019
+ srcChildren = [...srcChildren, { id, pose: landed }];
9020
+ }
9021
+ }
9022
+ }
9023
+ if (released.size > 0) {
9024
+ const ops = [
9025
+ ...releasedOps,
9026
+ ...translateCommitOps(scratch, unreleasedIds(scratch, released), dx, dy)
9027
+ ];
9028
+ if (ops.length > 0) commitOps(ops);
9029
+ return committed;
9030
+ }
9031
+ const reparentMode = resolveParams(opts?.params)?.["reparentOnDrop"] ?? "off";
9032
+ const dropTarget = reparentMode !== "off" ? resolveDropTarget(endCtx, scratch) : null;
9033
+ if (dropTarget) {
9034
+ const reparent = () => applyReparent(scratch, dropTarget, reparentMode, dx, dy, scratch.pc);
9035
+ if (lifecycle.transient) scratch.scene.untracked(reparent);
9036
+ else scratch.scene.batch(lifecycle.label, reparent);
9037
+ committed = true;
9038
+ } else {
9039
+ const ops = translateCommitOps(scratch, [...scratch.ids, ...scratch.cascadeIds], dx, dy);
9040
+ if (ops.length > 0) commitOps(ops);
9041
+ }
9042
+ return committed;
8581
9043
  };
8582
9044
  return {
8583
9045
  kind: "move",
8584
9046
  onMove(moveCtx) {
8585
9047
  if (!moveCtx.drag) return;
9048
+ if (!engaged) {
9049
+ const s = moveCtx.drag.screenDelta;
9050
+ if (s && !pastDragThreshold({ clientX: 0, clientY: 0 }, { clientX: s.x, clientY: s.y }, dragThresholdPx)) return;
9051
+ engaged = true;
9052
+ lifecycle.start(scratch.ids);
9053
+ }
8586
9054
  let dx = moveCtx.drag.delta.x;
8587
9055
  let dy = moveCtx.drag.delta.y;
8588
9056
  if (scratch.behaviors.length > 0) {
@@ -8629,171 +9097,14 @@ var moveAction = {
8629
9097
  syncPreviewOverrides(scratch);
8630
9098
  },
8631
9099
  onEnd(endCtx, reason) {
8632
- if (reason === "cancel") {
9100
+ let committed = false;
9101
+ try {
9102
+ if (reason === "commit") committed = commitMove(endCtx);
9103
+ } finally {
8633
9104
  dropPreviewOverrides(scratch);
8634
9105
  scratch.previews.clear();
8635
- return;
8636
- }
8637
- const { dx, dy } = scratch.currentDelta;
8638
- const commitOps = (ops, label) => {
8639
- if (scratch.applyOps) scratch.applyOps(ops, label);
8640
- else scratch.scene.applyBatch(ops, label, scratch.adapter);
8641
- };
8642
- if (scratch.behaviors.length > 0) {
8643
- const gctx = scratch.gestureCtx;
8644
- for (const [id, ori] of scratch.startPoses) {
8645
- gctx.current.set(id, translatePoseViaDescriptor(ori, dx, dy, scratch.projection));
8646
- }
8647
- for (const b of scratch.behaviors) {
8648
- const r = b.onEnd?.(gctx);
8649
- if (r === void 0) continue;
8650
- if (r === null) {
8651
- scratch.previews.clear();
8652
- return;
8653
- }
8654
- commitOps(r, "Move");
8655
- scratch.previews.clear();
8656
- return;
8657
- }
8658
- }
8659
- if (dx === 0 && dy === 0) {
8660
- scratch.previews.clear();
8661
- return;
8662
- }
8663
- if (scratch.layout && scratch.layoutPass) {
8664
- const lp = scratch.layoutPass;
8665
- const pc2 = scratch.pc;
8666
- const commitAdapter = scenePoseAdapter(scratch.scene);
8667
- const destId = lp.container.id;
8668
- const draggedIds = new Set(scratch.ids.map((id) => id));
8669
- const rebaseOpToLocal = (op) => {
8670
- if (op.name !== "transform") return op;
8671
- const a = op.args;
8672
- const curParent = scratch.scene.get(asNodeId(a.id))?.parent ?? null;
8673
- const toParent = draggedIds.has(a.id) ? destId : curParent;
8674
- return createTransformOp({
8675
- id: a.id,
8676
- from: rebaseLocalPose(commitAdapter, a.from, curParent, pc2.compose, pc2.decompose),
8677
- to: rebaseLocalPose(commitAdapter, a.to, toParent, pc2.compose, pc2.decompose),
8678
- label: a.label,
8679
- coalesceKey: a.coalesceKey
8680
- });
8681
- };
8682
- const reparentOps = [];
8683
- const dropOps = [];
8684
- for (const placement of lp.placements) {
8685
- if (placement.dragged.sourceContainerId !== destId) {
8686
- reparentOps.push(createReparentOp({
8687
- id: placement.dragged.id,
8688
- fromParentId: placement.dragged.sourceContainerId,
8689
- toParentId: destId,
8690
- label: "Reparent"
8691
- }));
8692
- }
8693
- const ops2 = lp.layout.commitDrop(
8694
- lp.container,
8695
- placement.children,
8696
- placement.dragged,
8697
- placement.target
8698
- );
8699
- for (const op of ops2) dropOps.push(rebaseOpToLocal(op));
8700
- }
8701
- const reflowOps = [];
8702
- for (const [cid, worldPose] of lp.sourceReflow) {
8703
- const parent = scratch.scene.get(asNodeId(cid))?.parent ?? null;
8704
- reflowOps.push(createTransformOp({
8705
- id: cid,
8706
- from: scratch.scene.get(asNodeId(cid)).pose,
8707
- to: rebaseLocalPose(commitAdapter, worldPose, parent, pc2.compose, pc2.decompose),
8708
- label: "Source reflow"
8709
- }));
8710
- }
8711
- const ops = [...reparentOps, ...dropOps, ...reflowOps];
8712
- if (ops.length > 0) {
8713
- commitOps(ops, ops[0].label ?? "Move");
8714
- }
8715
- scratch.previews.clear();
8716
- return;
8717
- }
8718
- const released = /* @__PURE__ */ new Set();
8719
- const releasedOps = [];
8720
- if (scratch.layout && !scratch.layoutPass) {
8721
- const pc2 = scratch.pc;
8722
- const commitAdapter = scenePoseAdapter(scratch.scene);
8723
- const rebaseReleased = (op) => {
8724
- if (op.name !== "transform") return op;
8725
- const a = op.args;
8726
- const parent = scratch.scene.get(asNodeId(a.id))?.parent ?? null;
8727
- return createTransformOp({
8728
- id: a.id,
8729
- from: rebaseLocalPose(commitAdapter, a.from, parent, pc2.compose, pc2.decompose),
8730
- to: rebaseLocalPose(commitAdapter, a.to, parent, pc2.compose, pc2.decompose),
8731
- label: a.label,
8732
- coalesceKey: a.coalesceKey
8733
- });
8734
- };
8735
- const sources = groupBySourceContainer(
8736
- scratch.ids.map((id) => ({
8737
- id,
8738
- sourceContainerId: scratch.scene.get(id)?.parent ?? null
8739
- })),
8740
- null
8741
- );
8742
- for (const [srcId, leaving] of sources) {
8743
- const srcLayout = scratch.layout.getLayout(srcId);
8744
- if (!srcLayout?.releaseDrop) continue;
8745
- const srcContainer = {
8746
- id: srcId,
8747
- bounds: scratch.projection.getBounds(
8748
- composeWorldPose(commitAdapter, srcId, pc2.compose)
8749
- )
8750
- };
8751
- let srcChildren = scratch.scene.childrenOf(asNodeId(srcId)).filter((cid) => !leaving.has(cid)).map((cid) => ({
8752
- id: cid,
8753
- pose: composeWorldPose(commitAdapter, cid, pc2.compose)
8754
- }));
8755
- for (const id of leaving) {
8756
- const startWorld = composeWorldPose(commitAdapter, id, pc2.compose);
8757
- const ops = srcLayout.releaseDrop(srcContainer, srcChildren, {
8758
- id,
8759
- originPose: startWorld,
8760
- pose: translatePoseViaDescriptor(startWorld, dx, dy, scratch.projection),
8761
- sourceContainerId: srcId
8762
- });
8763
- if (ops === null) continue;
8764
- released.add(id);
8765
- let landed = startWorld;
8766
- for (const op of ops) {
8767
- const a = op.args;
8768
- if (op.name === "transform" && a.id === id) landed = a.to;
8769
- releasedOps.push(rebaseReleased(op));
8770
- }
8771
- srcChildren = [...srcChildren, { id, pose: landed }];
8772
- }
8773
- }
8774
- }
8775
- if (released.size > 0) {
8776
- const ops = [
8777
- ...releasedOps,
8778
- ...translateCommitOps(scratch, unreleasedIds(scratch, released), dx, dy)
8779
- ];
8780
- if (ops.length > 0) commitOps(ops, ops[0].label ?? "Move");
8781
- scratch.previews.clear();
8782
- return;
9106
+ lifecycle.end(committed);
8783
9107
  }
8784
- const resolved = resolveParams(opts?.params);
8785
- const reparentMode = resolved?.["reparentOnDrop"] ?? "off";
8786
- const dropTarget = reparentMode !== "off" ? resolveDropTarget(endCtx, scratch) : null;
8787
- if (dropTarget) {
8788
- scratch.scene.batch("Move", () => {
8789
- applyReparent(scratch, dropTarget, reparentMode, dx, dy, scratch.pc);
8790
- });
8791
- } else {
8792
- const ops = translateCommitOps(scratch, [...scratch.ids, ...scratch.cascadeIds], dx, dy);
8793
- if (ops.length > 0) commitOps(ops, "Move");
8794
- }
8795
- dropPreviewOverrides(scratch);
8796
- scratch.previews.clear();
8797
9108
  },
8798
9109
  previewIds: () => scratch.previews.keys(),
8799
9110
  previewPose: (id) => scratch.previews.get(id) ?? null,
@@ -8913,14 +9224,16 @@ function resolveDeps(ctx) {
8913
9224
  behaviors: DEFAULT_RESIZE_BEHAVIORS,
8914
9225
  pointSnap: EMPTY_POINT_SNAP,
8915
9226
  expandIds: IDENTITY_EXPAND,
8916
- geometry
9227
+ geometry,
9228
+ policy: void 0
8917
9229
  };
8918
9230
  }
8919
9231
  return {
8920
9232
  behaviors: dep.constraints,
8921
9233
  pointSnap: dep.pointSnap,
8922
9234
  expandIds: dep.expandIds,
8923
- geometry
9235
+ geometry,
9236
+ policy: dep
8924
9237
  };
8925
9238
  }
8926
9239
  function buildPointSnapContext(pose, rotation, anchor, modifiers) {
@@ -9004,7 +9317,9 @@ function computeProposedBounds(ob, anchor, dx, dy, rotation) {
9004
9317
  var resizeAction = {
9005
9318
  id: "resize",
9006
9319
  label: "Resize",
9007
- defaultBinding: { kind: "drag" },
9320
+ // No default binding: a bare `{ kind: 'drag' }` claimed every drag at ambient
9321
+ // scope, tying areaSelect's. The resize handles bind it
9322
+ // (`selectionTransformBindings`, the `transform` preset).
9008
9323
  eligible: { capability: "transforms-selection" },
9009
9324
  requires: ["selection", "scene", "resizePolicy", "poseDescriptor", "applyOps", "geometryProjection", "poseComposition"],
9010
9325
  invoker: {
@@ -9020,7 +9335,7 @@ var resizeAction = {
9020
9335
  const affordance = ctx.drag?.affordance;
9021
9336
  const anchor = affordance?.anchor;
9022
9337
  if (!anchor) return {};
9023
- const { behaviors, pointSnap, expandIds, geometry } = resolveDeps(ctx);
9338
+ const { behaviors, pointSnap, expandIds, geometry, policy } = resolveDeps(ctx);
9024
9339
  const expanded = expandIds(ids);
9025
9340
  if (expanded.length === 0) return {};
9026
9341
  const isGroupPath = ids.length === 1 && (expanded.length !== 1 || expanded[0] !== ids[0]);
@@ -9081,7 +9396,78 @@ var resizeAction = {
9081
9396
  pointSnap,
9082
9397
  gestureCtx,
9083
9398
  applyOps,
9084
- geometryProjection
9399
+ geometryProjection,
9400
+ lifecycle: readGestureLifecycle(policy, "Resize", behaviors)
9401
+ };
9402
+ const { lifecycle } = scratch;
9403
+ lifecycle.start(writeIds);
9404
+ const commitResize = () => {
9405
+ const target = {
9406
+ scene,
9407
+ applyOps: scratch.applyOps,
9408
+ adapter: defaultCommitAdapter(scene, selection.adapterMethods)
9409
+ };
9410
+ for (const b of scratch.behaviors) {
9411
+ const r = b.onEnd?.(scratch.gestureCtx);
9412
+ if (r === void 0) continue;
9413
+ if (r === null) return false;
9414
+ commitGestureOps(target, lifecycle, r);
9415
+ return true;
9416
+ }
9417
+ if (scratch.previews.size === 0) return false;
9418
+ const ops = [];
9419
+ for (const id of scratch.writeIds) {
9420
+ const next = scratch.previews.get(id);
9421
+ if (next === void 0) continue;
9422
+ const from = scratch.startPoses.get(id);
9423
+ if (from === void 0) continue;
9424
+ let committed = next;
9425
+ if (scratch.geometryProjection) {
9426
+ const node = scratch.scene.get(id);
9427
+ if (node) {
9428
+ const sb = scratch.geometry.getBounds(from);
9429
+ const db = scratch.geometry.getBounds(next);
9430
+ const m = boxToBox(
9431
+ sb.x,
9432
+ sb.y,
9433
+ sb.width,
9434
+ sb.height,
9435
+ db.x,
9436
+ db.y,
9437
+ db.width,
9438
+ db.height
9439
+ );
9440
+ const dataOp = geometryDataOp(
9441
+ scratch.geometryProjection,
9442
+ { id, data: node.data, pose: from },
9443
+ m,
9444
+ lifecycle.label
9445
+ );
9446
+ if (dataOp) {
9447
+ ops.push(dataOp);
9448
+ if (db.width < 0 || db.height < 0) {
9449
+ const nb = {
9450
+ ...db,
9451
+ x: db.width < 0 ? db.x + db.width : db.x,
9452
+ y: db.height < 0 ? db.y + db.height : db.y,
9453
+ width: Math.abs(db.width),
9454
+ height: Math.abs(db.height)
9455
+ };
9456
+ committed = scratch.geometry.remapBounds(next, db, nb);
9457
+ }
9458
+ }
9459
+ }
9460
+ }
9461
+ ops.push(createTransformOp({
9462
+ id,
9463
+ from,
9464
+ to: committed,
9465
+ label: lifecycle.label
9466
+ }));
9467
+ }
9468
+ if (ops.length === 0) return false;
9469
+ commitGestureOps(target, lifecycle, ops);
9470
+ return true;
9085
9471
  };
9086
9472
  return {
9087
9473
  kind: "resize",
@@ -9183,79 +9569,14 @@ var resizeAction = {
9183
9569
  syncPreviewOverrides(scratch);
9184
9570
  },
9185
9571
  onEnd(_endCtx, reason) {
9186
- if (reason === "cancel") {
9572
+ let committed = false;
9573
+ try {
9574
+ if (reason === "commit") committed = commitResize();
9575
+ } finally {
9187
9576
  dropPreviewOverrides(scratch);
9188
9577
  scratch.previews.clear();
9189
- return;
9190
- }
9191
- for (const b of scratch.behaviors) {
9192
- const r = b.onEnd?.(scratch.gestureCtx);
9193
- if (r === null) {
9194
- dropPreviewOverrides(scratch);
9195
- scratch.previews.clear();
9196
- return;
9197
- }
9198
- }
9199
- if (scratch.previews.size === 0) {
9200
- dropPreviewOverrides(scratch);
9201
- return;
9202
- }
9203
- const ops = [];
9204
- for (const id of scratch.writeIds) {
9205
- const next = scratch.previews.get(id);
9206
- if (next === void 0) continue;
9207
- const from = scratch.startPoses.get(id);
9208
- if (from === void 0) continue;
9209
- let committed = next;
9210
- if (scratch.geometryProjection) {
9211
- const node = scratch.scene.get(id);
9212
- if (node) {
9213
- const sb = scratch.geometry.getBounds(from);
9214
- const db = scratch.geometry.getBounds(next);
9215
- const m = boxToBox(
9216
- sb.x,
9217
- sb.y,
9218
- sb.width,
9219
- sb.height,
9220
- db.x,
9221
- db.y,
9222
- db.width,
9223
- db.height
9224
- );
9225
- const dataOp = geometryDataOp(
9226
- scratch.geometryProjection,
9227
- { id, data: node.data, pose: from },
9228
- m,
9229
- "Resize"
9230
- );
9231
- if (dataOp) {
9232
- ops.push(dataOp);
9233
- if (db.width < 0 || db.height < 0) {
9234
- const nb = {
9235
- ...db,
9236
- x: db.width < 0 ? db.x + db.width : db.x,
9237
- y: db.height < 0 ? db.y + db.height : db.y,
9238
- width: Math.abs(db.width),
9239
- height: Math.abs(db.height)
9240
- };
9241
- committed = scratch.geometry.remapBounds(next, db, nb);
9242
- }
9243
- }
9244
- }
9245
- }
9246
- ops.push(createTransformOp({
9247
- id,
9248
- from,
9249
- to: committed,
9250
- label: "Resize"
9251
- }));
9252
- }
9253
- if (ops.length > 0) {
9254
- if (scratch.applyOps) scratch.applyOps(ops, "Resize");
9255
- else scratch.scene.applyBatch(ops, "Resize", defaultCommitAdapter(scratch.scene, selection.adapterMethods));
9578
+ lifecycle.end(committed);
9256
9579
  }
9257
- dropPreviewOverrides(scratch);
9258
- scratch.previews.clear();
9259
9580
  },
9260
9581
  previewIds: () => scratch.previews.keys(),
9261
9582
  previewPose: (id) => scratch.previews.get(id) ?? null
@@ -9276,8 +9597,6 @@ var resizeAction = {
9276
9597
  */
9277
9598
  enabled: () => true
9278
9599
  };
9279
-
9280
- // src/interactions/actions/defaults/rotate.ts
9281
9600
  function applyRotationDelta(d, pose, originRotation, delta3, originCenter, unionCenter, useUnionPivot) {
9282
9601
  const rotated = d.withRotation(pose, originRotation + delta3);
9283
9602
  if (!useUnionPivot) return rotated;
@@ -9292,15 +9611,19 @@ function applyRotationDelta(d, pose, originRotation, delta3, originCenter, union
9292
9611
  var rotateAction = {
9293
9612
  id: "rotate",
9294
9613
  label: "Rotate",
9295
- defaultBinding: { kind: "drag" },
9614
+ // No default binding. It used to be a bare `{ kind: 'drag' }`, which made
9615
+ // any drag no active tool claimed rotate a non-empty selection. The rotation
9616
+ // handle binds it (`selectionTransformBindings`, the `transform` preset).
9296
9617
  eligible: { capability: "transforms-selection" },
9297
9618
  requires: ["selection", "scene", "applyOps", "poseDescriptor", "poseComposition"],
9298
9619
  invoker: {
9299
9620
  timing: "ongoing",
9300
- start(ctx, _opts) {
9621
+ start(ctx, opts) {
9301
9622
  const selection = ctx.deps.selection;
9302
9623
  const scene = ctx.deps.scene;
9303
9624
  const applyOps = ctx.deps.applyOps;
9625
+ const params = resolveParams(opts?.params);
9626
+ const behaviors = opts?.behaviors ?? [];
9304
9627
  if (!selection || !scene) return {};
9305
9628
  const ids = selection.get();
9306
9629
  if (ids.length === 0) return {};
@@ -9343,35 +9666,109 @@ var rotateAction = {
9343
9666
  unionCenter,
9344
9667
  startPointerAngle,
9345
9668
  currentDelta: 0,
9346
- useUnionPivot: ids.length > 1,
9669
+ useUnionPivot: ids.length > 1 && params?.["pivot"] !== "each",
9347
9670
  previews: /* @__PURE__ */ new Map(),
9348
9671
  overrideEntries: /* @__PURE__ */ new Map(),
9349
- applyOps
9350
- };
9351
- const recomputePreviews = (delta3) => {
9352
- scratch.previews.clear();
9353
- if (delta3 === 0) {
9354
- syncPreviewOverrides(scratch);
9355
- return;
9672
+ applyOps,
9673
+ lifecycle: readGestureLifecycle(params, "Rotate", behaviors),
9674
+ behaviors,
9675
+ gestureCtx: {
9676
+ draggedIds: [...originPoses.keys()],
9677
+ origin: new Map([...originWorlds].map(([id, p]) => [id, p])),
9678
+ current: new Map([...originWorlds].map(([id, p]) => [id, p])),
9679
+ snap: null,
9680
+ modifiers: { ...ctx.modifiers },
9681
+ pointer: { worldX: ctx.world.x, worldY: ctx.world.y, clientX: 0, clientY: 0 },
9682
+ adapter: moveGestureAdapter(scene),
9683
+ scratch: {}
9356
9684
  }
9357
- for (const id of scratch.ids) {
9358
- const origin = scratch.originWorlds.get(id);
9359
- if (origin === void 0) continue;
9360
- const originRotation = scratch.originRotations.get(id) ?? 0;
9361
- const originCenter = scratch.originCenters.get(id) ?? { x: 0, y: 0 };
9362
- const turned = applyRotationDelta(
9685
+ };
9686
+ const { lifecycle } = scratch;
9687
+ const turnedWorlds = (delta3) => {
9688
+ const out = /* @__PURE__ */ new Map();
9689
+ for (const [id, origin] of scratch.originWorlds) {
9690
+ out.set(id, applyRotationDelta(
9363
9691
  scratch.descriptor,
9364
9692
  origin,
9365
- originRotation,
9693
+ scratch.originRotations.get(id) ?? 0,
9366
9694
  delta3,
9367
- originCenter,
9695
+ scratch.originCenters.get(id) ?? { x: 0, y: 0 },
9368
9696
  scratch.unionCenter,
9369
9697
  scratch.useUnionPivot
9370
- );
9371
- scratch.previews.set(id, scratch.frame.local(id, turned));
9698
+ ));
9699
+ }
9700
+ return out;
9701
+ };
9702
+ const syncGestureCurrent = (delta3) => {
9703
+ const turned = turnedWorlds(delta3);
9704
+ for (const [id, pose] of turned) scratch.gestureCtx.current.set(id, pose);
9705
+ return turned;
9706
+ };
9707
+ const shapeDelta = (moveCtx, raw) => {
9708
+ const gctx = scratch.gestureCtx;
9709
+ gctx.modifiers = { ...moveCtx.modifiers };
9710
+ gctx.pointer = { worldX: moveCtx.world.x, worldY: moveCtx.world.y, clientX: 0, clientY: 0 };
9711
+ const primary = scratch.originWorlds.keys().next().value;
9712
+ const originRotation = scratch.originRotations.get(primary) ?? 0;
9713
+ let delta3 = raw;
9714
+ let proposed = {
9715
+ pose: syncGestureCurrent(raw).get(primary),
9716
+ rotation: originRotation + raw
9717
+ };
9718
+ for (const b of scratch.behaviors) {
9719
+ const r = b.onMove?.(gctx, proposed);
9720
+ if (!r || r.pose === void 0) continue;
9721
+ const rotation = scratch.descriptor.getRotation?.(r.pose) ?? 0;
9722
+ delta3 = rotation - originRotation;
9723
+ proposed = { pose: r.pose, rotation };
9724
+ }
9725
+ return delta3;
9726
+ };
9727
+ const commitRotate = () => {
9728
+ const target = {
9729
+ scene,
9730
+ applyOps: scratch.applyOps,
9731
+ adapter: defaultCommitAdapter(scene, selection.adapterMethods)
9732
+ };
9733
+ if (scratch.behaviors.length > 0) {
9734
+ syncGestureCurrent(scratch.currentDelta);
9735
+ for (const b of scratch.behaviors) {
9736
+ const r = b.onEnd?.(scratch.gestureCtx);
9737
+ if (r === void 0) continue;
9738
+ if (r === null) return false;
9739
+ commitGestureOps(target, lifecycle, r);
9740
+ return true;
9741
+ }
9742
+ }
9743
+ if (scratch.currentDelta === 0) return false;
9744
+ const ops = [];
9745
+ for (const id of scratch.ids) {
9746
+ const next = scratch.previews.get(id);
9747
+ if (next === void 0) continue;
9748
+ const from = scratch.originPoses.get(id);
9749
+ if (from === void 0) continue;
9750
+ ops.push(createTransformOp({
9751
+ id,
9752
+ from,
9753
+ to: next,
9754
+ label: lifecycle.label
9755
+ }));
9756
+ }
9757
+ if (ops.length === 0) return false;
9758
+ commitGestureOps(target, lifecycle, ops);
9759
+ return true;
9760
+ };
9761
+ const recomputePreviews = (delta3) => {
9762
+ scratch.previews.clear();
9763
+ if (delta3 !== 0) {
9764
+ for (const [id, turned] of turnedWorlds(delta3)) {
9765
+ scratch.previews.set(id, scratch.frame.local(id, turned));
9766
+ }
9372
9767
  }
9373
9768
  syncPreviewOverrides(scratch);
9374
9769
  };
9770
+ for (const b of behaviors) b.onStart?.(scratch.gestureCtx);
9771
+ lifecycle.start(scratch.gestureCtx.draggedIds);
9375
9772
  return {
9376
9773
  kind: "rotate",
9377
9774
  onMove(moveCtx) {
@@ -9379,39 +9776,19 @@ var rotateAction = {
9379
9776
  moveCtx.world.y - scratch.unionCenter.y,
9380
9777
  moveCtx.world.x - scratch.unionCenter.x
9381
9778
  );
9382
- scratch.currentDelta = pointerAngle - scratch.startPointerAngle;
9779
+ const raw = pointerAngle - scratch.startPointerAngle;
9780
+ scratch.currentDelta = scratch.behaviors.length > 0 ? shapeDelta(moveCtx, raw) : raw;
9383
9781
  recomputePreviews(scratch.currentDelta);
9384
9782
  },
9385
9783
  onEnd(_endCtx, reason) {
9386
- if (reason === "cancel") {
9387
- dropPreviewOverrides(scratch);
9388
- scratch.previews.clear();
9389
- return;
9390
- }
9391
- if (scratch.currentDelta === 0) {
9784
+ let committed = false;
9785
+ try {
9786
+ if (reason === "commit") committed = commitRotate();
9787
+ } finally {
9392
9788
  dropPreviewOverrides(scratch);
9393
9789
  scratch.previews.clear();
9394
- return;
9395
- }
9396
- const ops = [];
9397
- for (const id of scratch.ids) {
9398
- const next = scratch.previews.get(id);
9399
- if (next === void 0) continue;
9400
- const from = scratch.originPoses.get(id);
9401
- if (from === void 0) continue;
9402
- ops.push(createTransformOp({
9403
- id,
9404
- from,
9405
- to: next,
9406
- label: "Rotate"
9407
- }));
9408
- }
9409
- if (ops.length > 0) {
9410
- if (scratch.applyOps) scratch.applyOps(ops, "Rotate");
9411
- else scratch.scene.applyBatch(ops, "Rotate", defaultCommitAdapter(scratch.scene, selection.adapterMethods));
9790
+ lifecycle.end(committed);
9412
9791
  }
9413
- dropPreviewOverrides(scratch);
9414
- scratch.previews.clear();
9415
9792
  },
9416
9793
  previewIds: () => scratch.previews.keys(),
9417
9794
  previewPose: (id) => scratch.previews.get(id) ?? null
@@ -9428,7 +9805,6 @@ var areaSelectAction = {
9428
9805
  // Crosshair while the marquee is being pulled — the select tool used to
9429
9806
  // read this off its own gesture scratch.
9430
9807
  activeCursor: "crosshair",
9431
- defaultBinding: { kind: "drag" },
9432
9808
  eligible: { capability: "creates-selection" },
9433
9809
  requires: ["areaSelect", "editAnchors", "view"],
9434
9810
  invoker: {
@@ -9618,7 +9994,6 @@ var insertAction = {
9618
9994
  id: "insert",
9619
9995
  label: "Insert",
9620
9996
  group: "insert",
9621
- defaultBinding: { kind: "drag" },
9622
9997
  eligible: { capability: "creates-shapes" },
9623
9998
  requires: ["insert", "selection", "snap", "textEdit"],
9624
9999
  invoker: {
@@ -9788,7 +10163,9 @@ function freshNodeId() {
9788
10163
  var cloneAction = {
9789
10164
  id: "clone",
9790
10165
  label: "Clone",
9791
- defaultBinding: { kind: "drag" },
10166
+ // No default binding: a bare `{ kind: 'drag' }` would clone a non-empty
10167
+ // selection on any drag no tool claimed. Alt-drag on a body binds it
10168
+ // (`selectionMoveBindings`, the `move` preset).
9792
10169
  // The platform's duplicate cursor, and the whole reason this field is
9793
10170
  // modifier-aware: the select tool binds clone behind `mods: { alt: true }`,
9794
10171
  // so the pump predicts it — and shows this — only while Alt is down.
@@ -9924,7 +10301,6 @@ var viewportDragPanAction = {
9924
10301
  id: "viewport.dragPan",
9925
10302
  label: "Drag to pan viewport",
9926
10303
  group: "viewport",
9927
- defaultBinding: { kind: "drag" },
9928
10304
  // Hover hint: when this action would win the drag at the hovered point
9929
10305
  // (e.g. empty canvas with no marquee-capable tool ahead of it), the
9930
10306
  // hover-cursor pump shows the open hand.
@@ -10178,20 +10554,9 @@ function cubicPointAt(p0, p1, p2, p3, t) {
10178
10554
  return { x, y };
10179
10555
  }
10180
10556
  function splitCubicAtT(p0, p1, p2, p3, t) {
10181
- const lerp = (a, b) => ({
10182
- x: a.x + (b.x - a.x) * t,
10183
- y: a.y + (b.y - a.y) * t
10184
- });
10185
- const q0 = lerp(p0, p1);
10186
- const q1 = lerp(p1, p2);
10187
- const q2 = lerp(p2, p3);
10188
- const r0 = lerp(q0, q1);
10189
- const r1 = lerp(q1, q2);
10190
- const s0 = lerp(r0, r1);
10191
- return {
10192
- left: [p0, q0, r0, s0],
10193
- right: [s0, r1, q2, p3]
10194
- };
10557
+ const [l, r] = splitCubicAt(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, t);
10558
+ const quad = (c) => [{ x: c[0], y: c[1] }, { x: c[2], y: c[3] }, { x: c[4], y: c[5] }, { x: c[6], y: c[7] }];
10559
+ return { left: quad(l), right: quad(r) };
10195
10560
  }
10196
10561
  function fitCubicThroughDeletion(prev, next) {
10197
10562
  const dx = next.x - prev.x;
@@ -10333,59 +10698,11 @@ function segmentEnds(anchors, closed, sub2, segIdx) {
10333
10698
  function isStraightSegment(a, b) {
10334
10699
  return a.outHandle == null && b.inHandle == null;
10335
10700
  }
10336
- var COARSE_SAMPLES = 32;
10337
- var REFINE_STEPS = 48;
10338
- var GOLDEN = (Math.sqrt(5) - 1) / 2;
10339
10701
  function nearestOnSegment(a, b, x, y) {
10340
- if (isStraightSegment(a, b)) {
10341
- const dx = b.x - a.x;
10342
- const dy = b.y - a.y;
10343
- const len2 = dx * dx + dy * dy;
10344
- const t2 = len2 === 0 ? 0 : Math.min(1, Math.max(0, ((x - a.x) * dx + (y - a.y) * dy) / len2));
10345
- const ex = a.x + dx * t2 - x;
10346
- const ey = a.y + dy * t2 - y;
10347
- return { t: t2, d2: ex * ex + ey * ey };
10348
- }
10349
10702
  const p1 = a.outHandle ?? a;
10350
10703
  const p2 = b.inHandle ?? b;
10351
- const d2At = (t2) => {
10352
- const q = cubicPointAt(a, p1, p2, b, t2);
10353
- return (q.x - x) ** 2 + (q.y - y) ** 2;
10354
- };
10355
- let bestT = 0;
10356
- let bestD2 = Infinity;
10357
- for (let k = 0; k <= COARSE_SAMPLES; k++) {
10358
- const t2 = k / COARSE_SAMPLES;
10359
- const d22 = d2At(t2);
10360
- if (d22 < bestD2) {
10361
- bestD2 = d22;
10362
- bestT = t2;
10363
- }
10364
- }
10365
- let lo = Math.max(0, bestT - 1 / COARSE_SAMPLES);
10366
- let hi = Math.min(1, bestT + 1 / COARSE_SAMPLES);
10367
- let m1 = hi - GOLDEN * (hi - lo);
10368
- let m2 = lo + GOLDEN * (hi - lo);
10369
- let f1 = d2At(m1);
10370
- let f2 = d2At(m2);
10371
- for (let i = 0; i < REFINE_STEPS; i++) {
10372
- if (f1 < f2) {
10373
- hi = m2;
10374
- m2 = m1;
10375
- f2 = f1;
10376
- m1 = hi - GOLDEN * (hi - lo);
10377
- f1 = d2At(m1);
10378
- } else {
10379
- lo = m1;
10380
- m1 = m2;
10381
- f1 = f2;
10382
- m2 = lo + GOLDEN * (hi - lo);
10383
- f2 = d2At(m2);
10384
- }
10385
- }
10386
- const t = (lo + hi) / 2;
10387
- const d2 = d2At(t);
10388
- return d2 < bestD2 ? { t, d2 } : { t: bestT, d2: bestD2 };
10704
+ const hit = isStraightSegment(a, b) ? nearestOnLine(x, y, a.x, a.y, b.x, b.y) : nearestOnCubic(x, y, a.x, a.y, p1.x, p1.y, p2.x, p2.y, b.x, b.y);
10705
+ return { t: hit.t, d2: hit.dist * hit.dist };
10389
10706
  }
10390
10707
  function nearestSegmentT(anchors, wx, wy, closed) {
10391
10708
  let bestD2 = Infinity;
@@ -10738,6 +11055,48 @@ var editAnchorsAction = {
10738
11055
  },
10739
11056
  enabled: () => true
10740
11057
  };
11058
+ var LASSO_VERTICES = scratchKey("lassoSelect.vertices");
11059
+ var MIN_AREA = 4;
11060
+ function selectFromLasso(opts) {
11061
+ const mode = opts?.mode ?? "intersect";
11062
+ return {
11063
+ defaultTransient: true,
11064
+ onEnd(ctx) {
11065
+ const adapter = ctx.adapter;
11066
+ if (!adapter.getSelection || !adapter.hitTestLasso) return null;
11067
+ const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];
11068
+ const startPose = ctx.origin.get("gesture");
11069
+ const shiftHeld = startPose ? startPose.shiftHeld === true : false;
11070
+ const from = adapter.getSelection();
11071
+ const tiny = vertices.length < 3 || polygonAabbArea(vertices) < MIN_AREA;
11072
+ if (tiny) {
11073
+ const to2 = shiftHeld ? from : [];
11074
+ const op2 = createSetSelectionOp({ from, to: to2 });
11075
+ return [op2];
11076
+ }
11077
+ const hits = adapter.hitTestLasso(vertices, mode);
11078
+ const to = shiftHeld ? mergeUnique(from, hits) : hits;
11079
+ const op = createSetSelectionOp({ from, to });
11080
+ return [op];
11081
+ }
11082
+ };
11083
+ }
11084
+ function polygonAabbArea(vertices) {
11085
+ if (vertices.length === 0) return 0;
11086
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
11087
+ for (const v of vertices) {
11088
+ if (v.x < minX) minX = v.x;
11089
+ if (v.y < minY) minY = v.y;
11090
+ if (v.x > maxX) maxX = v.x;
11091
+ if (v.y > maxY) maxY = v.y;
11092
+ }
11093
+ return (maxX - minX) * (maxY - minY);
11094
+ }
11095
+ function mergeUnique(a, b) {
11096
+ const out = [...a];
11097
+ for (const id of b) if (!out.includes(id)) out.push(id);
11098
+ return out;
11099
+ }
10741
11100
 
10742
11101
  // src/interactions/actions/defaults/lassoSelect.ts
10743
11102
  function polygonAABB(pts) {
@@ -10751,26 +11110,68 @@ function polygonAABB(pts) {
10751
11110
  }
10752
11111
  return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
10753
11112
  }
10754
- var MIN_VERTEX_SPACING = 2;
11113
+ function hitTestPolygon(dep, vertices, mode, view) {
11114
+ if (dep.hitTestLasso) return dep.hitTestLasso(vertices, mode, view);
11115
+ const aabb = polygonAABB(vertices);
11116
+ return aabb ? dep.hitTestArea(aabb, view) : [];
11117
+ }
11118
+ function behaviorAdapter(dep, view) {
11119
+ const adapter = {
11120
+ getSelection: () => dep.getSelection(),
11121
+ setSelection: (ids) => dep.setSelection(ids),
11122
+ hitTestArea: (rect) => dep.hitTestArea(rect, view),
11123
+ hitTestLasso: (polygon, mode) => hitTestPolygon(dep, polygon, mode, view),
11124
+ applyOps: (ops) => applyOpsTo(adapter, ops)
11125
+ };
11126
+ return adapter;
11127
+ }
11128
+ var DEFAULT_MIN_VERTEX_SPACING = 2;
10755
11129
  var lassoSelectAction = {
10756
11130
  id: "lassoSelect",
10757
11131
  label: "Lasso Select",
10758
- defaultBinding: { kind: "drag", mods: { shift: "optional" } },
10759
11132
  eligible: { capability: "creates-selection" },
10760
11133
  requires: ["lassoSelect", "view"],
10761
11134
  invoker: {
10762
11135
  timing: "ongoing",
10763
- start(ctx, _opts) {
11136
+ start(ctx, opts) {
10764
11137
  const dep = ctx.deps.lassoSelect;
10765
11138
  if (!dep) return {};
11139
+ const params = resolveParams(opts?.params);
11140
+ const view = ctx.deps.view;
11141
+ const vertices = [{ x: ctx.world.x, y: ctx.world.y }];
11142
+ const behaviors = opts?.behaviors ?? [];
11143
+ let gesture = null;
11144
+ if (behaviors.length > 0) {
11145
+ const pose = {
11146
+ worldX: ctx.world.x,
11147
+ worldY: ctx.world.y,
11148
+ shiftHeld: ctx.modifiers.shift
11149
+ };
11150
+ gesture = {
11151
+ draggedIds: ["gesture"],
11152
+ origin: /* @__PURE__ */ new Map([["gesture", pose]]),
11153
+ current: /* @__PURE__ */ new Map([["gesture", { ...pose }]]),
11154
+ snap: null,
11155
+ modifiers: { ...ctx.modifiers },
11156
+ pointer: { worldX: ctx.world.x, worldY: ctx.world.y, clientX: 0, clientY: 0 },
11157
+ adapter: behaviorAdapter(dep, view),
11158
+ scratch: {}
11159
+ };
11160
+ setScratch(gesture.scratch, LASSO_VERTICES, vertices);
11161
+ for (const b of behaviors) b.onStart?.(gesture);
11162
+ }
10766
11163
  const scratch = {
10767
11164
  dep,
10768
- view: ctx.deps.view,
10769
- vertices: [{ x: ctx.world.x, y: ctx.world.y }],
11165
+ mode: params?.mode ?? "intersect",
11166
+ minVertexSpacing: params?.minVertexSpacing ?? DEFAULT_MIN_VERTEX_SPACING,
11167
+ view,
11168
+ vertices,
10770
11169
  shiftHeld: ctx.modifiers.shift,
10771
11170
  currentX: ctx.world.x,
10772
11171
  currentY: ctx.world.y,
10773
- open: true
11172
+ open: true,
11173
+ behaviors,
11174
+ gesture
10774
11175
  };
10775
11176
  return {
10776
11177
  kind: "lasso",
@@ -10781,9 +11182,18 @@ var lassoSelectAction = {
10781
11182
  const last = scratch.vertices[scratch.vertices.length - 1];
10782
11183
  const dx = x - last.x;
10783
11184
  const dy = y - last.y;
10784
- if (dx * dx + dy * dy >= MIN_VERTEX_SPACING * MIN_VERTEX_SPACING) {
11185
+ const min = scratch.minVertexSpacing;
11186
+ if (dx * dx + dy * dy >= min * min) {
10785
11187
  scratch.vertices.push({ x, y });
10786
11188
  }
11189
+ const g = scratch.gesture;
11190
+ if (g) {
11191
+ g.modifiers = { ...moveCtx.modifiers };
11192
+ g.pointer = { worldX: x, worldY: y, clientX: 0, clientY: 0 };
11193
+ g.current.set("gesture", { worldX: x, worldY: y, shiftHeld: scratch.shiftHeld });
11194
+ const proposed = { vertices: scratch.vertices, shiftHeld: scratch.shiftHeld };
11195
+ for (const b of scratch.behaviors) b.onMove?.(g, proposed);
11196
+ }
10787
11197
  },
10788
11198
  overlay() {
10789
11199
  if (!scratch.open) return null;
@@ -10797,18 +11207,20 @@ var lassoSelectAction = {
10797
11207
  onEnd(_endCtx, reason) {
10798
11208
  scratch.open = false;
10799
11209
  if (reason === "cancel") return;
10800
- const { dep: d, view, vertices, shiftHeld } = scratch;
10801
- if (vertices.length < 3) {
11210
+ const { dep: d, mode, view: view2, vertices: vertices2, shiftHeld, gesture: g } = scratch;
11211
+ if (g) {
11212
+ for (const b of scratch.behaviors) {
11213
+ const r = b.onEnd?.(g);
11214
+ if (r === void 0) continue;
11215
+ if (r !== null) g.adapter.applyOps(r);
11216
+ return;
11217
+ }
11218
+ }
11219
+ if (vertices2.length < 3) {
10802
11220
  if (!shiftHeld) d.setSelection([]);
10803
11221
  return;
10804
11222
  }
10805
- let hits;
10806
- if (d.hitTestLasso) {
10807
- hits = d.hitTestLasso(vertices, "centers", view);
10808
- } else {
10809
- const aabb = polygonAABB(vertices);
10810
- hits = aabb ? d.hitTestArea(aabb, view) : [];
10811
- }
11223
+ const hits = hitTestPolygon(d, vertices2, mode, view2);
10812
11224
  if (shiftHeld) {
10813
11225
  const current = d.getSelection();
10814
11226
  const merged = [...current];
@@ -10829,7 +11241,6 @@ var sliceAction = {
10829
11241
  id: "slice",
10830
11242
  label: "Slice",
10831
11243
  group: "edit",
10832
- defaultBinding: { kind: "drag" },
10833
11244
  eligible: { capability: "edits-page" },
10834
11245
  requires: ["slice"],
10835
11246
  invoker: {
@@ -11225,9 +11636,13 @@ var escapeAction = {
11225
11636
  // `exitPathEdit`'s bare key spec, swallowing the Escape that should leave
11226
11637
  // edit mode. With this gate, `escape` reports a disabled reason in that
11227
11638
  // case and the dispatcher falls through to `exitPathEdit`.
11639
+ // With nothing selected there is nothing to clear, so the press falls
11640
+ // through to `tool.resetToDefault` instead of being spent here.
11228
11641
  enabled: (deps) => {
11229
11642
  const editAnchors = deps?.editAnchors;
11230
11643
  if (editAnchors?.editingId) return ActionDisabledReason.NotApplicable;
11644
+ const sel = deps?.selection?.get() ?? [];
11645
+ if (sel.length === 0) return ActionDisabledReason.NotApplicable;
11231
11646
  return true;
11232
11647
  }
11233
11648
  };
@@ -13666,12 +14081,27 @@ function useResizePolicy(options) {
13666
14081
  useDepSource("resizePolicy", () => {
13667
14082
  const o = optsRef.current;
13668
14083
  return {
14084
+ label: o.label,
14085
+ transient: o.transient,
14086
+ onGestureStart: o.onGestureStart,
14087
+ onGestureEnd: o.onGestureEnd,
13669
14088
  constraints: o.constraints ?? DEFAULT_RESIZE_BEHAVIORS,
13670
14089
  pointSnap: o.pointSnap ?? EMPTY2,
13671
14090
  expandIds: o.expandIds ?? IDENTITY_EXPAND2
13672
14091
  };
13673
14092
  });
13674
14093
  }
14094
+ function resizePolicyOptions(o) {
14095
+ return {
14096
+ constraints: o.behaviors,
14097
+ pointSnap: o.pointSnapBehaviors,
14098
+ expandIds: o.expandIds,
14099
+ label: o.resizeLabel,
14100
+ transient: o.transient,
14101
+ onGestureStart: o.onGestureStart,
14102
+ onGestureEnd: o.onGestureEnd
14103
+ };
14104
+ }
13675
14105
  function usePoseDescriptorDepSource(descriptor) {
13676
14106
  const ref = useRef(descriptor);
13677
14107
  ref.current = descriptor;
@@ -13816,63 +14246,6 @@ function useDecayLoop() {
13816
14246
  }, [cancel]);
13817
14247
  return { start, cancel };
13818
14248
  }
13819
- function dist(a, b) {
13820
- return Math.hypot(b.x - a.x, b.y - a.y);
13821
- }
13822
- function mid(a, b) {
13823
- return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
13824
- }
13825
- function usePinchGesture(canvasRef, onPinch, enabled = true) {
13826
- const onPinchRef = useRef(onPinch);
13827
- onPinchRef.current = onPinch;
13828
- useEffect(() => {
13829
- if (!enabled) return;
13830
- const el = canvasRef.current;
13831
- if (!el) return;
13832
- const pointers = /* @__PURE__ */ new Map();
13833
- let startDist = null;
13834
- function getTwo() {
13835
- const vals = [...pointers.values()];
13836
- if (vals.length < 2) return null;
13837
- return [vals[0], vals[1]];
13838
- }
13839
- function onDown(e) {
13840
- pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
13841
- const two = getTwo();
13842
- if (two) startDist = dist(two[0], two[1]);
13843
- }
13844
- function onMove(e) {
13845
- if (!pointers.has(e.pointerId)) return;
13846
- pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
13847
- const two = getTwo();
13848
- if (!two || startDist === null) return;
13849
- const currentDist = dist(two[0], two[1]);
13850
- const anchor = mid(two[0], two[1]);
13851
- const factor = currentDist / startDist;
13852
- startDist = currentDist;
13853
- onPinchRef.current(anchor, factor);
13854
- }
13855
- function onUp(e) {
13856
- pointers.delete(e.pointerId);
13857
- if (pointers.size < 2) {
13858
- startDist = null;
13859
- } else {
13860
- const two = getTwo();
13861
- if (two) startDist = dist(two[0], two[1]);
13862
- }
13863
- }
13864
- el.addEventListener("pointerdown", onDown);
13865
- el.addEventListener("pointermove", onMove);
13866
- el.addEventListener("pointerup", onUp);
13867
- el.addEventListener("pointercancel", onUp);
13868
- return () => {
13869
- el.removeEventListener("pointerdown", onDown);
13870
- el.removeEventListener("pointermove", onMove);
13871
- el.removeEventListener("pointerup", onUp);
13872
- el.removeEventListener("pointercancel", onUp);
13873
- };
13874
- }, [canvasRef, enabled]);
13875
- }
13876
14249
 
13877
14250
  // src/core/viewport/interpolateView.ts
13878
14251
  function axisCurve(x0, s0, x1, s1) {
@@ -14396,9 +14769,9 @@ function firstAfter(events, t) {
14396
14769
  let lo = 0;
14397
14770
  let hi = events.length;
14398
14771
  while (lo < hi) {
14399
- const mid2 = lo + hi >> 1;
14400
- if (events[mid2].t > t) hi = mid2;
14401
- else lo = mid2 + 1;
14772
+ const mid = lo + hi >> 1;
14773
+ if (events[mid].t > t) hi = mid;
14774
+ else lo = mid + 1;
14402
14775
  }
14403
14776
  return lo;
14404
14777
  }
@@ -14526,12 +14899,12 @@ function floorKeyIndex(keys, t) {
14526
14899
  let hi = keys.length - 1;
14527
14900
  let found = -1;
14528
14901
  while (lo <= hi) {
14529
- const mid2 = lo + hi >> 1;
14530
- if (keys[mid2].t <= t) {
14531
- found = mid2;
14532
- lo = mid2 + 1;
14902
+ const mid = lo + hi >> 1;
14903
+ if (keys[mid].t <= t) {
14904
+ found = mid;
14905
+ lo = mid + 1;
14533
14906
  } else {
14534
- hi = mid2 - 1;
14907
+ hi = mid - 1;
14535
14908
  }
14536
14909
  }
14537
14910
  return found;
@@ -15267,12 +15640,6 @@ function useViewAnimationOn(view, animator) {
15267
15640
  };
15268
15641
  }, [key]);
15269
15642
  }
15270
- function defineTool(def) {
15271
- return defineTool$1(def);
15272
- }
15273
- function defineViewportTool(def) {
15274
- return defineViewportTool$1(def);
15275
- }
15276
15643
  var useTools = useTools$1;
15277
15644
 
15278
15645
  // src/interactions/actions/defaults/toolActivate.ts
@@ -15359,6 +15726,7 @@ function useKeybindings(tools, options = {}) {
15359
15726
  useEffect(() => {
15360
15727
  if (optionsRef.current.disable) return;
15361
15728
  if (!registry2) return;
15729
+ if (Object.keys(toolsRef.current.registry).length === 0) return;
15362
15730
  const specs = [];
15363
15731
  for (const [toolId, keyOpts] of Object.entries(BUILTIN_SELECT_KEYS)) {
15364
15732
  if (toolsRef.current.has(toolId)) {
@@ -15398,8 +15766,6 @@ function useKeybindings(tools, options = {}) {
15398
15766
  };
15399
15767
  }, [registry2, tools]);
15400
15768
  }
15401
-
15402
- // src/tools/prefs.ts
15403
15769
  function prefUnit(system, display, opts) {
15404
15770
  let self;
15405
15771
  try {
@@ -15726,13 +16092,19 @@ function UnknownIcon({ className, size = 20 }) {
15726
16092
  }
15727
16093
  );
15728
16094
  }
15729
- function closedSubpaths(path, tolerance) {
16095
+ function closedSubpaths(path, tolerance, keepOpen = false) {
16096
+ const { closed, open } = flatSubpaths(path, tolerance);
16097
+ return keepOpen ? [...closed, ...open] : closed;
16098
+ }
16099
+ function flatSubpaths(path, tolerance) {
15730
16100
  const { commands, coords } = path;
15731
- const out = [];
16101
+ const closed = [];
16102
+ const open = [];
15732
16103
  let sub2 = [];
15733
16104
  forEachSegment(commands, coords, (cmd, ci, curX, curY) => {
15734
16105
  switch (cmd) {
15735
16106
  case PATH_M:
16107
+ if (sub2.length >= 2) open.push(sub2);
15736
16108
  sub2 = [coords[ci], coords[ci + 1]];
15737
16109
  break;
15738
16110
  case PATH_L:
@@ -15765,14 +16137,16 @@ function closedSubpaths(path, tolerance) {
15765
16137
  );
15766
16138
  break;
15767
16139
  case PATH_Z:
15768
- if (sub2.length >= 6) out.push(sub2);
16140
+ if (sub2.length >= 6) closed.push(sub2);
16141
+ else if (sub2.length >= 2) open.push(sub2);
15769
16142
  sub2 = [];
15770
16143
  break;
15771
16144
  default:
15772
16145
  throw new Error(`pathHitTest: unknown command ${cmd}`);
15773
16146
  }
15774
16147
  });
15775
- return out;
16148
+ if (sub2.length >= 2) open.push(sub2);
16149
+ return { closed, open };
15776
16150
  }
15777
16151
  function rectToVerts(r) {
15778
16152
  return [
@@ -15830,6 +16204,18 @@ function anyEdgeCrossesPolygon(subpaths, poly2) {
15830
16204
  return false;
15831
16205
  });
15832
16206
  }
16207
+ function anyOpenEdgeCrossesPolygon(runs, poly2) {
16208
+ for (const run2 of runs) {
16209
+ for (let i = 0; i + 3 < run2.length; i += 2) {
16210
+ const ax = run2[i], ay = run2[i + 1], bx = run2[i + 2], by = run2[i + 3];
16211
+ for (let k = 0; k < poly2.length; k++) {
16212
+ const p0 = poly2[k], p1 = poly2[(k + 1) % poly2.length];
16213
+ if (segmentsCross(ax, ay, bx, by, p0.x, p0.y, p1.x, p1.y)) return true;
16214
+ }
16215
+ }
16216
+ }
16217
+ return false;
16218
+ }
15833
16219
  function anyVertexInRect(subpaths, r) {
15834
16220
  for (const sub2 of subpaths) {
15835
16221
  for (let i = 0; i < sub2.length; i += 2) {
@@ -15903,128 +16289,34 @@ function pathIntersectsPolygon(path, polygon, opts = {}) {
15903
16289
  if (anyVertexInPolygon(subpaths, flattenVerts(polygon))) return true;
15904
16290
  return anyEdgeCrossesPolygon(subpaths, polygon);
15905
16291
  }
15906
-
15907
- // src/features/paths/markerSites.ts
15908
- var EPS2 = 1e-9;
15909
- function dir(pl, i, j) {
15910
- const dx = pl.points[j * 2] - pl.points[i * 2];
15911
- const dy = pl.points[j * 2 + 1] - pl.points[i * 2 + 1];
15912
- const len = Math.hypot(dx, dy);
15913
- if (len < EPS2) return null;
15914
- return { x: dx / len, y: dy / len };
15915
- }
15916
- function nextDistinct(pl, i, n) {
15917
- for (let k = i + 1; k < n; k++) if (dir(pl, i, k)) return k;
15918
- return -1;
16292
+ function polygonContainsPath(polygon, path, opts = {}) {
16293
+ if (polygon.length < 3) return false;
16294
+ const subpaths = path.kind === "rect" ? [flattenVerts(rectToVerts(path))] : closedSubpaths(path, opts.tolerance ?? DEFAULT_FLATTEN_TOLERANCE, true);
16295
+ if (subpaths.length === 0) return false;
16296
+ const flatPoly = flattenVerts(polygon);
16297
+ for (const sub2 of subpaths) {
16298
+ for (let i = 0; i < sub2.length; i += 2) {
16299
+ if (!pointInPolygon(flatPoly, sub2[i], sub2[i + 1])) return false;
16300
+ }
16301
+ }
16302
+ return !anyEdgeCrossesPolygon(subpaths, polygon);
15919
16303
  }
15920
- function prevDistinct(pl, i) {
15921
- for (let k = i - 1; k >= 0; k--) if (dir(pl, k, i)) return k;
15922
- return -1;
16304
+ function polygonIntersectsPath(polygon, path, opts = {}) {
16305
+ if (polygon.length < 3) return false;
16306
+ if (path.kind === "rect") return polygonsIntersect(rectToVerts(path), polygon);
16307
+ const { closed, open } = flatSubpaths(path, opts.tolerance ?? DEFAULT_FLATTEN_TOLERANCE);
16308
+ const flatPoly = flattenVerts(polygon);
16309
+ if (anyVertexInPolygon(closed, flatPoly) || anyVertexInPolygon(open, flatPoly)) return true;
16310
+ if (anyEdgeCrossesPolygon(closed, polygon) || anyOpenEdgeCrossesPolygon(open, polygon)) {
16311
+ return true;
16312
+ }
16313
+ return closed.length > 0 && pointInPath(path, polygon[0].x, polygon[0].y, opts);
15923
16314
  }
15924
- function markerSites(pl, want) {
15925
- const out = [];
15926
- const n = pl.points.length / 2;
15927
- if (n < 2) return out;
15928
- if (want.start && !pl.closed) {
15929
- const j = nextDistinct(pl, 0, n);
15930
- const d = j >= 0 ? dir(pl, 0, j) : null;
15931
- if (d) {
15932
- out.push({ x: pl.points[0], y: pl.points[1], angle: Math.atan2(-d.y, -d.x), role: "start" });
15933
- }
15934
- }
15935
- if (want.mid) {
15936
- const A = pl.anchorA, B = pl.anchorB, T = pl.anchorT;
15937
- if (A && B && T) {
15938
- for (let i = 1; i < n - 1; i++) {
15939
- if (A[i] !== B[i] || T[i] !== 0) continue;
15940
- const p = prevDistinct(pl, i);
15941
- const q = nextDistinct(pl, i, n);
15942
- if (p < 0 || q < 0) continue;
15943
- const inDir = dir(pl, p, i);
15944
- const outDir = dir(pl, i, q);
15945
- let bx = inDir.x + outDir.x;
15946
- let by = inDir.y + outDir.y;
15947
- if (Math.hypot(bx, by) < EPS2) {
15948
- bx = inDir.x;
15949
- by = inDir.y;
15950
- }
15951
- out.push({ x: pl.points[i * 2], y: pl.points[i * 2 + 1], angle: Math.atan2(by, bx), role: "mid" });
15952
- }
15953
- }
15954
- }
15955
- if (want.end && !pl.closed) {
15956
- const last = n - 1;
15957
- const p = prevDistinct(pl, last);
15958
- const d = p >= 0 ? dir(pl, p, last) : null;
15959
- if (d) {
15960
- out.push({ x: pl.points[last * 2], y: pl.points[last * 2 + 1], angle: Math.atan2(d.y, d.x), role: "end" });
15961
- }
15962
- }
15963
- return out;
15964
- }
15965
-
15966
- // src/features/paths/markerCommands.ts
15967
- function placed(path, site) {
15968
- const src = path;
15969
- const cos = Math.cos(site.angle);
15970
- const sin = Math.sin(site.angle);
15971
- const coords = new Float32Array(src.coords.length);
15972
- for (let i = 0; i < src.coords.length; i += 2) {
15973
- const x = src.coords[i], y = src.coords[i + 1];
15974
- coords[i] = site.x + x * cos - y * sin;
15975
- coords[i + 1] = site.y + x * sin + y * cos;
15976
- }
15977
- return { kind: "polygon", commands: src.commands, coords, fillRule: src.fillRule };
15978
- }
15979
- function resolvePaint(p, stroke, fallback) {
15980
- const v = p ?? fallback;
15981
- if (v === "none") return void 0;
15982
- return v === "line" ? stroke.paint : v;
15983
- }
15984
- function commandFor(entry, ref, stroke, strokeWidth, site) {
15985
- const size = resolveMarkerSize(ref, strokeWidth);
15986
- const geometry = entry.path({ size, stroke });
15987
- const angle = entry.orient === void 0 || entry.orient === "auto" ? site.angle : entry.orient;
15988
- const path = placed(geometry, { ...site, angle });
15989
- const fill = resolvePaint(entry.fill, stroke, "line");
15990
- const outline = entry.outline ? {
15991
- paint: resolvePaint(entry.outline.paint, stroke, "line"),
15992
- width: entry.outline.width * size
15993
- } : null;
15994
- if (fill === void 0 && (outline === null || outline.paint === void 0)) return null;
15995
- return {
15996
- kind: "path",
15997
- path,
15998
- ...fill ? { fill } : {},
15999
- ...outline && outline.paint ? { stroke: { paint: outline.paint, width: outline.width, cap: "round", join: "round" } } : {}
16000
- };
16001
- }
16002
- function markerDrawCommands(path, stroke, strokeWidth, flattenTolerance) {
16003
- const want = {
16004
- start: stroke.markerStart !== void 0,
16005
- mid: stroke.markerMid !== void 0,
16006
- end: stroke.markerEnd !== void 0
16007
- };
16008
- if (!want.start && !want.mid && !want.end) return [];
16009
- const refFor = (role) => role === "start" ? stroke.markerStart : role === "mid" ? stroke.markerMid : stroke.markerEnd;
16010
- const out = [];
16011
- for (const pl of extractPolylines(path, { flattenTolerance })) {
16012
- for (const site of markerSites(pl, want)) {
16013
- const ref = refFor(site.role);
16014
- if (ref === void 0) continue;
16015
- const entry = getMarker(markerKeyOf(ref));
16016
- if (entry === void 0) continue;
16017
- const cmd = commandFor(entry, ref, stroke, strokeWidth, site);
16018
- if (cmd) out.push(cmd);
16019
- }
16020
- }
16021
- return out;
16022
- }
16023
-
16024
- // src/core/fillInPoseFrame.ts
16025
- function fillInPoseFrame(fill, box) {
16026
- if (!isBuiltinKind(fill)) {
16027
- return getPaintKind(fill.fill)?.inPoseFrame?.(fill, box) ?? fill;
16315
+
16316
+ // src/core/fillInPoseFrame.ts
16317
+ function fillInPoseFrame(fill, box) {
16318
+ if (!isBuiltinKind(fill)) {
16319
+ return getPaintKind(fill.fill)?.inPoseFrame?.(fill, box) ?? fill;
16028
16320
  }
16029
16321
  if (isBoundsPattern(fill)) {
16030
16322
  const o = fill.origin ?? { x: 0, y: 0 };
@@ -16434,11 +16726,7 @@ function strokeInPoseFrame(stroke, box) {
16434
16726
  function inkReach(stroke, scale) {
16435
16727
  if (stroke === null) return { outset: 0, inset: 0 };
16436
16728
  const w = resolveStrokeWidth(stroke.width ?? 1, scale ?? 1);
16437
- const reach = Math.max(
16438
- markerInset(stroke.markerStart, w),
16439
- markerInset(stroke.markerEnd, w),
16440
- markerInset(stroke.markerMid, w)
16441
- );
16729
+ const reach = markerReach(stroke, w, scale);
16442
16730
  switch (stroke.align ?? "center") {
16443
16731
  case "inner":
16444
16732
  return { outset: reach, inset: w };
@@ -16490,9 +16778,7 @@ var PATH_PAINTER = {
16490
16778
  ...fill && d.vertexColors ? { vertexColors: d.vertexColors } : {},
16491
16779
  ...stroke ? { stroke } : {}
16492
16780
  };
16493
- if (!stroke) return [cmd];
16494
- const width = resolveStrokeWidth(stroke.width ?? 1, 1);
16495
- return [cmd, ...markerDrawCommands(projected, stroke, width, void 0)];
16781
+ return [cmd];
16496
16782
  }), ctx),
16497
16783
  silhouette: (node, pose) => {
16498
16784
  const d = node.data;
@@ -16657,9 +16943,7 @@ var DERIVED_PAINTER = {
16657
16943
  ...fill && d?.vertexColors ? { vertexColors: d.vertexColors } : {},
16658
16944
  ...stroke ? { stroke } : {}
16659
16945
  };
16660
- if (!stroke) return withAnimatedVertexColors([cmd], ctx);
16661
- const width = resolveStrokeWidth(stroke.width ?? 1, 1);
16662
- return withAnimatedVertexColors([cmd, ...markerDrawCommands(path, stroke, width, void 0)], ctx);
16946
+ return withAnimatedVertexColors([cmd], ctx);
16663
16947
  },
16664
16948
  // The derived path *is* the silhouette, and it is already absolute — the
16665
16949
  // pose is a placeholder, so nothing here can be recovered from it. Without
@@ -17010,6 +17294,103 @@ function zOf(getZIndex, id) {
17010
17294
  const z = getZIndex(id);
17011
17295
  return typeof z === "number" && Number.isFinite(z) ? z : -Infinity;
17012
17296
  }
17297
+ var SELECTION_MOVE_ID = "selection.move";
17298
+ var SELECTION_TRANSFORM_ID = "selection.transform";
17299
+ function selectionMoveBindings(options = {}) {
17300
+ const moveOpts = (() => {
17301
+ const move = options.move ?? {};
17302
+ const params = gestureLifecycleParams({ ...move, label: move.moveLabel });
17303
+ if (options.reparentOnDrop && options.reparentOnDrop !== "off") params.reparentOnDrop = options.reparentOnDrop;
17304
+ if (move.dragThresholdPx !== void 0) params.dragThresholdPx = move.dragThresholdPx;
17305
+ if (move.expandIds) params.expandIds = move.expandIds;
17306
+ const withParams = Object.keys(params).length > 0 ? { params } : void 0;
17307
+ const behaviors = options.move?.behaviors?.length ? { behaviors: options.move.behaviors } : void 0;
17308
+ return withParams || behaviors ? { opts: { ...withParams, ...behaviors } } : {};
17309
+ })();
17310
+ return [
17311
+ // Alt-drag on a body → clone (Illustrator convention).
17312
+ // Listed BEFORE bare move so the strict-modifier dispatcher
17313
+ // picks this when Alt is held; bare move matches no-mod drags.
17314
+ // Both selected and unselected bodies are valid clone targets — the
17315
+ // select tool's pointerDown classifier calls `selection.applyClick(top, mods)`
17316
+ // on an unselected hit before the drag fires, so cloneAction's
17317
+ // `start` sees the hit node in `selection.get()` either way.
17318
+ { spec: { kind: "drag", target: "selected-body", mods: { alt: true } }, actionId: "clone" },
17319
+ { spec: { kind: "drag", target: "unselected-body", mods: { alt: true } }, actionId: "clone" },
17320
+ {
17321
+ // Body-drag → move, BUT defer when the pointerdown hit a path
17322
+ // anchor / control affordance. Anchors lie on the curve so the
17323
+ // body classifier still reports 'selected-body'; without this
17324
+ // opt-out, move's binding beats editAnchors's on every anchor drag.
17325
+ //
17326
+ // `isAnchorOrControl` is the SAME predicate `editAnchorsAction`
17327
+ // matches on. That is load-bearing: the two must agree on what
17328
+ // counts as an anchor hit or move steals the drag — which is
17329
+ // exactly the bug this opt-out exists to prevent.
17330
+ spec: {
17331
+ kind: "drag",
17332
+ target: {
17333
+ kindOf: (afford, body) => body === "selected-body" && !isAnchorOrControl(afford)
17334
+ }
17335
+ },
17336
+ actionId: "move",
17337
+ ...moveOpts
17338
+ },
17339
+ // Body-drag on a NOT-yet-selected node → also move. The select tool's
17340
+ // pointerDown classifier selects the hit node before the drag fires (same
17341
+ // contract clone relies on), so by the time moveAction.start() runs the
17342
+ // node is in `selection.get()`. Without this binding an unselected-body
17343
+ // drag falls through to whatever ambient drag binding comes next — once
17344
+ // the `rotate` catch-all, which made the first drag rotate and later ones
17345
+ // move. Unselected nodes never carry anchor affordances (those gate on
17346
+ // selection), so the plain string-form target is sufficient here.
17347
+ {
17348
+ spec: { kind: "drag", target: "unselected-body" },
17349
+ actionId: "move",
17350
+ ...moveOpts
17351
+ }
17352
+ ];
17353
+ }
17354
+ function selectionTransformBindings(options = {}) {
17355
+ const rotate2 = options.rotate ?? {};
17356
+ const params = gestureLifecycleParams({ ...rotate2, label: rotate2.rotateLabel });
17357
+ if (rotate2.pivot) params.pivot = rotate2.pivot;
17358
+ const opts = {
17359
+ ...Object.keys(params).length > 0 ? { params } : {},
17360
+ ...rotate2.behaviors?.length ? { behaviors: rotate2.behaviors } : {}
17361
+ };
17362
+ return [
17363
+ { spec: { kind: "drag", target: { kindOf: isResizeHandle } }, actionId: "resize" },
17364
+ {
17365
+ spec: { kind: "drag", target: { kindOf: isRotateHandle } },
17366
+ actionId: "rotate",
17367
+ ...Object.keys(opts).length > 0 ? { opts } : {}
17368
+ }
17369
+ ];
17370
+ }
17371
+ function selectionMoveContribution(options = {}) {
17372
+ return {
17373
+ id: SELECTION_MOVE_ID,
17374
+ eligibility: { always: true },
17375
+ bindings: selectionMoveBindings(options)
17376
+ };
17377
+ }
17378
+ function selectionTransformContribution(options = {}) {
17379
+ return {
17380
+ id: SELECTION_TRANSFORM_ID,
17381
+ eligibility: { always: true },
17382
+ bindings: selectionTransformBindings(options)
17383
+ };
17384
+ }
17385
+ var AREA_SELECT_ID = "selection.areaSelect";
17386
+ var onEmptyCanvas = (hit, body) => hit == null && body === "empty";
17387
+ function areaSelectContribution() {
17388
+ return {
17389
+ id: AREA_SELECT_ID,
17390
+ eligibility: { always: true },
17391
+ bindings: [{ spec: { kind: "drag", target: { kindOf: onEmptyCanvas } }, actionId: "areaSelect" }]
17392
+ };
17393
+ }
17013
17394
 
17014
17395
  // src/core/selection/selectionTarget.ts
17015
17396
  var MULTI_RESIZE_TARGET_ID = "__weasel:multi-selection";
@@ -17136,21 +17517,14 @@ function useSelectTool(adapter, options) {
17136
17517
  },
17137
17518
  actions: [pickAction, collapseDeferredAction]
17138
17519
  });
17139
- const moveOpts = (() => {
17140
- const reparent = options.reparentOnDrop && options.reparentOnDrop !== "off" ? { params: { reparentOnDrop: options.reparentOnDrop } } : void 0;
17141
- const behaviors = options.move?.behaviors?.length ? { behaviors: options.move.behaviors } : void 0;
17142
- return reparent || behaviors ? { opts: { ...reparent, ...behaviors } } : {};
17143
- })();
17144
17520
  return {
17145
17521
  ...base,
17146
- // Declarative bindings for the gesture dispatcher.
17147
- // Binding priority (first match wins):
17148
- // 0. Press → classify + select (runs before click/drag classification).
17149
- // 1. Handle drags (resize) — guard on AffordanceHit kind.
17150
- // 2. Rotate-handle drag — single-selection rotation.
17151
- // 3. Body drag (selected OR unselected) — move the selection.
17152
- // 4. Empty drag — marquee area-select.
17153
- // 5. Click on empty (no modifiers) → clear selection.
17522
+ // Declarative bindings for the gesture dispatcher. The select tool
17523
+ // only chooses: press → classify + select, release → collapse a
17524
+ // deferred multi-click, empty drag → marquee, click on empty → clear.
17525
+ // Moving, cloning, resizing and rotating the selection are ambient
17526
+ // bindings owned by the selection (`selectionContributions.ts`), so
17527
+ // they run under any tool that leaves the gesture unclaimed.
17154
17528
  bindings: [
17155
17529
  // Every press that landed on the scene rather than on chrome. This
17156
17530
  // is the former `initial.pointerDown['*']` route: it classifies the
@@ -17190,55 +17564,13 @@ function useSelectTool(adapter, options) {
17190
17564
  },
17191
17565
  actionId: "select.collapseDeferred"
17192
17566
  },
17193
- { spec: { kind: "drag", target: { kindOf: isResizeHandle } }, actionId: "resize" },
17194
- { spec: { kind: "drag", target: { kindOf: isRotateHandle } }, actionId: "rotate" },
17195
- // Alt-drag on a body → clone (Illustrator convention).
17196
- // Listed BEFORE bare move so the strict-modifier dispatcher
17197
- // picks this when Alt is held; bare move matches no-mod drags.
17198
- // Both selected and unselected bodies are valid clone targets — the
17199
- // pointerDown classifier calls `selection.applyClick(top, mods)` on
17200
- // an unselected hit before the drag fires, so cloneAction's
17201
- // `start` sees the hit node in `selection.get()` either way.
17202
- { spec: { kind: "drag", target: "selected-body", mods: { alt: true } }, actionId: "clone" },
17203
- { spec: { kind: "drag", target: "unselected-body", mods: { alt: true } }, actionId: "clone" },
17567
+ // Empty drag → marquee. `onEmptyCanvas` rather than a bare `'empty'`
17568
+ // target: the ambient bindings that own the handles rank below this
17569
+ // active one, so `'empty'` took drags that start on them.
17204
17570
  {
17205
- // Body-drag → move, BUT defer when the pointerdown hit a path
17206
- // anchor / control affordance. Anchors lie on the curve so the
17207
- // body classifier still reports 'selected-body'; without this
17208
- // opt-out, move's active-scope binding beats editAnchors's
17209
- // ambient-scope binding on every anchor drag.
17210
- //
17211
- // `isAnchorOrControl` is the SAME predicate `editAnchorsAction`
17212
- // matches on. That is load-bearing: the two must agree on what
17213
- // counts as an anchor hit or move steals the drag — which is
17214
- // exactly the bug this opt-out exists to prevent. (It used to be
17215
- // a hand-rolled regex here that lacked the trailing-index
17216
- // requirement, so the two sides could disagree.)
17217
- spec: {
17218
- kind: "drag",
17219
- target: {
17220
- kindOf: (afford, body) => body === "selected-body" && !isAnchorOrControl(afford)
17221
- }
17222
- },
17223
- actionId: "move",
17224
- ...moveOpts
17225
- },
17226
- // Body-drag on a NOT-yet-selected node → also move. The pointerDown
17227
- // classifier selects the hit node before the drag fires (same
17228
- // contract clone relies on), so by the time moveAction.start() runs
17229
- // the node is in `selection.get()`. Without this active binding an
17230
- // unselected-body drag finds no active match and falls through to
17231
- // ambient scope, where the `rotate` catch-all (`{ kind: 'drag' }`,
17232
- // start()-guarded only on a non-empty selection) hijacks it — the
17233
- // "first drag rotates, later drags move" bug. Unselected nodes never
17234
- // carry anchor affordances (those gate on selection), so the plain
17235
- // string-form target is sufficient here.
17236
- {
17237
- spec: { kind: "drag", target: "unselected-body" },
17238
- actionId: "move",
17239
- ...moveOpts
17571
+ spec: { kind: "drag", target: { kindOf: onEmptyCanvas } },
17572
+ actionId: "areaSelect"
17240
17573
  },
17241
- { spec: { kind: "drag", target: "empty" }, actionId: "areaSelect" },
17242
17574
  // resize/rotate bind only drag (no click), so a click on either handle
17243
17575
  // (body genuinely empty) would hit target:'empty' and wrongly clear selection.
17244
17576
  {
@@ -17252,15 +17584,14 @@ function useSelectTool(adapter, options) {
17252
17584
  ]
17253
17585
  };
17254
17586
  },
17255
- // eslint-disable-next-line react-hooks/exhaustive-deps
17256
- [options.debug, options.reparentOnDrop, options.move, pickAction, collapseDeferredAction]
17587
+ [pickAction, collapseDeferredAction]
17257
17588
  );
17258
17589
  }
17259
17590
  function useHandTool(opts = {}) {
17260
17591
  const optsRef = useRef(opts);
17261
17592
  optsRef.current = opts;
17262
17593
  return useMemo(
17263
- () => Object.assign(defineViewportTool({
17594
+ () => Object.assign(defineTool({
17264
17595
  id: "hand",
17265
17596
  capabilities: ["navigation"],
17266
17597
  hotkey: "space",
@@ -17290,6 +17621,20 @@ function useHandTool(opts = {}) {
17290
17621
  []
17291
17622
  );
17292
17623
  }
17624
+
17625
+ // src/tools/builtin/hand/dragPanContribution.ts
17626
+ var DRAG_PAN_ID = "viewport.dragPanBinding";
17627
+ function dragPanContribution(params = {}) {
17628
+ return {
17629
+ id: DRAG_PAN_ID,
17630
+ eligibility: { always: true },
17631
+ bindings: [{
17632
+ spec: { kind: "drag" },
17633
+ actionId: "viewport.dragPan",
17634
+ ...Object.keys(params).length > 0 ? { opts: { params: { ...params } } } : {}
17635
+ }]
17636
+ };
17637
+ }
17293
17638
  var PRESENTATION = {
17294
17639
  label: "Text",
17295
17640
  icon: createElement(TextIcon),
@@ -17336,14 +17681,19 @@ useTextTool.options = {
17336
17681
  underline: flag("Underline", "underline"),
17337
17682
  strikethrough: flag("Strikethrough", "strikethrough"),
17338
17683
  overline: flag("Overline", "overline"),
17339
- // Its own kind rather than an enum: the two values are exclusive *and*
17340
- // absent is a third state an enum cannot name, so clicking the lit
17341
- // segment has to clear it. The host draws it and owns that rule.
17684
+ // Clearable: the two values are exclusive *and* absent is a third state,
17685
+ // so clicking the lit segment turns the script off.
17342
17686
  script: {
17343
- kind: "script",
17687
+ kind: "enum",
17344
17688
  name: "Script",
17345
17689
  description: "Raise or lower the range off the baseline, at a smaller size.",
17346
- default: void 0
17690
+ control: "toggle",
17691
+ clearable: true,
17692
+ default: void 0,
17693
+ options: [
17694
+ { value: "super", label: "Superscript", icon: "superscript" },
17695
+ { value: "sub", label: "Subscript", icon: "subscript" }
17696
+ ]
17347
17697
  },
17348
17698
  textTransform: {
17349
17699
  kind: "enum",
@@ -18009,52 +18359,13 @@ function useLineTool() {
18009
18359
  []
18010
18360
  );
18011
18361
  }
18012
- var LASSO_VERTICES = scratchKey("lassoSelect.vertices");
18013
- var MIN_AREA = 4;
18014
- function selectFromLasso(opts) {
18015
- const mode = opts?.mode ?? "intersect";
18016
- return {
18017
- defaultTransient: true,
18018
- onEnd(ctx) {
18019
- const adapter = ctx.adapter;
18020
- if (!adapter.getSelection || !adapter.hitTestLasso) return null;
18021
- const vertices = getScratch(ctx.scratch, LASSO_VERTICES) ?? [];
18022
- const startPose = ctx.origin.get("gesture");
18023
- const shiftHeld = startPose ? startPose.shiftHeld === true : false;
18024
- const from = adapter.getSelection();
18025
- const tiny = vertices.length < 3 || polygonAabbArea(vertices) < MIN_AREA;
18026
- if (tiny) {
18027
- const to2 = shiftHeld ? from : [];
18028
- const op2 = createSetSelectionOp({ from, to: to2 });
18029
- return [op2];
18030
- }
18031
- const hits = adapter.hitTestLasso(vertices, mode);
18032
- const to = shiftHeld ? mergeUnique(from, hits) : hits;
18033
- const op = createSetSelectionOp({ from, to });
18034
- return [op];
18035
- }
18036
- };
18037
- }
18038
- function polygonAabbArea(vertices) {
18039
- if (vertices.length === 0) return 0;
18040
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
18041
- for (const v of vertices) {
18042
- if (v.x < minX) minX = v.x;
18043
- if (v.y < minY) minY = v.y;
18044
- if (v.x > maxX) maxX = v.x;
18045
- if (v.y > maxY) maxY = v.y;
18046
- }
18047
- return (maxX - minX) * (maxY - minY);
18048
- }
18049
- function mergeUnique(a, b) {
18050
- const out = [...a];
18051
- for (const id of b) if (!out.includes(id)) out.push(id);
18052
- return out;
18053
- }
18054
-
18055
- // src/tools/builtin/lasso/useLassoTool.ts
18056
18362
  function useLassoTool(_adapter, options = {}) {
18057
- void (options.behaviors ?? [selectFromLasso({ mode: options.mode ?? "intersect" })]);
18363
+ const paramsRef = useRef({ mode: "intersect" });
18364
+ paramsRef.current = {
18365
+ mode: options.mode ?? "intersect",
18366
+ ...options.minVertexSpacing !== void 0 ? { minVertexSpacing: options.minVertexSpacing } : {}
18367
+ };
18368
+ const { behaviors } = options;
18058
18369
  return useMemo(() => {
18059
18370
  return defineTool({
18060
18371
  id: "lasso",
@@ -18071,10 +18382,19 @@ function useLassoTool(_adapter, options = {}) {
18071
18382
  // dispatcher invokes `lassoSelectAction`, which owns vertex buffering,
18072
18383
  // overlay state, and the commit-on-end selection write.
18073
18384
  bindings: [
18074
- { spec: { kind: "drag", mods: { shift: "optional" } }, actionId: "lassoSelect" }
18385
+ {
18386
+ spec: { kind: "drag", mods: { shift: "optional" } },
18387
+ actionId: "lassoSelect",
18388
+ // Thunked so a mode or spacing change reaches the next gesture
18389
+ // without rebuilding the tool.
18390
+ opts: {
18391
+ params: () => paramsRef.current,
18392
+ ...behaviors ? { behaviors } : {}
18393
+ }
18394
+ }
18075
18395
  ]
18076
18396
  });
18077
- }, [options.keybinding]);
18397
+ }, [options.keybinding, behaviors]);
18078
18398
  }
18079
18399
  var MIN_SIDES = 3;
18080
18400
  var MAX_SIDES = 32;
@@ -18324,7 +18644,7 @@ function isCustomEntry(v) {
18324
18644
 
18325
18645
  // src/canvas/layerOrder.ts
18326
18646
  var STANDARD_SLOT_SET = new Set(STANDARD_SLOTS);
18327
- function composeOrderedLayers(layersMap, standardLayers, sceneLayers) {
18647
+ function composeOrderedLayers(layersMap, standardLayers, sceneLayers, background) {
18328
18648
  const afterByParent = /* @__PURE__ */ new Map();
18329
18649
  const beforeByParent = /* @__PURE__ */ new Map();
18330
18650
  const tail = [];
@@ -18346,7 +18666,7 @@ function composeOrderedLayers(layersMap, standardLayers, sceneLayers) {
18346
18666
  }
18347
18667
  }
18348
18668
  const emitted = /* @__PURE__ */ new Set();
18349
- const out = [];
18669
+ const out = background ? [background] : [];
18350
18670
  function emitChild(child) {
18351
18671
  if (emitted.has(child.key)) {
18352
18672
  console.warn(`composeOrderedLayers: cycle detected involving custom layer "${child.key}"`);
@@ -18471,7 +18791,7 @@ function findMountedCanvas(root, hint) {
18471
18791
  function* toolsInPriorityOrder(tools) {
18472
18792
  const seen = /* @__PURE__ */ new Set();
18473
18793
  const hotkey = tools.hotkeyEngaged ? tools.registry[tools.hotkeyEngaged] : void 0;
18474
- const active = tools.registry[tools.active];
18794
+ const active = tools.active === null ? void 0 : tools.registry[tools.active];
18475
18795
  for (const t of [hotkey, active]) {
18476
18796
  if (t && !seen.has(t)) {
18477
18797
  seen.add(t);
@@ -18532,22 +18852,17 @@ var EMPTY_CHROME_STATE = {
18532
18852
  };
18533
18853
  function buildChromeState(args) {
18534
18854
  const { selection, multiActive: multiActive2, effectiveBoundsOf, modifiers, canRotate } = args;
18535
- const cached = { value: null, computed: false };
18536
18855
  return {
18537
18856
  selection,
18538
18857
  multiActive: multiActive2,
18539
18858
  boundsOf: effectiveBoundsOf,
18540
18859
  modifiers,
18541
18860
  canRotate: canRotate ?? (() => true),
18861
+ // Not cached: the state is memoized across renders, and poses move under
18862
+ // it (a committed move, a live preview) without any of its inputs changing.
18542
18863
  get unionBounds() {
18543
- if (cached.computed) return cached.value;
18544
- cached.computed = true;
18545
- if (!multiActive2) {
18546
- cached.value = null;
18547
- return null;
18548
- }
18549
- cached.value = unionAABB(selection.map((id) => effectiveBoundsOf(id)));
18550
- return cached.value;
18864
+ if (!multiActive2) return null;
18865
+ return unionAABB(selection.map((id) => effectiveBoundsOf(id)));
18551
18866
  }
18552
18867
  };
18553
18868
  }
@@ -19413,7 +19728,7 @@ function pathFor(d) {
19413
19728
  }
19414
19729
  return p;
19415
19730
  }
19416
- function commandFor2(op) {
19731
+ function commandFor(op) {
19417
19732
  return {
19418
19733
  kind: "path",
19419
19734
  path: pathFor(op.d),
@@ -19453,7 +19768,7 @@ function createPaintedCursorLayer(state) {
19453
19768
  at
19454
19769
  });
19455
19770
  const transform = new Float32Array([a, b, 0, c, d, 0, e, f, 1]);
19456
- const children = ops.map(commandFor2);
19771
+ const children = ops.map(commandFor);
19457
19772
  return [{ kind: "group", transform, children }];
19458
19773
  }
19459
19774
  };
@@ -19486,6 +19801,9 @@ function wrapNodeOutput(cmds, pose, alpha) {
19486
19801
  }
19487
19802
  return rotated;
19488
19803
  }
19804
+
19805
+ // src/canvas/hud.module.css
19806
+ var hud_default = {};
19489
19807
  function insetAlong(start, extent, size, align, by) {
19490
19808
  if (align === "start") return start + by;
19491
19809
  if (align === "end") return start + extent - size - by;
@@ -19624,20 +19942,8 @@ function CursorCoordsHud({ canvasRef, anchorRef, viewRef, offset }) {
19624
19942
  "div",
19625
19943
  {
19626
19944
  ref,
19627
- style: {
19628
- position: "fixed",
19629
- ...anchorStyle,
19630
- zIndex: 1e4,
19631
- background: "rgba(0,0,0,0.7)",
19632
- color: "#e8e8e8",
19633
- font: "11px ui-monospace, SFMono-Regular, Menlo, monospace",
19634
- padding: "6px 8px",
19635
- borderRadius: 4,
19636
- pointerEvents: "none",
19637
- lineHeight: 1.4,
19638
- whiteSpace: "pre",
19639
- textAlign: "right"
19640
- },
19945
+ className: `${hud_default.hud} ${hud_default.pre}`,
19946
+ style: anchorStyle,
19641
19947
  children: [
19642
19948
  `fps ${fps.toString().padStart(3)}`,
19643
19949
  "\n",
@@ -19648,9 +19954,6 @@ function CursorCoordsHud({ canvasRef, anchorRef, viewRef, offset }) {
19648
19954
  }
19649
19955
  );
19650
19956
  }
19651
-
19652
- // src/canvas/PickHud.module.css
19653
- var PickHud_default = {};
19654
19957
  function PickHud({ canvasRef, anchorRef, viewRef, pickEvery, pickBest, offset }) {
19655
19958
  const [state, setState] = useState({
19656
19959
  ids: [],
@@ -19702,25 +20005,22 @@ function PickHud({ canvasRef, anchorRef, viewRef, pickEvery, pickBest, offset })
19702
20005
  };
19703
20006
  }, [canvasRef, viewRef, pickEvery, pickBest]);
19704
20007
  if (!style) return null;
19705
- return /* @__PURE__ */ jsxs("div", { ref, className: PickHud_default.hud, style, children: [
19706
- /* @__PURE__ */ jsxs("div", { className: PickHud_default.header, children: [
20008
+ return /* @__PURE__ */ jsxs("div", { ref, className: `${hud_default.hud} ${hud_default.sized}`, style, children: [
20009
+ /* @__PURE__ */ jsxs("div", { className: `${hud_default.header} ${hud_default.muted}`, children: [
19707
20010
  "pickEvery (",
19708
20011
  state.ids.length,
19709
20012
  ")"
19710
20013
  ] }),
19711
- state.ids.length === 0 ? /* @__PURE__ */ jsx("div", { className: PickHud_default.empty, children: state.inCanvas ? "\u2014" : "off-canvas" }) : /* @__PURE__ */ jsx("ul", { className: PickHud_default.list, children: state.ids.slice().reverse().map((id) => /* @__PURE__ */ jsx(
20014
+ state.ids.length === 0 ? /* @__PURE__ */ jsx("div", { className: hud_default.absent, children: state.inCanvas ? "\u2014" : "off-canvas" }) : /* @__PURE__ */ jsx("ul", { className: hud_default.list, children: state.ids.slice().reverse().map((id) => /* @__PURE__ */ jsx(
19712
20015
  "li",
19713
20016
  {
19714
- className: id === state.best ? `${PickHud_default.item} ${PickHud_default.itemBest}` : PickHud_default.item,
20017
+ className: id === state.best ? `${hud_default.item} ${hud_default.itemBest}` : hud_default.item,
19715
20018
  children: id
19716
20019
  },
19717
20020
  id
19718
20021
  )) })
19719
20022
  ] });
19720
20023
  }
19721
-
19722
- // src/canvas/ModalityHud.module.css
19723
- var ModalityHud_default = {};
19724
20024
  function ModalityHud({ canvasRef, anchorRef, modeId, offset }) {
19725
20025
  const toolCtx = useOptionalActiveToolContext();
19726
20026
  const { ref, style } = useHostAnchor(
@@ -19734,18 +20034,18 @@ function ModalityHud({ canvasRef, anchorRef, modeId, offset }) {
19734
20034
  const active = toolCtx?.active ?? null;
19735
20035
  const hotkeys = toolCtx?.hotkeyStack ?? [];
19736
20036
  const hotkeyTop = hotkeys.length > 0 ? hotkeys[hotkeys.length - 1] : null;
19737
- return /* @__PURE__ */ jsxs("div", { ref, className: ModalityHud_default.hud, style, children: [
19738
- /* @__PURE__ */ jsxs("div", { className: ModalityHud_default.row, children: [
19739
- /* @__PURE__ */ jsx("span", { className: ModalityHud_default.label, children: "mode" }),
19740
- /* @__PURE__ */ jsx("span", { className: modeId ? ModalityHud_default.value : ModalityHud_default.valueMuted, children: modeId ?? "\u2014" })
20037
+ return /* @__PURE__ */ jsxs("div", { ref, className: `${hud_default.hud} ${hud_default.sized}`, style, children: [
20038
+ /* @__PURE__ */ jsxs("div", { className: hud_default.row, children: [
20039
+ /* @__PURE__ */ jsx("span", { className: hud_default.muted, children: "mode" }),
20040
+ /* @__PURE__ */ jsx("span", { className: modeId ? void 0 : hud_default.absent, children: modeId ?? "\u2014" })
19741
20041
  ] }),
19742
- /* @__PURE__ */ jsxs("div", { className: ModalityHud_default.row, children: [
19743
- /* @__PURE__ */ jsx("span", { className: ModalityHud_default.label, children: "active" }),
19744
- /* @__PURE__ */ jsx("span", { className: active ? ModalityHud_default.value : ModalityHud_default.valueMuted, children: active ?? "\u2014" })
20042
+ /* @__PURE__ */ jsxs("div", { className: hud_default.row, children: [
20043
+ /* @__PURE__ */ jsx("span", { className: hud_default.muted, children: "active" }),
20044
+ /* @__PURE__ */ jsx("span", { className: active ? void 0 : hud_default.absent, children: active ?? "\u2014" })
19745
20045
  ] }),
19746
- /* @__PURE__ */ jsxs("div", { className: ModalityHud_default.row, children: [
19747
- /* @__PURE__ */ jsx("span", { className: ModalityHud_default.label, children: "hotkey" }),
19748
- hotkeys.length <= 1 ? /* @__PURE__ */ jsx("span", { className: hotkeyTop ? ModalityHud_default.value : ModalityHud_default.valueMuted, children: hotkeyTop ?? "\u2014" }) : /* @__PURE__ */ jsx("span", { className: ModalityHud_default.stack, children: hotkeys.slice().reverse().map((id, i) => /* @__PURE__ */ jsx("span", { className: i === 0 ? ModalityHud_default.value : ModalityHud_default.valueMuted, children: id }, `${id}-${i}`)) })
20046
+ /* @__PURE__ */ jsxs("div", { className: hud_default.row, children: [
20047
+ /* @__PURE__ */ jsx("span", { className: hud_default.muted, children: "hotkey" }),
20048
+ hotkeys.length <= 1 ? /* @__PURE__ */ jsx("span", { className: hotkeyTop ? void 0 : hud_default.absent, children: hotkeyTop ?? "\u2014" }) : /* @__PURE__ */ jsx("span", { className: hud_default.stack, children: hotkeys.slice().reverse().map((id, i) => /* @__PURE__ */ jsx("span", { className: i === 0 ? void 0 : hud_default.absent, children: id }, `${id}-${i}`)) })
19749
20049
  ] })
19750
20050
  ] });
19751
20051
  }
@@ -19837,7 +20137,7 @@ function buildSceneLayers(cfg, adapter, debugSink, boundsOfFn, hideIds) {
19837
20137
  }
19838
20138
  function resolveToolsCursor(tools, ctxBase) {
19839
20139
  const id = tools.hotkeyEngaged ?? tools.active;
19840
- const tool = tools.registry[id];
20140
+ const tool = id === null ? void 0 : tools.registry[id];
19841
20141
  if (!tool?.cursor) return void 0;
19842
20142
  if (typeof tool.cursor !== "function") return resolveCursorTier(tool.cursor);
19843
20143
  if (!ctxBase) return void 0;
@@ -19861,6 +20161,7 @@ function CanvasInner(props, ref) {
19861
20161
  flattenTolerance,
19862
20162
  contentVersion,
19863
20163
  syncPaint = false,
20164
+ redrawOn,
19864
20165
  adapter: adapterProp,
19865
20166
  layers: layersMap,
19866
20167
  selection,
@@ -20260,8 +20561,12 @@ function CanvasInner(props, ref) {
20260
20561
  ...cfg.poseById ? { getPose: cfg.poseById, poseDescriptor: cfg.poseDescriptor ?? geometry } : {}
20261
20562
  });
20262
20563
  }
20263
- const effectiveLayersMap = backgroundLayer ? { ...layersMap, backgroundFill: { layer: backgroundLayer, before: "scene" } } : layersMap;
20264
- const out = composeOrderedLayers(effectiveLayersMap, standardLayers, sceneLayers);
20564
+ const out = composeOrderedLayers(
20565
+ layersMap,
20566
+ standardLayers,
20567
+ sceneLayers,
20568
+ backgroundLayer ?? void 0
20569
+ );
20265
20570
  if (decorationLayer) out.push(decorationLayer);
20266
20571
  if (tools) {
20267
20572
  placeToolOverlays(out, standardLayers.selectionOverlay, tools.getActiveOverlays);
@@ -20325,6 +20630,26 @@ function CanvasInner(props, ref) {
20325
20630
  viewRegistryVersion,
20326
20631
  paintedCursorLayer
20327
20632
  ]);
20633
+ useEffect(() => {
20634
+ const redraw = () => {
20635
+ requestRedraw();
20636
+ };
20637
+ const offs = [];
20638
+ for (const layer of layersWithDebug) if (layer.subscribe) offs.push(layer.subscribe(redraw));
20639
+ return () => {
20640
+ for (const off of offs) off();
20641
+ };
20642
+ }, [layersWithDebug, requestRedraw]);
20643
+ const redrawSources = useSameElements(redrawOn);
20644
+ useEffect(() => {
20645
+ const redraw = () => {
20646
+ requestRedraw();
20647
+ };
20648
+ const offs = (redrawSources ?? []).map((src) => src.subscribe(redraw));
20649
+ return () => {
20650
+ for (const off of offs) off();
20651
+ };
20652
+ }, [redrawSources, requestRedraw]);
20328
20653
  const shaderIdKey = shaders?.map((h) => h.id).join("|") ?? "";
20329
20654
  const paintInputsRef = useRef({
20330
20655
  layers: layersWithDebug,
@@ -20573,6 +20898,14 @@ function CanvasInner(props, ref) {
20573
20898
  ] });
20574
20899
  }
20575
20900
  var Canvas = forwardRef(CanvasInner);
20901
+ function useSameElements(list2) {
20902
+ const ref = useRef(list2);
20903
+ const prev = ref.current;
20904
+ if (prev !== list2 && !(prev && list2 && prev.length === list2.length && prev.every((x, i) => x === list2[i]))) {
20905
+ ref.current = list2;
20906
+ }
20907
+ return ref.current;
20908
+ }
20576
20909
 
20577
20910
  // src/canvas/colorOverrides.ts
20578
20911
  var clock = () => typeof performance !== "undefined" ? performance.now() : Date.now();
@@ -20602,73 +20935,127 @@ function wireSceneSlotToScene(slot, scene, alphaFor, colorOverrides) {
20602
20935
  alphaFor: composeAlphaFor(scene, alphaFor)
20603
20936
  };
20604
20937
  }
20605
- function flattenVerts2(poly2) {
20606
- const flat = new Array(poly2.length * 2);
20607
- for (let i = 0; i < poly2.length; i++) {
20608
- flat[i * 2] = poly2[i].x;
20609
- flat[i * 2 + 1] = poly2[i].y;
20938
+ function aabbOfPose(pose, descriptor = AUTO_POSE_DESCRIPTOR) {
20939
+ return descriptor.getBounds(pose);
20940
+ }
20941
+ var DEFAULT_PATH_STROKE_SLOP = 4;
20942
+ function poseContains(pose, wx, wy, tolerance, descriptor = AUTO_POSE_DESCRIPTOR) {
20943
+ if (isPathLike(pose)) {
20944
+ if (pointInPath(pose, wx, wy)) return true;
20945
+ return strokeHitTest(pose, wx, wy, tolerance ?? DEFAULT_PATH_STROKE_SLOP);
20610
20946
  }
20611
- return flat;
20947
+ const b = aabbOfPose(pose, descriptor);
20948
+ const t = tolerance ?? 0;
20949
+ return wx >= b.x - t && wx <= b.x + b.width + t && wy >= b.y - t && wy <= b.y + b.height + t;
20612
20950
  }
20613
- function polygonContainsRectCenter(poly2, rect) {
20614
- return pointInPolygon(flattenVerts2(poly2), rect.x + rect.width / 2, rect.y + rect.height / 2);
20951
+ function poseContainsRotated(pose, wx, wy, tolerance, descriptor = AUTO_POSE_DESCRIPTOR) {
20952
+ const r = descriptor === AUTO_POSE_DESCRIPTOR ? poseRotationOf(pose) : rotationAboutCenter(pose, descriptor);
20953
+ if (r) {
20954
+ const [lx, ly] = applyToPoint(rotateAboutPoint(r.cx, r.cy, -r.rotation), wx, wy);
20955
+ return poseContains(pose, lx, ly, tolerance, descriptor);
20956
+ }
20957
+ return poseContains(pose, wx, wy, tolerance, descriptor);
20615
20958
  }
20616
- function polygonContainsRect(poly2, rect) {
20617
- if (poly2.length < 3) return false;
20618
- const flat = flattenVerts2(poly2);
20619
- if (!pointInPolygon(flat, rect.x, rect.y)) return false;
20620
- if (!pointInPolygon(flat, rect.x + rect.width, rect.y)) return false;
20621
- if (!pointInPolygon(flat, rect.x + rect.width, rect.y + rect.height)) return false;
20622
- if (!pointInPolygon(flat, rect.x, rect.y + rect.height)) return false;
20623
- return !anyPolyEdgeCrossesRect(poly2, rect);
20959
+ function rotationAboutCenter(pose, descriptor) {
20960
+ const rotation = descriptor.getRotation?.(pose) ?? 0;
20961
+ if (!rotation) return null;
20962
+ const b = descriptor.getBounds(pose);
20963
+ return { cx: b.x + b.width / 2, cy: b.y + b.height / 2, rotation };
20624
20964
  }
20625
- function polygonIntersectsRect(poly2, rect) {
20626
- if (poly2.length < 3) return false;
20627
- const ab = polygonAabb(poly2);
20628
- if (ab.x + ab.width < rect.x || ab.x > rect.x + rect.width) return false;
20629
- if (ab.y + ab.height < rect.y || ab.y > rect.y + rect.height) return false;
20630
- for (const v of poly2) {
20631
- if (v.x >= rect.x && v.x <= rect.x + rect.width && v.y >= rect.y && v.y <= rect.y + rect.height) return true;
20965
+ function regionPickOptions(view, alphaOf, poseComposition) {
20966
+ return {
20967
+ ...poseComposition ? { poseComposition } : {},
20968
+ ...alphaOf ? { alphaOf } : {},
20969
+ ...view?.layerIsPainted ? { layerIsPainted: (layer) => view.layerIsPainted(layer) } : {}
20970
+ };
20971
+ }
20972
+ function hitTestArea(scene, bounds, opts, descriptor = AUTO_POSE_DESCRIPTOR, query = {}) {
20973
+ const { x, y, width: w, height: h } = bounds;
20974
+ const area = Array.from(rectToContour(x, y, w, h));
20975
+ return hitTestAreaPolygon(scene, area, { x, y, width: w, height: h }, true, opts, descriptor, query);
20976
+ }
20977
+ function hitTestAreaPolygon(scene, area, areaBounds, areaIsRect = false, opts, descriptor = AUTO_POSE_DESCRIPTOR, query = {}) {
20978
+ const ab = areaBounds ?? boundsOf(area);
20979
+ if (!ab) return [];
20980
+ const areaVerts = [];
20981
+ for (let i = 0; i + 1 < area.length; i += 2) areaVerts.push({ x: area[i], y: area[i + 1] });
20982
+ return walkArea(scene, ab, opts, descriptor, query, (node, pose, b, silhouette) => {
20983
+ if (areaIsRect && b.x >= ab.x && b.y >= ab.y && b.x + b.width <= ab.x + ab.width && b.y + b.height <= ab.y + ab.height) {
20984
+ return true;
20985
+ }
20986
+ if (silhouette) {
20987
+ return polygonIntersectsPath(areaVerts, pose);
20988
+ }
20989
+ const outline = outlineOf(node, pose, b);
20990
+ if (outline.kind === "rect" && areaIsRect) return true;
20991
+ return polygonIntersectsPath(areaVerts, outline);
20992
+ });
20993
+ }
20994
+ function hitTestLassoPolygon(scene, polygon, mode, opts, descriptor = AUTO_POSE_DESCRIPTOR, query = {}) {
20995
+ if (polygon.length < 3) return [];
20996
+ const area = [];
20997
+ for (const p of polygon) area.push(p.x, p.y);
20998
+ if (mode === "intersect") {
20999
+ return hitTestAreaPolygon(scene, area, void 0, false, opts, descriptor, query);
20632
21000
  }
20633
- const flat = flattenVerts2(poly2);
20634
- if (pointInPolygon(flat, rect.x, rect.y)) return true;
20635
- if (pointInPolygon(flat, rect.x + rect.width, rect.y)) return true;
20636
- if (pointInPolygon(flat, rect.x + rect.width, rect.y + rect.height)) return true;
20637
- if (pointInPolygon(flat, rect.x, rect.y + rect.height)) return true;
20638
- return anyPolyEdgeCrossesRect(poly2, rect);
21001
+ const ab = boundsOf(area);
21002
+ if (!ab) return [];
21003
+ return walkArea(scene, ab, opts, descriptor, query, (node, pose, b, silhouette) => {
21004
+ if (mode === "centers") {
21005
+ return pointInPolygon(area, b.x + b.width / 2, b.y + b.height / 2);
21006
+ }
21007
+ if (b.x < ab.x || b.y < ab.y || b.x + b.width > ab.x + ab.width || b.y + b.height > ab.y + ab.height) {
21008
+ return false;
21009
+ }
21010
+ const outline = silhouette ? pose : outlineOf(node, pose, b);
21011
+ return polygonContainsPath(polygon, outline);
21012
+ });
20639
21013
  }
20640
- function polygonAabb(poly2) {
20641
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
20642
- for (const v of poly2) {
20643
- if (v.x < minX) minX = v.x;
20644
- if (v.y < minY) minY = v.y;
20645
- if (v.x > maxX) maxX = v.x;
20646
- if (v.y > maxY) maxY = v.y;
21014
+ function walkArea(scene, ab, opts, descriptor, query, hits) {
21015
+ return pickWalk(scenePickSource(scene, opts), {
21016
+ includeContainers: query.includeContainers === true,
21017
+ clipAdmits: (clip, node, pose) => {
21018
+ const g = poseDescriptorForNode(descriptor, node);
21019
+ return pathIntersectsRect(clip, visualBoundsViaDescriptor(pose, g)) && pathIntersectsRect(clip, ab);
21020
+ },
21021
+ hits: (node, pose) => {
21022
+ const g = poseDescriptorForNode(descriptor, node);
21023
+ const silhouette = pose !== null && typeof pose === "object" && pose.kind === "polygon";
21024
+ const b = silhouette ? nodeMemo(node, "aabb", pose, () => aabbOfPose(pose, g)) : visualBoundsViaDescriptor(pose, g);
21025
+ if (!Number.isFinite(b.x) || !Number.isFinite(b.y) || !Number.isFinite(b.width) || !Number.isFinite(b.height)) {
21026
+ return false;
21027
+ }
21028
+ if (b.x >= ab.x + ab.width || b.x + b.width <= ab.x || b.y >= ab.y + ab.height || b.y + b.height <= ab.y) {
21029
+ return false;
21030
+ }
21031
+ return hits(node, pose, b, silhouette);
21032
+ }
21033
+ });
21034
+ }
21035
+ function outlineOf(node, pose, b) {
21036
+ const drawn = findShapeSilhouette(node, pose);
21037
+ if (drawn) return drawn;
21038
+ const r = poseRotationOf(pose);
21039
+ if (r) {
21040
+ const p = pose;
21041
+ return rotatePathAround(rectPath(p.x, p.y, p.width, p.height), r.cx, r.cy, r.rotation);
20647
21042
  }
20648
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
21043
+ return rectPath(b.x, b.y, b.width, b.height);
20649
21044
  }
20650
- function anyPolyEdgeCrossesRect(poly2, rect) {
20651
- const r0x = rect.x, r0y = rect.y;
20652
- const r1x = rect.x + rect.width, r1y = rect.y;
20653
- const r2x = rect.x + rect.width, r2y = rect.y + rect.height;
20654
- const r3x = rect.x, r3y = rect.y + rect.height;
20655
- for (let i = 0, j = poly2.length - 1; i < poly2.length; j = i++) {
20656
- const a = poly2[j], b = poly2[i];
20657
- if (segmentsCross(a.x, a.y, b.x, b.y, r0x, r0y, r1x, r1y)) return true;
20658
- if (segmentsCross(a.x, a.y, b.x, b.y, r1x, r1y, r2x, r2y)) return true;
20659
- if (segmentsCross(a.x, a.y, b.x, b.y, r2x, r2y, r3x, r3y)) return true;
20660
- if (segmentsCross(a.x, a.y, b.x, b.y, r3x, r3y, r0x, r0y)) return true;
21045
+ function boundsOf(coords) {
21046
+ if (coords.length < 2) return null;
21047
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
21048
+ for (let i = 0; i + 1 < coords.length; i += 2) {
21049
+ const x = coords[i], y = coords[i + 1];
21050
+ if (x < minX) minX = x;
21051
+ if (y < minY) minY = y;
21052
+ if (x > maxX) maxX = x;
21053
+ if (y > maxY) maxY = y;
20661
21054
  }
20662
- return false;
21055
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
20663
21056
  }
20664
21057
 
20665
21058
  // src/canvas/sceneAdapter.ts
20666
- function walkClipAware(scene, poseBounds, nodeTest, poseComposition) {
20667
- return pickWalk(scenePickSource(scene, poseComposition ? { poseComposition } : {}), {
20668
- hits: (node, pose) => nodeTest(node, pose),
20669
- clipAdmits: (clip, _node, pose) => pathIntersectsRect(clip, poseBounds(pose))
20670
- });
20671
- }
20672
21059
  var adapterFallbackIdCounter = 0;
20673
21060
  function adapterFallbackId() {
20674
21061
  return `n${(adapterFallbackIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -20704,6 +21091,7 @@ function sceneToAdapter(scene, options = {}) {
20704
21091
  );
20705
21092
  const composition = options.poseComposition;
20706
21093
  const composes = composition !== void 0 && composition.closure !== "identity";
21094
+ const pickOpts = composition ? { poseComposition: composition } : {};
20707
21095
  if (composes && options.cascadeContainerPose) {
20708
21096
  throw new Error(
20709
21097
  "sceneToAdapter: poseComposition and cascadeContainerPose contradict each other. Under a composing strategy a child's pose is already relative to its parent, so the cascade would move every descendant twice. Drop cascadeContainerPose."
@@ -20840,23 +21228,29 @@ function sceneToAdapter(scene, options = {}) {
20840
21228
  // AreaSelectAdapter surface — included unconditionally so plain
20841
21229
  // `useSelectTool(sceneToAdapter(scene, { selection }))` Just Works for the
20842
21230
  // marquee gesture. `applyOps` (above) dispatches transiently when called
20843
- // without a label, matching the AreaSelectAdapter contract; `hitTestArea`
20844
- // does an AABB-vs-AABB scan over `scene.renderOrder()`, reading bounds
20845
- // through the pose descriptor.
21231
+ // without a label, matching the AreaSelectAdapter contract.
20846
21232
  getSelection,
20847
21233
  setSelection,
21234
+ // The live marquee's and lasso's own hit-tests. Containers come back:
21235
+ // this adapter's consumer has nothing to fold children into them.
20848
21236
  hitTestArea(rect) {
20849
- return walkClipAware(scene, poseBounds, (_n, pose) => {
20850
- const b = poseBounds(pose);
20851
- return b.x < rect.x + rect.width && b.x + b.width > rect.x && b.y < rect.y + rect.height && b.y + b.height > rect.y;
20852
- }, composition);
21237
+ return hitTestArea(
21238
+ scene,
21239
+ rect,
21240
+ pickOpts,
21241
+ d,
21242
+ { includeContainers: true }
21243
+ );
20853
21244
  },
20854
21245
  hitTestLasso(polygon, mode) {
20855
- if (polygon.length < 3) return [];
20856
- return walkClipAware(scene, poseBounds, (_n, pose) => {
20857
- const b = poseBounds(pose);
20858
- return mode === "centers" ? polygonContainsRectCenter(polygon, b) : mode === "enclosed" ? polygonContainsRect(polygon, b) : polygonIntersectsRect(polygon, b);
20859
- }, composition);
21246
+ return hitTestLassoPolygon(
21247
+ scene,
21248
+ polygon,
21249
+ mode,
21250
+ pickOpts,
21251
+ d,
21252
+ { includeContainers: true }
21253
+ );
20860
21254
  },
20861
21255
  // commitInsert (gesture-time leaf insert) is opt-in: present only when
20862
21256
  // `options.commitInsert` is. The full insertNode/removeNode mutators
@@ -21757,6 +22151,9 @@ function createScene(options) {
21757
22151
  canUndo: () => raw.canUndo(),
21758
22152
  canRedo: () => raw.canRedo(),
21759
22153
  entries: () => raw.entries(),
22154
+ seal: () => raw.seal(),
22155
+ getVersion: () => raw.getVersion(),
22156
+ subscribe: (listener) => raw.subscribe(listener),
21760
22157
  commit(label) {
21761
22158
  raw.commit(label);
21762
22159
  notify2();
@@ -23310,11 +23707,6 @@ function CanvasView(props) {
23310
23707
  }, [registry2, effectiveView]);
23311
23708
  return null;
23312
23709
  }
23313
- function ActionsProviderIfRoot({ children }) {
23314
- const parent = useActionsRegistry();
23315
- if (parent) return /* @__PURE__ */ jsx(ActionsScope, { children });
23316
- return /* @__PURE__ */ jsx(ActionsProvider, { children });
23317
- }
23318
23710
  function contributionEntries(tools) {
23319
23711
  return [...Object.values(tools.registry), ...tools.ambient];
23320
23712
  }
@@ -23339,10 +23731,11 @@ function useKeyedInstall(items) {
23339
23731
  function useContributionRoles(tools, api) {
23340
23732
  const registry2 = useActionsRegistry();
23341
23733
  const depRegistry = useOptionalDepRegistry();
23734
+ const { registry: toolRegistry, ambient } = tools;
23342
23735
  useEffect(() => {
23343
23736
  if (!registry2) return;
23344
23737
  const unregisters = [];
23345
- for (const entry of contributionEntries(tools)) {
23738
+ for (const entry of contributionEntries({ registry: toolRegistry, ambient })) {
23346
23739
  for (const action of entry.actions ?? []) {
23347
23740
  unregisters.push(registry2.register(action));
23348
23741
  }
@@ -23350,7 +23743,7 @@ function useContributionRoles(tools, api) {
23350
23743
  return () => {
23351
23744
  for (const u of unregisters) u();
23352
23745
  };
23353
- }, [registry2, tools.registry, tools.ambient]);
23746
+ }, [registry2, toolRegistry, ambient]);
23354
23747
  const installs = /* @__PURE__ */ new Map();
23355
23748
  for (const entry of contributionEntries(tools)) {
23356
23749
  const deps = entry.deps;
@@ -23362,6 +23755,7 @@ function useContributionRoles(tools, api) {
23362
23755
  }
23363
23756
  }
23364
23757
  useKeyedInstall(installs);
23758
+ useOffhandAction(contributionEntries(tools));
23365
23759
  }
23366
23760
  function registerDeps(depRegistry, deps) {
23367
23761
  const unregisters = Object.keys(deps).map((name) => depRegistry.register(name, deps[name]));
@@ -23418,35 +23812,6 @@ function PointerPublisher({
23418
23812
  }, [ctx, canvasRef, registry2]);
23419
23813
  return null;
23420
23814
  }
23421
- function aabbOfPose(pose, descriptor = AUTO_POSE_DESCRIPTOR) {
23422
- return descriptor.getBounds(pose);
23423
- }
23424
- var DEFAULT_PATH_STROKE_SLOP = 4;
23425
- function poseContains(pose, wx, wy, tolerance, descriptor = AUTO_POSE_DESCRIPTOR) {
23426
- if (isPathLike(pose)) {
23427
- if (pointInPath(pose, wx, wy)) return true;
23428
- return strokeHitTest(pose, wx, wy, tolerance ?? DEFAULT_PATH_STROKE_SLOP);
23429
- }
23430
- const b = aabbOfPose(pose, descriptor);
23431
- const t = tolerance ?? 0;
23432
- return wx >= b.x - t && wx <= b.x + b.width + t && wy >= b.y - t && wy <= b.y + b.height + t;
23433
- }
23434
- function poseContainsRotated(pose, wx, wy, tolerance, descriptor = AUTO_POSE_DESCRIPTOR) {
23435
- const r = descriptor === AUTO_POSE_DESCRIPTOR ? poseRotationOf(pose) : rotationAboutCenter(pose, descriptor);
23436
- if (r) {
23437
- const [lx, ly] = applyToPoint(rotateAboutPoint(r.cx, r.cy, -r.rotation), wx, wy);
23438
- return poseContains(pose, lx, ly, tolerance, descriptor);
23439
- }
23440
- return poseContains(pose, wx, wy, tolerance, descriptor);
23441
- }
23442
- function rotationAboutCenter(pose, descriptor) {
23443
- const rotation = descriptor.getRotation?.(pose) ?? 0;
23444
- if (!rotation) return null;
23445
- const b = descriptor.getBounds(pose);
23446
- return { cx: b.x + b.width / 2, cy: b.y + b.height / 2, rotation };
23447
- }
23448
-
23449
- // src/canvas/SceneCanvas/useSceneSelectTool.ts
23450
23815
  var DEFAULT_PICK_TOLERANCE_PX = 4;
23451
23816
  function useSceneSelectTool(args) {
23452
23817
  const { scene, adapter, geometry, selectTool: opts, getView, alphaOf, layerIsPainted } = args;
@@ -23520,7 +23885,6 @@ function useSceneSelectTool(args) {
23520
23885
  }, [scene, adapter, boundsOfProp, d]);
23521
23886
  const selectTool = useSelectTool(adapter, {
23522
23887
  pickEvery: wiredHitBody,
23523
- move: wiredMoveOptions,
23524
23888
  ...opts?.pickBest ? { pickBest: opts.pickBest } : {},
23525
23889
  ...opts?.extendClickLocked ? { extendClickLocked: opts.extendClickLocked } : {}
23526
23890
  });
@@ -23534,8 +23898,100 @@ function useSceneSelectTool(args) {
23534
23898
  selectTool,
23535
23899
  pickEvery: wiredHitBody,
23536
23900
  pickBest: wiredPickBest,
23537
- boundsOf: wiredBoundsOf
23538
- };
23901
+ boundsOf: wiredBoundsOf,
23902
+ moveOptions: wiredMoveOptions
23903
+ };
23904
+ }
23905
+
23906
+ // src/canvas/SceneCanvas/features.ts
23907
+ var SCENE_CANVAS_FEATURES = [
23908
+ "view",
23909
+ "pick",
23910
+ "move",
23911
+ "transform",
23912
+ "edit",
23913
+ "arrange",
23914
+ "paths",
23915
+ "ingest",
23916
+ "draw"
23917
+ ];
23918
+ var BASE_FEATURES = SCENE_CANVAS_FEATURES.filter((f) => f !== "draw");
23919
+ var FEATURE_ACTION_IDS = {
23920
+ view: ["viewport.dragPan", "viewport.pinchZoom"],
23921
+ pick: ["areaSelect", "clearSelection", "lassoSelect"],
23922
+ move: ["move", "clone"],
23923
+ transform: ["resize", "rotate"],
23924
+ edit: [
23925
+ "escape",
23926
+ "cancelGesture",
23927
+ "selectAll",
23928
+ "duplicate",
23929
+ "delete",
23930
+ "group",
23931
+ "ungroup",
23932
+ "undo",
23933
+ "redo",
23934
+ "nudge.up",
23935
+ "nudge.down",
23936
+ "nudge.left",
23937
+ "nudge.right",
23938
+ "clipboard.cut",
23939
+ "clipboard.copy",
23940
+ "clipboard.paste",
23941
+ "setFill",
23942
+ "setStroke",
23943
+ "setFillOpacity",
23944
+ "setStrokeOpacity"
23945
+ ],
23946
+ arrange: [
23947
+ "flip",
23948
+ "reorder.forward",
23949
+ "reorder.backward",
23950
+ "align.left",
23951
+ "align.centerX",
23952
+ "align.right",
23953
+ "align.top",
23954
+ "align.centerY",
23955
+ "align.bottom",
23956
+ "distribute.horizontal",
23957
+ "distribute.vertical"
23958
+ ],
23959
+ paths: [
23960
+ "pathfinder.union",
23961
+ "pathfinder.subtract",
23962
+ "pathfinder.intersect",
23963
+ "pathfinder.exclude",
23964
+ "pathfinder.divide",
23965
+ "pathfinder.crop",
23966
+ "editAnchors",
23967
+ "enterPathEdit",
23968
+ "exitPathEdit",
23969
+ "insertPathAnchor",
23970
+ "nudgeAnchors.up",
23971
+ "nudgeAnchors.down",
23972
+ "nudgeAnchors.left",
23973
+ "nudgeAnchors.right",
23974
+ "deleteAnchors",
23975
+ "cutPathAtAnchor",
23976
+ "marqueeAnchors",
23977
+ "selectAnchor"
23978
+ ],
23979
+ ingest: ["ingest"]
23980
+ };
23981
+ var TOOL_DRIVEN_ACTION_IDS = ["insert", "insert.adjustRotation", "enterTextEdit"];
23982
+ function resolveFeatures(features, opts = {}) {
23983
+ const out = /* @__PURE__ */ new Set();
23984
+ for (const f of features ?? []) {
23985
+ if (f === "draw") for (const b of BASE_FEATURES) out.add(b);
23986
+ else out.add(f);
23987
+ }
23988
+ if (opts.viewport) out.add("view");
23989
+ return out;
23990
+ }
23991
+ function featureActionIds(features) {
23992
+ const ids = /* @__PURE__ */ new Set();
23993
+ for (const f of features) for (const id of FEATURE_ACTION_IDS[f]) ids.add(id);
23994
+ return ids;
23539
23995
  }
23540
23996
 
23541
23997
  // src/canvas/drawEnvelope.ts
@@ -24353,101 +24809,15 @@ function useBuiltinShapeTools(args) {
24353
24809
  addNode: (carrier) => {
24354
24810
  const node = makePenLeaf(carrier);
24355
24811
  adapter.insertNode(node);
24356
- return String(node.id);
24357
- },
24358
- makeNode: (carrier) => makePenLeaf(carrier),
24359
- setSelection: (ids) => adapter.setSelection(ids)
24360
- }
24361
- });
24362
- const lasso = useLassoTool(adapter, options?.lasso ?? {});
24363
- const text = useTextTool();
24364
- return { rect, ellipse, line, polygon, star, pen, pencil, lasso, text };
24365
- }
24366
- function DepRegistryProviderIfRoot({ children }) {
24367
- const parent = useOptionalDepRegistry();
24368
- if (parent) return /* @__PURE__ */ jsx(Fragment, { children });
24369
- return /* @__PURE__ */ jsx(DepRegistryProvider, { children });
24370
- }
24371
- function regionPickOptions(view, alphaOf, poseComposition) {
24372
- return {
24373
- ...poseComposition ? { poseComposition } : {},
24374
- ...alphaOf ? { alphaOf } : {},
24375
- ...view?.layerIsPainted ? { layerIsPainted: (layer) => view.layerIsPainted(layer) } : {}
24376
- };
24377
- }
24378
- function hitTestArea(scene, bounds, opts, descriptor = AUTO_POSE_DESCRIPTOR) {
24379
- const { x, y, width: w, height: h } = bounds;
24380
- const area = Array.from(rectToContour(x, y, w, h));
24381
- return hitTestAreaPolygon(scene, area, { x, y, width: w, height: h }, true, opts, descriptor);
24382
- }
24383
- function hitTestAreaPolygon(scene, area, areaBounds, areaIsRect = false, opts, descriptor = AUTO_POSE_DESCRIPTOR) {
24384
- const ab = areaBounds ?? boundsOf(area);
24385
- if (!ab) return [];
24386
- return pickWalk(scenePickSource(scene, opts), {
24387
- // A marquee that returns a container *and* its children selects the same
24388
- // ink twice; the container comes back through the selection's own
24389
- // parent-folding instead.
24390
- includeContainers: false,
24391
- clipAdmits: (clip, node, pose) => {
24392
- const g = poseDescriptorForNode(descriptor, node);
24393
- return pathIntersectsRect(clip, visualBoundsViaDescriptor(pose, g)) && pathIntersectsRect(clip, ab);
24394
- },
24395
- hits: (node, pose) => {
24396
- const g = poseDescriptorForNode(descriptor, node);
24397
- const silhouette = pose !== null && typeof pose === "object" && pose.kind === "polygon";
24398
- const b = silhouette ? nodeMemo(node, "aabb", pose, () => aabbOfPose(pose, g)) : visualBoundsViaDescriptor(pose, g);
24399
- if (!Number.isFinite(b.x) || !Number.isFinite(b.y) || !Number.isFinite(b.width) || !Number.isFinite(b.height)) {
24400
- return false;
24401
- }
24402
- if (b.x >= ab.x + ab.width || b.x + b.width <= ab.x || b.y >= ab.y + ab.height || b.y + b.height <= ab.y) {
24403
- return false;
24404
- }
24405
- if (areaIsRect && b.x >= ab.x && b.y >= ab.y && b.x + b.width <= ab.x + ab.width && b.y + b.height <= ab.y + ab.height) {
24406
- return true;
24407
- }
24408
- if (silhouette) {
24409
- return silhouetteOverlapsArea(pose.coords, area);
24410
- }
24411
- const drawn = findShapeSilhouette(node, pose);
24412
- if (drawn?.kind === "polygon") {
24413
- return silhouetteOverlapsArea(drawn.coords, area);
24414
- }
24415
- return true;
24812
+ return String(node.id);
24813
+ },
24814
+ makeNode: (carrier) => makePenLeaf(carrier),
24815
+ setSelection: (ids) => adapter.setSelection(ids)
24416
24816
  }
24417
24817
  });
24418
- }
24419
- function silhouetteOverlapsArea(sil, area) {
24420
- const ns = sil.length >> 1;
24421
- const na = area.length >> 1;
24422
- if (ns < 1 || na < 3) return false;
24423
- for (let i = 0; i < ns; i++) {
24424
- if (pointInPolygon(area, sil[i * 2], sil[i * 2 + 1])) return true;
24425
- }
24426
- for (let i = 0; i < na; i++) {
24427
- if (pointInPolygon(sil, area[i * 2], area[i * 2 + 1])) return true;
24428
- }
24429
- for (let i = 0, j = ns - 1; i < ns; j = i++) {
24430
- const ax = sil[j * 2], ay = sil[j * 2 + 1];
24431
- const bx = sil[i * 2], by = sil[i * 2 + 1];
24432
- for (let k = 0, l = na - 1; k < na; l = k++) {
24433
- const cx = area[l * 2], cy = area[l * 2 + 1];
24434
- const dx = area[k * 2], dy = area[k * 2 + 1];
24435
- if (segmentsCross(ax, ay, bx, by, cx, cy, dx, dy)) return true;
24436
- }
24437
- }
24438
- return false;
24439
- }
24440
- function boundsOf(coords) {
24441
- if (coords.length < 2) return null;
24442
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
24443
- for (let i = 0; i + 1 < coords.length; i += 2) {
24444
- const x = coords[i], y = coords[i + 1];
24445
- if (x < minX) minX = x;
24446
- if (y < minY) minY = y;
24447
- if (x > maxX) maxX = x;
24448
- if (y > maxY) maxY = y;
24449
- }
24450
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
24818
+ const lasso = useLassoTool(adapter, options?.lasso ?? {});
24819
+ const text = useTextTool();
24820
+ return { rect, ellipse, line, polygon, star, pen, pencil, lasso, text };
24451
24821
  }
24452
24822
  function useViewDepSource(currentViewRef, onViewChange, recenter, hostSize, animation, decay, layerIsPainted) {
24453
24823
  const wiring = useRef({
@@ -24543,6 +24913,7 @@ function useLassoSelectDepSource(scene, selection, descriptor, poseComposition,
24543
24913
  const d = descriptorRef.current;
24544
24914
  const a = alphaOfRef.current;
24545
24915
  return {
24916
+ hitTestLasso: (polygon, mode, view) => hitTestLassoPolygon(sc, polygon, mode, regionPickOptions(view, a, poseComposition), d),
24546
24917
  hitTestArea: (bounds, view) => hitTestArea(sc, bounds, regionPickOptions(view, a, poseComposition), d),
24547
24918
  getSelection: () => s.current,
24548
24919
  setSelection: (ids) => s.set(ids)
@@ -24765,7 +25136,7 @@ function chordLengthParam(samples) {
24765
25136
  ts[0] = 0;
24766
25137
  let total = 0;
24767
25138
  for (let i = 1; i < n; i++) {
24768
- total += dist2(samples[i - 1], samples[i]);
25139
+ total += dist(samples[i - 1], samples[i]);
24769
25140
  ts[i] = total;
24770
25141
  }
24771
25142
  if (total === 0) return null;
@@ -24798,13 +25169,13 @@ function solveLS(samples, ts, a, e, tStart, tEnd) {
24798
25169
  let alpha1;
24799
25170
  let alpha2;
24800
25171
  if (Math.abs(det) < 1e-12) {
24801
- const chord = dist2(a, e);
25172
+ const chord = dist(a, e);
24802
25173
  alpha1 = alpha2 = chord / 3;
24803
25174
  } else {
24804
25175
  alpha1 = (c22 * x1 - c12 * x2) / det;
24805
25176
  alpha2 = (c11 * x2 - c12 * x1) / det;
24806
25177
  if (alpha1 < 1e-6 || alpha2 < 1e-6) {
24807
- const chord = dist2(a, e);
25178
+ const chord = dist(a, e);
24808
25179
  alpha1 = alpha2 = chord / 3;
24809
25180
  }
24810
25181
  }
@@ -24839,7 +25210,7 @@ function newtonRefineT(p, t, p0, p1, p2, p3) {
24839
25210
  function sub(a, b) {
24840
25211
  return { x: a.x - b.x, y: a.y - b.y };
24841
25212
  }
24842
- function dist2(a, b) {
25213
+ function dist(a, b) {
24843
25214
  return Math.hypot(a.x - b.x, a.y - b.y);
24844
25215
  }
24845
25216
  function unit(v) {
@@ -25233,7 +25604,7 @@ function openFilePicker(opts = {}) {
25233
25604
  input.click();
25234
25605
  });
25235
25606
  }
25236
- function useActionsPropResolver(actions) {
25607
+ function useActionsPropResolver(actions, rev) {
25237
25608
  const reg = useActionsRegistry();
25238
25609
  const actionsRef = useRef(actions);
25239
25610
  actionsRef.current = actions;
@@ -25280,7 +25651,7 @@ function useActionsPropResolver(actions) {
25280
25651
  return () => {
25281
25652
  for (const u of unregisters) u();
25282
25653
  };
25283
- }, [reg, actions]);
25654
+ }, [reg, actions, rev]);
25284
25655
  }
25285
25656
  function useViewportActions(args) {
25286
25657
  const { pan, zoom, pinchZoom } = args;
@@ -25643,14 +26014,7 @@ function mergeLayersWithDefaults(user, targetScale = 1) {
25643
26014
  return result;
25644
26015
  }
25645
26016
  var NON_SHAPE_BUILTIN_TOOLS = ["select", "hand"];
25646
- var BUNDLE_TOOLS = {
25647
- minimal: ["select", "hand"],
25648
- // No `pencil`: freehand is a specialist instrument, not part of the
25649
- // everyday shape-drawing set. It stays in `exhaustive`, which means
25650
- // everything.
25651
- standard: ["select", "hand", "rect", "ellipse", "line"],
25652
- exhaustive: [...NON_SHAPE_BUILTIN_TOOLS, ...KIT_SHAPE_KINDS]
25653
- };
26017
+ var BUILTIN_TOOL_IDS = [...NON_SHAPE_BUILTIN_TOOLS, ...KIT_SHAPE_KINDS];
25654
26018
  var EMPTY_ID_SET = /* @__PURE__ */ new Set();
25655
26019
  var EMPTY_ANCHOR_SELECTION2 = /* @__PURE__ */ new Set();
25656
26020
  function isToolsApi(tools) {
@@ -25674,10 +26038,10 @@ function SceneCanvasInner(props, ref) {
25674
26038
  selectionOptions,
25675
26039
  selectionMode = "single",
25676
26040
  tools: toolsProp,
26041
+ features,
25677
26042
  enableKeybindings = true,
25678
26043
  enableGestureDispatcher = true,
25679
26044
  onToolsCreated,
25680
- toolBundle,
25681
26045
  defaultTools,
25682
26046
  toolOptions,
25683
26047
  initialActiveTool,
@@ -25703,7 +26067,7 @@ function SceneCanvasInner(props, ref) {
25703
26067
  isPointerInteractive,
25704
26068
  onDoubleClick,
25705
26069
  chromeVisibility,
25706
- getActiveMode,
26070
+ modes,
25707
26071
  getFocused: getFocusedProp,
25708
26072
  device,
25709
26073
  ...rest
@@ -25719,7 +26083,12 @@ function SceneCanvasInner(props, ref) {
25719
26083
  () => isSerialized(sceneInput) ? sceneFromJSON(sceneInput, {}) : null
25720
26084
  );
25721
26085
  const scene = bakedScene ?? sceneInput;
25722
- const { view: viewProp, onViewChange: onViewChangeProp, defaultView, ...restProps } = rest;
26086
+ const { view: viewProp, onViewChange: onViewChangeProp, defaultView, redrawOn: redrawOnProp, ...restProps } = rest;
26087
+ const getActiveMode = useMemo(() => modes ? getActiveModeFor(modes) : void 0, [modes]);
26088
+ const redrawOn = useMemo(
26089
+ () => modes ? [...redrawOnProp ?? [], modes] : redrawOnProp,
26090
+ [modes, redrawOnProp]
26091
+ );
25723
26092
  const internalCanvasRef = useRef(null);
25724
26093
  const canvasApiRef = useRef(null);
25725
26094
  const [canvasReady, setCanvasReady] = useState(false);
@@ -25921,7 +26290,13 @@ function SceneCanvasInner(props, ref) {
25921
26290
  poseComposition,
25922
26291
  cascadeContainerPose: !framed
25923
26292
  });
25924
- const { selectTool: internalSelect, pickEvery: internalPickEvery, pickBest: internalPickBest, boundsOf: internalBoundsOf } = useSceneSelectTool({
26293
+ const {
26294
+ selectTool: internalSelect,
26295
+ pickEvery: internalPickEvery,
26296
+ pickBest: internalPickBest,
26297
+ boundsOf: internalBoundsOf,
26298
+ moveOptions: internalMoveOptions
26299
+ } = useSceneSelectTool({
25925
26300
  scene,
25926
26301
  adapter,
25927
26302
  poseDescriptor: descriptor,
@@ -25953,7 +26328,13 @@ function SceneCanvasInner(props, ref) {
25953
26328
  return hit;
25954
26329
  };
25955
26330
  }, [adapter, internalPickEvery, isPointerInteractive, kindClassifier]);
25956
- const viewportRegistered = !!viewport;
26331
+ const featuresKey = (features ?? []).join("|");
26332
+ const hasViewport = viewport !== void 0;
26333
+ const enabled = useMemo(
26334
+ () => resolveFeatures(features, { viewport: hasViewport }),
26335
+ // eslint-disable-next-line react-hooks/exhaustive-deps
26336
+ [featuresKey, hasViewport]
26337
+ );
25957
26338
  const inertiaEnabled = !!viewport?.inertia;
25958
26339
  const inertiaObj = typeof viewport?.inertia === "object" ? viewport.inertia : void 0;
25959
26340
  const handToolInertia = inertiaEnabled ? {
@@ -25963,8 +26344,11 @@ function SceneCanvasInner(props, ref) {
25963
26344
  bounds: inertiaObj?.bounds
25964
26345
  } : void 0;
25965
26346
  const handTool = useHandTool(handToolInertia ? { inertia: handToolInertia } : {});
25966
- const viewportAmbient = [];
25967
- const baseRequestedTools = defaultTools ?? (toolBundle ? BUNDLE_TOOLS[toolBundle] : null) ?? (viewportRegistered ? ["select", "hand"] : ["select"]);
26347
+ const baseRequestedTools = [
26348
+ ...defaultTools ?? [],
26349
+ ...enabled.has("pick") ? ["select"] : [],
26350
+ ...enabled.has("view") ? ["hand"] : []
26351
+ ];
25968
26352
  const KNOWN_BUILTIN_IDS = /* @__PURE__ */ new Set([
25969
26353
  ...NON_SHAPE_BUILTIN_TOOLS,
25970
26354
  ...KIT_SHAPE_KINDS
@@ -25988,10 +26372,10 @@ function SceneCanvasInner(props, ref) {
25988
26372
  }
25989
26373
  const wants = (id) => baseRequestedTools.includes(id) || trueIds.has(id);
25990
26374
  const shapeTools = useBuiltinShapeTools({ scene, adapter, options: toolOptions });
25991
- const mergedAmbient = [...viewportAmbient, ...ambient ?? []];
26375
+ const mergedAmbient = ambient ?? [];
25992
26376
  const internalRegistry = {};
25993
26377
  if (wants("select")) internalRegistry.select = internalSelect;
25994
- if (wants("hand") && viewportRegistered) internalRegistry.hand = handTool;
26378
+ if (wants("hand")) internalRegistry.hand = handTool;
25995
26379
  if (wants("rect")) internalRegistry.rect = shapeTools.rect;
25996
26380
  if (wants("ellipse")) internalRegistry.ellipse = shapeTools.ellipse;
25997
26381
  if (wants("line")) internalRegistry.line = shapeTools.line;
@@ -26018,16 +26402,17 @@ function SceneCanvasInner(props, ref) {
26018
26402
  }
26019
26403
  const toolsTakeover = toolsProp && isToolsApi(toolsProp) ? toolsProp : null;
26020
26404
  const internalTools = useTools({
26021
- active: initialActiveTool ?? "select",
26405
+ active: initialActiveTool ?? ("select" in internalRegistry ? "select" : null),
26022
26406
  registry: internalRegistry,
26023
- ...mergedAmbient.length ? { ambient: mergedAmbient } : {}
26407
+ ...mergedAmbient.length ? { ambient: mergedAmbient } : {},
26408
+ ...modes ? { modes } : {}
26024
26409
  });
26025
- const toolsForEligibilityRef = useRef(null);
26026
- toolsForEligibilityRef.current = toolsTakeover ?? internalTools;
26410
+ const liveToolsRef = useRef(null);
26411
+ liveToolsRef.current = toolsTakeover ?? internalTools;
26027
26412
  const isToolEligible = useCallback((toolId) => {
26028
26413
  const getMode = getActiveModeRef.current;
26029
26414
  if (!getMode) return true;
26030
- const registry2 = toolsForEligibilityRef.current?.registry;
26415
+ const registry2 = liveToolsRef.current?.registry;
26031
26416
  const caps = registry2?.[toolId]?.eligibility.capabilities ?? [];
26032
26417
  if (caps.length === 0) return false;
26033
26418
  const allowed = getMode().allowedCapabilities;
@@ -26035,6 +26420,27 @@ function SceneCanvasInner(props, ref) {
26035
26420
  return false;
26036
26421
  }, []);
26037
26422
  const tools = toolsTakeover ?? internalTools;
26423
+ const selectionMoveOptions = useMemo(
26424
+ () => ({ move: internalMoveOptions }),
26425
+ [internalMoveOptions]
26426
+ );
26427
+ const rotateOptions = selectToolOpts?.rotate || void 0;
26428
+ const featureContributions = useMemo(() => [
26429
+ ...enabled.has("transform") ? [selectionTransformContribution({ rotate: rotateOptions })] : [],
26430
+ ...enabled.has("move") ? [selectionMoveContribution(selectionMoveOptions)] : []
26431
+ ], [enabled, selectionMoveOptions, rotateOptions]);
26432
+ const standardActionsExclude = useMemo(() => {
26433
+ const include = featureActionIds(enabled);
26434
+ const bound = [
26435
+ ...Object.values(tools.registry),
26436
+ ...tools.ambient,
26437
+ ...featureContributions
26438
+ ];
26439
+ for (const entry of bound) {
26440
+ for (const b of entry.bindings ?? []) include.add(b.actionId);
26441
+ }
26442
+ return KIT_STANDARD_ACTION_IDS.filter((id) => !include.has(id));
26443
+ }, [enabled, tools, featureContributions]);
26038
26444
  const onToolsCreatedRef = useRef(onToolsCreated);
26039
26445
  onToolsCreatedRef.current = onToolsCreated;
26040
26446
  useEffect(() => {
@@ -26123,6 +26529,11 @@ function SceneCanvasInner(props, ref) {
26123
26529
  getFocusedPropRef.current = getFocusedProp;
26124
26530
  const effectiveChromeVisibility = useMemo(() => {
26125
26531
  const defaults = {};
26532
+ if (!enabled.has("pick")) defaults["selection.outline"] = never;
26533
+ if (!enabled.has("transform")) {
26534
+ defaults["selection.resize-handles"] = never;
26535
+ defaults["selection.rotation-handle"] = never;
26536
+ }
26126
26537
  if (selectionMode === "none") {
26127
26538
  defaults["action.marquee"] = never;
26128
26539
  defaults["action.lasso"] = never;
@@ -26130,7 +26541,7 @@ function SceneCanvasInner(props, ref) {
26130
26541
  if (selectToolOpts?.rotate === false) defaults["selection.rotation-handle"] = never;
26131
26542
  if (Object.keys(defaults).length === 0) return chromeVisibility;
26132
26543
  return { ...defaults, ...chromeVisibility };
26133
- }, [chromeVisibility, selectionMode, selectToolOpts?.rotate]);
26544
+ }, [chromeVisibility, selectionMode, selectToolOpts?.rotate, enabled]);
26134
26545
  const chromeVisibilityRef = useRef(effectiveChromeVisibility);
26135
26546
  chromeVisibilityRef.current = effectiveChromeVisibility;
26136
26547
  const getActiveModeRef = useRef(getActiveMode);
@@ -26188,13 +26599,10 @@ function SceneCanvasInner(props, ref) {
26188
26599
  () => chromeCaps.isVisible(currentRuleInputs()),
26189
26600
  [chromeCaps, currentRuleInputs]
26190
26601
  );
26602
+ const wantsPen = wants("pen");
26191
26603
  const penPreviewLayer = useMemo(
26192
- () => wants("pen") ? createPenPreviewLayer({ penTool: shapeTools.pen }) : null,
26193
- // shapeTools.pen identity is stable across renders (returned from a hook
26194
- // that memoizes via defineTool). `wants('pen')` is recomputed each render
26195
- // from `baseRequestedTools` + patch-form `true` entries — capture both.
26196
- // eslint-disable-next-line react-hooks/exhaustive-deps
26197
- [shapeTools.pen, baseRequestedTools, trueIds]
26604
+ () => wantsPen ? createPenPreviewLayer({ penTool: shapeTools.pen }) : null,
26605
+ [shapeTools.pen, wantsPen]
26198
26606
  );
26199
26607
  useEffect(() => {
26200
26608
  canvasApiRef.current?.requestRedraw?.();
@@ -26396,12 +26804,12 @@ function SceneCanvasInner(props, ref) {
26396
26804
  onViewChange: notifyViewChange,
26397
26805
  shaders,
26398
26806
  flattenTolerance,
26807
+ ...redrawOn ? { redrawOn } : {},
26399
26808
  ...restProps
26400
26809
  }
26401
26810
  );
26402
26811
  const testHookSceneRef = useRef(scene);
26403
26812
  const testHookSelectionRef = useRef(selection);
26404
- const testHookActiveToolRef = useRef(null);
26405
26813
  testHookSceneRef.current = scene;
26406
26814
  testHookSelectionRef.current = selection;
26407
26815
  const testHookRef = useRef(null);
@@ -26411,12 +26819,12 @@ function SceneCanvasInner(props, ref) {
26411
26819
  getScene: () => testHookSceneRef.current,
26412
26820
  getSelectionIds: () => testHookSelectionRef.current?.current ?? [],
26413
26821
  getView: () => currentViewRef.current,
26414
- getActiveToolId: () => testHookActiveToolRef.current
26822
+ getActiveToolId: () => liveToolsRef.current?.active ?? null
26415
26823
  });
26416
26824
  testHookRef.current?._markReady();
26417
26825
  }
26418
26826
  }, []);
26419
- return /* @__PURE__ */ jsx(DeviceProfileProvider, { value: device, children: /* @__PURE__ */ jsx(ViewInputsProvider, { value: viewInputs, children: /* @__PURE__ */ jsx(DepRegistryProviderIfRoot, { children: /* @__PURE__ */ jsx(PointerProviderIfRoot, { children: /* @__PURE__ */ jsxs(ActionsProviderIfRoot, { children: [
26827
+ return /* @__PURE__ */ jsx(DeviceProfileProvider, { value: device, children: /* @__PURE__ */ jsx(ViewInputsProvider, { value: viewInputs, children: /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(PointerProviderIfRoot, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
26420
26828
  canvas,
26421
26829
  /* @__PURE__ */ jsx(PointerPublisher, { canvasRef: internalCanvasRef }),
26422
26830
  /* @__PURE__ */ jsx(
@@ -26427,6 +26835,7 @@ function SceneCanvasInner(props, ref) {
26427
26835
  adapter,
26428
26836
  actionDefaults,
26429
26837
  actions: resolvedActions,
26838
+ excludeActions: standardActionsExclude,
26430
26839
  currentViewRef,
26431
26840
  onViewChange: handleViewChange,
26432
26841
  resizeOptions: selectToolOpts?.resize,
@@ -26437,9 +26846,9 @@ function SceneCanvasInner(props, ref) {
26437
26846
  pickEvery: internalPickEvery,
26438
26847
  layerIsPainted: viewLayerGate,
26439
26848
  alphaOf: alphaFor ? composedAlphaFor : void 0,
26440
- viewportPanEnabled: viewport?.pan ?? true,
26441
- viewportZoom: resolvedViewportZoom,
26442
- viewportPinchZoom: viewport?.pinchZoom ?? true,
26849
+ viewportPanEnabled: enabled.has("view") ? viewport?.pan ?? true : false,
26850
+ viewportZoom: enabled.has("view") ? resolvedViewportZoom : false,
26851
+ viewportPinchZoom: enabled.has("view") ? viewport?.pinchZoom ?? true : false,
26443
26852
  viewportRecenter: viewport?.recenter,
26444
26853
  viewAnimation,
26445
26854
  editAnchorsExternalState,
@@ -26462,6 +26871,7 @@ function SceneCanvasInner(props, ref) {
26462
26871
  canvasRef: internalCanvasRef,
26463
26872
  canvasApiRef,
26464
26873
  tools,
26874
+ contributions: featureContributions,
26465
26875
  enabled: enableGestureDispatcher,
26466
26876
  keyboard: enableKeybindings,
26467
26877
  selectionRef,
@@ -26516,6 +26926,7 @@ function GestureDispatcherMounter({
26516
26926
  canvasRef,
26517
26927
  canvasApiRef,
26518
26928
  tools,
26929
+ contributions,
26519
26930
  enabled,
26520
26931
  keyboard,
26521
26932
  selectionRef,
@@ -26534,14 +26945,15 @@ function GestureDispatcherMounter({
26534
26945
  const registry2 = useActionsRegistry();
26535
26946
  const depRegistry = useDepRegistry();
26536
26947
  const viewRegistry = useOptionalViewRegistry();
26537
- const toolsById = useMemo(() => {
26948
+ const entriesById = useMemo(() => {
26538
26949
  const m = /* @__PURE__ */ new Map();
26539
26950
  for (const [id, tool] of Object.entries(tools.registry)) {
26540
26951
  m.set(id, tool);
26541
26952
  }
26542
- for (const tool of tools.ambient) m.set(tool.id, tool);
26953
+ for (const entry of contributions) m.set(entry.id, entry);
26954
+ for (const entry of tools.ambient) m.set(entry.id, entry);
26543
26955
  return m;
26544
- }, [tools.registry, tools.ambient]);
26956
+ }, [tools.registry, tools.ambient, contributions]);
26545
26957
  const boundsOfRef = useRef(boundsOf2);
26546
26958
  boundsOfRef.current = boundsOf2;
26547
26959
  const pickEveryRef = useRef(pickEvery);
@@ -26660,7 +27072,7 @@ function GestureDispatcherMounter({
26660
27072
  useGestureDispatcher({
26661
27073
  canvasRef,
26662
27074
  actions: registry2,
26663
- toolsById,
27075
+ entriesById,
26664
27076
  enabled,
26665
27077
  keyboard,
26666
27078
  affordanceAt: affordanceWithLayers,
@@ -26680,6 +27092,7 @@ function StandardActionsRegistrar({
26680
27092
  scene,
26681
27093
  adapter,
26682
27094
  actions,
27095
+ excludeActions,
26683
27096
  currentViewRef,
26684
27097
  onViewChange,
26685
27098
  resizeOptions,
@@ -26719,11 +27132,6 @@ function StandardActionsRegistrar({
26719
27132
  if (!registry2) return;
26720
27133
  return registry2.setDispatcher(dispatcher);
26721
27134
  }, [registry2, dispatcher]);
26722
- const wiredDepRegistry = useDepRegistry();
26723
- useEffect(() => {
26724
- if (!registry2) return;
26725
- return registry2.setDepRegistry(wiredDepRegistry);
26726
- }, [registry2, wiredDepRegistry]);
26727
27135
  useEffect(() => {
26728
27136
  if (!registry2) return;
26729
27137
  getActionRef.current = (id) => registry2.list().find((a) => a.id === id);
@@ -26744,7 +27152,7 @@ function StandardActionsRegistrar({
26744
27152
  decayLoop,
26745
27153
  layerIsPainted
26746
27154
  );
26747
- useStandardActions({ selection, scene, view, history: scene.history });
27155
+ useStandardActions({ selection, scene, view, history: scene.history, exclude: excludeActions });
26748
27156
  useDepSource("rootView", () => view);
26749
27157
  const pointer = usePointerContext();
26750
27158
  useDepSource("pointer", () => pointer ?? void 0);
@@ -26763,7 +27171,7 @@ function StandardActionsRegistrar({
26763
27171
  useDispatcherDepSource(dispatcher);
26764
27172
  usePoseDescriptorDepSource(poseDescriptor);
26765
27173
  usePoseCompositionDepSource(poseComposition);
26766
- useActionsPropResolver(actions);
27174
+ useActionsPropResolver(actions, excludeActions.join("|"));
26767
27175
  return /* @__PURE__ */ jsxs(Fragment, { children: [
26768
27176
  resizeOptions ? /* @__PURE__ */ jsx(ResizePolicyRegistrar, { options: resizeOptions }) : null,
26769
27177
  geometryProjection ? /* @__PURE__ */ jsx(GeometryProjectionRegistrar, { projection: geometryProjection }) : null
@@ -26772,11 +27180,7 @@ function StandardActionsRegistrar({
26772
27180
  function ResizePolicyRegistrar({
26773
27181
  options
26774
27182
  }) {
26775
- useResizePolicy({
26776
- constraints: options.behaviors,
26777
- pointSnap: options.pointSnapBehaviors,
26778
- expandIds: options.expandIds
26779
- });
27183
+ useResizePolicy(resizePolicyOptions(options));
26780
27184
  return null;
26781
27185
  }
26782
27186
  function GeometryProjectionRegistrar({
@@ -26787,7 +27191,7 @@ function GeometryProjectionRegistrar({
26787
27191
  }
26788
27192
  var SceneCanvasInnerForwardRef = forwardRef(SceneCanvasInner);
26789
27193
  function SceneCanvasWrapper(props, ref) {
26790
- return /* @__PURE__ */ jsx(ActiveToolContextProviderIfRoot, { children: /* @__PURE__ */ jsx(ViewRegistryProvider, { children: /* @__PURE__ */ jsx(SceneCanvasInnerForwardRef, { ...props, ref }) }) });
27194
+ return /* @__PURE__ */ jsx(InputScope, { yoke: props.yoke, children: /* @__PURE__ */ jsx(ViewRegistryProvider, { children: /* @__PURE__ */ jsx(SceneCanvasInnerForwardRef, { ...props, ref }) }) });
26791
27195
  }
26792
27196
  var SceneCanvas = forwardRef(SceneCanvasWrapper);
26793
27197
 
@@ -26837,6 +27241,18 @@ var markerEncoding = {
26837
27241
  },
26838
27242
  write: (value) => value === "" ? void 0 : value
26839
27243
  };
27244
+ var italicEncoding = {
27245
+ read: (stored) => stored === "italic",
27246
+ write: (on) => on ? "italic" : void 0
27247
+ };
27248
+ var alignEncoding = {
27249
+ read: (stored, style) => {
27250
+ if (stored === void 0) return void 0;
27251
+ const direction = style?.direction === "rtl" ? "rtl" : "ltr";
27252
+ return resolveAlign(stored, direction);
27253
+ },
27254
+ write: (edge2) => edge2
27255
+ };
26840
27256
  var markerOptions = () => [
26841
27257
  { value: "", label: "None" },
26842
27258
  ...listMarkers().map((m) => ({ value: m.id, label: m.id }))
@@ -26940,23 +27356,33 @@ function shapeSchema(opts = {}) {
26940
27356
  fontFamily: { kind: "font-family", name: "Font", description: "Registered font family.", default: "sans-serif" },
26941
27357
  fontSize: { kind: "number", name: "Size", description: "Font size, world units.", default: 16, min: 1, step: 1, pair: "Size / weight" },
26942
27358
  fontWeight: { kind: "number", name: "Weight", description: "Font weight, 100\u2013900.", default: 400, min: 100, max: 900, step: 100, pair: "Size / weight" },
26943
- fontStyle: { kind: "enum", name: "Style", description: "Upright or italic.", default: "normal", options: [{ value: "normal", label: "Normal" }, { value: "italic", label: "Italic" }] },
27359
+ // One row of segments, the strip every text editor
27360
+ // draws: the slant, the three rules, then the two
27361
+ // scripts. Italic is a flag here although the field
27362
+ // is `fontStyle`, whose only other value is upright.
27363
+ fontStyle: { kind: "boolean", name: "Italic", description: "Set the text in its italic face.", default: false, control: "toggle", icon: "italic", short: "I", pair: "Style", encoding: italicEncoding },
27364
+ underline: { kind: "boolean", name: "Underline", description: "Underline the text.", default: false, control: "toggle", icon: "underline", short: "U", pair: "Style" },
27365
+ strikethrough: { kind: "boolean", name: "Strikethrough", description: "Strike through the text.", default: false, control: "toggle", icon: "strikethrough", short: "S", pair: "Style" },
27366
+ overline: { kind: "boolean", name: "Overline", description: "Rule the text above its ascent.", default: false, control: "toggle", icon: "overline", short: "O", pair: "Style" },
27367
+ script: { kind: "enum", name: "Script", description: "Set the text smaller and raised or lowered, on the line it would otherwise hold.", default: void 0, control: "toggle", clearable: true, pair: "Style", options: [{ value: "super", label: "Superscript", icon: "superscript" }, { value: "sub", label: "Subscript", icon: "subscript" }] },
26944
27368
  letterSpacing: { kind: "number", name: "Tracking", description: "Extra advance per glyph, world units.", default: 0, step: 0.1 },
26945
- underline: { kind: "boolean", name: "Underline", description: "Underline the text.", default: false, control: "toggle", short: "U", pair: "Decoration" },
26946
- strikethrough: { kind: "boolean", name: "Strikethrough", description: "Strike through the text.", default: false, control: "toggle", short: "S", pair: "Decoration" },
26947
- overline: { kind: "boolean", name: "Overline", description: "Rule the text above its ascent.", default: false, control: "toggle", short: "O", pair: "Decoration" },
26948
27369
  textTransform: { kind: "enum", name: "Case", description: "Draw the text in capitals, lowercase or title case. The text itself is unchanged.", default: "none", control: "toggle", options: [{ value: "none", label: "None", short: "\u2013" }, { value: "uppercase", label: "Uppercase", short: "AA" }, { value: "lowercase", label: "Lowercase", short: "aa" }, { value: "capitalize", label: "Capitalize", short: "Aa" }] }
26949
27370
  }
26950
27371
  },
26951
27372
  paragraph: {
26952
27373
  name: "Paragraph",
26953
27374
  children: {
26954
- align: { kind: "enum", name: "Align", description: "Horizontal alignment.", default: "left", control: "toggle", options: [{ value: "left", label: "Left", short: "L" }, { value: "center", label: "Center", short: "C" }, { value: "right", label: "Right", short: "R" }] },
26955
- lineHeight: { kind: "number", name: "Leading", description: "Line height as a multiple of font size.", default: 1.2, min: 0.5, step: 0.1 }
27375
+ lineHeight: { kind: "number", name: "Leading", description: "Line height as a multiple of font size.", default: 1.2, min: 0.5, step: 0.1 },
27376
+ wrap: { kind: "boolean", name: "Wrap", description: "Break lines between words at the box width.", default: false, control: "switch" },
27377
+ direction: { kind: "enum", name: "Direction", description: "Reading direction.", default: "ltr", control: "toggle", options: [{ value: "ltr", label: "Left to right", short: "LTR" }, { value: "rtl", label: "Right to left", short: "RTL" }] },
27378
+ // Last, so the box's vertical alignment — a field of
27379
+ // the node rather than of its style — follows it.
27380
+ align: { kind: "enum", name: "Align", description: "Horizontal alignment.", default: "left", control: "toggle", encoding: alignEncoding, options: [{ value: "left", label: "Left", icon: "textAlignLeft" }, { value: "center", label: "Center", icon: "textAlignCenter" }, { value: "right", label: "Right", icon: "textAlignRight" }] }
26956
27381
  }
26957
27382
  }
26958
27383
  }
26959
- }
27384
+ },
27385
+ "data.verticalAlign": { kind: "enum", name: "Vertical", description: "Where the lines sit in the box's height.", default: "top", control: "toggle", options: [{ value: "top", label: "Top", icon: "textAlignTop" }, { value: "center", label: "Middle", icon: "textAlignMiddle" }, { value: "bottom", label: "Bottom", icon: "textAlignBottom" }] }
26960
27386
  }
26961
27387
  }
26962
27388
  } : {}
@@ -27545,7 +27971,7 @@ function MinimapCanvasInner(props) {
27545
27971
  useGestureDispatcher({
27546
27972
  canvasRef: localCanvasRef,
27547
27973
  actions,
27548
- toolsById: TOOLS_BY_ID,
27974
+ entriesById: TOOLS_BY_ID,
27549
27975
  clientToWorld: clientToWorld2,
27550
27976
  keyboard: false,
27551
27977
  channels: CHANNELS
@@ -27824,6 +28250,16 @@ function renderSceneToPixels(args) {
27824
28250
  renderer.dispose();
27825
28251
  }
27826
28252
  }
28253
+ function ActionsProviderIfRoot({ children }) {
28254
+ const parent = useActionsRegistry();
28255
+ if (parent) return /* @__PURE__ */ jsx(Fragment, { children });
28256
+ return /* @__PURE__ */ jsx(ActionsProvider, { children });
28257
+ }
28258
+ function DepRegistryProviderIfRoot({ children }) {
28259
+ const parent = useOptionalDepRegistry();
28260
+ if (parent) return /* @__PURE__ */ jsx(Fragment, { children });
28261
+ return /* @__PURE__ */ jsx(DepRegistryProvider, { children });
28262
+ }
27827
28263
  function WeaselProvider({ children, isolate = false }) {
27828
28264
  const Deps = isolate ? DepRegistryProvider : DepRegistryProviderIfRoot;
27829
28265
  const Actions = isolate ? ActionsProvider : ActionsProviderIfRoot;
@@ -27881,9 +28317,66 @@ function gateLayer(opts) {
27881
28317
  if (layer.defaultVisible !== void 0) wrapped.defaultVisible = layer.defaultVisible;
27882
28318
  if (layer.alwaysOn !== void 0) wrapped.alwaysOn = layer.alwaysOn;
27883
28319
  if (layer.space !== void 0) wrapped.space = layer.space;
28320
+ if (layer.subscribe) wrapped.subscribe = subscribeToSources([layer]);
27884
28321
  return wrapped;
27885
28322
  }
27886
28323
 
28324
+ // src/features/modes/modeLayers.ts
28325
+ var DEFAULT_INTENSITY = 0.12;
28326
+ function workspaceTintLayer(opts) {
28327
+ const { registry: registry2 } = opts;
28328
+ return {
28329
+ id: opts.id ?? "mode-tint",
28330
+ label: opts.label ?? "Mode tint",
28331
+ space: "screen",
28332
+ subscribe: registry2.subscribe,
28333
+ draw: (_data, view, dims) => {
28334
+ const ws = registry2.current().workspace;
28335
+ const tint = ws?.tint;
28336
+ if (!tint || tint === "transparent") return [];
28337
+ const opacity = opts.intensity ?? ws.intensity ?? DEFAULT_INTENSITY;
28338
+ const { width: W, height: H } = dims;
28339
+ const strip = (x, y, width, height, from, to) => ({
28340
+ kind: "path",
28341
+ path: { kind: "rect", x, y, width, height },
28342
+ fill: ws.gradient ? { fill: "linear-gradient", from, to, stops: [{ offset: 0, color: tint }, { offset: 1, color: "transparent" }], opacity } : { fill: "solid", color: tint, opacity }
28343
+ });
28344
+ const whole = () => [
28345
+ ws.gradient === "top-down" ? strip(0, 0, W, H, { x: 0, y: 0 }, { x: 0, y: H }) : strip(0, 0, W, H, { x: 0, y: H }, { x: 0, y: 0 })
28346
+ ];
28347
+ const page = opts.page?.();
28348
+ if (!page) return whole();
28349
+ const t = viewToTransform(view);
28350
+ const [sx0, sy0] = worldToScreen(page.x, page.y, t);
28351
+ const [sx1, sy1] = worldToScreen(page.x + page.width, page.y + page.height, t);
28352
+ const pl = Math.max(0, Math.min(sx0, sx1));
28353
+ const pt = Math.max(0, Math.min(sy0, sy1));
28354
+ const pr = Math.min(W, Math.max(sx0, sx1));
28355
+ const pb = Math.min(H, Math.max(sy0, sy1));
28356
+ if (pr <= pl || pb <= pt) {
28357
+ return [{ kind: "path", path: { kind: "rect", x: 0, y: 0, width: W, height: H }, fill: { fill: "solid", color: tint, opacity } }];
28358
+ }
28359
+ const cmds = [];
28360
+ const push = (x, y, w, h, from, to) => {
28361
+ if (w > 0 && h > 0) cmds.push(strip(x, y, w, h, from, to));
28362
+ };
28363
+ push(0, 0, W, pt, { x: 0, y: 0 }, { x: 0, y: pt });
28364
+ push(0, pb, W, H - pb, { x: 0, y: H }, { x: 0, y: pb });
28365
+ push(0, pt, pl, pb - pt, { x: 0, y: 0 }, { x: pl, y: 0 });
28366
+ push(pr, pt, W - pr, pb - pt, { x: W, y: 0 }, { x: pr, y: 0 });
28367
+ return cmds;
28368
+ }
28369
+ };
28370
+ }
28371
+ function modeDecorationLayer(decorations, opts = {}) {
28372
+ return {
28373
+ id: opts.id ?? "mode-decorations",
28374
+ label: opts.label ?? "Mode decorations",
28375
+ subscribe: decorations.subscribe,
28376
+ draw: () => decorations.paint()
28377
+ };
28378
+ }
28379
+
27887
28380
  // src/features/groups/children.ts
27888
28381
  function createChildrenLayer(opts) {
27889
28382
  return {
@@ -28989,7 +29482,8 @@ function useTextEdit(opts) {
28989
29482
  }
28990
29483
  if (e.key === "Escape") {
28991
29484
  e.preventDefault();
28992
- cancelEdit();
29485
+ if (optsRef.current.escape === "commit") commit();
29486
+ else cancelEdit();
28993
29487
  return;
28994
29488
  }
28995
29489
  const toggle = toggleForKey(e);
@@ -29138,6 +29632,7 @@ function applyOverlayStyle(el, style) {
29138
29632
  const decorations = [];
29139
29633
  if (style.underline) decorations.push("underline");
29140
29634
  if (style.strikethrough) decorations.push("line-through");
29635
+ if (style.overline) decorations.push("overline");
29141
29636
  el.style.textDecoration = decorations.length > 0 ? decorations.join(" ") : "none";
29142
29637
  el.style.textTransform = style.textTransform;
29143
29638
  el.style.whiteSpace = style.wrap ? "pre-wrap" : "pre";
@@ -29178,8 +29673,11 @@ function placeOverlay(el, clipBox, pose, clip, style) {
29178
29673
  el.style.left = `${pose.x + pose.width * (pose.zoom ?? 1) * anchor + 1 - (clip?.x ?? 0)}px`;
29179
29674
  el.style.width = style.wrap ? `${pose.width}px` : "max-content";
29180
29675
  el.style.minWidth = style.wrap ? "" : `${pose.width}px`;
29181
- el.style.fontSize = `${pose.fontSize}px`;
29182
- el.style.lineHeight = String(pose.lineHeight ?? style.lineHeight);
29676
+ const script = style.script ? SCRIPT_METRICS[style.script] : void 0;
29677
+ const lineHeight = pose.lineHeight ?? style.lineHeight;
29678
+ el.style.fontSize = `${pose.fontSize * (script?.size ?? 1)}px`;
29679
+ el.style.lineHeight = script ? `${pose.fontSize * lineHeight}px` : String(lineHeight);
29680
+ el.style.translate = script ? `0 ${-script.shift * pose.fontSize * (pose.zoom ?? 1)}px` : "";
29183
29681
  const aligned = pose.verticalAlign !== void 0 && pose.verticalAlign !== "top";
29184
29682
  el.style.minHeight = aligned ? "" : `${pose.height}px`;
29185
29683
  el.style.top = `${overlayTop(pose, aligned ? el.offsetHeight : pose.height) - (clip?.y ?? 0)}px`;
@@ -29221,6 +29719,7 @@ function useSceneTextEdit(scene, container, options = {}) {
29221
29719
  const edit = useTextEdit({
29222
29720
  container,
29223
29721
  isEditorChrome: (el) => optsRef.current.isEditorChrome?.(el) ?? false,
29722
+ escape: options.escape,
29224
29723
  getText: (id) => {
29225
29724
  const node = sceneRef.current.get(asNodeId(id));
29226
29725
  if (!node) return "";
@@ -29640,6 +30139,44 @@ function seedMeshPatch(color) {
29640
30139
  units: "bounds"
29641
30140
  };
29642
30141
  }
30142
+ function bandPatch(x0, x1, left, right) {
30143
+ const corners = [{ x: x0, y: 0 }, { x: x1, y: 0 }, { x: x1, y: 1 }, { x: x0, y: 1 }];
30144
+ const points = [];
30145
+ for (let i = 0; i < 4; i++) {
30146
+ const a = corners[i];
30147
+ const b = corners[(i + 1) % 4];
30148
+ points.push(a, { x: a.x + (b.x - a.x) / 3, y: a.y + (b.y - a.y) / 3 }, { x: a.x + 2 * (b.x - a.x) / 3, y: a.y + 2 * (b.y - a.y) / 3 });
30149
+ }
30150
+ return { points, colors: [left, right, right, left] };
30151
+ }
30152
+ function meshFromStops(stops) {
30153
+ const sorted = [...stops].sort((a, b) => a.offset - b.offset);
30154
+ if (sorted.length === 1) sorted.push({ offset: 1, color: sorted[0].color });
30155
+ if (sorted.length === 0) return seedMeshPatch("#000000ff");
30156
+ const patches = [];
30157
+ for (let i = 0; i < sorted.length - 1; i++) {
30158
+ const a = sorted[i];
30159
+ const b = sorted[i + 1];
30160
+ if (b.offset > a.offset) patches.push(bandPatch(a.offset, b.offset, a.color, b.color));
30161
+ }
30162
+ return { fill: MESH_GRADIENT_KIND, patches, units: "bounds" };
30163
+ }
30164
+ function meshStops(mesh) {
30165
+ const valid = mesh.patches.filter(isValidPatch);
30166
+ if (valid.length === 0) return [];
30167
+ const xs = valid.flatMap((p) => [p.points[0].x, p.points[3].x]);
30168
+ const lo = Math.min(...xs);
30169
+ const span = Math.max(...xs) - lo;
30170
+ const offsetOf = (x) => span > 0 ? (x - lo) / span : 0;
30171
+ const stops = [];
30172
+ for (const patch of valid) {
30173
+ for (const [point, color] of [[patch.points[0], patch.colors[0]], [patch.points[3], patch.colors[1]]]) {
30174
+ const offset = offsetOf(point.x);
30175
+ if (!stops.some((s) => s.offset === offset && s.color === color)) stops.push({ offset, color });
30176
+ }
30177
+ }
30178
+ return stops.sort((a, b) => a.offset - b.offset);
30179
+ }
29643
30180
  function mapPatches(mesh, move) {
29644
30181
  return mesh.patches.map((patch) => ({ ...patch, points: patch.points.map(move) }));
29645
30182
  }
@@ -29709,6 +30246,8 @@ registerPaintKind({
29709
30246
  // The first corner of the first patch: a mesh has no single color, and this
29710
30247
  // is the one a fallback swatch and an SVG paint fallback both read.
29711
30248
  colorOf: (paint) => asMesh(paint).patches?.[0]?.colors?.[0],
30249
+ stopsOf: (paint) => meshStops(asMesh(paint)),
30250
+ fromStops: (stops) => asPaint(meshFromStops(stops)),
29712
30251
  bind: bindMesh,
29713
30252
  inPoseFrame: (fill, box) => {
29714
30253
  const mesh = asMesh(fill);
@@ -29899,19 +30438,6 @@ function unionOfChildrenVia(descriptor) {
29899
30438
  return u === null ? null : poseDescriptorForNode(descriptor, node).fromBounds(u, node.pose);
29900
30439
  };
29901
30440
  }
29902
- var BEZIER_SAMPLES = 16;
29903
- function pointCubicDist2(px, py, x0, y0, x1, y1, x2, y2, x3, y3) {
29904
- let prevX = x0, prevY = y0;
29905
- let best = Infinity;
29906
- for (let i = 1; i <= BEZIER_SAMPLES; i++) {
29907
- const [sx, sy] = cubicEvalAt(x0, y0, x1, y1, x2, y2, x3, y3, i / BEZIER_SAMPLES);
29908
- const d2 = pointSegmentDist2(px, py, prevX, prevY, sx, sy);
29909
- if (d2 < best) best = d2;
29910
- prevX = sx;
29911
- prevY = sy;
29912
- }
29913
- return best;
29914
- }
29915
30441
  function pointToRectPerimeterDist(px, py, rx, ry, rw, rh) {
29916
30442
  const dx = Math.max(rx - px, px - (rx + rw), 0);
29917
30443
  const dy = Math.max(ry - py, py - (ry + rh), 0);
@@ -29924,52 +30450,66 @@ function pathDistanceToPoint(path, px, py) {
29924
30450
  if (path.kind === "rect") {
29925
30451
  return pointToRectPerimeterDist(px, py, path.x, path.y, path.width, path.height);
29926
30452
  }
29927
- return polygonPathDistance(path, px, py);
30453
+ return nearestOnPath(path.commands, path.coords, px, py)?.dist ?? Infinity;
29928
30454
  }
29929
- function polygonPathDistance(path, px, py) {
29930
- const { commands, coords } = path;
29931
- let startX = 0, startY = 0;
29932
- let best = Infinity;
29933
- const consider = (d2) => {
29934
- if (d2 < best) best = d2;
29935
- };
29936
- forEachSegment(commands, coords, (cmd, ci, curX, curY) => {
29937
- switch (cmd) {
29938
- case PATH_M:
29939
- startX = coords[ci];
29940
- startY = coords[ci + 1];
29941
- break;
29942
- case PATH_L:
29943
- consider(pointSegmentDist2(px, py, curX, curY, coords[ci], coords[ci + 1]));
29944
- break;
29945
- case PATH_C:
29946
- consider(pointCubicDist2(
29947
- px,
29948
- py,
29949
- curX,
29950
- curY,
29951
- coords[ci],
29952
- coords[ci + 1],
29953
- coords[ci + 2],
29954
- coords[ci + 3],
29955
- coords[ci + 4],
29956
- coords[ci + 5]
29957
- ));
29958
- break;
29959
- case PATH_Q: {
29960
- const x = coords[ci + 2], y = coords[ci + 3];
29961
- const [c1x, c1y, c2x, c2y] = elevateQuadraticToCubic(curX, curY, coords[ci], coords[ci + 1], x, y);
29962
- consider(pointCubicDist2(px, py, curX, curY, c1x, c1y, c2x, c2y, x, y));
29963
- break;
29964
- }
29965
- case PATH_Z:
29966
- consider(pointSegmentDist2(px, py, curX, curY, startX, startY));
29967
- break;
29968
- default:
29969
- throw new Error(`pathDistanceToPoint: unknown command code ${cmd}`);
29970
- }
29971
- });
29972
- return best === Infinity ? Infinity : Math.sqrt(best);
30455
+ function flattenVerts2(poly2) {
30456
+ const flat = new Array(poly2.length * 2);
30457
+ for (let i = 0; i < poly2.length; i++) {
30458
+ flat[i * 2] = poly2[i].x;
30459
+ flat[i * 2 + 1] = poly2[i].y;
30460
+ }
30461
+ return flat;
30462
+ }
30463
+ function polygonContainsRectCenter(poly2, rect) {
30464
+ return pointInPolygon(flattenVerts2(poly2), rect.x + rect.width / 2, rect.y + rect.height / 2);
30465
+ }
30466
+ function polygonContainsRect(poly2, rect) {
30467
+ if (poly2.length < 3) return false;
30468
+ const flat = flattenVerts2(poly2);
30469
+ if (!pointInPolygon(flat, rect.x, rect.y)) return false;
30470
+ if (!pointInPolygon(flat, rect.x + rect.width, rect.y)) return false;
30471
+ if (!pointInPolygon(flat, rect.x + rect.width, rect.y + rect.height)) return false;
30472
+ if (!pointInPolygon(flat, rect.x, rect.y + rect.height)) return false;
30473
+ return !anyPolyEdgeCrossesRect(poly2, rect);
30474
+ }
30475
+ function polygonIntersectsRect(poly2, rect) {
30476
+ if (poly2.length < 3) return false;
30477
+ const ab = polygonAabb(poly2);
30478
+ if (ab.x + ab.width < rect.x || ab.x > rect.x + rect.width) return false;
30479
+ if (ab.y + ab.height < rect.y || ab.y > rect.y + rect.height) return false;
30480
+ for (const v of poly2) {
30481
+ if (v.x >= rect.x && v.x <= rect.x + rect.width && v.y >= rect.y && v.y <= rect.y + rect.height) return true;
30482
+ }
30483
+ const flat = flattenVerts2(poly2);
30484
+ if (pointInPolygon(flat, rect.x, rect.y)) return true;
30485
+ if (pointInPolygon(flat, rect.x + rect.width, rect.y)) return true;
30486
+ if (pointInPolygon(flat, rect.x + rect.width, rect.y + rect.height)) return true;
30487
+ if (pointInPolygon(flat, rect.x, rect.y + rect.height)) return true;
30488
+ return anyPolyEdgeCrossesRect(poly2, rect);
30489
+ }
30490
+ function polygonAabb(poly2) {
30491
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
30492
+ for (const v of poly2) {
30493
+ if (v.x < minX) minX = v.x;
30494
+ if (v.y < minY) minY = v.y;
30495
+ if (v.x > maxX) maxX = v.x;
30496
+ if (v.y > maxY) maxY = v.y;
30497
+ }
30498
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
30499
+ }
30500
+ function anyPolyEdgeCrossesRect(poly2, rect) {
30501
+ const r0x = rect.x, r0y = rect.y;
30502
+ const r1x = rect.x + rect.width, r1y = rect.y;
30503
+ const r2x = rect.x + rect.width, r2y = rect.y + rect.height;
30504
+ const r3x = rect.x, r3y = rect.y + rect.height;
30505
+ for (let i = 0, j = poly2.length - 1; i < poly2.length; j = i++) {
30506
+ const a = poly2[j], b = poly2[i];
30507
+ if (segmentsCross(a.x, a.y, b.x, b.y, r0x, r0y, r1x, r1y)) return true;
30508
+ if (segmentsCross(a.x, a.y, b.x, b.y, r1x, r1y, r2x, r2y)) return true;
30509
+ if (segmentsCross(a.x, a.y, b.x, b.y, r2x, r2y, r3x, r3y)) return true;
30510
+ if (segmentsCross(a.x, a.y, b.x, b.y, r3x, r3y, r0x, r0y)) return true;
30511
+ }
30512
+ return false;
29973
30513
  }
29974
30514
  var PLACEHOLDER_FILL = { color: "#ffffff" };
29975
30515
  var PLACEHOLDER_STROKE = { paint: { color: "#ffffff" }, width: 1 };
@@ -30120,60 +30660,400 @@ function splitSubpaths(path) {
30120
30660
  });
30121
30661
  return out;
30122
30662
  }
30123
-
30124
- // src/features/paths/splitByLine.ts
30125
- function segmentsProperlyCross(a, b, c, d) {
30126
- const s = (p, q, r) => Math.sign((q.x - p.x) * (r.y - p.y) - (q.y - p.y) * (r.x - p.x));
30127
- const d1 = s(c, d, a), d2 = s(c, d, b), d3 = s(a, b, c), d4 = s(a, b, d);
30128
- return d1 !== d2 && d3 !== d4;
30663
+ function splitPathBySegment(path, a, b, opts = {}) {
30664
+ if (a.x === b.x && a.y === b.y) return null;
30665
+ const outRule = path.kind === "polygon" ? path.fillRule : "nonzero";
30666
+ let contours = parseContours(path);
30667
+ let fillRule = outRule;
30668
+ if (contoursIntersect(contours, opts.flattenTolerance ?? 0.5)) {
30669
+ contours = parseContours(pathUnion(path));
30670
+ fillRule = "nonzero";
30671
+ }
30672
+ const boundaries = orientBoundaries(contours, fillRule);
30673
+ if (boundaries.length === 0) return null;
30674
+ const crossings = findCrossings(boundaries, a, b);
30675
+ const partner = /* @__PURE__ */ new Map();
30676
+ for (let k = 0; k + 1 < crossings.length; k++) {
30677
+ const c0 = crossings[k], c1 = crossings[k + 1];
30678
+ if (c0.enters && !c1.enters && c1.s - c0.s > 1e-9) {
30679
+ partner.set(c0, c1);
30680
+ partner.set(c1, c0);
30681
+ }
30682
+ }
30683
+ if (partner.size === 0) return null;
30684
+ const { loops, untouched } = traceLoops(boundaries, partner);
30685
+ const traced = assembleRegions(loops, untouched.filter((c) => signedArea2(c) < 0));
30686
+ const islands = untouched.filter((c) => signedArea2(c) > 0);
30687
+ const pieces = traced.filter((r) => Math.abs(regionArea(r)) > 1e-6);
30688
+ if (islands.length > 0) {
30689
+ const leftover = untouched.filter((c) => !traced.some((r) => r.includes(c)));
30690
+ pieces.push(leftover);
30691
+ }
30692
+ return pieces.map((r) => toPath(r, outRule));
30693
+ }
30694
+ function evalSeg(s, t) {
30695
+ const u = 1 - t;
30696
+ if (s.length === 2) return { x: u * s[0].x + t * s[1].x, y: u * s[0].y + t * s[1].y };
30697
+ if (s.length === 3) {
30698
+ const a2 = u * u, b2 = 2 * u * t, c2 = t * t;
30699
+ return { x: a2 * s[0].x + b2 * s[1].x + c2 * s[2].x, y: a2 * s[0].y + b2 * s[1].y + c2 * s[2].y };
30700
+ }
30701
+ const a = u * u * u, b = 3 * u * u * t, c = 3 * u * t * t, d = t * t * t;
30702
+ return {
30703
+ x: a * s[0].x + b * s[1].x + c * s[2].x + d * s[3].x,
30704
+ y: a * s[0].y + b * s[1].y + c * s[2].y + d * s[3].y
30705
+ };
30129
30706
  }
30130
- function countBoundaryCrossings(path, a, b, tol) {
30131
- let n = 0;
30132
- for (const pl of extractPolylines(path, { flattenTolerance: tol })) {
30133
- const pts = pl.points;
30134
- const count = pts.length / 2;
30135
- const last = pl.closed ? count : count - 1;
30136
- for (let i = 0; i < last; i++) {
30137
- const j = (i + 1) % count;
30138
- const c = { x: pts[i * 2], y: pts[i * 2 + 1] };
30139
- const dd = { x: pts[j * 2], y: pts[j * 2 + 1] };
30140
- if (segmentsProperlyCross(a, b, c, dd)) n++;
30707
+ function derivSeg(s, t) {
30708
+ const n = s.length - 1;
30709
+ const d = [];
30710
+ for (let i = 0; i < n; i++) d.push({ x: n * (s[i + 1].x - s[i].x), y: n * (s[i + 1].y - s[i].y) });
30711
+ return d.length === 1 ? d[0] : evalSeg(d, t);
30712
+ }
30713
+ function splitSeg(s, t) {
30714
+ const pts = (c) => {
30715
+ const out = [];
30716
+ for (let i = 0; i < c.length; i += 2) out.push({ x: c[i], y: c[i + 1] });
30717
+ return out;
30718
+ };
30719
+ if (s.length === 2) {
30720
+ const m = evalSeg(s, t);
30721
+ return [[s[0], m], [m, s[1]]];
30722
+ }
30723
+ if (s.length === 3) {
30724
+ const [l2, r2] = splitQuadraticAt(s[0].x, s[0].y, s[1].x, s[1].y, s[2].x, s[2].y, t);
30725
+ return [pts(l2), pts(r2)];
30726
+ }
30727
+ const [l, r] = splitCubicAt(s[0].x, s[0].y, s[1].x, s[1].y, s[2].x, s[2].y, s[3].x, s[3].y, t);
30728
+ return [pts(l), pts(r)];
30729
+ }
30730
+ function subSeg(s, t0, t1, p0, p1) {
30731
+ if (t1 - t0 <= 0) return null;
30732
+ let out = s;
30733
+ if (t0 > 0) out = splitSeg(out, t0)[1];
30734
+ const u = (t1 - t0) / (1 - t0);
30735
+ if (u < 1) out = splitSeg(out, u)[0];
30736
+ out = out.slice();
30737
+ out[0] = p0;
30738
+ out[out.length - 1] = p1;
30739
+ return out;
30740
+ }
30741
+ var reverseContour = (c) => c.map((s) => s.slice().reverse()).reverse();
30742
+ function sampleContour(c) {
30743
+ const out = [];
30744
+ for (const s of c) {
30745
+ const n = s.length === 2 ? 1 : 16;
30746
+ for (let i = 0; i < n; i++) {
30747
+ const p = evalSeg(s, i / n);
30748
+ out.push(p.x, p.y);
30141
30749
  }
30142
30750
  }
30143
- return n;
30751
+ return out;
30144
30752
  }
30145
- function splitPathByLine(path, a, b, opts = {}) {
30146
- const tol = opts.flattenTolerance ?? 0.5;
30147
- if (a.x === b.x && a.y === b.y) return null;
30148
- if (countBoundaryCrossings(path, a, b, tol) < 2) return null;
30149
- const dx = b.x - a.x, dy = b.y - a.y;
30150
- const len = Math.hypot(dx, dy);
30151
- const ux = dx / len, uy = dy / len;
30152
- const nx = -uy, ny = ux;
30153
- const bb = boundsOfPath(path);
30154
- const R = (Math.hypot(bb.width, bb.height) + 1) * 4;
30155
- const mid2 = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
30156
- const p0 = { x: mid2.x - ux * R, y: mid2.y - uy * R };
30157
- const p1 = { x: mid2.x + ux * R, y: mid2.y + uy * R };
30158
- const sideQuad = (sign) => polygonFromPoints([
30159
- p0,
30160
- p1,
30161
- { x: p1.x + nx * R * sign, y: p1.y + ny * R * sign },
30162
- { x: p0.x + nx * R * sign, y: p0.y + ny * R * sign }
30163
- ]);
30164
- const fillRule = path.kind === "polygon" ? path.fillRule : "nonzero";
30165
- const sideA = withFillRule(pathIntersect(path, sideQuad(1)), fillRule);
30166
- const sideB = withFillRule(pathIntersect(path, sideQuad(-1)), fillRule);
30167
- const isSubstantial = (p) => {
30168
- if (p.commands.length === 0) return false;
30169
- const bb2 = boundsOfPath(p);
30170
- return bb2.width * bb2.height >= 1e-6;
30753
+ function signedArea2(c) {
30754
+ const pts = sampleContour(c);
30755
+ let a = 0;
30756
+ for (let i = 0; i < pts.length; i += 2) {
30757
+ const j = (i + 2) % pts.length;
30758
+ a += pts[i] * pts[j + 1] - pts[j] * pts[i + 1];
30759
+ }
30760
+ return a / 2;
30761
+ }
30762
+ function sideCoeffs(s, o, d) {
30763
+ return s.map((p) => d.x * (p.y - o.y) - d.y * (p.x - o.x));
30764
+ }
30765
+ function bernstein(f, t) {
30766
+ const u = 1 - t;
30767
+ if (f.length === 2) return u * f[0] + t * f[1];
30768
+ if (f.length === 3) return u * u * f[0] + 2 * u * t * f[1] + t * t * f[2];
30769
+ return u * u * u * f[0] + 3 * u * u * t * f[1] + 3 * u * t * t * f[2] + t * t * t * f[3];
30770
+ }
30771
+ function signChanges(f, zeroIsPositive) {
30772
+ const sgn = (v) => v > 0 ? 1 : v < 0 ? -1 : zeroIsPositive ? 1 : 0;
30773
+ const cuts = [0, ...criticalPoints(f), 1];
30774
+ const out = [];
30775
+ for (let i = 0; i + 1 < cuts.length; i++) {
30776
+ let lo = cuts[i], hi = cuts[i + 1];
30777
+ const slo = sgn(bernstein(f, lo)), shi = sgn(bernstein(f, hi));
30778
+ if (slo === 0 && i > 0) {
30779
+ const before = sgn(bernstein(f, lo - 1e-7)), after = sgn(bernstein(f, lo + 1e-7));
30780
+ if (before * after < 0) out.push({ t: lo, before, after });
30781
+ }
30782
+ if (slo * shi >= 0) continue;
30783
+ for (let it = 0; it < 64; it++) {
30784
+ const mid = (lo + hi) / 2;
30785
+ if (sgn(bernstein(f, mid)) === slo) lo = mid;
30786
+ else hi = mid;
30787
+ }
30788
+ out.push({ t: (lo + hi) / 2, before: slo, after: shi });
30789
+ }
30790
+ return out;
30791
+ }
30792
+ function criticalPoints(f) {
30793
+ if (f.length < 3) return [];
30794
+ const n = f.length - 1;
30795
+ const d = f.slice(1).map((v, i) => n * (v - f[i]));
30796
+ let A = 0, B, C;
30797
+ if (d.length === 2) {
30798
+ B = d[1] - d[0];
30799
+ C = d[0];
30800
+ } else {
30801
+ A = d[0] - 2 * d[1] + d[2];
30802
+ B = 2 * (d[1] - d[0]);
30803
+ C = d[0];
30804
+ }
30805
+ const out = [];
30806
+ const scale = Math.max(Math.abs(A), Math.abs(B), Math.abs(C)) || 1;
30807
+ if (Math.abs(A) / scale < 1e-12) {
30808
+ if (Math.abs(B) / scale > 1e-12) out.push(-C / B);
30809
+ } else {
30810
+ const disc = B * B - 4 * A * C;
30811
+ if (disc >= 0) {
30812
+ const q = -0.5 * (B + Math.sign(B || 1) * Math.sqrt(disc));
30813
+ out.push(q / A);
30814
+ if (q !== 0) out.push(C / q);
30815
+ }
30816
+ }
30817
+ return out.filter((t) => t > 0 && t < 1).sort((x, y) => x - y);
30818
+ }
30819
+ function endSide(f, fromStart) {
30820
+ const n = f.length;
30821
+ for (let i = 1; i < n; i++) {
30822
+ const v = fromStart ? f[i] : f[n - 1 - i];
30823
+ if (v !== 0) return Math.sign(v);
30824
+ }
30825
+ return 0;
30826
+ }
30827
+ function parseContours(path) {
30828
+ if (path.kind === "rect") {
30829
+ const { x, y, width: w, height: h } = path;
30830
+ const p = [{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }];
30831
+ return [p.map((q, i) => [q, p[(i + 1) % 4]])];
30832
+ }
30833
+ const out = [];
30834
+ let cur = [];
30835
+ let start = { x: 0, y: 0 };
30836
+ let pen = start;
30837
+ const finish = () => {
30838
+ if (cur.length > 0 && (pen.x !== start.x || pen.y !== start.y)) cur.push([pen, start]);
30839
+ if (cur.length > 0) out.push(cur);
30840
+ cur = [];
30841
+ pen = start;
30842
+ };
30843
+ const c = path.coords;
30844
+ let k = 0;
30845
+ for (const cmd of path.commands) {
30846
+ if (cmd === PATH_M) {
30847
+ finish();
30848
+ start = pen = { x: c[k], y: c[k + 1] };
30849
+ k += 2;
30850
+ } else if (cmd === PATH_L) {
30851
+ const e = { x: c[k], y: c[k + 1] };
30852
+ if (e.x !== pen.x || e.y !== pen.y) cur.push([pen, e]);
30853
+ pen = e;
30854
+ k += 2;
30855
+ } else if (cmd === PATH_Q) {
30856
+ const e = { x: c[k + 2], y: c[k + 3] };
30857
+ cur.push([pen, { x: c[k], y: c[k + 1] }, e]);
30858
+ pen = e;
30859
+ k += 4;
30860
+ } else if (cmd === PATH_C) {
30861
+ const e = { x: c[k + 4], y: c[k + 5] };
30862
+ cur.push([pen, { x: c[k], y: c[k + 1] }, { x: c[k + 2], y: c[k + 3] }, e]);
30863
+ pen = e;
30864
+ k += 6;
30865
+ } else if (cmd === PATH_Z) {
30866
+ finish();
30867
+ }
30868
+ }
30869
+ finish();
30870
+ return out;
30871
+ }
30872
+ function contoursIntersect(contours, tol) {
30873
+ const edges = [];
30874
+ contours.forEach((contour, ci) => {
30875
+ const pts = [];
30876
+ for (const s of contour) {
30877
+ let n = 1;
30878
+ if (s.length > 2) {
30879
+ let len = 0;
30880
+ for (let i = 0; i + 1 < s.length; i++) len += Math.hypot(s[i + 1].x - s[i].x, s[i + 1].y - s[i].y);
30881
+ n = Math.min(64, Math.max(2, Math.ceil(Math.sqrt(len / Math.max(tol, 1e-3)))));
30882
+ }
30883
+ for (let i = 0; i < n; i++) pts.push(evalSeg(s, i / n));
30884
+ }
30885
+ for (let i = 0; i < pts.length; i++) {
30886
+ const p = pts[i], q = pts[(i + 1) % pts.length];
30887
+ edges.push({ x0: p.x, y0: p.y, x1: q.x, y1: q.y, c: ci, i, n: pts.length });
30888
+ }
30889
+ });
30890
+ for (let i = 0; i < edges.length; i++) {
30891
+ const e = edges[i];
30892
+ for (let j = i + 1; j < edges.length; j++) {
30893
+ const f = edges[j];
30894
+ if (e.c === f.c && (j - i === 1 || i === 0 && f.i === f.n - 1)) continue;
30895
+ if (Math.max(e.x0, e.x1) < Math.min(f.x0, f.x1) || Math.max(f.x0, f.x1) < Math.min(e.x0, e.x1)) continue;
30896
+ if (Math.max(e.y0, e.y1) < Math.min(f.y0, f.y1) || Math.max(f.y0, f.y1) < Math.min(e.y0, e.y1)) continue;
30897
+ if (segmentsCross(e.x0, e.y0, e.x1, e.y1, f.x0, f.y0, f.x1, f.y1)) return true;
30898
+ }
30899
+ }
30900
+ return false;
30901
+ }
30902
+ function windingAt(q, contours) {
30903
+ const dir2 = { x: 1, y: 0 };
30904
+ let w = 0;
30905
+ for (const contour of contours) {
30906
+ for (const s of contour) {
30907
+ for (const c of signChanges(sideCoeffs(s, q, dir2), true)) {
30908
+ if (evalSeg(s, c.t).x > q.x) w += c.after > 0 ? 1 : -1;
30909
+ }
30910
+ }
30911
+ }
30912
+ return w;
30913
+ }
30914
+ function orientBoundaries(contours, fillRule) {
30915
+ const filled = (q) => {
30916
+ const w = windingAt(q, contours);
30917
+ return fillRule === "evenodd" ? (w & 1) !== 0 : w !== 0;
30918
+ };
30919
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
30920
+ for (const c of contours) for (const s of c) for (const p of s) {
30921
+ minX = Math.min(minX, p.x);
30922
+ maxX = Math.max(maxX, p.x);
30923
+ minY = Math.min(minY, p.y);
30924
+ maxY = Math.max(maxY, p.y);
30925
+ }
30926
+ const eps = Math.max(Math.hypot(maxX - minX, maxY - minY) * 1e-6, 1e-9);
30927
+ const out = [];
30928
+ for (const contour of contours) {
30929
+ let probe = contour[0];
30930
+ let best = -1;
30931
+ for (const s of contour) {
30932
+ const len = Math.hypot(s[s.length - 1].x - s[0].x, s[s.length - 1].y - s[0].y);
30933
+ if (len > best) {
30934
+ best = len;
30935
+ probe = s;
30936
+ }
30937
+ }
30938
+ const p = evalSeg(probe, 0.5);
30939
+ let tan = derivSeg(probe, 0.5);
30940
+ const tl = Math.hypot(tan.x, tan.y);
30941
+ if (tl === 0) continue;
30942
+ tan = { x: tan.x / tl, y: tan.y / tl };
30943
+ const left = filled({ x: p.x - tan.y * eps, y: p.y + tan.x * eps });
30944
+ const right = filled({ x: p.x + tan.y * eps, y: p.y - tan.x * eps });
30945
+ if (left && !right) out.push(contour);
30946
+ else if (right && !left) out.push(reverseContour(contour));
30947
+ }
30948
+ return out;
30949
+ }
30950
+ function findCrossings(boundaries, a, b) {
30951
+ const d = { x: b.x - a.x, y: b.y - a.y };
30952
+ const dd = d.x * d.x + d.y * d.y;
30953
+ const out = [];
30954
+ const push = (contour, seg, t, before, after) => {
30955
+ const pt = evalSeg(boundaries[contour][seg], t);
30956
+ const s = ((pt.x - a.x) * d.x + (pt.y - a.y) * d.y) / dd;
30957
+ if (s < 0 || s > 1) return;
30958
+ out.push({ contour, seg, t, pt, s, enters: before > 0 && after < 0 });
30959
+ };
30960
+ boundaries.forEach((contour, ci) => {
30961
+ const fs = contour.map((s) => sideCoeffs(s, a, d));
30962
+ const n = contour.length;
30963
+ fs.forEach((f, si) => {
30964
+ for (const c of signChanges(f, false)) push(ci, si, c.t, c.before, c.after);
30965
+ if (f[0] !== 0) return;
30966
+ const after = endSide(f, true);
30967
+ if (after === 0) return;
30968
+ let before = 0;
30969
+ for (let k = 1; k <= n && before === 0; k++) before = endSide(fs[(si - k + n) % n], false);
30970
+ if (before !== 0 && before !== after) push(ci, si, 0, before, after);
30971
+ });
30972
+ });
30973
+ return out.sort((x, y) => x.s - y.s);
30974
+ }
30975
+ function traceLoops(boundaries, partner) {
30976
+ const byContour = /* @__PURE__ */ new Map();
30977
+ for (const c of partner.keys()) {
30978
+ const list2 = byContour.get(c.contour) ?? [];
30979
+ list2.push(c);
30980
+ byContour.set(c.contour, list2);
30981
+ }
30982
+ const arcFrom = /* @__PURE__ */ new Map();
30983
+ const untouched = [];
30984
+ boundaries.forEach((contour, ci) => {
30985
+ const cuts = byContour.get(ci);
30986
+ if (!cuts) {
30987
+ untouched.push(contour);
30988
+ return;
30989
+ }
30990
+ cuts.sort((x, y) => x.seg - y.seg || x.t - y.t);
30991
+ for (let k = 0; k < cuts.length; k++) {
30992
+ const from = cuts[k], to = cuts[(k + 1) % cuts.length];
30993
+ arcFrom.set(from, { segs: arcBetween(contour, from, to, cuts.length === 1), end: to });
30994
+ }
30995
+ });
30996
+ const loops = [];
30997
+ const seen = /* @__PURE__ */ new Set();
30998
+ for (const start of arcFrom.keys()) {
30999
+ if (seen.has(start)) continue;
31000
+ const loop = [];
31001
+ let at = start;
31002
+ while (!seen.has(at)) {
31003
+ seen.add(at);
31004
+ const arc = arcFrom.get(at);
31005
+ loop.push(...arc.segs);
31006
+ const other = partner.get(arc.end);
31007
+ loop.push([arc.end.pt, other.pt]);
31008
+ at = other;
31009
+ }
31010
+ loops.push(loop);
31011
+ }
31012
+ return { loops, untouched };
31013
+ }
31014
+ function arcBetween(contour, from, to, whole) {
31015
+ const out = [];
31016
+ const add = (s) => {
31017
+ if (s) out.push(s);
30171
31018
  };
30172
- const pieces = [sideA, sideB].filter(isSubstantial);
30173
- return pieces.length === 2 ? pieces : null;
31019
+ if (!whole && to.seg === from.seg && to.t > from.t) {
31020
+ add(subSeg(contour[from.seg], from.t, to.t, from.pt, to.pt));
31021
+ return out;
31022
+ }
31023
+ const n = contour.length;
31024
+ const first = contour[from.seg];
31025
+ add(subSeg(first, from.t, 1, from.pt, first[first.length - 1]));
31026
+ for (let i = (from.seg + 1) % n; i !== to.seg; i = (i + 1) % n) out.push(contour[i]);
31027
+ const last = contour[to.seg];
31028
+ add(subSeg(last, 0, to.t, last[0], to.pt));
31029
+ return out;
31030
+ }
31031
+ function assembleRegions(outers, holes) {
31032
+ const regions = outers.map((o) => ({ loops: [o], ring: sampleContour(o), area: signedArea2(o) }));
31033
+ for (const h of holes) {
31034
+ const hp = evalSeg(h[0], 0.5);
31035
+ let best = null;
31036
+ for (const r of regions) {
31037
+ if (pointInPolygon(r.ring, hp.x, hp.y) && (!best || r.area < best.area)) best = r;
31038
+ }
31039
+ best?.loops.push(h);
31040
+ }
31041
+ return regions.map((r) => r.loops);
30174
31042
  }
30175
- function withFillRule(p, fillRule) {
30176
- return p.fillRule === fillRule ? p : { ...p, fillRule };
31043
+ var regionArea = (r) => r.reduce((s, c) => s + signedArea2(c), 0);
31044
+ function toPath(loops, fillRule) {
31045
+ const cmds = [];
31046
+ const xs = [];
31047
+ for (const loop of loops) {
31048
+ cmds.push(PATH_M);
31049
+ xs.push(loop[0][0].x, loop[0][0].y);
31050
+ for (const s of loop) {
31051
+ cmds.push(s.length === 2 ? PATH_L : s.length === 3 ? PATH_Q : PATH_C);
31052
+ for (let i = 1; i < s.length; i++) xs.push(s[i].x, s[i].y);
31053
+ }
31054
+ cmds.push(PATH_Z);
31055
+ }
31056
+ return { kind: "polygon", commands: new Uint8Array(cmds), coords: new Float32Array(xs), fillRule };
30177
31057
  }
30178
31058
 
30179
31059
  // src/features/paths/unionBoundsPath.ts
@@ -30471,13 +31351,13 @@ function findSpan(n, p, u, knots) {
30471
31351
  if (u <= knots[p]) return p;
30472
31352
  let lo = p;
30473
31353
  let hi = n;
30474
- let mid2 = lo + hi >> 1;
30475
- while (u < knots[mid2] || u >= knots[mid2 + 1]) {
30476
- if (u < knots[mid2]) hi = mid2;
30477
- else lo = mid2;
30478
- mid2 = lo + hi >> 1;
31354
+ let mid = lo + hi >> 1;
31355
+ while (u < knots[mid] || u >= knots[mid + 1]) {
31356
+ if (u < knots[mid]) hi = mid;
31357
+ else lo = mid;
31358
+ mid = lo + hi >> 1;
30479
31359
  }
30480
- return mid2;
31360
+ return mid;
30481
31361
  }
30482
31362
  function basisFunctions(i, u, p, knots) {
30483
31363
  const N = new Array(p + 1).fill(0);
@@ -32168,7 +33048,7 @@ function useRig(options) {
32168
33048
  }
32169
33049
 
32170
33050
  // src/layout/snaps.ts
32171
- function dist22(a, b) {
33051
+ function dist2(a, b) {
32172
33052
  const dx = a.x - b.x;
32173
33053
  const dy = a.y - b.y;
32174
33054
  return dx * dx + dy * dy;
@@ -32176,9 +33056,9 @@ function dist22(a, b) {
32176
33056
  function nearestOf(targets, pointer) {
32177
33057
  if (targets.length === 0) return null;
32178
33058
  let best = targets[0];
32179
- let bestD = dist22(best.origin, pointer);
33059
+ let bestD = dist2(best.origin, pointer);
32180
33060
  for (let i = 1; i < targets.length; i++) {
32181
- const d = dist22(targets[i].origin, pointer);
33061
+ const d = dist2(targets[i].origin, pointer);
32182
33062
  if (d < bestD) {
32183
33063
  bestD = d;
32184
33064
  best = targets[i];
@@ -32198,7 +33078,7 @@ function nearestWithin(opts) {
32198
33078
  pickTarget(targets, pointer) {
32199
33079
  const got = nearestOf(targets, pointer);
32200
33080
  if (got === null) return null;
32201
- return dist22(got.origin, pointer) <= tol2 ? got : null;
33081
+ return dist2(got.origin, pointer) <= tol2 ? got : null;
32202
33082
  }
32203
33083
  };
32204
33084
  }
@@ -32487,6 +33367,6 @@ function mergeContributions(...bundles) {
32487
33367
  return out;
32488
33368
  }
32489
33369
 
32490
- export { ACTION_GLYPHS, ANCHOR_HIT_BASE_PX, ActionsProviderIfRoot, AlignBottomIcon, AlignCenterXIcon, AlignCenterYIcon, AlignLeftIcon, AlignRightIcon, AlignTopIcon, ArrowIcon, BUILTIN_MARKERS, BUNDLE_TOOLS, BringForwardIcon, BringToFrontIcon, CROSSHAIR_HALO, CURVE_REPS, CanvasView, ColorOverrideRegistry, CopyIcon, CropIcon, CursorCoordsHud, CutIcon, DEFAULT_DEBUG_STROKES, DEFAULT_DEBUG_THEME, 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, DeleteIcon, DepRegistryProviderIfRoot, DeviceProfileProvider, DistributeHorizontalIcon, DistributeVerticalIcon, DivideIcon, DuplicateIcon, EASINGS, EFFECT_KIT_UNIFORMS, EFFECT_VERT_SRC, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, FlipXIcon, FlipYIcon, GHOST_STROKE, GroupIcon, HANDLE_BASE_PX, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_JOINT, IDENTITY_POSE_COMPOSITION, ImageIcon, IntersectIcon, KIT_SHAPE_KINDS, KIT_STANDARD_ACTION_IDS, LassoIcon, LineIcon, MESH_BAKE_SIZE, MESH_GRADIENT_KIND, MINIMAP_CENTER, MINIMAP_PAN, MIXED, MOVE_ANCHORS, MinimapCanvas, OUTLINE_MIN_SCREEN_PX, PAINTED_CURSOR_LAYER_ID, PATH_ANCHOR_CHROME_ID, PasteIcon, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_POSE_COMPOSITION, RIGID_POSE_COMPOSITION, ROTATION_HANDLE_BASE_PX, RectIcon, RedoIcon, SPRING_PRESETS, SPRITE_STRIDE, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, SendBackwardIcon, SendToBackIcon, ShaderCompileError, StarIcon, SubtractIcon, TOOL_PREF_KINDS, TextIcon, UNION_OF_CHILDREN, UndoIcon, UngroupIcon, UnionIcon, UnknownIcon, VERSION, VIEW_ANIMATION_KEY, WHEEL_ZOOM_STEP, WeaselProvider, WeaselRenderer, ZOOM_FLOOR, _resetMarkersForTests, _resetPaintKindsForTests, actionIs, actionShortcuts, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyStyleToRange, areaSelectAction, arrayAdapter, asNodeId, asPaint, bakeMesh, bezierCubic, bezierQuadratic, bindRig, blendPoses, blur, buildChromeCtx, buildGradientRamp, buildSceneViewCommands, canBringForward, canHover, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, centerRootOn, charOffsetToDomPosition, circlePath, clampView, clearSelectionAction, clipboardCopyAction, clipboardCutAction, clipboardPasteAction, cloneAction, coarsePointer, composeAffordanceLayer, composePath, composeRectPose, composeRigidPose, composeSelectionPose, composeWorldPose, computeFitView, computeFitViewport, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, cornerWeights, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createKeyState, createLinkedCursorContribution, createMinimapContribution, createMoveToIndexOp, createNodeProperties, createNodeRouting, createPaintedCursorLayer, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createPoseFeed, createPoseOverrides, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createSlew, createTextLayer, createTiledLayer, createVelocityTracker, createViewResolver, createViewportLayer, crosshairRects, cubicBezierEasing, cullDrawCommands, cycleFill, cycleVertexColors, decomposePath, decomposeRectPose, decomposeRigidPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, defineTool, defineViewportTool, definesFrame, deriveAlignmentGuides, deriveParallaxView, derivedDepOf, derivedPose, documentPose, 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, effectivePose, effectiveRangeStyle, enterTextEditAction, enumerateAnchors, evalPatch, fillInPoseFrame, fillToBoundsFrame, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, flattenPreviews, 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, insertPreviewExtent, interpolateView, isBuiltinToolPref, isGradientFill, isLayerPainted, isLayerVisible, isMeshGradientFill, isPatternSpec, isTensorPatch, isValidPatch, isVectorImageSrc, keySpecShortcut, lassoSelectAction, linear, listMarkers, listPaintKinds, makePinchZoomAction, makeViewportWheelPanAction, makeViewportZoomAction, markerDrawCommands, markerInset, markerKeyOf, markerSites, mat3, matchAlignment, meanScale, mergeAlphaFromPrev, mergeContributions, meshBounds, meshGradientXml, minimapCenterAction, minimapPanAction, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, nodeHasFlag, none, normalizeHex, normalizeView, normalizeZoom, not, nurbs, openFilePicker, or, paintAlpha, paintKindOf, paintWithAlpha, paintWithColor, parseColor, parseColorToRgba255, parseDebugFlags, patchBounds, patchCorner, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInTextPose, poseRotationOf, prefUnit, pressureToWidth, rainbowVertexColors, rebaseLocalPose, rectMarkerPath, registerContentHandler, registerEffect, registerMarker, registerNodeShape, registerPaintKind, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveEasing, resolveFillPattern, resolveMarkerSize, resolveOverlays, resolvePatternSpec, resolvePreviews, resolveSkeleton, resolveStrokeWidth, resolveVisibility, rgbaToHex, rigidRigApply, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotationDegreesUnit, rotationHandle, roundRectPath, roundToCell, runsToDom, sampleGradientStops, sampleTrack, sceneFromJSON, sceneNodeClientRect, sceneSelectionStore, sceneToAdapter, screenToWorld, seedMeshPatch, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, setFlagOverRange, shapeCoversPoint, slewToward, sliceAction, snapPoint, solid, solidVertexColors, spiro, splitPathByLine, splitSubpaths, springPose, springVertexColors, squarePath, staggerVertexColors, startThresholdDrag, strokeInsets, strokeOf, strokeWith, styleAtRange, subscribeImageReady, tessellate, tessellateStroke, textCommand, textCommandFromPose, textCommandFromRuns, tileGrid, tiledProject, toHex8, translateRectPose, trimPolyline, tweenPose, tweenVertexColors, unionBoundsPath, unionOfChildren, unionOfChildrenVia, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDeviceProfile, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHostAnchor, useHoverTracking, useImageTool, useKeyState, useKeybindings, useLassoTool, useLineTool, useNodeOverlayFrame, usePenTool, usePencilTool, usePinchGesture, usePointerStylus, usePolygonTool, usePoseCompositionDepSource, usePoseDescriptorDepSource, usePoseRun, usePublishSelection, useRectTool, useResizePolicy, useRig, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useViewAnimationOn, useVisibleRaf, viewToMat3, viewToTransform, viewZoom, viewportDragPanAction, viewportWheelPanAction, viewportZoomAction, viewportsAt, vignette, wheelPan, wheelZoom, wheelZoomFactor, when, withAlpha01, withCoord, withGradientKind, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
32491
- //# sourceMappingURL=chunk-MZAIDI6D.js.map
32492
- //# sourceMappingURL=chunk-MZAIDI6D.js.map
33370
+ export { ACTION_GLYPHS, ANCHOR_HIT_BASE_PX, AREA_SELECT_ID, ActionsProviderIfRoot, AlignBottomIcon, AlignCenterXIcon, AlignCenterYIcon, AlignLeftIcon, AlignRightIcon, AlignTopIcon, ArrowIcon, BUILTIN_MARKERS, BUILTIN_TOOL_IDS, BringForwardIcon, BringToFrontIcon, CROSSHAIR_HALO, CURVE_REPS, CanvasView, ColorOverrideRegistry, CopyIcon, CropIcon, CursorCoordsHud, CutIcon, DEFAULT_DEBUG_STROKES, DEFAULT_DEBUG_THEME, 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, DRAG_PAN_ID, DeleteIcon, DepRegistryProviderIfRoot, DeviceProfileProvider, DistributeHorizontalIcon, DistributeVerticalIcon, DivideIcon, DuplicateIcon, EASINGS, EFFECT_KIT_UNIFORMS, EFFECT_VERT_SRC, EllipseIcon, ExcludeIcon, EyedropperIcon, FALLBACK_FIT_VIEW, FEATURE_ACTION_IDS, FlipXIcon, FlipYIcon, GHOST_STROKE, GroupIcon, HANDLE_BASE_PX, HandIcon, IDENTITY_COLOR_MATRIX, IDENTITY_JOINT, IDENTITY_POSE_COMPOSITION, ImageIcon, IntersectIcon, KIT_SHAPE_KINDS, KIT_STANDARD_ACTION_IDS, LassoIcon, LineIcon, MESH_BAKE_SIZE, MESH_GRADIENT_KIND, MINIMAP_CENTER, MINIMAP_PAN, MIXED, MOVE_ANCHORS, MinimapCanvas, OUTLINE_MIN_SCREEN_PX, PAINTED_CURSOR_LAYER_ID, PATH_ANCHOR_CHROME_ID, PasteIcon, PenIcon, PencilIcon, PickHud, PointerProviderIfRoot, PolygonIcon, RECT_POSE_COMPOSITION, RIGID_POSE_COMPOSITION, ROTATION_HANDLE_BASE_PX, RectIcon, RedoIcon, SCENE_CANVAS_FEATURES, SELECTION_MOVE_ID, SELECTION_TRANSFORM_ID, SPRING_PRESETS, SPRITE_STRIDE, SceneCanvas, SceneViewCanvas, SelectIcon, SelectionContextProvider, SelectionContextProviderIfRoot, SendBackwardIcon, SendToBackIcon, ShaderCompileError, StarIcon, SubtractIcon, TOOL_DRIVEN_ACTION_IDS, TOOL_PREF_KINDS, TextIcon, UNION_OF_CHILDREN, UndoIcon, UngroupIcon, UnionIcon, UnknownIcon, VERSION, VIEW_ANIMATION_KEY, WHEEL_ZOOM_STEP, WeaselProvider, WeaselRenderer, ZOOM_FLOOR, _resetMarkersForTests, _resetPaintKindsForTests, actionIs, actionShortcuts, alignDeltaFor, alignInsertBehavior, alignMoveBehavior, alignResizeBehavior, always, and, animateLifecycle, animateOnSetPose, annulusSemiAxes, applyBooleanOp, applyStyleToRange, areaSelectAction, areaSelectContribution, arrayAdapter, asNodeId, asPaint, bakeMesh, bezierCubic, bezierQuadratic, bindRig, blendPoses, blur, buildChromeCtx, buildGradientRamp, buildSceneViewCommands, canBringForward, canHover, canSendBackward, capabilityAll, capabilityIn, capabilityIs, capabilityNot, caretIndexAt, cellAt, centerRootOn, charOffsetToDomPosition, circlePath, clampView, clearSelectionAction, clipboardCopyAction, clipboardCutAction, clipboardPasteAction, cloneAction, coarsePointer, composeAffordanceLayer, composePath, composeRectPose, composeRigidPose, composeSelectionPose, composeWorldPose, computeFitView, computeFitViewport, computeIndicatorCommand, computeWheelAction, cond, constrainTo45, containedThenNearest, cornerWeights, countPathAnchors, createCellHighlightLayer, createChildrenLayer, createCornerResizeAffordance, createDebugOverlayLayer, createDebugSink, createGridLayer, createGuidesLayer, createHistory2 as createHistory, createKeyState, createLinkedCursorContribution, createMinimapContribution, createMoveToIndexOp, createNodeProperties, createNodeRouting, createPaintedCursorLayer, createParallaxLayer, createPathAnchorAffordances, createPathEditingOverlayLayer, createPathLayer, createPenPreviewLayer, createPoseFeed, createPoseOverrides, createReorderOp, createRotationAffordance, createScene, createSelectionHandlesLayer, createSelectionOutlineLayer, createSelectionOverlayLayer, createSetDataOp, createSetLayerOp, createSetPathOp, createSetTextOp, createSlew, createTextLayer, createTiledLayer, createVelocityTracker, createViewResolver, createViewportLayer, crosshairRects, cubicBezierEasing, cullDrawCommands, cycleFill, cycleVertexColors, decomposePath, decomposeRectPose, decomposeRigidPose, defaultCommitAdapter, defaultDrawOne, defaultLabelTextRenderer, defaultNodeProperties, defaultNodeRouting, defaultVisibilityRules, definesFrame, deriveAlignmentGuides, deriveParallaxView, derivedDepOf, derivedPose, documentPose, domPositionToCharOffset, domToRuns, dragPanContribution, 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, effectivePose, effectiveRangeStyle, enterTextEditAction, enumerateAnchors, evalPatch, fillInPoseFrame, fillToBoundsFrame, findNodeShape, findShapeInk, findShapeSilhouette, fitTextPose, fitToBounds, fitViewToBounds, fitZoom, flattenPreviews, 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, insertPreviewExtent, interpolateView, isBuiltinToolPref, isGradientFill, isLayerPainted, isLayerVisible, isMeshGradientFill, isPatternSpec, isPlainObject, isTensorPatch, isValidPatch, isVectorImageSrc, keySpecShortcut, lassoSelectAction, linear, listGradientKinds, listMarkers, listPaintKinds, makePinchZoomAction, makeViewportWheelPanAction, makeViewportZoomAction, markerInset, markerKeyOf, markerRegistry, markerSites, mat3, matchAlignment, meanScale, mergeAlphaFromPrev, mergeContributions, meshBounds, meshFromStops, meshGradientXml, meshStops, minimapCenterAction, minimapPanAction, modeDecorationLayer, modeIn, modeIs, modeNot, modifierHeld, momentum, moveAction, multiActive, nearest, nearestWithin, nestedHitTester, never, nodeHasFlag, none, normalizeHex, normalizeView, normalizeZoom, not, nurbs, onEmptyCanvas, openFilePicker, or, paintAlpha, paintKindOf, paintKindRegistry, paintWithAlpha, paintWithColor, parseColor, parseColorToRgba255, parseDebugFlags, patchBounds, patchCorner, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathDivide, pathExclude, pathFromD, pathInPoseFrame, pathInWorld, pathIntersect, pathIntersectsPolygon, pathIntersectsRect, pathOriginProjection, pathSubtract, pathToAnchors, pathUnion, pickTopMostHit, pinchZoomAction, planPixelRender, pointInTextPose, poseRotationOf, prefUnit, pressureToWidth, programSourceRegistry, rainbowVertexColors, rebaseLocalPose, rectMarkerPath, registerContentHandler, registerEffect, registerMarker, registerNodeShape, registerPaintKind, registerProgram, renderLabel, renderSceneToCanvas, renderSceneToPixels, resizeAction, resolveColor, resolveEasing, resolveFillPattern, resolveMarkerSize, resolveOverlays, resolvePatternSpec, resolvePreviews, resolveSkeleton, resolveStrokeWidth, resolveVisibility, rgbaToHex, rigidRigApply, rotateAction, rotateAroundAABBCenter, rotatePathAround, rotationDegreesUnit, rotationHandle, roundRectPath, roundToCell, runsToDom, sampleGradientStops, sampleTrack, sceneFromJSON, sceneNodeClientRect, sceneSelectionStore, sceneToAdapter, screenToWorld, seedMeshPatch, selectFromLasso, selectionAtLeast, selectionEmpty, selectionIs, selectionMoveBindings, selectionMoveContribution, selectionTransformBindings, selectionTransformContribution, setFlagOverRange, shapeCoversPoint, slewToward, sliceAction, snapPoint, solid, solidVertexColors, spiro, splitPathBySegment, splitSubpaths, springPose, springVertexColors, squarePath, staggerVertexColors, startThresholdDrag, strokeInsets, strokeOf, strokeWith, styleAtRange, subscribeImageReady, subscribeToSources, switchGradientKind, tessellate, tessellateStroke, textCommand, textCommandFromPose, textCommandFromRuns, tileGrid, tiledProject, toHex8, translateRectPose, trimPolyline, tweenPose, tweenVertexColors, unionBoundsPath, unionOfChildren, unionOfChildrenVia, useAlign, useAnimator, useArrayAdapter, useAutoCenter, useBooleans, useBooleansAdapter, useCanvasFocus, useCanvasSize, useDecayLoop, useDeviceProfile, useDistribute, useDragGesture, useDragHandle, useDragRadial, useDragRect, useDropZone, useEllipseTool, useEyedropperTool, useGridCellHover, useGuides, useHandTool, useHandleDrag, useHostAnchor, useHoverTracking, useImageTool, useKeyState, useKeybindings, useLassoTool, useLineTool, useNodeOverlayFrame, usePenTool, usePencilTool, usePointerStylus, usePolygonTool, usePoseCompositionDepSource, usePoseDescriptorDepSource, usePoseRun, usePublishSelection, useRectTool, useResizePolicy, useRig, useScene, useSceneAdapter, useSceneTextEdit, useSelectTool, useSelection, useSelectionContext, useSimulation, useSliceDep, useStandardActions, useStarTool, useTextEdit, useTextTool, useTools, useVelocityTracker, useViewAnimation, useViewAnimationOn, useVisibleRaf, viewToMat3, viewToTransform, viewZoom, viewportDragPanAction, viewportWheelPanAction, viewportZoomAction, viewportsAt, vignette, wheelPan, wheelZoom, wheelZoomFactor, when, withAlpha01, withCoord, withGradientKind, workspaceTintLayer, worldEditToStorage, worldPoseLookup, worldToScreen, zoomAt, zoomAtLeast };
33371
+ //# sourceMappingURL=chunk-G4ZGQMY7.js.map
33372
+ //# sourceMappingURL=chunk-G4ZGQMY7.js.map