@tldraw/editor 5.4.0-next.ebc95cf01337 → 5.4.0

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 (233) hide show
  1. package/dist-cjs/index.d.ts +67 -1
  2. package/dist-cjs/index.js +4 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +7 -2
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/MenuClickCapture.js +6 -8
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/CanvasOverlays.js +1 -3
  9. package/dist-cjs/lib/components/default-components/CanvasOverlays.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +1 -1
  12. package/dist-cjs/lib/config/TLUserPreferences.js +6 -2
  13. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  14. package/dist-cjs/lib/config/createTLStore.js +1 -1
  15. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  16. package/dist-cjs/lib/editor/Editor.js +164 -280
  17. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  18. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +3 -3
  19. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  20. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +8 -8
  21. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
  22. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +2 -1
  23. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  24. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +79 -6
  25. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
  26. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +7 -0
  27. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
  28. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +0 -2
  29. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
  30. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +1 -2
  31. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  32. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +6 -3
  33. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  34. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +12 -1
  35. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  36. package/dist-cjs/lib/editor/overlays/OverlayManager.js +3 -7
  37. package/dist-cjs/lib/editor/overlays/OverlayManager.js.map +2 -2
  38. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +0 -1
  39. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +2 -2
  40. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +3 -1
  41. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +2 -2
  42. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +1 -0
  43. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  44. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +1 -1
  45. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  46. package/dist-cjs/lib/editor/tools/StateNode.js +3 -2
  47. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  48. package/dist-cjs/lib/globals/editors.js +57 -0
  49. package/dist-cjs/lib/globals/editors.js.map +7 -0
  50. package/dist-cjs/lib/globals/menus.js +9 -4
  51. package/dist-cjs/lib/globals/menus.js.map +2 -2
  52. package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -0
  53. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  54. package/dist-cjs/lib/hooks/useDocumentEvents.js +39 -9
  55. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  56. package/dist-cjs/lib/hooks/useLocalStore.js +10 -6
  57. package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
  58. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +1 -0
  59. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  60. package/dist-cjs/lib/hooks/usePeerIds.js +2 -1
  61. package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
  62. package/dist-cjs/lib/license/LicenseManager.js +9 -2
  63. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  64. package/dist-cjs/lib/license/LicenseProvider.js +16 -3
  65. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  66. package/dist-cjs/lib/license/useLicenseManagerState.js +5 -4
  67. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  68. package/dist-cjs/lib/primitives/Box.js +2 -1
  69. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  70. package/dist-cjs/lib/primitives/Vec.js +4 -3
  71. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  72. package/dist-cjs/lib/primitives/utils.js +2 -1
  73. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  74. package/dist-cjs/lib/utils/deepLinks.js +5 -1
  75. package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
  76. package/dist-cjs/lib/utils/rotation.js +1 -9
  77. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  78. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +17 -0
  79. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  80. package/dist-cjs/lib/utils/sync/alerts.js +1 -1
  81. package/dist-cjs/lib/utils/sync/alerts.js.map +1 -1
  82. package/dist-cjs/version.js +3 -3
  83. package/dist-cjs/version.js.map +1 -1
  84. package/dist-esm/index.d.mts +67 -1
  85. package/dist-esm/index.mjs +9 -2
  86. package/dist-esm/index.mjs.map +2 -2
  87. package/dist-esm/lib/TldrawEditor.mjs +9 -4
  88. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  89. package/dist-esm/lib/components/MenuClickCapture.mjs +6 -8
  90. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  91. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs +1 -3
  92. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs.map +2 -2
  93. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  94. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +1 -1
  95. package/dist-esm/lib/config/TLUserPreferences.mjs +6 -2
  96. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  97. package/dist-esm/lib/config/createTLStore.mjs +1 -1
  98. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  99. package/dist-esm/lib/editor/Editor.mjs +172 -281
  100. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  101. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +3 -3
  102. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  103. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +9 -9
  104. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
  105. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +2 -1
  106. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  107. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +81 -8
  108. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
  109. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +7 -0
  110. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
  111. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +0 -2
  112. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
  113. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -3
  114. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  115. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +6 -3
  116. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  117. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +12 -1
  118. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  119. package/dist-esm/lib/editor/overlays/OverlayManager.mjs +3 -7
  120. package/dist-esm/lib/editor/overlays/OverlayManager.mjs.map +2 -2
  121. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +0 -1
  122. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +2 -2
  123. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +3 -1
  124. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +2 -2
  125. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +1 -0
  126. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  127. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +1 -1
  128. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  129. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -2
  130. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  131. package/dist-esm/lib/globals/editors.mjs +37 -0
  132. package/dist-esm/lib/globals/editors.mjs.map +7 -0
  133. package/dist-esm/lib/globals/menus.mjs +9 -4
  134. package/dist-esm/lib/globals/menus.mjs.map +2 -2
  135. package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -0
  136. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  137. package/dist-esm/lib/hooks/useDocumentEvents.mjs +40 -10
  138. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  139. package/dist-esm/lib/hooks/useLocalStore.mjs +11 -7
  140. package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
  141. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +1 -0
  142. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  143. package/dist-esm/lib/hooks/usePeerIds.mjs +2 -1
  144. package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
  145. package/dist-esm/lib/license/LicenseManager.mjs +9 -2
  146. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  147. package/dist-esm/lib/license/LicenseProvider.mjs +16 -3
  148. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  149. package/dist-esm/lib/license/useLicenseManagerState.mjs +5 -4
  150. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  151. package/dist-esm/lib/primitives/Box.mjs +2 -1
  152. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  153. package/dist-esm/lib/primitives/Vec.mjs +4 -3
  154. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  155. package/dist-esm/lib/primitives/utils.mjs +2 -1
  156. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  157. package/dist-esm/lib/utils/deepLinks.mjs +5 -1
  158. package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
  159. package/dist-esm/lib/utils/rotation.mjs +1 -9
  160. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  161. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +17 -0
  162. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  163. package/dist-esm/lib/utils/sync/alerts.mjs +1 -1
  164. package/dist-esm/lib/utils/sync/alerts.mjs.map +1 -1
  165. package/dist-esm/version.mjs +3 -3
  166. package/dist-esm/version.mjs.map +1 -1
  167. package/package.json +13 -7
  168. package/src/index.ts +6 -1
  169. package/src/lib/TldrawEditor.tsx +16 -4
  170. package/src/lib/components/MenuClickCapture.tsx +19 -18
  171. package/src/lib/components/default-components/CanvasOverlays.tsx +1 -3
  172. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  173. package/src/lib/config/TLUserPreferences.test.ts +18 -1
  174. package/src/lib/config/TLUserPreferences.ts +8 -4
  175. package/src/lib/config/createTLStore.ts +1 -1
  176. package/src/lib/editor/Editor.ts +241 -359
  177. package/src/lib/editor/managers/ClickManager/ClickManager.ts +5 -3
  178. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +9 -9
  179. package/src/lib/editor/managers/FontManager/FontManager.ts +2 -1
  180. package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +199 -3
  181. package/src/lib/editor/managers/InputsManager/InputsManager.ts +128 -9
  182. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +12 -1
  183. package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +0 -2
  184. package/src/lib/editor/managers/TextManager/TextManager.ts +2 -6
  185. package/src/lib/editor/managers/TickManager/TickManager.test.ts +13 -0
  186. package/src/lib/editor/managers/TickManager/TickManager.ts +9 -3
  187. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +64 -3
  188. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +18 -1
  189. package/src/lib/editor/overlays/OverlayManager.ts +3 -7
  190. package/src/lib/editor/overlays/strokeShapeIndicators.ts +0 -1
  191. package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +5 -1
  192. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +3 -0
  193. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -1
  194. package/src/lib/editor/tools/StateNode.test.ts +64 -0
  195. package/src/lib/editor/tools/StateNode.ts +12 -2
  196. package/src/lib/globals/editors.test.ts +76 -0
  197. package/src/lib/globals/editors.ts +53 -0
  198. package/src/lib/globals/menus.test.ts +33 -0
  199. package/src/lib/globals/menus.ts +11 -4
  200. package/src/lib/hooks/useCanvasEvents.ts +2 -0
  201. package/src/lib/hooks/useDocumentEvents.ts +60 -16
  202. package/src/lib/hooks/useLocalStore.ts +16 -8
  203. package/src/lib/hooks/usePassThroughWheelEvents.ts +3 -0
  204. package/src/lib/hooks/usePeerIds.ts +2 -1
  205. package/src/lib/license/LicenseManager.test.ts +160 -0
  206. package/src/lib/license/LicenseManager.ts +13 -4
  207. package/src/lib/license/LicenseProvider.tsx +24 -3
  208. package/src/lib/license/useLicenseManagerState.ts +8 -6
  209. package/src/lib/primitives/Box.test.ts +9 -1
  210. package/src/lib/primitives/Box.ts +3 -1
  211. package/src/lib/primitives/Vec.test.ts +10 -0
  212. package/src/lib/primitives/Vec.ts +4 -4
  213. package/src/lib/primitives/geometry/Arc2d.test.ts +226 -3
  214. package/src/lib/primitives/geometry/CubicBezier2d.test.ts +173 -3
  215. package/src/lib/primitives/geometry/CubicSpline2d.test.ts +160 -3
  216. package/src/lib/primitives/geometry/Ellipse2d.test.ts +243 -3
  217. package/src/lib/primitives/geometry/Geometry2d.test.ts +553 -1
  218. package/src/lib/primitives/geometry/Group2d.test.ts +398 -3
  219. package/src/lib/primitives/geometry/Polyline2d.test.ts +207 -0
  220. package/src/lib/primitives/geometry/Stadium2d.test.ts +191 -3
  221. package/src/lib/primitives/utils.test.ts +10 -1
  222. package/src/lib/primitives/utils.ts +4 -1
  223. package/src/lib/test/useCanvasEvents.test.tsx +74 -0
  224. package/src/lib/test/useDocumentEvents.test.tsx +107 -0
  225. package/src/lib/test/usePassThroughWheelEvents.test.tsx +25 -2
  226. package/src/lib/utils/deepLinks.test.ts +15 -0
  227. package/src/lib/utils/deepLinks.ts +7 -1
  228. package/src/lib/utils/rotation.ts +4 -12
  229. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +56 -0
  230. package/src/lib/utils/sync/TLLocalSyncClient.ts +24 -0
  231. package/src/lib/utils/sync/alerts.ts +1 -1
  232. package/src/version.ts +3 -3
  233. package/src/lib/primitives/geometry/Polyline.test.ts +0 -5
