@tldraw/editor 5.4.0-next.ef99ab47e5ce → 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 (249) hide show
  1. package/dist-cjs/index.d.ts +114 -47
  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 +181 -294
  17. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  18. package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js +7 -3
  19. package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js.map +2 -2
  20. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +3 -3
  21. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  22. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +8 -8
  23. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
  24. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +2 -1
  25. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  26. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +79 -6
  27. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
  28. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +7 -0
  29. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
  30. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +0 -2
  31. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
  32. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +1 -2
  33. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  34. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +6 -3
  35. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  36. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +12 -1
  37. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  38. package/dist-cjs/lib/editor/overlays/OverlayManager.js +3 -7
  39. package/dist-cjs/lib/editor/overlays/OverlayManager.js.map +2 -2
  40. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +0 -1
  41. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +2 -2
  42. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +3 -1
  43. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +2 -2
  44. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +1 -0
  45. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  46. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +1 -1
  47. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  48. package/dist-cjs/lib/editor/tools/StateNode.js +3 -2
  49. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  50. package/dist-cjs/lib/globals/editors.js +57 -0
  51. package/dist-cjs/lib/globals/editors.js.map +7 -0
  52. package/dist-cjs/lib/globals/menus.js +9 -4
  53. package/dist-cjs/lib/globals/menus.js.map +2 -2
  54. package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -0
  55. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  56. package/dist-cjs/lib/hooks/useDocumentEvents.js +39 -9
  57. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  58. package/dist-cjs/lib/hooks/useLocalStore.js +10 -6
  59. package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
  60. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +1 -0
  61. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  62. package/dist-cjs/lib/hooks/usePeerIds.js +2 -1
  63. package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
  64. package/dist-cjs/lib/license/LicenseManager.js +9 -2
  65. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  66. package/dist-cjs/lib/license/LicenseProvider.js +16 -3
  67. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  68. package/dist-cjs/lib/license/useLicenseManagerState.js +5 -4
  69. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  70. package/dist-cjs/lib/primitives/Box.js +2 -1
  71. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  72. package/dist-cjs/lib/primitives/Vec.js +4 -3
  73. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  74. package/dist-cjs/lib/primitives/geometry/Group2d.js +63 -21
  75. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  76. package/dist-cjs/lib/primitives/utils.js +2 -1
  77. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  78. package/dist-cjs/lib/utils/deepLinks.js +5 -1
  79. package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
  80. package/dist-cjs/lib/utils/normalizeWheel.js +2 -0
  81. package/dist-cjs/lib/utils/normalizeWheel.js.map +2 -2
  82. package/dist-cjs/lib/utils/rotation.js +1 -9
  83. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  84. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +17 -0
  85. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  86. package/dist-cjs/lib/utils/sync/alerts.js +1 -1
  87. package/dist-cjs/lib/utils/sync/alerts.js.map +1 -1
  88. package/dist-cjs/version.js +3 -3
  89. package/dist-cjs/version.js.map +1 -1
  90. package/dist-esm/index.d.mts +114 -47
  91. package/dist-esm/index.mjs +9 -2
  92. package/dist-esm/index.mjs.map +2 -2
  93. package/dist-esm/lib/TldrawEditor.mjs +9 -4
  94. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  95. package/dist-esm/lib/components/MenuClickCapture.mjs +6 -8
  96. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  97. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs +1 -3
  98. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs.map +2 -2
  99. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  100. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +1 -1
  101. package/dist-esm/lib/config/TLUserPreferences.mjs +6 -2
  102. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  103. package/dist-esm/lib/config/createTLStore.mjs +1 -1
  104. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  105. package/dist-esm/lib/editor/Editor.mjs +189 -295
  106. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  107. package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs +7 -3
  108. package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs.map +2 -2
  109. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +3 -3
  110. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  111. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +9 -9
  112. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
  113. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +2 -1
  114. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  115. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +81 -8
  116. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
  117. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +7 -0
  118. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
  119. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +0 -2
  120. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
  121. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -3
  122. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  123. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +6 -3
  124. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  125. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +12 -1
  126. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  127. package/dist-esm/lib/editor/overlays/OverlayManager.mjs +3 -7
  128. package/dist-esm/lib/editor/overlays/OverlayManager.mjs.map +2 -2
  129. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +0 -1
  130. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +2 -2
  131. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +3 -1
  132. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +2 -2
  133. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +1 -0
  134. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  135. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +1 -1
  136. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  137. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -2
  138. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  139. package/dist-esm/lib/globals/editors.mjs +37 -0
  140. package/dist-esm/lib/globals/editors.mjs.map +7 -0
  141. package/dist-esm/lib/globals/menus.mjs +9 -4
  142. package/dist-esm/lib/globals/menus.mjs.map +2 -2
  143. package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -0
  144. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  145. package/dist-esm/lib/hooks/useDocumentEvents.mjs +40 -10
  146. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  147. package/dist-esm/lib/hooks/useLocalStore.mjs +11 -7
  148. package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
  149. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +1 -0
  150. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  151. package/dist-esm/lib/hooks/usePeerIds.mjs +2 -1
  152. package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
  153. package/dist-esm/lib/license/LicenseManager.mjs +9 -2
  154. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  155. package/dist-esm/lib/license/LicenseProvider.mjs +16 -3
  156. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  157. package/dist-esm/lib/license/useLicenseManagerState.mjs +5 -4
  158. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  159. package/dist-esm/lib/primitives/Box.mjs +2 -1
  160. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  161. package/dist-esm/lib/primitives/Vec.mjs +4 -3
  162. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  163. package/dist-esm/lib/primitives/geometry/Group2d.mjs +63 -21
  164. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  165. package/dist-esm/lib/primitives/utils.mjs +2 -1
  166. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  167. package/dist-esm/lib/utils/deepLinks.mjs +5 -1
  168. package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
  169. package/dist-esm/lib/utils/normalizeWheel.mjs +2 -0
  170. package/dist-esm/lib/utils/normalizeWheel.mjs.map +2 -2
  171. package/dist-esm/lib/utils/rotation.mjs +1 -9
  172. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  173. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +17 -0
  174. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  175. package/dist-esm/lib/utils/sync/alerts.mjs +1 -1
  176. package/dist-esm/lib/utils/sync/alerts.mjs.map +1 -1
  177. package/dist-esm/version.mjs +3 -3
  178. package/dist-esm/version.mjs.map +1 -1
  179. package/package.json +13 -7
  180. package/src/index.ts +6 -1
  181. package/src/lib/TldrawEditor.tsx +16 -4
  182. package/src/lib/components/MenuClickCapture.tsx +19 -18
  183. package/src/lib/components/default-components/CanvasOverlays.tsx +1 -3
  184. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  185. package/src/lib/config/TLUserPreferences.test.ts +18 -1
  186. package/src/lib/config/TLUserPreferences.ts +8 -4
  187. package/src/lib/config/createTLStore.ts +1 -1
  188. package/src/lib/editor/Editor.ts +266 -383
  189. package/src/lib/editor/derivations/shapeIdsInCurrentPage.ts +7 -3
  190. package/src/lib/editor/managers/ClickManager/ClickManager.ts +5 -3
  191. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +9 -9
  192. package/src/lib/editor/managers/FontManager/FontManager.ts +2 -1
  193. package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +199 -3
  194. package/src/lib/editor/managers/InputsManager/InputsManager.ts +128 -9
  195. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +12 -1
  196. package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +0 -2
  197. package/src/lib/editor/managers/TextManager/TextManager.ts +2 -6
  198. package/src/lib/editor/managers/TickManager/TickManager.test.ts +13 -0
  199. package/src/lib/editor/managers/TickManager/TickManager.ts +9 -3
  200. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +64 -3
  201. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +18 -1
  202. package/src/lib/editor/overlays/OverlayManager.ts +3 -7
  203. package/src/lib/editor/overlays/strokeShapeIndicators.ts +0 -1
  204. package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +5 -1
  205. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +3 -0
  206. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -1
  207. package/src/lib/editor/tools/StateNode.test.ts +64 -0
  208. package/src/lib/editor/tools/StateNode.ts +12 -2
  209. package/src/lib/globals/editors.test.ts +76 -0
  210. package/src/lib/globals/editors.ts +53 -0
  211. package/src/lib/globals/menus.test.ts +33 -0
  212. package/src/lib/globals/menus.ts +11 -4
  213. package/src/lib/hooks/useCanvasEvents.ts +2 -0
  214. package/src/lib/hooks/useDocumentEvents.ts +60 -16
  215. package/src/lib/hooks/useLocalStore.ts +16 -8
  216. package/src/lib/hooks/usePassThroughWheelEvents.ts +3 -0
  217. package/src/lib/hooks/usePeerIds.ts +2 -1
  218. package/src/lib/license/LicenseManager.test.ts +160 -0
  219. package/src/lib/license/LicenseManager.ts +13 -4
  220. package/src/lib/license/LicenseProvider.tsx +24 -3
  221. package/src/lib/license/useLicenseManagerState.ts +8 -6
  222. package/src/lib/primitives/Box.test.ts +9 -1
  223. package/src/lib/primitives/Box.ts +3 -1
  224. package/src/lib/primitives/Vec.test.ts +10 -0
  225. package/src/lib/primitives/Vec.ts +4 -4
  226. package/src/lib/primitives/geometry/Arc2d.test.ts +226 -3
  227. package/src/lib/primitives/geometry/CubicBezier2d.test.ts +173 -3
  228. package/src/lib/primitives/geometry/CubicSpline2d.test.ts +160 -3
  229. package/src/lib/primitives/geometry/Ellipse2d.test.ts +243 -3
  230. package/src/lib/primitives/geometry/Geometry2d.test.ts +553 -1
  231. package/src/lib/primitives/geometry/Group2d.test.ts +398 -3
  232. package/src/lib/primitives/geometry/Group2d.ts +63 -27
  233. package/src/lib/primitives/geometry/Polyline2d.test.ts +207 -0
  234. package/src/lib/primitives/geometry/Stadium2d.test.ts +191 -3
  235. package/src/lib/primitives/utils.test.ts +10 -1
  236. package/src/lib/primitives/utils.ts +4 -1
  237. package/src/lib/test/useCanvasEvents.test.tsx +74 -0
  238. package/src/lib/test/useDocumentEvents.test.tsx +107 -0
  239. package/src/lib/test/usePassThroughWheelEvents.test.tsx +25 -2
  240. package/src/lib/utils/deepLinks.test.ts +15 -0
  241. package/src/lib/utils/deepLinks.ts +7 -1
  242. package/src/lib/utils/normalizeWheel.test.ts +39 -0
  243. package/src/lib/utils/normalizeWheel.ts +6 -0
  244. package/src/lib/utils/rotation.ts +4 -12
  245. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +56 -0
  246. package/src/lib/utils/sync/TLLocalSyncClient.ts +24 -0
  247. package/src/lib/utils/sync/alerts.ts +1 -1
  248. package/src/version.ts +3 -3
  249. 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);
