@excalidraw/excalidraw 0.17.1-1d71f84 → 0.17.1-2f9526d

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 (443) hide show
  1. package/CHANGELOG.md +14 -2
  2. package/dist/browser/dev/excalidraw-assets-dev/{blockDiagram-91b80b7a-ACFH36JV.js → blockDiagram-91b80b7a-H47FTXHA.js} +5 -5
  3. package/dist/browser/dev/excalidraw-assets-dev/{c4Diagram-b2a90758-QZ27YR47.js → c4Diagram-b2a90758-NNJK6GKC.js} +3 -3
  4. package/dist/browser/dev/excalidraw-assets-dev/{chunk-HO2HMSK7.js → chunk-4KQVEBHW.js} +3 -3
  5. package/dist/browser/dev/excalidraw-assets-dev/{chunk-USGV265L.js → chunk-53YI56GV.js} +4 -4
  6. package/dist/browser/dev/excalidraw-assets-dev/{chunk-EDFX3S7X.js → chunk-A2WCJI4I.js} +3 -3
  7. package/dist/browser/dev/excalidraw-assets-dev/{chunk-IX4V72YG.js → chunk-EFLPX7NE.js} +6 -6
  8. package/dist/browser/dev/excalidraw-assets-dev/{chunk-MXVETLVM.js → chunk-JYIQCNWV.js} +2 -2
  9. package/dist/browser/dev/excalidraw-assets-dev/{chunk-YZIOORVX.js → chunk-LVIQQW6F.js} +2 -2
  10. package/dist/browser/dev/excalidraw-assets-dev/{chunk-6U7GQNJT.js → chunk-PXLO3FOU.js} +2 -2
  11. package/dist/browser/dev/excalidraw-assets-dev/{chunk-AK7SWNLN.js → chunk-Q6A4M3MN.js} +9 -5
  12. package/dist/browser/dev/excalidraw-assets-dev/chunk-Q6A4M3MN.js.map +7 -0
  13. package/dist/browser/dev/excalidraw-assets-dev/{chunk-RWZVJAQU.js → chunk-Q6NFAEKN.js} +4597 -2668
  14. package/dist/browser/dev/excalidraw-assets-dev/chunk-Q6NFAEKN.js.map +7 -0
  15. package/dist/browser/dev/excalidraw-assets-dev/{chunk-7DACDEY3.js → chunk-TO2AW5PW.js} +2 -2
  16. package/dist/browser/dev/excalidraw-assets-dev/{chunk-NJ77ZFNJ.js → chunk-VURILHLY.js} +2 -2
  17. package/dist/browser/dev/excalidraw-assets-dev/{chunk-2T2GU7NF.js → chunk-ZAYGSUHF.js} +2 -2
  18. package/dist/browser/dev/excalidraw-assets-dev/{chunk-Z3PH3V2B.js → chunk-ZQR5ML6Y.js} +26 -26
  19. package/dist/browser/dev/excalidraw-assets-dev/chunk-ZQR5ML6Y.js.map +7 -0
  20. package/dist/browser/dev/excalidraw-assets-dev/{classDiagram-30eddba6-QSLMH4JW.js → classDiagram-30eddba6-CUYIJICN.js} +5 -5
  21. package/dist/browser/dev/excalidraw-assets-dev/{classDiagram-v2-f2df5561-DY4DYQ5P.js → classDiagram-v2-f2df5561-K6WW6K73.js} +8 -8
  22. package/dist/browser/dev/excalidraw-assets-dev/{dist-Z46EOVOL.js → dist-6QVAH5JA.js} +37 -15
  23. package/dist/browser/dev/excalidraw-assets-dev/dist-6QVAH5JA.js.map +7 -0
  24. package/dist/browser/dev/excalidraw-assets-dev/{en-5TCZHGGJ.js → en-Y27YPU72.js} +2 -2
  25. package/dist/browser/dev/excalidraw-assets-dev/{erDiagram-47591fe2-SOOJRTCB.js → erDiagram-47591fe2-XGAD7EEP.js} +4 -4
  26. package/dist/browser/dev/excalidraw-assets-dev/{flowDiagram-5540d9b9-AHGL4KPK.js → flowDiagram-5540d9b9-B6EOVNNO.js} +9 -9
  27. package/dist/browser/dev/excalidraw-assets-dev/{flowDiagram-v2-3b53844e-56LDZZWY.js → flowDiagram-v2-3b53844e-NUG24FJH.js} +9 -9
  28. package/dist/browser/dev/excalidraw-assets-dev/{flowchart-elk-definition-5fe447d6-27LUKRI6.js → flowchart-elk-definition-5fe447d6-25Y7PCBL.js} +5 -5
  29. package/dist/browser/dev/excalidraw-assets-dev/{ganttDiagram-9a3bba1f-EHGYGNG6.js → ganttDiagram-9a3bba1f-GNL6ZDTC.js} +2 -2
  30. package/dist/browser/dev/excalidraw-assets-dev/{gitGraphDiagram-96e6b4ee-AJQNBDW5.js → gitGraphDiagram-96e6b4ee-HNW52NVO.js} +2 -2
  31. package/dist/browser/dev/excalidraw-assets-dev/{image-OFRRV5MB.css → image-O66MQ7WQ.css} +1 -1
  32. package/dist/browser/dev/excalidraw-assets-dev/image-O66MQ7WQ.css.map +7 -0
  33. package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js → image-Y5X7K6KW.js} +2 -2
  34. package/dist/browser/dev/excalidraw-assets-dev/{infoDiagram-bcd20f53-SWLLQVES.js → infoDiagram-bcd20f53-FWEUVFLT.js} +2 -2
  35. package/dist/browser/dev/excalidraw-assets-dev/{journeyDiagram-4fe6b3dc-7UAVCWOZ.js → journeyDiagram-4fe6b3dc-RZIUI7UG.js} +3 -3
  36. package/dist/browser/dev/excalidraw-assets-dev/{mindmap-definition-f354de21-SROW5KGM.js → mindmap-definition-f354de21-GBVN45GU.js} +3 -3
  37. package/dist/browser/dev/excalidraw-assets-dev/{pieDiagram-79897490-QKCI6NCB.js → pieDiagram-79897490-ECENNII6.js} +2 -2
  38. package/dist/browser/dev/excalidraw-assets-dev/{quadrantDiagram-62f64e94-LNYJZFC5.js → quadrantDiagram-62f64e94-ZMEOFVNL.js} +2 -2
  39. package/dist/browser/dev/excalidraw-assets-dev/{requirementDiagram-05bf5f74-ZZD7ZHFA.js → requirementDiagram-05bf5f74-FHZSFHCR.js} +4 -4
  40. package/dist/browser/dev/excalidraw-assets-dev/{sankeyDiagram-97764748-L75ZZ4UM.js → sankeyDiagram-97764748-VDKIKTA6.js} +2 -2
  41. package/dist/browser/dev/excalidraw-assets-dev/{sequenceDiagram-acc0e65c-6PCU7TDK.js → sequenceDiagram-acc0e65c-6JUSPVKX.js} +3 -3
  42. package/dist/browser/dev/excalidraw-assets-dev/{stateDiagram-0ff1cf1a-WM76WOPR.js → stateDiagram-0ff1cf1a-L3AKWENF.js} +5 -5
  43. package/dist/browser/dev/excalidraw-assets-dev/{stateDiagram-v2-9a9d610d-N4HZW3O2.js → stateDiagram-v2-9a9d610d-NU3GGMCH.js} +8 -8
  44. package/dist/browser/dev/excalidraw-assets-dev/{timeline-definition-fea2a41d-ZHGCAXGP.js → timeline-definition-fea2a41d-JGP7XCHW.js} +2 -2
  45. package/dist/browser/dev/excalidraw-assets-dev/{xychartDiagram-ab372869-2DLOVRAZ.js → xychartDiagram-ab372869-HLFHHF2I.js} +3 -3
  46. package/dist/browser/dev/index.css +72 -28
  47. package/dist/browser/dev/index.css.map +3 -3
  48. package/dist/browser/dev/index.js +2211 -1904
  49. package/dist/browser/dev/index.js.map +4 -4
  50. package/dist/browser/prod/excalidraw-assets/{blockDiagram-91b80b7a-ONPS22AM.js → blockDiagram-91b80b7a-FVCRVGN5.js} +1 -1
  51. package/dist/browser/prod/excalidraw-assets/{c4Diagram-b2a90758-XMIQY7ZT.js → c4Diagram-b2a90758-56CXO7GA.js} +1 -1
  52. package/dist/browser/prod/excalidraw-assets/{chunk-GCHQBOKV.js → chunk-635MQ3CK.js} +1 -1
  53. package/dist/browser/prod/excalidraw-assets/{chunk-P5M3G2RP.js → chunk-AIKXYJX3.js} +1 -1
  54. package/dist/browser/prod/excalidraw-assets/{chunk-E2YLWFZX.js → chunk-CR7VMNWC.js} +1 -1
  55. package/dist/browser/prod/excalidraw-assets/{chunk-WEYK4A2L.js → chunk-FFF2CSVG.js} +1 -1
  56. package/dist/browser/prod/excalidraw-assets/{chunk-R3HAIP6R.js → chunk-G4WDCSPE.js} +1 -1
  57. package/dist/browser/prod/excalidraw-assets/{chunk-HFOXJM22.js → chunk-HKZSHFLX.js} +1 -1
  58. package/dist/browser/prod/excalidraw-assets/{chunk-XIMFFJTE.js → chunk-IKCDYWMW.js} +1 -1
  59. package/dist/browser/prod/excalidraw-assets/{chunk-CTYINSWT.js → chunk-IZMZ6RPD.js} +2 -2
  60. package/dist/browser/prod/excalidraw-assets/{chunk-AHLLBBVJ.js → chunk-L5DS24G6.js} +1 -1
  61. package/dist/browser/prod/excalidraw-assets/chunk-MDMKPHYD.js +55 -0
  62. package/dist/browser/prod/excalidraw-assets/{chunk-CQJF3C6G.js → chunk-MUNOKHUD.js} +1 -1
  63. package/dist/browser/prod/excalidraw-assets/{chunk-NI6SYCUG.js → chunk-QOQYOOQ4.js} +1 -1
  64. package/dist/browser/prod/excalidraw-assets/{chunk-I2PZFXTK.js → chunk-ZTIWFPBM.js} +21 -21
  65. package/dist/browser/prod/excalidraw-assets/{classDiagram-30eddba6-IEJXXCVX.js → classDiagram-30eddba6-BCUTAUMD.js} +1 -1
  66. package/dist/browser/prod/excalidraw-assets/{classDiagram-v2-f2df5561-7LZDSWOS.js → classDiagram-v2-f2df5561-6SOXSGQ2.js} +1 -1
  67. package/dist/browser/prod/excalidraw-assets/dist-567JAXHK.js +7 -0
  68. package/dist/browser/prod/excalidraw-assets/{en-LROPV2RN.js → en-GSUSWMSH.js} +1 -1
  69. package/dist/browser/prod/excalidraw-assets/{erDiagram-47591fe2-E5V666CF.js → erDiagram-47591fe2-RE6HB7RM.js} +1 -1
  70. package/dist/browser/prod/excalidraw-assets/{flowDiagram-5540d9b9-GMBRCYVF.js → flowDiagram-5540d9b9-ZNJZBERW.js} +1 -1
  71. package/dist/browser/prod/excalidraw-assets/{flowDiagram-v2-3b53844e-Z4HUWP6B.js → flowDiagram-v2-3b53844e-LY44JLQJ.js} +1 -1
  72. package/dist/browser/prod/excalidraw-assets/{flowchart-elk-definition-5fe447d6-5ZCYTX5N.js → flowchart-elk-definition-5fe447d6-TMTJ6Z7O.js} +1 -1
  73. package/dist/browser/prod/excalidraw-assets/{ganttDiagram-9a3bba1f-WM32OMT5.js → ganttDiagram-9a3bba1f-5O6EA6LX.js} +1 -1
  74. package/dist/browser/prod/excalidraw-assets/{gitGraphDiagram-96e6b4ee-CAKZ2U6E.js → gitGraphDiagram-96e6b4ee-UHYNM5DI.js} +1 -1
  75. package/dist/browser/prod/excalidraw-assets/image-7MVXYJUE.js +1 -0
  76. package/dist/browser/prod/excalidraw-assets/{infoDiagram-bcd20f53-MUIKXGC4.js → infoDiagram-bcd20f53-BP77NQEH.js} +1 -1
  77. package/dist/browser/prod/excalidraw-assets/{journeyDiagram-4fe6b3dc-NYRV4HK2.js → journeyDiagram-4fe6b3dc-XMGKCMES.js} +1 -1
  78. package/dist/browser/prod/excalidraw-assets/{mindmap-definition-f354de21-MY55DRSM.js → mindmap-definition-f354de21-ZQRRBRWF.js} +1 -1
  79. package/dist/browser/prod/excalidraw-assets/{pieDiagram-79897490-47L6J6L2.js → pieDiagram-79897490-IGXEC2KX.js} +1 -1
  80. package/dist/browser/prod/excalidraw-assets/{quadrantDiagram-62f64e94-DF5C2GDT.js → quadrantDiagram-62f64e94-WTHHDYJL.js} +1 -1
  81. package/dist/browser/prod/excalidraw-assets/{requirementDiagram-05bf5f74-C4IMUBDN.js → requirementDiagram-05bf5f74-MV4OFRVW.js} +1 -1
  82. package/dist/browser/prod/excalidraw-assets/{sankeyDiagram-97764748-YHW7EUST.js → sankeyDiagram-97764748-ZGYUHEJT.js} +1 -1
  83. package/dist/browser/prod/excalidraw-assets/{sequenceDiagram-acc0e65c-H3XEHT32.js → sequenceDiagram-acc0e65c-IBSENK6W.js} +1 -1
  84. package/dist/browser/prod/excalidraw-assets/{stateDiagram-0ff1cf1a-Z5WB6Q3P.js → stateDiagram-0ff1cf1a-DB73XNZH.js} +1 -1
  85. package/dist/browser/prod/excalidraw-assets/{stateDiagram-v2-9a9d610d-T7OZETQC.js → stateDiagram-v2-9a9d610d-2OOBUPNR.js} +1 -1
  86. package/dist/browser/prod/excalidraw-assets/{timeline-definition-fea2a41d-VVC22BWF.js → timeline-definition-fea2a41d-P3NQQVDU.js} +1 -1
  87. package/dist/browser/prod/excalidraw-assets/{xychartDiagram-ab372869-JAXODQF7.js → xychartDiagram-ab372869-HI3XLK3Y.js} +1 -1
  88. package/dist/browser/prod/index.css +1 -1
  89. package/dist/browser/prod/index.js +24 -24
  90. package/dist/dev/{en-II4GK66F.json → en-OIPCBIOA.json} +8 -4
  91. package/dist/dev/index.css +72 -28
  92. package/dist/dev/index.css.map +3 -3
  93. package/dist/dev/index.js +5626 -3465
  94. package/dist/dev/index.js.map +4 -4
  95. package/dist/excalidraw/actions/actionAddToLibrary.d.ts +4 -4
  96. package/dist/excalidraw/actions/actionAddToLibrary.js +4 -3
  97. package/dist/excalidraw/actions/actionAlign.d.ts +22 -22
  98. package/dist/excalidraw/actions/actionAlign.js +7 -6
  99. package/dist/excalidraw/actions/actionBoundText.d.ts +10 -10
  100. package/dist/excalidraw/actions/actionBoundText.js +11 -5
  101. package/dist/excalidraw/actions/actionCanvas.d.ts +52 -52
  102. package/dist/excalidraw/actions/actionCanvas.js +19 -14
  103. package/dist/excalidraw/actions/actionClipboard.d.ts +24 -24
  104. package/dist/excalidraw/actions/actionClipboard.js +14 -13
  105. package/dist/excalidraw/actions/actionDeleteSelected.d.ts +10 -10
  106. package/dist/excalidraw/actions/actionDeleteSelected.js +6 -3
  107. package/dist/excalidraw/actions/actionDistribute.d.ts +10 -10
  108. package/dist/excalidraw/actions/actionDistribute.js +3 -2
  109. package/dist/excalidraw/actions/actionDuplicateSelection.d.ts +7 -8
  110. package/dist/excalidraw/actions/actionDuplicateSelection.js +7 -3
  111. package/dist/excalidraw/actions/actionElementLock.d.ts +9 -9
  112. package/dist/excalidraw/actions/actionElementLock.js +3 -2
  113. package/dist/excalidraw/actions/actionExport.d.ts +33 -33
  114. package/dist/excalidraw/actions/actionExport.js +15 -11
  115. package/dist/excalidraw/actions/actionFinalize.d.ts +9 -9
  116. package/dist/excalidraw/actions/actionFinalize.js +9 -5
  117. package/dist/excalidraw/actions/actionFlip.d.ts +10 -10
  118. package/dist/excalidraw/actions/actionFlip.js +12 -12
  119. package/dist/excalidraw/actions/actionFrame.d.ts +16 -171
  120. package/dist/excalidraw/actions/actionFrame.js +7 -6
  121. package/dist/excalidraw/actions/actionGroup.d.ts +12 -322
  122. package/dist/excalidraw/actions/actionGroup.js +9 -11
  123. package/dist/excalidraw/actions/actionHistory.d.ts +4 -3
  124. package/dist/excalidraw/actions/actionHistory.js +27 -28
  125. package/dist/excalidraw/actions/actionLinearEditor.d.ts +6 -4
  126. package/dist/excalidraw/actions/actionLinearEditor.js +21 -5
  127. package/dist/excalidraw/actions/actionLink.d.ts +5 -5
  128. package/dist/excalidraw/actions/actionLink.js +2 -1
  129. package/dist/excalidraw/actions/actionMenu.d.ts +8 -8
  130. package/dist/excalidraw/actions/actionMenu.js +4 -3
  131. package/dist/excalidraw/actions/actionNavigate.d.ts +4 -4
  132. package/dist/excalidraw/actions/actionNavigate.js +3 -2
  133. package/dist/excalidraw/actions/actionProperties.d.ts +34 -34
  134. package/dist/excalidraw/actions/actionProperties.js +19 -14
  135. package/dist/excalidraw/actions/actionSelectAll.d.ts +5 -5
  136. package/dist/excalidraw/actions/actionSelectAll.js +2 -1
  137. package/dist/excalidraw/actions/actionStyles.d.ts +9 -12
  138. package/dist/excalidraw/actions/actionStyles.js +4 -3
  139. package/dist/excalidraw/actions/actionTextAutoResize.d.ts +17 -0
  140. package/dist/excalidraw/actions/actionTextAutoResize.js +38 -0
  141. package/dist/excalidraw/actions/actionToggleGridMode.d.ts +7 -5
  142. package/dist/excalidraw/actions/actionToggleGridMode.js +6 -2
  143. package/dist/excalidraw/actions/actionToggleObjectsSnapMode.d.ts +4 -4
  144. package/dist/excalidraw/actions/actionToggleObjectsSnapMode.js +2 -1
  145. package/dist/excalidraw/actions/actionToggleStats.d.ts +4 -4
  146. package/dist/excalidraw/actions/actionToggleStats.js +2 -1
  147. package/dist/excalidraw/actions/actionToggleViewMode.d.ts +4 -4
  148. package/dist/excalidraw/actions/actionToggleViewMode.js +2 -1
  149. package/dist/excalidraw/actions/actionToggleZenMode.d.ts +4 -4
  150. package/dist/excalidraw/actions/actionToggleZenMode.js +2 -1
  151. package/dist/excalidraw/actions/actionZindex.d.ts +23 -19
  152. package/dist/excalidraw/actions/actionZindex.js +9 -4
  153. package/dist/excalidraw/actions/manager.d.ts +5 -5
  154. package/dist/excalidraw/actions/register.d.ts +1 -1
  155. package/dist/excalidraw/actions/shortcuts.d.ts +2 -2
  156. package/dist/excalidraw/actions/shortcuts.js +1 -1
  157. package/dist/excalidraw/actions/types.d.ts +8 -8
  158. package/dist/excalidraw/align.d.ts +1 -1
  159. package/dist/excalidraw/analytics.js +1 -1
  160. package/dist/excalidraw/animated-trail.d.ts +2 -2
  161. package/dist/excalidraw/appState.d.ts +5 -5
  162. package/dist/excalidraw/change.d.ts +191 -0
  163. package/dist/excalidraw/change.js +894 -0
  164. package/dist/excalidraw/charts.d.ts +1 -1
  165. package/dist/excalidraw/clients.d.ts +2 -2
  166. package/dist/excalidraw/clients.js +1 -1
  167. package/dist/excalidraw/clipboard.d.ts +3 -3
  168. package/dist/excalidraw/colors.d.ts +1 -1
  169. package/dist/excalidraw/components/Actions.d.ts +3 -3
  170. package/dist/excalidraw/components/Actions.js +9 -6
  171. package/dist/excalidraw/components/App.d.ts +28 -14
  172. package/dist/excalidraw/components/App.js +407 -187
  173. package/dist/excalidraw/components/ButtonIconSelect.js +1 -1
  174. package/dist/excalidraw/components/CheckboxItem.js +1 -1
  175. package/dist/excalidraw/components/ColorPicker/ColorInput.d.ts +1 -1
  176. package/dist/excalidraw/components/ColorPicker/ColorInput.js +1 -1
  177. package/dist/excalidraw/components/ColorPicker/ColorPicker.d.ts +4 -4
  178. package/dist/excalidraw/components/ColorPicker/ColorPicker.js +1 -1
  179. package/dist/excalidraw/components/ColorPicker/Picker.d.ts +3 -3
  180. package/dist/excalidraw/components/ColorPicker/PickerColorList.d.ts +1 -1
  181. package/dist/excalidraw/components/ColorPicker/PickerHeading.d.ts +1 -1
  182. package/dist/excalidraw/components/ColorPicker/ShadeList.d.ts +1 -1
  183. package/dist/excalidraw/components/ColorPicker/TopPicks.d.ts +1 -1
  184. package/dist/excalidraw/components/ColorPicker/colorPickerUtils.d.ts +2 -2
  185. package/dist/excalidraw/components/ColorPicker/colorPickerUtils.js +1 -1
  186. package/dist/excalidraw/components/ColorPicker/keyboardNavHandlers.d.ts +2 -2
  187. package/dist/excalidraw/components/ColorPicker/keyboardNavHandlers.js +1 -1
  188. package/dist/excalidraw/components/CommandPalette/CommandPalette.d.ts +1 -1
  189. package/dist/excalidraw/components/CommandPalette/CommandPalette.js +30 -15
  190. package/dist/excalidraw/components/CommandPalette/defaultCommandPaletteItems.d.ts +1 -1
  191. package/dist/excalidraw/components/CommandPalette/types.d.ts +3 -3
  192. package/dist/excalidraw/components/ConfirmDialog.d.ts +1 -1
  193. package/dist/excalidraw/components/ContextMenu.d.ts +2 -2
  194. package/dist/excalidraw/components/ContextMenu.js +2 -2
  195. package/dist/excalidraw/components/DarkModeToggle.d.ts +1 -1
  196. package/dist/excalidraw/components/DarkModeToggle.js +3 -1
  197. package/dist/excalidraw/components/DefaultSidebar.d.ts +2 -2
  198. package/dist/excalidraw/components/Dialog.js +1 -1
  199. package/dist/excalidraw/components/DialogActionButton.d.ts +1 -1
  200. package/dist/excalidraw/components/EyeDropper.d.ts +2 -2
  201. package/dist/excalidraw/components/FollowMode/FollowMode.d.ts +1 -1
  202. package/dist/excalidraw/components/FollowMode/FollowMode.js +1 -1
  203. package/dist/excalidraw/components/HelpDialog.js +8 -6
  204. package/dist/excalidraw/components/HintViewer.d.ts +1 -1
  205. package/dist/excalidraw/components/IconPicker.js +2 -2
  206. package/dist/excalidraw/components/ImageExportDialog.d.ts +1 -1
  207. package/dist/excalidraw/components/InitializeApp.d.ts +2 -2
  208. package/dist/excalidraw/components/JSONExportDialog.d.ts +3 -3
  209. package/dist/excalidraw/components/LayerUI.d.ts +4 -4
  210. package/dist/excalidraw/components/LayerUI.js +2 -2
  211. package/dist/excalidraw/components/LibraryMenu.d.ts +2 -2
  212. package/dist/excalidraw/components/LibraryMenuBrowseButton.d.ts +1 -1
  213. package/dist/excalidraw/components/LibraryMenuControlButtons.d.ts +1 -1
  214. package/dist/excalidraw/components/LibraryMenuHeaderContent.d.ts +2 -2
  215. package/dist/excalidraw/components/LibraryMenuItems.d.ts +1 -1
  216. package/dist/excalidraw/components/LibraryMenuSection.d.ts +5 -4
  217. package/dist/excalidraw/components/LibraryUnit.d.ts +2 -2
  218. package/dist/excalidraw/components/LoadingMessage.d.ts +1 -1
  219. package/dist/excalidraw/components/MagicSettings.js +2 -2
  220. package/dist/excalidraw/components/MobileMenu.d.ts +3 -3
  221. package/dist/excalidraw/components/MobileMenu.js +1 -1
  222. package/dist/excalidraw/components/Modal.d.ts +1 -1
  223. package/dist/excalidraw/components/OverwriteConfirm/OverwriteConfirmState.d.ts +1 -1
  224. package/dist/excalidraw/components/PasteChartDialog.d.ts +1 -1
  225. package/dist/excalidraw/components/PasteChartDialog.js +1 -1
  226. package/dist/excalidraw/components/PublishLibrary.d.ts +1 -1
  227. package/dist/excalidraw/components/RadioGroup.d.ts +2 -1
  228. package/dist/excalidraw/components/RadioGroup.js +1 -1
  229. package/dist/excalidraw/components/SVGLayer.d.ts +1 -1
  230. package/dist/excalidraw/components/Sidebar/Sidebar.d.ts +2 -2
  231. package/dist/excalidraw/components/Sidebar/Sidebar.js +1 -1
  232. package/dist/excalidraw/components/Sidebar/SidebarTab.d.ts +1 -1
  233. package/dist/excalidraw/components/Sidebar/SidebarTabTrigger.d.ts +1 -1
  234. package/dist/excalidraw/components/Sidebar/SidebarTrigger.d.ts +1 -1
  235. package/dist/excalidraw/components/Sidebar/common.d.ts +1 -1
  236. package/dist/excalidraw/components/Stack.d.ts +2 -2
  237. package/dist/excalidraw/components/Stats.d.ts +2 -2
  238. package/dist/excalidraw/components/TTDDialog/MermaidToExcalidraw.d.ts +1 -1
  239. package/dist/excalidraw/components/TTDDialog/MermaidToExcalidraw.js +6 -2
  240. package/dist/excalidraw/components/TTDDialog/TTDDialog.js +2 -2
  241. package/dist/excalidraw/components/TTDDialog/TTDDialogInput.d.ts +1 -1
  242. package/dist/excalidraw/components/TTDDialog/TTDDialogPanel.d.ts +1 -1
  243. package/dist/excalidraw/components/TTDDialog/TTDDialogPanels.d.ts +1 -1
  244. package/dist/excalidraw/components/TTDDialog/TTDDialogTabs.d.ts +1 -1
  245. package/dist/excalidraw/components/TTDDialog/TTDDialogTrigger.d.ts +1 -1
  246. package/dist/excalidraw/components/TTDDialog/common.d.ts +4 -4
  247. package/dist/excalidraw/components/TextField.d.ts +1 -1
  248. package/dist/excalidraw/components/Toast.d.ts +1 -1
  249. package/dist/excalidraw/components/ToolButton.d.ts +4 -2
  250. package/dist/excalidraw/components/ToolButton.js +1 -1
  251. package/dist/excalidraw/components/Trans.d.ts +1 -1
  252. package/dist/excalidraw/components/UserList.d.ts +1 -1
  253. package/dist/excalidraw/components/canvases/InteractiveCanvas.d.ts +3 -2
  254. package/dist/excalidraw/components/canvases/InteractiveCanvas.js +3 -2
  255. package/dist/excalidraw/components/canvases/StaticCanvas.d.ts +2 -2
  256. package/dist/excalidraw/components/canvases/StaticCanvas.js +2 -2
  257. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItem.js +2 -2
  258. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.d.ts +18 -0
  259. package/dist/excalidraw/components/dropdownMenu/DropdownMenuItemContentRadio.js +9 -0
  260. package/dist/excalidraw/components/footer/Footer.d.ts +2 -2
  261. package/dist/excalidraw/components/hyperlink/Hyperlink.d.ts +2 -2
  262. package/dist/excalidraw/components/hyperlink/Hyperlink.js +3 -3
  263. package/dist/excalidraw/components/hyperlink/helpers.d.ts +3 -3
  264. package/dist/excalidraw/components/hyperlink/helpers.js +2 -3
  265. package/dist/excalidraw/components/icons.d.ts +6 -1
  266. package/dist/excalidraw/components/icons.js +23 -5
  267. package/dist/excalidraw/components/main-menu/DefaultItems.d.ts +12 -2
  268. package/dist/excalidraw/components/main-menu/DefaultItems.js +38 -7
  269. package/dist/excalidraw/constants.d.ts +5 -5
  270. package/dist/excalidraw/constants.js +6 -3
  271. package/dist/excalidraw/context/ui-appState.d.ts +1 -1
  272. package/dist/excalidraw/cursor.d.ts +1 -1
  273. package/dist/excalidraw/data/EditorLocalStorage.d.ts +2 -2
  274. package/dist/excalidraw/data/blob.d.ts +5 -5
  275. package/dist/excalidraw/data/filesystem.d.ts +2 -1
  276. package/dist/excalidraw/data/index.d.ts +4 -4
  277. package/dist/excalidraw/data/json.d.ts +3 -3
  278. package/dist/excalidraw/data/library.d.ts +3 -3
  279. package/dist/excalidraw/data/magic.d.ts +3 -3
  280. package/dist/excalidraw/data/magic.js +2 -1
  281. package/dist/excalidraw/data/reconcile.d.ts +6 -0
  282. package/dist/excalidraw/data/reconcile.js +49 -0
  283. package/dist/excalidraw/data/resave.d.ts +2 -2
  284. package/dist/excalidraw/data/restore.d.ts +5 -5
  285. package/dist/excalidraw/data/restore.js +7 -7
  286. package/dist/excalidraw/data/transform.d.ts +4 -4
  287. package/dist/excalidraw/data/transform.js +12 -3
  288. package/dist/excalidraw/data/types.d.ts +3 -3
  289. package/dist/excalidraw/data/url.d.ts +1 -0
  290. package/dist/excalidraw/data/url.js +4 -1
  291. package/dist/excalidraw/element/ElementCanvasButtons.d.ts +1 -1
  292. package/dist/excalidraw/element/binding.d.ts +50 -9
  293. package/dist/excalidraw/element/binding.js +712 -155
  294. package/dist/excalidraw/element/bounds.d.ts +3 -4
  295. package/dist/excalidraw/element/bounds.js +0 -3
  296. package/dist/excalidraw/element/collision.d.ts +14 -19
  297. package/dist/excalidraw/element/collision.js +36 -713
  298. package/dist/excalidraw/element/containerCache.d.ts +1 -1
  299. package/dist/excalidraw/element/dragElements.d.ts +3 -3
  300. package/dist/excalidraw/element/embeddable.d.ts +9 -6
  301. package/dist/excalidraw/element/embeddable.js +98 -62
  302. package/dist/excalidraw/element/image.d.ts +2 -2
  303. package/dist/excalidraw/element/index.d.ts +2 -3
  304. package/dist/excalidraw/element/index.js +1 -2
  305. package/dist/excalidraw/element/linearElementEditor.d.ts +12 -12
  306. package/dist/excalidraw/element/linearElementEditor.js +7 -5
  307. package/dist/excalidraw/element/mutateElement.d.ts +4 -5
  308. package/dist/excalidraw/element/mutateElement.js +5 -3
  309. package/dist/excalidraw/element/newElement.d.ts +4 -9
  310. package/dist/excalidraw/element/newElement.js +17 -14
  311. package/dist/excalidraw/element/resizeElements.d.ts +4 -4
  312. package/dist/excalidraw/element/resizeElements.js +170 -97
  313. package/dist/excalidraw/element/resizeTest.d.ts +7 -7
  314. package/dist/excalidraw/element/resizeTest.js +53 -8
  315. package/dist/excalidraw/element/showSelectedShapeActions.d.ts +2 -2
  316. package/dist/excalidraw/element/sizeHelpers.d.ts +2 -2
  317. package/dist/excalidraw/element/sizeHelpers.js +3 -0
  318. package/dist/excalidraw/element/sortElements.d.ts +1 -1
  319. package/dist/excalidraw/element/textElement.d.ts +5 -6
  320. package/dist/excalidraw/element/textElement.js +13 -37
  321. package/dist/excalidraw/element/textWysiwyg.d.ts +10 -5
  322. package/dist/excalidraw/element/textWysiwyg.js +6 -8
  323. package/dist/excalidraw/element/transformHandles.d.ts +24 -6
  324. package/dist/excalidraw/element/transformHandles.js +22 -11
  325. package/dist/excalidraw/element/typeChecks.d.ts +4 -4
  326. package/dist/excalidraw/element/types.d.ts +33 -10
  327. package/dist/excalidraw/emitter.d.ts +1 -1
  328. package/dist/excalidraw/errors.d.ts +3 -0
  329. package/dist/excalidraw/errors.js +3 -0
  330. package/dist/excalidraw/fractionalIndex.d.ts +40 -0
  331. package/dist/excalidraw/fractionalIndex.js +241 -0
  332. package/dist/excalidraw/frame.d.ts +4 -4
  333. package/dist/excalidraw/gatransforms.d.ts +1 -1
  334. package/dist/excalidraw/gesture.d.ts +1 -1
  335. package/dist/excalidraw/groups.d.ts +5 -3
  336. package/dist/excalidraw/groups.js +17 -0
  337. package/dist/excalidraw/history.d.ts +35 -47
  338. package/dist/excalidraw/history.js +100 -167
  339. package/dist/excalidraw/hooks/useCreatePortalContainer.js +2 -1
  340. package/dist/excalidraw/hooks/useEmitter.d.ts +2 -0
  341. package/dist/excalidraw/hooks/useEmitter.js +13 -0
  342. package/dist/excalidraw/hooks/useLibraryItemSvg.d.ts +1 -1
  343. package/dist/excalidraw/i18n.d.ts +1 -1
  344. package/dist/excalidraw/index.d.ts +3 -1
  345. package/dist/excalidraw/index.js +2 -0
  346. package/dist/excalidraw/jotai.d.ts +1 -1
  347. package/dist/excalidraw/laser-trails.d.ts +3 -2
  348. package/dist/excalidraw/locales/en.json +8 -4
  349. package/dist/excalidraw/math.d.ts +2 -2
  350. package/dist/excalidraw/points.d.ts +1 -1
  351. package/dist/excalidraw/queue.d.ts +1 -1
  352. package/dist/excalidraw/renderer/helpers.d.ts +2 -2
  353. package/dist/excalidraw/renderer/helpers.js +2 -2
  354. package/dist/excalidraw/renderer/interactiveScene.d.ts +2 -2
  355. package/dist/excalidraw/renderer/interactiveScene.js +8 -7
  356. package/dist/excalidraw/renderer/renderElement.d.ts +3 -3
  357. package/dist/excalidraw/renderer/renderElement.js +5 -5
  358. package/dist/excalidraw/renderer/renderSnaps.d.ts +1 -1
  359. package/dist/excalidraw/renderer/renderSnaps.js +2 -1
  360. package/dist/excalidraw/renderer/staticScene.d.ts +1 -1
  361. package/dist/excalidraw/renderer/staticScene.js +14 -3
  362. package/dist/excalidraw/renderer/staticSvgScene.d.ts +4 -4
  363. package/dist/excalidraw/renderer/staticSvgScene.js +10 -0
  364. package/dist/excalidraw/scene/Fonts.d.ts +2 -4
  365. package/dist/excalidraw/scene/Fonts.js +6 -12
  366. package/dist/excalidraw/scene/Renderer.d.ts +4 -4
  367. package/dist/excalidraw/scene/Renderer.js +2 -3
  368. package/dist/excalidraw/scene/Scene.d.ts +19 -12
  369. package/dist/excalidraw/scene/Scene.js +44 -23
  370. package/dist/excalidraw/scene/Shape.d.ts +1 -1
  371. package/dist/excalidraw/scene/ShapeCache.d.ts +4 -4
  372. package/dist/excalidraw/scene/comparisons.d.ts +2 -2
  373. package/dist/excalidraw/scene/export.d.ts +2 -2
  374. package/dist/excalidraw/scene/export.js +6 -5
  375. package/dist/excalidraw/scene/scroll.d.ts +2 -2
  376. package/dist/excalidraw/scene/scrollbars.d.ts +3 -3
  377. package/dist/excalidraw/scene/selection.d.ts +2 -2
  378. package/dist/excalidraw/scene/types.d.ts +5 -4
  379. package/dist/excalidraw/scene/zoom.d.ts +1 -1
  380. package/dist/excalidraw/snapping.d.ts +4 -4
  381. package/dist/excalidraw/store.d.ts +99 -0
  382. package/dist/excalidraw/store.js +269 -0
  383. package/dist/excalidraw/types.d.ts +33 -19
  384. package/dist/excalidraw/utils.d.ts +11 -4
  385. package/dist/excalidraw/utils.js +8 -0
  386. package/dist/excalidraw/zindex.d.ts +4 -4
  387. package/dist/excalidraw/zindex.js +9 -13
  388. package/dist/prod/{en-II4GK66F.json → en-OIPCBIOA.json} +8 -4
  389. package/dist/prod/index.css +1 -1
  390. package/dist/prod/index.js +44 -44
  391. package/dist/utils/bbox.d.ts +2 -2
  392. package/dist/utils/collision.d.ts +4 -0
  393. package/dist/utils/collision.js +48 -0
  394. package/dist/utils/export.d.ts +2 -2
  395. package/dist/utils/geometry/geometry.d.ts +71 -0
  396. package/dist/utils/geometry/geometry.js +674 -0
  397. package/dist/utils/geometry/shape.d.ts +56 -0
  398. package/dist/utils/geometry/shape.js +168 -0
  399. package/dist/utils/withinBounds.d.ts +1 -1
  400. package/history.ts +163 -218
  401. package/package.json +3 -2
  402. package/dist/browser/dev/excalidraw-assets-dev/chunk-AK7SWNLN.js.map +0 -7
  403. package/dist/browser/dev/excalidraw-assets-dev/chunk-RWZVJAQU.js.map +0 -7
  404. package/dist/browser/dev/excalidraw-assets-dev/chunk-Z3PH3V2B.js.map +0 -7
  405. package/dist/browser/dev/excalidraw-assets-dev/dist-Z46EOVOL.js.map +0 -7
  406. package/dist/browser/dev/excalidraw-assets-dev/image-OFRRV5MB.css.map +0 -7
  407. package/dist/browser/prod/excalidraw-assets/chunk-LL4GORAM.js +0 -55
  408. package/dist/browser/prod/excalidraw-assets/dist-PIPZXALV.js +0 -6
  409. package/dist/browser/prod/excalidraw-assets/image-EFCJDJH3.js +0 -1
  410. /package/dist/browser/dev/excalidraw-assets-dev/{blockDiagram-91b80b7a-ACFH36JV.js.map → blockDiagram-91b80b7a-H47FTXHA.js.map} +0 -0
  411. /package/dist/browser/dev/excalidraw-assets-dev/{c4Diagram-b2a90758-QZ27YR47.js.map → c4Diagram-b2a90758-NNJK6GKC.js.map} +0 -0
  412. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-HO2HMSK7.js.map → chunk-4KQVEBHW.js.map} +0 -0
  413. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-USGV265L.js.map → chunk-53YI56GV.js.map} +0 -0
  414. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-EDFX3S7X.js.map → chunk-A2WCJI4I.js.map} +0 -0
  415. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-IX4V72YG.js.map → chunk-EFLPX7NE.js.map} +0 -0
  416. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-MXVETLVM.js.map → chunk-JYIQCNWV.js.map} +0 -0
  417. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-YZIOORVX.js.map → chunk-LVIQQW6F.js.map} +0 -0
  418. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-6U7GQNJT.js.map → chunk-PXLO3FOU.js.map} +0 -0
  419. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-7DACDEY3.js.map → chunk-TO2AW5PW.js.map} +0 -0
  420. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-NJ77ZFNJ.js.map → chunk-VURILHLY.js.map} +0 -0
  421. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-2T2GU7NF.js.map → chunk-ZAYGSUHF.js.map} +0 -0
  422. /package/dist/browser/dev/excalidraw-assets-dev/{classDiagram-30eddba6-QSLMH4JW.js.map → classDiagram-30eddba6-CUYIJICN.js.map} +0 -0
  423. /package/dist/browser/dev/excalidraw-assets-dev/{classDiagram-v2-f2df5561-DY4DYQ5P.js.map → classDiagram-v2-f2df5561-K6WW6K73.js.map} +0 -0
  424. /package/dist/browser/dev/excalidraw-assets-dev/{en-5TCZHGGJ.js.map → en-Y27YPU72.js.map} +0 -0
  425. /package/dist/browser/dev/excalidraw-assets-dev/{erDiagram-47591fe2-SOOJRTCB.js.map → erDiagram-47591fe2-XGAD7EEP.js.map} +0 -0
  426. /package/dist/browser/dev/excalidraw-assets-dev/{flowDiagram-5540d9b9-AHGL4KPK.js.map → flowDiagram-5540d9b9-B6EOVNNO.js.map} +0 -0
  427. /package/dist/browser/dev/excalidraw-assets-dev/{flowDiagram-v2-3b53844e-56LDZZWY.js.map → flowDiagram-v2-3b53844e-NUG24FJH.js.map} +0 -0
  428. /package/dist/browser/dev/excalidraw-assets-dev/{flowchart-elk-definition-5fe447d6-27LUKRI6.js.map → flowchart-elk-definition-5fe447d6-25Y7PCBL.js.map} +0 -0
  429. /package/dist/browser/dev/excalidraw-assets-dev/{ganttDiagram-9a3bba1f-EHGYGNG6.js.map → ganttDiagram-9a3bba1f-GNL6ZDTC.js.map} +0 -0
  430. /package/dist/browser/dev/excalidraw-assets-dev/{gitGraphDiagram-96e6b4ee-AJQNBDW5.js.map → gitGraphDiagram-96e6b4ee-HNW52NVO.js.map} +0 -0
  431. /package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js.map → image-Y5X7K6KW.js.map} +0 -0
  432. /package/dist/browser/dev/excalidraw-assets-dev/{infoDiagram-bcd20f53-SWLLQVES.js.map → infoDiagram-bcd20f53-FWEUVFLT.js.map} +0 -0
  433. /package/dist/browser/dev/excalidraw-assets-dev/{journeyDiagram-4fe6b3dc-7UAVCWOZ.js.map → journeyDiagram-4fe6b3dc-RZIUI7UG.js.map} +0 -0
  434. /package/dist/browser/dev/excalidraw-assets-dev/{mindmap-definition-f354de21-SROW5KGM.js.map → mindmap-definition-f354de21-GBVN45GU.js.map} +0 -0
  435. /package/dist/browser/dev/excalidraw-assets-dev/{pieDiagram-79897490-QKCI6NCB.js.map → pieDiagram-79897490-ECENNII6.js.map} +0 -0
  436. /package/dist/browser/dev/excalidraw-assets-dev/{quadrantDiagram-62f64e94-LNYJZFC5.js.map → quadrantDiagram-62f64e94-ZMEOFVNL.js.map} +0 -0
  437. /package/dist/browser/dev/excalidraw-assets-dev/{requirementDiagram-05bf5f74-ZZD7ZHFA.js.map → requirementDiagram-05bf5f74-FHZSFHCR.js.map} +0 -0
  438. /package/dist/browser/dev/excalidraw-assets-dev/{sankeyDiagram-97764748-L75ZZ4UM.js.map → sankeyDiagram-97764748-VDKIKTA6.js.map} +0 -0
  439. /package/dist/browser/dev/excalidraw-assets-dev/{sequenceDiagram-acc0e65c-6PCU7TDK.js.map → sequenceDiagram-acc0e65c-6JUSPVKX.js.map} +0 -0
  440. /package/dist/browser/dev/excalidraw-assets-dev/{stateDiagram-0ff1cf1a-WM76WOPR.js.map → stateDiagram-0ff1cf1a-L3AKWENF.js.map} +0 -0
  441. /package/dist/browser/dev/excalidraw-assets-dev/{stateDiagram-v2-9a9d610d-N4HZW3O2.js.map → stateDiagram-v2-9a9d610d-NU3GGMCH.js.map} +0 -0
  442. /package/dist/browser/dev/excalidraw-assets-dev/{timeline-definition-fea2a41d-ZHGCAXGP.js.map → timeline-definition-fea2a41d-JGP7XCHW.js.map} +0 -0
  443. /package/dist/browser/dev/excalidraw-assets-dev/{xychartDiagram-ab372869-2DLOVRAZ.js.map → xychartDiagram-ab372869-HLFHHF2I.js.map} +0 -0
