@excalidraw/excalidraw 0.17.1-7500-ac247a0 → 0.17.1-b7babe5

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 (255) hide show
  1. package/CHANGELOG.md +56 -2
  2. package/dist/browser/dev/excalidraw-assets-dev/{chunk-2W5GQUR4.js → chunk-6NMK7JTV.js} +13 -6
  3. package/dist/browser/dev/excalidraw-assets-dev/chunk-6NMK7JTV.js.map +7 -0
  4. package/dist/browser/dev/excalidraw-assets-dev/chunk-CX3RATXT.js +20324 -0
  5. package/dist/browser/dev/excalidraw-assets-dev/chunk-CX3RATXT.js.map +7 -0
  6. package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js → en-BZY7JRTM.js} +4 -2
  7. package/dist/browser/dev/excalidraw-assets-dev/{image-5TVMINCA.js → image-CVN3YKRW.js} +2 -4
  8. package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css +6 -0
  9. package/dist/browser/dev/excalidraw-assets-dev/image-LK4UNFRZ.css.map +7 -0
  10. package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js +161 -0
  11. package/dist/browser/dev/excalidraw-assets-dev/roundRect-T5BX56ZF.js.map +7 -0
  12. package/dist/browser/dev/index.css +189 -129
  13. package/dist/browser/dev/index.css.map +3 -3
  14. package/dist/browser/dev/index.js +34964 -37
  15. package/dist/browser/dev/index.js.map +4 -4
  16. package/dist/browser/prod/excalidraw-assets/chunk-VJAIK3AX.js +55 -0
  17. package/dist/browser/prod/excalidraw-assets/chunk-YYO5DFUW.js +11 -0
  18. package/dist/browser/prod/excalidraw-assets/en-O2YCQM2W.js +1 -0
  19. package/dist/browser/prod/excalidraw-assets/image-6FKY54X5.js +1 -0
  20. package/dist/browser/prod/excalidraw-assets/image-X66R2EM5.css +1 -0
  21. package/dist/browser/prod/excalidraw-assets/roundRect-2ACQK4DA.js +1 -0
  22. package/dist/browser/prod/index.css +1 -1
  23. package/dist/browser/prod/index.js +203 -1
  24. package/dist/{prod/en-RLIAOBCI.json → dev/en-EY7E2L5O.json} +10 -5
  25. package/dist/dev/index.css +189 -129
  26. package/dist/dev/index.css.map +3 -3
  27. package/dist/dev/index.js +38702 -39409
  28. package/dist/dev/index.js.map +4 -4
  29. package/dist/excalidraw/actions/actionAddToLibrary.d.ts +15 -15
  30. package/dist/excalidraw/actions/actionAlign.d.ts +6 -6
  31. package/dist/excalidraw/actions/actionAlign.js +2 -1
  32. package/dist/excalidraw/actions/actionBoundText.d.ts +10 -10
  33. package/dist/excalidraw/actions/actionBoundText.js +8 -8
  34. package/dist/excalidraw/actions/actionCanvas.d.ts +58 -58
  35. package/dist/excalidraw/actions/actionClipboard.d.ts +34 -34
  36. package/dist/excalidraw/actions/actionClipboard.js +9 -2
  37. package/dist/excalidraw/actions/actionDeleteSelected.d.ts +15 -15
  38. package/dist/excalidraw/actions/actionDeleteSelected.js +3 -2
  39. package/dist/excalidraw/actions/actionDistribute.d.ts +2 -2
  40. package/dist/excalidraw/actions/actionDistribute.js +1 -1
  41. package/dist/excalidraw/actions/actionDuplicateSelection.d.ts +1 -1
  42. package/dist/excalidraw/actions/actionDuplicateSelection.js +4 -3
  43. package/dist/excalidraw/actions/actionElementLock.d.ts +10 -10
  44. package/dist/excalidraw/actions/actionExport.d.ts +43 -43
  45. package/dist/excalidraw/actions/actionExport.js +4 -4
  46. package/dist/excalidraw/actions/actionFinalize.d.ts +9 -9
  47. package/dist/excalidraw/actions/actionFinalize.js +7 -6
  48. package/dist/excalidraw/actions/actionFlip.d.ts +2 -2
  49. package/dist/excalidraw/actions/actionFlip.js +11 -11
  50. package/dist/excalidraw/actions/actionFrame.d.ts +16 -16
  51. package/dist/excalidraw/actions/actionFrame.js +1 -1
  52. package/dist/excalidraw/actions/actionGroup.d.ts +10 -10
  53. package/dist/excalidraw/actions/actionGroup.js +3 -2
  54. package/dist/excalidraw/actions/actionLinearEditor.d.ts +5 -5
  55. package/dist/excalidraw/actions/actionLinearEditor.js +1 -1
  56. package/dist/excalidraw/{element/Hyperlink.d.ts → actions/actionLink.d.ts} +29 -51
  57. package/dist/excalidraw/actions/actionLink.js +40 -0
  58. package/dist/excalidraw/actions/actionMenu.d.ts +13 -13
  59. package/dist/excalidraw/actions/actionNavigate.d.ts +10 -10
  60. package/dist/excalidraw/actions/actionNavigate.js +1 -1
  61. package/dist/excalidraw/actions/actionProperties.d.ts +77 -77
  62. package/dist/excalidraw/actions/actionProperties.js +32 -27
  63. package/dist/excalidraw/actions/actionSelectAll.d.ts +5 -5
  64. package/dist/excalidraw/actions/actionSelectAll.js +1 -1
  65. package/dist/excalidraw/actions/actionStyles.d.ts +7 -7
  66. package/dist/excalidraw/actions/actionStyles.js +4 -4
  67. package/dist/excalidraw/actions/actionToggleGridMode.d.ts +5 -5
  68. package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +5 -5
  69. package/dist/excalidraw/actions/actionToggleStats.d.ts +5 -5
  70. package/dist/excalidraw/actions/actionToggleViewMode.d.ts +5 -5
  71. package/dist/excalidraw/actions/actionToggleZenMode.d.ts +5 -5
  72. package/dist/excalidraw/actions/index.d.ts +1 -1
  73. package/dist/excalidraw/actions/index.js +1 -1
  74. package/dist/excalidraw/actions/manager.js +2 -1
  75. package/dist/excalidraw/align.d.ts +2 -2
  76. package/dist/excalidraw/align.js +2 -2
  77. package/dist/excalidraw/animated-trail.d.ts +33 -0
  78. package/dist/excalidraw/animated-trail.js +96 -0
  79. package/dist/excalidraw/animation-frame-handler.d.ts +16 -0
  80. package/dist/excalidraw/animation-frame-handler.js +55 -0
  81. package/dist/excalidraw/appState.d.ts +1 -1
  82. package/dist/excalidraw/appState.js +1 -3
  83. package/dist/excalidraw/clipboard.js +5 -5
  84. package/dist/excalidraw/components/Actions.d.ts +3 -3
  85. package/dist/excalidraw/components/Actions.js +18 -7
  86. package/dist/excalidraw/components/App.d.ts +23 -16
  87. package/dist/excalidraw/components/App.js +387 -272
  88. package/dist/excalidraw/components/Button.d.ts +1 -1
  89. package/dist/excalidraw/components/FilledButton.d.ts +2 -2
  90. package/dist/excalidraw/components/FilledButton.js +27 -3
  91. package/dist/excalidraw/components/FollowMode/FollowMode.js +1 -1
  92. package/dist/excalidraw/components/ImageExportDialog.d.ts +2 -1
  93. package/dist/excalidraw/components/ImageExportDialog.js +17 -13
  94. package/dist/excalidraw/components/JSONExportDialog.js +1 -1
  95. package/dist/excalidraw/components/{LaserTool/LaserPointerButton.d.ts → LaserPointerButton.d.ts} +1 -1
  96. package/dist/excalidraw/components/{LaserTool/LaserPointerButton.js → LaserPointerButton.js} +2 -2
  97. package/dist/excalidraw/components/LayerUI.js +3 -3
  98. package/dist/excalidraw/components/MobileMenu.js +1 -1
  99. package/dist/excalidraw/components/ProjectName.d.ts +0 -1
  100. package/dist/excalidraw/components/ProjectName.js +1 -1
  101. package/dist/excalidraw/components/PublishLibrary.js +1 -1
  102. package/dist/excalidraw/components/SVGLayer.d.ts +8 -0
  103. package/dist/excalidraw/components/SVGLayer.js +20 -0
  104. package/dist/excalidraw/components/ShareableLinkDialog.js +10 -10
  105. package/dist/excalidraw/components/Sidebar/Sidebar.d.ts +1 -1
  106. package/dist/excalidraw/components/Stack.d.ts +2 -2
  107. package/dist/excalidraw/components/TTDDialog/common.js +10 -1
  108. package/dist/excalidraw/components/TextField.d.ts +5 -2
  109. package/dist/excalidraw/components/TextField.js +6 -3
  110. package/dist/excalidraw/components/Toast.d.ts +3 -2
  111. package/dist/excalidraw/components/Toast.js +2 -2
  112. package/dist/excalidraw/components/ToolButton.js +2 -1
  113. package/dist/excalidraw/components/canvases/InteractiveCanvas.d.ts +2 -2
  114. package/dist/excalidraw/components/canvases/InteractiveCanvas.js +6 -5
  115. package/dist/excalidraw/components/canvases/StaticCanvas.d.ts +4 -3
  116. package/dist/excalidraw/components/canvases/StaticCanvas.js +7 -5
  117. package/dist/excalidraw/components/dropdownMenu/DropdownMenuContent.js +22 -2
  118. package/dist/excalidraw/components/hyperlink/Hyperlink.d.ts +19 -0
  119. package/dist/excalidraw/{element → components/hyperlink}/Hyperlink.js +40 -115
  120. package/dist/excalidraw/components/hyperlink/helpers.d.ts +7 -0
  121. package/dist/excalidraw/components/hyperlink/helpers.js +49 -0
  122. package/dist/excalidraw/components/icons.d.ts +2 -1
  123. package/dist/excalidraw/components/icons.js +2 -1
  124. package/dist/excalidraw/components/live-collaboration/LiveCollaborationTrigger.js +3 -2
  125. package/dist/excalidraw/components/main-menu/DefaultItems.js +5 -2
  126. package/dist/excalidraw/constants.d.ts +6 -0
  127. package/dist/excalidraw/constants.js +6 -0
  128. package/dist/excalidraw/data/blob.js +13 -14
  129. package/dist/excalidraw/data/filesystem.d.ts +1 -1
  130. package/dist/excalidraw/data/index.d.ts +2 -1
  131. package/dist/excalidraw/data/index.js +20 -16
  132. package/dist/excalidraw/data/json.d.ts +1 -1
  133. package/dist/excalidraw/data/json.js +5 -3
  134. package/dist/excalidraw/data/library.d.ts +60 -8
  135. package/dist/excalidraw/data/library.js +302 -33
  136. package/dist/excalidraw/data/resave.d.ts +1 -1
  137. package/dist/excalidraw/data/resave.js +2 -2
  138. package/dist/excalidraw/data/restore.js +8 -13
  139. package/dist/excalidraw/data/transform.js +13 -9
  140. package/dist/excalidraw/distribute.d.ts +2 -2
  141. package/dist/excalidraw/distribute.js +2 -2
  142. package/dist/excalidraw/element/ElementCanvasButtons.d.ts +3 -2
  143. package/dist/excalidraw/element/ElementCanvasButtons.js +4 -4
  144. package/dist/excalidraw/element/binding.d.ts +9 -9
  145. package/dist/excalidraw/element/binding.js +61 -59
  146. package/dist/excalidraw/element/bounds.d.ts +5 -5
  147. package/dist/excalidraw/element/bounds.js +29 -32
  148. package/dist/excalidraw/element/collision.d.ts +11 -11
  149. package/dist/excalidraw/element/collision.js +49 -46
  150. package/dist/excalidraw/element/containerCache.d.ts +11 -0
  151. package/dist/excalidraw/element/containerCache.js +14 -0
  152. package/dist/excalidraw/element/dragElements.js +10 -19
  153. package/dist/excalidraw/element/embeddable.d.ts +12 -13
  154. package/dist/excalidraw/element/embeddable.js +17 -27
  155. package/dist/excalidraw/element/image.js +1 -2
  156. package/dist/excalidraw/element/index.d.ts +8 -1
  157. package/dist/excalidraw/element/index.js +23 -1
  158. package/dist/excalidraw/element/linearElementEditor.d.ts +36 -36
  159. package/dist/excalidraw/element/linearElementEditor.js +79 -80
  160. package/dist/excalidraw/element/newElement.d.ts +4 -6
  161. package/dist/excalidraw/element/newElement.js +11 -16
  162. package/dist/excalidraw/element/resizeElements.d.ts +6 -6
  163. package/dist/excalidraw/element/resizeElements.js +40 -46
  164. package/dist/excalidraw/element/resizeTest.d.ts +3 -3
  165. package/dist/excalidraw/element/resizeTest.js +4 -4
  166. package/dist/excalidraw/element/sizeHelpers.d.ts +2 -2
  167. package/dist/excalidraw/element/sizeHelpers.js +2 -2
  168. package/dist/excalidraw/element/textElement.d.ts +34 -21
  169. package/dist/excalidraw/element/textElement.js +87 -111
  170. package/dist/excalidraw/element/textWysiwyg.d.ts +1 -6
  171. package/dist/excalidraw/element/textWysiwyg.js +15 -37
  172. package/dist/excalidraw/element/transformHandles.d.ts +4 -4
  173. package/dist/excalidraw/element/transformHandles.js +6 -6
  174. package/dist/excalidraw/element/typeChecks.js +4 -1
  175. package/dist/excalidraw/element/types.d.ts +24 -11
  176. package/dist/excalidraw/frame.d.ts +26 -20
  177. package/dist/excalidraw/frame.js +157 -84
  178. package/dist/excalidraw/groups.d.ts +3 -3
  179. package/dist/excalidraw/groups.js +11 -3
  180. package/dist/excalidraw/history.d.ts +1 -1
  181. package/dist/excalidraw/hooks/useLibraryItemSvg.js +1 -1
  182. package/dist/excalidraw/index.d.ts +9 -10
  183. package/dist/excalidraw/index.js +16 -12
  184. package/dist/excalidraw/laser-trails.d.ts +19 -0
  185. package/dist/excalidraw/laser-trails.js +95 -0
  186. package/dist/excalidraw/locales/en.json +10 -5
  187. package/dist/excalidraw/queue.d.ts +9 -0
  188. package/dist/excalidraw/queue.js +27 -0
  189. package/dist/excalidraw/reactUtils.d.ts +14 -0
  190. package/dist/excalidraw/reactUtils.js +45 -0
  191. package/dist/excalidraw/renderer/helpers.d.ts +13 -0
  192. package/dist/excalidraw/renderer/helpers.js +39 -0
  193. package/dist/excalidraw/renderer/interactiveScene.d.ts +20 -0
  194. package/dist/excalidraw/renderer/{renderScene.js → interactiveScene.js} +199 -474
  195. package/dist/excalidraw/renderer/renderElement.d.ts +6 -6
  196. package/dist/excalidraw/renderer/renderElement.js +54 -366
  197. package/dist/excalidraw/renderer/staticScene.d.ts +11 -0
  198. package/dist/excalidraw/renderer/staticScene.js +205 -0
  199. package/dist/excalidraw/renderer/staticSvgScene.d.ts +5 -0
  200. package/dist/excalidraw/renderer/staticSvgScene.js +385 -0
  201. package/dist/excalidraw/scene/Fonts.js +2 -1
  202. package/dist/excalidraw/scene/Renderer.d.ts +1 -1
  203. package/dist/excalidraw/scene/Renderer.js +32 -20
  204. package/dist/excalidraw/scene/Scene.d.ts +10 -9
  205. package/dist/excalidraw/scene/Scene.js +45 -21
  206. package/dist/excalidraw/scene/Shape.d.ts +3 -1
  207. package/dist/excalidraw/scene/Shape.js +7 -5
  208. package/dist/excalidraw/scene/ShapeCache.d.ts +2 -1
  209. package/dist/excalidraw/scene/ShapeCache.js +1 -0
  210. package/dist/excalidraw/scene/comparisons.js +2 -1
  211. package/dist/excalidraw/scene/export.d.ts +3 -0
  212. package/dist/excalidraw/scene/export.js +20 -40
  213. package/dist/excalidraw/scene/index.d.ts +0 -1
  214. package/dist/excalidraw/scene/index.js +0 -1
  215. package/dist/excalidraw/scene/scrollbars.d.ts +1 -1
  216. package/dist/excalidraw/scene/scrollbars.js +1 -1
  217. package/dist/excalidraw/scene/selection.d.ts +5 -5
  218. package/dist/excalidraw/scene/selection.js +16 -14
  219. package/dist/excalidraw/scene/types.d.ts +11 -5
  220. package/dist/excalidraw/snapping.d.ts +7 -7
  221. package/dist/excalidraw/snapping.js +21 -20
  222. package/dist/excalidraw/types.d.ts +16 -17
  223. package/dist/excalidraw/utility-types.d.ts +7 -0
  224. package/dist/excalidraw/utils.d.ts +21 -16
  225. package/dist/excalidraw/utils.js +43 -45
  226. package/dist/{dev/en-RLIAOBCI.json → prod/en-EY7E2L5O.json} +10 -5
  227. package/dist/prod/index.css +1 -1
  228. package/dist/prod/index.js +42 -42
  229. package/dist/utils/bbox.d.ts +2 -2
  230. package/dist/utils/export.d.ts +3 -3
  231. package/dist/utils/export.js +3 -13
  232. package/dist/utils/index.d.ts +2 -2
  233. package/dist/utils/index.js +2 -2
  234. package/dist/utils/withinBounds.d.ts +1 -1
  235. package/dist/utils/withinBounds.js +5 -2
  236. package/package.json +4 -4
  237. package/dist/browser/dev/excalidraw-assets-dev/chunk-2W5GQUR4.js.map +0 -7
  238. package/dist/browser/dev/excalidraw-assets-dev/chunk-KGZXLFLR.js +0 -53497
  239. package/dist/browser/dev/excalidraw-assets-dev/chunk-KGZXLFLR.js.map +0 -7
  240. package/dist/browser/dev/excalidraw-assets-dev/image-3MFRCKYM.css +0 -5797
  241. package/dist/browser/dev/excalidraw-assets-dev/image-3MFRCKYM.css.map +0 -7
  242. package/dist/browser/prod/excalidraw-assets/chunk-4YN2HN3S.js +0 -257
  243. package/dist/browser/prod/excalidraw-assets/chunk-OWLL6VOG.js +0 -11
  244. package/dist/browser/prod/excalidraw-assets/en-ERQOR3OC.js +0 -1
  245. package/dist/browser/prod/excalidraw-assets/image-LTLHTTSE.js +0 -1
  246. package/dist/browser/prod/excalidraw-assets/image-QBL334OA.css +0 -1
  247. package/dist/excalidraw/components/LaserTool/LaserPathManager.d.ts +0 -28
  248. package/dist/excalidraw/components/LaserTool/LaserPathManager.js +0 -225
  249. package/dist/excalidraw/components/LaserTool/LaserTool.d.ts +0 -8
  250. package/dist/excalidraw/components/LaserTool/LaserTool.js +0 -15
  251. package/dist/excalidraw/renderer/renderScene.d.ts +0 -25
  252. package/dist/excalidraw/vite.config.d.mts +0 -2
  253. package/dist/excalidraw/vite.config.mjs +0 -13
  254. /package/dist/browser/dev/excalidraw-assets-dev/{en-OC6JWP3X.js.map → en-BZY7JRTM.js.map} +0 -0
  255. /package/dist/browser/dev/excalidraw-assets-dev/{image-5TVMINCA.js.map → image-CVN3YKRW.js.map} +0 -0
