@tldraw/editor 5.4.0-canary.042da660fa66 → 5.4.0-canary.059d049864ed

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 (155) hide show
  1. package/dist-cjs/index.d.ts +31 -1
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/lib/components/MenuClickCapture.js +6 -8
  4. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  5. package/dist-cjs/lib/components/default-components/CanvasOverlays.js +1 -3
  6. package/dist-cjs/lib/components/default-components/CanvasOverlays.js.map +2 -2
  7. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  8. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +1 -1
  9. package/dist-cjs/lib/config/TLUserPreferences.js +6 -2
  10. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  11. package/dist-cjs/lib/config/createTLStore.js +1 -1
  12. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  13. package/dist-cjs/lib/editor/Editor.js +139 -268
  14. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  15. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +8 -8
  16. package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
  17. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +2 -1
  18. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +79 -6
  20. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +0 -2
  22. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +1 -0
  24. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  25. package/dist-cjs/lib/editor/overlays/OverlayManager.js +3 -7
  26. package/dist-cjs/lib/editor/overlays/OverlayManager.js.map +2 -2
  27. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +0 -1
  28. package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +2 -2
  29. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +3 -1
  30. package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +2 -2
  31. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +1 -0
  32. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  33. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +1 -1
  34. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  35. package/dist-cjs/lib/editor/tools/StateNode.js +3 -2
  36. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  37. package/dist-cjs/lib/globals/menus.js +9 -4
  38. package/dist-cjs/lib/globals/menus.js.map +2 -2
  39. package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -0
  40. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  41. package/dist-cjs/lib/hooks/useDocumentEvents.js +9 -7
  42. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  43. package/dist-cjs/lib/hooks/useLocalStore.js +10 -6
  44. package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
  45. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +1 -0
  46. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  47. package/dist-cjs/lib/hooks/usePeerIds.js +2 -1
  48. package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
  49. package/dist-cjs/lib/primitives/Box.js +2 -1
  50. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  51. package/dist-cjs/lib/primitives/Vec.js +4 -3
  52. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  53. package/dist-cjs/lib/primitives/utils.js +2 -1
  54. package/dist-cjs/lib/primitives/utils.js.map +2 -2
  55. package/dist-cjs/lib/utils/rotation.js +1 -9
  56. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  57. package/dist-cjs/version.js +3 -3
  58. package/dist-cjs/version.js.map +1 -1
  59. package/dist-esm/index.d.mts +31 -1
  60. package/dist-esm/index.mjs +1 -1
  61. package/dist-esm/lib/components/MenuClickCapture.mjs +6 -8
  62. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  63. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs +1 -3
  64. package/dist-esm/lib/components/default-components/CanvasOverlays.mjs.map +2 -2
  65. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  66. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +1 -1
  67. package/dist-esm/lib/config/TLUserPreferences.mjs +6 -2
  68. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  69. package/dist-esm/lib/config/createTLStore.mjs +1 -1
  70. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  71. package/dist-esm/lib/editor/Editor.mjs +147 -269
  72. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  73. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +9 -9
  74. package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
  75. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +2 -1
  76. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  77. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +81 -8
  78. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
  79. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +0 -2
  80. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
  81. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +1 -0
  82. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  83. package/dist-esm/lib/editor/overlays/OverlayManager.mjs +3 -7
  84. package/dist-esm/lib/editor/overlays/OverlayManager.mjs.map +2 -2
  85. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +0 -1
  86. package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +2 -2
  87. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +3 -1
  88. package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +2 -2
  89. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +1 -0
  90. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  91. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +1 -1
  92. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  93. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -2
  94. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  95. package/dist-esm/lib/globals/menus.mjs +9 -4
  96. package/dist-esm/lib/globals/menus.mjs.map +2 -2
  97. package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -0
  98. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  99. package/dist-esm/lib/hooks/useDocumentEvents.mjs +9 -7
  100. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  101. package/dist-esm/lib/hooks/useLocalStore.mjs +11 -7
  102. package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
  103. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +1 -0
  104. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  105. package/dist-esm/lib/hooks/usePeerIds.mjs +2 -1
  106. package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
  107. package/dist-esm/lib/primitives/Box.mjs +2 -1
  108. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  109. package/dist-esm/lib/primitives/Vec.mjs +4 -3
  110. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  111. package/dist-esm/lib/primitives/utils.mjs +2 -1
  112. package/dist-esm/lib/primitives/utils.mjs.map +2 -2
  113. package/dist-esm/lib/utils/rotation.mjs +1 -9
  114. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  115. package/dist-esm/version.mjs +3 -3
  116. package/dist-esm/version.mjs.map +1 -1
  117. package/package.json +7 -7
  118. package/src/lib/components/MenuClickCapture.tsx +19 -18
  119. package/src/lib/components/default-components/CanvasOverlays.tsx +1 -3
  120. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  121. package/src/lib/config/TLUserPreferences.test.ts +18 -1
  122. package/src/lib/config/TLUserPreferences.ts +8 -4
  123. package/src/lib/config/createTLStore.ts +1 -1
  124. package/src/lib/editor/Editor.ts +196 -341
  125. package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +9 -9
  126. package/src/lib/editor/managers/FontManager/FontManager.ts +2 -1
  127. package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +199 -3
  128. package/src/lib/editor/managers/InputsManager/InputsManager.ts +128 -9
  129. package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +0 -2
  130. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +1 -0
  131. package/src/lib/editor/overlays/OverlayManager.ts +3 -7
  132. package/src/lib/editor/overlays/strokeShapeIndicators.ts +0 -1
  133. package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +5 -1
  134. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +3 -0
  135. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -1
  136. package/src/lib/editor/tools/StateNode.test.ts +64 -0
  137. package/src/lib/editor/tools/StateNode.ts +12 -2
  138. package/src/lib/globals/menus.test.ts +33 -0
  139. package/src/lib/globals/menus.ts +11 -4
  140. package/src/lib/hooks/useCanvasEvents.ts +2 -0
  141. package/src/lib/hooks/useDocumentEvents.ts +14 -7
  142. package/src/lib/hooks/useLocalStore.ts +16 -8
  143. package/src/lib/hooks/usePassThroughWheelEvents.ts +3 -0
  144. package/src/lib/hooks/usePeerIds.ts +2 -1
  145. package/src/lib/primitives/Box.test.ts +9 -1
  146. package/src/lib/primitives/Box.ts +3 -1
  147. package/src/lib/primitives/Vec.test.ts +10 -0
  148. package/src/lib/primitives/Vec.ts +4 -4
  149. package/src/lib/primitives/utils.test.ts +10 -1
  150. package/src/lib/primitives/utils.ts +4 -1
  151. package/src/lib/test/useCanvasEvents.test.tsx +74 -0
  152. package/src/lib/test/useDocumentEvents.test.tsx +31 -0
  153. package/src/lib/test/usePassThroughWheelEvents.test.tsx +25 -2
  154. package/src/lib/utils/rotation.ts +4 -12
  155. package/src/version.ts +3 -3