@@ -10,31 +10,33 @@ import { ActionManager } from "../actions/manager";
10
10
  import { actions } from "../actions/register";
11
11
  import { trackEvent } from "../analytics";
12
12
  import { getDefaultAppState, isEraserActive, isHandToolActive, } from "../appState";
13
- import { copyTextToSystemClipboard, parseClipboard, } from "../clipboard";
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, supportsResizeObserver, } from "../constants";
13
+ import { copyTextToSystemClipboard, parseClipboard } from "../clipboard";
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, supportsResizeObserver, DEFAULT_COLLISION_THRESHOLD, } 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, transformElements, updateTextElement, redrawTextBoundingBox, } from "../element";
19
- import { bindOrUnbindLinearElement, bindOrUnbindSelectedElements, fixBindingsAfterDeletion, fixBindingsAfterDuplication, getEligibleElementsForBinding, getHoveredElementForBinding, isBindingEnabled, isLinearElementSimpleAndAlreadyBound, maybeBindLinearElement, shouldEnableBindingForPointerEvent, unbindLinearElements, updateBoundElements, } from "../element/binding";
18
+ import { dragNewElement, dragSelectedElements, duplicateElement, getCommonBounds, getCursorForResizingElement, getDragOffsetXY, getElementWithTransformHandleType, getNormalizedDimensions, getResizeArrowDirection, getResizeOffsetXY, getLockedLinearCursorAlignSize, getTransformHandleTypeFromCoords, isInvisiblySmallElement, isNonDeletedElement, isTextElement, newElement, newLinearElement, newTextElement, newImageElement, transformElements, refreshTextDimensions, redrawTextBoundingBox, getElementAbsoluteCoords, } from "../element";
19
+ import { bindOrUnbindLinearElement, bindOrUnbindLinearElements, fixBindingsAfterDeletion, fixBindingsAfterDuplication, getHoveredElementForBinding, isBindingEnabled, isLinearElementSimpleAndAlreadyBound, maybeBindLinearElement, shouldEnableBindingForPointerEvent, updateBoundElements, getSuggestedBindingsForArrows, } from "../element/binding";
20
20
  import { LinearElementEditor } from "../element/linearElementEditor";