@@ -11,11 +11,11 @@ import { actions } from "../actions/register";
11
11
  import { trackEvent } from "../analytics";
12
12
  import { getDefaultAppState, isEraserActive, isHandToolActive, } from "../appState";
13
13
  import { copyTextToSystemClipboard, parseClipboard, } from "../clipboard";
14
- import { APP_NAME, CURSOR_TYPE, DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT, DEFAULT_VERTICAL_ALIGN, DRAGGING_THRESHOLD, ELEMENT_READY_TO_ERASE_OPACITY, ELEMENT_SHIFT_TRANSLATE_AMOUNT, ELEMENT_TRANSLATE_AMOUNT, ENV, EVENT, FRAME_STYLE, GRID_SIZE, IMAGE_MIME_TYPES, IMAGE_RENDER_TIMEOUT, isBrave, LINE_CONFIRM_THRESHOLD, MAX_ALLOWED_FILE_BYTES, MIME_TYPES, MQ_MAX_HEIGHT_LANDSCAPE, MQ_MAX_WIDTH_LANDSCAPE, MQ_MAX_WIDTH_PORTRAIT, MQ_RIGHT_SIDEBAR_MIN_WIDTH, POINTER_BUTTON, ROUNDNESS, SCROLL_TIMEOUT, TAP_TWICE_TIMEOUT, TEXT_TO_CENTER_SNAP_THRESHOLD, THEME, THEME_FILTER, TOUCH_CTX_MENU_TIMEOUT, VERTICAL_ALIGN, YOUTUBE_STATES, ZOOM_STEP, POINTER_EVENTS, TOOL_TYPE, EDITOR_LS_KEYS, isIOS, } from "../constants";
14
+ import { APP_NAME, CURSOR_TYPE, DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT, DEFAULT_VERTICAL_ALIGN, DRAGGING_THRESHOLD, ELEMENT_SHIFT_TRANSLATE_AMOUNT, ELEMENT_TRANSLATE_AMOUNT, ENV, EVENT, FRAME_STYLE, GRID_SIZE, IMAGE_MIME_TYPES, IMAGE_RENDER_TIMEOUT, isBrave, LINE_CONFIRM_THRESHOLD, MAX_ALLOWED_FILE_BYTES, MIME_TYPES, MQ_MAX_HEIGHT_LANDSCAPE, MQ_MAX_WIDTH_LANDSCAPE, MQ_MAX_WIDTH_PORTRAIT, MQ_RIGHT_SIDEBAR_MIN_WIDTH, POINTER_BUTTON, ROUNDNESS, SCROLL_TIMEOUT, TAP_TWICE_TIMEOUT, TEXT_TO_CENTER_SNAP_THRESHOLD, THEME, THEME_FILTER, TOUCH_CTX_MENU_TIMEOUT, VERTICAL_ALIGN, YOUTUBE_STATES, ZOOM_STEP, POINTER_EVENTS, TOOL_TYPE, EDITOR_LS_KEYS, isIOS, } from "../constants";
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, textWysiwyg, transformElements, updateTextElement, redrawTextBoundingBox, } from "../element";
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";
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";
@@ -28,12 +28,12 @@ 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, isOverScrollBars, isSomeElementSelected, } from "../scene";
31
+ import { calculateScrollCenter, getElementsAtPosition, 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, withBatchedUpdates, wrapEvent, withBatchedUpdatesThrottled, updateObject, updateActiveTool, getShortcutKey, isTransparent, easeToValuesRAF, muteFSAbortError, isTestEnv, easeOut, updateStable, addEventListener, } from "../utils";
36
- import { createSrcDoc, embeddableURLValidator, extractSrc, getEmbedLink, } from "../element/embeddable";
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";
36
+ import { createSrcDoc, embeddableURLValidator, maybeParseEmbedSrc, getEmbedLink, } from "../element/embeddable";
37
37
  import { ContextMenu, CONTEXT_MENU_SEPARATOR, } from "./ContextMenu";
38
38
  import LayerUI from "./LayerUI";
39
39
  import { Toast } from "./Toast";
@@ -44,12 +44,12 @@ import throttle from "lodash.throttle";
44
44
  import { fileOpen } from "../data/filesystem";
45
45
  import { bindTextToShapeAfterDuplication, getApproxMinLineHeight, getApproxMinLineWidth, getBoundTextElement, getContainerCenter, getContainerElement, getDefaultLineHeight, getLineHeightInPx, getTextBindableContainerAtPosition, isMeasureTextSupported, isValidTextContainer, } from "../element/textElement";
46
46
  import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
47
- import { showHyperlinkTooltip, hideHyperlinkToolip, Hyperlink, isPointHittingLink, isPointHittingLinkIcon, } from "../element/Hyperlink";
47
+ import { showHyperlinkTooltip, hideHyperlinkToolip, Hyperlink, } from "../components/hyperlink/Hyperlink";
48
48
  import { isLocalLink, normalizeLink, toValidURL } from "../data/url";
49
49
  import { shouldShowBoundingBox } from "../element/transformHandles";
50
50
  import { actionUnlockAllElements } from "../actions/actionElementLock";
51
51
  import { Fonts } from "../scene/Fonts";
52
- import { getFrameChildren, isCursorInFrame, bindElementsToFramesAfterDuplication, addElementsToFrame, replaceAllElementsInFrame, removeElementsFromFrame, getElementsInResizingFrame, getElementsInNewFrame, getContainingFrame, elementOverlapsWithFrame, updateFrameMembershipOfSelectedElements, isElementInFrame, getFrameLikeTitle, } from "../frame";
52
+ import { getFrameChildren, isCursorInFrame, bindElementsToFramesAfterDuplication, addElementsToFrame, replaceAllElementsInFrame, removeElementsFromFrame, getElementsInResizingFrame, getElementsInNewFrame, getContainingFrame, elementOverlapsWithFrame, updateFrameMembershipOfSelectedElements, isElementInFrame, getFrameLikeTitle, getElementsOverlappingFrame, filterElementsEligibleAsFrameChildren, } from "../frame";
53
53
  import { excludeElementsInFramesFromSelection, makeNextSelectedElementIds, } from "../scene/selection";
54
54
  import { actionPaste } from "../actions/actionClipboard";
55
55
  import { actionRemoveAllElementsFromFrame, actionSelectAllElementsInFrame, } from "../actions/actionFrame";
@@ -66,18 +66,25 @@ import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
66
66
  import { StaticCanvas, InteractiveCanvas } from "./canvases";
67
67
  import { Renderer } from "../scene/Renderer";
68
68
  import { ShapeCache } from "../scene/ShapeCache";
69
- import { LaserToolOverlay } from "./LaserTool/LaserTool";
70
- import { LaserPathManager } from "./LaserTool/LaserPathManager";
69
+ import { SVGLayer } from "./SVGLayer";
71
70
  import { setEraserCursor, setCursor, resetCursor, setCursorForShape, } from "../cursor";
72
71
  import { Emitter } from "../emitter";
73
72
  import { ElementCanvasButtons } from "../element/ElementCanvasButtons";
74
73
  import { diagramToHTML } from "../data/magic";
75
- import { elementsOverlappingBBox, exportToBlob } from "../../utils/index";
74
+ import { exportToBlob } from "../../utils/export";
76
75
  import { COLOR_PALETTE } from "../colors";
77
76
  import { ElementCanvasButton } from "./MagicButton";
78
77
  import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
79
78
  import { EditorLocalStorage } from "../data/EditorLocalStorage";
80
79
  import FollowMode from "./FollowMode/FollowMode";
80
+ import { AnimationFrameHandler } from "../animation-frame-handler";
81
+ import { AnimatedTrail } from "../animated-trail";
82
+ import { LaserTrails } from "../laser-trails";
83
+ import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
84
+ import { getRenderOpacity } from "../renderer/renderElement";
85
+ import { textWysiwyg } from "../element/textWysiwyg";
86
+ import { isOverScrollBars } from "../scene/scrollbars";
87
+ import { isPointHittingLink, isPointHittingLinkIcon, } from "./hyperlink/helpers";
81
88
  const AppContext = React.createContext(null);
82
89
  const AppPropsContext = React.createContext(null);
