@excalidraw/excalidraw 0.17.1-1d71f84 → 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 (111) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/browser/dev/excalidraw-assets-dev/{chunk-AK7SWNLN.js → chunk-23CKV3WP.js} +4 -2
  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-5TCZHGGJ.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 +770 -585
  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-CTYINSWT.js → chunk-SK23VHAR.js} +2 -2
  14. package/dist/browser/prod/excalidraw-assets/{en-LROPV2RN.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-II4GK66F.json → dev/en-CVBEBUBY.json} +3 -1
  19. package/dist/dev/index.css +20 -0
  20. package/dist/dev/index.css.map +2 -2
  21. package/dist/dev/index.js +2383 -2074
  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/actions/shortcuts.js +1 -1
  35. package/dist/excalidraw/analytics.js +1 -1
  36. package/dist/excalidraw/components/App.d.ts +13 -3
  37. package/dist/excalidraw/components/App.js +212 -83
  38. package/dist/excalidraw/components/CommandPalette/CommandPalette.js +24 -10
  39. package/dist/excalidraw/components/DarkModeToggle.js +3 -1
  40. package/dist/excalidraw/components/HelpDialog.js +8 -6
  41. package/dist/excalidraw/components/RadioGroup.d.ts +2 -1
  42. package/dist/excalidraw/components/RadioGroup.js +1 -1
  43. package/dist/excalidraw/components/TTDDialog/MermaidToExcalidraw.js +6 -2
  44. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.d.ts +18 -0
  45. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.js +9 -0
  46. package/dist/excalidraw/components/hyperlink/Hyperlink.js +3 -3
  47. package/dist/excalidraw/components/hyperlink/helpers.js +2 -3
  48. package/dist/excalidraw/components/icons.d.ts +3 -0
  49. package/dist/excalidraw/components/icons.js +5 -1
  50. package/dist/excalidraw/components/main-menu/DefaultItems.d.ts +12 -2
  51. package/dist/excalidraw/components/main-menu/DefaultItems.js +38 -7
  52. package/dist/excalidraw/constants.d.ts +0 -3
  53. package/dist/excalidraw/constants.js +0 -3
  54. package/dist/excalidraw/data/magic.js +2 -1
  55. package/dist/excalidraw/data/reconcile.d.ts +6 -0
  56. package/dist/excalidraw/data/reconcile.js +49 -0
  57. package/dist/excalidraw/data/restore.d.ts +3 -3
  58. package/dist/excalidraw/data/restore.js +5 -6
  59. package/dist/excalidraw/data/transform.d.ts +1 -1
  60. package/dist/excalidraw/data/transform.js +12 -3
  61. package/dist/excalidraw/element/binding.d.ts +22 -9
  62. package/dist/excalidraw/element/binding.js +403 -26
  63. package/dist/excalidraw/element/bounds.d.ts +0 -1
  64. package/dist/excalidraw/element/bounds.js +0 -3
  65. package/dist/excalidraw/element/collision.d.ts +14 -19
  66. package/dist/excalidraw/element/collision.js +36 -713
  67. package/dist/excalidraw/element/embeddable.js +18 -43
  68. package/dist/excalidraw/element/index.d.ts +0 -1
  69. package/dist/excalidraw/element/index.js +0 -1
  70. package/dist/excalidraw/element/linearElementEditor.d.ts +10 -10
  71. package/dist/excalidraw/element/linearElementEditor.js +6 -4
  72. package/dist/excalidraw/element/newElement.d.ts +1 -1
  73. package/dist/excalidraw/element/newElement.js +2 -1
  74. package/dist/excalidraw/element/textElement.d.ts +0 -1
  75. package/dist/excalidraw/element/textElement.js +0 -30
  76. package/dist/excalidraw/element/types.d.ts +17 -2
  77. package/dist/excalidraw/errors.d.ts +3 -0
  78. package/dist/excalidraw/errors.js +3 -0
  79. package/dist/excalidraw/fractionalIndex.d.ts +40 -0
  80. package/dist/excalidraw/fractionalIndex.js +241 -0
  81. package/dist/excalidraw/frame.d.ts +1 -1
  82. package/dist/excalidraw/hooks/useCreatePortalContainer.js +2 -1
  83. package/dist/excalidraw/locales/en.json +3 -1
  84. package/dist/excalidraw/renderer/helpers.js +2 -2
  85. package/dist/excalidraw/renderer/interactiveScene.js +1 -1
  86. package/dist/excalidraw/renderer/renderElement.js +3 -3
  87. package/dist/excalidraw/renderer/renderSnaps.js +2 -1
  88. package/dist/excalidraw/scene/Scene.d.ts +7 -6
  89. package/dist/excalidraw/scene/Scene.js +28 -13
  90. package/dist/excalidraw/scene/export.js +4 -3
  91. package/dist/excalidraw/types.d.ts +4 -3
  92. package/dist/excalidraw/utils.d.ts +1 -0
  93. package/dist/excalidraw/utils.js +1 -0
  94. package/dist/excalidraw/zindex.d.ts +2 -2
  95. package/dist/excalidraw/zindex.js +9 -13
  96. package/dist/{dev/en-II4GK66F.json → prod/en-CVBEBUBY.json} +3 -1
  97. package/dist/prod/index.css +1 -1
  98. package/dist/prod/index.js +36 -36
  99. package/dist/utils/collision.d.ts +4 -0
  100. package/dist/utils/collision.js +48 -0
  101. package/dist/utils/geometry/geometry.d.ts +71 -0
  102. package/dist/utils/geometry/geometry.js +674 -0
  103. package/dist/utils/geometry/shape.d.ts +55 -0
  104. package/dist/utils/geometry/shape.js +149 -0
  105. package/package.json +2 -1
  106. package/dist/browser/dev/excalidraw-assets-dev/chunk-AK7SWNLN.js.map +0 -7
  107. package/dist/browser/dev/excalidraw-assets-dev/chunk-RWZVJAQU.js.map +0 -7
  108. package/dist/browser/prod/excalidraw-assets/chunk-LL4GORAM.js +0 -55
  109. package/dist/browser/prod/excalidraw-assets/image-EFCJDJH3.js +0 -1
  110. /package/dist/browser/dev/excalidraw-assets-dev/{en-5TCZHGGJ.js.map → en-W7TECCRB.js.map} +0 -0
  111. /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
  });
