@excalidraw/excalidraw 0.17.1-3e334a6 → 0.17.1-4689a6b

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 (110) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/browser/dev/excalidraw-assets-dev/{chunk-M7HSOQ7X.js → chunk-23CKV3WP.js} +3 -1
  3. package/dist/browser/dev/excalidraw-assets-dev/chunk-23CKV3WP.js.map +7 -0
  4. package/dist/browser/dev/excalidraw-assets-dev/{chunk-RWZVJAQU.js → chunk-7D5BMEAB.js} +2227 -1976
  5. package/dist/browser/dev/excalidraw-assets-dev/chunk-7D5BMEAB.js.map +7 -0
  6. package/dist/browser/dev/excalidraw-assets-dev/{en-R45KN4KN.js → en-W7TECCRB.js} +2 -2
  7. package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js → image-JKT6GXZD.js} +2 -2
  8. package/dist/browser/dev/index.css +20 -0
  9. package/dist/browser/dev/index.css.map +2 -2
  10. package/dist/browser/dev/index.js +766 -580
  11. package/dist/browser/dev/index.js.map +4 -4
  12. package/dist/browser/prod/excalidraw-assets/chunk-DWOM5R6H.js +55 -0
  13. package/dist/browser/prod/excalidraw-assets/{chunk-DIHRGRYX.js → chunk-SK23VHAR.js} +1 -1
  14. package/dist/browser/prod/excalidraw-assets/{en-H6IY7PV6.js → en-SMMH575S.js} +1 -1
  15. package/dist/browser/prod/excalidraw-assets/image-WDEQS5RL.js +1 -0
  16. package/dist/browser/prod/index.css +1 -1
  17. package/dist/browser/prod/index.js +22 -22
  18. package/dist/{prod/en-N2RZZLK5.json → dev/en-CVBEBUBY.json} +2 -0
  19. package/dist/dev/index.css +20 -0
  20. package/dist/dev/index.css.map +2 -2
  21. package/dist/dev/index.js +2379 -2069
  22. package/dist/dev/index.js.map +4 -4
  23. package/dist/excalidraw/actions/actionBoundText.js +4 -1
  24. package/dist/excalidraw/actions/actionCanvas.js +3 -1
  25. package/dist/excalidraw/actions/actionDuplicateSelection.js +4 -0
  26. package/dist/excalidraw/actions/actionExport.d.ts +1 -1
  27. package/dist/excalidraw/actions/actionFinalize.d.ts +1 -1
  28. package/dist/excalidraw/actions/actionFinalize.js +3 -3
  29. package/dist/excalidraw/actions/actionFlip.d.ts +3 -3
  30. package/dist/excalidraw/actions/actionFlip.js +6 -6
  31. package/dist/excalidraw/actions/actionGroup.js +4 -2
  32. package/dist/excalidraw/actions/actionHistory.js +3 -0
  33. package/dist/excalidraw/actions/actionZindex.d.ts +11 -11
  34. package/dist/excalidraw/analytics.js +1 -1
  35. package/dist/excalidraw/components/App.d.ts +13 -3
  36. package/dist/excalidraw/components/App.js +211 -81
  37. package/dist/excalidraw/components/CommandPalette/CommandPalette.js +24 -10
  38. package/dist/excalidraw/components/DarkModeToggle.js +3 -1
  39. package/dist/excalidraw/components/HelpDialog.js +2 -2
  40. package/dist/excalidraw/components/RadioGroup.d.ts +2 -1
  41. package/dist/excalidraw/components/RadioGroup.js +1 -1
  42. package/dist/excalidraw/components/TTDDialog/MermaidToExcalidraw.js +6 -2
  43. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.d.ts +18 -0
  44. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.js +9 -0
  45. package/dist/excalidraw/components/hyperlink/Hyperlink.js +3 -3
  46. package/dist/excalidraw/components/hyperlink/helpers.js +2 -3
  47. package/dist/excalidraw/components/icons.d.ts +3 -0
  48. package/dist/excalidraw/components/icons.js +5 -1
  49. package/dist/excalidraw/components/main-menu/DefaultItems.d.ts +12 -2
  50. package/dist/excalidraw/components/main-menu/DefaultItems.js +38 -7
  51. package/dist/excalidraw/constants.d.ts +0 -3
  52. package/dist/excalidraw/constants.js +0 -3
  53. package/dist/excalidraw/data/magic.js +2 -1
  54. package/dist/excalidraw/data/reconcile.d.ts +6 -0
  55. package/dist/excalidraw/data/reconcile.js +49 -0
  56. package/dist/excalidraw/data/restore.d.ts +3 -3
  57. package/dist/excalidraw/data/restore.js +5 -6
  58. package/dist/excalidraw/data/transform.d.ts +1 -1
  59. package/dist/excalidraw/data/transform.js +12 -3
  60. package/dist/excalidraw/element/binding.d.ts +22 -9
  61. package/dist/excalidraw/element/binding.js +403 -26
  62. package/dist/excalidraw/element/bounds.d.ts +0 -1
  63. package/dist/excalidraw/element/bounds.js +0 -3
  64. package/dist/excalidraw/element/collision.d.ts +14 -19
  65. package/dist/excalidraw/element/collision.js +36 -713
  66. package/dist/excalidraw/element/embeddable.js +18 -43
  67. package/dist/excalidraw/element/index.d.ts +0 -1
  68. package/dist/excalidraw/element/index.js +0 -1
  69. package/dist/excalidraw/element/linearElementEditor.d.ts +10 -10
  70. package/dist/excalidraw/element/linearElementEditor.js +6 -4
  71. package/dist/excalidraw/element/newElement.d.ts +1 -1
  72. package/dist/excalidraw/element/newElement.js +2 -1
  73. package/dist/excalidraw/element/textElement.d.ts +0 -1
  74. package/dist/excalidraw/element/textElement.js +0 -30
  75. package/dist/excalidraw/element/types.d.ts +17 -2
  76. package/dist/excalidraw/errors.d.ts +3 -0
  77. package/dist/excalidraw/errors.js +3 -0
  78. package/dist/excalidraw/fractionalIndex.d.ts +40 -0
  79. package/dist/excalidraw/fractionalIndex.js +241 -0
  80. package/dist/excalidraw/frame.d.ts +1 -1
  81. package/dist/excalidraw/hooks/useCreatePortalContainer.js +2 -1
  82. package/dist/excalidraw/locales/en.json +2 -0
  83. package/dist/excalidraw/renderer/helpers.js +2 -2
  84. package/dist/excalidraw/renderer/interactiveScene.js +1 -1
  85. package/dist/excalidraw/renderer/renderElement.js +3 -3
  86. package/dist/excalidraw/renderer/renderSnaps.js +2 -1
  87. package/dist/excalidraw/scene/Scene.d.ts +7 -6
  88. package/dist/excalidraw/scene/Scene.js +28 -13
  89. package/dist/excalidraw/scene/export.js +4 -3
  90. package/dist/excalidraw/types.d.ts +4 -3
  91. package/dist/excalidraw/utils.d.ts +1 -0
  92. package/dist/excalidraw/utils.js +1 -0
  93. package/dist/excalidraw/zindex.d.ts +2 -2
  94. package/dist/excalidraw/zindex.js +9 -13
  95. package/dist/{dev/en-N2RZZLK5.json → prod/en-CVBEBUBY.json} +2 -0
  96. package/dist/prod/index.css +1 -1
  97. package/dist/prod/index.js +36 -36
  98. package/dist/utils/collision.d.ts +4 -0
  99. package/dist/utils/collision.js +48 -0
  100. package/dist/utils/geometry/geometry.d.ts +71 -0
  101. package/dist/utils/geometry/geometry.js +674 -0
  102. package/dist/utils/geometry/shape.d.ts +55 -0
  103. package/dist/utils/geometry/shape.js +149 -0
  104. package/package.json +2 -1
  105. package/dist/browser/dev/excalidraw-assets-dev/chunk-M7HSOQ7X.js.map +0 -7
  106. package/dist/browser/dev/excalidraw-assets-dev/chunk-RWZVJAQU.js.map +0 -7
  107. package/dist/browser/prod/excalidraw-assets/chunk-LL4GORAM.js +0 -55
  108. package/dist/browser/prod/excalidraw-assets/image-EFCJDJH3.js +0 -1
  109. /package/dist/browser/dev/excalidraw-assets-dev/{en-R45KN4KN.js.map → en-W7TECCRB.js.map} +0 -0
  110. /package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js.map → image-JKT6GXZD.js.map} +0 -0
