@pixerate/editor 0.21.0 → 0.23.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 (114) hide show
  1. package/README.md +100 -13
  2. package/THIRD_PARTY_NOTICES.md +88 -0
  3. package/dist/agent/index.cjs +86 -111
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +69 -211
  6. package/dist/agent/index.d.ts +69 -211
  7. package/dist/agent/index.js +77 -102
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
  10. package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
  11. package/dist/canvas/index.cjs +2112 -60
  12. package/dist/canvas/index.cjs.map +1 -1
  13. package/dist/canvas/index.d.cts +712 -181
  14. package/dist/canvas/index.d.ts +712 -181
  15. package/dist/canvas/index.js +2105 -53
  16. package/dist/canvas/index.js.map +1 -1
  17. package/dist/chunk-26BVFKHU.cjs +453 -0
  18. package/dist/chunk-26BVFKHU.cjs.map +1 -0
  19. package/dist/chunk-2H7ZHPEJ.js +762 -0
  20. package/dist/chunk-2H7ZHPEJ.js.map +1 -0
  21. package/dist/chunk-7DWMJCAT.js +453 -0
  22. package/dist/chunk-7DWMJCAT.js.map +1 -0
  23. package/dist/{chunk-TVRC4KVT.cjs → chunk-AA4ZEVCC.cjs} +845 -90
  24. package/dist/chunk-AA4ZEVCC.cjs.map +1 -0
  25. package/dist/chunk-ARRTP33I.js +101 -0
  26. package/dist/chunk-ARRTP33I.js.map +1 -0
  27. package/dist/{chunk-LA7PEGU3.cjs → chunk-CL37JGFW.cjs} +4 -4
  28. package/dist/{chunk-LA7PEGU3.cjs.map → chunk-CL37JGFW.cjs.map} +1 -1
  29. package/dist/chunk-CPOKFNBG.cjs +101 -0
  30. package/dist/chunk-CPOKFNBG.cjs.map +1 -0
  31. package/dist/chunk-G2I4QJOT.cjs +762 -0
  32. package/dist/chunk-G2I4QJOT.cjs.map +1 -0
  33. package/dist/{chunk-4QPLEUL2.cjs → chunk-GVSMRPHB.cjs} +1046 -54
  34. package/dist/chunk-GVSMRPHB.cjs.map +1 -0
  35. package/dist/{chunk-KYRZDWM4.js → chunk-JJDLPEWL.js} +2 -2
  36. package/dist/{chunk-GVW4P7BG.js → chunk-K3R5NG2D.js} +448 -54
  37. package/dist/chunk-K3R5NG2D.js.map +1 -0
  38. package/dist/{chunk-3TYMNULT.js → chunk-MJZBNWG7.js} +807 -52
  39. package/dist/chunk-MJZBNWG7.js.map +1 -0
  40. package/dist/{chunk-A7DKIA72.js → chunk-QBJ22YZR.js} +1059 -93
  41. package/dist/chunk-QBJ22YZR.js.map +1 -0
  42. package/dist/{chunk-CYFJCKC4.js → chunk-QGHGJ56O.js} +997 -5
  43. package/dist/chunk-QGHGJ56O.js.map +1 -0
  44. package/dist/{chunk-JYRIOFEN.cjs → chunk-RIKBJLS2.cjs} +1064 -98
  45. package/dist/chunk-RIKBJLS2.cjs.map +1 -0
  46. package/dist/{chunk-U6NDQFOK.cjs → chunk-XDMXXC4Z.cjs} +569 -175
  47. package/dist/chunk-XDMXXC4Z.cjs.map +1 -0
  48. package/dist/commands/index.cjs +860 -0
  49. package/dist/commands/index.cjs.map +1 -0
  50. package/dist/commands/index.d.cts +592 -0
  51. package/dist/commands/index.d.ts +592 -0
  52. package/dist/commands/index.js +860 -0
  53. package/dist/commands/index.js.map +1 -0
  54. package/dist/{controller-OUdxPY-X.d.ts → controller-pePCJLT1.d.cts} +216 -11
  55. package/dist/{controller-OUdxPY-X.d.cts → controller-pePCJLT1.d.ts} +216 -11
  56. package/dist/extensions/index.cjs +14 -4
  57. package/dist/extensions/index.cjs.map +1 -1
  58. package/dist/extensions/index.d.cts +3 -3
  59. package/dist/extensions/index.d.ts +3 -3
  60. package/dist/extensions/index.js +13 -3
  61. package/dist/history/index.cjs +12 -2
  62. package/dist/history/index.cjs.map +1 -1
  63. package/dist/history/index.d.cts +211 -7
  64. package/dist/history/index.d.ts +211 -7
  65. package/dist/history/index.js +13 -3
  66. package/dist/image-editor/index.cjs +418 -13
  67. package/dist/image-editor/index.cjs.map +1 -1
  68. package/dist/image-editor/index.d.cts +154 -3
  69. package/dist/image-editor/index.d.ts +154 -3
  70. package/dist/image-editor/index.js +417 -12
  71. package/dist/image-editor/index.js.map +1 -1
  72. package/dist/{index-k3mFbPx_.d.cts → index-CCr_z9kF.d.cts} +3 -3
  73. package/dist/{index-Ng1pG7JE.d.ts → index-Ck6ajwXh.d.ts} +3 -3
  74. package/dist/index.cjs +275 -83
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.d.cts +296 -47
  77. package/dist/index.d.ts +296 -47
  78. package/dist/index.js +266 -74
  79. package/dist/index.js.map +1 -1
  80. package/dist/registry-D5-R90p0.d.cts +209 -0
  81. package/dist/registry-D5-R90p0.d.ts +209 -0
  82. package/dist/{registry-iAHbqkYt.d.cts → registry-GJI4gToU.d.cts} +15 -4
  83. package/dist/{registry-iAHbqkYt.d.ts → registry-GJI4gToU.d.ts} +15 -4
  84. package/dist/serializers/index.cjs +3 -3
  85. package/dist/serializers/index.d.cts +1 -1
  86. package/dist/serializers/index.d.ts +1 -1
  87. package/dist/serializers/index.js +2 -2
  88. package/dist/spreadsheet/index.cjs +23 -3
  89. package/dist/spreadsheet/index.cjs.map +1 -1
  90. package/dist/spreadsheet/index.d.cts +97 -10
  91. package/dist/spreadsheet/index.d.ts +97 -10
  92. package/dist/spreadsheet/index.js +22 -2
  93. package/dist/{track-changes-G8JcWB8b.d.cts → track-changes-BSF58RK0.d.cts} +92 -9
  94. package/dist/{track-changes-G8JcWB8b.d.ts → track-changes-BSF58RK0.d.ts} +92 -9
  95. package/dist/{types-DMe0vFB1.d.cts → types-DX4Mh4SL.d.cts} +1 -1
  96. package/dist/{types-DMe0vFB1.d.ts → types-DX4Mh4SL.d.ts} +1 -1
  97. package/dist/types-GT5a5YAX.d.cts +86 -0
  98. package/dist/types-GT5a5YAX.d.ts +86 -0
  99. package/package.json +8 -2
  100. package/dist/chunk-2TWGLC5K.cjs +0 -262
  101. package/dist/chunk-2TWGLC5K.cjs.map +0 -1
  102. package/dist/chunk-3TYMNULT.js.map +0 -1
  103. package/dist/chunk-4QPLEUL2.cjs.map +0 -1
  104. package/dist/chunk-A7DKIA72.js.map +0 -1
  105. package/dist/chunk-CYFJCKC4.js.map +0 -1
  106. package/dist/chunk-GVW4P7BG.js.map +0 -1
  107. package/dist/chunk-JYRIOFEN.cjs.map +0 -1
  108. package/dist/chunk-KAADNAPF.js +0 -262
  109. package/dist/chunk-KAADNAPF.js.map +0 -1
  110. package/dist/chunk-TVRC4KVT.cjs.map +0 -1
  111. package/dist/chunk-U6NDQFOK.cjs.map +0 -1
  112. package/dist/types-BZgcDsC5.d.cts +0 -42
  113. package/dist/types-BZgcDsC5.d.ts +0 -42
  114. /package/dist/{chunk-KYRZDWM4.js.map → chunk-JJDLPEWL.js.map} +0 -0
@@ -1,4 +1,8 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5;
2
+
3
+
4
+ var _chunk26BVFKHUcjs = require('../chunk-26BVFKHU.cjs');
5
+
2
6
 
3
7
 
4
8
 
@@ -1073,6 +1077,10 @@ function invertNodeUpdates(nodes, updates) {
1073
1077
  }
1074
1078
 
1075
1079
  // src/canvas/guides.ts