@@ -4331,23 +4308,22 @@ class Editor extends EventEmitter {
4331
4308
  let inMarginClosestToEdgeHit = null;
4332
4309
  const searchMargin = Math.max(innerMargin, outerMargin, this.getHitTestMargin());
4333
4310
  const candidateIds = this._spatialIndex.getShapeIdsAtPoint(point, searchMargin);
4334
- const shapesToCheck = (opts.renderingOnly ? this.getCurrentPageRenderingShapesSorted() : this.getCurrentPageShapesSorted()).filter((shape) => {
4335
- if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) return false;
4336
- if (shape.isLocked && !hitLocked || this.isShapeHidden(shape) || this.isShapeOfType(shape, "group"))
4337
- return false;
4338
- const pageMask = this.getShapeMask(shape);
4339
- if (pageMask && !pointInPolygon(point, pageMask)) return false;
4340
- if (filter && !filter(shape)) return false;
4341
- return true;
4342
- });
4311
+ const shapesToCheck = opts.renderingOnly ? this.getCurrentPageRenderingShapesSorted() : this.getCurrentPageShapesSorted();
4343
4312
  for (let i = shapesToCheck.length - 1; i >= 0; i--) {
4344
4313
  const shape = shapesToCheck[i];
4314
+ if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) continue;
4315
+ if (shape.isLocked && !hitLocked || this.isShapeHidden(shape) || this.isShapeOfType(shape, "group")) {
4316
+ continue;
4317
+ }
4318
+ const pageMask = this.getShapeMask(shape);
4319
+ if (pageMask && !pointInPolygon(point, pageMask)) continue;
4320
+ if (filter && !filter(shape)) continue;
4345
4321
  const geometry = this.getShapeGeometry(shape);