@@ -15,12 +15,12 @@ import { APP_NAME, CURSOR_TYPE, DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT, DEFAULT_VERTI
15
15
  import { exportCanvas, loadFromBlob } from "../data";
16
16
  import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
17
17
  import { restore, restoreElements } from "../data/restore";
18
- import { dragNewElement, dragSelectedElements, duplicateElement, getCommonBounds, getCursorForResizingElement, getDragOffsetXY, getElementWithTransformHandleType, getNormalizedDimensions, getResizeArrowDirection, getResizeOffsetXY, getLockedLinearCursorAlignSize, getTransformHandleTypeFromCoords, hitTest, isHittingElementBoundingBoxWithoutHittingElement, isInvisiblySmallElement, isNonDeletedElement, isTextElement, newElement, newLinearElement, newTextElement, newImageElement, transformElements, updateTextElement, redrawTextBoundingBox, } from "../element";
18
+ import { dragNewElement, dragSelectedElements, duplicateElement, getCommonBounds, getCursorForResizingElement, getDragOffsetXY, getElementWithTransformHandleType, getNormalizedDimensions, getResizeArrowDirection, getResizeOffsetXY, getLockedLinearCursorAlignSize, getTransformHandleTypeFromCoords, isInvisiblySmallElement, isNonDeletedElement, isTextElement, newElement, newLinearElement, newTextElement, newImageElement, transformElements, updateTextElement, redrawTextBoundingBox, getElementAbsoluteCoords, } from "../element";
19
19
  import { bindOrUnbindLinearElement, bindOrUnbindSelectedElements, fixBindingsAfterDeletion, fixBindingsAfterDuplication, getEligibleElementsForBinding, getHoveredElementForBinding, isBindingEnabled, isLinearElementSimpleAndAlreadyBound, maybeBindLinearElement, shouldEnableBindingForPointerEvent, unbindLinearElements, updateBoundElements, } from "../element/binding";
20
20
  import { LinearElementEditor } from "../element/linearElementEditor";
21
21
  import { mutateElement, newElementWith } from "../element/mutateElement";
22
22
  import { deepCopyElement, duplicateElements, newFrameElement, newFreeDrawElement, newEmbeddableElement, newMagicFrameElement, newIframeElement, } from "../element/newElement";
23
- import { hasBoundTextElement, isArrowElement, isBindingElement, isBindingElementType, isBoundToContainer, isFrameLikeElement, isImageElement, isEmbeddableElement, isInitializedImageElement, isLinearElement, isLinearElementType, isUsingAdaptiveRadius, isFrameElement, isIframeElement, isIframeLikeElement, isMagicFrameElement, } from "../element/typeChecks";
23
+ import { hasBoundTextElement, isArrowElement, isBindingElement, isBindingElementType, isBoundToContainer, isFrameLikeElement, isImageElement, isEmbeddableElement, isInitializedImageElement, isLinearElement, isLinearElementType, isUsingAdaptiveRadius, isFrameElement, isIframeElement, isIframeLikeElement, isMagicFrameElement, isTextBindableContainer, } from "../element/typeChecks";
24
24
  import { getCenter, getDistance } from "../gesture";
25
25
  import { editGroupForSelectedElement, getElementsInGroup, getSelectedGroupIdForElement, getSelectedGroupIds, isElementInGroup, isSelectedViaGroup, selectGroupsForSelectedElements, } from "../groups";
26
26
  import History from "../history";
@@ -28,11 +28,13 @@ import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n";
28
28
  import { CODES, shouldResizeFromCenter, shouldMaintainAspectRatio, shouldRotateWithDiscreteAngle, isArrowKey, KEYS, } from "../keys";
29
29
  import { isElementInViewport } from "../element/sizeHelpers";
30
30
  import { distance2d, getCornerRadius, getGridPoint, isPathALoop, } from "../math";
31
- import { calculateScrollCenter, getElementsAtPosition, getElementsWithinSelection, getNormalizedZoom, getSelectedElements, hasBackground, isSomeElementSelected, } from "../scene";
31
+ import { calculateScrollCenter, getElementsWithinSelection, getNormalizedZoom, getSelectedElements, hasBackground, isSomeElementSelected, } from "../scene";
32
32
  import Scene from "../scene/Scene";
33
33
  import { getStateForZoom } from "../scene/zoom";
34
34
  import { findShapeByKey } from "../shapes";
35
- import { debounce, distance, getFontString, getNearestScrollableContainer, isInputLike, isToolIcon, isWritableElement, sceneCoordsToViewportCoords, tupleToCoors, viewportCoordsToSceneCoords, wrapEvent, updateObject, updateActiveTool, getShortcutKey, isTransparent, easeToValuesRAF, muteFSAbortError, isTestEnv, easeOut, updateStable, addEventListener, normalizeEOL, getDateTime, } from "../utils";
35
+ import { getClosedCurveShape, getCurveShape, getEllipseShape, getFreedrawShape, getPolygonShape, } from "../../utils/geometry/shape";
36
+ import { isPointInShape } from "../../utils/collision";
37
+ import { debounce, distance, getFontString, getNearestScrollableContainer, isInputLike, isToolIcon, isWritableElement, sceneCoordsToViewportCoords, tupleToCoors, viewportCoordsToSceneCoords, wrapEvent, updateObject, updateActiveTool, getShortcutKey, isTransparent, easeToValuesRAF, muteFSAbortError, isTestEnv, easeOut, arrayToMap, updateStable, addEventListener, normalizeEOL, getDateTime, } from "../utils";
36
38
  import { createSrcDoc, embeddableURLValidator, maybeParseEmbedSrc, getEmbedLink, } from "../element/embeddable";
37
39
  import { ContextMenu, CONTEXT_MENU_SEPARATOR, } from "./ContextMenu";
38
40
  import LayerUI from "./LayerUI";
@@ -42,8 +44,7 @@ import { dataURLToFile, generateIdFromFile, getDataURL, getFileFromEvent, ImageU
42
44
  import { getInitializedImageElements, loadHTMLImageElement, normalizeSVG, updateImageCache as _updateImageCache, } from "../element/image";
43
45
  import throttle from "lodash.throttle";
44
46
  import { fileOpen } from "../data/filesystem";
45
- import { bindTextToShapeAfterDuplication, getApproxMinLineHeight, getApproxMinLineWidth, getBoundTextElement, getContainerCenter, getContainerElement, getDefaultLineHeight, getLineHeightInPx, getTextBindableContainerAtPosition, isMeasureTextSupported, isValidTextContainer, } from "../element/textElement";
46
- import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
47
+ import { bindTextToShapeAfterDuplication, getApproxMinLineHeight, getApproxMinLineWidth, getBoundTextElement, getContainerCenter, getContainerElement, getDefaultLineHeight, getLineHeightInPx, isMeasureTextSupported, isValidTextContainer, } from "../element/textElement";
47
48
  import { showHyperlinkTooltip, hideHyperlinkToolip, Hyperlink, } from "../components/hyperlink/Hyperlink";
48
49
  import { isLocalLink, normalizeLink, toValidURL } from "../data/url";
49
50
  import { shouldShowBoundingBox } from "../element/transformHandles";
@@ -82,8 +83,10 @@ import { AnimatedTrail } from "../animated-trail";
82
83
  import { LaserTrails } from "../laser-trails";
83
84
  import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
84
85
  import { getRenderOpacity } from "../renderer/renderElement";
86
+ import { hitElementBoundText, hitElementBoundingBox, hitElementBoundingBoxOnly, hitElementItself, shouldTestInside, } from "../element/collision";
85
87
  import { textWysiwyg } from "../element/textWysiwyg";
86
88
  import { isOverScrollBars } from "../scene/scrollbars";
89
+ import { syncInvalidIndices, syncMovedIndices } from "../fractionalIndex";
87
90
  import { isPointHittingLink, isPointHittingLinkIcon, } from "./hyperlink/helpers";
88
91
  import { getShortcutFromShortcutName } from "../actions/shortcuts";
89
92
  const AppContext = React.createContext(null);
@@ -495,7 +498,7 @@ class App extends React.Component {
495
498
  html, body {
496
499
  width: 100%;
497
500
  height: 100%;
498
- color: ${this.state.theme === "dark" ? "white" : "black"};
501
+ color: ${this.state.theme === THEME.DARK ? "white" : "black"};
499
502
  }
500
503
  body {
501
504
  display: flex;
@@ -650,7 +653,7 @@ class App extends React.Component {
650
653
  ? src.srcdoc(this.state.theme)
651
654
  : undefined, src: src?.type !== "document" ? src?.link ?? "" : undefined,
652
655
  // https://stackoverflow.com/q/18470015
653
- scrolling: "no", referrerPolicy: "no-referrer-when-downgrade", title: "Excalidraw Embedded Content", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture", allowFullScreen: true, sandbox: "allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads" })) })] }) }, el.id));
656
+ scrolling: "no", referrerPolicy: "no-referrer-when-downgrade", title: "Excalidraw Embedded Content", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture", allowFullScreen: true, sandbox: `${src?.sandbox?.allowSameOrigin ? "allow-same-origin" : ""} allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads` })) })] }) }, el.id));
654
657
  }) }));