1080
+ function snapToGrid(value, spacing, origin = 0) {
1081
+ if (!(spacing > 0) || !Number.isFinite(spacing)) return value;
1082
+ return Math.round((value - origin) / spacing) * spacing + origin;
1083
+ }
1076
1084
  function buildAxis(entries) {
1077
1085
  entries.sort((a, b) => a.value - b.value);
1078
1086
  return {
@@ -1229,8 +1237,10 @@ function computeSnapGuides(moving, targets, options = {}) {
1229
1237
  };
1230
1238
  const sx = snapAxis(true);
1231
1239
  const sy = snapAxis(false);
1232
- const dx = _nullishCoalesce(sx.delta, () => ( 0));
1233
- const dy = _nullishCoalesce(sy.delta, () => ( 0));
1240
+ const grid = _nullishCoalesce(options.gridSize, () => ( 0));
1241
+ const gridDelta = (v, origin) => snapToGrid(v, grid, origin) - v;
1242
+ const dx = _nullishCoalesce(sx.delta, () => ( (grid > 0 ? gridDelta(moving.x, _nullishCoalesce(_optionalChain([options, 'access', _46 => _46.gridOrigin, 'optionalAccess', _47 => _47.x]), () => ( 0))) : 0)));
1243
+ const dy = _nullishCoalesce(sy.delta, () => ( (grid > 0 ? gridDelta(moving.y, _nullishCoalesce(_optionalChain([options, 'access', _48 => _48.gridOrigin, 'optionalAccess', _49 => _49.y]), () => ( 0))) : 0)));
1234
1244
  const snapped = { x: moving.x + dx, y: moving.y + dy, width: moving.width, height: moving.height };
1235
1245
  const guides = [];
1236
1246
  const alignmentGuides = (horizontal) => {
@@ -1244,12 +1254,12 @@ function computeSnapGuides(moving, targets, options = {}) {
1244
1254
  if (useCentres) {
1245
1255
  lines.push({ value: start + size / 2, kind: "centre", axis: horizontal ? index.centreX : index.centreY });
1246
1256
  }
1247
- for (const line of lines) {
1248
- const aligned = matches(line.axis, line.value, eps);
1257
+ for (const line2 of lines) {
1258
+ const aligned = matches(line2.axis, line2.value, eps);
1249
1259
  if (aligned.length === 0) continue;
1250
1260
  const span = _chunkWADCJ7A7cjs.boundsOf.call(void 0, [snapped, ...aligned]);
1251
1261
  guides.push(
1252
- horizontal ? { orientation: "vertical", kind: line.kind, x1: line.value, y1: span.y, x2: line.value, y2: span.y + span.height } : { orientation: "horizontal", kind: line.kind, x1: span.x, y1: line.value, x2: span.x + span.width, y2: line.value }
1262
+ horizontal ? { orientation: "vertical", kind: line2.kind, x1: line2.value, y1: span.y, x2: line2.value, y2: span.y + span.height } : { orientation: "horizontal", kind: line2.kind, x1: span.x, y1: line2.value, x2: span.x + span.width, y2: line2.value }
1253
1263
  );
1254
1264
  }
1255
1265
  };
@@ -1390,16 +1400,16 @@ function findConnectorSnap(pointer, targets, options = {}) {
1390
1400
  if (pointer.x < rect.x - radius || pointer.x > rect.x + rect.width + radius || pointer.y < rect.y - radius || pointer.y > rect.y + rect.height + radius) {
1391
1401
  continue;
1392
1402
  }
1393
- const inside = pointer.x >= rect.x && pointer.x <= rect.x + rect.width && pointer.y >= rect.y && pointer.y <= rect.y + rect.height;
1403
+ const inside2 = pointer.x >= rect.x && pointer.x <= rect.x + rect.width && pointer.y >= rect.y && pointer.y <= rect.y + rect.height;
1394
1404
  for (const position of _nullishCoalesce(target.positions, () => ( all))) {
1395
1405
  const point = options.mode === "edge" ? closestPointOnSide(rect, position, pointer) : handlePoint(rect, position);
1396
1406
  const sidePoint = closestPointOnSide(rect, position, pointer);
1397
1407
  const rank = Math.hypot(pointer.x - sidePoint.x, pointer.y - sidePoint.y);
1398
1408
  const distance = Math.hypot(pointer.x - point.x, pointer.y - point.y);
1399
- if (!inside && rank > radius) continue;
1400
- const better = !best || inside && !best.inside || inside === best.inside && rank < best.rank;
1409
+ if (!inside2 && rank > radius) continue;
1410
+ const better = !best || inside2 && !best.inside || inside2 === best.inside && rank < best.rank;
1401
1411
  if (better) {
1402
- best = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside, rank };
1412
+ best = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside: inside2, rank };
1403
1413
  }
1404
1414
  }
1405
1415
  }
@@ -1505,7 +1515,7 @@ var CanvasScenesStore = (_class3 = class {
1505
1515
  this.scenes = scenes;
1506
1516
  if (this.currentId && !scenes.some((s) => s.id === this.currentId)) this.currentId = null;
1507
1517
  this.cached = this.snapshot();
1508
- _optionalChain([this, 'access', _46 => _46.onChange, 'optionalCall', _47 => _47(this.cached)]);
1518
+ _optionalChain([this, 'access', _50 => _50.onChange, 'optionalCall', _51 => _51(this.cached)]);
1509
1519
  for (const listener of this.listeners) listener(this.cached);
1510
1520
  }
1511
1521
  getState() {
@@ -1533,7 +1543,7 @@ var CanvasScenesStore = (_class3 = class {
1533
1543
  add(input, index) {
1534
1544
  const scene = cloneScene({
1535
1545
  id: input.id && !this.getScene(input.id) ? input.id : this.createId(),
1536
- name: _optionalChain([input, 'access', _48 => _48.name, 'optionalAccess', _49 => _49.trim, 'call', _50 => _50()]) || this.nextDefaultName(),
1546
+ name: _optionalChain([input, 'access', _52 => _52.name, 'optionalAccess', _53 => _53.trim, 'call', _54 => _54()]) || this.nextDefaultName(),
1537
1547
  viewport: input.viewport,
1538
1548
  nodeIds: input.nodeIds
1539
1549
  });
@@ -1662,47 +1672,1720 @@ function fitViewportToNodes(nodes, size, ids, options = {}) {
1662
1672
  return bounds ? fitViewportToRect(bounds, size, options) : null;
1663
1673
  }
1664
1674
 
1665
- // src/canvas/arrangeShortcuts.ts
1666
- var DEFAULT_ARRANGE_SHORTCUTS = [
1667
- { action: "alignLeft", code: "KeyA", alt: true, label: "Alt+A" },
1668
- { action: "alignCenter", code: "KeyH", alt: true, label: "Alt+H" },
1669
- { action: "alignRight", code: "KeyD", alt: true, label: "Alt+D" },
1670
- { action: "alignTop", code: "KeyW", alt: true, label: "Alt+W" },
1671
- { action: "alignMiddle", code: "KeyV", alt: true, label: "Alt+V" },
1672
- { action: "alignBottom", code: "KeyS", alt: true, label: "Alt+S" },
1673
- { action: "distributeHorizontal", code: "KeyH", alt: true, shift: true, label: "Alt+Shift+H" },
1674
- { action: "distributeVertical", code: "KeyV", alt: true, shift: true, label: "Alt+Shift+V" },
1675
- { action: "lock", code: "KeyL", mod: true, label: "Mod+L" },
1676
- { action: "unlock", code: "KeyL", mod: true, alt: true, label: "Mod+Alt+L" },
1677
- { action: "bringForward", code: "BracketRight", mod: true, label: "Mod+]" },
1678
- { action: "sendBackward", code: "BracketLeft", mod: true, label: "Mod+[" },
1679
- { action: "bringToFront", code: "BracketRight", mod: true, alt: true, label: "Mod+Alt+]" },
1680
- { action: "sendToBack", code: "BracketLeft", mod: true, alt: true, label: "Mod+Alt+[" },
1681
- { action: "nextScene", code: "PageDown", label: "PageDown" },
1682
- { action: "previousScene", code: "PageUp", label: "PageUp" }
1683
- ];
1684
- var KEY_TO_CODE = {
1685
- "]": "BracketRight",
1686
- "[": "BracketLeft",
1687
- PageDown: "PageDown",
1688
- PageUp: "PageUp"
1675
+ // src/canvas/polygonBoolean.ts
1676
+ function ringSignedArea(ring) {
1677
+ let sum = 0;
1678
+ const n = ring.length;
1679
+ for (let i = 0; i < n; i++) {
1680
+ const [x1, y1] = ring[i];
1681
+ const [x2, y2] = ring[(i + 1) % n];
1682
+ sum += x1 * y2 - x2 * y1;
1683
+ }
1684
+ return sum / 2;
1685
+ }
1686
+ function polygonArea(polygon) {
1687
+ if (polygon.length === 0) return 0;
1688
+ let area = Math.abs(ringSignedArea(polygon[0]));
1689
+ for (let i = 1; i < polygon.length; i++) area -= Math.abs(ringSignedArea(polygon[i]));
1690
+ return area;
1691
+ }
1692
+ function multiPolygonArea(multi) {
1693
+ let total = 0;
1694
+ for (const polygon of multi) total += polygonArea(polygon);
1695
+ return total;
1696
+ }
1697
+ function multiPolygonBounds(input) {
1698
+ let minX = Infinity;
1699
+ let minY = Infinity;
1700
+ let maxX = -Infinity;
1701
+ let maxY = -Infinity;
1702
+ const visit = (ring) => {
1703
+ for (const [x, y] of ring) {
1704
+ if (x < minX) minX = x;
1705
+ if (y < minY) minY = y;
1706
+ if (x > maxX) maxX = x;
1707
+ if (y > maxY) maxY = y;
1708
+ }
1709
+ };
1710
+ for (const item of input) {
1711
+ if (item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0])) {
1712
+ for (const ring of item) visit(ring);
1713
+ } else {
1714
+ visit(item);
1715
+ }
1716
+ }
1717
+ return minX === Infinity ? null : { minX, minY, maxX, maxY };
1718
+ }
1719
+ function pointInRings(point, rings, fillRule = "nonzero") {
1720
+ const [px, py] = point;
1721
+ let winding = 0;
1722
+ for (const ring of rings) {
1723
+ const n = ring.length;
1724
+ for (let i = 0; i < n; i++) {
1725
+ const [ax, ay] = ring[i];
1726
+ const [bx, by] = ring[(i + 1) % n];
1727
+ if (ay <= py && by > py) {
1728
+ if (ax + (py - ay) / (by - ay) * (bx - ax) > px) winding++;
1729
+ } else if (by <= py && ay > py) {
1730
+ if (ax + (py - ay) / (by - ay) * (bx - ax) > px) winding--;
1731
+ }
1732
+ }
1733
+ }
1734
+ return fillRule === "evenodd" ? (winding & 1) === 1 : winding !== 0;
1735
+ }
1736
+ function pointInMultiPolygon(point, multi) {
1737
+ return multi.some((polygon) => pointInRings(point, polygon, "evenodd"));
1738
+ }
1739
+ function cleanRing(ring) {
1740
+ const out = [];
1741
+ for (const p of ring) {
1742
+ if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
1743
+ const last = out[out.length - 1];
1744
+ if (last && last[0] === p[0] && last[1] === p[1]) continue;
1745
+ out.push([p[0], p[1]]);
1746
+ }
1747
+ while (out.length > 1) {
1748
+ const first = out[0];
1749
+ const last = out[out.length - 1];
1750
+ if (first[0] === last[0] && first[1] === last[1]) out.pop();
1751
+ else break;
1752
+ }
1753
+ return out;
1754
+ }
1755
+ function isOperand(input) {
1756
+ return !Array.isArray(input) && typeof input === "object" && input !== null && Array.isArray(input.rings);
1757
+ }
1758
+ function normalizeInput(input) {
1759
+ if (isOperand(input)) {
1760
+ return {
1761
+ rings: input.rings.map(cleanRing).filter((r) => r.length >= 3),
1762
+ fillRule: _nullishCoalesce(input.fillRule, () => ( "nonzero"))
1763
+ };
1764
+ }
1765
+ const rings = [];
1766
+ for (const polygon of _nullishCoalesce(input, () => ( []))) {
1767
+ polygon.forEach((raw, index) => {
1768
+ const ring = cleanRing(raw);
1769
+ if (ring.length < 3) return;
1770
+ const area = ringSignedArea(ring);
1771
+ if (area === 0) return;
1772
+ const wantPositive = index === 0;
1773
+ rings.push(area > 0 === wantPositive ? ring : ring.reverse());
1774
+ });
1775
+ }
1776
+ return { rings, fillRule: "nonzero" };
1777
+ }
1778
+ var PointPool = (_class4 = class {
1779
+ constructor(eps) {;_class4.prototype.__init12.call(this);_class4.prototype.__init13.call(this);
1780
+ this.eps = eps;
1781
+ }
1782
+
1783
+ __init12() {this.vertices = []}
1784
+ __init13() {this.cells = /* @__PURE__ */ new Map()}
1785
+ get(x, y) {
1786
+ const cx = Math.floor(x / this.eps);
1787
+ const cy = Math.floor(y / this.eps);
1788
+ let best = null;
1789
+ let bestDist = Infinity;
1790
+ for (let dx = -1; dx <= 1; dx++) {
1791
+ for (let dy = -1; dy <= 1; dy++) {
1792
+ const ids = this.cells.get(`${cx + dx},${cy + dy}`);
1793
+ if (!ids) continue;
1794
+ for (const id of ids) {
1795
+ const v = this.vertices[id];
1796
+ const d = Math.hypot(v.x - x, v.y - y);
1797
+ if (d <= this.eps && d < bestDist) {
1798
+ best = v;
1799
+ bestDist = d;
1800
+ }
1801
+ }
1802
+ }
1803
+ }
1804
+ if (best) return best;
1805
+ const vertex = { id: this.vertices.length, x, y };
1806
+ this.vertices.push(vertex);
1807
+ const key = `${cx},${cy}`;
1808
+ const bucket = this.cells.get(key);
1809
+ if (bucket) bucket.push(vertex.id);
1810
+ else this.cells.set(key, [vertex.id]);
1811
+ return vertex;
1812
+ }
1813
+ }, _class4);
1814
+ function project(a, b, p) {
1815
+ const dx = b.x - a.x;
1816
+ const dy = b.y - a.y;
1817
+ const len2 = dx * dx + dy * dy;
1818
+ const t = len2 === 0 ? 0 : ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;
1819
+ const cx = a.x + t * dx;
1820
+ const cy = a.y + t * dy;
1821
+ return { t, dist: Math.hypot(p.x - cx, p.y - cy) };
1822
+ }
1823
+ function addSplit(splits, t, v) {
1824
+ const { a, b } = splits.segment;
1825
+ if (v.id === a.id || v.id === b.id) return;
1826
+ if (!splits.points.has(v.id)) splits.points.set(v.id, { t, v });
1827
+ }
1828
+ function splitPass(segments, pool, eps) {
1829
+ const order = segments.map((s, i) => ({
1830
+ i,
1831
+ minX: Math.min(s.a.x, s.b.x),
1832
+ maxX: Math.max(s.a.x, s.b.x),
1833
+ minY: Math.min(s.a.y, s.b.y),
1834
+ maxY: Math.max(s.a.y, s.b.y)
1835
+ })).sort((p, q) => p.minX - q.minX);
1836
+ const splits = segments.map((segment) => ({ segment, points: /* @__PURE__ */ new Map() }));
1837
+ for (let oi = 0; oi < order.length; oi++) {
1838
+ const bi = order[oi];
1839
+ const s1 = segments[bi.i];
1840
+ for (let oj = oi + 1; oj < order.length; oj++) {
1841
+ const bj = order[oj];
1842
+ if (bj.minX > bi.maxX + eps) break;
1843
+ if (bj.minY > bi.maxY + eps || bj.maxY < bi.minY - eps) continue;
1844
+ const s2 = segments[bj.i];
1845
+ const sharedA = s1.a.id === s2.a.id || s1.a.id === s2.b.id;
1846
+ const sharedB = s1.b.id === s2.a.id || s1.b.id === s2.b.id;
1847
+ if (sharedA && sharedB) continue;
1848
+ let touched = false;
1849
+ for (const [seg, other, split] of [
1850
+ [s1, s2, splits[bi.i]],
1851
+ [s2, s1, splits[bj.i]]
1852
+ ]) {
1853
+ for (const p of [other.a, other.b]) {
1854
+ if (p.id === seg.a.id || p.id === seg.b.id) continue;
1855
+ const { t: t2, dist: dist2 } = project(seg.a, seg.b, p);
1856
+ const len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);
1857
+ if (dist2 <= eps && t2 * len > eps && (1 - t2) * len > eps) {
1858
+ addSplit(split, t2, p);
1859
+ touched = true;
1860
+ }
1861
+ }
1862
+ }
1863
+ if (touched) continue;
1864
+ const d1x = s1.b.x - s1.a.x;
1865
+ const d1y = s1.b.y - s1.a.y;
1866
+ const d2x = s2.b.x - s2.a.x;
1867
+ const d2y = s2.b.y - s2.a.y;
1868
+ const denom = d1x * d2y - d1y * d2x;
1869
+ const len1 = Math.hypot(d1x, d1y);
1870
+ const len2 = Math.hypot(d2x, d2y);
1871
+ if (Math.abs(denom) <= 1e-12 * len1 * len2) continue;
1872
+ const ex = s2.a.x - s1.a.x;
1873
+ const ey = s2.a.y - s1.a.y;
1874
+ const t = (ex * d2y - ey * d2x) / denom;
1875
+ const u = (ex * d1y - ey * d1x) / denom;
1876
+ const tolT = eps / len1;
1877
+ const tolU = eps / len2;
1878
+ if (t < -tolT || t > 1 + tolT || u < -tolU || u > 1 + tolU) continue;
1879
+ if (sharedA || sharedB) continue;
1880
+ const v = pool.get(s1.a.x + t * d1x, s1.a.y + t * d1y);
1881
+ addSplit(splits[bi.i], project(s1.a, s1.b, v).t, v);
1882
+ addSplit(splits[bj.i], project(s2.a, s2.b, v).t, v);
1883
+ }
1884
+ }
1885
+ let changed = false;
1886
+ const out = [];
1887
+ for (const { segment, points } of splits) {
1888
+ if (points.size === 0) {
1889
+ out.push(segment);
1890
+ continue;
1891
+ }
1892
+ changed = true;
1893
+ const ordered = [...points.values()].sort((p, q) => p.t - q.t);
1894
+ let prev = segment.a;
1895
+ for (const { v } of ordered) {
1896
+ if (v.id !== prev.id) out.push({ a: prev, b: v, operand: segment.operand });
1897
+ prev = v;
1898
+ }
1899
+ if (prev.id !== segment.b.id) out.push({ a: prev, b: segment.b, operand: segment.operand });
1900
+ }
1901
+ return { segments: out, changed };
1902
+ }
1903
+ function defaultEpsilon(operands) {
1904
+ let max = 0;
1905
+ for (const op of operands) {
1906
+ for (const ring of op.rings) {
1907
+ for (const [x, y] of ring) {
1908
+ const m = Math.max(Math.abs(x), Math.abs(y));
1909
+ if (m > max) max = m;
1910
+ }
1911
+ }
1912
+ }
1913
+ return Math.max(1e-9, max * 1e-9);
1914
+ }
1915
+ function buildOverlay(inputs, options) {
1916
+ const operands = inputs.map(normalizeInput);
1917
+ const eps = options.epsilon && options.epsilon > 0 ? options.epsilon : defaultEpsilon(operands);
1918
+ const pool = new PointPool(eps);
1919
+ let segments = [];
1920
+ operands.forEach((op, operand) => {
1921
+ for (const ring of op.rings) {
1922
+ const verts = ring.map(([x, y]) => pool.get(x, y));
1923
+ for (let i = 0; i < verts.length; i++) {
1924
+ const a = verts[i];
1925
+ const b = verts[(i + 1) % verts.length];
1926
+ if (a.id !== b.id) segments.push({ a, b, operand });
1927
+ }
1928
+ }
1929
+ });
1930
+ for (let pass = 0; pass < 8; pass++) {
1931
+ const result = splitPass(segments, pool, eps);
1932
+ segments = result.segments;
1933
+ if (!result.changed) break;
1934
+ }
1935
+ const count = operands.length;
1936
+ const byKey = /* @__PURE__ */ new Map();
1937
+ for (const s of segments) {
1938
+ const forward = s.a.id < s.b.id;
1939
+ const a = forward ? s.a : s.b;
1940
+ const b = forward ? s.b : s.a;
1941
+ const key = `${a.id},${b.id}`;
1942
+ let edge = byKey.get(key);
1943
+ if (!edge) {
1944
+ edge = { a, b, mult: new Array(count).fill(0) };
1945
+ byKey.set(key, edge);
1946
+ }
1947
+ edge.mult[s.operand] += forward ? 1 : -1;
1948
+ }
1949
+ const edges = [...byKey.values()].filter((e) => e.mult.some((m) => m !== 0));
1950
+ const right = [];
1951
+ const left = [];
1952
+ for (let i = 0; i < edges.length; i++) {
1953
+ const edge = edges[i];
1954
+ const horizontal = Math.abs(edge.b.y - edge.a.y) < Math.abs(edge.b.x - edge.a.x);
1955
+ const tx = (v) => horizontal ? v.y : v.x;
1956
+ const ty = (v) => horizontal ? -v.x : v.y;
1957
+ const ax = tx(edge.a);
1958
+ const ay = ty(edge.a);
1959
+ const bx = tx(edge.b);
1960
+ const by = ty(edge.b);
1961
+ const mx = (ax + bx) / 2;
1962
+ const my = (ay + by) / 2;
1963
+ const winding = new Array(count).fill(0);
1964
+ for (let j = 0; j < edges.length; j++) {
1965
+ if (j === i) continue;
1966
+ const other = edges[j];
1967
+ const oax = tx(other.a);
1968
+ const oay = ty(other.a);
1969
+ const obx = tx(other.b);
1970
+ const oby = ty(other.b);
1971
+ let dir = 0;
1972
+ if (oay <= my && oby > my) dir = 1;
1973
+ else if (oby <= my && oay > my) dir = -1;
1974
+ else continue;
1975
+ const x = oax + (my - oay) / (oby - oay) * (obx - oax);
1976
+ if (x <= mx) continue;
1977
+ for (let k = 0; k < count; k++) {
1978
+ if (other.mult[k] !== 0) winding[k] += dir * other.mult[k];
1979
+ }
1980
+ }
1981
+ const sideIsRight = by > ay;
1982
+ const r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);
1983
+ right.push(r);
1984
+ left.push(r.map((w, k) => w + edge.mult[k]));
1985
+ }
1986
+ return { edges, right, left, fillRules: operands.map((o) => o.fillRule), eps };
1987
+ }
1988
+ function insideFlags(windings, fillRules) {
1989
+ return windings.map((w, k) => fillRules[k] === "evenodd" ? (w & 1) === 1 : w !== 0);
1990
+ }
1991
+ function traceRings(directed) {
1992
+ const outgoing = /* @__PURE__ */ new Map();
1993
+ for (const e of directed) {
1994
+ const list = outgoing.get(e.from.id);
1995
+ if (list) list.push(e);
1996
+ else outgoing.set(e.from.id, [e]);
1997
+ }
1998
+ const TWO_PI = Math.PI * 2;
1999
+ const next = (incoming) => {
2000
+ const candidates = outgoing.get(incoming.to.id);
2001
+ if (!candidates) return void 0;
2002
+ const back = Math.atan2(incoming.from.y - incoming.to.y, incoming.from.x - incoming.to.x);
2003
+ let best;
2004
+ let bestTurn = Infinity;
2005
+ for (const c of candidates) {
2006
+ let turn = back - c.angle;
2007
+ turn -= Math.floor(turn / TWO_PI) * TWO_PI;
2008
+ if (turn <= 1e-15) turn = TWO_PI;
2009
+ if (turn < bestTurn) {
2010
+ bestTurn = turn;
2011
+ best = c;
2012
+ }
2013
+ }
2014
+ return best;
2015
+ };
2016
+ const used = /* @__PURE__ */ new Set();
2017
+ const rings = [];
2018
+ for (const start of directed) {
2019
+ if (used.has(start)) continue;
2020
+ const ring = [];
2021
+ let edge = start;
2022
+ let guard = directed.length + 1;
2023
+ while (edge && !used.has(edge) && guard-- > 0) {
2024
+ used.add(edge);
2025
+ ring.push(edge.from);
2026
+ edge = next(edge);
2027
+ }
2028
+ if (ring.length >= 3) rings.push(ring);
2029
+ }
2030
+ return rings;
2031
+ }
2032
+ function removeCollinear(ring, eps) {
2033
+ let points = ring;
2034
+ let changed = true;
2035
+ while (changed && points.length > 3) {
2036
+ changed = false;
2037
+ const out = [];
2038
+ const n = points.length;
2039
+ for (let i = 0; i < n; i++) {
2040
+ const prev = out.length > 0 ? out[out.length - 1] : points[(i - 1 + n) % n];
2041
+ const cur = points[i];
2042
+ const nxt = points[(i + 1) % n];
2043
+ const dx = nxt[0] - prev[0];
2044
+ const dy = nxt[1] - prev[1];
2045
+ const len = Math.hypot(dx, dy);
2046
+ const cross = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;
2047
+ const dot2 = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;
2048
+ if (len > 0 && Math.abs(cross) / len <= eps && dot2 > 0 && dot2 < len * len) {
2049
+ changed = true;
2050
+ continue;
2051
+ }
2052
+ out.push(cur);
2053
+ }
2054
+ if (out.length < 3) return out;
2055
+ points = out;
2056
+ }
2057
+ return points;
2058
+ }
2059
+ function assemble(rings, eps) {
2060
+ const outers = [];
2061
+ const holes = [];
2062
+ const minArea = eps * eps;
2063
+ for (const vertices of rings) {
2064
+ const ring = removeCollinear(
2065
+ vertices.map((v) => [v.x, v.y]),
2066
+ eps
2067
+ );
2068
+ if (ring.length < 3) continue;
2069
+ const area = ringSignedArea(ring);
2070
+ if (Math.abs(area) <= minArea) continue;
2071
+ (area > 0 ? outers : holes).push({ ring, area: Math.abs(area) });
2072
+ }
2073
+ outers.sort((a, b) => b.area - a.area);
2074
+ const polygons = outers.map((o) => [o.ring]);
2075
+ for (const hole of holes) {
2076
+ const [p, q] = [hole.ring[0], hole.ring[1]];
2077
+ const sample = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
2078
+ let owner = -1;
2079
+ for (let i = outers.length - 1; i >= 0; i--) {
2080
+ if (outers[i].area >= hole.area && pointInRings(sample, [outers[i].ring], "evenodd")) {
2081
+ owner = i;
2082
+ break;
2083
+ }
2084
+ }
2085
+ if (owner >= 0) polygons[owner].push(hole.ring);
2086
+ }
2087
+ return polygons;
2088
+ }
2089
+ function extract(overlay, isInside) {
2090
+ const directed = [];
2091
+ overlay.edges.forEach((edge, i) => {
2092
+ const l = isInside(insideFlags(overlay.left[i], overlay.fillRules));
2093
+ const r = isInside(insideFlags(overlay.right[i], overlay.fillRules));
2094
+ if (l === r) return;
2095
+ const [from, to] = l ? [edge.a, edge.b] : [edge.b, edge.a];
2096
+ directed.push({ from, to, angle: Math.atan2(to.y - from.y, to.x - from.x) });
2097
+ });
2098
+ return assemble(traceRings(directed), overlay.eps);
2099
+ }
2100
+ function predicate(operation) {
2101
+ switch (operation) {
2102
+ case "union":
2103
+ return (f) => f.some(Boolean);
2104
+ case "intersection":
2105
+ return (f) => f.length > 0 && f.every(Boolean);
2106
+ case "difference":
2107
+ return (f) => f[0] === true && !f.slice(1).some(Boolean);
2108
+ case "xor":
2109
+ return (f) => f.filter(Boolean).length % 2 === 1;
2110
+ }
2111
+ }
2112
+ function polygonBoolean(operation, operands, options = {}) {
2113
+ if (operands.length === 0) return [];
2114
+ return extract(buildOverlay(operands, options), predicate(operation));
2115
+ }
2116
+ var unionPolygons = (a, b, options) => polygonBoolean("union", [a, b], options);
2117
+ var intersectPolygons = (a, b, options) => polygonBoolean("intersection", [a, b], options);
2118
+ var subtractPolygons = (a, b, options) => polygonBoolean("difference", [a, b], options);
2119
+ var xorPolygons = (a, b, options) => polygonBoolean("xor", [a, b], options);
2120
+ function dividePolygons(operands, options = {}) {
2121
+ if (operands.length === 0) return [];
2122
+ const overlay = buildOverlay(operands, options);
2123
+ const signatures = /* @__PURE__ */ new Map();
2124
+ const note = (windings) => {
2125
+ const flags = insideFlags(windings, overlay.fillRules);
2126
+ if (!flags.some(Boolean)) return;
2127
+ const key = flags.map((f) => f ? "1" : "0").join("");
2128
+ if (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => f ? [k] : []));
2129
+ };
2130
+ overlay.edges.forEach((_, i) => {
2131
+ note(overlay.left[i]);
2132
+ note(overlay.right[i]);
2133
+ });
2134
+ const pieces = [];
2135
+ const keys = [...signatures.keys()].sort();
2136
+ for (const key of keys) {
2137
+ const polygons = extract(overlay, (flags) => flags.map((f) => f ? "1" : "0").join("") === key);
2138
+ polygons.sort((a, b) => polygonArea(b) - polygonArea(a));
2139
+ for (const polygon of polygons) pieces.push({ polygon, operands: signatures.get(key) });
2140
+ }
2141
+ return pieces;
2142
+ }
2143
+
2144
+ // src/canvas/curveFit.ts
2145
+ function formatNumber(value, precision) {
2146
+ const factor = 10 ** precision;
2147
+ const rounded = Math.round(value * factor) / factor;
2148
+ return Object.is(rounded, -0) ? "0" : String(rounded);
2149
+ }
2150
+ var MAX_REPARAM = 6;
2151
+ var sub = (a, b) => [a[0] - b[0], a[1] - b[1]];
2152
+ var add = (a, v, s = 1) => [a[0] + v[0] * s, a[1] + v[1] * s];
2153
+ var dot = (a, b) => a[0] * b[0] + a[1] * b[1];
2154
+ var dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
2155
+ function unit(v) {
2156
+ const l = Math.hypot(v[0], v[1]);
2157
+ return l > 1e-12 && Number.isFinite(l) ? [v[0] / l, v[1] / l] : null;
2158
+ }
2159
+ function evalCubic(c, t) {
2160
+ const s = 1 - t;
2161
+ const b0 = s * s * s;
2162
+ const b1 = 3 * s * s * t;
2163
+ const b2 = 3 * s * t * t;
2164
+ const b3 = t * t * t;
2165
+ return [
2166
+ b0 * c.p0[0] + b1 * c.p1[0] + b2 * c.p2[0] + b3 * c.p3[0],
2167
+ b0 * c.p0[1] + b1 * c.p1[1] + b2 * c.p2[1] + b3 * c.p3[1]
2168
+ ];
2169
+ }
2170
+ function cubicPointAt(c, t) {
2171
+ return evalCubic(c, t);
2172
+ }
2173
+ function dedup(points) {
2174
+ const out = [];
2175
+ for (const p of points) {
2176
+ if (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
2177
+ if (out.length === 0 || dist(out[out.length - 1], p) > 1e-12) out.push(p);
2178
+ }
2179
+ return out;
2180
+ }
2181
+ function chordParams(pts) {
2182
+ const u = [0];
2183
+ let acc = 0;
2184
+ for (let i = 1; i < pts.length; i++) {
2185
+ acc += dist(pts[i - 1], pts[i]);
2186
+ u.push(acc);
2187
+ }
2188
+ return acc > 0 ? u.map((x) => x / acc) : u;
2189
+ }
2190
+ function generate(pts, u, t0, t1) {
2191
+ const p0 = pts[0];
2192
+ const p3 = pts[pts.length - 1];
2193
+ const dirIn = [-t1[0], -t1[1]];
2194
+ let c00 = 0;
2195
+ let c01 = 0;
2196
+ let c11 = 0;
2197
+ let x0 = 0;
2198
+ let x1 = 0;
2199
+ for (let i = 0; i < pts.length; i++) {
2200
+ const t = u[i];
2201
+ const s = 1 - t;
2202
+ const b0 = s * s * s;
2203
+ const b1 = 3 * s * s * t;
2204
+ const b2 = 3 * s * t * t;
2205
+ const b3 = t * t * t;
2206
+ const a0 = [t0[0] * b1, t0[1] * b1];
2207
+ const a1 = [dirIn[0] * b2, dirIn[1] * b2];
2208
+ c00 += dot(a0, a0);
2209
+ c01 += dot(a0, a1);
2210
+ c11 += dot(a1, a1);
2211
+ const tmp = [
2212
+ pts[i][0] - (p0[0] * (b0 + b1) + p3[0] * (b2 + b3)),
2213
+ pts[i][1] - (p0[1] * (b0 + b1) + p3[1] * (b2 + b3))
2214
+ ];
2215
+ x0 += dot(a0, tmp);
2216
+ x1 += dot(a1, tmp);
2217
+ }
2218
+ const det = c00 * c11 - c01 * c01;
2219
+ const seg = dist(p0, p3);
2220
+ const eps = 1e-6 * seg;
2221
+ let al = seg / 3;
2222
+ let ar = seg / 3;
2223
+ if (Math.abs(det) > 1e-12) {
2224
+ al = (x0 * c11 - x1 * c01) / det;
2225
+ ar = (c00 * x1 - c01 * x0) / det;
2226
+ }
2227
+ if (!Number.isFinite(al) || !Number.isFinite(ar) || al < eps || ar < eps) {
2228
+ al = seg / 3;
2229
+ ar = seg / 3;
2230
+ }
2231
+ const cap = seg * 4 + 1e-9;
2232
+ al = Math.min(al, cap);
2233
+ ar = Math.min(ar, cap);
2234
+ return { p0, p1: add(p0, t0, al), p2: add(p3, dirIn, ar), p3 };
2235
+ }
2236
+ function maxError(pts, c, u) {
2237
+ let e = 0;
2238
+ let idx = pts.length >> 1;
2239
+ for (let i = 0; i < pts.length; i++) {
2240
+ const d = dist(evalCubic(c, u[i]), pts[i]);
2241
+ if (d > e) {
2242
+ e = d;
2243
+ idx = i;
2244
+ }
2245
+ }
2246
+ return [e, idx];
2247
+ }
2248
+ function newton(c, p, t) {
2249
+ const s = 1 - t;
2250
+ const q = evalCubic(c, t);
2251
+ const d1 = [
2252
+ 3 * (s * s * (c.p1[0] - c.p0[0]) + 2 * s * t * (c.p2[0] - c.p1[0]) + t * t * (c.p3[0] - c.p2[0])),
2253
+ 3 * (s * s * (c.p1[1] - c.p0[1]) + 2 * s * t * (c.p2[1] - c.p1[1]) + t * t * (c.p3[1] - c.p2[1]))
2254
+ ];
2255
+ const d2 = [
2256
+ 6 * (s * (c.p2[0] - 2 * c.p1[0] + c.p0[0]) + t * (c.p3[0] - 2 * c.p2[0] + c.p1[0])),
2257
+ 6 * (s * (c.p2[1] - 2 * c.p1[1] + c.p0[1]) + t * (c.p3[1] - 2 * c.p2[1] + c.p1[1]))
2258
+ ];
2259
+ const diff = [q[0] - p[0], q[1] - p[1]];
2260
+ const num = dot(diff, d1);
2261
+ const den = dot(d1, d1) + dot(diff, d2);
2262
+ if (Math.abs(den) < 1e-12) return t;
2263
+ const nt = t - num / den;
2264
+ return Number.isFinite(nt) ? Math.min(1, Math.max(0, nt)) : t;
2265
+ }
2266
+ function fitSingleFrom(pts, u, t0, t1) {
2267
+ let best = generate(pts, u, t0, t1);
2268
+ let [bestErr, bestSplit] = maxError(pts, best, u);
2269
+ const params = u.slice();
2270
+ for (let round = 0; round < MAX_REPARAM; round++) {
2271
+ for (let i = 0; i < params.length; i++) params[i] = newton(best, pts[i], params[i]);
2272
+ const c = generate(pts, params, t0, t1);
2273
+ const [e, s] = maxError(pts, c, params);
2274
+ if (e < bestErr) {
2275
+ best = c;
2276
+ bestErr = e;
2277
+ bestSplit = s;
2278
+ } else break;
2279
+ }
2280
+ return [best, bestErr, bestSplit];
2281
+ }
2282
+ function fitRec(pts, t0, t1, tol, out, depth) {
2283
+ const n = pts.length;
2284
+ if (n === 2) {
2285
+ const d = dist(pts[0], pts[1]) / 3;
2286
+ out.push({ p0: pts[0], p1: add(pts[0], t0, d), p2: add(pts[1], t1, -d), p3: pts[1] });
2287
+ return;
2288
+ }
2289
+ const [c, err, splitAt] = fitSingleFrom(pts, chordParams(pts), t0, t1);
2290
+ if (err <= tol || depth > 40) {
2291
+ out.push(c);
2292
+ return;
2293
+ }
2294
+ const split = Math.min(n - 2, Math.max(1, splitAt));
2295
+ const tc = _nullishCoalesce(unit(sub(pts[split + 1], pts[split - 1])), () => ( t0));
2296
+ fitRec(pts.slice(0, split + 1), t0, tc, tol, out, depth + 1);
2297
+ fitRec(pts.slice(split), tc, t1, tol, out, depth + 1);
2298
+ }
2299
+ function tangentFrom(a, b, c) {
2300
+ const chord = unit(sub(b, a));
2301
+ if (!c) return chord;
2302
+ const d1 = dist(a, b);
2303
+ const d2 = dist(b, c);
2304
+ if (!(d1 > 0) || !(d2 > 0)) return chord;
2305
+ const ka = -(2 * d1 + d2) / (d1 * (d1 + d2));
2306
+ const kb = (d1 + d2) / (d1 * d2);
2307
+ const kc = -d1 / (d2 * (d1 + d2));
2308
+ const est = unit([ka * a[0] + kb * b[0] + kc * c[0], ka * a[1] + kb * b[1] + kc * c[1]]);
2309
+ return est && chord && dot(est, chord) > Math.cos(Math.PI / 4) ? est : chord;
2310
+ }
2311
+ function fitCubicRun(points, tolerance, t0, t1) {
2312
+ const pts = dedup(points);
2313
+ const out = [];
2314
+ if (pts.length < 2) return out;
2315
+ const n = pts.length;
2316
+ const start = _nullishCoalesce(_nullishCoalesce((t0 && unit([t0[0], t0[1]])), () => ( tangentFrom(pts[0], pts[1], pts[2]))), () => ( [1, 0]));
2317
+ const back = tangentFrom(pts[n - 1], pts[n - 2], pts[n - 3]);
2318
+ const end = _nullishCoalesce((t1 && unit([t1[0], t1[1]])), () => ( (back ? [-back[0], -back[1]] : [1, 0])));
2319
+ fitRec(pts, start, end, Math.max(tolerance, 1e-9), out, 0);
2320
+ return out;
2321
+ }
2322
+ function turnDeg(a, b, c) {
2323
+ const u = unit(sub(b, a));
2324
+ const v = unit(sub(c, b));
2325
+ if (!u || !v) return 0;
2326
+ return Math.acos(Math.min(1, Math.max(-1, dot(u, v)))) * 180 / Math.PI;
2327
+ }
2328
+ function findCorners(pts, closed, cornerAngle, span) {
2329
+ const n = pts.length;
2330
+ const at = (i) => pts[closed ? (i % n + n) % n : i];
2331
+ const reach = (i, step) => {
2332
+ for (let k = 1; k < (closed ? n : Infinity); k++) {
2333
+ const j = i + step * k;
2334
+ if (!closed && (j < 0 || j >= n)) return k > 1 ? j - step : null;
2335
+ if (dist(at(j), pts[i]) >= span) return j;
2336
+ }
2337
+ return null;
2338
+ };
2339
+ const turns = new Float64Array(n);
2340
+ const windows = [];
2341
+ for (let i = 0; i < n; i++) {
2342
+ windows.push([i, i]);
2343
+ if (!closed && (i === 0 || i === n - 1)) continue;
2344
+ const prev = reach(i, -1);
2345
+ const next = reach(i, 1);
2346
+ if (prev === null || next === null) continue;
2347
+ windows[i] = [prev, next];
2348
+ turns[i] = turnDeg(at(prev), pts[i], at(next));
2349
+ }
2350
+ const corners = [];
2351
+ for (let i = 0; i < n; i++) {
2352
+ if (!(turns[i] > cornerAngle)) continue;
2353
+ const [lo, hi] = windows[i];
2354
+ let sharpest = true;
2355
+ for (let j = lo; j <= hi && sharpest; j++) {
2356
+ const k = closed ? (j % n + n) % n : j;
2357
+ if (k === i || dist(pts[k], pts[i]) >= span) continue;
2358
+ if (turns[k] > turns[i] || turns[k] === turns[i] && k < i) sharpest = false;
2359
+ }
2360
+ if (sharpest) corners.push(i);
2361
+ }
2362
+ return corners;
2363
+ }
2364
+ function densify(run) {
2365
+ if (run.length < 3) return run;
2366
+ const lengths = [];
2367
+ for (let i = 1; i < run.length; i++) lengths.push(dist(run[i - 1], run[i]));
2368
+ const sorted = [...lengths].sort((a, b) => a - b);
2369
+ const median = sorted[sorted.length >> 1];
2370
+ if (!(median > 0)) return run;
2371
+ const out = [run[0]];
2372
+ for (let i = 1; i < run.length; i++) {
2373
+ const len = lengths[i - 1];
2374
+ if (len > 2 * median) {
2375
+ const steps = Math.min(64, Math.ceil(len / median));
2376
+ const a = run[i - 1];
2377
+ const b = run[i];
2378
+ for (let k = 1; k < steps; k++) {
2379
+ const t = k / steps;
2380
+ out.push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
2381
+ }
2382
+ }
2383
+ out.push(run[i]);
2384
+ }
2385
+ return out;
2386
+ }
2387
+ function fitCubics(points, tolerance, options = {}) {
2388
+ let pts = dedup(points);
2389
+ const closed = options.closed === true;
2390
+ if (closed && pts.length > 1 && dist(pts[0], pts[pts.length - 1]) <= 1e-12) pts = pts.slice(0, -1);
2391
+ if (pts.length < 2) return [];
2392
+ const tol = Math.max(tolerance, 1e-6);
2393
+ const cornerAngle = _nullishCoalesce(options.cornerAngleDeg, () => ( 30));
2394
+ const n = pts.length;
2395
+ const span = 4 * tol;
2396
+ if (!closed || n < 3) {
2397
+ const corners2 = [0, ...n > 2 ? findCorners(pts, false, cornerAngle, span) : [], n - 1];
2398
+ const out2 = [];
2399
+ for (let k = 1; k < corners2.length; k++) {
2400
+ const run = pts.slice(corners2[k - 1], corners2[k] + 1);
2401
+ out2.push(...fitCubicRun(densify(run), tol));
2402
+ }
2403
+ if (closed && n === 2) out2.push(...fitCubicRun([pts[1], pts[0]], tol));
2404
+ return out2;
2405
+ }
2406
+ const corners = findCorners(pts, true, cornerAngle, span);
2407
+ if (corners.length === 0) {
2408
+ const ring = [...pts, pts[0]];
2409
+ const t = _nullishCoalesce(unit(sub(pts[1], pts[n - 1])), () => ( [1, 0]));
2410
+ return fitCubicRun(densify(ring), tol, t, t);
2411
+ }
2412
+ const out = [];
2413
+ for (let k = 0; k < corners.length; k++) {
2414
+ const from = corners[k];
2415
+ const to = corners[(k + 1) % corners.length];
2416
+ const run = [];
2417
+ for (let i = from; ; i = (i + 1) % n) {
2418
+ run.push(pts[i]);
2419
+ if (run.length > 1 && i === to) break;
2420
+ }
2421
+ out.push(...fitCubicRun(densify(run), tol));
2422
+ }
2423
+ return out;
2424
+ }
2425
+ function isStraightCubic(c, tol) {
2426
+ const chord = sub(c.p3, c.p0);
2427
+ const len = Math.hypot(chord[0], chord[1]);
2428
+ if (len < 1e-12) return dist(c.p1, c.p0) <= tol && dist(c.p2, c.p0) <= tol;
2429
+ const dir = [chord[0] / len, chord[1] / len];
2430
+ return [c.p1, c.p2].every((h) => {
2431
+ const v = sub(h, c.p0);
2432
+ const along = dot(v, dir);
2433
+ const across = Math.abs(v[0] * dir[1] - v[1] * dir[0]);
2434
+ return across <= tol && along >= -tol && along <= len + tol;
2435
+ });
2436
+ }
2437
+ function cubicsToPathData(segments, options = {}) {
2438
+ if (segments.length === 0) return "";
2439
+ const f = (n) => formatNumber(n, _nullishCoalesce(options.precision, () => ( 3)));
2440
+ const dx = _nullishCoalesce(_optionalChain([options, 'access', _55 => _55.offset, 'optionalAccess', _56 => _56.x]), () => ( 0));
2441
+ const dy = _nullishCoalesce(_optionalChain([options, 'access', _57 => _57.offset, 'optionalAccess', _58 => _58.y]), () => ( 0));
2442
+ const pt = (p) => `${f(p[0] + dx)} ${f(p[1] + dy)}`;
2443
+ const straight = _nullishCoalesce(options.straightTolerance, () => ( 1e-3));
2444
+ let d = `M${pt(segments[0].p0)}`;
2445
+ segments.forEach((c, i) => {
2446
+ const last = options.closed && i === segments.length - 1;
2447
+ if (isStraightCubic(c, straight)) {
2448
+ if (!last) d += `L${pt(c.p3)}`;
2449
+ } else d += `C${pt(c.p1)} ${pt(c.p2)} ${pt(c.p3)}`;
2450
+ });
2451
+ return options.closed ? `${d}Z` : d;
2452
+ }
2453
+ function polylineToPathData(points, closed, options) {
2454
+ if (points.length < 2) return "";
2455
+ const f = (n) => formatNumber(n, _nullishCoalesce(options.precision, () => ( 3)));
2456
+ const dx = _nullishCoalesce(_optionalChain([options, 'access', _59 => _59.offset, 'optionalAccess', _60 => _60.x]), () => ( 0));
2457
+ const dy = _nullishCoalesce(_optionalChain([options, 'access', _61 => _61.offset, 'optionalAccess', _62 => _62.y]), () => ( 0));
2458
+ let d = `M${f(points[0][0] + dx)} ${f(points[0][1] + dy)}`;
2459
+ for (let i = 1; i < points.length; i++) d += `L${f(points[i][0] + dx)} ${f(points[i][1] + dy)}`;
2460
+ return closed ? `${d}Z` : d;
2461
+ }
2462
+ function fitPathData(ring, options = {}) {
2463
+ const closed = _nullishCoalesce(options.closed, () => ( true));
2464
+ const tolerance = _nullishCoalesce(options.tolerance, () => ( 0.5));
2465
+ const pts = dedup(ring);
2466
+ const segments = fitCubics(pts, tolerance, { ...options, closed });
2467
+ if (segments.length === 0) return "";
2468
+ const edges = closed ? pts.length : pts.length - 1;
2469
+ if (options.noWorse !== false) {
2470
+ const straight = segments.filter((c) => isStraightCubic(c, tolerance * 0.02)).length;
2471
+ if (segments.length >= edges && straight === segments.length) return polylineToPathData(pts, closed, options);
2472
+ if (segments.length > edges) return polylineToPathData(pts, closed, options);
2473
+ }
2474
+ return cubicsToPathData(segments, {
2475
+ precision: options.precision,
2476
+ offset: options.offset,
2477
+ closed,
2478
+ straightTolerance: tolerance * 0.02
2479
+ });
2480
+ }
2481
+ function fitFreehandPath(points, options = {}) {
2482
+ return fitPathData(points, { tolerance: 1, ...options, closed: false });
2483
+ }
2484
+
2485
+ // src/canvas/shapeGeometry.ts
2486
+ var SHAPE_KINDS = ["rect", "ellipse", "polygon", "star", "path"];
2487
+ function isShapeDescription(value) {
2488
+ if (!value || typeof value !== "object") return false;
2489
+ const kind = value.kind;
2490
+ if (!SHAPE_KINDS.includes(kind)) return false;
2491
+ return kind !== "path" || typeof value.d === "string";
2492
+ }
2493
+ var PARAM_COUNTS = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0 };
2494
+ var PathScanner = (_class5 = class {
2495
+ constructor(text) {;_class5.prototype.__init14.call(this);
2496
+ this.text = text;
2497
+ }
2498
+
2499
+ __init14() {this.pos = 0}
2500
+ skip() {
2501
+ while (this.pos < this.text.length && /[\s,]/.test(this.text[this.pos])) this.pos++;
2502
+ }
2503
+ /** The next command letter, or `null` when a number (or the end) follows. */
2504
+ command() {
2505
+ this.skip();
2506
+ const ch = this.text[this.pos];
2507
+ if (ch && /[MmLlHhVvCcSsQqTtAaZz]/.test(ch)) {
2508
+ this.pos++;
2509
+ return ch;
2510
+ }
2511
+ return null;
2512
+ }
2513
+ done() {
2514
+ this.skip();
2515
+ return this.pos >= this.text.length;
2516
+ }
2517
+ number() {
2518
+ this.skip();
2519
+ const match = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(this.text.slice(this.pos, this.pos + 64));
2520
+ if (!match) return null;
2521
+ this.pos += match[0].length;
2522
+ const value = Number(match[0]);
2523
+ return Number.isFinite(value) ? value : null;
2524
+ }
2525
+ flag() {
2526
+ this.skip();
2527
+ const ch = this.text[this.pos];
2528
+ if (ch === "0" || ch === "1") {
2529
+ this.pos++;
2530
+ return ch === "1" ? 1 : 0;
2531
+ }
2532
+ return null;
2533
+ }
2534
+ }, _class5);
2535
+ function parseSvgPath(d) {
2536
+ const scanner = new PathScanner(_nullishCoalesce(d, () => ( "")));
2537
+ const commands = [];
2538
+ let cx = 0;
2539
+ let cy = 0;
2540
+ let startX = 0;
2541
+ let startY = 0;
2542
+ let lastControl = null;
2543
+ let command = "";
2544
+ while (!scanner.done()) {
2545
+ const letter = scanner.command();
2546
+ if (letter) {
2547
+ command = letter;
2548
+ if (command === "z" || command === "Z") {
2549
+ commands.push({ type: "Z" });
2550
+ cx = startX;
2551
+ cy = startY;
2552
+ lastControl = null;
2553
+ continue;
2554
+ }
2555
+ } else if (!command || command === "z" || command === "Z") {
2556
+ break;
2557
+ }
2558
+ const lower = command.toLowerCase();
2559
+ const count = PARAM_COUNTS[lower];
2560
+ const args = [];
2561
+ for (let k = 0; k < count; k++) {
2562
+ const value = lower === "a" && (k === 3 || k === 4) ? scanner.flag() : scanner.number();
2563
+ if (value === null) return commands;
2564
+ args.push(value);
2565
+ }
2566
+ const rel = command !== command.toUpperCase();
2567
+ const ox = rel ? cx : 0;
2568
+ const oy = rel ? cy : 0;
2569
+ switch (lower) {
2570
+ case "m": {
2571
+ cx = args[0] + ox;
2572
+ cy = args[1] + oy;
2573
+ startX = cx;
2574
+ startY = cy;
2575
+ commands.push({ type: "M", x: cx, y: cy });
2576
+ lastControl = null;
2577
+ command = rel ? "l" : "L";
2578
+ break;
2579
+ }
2580
+ case "l":
2581
+ cx = args[0] + ox;
2582
+ cy = args[1] + oy;
2583
+ commands.push({ type: "L", x: cx, y: cy });
2584
+ lastControl = null;
2585
+ break;
2586
+ case "h":
2587
+ cx = args[0] + (rel ? cx : 0);
2588
+ commands.push({ type: "L", x: cx, y: cy });
2589
+ lastControl = null;
2590
+ break;
2591
+ case "v":
2592
+ cy = args[0] + (rel ? cy : 0);
2593
+ commands.push({ type: "L", x: cx, y: cy });
2594
+ lastControl = null;
2595
+ break;
2596
+ case "c": {
2597
+ const c = {
2598
+ type: "C",
2599
+ x1: args[0] + ox,
2600
+ y1: args[1] + oy,
2601
+ x2: args[2] + ox,
2602
+ y2: args[3] + oy,
2603
+ x: args[4] + ox,
2604
+ y: args[5] + oy
2605
+ };
2606
+ commands.push(c);
2607
+ lastControl = { x: c.x2, y: c.y2, type: "C" };
2608
+ cx = c.x;
2609
+ cy = c.y;
2610
+ break;
2611
+ }
2612
+ case "s": {
2613
+ const x1 = _optionalChain([lastControl, 'optionalAccess', _63 => _63.type]) === "C" ? 2 * cx - lastControl.x : cx;
2614
+ const y1 = _optionalChain([lastControl, 'optionalAccess', _64 => _64.type]) === "C" ? 2 * cy - lastControl.y : cy;
2615
+ const c = {
2616
+ type: "C",
2617
+ x1,
2618
+ y1,
2619
+ x2: args[0] + ox,
2620
+ y2: args[1] + oy,
2621
+ x: args[2] + ox,
2622
+ y: args[3] + oy
2623
+ };
2624
+ commands.push(c);
2625
+ lastControl = { x: c.x2, y: c.y2, type: "C" };
2626
+ cx = c.x;
2627
+ cy = c.y;
2628
+ break;
2629
+ }
2630
+ case "q": {
2631
+ const c = { type: "Q", x1: args[0] + ox, y1: args[1] + oy, x: args[2] + ox, y: args[3] + oy };
2632
+ commands.push(c);
2633
+ lastControl = { x: c.x1, y: c.y1, type: "Q" };
2634
+ cx = c.x;
2635
+ cy = c.y;
2636
+ break;
2637
+ }
2638
+ case "t": {
2639
+ const x1 = _optionalChain([lastControl, 'optionalAccess', _65 => _65.type]) === "Q" ? 2 * cx - lastControl.x : cx;
2640
+ const y1 = _optionalChain([lastControl, 'optionalAccess', _66 => _66.type]) === "Q" ? 2 * cy - lastControl.y : cy;
2641
+ const c = { type: "Q", x1, y1, x: args[0] + ox, y: args[1] + oy };
2642
+ commands.push(c);
2643
+ lastControl = { x: x1, y: y1, type: "Q" };
2644
+ cx = c.x;
2645
+ cy = c.y;
2646
+ break;
2647
+ }
2648
+ case "a": {
2649
+ const c = {
2650
+ type: "A",
2651
+ rx: Math.abs(args[0]),
2652
+ ry: Math.abs(args[1]),
2653
+ rotation: args[2],
2654
+ largeArc: args[3] !== 0,
2655
+ sweep: args[4] !== 0,
2656
+ x: args[5] + ox,
2657
+ y: args[6] + oy
2658
+ };
2659
+ commands.push(c);
2660
+ lastControl = null;
2661
+ cx = c.x;
2662
+ cy = c.y;
2663
+ break;
2664
+ }
2665
+ }
2666
+ }
2667
+ return commands;
2668
+ }
2669
+ function segmentCount(curvature, tolerance, max) {
2670
+ if (!(curvature > 0)) return 1;
2671
+ return Math.max(1, Math.min(max, Math.ceil(Math.sqrt(curvature / tolerance))));
2672
+ }
2673
+ function flattenCubic(out, p0, x1, y1, x2, y2, x, y, tolerance, max) {
2674
+ const dd = Math.max(
2675
+ Math.hypot(p0[0] - 2 * x1 + x2, p0[1] - 2 * y1 + y2),
2676
+ Math.hypot(x1 - 2 * x2 + x, y1 - 2 * y2 + y)
2677
+ );
2678
+ const n = segmentCount(6 * dd / 8, tolerance, max);
2679
+ for (let k = 1; k <= n; k++) {
2680
+ const t = k / n;
2681
+ const mt = 1 - t;
2682
+ const a = mt * mt * mt;
2683
+ const b = 3 * mt * mt * t;
2684
+ const c = 3 * mt * t * t;
2685
+ const e = t * t * t;
2686
+ out.push([a * p0[0] + b * x1 + c * x2 + e * x, a * p0[1] + b * y1 + c * y2 + e * y]);
2687
+ }
2688
+ }
2689
+ function flattenQuadratic(out, p0, x1, y1, x, y, tolerance, max) {
2690
+ const dd = Math.hypot(p0[0] - 2 * x1 + x, p0[1] - 2 * y1 + y);
2691
+ const n = segmentCount(2 * dd / 8, tolerance, max);
2692
+ for (let k = 1; k <= n; k++) {
2693
+ const t = k / n;
2694
+ const mt = 1 - t;
2695
+ out.push([mt * mt * p0[0] + 2 * mt * t * x1 + t * t * x, mt * mt * p0[1] + 2 * mt * t * y1 + t * t * y]);
2696
+ }
2697
+ }
2698
+ function arcSteps(radius, sweep, tolerance, max) {
2699
+ if (radius <= tolerance) return Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2)));
2700
+ const step = 2 * Math.acos(1 - tolerance / radius);
2701
+ return Math.max(1, Math.min(max, Math.ceil(Math.abs(sweep) / step)));
2702
+ }
2703
+ function flattenArc(out, p0, cmd, tolerance, max) {
2704
+ const [x0, y0] = p0;
2705
+ let { rx, ry } = cmd;
2706
+ const { x, y } = cmd;
2707
+ if (x0 === x && y0 === y) return;
2708
+ if (rx === 0 || ry === 0) {
2709
+ out.push([x, y]);
2710
+ return;
2711
+ }
2712
+ const phi = cmd.rotation * Math.PI / 180;
2713
+ const cos = Math.cos(phi);
2714
+ const sin = Math.sin(phi);
2715
+ const dx = (x0 - x) / 2;
2716
+ const dy = (y0 - y) / 2;
2717
+ const x1p = cos * dx + sin * dy;
2718
+ const y1p = -sin * dx + cos * dy;
2719
+ const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
2720
+ if (lambda > 1) {
2721
+ const s = Math.sqrt(lambda);
2722
+ rx *= s;
2723
+ ry *= s;
2724
+ }
2725
+ const num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;
2726
+ const den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;
2727
+ let coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));
2728
+ if (cmd.largeArc === cmd.sweep) coef = -coef;
2729
+ const cxp = coef * rx * y1p / ry;
2730
+ const cyp = -coef * ry * x1p / rx;
2731
+ const cx = cos * cxp - sin * cyp + (x0 + x) / 2;
2732
+ const cy = sin * cxp + cos * cyp + (y0 + y) / 2;
2733
+ const angle = (ux, uy, vx, vy) => {
2734
+ const a = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
2735
+ return a;
2736
+ };
2737
+ const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
2738
+ let delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
2739
+ if (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;
2740
+ else if (cmd.sweep && delta < 0) delta += 2 * Math.PI;
2741
+ const n = arcSteps(Math.max(rx, ry), delta, tolerance, max);
2742
+ for (let k = 1; k < n; k++) {
2743
+ const t = theta1 + delta * k / n;
2744
+ const ex = rx * Math.cos(t);
2745
+ const ey = ry * Math.sin(t);
2746
+ out.push([cos * ex - sin * ey + cx, sin * ex + cos * ey + cy]);
2747
+ }
2748
+ out.push([x, y]);
2749
+ }
2750
+ function flattenSvgPath(d, options = {}) {
2751
+ const tolerance = options.tolerance && options.tolerance > 0 ? options.tolerance : 0.25;
2752
+ const max = _nullishCoalesce(options.maxSegments, () => ( 512));
2753
+ const commands = typeof d === "string" ? parseSvgPath(d) : d;
2754
+ const rings = [];
2755
+ let current = [];
2756
+ let start = [0, 0];
2757
+ const flush = () => {
2758
+ if (current.length >= 3) rings.push(current);
2759
+ current = [];
2760
+ };
2761
+ const last = () => _nullishCoalesce(current[current.length - 1], () => ( start));
2762
+ for (const cmd of commands) {
2763
+ switch (cmd.type) {
2764
+ case "M":
2765
+ flush();
2766
+ start = [cmd.x, cmd.y];
2767
+ current = [start];
2768
+ break;
2769
+ case "L":
2770
+ if (current.length === 0) current = [start];
2771
+ current.push([cmd.x, cmd.y]);
2772
+ break;
2773
+ case "C":
2774
+ if (current.length === 0) current = [start];
2775
+ flattenCubic(current, last(), cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y, tolerance, max);
2776
+ break;
2777
+ case "Q":
2778
+ if (current.length === 0) current = [start];
2779
+ flattenQuadratic(current, last(), cmd.x1, cmd.y1, cmd.x, cmd.y, tolerance, max);
2780
+ break;
2781
+ case "A":
2782
+ if (current.length === 0) current = [start];
2783
+ flattenArc(current, last(), cmd, tolerance, max);
2784
+ break;
2785
+ case "Z":
2786
+ flush();
2787
+ break;
2788
+ }
2789
+ }
2790
+ flush();
2791
+ return rings.map(dedupeRing).filter((r) => r.length >= 3);
2792
+ }
2793
+ function dedupeRing(ring) {
2794
+ const out = [];
2795
+ for (const p of ring) {
2796
+ const prev = out[out.length - 1];
2797
+ if (!prev || prev[0] !== p[0] || prev[1] !== p[1]) out.push(p);
2798
+ }
2799
+ while (out.length > 1 && out[0][0] === out[out.length - 1][0] && out[0][1] === out[out.length - 1][1]) out.pop();
2800
+ return out;
2801
+ }
2802
+ function cornerRadii(shape, width, height) {
2803
+ const raw = _nullishCoalesce(shape.cornerRadius, () => ( 0));
2804
+ let radii = (typeof raw === "number" ? [raw, raw, raw, raw] : [...raw]).map(
2805
+ (r) => Number.isFinite(r) && r > 0 ? r : 0
2806
+ );
2807
+ const factor = Math.min(
2808
+ 1,
2809
+ width / Math.max(radii[0] + radii[1], 1e-12),
2810
+ height / Math.max(radii[1] + radii[2], 1e-12),
2811
+ width / Math.max(radii[2] + radii[3], 1e-12),
2812
+ height / Math.max(radii[3] + radii[0], 1e-12)
2813
+ );
2814
+ if (factor < 1) radii = radii.map((r) => r * factor);
2815
+ return radii;
2816
+ }
2817
+ function polygonVertices(count, inner, width, height) {
2818
+ const raw = [];
2819
+ const total = inner === null ? count : count * 2;
2820
+ for (let k = 0; k < total; k++) {
2821
+ const angle = -Math.PI / 2 + k * 2 * Math.PI / total;
2822
+ const r = inner !== null && k % 2 === 1 ? inner : 1;
2823
+ raw.push([r * Math.cos(angle), r * Math.sin(angle)]);
2824
+ }
2825
+ let minX = Infinity;
2826
+ let minY = Infinity;
2827
+ let maxX = -Infinity;
2828
+ let maxY = -Infinity;
2829
+ for (const [x, y] of raw) {
2830
+ minX = Math.min(minX, x);
2831
+ minY = Math.min(minY, y);
2832
+ maxX = Math.max(maxX, x);
2833
+ maxY = Math.max(maxY, y);
2834
+ }
2835
+ const sx = width / (maxX - minX || 1);
2836
+ const sy = height / (maxY - minY || 1);
2837
+ return raw.map(([x, y]) => [(x - minX) * sx, (y - minY) * sy]);
2838
+ }
2839
+ function rotationTransform(shape, size) {
2840
+ const deg = _nullishCoalesce(shape.rotation, () => ( 0));
2841
+ if (!deg || !Number.isFinite(deg)) return null;
2842
+ const rad = deg * Math.PI / 180;
2843
+ const cos = Math.cos(rad);
2844
+ const sin = Math.sin(rad);
2845
+ const cx = size.width / 2;
2846
+ const cy = size.height / 2;
2847
+ return ([x, y]) => [cx + (x - cx) * cos - (y - cy) * sin, cy + (x - cx) * sin + (y - cy) * cos];
2848
+ }
2849
+ function getShapeSvg(shape, size) {
2850
+ const { width: w, height: h } = size;
2851
+ const f = (n) => formatNumber2(n, 3);
2852
+ const transforms = [];
2853
+ if (shape.rotation) transforms.push(`rotate(${f(shape.rotation)} ${f(w / 2)} ${f(h / 2)})`);
2854
+ let d = "";
2855
+ let fillRule = "nonzero";
2856
+ switch (shape.kind) {
2857
+ case "rect": {
2858
+ const [tl, tr, br, bl] = cornerRadii(shape, w, h);
2859
+ if (tl + tr + br + bl === 0) {
2860
+ d = `M0 0H${f(w)}V${f(h)}H0Z`;
2861
+ } else {
2862
+ const arc = (r, x, y) => r > 0 ? `A${f(r)} ${f(r)} 0 0 1 ${f(x)} ${f(y)}` : "";
2863
+ d = `M${f(tl)} 0H${f(w - tr)}${arc(tr, w, tr)}V${f(h - br)}${arc(br, w - br, h)}H${f(bl)}${arc(bl, 0, h - bl)}V${f(tl)}${arc(tl, tl, 0)}Z`;
2864
+ }
2865
+ break;
2866
+ }
2867
+ case "ellipse": {
2868
+ const rx = w / 2;
2869
+ const ry = h / 2;
2870
+ d = `M0 ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 ${f(w)} ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 0 ${f(ry)}Z`;
2871
+ break;
2872
+ }
2873
+ case "polygon":
2874
+ case "star": {
2875
+ const points = shape.kind === "polygon" ? polygonVertices(Math.max(3, Math.floor(_nullishCoalesce(shape.sides, () => ( 6)))), null, w, h) : polygonVertices(Math.max(3, Math.floor(_nullishCoalesce(shape.points, () => ( 5)))), clampRatio(shape.innerRadius), w, h);
2876
+ d = ringsToPath([points], f);
2877
+ break;
2878
+ }
2879
+ case "path": {
2880
+ d = shape.d;
2881
+ fillRule = _nullishCoalesce(shape.fillRule, () => ( "nonzero"));
2882
+ const vb = validViewBox(shape.viewBox);
2883
+ if (vb) {
2884
+ transforms.push(
2885
+ `scale(${f(w / vb[2])} ${f(h / vb[3])})`,
2886
+ `translate(${f(-vb[0])} ${f(-vb[1])})`
2887
+ );
2888
+ }
2889
+ break;
2890
+ }
2891
+ }
2892
+ return transforms.length > 0 ? { d, transform: transforms.join(" "), fillRule } : { d, fillRule };
2893
+ }
2894
+ function getShapeRenderData(shape, size = {}) {
2895
+ const vb = shape.kind === "path" ? validViewBox(shape.viewBox) : null;
2896
+ const width = size.width && size.width > 0 ? size.width : vb ? vb[2] : 100;
2897
+ const height = size.height && size.height > 0 ? size.height : vb ? vb[3] : 100;
2898
+ const svg = getShapeSvg(shape, { width, height });
2899
+ const data = {
2900
+ viewBox: `0 0 ${formatNumber2(width, 3)} ${formatNumber2(height, 3)}`,
2901
+ width,
2902
+ height,
2903
+ d: svg.d,
2904
+ fillRule: svg.fillRule,
2905
+ fill: _nullishCoalesce(shape.fill, () => ( "currentColor")),
2906
+ stroke: _nullishCoalesce(shape.stroke, () => ( "none")),
2907
+ strokeWidth: _nullishCoalesce(shape.strokeWidth, () => ( 1))
2908
+ };
2909
+ if (svg.transform) data.transform = svg.transform;
2910
+ if (shape.opacity !== void 0) data.opacity = shape.opacity;
2911
+ return data;
2912
+ }
2913
+ function clampRatio(value) {
2914
+ if (value === void 0 || !Number.isFinite(value)) return 0.5;
2915
+ return Math.min(1, Math.max(0.01, value));
2916
+ }
2917
+ function validViewBox(vb) {
2918
+ if (!vb || vb.length !== 4 || !vb.every(Number.isFinite) || vb[2] <= 0 || vb[3] <= 0) return null;
2919
+ return vb;
2920
+ }
2921
+ function shapeToPolygon(shape, size, options = {}) {
2922
+ const { width: w, height: h } = size;
2923
+ if (!(w > 0) || !(h > 0)) return { rings: [], fillRule: "nonzero" };
2924
+ let rings;
2925
+ let fillRule = "nonzero";
2926
+ if (shape.kind === "path") {
2927
+ fillRule = _nullishCoalesce(shape.fillRule, () => ( "nonzero"));
2928
+ const vb = validViewBox(shape.viewBox);
2929
+ if (vb) {
2930
+ const sx = w / vb[2];
2931
+ const sy = h / vb[3];
2932
+ const tolerance = (_nullishCoalesce(options.tolerance, () => ( 0.25))) / Math.max(sx, sy);
2933
+ rings = flattenSvgPath(shape.d, { ...options, tolerance }).map(
2934
+ (ring) => ring.map(([x, y]) => [(x - vb[0]) * sx, (y - vb[1]) * sy])
2935
+ );
2936
+ } else {
2937
+ rings = flattenSvgPath(shape.d, options);
2938
+ }
2939
+ } else {
2940
+ const { d } = getShapeSvg({ ...shape, rotation: 0 }, size);
2941
+ rings = flattenSvgPath(d, options);
2942
+ }
2943
+ const rotate = rotationTransform(shape, size);
2944
+ if (rotate) rings = rings.map((ring) => ring.map(rotate));
2945
+ return { rings, fillRule };
2946
+ }
2947
+ function formatNumber2(value, precision = 3) {
2948
+ const factor = 10 ** precision;
2949
+ const rounded = Math.round(value * factor) / factor;
2950
+ return Object.is(rounded, -0) ? "0" : String(rounded);
2951
+ }
2952
+ function ringsToPath(rings, f) {
2953
+ let d = "";
2954
+ for (const ring of rings) {
2955
+ if (ring.length < 2) continue;
2956
+ d += `M${f(ring[0][0])} ${f(ring[0][1])}`;
2957
+ for (let i = 1; i < ring.length; i++) d += `L${f(ring[i][0])} ${f(ring[i][1])}`;
2958
+ d += "Z";
2959
+ }
2960
+ return d;
2961
+ }
2962
+ function segmentDistance(p, a, b) {
2963
+ const dx = b[0] - a[0];
2964
+ const dy = b[1] - a[1];
2965
+ const len2 = dx * dx + dy * dy;
2966
+ if (len2 === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);
2967
+ const t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));
2968
+ return Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));
2969
+ }
2970
+ function rdp(points, tolerance) {
2971
+ if (points.length <= 2) return points;
2972
+ const keep = new Uint8Array(points.length);
2973
+ keep[0] = 1;
2974
+ keep[points.length - 1] = 1;
2975
+ const stack = [[0, points.length - 1]];
2976
+ while (stack.length) {
2977
+ const [s, e] = stack.pop();
2978
+ let maxDist = -1;
2979
+ let index = -1;
2980
+ for (let i = s + 1; i < e; i++) {
2981
+ const dist2 = segmentDistance(points[i], points[s], points[e]);
2982
+ if (dist2 > maxDist) {
2983
+ maxDist = dist2;
2984
+ index = i;
2985
+ }
2986
+ }
2987
+ if (index !== -1 && maxDist > tolerance) {
2988
+ keep[index] = 1;
2989
+ stack.push([s, index], [index, e]);
2990
+ }
2991
+ }
2992
+ return points.filter((_, i) => keep[i]);
2993
+ }
2994
+ function simplifyRing(ring, tolerance) {
2995
+ if (ring.length <= 3 || !(tolerance > 0)) return [...ring];
2996
+ let far = 0;
2997
+ let farDist = -1;
2998
+ for (let i = 1; i < ring.length; i++) {
2999
+ const dist2 = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);
3000
+ if (dist2 > farDist) {
3001
+ farDist = dist2;
3002
+ far = i;
3003
+ }
3004
+ }
3005
+ const first = rdp(ring.slice(0, far + 1), tolerance);
3006
+ const second = rdp([...ring.slice(far), ring[0]], tolerance);
3007
+ const out = [...first.slice(0, -1), ...second.slice(0, -1)];
3008
+ return out.length >= 3 ? out : [];
3009
+ }
3010
+ function polygonToSvgPath(input, options = {}) {
3011
+ const precision = _nullishCoalesce(options.precision, () => ( 3));
3012
+ const f = (n) => formatNumber2(n, precision);
3013
+ const dx = _nullishCoalesce(_optionalChain([options, 'access', _67 => _67.offset, 'optionalAccess', _68 => _68.x]), () => ( 0));
3014
+ const dy = _nullishCoalesce(_optionalChain([options, 'access', _69 => _69.offset, 'optionalAccess', _70 => _70.y]), () => ( 0));
3015
+ const rings = [];
3016
+ const fit = options.curves === "fit";
3017
+ let fitted = "";
3018
+ for (const item of input) {
3019
+ const list = item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0]) ? item : [item];
3020
+ for (const ring of list) {
3021
+ let r = ring;
3022
+ if (options.simplify && options.simplify > 0) r = simplifyRing(r, options.simplify);
3023
+ if (r.length < 3) continue;
3024
+ if (fit) {
3025
+ fitted += fitPathData(r, {
3026
+ tolerance: _nullishCoalesce(options.curveTolerance, () => ( 0.25)),
3027
+ cornerAngleDeg: options.cornerAngleDeg,
3028
+ precision,
3029
+ offset: { x: dx, y: dy }
3030
+ });
3031
+ continue;
3032
+ }
3033
+ rings.push(dx || dy ? r.map(([x, y]) => [x + dx, y + dy]) : r);
3034
+ }
3035
+ }
3036
+ return fit ? fitted : ringsToPath(rings, f);
3037
+ }
3038
+
3039
+ // src/canvas/shapeNodes.ts
3040
+ var SHAPE_DATA_KEY = "shape";
3041
+ var SHAPE_NODE_TYPE = "shape";
3042
+ var SHAPE_OPERATIONS = ["unite", "intersect", "subtract", "exclude", "divide"];
3043
+ var BOOLEAN_FOR = {
3044
+ unite: "union",
3045
+ intersect: "intersection",
3046
+ subtract: "difference",
3047
+ exclude: "xor"
1689
3048
  };