@@ -39,6 +39,7 @@ import {
39
39
  Result,
40
40
  ZERO_INDEX_KEY,
41
41
  annotateError,
42
+ areArraysShallowEqual,
42
43
  assert,
43
44
  assertExists,
44
45
  bind,
@@ -83,6 +84,7 @@ import {
83
84
  import { getOwnerWindow } from "../exports/domUtils.mjs";
84
85
  import { exportToSvg } from "../exports/exportToSvg.mjs";
85
86
  import { getSvgAsImageWithOptions, trimSvgToContent } from "../exports/getSvgAsImage.mjs";
87
+ import { registerMountedEditor, unregisterMountedEditor } from "../globals/editors.mjs";
86
88
  import { tlmenus } from "../globals/menus.mjs";
87
89
  import { tltime } from "../globals/time.mjs";
88
90
  import { defaultTldrawOptions } from "../options.mjs";
@@ -91,7 +93,13 @@ import { EASINGS } from "../primitives/easings.mjs";
91
93
  import { Group2d } from "../primitives/geometry/Group2d.mjs";
92
94
  import { intersectPolygonPolygon } from "../primitives/intersect.mjs";
93
95
  import { Mat } from "../primitives/Mat.mjs";
94
- import { PI, approximately, areAnglesCompatible, clamp, pointInPolygon } from "../primitives/utils.mjs";
96
+ import {
97
+ HALF_PI,
98
+ approximately,
99
+ areAnglesCompatible,
100
+ clamp,
101
+ pointInPolygon
102
+ } from "../primitives/utils.mjs";
95
103
  import { Vec } from "../primitives/Vec.mjs";
96
104
  import { areShapesContentEqual } from "../utils/areShapesContentEqual.mjs";
97
105
  import { dataUrlToFile } from "../utils/assets.mjs";
@@ -289,6 +297,10 @@ class Editor extends EventEmitter {
289
297
  if (!nextPageState) nextPageState = { ...prevPageState };
290
298
  nextPageState.editingShapeId = null;
291
299
  }
300
+ if (prevPageState.croppingShapeId && shapesNoLongerInPage.has(prevPageState.croppingShapeId)) {
301
+ if (!nextPageState) nextPageState = { ...prevPageState };
302
+ nextPageState.croppingShapeId = null;
303
+ }
292
304
  const hintingShapeIds = prevPageState.hintingShapeIds.filter(
293
305
  (id) => !shapesNoLongerInPage.has(id)
294
306
  );
@@ -531,10 +543,11 @@ class Editor extends EventEmitter {
531
543
  this.cancelDoubleClick();
532
544
  }
533
545
  if (prev?.selectedShapeIds !== next?.selectedShapeIds) {
546
+ const selectedShapeIds = new Set(next.selectedShapeIds);
534
547
  const filtered = next.selectedShapeIds.filter((id) => {
535
548
  let parentId = this.getShape(id)?.parentId;
536
549
  while (isShapeId(parentId)) {
537
- if (next.selectedShapeIds.includes(parentId)) {
550
+ if (selectedShapeIds.has(parentId)) {
538
551
  return false;
539
552
  }
540
553
  parentId = this.getShape(parentId)?.parentId;
@@ -604,9 +617,11 @@ class Editor extends EventEmitter {
604
617
  this.on("tick", this._flushEventsForTick);
605
618
  this.on("mount", () => {
606
619
  this._isMounted.set(true);
620
+ registerMountedEditor(this);
607
621
  });
608
622
  this.on("unmount", () => {
609
623
  this._isMounted.set(false);
624
+ unregisterMountedEditor(this);
610
625
  });
611
626
  this.timers.requestAnimationFrame(() => {
612
627
  this._tickManager.start();
@@ -616,7 +631,10 @@ class Editor extends EventEmitter {
616
631
  const mode = this.store.props.collaboration.mode;
617
632
  this.disposables.add(
618
633
  react("update collaboration mode", () => {
619
- this.store.put([{ ...this.getInstanceState(), isReadonly: mode.get() === "readonly" }]);
634
+ const isReadonly = mode.get() === "readonly";
635
+ unsafe__withoutCapture(
636
+ () => this._updateInstanceState({ isReadonly }, { history: "ignore" })
637
+ );
620
638
  })
621
639
  );
622
640
  }
@@ -646,6 +664,14 @@ class Editor extends EventEmitter {
646
664
  );
647
665
  }
648
666
  options;
667
+ /**
668
+ * The license manager whose feature flags apply to this editor. Assigned by `<TldrawEditor />`
669
+ * when it creates the editor, so that UI rendered outside the editor tree can resolve the
670
+ * editor's license through the editor instance. Undefined for editors created directly.
671
+ *
672
+ * @internal
673
+ */
674
+ licenseManager;
649
675
  contextId = uniqueId();
650
676
  /**
651
677
  * The editor's store
@@ -840,6 +866,7 @@ class Editor extends EventEmitter {
840
866
  if (this._isMounted.get()) {
841
867
  this.emit("unmount");
842
868
  }
869
+ unregisterMountedEditor(this);
843
870
  this.stopCameraAnimation();
844
871
  if (this.getInstanceState().followingUserId) {
845
872
  this.stopFollowingUser();
@@ -965,7 +992,7 @@ class Editor extends EventEmitter {
965
992
  getShapeUtil(arg) {
966
993
  const type = typeof arg === "string" ? arg : arg.type;
967
994
  const shapeUtil = getOwnProperty(this.shapeUtils, type);
968
- assert(shapeUtil, `No shape util found for type "${type}"`);
995
+ if (!shapeUtil) throw new Error(`No shape util found for type "${type}"`);
969
996
  return shapeUtil;
970
997
  }
971
998
  hasShapeUtil(arg) {
@@ -1754,7 +1781,9 @@ class Editor extends EventEmitter {
1754
1781
  const selectedShapes = this.getSelectedShapes();
1755
1782
  if (!selectedShapes.length) return;
1756
1783
  const selectedShape = selectedShapes[0];
1757
- const children = this.getSortedChildIdsForParent(selectedShape.id).map((id) => this.getShape(id)).filter((i) => i);
1784
+ const children = compact(
1785
+ this.getSortedChildIdsForParent(selectedShape.id).map((id) => this.getShape(id))
1786
+ );
1758
1787
  const sortedChildren = this._getShapesInReadingOrder(children);
1759
1788
  if (sortedChildren.length === 0) return;
1760
1789
  this._selectShapesAndZoom([sortedChildren[0].id]);
@@ -1821,18 +1850,13 @@ class Editor extends EventEmitter {
1821
1850
  * @internal
1822
1851
  */
1823
1852
  getShapesSharedRotation(shapeIds) {
1824
- let foundFirst = false;
1825
1853
  let rotation = 0;
1826
1854
  for (let i = 0, n = shapeIds.length; i < n; i++) {
1827
- const pageTransform = this.getShapePageTransform(shapeIds[i]);
1828
- if (!pageTransform) continue;
1829
- if (foundFirst) {
1830
- if (pageTransform.rotation() !== rotation) {
1831
- return 0;
1832
- }
1833
- } else {
1834
- foundFirst = true;
1835
- rotation = pageTransform.rotation();
1855
+ const pageRotation = this.getShapePageTransform(shapeIds[i]).rotation();
1856
+ if (i === 0) {
1857
+ rotation = pageRotation;
1858
+ } else if (pageRotation !== rotation) {
1859
+ return 0;
1836
1860
  }
1837
1861
  }
1838
1862
  return rotation;
@@ -1858,11 +1882,9 @@ class Editor extends EventEmitter {
1858
1882
  return bounds;
1859
1883
  }
1860
1884
  const boxFromRotatedVertices = Box.FromPoints(
1861
- shapeIds.flatMap((id) => {
1862
- const pageTransform = this.getShapePageTransform(id);
1863
- if (!pageTransform) return [];
1864
- return pageTransform.applyToPoints(this.getShapeGeometry(id).bounds.corners);
1865
- }).map((p) => p.rot(-selectionRotation))
1885
+ shapeIds.flatMap(
1886
+ (id) => this.getShapePageTransform(id).applyToPoints(this.getShapeGeometry(id).bounds.corners)
1887
+ ).map((p) => p.rot(-selectionRotation))
1866
1888
  );
1867
1889
  boxFromRotatedVertices.point = boxFromRotatedVertices.point.rot(selectionRotation);
1868
1890
  return boxFromRotatedVertices;
@@ -1873,9 +1895,14 @@ class Editor extends EventEmitter {
1873
1895
  getSelectionRotatedScreenBounds() {
1874
1896
  const bounds = this.getSelectionRotatedPageBounds();
1875
1897
  if (!bounds) return void 0;
1876
- const { x, y } = this.pageToScreen(bounds.point);
1877
- const zoom = this.getZoomLevel();
1878
- return new Box(x, y, bounds.width * zoom, bounds.height * zoom);
1898
+ const screenBounds = this.getViewportScreenBounds();
1899
+ const { x: cx, y: cy, z: zoom } = this.getCamera();
1900
+ return new Box(
1901
+ (bounds.x + cx) * zoom + screenBounds.x,
1902
+ (bounds.y + cy) * zoom + screenBounds.y,
1903
+ bounds.width * zoom,
1904
+ bounds.height * zoom
1905
+ );
1879
1906
  }
1880
1907
  getFocusedGroupId() {
1881
1908
  return this.getCurrentPageState().focusedGroupId ?? this.getCurrentPageId();
@@ -1976,42 +2003,28 @@ class Editor extends EventEmitter {
1976
2003
  */
1977
2004
  setEditingShape(shape) {
1978
2005
  const id = typeof shape === "string" ? shape : shape?.id ?? null;
1979
- if (!id) {
2006
+ if (id && !this.canEditShape(id)) return this;
2007
+ this.run(() => {
2008
+ const prevEditingShapeId = this.getEditingShapeId();
2009
+ if (prevEditingShapeId) {
2010
+ const prevEditingShape = this.getShape(prevEditingShapeId);
2011
+ if (prevEditingShape) {
2012
+ this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape);
2013
+ }
2014
+ }
1980
2015
  this.run(
1981
2016
  () => {
1982
- const prevEditingShapeId = this.getEditingShapeId();
1983
- if (prevEditingShapeId) {
1984
- const prevEditingShape = this.getShape(prevEditingShapeId);
1985
- if (prevEditingShape) {
1986
- this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape);
1987
- }
1988
- }
1989
2017
  this._updateCurrentPageState({ editingShapeId: null });
1990
2018
  this._currentRichTextEditor.set(null);
2019
+ if (!id) return;
2020
+ this.select(id);
2021
+ this._updateCurrentPageState({ editingShapeId: id });
2022
+ const nextEditingShape = this.getShape(id);
2023
+ this.getShapeUtil(nextEditingShape).onEditStart?.(nextEditingShape);
1991
2024
  },
1992
2025
  { history: "ignore" }
1993
2026
  );
1994
- return this;
1995
- }
1996
- if (!this.canEditShape(id)) return this;
1997
- this.run(
1998
- () => {
1999
- const prevEditingShapeId = this.getEditingShapeId();
2000
- if (prevEditingShapeId) {
2001
- const prevEditingShape = this.getShape(prevEditingShapeId);
2002
- if (prevEditingShape) {
2003
- this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape);
2004
- }
2005
- }
2006
- this._updateCurrentPageState({ editingShapeId: null });
2007
- this._currentRichTextEditor.set(null);
2008
- this.select(id);
2009
- this._updateCurrentPageState({ editingShapeId: id });
2010
- const nextEditingShape = this.getShape(id);
2011
- this.getShapeUtil(nextEditingShape).onEditStart?.(nextEditingShape);
2012
- },
2013
- { history: "ignore" }
2014
- );
2027
+ });
2015
2028
  return this;
2016
2029
  }
2017
2030
  // Rich text editor
@@ -2117,19 +2130,12 @@ class Editor extends EventEmitter {
2117
2130
  * @public
2118
2131
  */
2119
2132
  setErasingShapes(shapes) {
2120
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((shape) => shape.id);
2133
+ const ids = typeof shapes[0] === "string" ? shapes.slice() : shapes.map((shape) => shape.id);
2121
2134
  ids.sort();
2122
2135
  const erasingShapeIds = this.getErasingShapeIds();
2123
2136
  this.run(
2124
2137
  () => {
2125
- if (ids.length === erasingShapeIds.length) {
2126
- for (let i = 0; i < ids.length; i++) {
2127
- if (ids[i] !== erasingShapeIds[i]) {
2128
- this._updateCurrentPageState({ erasingShapeIds: ids });
2129
- break;
2130
- }
2131
- }
2132
- } else {
2138
+ if (!areArraysShallowEqual(ids, erasingShapeIds)) {
2133
2139
  this._updateCurrentPageState({ erasingShapeIds: ids });
2134
2140
  }
2135
2141
  },
@@ -2162,6 +2168,7 @@ class Editor extends EventEmitter {
2162
2168
  if (!_shape) return false;
2163
2169
  const util = this.getShapeUtil(_shape);
2164
2170
  if (!util.canCrop(_shape)) return false;
2171
+ if (this.getIsReadonly()) return false;
2165
2172
  if (this.isShapeOrAncestorLocked(_shape)) return false;
2166
2173
  return true;
2167
2174
  }
@@ -2226,11 +2233,11 @@ class Editor extends EventEmitter {
2226
2233
  const collaborators = this.getCollaborators();
2227
2234
  let leaderPresence = null;
2228
2235
  while (targetUserId && !visited.includes(targetUserId)) {
2229
- leaderPresence = collaborators.find((c) => c.userId === targetUserId) ?? null;
2230
- targetUserId = leaderPresence?.followingUserId ?? null;
2231
- if (leaderPresence) {
2232
- visited.push(leaderPresence.userId);
2233
- }
2236
+ const nextPresence = collaborators.find((c) => c.userId === targetUserId);
2237
+ if (!nextPresence) break;
2238
+ leaderPresence = nextPresence;
2239
+ targetUserId = nextPresence.followingUserId ?? null;
2240
+ visited.push(nextPresence.userId);
2234
2241
  }
2235
2242
  return leaderPresence;
2236
2243
  }
@@ -2293,39 +2300,7 @@ class Editor extends EventEmitter {
2293
2300
  *
2294
2301
  * @public */
2295
2302
  getInitialZoom() {
2296
- const cameraOptions = this.getCameraOptions();
2297
- if (!cameraOptions.constraints) return 1;
2298
- if (cameraOptions.constraints.initialZoom === "default") return 1;
2299
- const { zx, zy } = getCameraFitXFitY(this, cameraOptions);
2300
- switch (cameraOptions.constraints.initialZoom) {
2301
- case "fit-min": {
2302
- return Math.max(zx, zy);
2303
- }
2304
- case "fit-max": {
2305
- return Math.min(zx, zy);
2306
- }
2307
- case "fit-x": {
2308
- return zx;
2309
- }
2310
- case "fit-y": {
2311
- return zy;
2312
- }
2313
- case "fit-min-100": {
2314
- return Math.min(1, Math.max(zx, zy));
2315
- }
2316
- case "fit-max-100": {
2317
- return Math.min(1, Math.min(zx, zy));
2318
- }
2319
- case "fit-x-100": {
2320
- return Math.min(1, zx);
2321
- }
2322
- case "fit-y-100": {
2323
- return Math.min(1, zy);
2324
- }
2325
- default: {
2326
- throw exhaustiveSwitchError(cameraOptions.constraints.initialZoom);
2327
- }
2328
- }
2303
+ return this._getFitZoom(this.getCameraOptions().constraints?.initialZoom ?? "default");
2329
2304
  }
2330
2305
  /**
2331
2306
  * Get the camera's base level for calculating actual zoom levels based on the zoom steps.
@@ -2337,11 +2312,13 @@ class Editor extends EventEmitter {
2337
2312
  *
2338
2313
  * @public */
2339
2314
  getBaseZoom() {
2315
+ return this._getFitZoom(this.getCameraOptions().constraints?.baseZoom ?? "default");
2316
+ }
2317
+ _getFitZoom(fit) {
2340
2318
  const cameraOptions = this.getCameraOptions();
2341
- if (!cameraOptions.constraints) return 1;
2342
- if (cameraOptions.constraints.baseZoom === "default") return 1;
2319
+ if (!cameraOptions.constraints || fit === "default") return 1;
2343
2320
  const { zx, zy } = getCameraFitXFitY(this, cameraOptions);
2344
- switch (cameraOptions.constraints.baseZoom) {
2321
+ switch (fit) {
2345
2322
  case "fit-min": {
2346
2323
  return Math.max(zx, zy);
2347
2324
  }
@@ -2367,7 +2344,7 @@ class Editor extends EventEmitter {
2367
2344
  return Math.min(1, zy);
2368
2345
  }
2369
2346
  default: {
2370
- throw exhaustiveSwitchError(cameraOptions.constraints.baseZoom);
2347
+ throw exhaustiveSwitchError(fit);
2371
2348
  }
2372
2349
  }
2373
2350
  }
@@ -2401,7 +2378,7 @@ class Editor extends EventEmitter {
2401
2378
  ...this._cameraOptions.__unsafe__getWithoutCapture(),
2402
2379
  ...opts
2403
2380
  });
2404
- if (next.zoomSteps?.length < 1) next.zoomSteps = [1];
2381
+ if (!next.zoomSteps || next.zoomSteps.length < 1) next.zoomSteps = [1];
2405
2382
  this._cameraOptions.set(next);
2406
2383
  this.setCamera(this.getCamera());
2407
2384
  return this;
@@ -2422,8 +2399,8 @@ class Editor extends EventEmitter {
2422
2399
  const vsb = this.getViewportScreenBounds();
2423
2400
  if (cameraOptions.constraints) {
2424
2401
  const { constraints } = cameraOptions;
2425
- const py = Math.min(constraints.padding.y, vsb.w / 2);
2426
- const px = Math.min(constraints.padding.x, vsb.h / 2);
2402
+ const px = Math.min(constraints.padding.x, vsb.w / 2);
2403
+ const py = Math.min(constraints.padding.y, vsb.h / 2);
2427
2404
  const bounds = Box.From(cameraOptions.constraints.bounds);
2428
2405
  const zx = (vsb.w - px * 2) / bounds.w;
2429
2406
  const zy = (vsb.h - py * 2) / bounds.h;
@@ -2462,12 +2439,12 @@ class Editor extends EventEmitter {
2462
2439
  break;
2463
2440
  }
2464
2441
  case "inside": {
2465
- if (z < zx) x = clamp(x, minX, (vsb.w - px) / z - bounds.w);
2442
+ if (z < zx) x = clamp(x, minX, (vsb.w - px) / z - bounds.w - bounds.x);
2466
2443
  else x = clamp(x, minX + freeW, minX);
2467
2444
  break;
2468
2445
  }
2469
2446
  case "outside": {
2470
- x = clamp(x, px / z - bounds.w, (vsb.w - px) / z);
2447
+ x = clamp(x, px / z - bounds.w - bounds.x, (vsb.w - px) / z - bounds.x);
2471
2448
  break;
2472
2449
  }
2473
2450
  case "free": {
@@ -2488,12 +2465,12 @@ class Editor extends EventEmitter {
2488
2465
  break;
2489
2466
  }
2490
2467
  case "inside": {
2491
- if (z < zy) y = clamp(y, minY, (vsb.h - py) / z - bounds.h);
2468
+ if (z < zy) y = clamp(y, minY, (vsb.h - py) / z - bounds.h - bounds.y);
2492
2469
  else y = clamp(y, minY + freeH, minY);
2493
2470
  break;
2494
2471
  }
2495
2472
  case "outside": {
2496
- y = clamp(y, py / z - bounds.h, (vsb.h - py) / z);
2473
+ y = clamp(y, py / z - bounds.h - bounds.y, (vsb.h - py) / z - bounds.y);
2497
2474
  break;
2498
2475
  }
2499
2476
  case "free": {
@@ -2861,9 +2838,9 @@ class Editor extends EventEmitter {
2861
2838
  }
2862
2839
  const remaining = duration - elapsed;
2863
2840
  const t = easing(1 - remaining / duration);
2864
- const left = start.minX + (end.minX - start.minX) * t;
2865
- const top = start.minY + (end.minY - start.minY) * t;
2866
- const right = start.maxX + (end.maxX - start.maxX) * t;
2841
+ const left = lerp(start.minX, end.minX, t);
2842
+ const top = lerp(start.minY, end.minY, t);
2843
+ const right = lerp(start.maxX, end.maxX, t);
2867
2844
  this._setCamera(new Vec(-left, -top, this.getViewportScreenBounds().width / (right - left)), {
2868
2845
  force: true
2869
2846
  });
@@ -2941,7 +2918,7 @@ class Editor extends EventEmitter {
2941
2918
  let newCy = cy + dy;
2942
2919
  let newCz = cz;
2943
2920
  if (dirZ !== 0) {
2944
- newCz = cz * (1 + dirZ * currentSpeed * elapsed);
2921
+ newCz = cz * Math.exp(dirZ * currentSpeed * elapsed);
2945
2922
  const center = this.getViewportScreenCenter();
2946
2923
  newCx += center.x / newCz - center.x / cz;
2947
2924
  newCy += center.y / newCz - center.y / cz;
@@ -3379,12 +3356,12 @@ class Editor extends EventEmitter {
3379
3356
  const renderingShapes = [];
3380
3357
  let nextIndex = this.options.maxShapesPerPage * 2;
3381
3358
  let nextBackgroundIndex = this.options.maxShapesPerPage;
3382
- const erasingShapeIds = this.getErasingShapeIds();
3359
+ const erasingShapeIds = new Set(this.getErasingShapeIds());
3383
3360
  const addShapeById = (id, opacity, isAncestorErasing) => {
3384
3361
  const shape = this.getShape(id);
3385
3362
  if (!shape) return;
3386
3363
  if (this.isShapeHidden(shape)) {
3387
- const isErasing = isAncestorErasing || erasingShapeIds.includes(id);
3364
+ const isErasing = isAncestorErasing || erasingShapeIds.has(id);
3388
3365
  for (const childId of this.getSortedChildIdsForParent(id)) {
3389
3366
  addShapeById(childId, opacity, isErasing);
3390
3367
  }
@@ -3394,7 +3371,7 @@ class Editor extends EventEmitter {
3394
3371
  let isShapeErasing = false;
3395
3372
  const util = this.getShapeUtil(shape);
3396
3373
  if (useEditorState) {
3397
- isShapeErasing = !isAncestorErasing && erasingShapeIds.includes(id);
3374
+ isShapeErasing = !isAncestorErasing && erasingShapeIds.has(id);
3398
3375
  if (isShapeErasing) {
3399
3376
  opacity *= 0.32;
3400
3377
  }
@@ -3986,10 +3963,8 @@ class Editor extends EventEmitter {
3986
3963
  }
3987
3964
  _getShapePageBoundsCache() {
3988
3965
  return this.store.createComputedCache("pageBoundsCache", (shape) => {
3989
- const pageTransform = this.getShapePageTransform(shape);
3990
- if (!pageTransform) return void 0;
3991
3966
  return Box.FromPoints(
3992
- pageTransform.applyToPoints(this.getShapeGeometry(shape).boundsVertices)
3967
+ this.getShapePageTransform(shape).applyToPoints(this.getShapeGeometry(shape).boundsVertices)
3993
3968
  );
3994
3969
  });
3995
3970
  }
@@ -4107,7 +4082,9 @@ class Editor extends EventEmitter {
4107
4082
  if (pageMask) {
4108
4083
  if (pageMask.length === 0) return void 0;
4109
4084
  const { corners } = pageBounds;
4110
- if (corners.every((p, i) => p && Vec.Equals(p, pageMask[i]))) return pageBounds.clone();
4085
+ if (pageMask.length === corners.length && corners.every((p, i) => Vec.Equals(p, pageMask[i]))) {
4086
+ return pageBounds.clone();
4087
+ }
4111
4088
  const intersection = intersectPolygonPolygon(pageMask, corners);
4112
4089
  if (!intersection) return;
4113
4090
  return Box.FromPoints(intersection);
@@ -4346,7 +4323,7 @@ class Editor extends EventEmitter {
4346
4323
  const pointInShapeSpace = this.getPointInShapeSpace(shape, point);
4347
4324
  const shapeUtil = this.getShapeUtil(shape);
4348
4325
  const isShapeFrameLike = this.isShapeFrameLike(shape);
4349
- if (isShapeFrameLike || (this.isShapeOfType(shape, "note") || this.isShapeOfType(shape, "arrow") || this.isShapeOfType(shape, "geo") && shape.props.fill === "none") && shapeUtil.getText(shape)?.trim()) {
4326
+ if (isGroup && (isShapeFrameLike || (this.isShapeOfType(shape, "note") || this.isShapeOfType(shape, "arrow") || this.isShapeOfType(shape, "geo") && shape.props.fill === "none") && shapeUtil.getText(shape)?.trim())) {
4350
4327
  for (const childGeometry of geometry.children) {
4351
4328
  if (childGeometry.isLabel && childGeometry.isPointInBounds(pointInShapeSpace)) {
4352
4329
  return shape;
@@ -4538,9 +4515,7 @@ class Editor extends EventEmitter {
4538
4515
  const freshShape = this.getShape(id);
4539
4516
  if (!freshShape) return new Vec(0, 0);
4540
4517
  if (isPageId(freshShape.parentId)) return Vec.From(point);
4541
- const parentTransform = this.getShapePageTransform(freshShape.parentId);
4542
- if (!parentTransform) return Vec.From(point);
4543
- return parentTransform.clone().invert().applyToPoint(point);
4518
+ return this.getShapePageTransform(freshShape.parentId).clone().invert().applyToPoint(point);
4544
4519
  }
4545
4520
  getCurrentPageShapes() {
4546
4521
  return Array.from(this.getCurrentPageShapeIds(), (id) => this.store.get(id));
@@ -4652,23 +4627,7 @@ class Editor extends EventEmitter {
4652
4627
  * @public
4653
4628
  */
4654
4629
  isShapeInPage(shape, pageId = this.getCurrentPageId()) {
4655
- const id = typeof shape === "string" ? shape : shape.id;
4656
- const shapeToCheck = this.getShape(id);
4657
- if (!shapeToCheck) return false;
4658
- let shapeIsInPage = false;
4659
- if (shapeToCheck.parentId === pageId) {
4660
- shapeIsInPage = true;
4661
- } else {
4662
- let parent = this.getShape(shapeToCheck.parentId);
4663
- isInPageSearch: while (parent) {
4664
- if (parent.parentId === pageId) {
4665
- shapeIsInPage = true;
4666
- break isInPageSearch;
4667
- }
4668
- parent = this.getShape(parent.parentId);
4669
- }
4670
- }
4671
- return shapeIsInPage;
4630
+ return this.getAncestorPageId(shape) === pageId;
4672
4631
  }
4673
4632
  /**
4674
4633
  * Get the id of the containing page for a given shape.
@@ -4749,10 +4708,7 @@ class Editor extends EventEmitter {
4749
4708
  for (let i = 0; i < shapesToReparent.length; i++) {
4750
4709
  const shape = shapesToReparent[i];
4751
4710
  const pageTransform = this.getShapePageTransform(shape);
4752
- if (!pageTransform) continue;
4753
- const pagePoint = pageTransform.point();
4754
- if (!pagePoint) continue;
4755
- const newPoint = invertedParentTransform.applyToPoint(pagePoint);
4711
+ const newPoint = invertedParentTransform.applyToPoint(pageTransform.point());
4756
4712
  const newRotation = pageTransform.rotation() - parentPageRotation;
4757
4713
  if (shape.id === parentId) {
4758
4714
  throw Error("Attempted to reparent a shape to itself!");
@@ -4841,7 +4797,7 @@ class Editor extends EventEmitter {
4841
4797
  */
4842
4798
  getShapeAndDescendantIds(ids) {
4843
4799
  const shapeIds = /* @__PURE__ */ new Set();
4844
- for (const shape of ids.map((id) => this.getShape(id)).sort(sortByIndex)) {
4800
+ for (const shape of compact(ids.map((id) => this.getShape(id))).sort(sortByIndex)) {
4845
4801
  shapeIds.add(shape.id);
4846
4802
  this.visitDescendants(shape, (descendantId) => {
4847
4803
  shapeIds.add(descendantId);
@@ -4863,11 +4819,12 @@ class Editor extends EventEmitter {
4863
4819
  const draggingShapes = compact(droppingShapes.map((s) => this.getShape(s))).filter(
4864
4820
  (s) => !s.isLocked && !this.isShapeHidden(s)
4865
4821
  );
4822
+ const excludedIds = this.getShapeAndDescendantIds(draggingShapes.map((s) => s.id));
4866
4823
  const maybeDraggingOverShapes = this.getShapesAtPoint(point, {
4867
4824
  hitInside: true,
4868
4825
  margin: 0
4869
4826
  }).filter(
4870
- (s) => !droppingShapes.includes(s) && !s.isLocked && !this.isShapeHidden(s) && !draggingShapes.includes(s)
4827
+ (s) => !droppingShapes.includes(s) && !s.isLocked && !this.isShapeHidden(s) && !excludedIds.has(s.id)
4871
4828
  );
4872
4829
  for (const maybeDraggingOverShape of maybeDraggingOverShapes) {
4873
4830
  const shapeUtil = this.getShapeUtil(maybeDraggingOverShape);
@@ -5120,8 +5077,7 @@ class Editor extends EventEmitter {
5120
5077
  for (const id of ids) {
5121
5078
  const shape = this.getShape(id);
5122
5079
  const localDelta = Vec.From(offset);
5123
- const parentTransform = this.getShapeParentTransform(shape);
5124
- if (parentTransform) localDelta.rot(-parentTransform.rotation());
5080
+ localDelta.rot(-this.getShapeParentTransform(shape).rotation());
5125
5081
  changes.push(this.getChangesToTranslateShape(shape, localDelta.add(shape)));
5126
5082
  }
5127
5083
  this.updateShapes(changes);
@@ -5176,7 +5132,7 @@ class Editor extends EventEmitter {
5176
5132
  if (!originalShape) continue;
5177
5133
  let ox = 0;
5178
5134
  let oy = 0;
5179
- if (offset && initialIds.has(originalId)) {
5135
+ if (offset && initialIds.has(originalId) && !shapeIdSet.has(originalShape.parentId)) {
5180
5136
  const parentTransform = this.getShapeParentTransform(originalShape);
5181
5137
  const vec = new Vec(offset.x, offset.y).rot(-parentTransform.rotation());
5182
5138
  ox = vec.x;
@@ -5290,26 +5246,17 @@ class Editor extends EventEmitter {
5290
5246
  toggleLock(shapes) {
5291
5247
  const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5292
5248
  if (this.getIsReadonly() || ids.length === 0) return this;
5293
- let allLocked = true, allUnlocked = true;
5249
+ let allLocked = true;
5294
5250
  const shapesToToggle = [];
5295
5251
  for (const id of ids) {
5296
5252
  const shape = this.getShape(id);
5297
5253
  if (shape) {
5298
5254
  shapesToToggle.push(shape);
5299
- if (shape.isLocked) {
5300
- allUnlocked = false;
5301
- } else {
5302
- allLocked = false;
5303
- }
5255
+ if (!shape.isLocked) allLocked = false;
5304
5256
  }
5305
5257
  }
5306
5258
  this.run(() => {
5307
- if (allUnlocked) {
5308
- this.updateShapes(
5309
- shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
5310
- );
5311
- this.setSelectedShapes([]);
5312
- } else if (allLocked) {
5259
+ if (allLocked) {
5313
5260
  this.updateShapes(
5314
5261
  shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: false }))
5315
5262
  );
@@ -5317,6 +5264,7 @@ class Editor extends EventEmitter {
5317
5264
  this.updateShapes(
5318
5265
  shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
5319
5266
  );
5267
+ this.setSelectedShapes([]);
5320
5268
  }
5321
5269
  });
5322
5270
  return this;
@@ -5427,9 +5375,10 @@ class Editor extends EventEmitter {
5427
5375
  const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5428
5376
  let freshShapes = compact(ids.map((id) => this.getShape(id)));
5429
5377
  if (opts?.filterAxisAligned) {
5430
- freshShapes = freshShapes.filter(
5431
- (s) => this.getShapePageTransform(s)?.rotation() % (PI / 2) === 0
5432
- );
5378
+ freshShapes = freshShapes.filter((s) => {
5379
+ const remainder = Math.abs(this.getShapePageTransform(s).rotation() % HALF_PI);
5380
+ return Math.min(remainder, HALF_PI - remainder) < 1e-9;
5381
+ });
5433
5382
  }
5434
5383
  const clusters = [];
5435
5384
  const allBounds = [];
@@ -5594,9 +5543,9 @@ class Editor extends EventEmitter {
5594
5543
  dim = "height";
5595
5544
  }
5596
5545
  let shapeGap = 0;
5546
+ shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min]);
5597
5547
  if (_gap === 0) {
5598
5548
  const gaps = {};
5599
- shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min]);
5600
5549
  for (let i = 0; i < len - 1; i++) {
5601
5550
  const currCluster = shapeClustersToStack[i];
5602
5551
  const nextCluster = shapeClustersToStack[i + 1];
@@ -5634,8 +5583,7 @@ class Editor extends EventEmitter {
5634
5583
  const shapeDelta = delta.clone();
5635
5584
  const parent = this.getShapeParent(shape);
5636
5585
  if (parent) {
5637
- const parentTransform = this.getShapePageTransform(parent);
5638
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5586
+ shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5639
5587
  }
5640
5588
  shapeDelta.add(shape);
5641
5589
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5673,7 +5621,7 @@ class Editor extends EventEmitter {
5673
5621
  }
5674
5622
  const commonBounds = Box.Common(allBounds);
5675
5623
  const maxWidth = commonBounds.width;
5676
- shapeClustersToPack.sort((a, b) => a.pageBounds.width - b.pageBounds.width).sort((a, b) => a.pageBounds.height - b.pageBounds.height);
5624
+ shapeClustersToPack.sort((a, b) => b.pageBounds.width - a.pageBounds.width).sort((a, b) => b.pageBounds.height - a.pageBounds.height);
5677
5625
  const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);
5678
5626
  const spaces = [new Box(commonBounds.x, commonBounds.y, startWidth, Infinity)];
5679
5627
  let width = 0;
@@ -5721,8 +5669,7 @@ class Editor extends EventEmitter {
5721
5669
  const shapeDelta = delta.clone();
5722
5670
  const parent = this.getShapeParent(shape);
5723
5671
  if (parent) {
5724
- const parentTransform = this.getShapeParentTransform(shape);
5725
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5672
+ shapeDelta.rot(-this.getShapeParentTransform(shape).rotation());
5726
5673
  }
5727
5674
  shapeDelta.add(shape);
5728
5675
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5788,8 +5735,7 @@ class Editor extends EventEmitter {
5788
5735
  const shapeDelta = delta.clone();
5789
5736
  const parent = this.getShapeParent(shape);
5790
5737
  if (parent) {
5791
- const parentTransform = this.getShapePageTransform(parent);
5792
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5738
+ shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5793
5739
  }
5794
5740
  shapeDelta.add(shape);
5795
5741
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5863,8 +5809,7 @@ class Editor extends EventEmitter {
5863
5809
  const shapeDelta = delta.clone();
5864
5810
  const parent = this.getShapeParent(shape);
5865
5811
  if (parent) {
5866
- const parentTransform = this.getShapePageTransform(parent);
5867
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5812
+ shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5868
5813
  }
5869
5814
  shapeDelta.add(shape);
5870
5815
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5919,8 +5864,7 @@ class Editor extends EventEmitter {
5919
5864
  scale[val] = commonBounds[dim] / pageBounds[dim];
5920
5865
  for (const shape of shapes2) {
5921
5866
  const shapeLocalOffset = localOffset.clone();
5922
- const parentTransform = this.getShapeParentTransform(shape);
5923
- if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation());
5867
+ shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation());
5924
5868
  shapeLocalOffset.add(shape);
5925
5869
  const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5926
5870
  this.updateShape(changes);
@@ -5976,8 +5920,7 @@ class Editor extends EventEmitter {
5976
5920
  );
5977
5921
  for (const shape of shapes2) {
5978
5922
  const shapeLocalOffset = localOffset.clone();
5979
- const parentTransform = this.getShapeParentTransform(shape);
5980
- if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation());
5923
+ shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation());
5981
5924
  shapeLocalOffset.add(shape);
5982
5925
  const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5983
5926
  this.updateShape(changes);
@@ -6026,9 +5969,7 @@ class Editor extends EventEmitter {
6026
5969
  const scaleOrigin = opts.scaleOrigin ?? this.getShapePageBounds(id)?.center;
6027
5970
  if (!scaleOrigin) return null;
6028
5971
  const pageTransform = opts.initialPageTransform ? Mat.Cast(opts.initialPageTransform) : this.getShapePageTransform(id);
6029
- if (!pageTransform) return null;
6030
5972
  const pageRotation = pageTransform.rotation();
6031
- if (pageRotation == null) return null;
6032
5973
  const scaleAxisRotation = opts.scaleAxisRotation ?? pageRotation;
6033
5974
  const initialBounds = opts.initialBounds ?? this.getShapeGeometry(id).bounds;
6034
5975
  if (!initialBounds) return null;
@@ -6179,7 +6120,6 @@ class Editor extends EventEmitter {
6179
6120
  const currentLocalBounds = this.getShapeGeometry(id).bounds;
6180
6121
  const currentPageCenter = Mat.applyToPoint(pageTransform, currentLocalBounds.center);
6181
6122
  const shapePageTransformOrigin = pageTransform.point();
6182
- if (!currentPageCenter || !shapePageTransformOrigin) return this;
6183
6123
  const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter);
6184
6124
  const postScaleShapePagePoint = Vec.Add(shapePageTransformOrigin, pageDelta);
6185
6125
  const { x, y } = this.getPointInParentSpace(id, postScaleShapePagePoint);
@@ -6438,10 +6378,10 @@ class Editor extends EventEmitter {
6438
6378
  if (animationIdForShape !== animationId) continue;
6439
6379
  updates.push({
6440
6380
  ...end,
6441
- x: start.x + (end.x - start.x) * t,
6442
- y: start.y + (end.y - start.y) * t,
6443
- opacity: start.opacity + (end.opacity - start.opacity) * t,
6444
- rotation: start.rotation + (end.rotation - start.rotation) * t,
6381
+ x: lerp(start.x, end.x, t),
6382
+ y: lerp(start.y, end.y, t),
6383
+ opacity: lerp(start.opacity, end.opacity, t),
6384
+ rotation: lerp(start.rotation, end.rotation, t),
6445
6385
  props: this.getShapeUtil(end).getInterpolatedProps?.(start, end, t) ?? end.props
6446
6386
  });
6447
6387
  }
@@ -6567,7 +6507,7 @@ class Editor extends EventEmitter {
6567
6507
  * @public
6568
6508
  */
6569
6509
  updateShapes(partials) {
6570
- const compactedPartials = Array(partials.length);
6510
+ const compactedPartials = [];
6571
6511
  for (let i = 0, n = partials.length; i < n; i++) {
6572
6512
  const partial = partials[i];
6573
6513
  if (!partial) continue;
@@ -6638,28 +6578,32 @@ class Editor extends EventEmitter {
6638
6578
  }
6639
6579
  /* --------------------- Styles --------------------- */
6640
6580
  /**
6641
- * Get all the current styles among the users selected shapes
6581
+ * Groups have no styles of their own: a style read or write on a selection applies to the
6582
+ * non-group shapes beneath each group, however deeply nested. Returns those shapes.
6642
6583
  *
6643
6584
  * @internal
6644
6585
  */
6645
- _extractSharedStyles(shape, sharedStyleMap) {
6646
- if (this.isShapeOfType(shape, "group")) {
6647
- const childIds = this._parentIdsToChildIds.get()[shape.id];
6648
- if (!childIds) return;
6649
- for (let i = 0, n = childIds.length; i < n; i++) {
6650
- this._extractSharedStyles(this.getShape(childIds[i]), sharedStyleMap);
6651
- }
6652
- } else {
6653
- for (const [style, propKey] of this.styleProps[shape.type]) {
6654
- sharedStyleMap.applyValue(style, getOwnProperty(shape.props, propKey));
6586
+ _getStyleableShapes(shapes) {
6587
+ const result = [];
6588
+ const visit = (shape) => {
6589
+ if (this.isShapeOfType(shape, "group")) {
6590
+ for (const childId of this.getSortedChildIdsForParent(shape.id)) {
6591
+ const child = this.getShape(childId);
6592
+ if (child) visit(child);
6593
+ }
6594
+ } else {
6595
+ result.push(shape);
6655
6596
  }
6656
- }
6597
+ };
6598
+ for (const shape of shapes) visit(shape);
6599
+ return result;
6657
6600
  }
6658
6601
  _getSelectionSharedStyles() {
6659
- const selectedShapes = this.getSelectedShapes();
6660
6602
  const sharedStyles = new SharedStyleMap();
6661
- for (const selectedShape of selectedShapes) {
6662
- this._extractSharedStyles(selectedShape, sharedStyles);
6603
+ for (const shape of this._getStyleableShapes(this.getSelectedShapes())) {
6604
+ for (const [style, propKey] of this.styleProps[shape.type]) {
6605
+ sharedStyles.applyValue(style, getOwnProperty(shape.props, propKey));
6606
+ }
6663
6607
  }
6664
6608
  return sharedStyles;
6665
6609
  }
@@ -6691,38 +6635,16 @@ class Editor extends EventEmitter {
6691
6635
  const styles = new SharedStyleMap();
6692
6636
  if (!currentTool) return styles;
6693
6637
  if (currentTool.shapeType) {
6694
- if (currentTool.shapeType === "frame" && !this.getShapeUtil("frame").options.showColors) {
6695
- for (const style of this.styleProps[currentTool.shapeType].keys()) {
6696
- if (style.id === "tldraw:color") continue;
6697
- styles.applyValue(style, this.getStyleForNextShape(style));
6698
- }
6699
- } else {
6700
- for (const style of this.styleProps[currentTool.shapeType].keys()) {
6701
- styles.applyValue(style, this.getStyleForNextShape(style));
6702
- }
6638
+ for (const style of this.styleProps[currentTool.shapeType].keys()) {
6639
+ styles.applyValue(style, this.getStyleForNextShape(style));
6703
6640
  }
6704
6641
  }
6705
6642
  return styles;
6706
6643
  }
6707
6644
  getSharedOpacity() {
6708
6645
  if (this.isIn("select") && this.getSelectedShapeIds().length > 0) {
6709
- const shapesToCheck = [];
6710
- const addShape = (shapeId) => {
6711
- const shape = this.getShape(shapeId);
6712
- if (!shape) return;
6713
- if (this.isShapeOfType(shape, "group")) {
6714
- for (const childId of this.getSortedChildIdsForParent(shape.id)) {
6715
- addShape(childId);
6716
- }
6717
- } else {
6718
- shapesToCheck.push(shape);
6719
- }
6720
- };
6721
- for (const shapeId of this.getSelectedShapeIds()) {
6722
- addShape(shapeId);
6723
- }
6724
6646
  let opacity = null;
6725
- for (const shape of shapesToCheck) {
6647
+ for (const shape of this._getStyleableShapes(this.getSelectedShapes())) {
6726
6648
  if (opacity === null) {
6727
6649
  opacity = shape.opacity;
6728
6650
  } else if (opacity !== shape.opacity) {
@@ -6761,28 +6683,12 @@ class Editor extends EventEmitter {
6761
6683
  setOpacityForSelectedShapes(opacity) {
6762
6684
  const selectedShapes = this.getSelectedShapes();
6763
6685
  if (selectedShapes.length > 0) {
6764
- const shapesToUpdate = [];
6765
- const addShapeById = (shape) => {
6766
- if (this.isShapeOfType(shape, "group")) {
6767
- const childIds = this.getSortedChildIdsForParent(shape);
6768
- for (const childId of childIds) {
6769
- addShapeById(this.getShape(childId));
6770
- }
6771
- } else {
6772
- shapesToUpdate.push(shape);
6773
- }
6774
- };
6775
- for (const id of selectedShapes) {
6776
- addShapeById(id);
6777
- }
6778
6686
  this.updateShapes(
6779
- shapesToUpdate.map((shape) => {
6780
- return {
6781
- id: shape.id,
6782
- type: shape.type,
6783
- opacity
6784
- };
6785
- })
6687
+ this._getStyleableShapes(selectedShapes).map((shape) => ({
6688
+ id: shape.id,
6689
+ type: shape.type,
6690
+ opacity
6691
+ }))
6786
6692
  );
6787
6693
  }
6788
6694
  return this;
@@ -6827,33 +6733,17 @@ class Editor extends EventEmitter {
6827
6733
  const selectedShapes = this.getSelectedShapes();
6828
6734
  if (selectedShapes.length > 0) {
6829
6735
  const updates = [];
6830
- const addShapeById = (shape) => {
6831
- if (this.isShapeOfType(shape, "group")) {
6832
- const childIds = this.getSortedChildIdsForParent(shape.id);
6833
- for (const childId of childIds) {
6834
- addShapeById(this.getShape(childId));
6835
- }
6836
- } else {
6837
- const util = this.getShapeUtil(shape);
6838
- const stylePropKey = this.styleProps[shape.type].get(style);
6839
- if (stylePropKey) {
6840
- const shapePartial = {
6841
- id: shape.id,
6842
- type: shape.type,
6843
- props: { [stylePropKey]: value }
6844
- };
6845
- updates.push({
6846
- util,
6847
- originalShape: shape,
6848
- updatePartial: shapePartial
6849
- });
6850
- }
6736
+ for (const shape of this._getStyleableShapes(selectedShapes)) {
6737
+ const stylePropKey = this.styleProps[shape.type].get(style);
6738
+ if (stylePropKey) {
6739
+ updates.push({
6740
+ id: shape.id,
6741
+ type: shape.type,
6742
+ props: { [stylePropKey]: value }
6743
+ });
6851
6744
  }
6852
- };
6853
- for (const shape of selectedShapes) {
6854
- addShapeById(shape);
6855
6745
  }
6856
- this.updateShapes(updates.map(({ updatePartial }) => updatePartial));
6746
+ this.updateShapes(updates);
6857
6747
  }
6858
6748
  return this;
6859
6749
  }
@@ -7249,7 +7139,6 @@ class Editor extends EventEmitter {
7249
7139
  if (shapeIdMap.has(newShape.parentId)) {
7250
7140
  newShape.parentId = shapeIdMap.get(oldShape.parentId);
7251
7141
  } else {
7252
- rootShapeIds.push(newShape.id);
7253
7142
  newShape.index = index;
7254
7143
  index = getIndexAbove(index);
7255
7144
  }
@@ -7274,8 +7163,11 @@ class Editor extends EventEmitter {
7274
7163
  continue;
7275
7164
  }
7276
7165
  if (asset.type === "image" && asset.props.src?.startsWith("data:image") || asset.type === "video" && asset.props.src?.startsWith("data:video")) {
7277
- assetsToUpdate.push(structuredClone(asset));
7278
- asset.props.src = null;
7166
+ assetsToUpdate.push(asset);
7167
+ const assetWithoutSrc = structuredClone(asset);
7168
+ assetWithoutSrc.props.src = null;
7169
+ assetsToCreate.push(assetWithoutSrc);
7170
+ continue;
7279
7171
  }
7280
7172
  assetsToCreate.push(asset);
7281
7173
  }
@@ -8102,7 +7994,7 @@ class Editor extends EventEmitter {
8102
7994
  if (info.type === "misc") {
8103
7995
  if (info.name === "cancel" || info.name === "complete") {
8104
7996
  this.inputs.setIsDragging(false);
8105
- if (this.inputs.getIsPanning()) {
7997
+ if (this.inputs.getIsPanning() && !this.inputs.keys.has("Space")) {
8106
7998
  this.inputs.setIsPanning(false);
8107
7999
  this.inputs.setIsSpacebarPanning(false);
8108
8000
  this.setCursor({ type: this._prevCursor, rotation: 0 });
@@ -8854,10 +8746,9 @@ function withIsolatedShapes(editor, shapeIds, callback) {
8854
8746
  }
8855
8747
  function getCameraFitXFitY(editor, cameraOptions) {
8856
8748
  if (!cameraOptions.constraints) throw Error("Should have constraints here");
8857
- const {
8858
- padding: { x: px, y: py }
8859
- } = cameraOptions.constraints;
8860
8749
  const vsb = editor.getViewportScreenBounds();
8750
+ const px = Math.min(cameraOptions.constraints.padding.x, vsb.w / 2);
8751
+ const py = Math.min(cameraOptions.constraints.padding.y, vsb.h / 2);
8861
8752
  const bounds = Box.From(cameraOptions.constraints.bounds);
8862
8753
  const zx = (vsb.w - px * 2) / bounds.w;
8863
8754
  const zy = (vsb.h - py * 2) / bounds.h;