21
21
  import { mutateElement, newElementWith } from "../element/mutateElement";
22
22
  import { deepCopyElement, duplicateElements, newFrameElement, newFreeDrawElement, newEmbeddableElement, newMagicFrameElement, newIframeElement, } from "../element/newElement";
23
- import { hasBoundTextElement, isArrowElement, isBindingElement, isBindingElementType, isBoundToContainer, isFrameLikeElement, isImageElement, isEmbeddableElement, isInitializedImageElement, isLinearElement, isLinearElementType, isUsingAdaptiveRadius, isFrameElement, isIframeElement, isIframeLikeElement, isMagicFrameElement, } from "../element/typeChecks";
23
+ import { hasBoundTextElement, isArrowElement, isBindingElement, isBindingElementType, isBoundToContainer, isFrameLikeElement, isImageElement, isEmbeddableElement, isInitializedImageElement, isLinearElement, isLinearElementType, isUsingAdaptiveRadius, isFrameElement, isIframeElement, isIframeLikeElement, isMagicFrameElement, isTextBindableContainer, } from "../element/typeChecks";
24
24
  import { getCenter, getDistance } from "../gesture";
25
25
  import { editGroupForSelectedElement, getElementsInGroup, getSelectedGroupIdForElement, getSelectedGroupIds, isElementInGroup, isSelectedViaGroup, selectGroupsForSelectedElements, } from "../groups";