@@ -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,
@@ -92,7 +93,13 @@ import { EASINGS } from "../primitives/easings.mjs";
92
93
  import { Group2d } from "../primitives/geometry/Group2d.mjs";
93
94
  import { intersectPolygonPolygon } from "../primitives/intersect.mjs";
94
95
  import { Mat } from "../primitives/Mat.mjs";
95
- 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";
96
103
  import { Vec } from "../primitives/Vec.mjs";
97
104
  import { areShapesContentEqual } from "../utils/areShapesContentEqual.mjs";
98
105
  import { dataUrlToFile } from "../utils/assets.mjs";
@@ -290,6 +297,10 @@ class Editor extends EventEmitter {
290
297
  if (!nextPageState) nextPageState = { ...prevPageState };
291
298
  nextPageState.editingShapeId = null;
292
299
  }
300
+ if (prevPageState.croppingShapeId && shapesNoLongerInPage.has(prevPageState.croppingShapeId)) {
301
+ if (!nextPageState) nextPageState = { ...prevPageState };
302
+ nextPageState.croppingShapeId = null;
303
+ }
293
304
  const hintingShapeIds = prevPageState.hintingShapeIds.filter(
294
305
  (id) => !shapesNoLongerInPage.has(id)
295
306
  );
@@ -532,10 +543,11 @@ class Editor extends EventEmitter {
532
543
  this.cancelDoubleClick();
533
544
  }