83
90
  const deviceContextInitialValue = {
@@ -163,12 +170,41 @@ class App extends React.Component {
163
170
  files = {};
164
171
  imageCache = new Map();
165
172
  iFrameRefs = new Map();
173
+ /**
174
+ * Indicates whether the embeddable's url has been validated for rendering.
175
+ * If value not set, indicates that the validation is pending.
176
+ * Initially or on url change the flag is not reset so that we can guarantee
177
+ * the validation came from a trusted source (the editor).
178
+ **/
179
+ embedsValidationStatus = new Map();
180
+ /** embeds that have been inserted to DOM (as a perf optim, we don't want to
181
+ * insert to DOM before user initially scrolls to them) */
182
+ initializedEmbeds = new Set();
183
+ elementsPendingErasure = new Set();
166
184
  hitLinkElement;
167
185
  lastPointerDownEvent = null;
168
186
  lastPointerUpEvent = null;
169
187
  lastPointerMoveEvent = null;
170
188
  lastViewportPosition = { x: 0, y: 0 };
171
- laserPathManager = new LaserPathManager(this);
189
+ animationFrameHandler = new AnimationFrameHandler();
190
+ laserTrails = new LaserTrails(this.animationFrameHandler, this);
191
+ eraserTrail = new AnimatedTrail(this.animationFrameHandler, this, {
192
+ streamline: 0.2,
193
+ size: 5,
194
+ keepHead: true,
195
+ sizeMapping: (c) => {
196
+ const DECAY_TIME = 200;
197
+ const DECAY_LENGTH = 10;
198
+ const t = Math.max(0, 1 - (performance.now() - c.pressure) / DECAY_TIME);
199
+ const l = (DECAY_LENGTH -
200
+ Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) /
201
+ DECAY_LENGTH;
202
+ return Math.min(easeOut(l), easeOut(t));
203
+ },
204
+ fill: () => this.state.theme === THEME.LIGHT
205
+ ? "rgba(0, 0, 0, 0.2)"
206
+ : "rgba(255, 255, 255, 0.2)",
207
+ });
172
208
  onChangeEmitter = new Emitter();
173
209
  onPointerDownEmitter = new Emitter();
174
210
  onPointerUpEmitter = new Emitter();
@@ -179,7 +215,7 @@ class App extends React.Component {
179
215
  constructor(props) {
180
216
  super(props);
181
217
  const defaultAppState = getDefaultAppState();
182
- const { excalidrawAPI, viewModeEnabled = false, zenModeEnabled = false, gridModeEnabled = false, objectsSnapModeEnabled = false, theme = defaultAppState.theme, name = defaultAppState.name, } = props;
218
+ const { excalidrawAPI, viewModeEnabled = false, zenModeEnabled = false, gridModeEnabled = false, objectsSnapModeEnabled = false, theme = defaultAppState.theme, name = `${t("labels.untitled")}-${getDateTime()}`, } = props;
183
219
  this.state = {
184
220
  ...defaultAppState,
185
221
  theme,
@@ -214,6 +250,7 @@ class App extends React.Component {
214
250
  getSceneElements: this.getSceneElements,
215
251
  getAppState: () => this.state,
216
252
  getFiles: () => this.files,
253
+ getName: this.getName,
217
254
  registerAction: (action) => {
218
255
  this.actionManager.registerAction(action);
219
256
  },
@@ -378,17 +415,20 @@ class App extends React.Component {
378
415
  sceneY >= el.y + el.height / 3 &&
379
416
  sceneY <= el.y + (2 * el.height) / 3);
380
417
  }
418
+ updateEmbedValidationStatus = (element, status) => {
419
+ this.embedsValidationStatus.set(element.id, status);
420
+ ShapeCache.delete(element);
421
+ };
381
422
  updateEmbeddables = () => {
382
423
  const iframeLikes = new Set();
383
424
  let updated = false;
384
425
  this.scene.getNonDeletedElements().filter((element) => {
385
426
  if (isEmbeddableElement(element)) {
386
427
  iframeLikes.add(element.id);
387
- if (element.validated == null) {
428
+ if (!this.embedsValidationStatus.has(element.id)) {
388
429
  updated = true;
389
430
  const validated = embeddableURLValidator(element.link, this.props.validateEmbeddable);
390
- mutateElement(element, { validated }, false);
391
- ShapeCache.delete(element);
431
+ this.updateEmbedValidationStatus(element, validated);
392
432
  }
393
433
  }
394
434
  else if (isIframeElement(element)) {
@@ -412,9 +452,20 @@ class App extends React.Component {
412
452
  const normalizedHeight = this.state.height;
413
453
  const embeddableElements = this.scene
414
454
  .getNonDeletedElements()
415
- .filter((el) => (isEmbeddableElement(el) && !!el.validated) || isIframeElement(el));
455
+ .filter((el) => (isEmbeddableElement(el) &&
456
+ this.embedsValidationStatus.get(el.id) === true) ||
457
+ isIframeElement(el));
416
458
  return (_jsx(_Fragment, { children: embeddableElements.map((el) => {
417
459
  const { x, y } = sceneCoordsToViewportCoords({ sceneX: el.x, sceneY: el.y }, this.state);
460
+ const isVisible = isElementInViewport(el, normalizedWidth, normalizedHeight, this.state, this.scene.getNonDeletedElementsMap());
461
+ const hasBeenInitialized = this.initializedEmbeds.has(el.id);
462
+ if (isVisible && !hasBeenInitialized) {
463
+ this.initializedEmbeds.add(el.id);
464
+ }
465
+ const shouldRender = isVisible || hasBeenInitialized;
466
+ if (!shouldRender) {
467
+ return null;
468
+ }
418
469
  let src;
419
470
  if (isIframeElement(el)) {
420
471
  src = null;
@@ -554,8 +605,6 @@ class App extends React.Component {
554
605
  else {
555
606
  src = getEmbedLink(toValidURL(el.link || ""));
556
607
  }
557
- // console.log({ src });
558
- const isVisible = isElementInViewport(el, normalizedWidth, normalizedHeight, this.state);
559
608
  const isActive = this.state.activeEmbeddable?.element === el &&
560
609
  this.state.activeEmbeddable?.state === "active";
561
610
  const isHovered = this.state.activeEmbeddable?.element === el &&
@@ -567,7 +616,7 @@ class App extends React.Component {
567
616
  ? `translate(${x - this.state.offsetLeft}px, ${y - this.state.offsetTop}px) scale(${scale})`
568
617
  : "none",
569
618
  display: isVisible ? "block" : "none",
570
- opacity: el.opacity / 100,
619
+ opacity: getRenderOpacity(el, getContainingFrame(el, this.scene.getNonDeletedElementsMap()), this.elementsPendingErasure),
571
620
  ["--embeddable-radius"]: `${getCornerRadius(Math.min(el.width, el.height), el)}px`,
572
621
  }, children: _jsxs("div", {
573
622
  //this is a hack that addresses isse with embedded excalidraw.com embeddable
@@ -659,16 +708,14 @@ class App extends React.Component {
659
708
  scrollX: this.state.scrollX,
660
709
  scrollY: this.state.scrollY,
661
710
  zoom: this.state.zoom,
662
- })) {
711
+ }, this.scene.getNonDeletedElementsMap())) {
663
712
  // if frame not visible, don't render its name
664
713
  return null;
665
714
  }
666
715
  const { x: x1, y: y1 } = sceneCoordsToViewportCoords({ sceneX: f.x, sceneY: f.y }, this.state);
667
716
  const FRAME_NAME_EDIT_PADDING = 6;
668
717
  const reset = () => {
669
- if (f.name?.trim() === "") {
670
- mutateElement(f, { name: null });
671
- }
718
+ mutateElement(f, { name: f.name?.trim() || null });
672
719
  this.setState({ editingFrame: null });
673
720
  };
674
721
  let frameNameJSX;
@@ -679,7 +726,7 @@ class App extends React.Component {
679
726
  mutateElement(f, {
680
727
  name: e.target.value,
681
728
  });
682
- }, onBlur: () => reset(), onKeyDown: (event) => {
729
+ }, onFocus: (e) => e.target.select(), onBlur: () => reset(), onKeyDown: (event) => {
683
730
  // for some inexplicable reason, `onBlur` triggered on ESC
684
731
  // does not reset `state.editingFrame` despite being called,
685
732
  // and we need to reset it here as well
@@ -740,11 +787,17 @@ class App extends React.Component {
740
787
  }, children: frameNameJSX }, f.id));
741
788
  });
742
789
  };
790
+ toggleOverscrollBehavior(event) {
791
+ // when pointer inside editor, disable overscroll behavior to prevent
792
+ // panning to trigger history back/forward on MacOS Chrome
793
+ document.documentElement.style.overscrollBehaviorX =
794
+ event.type === "pointerenter" ? "none" : "auto";
795
+ }
743
796
  render() {
744
797
  const selectedElements = this.scene.getSelectedElements(this.state);
745
798
  const { renderTopRightUI, renderCustomStats } = this.props;
746
799
  const versionNonce = this.scene.getVersionNonce();
747
- const { canvasElements, visibleElements } = this.renderer.getRenderableElements({
800
+ const { elementsMap, visibleElements } = this.renderer.getRenderableElements({
748
801
  versionNonce,
749
802
  zoom: this.state.zoom,
750
803
  offsetLeft: this.state.offsetLeft,
@@ -756,6 +809,7 @@ class App extends React.Component {
756
809
  editingElement: this.state.editingElement,
757
810
  pendingImageElementId: this.state.pendingImageElementId,
758
811
  });
812
+ const allElementsMap = this.scene.getNonDeletedElementsMap();
759
813
  const shouldBlockPointerEvents = !(this.state.editingElement && isLinearElement(this.state.editingElement)) &&
760
814
  (this.state.selectionElement ||
761
815
  this.state.draggingElement ||
@@ -773,18 +827,18 @@ class App extends React.Component {
773
827
  ["--ui-pointerEvents"]: shouldBlockPointerEvents
774
828
  ? POINTER_EVENTS.disabled
775
829
  : POINTER_EVENTS.enabled,
776
- }, ref: this.excalidrawContainerRef, onDrop: this.handleAppOnDrop, tabIndex: 0, onKeyDown: this.props.handleKeyboardGlobally ? undefined : this.onKeyDown, children: _jsx(AppContext.Provider, { value: this, children: _jsx(AppPropsContext.Provider, { value: this.props, children: _jsx(ExcalidrawContainerContext.Provider, { value: this.excalidrawContainerValue, children: _jsx(DeviceContext.Provider, { value: this.device, children: _jsx(ExcalidrawSetAppStateContext.Provider, { value: this.setAppState, children: _jsx(ExcalidrawAppStateContext.Provider, { value: this.state, children: _jsxs(ExcalidrawElementsContext.Provider, { value: this.scene.getNonDeletedElements(), children: [_jsxs(ExcalidrawActionManagerContext.Provider, { value: this.actionManager, children: [_jsx(LayerUI, { canvas: this.canvas, appState: this.state, files: this.files, setAppState: this.setAppState, actionManager: this.actionManager, elements: this.scene.getNonDeletedElements(), onLockToggle: this.toggleLock, onPenModeToggle: this.togglePenMode, onHandToolToggle: this.onHandToolToggle, langCode: getLanguage().code, renderTopRightUI: renderTopRightUI, renderCustomStats: renderCustomStats, showExitZenModeBtn: typeof this.props?.zenModeEnabled === "undefined" &&
830
+ }, ref: this.excalidrawContainerRef, onDrop: this.handleAppOnDrop, tabIndex: 0, onKeyDown: this.props.handleKeyboardGlobally ? undefined : this.onKeyDown, onPointerEnter: this.toggleOverscrollBehavior, onPointerLeave: this.toggleOverscrollBehavior, children: _jsx(AppContext.Provider, { value: this, children: _jsx(AppPropsContext.Provider, { value: this.props, children: _jsx(ExcalidrawContainerContext.Provider, { value: this.excalidrawContainerValue, children: _jsx(DeviceContext.Provider, { value: this.device, children: _jsx(ExcalidrawSetAppStateContext.Provider, { value: this.setAppState, children: _jsx(ExcalidrawAppStateContext.Provider, { value: this.state, children: _jsxs(ExcalidrawElementsContext.Provider, { value: this.scene.getNonDeletedElements(), children: [_jsxs(ExcalidrawActionManagerContext.Provider, { value: this.actionManager, children: [_jsx(LayerUI, { canvas: this.canvas, appState: this.state, files: this.files, setAppState: this.setAppState, actionManager: this.actionManager, elements: this.scene.getNonDeletedElements(), onLockToggle: this.toggleLock, onPenModeToggle: this.togglePenMode, onHandToolToggle: this.onHandToolToggle, langCode: getLanguage().code, renderTopRightUI: renderTopRightUI, renderCustomStats: renderCustomStats, showExitZenModeBtn: typeof this.props?.zenModeEnabled === "undefined" &&
777
831
  this.state.zenModeEnabled, UIOptions: this.props.UIOptions, onExportImage: this.onExportImage, renderWelcomeScreen: !this.state.isLoading &&
778
832
  this.state.showWelcomeScreen &&
779
833
  this.state.activeTool.type === "selection" &&
780
834
  !this.state.zenModeEnabled &&
781
- !this.scene.getElementsIncludingDeleted().length, app: this, isCollaborating: this.props.isCollaborating, openAIKey: this.OPENAI_KEY, isOpenAIKeyPersisted: this.OPENAI_KEY_IS_PERSISTED, onOpenAIAPIKeyChange: this.onOpenAIKeyChange, onMagicSettingsConfirm: this.onMagicSettingsConfirm, children: this.props.children }), _jsx("div", { className: "excalidraw-textEditorContainer" }), _jsx("div", { className: "excalidraw-contextMenuContainer" }), _jsx("div", { className: "excalidraw-eye-dropper-container" }), _jsx(LaserToolOverlay, { manager: this.laserPathManager }), selectedElements.length === 1 &&
782
- this.state.showHyperlinkPopup && (_jsx(Hyperlink, { element: firstSelectedElement, setAppState: this.setAppState, onLinkOpen: this.props.onLinkOpen, setToast: this.setToast }, firstSelectedElement.id)), this.props.aiEnabled !== false &&
835
+ !this.scene.getElementsIncludingDeleted().length, app: this, isCollaborating: this.props.isCollaborating, openAIKey: this.OPENAI_KEY, isOpenAIKeyPersisted: this.OPENAI_KEY_IS_PERSISTED, onOpenAIAPIKeyChange: this.onOpenAIKeyChange, onMagicSettingsConfirm: this.onMagicSettingsConfirm, children: this.props.children }), _jsx("div", { className: "excalidraw-textEditorContainer" }), _jsx("div", { className: "excalidraw-contextMenuContainer" }), _jsx("div", { className: "excalidraw-eye-dropper-container" }), _jsx(SVGLayer, { trails: [this.laserTrails, this.eraserTrail] }), selectedElements.length === 1 &&
836
+ this.state.showHyperlinkPopup && (_jsx(Hyperlink, { element: firstSelectedElement, elementsMap: allElementsMap, setAppState: this.setAppState, onLinkOpen: this.props.onLinkOpen, setToast: this.setToast, updateEmbedValidationStatus: this.updateEmbedValidationStatus }, firstSelectedElement.id)), this.props.aiEnabled !== false &&
783
837
  selectedElements.length === 1 &&
784
- isMagicFrameElement(firstSelectedElement) && (_jsx(ElementCanvasButtons, { element: firstSelectedElement, children: _jsx(ElementCanvasButton, { title: t("labels.convertToCode"), icon: MagicIcon, checked: false, onChange: () => this.onMagicFrameGenerate(firstSelectedElement, "button") }) })), selectedElements.length === 1 &&
838
+ isMagicFrameElement(firstSelectedElement) && (_jsx(ElementCanvasButtons, { element: firstSelectedElement, elementsMap: elementsMap, children: _jsx(ElementCanvasButton, { title: t("labels.convertToCode"), icon: MagicIcon, checked: false, onChange: () => this.onMagicFrameGenerate(firstSelectedElement, "button") }) })), selectedElements.length === 1 &&
785
839
  isIframeElement(firstSelectedElement) &&
786
840
  firstSelectedElement.customData?.generationData
787
- ?.status === "done" && (_jsxs(ElementCanvasButtons, { element: firstSelectedElement, children: [_jsx(ElementCanvasButton, { title: t("labels.copySource"), icon: copyIcon, checked: false, onChange: () => this.onIframeSrcCopy(firstSelectedElement) }), _jsx(ElementCanvasButton, { title: "Enter fullscreen", icon: fullscreenIcon, checked: false, onChange: () => {
841
+ ?.status === "done" && (_jsxs(ElementCanvasButtons, { element: firstSelectedElement, elementsMap: elementsMap, children: [_jsx(ElementCanvasButton, { title: t("labels.copySource"), icon: copyIcon, checked: false, onChange: () => this.onIframeSrcCopy(firstSelectedElement) }), _jsx(ElementCanvasButton, { title: "Enter fullscreen", icon: fullscreenIcon, checked: false, onChange: () => {
788
842
  const iframe = this.getHTMLIFrameElement(firstSelectedElement);
789
843
  if (iframe) {
790
844
  try {
@@ -813,12 +867,14 @@ class App extends React.Component {
813
867
  this.focusContainer();
814
868
  callback?.();
815
869
  });
816
- } })), _jsx(StaticCanvas, { canvas: this.canvas, rc: this.rc, elements: canvasElements, visibleElements: visibleElements, versionNonce: versionNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, renderConfig: {
870
+ } })), _jsx(StaticCanvas, { canvas: this.canvas, rc: this.rc, elementsMap: elementsMap, allElementsMap: allElementsMap, visibleElements: visibleElements, versionNonce: versionNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, renderConfig: {
817
871
  imageCache: this.imageCache,
818
872
  isExporting: false,
819
873
  renderGrid: true,
820
874
  canvasBackgroundColor: this.state.viewBackgroundColor,
821
- } }), _jsx(InteractiveCanvas, { containerRef: this.excalidrawContainerRef, canvas: this.interactiveCanvas, elements: canvasElements, visibleElements: visibleElements, selectedElements: selectedElements, versionNonce: versionNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, renderInteractiveSceneCallback: this.renderInteractiveSceneCallback, handleCanvasRef: this.handleInteractiveCanvasRef, onContextMenu: this.handleCanvasContextMenu, onPointerMove: this.handleCanvasPointerMove, onPointerUp: this.handleCanvasPointerUp, onPointerCancel: this.removePointer, onTouchMove: this.handleTouchMove, onPointerDown: this.handleCanvasPointerDown, onDoubleClick: this.handleCanvasDoubleClick }), this.state.userToFollow && (_jsx(FollowMode, { width: this.state.width, height: this.state.height, userToFollow: this.state.userToFollow, onDisconnect: this.maybeUnfollowRemoteUser })), this.renderFrameNames()] }), this.renderEmbeddables()] }) }) }) }) }) }) }) }));
875
+ embedsValidationStatus: this.embedsValidationStatus,
876
+ elementsPendingErasure: this.elementsPendingErasure,
877
+ } }), _jsx(InteractiveCanvas, { containerRef: this.excalidrawContainerRef, canvas: this.interactiveCanvas, elementsMap: elementsMap, visibleElements: visibleElements, selectedElements: selectedElements, versionNonce: versionNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, renderInteractiveSceneCallback: this.renderInteractiveSceneCallback, handleCanvasRef: this.handleInteractiveCanvasRef, onContextMenu: this.handleCanvasContextMenu, onPointerMove: this.handleCanvasPointerMove, onPointerUp: this.handleCanvasPointerUp, onPointerCancel: this.removePointer, onTouchMove: this.handleTouchMove, onPointerDown: this.handleCanvasPointerDown, onDoubleClick: this.handleCanvasDoubleClick }), this.state.userToFollow && (_jsx(FollowMode, { width: this.state.width, height: this.state.height, userToFollow: this.state.userToFollow, onDisconnect: this.maybeUnfollowRemoteUser })), this.renderFrameNames()] }), this.renderEmbeddables()] }) }) }) }) }) }) }) }));
822
878
  }
823
879
  focusContainer = () => {
824
880
  this.excalidrawContainerRef.current?.focus();
@@ -840,7 +896,7 @@ class App extends React.Component {
840
896
  trackEvent("export", type, "ui");
841
897
  const fileHandle = await exportCanvas(type, elements, this.state, this.files, {
842
898
  exportBackground: this.state.exportBackground,
843
- name: this.state.name,
899
+ name: this.getName(),
844
900
  viewBackgroundColor: this.state.viewBackgroundColor,
845
901
  exportingFrame: opts.exportingFrame,
846
902
  })
@@ -893,11 +949,7 @@ class App extends React.Component {
893
949
  trackEvent("ai", "generate (missing key)", "d2c");
894
950
  return;
895
951
  }
896
- const magicFrameChildren = elementsOverlappingBBox({
897
- elements: this.scene.getNonDeletedElements(),
898
- bounds: magicFrame,
899
- type: "overlap",
900
- }).filter((el) => !isMagicFrameElement(el));
952
+ const magicFrameChildren = getElementsOverlappingFrame(this.scene.getNonDeletedElements(), magicFrame).filter((el) => !isMagicFrameElement(el));
901
953
  if (!magicFrameChildren.length) {
902
954
  if (source === "button") {
903
955
  this.setState({ errorMessage: "Cannot generate from an empty frame" });
@@ -1152,7 +1204,7 @@ class App extends React.Component {
1152
1204
  let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
1153
1205
  let gridSize = actionResult?.appState?.gridSize || null;
1154
1206
  const theme = actionResult?.appState?.theme || this.props.theme || THEME.LIGHT;
1155
- let name = actionResult?.appState?.name ?? this.state.name;
1207
+ const name = actionResult?.appState?.name ?? this.state.name;
1156
1208
  const errorMessage = actionResult?.appState?.errorMessage ?? this.state.errorMessage;
1157
1209
  if (typeof this.props.viewModeEnabled !== "undefined") {
1158
1210
  viewModeEnabled = this.props.viewModeEnabled;
@@ -1163,9 +1215,6 @@ class App extends React.Component {
1163
1215
  if (typeof this.props.gridModeEnabled !== "undefined") {
1164
1216
  gridSize = this.props.gridModeEnabled ? GRID_SIZE : null;
1165
1217
  }
1166
- if (typeof this.props.name !== "undefined") {
1167
- name = this.props.name;
1168
- }
1169
1218
  editingElement =
1170
1219
  editingElement || actionResult.appState?.editingElement || null;
1171
1220
  if (editingElement?.isDeleted) {
@@ -1342,7 +1391,6 @@ class App extends React.Component {
1342
1391
  return false;
1343
1392
  };
1344
1393
  async componentDidMount() {
1345
- console.log("HELLO IS");
1346
1394
  this.unmounted = false;
1347
1395
  this.excalidrawContainerValue.container =
1348
1396
  this.excalidrawContainerRef.current;
@@ -1417,7 +1465,8 @@ class App extends React.Component {
1417
1465
  this.removeEventListeners();
1418
1466
  this.scene.destroy();
1419
1467
  this.library.destroy();
1420
- this.laserPathManager.destroy();
1468
+ this.laserTrails.stop();
1469
+ this.eraserTrail.stop();
1421
1470
  this.onChangeEmitter.clear();
1422
1471
  ShapeCache.destroy();
1423
1472
  SnapCache.destroy();
@@ -1425,6 +1474,7 @@ class App extends React.Component {
1425
1474
  isSomeElementSelected.clearCache();
1426
1475
  selectGroupsForSelectedElements.clearCache();
1427
1476
  touchTimeout = 0;
1477
+ document.documentElement.style.overscrollBehaviorX = "";
1428
1478
  }
1429
1479
  onResize = withBatchedUpdates(() => {
1430
1480
  this.scene
@@ -1484,8 +1534,9 @@ class App extends React.Component {
1484
1534
  }
1485
1535
  componentDidUpdate(prevProps, prevState) {
1486
1536
  this.updateEmbeddables();
1487
- if (!this.state.showWelcomeScreen &&
1488
- !this.scene.getElementsIncludingDeleted().length) {
1537
+ const elements = this.scene.getElementsIncludingDeleted();
1538
+ const elementsMap = this.scene.getNonDeletedElementsMap();
1539
+ if (!this.state.showWelcomeScreen && !elements.length) {
1489
1540
  this.setState({ showWelcomeScreen: true });
1490
1541
  }
1491
1542
  if (prevProps.UIOptions.dockedSidebarBreakpoint !==
@@ -1536,6 +1587,9 @@ class App extends React.Component {
1536
1587
  if (prevProps.langCode !== this.props.langCode) {
1537
1588
  this.updateLanguage();
1538
1589
  }
1590
+ if (isEraserActive(prevState) && !isEraserActive(this.state)) {
1591
+ this.eraserTrail.endPath();
1592
+ }
1539
1593
  if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) {
1540
1594
  this.setState({ viewModeEnabled: !!this.props.viewModeEnabled });
1541
1595
  }
@@ -1554,11 +1608,6 @@ class App extends React.Component {
1554
1608
  gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
1555
1609
  });
1556
1610
  }
1557
- if (this.props.name && prevProps.name !== this.props.name) {
1558
- this.setState({
1559
- name: this.props.name,
1560
- });
1561
- }
1562
1611
  this.excalidrawContainerRef.current?.classList.toggle("theme--dark", this.state.theme === "dark");
1563
1612
  if (this.state.editingLinearElement &&
1564
1613
  !this.state.selectedElementIds[this.state.editingLinearElement.elementId]) {
@@ -1587,19 +1636,19 @@ class App extends React.Component {
1587
1636
  multiElement != null &&
1588
1637
  isBindingEnabled(this.state) &&
1589
1638
  isBindingElement(multiElement, false)) {
1590
- maybeBindLinearElement(multiElement, this.state, this.scene, tupleToCoors(LinearElementEditor.getPointAtIndexGlobalCoordinates(multiElement, -1)));
1639
+ maybeBindLinearElement(multiElement, this.state, this.scene, tupleToCoors(LinearElementEditor.getPointAtIndexGlobalCoordinates(multiElement, -1, elementsMap)), elementsMap);
1591
1640
  }
1592
- this.history.record(this.state, this.scene.getElementsIncludingDeleted());
1641
+ this.history.record(this.state, elements);
1593
1642
  // Do not notify consumers if we're still loading the scene. Among other
1594
1643
  // potential issues, this fixes a case where the tab isn't focused during
1595
1644
  // init, which would trigger onChange with empty elements, which would then
1596
1645
  // override whatever is in localStorage currently.
1597
1646
  if (!this.state.isLoading) {
1598
- this.props.onChange?.(this.scene.getElementsIncludingDeleted(), this.state, this.files);
1599
- this.onChangeEmitter.trigger(this.scene.getElementsIncludingDeleted(), this.state, this.files);
1647
+ this.props.onChange?.(elements, this.state, this.files);
1648
+ this.onChangeEmitter.trigger(elements, this.state, this.files);
1600
1649
  }
1601
1650
  }
1602
- renderInteractiveSceneCallback = ({ atLeastOneVisibleElement, scrollBars, elements, }) => {
1651
+ renderInteractiveSceneCallback = ({ atLeastOneVisibleElement, scrollBars, elementsMap, }) => {
1603
1652
  if (scrollBars) {
1604
1653
  currentScrollBars = scrollBars;
1605
1654
  }
@@ -1607,7 +1656,7 @@ class App extends React.Component {
1607
1656
  // hide when editing text
1608
1657
  isTextElement(this.state.editingElement)
1609
1658
  ? false
1610
- : !atLeastOneVisibleElement && elements.length > 0;
1659
+ : !atLeastOneVisibleElement && elementsMap.size > 0;
1611
1660
  if (this.state.scrolledOutside !== scrolledOutside) {
1612
1661
  this.setState({ scrolledOutside });
1613
1662
  }
@@ -1776,18 +1825,40 @@ class App extends React.Component {
1776
1825
  });
1777
1826
  }
1778
1827
  else if (data.text) {
1779
- const maybeUrl = extractSrc(data.text);
1780
- if (!isPlainPaste &&
1781
- embeddableURLValidator(maybeUrl, this.props.validateEmbeddable) &&
1782
- (/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(maybeUrl) ||
1783
- getEmbedLink(maybeUrl)?.type === "video")) {
1784
- const embeddable = this.insertEmbeddableElement({
1785
- sceneX,
1786
- sceneY,
1787
- link: normalizeLink(maybeUrl),
1788
- });
1789
- if (embeddable) {
1790
- this.setState({ selectedElementIds: { [embeddable.id]: true } });
1828
+ const nonEmptyLines = normalizeEOL(data.text)
1829
+ .split(/\n+/)
1830
+ .map((s) => s.trim())
1831
+ .filter(Boolean);
1832
+ const embbeddableUrls = nonEmptyLines
1833
+ .map((str) => maybeParseEmbedSrc(str))
1834
+ .filter((string) => {
1835
+ return (embeddableURLValidator(string, this.props.validateEmbeddable) &&
1836
+ (/^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(string) ||
1837
+ getEmbedLink(string)?.type === "video"));
1838
+ });
1839
+ if (!IS_PLAIN_PASTE &&
1840
+ embbeddableUrls.length > 0 &&
1841
+ // if there were non-embeddable text (lines) mixed in with embeddable
1842
+ // urls, ignore and paste as text
1843
+ embbeddableUrls.length === nonEmptyLines.length) {
1844
+ const embeddables = [];
1845
+ for (const url of embbeddableUrls) {
1846
+ const prevEmbeddable = embeddables[embeddables.length - 1];
1847
+ const embeddable = this.insertEmbeddableElement({
1848
+ sceneX: prevEmbeddable
1849
+ ? prevEmbeddable.x + prevEmbeddable.width + 20
1850
+ : sceneX,
1851
+ sceneY,
1852
+ link: normalizeLink(url),
1853
+ });
1854
+ if (embeddable) {
1855
+ embeddables.push(embeddable);
1856
+ }
1857
+ }
1858
+ if (embeddables.length) {
1859
+ this.setState({
1860
+ selectedElementIds: Object.fromEntries(embeddables.map((embeddable) => [embeddable.id, true])),
1861
+ });
1791
1862
  }
1792
1863
  return;
1793
1864
  }
@@ -1823,15 +1894,20 @@ class App extends React.Component {
1823
1894
  }), {
1824
1895
  randomizeSeed: !opts.retainSeed,
1825
1896
  });
1826
- const nextElements = [
1897
+ const allElements = [
1827
1898
  ...this.scene.getElementsIncludingDeleted(),
1828
1899
  ...newElements,
1829
1900
  ];
1830
- this.scene.replaceAllElements(nextElements);
1901
+ const topLayerFrame = this.getTopLayerFrameAtSceneCoords({ x, y });
1902
+ if (topLayerFrame) {
1903
+ const eligibleElements = filterElementsEligibleAsFrameChildren(newElements, topLayerFrame);
1904
+ addElementsToFrame(allElements, eligibleElements, topLayerFrame);
1905
+ }
1906
+ this.scene.replaceAllElements(allElements);
1831
1907
  newElements.forEach((newElement) => {
1832
1908
  if (isTextElement(newElement) && isBoundToContainer(newElement)) {
1833
- const container = getContainerElement(newElement);
1834
- redrawTextBoundingBox(newElement, container);
1909
+ const container = getContainerElement(newElement, this.scene.getElementsMapIncludingDeleted());
1910
+ redrawTextBoundingBox(newElement, container, this.scene.getElementsMapIncludingDeleted());
1835
1911
  }
1836
1912
  });
1837
1913
  if (opts.files) {
@@ -1886,7 +1962,14 @@ class App extends React.Component {
1886
1962
  return { file: await ImageURLToFile(url) };
1887
1963
  }
1888
1964
  catch (error) {
1889
- return { errorMessage: error.message };
1965
+ let errorMessage = error.message;
1966
+ if (error.cause === "FETCH_ERROR") {
1967
+ errorMessage = t("errors.failedToFetchImage");
1968
+ }
1969
+ else if (error.cause === "UNSUPPORTED") {
1970
+ errorMessage = t("errors.unsupportedFileType");
1971
+ }
1972
+ return { errorMessage };
1890
1973
  }
1891
1974
  }));
1892
1975
  let y = sceneY;
@@ -2354,7 +2437,7 @@ class App extends React.Component {
2354
2437
  x: element.x + offsetX,
2355
2438
  y: element.y + offsetY,
2356
2439
  });
2357
- updateBoundElements(element, {
2440
+ updateBoundElements(element, this.scene.getNonDeletedElementsMap(), {
2358
2441
  simultaneouslyUpdated: selectedElements,
2359
2442
  });
2360
2443
  });
@@ -2372,7 +2455,7 @@ class App extends React.Component {
2372
2455
  selectedElements[0].id) {
2373
2456
  this.history.resumeRecording();
2374
2457
  this.setState({
2375
- editingLinearElement: new LinearElementEditor(selectedElement, this.scene),
2458
+ editingLinearElement: new LinearElementEditor(selectedElement),
2376
2459
  });
2377
2460
  }
2378
2461
  }
@@ -2383,7 +2466,7 @@ class App extends React.Component {
2383
2466
  if (!isTextElement(selectedElement)) {
2384
2467
  container = selectedElement;
2385
2468
  }
2386
- const midPoint = getContainerCenter(selectedElement, this.state);
2469
+ const midPoint = getContainerCenter(selectedElement, this.state, this.scene.getNonDeletedElementsMap());
2387
2470
  const sceneX = midPoint.x;
2388
2471
  const sceneY = midPoint.y;
2389
2472
  this.startTextEditing({
@@ -2497,9 +2580,10 @@ class App extends React.Component {
2497
2580
  }
2498
2581
  if (isArrowKey(event.key)) {
2499
2582
  const selectedElements = this.scene.getSelectedElements(this.state);
2583
+ const elementsMap = this.scene.getNonDeletedElementsMap();
2500
2584
  isBindingEnabled(this.state)
2501
- ? bindOrUnbindSelectedElements(selectedElements)
2502
- : unbindLinearElements(selectedElements);
2585
+ ? bindOrUnbindSelectedElements(selectedElements, this.scene.getNonDeletedElements(), elementsMap)
2586
+ : unbindLinearElements(selectedElements, elementsMap);
2503
2587
  this.setState({ suggestedBindings: [] });
2504
2588
  }
2505
2589
  });
@@ -2576,6 +2660,11 @@ class App extends React.Component {
2576
2660
  // touchscreen
2577
2661
  return gesture.pointers.size >= 2;
2578
2662
  };
2663
+ getName = () => {
2664
+ return (this.state.name ||
2665
+ this.props.name ||
2666
+ `${t("labels.untitled")}-${getDateTime()}`);
2667
+ };
2579
2668
  // fires only on Safari
2580
2669
  onGestureStart = withBatchedUpdates((event) => {
2581
2670
  event.preventDefault();
@@ -2628,11 +2717,13 @@ class App extends React.Component {
2628
2717
  gesture.initialScale = null;
2629
2718
  });
2630
2719
  handleTextWysiwyg(element, { isExistingElement = false, }) {
2720
+ const elementsMap = this.scene.getElementsMapIncludingDeleted();
2631
2721
  const updateElement = (text, originalText, isDeleted) => {
2632
2722
  this.scene.replaceAllElements([
2723
+ // Not sure why we include deleted elements as well hence using deleted elements map
2633
2724
  ...this.scene.getElementsIncludingDeleted().map((_element) => {
2634
2725
  if (_element.id === element.id && isTextElement(_element)) {
2635
- return updateTextElement(_element, {
2726
+ return updateTextElement(_element, getContainerElement(_element, elementsMap), elementsMap, {
2636
2727
  text,
2637
2728
  isDeleted,
2638
2729
  originalText,
@@ -2658,7 +2749,7 @@ class App extends React.Component {
2658
2749
  onChange: withBatchedUpdates((text) => {
2659
2750
  updateElement(text, text, false);
2660
2751
  if (isNonDeletedElement(element)) {
2661
- updateBoundElements(element);
2752
+ updateBoundElements(element, elementsMap);
2662
2753
  }
2663
2754
  }),
2664
2755
  onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
@@ -2734,7 +2825,7 @@ class App extends React.Component {
2734
2825
  const elementWithHighestZIndex = allHitElements[allHitElements.length - 1];
2735
2826
  // If we're hitting element with highest z-index only on its bounding box
2736
2827
  // while also hitting other element figure, the latter should be considered.
2737
- return isHittingElementBoundingBoxWithoutHittingElement(elementWithHighestZIndex, this.state, this.frameNameBoundsCache, x, y)
2828
+ return isHittingElementBoundingBoxWithoutHittingElement(elementWithHighestZIndex, this.state, this.frameNameBoundsCache, x, y, this.scene.getNonDeletedElementsMap())
2738
2829
  ? allHitElements[allHitElements.length - 2]
2739
2830
  : elementWithHighestZIndex;
2740
2831
  }
@@ -2751,13 +2842,14 @@ class App extends React.Component {
2751
2842
  .filter((element) => (includeLockedElements || !element.locked) &&
2752
2843
  (includeBoundTextElement ||
2753
2844
  !(isTextElement(element) && element.containerId)));
2754
- return getElementsAtPosition(elements, (element) => hitTest(element, this.state, this.frameNameBoundsCache, x, y)).filter((element) => {
2845
+ const elementsMap = this.scene.getNonDeletedElementsMap();
2846
+ return getElementsAtPosition(elements, (element) => hitTest(element, this.state, this.frameNameBoundsCache, x, y, elementsMap)).filter((element) => {
2755
2847
  // hitting a frame's element from outside the frame is not considered a hit
2756
- const containingFrame = getContainingFrame(element);
2848
+ const containingFrame = getContainingFrame(element, elementsMap);
2757
2849
  return containingFrame &&
2758
2850
  this.state.frameRendering.enabled &&
2759
2851
  this.state.frameRendering.clip
2760
- ? isCursorInFrame({ x, y }, containingFrame)
2852
+ ? isCursorInFrame({ x, y }, containingFrame, elementsMap)
2761
2853
  : true;
2762
2854
  });
2763
2855
  }
@@ -2766,7 +2858,7 @@ class App extends React.Component {
2766
2858
  let parentCenterPosition = insertAtParentCenter &&
2767
2859
  this.getTextWysiwygSnappedToCenterPosition(sceneX, sceneY, this.state, container);
2768
2860
  if (container && parentCenterPosition) {
2769
- const boundTextElementToContainer = getBoundTextElement(container);
2861
+ const boundTextElementToContainer = getBoundTextElement(container, this.scene.getNonDeletedElementsMap());
2770
2862
  if (!boundTextElementToContainer) {
2771
2863
  shouldBindToContainer = true;
2772
2864
  }
@@ -2778,7 +2870,7 @@ class App extends React.Component {
2778
2870
  existingTextElement = selectedElements[0];
2779
2871
  }
2780
2872
  else if (container) {
2781
- existingTextElement = getBoundTextElement(selectedElements[0]);
2873
+ existingTextElement = getBoundTextElement(selectedElements[0], this.scene.getNonDeletedElementsMap());
2782
2874
  }
2783
2875
  else {
2784
2876
  existingTextElement = this.getTextElementAtPosition(sceneX, sceneY);
@@ -2886,7 +2978,7 @@ class App extends React.Component {
2886
2978
  this.state.editingLinearElement.elementId !== selectedElements[0].id)) {
2887
2979
  this.history.resumeRecording();
2888
2980
  this.setState({
2889
- editingLinearElement: new LinearElementEditor(selectedElements[0], this.scene),
2981
+ editingLinearElement: new LinearElementEditor(selectedElements[0]),
2890
2982
  });
2891
2983
  return;
2892
2984
  }
@@ -2922,12 +3014,12 @@ class App extends React.Component {
2922
3014
  });
2923
3015
  return;
2924
3016
  }
2925
- const container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY);
3017
+ const container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
2926
3018
  if (container) {
2927
3019
  if (hasBoundTextElement(container) ||
2928
3020
  !isTransparent(container.backgroundColor) ||
2929
- isHittingElementNotConsideringBoundingBox(container, this.state, this.frameNameBoundsCache, [sceneX, sceneY])) {
2930
- const midPoint = getContainerCenter(container, this.state);
3021
+ isHittingElementNotConsideringBoundingBox(container, this.state, this.frameNameBoundsCache, [sceneX, sceneY], this.scene.getNonDeletedElementsMap())) {
3022
+ const midPoint = getContainerCenter(container, this.state, this.scene.getNonDeletedElementsMap());
2931
3023
  sceneX = midPoint.x;
2932
3024
  sceneY = midPoint.y;
2933
3025
  }
@@ -2951,7 +3043,7 @@ class App extends React.Component {
2951
3043
  }
2952
3044
  return (element.link &&
2953
3045
  index <= hitElementIndex &&
2954
- isPointHittingLink(element, this.state, [scenePointer.x, scenePointer.y], this.device.editor.isMobile));
3046
+ isPointHittingLink(element, this.scene.getNonDeletedElementsMap(), this.state, [scenePointer.x, scenePointer.y], this.device.editor.isMobile));
2955
3047
  });
2956
3048
  };
2957
3049
  redirectToLink = (event, isTouchScreen) => {
@@ -2963,9 +3055,10 @@ class App extends React.Component {
2963
3055
  return;
2964
3056
  }
2965
3057
  const lastPointerDownCoords = viewportCoordsToSceneCoords(this.lastPointerDownEvent, this.state);
2966
- const lastPointerDownHittingLinkIcon = isPointHittingLink(this.hitLinkElement, this.state, [lastPointerDownCoords.x, lastPointerDownCoords.y], this.device.editor.isMobile);
3058
+ const elementsMap = this.scene.getNonDeletedElementsMap();
3059
+ const lastPointerDownHittingLinkIcon = isPointHittingLink(this.hitLinkElement, elementsMap, this.state, [lastPointerDownCoords.x, lastPointerDownCoords.y], this.device.editor.isMobile);
2967
3060
  const lastPointerUpCoords = viewportCoordsToSceneCoords(this.lastPointerUpEvent, this.state);
2968
- const lastPointerUpHittingLinkIcon = isPointHittingLink(this.hitLinkElement, this.state, [lastPointerUpCoords.x, lastPointerUpCoords.y], this.device.editor.isMobile);
3061
+ const lastPointerUpHittingLinkIcon = isPointHittingLink(this.hitLinkElement, elementsMap, this.state, [lastPointerUpCoords.x, lastPointerUpCoords.y], this.device.editor.isMobile);
2969
3062
  if (lastPointerDownHittingLinkIcon && lastPointerUpHittingLinkIcon) {
2970
3063
  let url = this.hitLinkElement.link;
2971
3064
  if (url) {
@@ -2991,9 +3084,10 @@ class App extends React.Component {
2991
3084
  }
2992
3085
  };
2993
3086
  getTopLayerFrameAtSceneCoords = (sceneCoords) => {
3087
+ const elementsMap = this.scene.getNonDeletedElementsMap();
2994
3088
  const frames = this.scene
2995
3089
  .getNonDeletedFramesLikes()
2996
- .filter((frame) => isCursorInFrame(sceneCoords, frame));
3090
+ .filter((frame) => isCursorInFrame(sceneCoords, frame, elementsMap));
2997
3091
  return frames.length ? frames[frames.length - 1] : null;
2998
3092
  };
2999
3093
  handleCanvasPointerMove = (event) => {
@@ -3065,7 +3159,7 @@ class App extends React.Component {
3065
3159
  const { originOffset, snapLines } = getSnapLinesAtPointer(this.scene.getNonDeletedElements(), this.state, {
3066
3160
  x: scenePointerX,
3067
3161
  y: scenePointerY,
3068
- }, event);
3162
+ }, event, this.scene.getNonDeletedElementsMap());
3069
3163
  this.setState((prevState) => {
3070
3164
  const nextSnapLines = updateStable(prevState.snapLines, snapLines);
3071
3165
  const nextOriginOffset = prevState.originSnapOffset
@@ -3093,7 +3187,7 @@ class App extends React.Component {
3093
3187
  }
3094
3188
  if (this.state.editingLinearElement &&
3095
3189
  !this.state.editingLinearElement.isDragging) {
3096
- const editingLinearElement = LinearElementEditor.handlePointerMove(event, scenePointerX, scenePointerY, this.state);
3190
+ const editingLinearElement = LinearElementEditor.handlePointerMove(event, scenePointerX, scenePointerY, this.state, this.scene.getNonDeletedElementsMap());
3097
3191
  if (editingLinearElement &&
3098
3192
  editingLinearElement !== this.state.editingLinearElement) {
3099
3193
  // Since we are reading from previous state which is not possible with
@@ -3195,7 +3289,7 @@ class App extends React.Component {
3195
3289
  if (selectedElements.length === 1 &&
3196
3290
  !isOverScrollBar &&
3197
3291
  !this.state.editingLinearElement) {
3198
- const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, scenePointerX, scenePointerY, this.state.zoom, event.pointerType);
3292
+ const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, scenePointerX, scenePointerY, this.state.zoom, event.pointerType, this.scene.getNonDeletedElementsMap());
3199
3293
  if (elementWithTransformHandleType &&
3200
3294
  elementWithTransformHandleType.transformHandleType) {
3201
3295
  setCursor(this.interactiveCanvas, getCursorForResizingElement(elementWithTransformHandleType));
@@ -3219,7 +3313,7 @@ class App extends React.Component {
3219
3313
  if (this.hitLinkElement &&
3220
3314
  !this.state.selectedElementIds[this.hitLinkElement.id]) {
3221
3315
  setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
3222
- showHyperlinkTooltip(this.hitLinkElement, this.state);
3316
+ showHyperlinkTooltip(this.hitLinkElement, this.state, this.scene.getNonDeletedElementsMap());
3223
3317
  }
3224
3318
  else {
3225
3319
  hideHyperlinkToolip();
@@ -3270,34 +3364,49 @@ class App extends React.Component {
3270
3364
  }
3271
3365
  };
3272
3366
  handleEraser = (event, pointerDownState, scenePointer) => {
3273
- const updateElementIds = (elements) => {
3274
- elements.forEach((element) => {
3367
+ this.eraserTrail.addPointToPath(scenePointer.x, scenePointer.y);
3368
+ let didChange = false;
3369
+ const processedGroups = new Set();
3370
+ const nonDeletedElements = this.scene.getNonDeletedElements();
3371
+ const processElements = (elements) => {
3372
+ for (const element of elements) {
3275
3373
  if (element.locked) {
3276
3374
  return;
3277
3375
  }
3278
- idsToUpdate.push(element.id);
3279
3376
  if (event.altKey) {
3280
- if (pointerDownState.elementIdsToErase[element.id] &&
3281
- pointerDownState.elementIdsToErase[element.id].erase) {
3282
- pointerDownState.elementIdsToErase[element.id].erase = false;
3377
+ if (this.elementsPendingErasure.delete(element.id)) {
3378
+ didChange = true;
3283
3379
  }
3284
3380
  }
3285
- else if (!pointerDownState.elementIdsToErase[element.id]) {
3286
- pointerDownState.elementIdsToErase[element.id] = {
3287
- erase: true,
3288
- opacity: element.opacity,
3289
- };
3381
+ else if (!this.elementsPendingErasure.has(element.id)) {
3382
+ didChange = true;
3383
+ this.elementsPendingErasure.add(element.id);
3290
3384
  }
3291
- });
3385
+ // (un)erase groups atomically
3386
+ if (didChange && element.groupIds?.length) {
3387
+ const shallowestGroupId = element.groupIds.at(-1);
3388
+ if (!processedGroups.has(shallowestGroupId)) {
3389
+ processedGroups.add(shallowestGroupId);
3390
+ const elems = getElementsInGroup(nonDeletedElements, shallowestGroupId);
3391
+ for (const elem of elems) {
3392
+ if (event.altKey) {
3393
+ this.elementsPendingErasure.delete(elem.id);
3394
+ }
3395
+ else {
3396
+ this.elementsPendingErasure.add(elem.id);
3397
+ }
3398
+ }
3399
+ }
3400
+ }
3401
+ }
3292
3402
  };
3293
- const idsToUpdate = [];
3294
3403
  const distance = distance2d(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y, scenePointer.x, scenePointer.y);
3295
3404
  const threshold = 10 / this.state.zoom.value;
3296
3405
  const point = { ...pointerDownState.lastCoords };
3297
3406
  let samplingInterval = 0;
3298
3407
  while (samplingInterval <= distance) {
3299
3408
  const hitElements = this.getElementsAtPosition(point.x, point.y);
3300
- updateElementIds(hitElements);
3409
+ processElements(hitElements);
3301
3410
  // Exit since we reached current point
3302
3411
  if (samplingInterval === distance) {
3303
3412
  break;
@@ -3310,48 +3419,45 @@ class App extends React.Component {
3310
3419
  point.x = nextX;
3311
3420
  point.y = nextY;
3312
3421
  }
3313
- const elements = this.scene.getElementsIncludingDeleted().map((ele) => {
3314
- const id = isBoundToContainer(ele) && idsToUpdate.includes(ele.containerId)
3315
- ? ele.containerId
3316
- : ele.id;
3317
- if (idsToUpdate.includes(id)) {
3318
- if (event.altKey) {
3319
- if (pointerDownState.elementIdsToErase[id] &&
3320
- pointerDownState.elementIdsToErase[id].erase === false) {
3321
- return newElementWith(ele, {
3322
- opacity: pointerDownState.elementIdsToErase[id].opacity,
3323
- });
3422
+ pointerDownState.lastCoords.x = scenePointer.x;
3423
+ pointerDownState.lastCoords.y = scenePointer.y;
3424
+ if (didChange) {
3425
+ for (const element of this.scene.getNonDeletedElements()) {
3426
+ if (isBoundToContainer(element) &&
3427
+ (this.elementsPendingErasure.has(element.id) ||
3428
+ this.elementsPendingErasure.has(element.containerId))) {
3429
+ if (event.altKey) {
3430
+ this.elementsPendingErasure.delete(element.id);
3431
+ this.elementsPendingErasure.delete(element.containerId);
3432
+ }
3433
+ else {
3434
+ this.elementsPendingErasure.add(element.id);
3435
+ this.elementsPendingErasure.add(element.containerId);
3324
3436
  }
3325
- }
3326
- else {
3327
- return newElementWith(ele, {
3328
- opacity: ELEMENT_READY_TO_ERASE_OPACITY,
3329
- });
3330
3437
  }
3331
3438
  }
3332
- return ele;
3333
- });
3334
- this.scene.replaceAllElements(elements);
3335
- pointerDownState.lastCoords.x = scenePointer.x;
3336
- pointerDownState.lastCoords.y = scenePointer.y;
3439
+ this.elementsPendingErasure = new Set(this.elementsPendingErasure);
3440
+ this.onSceneUpdated();
3441
+ }
3337
3442
  };
3338
3443
  // set touch moving for mobile context menu
3339
3444
  handleTouchMove = (event) => {
3340
3445
  invalidateContextMenu = true;
3341
3446
  };
3342
3447
  handleHoverSelectedLinearElement(linearElementEditor, scenePointerX, scenePointerY) {
3343
- const element = LinearElementEditor.getElement(linearElementEditor.elementId);
3344
- const boundTextElement = getBoundTextElement(element);
3448
+ const elementsMap = this.scene.getNonDeletedElementsMap();
3449
+ const element = LinearElementEditor.getElement(linearElementEditor.elementId, elementsMap);
3450
+ const boundTextElement = getBoundTextElement(element, elementsMap);
3345
3451
  if (!element) {
3346
3452
  return;
3347
3453
  }
3348
3454
  if (this.state.selectedLinearElement) {
3349
3455
  let hoverPointIndex = -1;
3350
3456
  let segmentMidPointHoveredCoords = null;
3351
- if (isHittingElementNotConsideringBoundingBox(element, this.state, this.frameNameBoundsCache, [scenePointerX, scenePointerY])) {
3352
- hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(element, this.state.zoom, scenePointerX, scenePointerY);
3457
+ if (isHittingElementNotConsideringBoundingBox(element, this.state, this.frameNameBoundsCache, [scenePointerX, scenePointerY], elementsMap)) {
3458
+ hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(element, elementsMap, this.state.zoom, scenePointerX, scenePointerY);
3353
3459
  segmentMidPointHoveredCoords =
3354
- LinearElementEditor.getSegmentMidpointHitCoords(linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state);
3460
+ LinearElementEditor.getSegmentMidpointHitCoords(linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state, this.scene.getNonDeletedElementsMap());
3355
3461
  if (hoverPointIndex >= 0 || segmentMidPointHoveredCoords) {
3356
3462
  setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
3357
3463
  }
@@ -3360,11 +3466,11 @@ class App extends React.Component {
3360
3466
  }
3361
3467
  }
3362
3468
  else if (shouldShowBoundingBox([element], this.state) &&
3363
- isHittingElementBoundingBoxWithoutHittingElement(element, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY)) {
3469
+ isHittingElementBoundingBoxWithoutHittingElement(element, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, elementsMap)) {
3364
3470
  setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3365
3471
  }
3366
3472
  else if (boundTextElement &&
3367
- hitTest(boundTextElement, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY)) {
3473
+ hitTest(boundTextElement, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, this.scene.getNonDeletedElementsMap())) {
3368
3474
  setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3369
3475
  }
3370
3476
  if (this.state.selectedLinearElement.hoverPointIndex !== hoverPointIndex) {
@@ -3582,7 +3688,7 @@ class App extends React.Component {
3582
3688
  this.createFrameElementOnPointerDown(pointerDownState, this.state.activeTool.type);
3583
3689
  }
3584
3690
  else if (this.state.activeTool.type === "laser") {
3585
- this.laserPathManager.startPath(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y);
3691
+ this.laserTrails.startPath(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y);
3586
3692
  }
3587
3693
  else if (this.state.activeTool.type !== "eraser" &&
3588
3694
  this.state.activeTool.type !== "hand") {
@@ -3590,6 +3696,9 @@ class App extends React.Component {
3590
3696
  }
3591
3697
  this.props?.onPointerDown?.(this.state.activeTool, pointerDownState);
3592
3698
  this.onPointerDownEmitter.trigger(this.state.activeTool, pointerDownState, event);
3699
+ if (this.state.activeTool.type === "eraser") {
3700
+ this.eraserTrail.startPath(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y);
3701
+ }
3593
3702
  const onPointerMove = this.onPointerMoveFromPointerDownHandler(pointerDownState);
3594
3703
  const onPointerUp = this.onPointerUpFromPointerDownHandler(pointerDownState);
3595
3704
  const onKeyDown = this.onKeyDownFromPointerDownHandler(pointerDownState);
@@ -3627,10 +3736,7 @@ class App extends React.Component {
3627
3736
  !this.state.selectedElementIds[this.hitLinkElement.id]) {
3628
3737
  if (clicklength < 300 &&
3629
3738
  isIframeLikeElement(this.hitLinkElement) &&
3630
- !isPointHittingLinkIcon(this.hitLinkElement, this.state, [
3631
- scenePointer.x,
3632
- scenePointer.y,
3633
- ])) {
3739
+ !isPointHittingLinkIcon(this.hitLinkElement, this.scene.getNonDeletedElementsMap(), this.state, [scenePointer.x, scenePointer.y])) {
3634
3740
  this.handleEmbeddableCenterClick(this.hitLinkElement);
3635
3741
  }
3636
3742
  else {
@@ -3693,7 +3799,9 @@ class App extends React.Component {
3693
3799
  isPanning = true;
3694
3800
  event.preventDefault();
3695
3801
  let nextPastePrevented = false;
3696
- const isLinux = /Linux/.test(window.navigator.platform);
3802
+ const isLinux = typeof window === undefined
3803
+ ? false
3804
+ : /Linux/.test(window.navigator.platform);
3697
3805
  setCursor(this.interactiveCanvas, CURSOR_TYPE.GRABBING);
3698
3806
  let { clientX: lastX, clientY: lastY } = event;
3699
3807
  const onPointerMove = withBatchedUpdatesThrottled((event) => {
@@ -3816,7 +3924,6 @@ class App extends React.Component {
3816
3924
  boxSelection: {
3817
3925
  hasOccurred: false,
3818
3926
  },
3819
- elementIdsToErase: {},
3820
3927
  };
3821
3928
  }
3822
3929
  // Returns whether the event is a dragging a scrollbar
@@ -3867,9 +3974,10 @@ class App extends React.Component {
3867
3974
  handleSelectionOnPointerDown = (event, pointerDownState) => {
3868
3975
  if (this.state.activeTool.type === "selection") {
3869
3976
  const elements = this.scene.getNonDeletedElements();
3977
+ const elementsMap = this.scene.getNonDeletedElementsMap();
3870
3978
  const selectedElements = this.scene.getSelectedElements(this.state);
3871
3979
  if (selectedElements.length === 1 && !this.state.editingLinearElement) {
3872
- const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType);
3980
+ const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType, this.scene.getNonDeletedElementsMap());
3873
3981
  if (elementWithTransformHandleType != null) {
3874
3982
  this.setState({
3875
3983
  resizingElement: elementWithTransformHandleType.element,
@@ -3883,7 +3991,7 @@ class App extends React.Component {
3883
3991
  }
3884
3992
  if (pointerDownState.resize.handleType) {
3885
3993
  pointerDownState.resize.isResizing = true;
3886
- pointerDownState.resize.offset = tupleToCoors(getResizeOffsetXY(pointerDownState.resize.handleType, selectedElements, pointerDownState.origin.x, pointerDownState.origin.y));
3994
+ pointerDownState.resize.offset = tupleToCoors(getResizeOffsetXY(pointerDownState.resize.handleType, selectedElements, elementsMap, pointerDownState.origin.x, pointerDownState.origin.y));
3887
3995
  if (selectedElements.length === 1 &&
3888
3996
  isLinearElement(selectedElements[0]) &&
3889
3997
  selectedElements[0].points.length === 2) {
@@ -3893,7 +4001,7 @@ class App extends React.Component {
3893
4001
  else {
3894
4002
  if (this.state.selectedLinearElement) {
3895
4003
  const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
3896
- const ret = LinearElementEditor.handlePointerDown(event, this.state, this.history, pointerDownState.origin, linearElementEditor);
4004
+ const ret = LinearElementEditor.handlePointerDown(event, this.state, this.history, pointerDownState.origin, linearElementEditor, this.scene.getNonDeletedElements(), elementsMap);
3897
4005
  if (ret.hitElement) {
3898
4006
  pointerDownState.hit.element = ret.hitElement;
3899
4007
  }
@@ -4074,7 +4182,7 @@ class App extends React.Component {
4074
4182
  includeBoundTextElement: true,
4075
4183
  });
4076
4184
  // FIXME
4077
- let container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY);
4185
+ let container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
4078
4186
  if (hasBoundTextElement(element)) {
4079
4187
  container = element;
4080
4188
  sceneX = element.x + element.width / 2;
@@ -4132,7 +4240,7 @@ class App extends React.Component {
4132
4240
  points: [[0, 0]],
4133
4241
  pressures,
4134
4242
  });
4135
- const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene);
4243
+ const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
4136
4244
  this.scene.addNewElement(element);
4137
4245
  this.setState({
4138
4246
  draggingElement: element,
@@ -4176,8 +4284,11 @@ class App extends React.Component {
4176
4284
  if (!embedLink) {
4177
4285
  return;
4178
4286
  }
4179
- if (embedLink.warning) {
4180
- this.setToast({ message: embedLink.warning, closable: true });
4287
+ if (embedLink.error instanceof URIError) {
4288
+ this.setToast({
4289
+ message: t("toast.unrecognizedLinkFormat"),
4290
+ closable: true,
4291
+ });
4181
4292
  }
4182
4293
  const element = newEmbeddableElement({
4183
4294
  type: "embeddable",
@@ -4195,7 +4306,6 @@ class App extends React.Component {
4195
4306
  width: embedLink.intrinsicSize.w,
4196
4307
  height: embedLink.intrinsicSize.h,
4197
4308
  link,
4198
- validated: null,
4199
4309
  });
4200
4310
  this.scene.replaceAllElements([
4201
4311
  ...this.scene.getElementsIncludingDeleted(),
@@ -4308,7 +4418,7 @@ class App extends React.Component {
4308
4418
  mutateElement(element, {
4309
4419
  points: [...element.points, [0, 0]],
4310
4420
  });
4311
- const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene);
4421
+ const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
4312
4422
  this.scene.addNewElement(element);
4313
4423
  this.setState({
4314
4424
  draggingElement: element,
@@ -4353,7 +4463,6 @@ class App extends React.Component {
4353
4463
  if (elementType === "embeddable") {
4354
4464
  element = newEmbeddableElement({
4355
4465
  type: "embeddable",
4356
- validated: null,
4357
4466
  ...baseElementAttributes,
4358
4467
  });
4359
4468
  }
@@ -4409,7 +4518,7 @@ class App extends React.Component {
4409
4518
  selectedElements,
4410
4519
  }) &&
4411
4520
  (recomputeAnyways || !SnapCache.getReferenceSnapPoints())) {
4412
- SnapCache.setReferenceSnapPoints(getReferenceSnapPoints(this.scene.getNonDeletedElements(), selectedElements, this.state));
4521
+ SnapCache.setReferenceSnapPoints(getReferenceSnapPoints(this.scene.getNonDeletedElements(), selectedElements, this.state, this.scene.getNonDeletedElementsMap()));
4413
4522
  }
4414
4523
  }
4415
4524
  maybeCacheVisibleGaps(event, selectedElements, recomputeAnyways = false) {
@@ -4419,7 +4528,7 @@ class App extends React.Component {
4419
4528
  selectedElements,
4420
4529
  }) &&
4421
4530
  (recomputeAnyways || !SnapCache.getVisibleGaps())) {
4422
- SnapCache.setVisibleGaps(getVisibleGaps(this.scene.getNonDeletedElements(), selectedElements, this.state));
4531
+ SnapCache.setVisibleGaps(getVisibleGaps(this.scene.getNonDeletedElements(), selectedElements, this.state, this.scene.getNonDeletedElementsMap()));
4423
4532
  }
4424
4533
  }
4425
4534
  onKeyDownFromPointerDownHandler(pointerDownState) {
@@ -4462,7 +4571,7 @@ class App extends React.Component {
4462
4571
  return;
4463
4572
  }
4464
4573
  if (this.state.activeTool.type === "laser") {
4465
- this.laserPathManager.addPointToPath(pointerCoords.x, pointerCoords.y);
4574
+ this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
4466
4575
  }
4467
4576
  const [gridX, gridY] = getGridPoint(pointerCoords.x, pointerCoords.y, event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize);
4468
4577
  // for arrows/lines, don't start dragging until a given threshold
@@ -4483,10 +4592,11 @@ class App extends React.Component {
4483
4592
  return true;
4484
4593
  }
4485
4594
  }
4595
+ const elementsMap = this.scene.getNonDeletedElementsMap();
4486
4596
  if (this.state.selectedLinearElement) {
4487
4597
  const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
4488
- if (LinearElementEditor.shouldAddMidpoint(this.state.selectedLinearElement, pointerCoords, this.state)) {
4489
- const ret = LinearElementEditor.addMidpoint(this.state.selectedLinearElement, pointerCoords, this.state, !event[KEYS.CTRL_OR_CMD]);
4598
+ if (LinearElementEditor.shouldAddMidpoint(this.state.selectedLinearElement, pointerCoords, this.state, elementsMap)) {
4599
+ const ret = LinearElementEditor.addMidpoint(this.state.selectedLinearElement, pointerCoords, this.state, !event[KEYS.CTRL_OR_CMD], elementsMap);
4490
4600
  if (!ret) {
4491
4601
  return;
4492
4602
  }
@@ -4521,7 +4631,7 @@ class App extends React.Component {
4521
4631
  }
4522
4632
  const didDrag = LinearElementEditor.handlePointDragging(event, this.state, pointerCoords.x, pointerCoords.y, (element, pointsSceneCoords) => {
4523
4633
  this.maybeSuggestBindingsForLinearElementAtCoords(element, pointsSceneCoords);
4524
- }, linearElementEditor);
4634
+ }, linearElementEditor, this.scene.getNonDeletedElementsMap());
4525
4635
  if (didDrag) {
4526
4636
  pointerDownState.lastCoords.x = pointerCoords.x;
4527
4637
  pointerDownState.lastCoords.y = pointerCoords.y;
@@ -4602,7 +4712,7 @@ class App extends React.Component {
4602
4712
  // it snaps to its position if previously snapped already.
4603
4713
  this.maybeCacheVisibleGaps(event, selectedElements);
4604
4714
  this.maybeCacheReferenceSnapPoints(event, selectedElements);
4605
- const { snapOffset, snapLines } = snapDraggedElements(getSelectedElements(originalElements, this.state), dragOffset, this.state, event);
4715
+ const { snapOffset, snapLines } = snapDraggedElements(originalElements, dragOffset, this.state, event, this.scene.getNonDeletedElementsMap());
4606
4716
  this.setState({ snapLines });
4607
4717
  // when we're editing the name of a frame, we want the user to be
4608
4718
  // able to select and interact with the text input
@@ -4720,7 +4830,7 @@ class App extends React.Component {
4720
4830
  const elements = this.scene.getNonDeletedElements();
4721
4831
  // box-select line editor points
4722
4832
  if (this.state.editingLinearElement) {
4723
- LinearElementEditor.handleBoxSelection(event, this.state, this.setState.bind(this));
4833
+ LinearElementEditor.handleBoxSelection(event, this.state, this.setState.bind(this), this.scene.getNonDeletedElementsMap());
4724
4834
  // regular box-select
4725
4835
  }
4726
4836
  else {
@@ -4739,7 +4849,7 @@ class App extends React.Component {
4739
4849
  shouldReuseSelection = false;
4740
4850
  }
4741
4851
  }
4742
- const elementsWithinSelection = getElementsWithinSelection(elements, draggingElement);
4852
+ const elementsWithinSelection = getElementsWithinSelection(elements, draggingElement, this.scene.getNonDeletedElementsMap());
4743
4853
  this.setState((prevState) => {
4744
4854
  const nextSelectedElementIds = {
4745
4855
  ...(shouldReuseSelection && prevState.selectedElementIds),
@@ -4769,7 +4879,7 @@ class App extends React.Component {
4769
4879
  // select linear element only when we haven't box-selected anything else
4770
4880
  selectedLinearElement: elementsWithinSelection.length === 1 &&
4771
4881
  isLinearElement(elementsWithinSelection[0])
4772
- ? new LinearElementEditor(elementsWithinSelection[0], this.scene)
4882
+ ? new LinearElementEditor(elementsWithinSelection[0])
4773
4883
  : null,
4774
4884
  showHyperlinkPopup: elementsWithinSelection.length === 1 &&
4775
4885
  (elementsWithinSelection[0].link ||
@@ -4833,6 +4943,7 @@ class App extends React.Component {
4833
4943
  this.setState({
4834
4944
  selectedElementsAreBeingDragged: false,
4835
4945
  });
4946
+ const elementsMap = this.scene.getNonDeletedElementsMap();
4836
4947
  // Handle end of dragging a point of a linear element, might close a loop
4837
4948
  // and sets binding element
4838
4949
  if (this.state.editingLinearElement) {
@@ -4842,7 +4953,7 @@ class App extends React.Component {
4842
4953
  this.actionManager.executeAction(actionFinalize);
4843
4954
  }
4844
4955
  else {
4845
- const editingLinearElement = LinearElementEditor.handlePointerUp(childEvent, this.state.editingLinearElement, this.state);
4956
+ const editingLinearElement = LinearElementEditor.handlePointerUp(childEvent, this.state.editingLinearElement, this.state, this.scene.getNonDeletedElements(), elementsMap);
4846
4957
  if (editingLinearElement !== this.state.editingLinearElement) {
4847
4958
  this.setState({
4848
4959
  editingLinearElement,
@@ -4861,11 +4972,11 @@ class App extends React.Component {
4861
4972
  }
4862
4973
  }
4863
4974
  else {
4864
- const linearElementEditor = LinearElementEditor.handlePointerUp(childEvent, this.state.selectedLinearElement, this.state);
4975
+ const linearElementEditor = LinearElementEditor.handlePointerUp(childEvent, this.state.selectedLinearElement, this.state, this.scene.getNonDeletedElements(), elementsMap);
4865
4976
  const { startBindingElement, endBindingElement } = linearElementEditor;
4866
4977
  const element = this.scene.getElement(linearElementEditor.elementId);
4867
4978
  if (isBindingElement(element)) {
4868
- bindOrUnbindLinearElement(element, startBindingElement, endBindingElement);
4979
+ bindOrUnbindLinearElement(element, startBindingElement, endBindingElement, elementsMap);
4869
4980
  }
4870
4981
  if (linearElementEditor !== this.state.selectedLinearElement) {
4871
4982
  this.setState({
@@ -4886,6 +4997,7 @@ class App extends React.Component {
4886
4997
  if (this.state.pendingImageElementId) {
4887
4998
  this.setState({ pendingImageElementId: null });
4888
4999
  }
5000
+ this.props?.onPointerUp?.(activeTool, pointerDownState);
4889
5001
  this.onPointerUpEmitter.trigger(this.state.activeTool, pointerDownState, childEvent);
4890
5002
  if (draggingElement?.type === "freedraw") {
4891
5003
  const pointerCoords = viewportCoordsToSceneCoords(childEvent, this.state);
@@ -4952,7 +5064,7 @@ class App extends React.Component {
4952
5064
  else if (pointerDownState.drag.hasOccurred && !multiElement) {
4953
5065
  if (isBindingEnabled(this.state) &&
4954
5066
  isBindingElement(draggingElement, false)) {
4955
- maybeBindLinearElement(draggingElement, this.state, this.scene, pointerCoords);
5067
+ maybeBindLinearElement(draggingElement, this.state, this.scene, pointerCoords, elementsMap);
4956
5068
  }
4957
5069
  this.setState({ suggestedBindings: [], startBoundElement: null });
4958
5070
  if (!activeTool.locked) {
@@ -4966,7 +5078,7 @@ class App extends React.Component {
4966
5078
  ...prevState.selectedElementIds,
4967
5079
  [draggingElement.id]: true,
4968
5080
  }, prevState),
4969
- selectedLinearElement: new LinearElementEditor(draggingElement, this.scene),
5081
+ selectedLinearElement: new LinearElementEditor(draggingElement),
4970
5082
  }));
4971
5083
  }
4972
5084
  else {
@@ -4999,15 +5111,16 @@ class App extends React.Component {
4999
5111
  this.state.selectedLinearElement.isDragging) {
5000
5112
  const linearElement = this.scene.getElement(this.state.selectedLinearElement.elementId);
5001
5113
  if (linearElement?.frameId) {
5002
- const frame = getContainingFrame(linearElement);
5114
+ const frame = getContainingFrame(linearElement, elementsMap);
5003
5115
  if (frame && linearElement) {
5004
- if (!elementOverlapsWithFrame(linearElement, frame)) {
5116
+ if (!elementOverlapsWithFrame(linearElement, frame, this.scene.getNonDeletedElementsMap())) {
5005
5117
  // remove the linear element from all groups
5006
5118
  // before removing it from the frame as well
5007
5119
  mutateElement(linearElement, {
5008
5120
  groupIds: [],
5009
5121
  });
5010
- this.scene.replaceAllElements(removeElementsFromFrame(this.scene.getElementsIncludingDeleted(), [linearElement], this.state));
5122
+ removeElementsFromFrame([linearElement], this.scene.getNonDeletedElementsMap());
5123
+ this.scene.informMutation();
5011
5124
  }
5012
5125
  }
5013
5126
  }
@@ -5016,7 +5129,7 @@ class App extends React.Component {
5016
5129
  // update the relationships between selected elements and frames
5017
5130
  const topLayerFrame = this.getTopLayerFrameAtSceneCoords(sceneCoords);
5018
5131
  const selectedElements = this.scene.getSelectedElements(this.state);
5019
- let nextElements = this.scene.getElementsIncludingDeleted();
5132
+ let nextElements = this.scene.getElementsMapIncludingDeleted();
5020
5133
  const updateGroupIdsAfterEditingGroup = (elements) => {
5021
5134
  if (elements.length > 0) {
5022
5135
  for (const element of elements) {
@@ -5059,8 +5172,8 @@ class App extends React.Component {
5059
5172
  }
5060
5173
  }
5061
5174
  if (isFrameLikeElement(draggingElement)) {
5062
- const elementsInsideFrame = getElementsInNewFrame(this.scene.getElementsIncludingDeleted(), draggingElement);
5063
- this.scene.replaceAllElements(addElementsToFrame(this.scene.getElementsIncludingDeleted(), elementsInsideFrame, draggingElement));
5175
+ const elementsInsideFrame = getElementsInNewFrame(this.scene.getElementsIncludingDeleted(), draggingElement, this.scene.getNonDeletedElementsMap());
5176
+ this.scene.replaceAllElements(addElementsToFrame(this.scene.getElementsMapIncludingDeleted(), elementsInsideFrame, draggingElement));
5064
5177
  }
5065
5178
  mutateElement(draggingElement, getNormalizedDimensions(draggingElement));
5066
5179
  }
@@ -5079,7 +5192,7 @@ class App extends React.Component {
5079
5192
  .getSelectedElements(this.state)
5080
5193
  .filter((element) => isFrameLikeElement(element));
5081
5194
  for (const frame of selectedFrames) {
5082
- nextElements = replaceAllElementsInFrame(nextElements, getElementsInResizingFrame(this.scene.getElementsIncludingDeleted(), frame, this.state), frame, this.state);
5195
+ nextElements = replaceAllElementsInFrame(nextElements, getElementsInResizingFrame(this.scene.getElementsIncludingDeleted(), frame, this.state, elementsMap), frame, this);
5083
5196
  }
5084
5197
  this.scene.replaceAllElements(nextElements);
5085
5198
  }
@@ -5093,13 +5206,14 @@ class App extends React.Component {
5093
5206
  // the one we've hit
5094
5207
  if (selectedELements.length === 1) {
5095
5208
  this.setState({
5096
- selectedLinearElement: new LinearElementEditor(hitElement, this.scene),
5209
+ selectedLinearElement: new LinearElementEditor(hitElement),
5097
5210
  });
5098
5211
  }
5099
5212
  }
5100
5213
  const pointerStart = this.lastPointerDownEvent;
5101
5214
  const pointerEnd = this.lastPointerUpEvent || this.lastPointerMoveEvent;
5102
5215
  if (isEraserActive(this.state) && pointerStart && pointerEnd) {
5216
+ this.eraserTrail.endPath();
5103
5217
  const draggedDistance = distance2d(pointerStart.clientX, pointerStart.clientY, pointerEnd.clientX, pointerEnd.clientY);
5104
5218
  if (draggedDistance === 0) {
5105
5219
  const scenePointer = viewportCoordsToSceneCoords({
@@ -5107,16 +5221,13 @@ class App extends React.Component {
5107
5221
  clientY: pointerEnd.clientY,
5108
5222
  }, this.state);
5109
5223
  const hitElements = this.getElementsAtPosition(scenePointer.x, scenePointer.y);
5110
- hitElements.forEach((hitElement) => (pointerDownState.elementIdsToErase[hitElement.id] = {
5111
- erase: true,
5112
- opacity: hitElement.opacity,
5113
- }));
5224
+ hitElements.forEach((hitElement) => this.elementsPendingErasure.add(hitElement.id));
5114
5225
  }
5115
- this.eraseElements(pointerDownState);
5226
+ this.eraseElements();
5116
5227
  return;
5117
5228
  }
5118
- else if (Object.keys(pointerDownState.elementIdsToErase).length) {
5119
- this.restoreReadyToEraseElements(pointerDownState);
5229
+ else if (this.elementsPendingErasure.size) {
5230
+ this.restoreReadyToEraseElements();
5120
5231
  }
5121
5232
  if (hitElement &&
5122
5233
  !pointerDownState.drag.hasOccurred &&
@@ -5168,7 +5279,7 @@ class App extends React.Component {
5168
5279
  // set selectedLinearElement only if thats the only element selected
5169
5280
  selectedLinearElement: newSelectedElements.length === 1 &&
5170
5281
  isLinearElement(newSelectedElements[0])
5171
- ? new LinearElementEditor(newSelectedElements[0], this.scene)
5282
+ ? new LinearElementEditor(newSelectedElements[0])
5172
5283
  : prevState.selectedLinearElement,
5173
5284
  };
5174
5285
  });
@@ -5222,7 +5333,7 @@ class App extends React.Component {
5222
5333
  // Don't set `selectedLinearElement` if its same as the hitElement, this is mainly to prevent resetting the `hoverPointIndex` to -1.
5223
5334
  // Future we should update the API to take care of setting the correct `hoverPointIndex` when initialized
5224
5335
  prevState.selectedLinearElement?.elementId !== hitElement.id
5225
- ? new LinearElementEditor(hitElement, this.scene)
5336
+ ? new LinearElementEditor(hitElement)
5226
5337
  : prevState.selectedLinearElement,
5227
5338
  }));
5228
5339
  }
@@ -5230,7 +5341,7 @@ class App extends React.Component {
5230
5341
  if (!pointerDownState.drag.hasOccurred &&
5231
5342
  !this.state.isResizing &&
5232
5343
  ((hitElement &&
5233
- isHittingElementBoundingBoxWithoutHittingElement(hitElement, this.state, this.frameNameBoundsCache, pointerDownState.origin.x, pointerDownState.origin.y)) ||
5344
+ isHittingElementBoundingBoxWithoutHittingElement(hitElement, this.state, this.frameNameBoundsCache, pointerDownState.origin.x, pointerDownState.origin.y, this.scene.getNonDeletedElementsMap())) ||
5234
5345
  (!hitElement &&
5235
5346
  pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements))) {
5236
5347
  if (this.state.editingLinearElement) {
@@ -5266,12 +5377,12 @@ class App extends React.Component {
5266
5377
  this.history.resumeRecording();
5267
5378
  }
5268
5379
  if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
5269
- (isBindingEnabled(this.state)
5270
- ? bindOrUnbindSelectedElements
5271
- : unbindLinearElements)(this.scene.getSelectedElements(this.state));
5380
+ isBindingEnabled(this.state)
5381
+ ? bindOrUnbindSelectedElements(this.scene.getSelectedElements(this.state), this.scene.getNonDeletedElements(), elementsMap)
5382
+ : unbindLinearElements(this.scene.getSelectedElements(this.state), elementsMap);
5272
5383
  }
5273
5384
  if (activeTool.type === "laser") {
5274
- this.laserPathManager.endPath();
5385
+ this.laserTrails.endPath();
5275
5386
  return;
5276
5387
  }
5277
5388
  if (!activeTool.locked && activeTool.type !== "freedraw") {
@@ -5301,52 +5412,27 @@ class App extends React.Component {
5301
5412
  }
5302
5413
  });
5303
5414
  }
5304
- restoreReadyToEraseElements = (pointerDownState) => {
5305
- const elements = this.scene.getElementsIncludingDeleted().map((ele) => {
5306
- if (pointerDownState.elementIdsToErase[ele.id] &&
5307
- pointerDownState.elementIdsToErase[ele.id].erase) {
5308
- return newElementWith(ele, {
5309
- opacity: pointerDownState.elementIdsToErase[ele.id].opacity,
5310
- });
5311
- }
5312
- else if (isBoundToContainer(ele) &&
5313
- pointerDownState.elementIdsToErase[ele.containerId] &&
5314
- pointerDownState.elementIdsToErase[ele.containerId].erase) {
5315
- return newElementWith(ele, {
5316
- opacity: pointerDownState.elementIdsToErase[ele.containerId].opacity,
5317
- });
5318
- }
5319
- else if (ele.frameId &&
5320
- pointerDownState.elementIdsToErase[ele.frameId] &&
5321
- pointerDownState.elementIdsToErase[ele.frameId].erase) {
5322
- return newElementWith(ele, {
5323
- opacity: pointerDownState.elementIdsToErase[ele.frameId].opacity,
5324
- });
5325
- }
5326
- return ele;
5327
- });
5328
- this.scene.replaceAllElements(elements);
5415
+ restoreReadyToEraseElements = () => {
5416
+ this.elementsPendingErasure = new Set();
5417
+ this.onSceneUpdated();
5329
5418
  };
5330
- eraseElements = (pointerDownState) => {
5419
+ eraseElements = () => {
5420
+ let didChange = false;
5331
5421
  const elements = this.scene.getElementsIncludingDeleted().map((ele) => {
5332
- if (pointerDownState.elementIdsToErase[ele.id] &&
5333
- pointerDownState.elementIdsToErase[ele.id].erase) {
5334
- return newElementWith(ele, { isDeleted: true });
5335
- }
5336
- else if (isBoundToContainer(ele) &&
5337
- pointerDownState.elementIdsToErase[ele.containerId] &&
5338
- pointerDownState.elementIdsToErase[ele.containerId].erase) {
5339
- return newElementWith(ele, { isDeleted: true });
5340
- }
5341
- else if (ele.frameId &&
5342
- pointerDownState.elementIdsToErase[ele.frameId] &&
5343
- pointerDownState.elementIdsToErase[ele.frameId].erase) {
5422
+ if (this.elementsPendingErasure.has(ele.id) ||
5423
+ (ele.frameId && this.elementsPendingErasure.has(ele.frameId)) ||
5424
+ (isBoundToContainer(ele) &&
5425
+ this.elementsPendingErasure.has(ele.containerId))) {
5426
+ didChange = true;
5344
5427
  return newElementWith(ele, { isDeleted: true });
5345
5428
  }
5346
5429
  return ele;
5347
5430
  });
5348
- this.history.resumeRecording();
5349
- this.scene.replaceAllElements(elements);
5431
+ this.elementsPendingErasure = new Set();
5432
+ if (didChange) {
5433
+ this.history.resumeRecording();
5434
+ this.scene.replaceAllElements(elements);
5435
+ }
5350
5436
  };
5351
5437
  initializeImage = async ({ imageFile, imageElement: _imageElement, showCursorImagePreview = false, }) => {
5352
5438
  // at this point this should be guaranteed image file, but we do this check
@@ -5470,9 +5556,18 @@ class App extends React.Component {
5470
5556
  // mustn't be larger than 128 px
5471
5557
  // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Basic_User_Interface/Using_URL_values_for_the_cursor_property
5472
5558
  const cursorImageSizePx = 96;
5473
- const imagePreview = await resizeImageFile(imageFile, {
5474
- maxWidthOrHeight: cursorImageSizePx,
5475
- });
5559
+ let imagePreview;
5560
+ try {
5561
+ imagePreview = await resizeImageFile(imageFile, {
5562
+ maxWidthOrHeight: cursorImageSizePx,
5563
+ });
5564
+ }
5565
+ catch (e) {
5566
+ if (e.cause === "UNSUPPORTED") {
5567
+ throw new Error(t("errors.unsupportedFileType"));
5568
+ }
5569
+ throw e;
5570
+ }
5476
5571
  let previewDataURL = await getDataURL(imagePreview);
5477
5572
  // SVG cannot be resized via `resizeImageFile` so we resize by rendering to
5478
5573
  // a small canvas
@@ -5627,7 +5722,7 @@ class App extends React.Component {
5627
5722
  }
5628
5723
  };
5629
5724
  maybeSuggestBindingAtCursor = (pointerCoords) => {
5630
- const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this.scene);
5725
+ const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5631
5726
  this.setState({
5632
5727
  suggestedBindings: hoveredBindableElement != null ? [hoveredBindableElement] : [],
5633
5728
  });
@@ -5642,7 +5737,7 @@ class App extends React.Component {
5642
5737
  return;
5643
5738
  }
5644
5739
  const suggestedBindings = pointerCoords.reduce((acc, coords) => {
5645
- const hoveredBindableElement = getHoveredElementForBinding(coords, this.scene);
5740
+ const hoveredBindableElement = getHoveredElementForBinding(coords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5646
5741
  if (hoveredBindableElement != null &&
5647
5742
  !isLinearElementSimpleAndAlreadyBound(linearElement, oppositeBindingBoundElement?.id, hoveredBindableElement)) {
5648
5743
  acc.push(hoveredBindableElement);
@@ -5655,7 +5750,7 @@ class App extends React.Component {
5655
5750
  if (selectedElements.length > 50) {
5656
5751
  return;
5657
5752
  }
5658
- const suggestedBindings = getEligibleElementsForBinding(selectedElements);
5753
+ const suggestedBindings = getEligibleElementsForBinding(selectedElements, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5659
5754
  this.setState({ suggestedBindings });
5660
5755
  }
5661
5756
  clearSelection(hitElement) {
@@ -5722,8 +5817,9 @@ class App extends React.Component {
5722
5817
  return;
5723
5818
  }
5724
5819
  catch (error) {
5820
+ // Don't throw for image scene daa
5725
5821
  if (error.name !== "EncodingError") {
5726
- throw error;
5822
+ throw new Error(t("alerts.couldNotLoadInvalidFile"));
5727
5823
  }
5728
5824
  }
5729
5825
  }
@@ -5784,7 +5880,32 @@ class App extends React.Component {
5784
5880
  loadFileToCanvas = async (file, fileHandle) => {
5785
5881
  file = await normalizeFile(file);
5786
5882
  try {
5787
- const ret = await loadSceneOrLibraryFromBlob(file, this.state, this.scene.getElementsIncludingDeleted(), fileHandle);
5883
+ let ret;
5884
+ try {
5885
+ ret = await loadSceneOrLibraryFromBlob(file, this.state, this.scene.getElementsIncludingDeleted(), fileHandle);
5886
+ }
5887
+ catch (error) {
5888
+ const imageSceneDataError = error instanceof ImageSceneDataError;
5889
+ if (imageSceneDataError &&
5890
+ error.code === "IMAGE_NOT_CONTAINS_SCENE_DATA" &&
5891
+ !this.isToolSupported("image")) {
5892
+ this.setState({
5893
+ isLoading: false,
5894
+ errorMessage: t("errors.imageToolNotSupported"),
5895
+ });
5896
+ return;
5897
+ }
5898
+ const errorMessage = imageSceneDataError
5899
+ ? t("alerts.cannotRestoreFromImage")
5900
+ : t("alerts.couldNotLoadInvalidFile");
5901
+ this.setState({
5902
+ isLoading: false,
5903
+ errorMessage,
5904
+ });
5905
+ }
5906
+ if (!ret) {
5907
+ return;
5908
+ }
5788
5909
  if (ret.type === MIME_TYPES.excalidraw) {
5789
5910
  this.setState({ isLoading: true });
5790
5911
  this.syncActionResult({
@@ -5811,15 +5932,6 @@ class App extends React.Component {
5811
5932
  }
5812
5933
  }
5813
5934
  catch (error) {
5814
- if (error instanceof ImageSceneDataError &&
5815
- error.code === "IMAGE_NOT_CONTAINS_SCENE_DATA" &&
5816
- !this.isToolSupported("image")) {
5817
- this.setState({
5818
- isLoading: false,
5819
- errorMessage: t("errors.imageToolNotSupported"),
5820
- });
5821
- return;
5822
- }
5823
5935
  this.setState({ isLoading: false, errorMessage: error.message });
5824
5936
  }
5825
5937
  };
@@ -5856,7 +5968,7 @@ class App extends React.Component {
5856
5968
  selectedElementIds: { [element.id]: true },
5857
5969
  }, this.scene.getNonDeletedElements(), this.state, this),
5858
5970
  selectedLinearElement: isLinearElement(element)
5859
- ? new LinearElementEditor(element, this.scene)
5971
+ ? new LinearElementEditor(element)
5860
5972
  : null,
5861
5973
  }
5862
5974
  : this.state),
@@ -5893,7 +6005,7 @@ class App extends React.Component {
5893
6005
  }, {
5894
6006
  x: gridX - pointerDownState.originInGrid.x,
5895
6007
  y: gridY - pointerDownState.originInGrid.y,
5896
- });
6008
+ }, this.scene.getNonDeletedElementsMap());
5897
6009
  gridX += snapOffset.x;
5898
6010
  gridY += snapOffset.y;
5899
6011
  this.setState({
@@ -5907,7 +6019,7 @@ class App extends React.Component {
5907
6019
  if (this.state.activeTool.type === TOOL_TYPE.frame ||
5908
6020
  this.state.activeTool.type === TOOL_TYPE.magicframe) {
5909
6021
  this.setState({
5910
- elementsToHighlight: getElementsInResizingFrame(this.scene.getNonDeletedElements(), draggingElement, this.state),
6022
+ elementsToHighlight: getElementsInResizingFrame(this.scene.getNonDeletedElements(), draggingElement, this.state, this.scene.getNonDeletedElementsMap()),
5911
6023
  });
5912
6024
  }
5913
6025
  }
@@ -5956,13 +6068,13 @@ class App extends React.Component {
5956
6068
  snapLines,
5957
6069
  });
5958
6070
  }
5959
- if (transformElements(pointerDownState, transformHandleType, selectedElements, pointerDownState.resize.arrowDirection, shouldRotateWithDiscreteAngle(event), shouldResizeFromCenter(event), selectedElements.length === 1 && isImageElement(selectedElements[0])
6071
+ if (transformElements(pointerDownState.originalElements, transformHandleType, selectedElements, this.scene.getElementsMapIncludingDeleted(), shouldRotateWithDiscreteAngle(event), shouldResizeFromCenter(event), selectedElements.length === 1 && isImageElement(selectedElements[0])
5960
6072
  ? !shouldMaintainAspectRatio(event)
5961
- : shouldMaintainAspectRatio(event), resizeX, resizeY, pointerDownState.resize.center.x, pointerDownState.resize.center.y, this.state)) {
6073
+ : shouldMaintainAspectRatio(event), resizeX, resizeY, pointerDownState.resize.center.x, pointerDownState.resize.center.y)) {
5962
6074
  this.maybeSuggestBindingForAll(selectedElements);
5963
6075
  const elementsToHighlight = new Set();
5964
6076
  selectedFrames.forEach((frame) => {
5965
- getElementsInResizingFrame(this.scene.getNonDeletedElements(), frame, this.state).forEach((element) => elementsToHighlight.add(element));
6077
+ getElementsInResizingFrame(this.scene.getNonDeletedElements(), frame, this.state, this.scene.getNonDeletedElementsMap()).forEach((element) => elementsToHighlight.add(element));
5966
6078
  });
5967
6079
  this.setState({
5968
6080
  elementsToHighlight: [...elementsToHighlight],
@@ -6095,7 +6207,7 @@ class App extends React.Component {
6095
6207
  if (container) {
6096
6208
  let elementCenterX = container.x + container.width / 2;
6097
6209
  let elementCenterY = container.y + container.height / 2;
6098
- const elementCenter = getContainerCenter(container, appState);
6210
+ const elementCenter = getContainerCenter(container, appState, this.scene.getNonDeletedElementsMap());
6099
6211
  if (elementCenter) {
6100
6212
  elementCenterX = elementCenter.x;
6101
6213
  elementCenterY = elementCenter.y;
@@ -6180,18 +6292,21 @@ class App extends React.Component {
6180
6292
  this.setAppState({});
6181
6293
  }
6182
6294
  }
6183
- if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
6184
- window.h = window.h || {};
6185
- Object.defineProperties(window.h, {
6186
- elements: {
6187
- configurable: true,
6188
- get() {
6189
- return this.app?.scene.getElementsIncludingDeleted();
6190
- },
6191
- set(elements) {
6192
- return this.app?.scene.replaceAllElements(elements);
6295
+ export const createTestHook = () => {
6296
+ if (import.meta.env.MODE === ENV.TEST || import.meta.env.DEV) {
6297
+ window.h = window.h || {};
6298
+ Object.defineProperties(window.h, {
6299
+ elements: {
6300
+ configurable: true,
6301
+ get() {
6302
+ return this.app?.scene.getElementsIncludingDeleted();
6303
+ },
6304
+ set(elements) {
6305
+ return this.app?.scene.replaceAllElements(elements);
6306
+ },
6193
6307
  },
6194
- },
6195
- });
6196
- }
6308
+ });
6309
+ }
6310
+ };
6311
+ createTestHook();
6197
6312
  export default App;