26
- import History from "../history";
26
+ import { History } from "../history";
27
27
  import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n";
28
28
  import { CODES, shouldResizeFromCenter, shouldMaintainAspectRatio, shouldRotateWithDiscreteAngle, isArrowKey, KEYS, } from "../keys";
29
29
  import { isElementInViewport } from "../element/sizeHelpers";
30
30
  import { distance2d, getCornerRadius, getGridPoint, isPathALoop, } from "../math";
31
- import { calculateScrollCenter, getElementsAtPosition, getElementsWithinSelection, getNormalizedZoom, getSelectedElements, hasBackground, isSomeElementSelected, } from "../scene";
31
+ import { calculateScrollCenter, getElementsWithinSelection, getNormalizedZoom, getSelectedElements, hasBackground, isSomeElementSelected, } from "../scene";
32
32
  import Scene from "../scene/Scene";
33
33
  import { getStateForZoom } from "../scene/zoom";
34
34
  import { findShapeByKey } from "../shapes";
35
- import { debounce, distance, getFontString, getNearestScrollableContainer, isInputLike, isToolIcon, isWritableElement, sceneCoordsToViewportCoords, tupleToCoors, viewportCoordsToSceneCoords, wrapEvent, updateObject, updateActiveTool, getShortcutKey, isTransparent, easeToValuesRAF, muteFSAbortError, isTestEnv, easeOut, updateStable, addEventListener, normalizeEOL, getDateTime, } from "../utils";
35
+ import { getClosedCurveShape, getCurveShape, getEllipseShape, getFreedrawShape, getPolygonShape, getSelectionBoxShape, } from "../../utils/geometry/shape";
36
+ import { isPointInShape } from "../../utils/collision";
37
+ import { debounce, distance, getFontString, getNearestScrollableContainer, isInputLike, isToolIcon, isWritableElement, sceneCoordsToViewportCoords, tupleToCoors, viewportCoordsToSceneCoords, wrapEvent, updateObject, updateActiveTool, getShortcutKey, isTransparent, easeToValuesRAF, muteFSAbortError, isTestEnv, easeOut, updateStable, addEventListener, normalizeEOL, getDateTime, isShallowEqual, arrayToMap, } from "../utils";
36
38
  import { createSrcDoc, embeddableURLValidator, maybeParseEmbedSrc, getEmbedLink, } from "../element/embeddable";
37
- import { ContextMenu, CONTEXT_MENU_SEPARATOR, } from "./ContextMenu";
39
+ import { ContextMenu, CONTEXT_MENU_SEPARATOR } from "./ContextMenu";
38
40
  import LayerUI from "./LayerUI";
39
41
  import { Toast } from "./Toast";
40
42
  import { actionToggleViewMode } from "../actions/actionToggleViewMode";
@@ -42,8 +44,7 @@ import { dataURLToFile, generateIdFromFile, getDataURL, getFileFromEvent, ImageU
42
44
  import { getInitializedImageElements, loadHTMLImageElement, normalizeSVG, updateImageCache as _updateImageCache, } from "../element/image";
43
45
  import throttle from "lodash.throttle";
44
46
  import { fileOpen } from "../data/filesystem";
45
- import { bindTextToShapeAfterDuplication, getApproxMinLineHeight, getApproxMinLineWidth, getBoundTextElement, getContainerCenter, getContainerElement, getDefaultLineHeight, getLineHeightInPx, getTextBindableContainerAtPosition, isMeasureTextSupported, isValidTextContainer, } from "../element/textElement";
46
- import { isHittingElementNotConsideringBoundingBox } from "../element/collision";
47
+ import { bindTextToShapeAfterDuplication, getApproxMinLineHeight, getApproxMinLineWidth, getBoundTextElement, getContainerCenter, getContainerElement, getDefaultLineHeight, getLineHeightInPx, isMeasureTextSupported, isValidTextContainer, } from "../element/textElement";
47
48
  import { showHyperlinkTooltip, hideHyperlinkToolip, Hyperlink, } from "../components/hyperlink/Hyperlink";
48
49
  import { isLocalLink, normalizeLink, toValidURL } from "../data/url";
49
50
  import { shouldShowBoundingBox } from "../element/transformHandles";
@@ -61,7 +62,7 @@ import { getSnapLinesAtPointer, snapDraggedElements, isActiveToolNonLinearSnappa
61
62
  import { actionWrapTextInContainer } from "../actions/actionBoundText";
62
63
  import BraveMeasureTextError from "./BraveMeasureTextError";
63
64
  import { activeEyeDropperAtom } from "./EyeDropper";
64
- import { convertToExcalidrawElements, } from "../data/transform";
65
+ import { convertToExcalidrawElements } from "../data/transform";
65
66
  import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
66
67
  import { StaticCanvas, InteractiveCanvas } from "./canvases";
67
68
  import { Renderer } from "../scene/Renderer";
@@ -77,15 +78,19 @@ import { ElementCanvasButton } from "./MagicButton";
77
78
  import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
78
79
  import { EditorLocalStorage } from "../data/EditorLocalStorage";
79
80
  import FollowMode from "./FollowMode/FollowMode";
81
+ import { Store, StoreAction } from "../store";
80
82
  import { AnimationFrameHandler } from "../animation-frame-handler";
81
83
  import { AnimatedTrail } from "../animated-trail";
82
84
  import { LaserTrails } from "../laser-trails";
83
85
  import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
84
86
  import { getRenderOpacity } from "../renderer/renderElement";
87
+ import { hitElementBoundText, hitElementBoundingBoxOnly, hitElementItself, shouldTestInside, } from "../element/collision";
85
88
  import { textWysiwyg } from "../element/textWysiwyg";
86
89
  import { isOverScrollBars } from "../scene/scrollbars";
90
+ import { syncInvalidIndices, syncMovedIndices } from "../fractionalIndex";
87
91
  import { isPointHittingLink, isPointHittingLinkIcon, } from "./hyperlink/helpers";
88
92
  import { getShortcutFromShortcutName } from "../actions/shortcuts";
93
+ import { actionTextAutoResize } from "../actions/actionTextAutoResize";
89
94
  const AppContext = React.createContext(null);
90
95
  const AppPropsContext = React.createContext(null);
91
96
  const deviceContextInitialValue = {
@@ -165,6 +170,7 @@ class App extends React.Component {
165
170
  library;
166
171
  libraryItemsFromStorage;
167
172
  id;
173
+ store;
168
174
  history;
169
175
  excalidrawContainerValue;
170
176
  files = {};
@@ -236,6 +242,8 @@ class App extends React.Component {
236
242
  this.canvas = document.createElement("canvas");
237
243
  this.rc = rough.canvas(this.canvas);
238
244
  this.renderer = new Renderer(this.scene);
245
+ this.store = new Store();
246
+ this.history = new History();
239
247
  if (excalidrawAPI) {
240
248
  const api = {
241
249
  updateScene: this.updateScene,
@@ -279,14 +287,11 @@ class App extends React.Component {
279
287
  container: this.excalidrawContainerRef.current,
280
288
  id: this.id,
281
289
  };
282
- this.fonts = new Fonts({
283
- scene: this.scene,
284
- onSceneUpdated: this.onSceneUpdated,
285
- });
290
+ this.fonts = new Fonts({ scene: this.scene });
286
291
  this.history = new History();
287
292
  this.actionManager.registerAll(actions);
288
- this.actionManager.registerAction(createUndoAction(this.history));
289
- this.actionManager.registerAction(createRedoAction(this.history));
293
+ this.actionManager.registerAction(createUndoAction(this.history, this.store));
294
+ this.actionManager.registerAction(createRedoAction(this.history, this.store));
290
295
  }
291
296
  onWindowMessage(event) {
292
297
  if (event.origin !== "https://player.vimeo.com" &&
@@ -437,7 +442,7 @@ class App extends React.Component {
437
442
  return false;
438
443
  });
439
444
  if (updated) {
440
- this.scene.informMutation();
445
+ this.scene.triggerUpdate();
441
446
  }
442
447
  // GC
443
448
  this.iFrameRefs.forEach((ref, id) => {
@@ -495,7 +500,7 @@ class App extends React.Component {
495
500
  html, body {
496
501
  width: 100%;
497
502
  height: 100%;
498
- color: ${this.state.theme === "dark" ? "white" : "black"};
503
+ color: ${this.state.theme === THEME.DARK ? "white" : "black"};
499
504
  }
500
505
  body {
501
506
  display: flex;
@@ -650,7 +655,7 @@ class App extends React.Component {
650
655
  ? src.srcdoc(this.state.theme)
651
656
  : undefined, src: src?.type !== "document" ? src?.link ?? "" : undefined,
652
657
  // https://stackoverflow.com/q/18470015
653
- scrolling: "no", referrerPolicy: "no-referrer-when-downgrade", title: "Excalidraw Embedded Content", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture", allowFullScreen: true, sandbox: "allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads" })) })] }) }, el.id));
658
+ scrolling: "no", referrerPolicy: "no-referrer-when-downgrade", title: "Excalidraw Embedded Content", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture", allowFullScreen: true, sandbox: `${src?.sandbox?.allowSameOrigin ? "allow-same-origin" : ""} allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-presentation allow-downloads` })) })] }) }, el.id));
654
659
  }) }));
655
660
  }
656
661
  getFrameNameDOMId = (frameElement) => {
@@ -692,7 +697,7 @@ class App extends React.Component {
692
697
  if (!this.state.frameRendering.enabled || !this.state.frameRendering.name) {
693
698
  return null;
694
699
  }
695
- const isDarkTheme = this.state.theme === "dark";
700
+ const isDarkTheme = this.state.theme === THEME.DARK;
696
701
  let frameIndex = 0;
697
702
  let magicFrameIndex = 0;
698
703
  return this.scene.getNonDeletedFramesLikes().map((f) => {
@@ -796,9 +801,9 @@ class App extends React.Component {
796
801
  render() {
797
802
  const selectedElements = this.scene.getSelectedElements(this.state);
798
803
  const { renderTopRightUI, renderCustomStats } = this.props;
799
- const versionNonce = this.scene.getVersionNonce();
804
+ const sceneNonce = this.scene.getSceneNonce();
800
805
  const { elementsMap, visibleElements } = this.renderer.getRenderableElements({
801
- versionNonce,
806
+ sceneNonce,
802
807
  zoom: this.state.zoom,
803
808
  offsetLeft: this.state.offsetLeft,
804
809
  offsetTop: this.state.offsetTop,
@@ -867,14 +872,14 @@ class App extends React.Component {
867
872
  this.focusContainer();
868
873
  callback?.();
869
874
  });
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: {
875
+ } })), _jsx(StaticCanvas, { canvas: this.canvas, rc: this.rc, elementsMap: elementsMap, allElementsMap: allElementsMap, visibleElements: visibleElements, sceneNonce: sceneNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, renderConfig: {
871
876
  imageCache: this.imageCache,
872
877
  isExporting: false,
873
878
  renderGrid: true,
874
879
  canvasBackgroundColor: this.state.viewBackgroundColor,
875
880
  embedsValidationStatus: this.embedsValidationStatus,
876
881
  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()] }) }) }) }) }) }) }) }));
882
+ } }), _jsx(InteractiveCanvas, { containerRef: this.excalidrawContainerRef, canvas: this.interactiveCanvas, elementsMap: elementsMap, visibleElements: visibleElements, selectedElements: selectedElements, sceneNonce: sceneNonce, selectionNonce: this.state.selectionElement?.versionNonce, scale: window.devicePixelRatio, appState: this.state, device: this.device, 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()] }) }) }) }) }) }) }) }));
878
883
  }
879
884
  focusContainer = () => {
880
885
  this.excalidrawContainerRef.current?.focus();
@@ -924,7 +929,7 @@ class App extends React.Component {
924
929
  mutateElement(frameElement, { customData: { generationData: data } }, false);
925
930
  }
926
931
  this.magicGenerations.set(frameElement.id, data);
927
- this.onSceneUpdated();
932
+ this.triggerRender();
928
933
  };
929
934
  getTextFromElements(elements) {
930
935
  const text = elements
@@ -1122,7 +1127,7 @@ class App extends React.Component {
1122
1127
  opacity: 100,
1123
1128
  locked: false,
1124
1129
  });
1125
- this.scene.addNewElement(frame);
1130
+ this.scene.insertElement(frame);
1126
1131
  for (const child of selectedElements) {
1127
1132
  mutateElement(child, { frameId: frame.id });
1128
1133
  }
@@ -1146,13 +1151,13 @@ class App extends React.Component {
1146
1151
  if (shouldUpdateStrokeColor) {
1147
1152
  this.syncActionResult({
1148
1153
  appState: { ...this.state, currentItemStrokeColor: color },
1149
- commitToHistory: true,
1154
+ storeAction: StoreAction.CAPTURE,
1150
1155
  });
1151
1156
  }
1152
1157
  else {
1153
1158
  this.syncActionResult({
1154
1159
  appState: { ...this.state, currentItemBackgroundColor: color },
1155
- commitToHistory: true,
1160
+ storeAction: StoreAction.CAPTURE,
1156
1161
  });
1157
1162
  }
1158
1163
  }
@@ -1166,6 +1171,7 @@ class App extends React.Component {
1166
1171
  }
1167
1172
  return el;
1168
1173
  }),
1174
+ storeAction: StoreAction.CAPTURE,
1169
1175
  });
1170
1176
  }
1171
1177
  },