534
545
  if (prev?.selectedShapeIds !== next?.selectedShapeIds) {
546
+ const selectedShapeIds = new Set(next.selectedShapeIds);
535
547
  const filtered = next.selectedShapeIds.filter((id) => {
536
548
  let parentId = this.getShape(id)?.parentId;
537
549
  while (isShapeId(parentId)) {
538
- if (next.selectedShapeIds.includes(parentId)) {
550
+ if (selectedShapeIds.has(parentId)) {
539
551
  return false;
540
552
  }
541
553
  parentId = this.getShape(parentId)?.parentId;
@@ -619,7 +631,10 @@ class Editor extends EventEmitter {
619
631
  const mode = this.store.props.collaboration.mode;
620
632
  this.disposables.add(
621
633
  react("update collaboration mode", () => {
622
- 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
+ );
623
638
  })
624
639
  );
625
640
  }
@@ -977,7 +992,7 @@ class Editor extends EventEmitter {
977
992
  getShapeUtil(arg) {
978
993
  const type = typeof arg === "string" ? arg : arg.type;
979
994
  const shapeUtil = getOwnProperty(this.shapeUtils, type);
980
- assert(shapeUtil, `No shape util found for type "${type}"`);
995
+ if (!shapeUtil) throw new Error(`No shape util found for type "${type}"`);
981
996
  return shapeUtil;
982
997
  }
983
998
  hasShapeUtil(arg) {
@@ -1766,7 +1781,9 @@ class Editor extends EventEmitter {
1766
1781
  const selectedShapes = this.getSelectedShapes();
1767
1782
  if (!selectedShapes.length) return;
1768
1783
  const selectedShape = selectedShapes[0];
1769
- 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
+ );
1770
1787
  const sortedChildren = this._getShapesInReadingOrder(children);
1771
1788
  if (sortedChildren.length === 0) return;
1772
1789
  this._selectShapesAndZoom([sortedChildren[0].id]);
@@ -1833,18 +1850,13 @@ class Editor extends EventEmitter {
1833
1850
  * @internal
1834
1851
  */
1835
1852
  getShapesSharedRotation(shapeIds) {
1836
- let foundFirst = false;
1837
1853
  let rotation = 0;
1838
1854
  for (let i = 0, n = shapeIds.length; i < n; i++) {
1839
- const pageTransform = this.getShapePageTransform(shapeIds[i]);
1840
- if (!pageTransform) continue;
1841
- if (foundFirst) {
1842
- if (pageTransform.rotation() !== rotation) {
1843
- return 0;
1844
- }
1845
- } else {
1846
- foundFirst = true;
1847
- 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;
1848
1860
  }
1849
1861
  }
1850
1862
  return rotation;
@@ -1870,11 +1882,9 @@ class Editor extends EventEmitter {
1870
1882
  return bounds;
1871
1883
  }
1872
1884
  const boxFromRotatedVertices = Box.FromPoints(
1873
- shapeIds.flatMap((id) => {
1874
- const pageTransform = this.getShapePageTransform(id);
1875
- if (!pageTransform) return [];
1876
- return pageTransform.applyToPoints(this.getShapeGeometry(id).bounds.corners);
1877
- }).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))
1878
1888
  );
1879
1889
  boxFromRotatedVertices.point = boxFromRotatedVertices.point.rot(selectionRotation);
1880
1890
  return boxFromRotatedVertices;
@@ -1885,9 +1895,14 @@ class Editor extends EventEmitter {
1885
1895
  getSelectionRotatedScreenBounds() {
1886
1896
  const bounds = this.getSelectionRotatedPageBounds();
1887
1897
  if (!bounds) return void 0;
1888
- const { x, y } = this.pageToScreen(bounds.point);
1889
- const zoom = this.getZoomLevel();
1890
- 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
+ );
1891
1906
  }
1892
1907
  getFocusedGroupId() {
1893
1908
  return this.getCurrentPageState().focusedGroupId ?? this.getCurrentPageId();
@@ -1988,42 +2003,28 @@ class Editor extends EventEmitter {
1988
2003
  */
1989
2004
  setEditingShape(shape) {
1990
2005
  const id = typeof shape === "string" ? shape : shape?.id ?? null;
1991
- 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
+ }
1992
2015
  this.run(
1993
2016
  () => {
1994
- const prevEditingShapeId = this.getEditingShapeId();
1995
- if (prevEditingShapeId) {
1996
- const prevEditingShape = this.getShape(prevEditingShapeId);
1997
- if (prevEditingShape) {
1998
- this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape);
1999
- }
2000
- }
2001
2017
  this._updateCurrentPageState({ editingShapeId: null });
2002
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);
2003
2024
  },
2004
2025
  { history: "ignore" }
2005
2026
  );
2006
- return this;
2007
- }
2008
- if (!this.canEditShape(id)) return this;
2009
- this.run(
2010
- () => {
2011
- const prevEditingShapeId = this.getEditingShapeId();
2012
- if (prevEditingShapeId) {
2013
- const prevEditingShape = this.getShape(prevEditingShapeId);
2014
- if (prevEditingShape) {
2015
- this.getShapeUtil(prevEditingShape).onEditEnd?.(prevEditingShape);
2016
- }
2017
- }
2018
- this._updateCurrentPageState({ editingShapeId: null });
2019
- this._currentRichTextEditor.set(null);
2020
- this.select(id);
2021
- this._updateCurrentPageState({ editingShapeId: id });
2022
- const nextEditingShape = this.getShape(id);
2023
- this.getShapeUtil(nextEditingShape).onEditStart?.(nextEditingShape);
2024
- },
2025
- { history: "ignore" }
2026
- );
2027
+ });
2027
2028
  return this;
2028
2029
  }
2029
2030
  // Rich text editor
@@ -2129,19 +2130,12 @@ class Editor extends EventEmitter {
2129
2130
  * @public
2130
2131
  */
2131
2132
  setErasingShapes(shapes) {
2132
- 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);
2133
2134
  ids.sort();
2134
2135
  const erasingShapeIds = this.getErasingShapeIds();
2135
2136
  this.run(
2136
2137
  () => {
2137
- if (ids.length === erasingShapeIds.length) {
2138
- for (let i = 0; i < ids.length; i++) {
2139
- if (ids[i] !== erasingShapeIds[i]) {
2140
- this._updateCurrentPageState({ erasingShapeIds: ids });
2141
- break;
2142
- }
2143
- }
2144
- } else {
2138
+ if (!areArraysShallowEqual(ids, erasingShapeIds)) {
2145
2139
  this._updateCurrentPageState({ erasingShapeIds: ids });
2146
2140
  }
2147
2141
  },
@@ -2174,6 +2168,7 @@ class Editor extends EventEmitter {
2174
2168
  if (!_shape) return false;
2175
2169
  const util = this.getShapeUtil(_shape);
2176
2170
  if (!util.canCrop(_shape)) return false;
2171
+ if (this.getIsReadonly()) return false;
2177
2172
  if (this.isShapeOrAncestorLocked(_shape)) return false;
2178
2173
  return true;
2179
2174
  }
@@ -2238,11 +2233,11 @@ class Editor extends EventEmitter {
2238
2233
  const collaborators = this.getCollaborators();
2239
2234
  let leaderPresence = null;
2240
2235
  while (targetUserId && !visited.includes(targetUserId)) {
2241
- leaderPresence = collaborators.find((c) => c.userId === targetUserId) ?? null;
2242
- targetUserId = leaderPresence?.followingUserId ?? null;
2243
- if (leaderPresence) {
2244
- visited.push(leaderPresence.userId);
2245
- }
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);
2246
2241
  }
2247
2242
  return leaderPresence;
2248
2243
  }
@@ -2305,39 +2300,7 @@ class Editor extends EventEmitter {
2305
2300
  *
2306
2301
  * @public */
2307
2302
  getInitialZoom() {
2308
- const cameraOptions = this.getCameraOptions();
2309
- if (!cameraOptions.constraints) return 1;
2310
- if (cameraOptions.constraints.initialZoom === "default") return 1;
2311
- const { zx, zy } = getCameraFitXFitY(this, cameraOptions);
2312
- switch (cameraOptions.constraints.initialZoom) {
2313
- case "fit-min": {
2314
- return Math.max(zx, zy);
2315
- }
2316
- case "fit-max": {
2317
- return Math.min(zx, zy);
2318
- }
2319
- case "fit-x": {
2320
- return zx;
2321
- }
2322
- case "fit-y": {
2323
- return zy;
2324
- }
2325
- case "fit-min-100": {
2326
- return Math.min(1, Math.max(zx, zy));
2327
- }
2328
- case "fit-max-100": {
2329
- return Math.min(1, Math.min(zx, zy));
2330
- }
2331
- case "fit-x-100": {
2332
- return Math.min(1, zx);
2333
- }
2334
- case "fit-y-100": {
2335
- return Math.min(1, zy);
2336
- }
2337
- default: {
2338
- throw exhaustiveSwitchError(cameraOptions.constraints.initialZoom);
2339
- }
2340
- }
2303
+ return this._getFitZoom(this.getCameraOptions().constraints?.initialZoom ?? "default");
2341
2304
  }
2342
2305
  /**
2343
2306
  * Get the camera's base level for calculating actual zoom levels based on the zoom steps.
@@ -2349,11 +2312,13 @@ class Editor extends EventEmitter {
2349
2312
  *
2350
2313
  * @public */
2351
2314
  getBaseZoom() {
2315
+ return this._getFitZoom(this.getCameraOptions().constraints?.baseZoom ?? "default");
2316
+ }
2317
+ _getFitZoom(fit) {
2352
2318
  const cameraOptions = this.getCameraOptions();
2353
- if (!cameraOptions.constraints) return 1;
2354
- if (cameraOptions.constraints.baseZoom === "default") return 1;
2319
+ if (!cameraOptions.constraints || fit === "default") return 1;
2355
2320
  const { zx, zy } = getCameraFitXFitY(this, cameraOptions);
2356
- switch (cameraOptions.constraints.baseZoom) {
2321
+ switch (fit) {
2357
2322
  case "fit-min": {
2358
2323
  return Math.max(zx, zy);
2359
2324
  }
@@ -2379,7 +2344,7 @@ class Editor extends EventEmitter {
2379
2344
  return Math.min(1, zy);
2380
2345
  }
2381
2346
  default: {
2382
- throw exhaustiveSwitchError(cameraOptions.constraints.baseZoom);
2347
+ throw exhaustiveSwitchError(fit);
2383
2348
  }
2384
2349
  }
2385
2350
  }
@@ -2413,7 +2378,7 @@ class Editor extends EventEmitter {
2413
2378
  ...this._cameraOptions.__unsafe__getWithoutCapture(),
2414
2379
  ...opts
2415
2380
  });
2416
- if (next.zoomSteps?.length < 1) next.zoomSteps = [1];
2381
+ if (!next.zoomSteps || next.zoomSteps.length < 1) next.zoomSteps = [1];
2417
2382
  this._cameraOptions.set(next);
2418
2383
  this.setCamera(this.getCamera());
2419
2384
  return this;
@@ -2873,9 +2838,9 @@ class Editor extends EventEmitter {
2873
2838
  }
2874
2839
  const remaining = duration - elapsed;
2875
2840
  const t = easing(1 - remaining / duration);
2876
- const left = start.minX + (end.minX - start.minX) * t;
2877
- const top = start.minY + (end.minY - start.minY) * t;
2878
- 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);
2879
2844
  this._setCamera(new Vec(-left, -top, this.getViewportScreenBounds().width / (right - left)), {
2880
2845
  force: true
2881
2846
  });
@@ -2953,7 +2918,7 @@ class Editor extends EventEmitter {
2953
2918
  let newCy = cy + dy;
2954
2919
  let newCz = cz;
2955
2920
  if (dirZ !== 0) {
2956
- newCz = cz * (1 + dirZ * currentSpeed * elapsed);
2921
+ newCz = cz * Math.exp(dirZ * currentSpeed * elapsed);
2957
2922
  const center = this.getViewportScreenCenter();
2958
2923
  newCx += center.x / newCz - center.x / cz;
2959
2924
  newCy += center.y / newCz - center.y / cz;
@@ -3391,12 +3356,12 @@ class Editor extends EventEmitter {
3391
3356
  const renderingShapes = [];
3392
3357
  let nextIndex = this.options.maxShapesPerPage * 2;
3393
3358
  let nextBackgroundIndex = this.options.maxShapesPerPage;
3394
- const erasingShapeIds = this.getErasingShapeIds();
3359
+ const erasingShapeIds = new Set(this.getErasingShapeIds());
3395
3360
  const addShapeById = (id, opacity, isAncestorErasing) => {
3396
3361
  const shape = this.getShape(id);
3397
3362
  if (!shape) return;
3398
3363
  if (this.isShapeHidden(shape)) {
3399
- const isErasing = isAncestorErasing || erasingShapeIds.includes(id);
3364
+ const isErasing = isAncestorErasing || erasingShapeIds.has(id);
3400
3365
  for (const childId of this.getSortedChildIdsForParent(id)) {
3401
3366
  addShapeById(childId, opacity, isErasing);
3402
3367
  }
@@ -3406,7 +3371,7 @@ class Editor extends EventEmitter {
3406
3371
  let isShapeErasing = false;
3407
3372
  const util = this.getShapeUtil(shape);
3408
3373
  if (useEditorState) {
3409
- isShapeErasing = !isAncestorErasing && erasingShapeIds.includes(id);
3374
+ isShapeErasing = !isAncestorErasing && erasingShapeIds.has(id);
3410
3375
  if (isShapeErasing) {
3411
3376
  opacity *= 0.32;
3412
3377
  }
@@ -3998,10 +3963,8 @@ class Editor extends EventEmitter {
3998
3963
  }
3999
3964
  _getShapePageBoundsCache() {
4000
3965
  return this.store.createComputedCache("pageBoundsCache", (shape) => {
4001
- const pageTransform = this.getShapePageTransform(shape);
4002
- if (!pageTransform) return void 0;
4003
3966
  return Box.FromPoints(
4004
- pageTransform.applyToPoints(this.getShapeGeometry(shape).boundsVertices)
3967
+ this.getShapePageTransform(shape).applyToPoints(this.getShapeGeometry(shape).boundsVertices)
4005
3968
  );
4006
3969
  });
4007
3970
  }
@@ -4550,9 +4513,7 @@ class Editor extends EventEmitter {
4550
4513
  const freshShape = this.getShape(id);
4551
4514
  if (!freshShape) return new Vec(0, 0);
4552
4515
  if (isPageId(freshShape.parentId)) return Vec.From(point);
4553
- const parentTransform = this.getShapePageTransform(freshShape.parentId);
4554
- if (!parentTransform) return Vec.From(point);
4555
- return parentTransform.clone().invert().applyToPoint(point);
4516
+ return this.getShapePageTransform(freshShape.parentId).clone().invert().applyToPoint(point);
4556
4517
  }
4557
4518
  getCurrentPageShapes() {
4558
4519
  return Array.from(this.getCurrentPageShapeIds(), (id) => this.store.get(id));
@@ -4664,23 +4625,7 @@ class Editor extends EventEmitter {
4664
4625
  * @public
4665
4626
  */
4666
4627
  isShapeInPage(shape, pageId = this.getCurrentPageId()) {
4667
- const id = typeof shape === "string" ? shape : shape.id;
4668
- const shapeToCheck = this.getShape(id);
4669
- if (!shapeToCheck) return false;
4670
- let shapeIsInPage = false;
4671
- if (shapeToCheck.parentId === pageId) {
4672
- shapeIsInPage = true;
4673
- } else {
4674
- let parent = this.getShape(shapeToCheck.parentId);
4675
- isInPageSearch: while (parent) {
4676
- if (parent.parentId === pageId) {
4677
- shapeIsInPage = true;
4678
- break isInPageSearch;
4679
- }
4680
- parent = this.getShape(parent.parentId);
4681
- }
4682
- }
4683
- return shapeIsInPage;
4628
+ return this.getAncestorPageId(shape) === pageId;
4684
4629
  }
4685
4630
  /**
4686
4631
  * Get the id of the containing page for a given shape.
@@ -4761,10 +4706,7 @@ class Editor extends EventEmitter {
4761
4706
  for (let i = 0; i < shapesToReparent.length; i++) {
4762
4707
  const shape = shapesToReparent[i];
4763
4708
  const pageTransform = this.getShapePageTransform(shape);
4764
- if (!pageTransform) continue;
4765
- const pagePoint = pageTransform.point();
4766
- if (!pagePoint) continue;
4767
- const newPoint = invertedParentTransform.applyToPoint(pagePoint);
4709
+ const newPoint = invertedParentTransform.applyToPoint(pageTransform.point());
4768
4710
  const newRotation = pageTransform.rotation() - parentPageRotation;
4769
4711
  if (shape.id === parentId) {
4770
4712
  throw Error("Attempted to reparent a shape to itself!");
@@ -4853,7 +4795,7 @@ class Editor extends EventEmitter {
4853
4795
  */
4854
4796
  getShapeAndDescendantIds(ids) {
4855
4797
  const shapeIds = /* @__PURE__ */ new Set();
4856
- for (const shape of ids.map((id) => this.getShape(id)).sort(sortByIndex)) {
4798
+ for (const shape of compact(ids.map((id) => this.getShape(id))).sort(sortByIndex)) {
4857
4799
  shapeIds.add(shape.id);
4858
4800
  this.visitDescendants(shape, (descendantId) => {
4859
4801
  shapeIds.add(descendantId);
@@ -4875,11 +4817,12 @@ class Editor extends EventEmitter {
4875
4817
  const draggingShapes = compact(droppingShapes.map((s) => this.getShape(s))).filter(
4876
4818
  (s) => !s.isLocked && !this.isShapeHidden(s)
4877
4819
  );
4820
+ const excludedIds = this.getShapeAndDescendantIds(draggingShapes.map((s) => s.id));
4878
4821
  const maybeDraggingOverShapes = this.getShapesAtPoint(point, {
4879
4822
  hitInside: true,
4880
4823
  margin: 0
4881
4824
  }).filter(
4882
- (s) => !droppingShapes.includes(s) && !s.isLocked && !this.isShapeHidden(s) && !draggingShapes.includes(s)
4825
+ (s) => !droppingShapes.includes(s) && !s.isLocked && !this.isShapeHidden(s) && !excludedIds.has(s.id)
4883
4826
  );
4884
4827
  for (const maybeDraggingOverShape of maybeDraggingOverShapes) {
4885
4828
  const shapeUtil = this.getShapeUtil(maybeDraggingOverShape);
@@ -5132,8 +5075,7 @@ class Editor extends EventEmitter {
5132
5075
  for (const id of ids) {
5133
5076
  const shape = this.getShape(id);
5134
5077
  const localDelta = Vec.From(offset);
5135
- const parentTransform = this.getShapeParentTransform(shape);
5136
- if (parentTransform) localDelta.rot(-parentTransform.rotation());
5078
+ localDelta.rot(-this.getShapeParentTransform(shape).rotation());
5137
5079
  changes.push(this.getChangesToTranslateShape(shape, localDelta.add(shape)));
5138
5080
  }
5139
5081
  this.updateShapes(changes);
@@ -5188,7 +5130,7 @@ class Editor extends EventEmitter {
5188
5130
  if (!originalShape) continue;
5189
5131
  let ox = 0;
5190
5132
  let oy = 0;
5191
- if (offset && initialIds.has(originalId)) {
5133
+ if (offset && initialIds.has(originalId) && !shapeIdSet.has(originalShape.parentId)) {
5192
5134
  const parentTransform = this.getShapeParentTransform(originalShape);
5193
5135
  const vec = new Vec(offset.x, offset.y).rot(-parentTransform.rotation());
5194
5136
  ox = vec.x;
@@ -5302,26 +5244,17 @@ class Editor extends EventEmitter {
5302
5244
  toggleLock(shapes) {
5303
5245
  const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5304
5246
  if (this.getIsReadonly() || ids.length === 0) return this;
5305
- let allLocked = true, allUnlocked = true;
5247
+ let allLocked = true;
5306
5248
  const shapesToToggle = [];
5307
5249
  for (const id of ids) {
5308
5250
  const shape = this.getShape(id);
5309
5251
  if (shape) {
5310
5252
  shapesToToggle.push(shape);
5311
- if (shape.isLocked) {
5312
- allUnlocked = false;
5313
- } else {
5314
- allLocked = false;
5315
- }
5253
+ if (!shape.isLocked) allLocked = false;
5316
5254
  }
5317
5255
  }
5318
5256
  this.run(() => {
5319
- if (allUnlocked) {
5320
- this.updateShapes(
5321
- shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
5322
- );
5323
- this.setSelectedShapes([]);
5324
- } else if (allLocked) {
5257
+ if (allLocked) {
5325
5258
  this.updateShapes(
5326
5259
  shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: false }))
5327
5260
  );
@@ -5329,6 +5262,7 @@ class Editor extends EventEmitter {
5329
5262
  this.updateShapes(
5330
5263
  shapesToToggle.map((shape) => ({ id: shape.id, type: shape.type, isLocked: true }))
5331
5264
  );
5265
+ this.setSelectedShapes([]);
5332
5266
  }
5333
5267
  });
5334
5268
  return this;
@@ -5439,9 +5373,10 @@ class Editor extends EventEmitter {
5439
5373
  const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5440
5374
  let freshShapes = compact(ids.map((id) => this.getShape(id)));
5441
5375
  if (opts?.filterAxisAligned) {
5442
- freshShapes = freshShapes.filter(
5443
- (s) => this.getShapePageTransform(s)?.rotation() % (PI / 2) === 0
5444
- );
5376
+ freshShapes = freshShapes.filter((s) => {
5377
+ const remainder = Math.abs(this.getShapePageTransform(s).rotation() % HALF_PI);
5378
+ return Math.min(remainder, HALF_PI - remainder) < 1e-9;
5379
+ });
5445
5380
  }
5446
5381
  const clusters = [];
5447
5382
  const allBounds = [];
@@ -5606,9 +5541,9 @@ class Editor extends EventEmitter {
5606
5541
  dim = "height";
5607
5542
  }
5608
5543
  let shapeGap = 0;
5544
+ shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min]);
5609
5545
  if (_gap === 0) {
5610
5546
  const gaps = {};
5611
- shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min]);
5612
5547
  for (let i = 0; i < len - 1; i++) {
5613
5548
  const currCluster = shapeClustersToStack[i];
5614
5549
  const nextCluster = shapeClustersToStack[i + 1];
@@ -5646,8 +5581,7 @@ class Editor extends EventEmitter {
5646
5581
  const shapeDelta = delta.clone();
5647
5582
  const parent = this.getShapeParent(shape);
5648
5583
  if (parent) {
5649
- const parentTransform = this.getShapePageTransform(parent);
5650
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5584
+ shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5651
5585
  }
5652
5586
  shapeDelta.add(shape);
5653
5587
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5685,7 +5619,7 @@ class Editor extends EventEmitter {
5685
5619
  }
5686
5620
  const commonBounds = Box.Common(allBounds);
5687
5621
  const maxWidth = commonBounds.width;
5688
- shapeClustersToPack.sort((a, b) => a.pageBounds.width - b.pageBounds.width).sort((a, b) => a.pageBounds.height - b.pageBounds.height);
5622
+ shapeClustersToPack.sort((a, b) => b.pageBounds.width - a.pageBounds.width).sort((a, b) => b.pageBounds.height - a.pageBounds.height);
5689
5623
  const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);
5690
5624
  const spaces = [new Box(commonBounds.x, commonBounds.y, startWidth, Infinity)];
5691
5625
  let width = 0;
@@ -5733,8 +5667,7 @@ class Editor extends EventEmitter {
5733
5667
  const shapeDelta = delta.clone();
5734
5668
  const parent = this.getShapeParent(shape);
5735
5669
  if (parent) {
5736
- const parentTransform = this.getShapeParentTransform(shape);
5737
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5670
+ shapeDelta.rot(-this.getShapeParentTransform(shape).rotation());
5738
5671
  }
5739
5672
  shapeDelta.add(shape);
5740
5673
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5800,8 +5733,7 @@ class Editor extends EventEmitter {
5800
5733
  const shapeDelta = delta.clone();
5801
5734
  const parent = this.getShapeParent(shape);
5802
5735
  if (parent) {
5803
- const parentTransform = this.getShapePageTransform(parent);
5804
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5736
+ shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5805
5737
  }
5806
5738
  shapeDelta.add(shape);
5807
5739
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5875,8 +5807,7 @@ class Editor extends EventEmitter {
5875
5807
  const shapeDelta = delta.clone();
5876
5808
  const parent = this.getShapeParent(shape);
5877
5809
  if (parent) {
5878
- const parentTransform = this.getShapePageTransform(parent);
5879
- if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5810
+ shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5880
5811
  }
5881
5812
  shapeDelta.add(shape);
5882
5813
  changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
@@ -5931,8 +5862,7 @@ class Editor extends EventEmitter {
5931
5862
  scale[val] = commonBounds[dim] / pageBounds[dim];
5932
5863
  for (const shape of shapes2) {
5933
5864
  const shapeLocalOffset = localOffset.clone();
5934
- const parentTransform = this.getShapeParentTransform(shape);
5935
- if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation());
5865
+ shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation());
5936
5866
  shapeLocalOffset.add(shape);
5937
5867
  const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5938
5868
  this.updateShape(changes);
@@ -5988,8 +5918,7 @@ class Editor extends EventEmitter {
5988
5918
  );
5989
5919
  for (const shape of shapes2) {
5990
5920
  const shapeLocalOffset = localOffset.clone();
5991
- const parentTransform = this.getShapeParentTransform(shape);
5992
- if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation());
5921
+ shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation());
5993
5922
  shapeLocalOffset.add(shape);
5994
5923
  const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5995
5924
  this.updateShape(changes);
@@ -6038,9 +5967,7 @@ class Editor extends EventEmitter {
6038
5967
  const scaleOrigin = opts.scaleOrigin ?? this.getShapePageBounds(id)?.center;
6039
5968
  if (!scaleOrigin) return null;
6040
5969
  const pageTransform = opts.initialPageTransform ? Mat.Cast(opts.initialPageTransform) : this.getShapePageTransform(id);
6041
- if (!pageTransform) return null;
6042
5970
  const pageRotation = pageTransform.rotation();
6043
- if (pageRotation == null) return null;
6044
5971
  const scaleAxisRotation = opts.scaleAxisRotation ?? pageRotation;
6045
5972
  const initialBounds = opts.initialBounds ?? this.getShapeGeometry(id).bounds;
6046
5973
  if (!initialBounds) return null;
@@ -6191,7 +6118,6 @@ class Editor extends EventEmitter {
6191
6118
  const currentLocalBounds = this.getShapeGeometry(id).bounds;
6192
6119
  const currentPageCenter = Mat.applyToPoint(pageTransform, currentLocalBounds.center);
6193
6120
  const shapePageTransformOrigin = pageTransform.point();
6194
- if (!currentPageCenter || !shapePageTransformOrigin) return this;
6195
6121
  const pageDelta = Vec.Sub(postScaleShapePageCenter, currentPageCenter);
6196
6122
  const postScaleShapePagePoint = Vec.Add(shapePageTransformOrigin, pageDelta);
6197
6123
  const { x, y } = this.getPointInParentSpace(id, postScaleShapePagePoint);
@@ -6450,10 +6376,10 @@ class Editor extends EventEmitter {
6450
6376
  if (animationIdForShape !== animationId) continue;
6451
6377
  updates.push({
6452
6378
  ...end,
6453
- x: start.x + (end.x - start.x) * t,
6454
- y: start.y + (end.y - start.y) * t,
6455
- opacity: start.opacity + (end.opacity - start.opacity) * t,
6456
- rotation: start.rotation + (end.rotation - start.rotation) * t,
6379
+ x: lerp(start.x, end.x, t),
6380
+ y: lerp(start.y, end.y, t),
6381
+ opacity: lerp(start.opacity, end.opacity, t),
6382
+ rotation: lerp(start.rotation, end.rotation, t),
6457
6383
  props: this.getShapeUtil(end).getInterpolatedProps?.(start, end, t) ?? end.props
6458
6384
  });
6459
6385
  }
@@ -6579,7 +6505,7 @@ class Editor extends EventEmitter {
6579
6505
  * @public
6580
6506
  */
6581
6507
  updateShapes(partials) {
6582
- const compactedPartials = Array(partials.length);
6508
+ const compactedPartials = [];
6583
6509
  for (let i = 0, n = partials.length; i < n; i++) {
6584
6510
  const partial = partials[i];
6585
6511
  if (!partial) continue;
@@ -6650,28 +6576,32 @@ class Editor extends EventEmitter {
6650
6576
  }
6651
6577
  /* --------------------- Styles --------------------- */
6652
6578
  /**
6653
- * Get all the current styles among the users selected shapes
6579
+ * Groups have no styles of their own: a style read or write on a selection applies to the
6580
+ * non-group shapes beneath each group, however deeply nested. Returns those shapes.
6654
6581
  *
6655
6582
  * @internal
6656
6583
  */
6657
- _extractSharedStyles(shape, sharedStyleMap) {
6658
- if (this.isShapeOfType(shape, "group")) {
6659
- const childIds = this._parentIdsToChildIds.get()[shape.id];
6660
- if (!childIds) return;
6661
- for (let i = 0, n = childIds.length; i < n; i++) {
6662
- this._extractSharedStyles(this.getShape(childIds[i]), sharedStyleMap);
6663
- }
6664
- } else {
6665
- for (const [style, propKey] of this.styleProps[shape.type]) {
6666
- sharedStyleMap.applyValue(style, getOwnProperty(shape.props, propKey));
6584
+ _getStyleableShapes(shapes) {
6585
+ const result = [];
6586
+ const visit = (shape) => {
6587
+ if (this.isShapeOfType(shape, "group")) {
6588
+ for (const childId of this.getSortedChildIdsForParent(shape.id)) {
6589
+ const child = this.getShape(childId);
6590
+ if (child) visit(child);
6591
+ }
6592
+ } else {
6593
+ result.push(shape);
6667
6594
  }
6668
- }
6595
+ };
6596
+ for (const shape of shapes) visit(shape);
6597
+ return result;
6669
6598
  }
6670
6599
  _getSelectionSharedStyles() {
6671
- const selectedShapes = this.getSelectedShapes();
6672
6600
  const sharedStyles = new SharedStyleMap();
6673
- for (const selectedShape of selectedShapes) {
6674
- this._extractSharedStyles(selectedShape, sharedStyles);
6601
+ for (const shape of this._getStyleableShapes(this.getSelectedShapes())) {
6602
+ for (const [style, propKey] of this.styleProps[shape.type]) {
6603
+ sharedStyles.applyValue(style, getOwnProperty(shape.props, propKey));
6604
+ }
6675
6605
  }
6676
6606
  return sharedStyles;
6677
6607
  }
@@ -6703,38 +6633,16 @@ class Editor extends EventEmitter {
6703
6633
  const styles = new SharedStyleMap();
6704
6634
  if (!currentTool) return styles;
6705
6635
  if (currentTool.shapeType) {
6706
- if (currentTool.shapeType === "frame" && !this.getShapeUtil("frame").options.showColors) {
6707
- for (const style of this.styleProps[currentTool.shapeType].keys()) {
6708
- if (style.id === "tldraw:color") continue;
6709
- styles.applyValue(style, this.getStyleForNextShape(style));
6710
- }
6711
- } else {
6712
- for (const style of this.styleProps[currentTool.shapeType].keys()) {
6713
- styles.applyValue(style, this.getStyleForNextShape(style));
6714
- }
6636
+ for (const style of this.styleProps[currentTool.shapeType].keys()) {
6637
+ styles.applyValue(style, this.getStyleForNextShape(style));
6715
6638
  }
6716
6639
  }
6717
6640
  return styles;
6718
6641
  }
6719
6642
  getSharedOpacity() {
6720
6643
  if (this.isIn("select") && this.getSelectedShapeIds().length > 0) {
6721
- const shapesToCheck = [];
6722
- const addShape = (shapeId) => {
6723
- const shape = this.getShape(shapeId);
6724
- if (!shape) return;
6725
- if (this.isShapeOfType(shape, "group")) {
6726
- for (const childId of this.getSortedChildIdsForParent(shape.id)) {
6727
- addShape(childId);
6728
- }
6729
- } else {
6730
- shapesToCheck.push(shape);
6731
- }
6732
- };
6733
- for (const shapeId of this.getSelectedShapeIds()) {
6734
- addShape(shapeId);
6735
- }
6736
6644
  let opacity = null;
6737
- for (const shape of shapesToCheck) {
6645
+ for (const shape of this._getStyleableShapes(this.getSelectedShapes())) {
6738
6646
  if (opacity === null) {
6739
6647
  opacity = shape.opacity;
6740
6648
  } else if (opacity !== shape.opacity) {
@@ -6773,28 +6681,12 @@ class Editor extends EventEmitter {
6773
6681
  setOpacityForSelectedShapes(opacity) {
6774
6682
  const selectedShapes = this.getSelectedShapes();
6775
6683
  if (selectedShapes.length > 0) {
6776
- const shapesToUpdate = [];
6777
- const addShapeById = (shape) => {
6778
- if (this.isShapeOfType(shape, "group")) {
6779
- const childIds = this.getSortedChildIdsForParent(shape);
6780
- for (const childId of childIds) {
6781
- addShapeById(this.getShape(childId));
6782
- }
6783
- } else {
6784
- shapesToUpdate.push(shape);
6785
- }
6786
- };
6787
- for (const id of selectedShapes) {
6788
- addShapeById(id);
6789
- }
6790
6684
  this.updateShapes(
6791
- shapesToUpdate.map((shape) => {
6792
- return {
6793
- id: shape.id,
6794
- type: shape.type,
6795
- opacity
6796
- };
6797
- })
6685
+ this._getStyleableShapes(selectedShapes).map((shape) => ({
6686
+ id: shape.id,
6687
+ type: shape.type,
6688
+ opacity
6689
+ }))
6798
6690
  );
6799
6691
  }
6800
6692
  return this;
@@ -6839,33 +6731,17 @@ class Editor extends EventEmitter {
6839
6731
  const selectedShapes = this.getSelectedShapes();
6840
6732
  if (selectedShapes.length > 0) {
6841
6733
  const updates = [];
6842
- const addShapeById = (shape) => {
6843
- if (this.isShapeOfType(shape, "group")) {
6844
- const childIds = this.getSortedChildIdsForParent(shape.id);
6845
- for (const childId of childIds) {
6846
- addShapeById(this.getShape(childId));
6847
- }
6848
- } else {
6849
- const util = this.getShapeUtil(shape);
6850
- const stylePropKey = this.styleProps[shape.type].get(style);
6851
- if (stylePropKey) {
6852
- const shapePartial = {
6853
- id: shape.id,
6854
- type: shape.type,
6855
- props: { [stylePropKey]: value }
6856
- };
6857
- updates.push({
6858
- util,
6859
- originalShape: shape,
6860
- updatePartial: shapePartial
6861
- });
6862
- }
6734
+ for (const shape of this._getStyleableShapes(selectedShapes)) {
6735
+ const stylePropKey = this.styleProps[shape.type].get(style);
6736
+ if (stylePropKey) {
6737
+ updates.push({
6738
+ id: shape.id,
6739
+ type: shape.type,
6740
+ props: { [stylePropKey]: value }
6741
+ });
6863
6742
  }
6864
- };
6865
- for (const shape of selectedShapes) {
6866
- addShapeById(shape);
6867
6743
  }
6868
- this.updateShapes(updates.map(({ updatePartial }) => updatePartial));
6744
+ this.updateShapes(updates);
6869
6745
  }
6870
6746
  return this;
6871
6747
  }
@@ -7261,7 +7137,6 @@ class Editor extends EventEmitter {
7261
7137
  if (shapeIdMap.has(newShape.parentId)) {
7262
7138
  newShape.parentId = shapeIdMap.get(oldShape.parentId);
7263
7139
  } else {
7264
- rootShapeIds.push(newShape.id);
7265
7140
  newShape.index = index;
7266
7141
  index = getIndexAbove(index);
7267
7142
  }
@@ -7286,8 +7161,11 @@ class Editor extends EventEmitter {
7286
7161
  continue;
7287
7162
  }
7288
7163
  if (asset.type === "image" && asset.props.src?.startsWith("data:image") || asset.type === "video" && asset.props.src?.startsWith("data:video")) {
7289
- assetsToUpdate.push(structuredClone(asset));
7290
- asset.props.src = null;
7164
+ assetsToUpdate.push(asset);
7165
+ const assetWithoutSrc = structuredClone(asset);
7166
+ assetWithoutSrc.props.src = null;
7167
+ assetsToCreate.push(assetWithoutSrc);
7168
+ continue;
7291
7169
  }
7292
7170
  assetsToCreate.push(asset);
7293
7171
  }