@excalidraw/excalidraw 0.17.1-1d71f84 → 0.17.1-22b3927

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 (447) 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-7DACDEY3.js → chunk-TO2AW5PW.js} +2 -2
  14. package/dist/browser/dev/excalidraw-assets-dev/{chunk-RWZVJAQU.js → chunk-VC7RRIDZ.js} +4657 -2673
  15. package/dist/browser/dev/excalidraw-assets-dev/chunk-VC7RRIDZ.js.map +7 -0
  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-EDKQZH7Z.js → image-J7S3ALXP.js} +2 -2
  32. package/dist/browser/dev/excalidraw-assets-dev/{image-OFRRV5MB.css → image-O66MQ7WQ.css} +1 -1
  33. package/dist/browser/dev/excalidraw-assets-dev/image-O66MQ7WQ.css.map +7 -0
  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 +2387 -1934
  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-CWO763YJ.js +55 -0
  56. package/dist/browser/prod/excalidraw-assets/{chunk-WEYK4A2L.js → chunk-FFF2CSVG.js} +1 -1
  57. package/dist/browser/prod/excalidraw-assets/{chunk-R3HAIP6R.js → chunk-G4WDCSPE.js} +1 -1
  58. package/dist/browser/prod/excalidraw-assets/{chunk-HFOXJM22.js → chunk-HKZSHFLX.js} +1 -1
  59. package/dist/browser/prod/excalidraw-assets/{chunk-XIMFFJTE.js → chunk-IKCDYWMW.js} +1 -1
  60. package/dist/browser/prod/excalidraw-assets/{chunk-CTYINSWT.js → chunk-IZMZ6RPD.js} +2 -2
  61. package/dist/browser/prod/excalidraw-assets/{chunk-AHLLBBVJ.js → chunk-L5DS24G6.js} +1 -1
  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-SZBFRCU2.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 +5866 -3499
  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 +171 -18
  120. package/dist/excalidraw/actions/actionFrame.js +7 -6
  121. package/dist/excalidraw/actions/actionGroup.d.ts +16 -16
  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 +11 -11
  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 +901 -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 +491 -204
  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 +5 -3
  254. package/dist/excalidraw/components/canvases/InteractiveCanvas.js +5 -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 +6 -5
  270. package/dist/excalidraw/constants.js +11 -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 +9 -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 +4 -4
  300. package/dist/excalidraw/element/dragElements.js +27 -3
  301. package/dist/excalidraw/element/embeddable.d.ts +9 -6
  302. package/dist/excalidraw/element/embeddable.js +98 -62
  303. package/dist/excalidraw/element/image.d.ts +2 -2
  304. package/dist/excalidraw/element/index.d.ts +2 -3
  305. package/dist/excalidraw/element/index.js +1 -2
  306. package/dist/excalidraw/element/linearElementEditor.d.ts +12 -12
  307. package/dist/excalidraw/element/linearElementEditor.js +7 -5
  308. package/dist/excalidraw/element/mutateElement.d.ts +4 -5
  309. package/dist/excalidraw/element/mutateElement.js +5 -3
  310. package/dist/excalidraw/element/newElement.d.ts +6 -9
  311. package/dist/excalidraw/element/newElement.js +18 -15
  312. package/dist/excalidraw/element/resizeElements.d.ts +4 -4
  313. package/dist/excalidraw/element/resizeElements.js +172 -96
  314. package/dist/excalidraw/element/resizeTest.d.ts +7 -7
  315. package/dist/excalidraw/element/resizeTest.js +53 -8
  316. package/dist/excalidraw/element/showSelectedShapeActions.d.ts +2 -2
  317. package/dist/excalidraw/element/sizeHelpers.d.ts +2 -2
  318. package/dist/excalidraw/element/sizeHelpers.js +3 -0
  319. package/dist/excalidraw/element/sortElements.d.ts +1 -1
  320. package/dist/excalidraw/element/textElement.d.ts +6 -6
  321. package/dist/excalidraw/element/textElement.js +16 -37
  322. package/dist/excalidraw/element/textWysiwyg.d.ts +12 -6
  323. package/dist/excalidraw/element/textWysiwyg.js +38 -17
  324. package/dist/excalidraw/element/transformHandles.d.ts +24 -6
  325. package/dist/excalidraw/element/transformHandles.js +22 -11
  326. package/dist/excalidraw/element/typeChecks.d.ts +4 -4
  327. package/dist/excalidraw/element/types.d.ts +33 -10
  328. package/dist/excalidraw/emitter.d.ts +1 -1
  329. package/dist/excalidraw/errors.d.ts +3 -0
  330. package/dist/excalidraw/errors.js +3 -0
  331. package/dist/excalidraw/fractionalIndex.d.ts +40 -0
  332. package/dist/excalidraw/fractionalIndex.js +239 -0
  333. package/dist/excalidraw/frame.d.ts +4 -4
  334. package/dist/excalidraw/gatransforms.d.ts +1 -1
  335. package/dist/excalidraw/gesture.d.ts +1 -1
  336. package/dist/excalidraw/groups.d.ts +5 -3
  337. package/dist/excalidraw/groups.js +17 -0
  338. package/dist/excalidraw/history.d.ts +35 -47
  339. package/dist/excalidraw/history.js +100 -167
  340. package/dist/excalidraw/hooks/useCreatePortalContainer.js +2 -1
  341. package/dist/excalidraw/hooks/useEmitter.d.ts +2 -0
  342. package/dist/excalidraw/hooks/useEmitter.js +13 -0
  343. package/dist/excalidraw/hooks/useLibraryItemSvg.d.ts +1 -1
  344. package/dist/excalidraw/i18n.d.ts +1 -1
  345. package/dist/excalidraw/index.d.ts +3 -1
  346. package/dist/excalidraw/index.js +2 -0
  347. package/dist/excalidraw/jotai.d.ts +1 -1
  348. package/dist/excalidraw/laser-trails.d.ts +3 -2
  349. package/dist/excalidraw/locales/en.json +8 -4
  350. package/dist/excalidraw/math.d.ts +2 -2
  351. package/dist/excalidraw/mermaid.d.ts +2 -0
  352. package/dist/excalidraw/mermaid.js +28 -0
  353. package/dist/excalidraw/points.d.ts +1 -1
  354. package/dist/excalidraw/queue.d.ts +1 -1
  355. package/dist/excalidraw/renderer/helpers.d.ts +2 -2
  356. package/dist/excalidraw/renderer/helpers.js +2 -2
  357. package/dist/excalidraw/renderer/interactiveScene.d.ts +2 -2
  358. package/dist/excalidraw/renderer/interactiveScene.js +38 -11
  359. package/dist/excalidraw/renderer/renderElement.d.ts +4 -4
  360. package/dist/excalidraw/renderer/renderElement.js +7 -7
  361. package/dist/excalidraw/renderer/renderSnaps.d.ts +1 -1
  362. package/dist/excalidraw/renderer/renderSnaps.js +2 -1
  363. package/dist/excalidraw/renderer/staticScene.d.ts +1 -1
  364. package/dist/excalidraw/renderer/staticScene.js +14 -3
  365. package/dist/excalidraw/renderer/staticSvgScene.d.ts +4 -4
  366. package/dist/excalidraw/renderer/staticSvgScene.js +10 -0
  367. package/dist/excalidraw/scene/Fonts.d.ts +2 -4
  368. package/dist/excalidraw/scene/Fonts.js +6 -12
  369. package/dist/excalidraw/scene/Renderer.d.ts +4 -4
  370. package/dist/excalidraw/scene/Renderer.js +2 -3
  371. package/dist/excalidraw/scene/Scene.d.ts +19 -12
  372. package/dist/excalidraw/scene/Scene.js +44 -23
  373. package/dist/excalidraw/scene/Shape.d.ts +1 -1
  374. package/dist/excalidraw/scene/ShapeCache.d.ts +4 -4
  375. package/dist/excalidraw/scene/comparisons.d.ts +2 -2
  376. package/dist/excalidraw/scene/export.d.ts +2 -2
  377. package/dist/excalidraw/scene/export.js +6 -5
  378. package/dist/excalidraw/scene/scroll.d.ts +2 -2
  379. package/dist/excalidraw/scene/scrollbars.d.ts +3 -3
  380. package/dist/excalidraw/scene/selection.d.ts +2 -2
  381. package/dist/excalidraw/scene/types.d.ts +7 -5
  382. package/dist/excalidraw/scene/zoom.d.ts +1 -1
  383. package/dist/excalidraw/snapping.d.ts +4 -4
  384. package/dist/excalidraw/snapping.js +2 -1
  385. package/dist/excalidraw/store.d.ts +129 -0
  386. package/dist/excalidraw/store.js +296 -0
  387. package/dist/excalidraw/types.d.ts +34 -19
  388. package/dist/excalidraw/utils.d.ts +11 -4
  389. package/dist/excalidraw/utils.js +8 -0
  390. package/dist/excalidraw/zindex.d.ts +4 -4
  391. package/dist/excalidraw/zindex.js +9 -13
  392. package/dist/prod/{en-II4GK66F.json → en-OIPCBIOA.json} +8 -4
  393. package/dist/prod/index.css +1 -1
  394. package/dist/prod/index.js +44 -44
  395. package/dist/utils/bbox.d.ts +2 -2
  396. package/dist/utils/collision.d.ts +4 -0
  397. package/dist/utils/collision.js +48 -0
  398. package/dist/utils/export.d.ts +2 -2
  399. package/dist/utils/geometry/geometry.d.ts +71 -0
  400. package/dist/utils/geometry/geometry.js +674 -0
  401. package/dist/utils/geometry/shape.d.ts +56 -0
  402. package/dist/utils/geometry/shape.js +168 -0
  403. package/dist/utils/withinBounds.d.ts +1 -1
  404. package/history.ts +163 -218
  405. package/package.json +3 -2
  406. package/dist/browser/dev/excalidraw-assets-dev/chunk-AK7SWNLN.js.map +0 -7
  407. package/dist/browser/dev/excalidraw-assets-dev/chunk-RWZVJAQU.js.map +0 -7
  408. package/dist/browser/dev/excalidraw-assets-dev/chunk-Z3PH3V2B.js.map +0 -7
  409. package/dist/browser/dev/excalidraw-assets-dev/dist-Z46EOVOL.js.map +0 -7
  410. package/dist/browser/dev/excalidraw-assets-dev/image-OFRRV5MB.css.map +0 -7
  411. package/dist/browser/prod/excalidraw-assets/chunk-LL4GORAM.js +0 -55
  412. package/dist/browser/prod/excalidraw-assets/dist-PIPZXALV.js +0 -6
  413. package/dist/browser/prod/excalidraw-assets/image-EFCJDJH3.js +0 -1
  414. /package/dist/browser/dev/excalidraw-assets-dev/{blockDiagram-91b80b7a-ACFH36JV.js.map → blockDiagram-91b80b7a-H47FTXHA.js.map} +0 -0
  415. /package/dist/browser/dev/excalidraw-assets-dev/{c4Diagram-b2a90758-QZ27YR47.js.map → c4Diagram-b2a90758-NNJK6GKC.js.map} +0 -0
  416. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-HO2HMSK7.js.map → chunk-4KQVEBHW.js.map} +0 -0
  417. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-USGV265L.js.map → chunk-53YI56GV.js.map} +0 -0
  418. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-EDFX3S7X.js.map → chunk-A2WCJI4I.js.map} +0 -0
  419. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-IX4V72YG.js.map → chunk-EFLPX7NE.js.map} +0 -0
  420. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-MXVETLVM.js.map → chunk-JYIQCNWV.js.map} +0 -0
  421. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-YZIOORVX.js.map → chunk-LVIQQW6F.js.map} +0 -0
  422. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-6U7GQNJT.js.map → chunk-PXLO3FOU.js.map} +0 -0
  423. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-7DACDEY3.js.map → chunk-TO2AW5PW.js.map} +0 -0
  424. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-NJ77ZFNJ.js.map → chunk-VURILHLY.js.map} +0 -0
  425. /package/dist/browser/dev/excalidraw-assets-dev/{chunk-2T2GU7NF.js.map → chunk-ZAYGSUHF.js.map} +0 -0
  426. /package/dist/browser/dev/excalidraw-assets-dev/{classDiagram-30eddba6-QSLMH4JW.js.map → classDiagram-30eddba6-CUYIJICN.js.map} +0 -0
  427. /package/dist/browser/dev/excalidraw-assets-dev/{classDiagram-v2-f2df5561-DY4DYQ5P.js.map → classDiagram-v2-f2df5561-K6WW6K73.js.map} +0 -0
  428. /package/dist/browser/dev/excalidraw-assets-dev/{en-5TCZHGGJ.js.map → en-Y27YPU72.js.map} +0 -0
  429. /package/dist/browser/dev/excalidraw-assets-dev/{erDiagram-47591fe2-SOOJRTCB.js.map → erDiagram-47591fe2-XGAD7EEP.js.map} +0 -0
  430. /package/dist/browser/dev/excalidraw-assets-dev/{flowDiagram-5540d9b9-AHGL4KPK.js.map → flowDiagram-5540d9b9-B6EOVNNO.js.map} +0 -0
  431. /package/dist/browser/dev/excalidraw-assets-dev/{flowDiagram-v2-3b53844e-56LDZZWY.js.map → flowDiagram-v2-3b53844e-NUG24FJH.js.map} +0 -0
  432. /package/dist/browser/dev/excalidraw-assets-dev/{flowchart-elk-definition-5fe447d6-27LUKRI6.js.map → flowchart-elk-definition-5fe447d6-25Y7PCBL.js.map} +0 -0
  433. /package/dist/browser/dev/excalidraw-assets-dev/{ganttDiagram-9a3bba1f-EHGYGNG6.js.map → ganttDiagram-9a3bba1f-GNL6ZDTC.js.map} +0 -0
  434. /package/dist/browser/dev/excalidraw-assets-dev/{gitGraphDiagram-96e6b4ee-AJQNBDW5.js.map → gitGraphDiagram-96e6b4ee-HNW52NVO.js.map} +0 -0
  435. /package/dist/browser/dev/excalidraw-assets-dev/{image-EDKQZH7Z.js.map → image-J7S3ALXP.js.map} +0 -0
  436. /package/dist/browser/dev/excalidraw-assets-dev/{infoDiagram-bcd20f53-SWLLQVES.js.map → infoDiagram-bcd20f53-FWEUVFLT.js.map} +0 -0
  437. /package/dist/browser/dev/excalidraw-assets-dev/{journeyDiagram-4fe6b3dc-7UAVCWOZ.js.map → journeyDiagram-4fe6b3dc-RZIUI7UG.js.map} +0 -0
  438. /package/dist/browser/dev/excalidraw-assets-dev/{mindmap-definition-f354de21-SROW5KGM.js.map → mindmap-definition-f354de21-GBVN45GU.js.map} +0 -0
  439. /package/dist/browser/dev/excalidraw-assets-dev/{pieDiagram-79897490-QKCI6NCB.js.map → pieDiagram-79897490-ECENNII6.js.map} +0 -0
  440. /package/dist/browser/dev/excalidraw-assets-dev/{quadrantDiagram-62f64e94-LNYJZFC5.js.map → quadrantDiagram-62f64e94-ZMEOFVNL.js.map} +0 -0
  441. /package/dist/browser/dev/excalidraw-assets-dev/{requirementDiagram-05bf5f74-ZZD7ZHFA.js.map → requirementDiagram-05bf5f74-FHZSFHCR.js.map} +0 -0
  442. /package/dist/browser/dev/excalidraw-assets-dev/{sankeyDiagram-97764748-L75ZZ4UM.js.map → sankeyDiagram-97764748-VDKIKTA6.js.map} +0 -0
  443. /package/dist/browser/dev/excalidraw-assets-dev/{sequenceDiagram-acc0e65c-6PCU7TDK.js.map → sequenceDiagram-acc0e65c-6JUSPVKX.js.map} +0 -0
  444. /package/dist/browser/dev/excalidraw-assets-dev/{stateDiagram-0ff1cf1a-WM76WOPR.js.map → stateDiagram-0ff1cf1a-L3AKWENF.js.map} +0 -0
  445. /package/dist/browser/dev/excalidraw-assets-dev/{stateDiagram-v2-9a9d610d-N4HZW3O2.js.map → stateDiagram-v2-9a9d610d-NU3GGMCH.js.map} +0 -0
  446. /package/dist/browser/dev/excalidraw-assets-dev/{timeline-definition-fea2a41d-ZHGCAXGP.js.map → timeline-definition-fea2a41d-JGP7XCHW.js.map} +0 -0
  447. /package/dist/browser/dev/excalidraw-assets-dev/{xychartDiagram-ab372869-2DLOVRAZ.js.map → xychartDiagram-ab372869-HLFHHF2I.js.map} +0 -0