@@ -2355,8 +2348,7 @@ class App extends React.Component {
2355
2348
  !event.altKey) {
2356
2349
  this.setToast({
2357
2350
  message: t("commandPalette.shortcutHint", {
2358
- shortcutOne: getShortcutFromShortcutName("commandPalette"),
2359
- shortcutTwo: getShortcutFromShortcutName("commandPalette", 1),
2351
+ shortcut: getShortcutFromShortcutName("commandPalette"),
2360
2352
  }),
2361
2353
  });
2362
2354
  event.preventDefault();
@@ -2605,7 +2597,7 @@ class App extends React.Component {
2605
2597
  const selectedElements = this.scene.getSelectedElements(this.state);
2606
2598
  const elementsMap = this.scene.getNonDeletedElementsMap();
2607
2599
  isBindingEnabled(this.state)
2608
- ? bindOrUnbindSelectedElements(selectedElements, this.scene.getNonDeletedElements(), elementsMap)
2600
+ ? bindOrUnbindSelectedElements(selectedElements, this)
2609
2601
  : unbindLinearElements(selectedElements, elementsMap);
2610
2602
  this.setState({ suggestedBindings: [] });
2611
2603
  }
@@ -2835,6 +2827,57 @@ class App extends React.Component {
2835
2827
  }
2836
2828
  return null;
2837
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
+ }
2838
2881
  getElementAtPosition(x, y, opts) {
2839
2882
  const allHitElements = this.getElementsAtPosition(x, y, opts?.includeBoundTextElement, opts?.includeLockedElements);
2840
2883
  if (allHitElements.length > 1) {
@@ -2848,9 +2891,9 @@ class App extends React.Component {
2848
2891
  const elementWithHighestZIndex = allHitElements[allHitElements.length - 1];
2849
2892
  // If we're hitting element with highest z-index only on its bounding box
2850
2893
  // while also hitting other element figure, the latter should be considered.
2851
- return isHittingElementBoundingBoxWithoutHittingElement(elementWithHighestZIndex, this.state, this.frameNameBoundsCache, x, y, this.scene.getNonDeletedElementsMap())
2852
- ? allHitElements[allHitElements.length - 2]
2853
- : elementWithHighestZIndex;
2894
+ return isPointInShape([x, y], this.getElementShape(elementWithHighestZIndex))
2895
+ ? elementWithHighestZIndex
2896
+ : allHitElements[allHitElements.length - 2];
2854
2897
  }
2855
2898
  if (allHitElements.length === 1) {
2856
2899
  return allHitElements[0];
@@ -2858,15 +2901,17 @@ class App extends React.Component {
2858
2901
  return null;
2859
2902
  }
2860
2903
  getElementsAtPosition(x, y, includeBoundTextElement = false, includeLockedElements = false) {
2861
- const elements = includeBoundTextElement && includeLockedElements
2904
+ const iframeLikes = [];
2905
+ const elementsMap = this.scene.getNonDeletedElementsMap();
2906
+ const elements = (includeBoundTextElement && includeLockedElements
2862
2907
  ? this.scene.getNonDeletedElements()
2863
2908
  : this.scene
2864
2909
  .getNonDeletedElements()
2865
2910
  .filter((element) => (includeLockedElements || !element.locked) &&
2866
2911
  (includeBoundTextElement ||
2867
- !(isTextElement(element) && element.containerId)));
2868
- const elementsMap = this.scene.getNonDeletedElementsMap();
2869
- 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) => {
2870
2915
  // hitting a frame's element from outside the frame is not considered a hit
2871
2916
  const containingFrame = getContainingFrame(element, elementsMap);
2872
2917
  return containingFrame &&
@@ -2874,8 +2919,80 @@ class App extends React.Component {
2874
2919
  this.state.frameRendering.clip
2875
2920
  ? isCursorInFrame({ x, y }, containingFrame, elementsMap)
2876
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,
2877
2961
  });
2878
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
+ }
2879
2996
  startTextEditing = ({ sceneX, sceneY, insertAtParentCenter = true, container, }) => {
2880
2997
  let shouldBindToContainer = false;
2881
2998
  let parentCenterPosition = insertAtParentCenter &&
@@ -2974,7 +3091,7 @@ class App extends React.Component {
2974
3091
  this.scene.insertElementAtIndex(element, containerIndex + 1);
2975
3092
  }
2976
3093
  else {
2977
- this.scene.addNewElement(element);
3094
+ this.scene.insertElement(element);
2978
3095
  }
2979
3096
  }
2980
3097
  this.setState({
@@ -3033,11 +3150,17 @@ class App extends React.Component {
3033
3150
  });
3034
3151
  return;
3035
3152
  }
3036
- const container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
3153
+ const container = this.getTextBindableContainerAtPosition(sceneX, sceneY);
3037
3154
  if (container) {
3038
3155
  if (hasBoundTextElement(container) ||
3039
3156
  !isTransparent(container.backgroundColor) ||
3040
- 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
+ })) {
3041
3164
  const midPoint = getContainerCenter(container, this.state, this.scene.getNonDeletedElementsMap());
3042
3165
  sceneX = midPoint.x;
3043
3166
  sceneY = midPoint.y;
@@ -3092,7 +3215,7 @@ class App extends React.Component {
3092
3215
  }
3093
3216
  if (!customEvent?.defaultPrevented) {
3094
3217
  const target = isLocalLink(url) ? "_self" : "_blank";
3095
- const newWindow = window.open(undefined, target, "noopener noreferrer");
3218
+ const newWindow = window.open(undefined, target);
3096
3219
  // https://mathiasbynens.github.io/rel-noopener/
3097
3220
  if (newWindow) {
3098
3221
  newWindow.opener = null;
@@ -3420,7 +3543,7 @@ class App extends React.Component {
3420
3543
  }
3421
3544
  };
3422
3545
  const distance = distance2d(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y, scenePointer.x, scenePointer.y);
3423
- const threshold = 10 / this.state.zoom.value;
3546
+ const threshold = this.getHitThreshold();
3424
3547
  const point = { ...pointerDownState.lastCoords };
3425
3548
  let samplingInterval = 0;
3426
3549
  while (samplingInterval <= distance) {
@@ -3466,14 +3589,18 @@ class App extends React.Component {
3466
3589
  handleHoverSelectedLinearElement(linearElementEditor, scenePointerX, scenePointerY) {
3467
3590
  const elementsMap = this.scene.getNonDeletedElementsMap();
3468
3591
  const element = LinearElementEditor.getElement(linearElementEditor.elementId, elementsMap);
3469
- const boundTextElement = getBoundTextElement(element, elementsMap);
3470
3592
  if (!element) {
3471
3593
  return;
3472
3594
  }
3473
3595
  if (this.state.selectedLinearElement) {
3474
3596
  let hoverPointIndex = -1;
3475
3597
  let segmentMidPointHoveredCoords = null;
3476
- 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
+ })) {
3477
3604
  hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(element, elementsMap, this.state.zoom, scenePointerX, scenePointerY);
3478
3605
  segmentMidPointHoveredCoords =
3479
3606
  LinearElementEditor.getSegmentMidpointHitCoords(linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state, this.scene.getNonDeletedElementsMap());
@@ -3484,12 +3611,7 @@ class App extends React.Component {
3484
3611
  setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3485
3612
  }
3486
3613
  }
3487
- else if (shouldShowBoundingBox([element], this.state) &&
3488
- isHittingElementBoundingBoxWithoutHittingElement(element, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, elementsMap)) {
3489
- setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3490
- }
3491
- else if (boundTextElement &&
3492
- hitTest(boundTextElement, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, this.scene.getNonDeletedElementsMap())) {
3614
+ else if (this.hitElement(scenePointerX, scenePointerY, element)) {
3493
3615
  setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3494
3616
  }
3495
3617
  if (this.state.selectedLinearElement.hoverPointIndex !== hoverPointIndex) {
@@ -4020,7 +4142,7 @@ class App extends React.Component {
4020
4142
  else {
4021
4143
  if (this.state.selectedLinearElement) {
4022
4144
  const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
4023
- 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);
4024
4146
  if (ret.hitElement) {
4025
4147
  pointerDownState.hit.element = ret.hitElement;
4026
4148
  }
@@ -4181,7 +4303,7 @@ class App extends React.Component {
4181
4303
  return false;
4182
4304
  }
4183
4305
  // How many pixels off the shape boundary we still consider a hit
4184
- const threshold = 10 / this.state.zoom.value;
4306
+ const threshold = this.getHitThreshold();
4185
4307
  const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
4186
4308
  return (point.x > x1 - threshold &&
4187
4309
  point.x < x2 + threshold &&
@@ -4201,7 +4323,7 @@ class App extends React.Component {
4201
4323
  includeBoundTextElement: true,
4202
4324
  });
4203
4325
  // FIXME
4204
- let container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
4326
+ let container = this.getTextBindableContainerAtPosition(sceneX, sceneY);
4205
4327
  if (hasBoundTextElement(element)) {
4206
4328
  container = element;
4207
4329
  sceneX = element.x + element.width / 2;
@@ -4259,8 +4381,8 @@ class App extends React.Component {
4259
4381
  points: [[0, 0]],
4260
4382
  pressures,
4261
4383
  });
4262
- const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
4263
- this.scene.addNewElement(element);
4384
+ const boundElement = getHoveredElementForBinding(pointerDownState.origin, this);
4385
+ this.scene.insertElement(element);
4264
4386
  this.setState({
4265
4387
  draggingElement: element,
4266
4388
  editingElement: element,
@@ -4288,10 +4410,7 @@ class App extends React.Component {
4288
4410
  width,
4289
4411
  height,
4290
4412
  });
4291
- this.scene.replaceAllElements([
4292
- ...this.scene.getElementsIncludingDeleted(),
4293
- element,
4294
- ]);
4413
+ this.scene.insertElement(element);
4295
4414
  return element;
4296
4415
  };
4297
4416
  //create rectangle element with youtube top left on nearest grid point width / hight 640/360
@@ -4326,10 +4445,7 @@ class App extends React.Component {
4326
4445
  height: embedLink.intrinsicSize.h,
4327
4446
  link,
4328
4447
  });
4329
- this.scene.replaceAllElements([
4330
- ...this.scene.getElementsIncludingDeleted(),
4331
- element,
4332
- ]);
4448
+ this.scene.insertElement(element);
4333
4449
  return element;
4334
4450
  };
4335
4451
  createImageElement = ({ sceneX, sceneY, addToFrameUnderCursor = true, }) => {
@@ -4437,8 +4553,8 @@ class App extends React.Component {
4437
4553
  mutateElement(element, {
4438
4554
  points: [...element.points, [0, 0]],
4439
4555
  });
4440
- const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
4441
- this.scene.addNewElement(element);
4556
+ const boundElement = getHoveredElementForBinding(pointerDownState.origin, this);
4557
+ this.scene.insertElement(element);
4442
4558
  this.setState({
4443
4559
  draggingElement: element,
4444
4560
  editingElement: element,
@@ -4498,7 +4614,7 @@ class App extends React.Component {
4498
4614
  });
4499
4615
  }
4500
4616
  else {
4501
- this.scene.addNewElement(element);
4617
+ this.scene.insertElement(element);
4502
4618
  this.setState({
4503
4619
  multiElement: null,
4504
4620
  draggingElement: element,
@@ -4520,10 +4636,7 @@ class App extends React.Component {
4520
4636
  const frame = type === TOOL_TYPE.magicframe
4521
4637
  ? newMagicFrameElement(constructorOpts)
4522
4638
  : newFrameElement(constructorOpts);
4523
- this.scene.replaceAllElements([
4524
- ...this.scene.getElementsIncludingDeleted(),
4525
- frame,
4526
- ]);
4639
+ this.scene.insertElement(frame);
4527
4640
  this.setState({
4528
4641
  multiElement: null,
4529
4642
  draggingElement: frame,
@@ -4781,6 +4894,7 @@ class App extends React.Component {
4781
4894
  }
4782
4895
  }
4783
4896
  const nextSceneElements = [...nextElements, ...elementsToAppend];
4897
+ syncMovedIndices(nextSceneElements, arrayToMap(elementsToAppend));
4784
4898
  bindTextToShapeAfterDuplication(nextElements, elementsToAppend, oldIdToDuplicatedId);
4785
4899
  fixBindingsAfterDuplication(nextSceneElements, elementsToAppend, oldIdToDuplicatedId, "duplicatesServeAsOld");
4786
4900
  bindElementsToFramesAfterDuplication(nextSceneElements, elementsToAppend, oldIdToDuplicatedId);
@@ -4972,7 +5086,7 @@ class App extends React.Component {
4972
5086
  this.actionManager.executeAction(actionFinalize);
4973
5087
  }
4974
5088
  else {
4975
- 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);
4976
5090
  if (editingLinearElement !== this.state.editingLinearElement) {
4977
5091
  this.setState({
4978
5092
  editingLinearElement,
@@ -4991,7 +5105,7 @@ class App extends React.Component {
4991
5105
  }
4992
5106
  }
4993
5107
  else {
4994
- 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);
4995
5109
  const { startBindingElement, endBindingElement } = linearElementEditor;
4996
5110
  const element = this.scene.getElement(linearElementEditor.elementId);
4997
5111
  if (isBindingElement(element)) {
@@ -5083,7 +5197,7 @@ class App extends React.Component {
5083
5197
  else if (pointerDownState.drag.hasOccurred && !multiElement) {
5084
5198
  if (isBindingEnabled(this.state) &&
5085
5199
  isBindingElement(draggingElement, false)) {
5086
- maybeBindLinearElement(draggingElement, this.state, this.scene, pointerCoords, elementsMap);
5200
+ maybeBindLinearElement(draggingElement, this.state, pointerCoords, this);
5087
5201
  }
5088
5202
  this.setState({ suggestedBindings: [], startBoundElement: null });
5089
5203
  if (!activeTool.locked) {
@@ -5357,10 +5471,23 @@ class App extends React.Component {
5357
5471
  }));
5358
5472
  }
5359
5473
  }
5360
- if (!pointerDownState.drag.hasOccurred &&
5474
+ if (
5475
+ // not dragged
5476
+ !pointerDownState.drag.hasOccurred &&
5477
+ // not resized
5361
5478
  !this.state.isResizing &&
5479
+ // only hitting the bounding box of the previous hit element
5362
5480
  ((hitElement &&
5363
- 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)) ||
5364
5491
  (!hitElement &&
5365
5492
  pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements))) {
5366
5493
  if (this.state.editingLinearElement) {
@@ -5375,6 +5502,8 @@ class App extends React.Component {
5375
5502
  activeEmbeddable: null,
5376
5503
  });
5377
5504
  }
5505
+ // reset cursor
5506
+ setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
5378
5507
  return;
5379
5508
  }
5380
5509
  if (!activeTool.locked &&
@@ -5397,8 +5526,8 @@ class App extends React.Component {
5397
5526
  }
5398
5527
  if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
5399
5528
  isBindingEnabled(this.state)
5400
- ? bindOrUnbindSelectedElements(this.scene.getSelectedElements(this.state), this.scene.getNonDeletedElements(), elementsMap)
5401
- : unbindLinearElements(this.scene.getSelectedElements(this.state), elementsMap);
5529
+ ? bindOrUnbindSelectedElements(this.scene.getSelectedElements(this.state), this)
5530
+ : unbindLinearElements(this.scene.getNonDeletedElements(), elementsMap);
5402
5531
  }
5403
5532
  if (activeTool.type === "laser") {
5404
5533
  this.laserTrails.endPath();
@@ -5552,7 +5681,7 @@ class App extends React.Component {
5552
5681
  this.setState({ errorMessage: t("errors.imageToolNotSupported") });
5553
5682
  return;
5554
5683
  }
5555
- this.scene.addNewElement(imageElement);
5684
+ this.scene.insertElement(imageElement);
5556
5685
  try {
5557
5686
  return await this.initializeImage({
5558
5687
  imageFile,
@@ -5741,7 +5870,7 @@ class App extends React.Component {
5741
5870
  }
5742
5871
  };
5743
5872
  maybeSuggestBindingAtCursor = (pointerCoords) => {
5744
- const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5873
+ const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this);
5745
5874
  this.setState({
5746
5875
  suggestedBindings: hoveredBindableElement != null ? [hoveredBindableElement] : [],
5747
5876
  });
@@ -5756,7 +5885,7 @@ class App extends React.Component {
5756
5885
  return;
5757
5886
  }
5758
5887
  const suggestedBindings = pointerCoords.reduce((acc, coords) => {
5759
- const hoveredBindableElement = getHoveredElementForBinding(coords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5888
+ const hoveredBindableElement = getHoveredElementForBinding(coords, this);
5760
5889
  if (hoveredBindableElement != null &&
5761
5890
  !isLinearElementSimpleAndAlreadyBound(linearElement, oppositeBindingBoundElement?.id, hoveredBindableElement)) {
5762
5891
  acc.push(hoveredBindableElement);
@@ -5769,7 +5898,7 @@ class App extends React.Component {
5769
5898
  if (selectedElements.length > 50) {
5770
5899
  return;
5771
5900
  }
5772
- const suggestedBindings = getEligibleElementsForBinding(selectedElements, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5901
+ const suggestedBindings = getEligibleElementsForBinding(selectedElements, this);
5773
5902
  this.setState({ suggestedBindings });
5774
5903
  }
5775
5904
  clearSelection(hitElement) {
@@ -6321,7 +6450,7 @@ export const createTestHook = () => {
6321
6450
  return this.app?.scene.getElementsIncludingDeleted();
6322
6451
  },
6323
6452
  set(elements) {
6324
- return this.app?.scene.replaceAllElements(elements);
6453
+ return this.app?.scene.replaceAllElements(syncInvalidIndices(elements));
6325
6454
  },
6326
6455
  },
6327
6456
  });