4346
4322
  const isGroup = geometry instanceof Group2d;
4347
4323
  const pointInShapeSpace = this.getPointInShapeSpace(shape, point);
4348
4324
  const shapeUtil = this.getShapeUtil(shape);
4349
4325
  const isShapeFrameLike = this.isShapeFrameLike(shape);
4350
- 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())) {
4351
4327
  for (const childGeometry of geometry.children) {
4352
4328
  if (childGeometry.isLabel && childGeometry.isPointInBounds(pointInShapeSpace)) {
4353
4329
  return shape;
@@ -4446,11 +4422,15 @@ class Editor extends EventEmitter {
4446
4422
  getShapesAtPoint(point, opts = {}) {
4447
4423
  const margin = opts.margin ?? 0;
4448
4424
  const candidateIds = this._spatialIndex.getShapeIdsAtPoint(point, margin);
4449
- return this.getCurrentPageShapesSorted().filter((shape) => {
4450
- if (this.isShapeHidden(shape)) return false;
4451
- if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) return false;
4452
- return this.isPointInShape(shape, point, opts);
4453
- }).reverse();
4425
+ const sorted = this.getCurrentPageShapesSorted();
4426
+ const result = [];
4427
+ for (let i = sorted.length - 1; i >= 0; i--) {
4428
+ const shape = sorted[i];
4429
+ if (this.isShapeHidden(shape)) continue;
4430
+ if (!candidateIds.has(shape.id) && !this.isShapeFrameLike(shape)) continue;
4431
+ if (this.isPointInShape(shape, point, opts)) result.push(shape);
4432
+ }
4433
+ return result;
4454
4434
  }
4455
4435
  /**
4456
4436
  * Get shape IDs within the given bounds.
@@ -4535,9 +4515,7 @@ class Editor extends EventEmitter {
4535
4515
  const freshShape = this.getShape(id);
4536
4516
  if (!freshShape) return new Vec(0, 0);
4537
4517
  if (isPageId(freshShape.parentId)) return Vec.From(point);
4538
- const parentTransform = this.getShapePageTransform(freshShape.parentId);
4539
- if (!parentTransform) return Vec.From(point);
4540
- return parentTransform.clone().invert().applyToPoint(point);
4518
+ return this.getShapePageTransform(freshShape.parentId).clone().invert().applyToPoint(point);
4541
4519
  }
4542
4520
  getCurrentPageShapes() {
4543
4521
  return Array.from(this.getCurrentPageShapeIds(), (id) => this.store.get(id));
@@ -4649,23 +4627,7 @@ class Editor extends EventEmitter {
4649
4627
  * @public
4650
4628
  */
4651
4629
  isShapeInPage(shape, pageId = this.getCurrentPageId()) {
4652
- const id = typeof shape === "string" ? shape : shape.id;
4653
- const shapeToCheck = this.getShape(id);
4654
- if (!shapeToCheck) return false;
4655
- let shapeIsInPage = false;
4656
- if (shapeToCheck.parentId === pageId) {
4657
- shapeIsInPage = true;
4658
- } else {
4659
- let parent = this.getShape(shapeToCheck.parentId);
4660
- isInPageSearch: while (parent) {
4661
- if (parent.parentId === pageId) {
4662
- shapeIsInPage = true;
4663
- break isInPageSearch;
4664
- }
4665
- parent = this.getShape(parent.parentId);
4666
- }
4667
- }
4668
- return shapeIsInPage;
4630
+ return this.getAncestorPageId(shape) === pageId;
4669
4631
  }
4670
4632
  /**
4671
4633
  * Get the id of the containing page for a given shape.
@@ -4746,10 +4708,7 @@ class Editor extends EventEmitter {
4746
4708
  for (let i = 0; i < shapesToReparent.length; i++) {
4747
4709
  const shape = shapesToReparent[i];
4748
4710
  const pageTransform = this.getShapePageTransform(shape);
4749
- if (!pageTransform) continue;
4750
- const pagePoint = pageTransform.point();
4751
- if (!pagePoint) continue;
4752
- const newPoint = invertedParentTransform.applyToPoint(pagePoint);
4711
+ const newPoint = invertedParentTransform.applyToPoint(pageTransform.point());
4753
4712
  const newRotation = pageTransform.rotation() - parentPageRotation;
4754
4713
  if (shape.id === parentId) {
4755
4714
  throw Error("Attempted to reparent a shape to itself!");
@@ -4838,7 +4797,7 @@ class Editor extends EventEmitter {
4838
4797
  */
4839
4798
  getShapeAndDescendantIds(ids) {
4840
4799
  const shapeIds = /* @__PURE__ */ new Set();
4841
- 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)) {
4842
4801
  shapeIds.add(shape.id);
4843
4802
  this.visitDescendants(shape, (descendantId) => {
4844
4803
  shapeIds.add(descendantId);
@@ -4860,11 +4819,12 @@ class Editor extends EventEmitter {
4860
4819
  const draggingShapes = compact(droppingShapes.map((s) => this.getShape(s))).filter(
4861
4820
  (s) => !s.isLocked && !this.isShapeHidden(s)
4862
4821
  );
4822
+ const excludedIds = this.getShapeAndDescendantIds(draggingShapes.map((s) => s.id));
4863
4823
  const maybeDraggingOverShapes = this.getShapesAtPoint(point, {
4864
4824
  hitInside: true,
4865
4825
  margin: 0
4866
4826
  }).filter(
4867
- (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)
4868
4828
  );
4869
4829
  for (const maybeDraggingOverShape of maybeDraggingOverShapes) {
4870
4830
  const shapeUtil = this.getShapeUtil(maybeDraggingOverShape);
@@ -5117,8 +5077,7 @@ class Editor extends EventEmitter {
5117
5077
  for (const id of ids) {
5118
5078
  const shape = this.getShape(id);
5119
5079
  const localDelta = Vec.From(offset);
5120
- const parentTransform = this.getShapeParentTransform(shape);
5121
- if (parentTransform) localDelta.rot(-parentTransform.rotation());
5080
+ localDelta.rot(-this.getShapeParentTransform(shape).rotation());
5122
5081
  changes.push(this.getChangesToTranslateShape(shape, localDelta.add(shape)));
5123
5082
  }
5124
5083
  this.updateShapes(changes);
@@ -5173,7 +5132,7 @@ class Editor extends EventEmitter {
5173
5132
  if (!originalShape) continue;
5174
5133
  let ox = 0;
5175
5134
  let oy = 0;
5176
- if (offset && initialIds.has(originalId)) {
5135
+ if (offset && initialIds.has(originalId) && !shapeIdSet.has(originalShape.parentId)) {
5177
5136
  const parentTransform = this.getShapeParentTransform(originalShape);
5178
5137
  const vec = new Vec(offset.x, offset.y).rot(-parentTransform.rotation());
5179
5138
  ox = vec.x;
@@ -5287,26 +5246,17 @@ class Editor extends EventEmitter {
5287
5246
  toggleLock(shapes) {
5288
5247
  const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5289
5248
  if (this.getIsReadonly() || ids.length === 0) return this;
5290
- let allLocked = true, allUnlocked = true;
5249
+ let allLocked = true;
5291
5250
  const shapesToToggle = [];
5292
5251
  for (const id of ids) {
5293
5252
  const shape = this.getShape(id);
5294
5253
  if (shape) {
5295
5254
  shapesToToggle.push(shape);
5296
- if (shape.isLocked) {
5297
- allUnlocked = false;
5298
- } else {
5299
- allLocked = false;
5300
- }
5255
+ if (!shape.isLocked) allLocked = false;
5301
5256
  }
5302
5257
  }
5303
5258
  this.run(() => {
5304
- if (allUnlocked) {
5305
- this.updateShapes(
5306
- shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
5307
- );
5308
- this.setSelectedShapes([]);
5309
- } else if (allLocked) {
5259
+ if (allLocked) {
5310
5260
  this.updateShapes(
5311
5261
  shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: false }))
5312
5262
  );
@@ -5314,6 +5264,7 @@ class Editor extends EventEmitter {
5314
5264
  this.updateShapes(
5315
5265
  shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
5316
5266
  );
5267
+ this.setSelectedShapes([]);
5317
5268
  }
5318
5269
  });
5319
5270
  return this;
@@ -5424,9 +5375,10 @@ class Editor extends EventEmitter {
5424
5375
  const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5425
5376
  let freshShapes = compact(ids.map((id) => this.getShape(id)));
5426
5377
  if (opts?.filterAxisAligned) {
5427
- freshShapes = freshShapes.filter(
5428
- (s) => this.getShapePageTransform(s)?.rotation() % (PI / 2) === 0
5429
- );
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
+ });
5430
5382
  }
5431
5383
  const clusters = [];
5432
5384
  const allBounds = [];
@@ -5591,9 +5543,9 @@ class Editor extends EventEmitter {
5591
5543
  dim = "height";
5592
5544
  }
5593
5545
  let shapeGap = 0;
5546
+ shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min]);
5594
5547
  if (_gap === 0) {
5595
5548
  const gaps = {};
5596
- shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min]);
5597
5549
  for (let i = 0; i < len - 1; i++) {
5598
5550
  const currCluster = shapeClustersToStack[i];
5599
5551
  const nextCluster = shapeClustersToStack[i + 1];
@@ -5631,8 +5583,7 @@ class Editor extends EventEmitter {
5631
5583
  const shapeDelta = delta.clone();
5632
5584
  const parent = this.getShapeParent(shape);
5633
5585
  if (parent) {
5634
- const parentTransform = this.getShapePageTransform(parent);
5635
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5586
+ shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5636
5587
  }
5637
5588
  shapeDelta.add(shape);
5638
5589
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5670,7 +5621,7 @@ class Editor extends EventEmitter {
5670
5621
  }
5671
5622
  const commonBounds = Box.Common(allBounds);
5672
5623
  const maxWidth = commonBounds.width;
5673
- 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);
5674
5625
  const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);
5675
5626
  const spaces = [new Box(commonBounds.x, commonBounds.y, startWidth, Infinity)];
5676
5627
  let width = 0;
@@ -5718,8 +5669,7 @@ class Editor extends EventEmitter {
5718
5669
  const shapeDelta = delta.clone();
5719
5670
  const parent = this.getShapeParent(shape);
5720
5671
  if (parent) {
5721
- const parentTransform = this.getShapeParentTransform(shape);
5722
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5672
+ shapeDelta.rot(-this.getShapeParentTransform(shape).rotation());
5723
5673
  }
5724
5674
  shapeDelta.add(shape);
5725
5675
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5785,8 +5735,7 @@ class Editor extends EventEmitter {
5785
5735
  const shapeDelta = delta.clone();
5786
5736
  const parent = this.getShapeParent(shape);
5787
5737
  if (parent) {
5788
- const parentTransform = this.getShapePageTransform(parent);
5789
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5738
+ shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5790
5739
  }
5791
5740
  shapeDelta.add(shape);
5792
5741
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5860,8 +5809,7 @@ class Editor extends EventEmitter {
5860
5809
  const shapeDelta = delta.clone();
5861
5810
  const parent = this.getShapeParent(shape);
5862
5811
  if (parent) {
5863
- const parentTransform = this.getShapePageTransform(parent);
5864
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5812
+ shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5865
5813
  }
5866
5814
  shapeDelta.add(shape);
5867
5815
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5916,8 +5864,7 @@ class Editor extends EventEmitter {
5916
5864
  scale[val] = commonBounds[dim] / pageBounds[dim];
5917
5865
  for (const shape of shapes2) {
5918
5866
  const shapeLocalOffset = localOffset.clone();
5919
- const parentTransform = this.getShapeParentTransform(shape);
5920
- if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation());
5867
+ shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation());
5921
5868
  shapeLocalOffset.add(shape);
5922
5869
  const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5923
5870
  this.updateShape(changes);
@@ -5973,8 +5920,7 @@ class Editor extends EventEmitter {
5973
5920
  );
5974
5921
  for (const shape of shapes2) {
5975
5922
  const shapeLocalOffset = localOffset.clone();
5976
- const parentTransform = this.getShapeParentTransform(shape);
5977
- if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation());
5923
+ shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation());
5978
5924
  shapeLocalOffset.add(shape);
5979
5925
  const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5980
5926
  this.updateShape(changes);
@@ -6023,9 +5969,7 @@ class Editor extends EventEmitter {
6023
5969
  const scaleOrigin = opts.scaleOrigin ?? this.getShapePageBounds(id)?.center;
6024
5970
  if (!scaleOrigin) return null;
6025
5971
  const pageTransform = opts.initialPageTransform ? Mat.Cast(opts.initialPageTransform) : this.getShapePageTransform(id);
6026
- if (!pageTransform) return null;
6027
5972
  const pageRotation = pageTransform.rotation();
6028
- if (pageRotation == null) return null;
6029
5973
  const scaleAxisRotation = opts.scaleAxisRotation ?? pageRotation;
6030
5974
  const initialBounds = opts.initialBounds ?? this.getShapeGeometry(id).bounds;
6031
5975
  if (!initialBounds) return null;
@@ -6176,7 +6120,6 @@ class Editor extends EventEmitter {
6176
6120
  const currentLocalBounds = this.getShapeGeometry(id).bounds;
6177
6121
  const currentPageCenter = Mat.applyToPoint(pageTransform, currentLocalBounds.center);
6178
6122
  const shapePageTransformOrigin = pageTransform.point();
6179
- if (!currentPageCenter || !shapePageTransformOrigin) return this;
6180
6123
  const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter);
6181
6124
  const postScaleShapePagePoint = Vec.Add(shapePageTransformOrigin, pageDelta);
6182
6125
  const { x, y } = this.getPointInParentSpace(id, postScaleShapePagePoint);
@@ -6435,10 +6378,10 @@ class Editor extends EventEmitter {
6435
6378
  if (animationIdForShape !== animationId) continue;
6436
6379
  updates.push({
6437
6380
  ...end,
6438
- x: start.x + (end.x - start.x) * t,
6439
- y: start.y + (end.y - start.y) * t,
6440
- opacity: start.opacity + (end.opacity - start.opacity) * t,
6441
- 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),
6442
6385
  props: this.getShapeUtil(end).getInterpolatedProps?.(start, end, t) ?? end.props
6443
6386
  });
6444
6387
  }
@@ -6564,7 +6507,7 @@ class Editor extends EventEmitter {
6564
6507
  * @public
6565
6508
  */
6566
6509
  updateShapes(partials) {
6567
- const compactedPartials = Array(partials.length);
6510
+ const compactedPartials = [];
6568
6511
  for (let i = 0, n = partials.length; i < n; i++) {
6569
6512
  const partial = partials[i];
6570
6513
  if (!partial) continue;
@@ -6635,28 +6578,32 @@ class Editor extends EventEmitter {
6635
6578
  }
6636
6579
  /* --------------------- Styles --------------------- */
6637
6580
  /**
6638
- * 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.
6639
6583
  *
6640
6584
  * @internal
6641
6585
  */
6642
- _extractSharedStyles(shape, sharedStyleMap) {
6643
- if (this.isShapeOfType(shape, "group")) {
6644
- const childIds = this._parentIdsToChildIds.get()[shape.id];
6645
- if (!childIds) return;
6646
- for (let i = 0, n = childIds.length; i < n; i++) {
6647
- this._extractSharedStyles(this.getShape(childIds[i]), sharedStyleMap);
6648
- }
6649
- } else {
6650
- for (const [style, propKey] of this.styleProps[shape.type]) {
6651
- 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);
6652
6596
  }
6653
- }
6597
+ };
6598
+ for (const shape of shapes) visit(shape);
6599
+ return result;
6654
6600
  }
6655
6601
  _getSelectionSharedStyles() {
6656
- const selectedShapes = this.getSelectedShapes();
6657
6602
  const sharedStyles = new SharedStyleMap();
6658
- for (const selectedShape of selectedShapes) {
6659
- 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
+ }
6660
6607
  }
6661
6608
  return sharedStyles;
6662
6609
  }
@@ -6688,38 +6635,16 @@ class Editor extends EventEmitter {
6688
6635
  const styles = new SharedStyleMap();
6689
6636
  if (!currentTool) return styles;
6690
6637
  if (currentTool.shapeType) {
6691
- if (currentTool.shapeType === "frame" && !this.getShapeUtil("frame").options.showColors) {
6692
- for (const style of this.styleProps[currentTool.shapeType].keys()) {
6693
- if (style.id === "tldraw:color") continue;
6694
- styles.applyValue(style, this.getStyleForNextShape(style));
6695
- }
6696
- } else {
6697
- for (const style of this.styleProps[currentTool.shapeType].keys()) {
6698
- styles.applyValue(style, this.getStyleForNextShape(style));
6699
- }
6638
+ for (const style of this.styleProps[currentTool.shapeType].keys()) {
6639
+ styles.applyValue(style, this.getStyleForNextShape(style));
6700
6640
  }
6701
6641
  }
6702
6642
  return styles;
6703
6643
  }
6704
6644
  getSharedOpacity() {
6705
6645
  if (this.isIn("select") && this.getSelectedShapeIds().length > 0) {
6706
- const shapesToCheck = [];
6707
- const addShape = (shapeId) => {
6708
- const shape = this.getShape(shapeId);
6709
- if (!shape) return;
6710
- if (this.isShapeOfType(shape, "group")) {
6711
- for (const childId of this.getSortedChildIdsForParent(shape.id)) {
6712
- addShape(childId);
6713
- }
6714
- } else {
6715
- shapesToCheck.push(shape);
6716
- }
6717
- };
6718
- for (const shapeId of this.getSelectedShapeIds()) {
6719
- addShape(shapeId);
6720
- }
6721
6646
  let opacity = null;
6722
- for (const shape of shapesToCheck) {
6647
+ for (const shape of this._getStyleableShapes(this.getSelectedShapes())) {
6723
6648
  if (opacity === null) {
6724
6649
  opacity = shape.opacity;
6725
6650
  } else if (opacity !== shape.opacity) {
@@ -6758,28 +6683,12 @@ class Editor extends EventEmitter {
6758
6683
  setOpacityForSelectedShapes(opacity) {
6759
6684
  const selectedShapes = this.getSelectedShapes();
6760
6685
  if (selectedShapes.length > 0) {
6761
- const shapesToUpdate = [];
6762
- const addShapeById = (shape) => {
6763
- if (this.isShapeOfType(shape, "group")) {
6764
- const childIds = this.getSortedChildIdsForParent(shape);
6765
- for (const childId of childIds) {
6766
- addShapeById(this.getShape(childId));
6767
- }
6768
- } else {
6769
- shapesToUpdate.push(shape);
6770
- }
6771
- };
6772
- for (const id of selectedShapes) {
6773
- addShapeById(id);
6774
- }
6775
6686
  this.updateShapes(
6776
- shapesToUpdate.map((shape) => {
6777
- return {
6778
- id: shape.id,
6779
- type: shape.type,
6780
- opacity
6781
- };
6782
- })
6687
+ this._getStyleableShapes(selectedShapes).map((shape) => ({
6688
+ id: shape.id,
6689
+ type: shape.type,
6690
+ opacity
6691
+ }))
6783
6692
  );
6784
6693
  }
6785
6694
  return this;
@@ -6824,33 +6733,17 @@ class Editor extends EventEmitter {
6824
6733
  const selectedShapes = this.getSelectedShapes();
6825
6734
  if (selectedShapes.length > 0) {
6826
6735
  const updates = [];
6827
- const addShapeById = (shape) => {
6828
- if (this.isShapeOfType(shape, "group")) {
6829
- const childIds = this.getSortedChildIdsForParent(shape.id);
6830
- for (const childId of childIds) {
6831
- addShapeById(this.getShape(childId));
6832
- }
6833
- } else {
6834
- const util = this.getShapeUtil(shape);
6835
- const stylePropKey = this.styleProps[shape.type].get(style);
6836
- if (stylePropKey) {
6837
- const shapePartial = {
6838
- id: shape.id,
6839
- type: shape.type,
6840
- props: { [stylePropKey]: value }
6841
- };
6842
- updates.push({
6843
- util,
6844
- originalShape: shape,
6845
- updatePartial: shapePartial
6846
- });
6847
- }
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
+ });
6848
6744
  }
6849
- };
6850
- for (const shape of selectedShapes) {
6851
- addShapeById(shape);
6852
6745
  }
6853
- this.updateShapes(updates.map(({ updatePartial }) => updatePartial));
6746
+ this.updateShapes(updates);
6854
6747
  }
6855
6748
  return this;
6856
6749
  }
@@ -7246,7 +7139,6 @@ class Editor extends EventEmitter {
7246
7139
  if (shapeIdMap.has(newShape.parentId)) {
7247
7140
  newShape.parentId = shapeIdMap.get(oldShape.parentId);
7248
7141
  } else {
7249
- rootShapeIds.push(newShape.id);
7250
7142
  newShape.index = index;
7251
7143
  index = getIndexAbove(index);
7252
7144
  }
@@ -7271,8 +7163,11 @@ class Editor extends EventEmitter {
7271
7163
  continue;
7272
7164
  }
7273
7165
  if (asset.type === "image" && asset.props.src?.startsWith("data:image") || asset.type === "video" && asset.props.src?.startsWith("data:video")) {
7274
- assetsToUpdate.push(structuredClone(asset));
7275
- asset.props.src = null;
7166
+ assetsToUpdate.push(asset);
7167
+ const assetWithoutSrc = structuredClone(asset);
7168
+ assetWithoutSrc.props.src = null;
7169
+ assetsToCreate.push(assetWithoutSrc);
7170
+ continue;
7276
7171
  }
7277
7172
  assetsToCreate.push(asset);
7278
7173
  }
@@ -8099,7 +7994,7 @@ class Editor extends EventEmitter {
8099
7994
  if (info.type === "misc") {
8100
7995
  if (info.name === "cancel" || info.name === "complete") {
8101
7996
  this.inputs.setIsDragging(false);
8102
- if (this.inputs.getIsPanning()) {
7997
+ if (this.inputs.getIsPanning() && !this.inputs.keys.has("Space")) {
8103
7998
  this.inputs.setIsPanning(false);
8104
7999
  this.inputs.setIsSpacebarPanning(false);
8105
8000
  this.setCursor({ type: this._prevCursor, rotation: 0 });
@@ -8851,10 +8746,9 @@ function withIsolatedShapes(editor, shapeIds, callback) {
8851
8746
  }
8852
8747
  function getCameraFitXFitY(editor, cameraOptions) {
8853
8748
  if (!cameraOptions.constraints) throw Error("Should have constraints here");
8854
- const {
8855
- padding: { x: px, y: py }
8856
- } = cameraOptions.constraints;
8857
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);
8858
8752
  const bounds = Box.From(cameraOptions.constraints.bounds);
8859
8753
  const zx = (vsb.w - px * 2) / bounds.w;
8860
8754
  const zy = (vsb.h - py * 2) / bounds.h;