@dwelle/excalidraw 0.5.0-ef89e88 → 0.5.0-efddbb7

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 (103) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +111 -14
  3. package/dist/dev/{chunk-UPL5GSGP.js → chunk-AVAC6BVM.js} +11 -2
  4. package/dist/dev/chunk-AVAC6BVM.js.map +7 -0
  5. package/dist/dev/{chunk-VLJRILK3.js → chunk-OKDHCI5V.js} +1844 -1440
  6. package/dist/dev/chunk-OKDHCI5V.js.map +7 -0
  7. package/dist/dev/{chunk-H5SL5I5R.js → chunk-UEBEBUE2.js} +2 -2
  8. package/dist/dev/components/TTDDialog/{CodeMirrorEditor-65RQRK4A.js → CodeMirrorEditor-2YIEAZU7.js} +2 -2
  9. package/dist/dev/data/{image-JR54FB4B.js → image-O7OJXMIJ.js} +3 -3
  10. package/dist/dev/index.css +85 -43
  11. package/dist/dev/index.css.map +3 -3
  12. package/dist/dev/index.js +2939 -2010
  13. package/dist/dev/index.js.map +4 -4
  14. package/dist/dev/locales/{en-IZ5JJCQN.js → en-XVKSJYG2.js} +4 -2
  15. package/dist/dev/subset-shared.chunk.js +1 -1
  16. package/dist/dev/subset-worker.chunk.js +1 -1
  17. package/dist/prod/chunk-5EYFVTC7.js +34 -0
  18. package/dist/prod/{chunk-MYTKOPOQ.js → chunk-T6UH5ZKV.js} +5 -5
  19. package/dist/prod/{chunk-B47HEJUH.js → chunk-Y6NXDCR7.js} +1 -1
  20. package/dist/prod/components/TTDDialog/{CodeMirrorEditor-B64TEOC3.js → CodeMirrorEditor-JV2LT6ZS.js} +1 -1
  21. package/dist/prod/data/image-XRO5552B.js +1 -0
  22. package/dist/prod/index.css +1 -1
  23. package/dist/prod/index.js +28 -28
  24. package/dist/prod/locales/en-BNQNBQ43.js +1 -0
  25. package/dist/prod/subset-shared.chunk.js +1 -1
  26. package/dist/prod/subset-worker.chunk.js +1 -1
  27. package/dist/types/common/src/appEventBus.d.ts +27 -0
  28. package/dist/types/common/src/index.d.ts +2 -0
  29. package/dist/types/common/src/utils.d.ts +1 -3
  30. package/dist/types/common/src/versionedSnapshotStore.d.ts +17 -0
  31. package/dist/types/element/src/Scene.d.ts +5 -3
  32. package/dist/types/element/src/bounds.d.ts +4 -2
  33. package/dist/types/element/src/duplicate.d.ts +1 -0
  34. package/dist/types/element/src/frame.d.ts +7 -6
  35. package/dist/types/element/src/linearElementEditor.d.ts +2 -2
  36. package/dist/types/element/src/selection.d.ts +2 -2
  37. package/dist/types/element/src/shape.d.ts +1 -1
  38. package/dist/types/element/src/typeChecks.d.ts +1 -0
  39. package/dist/types/element/src/utils.d.ts +1 -1
  40. package/dist/types/excalidraw/actions/actionAddToLibrary.d.ts +8 -8
  41. package/dist/types/excalidraw/actions/actionBoundText.d.ts +6 -6
  42. package/dist/types/excalidraw/actions/actionCanvas.d.ts +30 -30
  43. package/dist/types/excalidraw/actions/actionClipboard.d.ts +6 -6
  44. package/dist/types/excalidraw/actions/actionCropEditor.d.ts +3 -3
  45. package/dist/types/excalidraw/actions/actionDeleteSelected.d.ts +9 -9
  46. package/dist/types/excalidraw/actions/actionDeselect.d.ts +160 -0
  47. package/dist/types/excalidraw/actions/actionElementLink.d.ts +3 -3
  48. package/dist/types/excalidraw/actions/actionElementLock.d.ts +6 -6
  49. package/dist/types/excalidraw/actions/actionEmbeddable.d.ts +3 -3
  50. package/dist/types/excalidraw/actions/actionExport.d.ts +48 -323
  51. package/dist/types/excalidraw/actions/actionFrame.d.ts +12 -12
  52. package/dist/types/excalidraw/actions/actionGroup.d.ts +6 -6
  53. package/dist/types/excalidraw/actions/actionLinearEditor.d.ts +3 -3
  54. package/dist/types/excalidraw/actions/actionLink.d.ts +3 -3
  55. package/dist/types/excalidraw/actions/actionMenu.d.ts +3 -3
  56. package/dist/types/excalidraw/actions/actionProperties.d.ts +6 -6
  57. package/dist/types/excalidraw/actions/actionSelectAll.d.ts +3 -3
  58. package/dist/types/excalidraw/actions/actionStyles.d.ts +2 -2
  59. package/dist/types/excalidraw/actions/actionToggleArrowBinding.d.ts +3 -3
  60. package/dist/types/excalidraw/actions/actionToggleGridMode.d.ts +3 -3
  61. package/dist/types/excalidraw/actions/actionToggleMidpointSnapping.d.ts +3 -3
  62. package/dist/types/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +3 -3
  63. package/dist/types/excalidraw/actions/actionToggleSearchMenu.d.ts +3 -3
  64. package/dist/types/excalidraw/actions/actionToggleStats.d.ts +3 -3
  65. package/dist/types/excalidraw/actions/actionToggleViewMode.d.ts +3 -3
  66. package/dist/types/excalidraw/actions/actionToggleZenMode.d.ts +3 -3
  67. package/dist/types/excalidraw/actions/index.d.ts +1 -0
  68. package/dist/types/excalidraw/actions/types.d.ts +1 -1
  69. package/dist/types/excalidraw/appState.d.ts +1 -0
  70. package/dist/types/excalidraw/clipboard.d.ts +2 -3
  71. package/dist/types/excalidraw/components/App.d.ts +41 -10
  72. package/dist/types/excalidraw/components/AppStateObserver.d.ts +37 -0
  73. package/dist/types/excalidraw/components/TTDDialog/utils/TTDStreamFetch.d.ts +1 -1
  74. package/dist/types/excalidraw/components/Toast.d.ts +8 -4
  75. package/dist/types/excalidraw/components/canvases/InteractiveCanvas.d.ts +1 -1
  76. package/dist/types/excalidraw/components/canvases/NewElementCanvas.d.ts +1 -0
  77. package/dist/types/excalidraw/components/canvases/StaticCanvas.d.ts +1 -1
  78. package/dist/types/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.d.ts +2 -1
  79. package/dist/types/excalidraw/components/main-menu/DefaultItems.d.ts +1 -0
  80. package/dist/types/excalidraw/components/shapes.d.ts +7 -0
  81. package/dist/types/excalidraw/data/blob.d.ts +13 -14
  82. package/dist/types/excalidraw/data/filesystem.d.ts +3 -5
  83. package/dist/types/excalidraw/data/index.d.ts +2 -3
  84. package/dist/types/excalidraw/data/json.d.ts +21 -13
  85. package/dist/types/excalidraw/data/resave.d.ts +7 -2
  86. package/dist/types/excalidraw/hooks/useAppStateValue.d.ts +29 -0
  87. package/dist/types/excalidraw/index.d.ts +23 -3
  88. package/dist/types/excalidraw/scene/Renderer.d.ts +425 -19
  89. package/dist/types/excalidraw/types.d.ts +76 -5
  90. package/dist/types/fractional-indexing/src/index.d.ts +29 -0
  91. package/dist/types/math/src/constants.d.ts +0 -1
  92. package/dist/types/math/src/curve.d.ts +4 -1
  93. package/package.json +4 -5
  94. package/dist/dev/chunk-UPL5GSGP.js.map +0 -7
  95. package/dist/dev/chunk-VLJRILK3.js.map +0 -7
  96. package/dist/prod/chunk-ZTGTHG3M.js +0 -34
  97. package/dist/prod/data/image-I4FBW6ZT.js +0 -1
  98. package/dist/prod/locales/en-4R6EP6SP.js +0 -1
  99. package/dist/types/utils/src/index.d.ts +0 -4
  100. /package/dist/dev/{chunk-H5SL5I5R.js.map → chunk-UEBEBUE2.js.map} +0 -0
  101. /package/dist/dev/components/TTDDialog/{CodeMirrorEditor-65RQRK4A.js.map → CodeMirrorEditor-2YIEAZU7.js.map} +0 -0
  102. /package/dist/dev/data/{image-JR54FB4B.js.map → image-O7OJXMIJ.js.map} +0 -0
  103. /package/dist/dev/locales/{en-IZ5JJCQN.js.map → en-XVKSJYG2.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  define_import_meta_env_default
3
- } from "./chunk-H5SL5I5R.js";
3
+ } from "./chunk-UEBEBUE2.js";
4
4
  import {
5
5
  __publicField
6
6
  } from "./chunk-XDFCUUT6.js";
@@ -348,8 +348,15 @@ var initial_guesses = [
348
348
  [0.2, 0],
349
349
  [0.8, 0]
350
350
  ];