1690
- function eventCode(event) {
1691
- if (event.code) return event.code;
1692
- if (KEY_TO_CODE[event.key]) return KEY_TO_CODE[event.key];
1693
- if (/^[a-z]$/i.test(event.key)) return `Key${event.key.toUpperCase()}`;
1694
- return event.key;
3049
+ function isShapeNode(node) {
3050
+ return !!node && isShapeDescription(_optionalChain([node, 'access', _71 => _71.data, 'optionalAccess', _72 => _72[SHAPE_DATA_KEY]]));
3051
+ }
3052
+ function getNodeShape(node) {
3053
+ const shape = _optionalChain([node, 'optionalAccess', _73 => _73.data, 'optionalAccess', _74 => _74[SHAPE_DATA_KEY]]);
3054
+ return isShapeDescription(shape) ? shape : null;
3055
+ }
3056
+ function shapeNodeToPolygon(node, lookup, options = {}) {
3057
+ const shape = getNodeShape(node);
3058
+ if (!shape) return null;
3059
+ const size = getNodeSize(node, options);
3060
+ const origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;
3061
+ const { rings, fillRule } = shapeToPolygon(shape, size, { tolerance: options.tolerance });
3062
+ return {
3063
+ rings: rings.map((ring) => ring.map(([x, y]) => [x + origin.x, y + origin.y])),
3064
+ fillRule
3065
+ };
1695
3066
  }
1696
- function matchArrangeShortcut(event, bindings = DEFAULT_ARRANGE_SHORTCUTS) {
1697
- if (isEventFromTextInput(event)) return null;
1698
- const code = eventCode(event);
1699
- const mod = event.metaKey || event.ctrlKey;
1700
- for (const binding of bindings) {
1701
- if (binding.code === code && !!binding.mod === mod && !!binding.alt === event.altKey && !!binding.shift === event.shiftKey) {
1702
- return binding.action;
3067
+ var idCounter = 0;
3068
+ function defaultId2(index) {
3069
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
3070
+ idCounter += 1;
3071
+ return `shape-${Date.now().toString(36)}-${idCounter}-${index}`;
3072
+ }
3073
+ function stackOrder(nodes, ids) {
3074
+ return nodes.map((node, index) => ({ node, index, z: typeof node.zIndex === "number" ? node.zIndex : 0 })).filter((entry) => ids.has(entry.node.id)).sort((a, b) => a.z - b.z || a.index - b.index).map((entry) => entry.node);
3075
+ }
3076
+ function canCombineShapeNodes(nodes, ids) {
3077
+ const idSet = new Set(ids);
3078
+ const shapes = nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));
3079
+ if (shapes.some((n) => isLocked(n))) return { ok: false, reason: "locked" };
3080
+ if (shapes.length < 2) return { ok: false, reason: "too-few" };
3081
+ return { ok: true, shapeIds: shapes.map((n) => n.id) };
3082
+ }
3083
+ function combineShapeNodes(nodes, ids, operation, options = {}) {
3084
+ const idList = [...ids];
3085
+ const check = canCombineShapeNodes(nodes, idList);
3086
+ if (!check.ok) return { ok: false, operation, reason: check.reason };
3087
+ const lookup = createNodeLookup(nodes);
3088
+ const shapeIds = new Set(check.shapeIds);
3089
+ const ordered = stackOrder(nodes, shapeIds);
3090
+ const operands = ordered.map((node) => shapeNodeToPolygon(node, lookup, options));
3091
+ const parentIds = new Set(ordered.map((n) => _nullishCoalesce(n.parentId, () => ( ""))));
3092
+ const sharedParent = parentIds.size === 1 ? ordered[0].parentId : void 0;
3093
+ const parentOrigin = sharedParent && lookup.get(sharedParent) ? getAbsolutePosition(lookup.get(sharedParent), lookup) : { x: 0, y: 0 };
3094
+ const pickSource = () => {
3095
+ const style = _nullishCoalesce(options.styleFrom, () => ( "bottom"));
3096
+ if (style === "top") return ordered[ordered.length - 1];
3097
+ if (style === "first") {
3098
+ const first = idList.find((id) => shapeIds.has(id));
3099
+ return first && lookup.get(first) || ordered[0];
3100
+ }
3101
+ if (typeof style === "object") return shapeIds.has(style.id) && lookup.get(style.id) || ordered[0];
3102
+ return ordered[0];
3103
+ };
3104
+ const createId = _nullishCoalesce(options.createId, () => ( defaultId2));
3105
+ const build = (polygon, source, index) => {
3106
+ const bounds = multiPolygonBounds(polygon);
3107
+ if (!bounds) return null;
3108
+ const width = bounds.maxX - bounds.minX;
3109
+ const height = bounds.maxY - bounds.minY;
3110
+ if (!(width > 0) || !(height > 0)) return null;
3111
+ const d = polygonToSvgPath(polygon, {
3112
+ precision: options.precision,
3113
+ simplify: options.simplify,
3114
+ offset: { x: -bounds.minX, y: -bounds.minY },
3115
+ curves: _nullishCoalesce(options.curves, () => ( "fit")),
3116
+ curveTolerance: _nullishCoalesce(_nullishCoalesce(options.curveTolerance, () => ( options.tolerance)), () => ( 0.25)),
3117
+ cornerAngleDeg: options.cornerAngleDeg
3118
+ });
3119
+ if (!d) return null;
3120
+ const sourceShape = getNodeShape(source);
3121
+ const shape = {
3122
+ kind: "path",
3123
+ d,
3124
+ viewBox: [0, 0, roundSize(width), roundSize(height)],
3125
+ fillRule: "nonzero"
3126
+ };
3127
+ for (const key of ["fill", "stroke", "strokeWidth", "opacity"]) {
3128
+ if (sourceShape[key] !== void 0) shape[key] = sourceShape[key];
1703
3129
  }
3130
+ const {
3131
+ [LOCKED_DATA_KEY]: _locked,
3132
+ [LOCKED_PREVIOUS_FLAGS_KEY]: _previous,
3133
+ ...data
3134
+ } = _nullishCoalesce(source.data, () => ( {}));
3135
+ const {
3136
+ measured: _measured,
3137
+ selected: _selected,
3138
+ dragging: _dragging,
3139
+ positionAbsolute: _positionAbsolute,
3140
+ internals: _internals,
3141
+ parentId: _parentId,
3142
+ ...rest
3143
+ } = source;
3144
+ const node = {
3145
+ ...rest,
3146
+ id: createId(index),
3147
+ position: { x: bounds.minX - parentOrigin.x, y: bounds.minY - parentOrigin.y },
3148
+ width,
3149
+ height,
3150
+ data: { ...data, [SHAPE_DATA_KEY]: shape }
3151
+ };
3152
+ if (sharedParent) node.parentId = sharedParent;
3153
+ if (options.nodeType !== void 0) node.type = options.nodeType;
3154
+ if (source.style) node.style = withStyleSize(source.style, { width, height });
3155
+ if (options.select !== false) node.selected = true;
3156
+ return node;
3157
+ };
3158
+ const created = [];
3159
+ if (operation === "divide") {
3160
+ const pieces = dividePolygons(operands, { epsilon: options.epsilon });
3161
+ pieces.forEach((piece) => {
3162
+ const source = ordered[piece.operands[piece.operands.length - 1]];
3163
+ const node = build([piece.polygon], source, created.length);
3164
+ if (node) created.push(node);
3165
+ });
3166
+ } else {
3167
+ const result = polygonBoolean(BOOLEAN_FOR[operation], operands, { epsilon: options.epsilon });
3168
+ const node = build(result, pickSource(), 0);
3169
+ if (node) created.push(node);
1704
3170
  }
1705
- return null;
3171
+ if (created.length === 0) return { ok: false, operation, reason: "empty" };
3172
+ return { ok: true, operation, removeIds: ordered.map((n) => n.id), nodes: created };
3173
+ }
3174
+ function roundSize(value) {
3175
+ return Math.round(value * 1e3) / 1e3;
3176
+ }
3177
+ function applyShapeCombination(nodes, change, options = {}) {
3178
+ const remove = new Set(change.removeIds);
3179
+ const insertAt = nodes.findIndex((n) => remove.has(n.id));
3180
+ const deselect = options.deselect !== false && change.nodes.some((n) => n.selected);
3181
+ const out = [];
3182
+ nodes.forEach((node, index) => {
3183
+ if (index === insertAt) out.push(...change.nodes);
3184
+ if (remove.has(node.id)) return;
3185
+ out.push(deselect && node.selected ? { ...node, selected: false } : node);
3186
+ });
3187
+ if (insertAt === -1) out.push(...change.nodes);
3188
+ return out;
3189
+ }
3190
+ function revertShapeCombination(nodes, change, previous) {
3191
+ const created = new Set(change.nodes.map((n) => n.id));
3192
+ const removed = new Set(change.removeIds);
3193
+ const out = nodes.filter((n) => !created.has(n.id));
3194
+ previous.forEach((node, index) => {
3195
+ if (!removed.has(node.id) || out.some((n) => n.id === node.id)) return;
3196
+ out.splice(Math.min(index, out.length), 0, node);
3197
+ });
3198
+ return out;
3199
+ }
3200
+
3201
+ // src/canvas/resizeSnap.ts
3202
+ var RESIZE_HANDLES = [
3203
+ "top-left",
3204
+ "top",
3205
+ "top-right",
3206
+ "right",
3207
+ "bottom-right",
3208
+ "bottom",
3209
+ "bottom-left",
3210
+ "left"
3211
+ ];
3212
+ function resizeHandleAxes(handle) {
3213
+ const x = handle.includes("left") ? -1 : handle.includes("right") ? 1 : 0;
3214
+ const y = handle.includes("top") ? -1 : handle.includes("bottom") ? 1 : 0;
3215
+ return { x, y };
3216
+ }
3217
+ function inferResizeHandle(start, next, eps = 1e-6) {
3218
+ const moved = (a, b) => Math.abs(a - b) > eps;
3219
+ const left = moved(start.x, next.x);
3220
+ const right = moved(start.x + start.width, next.x + next.width);
3221
+ const top = moved(start.y, next.y);
3222
+ const bottom = moved(start.y + start.height, next.y + next.height);
3223
+ const h = left ? "left" : right ? "right" : "";
3224
+ const v = top ? "top" : bottom ? "bottom" : "";
3225
+ if (h && v) return `${v}-${h}`;
3226
+ return h || v || null;
3227
+ }
3228
+ function within(axis, v, tolerance) {
3229
+ const { values } = axis;
3230
+ let lo = 0;
3231
+ let hi = values.length;
3232
+ while (lo < hi) {
3233
+ const mid = lo + hi >>> 1;
3234
+ if (values[mid] < v - tolerance) lo = mid + 1;
3235
+ else hi = mid;
3236
+ }
3237
+ const out = [];
3238
+ for (let i = lo; i < values.length && values[i] <= v + tolerance; i++) out.push(values[i]);
3239
+ return out;
3240
+ }
3241
+ function matching(axis, v, eps) {
3242
+ const { values, rects } = axis;
3243
+ const out = [];
3244
+ for (let i = 0; i < values.length; i++) if (Math.abs(values[i] - v) <= eps) out.push(rects[i]);
3245
+ return out;
3246
+ }
3247
+ function snapResize(rect, handle, targets, options = {}) {
3248
+ const index = Array.isArray(targets) ? createGuideIndex(targets) : targets;
3249
+ const zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;
3250
+ const tolerance = (_nullishCoalesce(options.threshold, () => ( 6))) / zoom;
3251
+ const eps = 0.5 / zoom;
3252
+ const useEdges = options.edges !== false;
3253
+ const useCentres = options.centres !== false;
3254
+ const grid = _nullishCoalesce(options.gridSize, () => ( 0));
3255
+ const axes = resizeHandleAxes(handle);
3256
+ const unchanged = { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false };
3257
+ if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) return unchanged;
3258
+ const x0 = rect.x;
3259
+ const x1 = rect.x + rect.width;
3260
+ const y0 = rect.y;
3261
+ const y1 = rect.y + rect.height;
3262
+ const bestHit = (horizontal) => {
3263
+ const dir = horizontal ? axes.x : axes.y;
3264
+ if (dir === 0) return null;
3265
+ const moving = horizontal ? dir < 0 ? x0 : x1 : dir < 0 ? y0 : y1;
3266
+ const fixed = horizontal ? dir < 0 ? x1 : x0 : dir < 0 ? y1 : y0;
3267
+ const min = _nullishCoalesce((horizontal ? options.minWidth : options.minHeight), () => ( 0));
3268
+ const valid = (t) => {
3269
+ const size = dir < 0 ? fixed - t : t - fixed;
3270
+ return Number.isFinite(size) && size > 1e-9 && size >= min;
3271
+ };
3272
+ let best = null;
3273
+ const consider = (values, kind) => {
3274
+ for (const t of values) {
3275
+ if (!valid(t)) continue;
3276
+ if (!best || Math.abs(t - moving) < Math.abs(best.value - moving)) best = { value: t, kind };
3277
+ }
3278
+ };
3279
+ if (useEdges) consider(within(horizontal ? index.edgeX : index.edgeY, moving, tolerance), "edge");
3280
+ if (useCentres) consider(within(horizontal ? index.centreX : index.centreY, moving, tolerance), "centre");
3281
+ if (!best && grid > 0) {
3282
+ const origin = horizontal ? _nullishCoalesce(_optionalChain([options, 'access', _75 => _75.gridOrigin, 'optionalAccess', _76 => _76.x]), () => ( 0)) : _nullishCoalesce(_optionalChain([options, 'access', _77 => _77.gridOrigin, 'optionalAccess', _78 => _78.y]), () => ( 0));
3283
+ consider([snapToGrid(moving, grid, origin)], "grid");
3284
+ }
3285
+ return best;
3286
+ };
3287
+ let nx0 = x0;
3288
+ let nx1 = x1;
3289
+ let ny0 = y0;
3290
+ let ny1 = y1;
3291
+ const setEdge = (horizontal, value) => {
3292
+ const dir = horizontal ? axes.x : axes.y;
3293
+ if (horizontal) {
3294
+ if (dir < 0) nx0 = value;
3295
+ else nx1 = value;
3296
+ } else if (dir < 0) ny0 = value;
3297
+ else ny1 = value;
3298
+ };
3299
+ let hitX = bestHit(true);
3300
+ let hitY = bestHit(false);
3301
+ const ratio = rect.height > 0 ? rect.width / rect.height : 0;
3302
+ if (options.keepAspectRatio && ratio > 0 && (hitX || hitY)) {
3303
+ if (axes.x !== 0 && axes.y !== 0) {
3304
+ const dist2 = (h, horizontal) => h ? Math.abs(h.value - (horizontal ? axes.x < 0 ? x0 : x1 : axes.y < 0 ? y0 : y1)) : Infinity;
3305
+ const useX = dist2(hitX, true) <= dist2(hitY, false);
3306
+ if (useX && hitX) {
3307
+ setEdge(true, hitX.value);
3308
+ const height = (nx1 - nx0) / ratio;
3309
+ if (axes.y < 0) ny0 = ny1 - height;
3310
+ else ny1 = ny0 + height;
3311
+ hitY = null;
3312
+ } else if (hitY) {
3313
+ setEdge(false, hitY.value);
3314
+ const width = (ny1 - ny0) * ratio;
3315
+ if (axes.x < 0) nx0 = nx1 - width;
3316
+ else nx1 = nx0 + width;
3317
+ hitX = null;
3318
+ }
3319
+ const min = (v) => _nullishCoalesce(v, () => ( 0));
3320
+ if (nx1 - nx0 < min(options.minWidth) || ny1 - ny0 < min(options.minHeight) || !(nx1 > nx0 && ny1 > ny0)) {
3321
+ return unchanged;
3322
+ }
3323
+ } else if (axes.x !== 0 && hitX) {
3324
+ setEdge(true, hitX.value);
3325
+ const height = (nx1 - nx0) / ratio;
3326
+ const cy = (y0 + y1) / 2;
3327
+ ny0 = cy - height / 2;
3328
+ ny1 = cy + height / 2;
3329
+ } else if (axes.y !== 0 && hitY) {
3330
+ setEdge(false, hitY.value);
3331
+ const width = (ny1 - ny0) * ratio;
3332
+ const cx = (x0 + x1) / 2;
3333
+ nx0 = cx - width / 2;
3334
+ nx1 = cx + width / 2;
3335
+ }
3336
+ } else {
3337
+ if (hitX) setEdge(true, hitX.value);
3338
+ if (hitY) setEdge(false, hitY.value);
3339
+ }
3340
+ const out = { x: nx0, y: ny0, width: nx1 - nx0, height: ny1 - ny0 };
3341
+ const guides = [];
3342
+ const guideFor = (horizontal, hit) => {
3343
+ if (!hit || hit.kind === "grid") return;
3344
+ const axis = hit.kind === "edge" ? horizontal ? index.edgeX : index.edgeY : horizontal ? index.centreX : index.centreY;
3345
+ const aligned = matching(axis, hit.value, eps);
3346
+ const span = _chunkWADCJ7A7cjs.boundsOf.call(void 0, [out, ...aligned]);
3347
+ guides.push(
3348
+ horizontal ? { orientation: "vertical", kind: hit.kind, x1: hit.value, y1: span.y, x2: hit.value, y2: span.y + span.height } : { orientation: "horizontal", kind: hit.kind, x1: span.x, y1: hit.value, x2: span.x + span.width, y2: hit.value }
3349
+ );
3350
+ };
3351
+ guideFor(true, hitX);
3352
+ guideFor(false, hitY);
3353
+ const changed = out.x !== rect.x || out.y !== rect.y || out.width !== rect.width || out.height !== rect.height;
3354
+ return {
3355
+ rect: out,
3356
+ guides,
3357
+ snappedX: !!hitX && hitX.kind !== "grid",
3358
+ snappedY: !!hitY && hitY.kind !== "grid",
3359
+ changed
3360
+ };
3361
+ }
3362
+ function snapNodeResize(nodes, id, rect, handle, options = {}) {
3363
+ const lookup = createNodeLookup(nodes);
3364
+ const node = lookup.get(id);
3365
+ if (!node || isLocked(node)) {
3366
+ return { rect: { ...rect }, guides: [], snappedX: false, snappedY: false, changed: false, updates: [] };
3367
+ }
3368
+ const index = _nullishCoalesce(options.index, () => ( createNodeGuideIndex(nodes, [id], options)));
3369
+ const result = snapResize(rect, handle, index, options);
3370
+ if (!result.changed) return { ...result, updates: [] };
3371
+ const parent = node.parentId ? lookup.get(node.parentId) : void 0;
3372
+ const origin = parent ? getAbsolutePosition(parent, lookup) : { x: 0, y: 0 };
3373
+ return {
3374
+ ...result,
3375
+ updates: [
3376
+ {
3377
+ id,
3378
+ position: { x: result.rect.x - origin.x, y: result.rect.y - origin.y },
3379
+ width: result.rect.width,
3380
+ height: result.rect.height
3381
+ }
3382
+ ]
3383
+ };
3384
+ }
3385
+ function getResizingNodeRect(node, lookup, options = {}) {
3386
+ const { x, y } = node.parentId ? getAbsolutePosition(node, lookup) : node.position;
3387
+ const size = getNodeSize(node, options);
3388
+ return { x, y, width: _nullishCoalesce(node.width, () => ( size.width)), height: _nullishCoalesce(node.height, () => ( size.height)) };
1706
3389
  }
1707
3390
 
1708
3391
  // src/canvas/arrangeController.ts
@@ -1710,11 +3393,16 @@ function createCanvasArrangeController(options) {
1710
3393
  let guides = [];
1711
3394
  let dragIndex = null;
1712
3395
  let dragKey = "";
1713
- const selectedIds = (ids) => ids ? [...ids] : _nullishCoalesce(_optionalChain([options, 'access', _51 => _51.getSelectedIds, 'optionalCall', _52 => _52()]), () => ( options.getNodes().filter((n) => n.selected).map((n) => n.id)));
3396
+ let lastSnapped = /* @__PURE__ */ new Map();
3397
+ let resizeId = null;
3398
+ let resizeStart = null;
3399
+ let resizeIndex = null;
3400
+ let lastResized = null;
3401
+ const selectedIds = (ids) => ids ? [...ids] : _nullishCoalesce(_optionalChain([options, 'access', _79 => _79.getSelectedIds, 'optionalCall', _80 => _80()]), () => ( options.getNodes().filter((n) => n.selected).map((n) => n.id)));
1714
3402
  const setGuides = (next) => {
1715
3403
  if (next.length === 0 && guides.length === 0) return;
1716
3404
  guides = next;
1717
- _optionalChain([options, 'access', _53 => _53.onGuidesChange, 'optionalCall', _54 => _54(guides)]);
3405
+ _optionalChain([options, 'access', _81 => _81.onGuidesChange, 'optionalCall', _82 => _82(guides)]);
1718
3406
  };
1719
3407
  function apply(name, updates) {
1720
3408
  if (updates.length === 0) return updates;
@@ -1777,6 +3465,30 @@ function createCanvasArrangeController(options) {
1777
3465
  return anyUnlocked ? controller.lock(list) : controller.unlock(list);
1778
3466
  },
1779
3467
  isLocked: (id) => isLocked(options.getNodes().find((n) => n.id === id)),
3468
+ /**
3469
+ * Freeform shape operation (Unite / Intersect / Subtract / Exclude / Divide) on the
3470
+ * shape nodes among `ids` (default: the selection). Replaces them with the result as
3471
+ * one undoable step. Refuses without changing anything when a selected shape is
3472
+ * locked, fewer than two shapes are selected, or the result is empty.
3473
+ */
3474
+ combineShapes(operation, ids, combineOptions = {}) {
3475
+ const before = options.getNodes();
3476
+ const result = combineShapeNodes(before, selectedIds(ids), operation, { ...rect(), ...combineOptions });
3477
+ if (!result.ok) return result;
3478
+ const name = `${operation[0].toUpperCase()}${operation.slice(1)} Shapes`;
3479
+ const run = () => options.setNodes(applyShapeCombination(options.getNodes(), result));
3480
+ const undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));
3481
+ if (options.history) options.history.execute({ name, execute: run, undo });
3482
+ else run();
3483
+ return result;
3484
+ },
3485
+ unite: (ids, o) => controller.combineShapes("unite", ids, o),
3486
+ intersect: (ids, o) => controller.combineShapes("intersect", ids, o),
3487
+ subtract: (ids, o) => controller.combineShapes("subtract", ids, o),
3488
+ exclude: (ids, o) => controller.combineShapes("exclude", ids, o),
3489
+ divide: (ids, o) => controller.combineShapes("divide", ids, o),
3490
+ /** Whether the shape operations can run on `ids` (default: the selection). */
3491
+ canCombineShapes: (ids) => canCombineShapeNodes(options.getNodes(), selectedIds(ids)).ok,
1780
3492
  /**
1781
3493
  * Runs a shortcut action. Scene actions are not handled here (see the scenes store);
1782
3494
  * returns `false` for them and for unknown actions.
@@ -1848,11 +3560,13 @@ function createCanvasArrangeController(options) {
1848
3560
  const result = snapNodeDrag(options.getNodes(), list, {
1849
3561
  ...rect(),
1850
3562
  ...options.guides,
1851
- zoom: _nullishCoalesce(_optionalChain([options, 'access', _55 => _55.getZoom, 'optionalCall', _56 => _56()]), () => ( 1)),
3563
+ zoom: _nullishCoalesce(_optionalChain([options, 'access', _83 => _83.getZoom, 'optionalCall', _84 => _84()]), () => ( 1)),
1852
3564
  index: dragIndex
1853
3565
  });
3566
+ lastSnapped = /* @__PURE__ */ new Map();
1854
3567
  if (result.updates.length > 0) {
1855
3568
  options.setNodes(applyNodeUpdates(options.getNodes(), result.updates));
3569
+ for (const u of result.updates) if (u.position) lastSnapped.set(u.id, u.position);
1856
3570
  }
1857
3571
  setGuides(result.guides);
1858
3572
  return result;
@@ -1863,10 +3577,17 @@ function createCanvasArrangeController(options) {
1863
3577
  * A batch without a dragging position change ends the drag and clears the guides.
1864
3578
  */
1865
3579
  snapPositionChanges(changes) {
1866
- const dragging = changes.filter((c) => c.type === "position" && c.position && c.dragging);
3580
+ const dragging = changes.filter(
3581
+ (c) => c.type === "position" && typeof c.id === "string" && !!c.position && !!c.dragging
3582
+ );
1867
3583
  if (dragging.length === 0) {
1868
- if (changes.some((c) => c.type === "position" && c.dragging === false)) controller.endDrag();
1869
- return changes;
3584
+ if (!changes.some((c) => c.type === "position" && c.dragging === false)) return changes;
3585
+ const snapped = lastSnapped;
3586
+ lastSnapped = /* @__PURE__ */ new Map();
3587
+ controller.endDrag();
3588
+ const keep = (c) => c.type === "position" && c.dragging === false && !!c.position && c.id !== void 0 && snapped.has(c.id);
3589
+ if (!changes.some(keep)) return changes;
3590
+ return changes.map((c) => keep(c) ? { ...c, position: snapped.get(c.id) } : c);
1870
3591
  }
1871
3592
  if (options.snapping === false) return changes;
1872
3593
  const ids = dragging.map((c) => c.id);
@@ -1877,28 +3598,359 @@ function createCanvasArrangeController(options) {
1877
3598
  const result = snapNodeDrag(nodes, ids, {
1878
3599
  ...rect(),
1879
3600
  ...options.guides,
1880
- zoom: _nullishCoalesce(_optionalChain([options, 'access', _57 => _57.getZoom, 'optionalCall', _58 => _58()]), () => ( 1)),
3601
+ zoom: _nullishCoalesce(_optionalChain([options, 'access', _85 => _85.getZoom, 'optionalCall', _86 => _86()]), () => ( 1)),
1881
3602
  index: dragIndex
1882
3603
  });
1883
3604
  setGuides(result.guides);
1884
3605
  const { x: dx, y: dy } = result.offset;
3606
+ lastSnapped = /* @__PURE__ */ new Map();
1885
3607
  if (dx === 0 && dy === 0) return changes;
1886
3608
  const parents = new Map(nodes.map((n) => [n.id, n.parentId]));
1887
3609
  const shift = new Set(ids.filter((id) => !proposed.has(_nullishCoalesce(parents.get(id), () => ( "")))));
1888
- return changes.map(
1889
- (c) => c.type === "position" && c.position && c.dragging && shift.has(c.id) ? { ...c, position: { x: c.position.x + dx, y: c.position.y + dy } } : c
3610
+ return changes.map((c) => {
3611
+ if (!(c.type === "position" && c.position && c.dragging && c.id !== void 0 && shift.has(c.id))) return c;
3612
+ const position = { x: c.position.x + dx, y: c.position.y + dy };
3613
+ lastSnapped.set(c.id, position);
3614
+ return { ...c, position };
3615
+ });
3616
+ },
3617
+ /**
3618
+ * Starts resize snapping for node `id`: records its box (to tell which handle is
3619
+ * dragged) and caches the snap targets. Optional: `resize` starts lazily.
3620
+ */
3621
+ beginResize(id) {
3622
+ const nodes = options.getNodes();
3623
+ const lookup = createNodeLookup(nodes);
3624
+ const node = lookup.get(id);
3625
+ resizeId = id;
3626
+ resizeStart = node ? getResizingNodeRect(node, lookup, rect()) : null;
3627
+ resizeIndex = createNodeGuideIndex(nodes, [id], rect());
3628
+ lastResized = null;
3629
+ },
3630
+ /**
3631
+ * Snaps node `id` as currently sized in `getNodes()` (call it after the flow wrote the
3632
+ * new size, e.g. SvelteFlow's `NodeResizer` `onResize`, deferred by a microtask) and
3633
+ * writes the snapped box back through `setNodes` (not recorded in history). `handle`
3634
+ * defaults to the one inferred from the edges that moved since `beginResize`. A locked
3635
+ * node is restored to its starting box.
3636
+ */
3637
+ resize(id, handle) {
3638
+ if (resizeId !== id) controller.beginResize(id);
3639
+ const nodes = options.getNodes();
3640
+ const lookup = createNodeLookup(nodes);
3641
+ const node = lookup.get(id);
3642
+ if (!node || !resizeStart) return null;
3643
+ const proposed = getResizingNodeRect(node, lookup, rect());
3644
+ if (isLocked(node)) {
3645
+ const start = resizeStart;
3646
+ const parent = node.parentId ? lookup.get(node.parentId) : void 0;
3647
+ const origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };
3648
+ if (proposed.x !== start.x || proposed.y !== start.y || proposed.width !== start.width || proposed.height !== start.height) {
3649
+ options.setNodes(
3650
+ applyNodeUpdates(nodes, [
3651
+ { id, position: { x: start.x - origin.x, y: start.y - origin.y }, width: start.width, height: start.height }
3652
+ ])
3653
+ );
3654
+ }
3655
+ return null;
3656
+ }
3657
+ if ((_nullishCoalesce(options.resizeSnapping, () => ( options.snapping))) === false) return null;
3658
+ const h = _nullishCoalesce(handle, () => ( inferResizeHandle(resizeStart, proposed)));
3659
+ if (!h) {
3660
+ setGuides([]);
3661
+ return null;
3662
+ }
3663
+ const result = snapNodeResize(nodes, id, proposed, h, {
3664
+ ...rect(),
3665
+ ...options.guides,
3666
+ ...options.resize,
3667
+ zoom: _nullishCoalesce(_optionalChain([options, 'access', _87 => _87.getZoom, 'optionalCall', _88 => _88()]), () => ( 1)),
3668
+ index: resizeIndex
3669
+ });
3670
+ lastResized = result.updates.length > 0 ? result : null;
3671
+ if (result.updates.length > 0) options.setNodes(applyNodeUpdates(nodes, result.updates));
3672
+ setGuides(result.guides);
3673
+ return result;
3674
+ },
3675
+ /**
3676
+ * Ends resize snapping: clears guides and re-applies the last snapped box if the flow
3677
+ * wrote the raw pointer size afterwards.
3678
+ */
3679
+ endResize() {
3680
+ const last = lastResized;
3681
+ resizeId = null;
3682
+ resizeStart = null;
3683
+ resizeIndex = null;
3684
+ lastResized = null;
3685
+ setGuides([]);
3686
+ const update = _optionalChain([last, 'optionalAccess', _89 => _89.updates, 'access', _90 => _90[0]]);
3687
+ if (!update) return;
3688
+ const node = options.getNodes().find((n) => n.id === update.id);
3689
+ if (!node) return;
3690
+ const drift = node.position.x !== update.position.x || node.position.y !== update.position.y || node.width !== update.width || node.height !== update.height;
3691
+ if (drift) options.setNodes(applyNodeUpdates(options.getNodes(), [update]));
3692
+ },
3693
+ /**
3694
+ * React Flow style: snaps the `dimensions` (and matching `position`) changes a
3695
+ * `NodeResizer` emits in an `onNodesChange` batch and updates `guides`. The resize-end
3696
+ * change (`resizing: false`) carries the raw pointer size, so the last snapped size is
3697
+ * kept. Resize changes of locked nodes are dropped. Returns the (possibly new) changes.
3698
+ */
3699
+ snapResizeChanges(changes) {
3700
+ const dims = changes.find(
3701
+ (c) => c.type === "dimensions" && typeof c.id === "string" && !!c.dimensions && typeof c.resizing === "boolean"
1890
3702
  );
3703
+ if (!dims) return changes;
3704
+ const id = dims.id;
3705
+ const nodes = options.getNodes();
3706
+ const lookup = createNodeLookup(nodes);
3707
+ const node = lookup.get(id);
3708
+ const isResizeChange = (c) => c.id === id && (c.type === "dimensions" || c.type === "position");
3709
+ if (node && isLocked(node)) {
3710
+ if (dims.resizing === false) controller.endResize();
3711
+ return changes.filter((c) => !isResizeChange(c));
3712
+ }
3713
+ if (dims.resizing === false) {
3714
+ const last = _optionalChain([lastResized, 'optionalAccess', _91 => _91.updates, 'access', _92 => _92[0]]);
3715
+ lastResized = null;
3716
+ controller.endResize();
3717
+ if (!last || last.id !== id) return changes;
3718
+ return changes.map(
3719
+ (c) => c === dims ? { ...c, dimensions: { width: last.width, height: last.height } } : c
3720
+ );
3721
+ }
3722
+ if (!node || (_nullishCoalesce(options.resizeSnapping, () => ( options.snapping))) === false) return changes;
3723
+ if (resizeId !== id) controller.beginResize(id);
3724
+ if (!resizeStart) return changes;
3725
+ const posChange = changes.find((c) => c.id === id && c.type === "position" && !!c.position);
3726
+ const parent = node.parentId ? lookup.get(node.parentId) : void 0;
3727
+ const origin = parent ? getResizingNodeRect(parent, lookup, rect()) : { x: 0, y: 0 };
3728
+ const position = _nullishCoalesce(_optionalChain([posChange, 'optionalAccess', _93 => _93.position]), () => ( node.position));
3729
+ const proposed = {
3730
+ x: position.x + origin.x,
3731
+ y: position.y + origin.y,
3732
+ width: dims.dimensions.width,
3733
+ height: dims.dimensions.height
3734
+ };
3735
+ const handle = inferResizeHandle(resizeStart, proposed);
3736
+ if (!handle) {
3737
+ setGuides([]);
3738
+ return changes;
3739
+ }
3740
+ const result = snapNodeResize(nodes, id, proposed, handle, {
3741
+ ...rect(),
3742
+ ...options.guides,
3743
+ ...options.resize,
3744
+ zoom: _nullishCoalesce(_optionalChain([options, 'access', _94 => _94.getZoom, 'optionalCall', _95 => _95()]), () => ( 1)),
3745
+ index: resizeIndex
3746
+ });
3747
+ setGuides(result.guides);
3748
+ const update = result.updates[0];
3749
+ lastResized = update ? result : null;
3750
+ if (!update) return changes;
3751
+ const next = changes.map((c) => {
3752
+ if (c === dims) return { ...c, dimensions: { width: update.width, height: update.height } };
3753
+ if (c === posChange) return { ...c, position: update.position };
3754
+ return c;
3755
+ });
3756
+ if (!posChange && (update.position.x !== node.position.x || update.position.y !== node.position.y)) {
3757
+ next.push({ id, type: "position", position: update.position });
3758
+ }
3759
+ return next;
1891
3760
  },
1892
- /** Clears guides and the cached snap targets. */
3761
+ /**
3762
+ * Clears guides and the cached snap targets. Call it from the flow's drag-stop
3763
+ * handler: flow libraries write the raw pointer position when a drag ends, so the
3764
+ * last snapped positions from `drag` are written back here.
3765
+ */
1893
3766
  endDrag() {
3767
+ const snapped = lastSnapped;
1894
3768
  dragIndex = null;
1895
3769
  dragKey = "";
3770
+ lastSnapped = /* @__PURE__ */ new Map();
1896
3771
  setGuides([]);
3772
+ if (snapped.size === 0) return;
3773
+ const nodes = options.getNodes();
3774
+ const drift = nodes.some((n) => {
3775
+ const p = snapped.get(n.id);
3776
+ return !!p && (n.position.x !== p.x || n.position.y !== p.y);
3777
+ });
3778
+ if (drift) {
3779
+ options.setNodes(applyNodeUpdates(nodes, [...snapped].map(([id, position]) => ({ id, position }))));
3780
+ }
1897
3781
  }
1898
3782
  };
1899
3783
  return controller;
1900
3784
  }
1901
3785
 
3786
+ // src/canvas/measure.ts
3787
+ var EPS = 1e-6;
3788
+ function measureLabel(distance) {
3789
+ const v = Math.round(Math.abs(distance) * 100) / 100;
3790
+ return Object.is(v, -0) ? "0" : String(v);
3791
+ }
3792
+ var toBox = (r) => ({ x0: r.x, y0: r.y, x1: r.x + r.width, y1: r.y + r.height });
3793
+ var finiteBox = (b) => [b.x0, b.y0, b.x1, b.y1].every(Number.isFinite);
3794
+ function line(kind, x1, y1, x2, y2) {
3795
+ return { orientation: x1 === x2 ? "vertical" : "horizontal", kind, x1, y1, x2, y2 };
3796
+ }
3797
+ function measured(out, x1, y1, x2, y2) {
3798
+ const d = Math.hypot(x2 - x1, y2 - y1);
3799
+ if (d > EPS && Number.isFinite(d)) out.push({ ...line("measure", x1, y1, x2, y2), label: measureLabel(d) });
3800
+ }
3801
+ function extension(out, x1, y1, x2, y2) {
3802
+ if (Math.hypot(x2 - x1, y2 - y1) > EPS) out.push(line("extension", x1, y1, x2, y2));
3803
+ }
3804
+ function contains(outer, inner) {
3805
+ return outer.x0 <= inner.x0 + EPS && outer.y0 <= inner.y0 + EPS && outer.x1 >= inner.x1 - EPS && outer.y1 >= inner.y1 - EPS;
3806
+ }
3807
+ function overlapMid(a0, a1, b0, b1) {
3808
+ const lo = Math.max(a0, b0);
3809
+ const hi = Math.min(a1, b1);
3810
+ return lo <= hi ? (lo + hi) / 2 : null;
3811
+ }
3812
+ function inside(out, inner, outer) {
3813
+ const cx = (inner.x0 + inner.x1) / 2;
3814
+ const cy = (inner.y0 + inner.y1) / 2;
3815
+ measured(out, outer.x0, cy, inner.x0, cy);
3816
+ measured(out, inner.x1, cy, outer.x1, cy);
3817
+ measured(out, cx, outer.y0, cx, inner.y0);
3818
+ measured(out, cx, inner.y1, cx, outer.y1);
3819
+ }
3820
+ function measureBetween(a, b) {
3821
+ const out = [];
3822
+ const A = toBox(a);
3823
+ const B = toBox(b);
3824
+ if (!finiteBox(A) || !finiteBox(B)) return out;
3825
+ if (contains(B, A)) {
3826
+ inside(out, A, B);
3827
+ return out;
3828
+ }
3829
+ if (contains(A, B)) {
3830
+ inside(out, B, A);
3831
+ return out;
3832
+ }
3833
+ const ox = overlapMid(A.x0, A.x1, B.x0, B.x1);
3834
+ const oy = overlapMid(A.y0, A.y1, B.y0, B.y1);
3835
+ const overlapping = A.x0 < B.x1 && B.x0 < A.x1 && A.y0 < B.y1 && B.y0 < A.y1;
3836
+ if (overlapping && ox !== null && oy !== null) {
3837
+ measured(out, A.x0, oy, B.x0, oy);
3838
+ measured(out, A.x1, oy, B.x1, oy);
3839
+ measured(out, ox, A.y0, ox, B.y0);
3840
+ measured(out, ox, A.y1, ox, B.y1);
3841
+ return out;
3842
+ }
3843
+ const cx = (A.x0 + A.x1) / 2;
3844
+ const cy = (A.y0 + A.y1) / 2;
3845
+ const gapX = B.x0 >= A.x1 ? [A.x1, B.x0] : B.x1 <= A.x0 ? [A.x0, B.x1] : null;
3846
+ if (gapX) {
3847
+ const [from, to] = gapX;
3848
+ const y = _nullishCoalesce(oy, () => ( cy));
3849
+ measured(out, from, y, to, y);
3850
+ if (oy === null) extension(out, to, y, to, y < B.y0 ? B.y0 : B.y1);
3851
+ }
3852
+ const gapY = B.y0 >= A.y1 ? [A.y1, B.y0] : B.y1 <= A.y0 ? [A.y0, B.y1] : null;
3853
+ if (gapY) {
3854
+ const [from, to] = gapY;
3855
+ const x = _nullishCoalesce(ox, () => ( cx));
3856
+ measured(out, x, from, x, to);
3857
+ if (ox === null) extension(out, x, to, x < B.x0 ? B.x0 : B.x1, to);
3858
+ }
3859
+ return out;
3860
+ }
3861
+ function measureToLine(a, orientation, at) {
3862
+ const out = [];
3863
+ const A = toBox(a);
3864
+ if (!finiteBox(A) || !Number.isFinite(at)) return out;
3865
+ const vertical = orientation === "vertical";
3866
+ const cx = (A.x0 + A.x1) / 2;
3867
+ const cy = (A.y0 + A.y1) / 2;
3868
+ const [lo, hi] = vertical ? [A.x0, A.x1] : [A.y0, A.y1];
3869
+ const seg = (from, to) => vertical ? measured(out, from, cy, to, cy) : measured(out, cx, from, cx, to);
3870
+ if (at >= hi) seg(hi, at);
3871
+ else if (at <= lo) seg(at, lo);
3872
+ else {
3873
+ seg(lo, at);
3874
+ seg(at, hi);
3875
+ }
3876
+ return out;
3877
+ }
3878
+ function measureSelection(nodes, selectedIds, hoveredId, options = {}) {
3879
+ const none = (selection2 = null) => ({ guides: [], target: null, selection: selection2 });
3880
+ const lookup = createNodeLookup(nodes);
3881
+ const selected = [...new Set(selectedIds)].map((id) => lookup.get(id)).filter((n) => !!n && n.hidden !== true);
3882
+ if (selected.length === 0) return none();
3883
+ const selection = _chunkWADCJ7A7cjs.boundsOf.call(void 0, selected.map((n) => getNodeRect(n, lookup, options)));
3884
+ if (!selection) return none();
3885
+ const selectedSet = new Set(selected.map((n) => n.id));
3886
+ let targetId = _nullishCoalesce(hoveredId, () => ( null));
3887
+ if (targetId === null || selectedSet.has(targetId)) {
3888
+ const parentId = selected[0].parentId;
3889
+ const sameParent = selected.every((n) => n.parentId === parentId);
3890
+ targetId = parentId && sameParent && !selectedSet.has(parentId) ? parentId : null;
3891
+ }
3892
+ const target = targetId !== null ? lookup.get(targetId) : void 0;
3893
+ if (!target || target.hidden === true) return none(selection);
3894
+ const rect = getNodeRect(target, lookup, options);
3895
+ return { guides: measureBetween(selection, rect), target: rect, selection };
3896
+ }
3897
+ function isMeasureModifierHeld(event) {
3898
+ if (!event) return false;
3899
+ if (event.type === "keyup" && event.key === "Alt") return false;
3900
+ return event.altKey === true || event.type === "keydown" && event.key === "Alt";
3901
+ }
3902
+
3903
+
3904
+
3905
+
3906
+
3907
+
3908
+
3909
+
3910
+
3911
+
3912
+
3913
+
3914
+
3915
+
3916
+
3917
+
3918
+
3919
+
3920
+
3921
+
3922
+
3923
+
3924
+
3925
+
3926
+
3927
+
3928
+
3929
+
3930
+
3931
+
3932
+
3933
+
3934
+
3935
+
3936
+
3937
+
3938
+
3939
+
3940
+
3941
+
3942
+
3943
+
3944
+
3945
+
3946
+
3947
+
3948
+
3949
+
3950
+
3951
+
3952
+
3953
+
1902
3954
 
1903
3955
 
1904
3956
 
@@ -1978,5 +4030,5 @@ function createCanvasArrangeController(options) {
1978
4030
 
1979
4031
 
1980
4032
 
1981
- exports.CONNECTION_LINE_TYPES = CONNECTION_LINE_TYPES; exports.CanvasScenesStore = CanvasScenesStore; exports.DEFAULT_ARRANGE_SHORTCUTS = DEFAULT_ARRANGE_SHORTCUTS; exports.DEFAULT_NODE_HEIGHT = _chunkWADCJ7A7cjs.DEFAULT_NODE_HEIGHT; exports.DEFAULT_NODE_WIDTH = _chunkWADCJ7A7cjs.DEFAULT_NODE_WIDTH; exports.DEFAULT_PLACEMENT_GAP = _chunkWADCJ7A7cjs.DEFAULT_PLACEMENT_GAP; exports.LOCKED_DATA_KEY = LOCKED_DATA_KEY; exports.LOCKED_PREVIOUS_FLAGS_KEY = LOCKED_PREVIOUS_FLAGS_KEY; exports.ManualClock = ManualClock; exports.StreamingLayoutManager = StreamingLayoutManager; exports.alignNodes = alignNodes; exports.applyNodeUpdates = applyNodeUpdates; exports.blendLayout = blendLayout; exports.boundsOf = _chunkWADCJ7A7cjs.boundsOf; exports.calculateDagreReflowDisplacement = calculateDagreReflowDisplacement; exports.calculateDirectionalDisplacement = calculateDirectionalDisplacement; exports.canvasHeight = canvasHeight; exports.centerNodes = _chunkWADCJ7A7cjs.centerNodes; exports.centre = _chunkWADCJ7A7cjs.centre; exports.collectDescendantIds = collectDescendantIds; exports.computeNodesBoundingBox = computeNodesBoundingBox; exports.computeSnapGuides = computeSnapGuides; exports.connectedOnly = connectedOnly; exports.createBezierTrajectory = createBezierTrajectory; exports.createCanvasArrangeController = createCanvasArrangeController; exports.createCanvasScenesStore = createCanvasScenesStore; exports.createFanOutTrajectory = createFanOutTrajectory; exports.createGuideIndex = createGuideIndex; exports.createNodeGuideIndex = createNodeGuideIndex; exports.createNodeLookup = createNodeLookup; exports.defaultEasing = defaultEasing; exports.distributeNodes = distributeNodes; exports.ensureLayout = _chunkWADCJ7A7cjs.ensureLayout; exports.estimateCanvasHeight = estimateCanvasHeight; exports.estimateNodeSize = _chunkWADCJ7A7cjs.estimateNodeSize; exports.findConnectorSnap = findConnectorSnap; exports.fitViewportToNodes = fitViewportToNodes; exports.fitViewportToRect = fitViewportToRect; exports.flowSide = _chunkWADCJ7A7cjs.flowSide; exports.getAbsolutePosition = getAbsolutePosition; exports.getCenteredNodePosition = _chunkWADCJ7A7cjs.getCenteredNodePosition; exports.getLayoutedNodes = _chunkWADCJ7A7cjs.getLayoutedNodes; exports.getNodeRect = getNodeRect; exports.getNodeSize = getNodeSize; exports.handlePoint = handlePoint; exports.invertNodeUpdates = invertNodeUpdates; exports.isEventFromTextInput = isEventFromTextInput; exports.isLocked = isLocked; exports.isValidCanvasEdge = isValidCanvasEdge; exports.isValidCanvasNode = isValidCanvasNode; exports.isValidCanvasScene = isValidCanvasScene; exports.isValidUrl = isValidUrl; exports.layoutBounds = layoutBounds; exports.linearPositionTrajectory = linearPositionTrajectory; exports.linearTrajectory = linearTrajectory; exports.lockNodes = lockNodes; exports.matchArrangeShortcut = matchArrangeShortcut; exports.matchSize = matchSize; exports.nearestHandlePositions = nearestHandlePositions; exports.oppositePosition = oppositePosition; exports.overlaps = _chunkWADCJ7A7cjs.overlaps; exports.pageHidden = pageHidden; exports.parseCanvasClipboardData = parseCanvasClipboardData; exports.parseCanvasScenes = parseCanvasScenes; exports.placeBeside = _chunkWADCJ7A7cjs.placeBeside; exports.positionToSide = positionToSide; exports.readStyleSize = readStyleSize; exports.realClock = realClock; exports.remapPastedNodes = remapPastedNodes; exports.reorderNodes = reorderNodes; exports.repositionNodesTo = repositionNodesTo; exports.runMultiNodeTransition = runMultiNodeTransition; exports.serializeCanvasNodes = serializeCanvasNodes; exports.sideToPosition = sideToPosition; exports.snapNodeDrag = snapNodeDrag; exports.toggleLockNodes = toggleLockNodes; exports.unlockNodes = unlockNodes; exports.withStyleOpacity = withStyleOpacity; exports.withStyleSize = withStyleSize;
4033
+ exports.CONNECTION_LINE_TYPES = CONNECTION_LINE_TYPES; exports.CanvasScenesStore = CanvasScenesStore; exports.DEFAULT_ARRANGE_SHORTCUTS = _chunk26BVFKHUcjs.DEFAULT_ARRANGE_SHORTCUTS; exports.DEFAULT_NODE_HEIGHT = _chunkWADCJ7A7cjs.DEFAULT_NODE_HEIGHT; exports.DEFAULT_NODE_WIDTH = _chunkWADCJ7A7cjs.DEFAULT_NODE_WIDTH; exports.DEFAULT_PLACEMENT_GAP = _chunkWADCJ7A7cjs.DEFAULT_PLACEMENT_GAP; exports.LOCKED_DATA_KEY = LOCKED_DATA_KEY; exports.LOCKED_PREVIOUS_FLAGS_KEY = LOCKED_PREVIOUS_FLAGS_KEY; exports.ManualClock = ManualClock; exports.RESIZE_HANDLES = RESIZE_HANDLES; exports.SHAPE_DATA_KEY = SHAPE_DATA_KEY; exports.SHAPE_KINDS = SHAPE_KINDS; exports.SHAPE_NODE_TYPE = SHAPE_NODE_TYPE; exports.SHAPE_OPERATIONS = SHAPE_OPERATIONS; exports.StreamingLayoutManager = StreamingLayoutManager; exports.alignNodes = alignNodes; exports.applyNodeUpdates = applyNodeUpdates; exports.applyShapeCombination = applyShapeCombination; exports.blendLayout = blendLayout; exports.boundsOf = _chunkWADCJ7A7cjs.boundsOf; exports.calculateDagreReflowDisplacement = calculateDagreReflowDisplacement; exports.calculateDirectionalDisplacement = calculateDirectionalDisplacement; exports.canCombineShapeNodes = canCombineShapeNodes; exports.canvasHeight = canvasHeight; exports.centerNodes = _chunkWADCJ7A7cjs.centerNodes; exports.centre = _chunkWADCJ7A7cjs.centre; exports.collectDescendantIds = collectDescendantIds; exports.combineShapeNodes = combineShapeNodes; exports.computeNodesBoundingBox = computeNodesBoundingBox; exports.computeSnapGuides = computeSnapGuides; exports.connectedOnly = connectedOnly; exports.createBezierTrajectory = createBezierTrajectory; exports.createCanvasArrangeController = createCanvasArrangeController; exports.createCanvasScenesStore = createCanvasScenesStore; exports.createFanOutTrajectory = createFanOutTrajectory; exports.createGuideIndex = createGuideIndex; exports.createNodeGuideIndex = createNodeGuideIndex; exports.createNodeLookup = createNodeLookup; exports.cubicPointAt = cubicPointAt; exports.cubicsToPathData = cubicsToPathData; exports.defaultEasing = defaultEasing; exports.distributeNodes = distributeNodes; exports.dividePolygons = dividePolygons; exports.ensureLayout = _chunkWADCJ7A7cjs.ensureLayout; exports.estimateCanvasHeight = estimateCanvasHeight; exports.estimateNodeSize = _chunkWADCJ7A7cjs.estimateNodeSize; exports.findConnectorSnap = findConnectorSnap; exports.fitCubicRun = fitCubicRun; exports.fitCubics = fitCubics; exports.fitFreehandPath = fitFreehandPath; exports.fitPathData = fitPathData; exports.fitViewportToNodes = fitViewportToNodes; exports.fitViewportToRect = fitViewportToRect; exports.flattenSvgPath = flattenSvgPath; exports.flowSide = _chunkWADCJ7A7cjs.flowSide; exports.formatNumber = formatNumber2; exports.getAbsolutePosition = getAbsolutePosition; exports.getCenteredNodePosition = _chunkWADCJ7A7cjs.getCenteredNodePosition; exports.getLayoutedNodes = _chunkWADCJ7A7cjs.getLayoutedNodes; exports.getNodeRect = getNodeRect; exports.getNodeShape = getNodeShape; exports.getNodeSize = getNodeSize; exports.getResizingNodeRect = getResizingNodeRect; exports.getShapeRenderData = getShapeRenderData; exports.getShapeSvg = getShapeSvg; exports.handlePoint = handlePoint; exports.inferResizeHandle = inferResizeHandle; exports.intersectPolygons = intersectPolygons; exports.invertNodeUpdates = invertNodeUpdates; exports.isEventFromTextInput = isEventFromTextInput; exports.isLocked = isLocked; exports.isMeasureModifierHeld = isMeasureModifierHeld; exports.isShapeDescription = isShapeDescription; exports.isShapeNode = isShapeNode; exports.isStraightCubic = isStraightCubic; exports.isValidCanvasEdge = isValidCanvasEdge; exports.isValidCanvasNode = isValidCanvasNode; exports.isValidCanvasScene = isValidCanvasScene; exports.isValidUrl = isValidUrl; exports.layoutBounds = layoutBounds; exports.linearPositionTrajectory = linearPositionTrajectory; exports.linearTrajectory = linearTrajectory; exports.lockNodes = lockNodes; exports.matchArrangeShortcut = _chunk26BVFKHUcjs.matchArrangeShortcut; exports.matchSize = matchSize; exports.measureBetween = measureBetween; exports.measureLabel = measureLabel; exports.measureSelection = measureSelection; exports.measureToLine = measureToLine; exports.multiPolygonArea = multiPolygonArea; exports.multiPolygonBounds = multiPolygonBounds; exports.nearestHandlePositions = nearestHandlePositions; exports.oppositePosition = oppositePosition; exports.overlaps = _chunkWADCJ7A7cjs.overlaps; exports.pageHidden = pageHidden; exports.parseCanvasClipboardData = parseCanvasClipboardData; exports.parseCanvasScenes = parseCanvasScenes; exports.parseSvgPath = parseSvgPath; exports.placeBeside = _chunkWADCJ7A7cjs.placeBeside; exports.pointInMultiPolygon = pointInMultiPolygon; exports.pointInRings = pointInRings; exports.polygonArea = polygonArea; exports.polygonBoolean = polygonBoolean; exports.polygonToSvgPath = polygonToSvgPath; exports.positionToSide = positionToSide; exports.readStyleSize = readStyleSize; exports.realClock = realClock; exports.remapPastedNodes = remapPastedNodes; exports.reorderNodes = reorderNodes; exports.repositionNodesTo = repositionNodesTo; exports.resizeHandleAxes = resizeHandleAxes; exports.revertShapeCombination = revertShapeCombination; exports.ringSignedArea = ringSignedArea; exports.runMultiNodeTransition = runMultiNodeTransition; exports.serializeCanvasNodes = serializeCanvasNodes; exports.shapeNodeToPolygon = shapeNodeToPolygon; exports.shapeToPolygon = shapeToPolygon; exports.sideToPosition = sideToPosition; exports.simplifyRing = simplifyRing; exports.snapNodeDrag = snapNodeDrag; exports.snapNodeResize = snapNodeResize; exports.snapResize = snapResize; exports.snapToGrid = snapToGrid; exports.subtractPolygons = subtractPolygons; exports.toggleLockNodes = toggleLockNodes; exports.unionPolygons = unionPolygons; exports.unlockNodes = unlockNodes; exports.withStyleOpacity = withStyleOpacity; exports.withStyleSize = withStyleSize; exports.xorPolygons = xorPolygons;
1982
4034
  //# sourceMappingURL=index.cjs.map