@@ -10,31 +10,34 @@ 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 { DEFAULT_FONT_SIZE } from "../constants";
15
+ 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, DEFAULT_TEXT_ALIGN, } from "../constants";
15
16
  import { exportCanvas, loadFromBlob } from "../data";
16
17
  import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
17
18
  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";
19
+ 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";
20
+ import { bindOrUnbindLinearElement, bindOrUnbindLinearElements, fixBindingsAfterDeletion, fixBindingsAfterDuplication, getHoveredElementForBinding, isBindingEnabled, isLinearElementSimpleAndAlreadyBound, maybeBindLinearElement, shouldEnableBindingForPointerEvent, updateBoundElements, getSuggestedBindingsForArrows, } from "../element/binding";
20
21
  import { LinearElementEditor } from "../element/linearElementEditor";
21
22
  import { mutateElement, newElementWith } from "../element/mutateElement";
22
23
  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";
24
+ import { hasBoundTextElement, isArrowElement, isBindingElement, isBindingElementType, isBoundToContainer, isFrameLikeElement, isImageElement, isEmbeddableElement, isInitializedImageElement, isLinearElement, isLinearElementType, isUsingAdaptiveRadius, isFrameElement, isIframeElement, isIframeLikeElement, isMagicFrameElement, isTextBindableContainer, } from "../element/typeChecks";
24
25
  import { getCenter, getDistance } from "../gesture";
25
26
  import { editGroupForSelectedElement, getElementsInGroup, getSelectedGroupIdForElement, getSelectedGroupIds, isElementInGroup, isSelectedViaGroup, selectGroupsForSelectedElements, } from "../groups";
26
- import History from "../history";
27
+ import { History } from "../history";
27
28
  import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n";
28
29
  import { CODES, shouldResizeFromCenter, shouldMaintainAspectRatio, shouldRotateWithDiscreteAngle, isArrowKey, KEYS, } from "../keys";
29
30
  import { isElementInViewport } from "../element/sizeHelpers";
30
31
  import { distance2d, getCornerRadius, getGridPoint, isPathALoop, } from "../math";
31
- import { calculateScrollCenter, getElementsAtPosition, getElementsWithinSelection, getNormalizedZoom, getSelectedElements, hasBackground, isSomeElementSelected, } from "../scene";
32
+ import { calculateScrollCenter, getElementsWithinSelection, getNormalizedZoom, getSelectedElements, hasBackground, isSomeElementSelected, } from "../scene";
32
33
  import Scene from "../scene/Scene";
33
34
  import { getStateForZoom } from "../scene/zoom";
34
35
  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";
36
+ import { getClosedCurveShape, getCurveShape, getEllipseShape, getFreedrawShape, getPolygonShape, getSelectionBoxShape, } from "../../utils/geometry/shape";
37
+ import { isPointInShape } from "../../utils/collision";
38
+ 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
39
  import { createSrcDoc, embeddableURLValidator, maybeParseEmbedSrc, getEmbedLink, } from "../element/embeddable";
37
- import { ContextMenu, CONTEXT_MENU_SEPARATOR, } from "./ContextMenu";
40
+ import { ContextMenu, CONTEXT_MENU_SEPARATOR } from "./ContextMenu";
38
41
  import LayerUI from "./LayerUI";
39
42
  import { Toast } from "./Toast";
40
43
  import { actionToggleViewMode } from "../actions/actionToggleViewMode";
@@ -42,8 +45,7 @@ import { dataURLToFile, generateIdFromFile, getDataURL, getFileFromEvent, ImageU
42
45
  import { getInitializedImageElements, loadHTMLImageElement, normalizeSVG, updateImageCache as _updateImageCache, } from "../element/image";
43
46
  import throttle from "lodash.throttle";
44
47
  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";
48
+ import { bindTextToShapeAfterDuplication, getApproxMinLineHeight, getApproxMinLineWidth, getBoundTextElement, getContainerCenter, getContainerElement, getDefaultLineHeight, getLineHeightInPx, getMinTextElementWidth, isMeasureTextSupported, isValidTextContainer, measureText, wrapText, } from "../element/textElement";
47
49
  import { showHyperlinkTooltip, hideHyperlinkToolip, Hyperlink, } from "../components/hyperlink/Hyperlink";
48
50
  import { isLocalLink, normalizeLink, toValidURL } from "../data/url";
49
51
  import { shouldShowBoundingBox } from "../element/transformHandles";
@@ -61,7 +63,7 @@ import { getSnapLinesAtPointer, snapDraggedElements, isActiveToolNonLinearSnappa
61
63
  import { actionWrapTextInContainer } from "../actions/actionBoundText";
62
64
  import BraveMeasureTextError from "./BraveMeasureTextError";
63
65
  import { activeEyeDropperAtom } from "./EyeDropper";
64
- import { convertToExcalidrawElements, } from "../data/transform";
66
+ import { convertToExcalidrawElements } from "../data/transform";
65
67
  import { isSidebarDockedAtom } from "./Sidebar/Sidebar";
66
68
  import { StaticCanvas, InteractiveCanvas } from "./canvases";
67
69
  import { Renderer } from "../scene/Renderer";
@@ -77,15 +79,21 @@ import { ElementCanvasButton } from "./MagicButton";
77
79
  import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
78
80
  import { EditorLocalStorage } from "../data/EditorLocalStorage";
79
81
  import FollowMode from "./FollowMode/FollowMode";
82
+ import { Store, StoreAction } from "../store";
80
83
  import { AnimationFrameHandler } from "../animation-frame-handler";
81
84
  import { AnimatedTrail } from "../animated-trail";
82
85
  import { LaserTrails } from "../laser-trails";
83
86
  import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
84
87
  import { getRenderOpacity } from "../renderer/renderElement";
88
+ import { hitElementBoundText, hitElementBoundingBoxOnly, hitElementItself, shouldTestInside, } from "../element/collision";
85
89
  import { textWysiwyg } from "../element/textWysiwyg";
86
90
  import { isOverScrollBars } from "../scene/scrollbars";
91
+ import { syncInvalidIndices, syncMovedIndices } from "../fractionalIndex";
87
92
  import { isPointHittingLink, isPointHittingLinkIcon, } from "./hyperlink/helpers";
88
93
  import { getShortcutFromShortcutName } from "../actions/shortcuts";
94
+ import { actionTextAutoResize } from "../actions/actionTextAutoResize";
95
+ import { getVisibleSceneBounds } from "../element/bounds";
96
+ import { isMaybeMermaidDefinition } from "../mermaid";
89
97
  const AppContext = React.createContext(null);
90
98
  const AppPropsContext = React.createContext(null);
91
99
  const deviceContextInitialValue = {
@@ -165,6 +173,7 @@ class App extends React.Component {
165
173
  library;
166
174
  libraryItemsFromStorage;
167
175
  id;
176
+ store;
168
177
  history;
169
178
  excalidrawContainerValue;
170
179
  files = {};
@@ -236,6 +245,8 @@ class App extends React.Component {
236
245
  this.canvas = document.createElement("canvas");
237
246
  this.rc = rough.canvas(this.canvas);
238
247
  this.renderer = new Renderer(this.scene);
248
+ this.store = new Store();
249
+ this.history = new History();
239
250
  if (excalidrawAPI) {
240
251
  const api = {
241
252
  updateScene: this.updateScene,
@@ -279,14 +290,11 @@ class App extends React.Component {
279
290
  container: this.excalidrawContainerRef.current,
280
291
  id: this.id,
281
292
  };
282
- this.fonts = new Fonts({
283
- scene: this.scene,
284
- onSceneUpdated: this.onSceneUpdated,
285
- });
293
+ this.fonts = new Fonts({ scene: this.scene });
286
294
  this.history = new History();
287
295
  this.actionManager.registerAll(actions);
288
- this.actionManager.registerAction(createUndoAction(this.history));
289
- this.actionManager.registerAction(createRedoAction(this.history));
296
+ this.actionManager.registerAction(createUndoAction(this.history, this.store));
297
+ this.actionManager.registerAction(createRedoAction(this.history, this.store));
290
298
  }
291
299
  onWindowMessage(event) {
292
300
  if (event.origin !== "https://player.vimeo.com" &&
@@ -437,7 +445,7 @@ class App extends React.Component {
437
445
  return false;
438
446
  });
439
447
  if (updated) {
440
- this.scene.informMutation();
448
+ this.scene.triggerUpdate();
441
449
  }
442
450
  // GC
443
451
  this.iFrameRefs.forEach((ref, id) => {
@@ -495,7 +503,7 @@ class App extends React.Component {
495
503
  html, body {
496
504
  width: 100%;
497
505
  height: 100%;
498
- color: ${this.state.theme === "dark" ? "white" : "black"};
506
+ color: ${this.state.theme === THEME.DARK ? "white" : "black"};
499
507
  }
500
508
  body {
501
509
  display: flex;
@@ -650,7 +658,7 @@ class App extends React.Component {
650
658
  ? src.srcdoc(this.state.theme)
651
659
  : undefined, src: src?.type !== "document" ? src?.link ?? "" : undefined,
652
660
  // 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));
661
+ 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
662
  }) }));
655
663
  }
656
664
  getFrameNameDOMId = (frameElement) => {
@@ -692,7 +700,7 @@ class App extends React.Component {
692
700
  if (!this.state.frameRendering.enabled || !this.state.frameRendering.name) {
693
701
  return null;
694
702
  }
695
- const isDarkTheme = this.state.theme === "dark";
703
+ const isDarkTheme = this.state.theme === THEME.DARK;
696
704
  let frameIndex = 0;
697
705
  let magicFrameIndex = 0;
698
706
  return this.scene.getNonDeletedFramesLikes().map((f) => {
@@ -796,9 +804,9 @@ class App extends React.Component {
796
804
  render() {
797
805
  const selectedElements = this.scene.getSelectedElements(this.state);
798
806
  const { renderTopRightUI, renderCustomStats } = this.props;
799
- const versionNonce = this.scene.getVersionNonce();
807
+ const sceneNonce = this.scene.getSceneNonce();
800
808
  const { elementsMap, visibleElements } = this.renderer.getRenderableElements({
801
- versionNonce,
809
+ sceneNonce,
802
810
  zoom: this.state.zoom,
803
811
  offsetLeft: this.state.offsetLeft,
804
812
  offsetTop: this.state.offsetTop,
@@ -867,14 +875,14 @@ class App extends React.Component {
867
875
  this.focusContainer();
868
876
  callback?.();
869
877
  });
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: {
878
+ } })), _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
879
  imageCache: this.imageCache,
872
880
  isExporting: false,
873
881
  renderGrid: true,
874
882
  canvasBackgroundColor: this.state.viewBackgroundColor,
875
883
  embedsValidationStatus: this.embedsValidationStatus,
876
884
  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()] }) }) }) }) }) }) }) }));