351
- var calculate = ([t0, s0], l, c) => {
352
- const solution = solveWithAnalyticalJacobian(c, l, t0, s0, 0.01, 4);
351
+ var calculate = ([t0, s0], l, c, tolerance = 0.01, iterLimit = 4) => {
352
+ const solution = solveWithAnalyticalJacobian(
353
+ c,
354
+ l,
355
+ t0,
356
+ s0,
357
+ tolerance,
358
+ iterLimit
359
+ );
353
360
  if (!solution) {
354
361
  return null;
355
362
  }
@@ -359,16 +366,34 @@ var calculate = ([t0, s0], l, c) => {
359
366
  }
360
367
  return bezierEquation(c, t);
361
368
  };
362
- function curveIntersectLineSegment(c, l) {
363
- let solution = calculate(initial_guesses[0], l, c);
369
+ function curveIntersectLineSegment(c, l, opts) {
370
+ let solution = calculate(
371
+ initial_guesses[0],
372
+ l,
373
+ c,
374
+ opts?.tolerance,
375
+ opts?.iterLimit
376
+ );
364
377
  if (solution) {
365
378
  return [solution];
366
379
  }
367
- solution = calculate(initial_guesses[1], l, c);
380
+ solution = calculate(
381
+ initial_guesses[1],
382
+ l,
383
+ c,
384
+ opts?.tolerance,
385
+ opts?.iterLimit
386
+ );
368
387
  if (solution) {
369
388
  return [solution];
370
389
  }
371
- solution = calculate(initial_guesses[2], l, c);
390
+ solution = calculate(
391
+ initial_guesses[2],
392
+ l,
393
+ c,
394
+ opts?.tolerance,
395
+ opts?.iterLimit
396
+ );
372
397
  if (solution) {
373
398
  return [solution];
374
399
  }
@@ -1846,32 +1871,23 @@ var debounce = (fn, timeout) => {
1846
1871
  };
1847
1872
  return ret;
1848
1873
  };
1849
- var throttleRAF = (fn, opts) => {
1874
+ var throttleRAF = (fn) => {
1850
1875
  let timerId = null;
1851
1876
  let lastArgs = null;
1852
- let lastArgsTrailing = null;
1853
- const scheduleFunc = (args) => {
1877
+ const scheduleFunc = () => {
1854
1878
  timerId = window.requestAnimationFrame(() => {
1855
1879
  timerId = null;
1856
- fn(...args);
1880
+ const args = lastArgs;
1857
1881
  lastArgs = null;
1858
- if (lastArgsTrailing) {
1859
- lastArgs = lastArgsTrailing;
1860
- lastArgsTrailing = null;
1861
- scheduleFunc(lastArgs);
1882
+ if (args) {
1883
+ fn(...args);
1862
1884
  }
1863
1885
  });
1864
1886
  };
1865
1887
  const ret = (...args) => {
1866
- if (isTestEnv()) {
1867
- fn(...args);
1868
- return;
1869
- }
1870
1888
  lastArgs = args;
1871
1889
  if (timerId === null) {
1872
- scheduleFunc(lastArgs);
1873
- } else if (opts?.trailing) {
1874
- lastArgsTrailing = args;
1890
+ scheduleFunc();
1875
1891
  }
1876
1892
  };
1877
1893
  ret.flush = () => {
@@ -1880,12 +1896,12 @@ var throttleRAF = (fn, opts) => {
1880
1896
  timerId = null;
1881
1897
  }
1882
1898
  if (lastArgs) {
1883
- fn(...lastArgsTrailing || lastArgs);
1884
- lastArgs = lastArgsTrailing = null;
1899
+ fn(...lastArgs);
1900
+ lastArgs = null;
1885
1901
  }
1886
1902
  };
1887
1903
  ret.cancel = () => {
1888
- lastArgs = lastArgsTrailing = null;
1904
+ lastArgs = null;
1889
1905
  if (timerId !== null) {
1890
1906
  cancelAnimationFrame(timerId);
1891
1907
  timerId = null;
@@ -2117,10 +2133,6 @@ var arrayToMap = (items) => {
2117
2133
  return acc;
2118
2134
  }, /* @__PURE__ */ new Map());
2119
2135
  };
2120
- var arrayToMapWithIndex = (elements) => elements.reduce((acc, element, idx) => {
2121
- acc.set(element.id, [element, idx]);
2122
- return acc;
2123
- }, /* @__PURE__ */ new Map());
2124
2136
  var arrayToObject = (array, groupBy) => array.reduce((acc, value, idx) => {
2125
2137
  acc[groupBy ? groupBy(value) : idx] = value;
2126
2138
  return acc;
@@ -2473,6 +2485,81 @@ var Emitter = class {
2473
2485
  }
2474
2486
  };
2475
2487
 
2488
+ // ../common/src/appEventBus.ts
2489
+ var AppEventBus = class {
2490
+ constructor(behavior) {
2491
+ this.behavior = behavior;
2492
+ __publicField(this, "emitters", /* @__PURE__ */ new Map());
2493
+ __publicField(this, "lastPayload", /* @__PURE__ */ new Map());
2494
+ __publicField(this, "emittedOnce", /* @__PURE__ */ new Set());
2495
+ }
2496
+ getEmitter(name) {
2497
+ let emitter = this.emitters.get(name);
2498
+ if (!emitter) {
2499
+ emitter = new Emitter();
2500
+ this.emitters.set(name, emitter);
2501
+ }
2502
+ return emitter;
2503
+ }
2504
+ toPromiseValue(args) {
2505
+ return args.length === 1 ? args[0] : args;
2506
+ }
2507
+ on(name, callback) {
2508
+ const eventBehavior = this.behavior[name];
2509
+ const cachedPayload = this.lastPayload.get(name);
2510
+ if (callback) {
2511
+ if (eventBehavior.replay === "last" && cachedPayload) {
2512
+ queueMicrotask(() => callback(...cachedPayload));
2513
+ if (eventBehavior.cardinality === "once") {
2514
+ return () => {
2515
+ };
2516
+ }
2517
+ }
2518
+ return this.getEmitter(name).on(callback);
2519
+ }
2520
+ if (eventBehavior.cardinality !== "once" || eventBehavior.replay !== "last") {
2521
+ throw new Error(`Event "${String(name)}" requires a callback`);
2522
+ }
2523
+ if (cachedPayload) {
2524
+ return Promise.resolve(this.toPromiseValue(cachedPayload));
2525
+ }
2526
+ return new Promise((resolve) => {
2527
+ this.getEmitter(name).once((...args) => {
2528
+ resolve(this.toPromiseValue(args));
2529
+ });
2530
+ });
2531
+ }
2532
+ emit(name, ...args) {
2533
+ const eventBehavior = this.behavior[name];
2534
+ if (!isProdEnv()) {
2535
+ if (eventBehavior.cardinality === "once") {
2536
+ if (this.emittedOnce.has(name)) {
2537
+ throw new Error(`Event "${String(name)}" can only be emitted once`);
2538
+ }
2539
+ this.emittedOnce.add(name);
2540
+ }
2541
+ }
2542
+ if (eventBehavior.replay === "last") {
2543
+ this.lastPayload.set(name, args);
2544
+ }
2545
+ try {
2546
+ this.getEmitter(name).trigger(...args);
2547
+ } finally {
2548
+ if (eventBehavior.cardinality === "once") {
2549
+ this.getEmitter(name).clear();
2550
+ }
2551
+ }
2552
+ }
2553
+ clear() {
2554
+ this.lastPayload.clear();
2555
+ this.emittedOnce.clear();
2556
+ for (const emitter of this.emitters.values()) {
2557
+ emitter.clear();
2558
+ }
2559
+ this.emitters.clear();
2560
+ }
2561
+ };
2562
+
2476
2563
  // ../common/debug.ts
2477
2564
  var lessPrecise = (num, precision = 5) => parseFloat(num.toPrecision(precision));
2478
2565
  var getAvgFrameTime = (times) => lessPrecise(times.reduce((a, b) => a + b) / times.length);
@@ -2722,7 +2809,6 @@ var getDefaultAppState = () => {
2722
2809
  open: false,
2723
2810
  panels: STATS_PANELS.generalStats | STATS_PANELS.elementProperties
2724
2811
  },
2725
- startBoundElement: null,
2726
2812
  suggestedBinding: null,
2727
2813
  frameRendering: { enabled: true, clip: true, name: true, outline: true },
2728
2814
  frameToHighlight: null,
@@ -2750,7 +2836,8 @@ var getDefaultAppState = () => {
2750
2836
  searchMatches: null,
2751
2837
  lockedMultiSelections: {},
2752
2838
  activeLockedId: null,
2753
- bindMode: "orbit"
2839
+ bindMode: "orbit",
2840
+ boxSelectionMode: "contain"
2754
2841
  };
2755
2842
  };
2756
2843
  var APP_STATE_STORAGE_CONF = /* @__PURE__ */ ((config) => config)({
@@ -2800,6 +2887,7 @@ var APP_STATE_STORAGE_CONF = /* @__PURE__ */ ((config) => config)({
2800
2887
  gridModeEnabled: { browser: true, export: true, server: true },
2801
2888
  height: { browser: false, export: false, server: false },
2802
2889
  isBindingEnabled: { browser: true, export: false, server: false },
2890
+ boxSelectionMode: { browser: true, export: false, server: false },
2803
2891
  bindingPreference: { browser: true, export: false, server: false },
2804
2892
  isMidpointSnappingEnabled: { browser: true, export: false, server: false },
2805
2893
  defaultSidebarDockedPreference: {
@@ -2836,7 +2924,6 @@ var APP_STATE_STORAGE_CONF = /* @__PURE__ */ ((config) => config)({
2836
2924
  selectionElement: { browser: false, export: false, server: false },
2837
2925
  shouldCacheIgnoreZoom: { browser: true, export: false, server: false },
2838
2926
  stats: { browser: true, export: false, server: false },
2839
- startBoundElement: { browser: false, export: false, server: false },
2840
2927
  suggestedBinding: { browser: false, export: false, server: false },
2841
2928
  frameRendering: { browser: false, export: false, server: false },
2842
2929
  frameToHighlight: { browser: false, export: false, server: false },
@@ -3724,6 +3811,25 @@ var canBecomePolygon = (points) => {
3724
3811
  return points.length > 3 || // 3-point polygons can't have all points in a single line
3725
3812
  points.length === 3 && !pointsEqual(points[0], points[points.length - 1]);
3726
3813
  };
3814
+ var isEligibleFrameChildType = (type) => {
3815
+ switch (type) {
3816
+ case "rectangle":
3817
+ case "diamond":
3818
+ case "ellipse":
3819
+ case "arrow":
3820
+ case "line":
3821
+ case "freedraw":
3822
+ case "text":
3823
+ case "image":
3824
+ case "frame":
3825
+ case "embeddable": {
3826
+ return true;
3827
+ }
3828
+ default: {
3829
+ return false;
3830
+ }
3831
+ }
3832
+ };
3727
3833
 
3728
3834
  // ../element/src/utils.ts
3729
3835
  var ElementShapesCache = /* @__PURE__ */ new WeakMap();
@@ -3758,12 +3864,12 @@ var setElementShapesCacheEntry = (element, shape, offset) => {
3758
3864
  }
3759
3865
  shapes.set(offset, shape);
3760
3866
  };
3761
- function deconstructLinearOrFreeDrawElement(element) {
3867
+ function deconstructLinearOrFreeDrawElement(element, elementsMap) {
3762
3868
  const cachedShape = getElementShapesCacheEntry(element, 0);
3763
3869
  if (cachedShape) {
3764
3870
  return cachedShape;
3765
3871
  }
3766
- const ops = generateLinearCollisionShape(element);
3872
+ const ops = generateLinearCollisionShape(element, elementsMap);
3767
3873
  const lines = [];
3768
3874
  const curves = [];
3769
3875
  for (let idx = 0; idx < ops.length; idx += 1) {
@@ -4170,7 +4276,7 @@ var getSnapOutlineMidPoint = (point, element, elementsMap, zoom) => {
4170
4276
  )
4171
4277
  ];
4172
4278
  const candidate = sideMidpoints.find(
4173
- (midpoint) => pointDistance(point, midpoint) <= maxBindingDistance_simple(zoom) + element.strokeWidth / 2 && !hitElementItself({
4279
+ (midpoint2) => pointDistance(point, midpoint2) <= maxBindingDistance_simple(zoom) + element.strokeWidth / 2 && !hitElementItself({
4174
4280
  point,
4175
4281
  element,
4176
4282
  threshold: 0,
@@ -5060,7 +5166,8 @@ var getContainerCenter = (container, appState, elementsMap) => {
5060
5166
  if (!midSegmentMidpoint) {
5061
5167
  midSegmentMidpoint = LinearElementEditor.getSegmentMidPoint(
5062
5168
  container,
5063
- index + 1
5169
+ index + 1,
5170
+ elementsMap
5064
5171
  );
5065
5172
  }
5066
5173
  return { x: midSegmentMidpoint[0], y: midSegmentMidpoint[1] };
@@ -5195,7 +5302,7 @@ var distanceToElement = (element, elementsMap, p) => {
5195
5302
  case "line":
5196
5303
  case "arrow":
5197
5304
  case "freedraw":
5198
- return distanceToLinearOrFreeDraElement(element, p);
5305
+ return distanceToLinearOrFreeDraElement(element, elementsMap, p);
5199
5306
  }
5200
5307
  };
5201
5308
  var distanceToRectanguloidElement = (element, elementsMap, p) => {
@@ -5224,20 +5331,32 @@ var distanceToEllipseElement = (element, elementsMap, p) => {
5224
5331
  ellipse(center, element.width / 2, element.height / 2)
5225
5332
  );
5226
5333
  };
5227
- var distanceToLinearOrFreeDraElement = (element, p) => {
5228
- const [lines, curves] = deconstructLinearOrFreeDrawElement(element);
5334
+ var distanceToLinearOrFreeDraElement = (element, elementsMap, p) => {
5335
+ const [lines, curves] = deconstructLinearOrFreeDrawElement(
5336
+ element,
5337
+ elementsMap
5338
+ );
5229
5339
  return Math.min(
5230
5340
  ...lines.map((s) => distanceToLineSegment(p, s)),
5231
5341
  ...curves.map((a) => curvePointDistance(a, p))
5232
5342
  );
5233
5343
  };
5234
5344
 
5345
+ // ../element/src/comparisons.ts
5346
+ var hasBackground = (type) => type === "rectangle" || type === "iframe" || type === "embeddable" || type === "ellipse" || type === "diamond" || type === "line" || type === "freedraw";
5347
+ var hasStrokeColor = (type) => type === "rectangle" || type === "ellipse" || type === "diamond" || type === "freedraw" || type === "arrow" || type === "line" || type === "text" || type === "embeddable";
5348
+ var hasStrokeWidth = (type) => type === "rectangle" || type === "iframe" || type === "embeddable" || type === "ellipse" || type === "diamond" || type === "freedraw" || type === "arrow" || type === "line";
5349
+ var hasStrokeStyle = (type) => type === "rectangle" || type === "iframe" || type === "embeddable" || type === "ellipse" || type === "diamond" || type === "arrow" || type === "line";
5350
+ var canChangeRoundness = (type) => type === "rectangle" || type === "iframe" || type === "embeddable" || type === "line" || type === "diamond" || type === "image";
5351
+ var toolIsArrow = (type) => type === "arrow";
5352
+ var canHaveArrowheads = (type) => type === "arrow";
5353
+
5235
5354
  // ../element/src/collision.ts
5236
5355
  var shouldTestInside = (element) => {
5237
5356
  if (element.type === "arrow") {
5238
5357
  return false;
5239
5358
  }
5240
- const isDraggableFromInside = !isTransparent(element.backgroundColor) || hasBoundTextElement(element) || isIframeLikeElement(element) || isTextElement(element);
5359
+ const isDraggableFromInside = hasBackground(element.type) && !isTransparent(element.backgroundColor) || hasBoundTextElement(element) || isIframeLikeElement(element) || isTextElement(element);
5241
5360
  if (element.type === "line") {
5242
5361
  return isDraggableFromInside && isPathALoop(element.points);
5243
5362
  }
@@ -5274,14 +5393,11 @@ var hitElementItself = ({
5274
5393
  )
5275
5394
  ) : false;
5276
5395
  const bounds = getElementBounds(element, elementsMap, true);
5277
- const hitBounds = isPointWithinBounds(
5278
- pointFrom(bounds[0] - threshold, bounds[1] - threshold),
5279
- pointRotateRads(
5280
- point,
5281
- getCenterForBounds(bounds),
5282
- -element.angle
5283
- ),
5284
- pointFrom(bounds[2] + threshold, bounds[3] + threshold)
5396
+ const hitBounds = isPointInRotatedBounds(
5397
+ point,
5398
+ bounds,
5399
+ element.angle,
5400
+ threshold
5285
5401
  );
5286
5402
  if (!hitBounds && !hitFrameName) {
5287
5403
  return false;
@@ -5299,13 +5415,17 @@ var hitElementItself = ({
5299
5415
  cachedHit = result;
5300
5416
  return result;
5301
5417
  };
5418
+ var isPointInRotatedBounds = (point, bounds, angle, tolerance = 0) => {
5419
+ const adjustedPoint = angle === 0 ? point : pointRotateRads(point, getCenterForBounds(bounds), -angle);
5420
+ return isPointWithinBounds(
5421
+ pointFrom(bounds[0] - tolerance, bounds[1] - tolerance),
5422
+ adjustedPoint,
5423
+ pointFrom(bounds[2] + tolerance, bounds[3] + tolerance)
5424
+ );
5425
+ };
5302
5426
  var hitElementBoundingBox = (point, element, elementsMap, tolerance = 0) => {
5303
- let [x1, y1, x2, y2] = getElementBounds(element, elementsMap);
5304
- x1 -= tolerance;
5305
- y1 -= tolerance;
5306
- x2 += tolerance;
5307
- y2 += tolerance;
5308
- return isPointWithinBounds(pointFrom(x1, y1), point, pointFrom(x2, y2));
5427
+ const bounds = getElementBounds(element, elementsMap, true);
5428
+ return isPointInRotatedBounds(point, bounds, element.angle, tolerance);
5309
5429
  };
5310
5430
  var hitElementBoundingBoxOnly = (hitArgs, elementsMap) => !hitElementItself(hitArgs) && // bound text is considered part of the element (even if it's outside the bounding box)
5311
5431
  !hitElementBoundText(hitArgs.point, hitArgs.element, elementsMap) && hitElementBoundingBox(hitArgs.point, hitArgs.element, elementsMap);
@@ -5369,7 +5489,7 @@ var getAllHoveredElementAtPoint = (point, elements, elementsMap, tolerance) => {
5369
5489
  );
5370
5490
  if (isBindableElement(element, false) && bindingBorderTest(element, point, elementsMap, tolerance)) {
5371
5491
  candidateElements.push(element);
5372
- if (!isTransparent(element.backgroundColor)) {
5492
+ if (hasBackground(element.type) && !isTransparent(element.backgroundColor)) {
5373
5493
  break;
5374
5494
  }
5375
5495
  }
@@ -5464,7 +5584,12 @@ var intersectElementWithLineSegment = (element, elementsMap, line2, offset = 0,
5464
5584
  case "line":
5465
5585
  case "freedraw":
5466
5586
  case "arrow":
5467
- return intersectLinearOrFreeDrawWithLineSegment(element, line2, onlyFirst);
5587
+ return intersectLinearOrFreeDrawWithLineSegment(
5588
+ element,
5589
+ line2,
5590
+ elementsMap,
5591
+ onlyFirst
5592
+ );
5468
5593
  }
5469
5594
  };
5470
5595
  var curveIntersections = (curves, segment, intersections, center, angle, onlyFirst = false) => {
@@ -5503,8 +5628,11 @@ var lineIntersections = (lines, segment, intersections, center, angle, onlyFirst
5503
5628
  }
5504
5629
  return intersections;
5505
5630
  };
5506
- var intersectLinearOrFreeDrawWithLineSegment = (element, segment, onlyFirst = false) => {
5507
- const [lines, curves] = deconstructLinearOrFreeDrawElement(element);
5631
+ var intersectLinearOrFreeDrawWithLineSegment = (element, segment, elementsMap, onlyFirst = false) => {
5632
+ const [lines, curves] = deconstructLinearOrFreeDrawElement(
5633
+ element,
5634
+ elementsMap
5635
+ );
5508
5636
  const intersections = [];
5509
5637
  for (const l of lines) {
5510
5638
  const intersection = lineSegmentIntersectionPoints(l, segment);
@@ -5526,7 +5654,9 @@ var intersectLinearOrFreeDrawWithLineSegment = (element, segment, onlyFirst = fa
5526
5654
  if (!doBoundsIntersect(b1, b2)) {
5527
5655
  continue;
5528
5656
  }
5529
- const hits = curveIntersectLineSegment(c, segment);
5657
+ const hits = curveIntersectLineSegment(c, segment, {
5658
+ iterLimit: 10
5659
+ });
5530
5660
  if (hits.length > 0) {
5531
5661
  intersections.push(...hits);
5532
5662
  if (onlyFirst) {
@@ -7190,7 +7320,7 @@ var normalizeArrowElementUpdate = (global, nextFixedSegments, startIsSpecial, en
7190
7320
  vectorScale(vectorFromPoint(global[0]), -1)
7191
7321
  )
7192
7322
  );
7193
- if (offsetX < -MAX_POS || offsetX > MAX_POS || offsetY < -MAX_POS || offsetY > MAX_POS || offsetX + points[points.length - 1][0] < -MAX_POS || offsetY + points[points.length - 1][0] > MAX_POS || offsetX + points[points.length - 1][1] < -MAX_POS || offsetY + points[points.length - 1][1] > MAX_POS) {
7323
+ if (offsetX < -MAX_POS || offsetX > MAX_POS || offsetY < -MAX_POS || offsetY > MAX_POS || offsetX + points[points.length - 1][0] < -MAX_POS || offsetX + points[points.length - 1][0] > MAX_POS || offsetY + points[points.length - 1][1] < -MAX_POS || offsetY + points[points.length - 1][1] > MAX_POS) {
7194
7324
  console.error(
7195
7325
  "Elbow arrow normalization is outside reasonable bounds (> 1e6)",
7196
7326
  {
@@ -7794,11 +7924,7 @@ var getBindingStrategyForDraggingBindingElementEndpoints_simple = (arrow, draggi
7794
7924
  threshold: 0,
7795
7925
  overrideShouldTestInside: true
7796
7926
  });
7797
- if (otherBinding && otherBinding.elementId === hit?.id) {
7798
- invariant(
7799
- !opts?.newArrow || appState.selectedLinearElement?.initialState.origin,
7800
- "appState.selectedLinearElement.initialState.origin must be defined for new arrows"
7801
- );
7927
+ if (otherBinding && otherBinding.elementId === hit?.id && (!opts?.newArrow || appState.selectedLinearElement?.initialState.origin)) {
7802
7928
  return {
7803
7929
  start: {
7804
7930
  mode: "inside",
@@ -8608,8 +8734,8 @@ var calculateFixedPointForElbowArrowBinding = (linearElement, hoveredElement, st
8608
8734
  );
8609
8735
  return {
8610
8736
  fixedPoint: normalizeFixedPoint([
8611
- (nonRotatedSnappedGlobalPoint[0] - hoveredElement.x) / hoveredElement.width,
8612
- (nonRotatedSnappedGlobalPoint[1] - hoveredElement.y) / hoveredElement.height
8737
+ (nonRotatedSnappedGlobalPoint[0] - hoveredElement.x) / Math.max(hoveredElement.width, PRECISION),
8738
+ (nonRotatedSnappedGlobalPoint[1] - hoveredElement.y) / Math.max(hoveredElement.height, PRECISION)
8613
8739
  ])
8614
8740
  };
8615
8741
  };
@@ -8625,8 +8751,8 @@ var calculateFixedPointForNonElbowArrowBinding = (linearElement, hoveredElement,
8625
8751
  elementCenter,
8626
8752
  -hoveredElement.angle
8627
8753
  );
8628
- const fixedPointX = (nonRotatedPoint[0] - hoveredElement.x) / hoveredElement.width;
8629
- const fixedPointY = (nonRotatedPoint[1] - hoveredElement.y) / hoveredElement.height;
8754
+ const fixedPointX = (nonRotatedPoint[0] - hoveredElement.x) / Math.max(hoveredElement.width, PRECISION);
8755
+ const fixedPointY = (nonRotatedPoint[1] - hoveredElement.y) / Math.max(hoveredElement.height, PRECISION);
8630
8756
  return {
8631
8757
  fixedPoint: normalizeFixedPoint([fixedPointX, fixedPointY])
8632
8758
  };
@@ -9216,12 +9342,14 @@ var _LinearElementEditor = class _LinearElementEditor {
9216
9342
  return false;
9217
9343
  });
9218
9344
  }
9219
- invariant(
9220
- lastClickedPoint > -1 && selectedPointsIndices.includes(lastClickedPoint) && element.points[lastClickedPoint],
9221
- `There must be a valid lastClickedPoint in order to drag it. selectedPointsIndices(${JSON.stringify(
9222
- selectedPointsIndices
9223
- )}) points(0..${element.points.length - 1}) lastClickedPoint(${lastClickedPoint})`
9224
- );
9345
+ if (lastClickedPoint < 0 || !selectedPointsIndices.includes(lastClickedPoint) || !element.points[lastClickedPoint]) {
9346
+ console.error(
9347
+ `There must be a valid lastClickedPoint in order to drag it. selectedPointsIndices(${JSON.stringify(
9348
+ selectedPointsIndices
9349
+ )}) points(0..${element.points.length - 1}) lastClickedPoint(${lastClickedPoint}) isElbowArrow: ${elbowed}`
9350
+ );
9351
+ lastClickedPoint = element.points.length - 1;
9352
+ }
9225
9353
  const draggingPoint = element.points[lastClickedPoint];
9226
9354
  const pivotPoint = element.points[lastClickedPoint === 0 ? 1 : lastClickedPoint - 1];
9227
9355
  const singlePointDragged = selectedPointsIndices.length === 1;
@@ -9418,7 +9546,7 @@ var _LinearElementEditor = class _LinearElementEditor {
9418
9546
  }
9419
9547
  };
9420
9548
  }
9421
- static isSegmentTooShort(element, startPoint, endPoint, index, zoom) {
9549
+ static isSegmentTooShort(element, startPoint, endPoint, index, zoom, elementsMap) {
9422
9550
  if (isElbowArrow(element)) {
9423
9551
  if (index >= 0 && index < element.points.length) {
9424
9552
  return pointDistance(startPoint, endPoint) * zoom.value < _LinearElementEditor.POINT_HANDLE_SIZE / 2;
@@ -9427,7 +9555,10 @@ var _LinearElementEditor = class _LinearElementEditor {
9427
9555
  }
9428
9556
  let distance2 = pointDistance(startPoint, endPoint);
9429
9557
  if (element.points.length > 2 && element.roundness) {
9430
- const [lines, curves] = deconstructLinearOrFreeDrawElement(element);
9558
+ const [lines, curves] = deconstructLinearOrFreeDrawElement(
9559
+ element,
9560
+ elementsMap
9561
+ );
9431
9562
  invariant(
9432
9563
  lines.length === 0 && curves.length > 0,
9433
9564
  "Only linears built out of curves are supported"
@@ -9440,7 +9571,7 @@ var _LinearElementEditor = class _LinearElementEditor {
9440
9571
  }
9441
9572
  return distance2 * zoom.value < _LinearElementEditor.POINT_HANDLE_SIZE * 4;
9442
9573
  }
9443
- static getSegmentMidPoint(element, index) {
9574
+ static getSegmentMidPoint(element, index, elementsMap) {
9444
9575
  if (isElbowArrow(element)) {
9445
9576
  invariant(
9446
9577
  element.points.length >= index,
@@ -9449,7 +9580,10 @@ var _LinearElementEditor = class _LinearElementEditor {
9449
9580
  const p = pointCenter(element.points[index - 1], element.points[index]);
9450
9581
  return pointFrom(element.x + p[0], element.y + p[1]);
9451
9582
  }
9452
- const [lines, curves] = deconstructLinearOrFreeDrawElement(element);
9583
+ const [lines, curves] = deconstructLinearOrFreeDrawElement(
9584
+ element,
9585
+ elementsMap
9586
+ );
9453
9587
  invariant(
9454
9588
  lines.length === 0 && curves.length > 0 || lines.length > 0 && curves.length === 0,
9455
9589
  "Only linears built out of either segments or curves are supported"
@@ -9969,7 +10103,7 @@ var _LinearElementEditor = class _LinearElementEditor {
9969
10103
  pointerDownState: linearElementEditor.initialState,
9970
10104
  selectedPointsIndices: linearElementEditor.selectedPointsIndices
9971
10105
  };
9972
- const midpoint = _LinearElementEditor.createPointAt(
10106
+ const midpoint2 = _LinearElementEditor.createPointAt(
9973
10107
  element,
9974
10108
  elementsMap,
9975
10109
  pointerCoords.x,
@@ -9978,7 +10112,7 @@ var _LinearElementEditor = class _LinearElementEditor {
9978
10112
  );
9979
10113
  const points = [
9980
10114
  ...element.points.slice(0, segmentMidpoint.index),
9981
- midpoint,
10115
+ midpoint2,
9982
10116
  ...element.points.slice(segmentMidpoint.index)
9983
10117
  ];
9984
10118
  scene.mutateElement(element, { points });
@@ -10149,7 +10283,8 @@ __publicField(_LinearElementEditor, "getEditorMidPoints", (element, elementsMap,
10149
10283
  element.points[index],
10150
10284
  element.points[index + 1],
10151
10285
  index,
10152
- appState.zoom
10286
+ appState.zoom,
10287
+ elementsMap
10153
10288
  )) {
10154
10289
  midpoints.push(null);
10155
10290
  index++;
@@ -10157,7 +10292,8 @@ __publicField(_LinearElementEditor, "getEditorMidPoints", (element, elementsMap,
10157
10292
  }
10158
10293
  const segmentMidPoint = _LinearElementEditor.getSegmentMidPoint(
10159
10294
  element,
10160
- index + 1
10295
+ index + 1,
10296
+ elementsMap
10161
10297
  );
10162
10298
  midpoints.push(segmentMidPoint);
10163
10299
  index++;
@@ -10244,7 +10380,8 @@ __publicField(_LinearElementEditor, "getBoundTextElementPosition", (element, bou
10244
10380
  const index = element.points.length / 2 - 1;
10245
10381
  const midSegmentMidpoint = _LinearElementEditor.getSegmentMidPoint(
10246
10382
  element,
10247
- index + 1
10383
+ index + 1,
10384
+ elementsMap
10248
10385
  );
10249
10386
  x = midSegmentMidpoint[0] - boundTextElement.width / 2;
10250
10387
  y = midSegmentMidpoint[1] - boundTextElement.height / 2;
@@ -10366,13 +10503,11 @@ var normalizeSelectedPoints = (points) => {
10366
10503
  var pointDraggingUpdates = (selectedPointsIndices, deltaX, deltaY, scenePointerX, scenePointerY, elementsMap, element, elements, app, angleLocked, altKey, linearElementEditor) => {
10367
10504
  const naiveDraggingPoints = new Map(
10368
10505
  selectedPointsIndices.map((pointIndex) => {
10506
+ const point = element.points[pointIndex] ?? element.points.at(-1);
10369
10507
  return [
10370
10508
  pointIndex,
10371
10509
  {
10372
- point: pointFrom(
10373
- element.points[pointIndex][0] + deltaX,
10374
- element.points[pointIndex][1] + deltaY
10375
- ),
10510
+ point: pointFrom(point[0] + deltaX, point[1] + deltaY),
10376
10511
  isDragging: true
10377
10512
  }
10378
10513
  ];
@@ -10559,7 +10694,7 @@ var pointDraggingUpdates = (selectedPointsIndices, deltaX, deltaY, scenePointerX
10559
10694
  nextArrow.endBinding.elementId
10560
10695
  ) : null;
10561
10696
  const endLocalPoint = startIsDraggingOverEndElement ? nextArrow.points[nextArrow.points.length - 1] : endIsDraggingOverStartElement && app.state.bindMode !== "inside" && getFeatureFlag("COMPLEX_BINDINGS") ? nextArrow.points[0] : endBindable ? updateBoundPoint(
10562
- element,
10697
+ nextArrow,
10563
10698
  "endBinding",
10564
10699
  nextArrow.endBinding,
10565
10700
  endBindable,
@@ -10571,7 +10706,7 @@ var pointDraggingUpdates = (selectedPointsIndices, deltaX, deltaY, scenePointerX
10571
10706
  nextArrow.startBinding.elementId
10572
10707
  ) : null;
10573
10708
  const startLocalPoint = endIsDraggingOverStartElement && getFeatureFlag("COMPLEX_BINDINGS") ? nextArrow.points[0] : startIsDraggingOverEndElement && app.state.bindMode !== "inside" && getFeatureFlag("COMPLEX_BINDINGS") ? endLocalPoint : startBindable ? updateBoundPoint(
10574
- element,
10709
+ nextArrow,
10575
10710
  "startBinding",
10576
10711
  nextArrow.startBinding,
10577
10712
  startBindable,
@@ -10639,131 +10774,6 @@ function doLineSegmentsIntersect(a, b) {
10639
10774
  return doBBoxesIntersect(getBBox(a), getBBox(b)) && isLineSegmentTouchingOrCrossingLine(a, b) && isLineSegmentTouchingOrCrossingLine(b, a);
10640
10775
  }
10641
10776
 
10642
- // ../utils/src/withinBounds.ts
10643
- var getNonLinearElementRelativePoints = (element) => {
10644
- if (element.type === "diamond") {
10645
- return [
10646
- pointFrom(element.width / 2, 0),
10647
- pointFrom(element.width, element.height / 2),
10648
- pointFrom(element.width / 2, element.height),
10649
- pointFrom(0, element.height / 2)
10650
- ];
10651
- }
10652
- return [
10653
- pointFrom(0, 0),
10654
- pointFrom(0 + element.width, 0),
10655
- pointFrom(0 + element.width, element.height),
10656
- pointFrom(0, element.height)
10657
- ];
10658
- };
10659
- var getElementRelativePoints = (element) => {
10660
- if (isLinearElement(element) || isFreeDrawElement(element)) {
10661
- return element.points;
10662
- }
10663
- return getNonLinearElementRelativePoints(element);
10664
- };
10665
- var getMinMaxPoints = (points) => {
10666
- const ret = points.reduce(
10667
- (limits, [x, y]) => {
10668
- limits.minY = Math.min(limits.minY, y);
10669
- limits.minX = Math.min(limits.minX, x);
10670
- limits.maxX = Math.max(limits.maxX, x);
10671
- limits.maxY = Math.max(limits.maxY, y);
10672
- return limits;
10673
- },
10674
- {
10675
- minX: Infinity,
10676
- minY: Infinity,
10677
- maxX: -Infinity,
10678
- maxY: -Infinity,
10679
- cx: 0,
10680
- cy: 0
10681
- }
10682
- );
10683
- ret.cx = (ret.maxX + ret.minX) / 2;
10684
- ret.cy = (ret.maxY + ret.minY) / 2;
10685
- return ret;
10686
- };
10687
- var getRotatedBBox = (element) => {
10688
- const points = getElementRelativePoints(element);
10689
- const { cx, cy } = getMinMaxPoints(points);
10690
- const centerPoint = pointFrom(cx, cy);
10691
- const rotatedPoints = points.map(
10692
- (p) => pointRotateRads(p, centerPoint, element.angle)
10693
- );
10694
- const { minX, minY, maxX, maxY } = getMinMaxPoints(rotatedPoints);
10695
- return [
10696
- minX + element.x,
10697
- minY + element.y,
10698
- maxX + element.x,
10699
- maxY + element.y
10700
- ];
10701
- };
10702
- var isElementInsideBBox = (element, bbox, eitherDirection = false) => {
10703
- const elementBBox = getRotatedBBox(element);
10704
- const elementInsideBbox = bbox[0] <= elementBBox[0] && bbox[2] >= elementBBox[2] && bbox[1] <= elementBBox[1] && bbox[3] >= elementBBox[3];
10705
- if (!eitherDirection) {
10706
- return elementInsideBbox;
10707
- }
10708
- if (elementInsideBbox) {
10709
- return true;
10710
- }
10711
- return elementBBox[0] <= bbox[0] && elementBBox[2] >= bbox[2] && elementBBox[1] <= bbox[1] && elementBBox[3] >= bbox[3];
10712
- };
10713
- var elementPartiallyOverlapsWithOrContainsBBox = (element, bbox) => {
10714
- const elementBBox = getRotatedBBox(element);
10715
- return (rangeIncludesValue(elementBBox[0], rangeInclusive(bbox[0], bbox[2])) || rangeIncludesValue(
10716
- bbox[0],
10717
- rangeInclusive(elementBBox[0], elementBBox[2])
10718
- )) && (rangeIncludesValue(elementBBox[1], rangeInclusive(bbox[1], bbox[3])) || rangeIncludesValue(
10719
- bbox[1],
10720
- rangeInclusive(elementBBox[1], elementBBox[3])
10721
- ));
10722
- };
10723
- var elementsOverlappingBBox = ({
10724
- elements,
10725
- bounds,
10726
- type,
10727
- errorMargin = 0
10728
- }) => {
10729
- if (isExcalidrawElement(bounds)) {
10730
- bounds = getElementBounds(bounds, arrayToMap(elements));
10731
- }
10732
- const adjustedBBox = [
10733
- bounds[0] - errorMargin,
10734
- bounds[1] - errorMargin,
10735
- bounds[2] + errorMargin,
10736
- bounds[3] + errorMargin
10737
- ];
10738
- const includedElementSet = /* @__PURE__ */ new Set();
10739
- for (const element of elements) {
10740
- if (includedElementSet.has(element.id)) {
10741
- continue;
10742
- }
10743
- const isOverlaping = type === "overlap" ? elementPartiallyOverlapsWithOrContainsBBox(element, adjustedBBox) : type === "inside" ? isElementInsideBBox(element, adjustedBBox) : isElementInsideBBox(element, adjustedBBox, true);
10744
- if (isOverlaping) {
10745
- includedElementSet.add(element.id);
10746
- if (element.boundElements) {
10747
- for (const boundElement of element.boundElements) {
10748
- includedElementSet.add(boundElement.id);
10749
- }
10750
- }
10751
- if (isTextElement(element) && element.containerId) {
10752
- includedElementSet.add(element.containerId);
10753
- }
10754
- if (isArrowElement(element)) {
10755
- if (element.startBinding) {
10756
- includedElementSet.add(element.startBinding.elementId);
10757
- }
10758
- if (element.endBinding) {
10759
- includedElementSet.add(element.endBinding?.elementId);
10760
- }
10761
- }
10762
- }
10763
- }
10764
- return elements.filter((element) => includedElementSet.has(element.id));
10765
- };
10766
-
10767
10777
  // ../element/src/groups.ts
10768
10778
  var selectGroup = (groupId, appState, elements) => {
10769
10779
  const elementsInGroup = elements.reduce(
@@ -11037,13 +11047,8 @@ var getSelectedElementsByGroup = (selectedElements, elementsMap, appState) => {
11037
11047
  };
11038
11048
 
11039
11049
  // ../element/src/selection.ts
11040
- var excludeElementsInFramesFromSelection = (selectedElements) => {
11041
- const framesInSelection = /* @__PURE__ */ new Set();
11042
- selectedElements.forEach((element) => {
11043
- if (isFrameLikeElement(element)) {
11044
- framesInSelection.add(element.id);
11045
- }
11046
- });
11050
+ var shouldIgnoreElementFromSelection = (element) => element.locked || isBoundToContainer(element);
11051
+ var excludeElementsFromFrames = (selectedElements, framesInSelection) => {
11047
11052
  return selectedElements.filter((element) => {
11048
11053
  if (element.frameId && framesInSelection.has(element.frameId)) {
11049
11054
  return false;
@@ -11051,90 +11056,273 @@ var excludeElementsInFramesFromSelection = (selectedElements) => {
11051
11056
  return true;
11052
11057
  });
11053
11058
  };
11054
- var getElementsWithinSelection = (elements, selection, elementsMap, excludeElementsInFrames = true) => {
11055
- const [selectionX1, selectionY1, selectionX2, selectionY2] = getElementAbsoluteCoords(selection, elementsMap);
11056
- let elementsInSelection = elements.filter((element) => {
11057
- let [elementX1, elementY1, elementX2, elementY2] = getElementBounds(
11058
- element,
11059
- elementsMap
11060
- );
11061
- const containingFrame = getContainingFrame(element, elementsMap);
11062
- if (containingFrame) {
11063
- const [fx1, fy1, fx2, fy2] = getElementBounds(
11064
- containingFrame,
11065
- elementsMap
11066
- );
11067
- elementX1 = Math.max(fx1, elementX1);
11068
- elementY1 = Math.max(fy1, elementY1);
11069
- elementX2 = Math.min(fx2, elementX2);
11070
- elementY2 = Math.min(fy2, elementY2);
11059
+ var excludeElementsInFramesFromSelection = (selectedElements) => {
11060
+ const framesInSelection = /* @__PURE__ */ new Set();
11061
+ selectedElements.forEach((element) => {
11062
+ if (isFrameLikeElement(element)) {
11063
+ framesInSelection.add(element.id);
11071
11064
  }
11072
- return element.locked === false && element.type !== "selection" && !isBoundToContainer(element) && selectionX1 <= elementX1 && selectionY1 <= elementY1 && selectionX2 >= elementX2 && selectionY2 >= elementY2;
11073
11065
  });
11074
- elementsInSelection = excludeElementsInFrames ? excludeElementsInFramesFromSelection(elementsInSelection) : elementsInSelection;
11075
- elementsInSelection = elementsInSelection.filter((element) => {
11076
- const containingFrame = getContainingFrame(element, elementsMap);
11077
- if (containingFrame) {
11078
- return elementOverlapsWithFrame(element, containingFrame, elementsMap);
11066
+ return excludeElementsFromFrames(selectedElements, framesInSelection);
11067
+ };
11068
+ var getElementsWithinSelection = (elements, selection, elementsMap, excludeElementsInFrames = true, boxSelectionMode = "contain") => {
11069
+ const [selectionStartX, selectionStartY, selectionEndX, selectionEndY] = getElementAbsoluteCoords(selection, elementsMap);
11070
+ const selectionX1 = Math.min(selectionStartX, selectionEndX);
11071
+ const selectionY1 = Math.min(selectionStartY, selectionEndY);
11072
+ const selectionX2 = Math.max(selectionStartX, selectionEndX);
11073
+ const selectionY2 = Math.max(selectionStartY, selectionEndY);
11074
+ const selectionBounds = [
11075
+ selectionX1,
11076
+ selectionY1,
11077
+ selectionX2,
11078
+ selectionY2
11079
+ ];
11080
+ const selectionEdges = [
11081
+ lineSegment(
11082
+ pointFrom(selectionX1, selectionY1),
11083
+ pointFrom(selectionX2, selectionY1)
11084
+ ),
11085
+ lineSegment(
11086
+ pointFrom(selectionX2, selectionY1),
11087
+ pointFrom(selectionX2, selectionY2)
11088
+ ),
11089
+ lineSegment(
11090
+ pointFrom(selectionX2, selectionY2),
11091
+ pointFrom(selectionX1, selectionY2)
11092
+ ),
11093
+ lineSegment(
11094
+ pointFrom(selectionX1, selectionY2),
11095
+ pointFrom(selectionX1, selectionY1)
11096
+ )
11097
+ ];
11098
+ const framesInSelection = excludeElementsInFrames ? /* @__PURE__ */ new Set() : null;
11099
+ const groups = {};
11100
+ const elementsInSelection = /* @__PURE__ */ new Set();
11101
+ for (const element of elements) {
11102
+ if (shouldIgnoreElementFromSelection(element)) {
11103
+ continue;
11079
11104
  }
11080
- return true;
11081
- });
11082
- return elementsInSelection;
11083
- };
11084
- var getVisibleAndNonSelectedElements = (elements, selectedElements, appState, elementsMap) => {
11085
- const selectedElementsSet = new Set(
11086
- selectedElements.map((element) => element.id)
11087
- );
11088
- return elements.filter((element) => {
11089
- const isVisible = isElementInViewport(
11090
- element,
11091
- appState.width,
11092
- appState.height,
11093
- appState,
11094
- elementsMap
11095
- );
11096
- return !selectedElementsSet.has(element.id) && isVisible;
11097
- });
11098
- };
11099
- var isSomeElementSelected = function() {
11100
- let lastElements = null;
11101
- let lastSelectedElementIds = null;
11102
- let isSelected = null;
11103
- const ret = (elements, appState) => {
11104
- if (isSelected != null && elements === lastElements && appState.selectedElementIds === lastSelectedElementIds) {
11105
- return isSelected;
11105
+ const groupId = element.groupIds.at(-1);
11106
+ if (groupId) {
11107
+ if (!groups[groupId]) {
11108
+ groups[groupId] = [];
11109
+ }
11110
+ groups[groupId].push(element);
11111
+ }
11112
+ const strokeWidth = element.strokeWidth;
11113
+ let labelAABB = null;
11114
+ let elementAABB = getElementBounds(element, elementsMap);
11115
+ elementAABB = [
11116
+ elementAABB[0] - strokeWidth / 2,
11117
+ elementAABB[1] - strokeWidth / 2,
11118
+ elementAABB[2] + strokeWidth / 2,
11119
+ elementAABB[3] + strokeWidth / 2
11120
+ ];
11121
+ const boundTextElement = isArrowElement(element) && getBoundTextElement(element, elementsMap);
11122
+ if (boundTextElement) {
11123
+ const { x, y } = LinearElementEditor.getBoundTextElementPosition(
11124
+ element,
11125
+ boundTextElement,
11126
+ elementsMap
11127
+ );
11128
+ labelAABB = [
11129
+ x,
11130
+ y,
11131
+ x + boundTextElement.width,
11132
+ y + boundTextElement.height
11133
+ ];
11106
11134
  }
11107
- isSelected = elements.some(
11108
- (element) => appState.selectedElementIds[element.id]
11109
- );
11110
- lastElements = elements;
11111
- lastSelectedElementIds = appState.selectedElementIds;
11112
- return isSelected;
11113
- };
11114
- ret.clearCache = () => {
11115
- lastElements = null;
11116
- lastSelectedElementIds = null;
11117
- isSelected = null;
11118
- };
11119
- return ret;
11120
- }();
11121
- var getSelectedElements = (elements, appState, opts) => {
11122
- const addedElements = /* @__PURE__ */ new Set();
11123
- const selectedElements = [];
11124
- for (const element of elements.values()) {
11125
- if (appState.selectedElementIds[element.id]) {
11126
- selectedElements.push(element);
11127
- addedElements.add(element.id);
11135
+ const associatedFrame = getContainingFrame(element, elementsMap);
11136
+ if (associatedFrame && elementOverlapsWithFrame(element, associatedFrame, elementsMap)) {
11137
+ const frameAABB = getElementBounds(associatedFrame, elementsMap);
11138
+ elementAABB = [
11139
+ Math.max(elementAABB[0], frameAABB[0]),
11140
+ Math.max(elementAABB[1], frameAABB[1]),
11141
+ Math.min(elementAABB[2], frameAABB[2]),
11142
+ Math.min(elementAABB[3], frameAABB[3])
11143
+ ];
11144
+ labelAABB = labelAABB ? [
11145
+ Math.max(labelAABB[0], frameAABB[0]),
11146
+ Math.max(labelAABB[1], frameAABB[1]),
11147
+ Math.min(labelAABB[2], frameAABB[2]),
11148
+ Math.min(labelAABB[3], frameAABB[3])
11149
+ ] : null;
11150
+ }
11151
+ const commonAABB2 = labelAABB ? [
11152
+ Math.min(labelAABB[0], elementAABB[0]),
11153
+ Math.min(labelAABB[1], elementAABB[1]),
11154
+ Math.max(labelAABB[2], elementAABB[2]),
11155
+ Math.max(labelAABB[3], elementAABB[3])
11156
+ ] : elementAABB;
11157
+ if (boundsContainBounds(selectionBounds, commonAABB2)) {
11158
+ if (framesInSelection && isFrameLikeElement(element)) {
11159
+ framesInSelection.add(element.id);
11160
+ }
11161
+ elementsInSelection.add(element);
11128
11162
  continue;
11129
11163
  }
11130
- if (opts?.includeBoundTextElement && isBoundToContainer(element) && appState.selectedElementIds[element?.containerId]) {
11131
- selectedElements.push(element);
11132
- addedElements.add(element.id);
11164
+ if (boxSelectionMode === "overlap" && labelAABB && doBoundsIntersect(selectionBounds, labelAABB)) {
11165
+ elementsInSelection.add(element);
11133
11166
  continue;
11134
11167
  }
11135
- }
11136
- if (opts?.includeElementsInFrames) {
11137
- const elementsToInclude = [];
11168
+ if (boxSelectionMode === "overlap" && doBoundsIntersect(selectionBounds, elementAABB)) {
11169
+ let hasIntersection = false;
11170
+ if (isLinearElement(element) || isFreeDrawElement(element)) {
11171
+ const center = elementCenterPoint(element, elementsMap);
11172
+ hasIntersection = element.points.some((point) => {
11173
+ const rotatedPoint = pointRotateRads(
11174
+ pointFrom(element.x + point[0], element.y + point[1]),
11175
+ center,
11176
+ element.angle
11177
+ );
11178
+ return pointInsideBounds(rotatedPoint, selectionBounds);
11179
+ });
11180
+ } else {
11181
+ const nonRotatedElementBounds = getElementBounds(
11182
+ element,
11183
+ elementsMap,
11184
+ true
11185
+ );
11186
+ const center = elementCenterPoint(element, elementsMap);
11187
+ hasIntersection = [
11188
+ pointRotateRads(
11189
+ pointFrom(
11190
+ (nonRotatedElementBounds[0] + nonRotatedElementBounds[2]) / 2,
11191
+ nonRotatedElementBounds[1]
11192
+ ),
11193
+ center,
11194
+ element.angle
11195
+ ),
11196
+ pointRotateRads(
11197
+ pointFrom(
11198
+ nonRotatedElementBounds[2],
11199
+ (nonRotatedElementBounds[1] + nonRotatedElementBounds[3]) / 2
11200
+ ),
11201
+ center,
11202
+ element.angle
11203
+ ),
11204
+ pointRotateRads(
11205
+ pointFrom(
11206
+ (nonRotatedElementBounds[0] + nonRotatedElementBounds[2]) / 2,
11207
+ nonRotatedElementBounds[3]
11208
+ ),
11209
+ center,
11210
+ element.angle
11211
+ ),
11212
+ pointRotateRads(
11213
+ pointFrom(
11214
+ nonRotatedElementBounds[0],
11215
+ (nonRotatedElementBounds[1] + nonRotatedElementBounds[3]) / 2
11216
+ ),
11217
+ center,
11218
+ element.angle
11219
+ )
11220
+ ].some((point) => {
11221
+ return pointInsideBounds(
11222
+ pointRotateRads(point, center, element.angle),
11223
+ selectionBounds
11224
+ );
11225
+ });
11226
+ }
11227
+ if (!hasIntersection) {
11228
+ hasIntersection = selectionEdges.some(
11229
+ (selectionEdge) => intersectElementWithLineSegment(
11230
+ element,
11231
+ elementsMap,
11232
+ selectionEdge,
11233
+ strokeWidth / 2,
11234
+ true
11235
+ // Stop at first hit for better performance
11236
+ ).length > 0
11237
+ );
11238
+ }
11239
+ if (hasIntersection) {
11240
+ if (framesInSelection && isFrameLikeElement(element)) {
11241
+ framesInSelection.add(element.id);
11242
+ }
11243
+ elementsInSelection.add(element);
11244
+ continue;
11245
+ }
11246
+ }
11247
+ }
11248
+ if (framesInSelection) {
11249
+ elementsInSelection.forEach((element) => {
11250
+ if (element.frameId && framesInSelection.has(element.frameId)) {
11251
+ elementsInSelection.delete(element);
11252
+ }
11253
+ });
11254
+ }
11255
+ if (boxSelectionMode === "overlap") {
11256
+ Array.from(elementsInSelection).forEach((element) => {
11257
+ const groupId = element.groupIds.at(-1);
11258
+ const group = groupId ? groups[groupId] : null;
11259
+ group?.forEach((groupElement) => elementsInSelection.add(groupElement));
11260
+ });
11261
+ } else if (boxSelectionMode === "contain") {
11262
+ elementsInSelection.forEach((element) => {
11263
+ const groupId = element.groupIds.at(-1);
11264
+ const group = groupId ? groups[groupId] : null;
11265
+ if (group && !group.every((groupElement) => elementsInSelection.has(groupElement))) {
11266
+ elementsInSelection.delete(element);
11267
+ }
11268
+ });
11269
+ }
11270
+ return elements.filter((element) => elementsInSelection.has(element));
11271
+ };
11272
+ var getVisibleAndNonSelectedElements = (elements, selectedElements, appState, elementsMap) => {
11273
+ const selectedElementsSet = new Set(
11274
+ selectedElements.map((element) => element.id)
11275
+ );
11276
+ return elements.filter((element) => {
11277
+ const isVisible = isElementInViewport(
11278
+ element,
11279
+ appState.width,
11280
+ appState.height,
11281
+ appState,
11282
+ elementsMap
11283
+ );
11284
+ return !selectedElementsSet.has(element.id) && isVisible;
11285
+ });
11286
+ };
11287
+ var isSomeElementSelected = function() {
11288
+ let lastElements = null;
11289
+ let lastSelectedElementIds = null;
11290
+ let isSelected = null;
11291
+ const ret = (elements, appState) => {
11292
+ if (isSelected != null && elements === lastElements && appState.selectedElementIds === lastSelectedElementIds) {
11293
+ return isSelected;
11294
+ }
11295
+ isSelected = elements.some(
11296
+ (element) => appState.selectedElementIds[element.id]
11297
+ );
11298
+ lastElements = elements;
11299
+ lastSelectedElementIds = appState.selectedElementIds;
11300
+ return isSelected;
11301
+ };
11302
+ ret.clearCache = () => {
11303
+ lastElements = null;
11304
+ lastSelectedElementIds = null;
11305
+ isSelected = null;
11306
+ };
11307
+ return ret;
11308
+ }();
11309
+ var getSelectedElements = (elements, appState, opts) => {
11310
+ const addedElements = /* @__PURE__ */ new Set();
11311
+ const selectedElements = [];
11312
+ for (const element of elements.values()) {
11313
+ if (appState.selectedElementIds[element.id]) {
11314
+ selectedElements.push(element);
11315
+ addedElements.add(element.id);
11316
+ continue;
11317
+ }
11318
+ if (opts?.includeBoundTextElement && isBoundToContainer(element) && appState.selectedElementIds[element?.containerId]) {
11319
+ selectedElements.push(element);
11320
+ addedElements.add(element.id);
11321
+ continue;
11322
+ }
11323
+ }
11324
+ if (opts?.includeElementsInFrames) {
11325
+ const elementsToInclude = [];
11138
11326
  selectedElements.forEach((element) => {
11139
11327
  if (isFrameLikeElement(element)) {
11140
11328
  getFrameChildren(elements, element.id).forEach(
@@ -11196,486 +11384,984 @@ var getActiveTextElement = (selectedElements, appState) => {
11196
11384
  return activeTextElement || null;
11197
11385
  };
11198
11386
 
11199
- // ../element/src/frame.ts
11200
- var bindElementsToFramesAfterDuplication = (nextElements, origElements, origIdToDuplicateId) => {
11201
- const nextElementMap = arrayToMap(nextElements);
11202
- for (const element of origElements) {
11203
- if (element.frameId) {
11204
- const nextElementId = origIdToDuplicateId.get(element.id);
11205
- const nextFrameId = origIdToDuplicateId.get(element.frameId);
11206
- const nextElement = nextElementId && nextElementMap.get(nextElementId);
11207
- if (nextElement) {
11208
- mutateElement(nextElement, nextElementMap, {
11209
- frameId: nextFrameId ?? null
11210
- });
11211
- }
11387
+ // ../fractional-indexing/src/index.ts
11388
+ var BASE_62_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
11389
+ function midpoint(a, b, digits) {
11390
+ const zero = digits[0];
11391
+ if (b != null && a >= b) {
11392
+ throw new Error(`${a} >= ${b}`);
11393
+ }
11394
+ if (a.slice(-1) === zero || b && b.slice(-1) === zero) {
11395
+ throw new Error("trailing zero");
11396
+ }
11397
+ if (b) {
11398
+ let n = 0;
11399
+ while ((a[n] || zero) === b[n]) {
11400
+ n++;
11401
+ }
11402
+ if (n > 0) {
11403
+ return b.slice(0, n) + midpoint(a.slice(n), b.slice(n), digits);
11212
11404
  }
11213
11405
  }
11214
- };
11215
- function isElementIntersectingFrame(element, frame, elementsMap) {
11216
- const frameLineSegments = getElementLineSegments(frame, elementsMap);
11217
- const elementLineSegments = getElementLineSegments(element, elementsMap);
11218
- const intersecting = frameLineSegments.some(
11219
- (frameLineSegment) => elementLineSegments.some(
11220
- (elementLineSegment) => doLineSegmentsIntersect(frameLineSegment, elementLineSegment)
11221
- )
11222
- );
11223
- return intersecting;
11406
+ const digitA = a ? digits.indexOf(a[0]) : 0;
11407
+ const digitB = b != null ? digits.indexOf(b[0]) : digits.length;
11408
+ if (digitB - digitA > 1) {
11409
+ const midDigit = Math.round(0.5 * (digitA + digitB));
11410
+ return digits[midDigit];
11411
+ }
11412
+ if (b && b.length > 1) {
11413
+ return b.slice(0, 1);
11414
+ }
11415
+ return digits[digitA] + midpoint(a.slice(1), null, digits);
11224
11416
  }
11225
- var getElementsCompletelyInFrame = (elements, frame, elementsMap) => omitGroupsContainingFrameLikes(
11226
- getElementsWithinSelection(elements, frame, elementsMap, false)
11227
- ).filter(
11228
- (element) => !isFrameLikeElement(element) && !element.frameId || element.frameId === frame.id
11229
- );
11230
- var isElementContainingFrame = (element, frame, elementsMap) => {
11231
- return getElementsWithinSelection([frame], element, elementsMap).some(
11232
- (e) => e.id === frame.id
11233
- );
11234
- };
11235
- var elementsAreInFrameBounds = (elements, frame, elementsMap) => {
11236
- const [frameX1, frameY1, frameX2, frameY2] = getElementAbsoluteCoords(
11237
- frame,
11238
- elementsMap
11239
- );
11240
- const [elementX1, elementY1, elementX2, elementY2] = getCommonBounds(elements);
11241
- return frameX1 <= elementX1 && frameY1 <= elementY1 && frameX2 >= elementX2 && frameY2 >= elementY2;
11242
- };
11243
- var elementOverlapsWithFrame = (element, frame, elementsMap) => {
11244
- return elementsAreInFrameBounds([element], frame, elementsMap) || isElementIntersectingFrame(element, frame, elementsMap) || isElementContainingFrame(element, frame, elementsMap);
11245
- };
11246
- var isCursorInFrame = (cursorCoords, frame, elementsMap) => {
11247
- const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame, elementsMap);
11248
- return isPointWithinBounds(
11249
- pointFrom(fx1, fy1),
11250
- pointFrom(cursorCoords.x, cursorCoords.y),
11251
- pointFrom(fx2, fy2)
11252
- );
11253
- };
11254
- var groupByFrameLikes = (elements) => {
11255
- const frameElementsMap = /* @__PURE__ */ new Map();
11256
- for (const element of elements) {
11257
- const frameId = isFrameLikeElement(element) ? element.id : element.frameId;
11258
- if (frameId && !frameElementsMap.has(frameId)) {
11259
- frameElementsMap.set(frameId, getFrameChildren(elements, frameId));
11417
+ function validateInteger(int) {
11418
+ if (int.length !== getIntegerLength(int[0])) {
11419
+ throw new Error(`invalid integer part of order key: ${int}`);
11420
+ }
11421
+ }
11422
+ function getIntegerLength(head) {
11423
+ if (head >= "a" && head <= "z") {
11424
+ return head.charCodeAt(0) - "a".charCodeAt(0) + 2;
11425
+ } else if (head >= "A" && head <= "Z") {
11426
+ return "Z".charCodeAt(0) - head.charCodeAt(0) + 2;
11427
+ }
11428
+ throw new Error(`invalid order key head: ${head}`);
11429
+ }
11430
+ function getIntegerPart(key) {
11431
+ const integerPartLength = getIntegerLength(key[0]);
11432
+ if (integerPartLength > key.length) {
11433
+ throw new Error(`invalid order key: ${key}`);
11434
+ }
11435
+ return key.slice(0, integerPartLength);
11436
+ }
11437
+ function validateOrderKey(key, digits = BASE_62_DIGITS) {
11438
+ const validChars = key.split("").every((char) => digits.includes(char));
11439
+ if (key === `A${digits[0].repeat(26)}` || !validChars) {
11440
+ throw new Error(`invalid order key: ${key}`);
11441
+ }
11442
+ const i = getIntegerPart(key);
11443
+ const f = key.slice(i.length);
11444
+ if (f.slice(-1) === digits[0]) {
11445
+ throw new Error(`invalid order key: ${key}`);
11446
+ }
11447
+ }
11448
+ function incrementInteger(x, digits) {
11449
+ validateInteger(x);
11450
+ const [head, ...digs] = x.split("");
11451
+ let carry = true;
11452
+ for (let i = digs.length - 1; carry && i >= 0; i--) {
11453
+ const d = digits.indexOf(digs[i]) + 1;
11454
+ if (d === digits.length) {
11455
+ digs[i] = digits[0];
11456
+ } else {
11457
+ digs[i] = digits[d];
11458
+ carry = false;
11260
11459
  }
11261
11460
  }
11262
- return frameElementsMap;
11263
- };
11264
- var getFrameChildren = (allElements, frameId) => {
11265
- const frameChildren = [];
11266
- for (const element of allElements.values()) {
11267
- if (element.frameId === frameId) {
11268
- frameChildren.push(element);
11461
+ if (carry) {
11462
+ if (head === "Z") {
11463
+ return `a${digits[0]}`;
11464
+ }
11465
+ if (head === "z") {
11466
+ return null;
11269
11467
  }
11468
+ const h = String.fromCharCode(head.charCodeAt(0) + 1);
11469
+ if (h > "a") {
11470
+ digs.push(digits[0]);
11471
+ } else {
11472
+ digs.pop();
11473
+ }
11474
+ return h + digs.join("");
11270
11475
  }
11271
- return frameChildren;
11272
- };
11273
- var getFrameLikeElements = (allElements) => {
11274
- return allElements.filter(
11275
- (element) => isFrameLikeElement(element)
11276
- );
11277
- };
11278
- var getRootElements = (allElements) => {
11279
- const frameElements = arrayToMap(getFrameLikeElements(allElements));
11280
- return allElements.filter(
11281
- (element) => frameElements.has(element.id) || !element.frameId || !frameElements.has(element.frameId)
11282
- );
11283
- };
11284
- var getElementsInResizingFrame = (allElements, frame, appState, elementsMap) => {
11285
- const prevElementsInFrame = getFrameChildren(allElements, frame.id);
11286
- const nextElementsInFrame = new Set(prevElementsInFrame);
11287
- const elementsCompletelyInFrame = /* @__PURE__ */ new Set([
11288
- ...getElementsCompletelyInFrame(allElements, frame, elementsMap),
11289
- ...prevElementsInFrame.filter(
11290
- (element) => isElementContainingFrame(element, frame, elementsMap)
11291
- )
11292
- ]);
11293
- const elementsNotCompletelyInFrame = prevElementsInFrame.filter(
11294
- (element) => !elementsCompletelyInFrame.has(element)
11295
- );
11296
- const groupsToKeep = new Set(
11297
- Array.from(elementsCompletelyInFrame).flatMap(
11298
- (element) => element.groupIds
11299
- )
11300
- );
11301
- for (const element of elementsNotCompletelyInFrame) {
11302
- if (!isElementIntersectingFrame(element, frame, elementsMap)) {
11303
- if (element.groupIds.length === 0) {
11304
- nextElementsInFrame.delete(element);
11305
- }
11306
- } else if (element.groupIds.length > 0) {
11307
- for (const id of element.groupIds) {
11308
- groupsToKeep.add(id);
11309
- }
11476
+ return head + digs.join("");
11477
+ }
11478
+ function decrementInteger(x, digits) {
11479
+ validateInteger(x);
11480
+ const [head, ...digs] = x.split("");
11481
+ let borrow = true;
11482
+ for (let i = digs.length - 1; borrow && i >= 0; i--) {
11483
+ const d = digits.indexOf(digs[i]) - 1;
11484
+ if (d === -1) {
11485
+ digs[i] = digits.slice(-1);
11486
+ } else {
11487
+ digs[i] = digits[d];
11488
+ borrow = false;
11310
11489
  }
11311
11490
  }
11312
- for (const element of elementsNotCompletelyInFrame) {
11313
- if (element.groupIds.length > 0) {
11314
- let shouldRemoveElement = true;
11315
- for (const id of element.groupIds) {
11316
- if (groupsToKeep.has(id)) {
11317
- shouldRemoveElement = false;
11318
- }
11319
- }
11320
- if (shouldRemoveElement) {
11321
- nextElementsInFrame.delete(element);
11322
- }
11491
+ if (borrow) {
11492
+ if (head === "a") {
11493
+ return `Z${digits.slice(-1)}`;
11494
+ }
11495
+ if (head === "A") {
11496
+ return null;
11323
11497
  }
11498
+ const h = String.fromCharCode(head.charCodeAt(0) - 1);
11499
+ if (h < "Z") {
11500
+ digs.push(digits.slice(-1));
11501
+ } else {
11502
+ digs.pop();
11503
+ }
11504
+ return h + digs.join("");
11324
11505
  }
11325
- const individualElementsCompletelyInFrame = Array.from(
11326
- elementsCompletelyInFrame
11327
- ).filter((element) => element.groupIds.length === 0);
11328
- for (const element of individualElementsCompletelyInFrame) {
11329
- nextElementsInFrame.add(element);
11506
+ return head + digs.join("");
11507
+ }
11508
+ function generateKeyBetween(a, b, digits = BASE_62_DIGITS) {
11509
+ if (a != null) {
11510
+ validateOrderKey(a, digits);
11330
11511
  }
11331
- const newGroupElementsCompletelyInFrame = Array.from(
11332
- elementsCompletelyInFrame
11333
- ).filter((element) => element.groupIds.length > 0);
11334
- const groupIds = selectGroupsFromGivenElements(
11335
- newGroupElementsCompletelyInFrame,
11336
- appState
11337
- );
11338
- for (const [id, isSelected] of Object.entries(groupIds)) {
11339
- if (isSelected) {
11340
- const elementsInGroup = getElementsInGroup(allElements, id);
11341
- if (elementsAreInFrameBounds(elementsInGroup, frame, elementsMap)) {
11342
- for (const element of elementsInGroup) {
11343
- nextElementsInFrame.add(element);
11344
- }
11345
- }
11346
- }
11512
+ if (b != null) {
11513
+ validateOrderKey(b, digits);
11347
11514
  }
11348
- return [...nextElementsInFrame].filter((element) => {
11349
- return !(isTextElement(element) && element.containerId);
11350
- });
11351
- };
11352
- var getElementsInNewFrame = (elements, frame, elementsMap) => {
11353
- return omitPartialGroups(
11354
- omitGroupsContainingFrameLikes(
11355
- elements,
11356
- getElementsCompletelyInFrame(elements, frame, elementsMap)
11357
- ),
11358
- frame,
11359
- elementsMap
11360
- );
11361
- };
11362
- var omitPartialGroups = (elements, frame, allElementsMap) => {
11363
- const elementsToReturn = [];
11364
- const checkedGroups = /* @__PURE__ */ new Map();
11365
- for (const element of elements) {
11366
- let shouldOmit = false;
11367
- if (element.groupIds.length > 0) {
11368
- if (element.groupIds.some((gid) => checkedGroups.get(gid))) {
11369
- shouldOmit = true;
11370
- } else {
11371
- const allElementsInGroup = new Set(
11372
- element.groupIds.flatMap(
11373
- (gid) => getElementsInGroup(allElementsMap, gid)
11374
- )
11375
- );
11376
- shouldOmit = !elementsAreInFrameBounds(
11377
- Array.from(allElementsInGroup),
11378
- frame,
11379
- allElementsMap
11380
- );
11381
- }
11382
- element.groupIds.forEach((gid) => {
11383
- checkedGroups.set(gid, shouldOmit);
11384
- });
11515
+ if (a != null && b != null && a >= b) {
11516
+ throw new Error(`${a} >= ${b}`);
11517
+ }
11518
+ if (a == null) {
11519
+ if (b == null) {
11520
+ return `a${digits[0]}`;
11385
11521
  }
11386
- if (!shouldOmit) {
11387
- elementsToReturn.push(element);
11522
+ const ib2 = getIntegerPart(b);
11523
+ const fb2 = b.slice(ib2.length);
11524
+ if (ib2 === `A${digits[0].repeat(26)}`) {
11525
+ return ib2 + midpoint("", fb2, digits);
11388
11526
  }
11527
+ if (ib2 < b) {
11528
+ return ib2;
11529
+ }
11530
+ const res = decrementInteger(ib2, digits);
11531
+ if (res == null) {
11532
+ throw new Error("cannot decrement any more");
11533
+ }
11534
+ return res;
11389
11535
  }
11390
- return elementsToReturn;
11391
- };
11392
- var getContainingFrame = (element, elementsMap) => {
11393
- if (!element.frameId) {
11394
- return null;
11536
+ if (b == null) {
11537
+ const ia2 = getIntegerPart(a);
11538
+ const fa2 = a.slice(ia2.length);
11539
+ const i2 = incrementInteger(ia2, digits);
11540
+ return i2 == null ? ia2 + midpoint(fa2, null, digits) : i2;
11395
11541
  }
11396
- return elementsMap.get(element.frameId) || null;
11397
- };
11398
- var filterElementsEligibleAsFrameChildren = (elements, frame) => {
11399
- const otherFrames = /* @__PURE__ */ new Set();
11400
- const elementsMap = arrayToMap(elements);
11401
- elements = omitGroupsContainingFrameLikes(elements);
11402
- for (const element of elements) {
11403
- if (isFrameLikeElement(element) && element.id !== frame.id) {
11404
- otherFrames.add(element.id);
11405
- }
11542
+ const ia = getIntegerPart(a);
11543
+ const fa = a.slice(ia.length);
11544
+ const ib = getIntegerPart(b);
11545
+ const fb = b.slice(ib.length);
11546
+ if (ia === ib) {
11547
+ return ia + midpoint(fa, fb, digits);
11406
11548
  }
11407
- const processedGroups = /* @__PURE__ */ new Set();
11408
- const eligibleElements = [];
11409
- for (const element of elements) {
11410
- if (isFrameLikeElement(element) || element.frameId && otherFrames.has(element.frameId)) {
11411
- continue;
11549
+ const i = incrementInteger(ia, digits);
11550
+ if (i == null) {
11551
+ throw new Error("cannot increment any more");
11552
+ }
11553
+ if (i < b) {
11554
+ return i;
11555
+ }
11556
+ return ia + midpoint(fa, null, digits);
11557
+ }
11558
+ function generateNKeysBetween(a, b, n, digits = BASE_62_DIGITS) {
11559
+ if (n === 0) {
11560
+ return [];
11561
+ }
11562
+ if (n === 1) {
11563
+ return [generateKeyBetween(a, b, digits)];
11564
+ }
11565
+ if (b == null) {
11566
+ let c2 = generateKeyBetween(a, b, digits);
11567
+ const result = [c2];
11568
+ for (let i = 0; i < n - 1; i++) {
11569
+ c2 = generateKeyBetween(c2, b, digits);
11570
+ result.push(c2);
11412
11571
  }
11413
- if (element.groupIds.length) {
11414
- const shallowestGroupId = element.groupIds.at(-1);
11415
- if (!processedGroups.has(shallowestGroupId)) {
11416
- processedGroups.add(shallowestGroupId);
11417
- const groupElements = getElementsInGroup(elements, shallowestGroupId);
11418
- if (groupElements.some(
11419
- (el) => elementOverlapsWithFrame(el, frame, elementsMap)
11420
- )) {
11421
- for (const child of groupElements) {
11422
- eligibleElements.push(child);
11423
- }
11424
- }
11425
- }
11426
- } else {
11427
- const overlaps = elementOverlapsWithFrame(element, frame, elementsMap);
11428
- if (overlaps) {
11429
- eligibleElements.push(element);
11430
- }
11572
+ return result;
11573
+ }
11574
+ if (a == null) {
11575
+ let c2 = generateKeyBetween(a, b, digits);
11576
+ const result = [c2];
11577
+ for (let i = 0; i < n - 1; i++) {
11578
+ c2 = generateKeyBetween(a, c2, digits);
11579
+ result.push(c2);
11431
11580
  }
11581
+ result.reverse();
11582
+ return result;
11583
+ }
11584
+ const mid = Math.floor(n / 2);
11585
+ const c = generateKeyBetween(a, b, digits);
11586
+ return [
11587
+ ...generateNKeysBetween(a, c, mid, digits),
11588
+ c,
11589
+ ...generateNKeysBetween(c, b, n - mid - 1, digits)
11590
+ ];
11591
+ }
11592
+
11593
+ // ../element/src/fractionalIndex.ts
11594
+ var InvalidFractionalIndexError = class extends Error {
11595
+ constructor() {
11596
+ super(...arguments);
11597
+ __publicField(this, "code", "ELEMENT_HAS_INVALID_INDEX");
11432
11598
  }
11433
- return eligibleElements;
11434
11599
  };
11435
- var addElementsToFrame = (allElements, elementsToAdd, frame, appState) => {
11436
- const elementsMap = arrayToMap(allElements);
11437
- const currTargetFrameChildrenMap = /* @__PURE__ */ new Map();
11438
- for (const element of allElements.values()) {
11439
- if (element.frameId === frame.id) {
11440
- currTargetFrameChildrenMap.set(element.id, true);
11600
+ var validateFractionalIndices = (elements, {
11601
+ shouldThrow = false,
11602
+ includeBoundTextValidation = false,
11603
+ ignoreLogs,
11604
+ reconciliationContext
11605
+ }) => {
11606
+ const errorMessages = [];
11607
+ const stringifyElement = (element) => `${element?.index}:${element?.id}:${element?.type}:${element?.isDeleted}:${element?.version}:${element?.versionNonce}`;
11608
+ const indices = elements.map((x) => x.index);
11609
+ for (const [i, index] of indices.entries()) {
11610
+ const predecessorIndex = indices[i - 1];
11611
+ const successorIndex = indices[i + 1];
11612
+ if (!isValidFractionalIndex(index, predecessorIndex, successorIndex)) {
11613
+ errorMessages.push(
11614
+ `Fractional indices invariant has been compromised: "${stringifyElement(
11615
+ elements[i - 1]
11616
+ )}", "${stringifyElement(elements[i])}", "${stringifyElement(
11617
+ elements[i + 1]
11618
+ )}"`
11619
+ );
11441
11620
  }
11442
- }
11443
- const suppliedElementsToAddSet = new Set(elementsToAdd.map((el) => el.id));
11444
- const finalElementsToAdd = [];
11445
- const otherFrames = /* @__PURE__ */ new Set();
11446
- for (const element of elementsToAdd) {
11447
- if (isFrameLikeElement(element) && element.id !== frame.id) {
11448
- otherFrames.add(element.id);
11621
+ if (includeBoundTextValidation && hasBoundTextElement(elements[i])) {
11622
+ const container = elements[i];
11623
+ const text = getBoundTextElement(container, arrayToMap(elements));
11624
+ if (text && text.index <= container.index) {
11625
+ errorMessages.push(
11626
+ `Fractional indices invariant for bound elements has been compromised: "${stringifyElement(
11627
+ text
11628
+ )}", "${stringifyElement(container)}"`
11629
+ );
11630
+ }
11449
11631
  }
11450
11632
  }
11451
- for (const element of omitGroupsContainingFrameLikes(
11452
- allElements,
11453
- elementsToAdd
11454
- )) {
11455
- if (isFrameLikeElement(element) || element.frameId && otherFrames.has(element.frameId)) {
11456
- continue;
11457
- }
11458
- if (element.frameId && appState.selectedElementIds[element.id] && appState.selectedElementIds[element.frameId]) {
11459
- continue;
11633
+ if (errorMessages.length) {
11634
+ const error = new InvalidFractionalIndexError();
11635
+ const additionalContext = [];
11636
+ if (reconciliationContext) {
11637
+ additionalContext.push("Additional reconciliation context:");
11638
+ additionalContext.push(
11639
+ reconciliationContext.localElements.map((x) => stringifyElement(x))
11640
+ );
11641
+ additionalContext.push(
11642
+ reconciliationContext.remoteElements.map((x) => stringifyElement(x))
11643
+ );
11460
11644
  }
11461
- if (!currTargetFrameChildrenMap.has(element.id)) {
11462
- finalElementsToAdd.push(element);
11645
+ if (!ignoreLogs) {
11646
+ console.error(
11647
+ errorMessages.join("\n\n"),
11648
+ error.stack,
11649
+ elements.map((x) => stringifyElement(x)),
11650
+ ...additionalContext
11651
+ );
11463
11652
  }
11464
- const boundTextElement = getBoundTextElement(element, elementsMap);
11465
- if (boundTextElement && !suppliedElementsToAddSet.has(boundTextElement.id) && !currTargetFrameChildrenMap.has(boundTextElement.id)) {
11466
- finalElementsToAdd.push(boundTextElement);
11653
+ if (shouldThrow) {
11654
+ throw error;
11467
11655
  }
11468
11656
  }
11469
- for (const element of finalElementsToAdd) {
11470
- mutateElement(element, elementsMap, {
11471
- frameId: frame.id
11472
- });
11473
- }
11474
- return allElements;
11475
11657
  };
11476
- var removeElementsFromFrame = (elementsToRemove, elementsMap) => {
11477
- const _elementsToRemove = /* @__PURE__ */ new Map();
11478
- const toRemoveElementsByFrame = /* @__PURE__ */ new Map();
11479
- for (const element of elementsToRemove) {
11480
- if (element.frameId) {
11481
- _elementsToRemove.set(element.id, element);
11482
- const arr = toRemoveElementsByFrame.get(element.frameId) || [];
11483
- arr.push(element);
11484
- const boundTextElement = getBoundTextElement(element, elementsMap);
11485
- if (boundTextElement) {
11486
- _elementsToRemove.set(boundTextElement.id, boundTextElement);
11487
- arr.push(boundTextElement);
11658
+ var orderByFractionalIndex = (elements) => {
11659
+ return elements.sort((a, b) => {
11660
+ if (isOrderedElement(a) && isOrderedElement(b)) {
11661
+ if (a.index < b.index) {
11662
+ return -1;
11663
+ } else if (a.index > b.index) {
11664
+ return 1;
11488
11665
  }
11489
- toRemoveElementsByFrame.set(element.frameId, arr);
11666
+ return a.id < b.id ? -1 : 1;
11490
11667
  }
11491
- }
11492
- for (const [, element] of _elementsToRemove) {
11493
- mutateElement(element, elementsMap, {
11494
- frameId: null
11668
+ return 1;
11669
+ });
11670
+ };
11671
+ var syncMovedIndices = (elements, movedElements) => {
11672
+ try {
11673
+ const elementsMap = arrayToMap(elements);
11674
+ const indicesGroups = getMovedIndicesGroups(elements, movedElements);
11675
+ const elementsUpdates = generateIndices(elements, indicesGroups);
11676
+ const elementsCandidates = elements.map((x) => {
11677
+ const elementUpdates = elementsUpdates.get(x);
11678
+ if (elementUpdates) {
11679
+ return { ...x, index: elementUpdates.index };
11680
+ }
11681
+ return x;
11495
11682
  });
11683
+ validateFractionalIndices(
11684
+ elementsCandidates,
11685
+ // we don't autofix invalid bound text indices, hence don't include it in the validation
11686
+ {
11687
+ includeBoundTextValidation: false,
11688
+ shouldThrow: true,
11689
+ ignoreLogs: true
11690
+ }
11691
+ );
11692
+ for (const [element, { index }] of elementsUpdates) {
11693
+ mutateElement(element, elementsMap, { index });
11694
+ }
11695
+ } catch (e) {
11696
+ syncInvalidIndices(elements);
11496
11697
  }
11698
+ return elements;
11497
11699
  };
11498
- var removeAllElementsFromFrame = (allElements, frame) => {
11499
- const elementsInFrame = getFrameChildren(allElements, frame.id);
11500
- removeElementsFromFrame(elementsInFrame, arrayToMap(allElements));
11501
- return allElements;
11502
- };
11503
- var replaceAllElementsInFrame = (allElements, nextElementsInFrame, frame, app) => {
11504
- return addElementsToFrame(
11505
- removeAllElementsFromFrame(allElements, frame),
11506
- nextElementsInFrame,
11507
- frame,
11508
- app.state
11509
- ).slice();
11700
+ var syncInvalidIndices = (elements) => {
11701
+ const elementsMap = arrayToMap(elements);
11702
+ const indicesGroups = getInvalidIndicesGroups(elements);
11703
+ const elementsUpdates = generateIndices(elements, indicesGroups);
11704
+ for (const [element, { index }] of elementsUpdates) {
11705
+ mutateElement(element, elementsMap, { index });
11706
+ }
11707
+ return elements;
11510
11708
  };
11511
- var updateFrameMembershipOfSelectedElements = (allElements, appState, app) => {
11512
- const selectedElements = app.scene.getSelectedElements({
11513
- selectedElementIds: appState.selectedElementIds,
11514
- // supplying elements explicitly in case we're passed non-state elements
11515
- elements: allElements
11516
- });
11517
- const elementsToFilter = new Set(selectedElements);
11518
- if (appState.editingGroupId) {
11519
- for (const element of selectedElements) {
11520
- if (element.groupIds.length === 0) {
11521
- elementsToFilter.add(element);
11522
- } else {
11523
- element.groupIds.flatMap((gid) => getElementsInGroup(allElements, gid)).forEach((element2) => elementsToFilter.add(element2));
11524
- }
11525
- }
11709
+ var syncInvalidIndicesImmutable = (elements) => {
11710
+ const syncedElements = arrayToMap(elements);
11711
+ const indicesGroups = getInvalidIndicesGroups(elements);
11712
+ const elementsUpdates = generateIndices(elements, indicesGroups);
11713
+ for (const [element, { index }] of elementsUpdates) {
11714
+ syncedElements.set(element.id, newElementWith(element, { index }));
11526
11715
  }
11527
- const elementsToRemove = /* @__PURE__ */ new Set();
11528
- const elementsMap = arrayToMap(allElements);
11529
- elementsToFilter.forEach((element) => {
11530
- if (element.frameId && !isFrameLikeElement(element) && !isElementInFrame(element, elementsMap, appState)) {
11531
- elementsToRemove.add(element);
11716
+ return syncedElements;
11717
+ };
11718
+ var getMovedIndicesGroups = (elements, movedElements) => {
11719
+ const indicesGroups = [];
11720
+ let i = 0;
11721
+ while (i < elements.length) {
11722
+ if (movedElements.has(elements[i].id)) {
11723
+ const indicesGroup = [i - 1, i];
11724
+ while (++i < elements.length) {
11725
+ if (!movedElements.has(elements[i].id)) {
11726
+ break;
11727
+ }
11728
+ indicesGroup.push(i);
11729
+ }
11730
+ indicesGroup.push(i);
11731
+ indicesGroups.push(indicesGroup);
11732
+ } else {
11733
+ i++;
11532
11734
  }
11533
- });
11534
- if (elementsToRemove.size > 0) {
11535
- removeElementsFromFrame(elementsToRemove, elementsMap);
11536
11735
  }
11537
- return allElements;
11736
+ return indicesGroups;
11538
11737
  };
11539
- var omitGroupsContainingFrameLikes = (allElements, selectedElements) => {
11540
- const uniqueGroupIds = /* @__PURE__ */ new Set();
11541
- const elements = selectedElements || allElements;
11542
- for (const el of elements.values()) {
11543
- const topMostGroupId = el.groupIds[el.groupIds.length - 1];
11544
- if (topMostGroupId) {
11545
- uniqueGroupIds.add(topMostGroupId);
11738
+ var getInvalidIndicesGroups = (elements) => {
11739
+ const indicesGroups = [];
11740
+ let lowerBound = void 0;
11741
+ let upperBound = void 0;
11742
+ let lowerBoundIndex = -1;
11743
+ let upperBoundIndex = 0;
11744
+ const getLowerBound = (index) => {
11745
+ const lowerBound2 = elements[lowerBoundIndex] ? elements[lowerBoundIndex].index : void 0;
11746
+ const candidate = elements[index - 1]?.index;
11747
+ if (!lowerBound2 && candidate || // first lowerBound
11748
+ lowerBound2 && candidate && candidate > lowerBound2) {
11749
+ return [candidate, index - 1];
11546
11750
  }
11547
- }
11548
- const rejectedGroupIds = /* @__PURE__ */ new Set();
11549
- for (const groupId of uniqueGroupIds) {
11550
- if (getElementsInGroup(allElements, groupId).some(
11551
- (el) => isFrameLikeElement(el)
11552
- )) {
11553
- rejectedGroupIds.add(groupId);
11751
+ return [lowerBound2, lowerBoundIndex];
11752
+ };
11753
+ const getUpperBound = (index) => {
11754
+ const upperBound2 = elements[upperBoundIndex] ? elements[upperBoundIndex].index : void 0;
11755
+ if (upperBound2 && index < upperBoundIndex) {
11756
+ return [upperBound2, upperBoundIndex];
11554
11757
  }
11555
- }
11556
- const ret = [];
11557
- for (const element of elements.values()) {
11558
- if (!rejectedGroupIds.has(element.groupIds[element.groupIds.length - 1])) {
11559
- ret.push(element);
11758
+ let i2 = upperBoundIndex;
11759
+ while (++i2 < elements.length) {
11760
+ const candidate = elements[i2]?.index;
11761
+ if (!upperBound2 && candidate || // first upperBound
11762
+ upperBound2 && candidate && candidate > upperBound2) {
11763
+ return [candidate, i2];
11764
+ }
11765
+ }
11766
+ return [void 0, i2];
11767
+ };
11768
+ let i = 0;
11769
+ while (i < elements.length) {
11770
+ const current = elements[i].index;
11771
+ [lowerBound, lowerBoundIndex] = getLowerBound(i);
11772
+ [upperBound, upperBoundIndex] = getUpperBound(i);
11773
+ if (!isValidFractionalIndex(current, lowerBound, upperBound)) {
11774
+ const indicesGroup = [lowerBoundIndex, i];
11775
+ while (++i < elements.length) {
11776
+ const current2 = elements[i].index;
11777
+ const [nextLowerBound, nextLowerBoundIndex] = getLowerBound(i);
11778
+ const [nextUpperBound, nextUpperBoundIndex] = getUpperBound(i);
11779
+ if (isValidFractionalIndex(current2, nextLowerBound, nextUpperBound)) {
11780
+ break;
11781
+ }
11782
+ [lowerBound, lowerBoundIndex] = [nextLowerBound, nextLowerBoundIndex];
11783
+ [upperBound, upperBoundIndex] = [nextUpperBound, nextUpperBoundIndex];
11784
+ indicesGroup.push(i);
11785
+ }
11786
+ indicesGroup.push(upperBoundIndex);
11787
+ indicesGroups.push(indicesGroup);
11788
+ } else {
11789
+ i++;
11560
11790
  }
11561
11791
  }
11562
- return ret;
11792
+ return indicesGroups;
11563
11793
  };
11564
- var getTargetFrame = (element, elementsMap, appState) => {
11565
- const _element = isTextElement(element) ? getContainerElement(element, elementsMap) || element : element;
11566
- if (_element.frameId && appState.selectedElementIds[_element.id] && appState.selectedElementIds[_element.frameId]) {
11567
- return getContainingFrame(_element, elementsMap);
11794
+ var isValidFractionalIndex = (index, predecessor, successor) => {
11795
+ if (!index) {
11796
+ return false;
11568
11797
  }
11569
- return appState.selectedElementIds[_element.id] && appState.selectedElementsAreBeingDragged ? appState.frameToHighlight : getContainingFrame(_element, elementsMap);
11570
- };
11571
- var isElementInFrame = (element, allElementsMap, appState, opts) => {
11572
- const frame = opts?.targetFrame ?? getTargetFrame(element, allElementsMap, appState);
11573
- if (!frame) {
11798
+ try {
11799
+ validateOrderKey(index);
11800
+ } catch {
11574
11801
  return false;
11575
11802
  }
11576
- const _element = isTextElement(element) ? getContainerElement(element, allElementsMap) || element : element;
11577
- const setGroupsInFrame = (isInFrame) => {
11578
- if (opts?.checkedGroups) {
11579
- _element.groupIds.forEach((groupId) => {
11580
- opts.checkedGroups?.set(groupId, isInFrame);
11581
- });
11582
- }
11583
- };
11584
- if (
11585
- // if the element is not selected, or it is selected but not being dragged,
11586
- // frame membership won't update, so return true
11587
- !appState.selectedElementIds[_element.id] || !appState.selectedElementsAreBeingDragged || // if both frame and element are selected, won't update membership, so return true
11588
- appState.selectedElementIds[_element.id] && appState.selectedElementIds[frame.id]
11589
- ) {
11590
- return true;
11803
+ if (predecessor && successor) {
11804
+ return predecessor < index && index < successor;
11591
11805
  }
11592
- if (_element.groupIds.length === 0) {
11593
- return elementOverlapsWithFrame(_element, frame, allElementsMap);
11806
+ if (!predecessor && successor) {
11807
+ return index < successor;
11594
11808
  }
11595
- for (const gid of _element.groupIds) {
11596
- if (opts?.checkedGroups?.has(gid)) {
11597
- return opts.checkedGroups.get(gid);
11598
- }
11809
+ if (predecessor && !successor) {
11810
+ return predecessor < index;
11599
11811
  }
11600
- const allElementsInGroup = new Set(
11601
- _element.groupIds.filter((gid) => {
11602
- if (opts?.checkedGroups) {
11603
- return !opts.checkedGroups.has(gid);
11604
- }
11605
- return true;
11606
- }).flatMap((gid) => getElementsInGroup(allElementsMap, gid))
11607
- );
11608
- if (appState.editingGroupId && appState.selectedElementsAreBeingDragged) {
11609
- const selectedElements = new Set(
11610
- getSelectedElements(allElementsMap, appState)
11812
+ return !!index;
11813
+ };
11814
+ var generateIndices = (elements, indicesGroups) => {
11815
+ const elementsUpdates = /* @__PURE__ */ new Map();
11816
+ for (const indices of indicesGroups) {
11817
+ const lowerBoundIndex = indices.shift();
11818
+ const upperBoundIndex = indices.pop();
11819
+ const fractionalIndices = generateNKeysBetween(
11820
+ elements[lowerBoundIndex]?.index,
11821
+ elements[upperBoundIndex]?.index,
11822
+ indices.length
11611
11823
  );
11612
- const editingGroupOverlapsFrame = appState.frameToHighlight !== null;
11613
- if (editingGroupOverlapsFrame) {
11614
- return true;
11615
- }
11616
- selectedElements.forEach((selectedElement) => {
11617
- allElementsInGroup.delete(selectedElement);
11618
- });
11619
- }
11620
- for (const elementInGroup of allElementsInGroup) {
11621
- if (isFrameLikeElement(elementInGroup)) {
11622
- setGroupsInFrame(false);
11623
- return false;
11624
- }
11625
- }
11626
- for (const elementInGroup of allElementsInGroup) {
11627
- if (elementOverlapsWithFrame(elementInGroup, frame, allElementsMap)) {
11628
- setGroupsInFrame(true);
11629
- return true;
11824
+ for (let i = 0; i < indices.length; i++) {
11825
+ const element = elements[indices[i]];
11826
+ elementsUpdates.set(element, {
11827
+ index: fractionalIndices[i]
11828
+ });
11630
11829
  }
11631
11830
  }
11632
- return false;
11831
+ return elementsUpdates;
11633
11832
  };
11634
- var shouldApplyFrameClip = (element, frame, appState, elementsMap, checkedGroups) => {
11635
- if (!appState.frameRendering || !appState.frameRendering.clip) {
11636
- return false;
11637
- }
11638
- const shouldClipElementItself = isElementIntersectingFrame(element, frame, elementsMap) || isElementContainingFrame(element, frame, elementsMap);
11639
- if (shouldClipElementItself) {
11640
- for (const groupId of element.groupIds) {
11641
- checkedGroups?.set(groupId, true);
11642
- }
11833
+ var isOrderedElement = (element) => {
11834
+ if (element.index) {
11643
11835
  return true;
11644
11836
  }
11645
- if (!shouldClipElementItself && element.groupIds.length > 0 && !elementsAreInFrameBounds([element], frame, elementsMap)) {
11646
- let shouldClip = false;
11647
- if (!appState.selectedElementsAreBeingDragged) {
11648
- shouldClip = element.frameId === frame.id;
11649
- for (const groupId of element.groupIds) {
11650
- checkedGroups?.set(groupId, shouldClip);
11837
+ return false;
11838
+ };
11839
+
11840
+ // ../element/src/frame.ts
11841
+ var bindElementsToFramesAfterDuplication = (nextElements, origElements, origIdToDuplicateId) => {
11842
+ const nextElementMap = arrayToMap(nextElements);
11843
+ for (const element of origElements) {
11844
+ if (element.frameId) {
11845
+ const nextElementId = origIdToDuplicateId.get(element.id);
11846
+ const nextFrameId = origIdToDuplicateId.get(element.frameId);
11847
+ const nextElement = nextElementId && nextElementMap.get(nextElementId);
11848
+ if (nextElement) {
11849
+ mutateElement(nextElement, nextElementMap, {
11850
+ frameId: nextFrameId ?? null
11851
+ });
11651
11852
  }
11652
- } else {
11653
- shouldClip = isElementInFrame(element, elementsMap, appState, {
11654
- targetFrame: frame,
11655
- checkedGroups
11656
- });
11657
11853
  }
11658
- for (const groupId of element.groupIds) {
11659
- checkedGroups?.set(groupId, shouldClip);
11660
- }
11661
- return shouldClip;
11662
11854
  }
11663
- return false;
11664
- };
11665
- var DEFAULT_FRAME_NAME = "Frame";
11666
- var DEFAULT_AI_FRAME_NAME = "AI Frame";
11667
- var getDefaultFrameName = (element) => {
11668
- return isFrameElement(element) ? DEFAULT_FRAME_NAME : DEFAULT_AI_FRAME_NAME;
11669
11855
  };
11670
- var getFrameLikeTitle = (element) => {
11671
- return element.name === null ? getDefaultFrameName(element) : element.name;
11856
+ function isElementIntersectingFrame(element, frame, elementsMap) {
11857
+ const frameLineSegments = getElementLineSegments(frame, elementsMap);
11858
+ const elementLineSegments = getElementLineSegments(element, elementsMap);
11859
+ const intersecting = frameLineSegments.some(
11860
+ (frameLineSegment) => elementLineSegments.some(
11861
+ (elementLineSegment) => doLineSegmentsIntersect(frameLineSegment, elementLineSegment)
11862
+ )
11863
+ );
11864
+ return intersecting;
11865
+ }
11866
+ var getElementsCompletelyInFrame = (elements, frame, elementsMap) => omitGroupsContainingFrameLikes(
11867
+ getElementsWithinSelection(elements, frame, elementsMap, false)
11868
+ ).filter(
11869
+ (element) => !isFrameLikeElement(element) && !element.frameId || element.frameId === frame.id
11870
+ );
11871
+ var isElementContainingFrame = (element, frame, elementsMap) => {
11872
+ return boundsContainBounds(
11873
+ getElementBounds(element, elementsMap),
11874
+ getElementBounds(frame, elementsMap)
11875
+ );
11876
+ };
11877
+ var elementsAreInFrameBounds = (elements, frame, elementsMap) => {
11878
+ const [frameX1, frameY1, frameX2, frameY2] = getElementAbsoluteCoords(
11879
+ frame,
11880
+ elementsMap
11881
+ );
11882
+ const [elementX1, elementY1, elementX2, elementY2] = getCommonBounds(elements);
11883
+ return frameX1 <= elementX1 && frameY1 <= elementY1 && frameX2 >= elementX2 && frameY2 >= elementY2;
11884
+ };
11885
+ var elementOverlapsWithFrame = (element, frame, elementsMap) => {
11886
+ return elementsAreInFrameBounds([element], frame, elementsMap) || isElementIntersectingFrame(element, frame, elementsMap) || isElementContainingFrame(element, frame, elementsMap);
11887
+ };
11888
+ var isCursorInFrame = (cursorCoords, frame, elementsMap) => {
11889
+ const [fx1, fy1, fx2, fy2] = getElementAbsoluteCoords(frame, elementsMap);
11890
+ return isPointWithinBounds(
11891
+ pointFrom(fx1, fy1),
11892
+ pointFrom(cursorCoords.x, cursorCoords.y),
11893
+ pointFrom(fx2, fy2)
11894
+ );
11895
+ };
11896
+ var groupByFrameLikes = (elements) => {
11897
+ const frameElementsMap = /* @__PURE__ */ new Map();
11898
+ for (const element of elements) {
11899
+ const frameId = isFrameLikeElement(element) ? element.id : element.frameId;
11900
+ if (frameId && !frameElementsMap.has(frameId)) {
11901
+ frameElementsMap.set(frameId, getFrameChildren(elements, frameId));
11902
+ }
11903
+ }
11904
+ return frameElementsMap;
11905
+ };
11906
+ var getFrameChildren = (allElements, frameId) => {
11907
+ const frameChildren = [];
11908
+ for (const element of allElements.values()) {
11909
+ if (element.frameId === frameId) {
11910
+ frameChildren.push(element);
11911
+ }
11912
+ }
11913
+ return frameChildren;
11914
+ };
11915
+ var getFrameLikeElements = (allElements) => {
11916
+ return allElements.filter(
11917
+ (element) => isFrameLikeElement(element)
11918
+ );
11919
+ };
11920
+ var getRootElements = (allElements) => {
11921
+ const frameElements = arrayToMap(getFrameLikeElements(allElements));
11922
+ return allElements.filter(
11923
+ (element) => frameElements.has(element.id) || !element.frameId || !frameElements.has(element.frameId)
11924
+ );
11925
+ };
11926
+ var getElementsInResizingFrame = (allElements, frame, appState, elementsMap) => {
11927
+ const prevElementsInFrame = getFrameChildren(allElements, frame.id);
11928
+ const nextElementsInFrame = new Set(prevElementsInFrame);
11929
+ const elementsCompletelyInFrame = /* @__PURE__ */ new Set([
11930
+ ...getElementsCompletelyInFrame(allElements, frame, elementsMap),
11931
+ ...prevElementsInFrame.filter(
11932
+ (element) => isElementContainingFrame(element, frame, elementsMap)
11933
+ )
11934
+ ]);
11935
+ const elementsNotCompletelyInFrame = prevElementsInFrame.filter(
11936
+ (element) => !elementsCompletelyInFrame.has(element)
11937
+ );
11938
+ const groupsToKeep = new Set(
11939
+ Array.from(elementsCompletelyInFrame).flatMap(
11940
+ (element) => element.groupIds
11941
+ )
11942
+ );
11943
+ for (const element of elementsNotCompletelyInFrame) {
11944
+ if (!isElementIntersectingFrame(element, frame, elementsMap)) {
11945
+ if (element.groupIds.length === 0) {
11946
+ nextElementsInFrame.delete(element);
11947
+ }
11948
+ } else if (element.groupIds.length > 0) {
11949
+ for (const id of element.groupIds) {
11950
+ groupsToKeep.add(id);
11951
+ }
11952
+ }
11953
+ }
11954
+ for (const element of elementsNotCompletelyInFrame) {
11955
+ if (element.groupIds.length > 0) {
11956
+ let shouldRemoveElement = true;
11957
+ for (const id of element.groupIds) {
11958
+ if (groupsToKeep.has(id)) {
11959
+ shouldRemoveElement = false;
11960
+ }
11961
+ }
11962
+ if (shouldRemoveElement) {
11963
+ nextElementsInFrame.delete(element);
11964
+ }
11965
+ }
11966
+ }
11967
+ const individualElementsCompletelyInFrame = Array.from(
11968
+ elementsCompletelyInFrame
11969
+ ).filter((element) => element.groupIds.length === 0);
11970
+ for (const element of individualElementsCompletelyInFrame) {
11971
+ nextElementsInFrame.add(element);
11972
+ }
11973
+ const newGroupElementsCompletelyInFrame = Array.from(
11974
+ elementsCompletelyInFrame
11975
+ ).filter((element) => element.groupIds.length > 0);
11976
+ const groupIds = selectGroupsFromGivenElements(
11977
+ newGroupElementsCompletelyInFrame,
11978
+ appState
11979
+ );
11980
+ for (const [id, isSelected] of Object.entries(groupIds)) {
11981
+ if (isSelected) {
11982
+ const elementsInGroup = getElementsInGroup(allElements, id);
11983
+ if (elementsAreInFrameBounds(elementsInGroup, frame, elementsMap)) {
11984
+ for (const element of elementsInGroup) {
11985
+ nextElementsInFrame.add(element);
11986
+ }
11987
+ }
11988
+ }
11989
+ }
11990
+ return [...nextElementsInFrame].filter((element) => {
11991
+ return !(isTextElement(element) && element.containerId);
11992
+ });
11993
+ };
11994
+ var getElementsInNewFrame = (elements, frame, elementsMap) => {
11995
+ return omitPartialGroups(
11996
+ omitGroupsContainingFrameLikes(
11997
+ elements,
11998
+ getElementsCompletelyInFrame(elements, frame, elementsMap)
11999
+ ),
12000
+ frame,
12001
+ elementsMap
12002
+ );
12003
+ };
12004
+ var omitPartialGroups = (elements, frame, allElementsMap) => {
12005
+ const elementsToReturn = [];
12006
+ const checkedGroups = /* @__PURE__ */ new Map();
12007
+ for (const element of elements) {
12008
+ let shouldOmit = false;
12009
+ if (element.groupIds.length > 0) {
12010
+ if (element.groupIds.some((gid) => checkedGroups.get(gid))) {
12011
+ shouldOmit = true;
12012
+ } else {
12013
+ const allElementsInGroup = new Set(
12014
+ element.groupIds.flatMap(
12015
+ (gid) => getElementsInGroup(allElementsMap, gid)
12016
+ )
12017
+ );
12018
+ shouldOmit = !elementsAreInFrameBounds(
12019
+ Array.from(allElementsInGroup),
12020
+ frame,
12021
+ allElementsMap
12022
+ );
12023
+ }
12024
+ element.groupIds.forEach((gid) => {
12025
+ checkedGroups.set(gid, shouldOmit);
12026
+ });
12027
+ }
12028
+ if (!shouldOmit) {
12029
+ elementsToReturn.push(element);
12030
+ }
12031
+ }
12032
+ return elementsToReturn;
12033
+ };
12034
+ var getContainingFrame = (element, elementsMap) => {
12035
+ if (!element.frameId) {
12036
+ return null;
12037
+ }
12038
+ return elementsMap.get(element.frameId) || null;
12039
+ };
12040
+ var filterElementsEligibleAsFrameChildren = (elements, frame) => {
12041
+ const otherFrames = /* @__PURE__ */ new Set();
12042
+ const elementsMap = arrayToMap(elements);
12043
+ elements = omitGroupsContainingFrameLikes(elements);
12044
+ for (const element of elements) {
12045
+ if (isFrameLikeElement(element) && element.id !== frame.id) {
12046
+ otherFrames.add(element.id);
12047
+ }
12048
+ }
12049
+ const processedGroups = /* @__PURE__ */ new Set();
12050
+ const eligibleElements = [];
12051
+ for (const element of elements) {
12052
+ if (isFrameLikeElement(element) || element.frameId && otherFrames.has(element.frameId)) {
12053
+ continue;
12054
+ }
12055
+ if (element.groupIds.length) {
12056
+ const shallowestGroupId = element.groupIds.at(-1);
12057
+ if (!processedGroups.has(shallowestGroupId)) {
12058
+ processedGroups.add(shallowestGroupId);
12059
+ const groupElements = getElementsInGroup(elements, shallowestGroupId);
12060
+ if (groupElements.some(
12061
+ (el) => elementOverlapsWithFrame(el, frame, elementsMap)
12062
+ )) {
12063
+ for (const child of groupElements) {
12064
+ eligibleElements.push(child);
12065
+ }
12066
+ }
12067
+ }
12068
+ } else {
12069
+ const overlaps = elementOverlapsWithFrame(element, frame, elementsMap);
12070
+ if (overlaps) {
12071
+ eligibleElements.push(element);
12072
+ }
12073
+ }
12074
+ }
12075
+ return eligibleElements;
12076
+ };
12077
+ var getCommonFrameId = (elements) => {
12078
+ let commonFrameId;
12079
+ for (const element of elements) {
12080
+ if (isFrameLikeElement(element) || !element.frameId) {
12081
+ return null;
12082
+ }
12083
+ if (commonFrameId === void 0) {
12084
+ commonFrameId = element.frameId;
12085
+ } else if (commonFrameId !== element.frameId) {
12086
+ return null;
12087
+ }
12088
+ }
12089
+ return commonFrameId ?? null;
12090
+ };
12091
+ var getFrameChildrenInsertionIndex = (elements, frameId) => {
12092
+ for (let index = elements.length - 1; index >= 0; index--) {
12093
+ const element = elements[index];
12094
+ if (element.id === frameId) {
12095
+ return index;
12096
+ } else if (element.frameId === frameId) {
12097
+ return index + 1;
12098
+ }
12099
+ }
12100
+ return null;
12101
+ };
12102
+ var addElementsToFrame = (allElements, elementsToAdd, frame) => {
12103
+ const elementsMap = arrayToMap(allElements);
12104
+ const commonFrameId = getCommonFrameId(elementsToAdd);
12105
+ const finalElementsToAdd = /* @__PURE__ */ new Set();
12106
+ const otherFrames = /* @__PURE__ */ new Set();
12107
+ for (const element of elementsToAdd) {
12108
+ if (isFrameLikeElement(element) && element.id !== frame.id) {
12109
+ otherFrames.add(element.id);
12110
+ }
12111
+ }
12112
+ for (const element of omitGroupsContainingFrameLikes(
12113
+ allElements,
12114
+ elementsToAdd
12115
+ )) {
12116
+ if (isFrameLikeElement(element) || element.frameId && otherFrames.has(element.frameId)) {
12117
+ continue;
12118
+ }
12119
+ if (element.frameId && element.frameId !== frame.id) {
12120
+ continue;
12121
+ }
12122
+ finalElementsToAdd.add(element);
12123
+ const boundTextElement = getBoundTextElement(element, elementsMap);
12124
+ if (boundTextElement && !finalElementsToAdd.has(boundTextElement)) {
12125
+ finalElementsToAdd.add(boundTextElement);
12126
+ }
12127
+ }
12128
+ for (const element of finalElementsToAdd) {
12129
+ if (element.frameId !== frame.id) {
12130
+ mutateElement(element, elementsMap, {
12131
+ frameId: frame.id
12132
+ });
12133
+ }
12134
+ }
12135
+ if (!finalElementsToAdd.size || // if all elements to add already belong to the frame, then we don't want to
12136
+ // reorder (case: we're dragging element children within the frame)
12137
+ commonFrameId === frame.id) {
12138
+ return allElements;
12139
+ }
12140
+ const otherElements = Array.from(allElements.values()).filter(
12141
+ (element) => !finalElementsToAdd.has(element)
12142
+ );
12143
+ const insertionIndex = getFrameChildrenInsertionIndex(
12144
+ otherElements,
12145
+ frame.id
12146
+ );
12147
+ if (insertionIndex === null) {
12148
+ return allElements;
12149
+ }
12150
+ const reorderedElements = [
12151
+ ...otherElements.slice(0, insertionIndex),
12152
+ ...finalElementsToAdd,
12153
+ ...otherElements.slice(insertionIndex)
12154
+ ];
12155
+ syncMovedIndices(reorderedElements, arrayToMap([...finalElementsToAdd]));
12156
+ return Array.isArray(allElements) ? reorderedElements : new Map(reorderedElements.map((element) => [element.id, element]));
12157
+ };
12158
+ var removeElementsFromFrame = (elementsToRemove, elementsMap) => {
12159
+ const _elementsToRemove = /* @__PURE__ */ new Map();
12160
+ const toRemoveElementsByFrame = /* @__PURE__ */ new Map();
12161
+ for (const element of elementsToRemove) {
12162
+ if (element.frameId) {
12163
+ _elementsToRemove.set(element.id, element);
12164
+ const arr = toRemoveElementsByFrame.get(element.frameId) || [];
12165
+ arr.push(element);
12166
+ const boundTextElement = getBoundTextElement(element, elementsMap);
12167
+ if (boundTextElement) {
12168
+ _elementsToRemove.set(boundTextElement.id, boundTextElement);
12169
+ arr.push(boundTextElement);
12170
+ }
12171
+ toRemoveElementsByFrame.set(element.frameId, arr);
12172
+ }
12173
+ }
12174
+ for (const [, element] of _elementsToRemove) {
12175
+ mutateElement(element, elementsMap, {
12176
+ frameId: null
12177
+ });
12178
+ }
12179
+ };
12180
+ var removeAllElementsFromFrame = (allElements, frame) => {
12181
+ const elementsInFrame = getFrameChildren(allElements, frame.id);
12182
+ removeElementsFromFrame(elementsInFrame, arrayToMap(allElements));
12183
+ return allElements;
12184
+ };
12185
+ var replaceAllElementsInFrame = (allElements, nextElementsInFrame, frame) => {
12186
+ return addElementsToFrame(
12187
+ removeAllElementsFromFrame(allElements, frame),
12188
+ nextElementsInFrame,
12189
+ frame
12190
+ ).slice();
12191
+ };
12192
+ var updateFrameMembershipOfSelectedElements = (allElements, appState, app) => {
12193
+ const selectedElements = app.scene.getSelectedElements({
12194
+ selectedElementIds: appState.selectedElementIds,
12195
+ // supplying elements explicitly in case we're passed non-state elements
12196
+ elements: allElements
12197
+ });
12198
+ const elementsToFilter = new Set(selectedElements);
12199
+ if (appState.editingGroupId) {
12200
+ for (const element of selectedElements) {
12201
+ if (element.groupIds.length === 0) {
12202
+ elementsToFilter.add(element);
12203
+ } else {
12204
+ element.groupIds.flatMap((gid) => getElementsInGroup(allElements, gid)).forEach((element2) => elementsToFilter.add(element2));
12205
+ }
12206
+ }
12207
+ }
12208
+ const elementsToRemove = /* @__PURE__ */ new Set();
12209
+ const elementsMap = arrayToMap(allElements);
12210
+ elementsToFilter.forEach((element) => {
12211
+ if (element.frameId && !isFrameLikeElement(element) && !isElementInFrame(element, elementsMap, appState)) {
12212
+ elementsToRemove.add(element);
12213
+ }
12214
+ });
12215
+ if (elementsToRemove.size > 0) {
12216
+ removeElementsFromFrame(elementsToRemove, elementsMap);
12217
+ }
12218
+ return allElements;
12219
+ };
12220
+ var omitGroupsContainingFrameLikes = (allElements, selectedElements) => {
12221
+ const uniqueGroupIds = /* @__PURE__ */ new Set();
12222
+ const elements = selectedElements || allElements;
12223
+ for (const el of elements.values()) {
12224
+ const topMostGroupId = el.groupIds[el.groupIds.length - 1];
12225
+ if (topMostGroupId) {
12226
+ uniqueGroupIds.add(topMostGroupId);
12227
+ }
12228
+ }
12229
+ const rejectedGroupIds = /* @__PURE__ */ new Set();
12230
+ for (const groupId of uniqueGroupIds) {
12231
+ if (getElementsInGroup(allElements, groupId).some(
12232
+ (el) => isFrameLikeElement(el)
12233
+ )) {
12234
+ rejectedGroupIds.add(groupId);
12235
+ }
12236
+ }
12237
+ const ret = [];
12238
+ for (const element of elements.values()) {
12239
+ if (!rejectedGroupIds.has(element.groupIds[element.groupIds.length - 1])) {
12240
+ ret.push(element);
12241
+ }
12242
+ }
12243
+ return ret;
12244
+ };
12245
+ var getTargetFrame = (element, elementsMap, appState) => {
12246
+ const _element = isTextElement(element) ? getContainerElement(element, elementsMap) || element : element;
12247
+ if (_element.frameId && appState.selectedElementIds[_element.id] && appState.selectedElementIds[_element.frameId]) {
12248
+ return getContainingFrame(_element, elementsMap);
12249
+ }
12250
+ return appState.selectedElementIds[_element.id] && appState.selectedElementsAreBeingDragged ? appState.frameToHighlight : getContainingFrame(_element, elementsMap);
12251
+ };
12252
+ var isElementInFrame = (element, allElementsMap, appState, opts) => {
12253
+ const frame = opts?.targetFrame ?? getTargetFrame(element, allElementsMap, appState);
12254
+ if (!frame) {
12255
+ return false;
12256
+ }
12257
+ const _element = isTextElement(element) ? getContainerElement(element, allElementsMap) || element : element;
12258
+ const setGroupsInFrame = (isInFrame) => {
12259
+ if (opts?.checkedGroups) {
12260
+ _element.groupIds.forEach((groupId) => {
12261
+ opts.checkedGroups?.set(groupId, isInFrame);
12262
+ });
12263
+ }
12264
+ };
12265
+ if (
12266
+ // if the element is not selected, or it is selected but not being dragged,
12267
+ // frame membership won't update, so return true
12268
+ !appState.selectedElementIds[_element.id] || !appState.selectedElementsAreBeingDragged || // if both frame and element are selected, won't update membership, so return true
12269
+ appState.selectedElementIds[_element.id] && appState.selectedElementIds[frame.id]
12270
+ ) {
12271
+ return true;
12272
+ }
12273
+ if (_element.groupIds.length === 0) {
12274
+ return elementOverlapsWithFrame(_element, frame, allElementsMap);
12275
+ }
12276
+ for (const gid of _element.groupIds) {
12277
+ if (opts?.checkedGroups?.has(gid)) {
12278
+ return opts.checkedGroups.get(gid);
12279
+ }
12280
+ }
12281
+ const allElementsInGroup = new Set(
12282
+ _element.groupIds.filter((gid) => {
12283
+ if (opts?.checkedGroups) {
12284
+ return !opts.checkedGroups.has(gid);
12285
+ }
12286
+ return true;
12287
+ }).flatMap((gid) => getElementsInGroup(allElementsMap, gid))
12288
+ );
12289
+ if (appState.editingGroupId && appState.selectedElementsAreBeingDragged) {
12290
+ const selectedElements = new Set(
12291
+ getSelectedElements(allElementsMap, appState)
12292
+ );
12293
+ const editingGroupOverlapsFrame = appState.frameToHighlight !== null;
12294
+ if (editingGroupOverlapsFrame) {
12295
+ return true;
12296
+ }
12297
+ selectedElements.forEach((selectedElement) => {
12298
+ allElementsInGroup.delete(selectedElement);
12299
+ });
12300
+ }
12301
+ for (const elementInGroup of allElementsInGroup) {
12302
+ if (isFrameLikeElement(elementInGroup)) {
12303
+ setGroupsInFrame(false);
12304
+ return false;
12305
+ }
12306
+ }
12307
+ for (const elementInGroup of allElementsInGroup) {
12308
+ if (elementOverlapsWithFrame(elementInGroup, frame, allElementsMap)) {
12309
+ setGroupsInFrame(true);
12310
+ return true;
12311
+ }
12312
+ }
12313
+ return false;
11672
12314
  };
11673
- var getElementsOverlappingFrame = (elements, frame) => {
11674
- return elementsOverlappingBBox({
11675
- elements,
11676
- bounds: frame,
11677
- type: "overlap"
11678
- }).filter((el) => !el.frameId || el.frameId === frame.id);
12315
+ var shouldApplyFrameClip = (element, frame, appState, elementsMap, checkedGroups) => {
12316
+ if (!appState.frameRendering || !appState.frameRendering.clip) {
12317
+ return false;
12318
+ }
12319
+ const shouldClipElementItself = isElementIntersectingFrame(element, frame, elementsMap) || isElementContainingFrame(element, frame, elementsMap);
12320
+ if (shouldClipElementItself) {
12321
+ for (const groupId of element.groupIds) {
12322
+ checkedGroups?.set(groupId, true);
12323
+ }
12324
+ return true;
12325
+ }
12326
+ if (!shouldClipElementItself && element.groupIds.length > 0 && !elementsAreInFrameBounds([element], frame, elementsMap)) {
12327
+ let shouldClip = false;
12328
+ if (!appState.selectedElementsAreBeingDragged) {
12329
+ shouldClip = element.frameId === frame.id;
12330
+ for (const groupId of element.groupIds) {
12331
+ checkedGroups?.set(groupId, shouldClip);
12332
+ }
12333
+ } else {
12334
+ shouldClip = isElementInFrame(element, elementsMap, appState, {
12335
+ targetFrame: frame,
12336
+ checkedGroups
12337
+ });
12338
+ }
12339
+ for (const groupId of element.groupIds) {
12340
+ checkedGroups?.set(groupId, shouldClip);
12341
+ }
12342
+ return shouldClip;
12343
+ }
12344
+ return false;
12345
+ };
12346
+ var DEFAULT_FRAME_NAME = "Frame";
12347
+ var DEFAULT_AI_FRAME_NAME = "AI Frame";
12348
+ var getDefaultFrameName = (element) => {
12349
+ return isFrameElement(element) ? DEFAULT_FRAME_NAME : DEFAULT_AI_FRAME_NAME;
12350
+ };
12351
+ var getFrameLikeTitle = (element) => {
12352
+ return element.name === null ? getDefaultFrameName(element) : element.name;
12353
+ };
12354
+ var getElementsOverlappingFrame = (elements, frame, elementsMap) => {
12355
+ return elements.filter(
12356
+ (el) => (
12357
+ // exclude elements which are overlapping, but are in a different frame,
12358
+ // and thus invisible in target frame
12359
+ (!el.frameId || el.frameId === frame.id) && doBoundsIntersect(
12360
+ getElementBounds(el, elementsMap),
12361
+ getElementBounds(frame, elementsMap)
12362
+ )
12363
+ )
12364
+ );
11679
12365
  };
11680
12366
  var frameAndChildrenSelectedTogether = (selectedElements) => {
11681
12367
  const selectedElementsMap = arrayToMap(selectedElements);
@@ -12367,15 +13053,6 @@ function getFreedrawOutlineAsSegments(element, points, elementsMap) {
12367
13053
  );
12368
13054
  }
12369
13055
 
12370
- // ../element/src/comparisons.ts
12371
- var hasBackground = (type) => type === "rectangle" || type === "iframe" || type === "embeddable" || type === "ellipse" || type === "diamond" || type === "line" || type === "freedraw";
12372
- var hasStrokeColor = (type) => type === "rectangle" || type === "ellipse" || type === "diamond" || type === "freedraw" || type === "arrow" || type === "line" || type === "text" || type === "embeddable";
12373
- var hasStrokeWidth = (type) => type === "rectangle" || type === "iframe" || type === "embeddable" || type === "ellipse" || type === "diamond" || type === "freedraw" || type === "arrow" || type === "line";
12374
- var hasStrokeStyle = (type) => type === "rectangle" || type === "iframe" || type === "embeddable" || type === "ellipse" || type === "diamond" || type === "arrow" || type === "line";
12375
- var canChangeRoundness = (type) => type === "rectangle" || type === "iframe" || type === "embeddable" || type === "line" || type === "diamond" || type === "image";
12376
- var toolIsArrow = (type) => type === "arrow";
12377
- var canHaveArrowheads = (type) => type === "arrow";
12378
-
12379
13056
  // ../element/src/shape.ts
12380
13057
  var _ShapeCache = class _ShapeCache {
12381
13058
  };
@@ -12552,7 +13229,6 @@ var generateArrowheadOutlineCircle = (generator, options, strokeColor, arrowhead
12552
13229
  return [generator.circle(x, y, diameter * diameterScale, circleOptions)];
12553
13230
  };
12554
13231
  var getArrowheadShapes = (element, shape, position, arrowhead, generator, options, canvasBackgroundColor, isDarkMode) => {
12555
- canvasBackgroundColor = canvasBackgroundColor || "transparent";
12556
13232
  if (arrowhead === null) {
12557
13233
  return [];
12558
13234
  }
@@ -12729,7 +13405,7 @@ var getArrowheadShapes = (element, shape, position, arrowhead, generator, option
12729
13405
  }
12730
13406
  }
12731
13407
  };
12732
- var generateLinearCollisionShape = (element) => {
13408
+ var generateLinearCollisionShape = (element, elementsMap) => {
12733
13409
  const generator = new RoughGenerator();
12734
13410
  const options = {
12735
13411
  seed: element.seed,
@@ -12738,20 +13414,7 @@ var generateLinearCollisionShape = (element) => {
12738
13414
  roughness: 0,
12739
13415
  preserveVertices: true
12740
13416
  };
12741
- const center = getCenterForBounds(
12742
- // Need a non-rotated center point
12743
- element.points.reduce(
12744
- (acc, point) => {
12745
- return [
12746
- Math.min(element.x + point[0], acc[0]),
12747
- Math.min(element.y + point[1], acc[1]),
12748
- Math.max(element.x + point[0], acc[2]),
12749
- Math.max(element.y + point[1], acc[3])
12750
- ];
12751
- },
12752
- [Infinity, Infinity, -Infinity, -Infinity]
12753
- )
12754
- );
13417
+ const center = elementCenterPoint(element, elementsMap);
12755
13418
  switch (element.type) {
12756
13419
  case "line":
12757
13420
  case "arrow": {
@@ -13633,7 +14296,7 @@ var getMinMaxXYFromCurvePathOps = (ops, transformXY) => {
13633
14296
  );
13634
14297
  return [minX, minY, maxX, maxY];
13635
14298
  };
13636
- var getBoundsFromPoints = (points) => {
14299
+ var getBoundsFromPoints = (points, padding = 0) => {
13637
14300
  let minX = Infinity;
13638
14301
  let minY = Infinity;
13639
14302
  let maxX = -Infinity;
@@ -13644,7 +14307,7 @@ var getBoundsFromPoints = (points) => {
13644
14307
  maxX = Math.max(maxX, x);
13645
14308
  maxY = Math.max(maxY, y);
13646
14309
  }
13647
- return [minX, minY, maxX, maxY];
14310
+ return [minX - padding, minY - padding, maxX + padding, maxY + padding];
13648
14311
  };
13649
14312
  var getFreeDrawElementAbsoluteCoords = (element) => {
13650
14313
  const [minX, minY, maxX, maxY] = getBoundsFromPoints(element.points);
@@ -14028,6 +14691,7 @@ var aabbForElement = (element, elementsMap, offset) => {
14028
14691
  return bounds;
14029
14692
  };
14030
14693
  var pointInsideBounds = (p, bounds) => p[0] > bounds[0] && p[0] < bounds[2] && p[1] > bounds[1] && p[1] < bounds[3];
14694
+ var pointInsideBoundsInclusive = (p, bounds) => p[0] >= bounds[0] && p[0] <= bounds[2] && p[1] >= bounds[1] && p[1] <= bounds[3];
14031
14695
  var doBoundsIntersect = (bounds1, bounds2) => {
14032
14696
  if (bounds1 == null || bounds2 == null) {
14033
14697
  return false;
@@ -14036,8 +14700,14 @@ var doBoundsIntersect = (bounds1, bounds2) => {
14036
14700
  const [minX2, minY2, maxX2, maxY2] = bounds2;
14037
14701
  return minX1 < maxX2 && maxX1 > minX2 && minY1 < maxY2 && maxY1 > minY2;
14038
14702
  };
14703
+ var boundsContainBounds = (outerBounds, innerBounds) => [
14704
+ pointFrom(innerBounds[0], innerBounds[1]),
14705
+ pointFrom(innerBounds[0], innerBounds[3]),
14706
+ pointFrom(innerBounds[2], innerBounds[1]),
14707
+ pointFrom(innerBounds[2], innerBounds[3])
14708
+ ].every((point) => pointInsideBoundsInclusive(point, outerBounds));
14039
14709
  var elementCenterPoint = (element, elementsMap, xOffset = 0, yOffset = 0) => {
14040
- if (isLinearElement(element)) {
14710
+ if (isLinearElement(element) || isFreeDrawElement(element)) {
14041
14711
  const [x1, y1, x2, y2] = getElementAbsoluteCoords(element, elementsMap);
14042
14712
  const [x3, y3] = pointFrom((x1 + x2) / 2, (y1 + y2) / 2);
14043
14713
  return pointFrom(x3 + xOffset, y3 + yOffset);
@@ -14218,78 +14888,60 @@ var calculateTranslation = (group, selectionBoundingBox, { axis, position }) =>
14218
14888
  };
14219
14889
 
14220
14890
  // ../element/src/sortElements.ts
14221
- var normalizeGroupElementOrder = (elements) => {
14222
- const origElements = elements.slice();
14223
- const sortedElements = /* @__PURE__ */ new Set();
14224
- const orderInnerGroups = (elements2) => {
14225
- const firstGroupSig = elements2[0]?.groupIds?.join("");
14226
- const aGroup = [elements2[0]];
14227
- const bGroup = [];
14228
- for (const element of elements2.slice(1)) {
14229
- if (element.groupIds?.join("") === firstGroupSig) {
14230
- aGroup.push(element);
14231
- } else {
14232
- bGroup.push(element);
14233
- }
14234
- }
14235
- return bGroup.length ? [...aGroup, ...orderInnerGroups(bGroup)] : aGroup;
14891
+ var defragmentGroups = (elements) => {
14892
+ const groupIdAtLevel = (element, level) => {
14893
+ return element.groupIds[element.groupIds.length - level - 1];
14236
14894
  };
14237
- const groupHandledElements = /* @__PURE__ */ new Map();
14238
- origElements.forEach((element, idx) => {
14239
- if (groupHandledElements.has(element.id)) {
14240
- return;
14241
- }
14242
- if (element.groupIds?.length) {
14243
- const topGroup = element.groupIds[element.groupIds.length - 1];
14244
- const groupElements = origElements.slice(idx).filter((element2) => {
14245
- const ret = element2?.groupIds?.some((id) => id === topGroup);
14246
- if (ret) {
14247
- groupHandledElements.set(element2.id, true);
14248
- }
14249
- return ret;
14250
- });
14251
- for (const elem of orderInnerGroups(groupElements)) {
14252
- sortedElements.add(elem);
14895
+ const orderLevel = (levelElements, level) => {
14896
+ const buckets = /* @__PURE__ */ new Map();
14897
+ const slots = [];
14898
+ for (const element of levelElements) {
14899
+ const groupId = groupIdAtLevel(element, level);
14900
+ if (groupId === void 0) {
14901
+ slots.push(element);
14902
+ continue;
14253
14903
  }
14254
- } else {
14255
- sortedElements.add(element);
14904
+ let bucket = buckets.get(groupId);
14905
+ if (!bucket) {
14906
+ bucket = [];
14907
+ buckets.set(groupId, bucket);
14908
+ slots.push(groupId);
14909
+ }
14910
+ bucket.push(element);
14256
14911
  }
14257
- });
14258
- if (sortedElements.size !== elements.length) {
14259
- console.error("normalizeGroupElementOrder: lost some elements... bailing!");
14912
+ return slots.flatMap(
14913
+ (slot) => typeof slot === "string" ? orderLevel(buckets.get(slot), level + 1) : [slot]
14914
+ );
14915
+ };
14916
+ const sortedElements = orderLevel(elements, 0);
14917
+ if (sortedElements.length !== elements.length) {
14918
+ console.error("defragmentGroups: lost some elements... bailing!");
14260
14919
  return elements;
14261
14920
  }
14262
- return [...sortedElements];
14921
+ return sortedElements;
14263
14922
  };
14264
14923
  var normalizeBoundElementsOrder = (elements) => {
14265
- const elementsMap = arrayToMapWithIndex(elements);
14266
- const origElements = elements.slice();
14924
+ const elementsMap = arrayToMap(elements);
14267
14925
  const sortedElements = /* @__PURE__ */ new Set();
14268
- origElements.forEach((element, idx) => {
14269
- if (!element) {
14270
- return;
14926
+ for (const element of elements) {
14927
+ if (sortedElements.has(element)) {
14928
+ continue;
14271
14929
  }
14272
14930
  if (element.boundElements?.length) {
14273
14931
  sortedElements.add(element);
14274
- origElements[idx] = null;
14275
- element.boundElements.forEach((boundElement) => {
14932
+ for (const boundElement of element.boundElements) {
14276
14933
  const child = elementsMap.get(boundElement.id);
14277
14934
  if (child && boundElement.type === "text") {
14278
- sortedElements.add(child[0]);
14279
- origElements[child[1]] = null;
14935
+ sortedElements.add(child);
14280
14936
  }
14281
- });
14282
- } else if (element.type === "text" && element.containerId) {
14283
- const parent = elementsMap.get(element.containerId);
14284
- if (!parent?.[0].boundElements?.find((x) => x.id === element.id)) {
14285
- sortedElements.add(element);
14286
- origElements[idx] = null;
14287
14937
  }
14288
- } else {
14289
- sortedElements.add(element);
14290
- origElements[idx] = null;
14938
+ continue;
14291
14939
  }
14292
- });
14940
+ if (element.type === "text" && element.containerId && elementsMap.get(element.containerId)?.boundElements?.some((el) => el.id === element.id)) {
14941
+ continue;
14942
+ }
14943
+ sortedElements.add(element);
14944
+ }
14293
14945
  if (sortedElements.size !== elements.length) {
14294
14946
  console.error(
14295
14947
  "normalizeBoundElementsOrder: lost some elements... bailing!"
@@ -14299,7 +14951,7 @@ var normalizeBoundElementsOrder = (elements) => {
14299
14951
  return [...sortedElements];
14300
14952
  };
14301
14953
  var normalizeElementOrder = (elements) => {
14302
- return normalizeBoundElementsOrder(normalizeGroupElementOrder(elements));
14954
+ return normalizeBoundElementsOrder(defragmentGroups(elements));
14303
14955
  };
14304
14956
 
14305
14957
  // ../element/src/duplicate.ts
@@ -14341,6 +14993,7 @@ var duplicateElements = (opts) => {
14341
14993
  const duplicateElementsMap = /* @__PURE__ */ new Map();
14342
14994
  const elementsMap = arrayToMap(elements);
14343
14995
  const _idsOfElementsToDuplicate = opts.type === "in-place" ? opts.idsOfElementsToDuplicate : new Map(elements.map((el) => [el.id, el]));
14996
+ const preserveFrameChildrenOrder = opts.type === "everything" && opts.preserveFrameChildrenOrder;
14344
14997
  if (opts.type === "in-place") {
14345
14998
  for (const groupId of Object.keys(opts.appState.selectedGroupIds)) {
14346
14999
  elements.filter((el) => el.groupIds?.includes(groupId)).forEach((el) => _idsOfElementsToDuplicate.set(el.id, el));
@@ -14400,7 +15053,7 @@ var duplicateElements = (opts) => {
14400
15053
  const groupId = getSelectedGroupForElement(appState, element);
14401
15054
  if (groupId) {
14402
15055
  const groupElements = getElementsInGroup(elements, groupId).flatMap(
14403
- (element2) => isFrameLikeElement(element2) ? [...getFrameChildren(elements, element2.id), element2] : [element2]
15056
+ (element2) => isFrameLikeElement(element2) && !preserveFrameChildrenOrder ? [...getFrameChildren(elements, element2.id), element2] : [element2]
14404
15057
  );
14405
15058
  const targetIndex = findLastIndex(elementsWithDuplicates, (el) => {
14406
15059
  return el.groupIds?.includes(groupId);
@@ -14408,11 +15061,18 @@ var duplicateElements = (opts) => {
14408
15061
  insertBeforeOrAfterIndex(targetIndex, copyElements(groupElements));
14409
15062
  continue;
14410
15063
  }
14411
- if (element.frameId && frameIdsToDuplicate.has(element.frameId)) {
15064
+ if (!preserveFrameChildrenOrder && element.frameId && frameIdsToDuplicate.has(element.frameId)) {
14412
15065
  continue;
14413
15066
  }
14414
15067
  if (isFrameLikeElement(element)) {
14415
15068
  const frameId = element.id;
15069
+ if (preserveFrameChildrenOrder) {
15070
+ insertBeforeOrAfterIndex(
15071
+ findLastIndex(elementsWithDuplicates, (el) => el.id === frameId),
15072
+ copyElements(element)
15073
+ );
15074
+ continue;
15075
+ }
14416
15076
  const frameChildren = getFrameChildren(elements, frameId);
14417
15077
  const targetIndex = findLastIndex(elementsWithDuplicates, (el) => {
14418
15078
  return el.frameId === frameId || el.id === frameId;
@@ -14839,602 +15499,359 @@ var StoreDelta = class _StoreDelta {
14839
15499
  /**
14840
15500
  * Create a new instance of `StoreDelta`.
14841
15501
  */
14842
- static create(elements, appState, opts = {
14843
- id: randomId()
14844
- }) {
14845
- return new this(opts.id, elements, appState);
14846
- }
14847
- /**
14848
- * Calculate the delta between the previous and next snapshot.
14849
- */
14850
- static calculate(prevSnapshot, nextSnapshot) {
14851
- const elementsDelta = nextSnapshot.metadata.didElementsChange ? ElementsDelta.calculate(prevSnapshot.elements, nextSnapshot.elements) : ElementsDelta.empty();
14852
- const appStateDelta = nextSnapshot.metadata.didAppStateChange ? AppStateDelta.calculate(prevSnapshot.appState, nextSnapshot.appState) : AppStateDelta.empty();
14853
- return this.create(elementsDelta, appStateDelta);
14854
- }
14855
- /**
14856
- * Restore a store delta instance from a DTO.
14857
- */
14858
- static restore(storeDeltaDTO) {
14859
- const { id, elements, appState } = storeDeltaDTO;
14860
- return new this(
14861
- id,
14862
- ElementsDelta.restore(elements),
14863
- AppStateDelta.restore(appState)
14864
- );
14865
- }
14866
- /**
14867
- * Parse and load the delta from the remote payload.
14868
- */
14869
- static load({
14870
- id,
14871
- elements: { added, removed, updated },
14872
- appState: { delta: appStateDelta }
14873
- }) {
14874
- const elements = ElementsDelta.create(added, removed, updated);
14875
- const appState = AppStateDelta.create(appStateDelta);
14876
- return new this(id, elements, appState);
14877
- }
14878
- /**
14879
- * Squash the passed deltas into the aggregated delta instance.
14880
- */
14881
- static squash(...deltas) {
14882
- const aggregatedDelta = _StoreDelta.empty();
14883
- for (const delta of deltas) {
14884
- aggregatedDelta.elements.squash(delta.elements);
14885
- aggregatedDelta.appState.squash(delta.appState);
14886
- }
14887
- return aggregatedDelta;
14888
- }
14889
- /**
14890
- * Inverse store delta, creates new instance of `StoreDelta`.
14891
- */
14892
- static inverse(delta) {
14893
- return this.create(delta.elements.inverse(), delta.appState.inverse());
14894
- }
14895
- /**
14896
- * Apply the delta to the passed elements and appState, does not modify the snapshot.
14897
- */
14898
- static applyTo(delta, elements, appState, options) {
14899
- const [nextElements, elementsContainVisibleChange] = delta.elements.applyTo(
14900
- elements,
14901
- StoreSnapshot.empty().elements,
14902
- options
14903
- );
14904
- const [nextAppState, appStateContainsVisibleChange] = delta.appState.applyTo(appState, nextElements);
14905
- const appliedVisibleChanges = elementsContainVisibleChange || appStateContainsVisibleChange;
14906
- return [nextElements, nextAppState, appliedVisibleChanges];
14907
- }
14908
- /**
14909
- * Apply latest (remote) changes to the delta, creates new instance of `StoreDelta`.
14910
- */
14911
- static applyLatestChanges(delta, prevElements, nextElements, modifierOptions) {
14912
- return this.create(
14913
- delta.elements.applyLatestChanges(
14914
- prevElements,
14915
- nextElements,
14916
- modifierOptions
14917
- ),
14918
- delta.appState,
14919
- {
14920
- id: delta.id
14921
- }
14922
- );
14923
- }
14924
- static empty() {
14925
- return _StoreDelta.create(ElementsDelta.empty(), AppStateDelta.empty());
14926
- }
14927
- isEmpty() {
14928
- return this.elements.isEmpty() && this.appState.isEmpty();
14929
- }
14930
- };
14931
- var StoreSnapshot = class _StoreSnapshot {
14932
- constructor(elements, appState, metadata = {
14933
- didElementsChange: false,
14934
- didAppStateChange: false,
14935
- isEmpty: false
14936
- }) {
14937
- this.elements = elements;
14938
- this.appState = appState;
14939
- this.metadata = metadata;
14940
- __publicField(this, "_lastChangedElementsHash", 0);
14941
- __publicField(this, "_lastChangedAppStateHash", 0);
14942
- }
14943
- static create(elements, appState, metadata = {
14944
- didElementsChange: false,
14945
- didAppStateChange: false
14946
- }) {
14947
- return new _StoreSnapshot(
14948
- elements,
14949
- isObservedAppState(appState) ? appState : getObservedAppState(appState),
14950
- metadata
14951
- );
14952
- }
14953
- static empty() {
14954
- return new _StoreSnapshot(
14955
- /* @__PURE__ */ new Map(),
14956
- getDefaultObservedAppState(),
14957
- {
14958
- didElementsChange: false,
14959
- didAppStateChange: false,
14960
- isEmpty: true
14961
- }
14962
- );
14963
- }
14964
- getChangedElements(prevSnapshot) {
14965
- const changedElements = {};
14966
- for (const prevElement of toIterable(prevSnapshot.elements)) {
14967
- const nextElement = this.elements.get(prevElement.id);
14968
- if (!nextElement) {
14969
- changedElements[prevElement.id] = newElementWith(prevElement, {
14970
- isDeleted: true
14971
- });
14972
- }
14973
- }
14974
- for (const nextElement of toIterable(this.elements)) {
14975
- if (prevSnapshot.elements.get(nextElement.id) !== nextElement) {
14976
- changedElements[nextElement.id] = nextElement;
14977
- }
14978
- }
14979
- return changedElements;
14980
- }
14981
- getChangedAppState(prevSnapshot) {
14982
- return Delta.getRightDifferences(
14983
- prevSnapshot.appState,
14984
- this.appState
14985
- ).reduce(
14986
- (acc, key) => Object.assign(acc, {
14987
- [key]: this.appState[key]
14988
- }),
14989
- {}
14990
- );
14991
- }
14992
- isEmpty() {
14993
- return this.metadata.isEmpty;
15502
+ static create(elements, appState, opts = {
15503
+ id: randomId()
15504
+ }) {
15505
+ return new this(opts.id, elements, appState);
14994
15506
  }
14995
15507
  /**
14996
- * Apply the change and return a new snapshot instance.
15508
+ * Calculate the delta between the previous and next snapshot.
14997
15509
  */
14998
- applyChange(change) {
14999
- const nextElements = new Map(this.elements);
15000
- for (const [id, changedElement] of Object.entries(change.elements)) {
15001
- nextElements.set(id, changedElement);
15002
- }
15003
- const nextAppState = getObservedAppState({
15004
- ...this.appState,
15005
- ...change.appState
15006
- });
15007
- return _StoreSnapshot.create(nextElements, nextAppState, {
15008
- // by default we assume that change is different from what we have in the snapshot
15009
- // so that we trigger the delta calculation and if it isn't different, delta will be empty
15010
- didElementsChange: Object.keys(change.elements).length > 0,
15011
- didAppStateChange: Object.keys(change.appState).length > 0
15012
- });
15510
+ static calculate(prevSnapshot, nextSnapshot) {
15511
+ const elementsDelta = nextSnapshot.metadata.didElementsChange ? ElementsDelta.calculate(prevSnapshot.elements, nextSnapshot.elements) : ElementsDelta.empty();
15512
+ const appStateDelta = nextSnapshot.metadata.didAppStateChange ? AppStateDelta.calculate(prevSnapshot.appState, nextSnapshot.appState) : AppStateDelta.empty();
15513
+ return this.create(elementsDelta, appStateDelta);
15013
15514
  }
15014
15515
  /**
15015
- * Efficiently clone the existing snapshot, only if we detected changes.
15016
- *
15017
- * @returns same instance if there are no changes detected, new instance otherwise.
15516
+ * Restore a store delta instance from a DTO.
15018
15517
  */
15019
- maybeClone(action, elements, appState) {
15020
- const options = {
15021
- shouldCompareHashes: false
15022
- };
15023
- if (action === CaptureUpdateAction.EVENTUALLY) {
15024
- options.shouldCompareHashes = true;
15025
- }
15026
- const nextElementsSnapshot = this.maybeCreateElementsSnapshot(
15027
- elements,
15028
- options
15029
- );
15030
- const nextAppStateSnapshot = this.maybeCreateAppStateSnapshot(
15031
- appState,
15032
- options
15033
- );
15034
- let didElementsChange = false;
15035
- let didAppStateChange = false;
15036
- if (this.elements !== nextElementsSnapshot) {
15037
- didElementsChange = true;
15038
- }
15039
- if (this.appState !== nextAppStateSnapshot) {
15040
- didAppStateChange = true;
15041
- }
15042
- if (!didElementsChange && !didAppStateChange) {
15043
- return this;
15044
- }
15045
- const snapshot = new _StoreSnapshot(
15046
- nextElementsSnapshot,
15047
- nextAppStateSnapshot,
15048
- {
15049
- didElementsChange,
15050
- didAppStateChange
15051
- }
15518
+ static restore(storeDeltaDTO) {
15519
+ const { id, elements, appState } = storeDeltaDTO;
15520
+ return new this(
15521
+ id,
15522
+ ElementsDelta.restore(elements),
15523
+ AppStateDelta.restore(appState)
15052
15524
  );
15053
- return snapshot;
15054
15525
  }
15055
- maybeCreateAppStateSnapshot(appState, options = {
15056
- shouldCompareHashes: false
15526
+ /**
15527
+ * Parse and load the delta from the remote payload.
15528
+ */
15529
+ static load({
15530
+ id,
15531
+ elements: { added, removed, updated },
15532
+ appState: { delta: appStateDelta }
15057
15533
  }) {
15058
- if (!appState) {
15059
- return this.appState;
15060
- }
15061
- const nextAppStateSnapshot = !isObservedAppState(appState) ? getObservedAppState(appState) : appState;
15062
- const didAppStateChange = this.detectChangedAppState(
15063
- nextAppStateSnapshot,
15064
- options
15065
- );
15066
- if (!didAppStateChange) {
15067
- return this.appState;
15068
- }
15069
- return nextAppStateSnapshot;
15534
+ const elements = ElementsDelta.create(added, removed, updated);
15535
+ const appState = AppStateDelta.create(appStateDelta);
15536
+ return new this(id, elements, appState);
15070
15537
  }
15071
- maybeCreateElementsSnapshot(elements, options = {
15072
- shouldCompareHashes: false
15073
- }) {
15074
- if (!elements) {
15075
- return this.elements;
15076
- }
15077
- const changedElements = this.detectChangedElements(elements, options);
15078
- if (!changedElements?.size) {
15079
- return this.elements;
15538
+ /**
15539
+ * Squash the passed deltas into the aggregated delta instance.
15540
+ */
15541
+ static squash(...deltas) {
15542
+ const aggregatedDelta = _StoreDelta.empty();
15543
+ for (const delta of deltas) {
15544
+ aggregatedDelta.elements.squash(delta.elements);
15545
+ aggregatedDelta.appState.squash(delta.appState);
15080
15546
  }
15081
- const elementsSnapshot = this.createElementsSnapshot(changedElements);
15082
- return elementsSnapshot;
15547
+ return aggregatedDelta;
15083
15548
  }
15084
- detectChangedAppState(nextObservedAppState, options = {
15085
- shouldCompareHashes: false
15086
- }) {
15087
- if (this.appState === nextObservedAppState) {
15088
- return;
15089
- }
15090
- const didAppStateChange = Delta.isRightDifferent(
15091
- this.appState,
15092
- nextObservedAppState
15093
- );
15094
- if (!didAppStateChange) {
15095
- return;
15096
- }
15097
- const changedAppStateHash = hashString(
15098
- JSON.stringify(nextObservedAppState)
15099
- );
15100
- if (options.shouldCompareHashes && this._lastChangedAppStateHash === changedAppStateHash) {
15101
- return;
15102
- }
15103
- this._lastChangedAppStateHash = changedAppStateHash;
15104
- return didAppStateChange;
15549
+ /**
15550
+ * Inverse store delta, creates new instance of `StoreDelta`.
15551
+ */
15552
+ static inverse(delta) {
15553
+ return this.create(delta.elements.inverse(), delta.appState.inverse());
15105
15554
  }
15106
15555
  /**
15107
- * Detect if there are any changed elements.
15556
+ * Apply the delta to the passed elements and appState, does not modify the snapshot.
15108
15557
  */
15109
- detectChangedElements(nextElements, options = {
15110
- shouldCompareHashes: false
15111
- }) {
15112
- if (this.elements === nextElements) {
15113
- return;
15114
- }
15115
- const changedElements = /* @__PURE__ */ new Map();
15116
- for (const prevElement of toIterable(this.elements)) {
15117
- const nextElement = nextElements.get(prevElement.id);
15118
- if (!nextElement) {
15119
- changedElements.set(
15120
- prevElement.id,
15121
- newElementWith(prevElement, { isDeleted: true })
15122
- );
15123
- }
15124
- }
15125
- for (const nextElement of toIterable(nextElements)) {
15126
- const prevElement = this.elements.get(nextElement.id);
15127
- if (!prevElement || // element was added
15128
- prevElement.version < nextElement.version) {
15129
- if (isImageElement(nextElement) && !isInitializedImageElement(nextElement)) {
15130
- continue;
15131
- }
15132
- changedElements.set(nextElement.id, nextElement);
15133
- }
15134
- }
15135
- if (!changedElements.size) {
15136
- return;
15137
- }
15138
- const changedElementsHash = hashElementsVersion(changedElements);
15139
- if (options.shouldCompareHashes && this._lastChangedElementsHash === changedElementsHash) {
15140
- return;
15141
- }
15142
- this._lastChangedElementsHash = changedElementsHash;
15143
- return changedElements;
15558
+ static applyTo(delta, elements, appState, options) {
15559
+ const [nextElements, elementsContainVisibleChange] = delta.elements.applyTo(
15560
+ elements,
15561
+ StoreSnapshot.empty().elements,
15562
+ options
15563
+ );
15564
+ const [nextAppState, appStateContainsVisibleChange] = delta.appState.applyTo(appState, nextElements);
15565
+ const appliedVisibleChanges = elementsContainVisibleChange || appStateContainsVisibleChange;
15566
+ return [nextElements, nextAppState, appliedVisibleChanges];
15144
15567
  }
15145
15568
  /**
15146
- * Perform structural clone, deep cloning only elements that changed.
15569
+ * Apply latest (remote) changes to the delta, creates new instance of `StoreDelta`.
15147
15570
  */
15148
- createElementsSnapshot(changedElements) {
15149
- const clonedElements = /* @__PURE__ */ new Map();
15150
- for (const prevElement of toIterable(this.elements)) {
15151
- clonedElements.set(prevElement.id, prevElement);
15152
- }
15153
- for (const changedElement of toIterable(changedElements)) {
15154
- clonedElements.set(changedElement.id, deepCopyElement(changedElement));
15155
- }
15156
- return clonedElements;
15571
+ static applyLatestChanges(delta, prevElements, nextElements, modifierOptions) {
15572
+ return this.create(
15573
+ delta.elements.applyLatestChanges(
15574
+ prevElements,
15575
+ nextElements,
15576
+ modifierOptions
15577
+ ),
15578
+ delta.appState,
15579
+ {
15580
+ id: delta.id
15581
+ }
15582
+ );
15157
15583
  }
15158
- };
15159
- var hiddenObservedAppStateProp = "__observedAppState";
15160
- var getDefaultObservedAppState = () => {
15161
- return {
15162
- name: null,
15163
- editingGroupId: null,
15164
- viewBackgroundColor: COLOR_PALETTE.white,
15165
- selectedElementIds: {},
15166
- selectedGroupIds: {},
15167
- selectedLinearElement: null,
15168
- croppingElementId: null,
15169
- activeLockedId: null,
15170
- lockedMultiSelections: {}
15171
- };
15172
- };
15173
- var getObservedAppState = (appState) => {
15174
- const observedAppState = {
15175
- name: appState.name,
15176
- editingGroupId: appState.editingGroupId,
15177
- viewBackgroundColor: appState.viewBackgroundColor,
15178
- selectedElementIds: appState.selectedElementIds,
15179
- selectedGroupIds: appState.selectedGroupIds,
15180
- croppingElementId: appState.croppingElementId,
15181
- activeLockedId: appState.activeLockedId,
15182
- lockedMultiSelections: appState.lockedMultiSelections,
15183
- selectedLinearElement: appState.selectedLinearElement ? {
15184
- elementId: appState.selectedLinearElement.elementId,
15185
- isEditing: !!appState.selectedLinearElement.isEditing
15186
- } : null
15187
- };
15188
- Reflect.defineProperty(observedAppState, hiddenObservedAppStateProp, {
15189
- value: true,
15190
- enumerable: false
15191
- });
15192
- return observedAppState;
15193
- };
15194
- var isObservedAppState = (appState) => !!Reflect.get(appState, hiddenObservedAppStateProp);
15195
-
15196
- // ../element/src/fractionalIndex.ts
15197
- import { generateNKeysBetween } from "fractional-indexing";
15198
- var InvalidFractionalIndexError = class extends Error {
15199
- constructor() {
15200
- super(...arguments);
15201
- __publicField(this, "code", "ELEMENT_HAS_INVALID_INDEX");
15584
+ static empty() {
15585
+ return _StoreDelta.create(ElementsDelta.empty(), AppStateDelta.empty());
15586
+ }
15587
+ isEmpty() {
15588
+ return this.elements.isEmpty() && this.appState.isEmpty();
15202
15589
  }
15203
15590
  };
15204
- var validateFractionalIndices = (elements, {
15205
- shouldThrow = false,
15206
- includeBoundTextValidation = false,
15207
- ignoreLogs,
15208
- reconciliationContext
15209
- }) => {
15210
- const errorMessages = [];
15211
- const stringifyElement = (element) => `${element?.index}:${element?.id}:${element?.type}:${element?.isDeleted}:${element?.version}:${element?.versionNonce}`;
15212
- const indices = elements.map((x) => x.index);
15213
- for (const [i, index] of indices.entries()) {
15214
- const predecessorIndex = indices[i - 1];
15215
- const successorIndex = indices[i + 1];
15216
- if (!isValidFractionalIndex(index, predecessorIndex, successorIndex)) {
15217
- errorMessages.push(
15218
- `Fractional indices invariant has been compromised: "${stringifyElement(
15219
- elements[i - 1]
15220
- )}", "${stringifyElement(elements[i])}", "${stringifyElement(
15221
- elements[i + 1]
15222
- )}"`
15223
- );
15591
+ var StoreSnapshot = class _StoreSnapshot {
15592
+ constructor(elements, appState, metadata = {
15593
+ didElementsChange: false,
15594
+ didAppStateChange: false,
15595
+ isEmpty: false
15596
+ }) {
15597
+ this.elements = elements;
15598
+ this.appState = appState;
15599
+ this.metadata = metadata;
15600
+ __publicField(this, "_lastChangedElementsHash", 0);
15601
+ __publicField(this, "_lastChangedAppStateHash", 0);
15602
+ }
15603
+ static create(elements, appState, metadata = {
15604
+ didElementsChange: false,
15605
+ didAppStateChange: false
15606
+ }) {
15607
+ return new _StoreSnapshot(
15608
+ elements,
15609
+ isObservedAppState(appState) ? appState : getObservedAppState(appState),
15610
+ metadata
15611
+ );
15612
+ }
15613
+ static empty() {
15614
+ return new _StoreSnapshot(
15615
+ /* @__PURE__ */ new Map(),
15616
+ getDefaultObservedAppState(),
15617
+ {
15618
+ didElementsChange: false,
15619
+ didAppStateChange: false,
15620
+ isEmpty: true
15621
+ }
15622
+ );
15623
+ }
15624
+ getChangedElements(prevSnapshot) {
15625
+ const changedElements = {};
15626
+ for (const prevElement of toIterable(prevSnapshot.elements)) {
15627
+ const nextElement = this.elements.get(prevElement.id);
15628
+ if (!nextElement) {
15629
+ changedElements[prevElement.id] = newElementWith(prevElement, {
15630
+ isDeleted: true
15631
+ });
15632
+ }
15224
15633
  }
15225
- if (includeBoundTextValidation && hasBoundTextElement(elements[i])) {
15226
- const container = elements[i];
15227
- const text = getBoundTextElement(container, arrayToMap(elements));
15228
- if (text && text.index <= container.index) {
15229
- errorMessages.push(
15230
- `Fractional indices invariant for bound elements has been compromised: "${stringifyElement(
15231
- text
15232
- )}", "${stringifyElement(container)}"`
15233
- );
15634
+ for (const nextElement of toIterable(this.elements)) {
15635
+ if (prevSnapshot.elements.get(nextElement.id) !== nextElement) {
15636
+ changedElements[nextElement.id] = nextElement;
15234
15637
  }
15235
15638
  }
15639
+ return changedElements;
15236
15640
  }
15237
- if (errorMessages.length) {
15238
- const error = new InvalidFractionalIndexError();
15239
- const additionalContext = [];
15240
- if (reconciliationContext) {
15241
- additionalContext.push("Additional reconciliation context:");
15242
- additionalContext.push(
15243
- reconciliationContext.localElements.map((x) => stringifyElement(x))
15244
- );
15245
- additionalContext.push(
15246
- reconciliationContext.remoteElements.map((x) => stringifyElement(x))
15247
- );
15641
+ getChangedAppState(prevSnapshot) {
15642
+ return Delta.getRightDifferences(
15643
+ prevSnapshot.appState,
15644
+ this.appState
15645
+ ).reduce(
15646
+ (acc, key) => Object.assign(acc, {
15647
+ [key]: this.appState[key]
15648
+ }),
15649
+ {}
15650
+ );
15651
+ }
15652
+ isEmpty() {
15653
+ return this.metadata.isEmpty;
15654
+ }
15655
+ /**
15656
+ * Apply the change and return a new snapshot instance.
15657
+ */
15658
+ applyChange(change) {
15659
+ const nextElements = new Map(this.elements);
15660
+ for (const [id, changedElement] of Object.entries(change.elements)) {
15661
+ nextElements.set(id, changedElement);
15248
15662
  }
15249
- if (!ignoreLogs) {
15250
- console.error(
15251
- errorMessages.join("\n\n"),
15252
- error.stack,
15253
- elements.map((x) => stringifyElement(x)),
15254
- ...additionalContext
15255
- );
15663
+ const nextAppState = getObservedAppState({
15664
+ ...this.appState,
15665
+ ...change.appState
15666
+ });
15667
+ return _StoreSnapshot.create(nextElements, nextAppState, {
15668
+ // by default we assume that change is different from what we have in the snapshot
15669
+ // so that we trigger the delta calculation and if it isn't different, delta will be empty
15670
+ didElementsChange: Object.keys(change.elements).length > 0,
15671
+ didAppStateChange: Object.keys(change.appState).length > 0
15672
+ });
15673
+ }
15674
+ /**
15675
+ * Efficiently clone the existing snapshot, only if we detected changes.
15676
+ *
15677
+ * @returns same instance if there are no changes detected, new instance otherwise.
15678
+ */
15679
+ maybeClone(action, elements, appState) {
15680
+ const options = {
15681
+ shouldCompareHashes: false
15682
+ };
15683
+ if (action === CaptureUpdateAction.EVENTUALLY) {
15684
+ options.shouldCompareHashes = true;
15256
15685
  }
15257
- if (shouldThrow) {
15258
- throw error;
15686
+ const nextElementsSnapshot = this.maybeCreateElementsSnapshot(
15687
+ elements,
15688
+ options
15689
+ );
15690
+ const nextAppStateSnapshot = this.maybeCreateAppStateSnapshot(
15691
+ appState,
15692
+ options
15693
+ );
15694
+ let didElementsChange = false;
15695
+ let didAppStateChange = false;
15696
+ if (this.elements !== nextElementsSnapshot) {
15697
+ didElementsChange = true;
15259
15698
  }
15260
- }
15261
- };
15262
- var orderByFractionalIndex = (elements) => {
15263
- return elements.sort((a, b) => {
15264
- if (isOrderedElement(a) && isOrderedElement(b)) {
15265
- if (a.index < b.index) {
15266
- return -1;
15267
- } else if (a.index > b.index) {
15268
- return 1;
15269
- }
15270
- return a.id < b.id ? -1 : 1;
15699
+ if (this.appState !== nextAppStateSnapshot) {
15700
+ didAppStateChange = true;
15271
15701
  }
15272
- return 1;
15273
- });
15274
- };
15275
- var syncMovedIndices = (elements, movedElements) => {
15276
- try {
15277
- const elementsMap = arrayToMap(elements);
15278
- const indicesGroups = getMovedIndicesGroups(elements, movedElements);
15279
- const elementsUpdates = generateIndices(elements, indicesGroups);
15280
- const elementsCandidates = elements.map((x) => {
15281
- const elementUpdates = elementsUpdates.get(x);
15282
- if (elementUpdates) {
15283
- return { ...x, index: elementUpdates.index };
15284
- }
15285
- return x;
15286
- });
15287
- validateFractionalIndices(
15288
- elementsCandidates,
15289
- // we don't autofix invalid bound text indices, hence don't include it in the validation
15702
+ if (!didElementsChange && !didAppStateChange) {
15703
+ return this;
15704
+ }
15705
+ const snapshot = new _StoreSnapshot(
15706
+ nextElementsSnapshot,
15707
+ nextAppStateSnapshot,
15290
15708
  {
15291
- includeBoundTextValidation: false,
15292
- shouldThrow: true,
15293
- ignoreLogs: true
15709
+ didElementsChange,
15710
+ didAppStateChange
15294
15711
  }
15295
15712
  );
15296
- for (const [element, { index }] of elementsUpdates) {
15297
- mutateElement(element, elementsMap, { index });
15298
- }
15299
- } catch (e) {
15300
- syncInvalidIndices(elements);
15301
- }
15302
- return elements;
15303
- };
15304
- var syncInvalidIndices = (elements) => {
15305
- const elementsMap = arrayToMap(elements);
15306
- const indicesGroups = getInvalidIndicesGroups(elements);
15307
- const elementsUpdates = generateIndices(elements, indicesGroups);
15308
- for (const [element, { index }] of elementsUpdates) {
15309
- mutateElement(element, elementsMap, { index });
15713
+ return snapshot;
15310
15714
  }
15311
- return elements;
15312
- };
15313
- var syncInvalidIndicesImmutable = (elements) => {
15314
- const syncedElements = arrayToMap(elements);
15315
- const indicesGroups = getInvalidIndicesGroups(elements);
15316
- const elementsUpdates = generateIndices(elements, indicesGroups);
15317
- for (const [element, { index }] of elementsUpdates) {
15318
- syncedElements.set(element.id, newElementWith(element, { index }));
15715
+ maybeCreateAppStateSnapshot(appState, options = {
15716
+ shouldCompareHashes: false
15717
+ }) {
15718
+ if (!appState) {
15719
+ return this.appState;
15720
+ }
15721
+ const nextAppStateSnapshot = !isObservedAppState(appState) ? getObservedAppState(appState) : appState;
15722
+ const didAppStateChange = this.detectChangedAppState(
15723
+ nextAppStateSnapshot,
15724
+ options
15725
+ );
15726
+ if (!didAppStateChange) {
15727
+ return this.appState;
15728
+ }
15729
+ return nextAppStateSnapshot;
15319
15730
  }
15320
- return syncedElements;
15321
- };
15322
- var getMovedIndicesGroups = (elements, movedElements) => {
15323
- const indicesGroups = [];
15324
- let i = 0;
15325
- while (i < elements.length) {
15326
- if (movedElements.has(elements[i].id)) {
15327
- const indicesGroup = [i - 1, i];
15328
- while (++i < elements.length) {
15329
- if (!movedElements.has(elements[i].id)) {
15330
- break;
15331
- }
15332
- indicesGroup.push(i);
15333
- }
15334
- indicesGroup.push(i);
15335
- indicesGroups.push(indicesGroup);
15336
- } else {
15337
- i++;
15731
+ maybeCreateElementsSnapshot(elements, options = {
15732
+ shouldCompareHashes: false
15733
+ }) {
15734
+ if (!elements) {
15735
+ return this.elements;
15736
+ }
15737
+ const changedElements = this.detectChangedElements(elements, options);
15738
+ if (!changedElements?.size) {
15739
+ return this.elements;
15338
15740
  }
15741
+ const elementsSnapshot = this.createElementsSnapshot(changedElements);
15742
+ return elementsSnapshot;
15339
15743
  }
15340
- return indicesGroups;
15341
- };
15342
- var getInvalidIndicesGroups = (elements) => {
15343
- const indicesGroups = [];
15344
- let lowerBound = void 0;
15345
- let upperBound = void 0;
15346
- let lowerBoundIndex = -1;
15347
- let upperBoundIndex = 0;
15348
- const getLowerBound = (index) => {
15349
- const lowerBound2 = elements[lowerBoundIndex] ? elements[lowerBoundIndex].index : void 0;
15350
- const candidate = elements[index - 1]?.index;
15351
- if (!lowerBound2 && candidate || // first lowerBound
15352
- lowerBound2 && candidate && candidate > lowerBound2) {
15353
- return [candidate, index - 1];
15744
+ detectChangedAppState(nextObservedAppState, options = {
15745
+ shouldCompareHashes: false
15746
+ }) {
15747
+ if (this.appState === nextObservedAppState) {
15748
+ return;
15354
15749
  }
15355
- return [lowerBound2, lowerBoundIndex];
15356
- };
15357
- const getUpperBound = (index) => {
15358
- const upperBound2 = elements[upperBoundIndex] ? elements[upperBoundIndex].index : void 0;
15359
- if (upperBound2 && index < upperBoundIndex) {
15360
- return [upperBound2, upperBoundIndex];
15750
+ const didAppStateChange = Delta.isRightDifferent(
15751
+ this.appState,
15752
+ nextObservedAppState
15753
+ );
15754
+ if (!didAppStateChange) {
15755
+ return;
15361
15756
  }
15362
- let i2 = upperBoundIndex;
15363
- while (++i2 < elements.length) {
15364
- const candidate = elements[i2]?.index;
15365
- if (!upperBound2 && candidate || // first upperBound
15366
- upperBound2 && candidate && candidate > upperBound2) {
15367
- return [candidate, i2];
15757
+ const changedAppStateHash = hashString(
15758
+ JSON.stringify(nextObservedAppState)
15759
+ );
15760
+ if (options.shouldCompareHashes && this._lastChangedAppStateHash === changedAppStateHash) {
15761
+ return;
15762
+ }
15763
+ this._lastChangedAppStateHash = changedAppStateHash;
15764
+ return didAppStateChange;
15765
+ }
15766
+ /**
15767
+ * Detect if there are any changed elements.
15768
+ */
15769
+ detectChangedElements(nextElements, options = {
15770
+ shouldCompareHashes: false
15771
+ }) {
15772
+ if (this.elements === nextElements) {
15773
+ return;
15774
+ }
15775
+ const changedElements = /* @__PURE__ */ new Map();
15776
+ for (const prevElement of toIterable(this.elements)) {
15777
+ const nextElement = nextElements.get(prevElement.id);
15778
+ if (!nextElement) {
15779
+ changedElements.set(
15780
+ prevElement.id,
15781
+ newElementWith(prevElement, { isDeleted: true })
15782
+ );
15368
15783
  }
15369
15784
  }
15370
- return [void 0, i2];
15371
- };
15372
- let i = 0;
15373
- while (i < elements.length) {
15374
- const current = elements[i].index;
15375
- [lowerBound, lowerBoundIndex] = getLowerBound(i);
15376
- [upperBound, upperBoundIndex] = getUpperBound(i);
15377
- if (!isValidFractionalIndex(current, lowerBound, upperBound)) {
15378
- const indicesGroup = [lowerBoundIndex, i];
15379
- while (++i < elements.length) {
15380
- const current2 = elements[i].index;
15381
- const [nextLowerBound, nextLowerBoundIndex] = getLowerBound(i);
15382
- const [nextUpperBound, nextUpperBoundIndex] = getUpperBound(i);
15383
- if (isValidFractionalIndex(current2, nextLowerBound, nextUpperBound)) {
15384
- break;
15785
+ for (const nextElement of toIterable(nextElements)) {
15786
+ const prevElement = this.elements.get(nextElement.id);
15787
+ if (!prevElement || // element was added
15788
+ prevElement.version < nextElement.version) {
15789
+ if (isImageElement(nextElement) && !isInitializedImageElement(nextElement)) {
15790
+ continue;
15385
15791
  }
15386
- [lowerBound, lowerBoundIndex] = [nextLowerBound, nextLowerBoundIndex];
15387
- [upperBound, upperBoundIndex] = [nextUpperBound, nextUpperBoundIndex];
15388
- indicesGroup.push(i);
15792
+ changedElements.set(nextElement.id, nextElement);
15389
15793
  }
15390
- indicesGroup.push(upperBoundIndex);
15391
- indicesGroups.push(indicesGroup);
15392
- } else {
15393
- i++;
15394
15794
  }
15795
+ if (!changedElements.size) {
15796
+ return;
15797
+ }
15798
+ const changedElementsHash = hashElementsVersion(changedElements);
15799
+ if (options.shouldCompareHashes && this._lastChangedElementsHash === changedElementsHash) {
15800
+ return;
15801
+ }
15802
+ this._lastChangedElementsHash = changedElementsHash;
15803
+ return changedElements;
15395
15804
  }
15396
- return indicesGroups;
15397
- };
15398
- var isValidFractionalIndex = (index, predecessor, successor) => {
15399
- if (!index) {
15400
- return false;
15401
- }
15402
- if (predecessor && successor) {
15403
- return predecessor < index && index < successor;
15404
- }
15405
- if (!predecessor && successor) {
15406
- return index < successor;
15407
- }
15408
- if (predecessor && !successor) {
15409
- return predecessor < index;
15410
- }
15411
- return !!index;
15412
- };
15413
- var generateIndices = (elements, indicesGroups) => {
15414
- const elementsUpdates = /* @__PURE__ */ new Map();
15415
- for (const indices of indicesGroups) {
15416
- const lowerBoundIndex = indices.shift();
15417
- const upperBoundIndex = indices.pop();
15418
- const fractionalIndices = generateNKeysBetween(
15419
- elements[lowerBoundIndex]?.index,
15420
- elements[upperBoundIndex]?.index,
15421
- indices.length
15422
- );
15423
- for (let i = 0; i < indices.length; i++) {
15424
- const element = elements[indices[i]];
15425
- elementsUpdates.set(element, {
15426
- index: fractionalIndices[i]
15427
- });
15805
+ /**
15806
+ * Perform structural clone, deep cloning only elements that changed.
15807
+ */
15808
+ createElementsSnapshot(changedElements) {
15809
+ const clonedElements = /* @__PURE__ */ new Map();
15810
+ for (const prevElement of toIterable(this.elements)) {
15811
+ clonedElements.set(prevElement.id, prevElement);
15812
+ }
15813
+ for (const changedElement of toIterable(changedElements)) {
15814
+ clonedElements.set(changedElement.id, deepCopyElement(changedElement));
15428
15815
  }
15816
+ return clonedElements;
15429
15817
  }
15430
- return elementsUpdates;
15431
15818
  };
15432
- var isOrderedElement = (element) => {
15433
- if (element.index) {
15434
- return true;
15435
- }
15436
- return false;
15819
+ var hiddenObservedAppStateProp = "__observedAppState";
15820
+ var getDefaultObservedAppState = () => {
15821
+ return {
15822
+ name: null,
15823
+ editingGroupId: null,
15824
+ viewBackgroundColor: COLOR_PALETTE.white,
15825
+ selectedElementIds: {},
15826
+ selectedGroupIds: {},
15827
+ selectedLinearElement: null,
15828
+ croppingElementId: null,
15829
+ activeLockedId: null,
15830
+ lockedMultiSelections: {}
15831
+ };
15832
+ };
15833
+ var getObservedAppState = (appState) => {
15834
+ const observedAppState = {
15835
+ name: appState.name,
15836
+ editingGroupId: appState.editingGroupId,
15837
+ viewBackgroundColor: appState.viewBackgroundColor,
15838
+ selectedElementIds: appState.selectedElementIds,
15839
+ selectedGroupIds: appState.selectedGroupIds,
15840
+ croppingElementId: appState.croppingElementId,
15841
+ activeLockedId: appState.activeLockedId,
15842
+ lockedMultiSelections: appState.lockedMultiSelections,
15843
+ selectedLinearElement: appState.selectedLinearElement ? {
15844
+ elementId: appState.selectedLinearElement.elementId,
15845
+ isEditing: !!appState.selectedLinearElement.isEditing
15846
+ } : null
15847
+ };
15848
+ Reflect.defineProperty(observedAppState, hiddenObservedAppStateProp, {
15849
+ value: true,
15850
+ enumerable: false
15851
+ });
15852
+ return observedAppState;
15437
15853
  };
15854
+ var isObservedAppState = (appState) => !!Reflect.get(appState, hiddenObservedAppStateProp);
15438
15855
 
15439
15856
  // ../element/src/Scene.ts
15440
15857
  import throttle from "lodash.throttle";
@@ -15500,16 +15917,9 @@ var Scene = class {
15500
15917
  * cache-invalidation nonce at the moment.
15501
15918
  */
15502
15919
  __publicField(this, "sceneNonce");
15920
+ /** low-level - generally use app.insertNewElement() */
15503
15921
  __publicField(this, "insertElement", (element) => {
15504
- const index = element.frameId ? this.getElementIndex(element.frameId) : this.elements.length;
15505
- this.insertElementAtIndex(element, index);
15506
- });
15507
- __publicField(this, "insertElements", (elements) => {
15508
- if (!elements.length) {
15509
- return;
15510
- }
15511
- const index = elements[0]?.frameId ? this.getElementIndex(elements[0].frameId) : this.elements.length;
15512
- this.insertElementsAtIndex(elements, index);
15922
+ this.insertElementsAtIndex([element], null);
15513
15923
  });
15514
15924
  __publicField(this, "getContainerElement", (element) => {
15515
15925
  if (!element) {
@@ -15662,24 +16072,14 @@ var Scene = class {
15662
16072
  this.selectedElementsCache.cache.clear();
15663
16073
  this.callbacks.clear();
15664
16074
  }
15665
- insertElementAtIndex(element, index) {
15666
- if (!Number.isFinite(index) || index < 0) {
15667
- throw new Error(
15668
- "insertElementAtIndex can only be called with index >= 0"
15669
- );
15670
- }
15671
- const nextElements = [
15672
- ...this.elements.slice(0, index),
15673
- element,
15674
- ...this.elements.slice(index)
15675
- ];
15676
- syncMovedIndices(nextElements, arrayToMap([element]));
15677
- this.replaceAllElements(nextElements);
15678
- }
16075
+ /** low-level - generally use app.insertNewElements() */
15679
16076
  insertElementsAtIndex(elements, index) {
15680
16077
  if (!elements.length) {
15681
16078
  return;
15682
16079
  }
16080
+ if (index === null) {
16081
+ index = this.elements.length;
16082
+ }
15683
16083
  if (!Number.isFinite(index) || index < 0) {
15684
16084
  throw new Error(
15685
16085
  "insertElementAtIndex can only be called with index >= 0"
@@ -21484,7 +21884,6 @@ import {
21484
21884
  fileSave as _fileSave,
21485
21885
  supported as nativeFileSystemSupported
21486
21886
  } from "browser-fs-access";
21487
- var INPUT_CHANGE_INTERVAL_MS = 5e3;
21488
21887
  var fileOpen = async (opts) => {
21489
21888
  const mimeTypes = opts.extensions?.reduce((mimeTypes2, type) => {
21490
21889
  mimeTypes2.push(MIME_TYPES[type]);
@@ -21500,39 +21899,7 @@ var fileOpen = async (opts) => {
21500
21899
  description: opts.description,
21501
21900
  extensions,
21502
21901
  mimeTypes,
21503
- multiple: opts.multiple ?? false,
21504
- legacySetup: (resolve, reject, input) => {
21505
- const scheduleRejection = debounce(reject, INPUT_CHANGE_INTERVAL_MS);
21506
- const focusHandler = () => {
21507
- checkForFile();
21508
- document.addEventListener("keyup" /* KEYUP */, scheduleRejection);
21509
- document.addEventListener("pointerup" /* POINTER_UP */, scheduleRejection);
21510
- scheduleRejection();
21511
- };
21512
- const checkForFile = () => {
21513
- if (input.files?.length) {
21514
- const ret = opts.multiple ? [...input.files] : input.files[0];
21515
- resolve(ret);
21516
- }
21517
- };
21518
- requestAnimationFrame(() => {
21519
- window.addEventListener("focus" /* FOCUS */, focusHandler);
21520
- });
21521
- const interval = window.setInterval(() => {
21522
- checkForFile();
21523
- }, INPUT_CHANGE_INTERVAL_MS);
21524
- return (rejectPromise) => {
21525
- clearInterval(interval);
21526
- scheduleRejection.cancel();
21527
- window.removeEventListener("focus" /* FOCUS */, focusHandler);
21528
- document.removeEventListener("keyup" /* KEYUP */, scheduleRejection);
21529
- document.removeEventListener("pointerup" /* POINTER_UP */, scheduleRejection);
21530
- if (rejectPromise) {
21531
- console.warn("Opening the file was canceled (legacy-fs).");
21532
- rejectPromise(new AbortError());
21533
- }
21534
- };
21535
- }
21902
+ multiple: opts.multiple ?? false
21536
21903
  });
21537
21904
  if (Array.isArray(files)) {
21538
21905
  return await Promise.all(
@@ -21550,7 +21917,8 @@ var fileSave = (blob, opts) => {
21550
21917
  extensions: [`.${opts.extension}`],
21551
21918
  mimeTypes: opts.mimeTypes
21552
21919
  },
21553
- opts.fileHandle
21920
+ opts.fileHandle,
21921
+ false
21554
21922
  );
21555
21923
  };
21556
21924
 
@@ -21578,18 +21946,24 @@ var serializeAsJSON = (elements, appState, files, type) => {
21578
21946
  };
21579
21947
  return JSON.stringify(data, null, 2);
21580
21948
  };
21581
- var saveAsJSON = async (elements, appState, files, name = appState.name || DEFAULT_FILENAME) => {
21582
- const serialized = serializeAsJSON(elements, appState, files, "local");
21583
- const blob = new Blob([serialized], {
21584
- type: MIME_TYPES.excalidraw
21949
+ var saveAsJSON = async ({
21950
+ data,
21951
+ filename,
21952
+ fileHandle
21953
+ }) => {
21954
+ const blob = Promise.resolve(data).then(({ elements, appState, files }) => {
21955
+ const serialized = serializeAsJSON(elements, appState, files, "local");
21956
+ return new Blob([serialized], {
21957
+ type: MIME_TYPES.excalidraw
21958
+ });
21585
21959
  });
21586
- const fileHandle = await fileSave(blob, {
21587
- name,
21960
+ const savedFileHandle = await fileSave(blob, {
21961
+ name: filename,
21588
21962
  extension: "excalidraw",
21589
21963
  description: "Excalidraw file",
21590
- fileHandle: isImageFileHandle(appState.fileHandle) ? null : appState.fileHandle
21964
+ fileHandle: isImageFileHandle(fileHandle) ? null : fileHandle
21591
21965
  });
21592
- return { fileHandle };
21966
+ return { fileHandle: savedFileHandle };
21593
21967
  };
21594
21968
  var loadFromJSON = async (localAppState, localElements) => {
21595
21969
  const file = await fileOpen({
@@ -24422,8 +24796,7 @@ var _renderStaticScene = ({
24422
24796
  var renderStaticSceneThrottled = throttleRAF(
24423
24797
  (config) => {
24424
24798
  _renderStaticScene(config);
24425
- },
24426
- { trailing: true }
24799
+ }
24427
24800
  );
24428
24801
  var renderStaticScene = (renderConfig, throttle2) => {
24429
24802
  if (throttle2) {
@@ -24992,6 +25365,7 @@ var renderSceneToSvg = (elements, elementsMap, rsvg, svgRoot, files, renderConfi
24992
25365
  };
24993
25366
 
24994
25367
  // data/restore.ts
25368
+ var MAX_ARROW_PX = 75e3;
24995
25369
  var AllowedExcalidrawActiveTools = {
24996
25370
  selection: true,
24997
25371
  lasso: true,
@@ -25084,7 +25458,9 @@ var repairBinding = (element, binding, targetElementsMap, existingElementsMap, s
25084
25458
  fixedPoint
25085
25459
  };
25086
25460
  }
25087
- console.error(`could not repair binding for element`);
25461
+ console.error(
25462
+ `Could not repair binding for element "${boundElement?.id}" out of (${elementsMap?.size}) elements`
25463
+ );
25088
25464
  } catch (error) {
25089
25465
  console.error("Error repairing binding:", error);
25090
25466
  }
@@ -25247,8 +25623,8 @@ var restoreElement = (element, targetElementsMap, existingElementsMap, opts) =>
25247
25623
  startArrowhead: startArrowhead2,
25248
25624
  endArrowhead: endArrowhead2,
25249
25625
  points: points2,
25250
- x: x2,
25251
- y: y2,
25626
+ x: x2 ?? 0,
25627
+ y: y2 ?? 0,
25252
25628
  elbowed: element.elbowed,
25253
25629
  ...getSizeFromPoints(points2)
25254
25630
  };
@@ -25259,12 +25635,30 @@ var restoreElement = (element, targetElementsMap, existingElementsMap, opts) =>
25259
25635
  startIsSpecial: element.startIsSpecial,
25260
25636
  endIsSpecial: element.endIsSpecial
25261
25637
  }) : restoreElementWithProperties(element, base);
25262
- return {
25638
+ const normalizedRestoredElement = {
25263
25639
  ...restoredElement,
25264
25640
  ...LinearElementEditor.getNormalizeElementPointsAndCoords(
25265
25641
  restoredElement
25266
25642
  )
25267
25643
  };
25644
+ if (normalizedRestoredElement.width > MAX_ARROW_PX || normalizedRestoredElement.height > MAX_ARROW_PX) {
25645
+ console.error(
25646
+ `Removing extremely large arrow ${normalizedRestoredElement.id} (type: ${isElbowArrow(normalizedRestoredElement) ? "elbow" : "simple"}, width: ${normalizedRestoredElement.width}, height: ${normalizedRestoredElement.height}, x: ${normalizedRestoredElement.x}, y: ${normalizedRestoredElement.y})`
25647
+ );
25648
+ return {
25649
+ ...normalizedRestoredElement,
25650
+ x: 0,
25651
+ y: 0,
25652
+ width: 100,
25653
+ height: 100,
25654
+ points: [
25655
+ pointFrom(0, 0),
25656
+ pointFrom(100, 100)
25657
+ ],
25658
+ isDeleted: true
25659
+ };
25660
+ }
25661
+ return normalizedRestoredElement;
25268
25662
  }
25269
25663
  case "ellipse":
25270
25664
  case "rectangle":
@@ -25489,6 +25883,10 @@ var restoreAppState = (appState, localAppState) => {
25489
25883
  const localValue = localAppState ? localAppState[key] : void 0;
25490
25884
  nextAppState[key] = suppliedValue !== void 0 ? suppliedValue : localValue !== void 0 ? localValue : defaultValue;
25491
25885
  }
25886
+ const boxSelectionMode = appState.boxSelectionMode ?? localAppState?.boxSelectionMode;
25887
+ if (boxSelectionMode !== void 0) {
25888
+ nextAppState.boxSelectionMode = boxSelectionMode;
25889
+ }
25492
25890
  return {
25493
25891
  ...nextAppState,
25494
25892
  cursorButton: localAppState?.cursorButton || "up",
@@ -25619,7 +26017,11 @@ var prepareElementsForRender = ({
25619
26017
  }) => {
25620
26018
  let nextElements;
25621
26019
  if (exportingFrame) {
25622
- nextElements = getElementsOverlappingFrame(elements, exportingFrame);
26020
+ nextElements = getElementsOverlappingFrame(
26021
+ elements,
26022
+ exportingFrame,
26023
+ arrayToMap(elements)
26024
+ );
25623
26025
  } else if (frameRendering.enabled && frameRendering.name) {
25624
26026
  nextElements = addFrameLabelsAsTextElements(elements, {
25625
26027
  exportWithDarkMode
@@ -26018,7 +26420,7 @@ var parseFileContents = async (blob) => {
26018
26420
  let contents;
26019
26421
  if (blob.type === MIME_TYPES.png) {
26020
26422
  try {
26021
- return await (await import("./data/image-JR54FB4B.js")).decodePngMetadata(blob);
26423
+ return await (await import("./data/image-O7OJXMIJ.js")).decodePngMetadata(blob);
26022
26424
  } catch (error) {
26023
26425
  if (error.message === "INVALID") {
26024
26426
  throw new ImageSceneDataError(
@@ -26407,6 +26809,7 @@ export {
26407
26809
  rangeInclusive,
26408
26810
  rangesOverlap,
26409
26811
  rangeIntersection,
26812
+ rangeIncludesValue,
26410
26813
  supportsResizeObserver,
26411
26814
  APP_NAME,
26412
26815
  DRAGGING_THRESHOLD,
@@ -26582,6 +26985,7 @@ export {
26582
26985
  isLocalLink,
26583
26986
  toValidURL,
26584
26987
  Emitter,
26988
+ AppEventBus,
26585
26989
  MINIMAL_CROP_SIZE,
26586
26990
  cropElement,
26587
26991
  getUncroppedWidthAndHeight,
@@ -26616,6 +27020,7 @@ export {
26616
27020
  getLinearElementSubType,
26617
27021
  isValidPolygon,
26618
27022
  canBecomePolygon,
27023
+ isEligibleFrameChildType,
26619
27024
  deconstructRectanguloidElement,
26620
27025
  getDiamondBaseCorners,
26621
27026
  deconstructDiamondElement,
@@ -26653,6 +27058,13 @@ export {
26653
27058
  getBoundTextMaxWidth,
26654
27059
  getBoundTextMaxHeight,
26655
27060
  getTextFromElements,
27061
+ hasBackground,
27062
+ hasStrokeColor,
27063
+ hasStrokeWidth,
27064
+ hasStrokeStyle,
27065
+ canChangeRoundness,
27066
+ toolIsArrow,
27067
+ canHaveArrowheads,
26656
27068
  shouldTestInside,
26657
27069
  hitElementItself,
26658
27070
  hitElementBoundingBox,
@@ -26679,9 +27091,6 @@ export {
26679
27091
  fixBindingsAfterDeletion,
26680
27092
  getGlobalFixedPointForBindableElement,
26681
27093
  LinearElementEditor,
26682
- isElementInsideBBox,
26683
- elementPartiallyOverlapsWithOrContainsBBox,
26684
- elementsOverlappingBBox,
26685
27094
  selectGroup,
26686
27095
  selectGroupsForSelectedElements,
26687
27096
  isSelectedViaGroup,
@@ -26706,6 +27115,10 @@ export {
26706
27115
  makeNextSelectedElementIds,
26707
27116
  getSelectionStateForElements,
26708
27117
  getActiveTextElement,
27118
+ validateFractionalIndices,
27119
+ orderByFractionalIndex,
27120
+ syncMovedIndices,
27121
+ syncInvalidIndices,
26709
27122
  elementOverlapsWithFrame,
26710
27123
  isCursorInFrame,
26711
27124
  groupByFrameLikes,
@@ -26716,6 +27129,8 @@ export {
26716
27129
  getElementsInNewFrame,
26717
27130
  getContainingFrame,
26718
27131
  filterElementsEligibleAsFrameChildren,
27132
+ getCommonFrameId,
27133
+ getFrameChildrenInsertionIndex,
26719
27134
  addElementsToFrame,
26720
27135
  removeElementsFromFrame,
26721
27136
  removeAllElementsFromFrame,
@@ -26733,13 +27148,6 @@ export {
26733
27148
  renderSelectionElement,
26734
27149
  renderElement,
26735
27150
  getFreedrawOutlineAsSegments,
26736
- hasBackground,
26737
- hasStrokeColor,
26738
- hasStrokeWidth,
26739
- hasStrokeStyle,
26740
- canChangeRoundness,
26741
- toolIsArrow,
26742
- canHaveArrowheads,
26743
27151
  ShapeCache,
26744
27152
  toggleLinePolygonState,
26745
27153
  getFreedrawOutlinePoints,
@@ -26765,10 +27173,6 @@ export {
26765
27173
  StoreChange,
26766
27174
  StoreDelta,
26767
27175
  getObservedAppState,
26768
- validateFractionalIndices,
26769
- orderByFractionalIndex,
26770
- syncMovedIndices,
26771
- syncInvalidIndices,
26772
27176
  Scene,
26773
27177
  distributeElements,
26774
27178
  dragSelectedElements,
@@ -26896,4 +27300,4 @@ export {
26896
27300
  createFile,
26897
27301
  normalizeFile
26898
27302
  };
26899
- //# sourceMappingURL=chunk-VLJRILK3.js.map
27303
+ //# sourceMappingURL=chunk-OKDHCI5V.js.map