655
658
  }
656
659
  getFrameNameDOMId = (frameElement) => {
@@ -692,7 +695,7 @@ class App extends React.Component {
692
695
  if (!this.state.frameRendering.enabled || !this.state.frameRendering.name) {
693
696
  return null;
694
697
  }
695
- const isDarkTheme = this.state.theme === "dark";
698
+ const isDarkTheme = this.state.theme === THEME.DARK;
696
699
  let frameIndex = 0;
697
700
  let magicFrameIndex = 0;
698
701
  return this.scene.getNonDeletedFramesLikes().map((f) => {
@@ -1122,7 +1125,7 @@ class App extends React.Component {
1122
1125
  opacity: 100,
1123
1126
  locked: false,
1124
1127
  });
1125
- this.scene.addNewElement(frame);
1128
+ this.scene.insertElement(frame);
1126
1129
  for (const child of selectedElements) {
1127
1130
  mutateElement(child, { frameId: frame.id });
1128
1131
  }
@@ -1608,7 +1611,7 @@ class App extends React.Component {
1608
1611
  gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
1609
1612
  });
1610
1613
  }
1611
- this.excalidrawContainerRef.current?.classList.toggle("theme--dark", this.state.theme === "dark");
1614
+ this.excalidrawContainerRef.current?.classList.toggle("theme--dark", this.state.theme === THEME.DARK);
1612
1615
  if (this.state.editingLinearElement &&
1613
1616
  !this.state.selectedElementIds[this.state.editingLinearElement.elementId]) {
1614
1617
  // defer so that the commitToHistory flag isn't reset via current update
@@ -1636,7 +1639,7 @@ class App extends React.Component {
1636
1639
  multiElement != null &&
1637
1640
  isBindingEnabled(this.state) &&
1638
1641
  isBindingElement(multiElement, false)) {
1639
- maybeBindLinearElement(multiElement, this.state, this.scene, tupleToCoors(LinearElementEditor.getPointAtIndexGlobalCoordinates(multiElement, -1, elementsMap)), elementsMap);
1642
+ maybeBindLinearElement(multiElement, this.state, tupleToCoors(LinearElementEditor.getPointAtIndexGlobalCoordinates(multiElement, -1, elementsMap)), this);
1640
1643
  }
1641
1644
  this.history.record(this.state, elements);
1642
1645
  // Do not notify consumers if we're still loading the scene. Among other
@@ -1894,16 +1897,15 @@ class App extends React.Component {
1894
1897
  }), {
1895
1898
  randomizeSeed: !opts.retainSeed,
1896
1899
  });