885
+ } }), _jsx(InteractiveCanvas, { containerRef: this.excalidrawContainerRef, canvas: this.interactiveCanvas, elementsMap: elementsMap, visibleElements: visibleElements, allElementsMap: allElementsMap, 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
886
  }
879
887
  focusContainer = () => {
880
888
  this.excalidrawContainerRef.current?.focus();
@@ -924,7 +932,7 @@ class App extends React.Component {
924
932
  mutateElement(frameElement, { customData: { generationData: data } }, false);
925
933
  }
926
934
  this.magicGenerations.set(frameElement.id, data);
927
- this.onSceneUpdated();
935
+ this.triggerRender();
928
936
  };
929
937
  getTextFromElements(elements) {
930
938
  const text = elements
@@ -1122,7 +1130,7 @@ class App extends React.Component {
1122
1130
  opacity: 100,
1123
1131
  locked: false,
1124
1132
  });
1125
- this.scene.addNewElement(frame);
1133
+ this.scene.insertElement(frame);
1126
1134
  for (const child of selectedElements) {
1127
1135
  mutateElement(child, { frameId: frame.id });
1128
1136
  }
@@ -1146,13 +1154,13 @@ class App extends React.Component {
1146
1154
  if (shouldUpdateStrokeColor) {
1147
1155
  this.syncActionResult({
1148
1156
  appState: { ...this.state, currentItemStrokeColor: color },
1149
- commitToHistory: true,
1157
+ storeAction: StoreAction.CAPTURE,
1150
1158
  });
1151
1159
  }
1152
1160
  else {
1153
1161
  this.syncActionResult({
1154
1162
  appState: { ...this.state, currentItemBackgroundColor: color },
1155
- commitToHistory: true,
1163
+ storeAction: StoreAction.CAPTURE,
1156
1164
  });
1157
1165
  }
1158
1166
  }
@@ -1166,6 +1174,7 @@ class App extends React.Component {
1166
1174
  }
1167
1175
  return el;
1168
1176
  }),
1177
+ storeAction: StoreAction.CAPTURE,
1169
1178
  });
1170
1179
  }
1171
1180
  },
@@ -1185,10 +1194,13 @@ class App extends React.Component {
1185
1194
  editingElement = element;
1186
1195
  }
1187
1196
  });
1188
- this.scene.replaceAllElements(actionResult.elements);
1189
- if (actionResult.commitToHistory) {
1190
- this.history.resumeRecording();
1197
+ if (actionResult.storeAction === StoreAction.UPDATE) {
1198
+ this.store.shouldUpdateSnapshot();
1191
1199
  }
1200
+ else if (actionResult.storeAction === StoreAction.CAPTURE) {
1201
+ this.store.shouldCaptureIncrement();
1202
+ }
1203
+ this.scene.replaceAllElements(actionResult.elements);
1192
1204
  }
1193
1205
  if (actionResult.files) {
1194
1206
  this.files = actionResult.replaceFiles
@@ -1197,8 +1209,11 @@ class App extends React.Component {
1197
1209
  this.addNewImagesToImageCache();
1198
1210
  }
1199
1211
  if (actionResult.appState || editingElement || this.state.contextMenu) {
1200
- if (actionResult.commitToHistory) {
1201
- this.history.resumeRecording();
1212
+ if (actionResult.storeAction === StoreAction.UPDATE) {
1213
+ this.store.shouldUpdateSnapshot();
1214
+ }
1215
+ else if (actionResult.storeAction === StoreAction.CAPTURE) {
1216
+ this.store.shouldCaptureIncrement();
1202
1217
  }
1203
1218
  let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
1204
1219
  let zenModeEnabled = actionResult?.appState?.zenModeEnabled || false;
@@ -1237,10 +1252,6 @@ class App extends React.Component {
1237
1252
  name,
1238
1253
  errorMessage,
1239
1254
  });
1240
- }, () => {
1241
- if (actionResult.syncHistory) {
1242
- this.history.setCurrentState(this.state, this.scene.getElementsIncludingDeleted());
1243
- }
1244
1255
  });
1245
1256
  }
1246
1257
  });