@@ -1185,10 +1191,13 @@ class App extends React.Component {
1185
1191
  editingElement = element;
1186
1192
  }
1187
1193
  });
1188
- this.scene.replaceAllElements(actionResult.elements);
1189
- if (actionResult.commitToHistory) {
1190
- this.history.resumeRecording();
1194
+ if (actionResult.storeAction === StoreAction.UPDATE) {
1195
+ this.store.shouldUpdateSnapshot();
1196
+ }
1197
+ else if (actionResult.storeAction === StoreAction.CAPTURE) {
1198
+ this.store.shouldCaptureIncrement();
1191
1199
  }
1200
+ this.scene.replaceAllElements(actionResult.elements);
1192
1201
  }
1193
1202
  if (actionResult.files) {
1194
1203
  this.files = actionResult.replaceFiles
@@ -1197,8 +1206,11 @@ class App extends React.Component {
1197
1206
  this.addNewImagesToImageCache();
1198
1207
  }
1199
1208
  if (actionResult.appState || editingElement || this.state.contextMenu) {
1200
- if (actionResult.commitToHistory) {
1201
- this.history.resumeRecording();
1209
+ if (actionResult.storeAction === StoreAction.UPDATE) {
1210
+ this.store.shouldUpdateSnapshot();
1211
+ }
1212
+ else if (actionResult.storeAction === StoreAction.CAPTURE) {
1213
+ this.store.shouldCaptureIncrement();
1202
1214
  }
1203
1215
  let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
1204
1216
  let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
@@ -1237,10 +1249,6 @@ class App extends React.Component {
1237
1249
  name,
1238
1250
  errorMessage,
1239
1251
  });
1240
- }, () => {
1241
- if (actionResult.syncHistory) {
1242
- this.history.setCurrentState(this.state, this.scene.getElementsIncludingDeleted());
1243
- }
1244
1252
  });
1245
1253
  }
1246
1254
  });
@@ -1258,6 +1266,9 @@ class App extends React.Component {
1258
1266
  resetHistory = () => {
1259
1267
  this.history.clear();
1260
1268
  };
1269
+ resetStore = () => {
1270
+ this.store.clear();
1271
+ };
1261
1272
  /**
1262
1273
  * Resets scene & history.
1263
1274
  * ! Do not use to clear scene user action !
@@ -1269,6 +1280,7 @@ class App extends React.Component {
1269
1280
  isLoading: opts?.resetLoadingState ? false : state.isLoading,
1270
1281
  theme: this.state.theme,
1271
1282
  }));
1283
+ this.resetStore();
1272
1284
  this.resetHistory();
1273
1285
  });
1274
1286
  initializeScene = async () => {
@@ -1343,10 +1355,11 @@ class App extends React.Component {
1343
1355
  // text elements on canvas, and rerender them once done. This also
1344
1356
  // seems faster even in browsers that do fire the loadingdone event.
1345
1357
  this.fonts.loadFontsForElements(scene.elements);
1358
+ this.resetStore();
1346
1359
  this.resetHistory();
1347
1360
  this.syncActionResult({
1348
1361
  ...scene,
1349
- commitToHistory: true,
1362
+ storeAction: StoreAction.UPDATE,
1350
1363
  });
1351
1364
  };
1352
1365
  isMobileBreakpoint = (width, height) => {
@@ -1417,9 +1430,16 @@ class App extends React.Component {
1417
1430
  configurable: true,
1418
1431
  value: this.history,
1419
1432
  },
1433
+ store: {
1434
+ configurable: true,
1435
+ value: this.store,
1436
+ },
1420
1437
  });
1421
1438
  }
1422
- this.scene.addCallback(this.onSceneUpdated);
1439
+ this.store.onStoreIncrementEmitter.on((increment) => {
1440
+ this.history.record(increment.elementsChange, increment.appStateChange);
1441
+ });
1442
+ this.scene.onUpdate(this.triggerRender);
1423
1443
  this.addEventListeners();
1424
1444
  if (this.props.autoFocus && this.excalidrawContainerRef.current) {
1425
1445
  this.focusContainer();
@@ -1457,6 +1477,7 @@ class App extends React.Component {
1457
1477
  componentWillUnmount() {
1458
1478
  this.renderer.destroy();
1459
1479
  this.scene = new Scene();
1480
+ this.fonts = new Fonts({ scene: this.scene });
1460
1481
  this.renderer = new Renderer(this.scene);
1461
1482
  this.files = {};
1462
1483
  this.imageCache.clear();
@@ -1468,6 +1489,7 @@ class App extends React.Component {
1468
1489
  this.laserTrails.stop();
1469
1490
  this.eraserTrail.stop();
1470
1491
  this.onChangeEmitter.clear();
1492
+ this.store.onStoreIncrementEmitter.clear();
1471
1493
  ShapeCache.destroy();
1472
1494
  SnapCache.destroy();
1473
1495
  clearTimeout(touchTimeout);
@@ -1520,6 +1542,9 @@ class App extends React.Component {
1520
1542
  // Safari-only desktop pinch zoom
1521
1543
  addEventListener(document, EVENT.GESTURE_START, this.onGestureStart, false), addEventListener(document, EVENT.GESTURE_CHANGE, this.onGestureChange, false), addEventListener(document, EVENT.GESTURE_END, this.onGestureEnd, false), addEventListener(window, EVENT.FOCUS, () => {
1522
1544
  this.maybeCleanupAfterMissingPointerUp(null);
1545
+ // browsers (chrome?) tend to free up memory a lot, which results
1546
+ // in canvas context being cleared. Thus re-render on focus.
1547
+ this.triggerRender(true);
1523
1548
  }));
1524
1549
  if (this.state.viewModeEnabled) {
1525
1550
  return;
@@ -1535,7 +1560,8 @@ class App extends React.Component {
1535
1560
  componentDidUpdate(prevProps, prevState) {
1536
1561
  this.updateEmbeddables();
1537
1562
  const elements = this.scene.getElementsIncludingDeleted();
1538
- const elementsMap = this.scene.getNonDeletedElementsMap();
1563
+ const elementsMap = this.scene.getElementsMapIncludingDeleted();
1564
+ const nonDeletedElementsMap = this.scene.getNonDeletedElementsMap();
1539
1565
  if (!this.state.showWelcomeScreen && !elements.length) {
1540
1566
  this.setState({ showWelcomeScreen: true });
1541
1567
  }
@@ -1608,10 +1634,10 @@ class App extends React.Component {
1608
1634
  gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
1609
1635
  });
1610
1636
  }
1611
- this.excalidrawContainerRef.current?.classList.toggle("theme--dark", this.state.theme === "dark");
1637
+ this.excalidrawContainerRef.current?.classList.toggle("theme--dark", this.state.theme === THEME.DARK);
1612
1638
  if (this.state.editingLinearElement &&
1613
1639
  !this.state.selectedElementIds[this.state.editingLinearElement.elementId]) {
1614
- // defer so that the commitToHistory flag isn't reset via current update
1640
+ // defer so that the storeAction flag isn't reset via current update
1615
1641
  setTimeout(() => {
1616
1642
  // execute only if the condition still holds when the deferred callback
1617
1643
  // executes (it can be scheduled multiple times depending on how
@@ -1636,9 +1662,9 @@ class App extends React.Component {
1636
1662
  multiElement != null &&
1637
1663
  isBindingEnabled(this.state) &&
1638
1664
  isBindingElement(multiElement, false)) {
1639
- maybeBindLinearElement(multiElement, this.state, this.scene, tupleToCoors(LinearElementEditor.getPointAtIndexGlobalCoordinates(multiElement, -1, elementsMap)), elementsMap);
1665
+ maybeBindLinearElement(multiElement, this.state, tupleToCoors(LinearElementEditor.getPointAtIndexGlobalCoordinates(multiElement, -1, nonDeletedElementsMap)), this);
1640
1666
  }
1641
- this.history.record(this.state, elements);
1667
+ this.store.commit(elementsMap, this.state);
1642
1668
  // Do not notify consumers if we're still loading the scene. Among other
1643
1669
  // potential issues, this fixes a case where the tab isn't focused during
1644
1670
  // init, which would trigger onChange with empty elements, which would then
@@ -1894,16 +1920,15 @@ class App extends React.Component {
1894
1920
  }), {
1895
1921
  randomizeSeed: !opts.retainSeed,
1896
1922
  });
1897
- const allElements = [
1898
- ...this.scene.getElementsIncludingDeleted(),
1899
- ...newElements,
1900
- ];
1923
+ const prevElements = this.scene.getElementsIncludingDeleted();
1924
+ const nextElements = [...prevElements, ...newElements];
1925
+ syncMovedIndices(nextElements, arrayToMap(newElements));
1901
1926
  const topLayerFrame = this.getTopLayerFrameAtSceneCoords({ x, y });
1902
1927
  if (topLayerFrame) {
1903
1928
  const eligibleElements = filterElementsEligibleAsFrameChildren(newElements, topLayerFrame);
1904
- addElementsToFrame(allElements, eligibleElements, topLayerFrame);
1929
+ addElementsToFrame(nextElements, eligibleElements, topLayerFrame);
1905
1930
  }
1906
- this.scene.replaceAllElements(allElements);
1931
+ this.scene.replaceAllElements(nextElements);
1907
1932
  newElements.forEach((newElement) => {
1908
1933
  if (isTextElement(newElement) && isBoundToContainer(newElement)) {
1909
1934
  const container = getContainerElement(newElement, this.scene.getElementsMapIncludingDeleted());
@@ -1913,7 +1938,7 @@ class App extends React.Component {
1913
1938
  if (opts.files) {
1914
1939
  this.files = { ...this.files, ...opts.files };
1915
1940
  }
1916
- this.history.resumeRecording();
1941
+ this.store.shouldCaptureIncrement();
1917
1942
  const nextElementsToSelect = excludeElementsInFramesFromSelection(newElements);
1918
1943
  this.setState({
1919
1944
  ...this.state,
@@ -2071,16 +2096,7 @@ class App extends React.Component {
2071
2096
  if (textElements.length === 0) {
2072
2097
  return;
2073
2098
  }
2074
- const frameId = textElements[0].frameId;
2075
- if (frameId) {
2076
- this.scene.insertElementsAtIndex(textElements, this.scene.getElementIndex(frameId));
2077
- }
2078
- else {
2079
- this.scene.replaceAllElements([
2080
- ...this.scene.getElementsIncludingDeleted(),
2081
- ...textElements,
2082
- ]);
2083
- }
2099
+ this.scene.insertElements(textElements);
2084
2100
  this.setState({
2085
2101
  selectedElementIds: makeNextSelectedElementIds(Object.fromEntries(textElements.map((el) => [el.id, true])), this.state),
2086
2102
  });
@@ -2096,7 +2112,7 @@ class App extends React.Component {
2096
2112
  });
2097
2113
  PLAIN_PASTE_TOAST_SHOWN = true;
2098
2114
  }
2099
- this.history.resumeRecording();
2115
+ this.store.shouldCaptureIncrement();
2100
2116
  }
2101
2117
  setAppState = (state, callback) => {
2102
2118
  this.setState(state, callback);
@@ -2278,25 +2294,49 @@ class App extends React.Component {
2278
2294
  ShapeCache.delete(element);
2279
2295
  }
2280
2296
  });
2281
- this.scene.informMutation();
2297
+ this.scene.triggerUpdate();
2282
2298
  this.addNewImagesToImageCache();
2283
2299
  });
2284
2300
  updateScene = withBatchedUpdates((sceneData) => {
2285
- if (sceneData.commitToHistory) {
2286
- this.history.resumeRecording();
2301
+ const nextElements = syncInvalidIndices(sceneData.elements ?? []);
2302
+ if (sceneData.storeAction && sceneData.storeAction !== StoreAction.NONE) {
2303
+ const prevCommittedAppState = this.store.snapshot.appState;
2304
+ const prevCommittedElements = this.store.snapshot.elements;
2305
+ const nextCommittedAppState = sceneData.appState
2306
+ ? Object.assign({}, prevCommittedAppState, sceneData.appState) // new instance, with partial appstate applied to previously captured one, including hidden prop inside `prevCommittedAppState`
2307
+ : prevCommittedAppState;
2308
+ const nextCommittedElements = sceneData.elements
2309
+ ? this.store.filterUncomittedElements(this.scene.getElementsMapIncludingDeleted(), // Only used to detect uncomitted local elements
2310
+ arrayToMap(nextElements))
2311
+ : prevCommittedElements;
2312
+ // WARN: store action always performs deep clone of changed elements, for ephemeral remote updates (i.e. remote dragging, resizing, drawing) we might consider doing something smarter
2313
+ // do NOT schedule store actions (execute after re-render), as it might cause unexpected concurrency issues if not handled well
2314
+ if (sceneData.storeAction === StoreAction.CAPTURE) {
2315
+ this.store.captureIncrement(nextCommittedElements, nextCommittedAppState);
2316
+ }
2317
+ else if (sceneData.storeAction === StoreAction.UPDATE) {
2318
+ this.store.updateSnapshot(nextCommittedElements, nextCommittedAppState);
2319
+ }
2287
2320
  }
2288
2321
  if (sceneData.appState) {
2289
2322
  this.setState(sceneData.appState);
2290
2323
  }
2291
2324
  if (sceneData.elements) {
2292
- this.scene.replaceAllElements(sceneData.elements);
2325
+ this.scene.replaceAllElements(nextElements);
2293
2326
  }
2294
2327
  if (sceneData.collaborators) {
2295
2328
  this.setState({ collaborators: sceneData.collaborators });
2296
2329
  }
2297
2330
  });
2298
- onSceneUpdated = () => {
2299
- this.setState({});
2331
+ triggerRender = (
2332
+ /** force always re-renders canvas even if no change */
2333
+ force) => {
2334
+ if (force === true) {
2335
+ this.scene.triggerUpdate();
2336
+ }
2337
+ else {
2338
+ this.setState({});
2339
+ }
2300
2340
  };