1897
- const allElements = [
1898
- ...this.scene.getElementsIncludingDeleted(),
1899
- ...newElements,
1900
- ];
1900
+ const prevElements = this.scene.getElementsIncludingDeleted();
1901
+ const nextElements = [...prevElements, ...newElements];
1902
+ syncMovedIndices(nextElements, arrayToMap(newElements));
1901
1903
  const topLayerFrame = this.getTopLayerFrameAtSceneCoords({ x, y });
1902
1904
  if (topLayerFrame) {
1903
1905
  const eligibleElements = filterElementsEligibleAsFrameChildren(newElements, topLayerFrame);
1904
- addElementsToFrame(allElements, eligibleElements, topLayerFrame);
1906
+ addElementsToFrame(nextElements, eligibleElements, topLayerFrame);
1905
1907
  }
1906
- this.scene.replaceAllElements(allElements);
1908
+ this.scene.replaceAllElements(nextElements);
1907
1909
  newElements.forEach((newElement) => {
1908
1910
  if (isTextElement(newElement) && isBoundToContainer(newElement)) {
1909
1911
  const container = getContainerElement(newElement, this.scene.getElementsMapIncludingDeleted());
@@ -2071,16 +2073,7 @@ class App extends React.Component {
2071
2073
  if (textElements.length === 0) {
2072
2074
  return;
2073
2075
  }
2074
- const frameId = textElements[0].frameId;
2075
- if (frameId) {
2076
- this.scene.insertElementsAtIndex(textElements, this.scene.getElementIndex(frameId));
2077
- }
2078
- else {
2079
- this.scene.replaceAllElements([
2080
- ...this.scene.getElementsIncludingDeleted(),
2081
- ...textElements,
2082
- ]);
2083
- }
2076
+ this.scene.insertElements(textElements);
2084
2077
  this.setState({
2085
2078
  selectedElementIds: makeNextSelectedElementIds(Object.fromEntries(textElements.map((el) => [el.id, true])), this.state),
2086
2079
  });
@@ -2604,7 +2597,7 @@ class App extends React.Component {
2604
2597
  const selectedElements = this.scene.getSelectedElements(this.state);
2605
2598
  const elementsMap = this.scene.getNonDeletedElementsMap();
2606
2599
  isBindingEnabled(this.state)
2607
- ? bindOrUnbindSelectedElements(selectedElements, this.scene.getNonDeletedElements(), elementsMap)
2600
+ ? bindOrUnbindSelectedElements(selectedElements, this)
2608
2601
  : unbindLinearElements(selectedElements, elementsMap);
2609
2602
  this.setState({ suggestedBindings: [] });
2610
2603
  }
@@ -2834,6 +2827,57 @@ class App extends React.Component {
2834
2827
  }
2835
2828
  return null;
2836
2829
  }
2830
+ /**
2831
+ * get the pure geometric shape of an excalidraw element
2832
+ * which is then used for hit detection
2833
+ */
2834
+ getElementShape(element) {
2835
+ switch (element.type) {
2836
+ case "rectangle":
2837
+ case "diamond":
2838
+ case "frame":
2839
+ case "magicframe":
2840
+ case "embeddable":
2841
+ case "image":
2842
+ case "iframe":
2843
+ case "text":
2844
+ case "selection":
2845
+ return getPolygonShape(element);
2846
+ case "arrow":
2847
+ case "line": {
2848
+ const roughShape = ShapeCache.get(element)?.[0] ??
2849
+ ShapeCache.generateElementShape(element, null)[0];
2850
+ const [, , , , cx, cy] = getElementAbsoluteCoords(element, this.scene.getNonDeletedElementsMap());
2851
+ return shouldTestInside(element)
2852
+ ? getClosedCurveShape(element, roughShape, [element.x, element.y], element.angle, [cx, cy])
2853
+ : getCurveShape(roughShape, [element.x, element.y], element.angle, [
2854
+ cx,
2855
+ cy,
2856
+ ]);
2857
+ }
2858
+ case "ellipse":
2859
+ return getEllipseShape(element);
2860
+ case "freedraw": {
2861
+ const [, , , , cx, cy] = getElementAbsoluteCoords(element, this.scene.getNonDeletedElementsMap());
2862
+ return getFreedrawShape(element, [cx, cy], shouldTestInside(element));
2863
+ }
2864
+ }
2865
+ }
2866
+ getBoundTextShape(element) {
2867
+ const boundTextElement = getBoundTextElement(element, this.scene.getNonDeletedElementsMap());
2868
+ if (boundTextElement) {
2869
+ if (element.type === "arrow") {
2870
+ return this.getElementShape({
2871
+ ...boundTextElement,
2872
+ // arrow's bound text accurate position is not stored in the element's property
2873
+ // but rather calculated and returned from the following static method
2874
+ ...LinearElementEditor.getBoundTextElementPosition(element, boundTextElement, this.scene.getNonDeletedElementsMap()),
2875
+ });
2876
+ }
2877
+ return this.getElementShape(boundTextElement);
2878
+ }
2879
+ return null;
2880
+ }
2837
2881
  getElementAtPosition(x, y, opts) {
2838
2882
  const allHitElements = this.getElementsAtPosition(x, y, opts?.includeBoundTextElement, opts?.includeLockedElements);
2839
2883
  if (allHitElements.length > 1) {
@@ -2847,9 +2891,9 @@ class App extends React.Component {
2847
2891
  const elementWithHighestZIndex = allHitElements[allHitElements.length - 1];
2848
2892
  // If we're hitting element with highest z-index only on its bounding box
2849
2893
  // while also hitting other element figure, the latter should be considered.
2850
- return isHittingElementBoundingBoxWithoutHittingElement(elementWithHighestZIndex, this.state, this.frameNameBoundsCache, x, y, this.scene.getNonDeletedElementsMap())
2851
- ? allHitElements[allHitElements.length - 2]
2852
- : elementWithHighestZIndex;
2894
+ return isPointInShape([x, y], this.getElementShape(elementWithHighestZIndex))
2895
+ ? elementWithHighestZIndex
2896
+ : allHitElements[allHitElements.length - 2];
2853
2897
  }
2854
2898
  if (allHitElements.length === 1) {
2855
2899
  return allHitElements[0];
@@ -2857,15 +2901,17 @@ class App extends React.Component {
2857
2901
  return null;
2858
2902
  }
2859
2903
  getElementsAtPosition(x, y, includeBoundTextElement = false, includeLockedElements = false) {
2860
- const elements = includeBoundTextElement && includeLockedElements
2904
+ const iframeLikes = [];
2905
+ const elementsMap = this.scene.getNonDeletedElementsMap();
2906
+ const elements = (includeBoundTextElement && includeLockedElements
2861
2907
  ? this.scene.getNonDeletedElements()
2862
2908
  : this.scene
2863
2909
  .getNonDeletedElements()
2864
2910
  .filter((element) => (includeLockedElements || !element.locked) &&
2865
2911
  (includeBoundTextElement ||
2866
- !(isTextElement(element) && element.containerId)));
2867
- const elementsMap = this.scene.getNonDeletedElementsMap();
2868
- return getElementsAtPosition(elements, (element) => hitTest(element, this.state, this.frameNameBoundsCache, x, y, elementsMap)).filter((element) => {
2912
+ !(isTextElement(element) && element.containerId))))
2913
+ .filter((el) => this.hitElement(x, y, el))
2914
+ .filter((element) => {
2869
2915
  // hitting a frame's element from outside the frame is not considered a hit
2870
2916
  const containingFrame = getContainingFrame(element, elementsMap);
2871
2917
  return containingFrame &&
@@ -2873,8 +2919,80 @@ class App extends React.Component {
2873
2919
  this.state.frameRendering.clip
2874
2920
  ? isCursorInFrame({ x, y }, containingFrame, elementsMap)
2875
2921
  : true;
2922
+ })
2923
+ .filter((el) => {
2924
+ // The parameter elements comes ordered from lower z-index to higher.
2925
+ // We want to preserve that order on the returned array.
2926
+ // Exception being embeddables which should be on top of everything else in
2927
+ // terms of hit testing.
2928
+ if (isIframeElement(el)) {
2929
+ iframeLikes.push(el);
2930
+ return false;
2931
+ }
2932
+ return true;
2933
+ })
2934
+ .concat(iframeLikes);
2935
+ return elements;
2936
+ }
2937
+ getHitThreshold() {
2938
+ return 10 / this.state.zoom.value;
2939
+ }
2940
+ hitElement(x, y, element, considerBoundingBox = true) {
2941
+ // if the element is selected, then hit test is done against its bounding box
2942
+ if (considerBoundingBox &&
2943
+ this.state.selectedElementIds[element.id] &&
2944
+ shouldShowBoundingBox([element], this.state)) {
2945
+ return hitElementBoundingBox(x, y, element, this.scene.getNonDeletedElementsMap(), this.getHitThreshold());
2946
+ }
2947
+ // take bound text element into consideration for hit collision as well
2948
+ const hitBoundTextOfElement = hitElementBoundText(x, y, this.getBoundTextShape(element));
2949
+ if (hitBoundTextOfElement) {
2950
+ return true;
2951
+ }
2952
+ return hitElementItself({
2953
+ x,
2954
+ y,
2955
+ element,
2956
+ shape: this.getElementShape(element),
2957
+ threshold: this.getHitThreshold(),
2958
+ frameNameBound: isFrameLikeElement(element)
2959
+ ? this.frameNameBoundsCache.get(element)
2960
+ : null,
2876
2961
  });
2877
2962
  }
2963
+ getTextBindableContainerAtPosition(x, y) {
2964
+ const elements = this.scene.getNonDeletedElements();
2965
+ const selectedElements = this.scene.getSelectedElements(this.state);
2966
+ if (selectedElements.length === 1) {
2967
+ return isTextBindableContainer(selectedElements[0], false)
2968
+ ? selectedElements[0]
2969
+ : null;
2970
+ }
2971
+ let hitElement = null;
2972
+ // We need to do hit testing from front (end of the array) to back (beginning of the array)
2973
+ for (let index = elements.length - 1; index >= 0; --index) {
2974
+ if (elements[index].isDeleted) {
2975
+ continue;
2976
+ }
2977
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(elements[index], this.scene.getNonDeletedElementsMap());
2978
+ if (isArrowElement(elements[index]) &&
2979
+ hitElementItself({
2980
+ x,
2981
+ y,
2982
+ element: elements[index],
2983
+ shape: this.getElementShape(elements[index]),
2984
+ threshold: this.getHitThreshold(),
2985
+ })) {
2986
+ hitElement = elements[index];
2987
+ break;
2988
+ }
2989
+ else if (x1 < x && x < x2 && y1 < y && y < y2) {
2990
+ hitElement = elements[index];
2991
+ break;
2992
+ }
2993
+ }
2994
+ return isTextBindableContainer(hitElement, false) ? hitElement : null;
2995
+ }
2878
2996
  startTextEditing = ({ sceneX, sceneY, insertAtParentCenter = true, container, }) => {
2879
2997
  let shouldBindToContainer = false;
2880
2998
  let parentCenterPosition = insertAtParentCenter &&
@@ -2973,7 +3091,7 @@ class App extends React.Component {
2973
3091
  this.scene.insertElementAtIndex(element, containerIndex + 1);
2974
3092
  }
2975
3093
  else {
2976
- this.scene.addNewElement(element);
3094
+ this.scene.insertElement(element);
2977
3095
  }
2978
3096
  }
2979
3097
  this.setState({
@@ -3032,11 +3150,17 @@ class App extends React.Component {
3032
3150
  });
3033
3151
  return;
3034
3152
  }
3035
- const container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
3153
+ const container = this.getTextBindableContainerAtPosition(sceneX, sceneY);
3036
3154
  if (container) {
3037
3155
  if (hasBoundTextElement(container) ||
3038
3156
  !isTransparent(container.backgroundColor) ||
3039
- isHittingElementNotConsideringBoundingBox(container, this.state, this.frameNameBoundsCache, [sceneX, sceneY], this.scene.getNonDeletedElementsMap())) {
3157
+ hitElementItself({
3158
+ x: sceneX,
3159
+ y: sceneY,
3160
+ element: container,
3161
+ shape: this.getElementShape(container),
3162
+ threshold: this.getHitThreshold(),
3163
+ })) {
3040
3164
  const midPoint = getContainerCenter(container, this.state, this.scene.getNonDeletedElementsMap());
3041
3165
  sceneX = midPoint.x;
3042
3166
  sceneY = midPoint.y;
@@ -3091,7 +3215,7 @@ class App extends React.Component {
3091
3215
  }
3092
3216
  if (!customEvent?.defaultPrevented) {
3093
3217
  const target = isLocalLink(url) ? "_self" : "_blank";
3094
- const newWindow = window.open(undefined, target, "noopener noreferrer");
3218
+ const newWindow = window.open(undefined, target);
3095
3219
  // https://mathiasbynens.github.io/rel-noopener/
3096
3220
  if (newWindow) {
3097
3221
  newWindow.opener = null;
@@ -3419,7 +3543,7 @@ class App extends React.Component {
3419
3543
  }
3420
3544
  };
3421
3545
  const distance = distance2d(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y, scenePointer.x, scenePointer.y);
3422
- const threshold = 10 / this.state.zoom.value;
3546
+ const threshold = this.getHitThreshold();
3423
3547
  const point = { ...pointerDownState.lastCoords };
3424
3548
  let samplingInterval = 0;
3425
3549
  while (samplingInterval <= distance) {
@@ -3465,14 +3589,18 @@ class App extends React.Component {
3465
3589
  handleHoverSelectedLinearElement(linearElementEditor, scenePointerX, scenePointerY) {
3466
3590
  const elementsMap = this.scene.getNonDeletedElementsMap();
3467
3591
  const element = LinearElementEditor.getElement(linearElementEditor.elementId, elementsMap);
3468
- const boundTextElement = getBoundTextElement(element, elementsMap);
3469
3592
  if (!element) {
3470
3593
  return;
3471
3594
  }
3472
3595
  if (this.state.selectedLinearElement) {
3473
3596
  let hoverPointIndex = -1;
3474
3597
  let segmentMidPointHoveredCoords = null;
3475
- if (isHittingElementNotConsideringBoundingBox(element, this.state, this.frameNameBoundsCache, [scenePointerX, scenePointerY], elementsMap)) {
3598
+ if (hitElementItself({
3599
+ x: scenePointerX,
3600
+ y: scenePointerY,
3601
+ element,
3602
+ shape: this.getElementShape(element),
3603
+ })) {
3476
3604
  hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(element, elementsMap, this.state.zoom, scenePointerX, scenePointerY);
3477
3605
  segmentMidPointHoveredCoords =
3478
3606
  LinearElementEditor.getSegmentMidpointHitCoords(linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state, this.scene.getNonDeletedElementsMap());
@@ -3483,12 +3611,7 @@ class App extends React.Component {
3483
3611
  setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3484
3612
  }
3485
3613
  }
3486
- else if (shouldShowBoundingBox([element], this.state) &&
3487
- isHittingElementBoundingBoxWithoutHittingElement(element, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, elementsMap)) {
3488
- setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3489
- }
3490
- else if (boundTextElement &&
3491
- hitTest(boundTextElement, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, this.scene.getNonDeletedElementsMap())) {
3614
+ else if (this.hitElement(scenePointerX, scenePointerY, element)) {
3492
3615
  setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3493
3616
  }
3494
3617
  if (this.state.selectedLinearElement.hoverPointIndex !== hoverPointIndex) {
@@ -4019,7 +4142,7 @@ class App extends React.Component {
4019
4142
  else {
4020
4143
  if (this.state.selectedLinearElement) {
4021
4144
  const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
4022
- const ret = LinearElementEditor.handlePointerDown(event, this.state, this.history, pointerDownState.origin, linearElementEditor, this.scene.getNonDeletedElements(), elementsMap);
4145
+ const ret = LinearElementEditor.handlePointerDown(event, this.state, this.history, pointerDownState.origin, linearElementEditor, this);
4023
4146
  if (ret.hitElement) {
4024
4147
  pointerDownState.hit.element = ret.hitElement;
4025
4148
  }
@@ -4180,7 +4303,7 @@ class App extends React.Component {
4180
4303
  return false;
4181
4304
  }
4182
4305
  // How many pixels off the shape boundary we still consider a hit
4183
- const threshold = 10 / this.state.zoom.value;
4306
+ const threshold = this.getHitThreshold();
4184
4307
  const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
4185
4308
  return (point.x > x1 - threshold &&
4186
4309
  point.x < x2 + threshold &&
@@ -4200,7 +4323,7 @@ class App extends React.Component {
4200
4323
  includeBoundTextElement: true,
4201
4324
  });
4202
4325
  // FIXME
4203
- let container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
4326
+ let container = this.getTextBindableContainerAtPosition(sceneX, sceneY);
4204
4327
  if (hasBoundTextElement(element)) {
4205
4328
  container = element;
4206
4329
  sceneX = element.x + element.width / 2;
@@ -4258,8 +4381,8 @@ class App extends React.Component {
4258
4381
  points: [[0, 0]],
4259
4382
  pressures,
4260
4383
  });
4261
- const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
4262
- this.scene.addNewElement(element);
4384
+ const boundElement = getHoveredElementForBinding(pointerDownState.origin, this);
4385
+ this.scene.insertElement(element);
4263
4386
  this.setState({
4264
4387
  draggingElement: element,
4265
4388
  editingElement: element,
@@ -4287,10 +4410,7 @@ class App extends React.Component {
4287
4410
  width,
4288
4411
  height,
4289
4412
  });
4290
- this.scene.replaceAllElements([
4291
- ...this.scene.getElementsIncludingDeleted(),
4292
- element,
4293
- ]);
4413
+ this.scene.insertElement(element);
4294
4414
  return element;
4295
4415
  };
4296
4416
  //create rectangle element with youtube top left on nearest grid point width / hight 640/360
@@ -4325,10 +4445,7 @@ class App extends React.Component {
4325
4445
  height: embedLink.intrinsicSize.h,
4326
4446
  link,
4327
4447
  });
4328
- this.scene.replaceAllElements([
4329
- ...this.scene.getElementsIncludingDeleted(),
4330
- element,
4331
- ]);
4448
+ this.scene.insertElement(element);
4332
4449
  return element;
4333
4450
  };
4334
4451
  createImageElement = ({ sceneX, sceneY, addToFrameUnderCursor = true, }) => {
@@ -4436,8 +4553,8 @@ class App extends React.Component {
4436
4553
  mutateElement(element, {
4437
4554
  points: [...element.points, [0, 0]],
4438
4555
  });
4439
- const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
4440
- this.scene.addNewElement(element);
4556
+ const boundElement = getHoveredElementForBinding(pointerDownState.origin, this);
4557
+ this.scene.insertElement(element);
4441
4558
  this.setState({
4442
4559
  draggingElement: element,
4443
4560
  editingElement: element,
@@ -4497,7 +4614,7 @@ class App extends React.Component {
4497
4614
  });
4498
4615
  }
4499
4616
  else {
4500
- this.scene.addNewElement(element);
4617
+ this.scene.insertElement(element);
4501
4618
  this.setState({
4502
4619
  multiElement: null,
4503
4620
  draggingElement: element,
@@ -4519,10 +4636,7 @@ class App extends React.Component {
4519
4636
  const frame = type === TOOL_TYPE.magicframe
4520
4637
  ? newMagicFrameElement(constructorOpts)
4521
4638
  : newFrameElement(constructorOpts);
4522
- this.scene.replaceAllElements([
4523
- ...this.scene.getElementsIncludingDeleted(),
4524
- frame,
4525
- ]);
4639
+ this.scene.insertElement(frame);
4526
4640
  this.setState({
4527
4641
  multiElement: null,
4528
4642
  draggingElement: frame,
@@ -4780,6 +4894,7 @@ class App extends React.Component {
4780
4894
  }
4781
4895
  }
4782
4896
  const nextSceneElements = [...nextElements, ...elementsToAppend];
4897
+ syncMovedIndices(nextSceneElements, arrayToMap(elementsToAppend));
4783
4898
  bindTextToShapeAfterDuplication(nextElements, elementsToAppend, oldIdToDuplicatedId);
4784
4899
  fixBindingsAfterDuplication(nextSceneElements, elementsToAppend, oldIdToDuplicatedId, "duplicatesServeAsOld");
4785
4900
  bindElementsToFramesAfterDuplication(nextSceneElements, elementsToAppend, oldIdToDuplicatedId);
@@ -4971,7 +5086,7 @@ class App extends React.Component {
4971
5086
  this.actionManager.executeAction(actionFinalize);
4972
5087
  }
4973
5088
  else {
4974
- const editingLinearElement = LinearElementEditor.handlePointerUp(childEvent, this.state.editingLinearElement, this.state, this.scene.getNonDeletedElements(), elementsMap);
5089
+ const editingLinearElement = LinearElementEditor.handlePointerUp(childEvent, this.state.editingLinearElement, this.state, this);
4975
5090
  if (editingLinearElement !== this.state.editingLinearElement) {
4976
5091
  this.setState({
4977
5092
  editingLinearElement,
@@ -4990,7 +5105,7 @@ class App extends React.Component {
4990
5105
  }
4991
5106
  }
4992
5107
  else {
4993
- const linearElementEditor = LinearElementEditor.handlePointerUp(childEvent, this.state.selectedLinearElement, this.state, this.scene.getNonDeletedElements(), elementsMap);
5108
+ const linearElementEditor = LinearElementEditor.handlePointerUp(childEvent, this.state.selectedLinearElement, this.state, this);
4994
5109
  const { startBindingElement, endBindingElement } = linearElementEditor;
4995
5110
  const element = this.scene.getElement(linearElementEditor.elementId);
4996
5111
  if (isBindingElement(element)) {
@@ -5082,7 +5197,7 @@ class App extends React.Component {
5082
5197
  else if (pointerDownState.drag.hasOccurred && !multiElement) {
5083
5198
  if (isBindingEnabled(this.state) &&
5084
5199
  isBindingElement(draggingElement, false)) {
5085
- maybeBindLinearElement(draggingElement, this.state, this.scene, pointerCoords, elementsMap);
5200
+ maybeBindLinearElement(draggingElement, this.state, pointerCoords, this);
5086
5201
  }
5087
5202
  this.setState({ suggestedBindings: [], startBoundElement: null });
5088
5203
  if (!activeTool.locked) {
@@ -5356,10 +5471,23 @@ class App extends React.Component {
5356
5471
  }));
5357
5472
  }
5358
5473
  }
5359
- if (!pointerDownState.drag.hasOccurred &&
5474
+ if (
5475
+ // not dragged
5476
+ !pointerDownState.drag.hasOccurred &&
5477
+ // not resized
5360
5478
  !this.state.isResizing &&
5479
+ // only hitting the bounding box of the previous hit element
5361
5480
  ((hitElement &&
5362
- isHittingElementBoundingBoxWithoutHittingElement(hitElement, this.state, this.frameNameBoundsCache, pointerDownState.origin.x, pointerDownState.origin.y, this.scene.getNonDeletedElementsMap())) ||
5481
+ hitElementBoundingBoxOnly({
5482
+ x: pointerDownState.origin.x,
5483
+ y: pointerDownState.origin.y,
5484
+ element: hitElement,
5485
+ shape: this.getElementShape(hitElement),
5486
+ threshold: this.getHitThreshold(),
5487
+ frameNameBound: isFrameLikeElement(hitElement)
5488
+ ? this.frameNameBoundsCache.get(hitElement)
5489
+ : null,
5490
+ }, elementsMap)) ||
5363
5491
  (!hitElement &&
5364
5492
  pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements))) {
5365
5493
  if (this.state.editingLinearElement) {
@@ -5374,6 +5502,8 @@ class App extends React.Component {
5374
5502
  activeEmbeddable: null,
5375
5503
  });
5376
5504
  }
5505
+ // reset cursor
5506
+ setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
5377
5507
  return;
5378
5508
  }
5379
5509
  if (!activeTool.locked &&
@@ -5396,8 +5526,8 @@ class App extends React.Component {
5396
5526
  }
5397
5527
  if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
5398
5528
  isBindingEnabled(this.state)
5399
- ? bindOrUnbindSelectedElements(this.scene.getSelectedElements(this.state), this.scene.getNonDeletedElements(), elementsMap)
5400
- : unbindLinearElements(this.scene.getSelectedElements(this.state), elementsMap);
5529
+ ? bindOrUnbindSelectedElements(this.scene.getSelectedElements(this.state), this)
5530
+ : unbindLinearElements(this.scene.getNonDeletedElements(), elementsMap);
5401
5531
  }
5402
5532
  if (activeTool.type === "laser") {
5403
5533
  this.laserTrails.endPath();
@@ -5551,7 +5681,7 @@ class App extends React.Component {
5551
5681
  this.setState({ errorMessage: t("errors.imageToolNotSupported") });
5552
5682
  return;
5553
5683
  }
5554
- this.scene.addNewElement(imageElement);
5684
+ this.scene.insertElement(imageElement);
5555
5685
  try {
5556
5686
  return await this.initializeImage({
5557
5687
  imageFile,
@@ -5740,7 +5870,7 @@ class App extends React.Component {
5740
5870
  }
5741
5871
  };
5742
5872
  maybeSuggestBindingAtCursor = (pointerCoords) => {
5743
- const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5873
+ const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this);
5744
5874
  this.setState({
5745
5875
  suggestedBindings: hoveredBindableElement != null ? [hoveredBindableElement] : [],
5746
5876
  });
@@ -5755,7 +5885,7 @@ class App extends React.Component {
5755
5885
  return;
5756
5886
  }
5757
5887
  const suggestedBindings = pointerCoords.reduce((acc, coords) => {
5758
- const hoveredBindableElement = getHoveredElementForBinding(coords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5888
+ const hoveredBindableElement = getHoveredElementForBinding(coords, this);
5759
5889
  if (hoveredBindableElement != null &&
5760
5890
  !isLinearElementSimpleAndAlreadyBound(linearElement, oppositeBindingBoundElement?.id, hoveredBindableElement)) {
5761
5891
  acc.push(hoveredBindableElement);
@@ -5768,7 +5898,7 @@ class App extends React.Component {
5768
5898
  if (selectedElements.length > 50) {
5769
5899
  return;
5770
5900
  }
5771
- const suggestedBindings = getEligibleElementsForBinding(selectedElements, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5901
+ const suggestedBindings = getEligibleElementsForBinding(selectedElements, this);
5772
5902
  this.setState({ suggestedBindings });
5773
5903
  }
5774
5904
  clearSelection(hitElement) {
@@ -6320,7 +6450,7 @@ export const createTestHook = () => {
6320
6450
  return this.app?.scene.getElementsIncludingDeleted();
6321
6451
  },
6322
6452
  set(elements) {
6323
- return this.app?.scene.replaceAllElements(elements);
6453
+ return this.app?.scene.replaceAllElements(syncInvalidIndices(elements));
6324
6454
  },
6325
6455
  },
6326
6456
  });