@@ -1258,6 +1269,9 @@ class App extends React.Component {
1258
1269
  resetHistory = () => {
1259
1270
  this.history.clear();
1260
1271
  };
1272
+ resetStore = () => {
1273
+ this.store.clear();
1274
+ };
1261
1275
  /**
1262
1276
  * Resets scene & history.
1263
1277
  * ! Do not use to clear scene user action !
@@ -1269,6 +1283,7 @@ class App extends React.Component {
1269
1283
  isLoading: opts?.resetLoadingState ? false : state.isLoading,
1270
1284
  theme: this.state.theme,
1271
1285
  }));
1286
+ this.resetStore();
1272
1287
  this.resetHistory();
1273
1288
  });
1274
1289
  initializeScene = async () => {
@@ -1343,10 +1358,11 @@ class App extends React.Component {
1343
1358
  // text elements on canvas, and rerender them once done. This also
1344
1359
  // seems faster even in browsers that do fire the loadingdone event.
1345
1360
  this.fonts.loadFontsForElements(scene.elements);
1361
+ this.resetStore();
1346
1362
  this.resetHistory();
1347
1363
  this.syncActionResult({
1348
1364
  ...scene,
1349
- commitToHistory: true,
1365
+ storeAction: StoreAction.UPDATE,
1350
1366
  });
1351
1367
  };
1352
1368
  isMobileBreakpoint = (width, height) => {
@@ -1417,9 +1433,16 @@ class App extends React.Component {
1417
1433
  configurable: true,
1418
1434
  value: this.history,
1419
1435
  },
1436
+ store: {
1437
+ configurable: true,
1438
+ value: this.store,
1439
+ },
1420
1440
  });
1421
1441
  }
1422
- this.scene.addCallback(this.onSceneUpdated);
1442
+ this.store.onStoreIncrementEmitter.on((increment) => {
1443
+ this.history.record(increment.elementsChange, increment.appStateChange);
1444
+ });
1445
+ this.scene.onUpdate(this.triggerRender);
1423
1446
  this.addEventListeners();
1424
1447
  if (this.props.autoFocus && this.excalidrawContainerRef.current) {
1425
1448
  this.focusContainer();
@@ -1457,6 +1480,7 @@ class App extends React.Component {
1457
1480
  componentWillUnmount() {
1458
1481
  this.renderer.destroy();
1459
1482
  this.scene = new Scene();
1483
+ this.fonts = new Fonts({ scene: this.scene });
1460
1484
  this.renderer = new Renderer(this.scene);
1461
1485
  this.files = {};
1462
1486
  this.imageCache.clear();
@@ -1468,6 +1492,7 @@ class App extends React.Component {
1468
1492
  this.laserTrails.stop();
1469
1493
  this.eraserTrail.stop();
1470
1494
  this.onChangeEmitter.clear();
1495
+ this.store.onStoreIncrementEmitter.clear();
1471
1496
  ShapeCache.destroy();
1472
1497
  SnapCache.destroy();
1473
1498
  clearTimeout(touchTimeout);
@@ -1511,7 +1536,7 @@ class App extends React.Component {
1511
1536
  this.onRemoveEventListenersEmitter.once(addEventListener(document, EVENT.KEYDOWN, this.onKeyDown, false));
1512
1537
  }
1513
1538
  this.onRemoveEventListenersEmitter.once(addEventListener(this.excalidrawContainerRef.current, EVENT.WHEEL, this.onWheel, { passive: false }), addEventListener(window, EVENT.MESSAGE, this.onWindowMessage, false), addEventListener(document, EVENT.POINTER_UP, this.removePointer), // #3553
1514
- addEventListener(document, EVENT.COPY, this.onCopy), addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }), addEventListener(document, EVENT.MOUSE_MOVE, this.updateCurrentCursorPosition),
1539
+ addEventListener(document, EVENT.COPY, this.onCopy), addEventListener(document, EVENT.KEYUP, this.onKeyUp, { passive: true }), addEventListener(document, EVENT.POINTER_MOVE, this.updateCurrentCursorPosition),
1515
1540
  // rerender text elements on font load to fix #637 && #1553
1516
1541
  addEventListener(document.fonts, "loadingdone", (event) => {
1517
1542
  const loadedFontFaces = event.fontfaces;
@@ -1520,6 +1545,9 @@ class App extends React.Component {
1520
1545
  // Safari-only desktop pinch zoom
1521
1546
  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
1547
  this.maybeCleanupAfterMissingPointerUp(null);
1548
+ // browsers (chrome?) tend to free up memory a lot, which results
1549
+ // in canvas context being cleared. Thus re-render on focus.
1550
+ this.triggerRender(true);
1523
1551
  }));
1524
1552
  if (this.state.viewModeEnabled) {
1525
1553
  return;
@@ -1535,7 +1563,8 @@ class App extends React.Component {
1535
1563
  componentDidUpdate(prevProps, prevState) {
1536
1564
  this.updateEmbeddables();
1537
1565
  const elements = this.scene.getElementsIncludingDeleted();
1538
- const elementsMap = this.scene.getNonDeletedElementsMap();
1566
+ const elementsMap = this.scene.getElementsMapIncludingDeleted();
1567
+ const nonDeletedElementsMap = this.scene.getNonDeletedElementsMap();
1539
1568
  if (!this.state.showWelcomeScreen && !elements.length) {
1540
1569
  this.setState({ showWelcomeScreen: true });
1541
1570
  }
@@ -1608,10 +1637,10 @@ class App extends React.Component {
1608
1637
  gridSize: this.props.gridModeEnabled ? GRID_SIZE : null,
1609
1638
  });
1610
1639
  }
1611
- this.excalidrawContainerRef.current?.classList.toggle("theme--dark", this.state.theme === "dark");
1640
+ this.excalidrawContainerRef.current?.classList.toggle("theme--dark", this.state.theme === THEME.DARK);
1612
1641
  if (this.state.editingLinearElement &&
1613
1642
  !this.state.selectedElementIds[this.state.editingLinearElement.elementId]) {
1614
- // defer so that the commitToHistory flag isn't reset via current update
1643
+ // defer so that the storeAction flag isn't reset via current update
1615
1644
  setTimeout(() => {
1616
1645
  // execute only if the condition still holds when the deferred callback
1617
1646
  // executes (it can be scheduled multiple times depending on how
@@ -1636,9 +1665,9 @@ class App extends React.Component {
1636
1665
  multiElement != null &&
1637
1666
  isBindingEnabled(this.state) &&
1638
1667
  isBindingElement(multiElement, false)) {
1639
- maybeBindLinearElement(multiElement, this.state, this.scene, tupleToCoors(LinearElementEditor.getPointAtIndexGlobalCoordinates(multiElement, -1, elementsMap)), elementsMap);
1668
+ maybeBindLinearElement(multiElement, this.state, tupleToCoors(LinearElementEditor.getPointAtIndexGlobalCoordinates(multiElement, -1, nonDeletedElementsMap)), this);
1640
1669
  }
1641
- this.history.record(this.state, elements);
1670
+ this.store.commit(elementsMap, this.state);
1642
1671
  // Do not notify consumers if we're still loading the scene. Among other
1643
1672
  // potential issues, this fixes a case where the tab isn't focused during
1644
1673
  // init, which would trigger onChange with empty elements, which would then
@@ -1825,6 +1854,26 @@ class App extends React.Component {
1825
1854
  });
1826
1855
  }
1827
1856
  else if (data.text) {
1857
+ if (data.text && isMaybeMermaidDefinition(data.text)) {
1858
+ const api = await import("@excalidraw/mermaid-to-excalidraw");
1859
+ try {
1860
+ const { elements: skeletonElements, files } = await api.parseMermaidToExcalidraw(data.text, {
1861
+ fontSize: DEFAULT_FONT_SIZE,
1862
+ });
1863
+ const elements = convertToExcalidrawElements(skeletonElements, {
1864
+ regenerateIds: true,
1865
+ });
1866
+ this.addElementsFromPasteOrLibrary({
1867
+ elements,
1868
+ files,
1869
+ position: "cursor",
1870
+ });
1871
+ return;
1872
+ }
1873
+ catch (err) {
1874
+ console.warn(`parsing pasted text as mermaid definition failed: ${err.message}`);
1875
+ }
1876
+ }
1828
1877
  const nonEmptyLines = normalizeEOL(data.text)
1829
1878
  .split(/\n+/)
1830
1879
  .map((s) => s.trim())
@@ -1894,16 +1943,15 @@ class App extends React.Component {
1894
1943
  }), {
1895
1944
  randomizeSeed: !opts.retainSeed,
1896
1945
  });
1897
- const allElements = [
1898
- ...this.scene.getElementsIncludingDeleted(),
1899
- ...newElements,
1900
- ];
1946
+ const prevElements = this.scene.getElementsIncludingDeleted();
1947
+ const nextElements = [...prevElements, ...newElements];
1948
+ syncMovedIndices(nextElements, arrayToMap(newElements));
1901
1949
  const topLayerFrame = this.getTopLayerFrameAtSceneCoords({ x, y });
1902
1950
  if (topLayerFrame) {
1903
1951
  const eligibleElements = filterElementsEligibleAsFrameChildren(newElements, topLayerFrame);
1904
- addElementsToFrame(allElements, eligibleElements, topLayerFrame);
1952
+ addElementsToFrame(nextElements, eligibleElements, topLayerFrame);
1905
1953
  }
1906
- this.scene.replaceAllElements(allElements);
1954
+ this.scene.replaceAllElements(nextElements);
1907
1955
  newElements.forEach((newElement) => {
1908
1956
  if (isTextElement(newElement) && isBoundToContainer(newElement)) {
1909
1957
  const container = getContainerElement(newElement, this.scene.getElementsMapIncludingDeleted());
@@ -1913,7 +1961,7 @@ class App extends React.Component {
1913
1961
  if (opts.files) {
1914
1962
  this.files = { ...this.files, ...opts.files };
1915
1963
  }
1916
- this.history.resumeRecording();
1964
+ this.store.shouldCaptureIncrement();
1917
1965
  const nextElementsToSelect = excludeElementsInFramesFromSelection(newElements);
1918
1966
  this.setState({
1919
1967
  ...this.state,
@@ -2030,27 +2078,46 @@ class App extends React.Component {
2030
2078
  text,
2031
2079
  fontSize: this.state.currentItemFontSize,
2032
2080
  fontFamily: this.state.currentItemFontFamily,
2033
- textAlign: this.state.currentItemTextAlign,
2081
+ textAlign: DEFAULT_TEXT_ALIGN,
2034
2082
  verticalAlign: DEFAULT_VERTICAL_ALIGN,
2035
2083
  locked: false,
2036
2084
  };
2085
+ const fontString = getFontString({
2086
+ fontSize: textElementProps.fontSize,
2087
+ fontFamily: textElementProps.fontFamily,
2088
+ });
2089
+ const lineHeight = getDefaultLineHeight(textElementProps.fontFamily);
2090
+ const [x1, , x2] = getVisibleSceneBounds(this.state);
2091
+ // long texts should not go beyond 800 pixels in width nor should it go below 200 px
2092
+ const maxTextWidth = Math.max(Math.min((x2 - x1) * 0.5, 800), 200);
2037
2093
  const LINE_GAP = 10;
2038
2094
  let currentY = y;
2039
2095
  const lines = isPlainPaste ? [text] : text.split("\n");
2040
2096
  const textElements = lines.reduce((acc, line, idx) => {
2041
- const text = line.trim();
2042
- const lineHeight = getDefaultLineHeight(textElementProps.fontFamily);
2043
- if (text.length) {
2097
+ const originalText = line.trim();
2098
+ if (originalText.length) {
2044
2099
  const topLayerFrame = this.getTopLayerFrameAtSceneCoords({
2045
2100
  x,
2046
2101
  y: currentY,
2047
2102
  });
2103
+ let metrics = measureText(originalText, fontString, lineHeight);
2104
+ const isTextWrapped = metrics.width > maxTextWidth;
2105
+ const text = isTextWrapped
2106
+ ? wrapText(originalText, fontString, maxTextWidth)
2107
+ : originalText;
2108
+ metrics = isTextWrapped
2109
+ ? measureText(text, fontString, lineHeight)
2110
+ : metrics;
2111
+ const startX = x - metrics.width / 2;
2112
+ const startY = currentY - metrics.height / 2;
2048
2113
  const element = newTextElement({
2049
2114
  ...textElementProps,
2050
- x,
2051
- y: currentY,
2115
+ x: startX,
2116
+ y: startY,
2052
2117
  text,
2118
+ originalText,
2053
2119
  lineHeight,
2120
+ autoResize: !isTextWrapped,
2054
2121
  frameId: topLayerFrame ? topLayerFrame.id : null,
2055
2122
  });
2056
2123
  acc.push(element);
@@ -2071,16 +2138,7 @@ class App extends React.Component {
2071
2138
  if (textElements.length === 0) {
2072
2139
  return;
2073
2140
  }
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
- }
2141
+ this.scene.insertElements(textElements);
2084
2142
  this.setState({
2085
2143
  selectedElementIds: makeNextSelectedElementIds(Object.fromEntries(textElements.map((el) => [el.id, true])), this.state),
2086
2144
  });
@@ -2096,7 +2154,7 @@ class App extends React.Component {
2096
2154
  });
2097
2155
  PLAIN_PASTE_TOAST_SHOWN = true;
2098
2156
  }
2099
- this.history.resumeRecording();
2157
+ this.store.shouldCaptureIncrement();
2100
2158
  }
2101
2159
  setAppState = (state, callback) => {
2102
2160
  this.setState(state, callback);
@@ -2278,25 +2336,49 @@ class App extends React.Component {
2278
2336
  ShapeCache.delete(element);
2279
2337
  }
2280
2338
  });
2281
- this.scene.informMutation();
2339
+ this.scene.triggerUpdate();
2282
2340
  this.addNewImagesToImageCache();
2283
2341
  });
2284
2342
  updateScene = withBatchedUpdates((sceneData) => {
2285
- if (sceneData.commitToHistory) {
2286
- this.history.resumeRecording();
2343
+ const nextElements = syncInvalidIndices(sceneData.elements ?? []);
2344
+ if (sceneData.storeAction && sceneData.storeAction !== StoreAction.NONE) {
2345
+ const prevCommittedAppState = this.store.snapshot.appState;
2346
+ const prevCommittedElements = this.store.snapshot.elements;
2347
+ const nextCommittedAppState = sceneData.appState
2348
+ ? Object.assign({}, prevCommittedAppState, sceneData.appState) // new instance, with partial appstate applied to previously captured one, including hidden prop inside `prevCommittedAppState`
2349
+ : prevCommittedAppState;
2350
+ const nextCommittedElements = sceneData.elements
2351
+ ? this.store.filterUncomittedElements(this.scene.getElementsMapIncludingDeleted(), // Only used to detect uncomitted local elements
2352
+ arrayToMap(nextElements))
2353
+ : prevCommittedElements;
2354
+ // 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
2355
+ // do NOT schedule store actions (execute after re-render), as it might cause unexpected concurrency issues if not handled well
2356
+ if (sceneData.storeAction === StoreAction.CAPTURE) {
2357
+ this.store.captureIncrement(nextCommittedElements, nextCommittedAppState);
2358
+ }
2359
+ else if (sceneData.storeAction === StoreAction.UPDATE) {
2360
+ this.store.updateSnapshot(nextCommittedElements, nextCommittedAppState);
2361
+ }
2287
2362
  }
2288
2363
  if (sceneData.appState) {
2289
2364
  this.setState(sceneData.appState);
2290
2365
  }
2291
2366
  if (sceneData.elements) {
2292
- this.scene.replaceAllElements(sceneData.elements);
2367
+ this.scene.replaceAllElements(nextElements);
2293
2368
  }
2294
2369
  if (sceneData.collaborators) {
2295
2370
  this.setState({ collaborators: sceneData.collaborators });
2296
2371
  }
2297
2372
  });
2298
- onSceneUpdated = () => {
2299
- this.setState({});
2373
+ triggerRender = (
2374
+ /** force always re-renders canvas even if no change */
2375
+ force) => {
2376
+ if (force === true) {
2377
+ this.scene.triggerUpdate();
2378
+ }
2379
+ else {
2380
+ this.setState({});
2381
+ }
2300
2382
  };