2301
2341
  /**
2302
2342
  * @returns whether the menu was toggled on or off
@@ -2355,8 +2395,7 @@ class App extends React.Component {
2355
2395
  !event.altKey) {
2356
2396
  this.setToast({
2357
2397
  message: t("commandPalette.shortcutHint", {
2358
- shortcutOne: getShortcutFromShortcutName("commandPalette"),
2359
- shortcutTwo: getShortcutFromShortcutName("commandPalette", 1),
2398
+ shortcut: getShortcutFromShortcutName("commandPalette"),
2360
2399
  }),
2361
2400
  });
2362
2401
  event.preventDefault();
@@ -2464,7 +2503,9 @@ class App extends React.Component {
2464
2503
  simultaneouslyUpdated: selectedElements,
2465
2504
  });
2466
2505
  });
2467
- this.maybeSuggestBindingForAll(selectedElements);
2506
+ this.setState({
2507
+ suggestedBindings: getSuggestedBindingsForArrows(selectedElements, this),
2508
+ });
2468
2509
  event.preventDefault();
2469
2510
  }
2470
2511
  else if (event.key === KEYS.ENTER) {
@@ -2476,7 +2517,7 @@ class App extends React.Component {
2476
2517
  if (!this.state.editingLinearElement ||
2477
2518
  this.state.editingLinearElement.elementId !==
2478
2519
  selectedElements[0].id) {
2479
- this.history.resumeRecording();
2520
+ this.store.shouldCaptureIncrement();
2480
2521
  this.setState({
2481
2522
  editingLinearElement: new LinearElementEditor(selectedElement),
2482
2523
  });
@@ -2602,11 +2643,7 @@ class App extends React.Component {
2602
2643
  this.setState({ isBindingEnabled: true });
2603
2644
  }
2604
2645
  if (isArrowKey(event.key)) {
2605
- const selectedElements = this.scene.getSelectedElements(this.state);
2606
- const elementsMap = this.scene.getNonDeletedElementsMap();
2607
- isBindingEnabled(this.state)
2608
- ? bindOrUnbindSelectedElements(selectedElements, this.scene.getNonDeletedElements(), elementsMap)
2609
- : unbindLinearElements(selectedElements, elementsMap);
2646
+ bindOrUnbindLinearElements(this.scene.getSelectedElements(this.state).filter(isLinearElement), this, isBindingEnabled(this.state), this.state.selectedLinearElement?.selectedPointsIndices ?? []);
2610
2647
  this.setState({ suggestedBindings: [] });
2611
2648
  }
2612
2649
  });
@@ -2644,6 +2681,9 @@ class App extends React.Component {
2644
2681
  originSnapOffset: null,
2645
2682
  activeEmbeddable: null,
2646
2683
  };
2684
+ if (nextActiveTool.type === "freedraw") {
2685
+ this.store.shouldCaptureIncrement();
2686
+ }
2647
2687
  if (nextActiveTool.type !== "selection") {
2648
2688
  return {
2649
2689
  ...prevState,
@@ -2741,15 +2781,16 @@ class App extends React.Component {
2741
2781
  });
2742
2782
  handleTextWysiwyg(element, { isExistingElement = false, }) {
2743
2783
  const elementsMap = this.scene.getElementsMapIncludingDeleted();
2744
- const updateElement = (text, originalText, isDeleted) => {
2784
+ const updateElement = (nextOriginalText, isDeleted) => {
2745
2785
  this.scene.replaceAllElements([
2746
2786
  // Not sure why we include deleted elements as well hence using deleted elements map
2747
2787
  ...this.scene.getElementsIncludingDeleted().map((_element) => {
2748
2788
  if (_element.id === element.id && isTextElement(_element)) {
2749
- return updateTextElement(_element, getContainerElement(_element, elementsMap), elementsMap, {
2750
- text,
2751
- isDeleted,
2752
- originalText,
2789
+ return newElementWith(_element, {
2790
+ originalText: nextOriginalText,
2791
+ isDeleted: isDeleted ?? _element.isDeleted,
2792
+ // returns (wrapped) text and new dimensions
2793
+ ...refreshTextDimensions(_element, getContainerElement(_element, elementsMap), elementsMap, nextOriginalText),
2753
2794
  });
2754
2795
  }
2755
2796
  return _element;
@@ -2769,15 +2810,15 @@ class App extends React.Component {
2769
2810
  viewportY - this.state.offsetTop,
2770
2811
  ];
2771
2812
  },
2772
- onChange: withBatchedUpdates((text) => {
2773
- updateElement(text, text, false);
2813
+ onChange: withBatchedUpdates((nextOriginalText) => {
2814
+ updateElement(nextOriginalText, false);
2774
2815
  if (isNonDeletedElement(element)) {
2775
2816
  updateBoundElements(element, elementsMap);
2776
2817
  }
2777
2818
  }),
2778
- onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
2779
- const isDeleted = !text.trim();
2780
- updateElement(text, originalText, isDeleted);
2819
+ onSubmit: withBatchedUpdates(({ viaKeyboard, nextOriginalText }) => {
2820
+ const isDeleted = !nextOriginalText.trim();
2821
+ updateElement(nextOriginalText, isDeleted);
2781
2822
  // select the created text element only if submitting via keyboard
2782
2823
  // (when submitting via click it should act as signal to deselect)
2783
2824
  if (!isDeleted && viaKeyboard) {
@@ -2797,7 +2838,7 @@ class App extends React.Component {
2797
2838
  ]);
2798
2839
  }
2799
2840
  if (!isDeleted || isExistingElement) {
2800
- this.history.resumeRecording();
2841
+ this.store.shouldCaptureIncrement();
2801
2842
  }
2802
2843
  this.setState({
2803
2844
  draggingElement: null,
@@ -2816,7 +2857,7 @@ class App extends React.Component {
2816
2857
  this.deselectElements();
2817
2858
  // do an initial update to re-initialize element position since we were
2818
2859
  // modifying element's x/y for sake of editor (case: syncing to remote)
2819
- updateElement(element.text, element.originalText, false);
2860
+ updateElement(element.originalText, false);
2820
2861
  }
2821
2862
  deselectElements() {
2822
2863
  this.setState({
@@ -2835,6 +2876,57 @@ class App extends React.Component {
2835
2876
  }
2836
2877
  return null;
2837
2878
  }
2879
+ /**
2880
+ * get the pure geometric shape of an excalidraw element
2881
+ * which is then used for hit detection
2882
+ */
2883
+ getElementShape(element) {
2884
+ switch (element.type) {
2885
+ case "rectangle":
2886
+ case "diamond":
2887
+ case "frame":
2888
+ case "magicframe":
2889
+ case "embeddable":
2890
+ case "image":
2891
+ case "iframe":
2892
+ case "text":
2893
+ case "selection":
2894
+ return getPolygonShape(element);
2895
+ case "arrow":
2896
+ case "line": {
2897
+ const roughShape = ShapeCache.get(element)?.[0] ??
2898
+ ShapeCache.generateElementShape(element, null)[0];
2899
+ const [, , , , cx, cy] = getElementAbsoluteCoords(element, this.scene.getNonDeletedElementsMap());
2900
+ return shouldTestInside(element)
2901
+ ? getClosedCurveShape(element, roughShape, [element.x, element.y], element.angle, [cx, cy])
2902
+ : getCurveShape(roughShape, [element.x, element.y], element.angle, [
2903
+ cx,
2904
+ cy,
2905
+ ]);
2906
+ }
2907
+ case "ellipse":
2908
+ return getEllipseShape(element);
2909
+ case "freedraw": {
2910
+ const [, , , , cx, cy] = getElementAbsoluteCoords(element, this.scene.getNonDeletedElementsMap());
2911
+ return getFreedrawShape(element, [cx, cy], shouldTestInside(element));
2912
+ }
2913
+ }
2914
+ }
2915
+ getBoundTextShape(element) {
2916
+ const boundTextElement = getBoundTextElement(element, this.scene.getNonDeletedElementsMap());
2917
+ if (boundTextElement) {
2918
+ if (element.type === "arrow") {
2919
+ return this.getElementShape({
2920
+ ...boundTextElement,
2921
+ // arrow's bound text accurate position is not stored in the element's property
2922
+ // but rather calculated and returned from the following static method
2923
+ ...LinearElementEditor.getBoundTextElementPosition(element, boundTextElement, this.scene.getNonDeletedElementsMap()),
2924
+ });
2925
+ }
2926
+ return this.getElementShape(boundTextElement);
2927
+ }
2928
+ return null;
2929
+ }
2838
2930
  getElementAtPosition(x, y, opts) {
2839
2931
  const allHitElements = this.getElementsAtPosition(x, y, opts?.includeBoundTextElement, opts?.includeLockedElements);
2840
2932
  if (allHitElements.length > 1) {
@@ -2848,9 +2940,20 @@ class App extends React.Component {
2848
2940
  const elementWithHighestZIndex = allHitElements[allHitElements.length - 1];
2849
2941
  // If we're hitting element with highest z-index only on its bounding box
2850
2942
  // while also hitting other element figure, the latter should be considered.
2851
- return isHittingElementBoundingBoxWithoutHittingElement(elementWithHighestZIndex, this.state, this.frameNameBoundsCache, x, y, this.scene.getNonDeletedElementsMap())
2852
- ? allHitElements[allHitElements.length - 2]
2853
- : elementWithHighestZIndex;
2943
+ return hitElementItself({
2944
+ x,
2945
+ y,
2946
+ element: elementWithHighestZIndex,
2947
+ shape: this.getElementShape(elementWithHighestZIndex),
2948
+ // when overlapping, we would like to be more precise
2949
+ // this also avoids the need to update past tests
2950
+ threshold: this.getElementHitThreshold() / 2,
2951
+ frameNameBound: isFrameLikeElement(elementWithHighestZIndex)
2952
+ ? this.frameNameBoundsCache.get(elementWithHighestZIndex)
2953
+ : null,
2954
+ })
2955
+ ? elementWithHighestZIndex
2956
+ : allHitElements[allHitElements.length - 2];
2854
2957
  }
2855
2958
  if (allHitElements.length === 1) {
2856
2959
  return allHitElements[0];
@@ -2858,15 +2961,17 @@ class App extends React.Component {
2858
2961
  return null;
2859
2962
  }
2860
2963
  getElementsAtPosition(x, y, includeBoundTextElement = false, includeLockedElements = false) {
2861
- const elements = includeBoundTextElement && includeLockedElements
2964
+ const iframeLikes = [];
2965
+ const elementsMap = this.scene.getNonDeletedElementsMap();
2966
+ const elements = (includeBoundTextElement && includeLockedElements
2862
2967
  ? this.scene.getNonDeletedElements()
2863
2968
  : this.scene
2864
2969
  .getNonDeletedElements()
2865
2970
  .filter((element) => (includeLockedElements || !element.locked) &&
2866
2971
  (includeBoundTextElement ||
2867
- !(isTextElement(element) && element.containerId)));
2868
- const elementsMap = this.scene.getNonDeletedElementsMap();
2869
- return getElementsAtPosition(elements, (element) => hitTest(element, this.state, this.frameNameBoundsCache, x, y, elementsMap)).filter((element) => {
2972
+ !(isTextElement(element) && element.containerId))))
2973
+ .filter((el) => this.hitElement(x, y, el))
2974
+ .filter((element) => {
2870
2975
  // hitting a frame's element from outside the frame is not considered a hit
2871
2976
  const containingFrame = getContainingFrame(element, elementsMap);
2872
2977
  return containingFrame &&
@@ -2874,8 +2979,81 @@ class App extends React.Component {
2874
2979
  this.state.frameRendering.clip
2875
2980
  ? isCursorInFrame({ x, y }, containingFrame, elementsMap)
2876
2981
  : true;
2982
+ })
2983
+ .filter((el) => {
2984
+ // The parameter elements comes ordered from lower z-index to higher.
2985
+ // We want to preserve that order on the returned array.
2986
+ // Exception being embeddables which should be on top of everything else in
2987
+ // terms of hit testing.
2988
+ if (isIframeElement(el)) {
2989
+ iframeLikes.push(el);
2990
+ return false;
2991
+ }
2992
+ return true;
2993
+ })
2994
+ .concat(iframeLikes);
2995
+ return elements;
2996
+ }
2997
+ getElementHitThreshold() {
2998
+ return DEFAULT_COLLISION_THRESHOLD / this.state.zoom.value;
2999
+ }
3000
+ hitElement(x, y, element, considerBoundingBox = true) {
3001
+ // if the element is selected, then hit test is done against its bounding box
3002
+ if (considerBoundingBox &&
3003
+ this.state.selectedElementIds[element.id] &&
3004
+ shouldShowBoundingBox([element], this.state)) {
3005
+ const selectionShape = getSelectionBoxShape(element, this.scene.getNonDeletedElementsMap(), this.getElementHitThreshold());
3006
+ return isPointInShape([x, y], selectionShape);
3007
+ }
3008
+ // take bound text element into consideration for hit collision as well
3009
+ const hitBoundTextOfElement = hitElementBoundText(x, y, this.getBoundTextShape(element));
3010
+ if (hitBoundTextOfElement) {
3011
+ return true;
3012
+ }
3013
+ return hitElementItself({
3014
+ x,
3015
+ y,
3016
+ element,
3017
+ shape: this.getElementShape(element),
3018
+ threshold: this.getElementHitThreshold(),
3019
+ frameNameBound: isFrameLikeElement(element)
3020
+ ? this.frameNameBoundsCache.get(element)
3021
+ : null,
2877
3022
  });
2878
3023
  }
3024
+ getTextBindableContainerAtPosition(x, y) {
3025
+ const elements = this.scene.getNonDeletedElements();
3026
+ const selectedElements = this.scene.getSelectedElements(this.state);
3027
+ if (selectedElements.length === 1) {
3028
+ return isTextBindableContainer(selectedElements[0], false)
3029
+ ? selectedElements[0]
3030
+ : null;
3031
+ }
3032
+ let hitElement = null;
3033
+ // We need to do hit testing from front (end of the array) to back (beginning of the array)
3034
+ for (let index = elements.length - 1; index >= 0; --index) {
3035
+ if (elements[index].isDeleted) {
3036
+ continue;
3037
+ }
3038
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(elements[index], this.scene.getNonDeletedElementsMap());
3039
+ if (isArrowElement(elements[index]) &&
3040
+ hitElementItself({
3041
+ x,
3042
+ y,
3043
+ element: elements[index],
3044
+ shape: this.getElementShape(elements[index]),
3045
+ threshold: this.getElementHitThreshold(),
3046
+ })) {
3047
+ hitElement = elements[index];
3048
+ break;
3049
+ }
3050
+ else if (x1 < x && x < x2 && y1 < y && y < y2) {
3051
+ hitElement = elements[index];
3052
+ break;
3053
+ }
3054
+ }
3055
+ return isTextBindableContainer(hitElement, false) ? hitElement : null;
3056
+ }
2879
3057
  startTextEditing = ({ sceneX, sceneY, insertAtParentCenter = true, container, }) => {
2880
3058
  let shouldBindToContainer = false;
2881
3059
  let parentCenterPosition = insertAtParentCenter &&
@@ -2974,7 +3152,7 @@ class App extends React.Component {
2974
3152
  this.scene.insertElementAtIndex(element, containerIndex + 1);
2975
3153
  }
2976
3154
  else {
2977
- this.scene.addNewElement(element);
3155
+ this.scene.insertElement(element);
2978
3156
  }
2979
3157
  }
2980
3158
  this.setState({
@@ -2999,7 +3177,7 @@ class App extends React.Component {
2999
3177
  if (event[KEYS.CTRL_OR_CMD] &&
3000
3178
  (!this.state.editingLinearElement ||
3001
3179
  this.state.editingLinearElement.elementId !== selectedElements[0].id)) {
3002
- this.history.resumeRecording();
3180
+ this.store.shouldCaptureIncrement();
3003
3181
  this.setState({
3004
3182
  editingLinearElement: new LinearElementEditor(selectedElements[0]),
3005
3183
  });
@@ -3014,6 +3192,7 @@ class App extends React.Component {
3014
3192
  const selectedGroupId = hitElement &&
3015
3193
  getSelectedGroupIdForElement(hitElement, this.state.selectedGroupIds);
3016
3194
  if (selectedGroupId) {
3195
+ this.store.shouldCaptureIncrement();
3017
3196
  this.setState((prevState) => ({
3018
3197
  ...prevState,
3019
3198
  ...selectGroupsForSelectedElements({
@@ -3033,11 +3212,17 @@ class App extends React.Component {
3033
3212
  });
3034
3213
  return;
3035
3214
  }
3036
- const container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
3215
+ const container = this.getTextBindableContainerAtPosition(sceneX, sceneY);
3037
3216
  if (container) {
3038
3217
  if (hasBoundTextElement(container) ||
3039
3218
  !isTransparent(container.backgroundColor) ||
3040
- isHittingElementNotConsideringBoundingBox(container, this.state, this.frameNameBoundsCache, [sceneX, sceneY], this.scene.getNonDeletedElementsMap())) {
3219
+ hitElementItself({
3220
+ x: sceneX,
3221
+ y: sceneY,
3222
+ element: container,
3223
+ shape: this.getElementShape(container),
3224
+ threshold: this.getElementHitThreshold(),
3225
+ })) {
3041
3226
  const midPoint = getContainerCenter(container, this.state, this.scene.getNonDeletedElementsMap());
3042
3227
  sceneX = midPoint.x;
3043
3228
  sceneY = midPoint.y;
@@ -3092,7 +3277,7 @@ class App extends React.Component {
3092
3277
  }
3093
3278
  if (!customEvent?.defaultPrevented) {
3094
3279
  const target = isLocalLink(url) ? "_self" : "_blank";
3095
- const newWindow = window.open(undefined, target, "noopener noreferrer");
3280
+ const newWindow = window.open(undefined, target);
3096
3281
  // https://mathiasbynens.github.io/rel-noopener/
3097
3282
  if (newWindow) {
3098
3283
  newWindow.opener = null;
@@ -3142,8 +3327,11 @@ class App extends React.Component {
3142
3327
  }, state);
3143
3328
  this.translateCanvas({
3144
3329
  zoom: zoomState.zoom,
3145
- scrollX: zoomState.scrollX + deltaX / nextZoom,
3146
- scrollY: zoomState.scrollY + deltaY / nextZoom,
3330
+ // 2x multiplier is just a magic number that makes this work correctly
3331
+ // on touchscreen devices (note: if we get report that panning is slower/faster
3332
+ // than actual movement, consider swapping with devicePixelRatio)
3333
+ scrollX: zoomState.scrollX + 2 * (deltaX / nextZoom),
3334
+ scrollY: zoomState.scrollY + 2 * (deltaY / nextZoom),
3147
3335
  shouldCacheIgnoreZoom: true,
3148
3336
  });
3149
3337
  });
@@ -3308,15 +3496,23 @@ class App extends React.Component {
3308
3496
  if (selectedElements.length === 1 &&
3309
3497
  !isOverScrollBar &&
3310
3498
  !this.state.editingLinearElement) {
3311
- const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, scenePointerX, scenePointerY, this.state.zoom, event.pointerType, this.scene.getNonDeletedElementsMap());
3312
- if (elementWithTransformHandleType &&
3313
- elementWithTransformHandleType.transformHandleType) {
3314
- setCursor(this.interactiveCanvas, getCursorForResizingElement(elementWithTransformHandleType));
3315
- return;
3499
+ // for linear elements, we'd like to prioritize point dragging over edge resizing
3500
+ // therefore, we update and check hovered point index first
3501
+ if (this.state.selectedLinearElement) {
3502
+ this.handleHoverSelectedLinearElement(this.state.selectedLinearElement, scenePointerX, scenePointerY);
3503
+ }
3504
+ if (!this.state.selectedLinearElement ||
3505
+ this.state.selectedLinearElement.hoverPointIndex === -1) {
3506
+ const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, scenePointerX, scenePointerY, this.state.zoom, event.pointerType, this.scene.getNonDeletedElementsMap(), this.device);
3507
+ if (elementWithTransformHandleType &&
3508
+ elementWithTransformHandleType.transformHandleType) {
3509
+ setCursor(this.interactiveCanvas, getCursorForResizingElement(elementWithTransformHandleType));
3510
+ return;
3511
+ }
3316
3512
  }
3317
3513
  }
3318
3514
  else if (selectedElements.length > 1 && !isOverScrollBar) {
3319
- const transformHandleType = getTransformHandleTypeFromCoords(getCommonBounds(selectedElements), scenePointerX, scenePointerY, this.state.zoom, event.pointerType);
3515
+ const transformHandleType = getTransformHandleTypeFromCoords(getCommonBounds(selectedElements), scenePointerX, scenePointerY, this.state.zoom, event.pointerType, this.device);
3320
3516
  if (transformHandleType) {
3321
3517
  setCursor(this.interactiveCanvas, getCursorForResizingElement({
3322
3518
  transformHandleType,
@@ -3420,7 +3616,7 @@ class App extends React.Component {
3420
3616
  }
3421
3617
  };
3422
3618
  const distance = distance2d(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y, scenePointer.x, scenePointer.y);
3423
- const threshold = 10 / this.state.zoom.value;
3619
+ const threshold = this.getElementHitThreshold();
3424
3620
  const point = { ...pointerDownState.lastCoords };
3425
3621
  let samplingInterval = 0;
3426
3622
  while (samplingInterval <= distance) {
@@ -3456,7 +3652,7 @@ class App extends React.Component {
3456
3652
  }
3457
3653
  }
3458
3654
  this.elementsPendingErasure = new Set(this.elementsPendingErasure);
3459
- this.onSceneUpdated();
3655
+ this.triggerRender();
3460
3656
  }
3461
3657
  };
3462
3658
  // set touch moving for mobile context menu
@@ -3466,30 +3662,29 @@ class App extends React.Component {
3466
3662
  handleHoverSelectedLinearElement(linearElementEditor, scenePointerX, scenePointerY) {
3467
3663
  const elementsMap = this.scene.getNonDeletedElementsMap();
3468
3664
  const element = LinearElementEditor.getElement(linearElementEditor.elementId, elementsMap);
3469
- const boundTextElement = getBoundTextElement(element, elementsMap);
3470
3665
  if (!element) {
3471
3666
  return;
3472
3667
  }
3473
3668
  if (this.state.selectedLinearElement) {
3474
3669
  let hoverPointIndex = -1;
3475
3670
  let segmentMidPointHoveredCoords = null;
3476
- if (isHittingElementNotConsideringBoundingBox(element, this.state, this.frameNameBoundsCache, [scenePointerX, scenePointerY], elementsMap)) {
3671
+ if (hitElementItself({
3672
+ x: scenePointerX,
3673
+ y: scenePointerY,
3674
+ element,
3675
+ shape: this.getElementShape(element),
3676
+ })) {
3477
3677
  hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(element, elementsMap, this.state.zoom, scenePointerX, scenePointerY);
3478
3678
  segmentMidPointHoveredCoords =
3479
3679
  LinearElementEditor.getSegmentMidpointHitCoords(linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state, this.scene.getNonDeletedElementsMap());
3480
3680
  if (hoverPointIndex >= 0 || segmentMidPointHoveredCoords) {
3481
3681
  setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
3482
3682
  }
3483
- else {
3683
+ else if (this.hitElement(scenePointerX, scenePointerY, element)) {
3484
3684
  setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3485
3685
  }
3486
3686
  }
3487
- else if (shouldShowBoundingBox([element], this.state) &&
3488
- isHittingElementBoundingBoxWithoutHittingElement(element, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, elementsMap)) {
3489
- setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3490
- }
3491
- else if (boundTextElement &&
3492
- hitTest(boundTextElement, this.state, this.frameNameBoundsCache, scenePointerX, scenePointerY, this.scene.getNonDeletedElementsMap())) {
3687
+ else if (this.hitElement(scenePointerX, scenePointerY, element)) {
3493
3688
  setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3494
3689
  }
3495
3690
  if (this.state.selectedLinearElement.hoverPointIndex !== hoverPointIndex) {
@@ -3556,6 +3751,7 @@ class App extends React.Component {
3556
3751
  return obj;
3557
3752
  }, {}), this.state),
3558
3753
  },
3754
+ storeAction: StoreAction.UPDATE,
3559
3755
  });
3560
3756
  return;
3561
3757
  }
@@ -3995,8 +4191,11 @@ class App extends React.Component {
3995
4191
  const elements = this.scene.getNonDeletedElements();
3996
4192
  const elementsMap = this.scene.getNonDeletedElementsMap();
3997
4193
  const selectedElements = this.scene.getSelectedElements(this.state);
3998
- if (selectedElements.length === 1 && !this.state.editingLinearElement) {
3999
- const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType, this.scene.getNonDeletedElementsMap());
4194
+ if (selectedElements.length === 1 &&
4195
+ !this.state.editingLinearElement &&
4196
+ !(this.state.selectedLinearElement &&
4197
+ this.state.selectedLinearElement.hoverPointIndex !== -1)) {
4198
+ const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType, this.scene.getNonDeletedElementsMap(), this.device);
4000
4199
  if (elementWithTransformHandleType != null) {
4001
4200
  this.setState({
4002
4201
  resizingElement: elementWithTransformHandleType.element,
@@ -4006,7 +4205,7 @@ class App extends React.Component {
4006
4205
  }
4007
4206
  }
4008
4207
  else if (selectedElements.length > 1) {
4009
- pointerDownState.resize.handleType = getTransformHandleTypeFromCoords(getCommonBounds(selectedElements), pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType);
4208
+ pointerDownState.resize.handleType = getTransformHandleTypeFromCoords(getCommonBounds(selectedElements), pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType, this.device);
4010
4209
  }
4011
4210
  if (pointerDownState.resize.handleType) {
4012
4211
  pointerDownState.resize.isResizing = true;
@@ -4020,7 +4219,7 @@ class App extends React.Component {
4020
4219
  else {
4021
4220
  if (this.state.selectedLinearElement) {
4022
4221
  const linearElementEditor = this.state.editingLinearElement || this.state.selectedLinearElement;
4023
- const ret = LinearElementEditor.handlePointerDown(event, this.state, this.history, pointerDownState.origin, linearElementEditor, this.scene.getNonDeletedElements(), elementsMap);
4222
+ const ret = LinearElementEditor.handlePointerDown(event, this.state, this.store, pointerDownState.origin, linearElementEditor, this);
4024
4223
  if (ret.hitElement) {
4025
4224
  pointerDownState.hit.element = ret.hitElement;
4026
4225
  }
@@ -4181,7 +4380,7 @@ class App extends React.Component {
4181
4380
  return false;
4182
4381
  }
4183
4382
  // How many pixels off the shape boundary we still consider a hit
4184
- const threshold = 10 / this.state.zoom.value;
4383
+ const threshold = this.getElementHitThreshold();
4185
4384
  const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
4186
4385
  return (point.x > x1 - threshold &&
4187
4386
  point.x < x2 + threshold &&
@@ -4201,7 +4400,7 @@ class App extends React.Component {
4201
4400
  includeBoundTextElement: true,
4202
4401
  });
4203
4402
  // FIXME
4204
- let container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
4403
+ let container = this.getTextBindableContainerAtPosition(sceneX, sceneY);
4205
4404
  if (hasBoundTextElement(element)) {
4206
4405
  container = element;
4207
4406
  sceneX = element.x + element.width / 2;
@@ -4259,8 +4458,8 @@ class App extends React.Component {
4259
4458
  points: [[0, 0]],
4260
4459
  pressures,
4261
4460
  });
4262
- const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
4263
- this.scene.addNewElement(element);
4461
+ const boundElement = getHoveredElementForBinding(pointerDownState.origin, this);
4462
+ this.scene.insertElement(element);
4264
4463
  this.setState({
4265
4464
  draggingElement: element,
4266
4465
  editingElement: element,
@@ -4288,10 +4487,7 @@ class App extends React.Component {
4288
4487
  width,
4289
4488
  height,
4290
4489
  });
4291
- this.scene.replaceAllElements([
4292
- ...this.scene.getElementsIncludingDeleted(),
4293
- element,
4294
- ]);
4490
+ this.scene.insertElement(element);
4295
4491
  return element;
4296
4492
  };
4297
4493
  //create rectangle element with youtube top left on nearest grid point width / hight 640/360
@@ -4326,10 +4522,7 @@ class App extends React.Component {
4326
4522
  height: embedLink.intrinsicSize.h,
4327
4523
  link,
4328
4524
  });
4329
- this.scene.replaceAllElements([
4330
- ...this.scene.getElementsIncludingDeleted(),
4331
- element,
4332
- ]);
4525
+ this.scene.insertElement(element);
4333
4526
  return element;
4334
4527
  };
4335
4528
  createImageElement = ({ sceneX, sceneY, addToFrameUnderCursor = true, }) => {
@@ -4437,8 +4630,8 @@ class App extends React.Component {
4437
4630
  mutateElement(element, {
4438
4631
  points: [...element.points, [0, 0]],
4439
4632
  });
4440
- const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
4441
- this.scene.addNewElement(element);
4633
+ const boundElement = getHoveredElementForBinding(pointerDownState.origin, this);
4634
+ this.scene.insertElement(element);
4442
4635
  this.setState({
4443
4636
  draggingElement: element,
4444
4637
  editingElement: element,
@@ -4498,7 +4691,7 @@ class App extends React.Component {
4498
4691
  });
4499
4692
  }
4500
4693
  else {
4501
- this.scene.addNewElement(element);
4694
+ this.scene.insertElement(element);
4502
4695
  this.setState({
4503
4696
  multiElement: null,
4504
4697
  draggingElement: element,
@@ -4520,10 +4713,7 @@ class App extends React.Component {
4520
4713
  const frame = type === TOOL_TYPE.magicframe
4521
4714
  ? newMagicFrameElement(constructorOpts)
4522
4715
  : newFrameElement(constructorOpts);
4523
- this.scene.replaceAllElements([
4524
- ...this.scene.getElementsIncludingDeleted(),
4525
- frame,
4526
- ]);
4716
+ this.scene.insertElement(frame);
4527
4717
  this.setState({
4528
4718
  multiElement: null,
4529
4719
  draggingElement: frame,
@@ -4737,7 +4927,9 @@ class App extends React.Component {
4737
4927
  // able to select and interact with the text input
4738
4928
  !this.state.editingFrame &&
4739
4929
  dragSelectedElements(pointerDownState, selectedElements, dragOffset, this.state, this.scene, snapOffset, event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize);
4740
- this.maybeSuggestBindingForAll(selectedElements);
4930
+ this.setState({
4931
+ suggestedBindings: getSuggestedBindingsForArrows(selectedElements, this),
4932
+ });
4741
4933
  // We duplicate the selected element if alt is pressed on pointer move
4742
4934
  if (event.altKey && !pointerDownState.hit.hasBeenDuplicated) {
4743
4935
  // Move the currently selected elements to the top of the z index stack, and
@@ -4781,6 +4973,7 @@ class App extends React.Component {
4781
4973
  }
4782
4974
  }
4783
4975
  const nextSceneElements = [...nextElements, ...elementsToAppend];
4976
+ syncMovedIndices(nextSceneElements, arrayToMap(elementsToAppend));
4784
4977
  bindTextToShapeAfterDuplication(nextElements, elementsToAppend, oldIdToDuplicatedId);
4785
4978
  fixBindingsAfterDuplication(nextSceneElements, elementsToAppend, oldIdToDuplicatedId, "duplicatesServeAsOld");
4786
4979
  bindElementsToFramesAfterDuplication(nextSceneElements, elementsToAppend, oldIdToDuplicatedId);
@@ -4972,7 +5165,7 @@ class App extends React.Component {
4972
5165
  this.actionManager.executeAction(actionFinalize);
4973
5166
  }
4974
5167
  else {
4975
- const editingLinearElement = LinearElementEditor.handlePointerUp(childEvent, this.state.editingLinearElement, this.state, this.scene.getNonDeletedElements(), elementsMap);
5168
+ const editingLinearElement = LinearElementEditor.handlePointerUp(childEvent, this.state.editingLinearElement, this.state, this);
4976
5169
  if (editingLinearElement !== this.state.editingLinearElement) {
4977
5170
  this.setState({
4978
5171
  editingLinearElement,
@@ -4991,7 +5184,7 @@ class App extends React.Component {
4991
5184
  }
4992
5185
  }
4993
5186
  else {
4994
- const linearElementEditor = LinearElementEditor.handlePointerUp(childEvent, this.state.selectedLinearElement, this.state, this.scene.getNonDeletedElements(), elementsMap);
5187
+ const linearElementEditor = LinearElementEditor.handlePointerUp(childEvent, this.state.selectedLinearElement, this.state, this);
4995
5188
  const { startBindingElement, endBindingElement } = linearElementEditor;
4996
5189
  const element = this.scene.getElement(linearElementEditor.elementId);
4997
5190
  if (isBindingElement(element)) {
@@ -5060,7 +5253,7 @@ class App extends React.Component {
5060
5253
  }
5061
5254
  if (isLinearElement(draggingElement)) {
5062
5255
  if (draggingElement.points.length > 1) {
5063
- this.history.resumeRecording();
5256
+ this.store.shouldCaptureIncrement();
5064
5257
  }
5065
5258
  const pointerCoords = viewportCoordsToSceneCoords(childEvent, this.state);
5066
5259
  if (!pointerDownState.drag.hasOccurred &&
@@ -5083,7 +5276,7 @@ class App extends React.Component {
5083
5276
  else if (pointerDownState.drag.hasOccurred && !multiElement) {
5084
5277
  if (isBindingEnabled(this.state) &&
5085
5278
  isBindingElement(draggingElement, false)) {
5086
- maybeBindLinearElement(draggingElement, this.state, this.scene, pointerCoords, elementsMap);
5279
+ maybeBindLinearElement(draggingElement, this.state, pointerCoords, this);
5087
5280
  }
5088
5281
  this.setState({ suggestedBindings: [], startBoundElement: null });
5089
5282
  if (!activeTool.locked) {
@@ -5112,11 +5305,15 @@ class App extends React.Component {
5112
5305
  draggingElement &&
5113
5306
  isInvisiblySmallElement(draggingElement)) {
5114
5307
  // remove invisible element which was added in onPointerDown
5115
- this.scene.replaceAllElements(this.scene
5116
- .getElementsIncludingDeleted()
5117
- .filter((el) => el.id !== draggingElement.id));
5118
- this.setState({
5119
- draggingElement: null,
5308
+ // update the store snapshot, so that invisible elements are not captured by the store
5309
+ this.updateScene({
5310
+ elements: this.scene
5311
+ .getElementsIncludingDeleted()
5312
+ .filter((el) => el.id !== draggingElement.id),
5313
+ appState: {
5314
+ draggingElement: null,
5315
+ },
5316
+ storeAction: StoreAction.UPDATE,
5120
5317
  });
5121
5318
  return;
5122
5319
  }
@@ -5139,7 +5336,7 @@ class App extends React.Component {
5139
5336
  groupIds: [],
5140
5337
  });
5141
5338
  removeElementsFromFrame([linearElement], this.scene.getNonDeletedElementsMap());
5142
- this.scene.informMutation();
5339
+ this.scene.triggerUpdate();
5143
5340
  }
5144
5341
  }
5145
5342
  }
@@ -5197,12 +5394,16 @@ class App extends React.Component {
5197
5394
  mutateElement(draggingElement, getNormalizedDimensions(draggingElement));
5198
5395
  }
5199
5396
  if (resizingElement) {
5200
- this.history.resumeRecording();
5397
+ this.store.shouldCaptureIncrement();
5201
5398
  }
5202
5399
  if (resizingElement && isInvisiblySmallElement(resizingElement)) {
5203
- this.scene.replaceAllElements(this.scene
5204
- .getElementsIncludingDeleted()
5205
- .filter((el) => el.id !== resizingElement.id));
5400
+ // update the store snapshot, so that invisible elements are not captured by the store
5401
+ this.updateScene({
5402
+ elements: this.scene
5403
+ .getElementsIncludingDeleted()
5404
+ .filter((el) => el.id !== resizingElement.id),
5405
+ storeAction: StoreAction.UPDATE,
5406
+ });
5206
5407
  }
5207
5408
  // handle frame membership for resizing frames and/or selected elements
5208
5409
  if (pointerDownState.resize.isResizing) {
@@ -5357,10 +5558,23 @@ class App extends React.Component {
5357
5558
  }));
5358
5559
  }
5359
5560
  }
5360
- if (!pointerDownState.drag.hasOccurred &&
5561
+ if (
5562
+ // not dragged
5563
+ !pointerDownState.drag.hasOccurred &&
5564
+ // not resized
5361
5565
  !this.state.isResizing &&
5566
+ // only hitting the bounding box of the previous hit element
5362
5567
  ((hitElement &&
5363
- isHittingElementBoundingBoxWithoutHittingElement(hitElement, this.state, this.frameNameBoundsCache, pointerDownState.origin.x, pointerDownState.origin.y, this.scene.getNonDeletedElementsMap())) ||
5568
+ hitElementBoundingBoxOnly({
5569
+ x: pointerDownState.origin.x,
5570
+ y: pointerDownState.origin.y,
5571
+ element: hitElement,
5572
+ shape: this.getElementShape(hitElement),
5573
+ threshold: this.getElementHitThreshold(),
5574
+ frameNameBound: isFrameLikeElement(hitElement)
5575
+ ? this.frameNameBoundsCache.get(hitElement)
5576
+ : null,
5577
+ }, elementsMap)) ||
5364
5578
  (!hitElement &&
5365
5579
  pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements))) {
5366
5580
  if (this.state.editingLinearElement) {
@@ -5375,6 +5589,8 @@ class App extends React.Component {
5375
5589
  activeEmbeddable: null,
5376
5590
  });
5377
5591
  }
5592
+ // reset cursor
5593
+ setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
5378
5594
  return;
5379
5595
  }
5380
5596
  if (!activeTool.locked &&
@@ -5392,13 +5608,17 @@ class App extends React.Component {
5392
5608
  }));
5393
5609
  }
5394
5610
  if (activeTool.type !== "selection" ||
5395
- isSomeElementSelected(this.scene.getNonDeletedElements(), this.state)) {
5396
- this.history.resumeRecording();
5611
+ isSomeElementSelected(this.scene.getNonDeletedElements(), this.state) ||
5612
+ !isShallowEqual(this.state.previousSelectedElementIds, this.state.selectedElementIds)) {
5613
+ this.store.shouldCaptureIncrement();
5397
5614
  }
5398
5615
  if (pointerDownState.drag.hasOccurred || isResizing || isRotating) {
5399
- isBindingEnabled(this.state)
5400
- ? bindOrUnbindSelectedElements(this.scene.getSelectedElements(this.state), this.scene.getNonDeletedElements(), elementsMap)
5401
- : unbindLinearElements(this.scene.getSelectedElements(this.state), elementsMap);
5616
+ // We only allow binding via linear elements, specifically via dragging
5617
+ // the endpoints ("start" or "end").
5618
+ const linearElements = this.scene
5619
+ .getSelectedElements(this.state)
5620
+ .filter(isLinearElement);
5621
+ bindOrUnbindLinearElements(linearElements, this, isBindingEnabled(this.state), this.state.selectedLinearElement?.selectedPointsIndices ?? []);
5402
5622
  }
5403
5623
  if (activeTool.type === "laser") {
5404
5624
  this.laserTrails.endPath();
@@ -5433,7 +5653,7 @@ class App extends React.Component {
5433
5653
  }
5434
5654
  restoreReadyToEraseElements = () => {
5435
5655
  this.elementsPendingErasure = new Set();
5436
- this.onSceneUpdated();
5656
+ this.triggerRender();
5437
5657
  };
5438
5658
  eraseElements = () => {
5439
5659
  let didChange = false;
@@ -5449,7 +5669,7 @@ class App extends React.Component {
5449
5669
  });
5450
5670
  this.elementsPendingErasure = new Set();
5451
5671
  if (didChange) {
5452
- this.history.resumeRecording();
5672
+ this.store.shouldCaptureIncrement();
5453
5673
  this.scene.replaceAllElements(elements);
5454
5674
  }
5455
5675
  };
@@ -5552,7 +5772,7 @@ class App extends React.Component {
5552
5772
  this.setState({ errorMessage: t("errors.imageToolNotSupported") });
5553
5773
  return;
5554
5774
  }
5555
- this.scene.addNewElement(imageElement);
5775
+ this.scene.insertElement(imageElement);
5556
5776
  try {
5557
5777
  return await this.initializeImage({
5558
5778
  imageFile,
@@ -5725,7 +5945,7 @@ class App extends React.Component {
5725
5945
  if (uncachedImageElements.length) {
5726
5946
  const { updatedFiles } = await this.updateImageCache(uncachedImageElements, files);
5727
5947
  if (updatedFiles.size) {
5728
- this.scene.informMutation();
5948
+ this.scene.triggerUpdate();
5729
5949
  }
5730
5950
  }
5731
5951
  };
@@ -5741,7 +5961,7 @@ class App extends React.Component {
5741
5961
  }
5742
5962
  };
5743
5963
  maybeSuggestBindingAtCursor = (pointerCoords) => {
5744
- const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5964
+ const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this);
5745
5965
  this.setState({
5746
5966
  suggestedBindings: hoveredBindableElement != null ? [hoveredBindableElement] : [],
5747
5967
  });
@@ -5756,7 +5976,7 @@ class App extends React.Component {
5756
5976
  return;
5757
5977
  }
5758
5978
  const suggestedBindings = pointerCoords.reduce((acc, coords) => {
5759
- const hoveredBindableElement = getHoveredElementForBinding(coords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5979
+ const hoveredBindableElement = getHoveredElementForBinding(coords, this);
5760
5980
  if (hoveredBindableElement != null &&
5761
5981
  !isLinearElementSimpleAndAlreadyBound(linearElement, oppositeBindingBoundElement?.id, hoveredBindableElement)) {
5762
5982
  acc.push(hoveredBindableElement);
@@ -5765,13 +5985,6 @@ class App extends React.Component {
5765
5985
  }, []);
5766
5986
  this.setState({ suggestedBindings });
5767
5987
  };
5768
- maybeSuggestBindingForAll(selectedElements) {
5769
- if (selectedElements.length > 50) {
5770
- return;
5771
- }
5772
- const suggestedBindings = getEligibleElementsForBinding(selectedElements, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
5773
- this.setState({ suggestedBindings });
5774
- }
5775
5988
  clearSelection(hitElement) {
5776
5989
  this.setState((prevState) => ({
5777
5990
  selectedElementIds: makeNextSelectedElementIds({}, prevState),
@@ -5831,7 +6044,7 @@ class App extends React.Component {
5831
6044
  isLoading: false,
5832
6045
  },
5833
6046
  replaceFiles: true,
5834
- commitToHistory: true,
6047
+ storeAction: StoreAction.CAPTURE,
5835
6048
  });
5836
6049
  return;
5837
6050
  }
@@ -5899,9 +6112,10 @@ class App extends React.Component {
5899
6112
  loadFileToCanvas = async (file, fileHandle) => {
5900
6113
  file = await normalizeFile(file);
5901
6114
  try {
6115
+ const elements = this.scene.getElementsIncludingDeleted();
5902
6116
  let ret;
5903
6117
  try {
5904
- ret = await loadSceneOrLibraryFromBlob(file, this.state, this.scene.getElementsIncludingDeleted(), fileHandle);
6118
+ ret = await loadSceneOrLibraryFromBlob(file, this.state, elements, fileHandle);
5905
6119
  }
5906
6120
  catch (error) {
5907
6121
  const imageSceneDataError = error instanceof ImageSceneDataError;
@@ -5926,6 +6140,10 @@ class App extends React.Component {
5926
6140
  return;
5927
6141
  }
5928
6142
  if (ret.type === MIME_TYPES.excalidraw) {
6143
+ // restore the fractional indices by mutating elements
6144
+ syncInvalidIndices(elements.concat(ret.data.elements));
6145
+ // update the store snapshot for old elements, otherwise we would end up with duplicated fractional indices on undo
6146
+ this.store.updateSnapshot(arrayToMap(elements), this.state);
5929
6147
  this.setState({ isLoading: true });
5930
6148
  this.syncActionResult({
5931
6149
  ...ret.data,
@@ -5934,7 +6152,7 @@ class App extends React.Component {
5934
6152
  isLoading: false,
5935
6153
  },
5936
6154
  replaceFiles: true,
5937
- commitToHistory: true,
6155
+ storeAction: StoreAction.CAPTURE,
5938
6156
  });
5939
6157
  }
5940
6158
  else if (ret.type === MIME_TYPES.excalidrawlib) {
@@ -6033,7 +6251,6 @@ class App extends React.Component {
6033
6251
  dragNewElement(draggingElement, this.state.activeTool.type, pointerDownState.originInGrid.x, pointerDownState.originInGrid.y, gridX, gridY, distance(pointerDownState.originInGrid.x, gridX), distance(pointerDownState.originInGrid.y, gridY), isImageElement(draggingElement)
6034
6252
  ? !shouldMaintainAspectRatio(event)
6035
6253
  : shouldMaintainAspectRatio(event), shouldResizeFromCenter(event), aspectRatio, this.state.originSnapOffset);
6036
- this.maybeSuggestBindingForAll([draggingElement]);
6037
6254
  // highlight elements that are to be added to frames on frames creation
6038
6255
  if (this.state.activeTool.type === TOOL_TYPE.frame ||
6039
6256
  this.state.activeTool.type === TOOL_TYPE.magicframe) {
@@ -6087,16 +6304,17 @@ class App extends React.Component {
6087
6304
  snapLines,
6088
6305
  });
6089
6306
  }
6090
- if (transformElements(pointerDownState.originalElements, transformHandleType, selectedElements, this.scene.getElementsMapIncludingDeleted(), shouldRotateWithDiscreteAngle(event), shouldResizeFromCenter(event), selectedElements.length === 1 && isImageElement(selectedElements[0])
6307
+ if (transformElements(pointerDownState.originalElements, transformHandleType, selectedElements, this.scene.getElementsMapIncludingDeleted(), shouldRotateWithDiscreteAngle(event), shouldResizeFromCenter(event), selectedElements.some((element) => isImageElement(element))
6091
6308
  ? !shouldMaintainAspectRatio(event)
6092
6309
  : shouldMaintainAspectRatio(event), resizeX, resizeY, pointerDownState.resize.center.x, pointerDownState.resize.center.y)) {
6093
- this.maybeSuggestBindingForAll(selectedElements);
6310
+ const suggestedBindings = getSuggestedBindingsForArrows(selectedElements, this);
6094
6311
  const elementsToHighlight = new Set();
6095
6312
  selectedFrames.forEach((frame) => {
6096
6313
  getElementsInResizingFrame(this.scene.getNonDeletedElements(), frame, this.state, this.scene.getNonDeletedElementsMap()).forEach((element) => elementsToHighlight.add(element));
6097
6314
  });
6098
6315
  this.setState({
6099
6316
  elementsToHighlight: [...elementsToHighlight],
6317
+ suggestedBindings,
6100
6318
  });
6101
6319
  return true;
6102
6320
  }
@@ -6141,6 +6359,7 @@ class App extends React.Component {
6141
6359
  return [actionCopy, ...options];
6142
6360
  }
6143
6361
  return [
6362
+ CONTEXT_MENU_SEPARATOR,
6144
6363
  actionCut,
6145
6364
  actionCopy,
6146
6365
  actionPaste,
@@ -6153,6 +6372,7 @@ class App extends React.Component {
6153
6372
  actionPasteStyles,
6154
6373
  CONTEXT_MENU_SEPARATOR,
6155
6374
  actionGroup,
6375
+ actionTextAutoResize,
6156
6376
  actionUnbindText,
6157
6377
  actionBindText,
6158
6378
  actionWrapTextInContainer,
@@ -6321,7 +6541,7 @@ export const createTestHook = () => {
6321
6541
  return this.app?.scene.getElementsIncludingDeleted();
6322
6542
  },
6323
6543
  set(elements) {
6324
- return this.app?.scene.replaceAllElements(elements);
6544
+ return this.app?.scene.replaceAllElements(syncInvalidIndices(elements));
6325
6545
  },
6326
6546
  },
6327
6547
  });