2301
2383
  /**
2302
2384
  * @returns whether the menu was toggled on or off
@@ -2355,8 +2437,7 @@ class App extends React.Component {
2355
2437
  !event.altKey) {
2356
2438
  this.setToast({
2357
2439
  message: t("commandPalette.shortcutHint", {
2358
- shortcutOne: getShortcutFromShortcutName("commandPalette"),
2359
- shortcutTwo: getShortcutFromShortcutName("commandPalette", 1),
2440
+ shortcut: getShortcutFromShortcutName("commandPalette"),
2360
2441
  }),
2361
2442
  });
2362
2443
  event.preventDefault();
@@ -2464,7 +2545,9 @@ class App extends React.Component {
2464
2545
  simultaneouslyUpdated: selectedElements,
2465
2546
  });
2466
2547
  });
2467
- this.maybeSuggestBindingForAll(selectedElements);
2548
+ this.setState({
2549
+ suggestedBindings: getSuggestedBindingsForArrows(selectedElements, this),
2550
+ });
2468
2551
  event.preventDefault();
2469
2552
  }
2470
2553
  else if (event.key === KEYS.ENTER) {
@@ -2476,7 +2559,7 @@ class App extends React.Component {
2476
2559
  if (!this.state.editingLinearElement ||
2477
2560
  this.state.editingLinearElement.elementId !==
2478
2561
  selectedElements[0].id) {
2479
- this.history.resumeRecording();
2562
+ this.store.shouldCaptureIncrement();
2480
2563
  this.setState({
2481
2564
  editingLinearElement: new LinearElementEditor(selectedElement),
2482
2565
  });
@@ -2602,11 +2685,7 @@ class App extends React.Component {
2602
2685
  this.setState({ isBindingEnabled: true });
2603
2686
  }
2604
2687
  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);
2688
+ bindOrUnbindLinearElements(this.scene.getSelectedElements(this.state).filter(isLinearElement), this, isBindingEnabled(this.state), this.state.selectedLinearElement?.selectedPointsIndices ?? []);
2610
2689
  this.setState({ suggestedBindings: [] });
2611
2690
  }
2612
2691
  });
@@ -2644,6 +2723,9 @@ class App extends React.Component {
2644
2723
  originSnapOffset: null,
2645
2724
  activeEmbeddable: null,
2646
2725
  };
2726
+ if (nextActiveTool.type === "freedraw") {
2727
+ this.store.shouldCaptureIncrement();
2728
+ }
2647
2729
  if (nextActiveTool.type !== "selection") {
2648
2730
  return {
2649
2731
  ...prevState,
@@ -2741,15 +2823,16 @@ class App extends React.Component {
2741
2823
  });
2742
2824
  handleTextWysiwyg(element, { isExistingElement = false, }) {
2743
2825
  const elementsMap = this.scene.getElementsMapIncludingDeleted();
2744
- const updateElement = (text, originalText, isDeleted) => {
2826
+ const updateElement = (nextOriginalText, isDeleted) => {
2745
2827
  this.scene.replaceAllElements([
2746
2828
  // Not sure why we include deleted elements as well hence using deleted elements map
2747
2829
  ...this.scene.getElementsIncludingDeleted().map((_element) => {
2748
2830
  if (_element.id === element.id && isTextElement(_element)) {
2749
- return updateTextElement(_element, getContainerElement(_element, elementsMap), elementsMap, {
2750
- text,
2751
- isDeleted,
2752
- originalText,
2831
+ return newElementWith(_element, {
2832
+ originalText: nextOriginalText,
2833
+ isDeleted: isDeleted ?? _element.isDeleted,
2834
+ // returns (wrapped) text and new dimensions
2835
+ ...refreshTextDimensions(_element, getContainerElement(_element, elementsMap), elementsMap, nextOriginalText),
2753
2836
  });
2754
2837
  }
2755
2838
  return _element;
@@ -2769,15 +2852,15 @@ class App extends React.Component {
2769
2852
  viewportY - this.state.offsetTop,
2770
2853
  ];
2771
2854
  },
2772
- onChange: withBatchedUpdates((text) => {
2773
- updateElement(text, text, false);
2855
+ onChange: withBatchedUpdates((nextOriginalText) => {
2856
+ updateElement(nextOriginalText, false);
2774
2857
  if (isNonDeletedElement(element)) {
2775
2858
  updateBoundElements(element, elementsMap);
2776
2859
  }
2777
2860
  }),
2778
- onSubmit: withBatchedUpdates(({ text, viaKeyboard, originalText }) => {
2779
- const isDeleted = !text.trim();
2780
- updateElement(text, originalText, isDeleted);
2861
+ onSubmit: withBatchedUpdates(({ viaKeyboard, nextOriginalText }) => {
2862
+ const isDeleted = !nextOriginalText.trim();
2863
+ updateElement(nextOriginalText, isDeleted);
2781
2864
  // select the created text element only if submitting via keyboard
2782
2865
  // (when submitting via click it should act as signal to deselect)
2783
2866
  if (!isDeleted && viaKeyboard) {
@@ -2797,7 +2880,7 @@ class App extends React.Component {
2797
2880
  ]);
2798
2881
  }
2799
2882
  if (!isDeleted || isExistingElement) {
2800
- this.history.resumeRecording();
2883
+ this.store.shouldCaptureIncrement();
2801
2884
  }
2802
2885
  this.setState({
2803
2886
  draggingElement: null,
@@ -2811,12 +2894,17 @@ class App extends React.Component {
2811
2894
  element,
2812
2895
  excalidrawContainer: this.excalidrawContainerRef.current,
2813
2896
  app: this,
2897
+ // when text is selected, it's hard (at least on iOS) to re-position the
2898
+ // caret (i.e. deselect). There's not much use for always selecting
2899
+ // the text on edit anyway (and users can select-all from contextmenu
2900
+ // if needed)
2901
+ autoSelect: !this.device.isTouchScreen,
2814
2902
  });
2815
2903
  // deselect all other elements when inserting text
2816
2904
  this.deselectElements();
2817
2905
  // do an initial update to re-initialize element position since we were
2818
2906
  // modifying element's x/y for sake of editor (case: syncing to remote)
2819
- updateElement(element.text, element.originalText, false);
2907
+ updateElement(element.originalText, false);
2820
2908
  }
2821
2909
  deselectElements() {
2822
2910
  this.setState({
@@ -2835,6 +2923,57 @@ class App extends React.Component {
2835
2923
  }
2836
2924
  return null;
2837
2925
  }
2926
+ /**
2927
+ * get the pure geometric shape of an excalidraw element
2928
+ * which is then used for hit detection
2929
+ */
2930
+ getElementShape(element) {
2931
+ switch (element.type) {
2932
+ case "rectangle":
2933
+ case "diamond":
2934
+ case "frame":
2935
+ case "magicframe":
2936
+ case "embeddable":
2937
+ case "image":
2938
+ case "iframe":
2939
+ case "text":
2940
+ case "selection":
2941
+ return getPolygonShape(element);
2942
+ case "arrow":
2943
+ case "line": {
2944
+ const roughShape = ShapeCache.get(element)?.[0] ??
2945
+ ShapeCache.generateElementShape(element, null)[0];
2946
+ const [, , , , cx, cy] = getElementAbsoluteCoords(element, this.scene.getNonDeletedElementsMap());
2947
+ return shouldTestInside(element)
2948
+ ? getClosedCurveShape(element, roughShape, [element.x, element.y], element.angle, [cx, cy])
2949
+ : getCurveShape(roughShape, [element.x, element.y], element.angle, [
2950
+ cx,
2951
+ cy,
2952
+ ]);
2953
+ }
2954
+ case "ellipse":
2955
+ return getEllipseShape(element);
2956
+ case "freedraw": {
2957
+ const [, , , , cx, cy] = getElementAbsoluteCoords(element, this.scene.getNonDeletedElementsMap());
2958
+ return getFreedrawShape(element, [cx, cy], shouldTestInside(element));
2959
+ }
2960
+ }
2961
+ }
2962
+ getBoundTextShape(element) {
2963
+ const boundTextElement = getBoundTextElement(element, this.scene.getNonDeletedElementsMap());
2964
+ if (boundTextElement) {
2965
+ if (element.type === "arrow") {
2966
+ return this.getElementShape({
2967
+ ...boundTextElement,
2968
+ // arrow's bound text accurate position is not stored in the element's property
2969
+ // but rather calculated and returned from the following static method
2970
+ ...LinearElementEditor.getBoundTextElementPosition(element, boundTextElement, this.scene.getNonDeletedElementsMap()),
2971
+ });
2972
+ }
2973
+ return this.getElementShape(boundTextElement);
2974
+ }
2975
+ return null;
2976
+ }
2838
2977
  getElementAtPosition(x, y, opts) {
2839
2978
  const allHitElements = this.getElementsAtPosition(x, y, opts?.includeBoundTextElement, opts?.includeLockedElements);
2840
2979
  if (allHitElements.length > 1) {
@@ -2848,9 +2987,20 @@ class App extends React.Component {
2848
2987
  const elementWithHighestZIndex = allHitElements[allHitElements.length - 1];
2849
2988
  // If we're hitting element with highest z-index only on its bounding box
2850
2989
  // 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;
2990
+ return hitElementItself({
2991
+ x,
2992
+ y,
2993
+ element: elementWithHighestZIndex,
2994
+ shape: this.getElementShape(elementWithHighestZIndex),
2995
+ // when overlapping, we would like to be more precise
2996
+ // this also avoids the need to update past tests
2997
+ threshold: this.getElementHitThreshold() / 2,
2998
+ frameNameBound: isFrameLikeElement(elementWithHighestZIndex)
2999
+ ? this.frameNameBoundsCache.get(elementWithHighestZIndex)
3000
+ : null,
3001
+ })
3002
+ ? elementWithHighestZIndex
3003
+ : allHitElements[allHitElements.length - 2];
2854
3004
  }
2855
3005
  if (allHitElements.length === 1) {
2856
3006
  return allHitElements[0];
@@ -2858,15 +3008,17 @@ class App extends React.Component {
2858
3008
  return null;
2859
3009
  }
2860
3010
  getElementsAtPosition(x, y, includeBoundTextElement = false, includeLockedElements = false) {
2861
- const elements = includeBoundTextElement && includeLockedElements
3011
+ const iframeLikes = [];
3012
+ const elementsMap = this.scene.getNonDeletedElementsMap();
3013
+ const elements = (includeBoundTextElement && includeLockedElements
2862
3014
  ? this.scene.getNonDeletedElements()
2863
3015
  : this.scene
2864
3016
  .getNonDeletedElements()
2865
3017
  .filter((element) => (includeLockedElements || !element.locked) &&
2866
3018
  (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) => {
3019
+ !(isTextElement(element) && element.containerId))))
3020
+ .filter((el) => this.hitElement(x, y, el))
3021
+ .filter((element) => {
2870
3022
  // hitting a frame's element from outside the frame is not considered a hit
2871
3023
  const containingFrame = getContainingFrame(element, elementsMap);
2872
3024
  return containingFrame &&
@@ -2874,9 +3026,82 @@ class App extends React.Component {
2874
3026
  this.state.frameRendering.clip
2875
3027
  ? isCursorInFrame({ x, y }, containingFrame, elementsMap)
2876
3028
  : true;
3029
+ })
3030
+ .filter((el) => {
3031
+ // The parameter elements comes ordered from lower z-index to higher.
3032
+ // We want to preserve that order on the returned array.
3033
+ // Exception being embeddables which should be on top of everything else in
3034
+ // terms of hit testing.
3035
+ if (isIframeElement(el)) {
3036
+ iframeLikes.push(el);
3037
+ return false;
3038
+ }
3039
+ return true;
3040
+ })
3041
+ .concat(iframeLikes);
3042
+ return elements;
3043
+ }
3044
+ getElementHitThreshold() {
3045
+ return DEFAULT_COLLISION_THRESHOLD / this.state.zoom.value;
3046
+ }
3047
+ hitElement(x, y, element, considerBoundingBox = true) {
3048
+ // if the element is selected, then hit test is done against its bounding box
3049
+ if (considerBoundingBox &&
3050
+ this.state.selectedElementIds[element.id] &&
3051
+ shouldShowBoundingBox([element], this.state)) {
3052
+ const selectionShape = getSelectionBoxShape(element, this.scene.getNonDeletedElementsMap(), this.getElementHitThreshold());
3053
+ return isPointInShape([x, y], selectionShape);
3054
+ }
3055
+ // take bound text element into consideration for hit collision as well
3056
+ const hitBoundTextOfElement = hitElementBoundText(x, y, this.getBoundTextShape(element));
3057
+ if (hitBoundTextOfElement) {
3058
+ return true;
3059
+ }
3060
+ return hitElementItself({
3061
+ x,
3062
+ y,
3063
+ element,
3064
+ shape: this.getElementShape(element),
3065
+ threshold: this.getElementHitThreshold(),
3066
+ frameNameBound: isFrameLikeElement(element)
3067
+ ? this.frameNameBoundsCache.get(element)
3068
+ : null,
2877
3069
  });
2878
3070
  }
2879
- startTextEditing = ({ sceneX, sceneY, insertAtParentCenter = true, container, }) => {
3071
+ getTextBindableContainerAtPosition(x, y) {
3072
+ const elements = this.scene.getNonDeletedElements();
3073
+ const selectedElements = this.scene.getSelectedElements(this.state);
3074
+ if (selectedElements.length === 1) {
3075
+ return isTextBindableContainer(selectedElements[0], false)
3076
+ ? selectedElements[0]
3077
+ : null;
3078
+ }
3079
+ let hitElement = null;
3080
+ // We need to do hit testing from front (end of the array) to back (beginning of the array)
3081
+ for (let index = elements.length - 1; index >= 0; --index) {
3082
+ if (elements[index].isDeleted) {
3083
+ continue;
3084
+ }
3085
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(elements[index], this.scene.getNonDeletedElementsMap());
3086
+ if (isArrowElement(elements[index]) &&
3087
+ hitElementItself({
3088
+ x,
3089
+ y,
3090
+ element: elements[index],
3091
+ shape: this.getElementShape(elements[index]),
3092
+ threshold: this.getElementHitThreshold(),
3093
+ })) {
3094
+ hitElement = elements[index];
3095
+ break;
3096
+ }
3097
+ else if (x1 < x && x < x2 && y1 < y && y < y2) {
3098
+ hitElement = elements[index];
3099
+ break;
3100
+ }
3101
+ }
3102
+ return isTextBindableContainer(hitElement, false) ? hitElement : null;
3103
+ }
3104
+ startTextEditing = ({ sceneX, sceneY, insertAtParentCenter = true, container, autoEdit = true, }) => {
2880
3105
  let shouldBindToContainer = false;
2881
3106
  let parentCenterPosition = insertAtParentCenter &&
2882
3107
  this.getTextWysiwygSnappedToCenterPosition(sceneX, sceneY, this.state, container);
@@ -2974,15 +3199,20 @@ class App extends React.Component {
2974
3199
  this.scene.insertElementAtIndex(element, containerIndex + 1);
2975
3200
  }
2976
3201
  else {
2977
- this.scene.addNewElement(element);
3202
+ this.scene.insertElement(element);
2978
3203
  }
2979
3204
  }
2980
- this.setState({
2981
- editingElement: element,
2982
- });
2983
- this.handleTextWysiwyg(element, {
2984
- isExistingElement: !!existingTextElement,
2985
- });
3205
+ if (autoEdit || existingTextElement || container) {
3206
+ this.handleTextWysiwyg(element, {
3207
+ isExistingElement: !!existingTextElement,
3208
+ });
3209
+ }
3210
+ else {
3211
+ this.setState({
3212
+ draggingElement: element,
3213
+ multiElement: null,
3214
+ });
3215
+ }
2986
3216
  };
2987
3217
  handleCanvasDoubleClick = (event) => {
2988
3218
  // case: double-clicking with arrow/line tool selected would both create
@@ -2999,7 +3229,7 @@ class App extends React.Component {
2999
3229
  if (event[KEYS.CTRL_OR_CMD] &&
3000
3230
  (!this.state.editingLinearElement ||
3001
3231
  this.state.editingLinearElement.elementId !== selectedElements[0].id)) {
3002
- this.history.resumeRecording();
3232
+ this.store.shouldCaptureIncrement();
3003
3233
  this.setState({
3004
3234
  editingLinearElement: new LinearElementEditor(selectedElements[0]),
3005
3235
  });
@@ -3014,6 +3244,7 @@ class App extends React.Component {
3014
3244
  const selectedGroupId = hitElement &&
3015
3245
  getSelectedGroupIdForElement(hitElement, this.state.selectedGroupIds);
3016
3246
  if (selectedGroupId) {
3247
+ this.store.shouldCaptureIncrement();
3017
3248
  this.setState((prevState) => ({
3018
3249
  ...prevState,
3019
3250
  ...selectGroupsForSelectedElements({
@@ -3033,11 +3264,17 @@ class App extends React.Component {
3033
3264
  });
3034
3265
  return;
3035
3266
  }
3036
- const container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
3267
+ const container = this.getTextBindableContainerAtPosition(sceneX, sceneY);
3037
3268
  if (container) {
3038
3269
  if (hasBoundTextElement(container) ||
3039
3270
  !isTransparent(container.backgroundColor) ||
3040
- isHittingElementNotConsideringBoundingBox(container, this.state, this.frameNameBoundsCache, [sceneX, sceneY], this.scene.getNonDeletedElementsMap())) {
3271
+ hitElementItself({
3272
+ x: sceneX,
3273
+ y: sceneY,
3274
+ element: container,
3275
+ shape: this.getElementShape(container),
3276
+ threshold: this.getElementHitThreshold(),
3277
+ })) {
3041
3278
  const midPoint = getContainerCenter(container, this.state, this.scene.getNonDeletedElementsMap());
3042
3279
  sceneX = midPoint.x;
3043
3280
  sceneY = midPoint.y;
@@ -3092,7 +3329,7 @@ class App extends React.Component {
3092
3329
  }
3093
3330
  if (!customEvent?.defaultPrevented) {
3094
3331
  const target = isLocalLink(url) ? "_self" : "_blank";
3095
- const newWindow = window.open(undefined, target, "noopener noreferrer");
3332
+ const newWindow = window.open(undefined, target);
3096
3333
  // https://mathiasbynens.github.io/rel-noopener/
3097
3334
  if (newWindow) {
3098
3335
  newWindow.opener = null;
@@ -3142,8 +3379,11 @@ class App extends React.Component {
3142
3379
  }, state);
3143
3380
  this.translateCanvas({
3144
3381
  zoom: zoomState.zoom,
3145
- scrollX: zoomState.scrollX + deltaX / nextZoom,
3146
- scrollY: zoomState.scrollY + deltaY / nextZoom,
3382
+ // 2x multiplier is just a magic number that makes this work correctly
3383
+ // on touchscreen devices (note: if we get report that panning is slower/faster
3384
+ // than actual movement, consider swapping with devicePixelRatio)
3385
+ scrollX: zoomState.scrollX + 2 * (deltaX / nextZoom),
3386
+ scrollY: zoomState.scrollY + 2 * (deltaY / nextZoom),
3147
3387
  shouldCacheIgnoreZoom: true,
3148
3388
  });
3149
3389
  });
@@ -3308,15 +3548,23 @@ class App extends React.Component {
3308
3548
  if (selectedElements.length === 1 &&
3309
3549
  !isOverScrollBar &&
3310
3550
  !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;
3551
+ // for linear elements, we'd like to prioritize point dragging over edge resizing
3552
+ // therefore, we update and check hovered point index first
3553
+ if (this.state.selectedLinearElement) {
3554
+ this.handleHoverSelectedLinearElement(this.state.selectedLinearElement, scenePointerX, scenePointerY);
3555
+ }
3556
+ if (!this.state.selectedLinearElement ||
3557
+ this.state.selectedLinearElement.hoverPointIndex === -1) {
3558
+ const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, scenePointerX, scenePointerY, this.state.zoom, event.pointerType, this.scene.getNonDeletedElementsMap(), this.device);
3559
+ if (elementWithTransformHandleType &&
3560
+ elementWithTransformHandleType.transformHandleType) {
3561
+ setCursor(this.interactiveCanvas, getCursorForResizingElement(elementWithTransformHandleType));
3562
+ return;
3563
+ }
3316
3564
  }
3317
3565
  }
3318
3566
  else if (selectedElements.length > 1 && !isOverScrollBar) {
3319
- const transformHandleType = getTransformHandleTypeFromCoords(getCommonBounds(selectedElements), scenePointerX, scenePointerY, this.state.zoom, event.pointerType);
3567
+ const transformHandleType = getTransformHandleTypeFromCoords(getCommonBounds(selectedElements), scenePointerX, scenePointerY, this.state.zoom, event.pointerType, this.device);
3320
3568
  if (transformHandleType) {
3321
3569
  setCursor(this.interactiveCanvas, getCursorForResizingElement({
3322
3570
  transformHandleType,
@@ -3420,7 +3668,7 @@ class App extends React.Component {
3420
3668
  }
3421
3669
  };
3422
3670
  const distance = distance2d(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y, scenePointer.x, scenePointer.y);
3423
- const threshold = 10 / this.state.zoom.value;
3671
+ const threshold = this.getElementHitThreshold();
3424
3672
  const point = { ...pointerDownState.lastCoords };
3425
3673
  let samplingInterval = 0;
3426
3674
  while (samplingInterval <= distance) {
@@ -3456,7 +3704,7 @@ class App extends React.Component {
3456
3704
  }
3457
3705
  }
3458
3706
  this.elementsPendingErasure = new Set(this.elementsPendingErasure);
3459
- this.onSceneUpdated();
3707
+ this.triggerRender();
3460
3708
  }
3461
3709
  };
3462
3710
  // set touch moving for mobile context menu
@@ -3466,30 +3714,29 @@ class App extends React.Component {
3466
3714
  handleHoverSelectedLinearElement(linearElementEditor, scenePointerX, scenePointerY) {
3467
3715
  const elementsMap = this.scene.getNonDeletedElementsMap();
3468
3716
  const element = LinearElementEditor.getElement(linearElementEditor.elementId, elementsMap);
3469
- const boundTextElement = getBoundTextElement(element, elementsMap);
3470
3717
  if (!element) {
3471
3718
  return;
3472
3719
  }
3473
3720
  if (this.state.selectedLinearElement) {
3474
3721
  let hoverPointIndex = -1;
3475
3722
  let segmentMidPointHoveredCoords = null;
3476
- if (isHittingElementNotConsideringBoundingBox(element, this.state, this.frameNameBoundsCache, [scenePointerX, scenePointerY], elementsMap)) {
3723
+ if (hitElementItself({
3724
+ x: scenePointerX,
3725
+ y: scenePointerY,
3726
+ element,
3727
+ shape: this.getElementShape(element),
3728
+ })) {
3477
3729
  hoverPointIndex = LinearElementEditor.getPointIndexUnderCursor(element, elementsMap, this.state.zoom, scenePointerX, scenePointerY);
3478
3730
  segmentMidPointHoveredCoords =
3479
3731
  LinearElementEditor.getSegmentMidpointHitCoords(linearElementEditor, { x: scenePointerX, y: scenePointerY }, this.state, this.scene.getNonDeletedElementsMap());
3480
3732
  if (hoverPointIndex >= 0 || segmentMidPointHoveredCoords) {
3481
3733
  setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
3482
3734
  }
3483
- else {
3735
+ else if (this.hitElement(scenePointerX, scenePointerY, element)) {
3484
3736
  setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3485
3737
  }
3486
3738
  }
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())) {
3739
+ else if (this.hitElement(scenePointerX, scenePointerY, element)) {
3493
3740
  setCursor(this.interactiveCanvas, CURSOR_TYPE.MOVE);
3494
3741
  }
3495
3742
  if (this.state.selectedLinearElement.hoverPointIndex !== hoverPointIndex) {
@@ -3556,6 +3803,7 @@ class App extends React.Component {
3556
3803
  return obj;
3557
3804
  }, {}), this.state),
3558
3805
  },
3806
+ storeAction: StoreAction.UPDATE,
3559
3807
  });
3560
3808
  return;
3561
3809
  }
@@ -3667,7 +3915,6 @@ class App extends React.Component {
3667
3915
  }
3668
3916
  if (this.state.activeTool.type === "text") {
3669
3917
  this.handleTextOnPointerDown(event, pointerDownState);
3670
- return;
3671
3918
  }
3672
3919
  else if (this.state.activeTool.type === "arrow" ||
3673
3920
  this.state.activeTool.type === "line") {
@@ -3995,8 +4242,11 @@ class App extends React.Component {
3995
4242
  const elements = this.scene.getNonDeletedElements();
3996
4243
  const elementsMap = this.scene.getNonDeletedElementsMap();
3997
4244
  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());
4245
+ if (selectedElements.length === 1 &&
4246
+ !this.state.editingLinearElement &&
4247
+ !(this.state.selectedLinearElement &&
4248
+ this.state.selectedLinearElement.hoverPointIndex !== -1)) {
4249
+ const elementWithTransformHandleType = getElementWithTransformHandleType(elements, this.state, pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType, this.scene.getNonDeletedElementsMap(), this.device);
4000
4250
  if (elementWithTransformHandleType != null) {
4001
4251
  this.setState({
4002
4252
  resizingElement: elementWithTransformHandleType.element,
@@ -4006,7 +4256,7 @@ class App extends React.Component {
4006
4256
  }
4007
4257
  }
4008
4258
  else if (selectedElements.length > 1) {
4009
- pointerDownState.resize.handleType = getTransformHandleTypeFromCoords(getCommonBounds(selectedElements), pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType);
4259
+ pointerDownState.resize.handleType = getTransformHandleTypeFromCoords(getCommonBounds(selectedElements), pointerDownState.origin.x, pointerDownState.origin.y, this.state.zoom, event.pointerType, this.device);
4010
4260
  }
4011
4261
  if (pointerDownState.resize.handleType) {
4012
4262
  pointerDownState.resize.isResizing = true;
@@ -4020,7 +4270,7 @@ class App extends React.Component {
4020
4270
  else {
4021
4271
  if (this.state.selectedLinearElement) {
4022
4272
  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);
4273
+ const ret = LinearElementEditor.handlePointerDown(event, this.state, this.store, pointerDownState.origin, linearElementEditor, this);
4024
4274
  if (ret.hitElement) {
4025
4275
  pointerDownState.hit.element = ret.hitElement;
4026
4276
  }
@@ -4181,7 +4431,7 @@ class App extends React.Component {
4181
4431
  return false;
4182
4432
  }
4183
4433
  // How many pixels off the shape boundary we still consider a hit
4184
- const threshold = 10 / this.state.zoom.value;
4434
+ const threshold = this.getElementHitThreshold();
4185
4435
  const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
4186
4436
  return (point.x > x1 - threshold &&
4187
4437
  point.x < x2 + threshold &&
@@ -4201,7 +4451,7 @@ class App extends React.Component {
4201
4451
  includeBoundTextElement: true,
4202
4452
  });
4203
4453
  // FIXME
4204
- let container = getTextBindableContainerAtPosition(this.scene.getNonDeletedElements(), this.state, sceneX, sceneY, this.scene.getNonDeletedElementsMap());
4454
+ let container = this.getTextBindableContainerAtPosition(sceneX, sceneY);
4205
4455
  if (hasBoundTextElement(element)) {
4206
4456
  container = element;
4207
4457
  sceneX = element.x + element.width / 2;
@@ -4212,6 +4462,7 @@ class App extends React.Component {
4212
4462
  sceneY,
4213
4463
  insertAtParentCenter: !event.altKey,
4214
4464
  container,
4465
+ autoEdit: false,
4215
4466
  });
4216
4467
  resetCursor(this.interactiveCanvas);
4217
4468
  if (!this.state.activeTool.locked) {
@@ -4259,8 +4510,8 @@ class App extends React.Component {
4259
4510
  points: [[0, 0]],
4260
4511
  pressures,
4261
4512
  });
4262
- const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
4263
- this.scene.addNewElement(element);
4513
+ const boundElement = getHoveredElementForBinding(pointerDownState.origin, this);
4514
+ this.scene.insertElement(element);
4264
4515
  this.setState({
4265
4516
  draggingElement: element,
4266
4517
  editingElement: element,
@@ -4288,10 +4539,7 @@ class App extends React.Component {
4288
4539
  width,
4289
4540
  height,
4290
4541
  });
4291
- this.scene.replaceAllElements([
4292
- ...this.scene.getElementsIncludingDeleted(),
4293
- element,
4294
- ]);
4542
+ this.scene.insertElement(element);
4295
4543
  return element;
4296
4544
  };
4297
4545
  //create rectangle element with youtube top left on nearest grid point width / hight 640/360
@@ -4326,10 +4574,7 @@ class App extends React.Component {
4326
4574
  height: embedLink.intrinsicSize.h,
4327
4575
  link,
4328
4576
  });
4329
- this.scene.replaceAllElements([
4330
- ...this.scene.getElementsIncludingDeleted(),
4331
- element,
4332
- ]);
4577
+ this.scene.insertElement(element);
4333
4578
  return element;
4334
4579
  };
4335
4580
  createImageElement = ({ sceneX, sceneY, addToFrameUnderCursor = true, }) => {
@@ -4437,8 +4682,8 @@ class App extends React.Component {
4437
4682
  mutateElement(element, {
4438
4683
  points: [...element.points, [0, 0]],
4439
4684
  });
4440
- const boundElement = getHoveredElementForBinding(pointerDownState.origin, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
4441
- this.scene.addNewElement(element);
4685
+ const boundElement = getHoveredElementForBinding(pointerDownState.origin, this);
4686
+ this.scene.insertElement(element);
4442
4687
  this.setState({
4443
4688
  draggingElement: element,
4444
4689
  editingElement: element,
@@ -4498,7 +4743,7 @@ class App extends React.Component {
4498
4743
  });
4499
4744
  }
4500
4745
  else {
4501
- this.scene.addNewElement(element);
4746
+ this.scene.insertElement(element);
4502
4747
  this.setState({
4503
4748
  multiElement: null,
4504
4749
  draggingElement: element,
@@ -4520,10 +4765,7 @@ class App extends React.Component {
4520
4765
  const frame = type === TOOL_TYPE.magicframe
4521
4766
  ? newMagicFrameElement(constructorOpts)
4522
4767
  : newFrameElement(constructorOpts);
4523
- this.scene.replaceAllElements([
4524
- ...this.scene.getElementsIncludingDeleted(),
4525
- frame,
4526
- ]);
4768
+ this.scene.insertElement(frame);
4527
4769
  this.setState({
4528
4770
  multiElement: null,
4529
4771
  draggingElement: frame,
@@ -4737,7 +4979,9 @@ class App extends React.Component {
4737
4979
  // able to select and interact with the text input
4738
4980
  !this.state.editingFrame &&
4739
4981
  dragSelectedElements(pointerDownState, selectedElements, dragOffset, this.state, this.scene, snapOffset, event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize);
4740
- this.maybeSuggestBindingForAll(selectedElements);
4982
+ this.setState({
4983
+ suggestedBindings: getSuggestedBindingsForArrows(selectedElements, this),
4984
+ });
4741
4985
  // We duplicate the selected element if alt is pressed on pointer move
4742
4986
  if (event.altKey && !pointerDownState.hit.hasBeenDuplicated) {
4743
4987
  // Move the currently selected elements to the top of the z index stack, and
@@ -4781,6 +5025,7 @@ class App extends React.Component {
4781
5025
  }
4782
5026
  }
4783
5027
  const nextSceneElements = [...nextElements, ...elementsToAppend];
5028
+ syncMovedIndices(nextSceneElements, arrayToMap(elementsToAppend));
4784
5029
  bindTextToShapeAfterDuplication(nextElements, elementsToAppend, oldIdToDuplicatedId);
4785
5030
  fixBindingsAfterDuplication(nextSceneElements, elementsToAppend, oldIdToDuplicatedId, "duplicatesServeAsOld");
4786
5031
  bindElementsToFramesAfterDuplication(nextSceneElements, elementsToAppend, oldIdToDuplicatedId);
@@ -4972,7 +5217,7 @@ class App extends React.Component {
4972
5217
  this.actionManager.executeAction(actionFinalize);
4973
5218
  }
4974
5219
  else {
4975
- const editingLinearElement = LinearElementEditor.handlePointerUp(childEvent, this.state.editingLinearElement, this.state, this.scene.getNonDeletedElements(), elementsMap);
5220
+ const editingLinearElement = LinearElementEditor.handlePointerUp(childEvent, this.state.editingLinearElement, this.state, this);
4976
5221
  if (editingLinearElement !== this.state.editingLinearElement) {
4977
5222
  this.setState({
4978
5223
  editingLinearElement,
@@ -4991,7 +5236,7 @@ class App extends React.Component {
4991
5236
  }
4992
5237
  }
4993
5238
  else {
4994
- const linearElementEditor = LinearElementEditor.handlePointerUp(childEvent, this.state.selectedLinearElement, this.state, this.scene.getNonDeletedElements(), elementsMap);
5239
+ const linearElementEditor = LinearElementEditor.handlePointerUp(childEvent, this.state.selectedLinearElement, this.state, this);
4995
5240
  const { startBindingElement, endBindingElement } = linearElementEditor;
4996
5241
  const element = this.scene.getElement(linearElementEditor.elementId);
4997
5242
  if (isBindingElement(element)) {
@@ -5060,7 +5305,7 @@ class App extends React.Component {
5060
5305
  }
5061
5306
  if (isLinearElement(draggingElement)) {
5062
5307
  if (draggingElement.points.length > 1) {
5063
- this.history.resumeRecording();
5308
+ this.store.shouldCaptureIncrement();
5064
5309
  }
5065
5310
  const pointerCoords = viewportCoordsToSceneCoords(childEvent, this.state);
5066
5311
  if (!pointerDownState.drag.hasOccurred &&
@@ -5083,7 +5328,7 @@ class App extends React.Component {
5083
5328
  else if (pointerDownState.drag.hasOccurred && !multiElement) {
5084
5329
  if (isBindingEnabled(this.state) &&
5085
5330
  isBindingElement(draggingElement, false)) {
5086
- maybeBindLinearElement(draggingElement, this.state, this.scene, pointerCoords, elementsMap);
5331
+ maybeBindLinearElement(draggingElement, this.state, pointerCoords, this);
5087
5332
  }
5088
5333
  this.setState({ suggestedBindings: [], startBoundElement: null });
5089
5334
  if (!activeTool.locked) {
@@ -5108,15 +5353,34 @@ class App extends React.Component {
5108
5353
  }
5109
5354
  return;
5110
5355
  }
5356
+ if (isTextElement(draggingElement)) {
5357
+ const minWidth = getMinTextElementWidth(getFontString({
5358
+ fontSize: draggingElement.fontSize,
5359
+ fontFamily: draggingElement.fontFamily,
5360
+ }), draggingElement.lineHeight);
5361
+ if (draggingElement.width < minWidth) {
5362
+ mutateElement(draggingElement, {
5363
+ autoResize: true,
5364
+ });
5365
+ }
5366
+ this.resetCursor();
5367
+ this.handleTextWysiwyg(draggingElement, {
5368
+ isExistingElement: true,
5369
+ });
5370
+ }
5111
5371
  if (activeTool.type !== "selection" &&
5112
5372
  draggingElement &&
5113
5373
  isInvisiblySmallElement(draggingElement)) {
5114
5374
  // 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,
5375
+ // update the store snapshot, so that invisible elements are not captured by the store
5376
+ this.updateScene({
5377
+ elements: this.scene
5378
+ .getElementsIncludingDeleted()
5379
+ .filter((el) => el.id !== draggingElement.id),
5380
+ appState: {
5381
+ draggingElement: null,
5382
+ },
5383
+ storeAction: StoreAction.UPDATE,
5120
5384
  });
5121
5385
  return;
5122
5386
  }
@@ -5139,7 +5403,7 @@ class App extends React.Component {
5139
5403
  groupIds: [],
5140
5404
  });
5141
5405
  removeElementsFromFrame([linearElement], this.scene.getNonDeletedElementsMap());
5142
- this.scene.informMutation();
5406
+ this.scene.triggerUpdate();
5143
5407
  }
5144
5408
  }
5145
5409
  }
@@ -5197,12 +5461,16 @@ class App extends React.Component {
5197
5461
  mutateElement(draggingElement, getNormalizedDimensions(draggingElement));
5198
5462
  }
5199
5463
  if (resizingElement) {
5200
- this.history.resumeRecording();
5464
+ this.store.shouldCaptureIncrement();
5201
5465
  }
5202
5466
  if (resizingElement && isInvisiblySmallElement(resizingElement)) {
5203
- this.scene.replaceAllElements(this.scene
5204
- .getElementsIncludingDeleted()
5205
- .filter((el) => el.id !== resizingElement.id));
5467
+ // update the store snapshot, so that invisible elements are not captured by the store
5468
+ this.updateScene({
5469
+ elements: this.scene
5470
+ .getElementsIncludingDeleted()
5471
+ .filter((el) => el.id !== resizingElement.id),
5472
+ storeAction: StoreAction.UPDATE,
5473
+ });
5206
5474
  }
5207
5475
  // handle frame membership for resizing frames and/or selected elements
5208
5476
  if (pointerDownState.resize.isResizing) {
@@ -5357,10 +5625,23 @@ class App extends React.Component {
5357
5625
  }));
5358
5626
  }
5359
5627
  }
5360
- if (!pointerDownState.drag.hasOccurred &&
5628
+ if (
5629
+ // not dragged
5630
+ !pointerDownState.drag.hasOccurred &&
5631
+ // not resized
5361
5632
  !this.state.isResizing &&
5633
+ // only hitting the bounding box of the previous hit element
5362
5634
  ((hitElement &&
5363
- isHittingElementBoundingBoxWithoutHittingElement(hitElement, this.state, this.frameNameBoundsCache, pointerDownState.origin.x, pointerDownState.origin.y, this.scene.getNonDeletedElementsMap())) ||
5635
+ hitElementBoundingBoxOnly({
5636
+ x: pointerDownState.origin.x,
5637
+ y: pointerDownState.origin.y,
5638
+ element: hitElement,
5639
+ shape: this.getElementShape(hitElement),
5640
+ threshold: this.getElementHitThreshold(),
5641
+ frameNameBound: isFrameLikeElement(hitElement)
5642
+ ? this.frameNameBoundsCache.get(hitElement)
5643
+ : null,
5644
+ }, elementsMap)) ||
5364
5645
  (!hitElement &&
5365
5646
  pointerDownState.hit.hasHitCommonBoundingBoxOfSelectedElements))) {
5366
5647
  if (this.state.editingLinearElement) {
@@ -5375,6 +5656,8 @@ class App extends React.Component {
5375
5656
  activeEmbeddable: null,
5376
5657
  });
5377
5658
  }
5659
+ // reset cursor
5660
+ setCursor(this.interactiveCanvas, CURSOR_TYPE.AUTO);
5378
5661
  return;
5379
5662
  }
5380
5663
  if (!activeTool.locked &&
@@ -5392,13 +5675,17 @@ class App extends React.Component {
5392
5675
  }));
5393
5676
  }
5394
5677
  if (activeTool.type !== "selection" ||
5395
- isSomeElementSelected(this.scene.getNonDeletedElements(), this.state)) {
5396
- this.history.resumeRecording();
5678
+ isSomeElementSelected(this.scene.getNonDeletedElements(), this.state) ||
5679
+ !isShallowEqual(this.state.previousSelectedElementIds, this.state.selectedElementIds)) {
5680
+ this.store.shouldCaptureIncrement();
5397
5681
  }
5398
5682
  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);
5683
+ // We only allow binding via linear elements, specifically via dragging
5684
+ // the endpoints ("start" or "end").
5685
+ const linearElements = this.scene
5686
+ .getSelectedElements(this.state)
5687
+ .filter(isLinearElement);
5688
+ bindOrUnbindLinearElements(linearElements, this, isBindingEnabled(this.state), this.state.selectedLinearElement?.selectedPointsIndices ?? []);
5402
5689
  }
5403
5690
  if (activeTool.type === "laser") {
5404
5691
  this.laserTrails.endPath();
@@ -5433,7 +5720,7 @@ class App extends React.Component {
5433
5720
  }
5434
5721
  restoreReadyToEraseElements = () => {
5435
5722
  this.elementsPendingErasure = new Set();
5436
- this.onSceneUpdated();
5723
+ this.triggerRender();
5437
5724
  };
5438
5725
  eraseElements = () => {
5439
5726
  let didChange = false;
@@ -5449,7 +5736,7 @@ class App extends React.Component {
5449
5736
  });
5450
5737
  this.elementsPendingErasure = new Set();
5451
5738
  if (didChange) {
5452
- this.history.resumeRecording();
5739
+ this.store.shouldCaptureIncrement();
5453
5740
  this.scene.replaceAllElements(elements);
5454
5741
  }
5455
5742
  };
@@ -5552,7 +5839,7 @@ class App extends React.Component {
5552
5839
  this.setState({ errorMessage: t("errors.imageToolNotSupported") });
5553
5840
  return;
5554
5841
  }
5555
- this.scene.addNewElement(imageElement);
5842
+ this.scene.insertElement(imageElement);
5556
5843
  try {
5557
5844
  return await this.initializeImage({
5558
5845
  imageFile,
@@ -5725,7 +6012,7 @@ class App extends React.Component {
5725
6012
  if (uncachedImageElements.length) {
5726
6013
  const { updatedFiles } = await this.updateImageCache(uncachedImageElements, files);
5727
6014
  if (updatedFiles.size) {
5728
- this.scene.informMutation();
6015
+ this.scene.triggerUpdate();
5729
6016
  }
5730
6017
  }
5731
6018
  };
@@ -5741,7 +6028,7 @@ class App extends React.Component {
5741
6028
  }
5742
6029
  };
5743
6030
  maybeSuggestBindingAtCursor = (pointerCoords) => {
5744
- const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
6031
+ const hoveredBindableElement = getHoveredElementForBinding(pointerCoords, this);
5745
6032
  this.setState({
5746
6033
  suggestedBindings: hoveredBindableElement != null ? [hoveredBindableElement] : [],
5747
6034
  });
@@ -5756,7 +6043,7 @@ class App extends React.Component {
5756
6043
  return;
5757
6044
  }
5758
6045
  const suggestedBindings = pointerCoords.reduce((acc, coords) => {
5759
- const hoveredBindableElement = getHoveredElementForBinding(coords, this.scene.getNonDeletedElements(), this.scene.getNonDeletedElementsMap());
6046
+ const hoveredBindableElement = getHoveredElementForBinding(coords, this);
5760
6047
  if (hoveredBindableElement != null &&
5761
6048
  !isLinearElementSimpleAndAlreadyBound(linearElement, oppositeBindingBoundElement?.id, hoveredBindableElement)) {
5762
6049
  acc.push(hoveredBindableElement);
@@ -5765,13 +6052,6 @@ class App extends React.Component {
5765
6052
  }, []);
5766
6053
  this.setState({ suggestedBindings });
5767
6054
  };
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
6055
  clearSelection(hitElement) {
5776
6056
  this.setState((prevState) => ({
5777
6057
  selectedElementIds: makeNextSelectedElementIds({}, prevState),
@@ -5831,7 +6111,7 @@ class App extends React.Component {
5831
6111
  isLoading: false,
5832
6112
  },
5833
6113
  replaceFiles: true,
5834
- commitToHistory: true,
6114
+ storeAction: StoreAction.CAPTURE,
5835
6115
  });
5836
6116
  return;
5837
6117
  }
@@ -5899,9 +6179,10 @@ class App extends React.Component {
5899
6179
  loadFileToCanvas = async (file, fileHandle) => {
5900
6180
  file = await normalizeFile(file);
5901
6181
  try {
6182
+ const elements = this.scene.getElementsIncludingDeleted();
5902
6183
  let ret;
5903
6184
  try {
5904
- ret = await loadSceneOrLibraryFromBlob(file, this.state, this.scene.getElementsIncludingDeleted(), fileHandle);
6185
+ ret = await loadSceneOrLibraryFromBlob(file, this.state, elements, fileHandle);
5905
6186
  }
5906
6187
  catch (error) {
5907
6188
  const imageSceneDataError = error instanceof ImageSceneDataError;
@@ -5926,6 +6207,10 @@ class App extends React.Component {
5926
6207
  return;
5927
6208
  }
5928
6209
  if (ret.type === MIME_TYPES.excalidraw) {
6210
+ // restore the fractional indices by mutating elements
6211
+ syncInvalidIndices(elements.concat(ret.data.elements));
6212
+ // update the store snapshot for old elements, otherwise we would end up with duplicated fractional indices on undo
6213
+ this.store.updateSnapshot(arrayToMap(elements), this.state);
5929
6214
  this.setState({ isLoading: true });
5930
6215
  this.syncActionResult({
5931
6216
  ...ret.data,
@@ -5934,7 +6219,7 @@ class App extends React.Component {
5934
6219
  isLoading: false,
5935
6220
  },
5936
6221
  replaceFiles: true,
5937
- commitToHistory: true,
6222
+ storeAction: StoreAction.CAPTURE,
5938
6223
  });
5939
6224
  }
5940
6225
  else if (ret.type === MIME_TYPES.excalidrawlib) {
@@ -6006,7 +6291,7 @@ class App extends React.Component {
6006
6291
  }
6007
6292
  if (draggingElement.type === "selection" &&
6008
6293
  this.state.activeTool.type !== "eraser") {
6009
- dragNewElement(draggingElement, this.state.activeTool.type, pointerDownState.origin.x, pointerDownState.origin.y, pointerCoords.x, pointerCoords.y, distance(pointerDownState.origin.x, pointerCoords.x), distance(pointerDownState.origin.y, pointerCoords.y), shouldMaintainAspectRatio(event), shouldResizeFromCenter(event));
6294
+ dragNewElement(draggingElement, this.state.activeTool.type, pointerDownState.origin.x, pointerDownState.origin.y, pointerCoords.x, pointerCoords.y, distance(pointerDownState.origin.x, pointerCoords.x), distance(pointerDownState.origin.y, pointerCoords.y), shouldMaintainAspectRatio(event), shouldResizeFromCenter(event), this.state.zoom.value);
6010
6295
  }
6011
6296
  else {
6012
6297
  let [gridX, gridY] = getGridPoint(pointerCoords.x, pointerCoords.y, event[KEYS.CTRL_OR_CMD] ? null : this.state.gridSize);
@@ -6032,8 +6317,7 @@ class App extends React.Component {
6032
6317
  });
6033
6318
  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
6319
  ? !shouldMaintainAspectRatio(event)
6035
- : shouldMaintainAspectRatio(event), shouldResizeFromCenter(event), aspectRatio, this.state.originSnapOffset);
6036
- this.maybeSuggestBindingForAll([draggingElement]);
6320
+ : shouldMaintainAspectRatio(event), shouldResizeFromCenter(event), this.state.zoom.value, aspectRatio, this.state.originSnapOffset);
6037
6321
  // highlight elements that are to be added to frames on frames creation
6038
6322
  if (this.state.activeTool.type === TOOL_TYPE.frame ||
6039
6323
  this.state.activeTool.type === TOOL_TYPE.magicframe) {
@@ -6087,16 +6371,17 @@ class App extends React.Component {
6087
6371
  snapLines,
6088
6372
  });
6089
6373
  }
6090
- if (transformElements(pointerDownState.originalElements, transformHandleType, selectedElements, this.scene.getElementsMapIncludingDeleted(), shouldRotateWithDiscreteAngle(event), shouldResizeFromCenter(event), selectedElements.length === 1 && isImageElement(selectedElements[0])
6374
+ if (transformElements(pointerDownState.originalElements, transformHandleType, selectedElements, this.scene.getElementsMapIncludingDeleted(), shouldRotateWithDiscreteAngle(event), shouldResizeFromCenter(event), selectedElements.some((element) => isImageElement(element))
6091
6375
  ? !shouldMaintainAspectRatio(event)
6092
6376
  : shouldMaintainAspectRatio(event), resizeX, resizeY, pointerDownState.resize.center.x, pointerDownState.resize.center.y)) {
6093
- this.maybeSuggestBindingForAll(selectedElements);
6377
+ const suggestedBindings = getSuggestedBindingsForArrows(selectedElements, this);
6094
6378
  const elementsToHighlight = new Set();
6095
6379
  selectedFrames.forEach((frame) => {
6096
6380
  getElementsInResizingFrame(this.scene.getNonDeletedElements(), frame, this.state, this.scene.getNonDeletedElementsMap()).forEach((element) => elementsToHighlight.add(element));
6097
6381
  });
6098
6382
  this.setState({
6099
6383
  elementsToHighlight: [...elementsToHighlight],
6384
+ suggestedBindings,
6100
6385
  });
6101
6386
  return true;
6102
6387
  }
@@ -6141,6 +6426,7 @@ class App extends React.Component {
6141
6426
  return [actionCopy, ...options];
6142
6427
  }
6143
6428
  return [
6429
+ CONTEXT_MENU_SEPARATOR,
6144
6430
  actionCut,
6145
6431
  actionCopy,
6146
6432
  actionPaste,
@@ -6153,6 +6439,7 @@ class App extends React.Component {
6153
6439
  actionPasteStyles,
6154
6440
  CONTEXT_MENU_SEPARATOR,
6155
6441
  actionGroup,
6442
+ actionTextAutoResize,
6156
6443
  actionUnbindText,
6157
6444
  actionBindText,
6158
6445
  actionWrapTextInContainer,
@@ -6321,7 +6608,7 @@ export const createTestHook = () => {
6321
6608
  return this.app?.scene.getElementsIncludingDeleted();
6322
6609
  },
6323
6610
  set(elements) {
6324
- return this.app?.scene.replaceAllElements(elements);
6611
+ return this.app?.scene.replaceAllElements(syncInvalidIndices(elements));
6325
6612
  },
6326
6613
  },
6327
6614
  });