@tldraw/editor 5.5.0-next.f9308d324e1c → 5.6.0-canary.5bfc37740468

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 (203) hide show
  1. package/dist-cjs/index.d.ts +49 -23
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +16 -12
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/MenuClickCapture.js +42 -3
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +5 -1
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +2 -5
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +3 -3
  12. package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +2 -2
  13. package/dist-cjs/lib/editor/Editor.js +183 -185
  14. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  15. package/dist-cjs/lib/editor/derivations/bindingsIndex.js +2 -0
  16. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  17. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +3 -2
  18. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/EditorManager.js +11 -2
  20. package/dist-cjs/lib/editor/managers/EditorManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js +49 -63
  22. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +1 -1
  24. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
  25. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +24 -24
  26. package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
  27. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js +7 -1
  28. package/dist-cjs/lib/editor/managers/SnapManager/HandleSnaps.js.map +2 -2
  29. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js +4 -3
  30. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js.map +2 -2
  31. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +4 -4
  32. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  33. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +14 -6
  34. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +3 -3
  35. package/dist-cjs/lib/editor/shapes/shared/resizeBox.js +21 -16
  36. package/dist-cjs/lib/editor/shapes/shared/resizeBox.js.map +2 -2
  37. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  38. package/dist-cjs/lib/exports/ExportDelay.js +10 -2
  39. package/dist-cjs/lib/exports/ExportDelay.js.map +2 -2
  40. package/dist-cjs/lib/exports/FontEmbedder.js +25 -11
  41. package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
  42. package/dist-cjs/lib/exports/StyleEmbedder.js +9 -4
  43. package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
  44. package/dist-cjs/lib/exports/fetchCache.js +3 -0
  45. package/dist-cjs/lib/exports/fetchCache.js.map +2 -2
  46. package/dist-cjs/lib/exports/getSvgAsImage.js +10 -4
  47. package/dist-cjs/lib/exports/getSvgAsImage.js.map +2 -2
  48. package/dist-cjs/lib/exports/getSvgJsx.js +18 -9
  49. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  50. package/dist-cjs/lib/exports/parseCss.js +1 -1
  51. package/dist-cjs/lib/exports/parseCss.js.map +1 -1
  52. package/dist-cjs/lib/hooks/EditorComponentsContext.js.map +2 -2
  53. package/dist-cjs/lib/hooks/useCanvasEvents.js +24 -0
  54. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  55. package/dist-cjs/lib/hooks/useCursor.js +12 -8
  56. package/dist-cjs/lib/hooks/useCursor.js.map +2 -2
  57. package/dist-cjs/lib/hooks/useIdentity.js +6 -2
  58. package/dist-cjs/lib/hooks/useIdentity.js.map +3 -3
  59. package/dist-cjs/lib/license/LicenseManager.js +9 -1
  60. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  61. package/dist-cjs/lib/license/LicenseProvider.js +12 -4
  62. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  63. package/dist-cjs/lib/utils/debug-flags.js +0 -6
  64. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  65. package/dist-cjs/lib/utils/deepLinks.js +2 -1
  66. package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
  67. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js +32 -25
  68. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js.map +2 -2
  69. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +88 -40
  70. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  71. package/dist-cjs/version.js +3 -3
  72. package/dist-cjs/version.js.map +1 -1
  73. package/dist-esm/index.d.mts +49 -23
  74. package/dist-esm/index.mjs +4 -2
  75. package/dist-esm/index.mjs.map +2 -2
  76. package/dist-esm/lib/TldrawEditor.mjs +17 -13
  77. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  78. package/dist-esm/lib/components/MenuClickCapture.mjs +42 -3
  79. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  80. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +5 -1
  81. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  82. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +2 -5
  83. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +3 -3
  84. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  85. package/dist-esm/lib/editor/Editor.mjs +185 -186
  86. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  87. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +2 -0
  88. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  89. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +3 -2
  90. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  91. package/dist-esm/lib/editor/managers/EditorManager.mjs +11 -2
  92. package/dist-esm/lib/editor/managers/EditorManager.mjs.map +2 -2
  93. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs +49 -63
  94. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs.map +2 -2
  95. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +1 -1
  96. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
  97. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +24 -24
  98. package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
  99. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs +7 -1
  100. package/dist-esm/lib/editor/managers/SnapManager/HandleSnaps.mjs.map +2 -2
  101. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs +4 -3
  102. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +2 -2
  103. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +4 -4
  104. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  105. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +14 -6
  106. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +3 -3
  107. package/dist-esm/lib/editor/shapes/shared/resizeBox.mjs +21 -16
  108. package/dist-esm/lib/editor/shapes/shared/resizeBox.mjs.map +2 -2
  109. package/dist-esm/lib/exports/ExportDelay.mjs +11 -3
  110. package/dist-esm/lib/exports/ExportDelay.mjs.map +2 -2
  111. package/dist-esm/lib/exports/FontEmbedder.mjs +25 -11
  112. package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
  113. package/dist-esm/lib/exports/StyleEmbedder.mjs +9 -4
  114. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
  115. package/dist-esm/lib/exports/fetchCache.mjs +3 -0
  116. package/dist-esm/lib/exports/fetchCache.mjs.map +2 -2
  117. package/dist-esm/lib/exports/getSvgAsImage.mjs +10 -4
  118. package/dist-esm/lib/exports/getSvgAsImage.mjs.map +2 -2
  119. package/dist-esm/lib/exports/getSvgJsx.mjs +18 -9
  120. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  121. package/dist-esm/lib/exports/parseCss.mjs +1 -1
  122. package/dist-esm/lib/exports/parseCss.mjs.map +1 -1
  123. package/dist-esm/lib/hooks/EditorComponentsContext.mjs.map +2 -2
  124. package/dist-esm/lib/hooks/useCanvasEvents.mjs +24 -0
  125. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  126. package/dist-esm/lib/hooks/useCursor.mjs +12 -8
  127. package/dist-esm/lib/hooks/useCursor.mjs.map +2 -2
  128. package/dist-esm/lib/hooks/useIdentity.mjs +7 -3
  129. package/dist-esm/lib/hooks/useIdentity.mjs.map +3 -3
  130. package/dist-esm/lib/license/LicenseManager.mjs +9 -1
  131. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  132. package/dist-esm/lib/license/LicenseProvider.mjs +13 -5
  133. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  134. package/dist-esm/lib/utils/debug-flags.mjs +0 -6
  135. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  136. package/dist-esm/lib/utils/deepLinks.mjs +2 -1
  137. package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
  138. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs +32 -25
  139. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs.map +2 -2
  140. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +89 -41
  141. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  142. package/dist-esm/version.mjs +3 -3
  143. package/dist-esm/version.mjs.map +1 -1
  144. package/editor.css +9 -1
  145. package/package.json +18 -23
  146. package/src/index.ts +5 -1
  147. package/src/lib/TldrawEditor.tsx +35 -21
  148. package/src/lib/components/MenuClickCapture.tsx +55 -4
  149. package/src/lib/components/default-components/DefaultCanvas.tsx +7 -1
  150. package/src/lib/components/default-components/DefaultErrorFallback.tsx +13 -5
  151. package/src/lib/components/default-components/DefaultSpinner.tsx +4 -1
  152. package/src/lib/editor/Editor.test.ts +65 -0
  153. package/src/lib/editor/Editor.ts +299 -303
  154. package/src/lib/editor/derivations/bindingsIndex.ts +3 -0
  155. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +1 -0
  156. package/src/lib/editor/managers/ClickManager/ClickManager.ts +3 -2
  157. package/src/lib/editor/managers/EditorManager.test.ts +32 -1
  158. package/src/lib/editor/managers/EditorManager.ts +15 -3
  159. package/src/lib/editor/managers/PerformanceManager/PerformanceManager.test.ts +80 -0
  160. package/src/lib/editor/managers/PerformanceManager/PerformanceManager.ts +54 -81
  161. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +1 -1
  162. package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +32 -36
  163. package/src/lib/editor/managers/SnapManager/HandleSnaps.ts +13 -2
  164. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +13 -0
  165. package/src/lib/editor/managers/SnapManager/SnapManager.ts +6 -4
  166. package/src/lib/editor/managers/TextManager/TextManager.test.ts +74 -0
  167. package/src/lib/editor/managers/TextManager/TextManager.ts +6 -5
  168. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +23 -8
  169. package/src/lib/editor/shapes/shared/resizeBox.ts +21 -16
  170. package/src/lib/editor/types/external-content.ts +5 -0
  171. package/src/lib/exports/ExportDelay.tsx +16 -4
  172. package/src/lib/exports/FontEmbedder.test.ts +99 -0
  173. package/src/lib/exports/FontEmbedder.ts +32 -11
  174. package/src/lib/exports/StyleEmbedder.ts +13 -4
  175. package/src/lib/exports/fetchCache.test.ts +22 -0
  176. package/src/lib/exports/fetchCache.ts +4 -0
  177. package/src/lib/exports/getSvgAsImage.ts +13 -5
  178. package/src/lib/exports/getSvgJsx.test.ts +43 -1
  179. package/src/lib/exports/getSvgJsx.tsx +28 -16
  180. package/src/lib/exports/parseCss.test.ts +4 -0
  181. package/src/lib/exports/parseCss.ts +2 -2
  182. package/src/lib/hooks/EditorComponentsContext.tsx +2 -1
  183. package/src/lib/hooks/useCanvasEvents.ts +42 -0
  184. package/src/lib/hooks/useCursor.ts +14 -7
  185. package/src/lib/hooks/useIdentity.tsx +12 -1
  186. package/src/lib/license/LicenseManager.ts +13 -1
  187. package/src/lib/license/LicenseProvider.test.tsx +76 -0
  188. package/src/lib/license/LicenseProvider.tsx +19 -5
  189. package/src/lib/test/preserveFocus.test.tsx +81 -0
  190. package/src/lib/test/useCursor.test.tsx +36 -0
  191. package/src/lib/utils/debug-flags.ts +0 -6
  192. package/src/lib/utils/deepLinks.test.ts +15 -0
  193. package/src/lib/utils/deepLinks.ts +8 -3
  194. package/src/lib/utils/sync/LocalIndexedDb.test.ts +121 -1
  195. package/src/lib/utils/sync/LocalIndexedDb.ts +41 -28
  196. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +330 -1
  197. package/src/lib/utils/sync/TLLocalSyncClient.ts +129 -55
  198. package/src/version.ts +3 -3
  199. package/dist-cjs/lib/primitives/geometry/geometry.bench.js +0 -133
  200. package/dist-cjs/lib/primitives/geometry/geometry.bench.js.map +0 -7
  201. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs +0 -132
  202. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs.map +0 -7
  203. package/src/lib/primitives/geometry/geometry.bench.ts +0 -179
@@ -95,6 +95,10 @@ var import_UserPreferencesManager = require("./managers/UserPreferencesManager/U
95
95
  var import_OverlayManager = require("./overlays/OverlayManager");
96
96
  var import_RootState = require("./tools/RootState");
97
97
  const RENDERING_SHAPES_SORT_CACHE_THRESHOLD = 100;
98
+ const AXIS = {
99
+ horizontal: { val: "x", min: "minX", max: "maxX", dim: "width" },
100
+ vertical: { val: "y", min: "minY", max: "maxY", dim: "height" }
101
+ };
98
102
  class Editor extends import_eventemitter3.default {
99
103
  id = (0, import_utils.uniqueId)();
100
104
  constructor({
@@ -421,7 +425,7 @@ class Editor extends import_eventemitter3.default {
421
425
  }
422
426
  }
423
427
  if (deleteBindingIds.length) {
424
- this.deleteBindings(deleteBindingIds);
428
+ this.store.remove(deleteBindingIds);
425
429
  }
426
430
  }
427
431
  },
@@ -1381,6 +1385,7 @@ class Editor extends import_eventemitter3.default {
1381
1385
  * @public
1382
1386
  **/
1383
1387
  updateDocumentSettings(settings) {
1388
+ if (this.getIsReadonly()) return this;
1384
1389
  this.run(
1385
1390
  () => {
1386
1391
  this.store.put([{ ...this.getDocumentSettings(), ...settings }]);
@@ -1403,7 +1408,7 @@ class Editor extends import_eventemitter3.default {
1403
1408
  updateInstanceState(partial, historyOptions) {
1404
1409
  this._updateInstanceState(partial, { history: "ignore", ...historyOptions });
1405
1410
  if (partial.isChangingStyle !== void 0) {
1406
- clearTimeout(this._isChangingStyleTimeout);
1411
+ this.timers.clearTimeout(this._isChangingStyleTimeout);
1407
1412
  if (partial.isChangingStyle === true) {
1408
1413
  this._isChangingStyleTimeout = this.timers.setTimeout(() => {
1409
1414
  this._updateInstanceState({ isChangingStyle: false }, { history: "ignore" });
@@ -1540,7 +1545,7 @@ class Editor extends import_eventemitter3.default {
1540
1545
  * @public
1541
1546
  */
1542
1547
  select(...shapes) {
1543
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((shape) => shape.id);
1548
+ const ids = toShapeIds(shapes);
1544
1549
  this.setSelectedShapes(ids);
1545
1550
  return this;
1546
1551
  }
@@ -1555,7 +1560,7 @@ class Editor extends import_eventemitter3.default {
1555
1560
  * @public
1556
1561
  */
1557
1562
  deselect(...shapes) {
1558
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((shape) => shape.id);
1563
+ const ids = toShapeIds(shapes);
1559
1564
  const selectedShapeIds = this.getSelectedShapeIds();
1560
1565
  if (selectedShapeIds.length > 0 && ids.length > 0) {
1561
1566
  this.setSelectedShapes(selectedShapeIds.filter((id) => !ids.includes(id)));
@@ -1593,7 +1598,7 @@ class Editor extends import_eventemitter3.default {
1593
1598
  }
1594
1599
  const ids = this.getSortedChildIdsForParent(parentToSelectWithinId);
1595
1600
  if (ids.length <= 0) return this;
1596
- this.setSelectedShapes(this._getUnlockedShapeIds(ids));
1601
+ this.setSelectedShapes(ids.filter((id) => !this.getShape(id)?.isLocked));
1597
1602
  return this;
1598
1603
  }
1599
1604
  /**
@@ -1610,12 +1615,15 @@ class Editor extends import_eventemitter3.default {
1610
1615
  const selectedShapeIds = this.getSelectedShapeIds();
1611
1616
  const firstParentId = selectedShapeIds[0] ? this.getShape(selectedShapeIds[0])?.parentId : null;
1612
1617
  const isSelectedWithinContainer = firstParentId && selectedShapeIds.every((shapeId) => this.getShape(shapeId)?.parentId === firstParentId) && !(0, import_tlschema.isPageId)(firstParentId);
1613
- const filteredShapes = isSelectedWithinContainer ? this.getCurrentPageShapes().filter((shape2) => shape2.parentId === firstParentId) : this.getCurrentPageShapes().filter((shape2) => (0, import_tlschema.isPageId)(shape2.parentId));
1614
- const readingOrderShapes = isSelectedWithinContainer ? this._getShapesInReadingOrder(filteredShapes) : this.getCurrentPageShapesInReadingOrder();
1618
+ const filteredShapes = this.getCurrentPageShapes().filter(
1619
+ (shape2) => !this.isShapeOrAncestorLocked(shape2) && (isSelectedWithinContainer ? shape2.parentId === firstParentId : (0, import_tlschema.isPageId)(shape2.parentId))
1620
+ );
1621
+ const readingOrderShapes = this._getShapesInReadingOrder(filteredShapes);
1615
1622
  const currentShapeId = selectedShapeIds.length === 1 ? selectedShapeIds[0] : readingOrderShapes.find((shape2) => selectedShapeIds.includes(shape2.id))?.id;
1616
1623
  let adjacentShapeId;
1617
1624
  if (direction === "next" || direction === "prev") {
1618
1625
  const shapeIds = readingOrderShapes.map((shape2) => shape2.id);
1626
+ if (shapeIds.length === 0) return;
1619
1627
  const currentIndex = currentShapeId ? shapeIds.indexOf(currentShapeId) : -1;
1620
1628
  const startIndex = currentIndex === -1 && direction === "prev" ? 0 : currentIndex;
1621
1629
  const adjacentIndex = (startIndex + (direction === "next" ? 1 : -1) + shapeIds.length) % shapeIds.length;
@@ -1717,13 +1725,14 @@ class Editor extends import_eventemitter3.default {
1717
1725
  });
1718
1726
  if (shapesInDirection.length === 0) return currentShapeId;
1719
1727
  const lowestScoringShape = (0, import_utils.minBy)(shapesInDirection, ({ center }) => {
1720
- const distance = import_Vec.Vec.Dist2(currentCenter, center);
1728
+ const distance = import_Vec.Vec.Dist(currentCenter, center);
1721
1729
  const dirProp = ["left", "right"].includes(direction) ? "x" : "y";
1722
1730
  const directionalDistance = Math.abs(center[dirProp] - currentCenter[dirProp]);
1723
1731
  const offProp = ["left", "right"].includes(direction) ? "y" : "x";
1724
1732
  const offAxisDeviation = Math.abs(center[offProp] - currentCenter[offProp]);
1725
- const angle = Math.abs(import_Vec.Vec.Angle(currentCenter, center) * (180 / Math.PI));
1726
- const angleDeviation = Math.abs(angle - directionToAngle[direction]);
1733
+ const angle = (import_Vec.Vec.Angle(currentCenter, center) * (180 / Math.PI) + 360) % 360;
1734
+ const rawAngleDeviation = Math.abs(angle - directionToAngle[direction]);
1735
+ const angleDeviation = Math.min(rawAngleDeviation, 360 - rawAngleDeviation);
1727
1736
  return distance * 1 + // Base distance
1728
1737
  offAxisDeviation * 2 + // Heavy penalty for off-axis deviation
1729
1738
  (distance - directionalDistance) * 1.5 + // Penalty for diagonal distance
@@ -2061,7 +2070,7 @@ class Editor extends import_eventemitter3.default {
2061
2070
  * @public
2062
2071
  */
2063
2072
  setHintingShapes(shapes) {
2064
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((shape) => shape.id);
2073
+ const ids = toShapeIds(shapes);
2065
2074
  this.run(
2066
2075
  () => {
2067
2076
  this._updateCurrentPageState({ hintingShapeIds: (0, import_utils.dedupe)(ids) });
@@ -2091,7 +2100,7 @@ class Editor extends import_eventemitter3.default {
2091
2100
  * @public
2092
2101
  */
2093
2102
  setErasingShapes(shapes) {
2094
- const ids = typeof shapes[0] === "string" ? shapes.slice() : shapes.map((shape) => shape.id);
2103
+ const ids = toShapeIds(shapes).slice();
2095
2104
  ids.sort();
2096
2105
  const erasingShapeIds = this.getErasingShapeIds();
2097
2106
  this.run(
@@ -2498,14 +2507,16 @@ class Editor extends import_eventemitter3.default {
2498
2507
  setCamera(point, opts) {
2499
2508
  const { isLocked } = this._cameraOptions.__unsafe__getWithoutCapture();
2500
2509
  if (isLocked && !opts?.force) return this;
2510
+ const _point = new import_Vec.Vec(point.x, point.y, point.z ?? this.getZoomLevel());
2511
+ if (!Number.isFinite(_point.x) || !Number.isFinite(_point.y) || !Number.isFinite(_point.z)) {
2512
+ throw Error(
2513
+ `Editor.setCamera: expected finite values, got (${_point.x}, ${_point.y}, ${_point.z}).`
2514
+ );
2515
+ }
2501
2516
  this.stopCameraAnimation();
2502
2517
  if (this.getInstanceState().followingUserId) {
2503
2518
  this.stopFollowingUser();
2504
2519
  }
2505
- const _point = import_Vec.Vec.Cast(point);
2506
- if (!Number.isFinite(_point.x)) _point.x = 0;
2507
- if (!Number.isFinite(_point.y)) _point.y = 0;
2508
- if (_point.z === void 0 || !Number.isFinite(_point.z)) point.z = this.getZoomLevel();
2509
2520
  const camera = this.getConstrainedCamera(_point, opts);
2510
2521
  if (opts?.animation) {
2511
2522
  const { width, height } = this.getViewportScreenBounds();
@@ -2790,11 +2801,14 @@ class Editor extends import_eventemitter3.default {
2790
2801
  _animateViewport(ms) {
2791
2802
  if (!this._viewportAnimation) return;
2792
2803
  this._viewportAnimation.elapsed += ms;
2793
- const { elapsed, easing, duration, start, end } = this._viewportAnimation;
2804
+ const { elapsed, easing, duration, start, end, opts } = this._viewportAnimation;
2794
2805
  if (elapsed > duration) {
2795
2806
  this.off("tick", this._animateViewport);
2796
2807
  this._viewportAnimation = null;
2797
- this._setCamera(new import_Vec.Vec(-end.x, -end.y, this.getViewportScreenBounds().width / end.width));
2808
+ this._setCamera(
2809
+ new import_Vec.Vec(-end.x, -end.y, this.getViewportScreenBounds().width / end.width),
2810
+ opts
2811
+ );
2798
2812
  return;
2799
2813
  }
2800
2814
  const remaining = duration - elapsed;
@@ -2832,7 +2846,8 @@ class Editor extends import_eventemitter3.default {
2832
2846
  duration: duration / animationSpeed,
2833
2847
  easing,
2834
2848
  start: viewportPageBounds.clone(),
2835
- end: targetViewportPage.clone()
2849
+ end: targetViewportPage.clone(),
2850
+ opts: rest
2836
2851
  };
2837
2852
  this.once("stop-camera-animation", () => {
2838
2853
  this.off("tick", this._animateViewport);
@@ -2918,6 +2933,7 @@ class Editor extends import_eventemitter3.default {
2918
2933
  }
2919
2934
  const isOnSamePage = presence.currentPageId === this.getCurrentPageId();
2920
2935
  if (!isOnSamePage) {
2936
+ this.markHistoryStoppingPoint("change-page");
2921
2937
  this.setCurrentPage(presence.currentPageId);
2922
2938
  }
2923
2939
  if (opts && opts.animation && !isOnSamePage) {
@@ -3641,7 +3657,9 @@ class Editor extends import_eventemitter3.default {
3641
3657
  const id = typeof page === "string" ? page : page.id;
3642
3658
  const freshPage = this.getPage(id);
3643
3659
  if (!freshPage) return this;
3644
- const prevCamera = { ...this.getCamera() };
3660
+ const prevCamera = {
3661
+ ...this.store.get(import_tlschema.CameraRecordType.createId(freshPage.id)) ?? this.getCamera()
3662
+ };
3645
3663
  const content = this.getContentFromCurrentPage(this.getSortedChildIdsForParent(freshPage.id));
3646
3664
  this.run(() => {
3647
3665
  const pages = this.getPages();
@@ -3650,7 +3668,7 @@ class Editor extends import_eventemitter3.default {
3650
3668
  this.setCurrentPage(createId);
3651
3669
  this.setCamera(prevCamera);
3652
3670
  if (content) {
3653
- return this.putContentOntoCurrentPage(content);
3671
+ return this.putContentOntoCurrentPage(content, { preservePosition: true });
3654
3672
  }
3655
3673
  });
3656
3674
  return this;
@@ -3749,7 +3767,9 @@ class Editor extends import_eventemitter3.default {
3749
3767
  if (ids.length <= 0) return this;
3750
3768
  this.run(
3751
3769
  () => {
3752
- this.store.props.assets.remove?.(ids);
3770
+ Promise.resolve(this.store.props.assets.remove?.(ids)).catch(
3771
+ (err) => console.error("Error while removing assets from the asset store:", err)
3772
+ );
3753
3773
  this.store.remove(ids);
3754
3774
  },
3755
3775
  { history: "ignore" }
@@ -4131,7 +4151,7 @@ class Editor extends import_eventemitter3.default {
4131
4151
  if (shapes.length === 0) {
4132
4152
  return;
4133
4153
  }
4134
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4154
+ const ids = toShapeIds(shapes);
4135
4155
  const freshShapes = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
4136
4156
  if (freshShapes.length === 1) {
4137
4157
  const parentId = freshShapes[0].parentId;
@@ -4228,12 +4248,14 @@ class Editor extends import_eventemitter3.default {
4228
4248
  */
4229
4249
  getSelectedShapeAtPoint(point) {
4230
4250
  const selectedShapeIds = this.getSelectedShapeIds();
4231
- const margin = this.options.hitTestMargin / this.getZoomLevel();
4251
+ if (selectedShapeIds.length === 0) return void 0;
4252
+ const selectedShapeIdSet = new Set(selectedShapeIds);
4253
+ const margin = this.getHitTestMargin();
4232
4254
  const sortedShapes = this.getCurrentPageShapesSorted();
4233
4255
  for (let i = sortedShapes.length - 1; i >= 0; i--) {
4234
4256
  const shape = sortedShapes[i];
4235
4257
  if (shape.type === "group") continue;
4236
- if (!selectedShapeIds.includes(shape.id)) continue;
4258
+ if (!selectedShapeIdSet.has(shape.id)) continue;
4237
4259
  if (this.getShapeGeometry(shape).hitTestPoint(
4238
4260
  this.getPointInShapeSpace(shape, point),
4239
4261
  margin,
@@ -4331,7 +4353,6 @@ class Editor extends import_eventemitter3.default {
4331
4353
  }
4332
4354
  return inMarginClosestToEdgeHit || shape;
4333
4355
  } else {
4334
- if (this.getShapePageBounds(shape).contains(viewportPageBounds)) continue;
4335
4356
  if (hitInside ? (
4336
4357
  // On hitInside, the distance will be negative for hits inside
4337
4358
  // If the distance is positive, check against the outer margin
@@ -4348,6 +4369,7 @@ class Editor extends import_eventemitter3.default {
4348
4369
  inMarginClosestToEdgeHit = shape;
4349
4370
  }
4350
4371
  } else if (!inMarginClosestToEdgeHit) {
4372
+ if (this.getShapePageBounds(shape).contains(viewportPageBounds)) continue;
4351
4373
  const { area } = geometry;
4352
4374
  if (area < inHollowSmallestArea) {
4353
4375
  inHollowSmallestArea = area;
@@ -4358,6 +4380,9 @@ class Editor extends import_eventemitter3.default {
4358
4380
  }
4359
4381
  } else {
4360
4382
  if (distance < this.getHitTestMargin()) {
4383
+ if (inMarginClosestToEdgeHit && inMarginClosestToEdgeDistance <= distance) {
4384
+ return inMarginClosestToEdgeHit;
4385
+ }
4361
4386
  return shape;
4362
4387
  }
4363
4388
  }
@@ -4634,7 +4659,7 @@ class Editor extends import_eventemitter3.default {
4634
4659
  * @public
4635
4660
  */
4636
4661
  reparentShapes(shapes, parentId, insertIndex) {
4637
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4662
+ const ids = toShapeIds(shapes);
4638
4663
  if (ids.length === 0) return this;
4639
4664
  const changes = [];
4640
4665
  const parentTransform = (0, import_tlschema.isPageId)(parentId) ? import_Mat.Mat.Identity() : this.getShapePageTransform(parentId);
@@ -4863,6 +4888,7 @@ class Editor extends import_eventemitter3.default {
4863
4888
  * binding, but the `type`, `toId`, and `fromId` must all be provided.
4864
4889
  */
4865
4890
  createBindings(partials) {
4891
+ if (this.getIsReadonly()) return this;
4866
4892
  const bindings = [];
4867
4893
  for (const partial of partials) {
4868
4894
  const fromShape = this.getShape(partial.fromId);
@@ -4897,6 +4923,7 @@ class Editor extends import_eventemitter3.default {
4897
4923
  * binding is skipped. The changes from the partial are merged into the existing record.
4898
4924
  */
4899
4925
  updateBindings(partials) {
4926
+ if (this.getIsReadonly()) return this;
4900
4927
  const updated = [];
4901
4928
  for (const partial of partials) {
4902
4929
  if (!partial) continue;
@@ -4925,6 +4952,16 @@ class Editor extends import_eventemitter3.default {
4925
4952
  * Delete several bindings by their IDs. If a binding ID doesn't exist, it's ignored.
4926
4953
  */
4927
4954
  deleteBindings(bindings, { isolateShapes = false } = {}) {
4955
+ if (this.getIsReadonly()) return this;
4956
+ return this._deleteBindings(bindings, { isolateShapes });
4957
+ }
4958
+ /**
4959
+ * Unguarded so that withIsolatedShapes can transiently isolate shapes for copy and export in
4960
+ * readonly mode; the public deleteBindings is what readonly blocks.
4961
+ *
4962
+ * @internal
4963
+ */
4964
+ _deleteBindings(bindings, { isolateShapes = false } = {}) {
4928
4965
  const ids = bindings.map((binding) => typeof binding === "string" ? binding : binding.id);
4929
4966
  if (isolateShapes) {
4930
4967
  this.store.atomic(() => {
@@ -4983,7 +5020,7 @@ class Editor extends import_eventemitter3.default {
4983
5020
  * @param opts - The options for the rotation.
4984
5021
  */
4985
5022
  rotateShapesBy(shapes, delta, opts) {
4986
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5023
+ const ids = toShapeIds(shapes);
4987
5024
  if (ids.length <= 0) return this;
4988
5025
  const snapshot = (0, import_rotation.getRotationSnapshot)({ editor: this, ids });
4989
5026
  if (!snapshot) return this;
@@ -5020,6 +5057,14 @@ class Editor extends import_eventemitter3.default {
5020
5057
  }
5021
5058
  return workingShape;
5022
5059
  }
5060
+ // Counter-rotates the page-space delta into the parent's space; without that a child of a rotated
5061
+ // frame or group would move along its parent's axes instead of the page's.
5062
+ // todo: a shape laid out together with its own parent moves twice, once with the parent and once
5063
+ // on its own; the layout commands should skip shapes whose ancestors are also being laid out
5064
+ getChangesToTranslateShapeByPageDelta(shape, pageDelta) {
5065
+ const localDelta = import_Vec.Vec.From(pageDelta).rot(-this.getShapeParentTransform(shape).rotation());
5066
+ return this.getChangesToTranslateShape(shape, localDelta.add(shape));
5067
+ }
5023
5068
  /**
5024
5069
  * Move shapes by a delta.
5025
5070
  *
@@ -5032,14 +5077,12 @@ class Editor extends import_eventemitter3.default {
5032
5077
  * @param offset - The offset to apply to the shapes.
5033
5078
  */
5034
5079
  nudgeShapes(shapes, offset) {
5035
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5080
+ const ids = toShapeIds(shapes);
5036
5081
  if (ids.length <= 0) return this;
5037
5082
  const changes = [];
5038
5083
  for (const id of ids) {
5039
5084
  const shape = this.getShape(id);
5040
- const localDelta = import_Vec.Vec.From(offset);
5041
- localDelta.rot(-this.getShapeParentTransform(shape).rotation());
5042
- changes.push(this.getChangesToTranslateShape(shape, localDelta.add(shape)));
5085
+ changes.push(this.getChangesToTranslateShapeByPageDelta(shape, offset));
5043
5086
  }
5044
5087
  this.updateShapes(changes);
5045
5088
  return this;
@@ -5060,7 +5103,7 @@ class Editor extends import_eventemitter3.default {
5060
5103
  */
5061
5104
  duplicateShapes(shapes, offset) {
5062
5105
  this.run(() => {
5063
- const _ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5106
+ const _ids = toShapeIds(shapes);
5064
5107
  const ids = this._shouldIgnoreShapeLock ? _ids : this._getUnlockedShapeIds(_ids);
5065
5108
  if (ids.length <= 0) return this;
5066
5109
  const initialIds = new Set(ids);
@@ -5169,7 +5212,7 @@ class Editor extends import_eventemitter3.default {
5169
5212
  * @public
5170
5213
  */
5171
5214
  moveShapesToPage(shapes, pageId) {
5172
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5215
+ const ids = toShapeIds(shapes);
5173
5216
  if (ids.length === 0) return this;
5174
5217
  if (this.getIsReadonly()) return this;
5175
5218
  const currentPageId = this.getCurrentPageId();
@@ -5205,7 +5248,7 @@ class Editor extends import_eventemitter3.default {
5205
5248
  * @public
5206
5249
  */
5207
5250
  toggleLock(shapes) {
5208
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5251
+ const ids = toShapeIds(shapes);
5209
5252
  if (this.getIsReadonly() || ids.length === 0) return this;
5210
5253
  let allLocked = true;
5211
5254
  const shapesToToggle = [];
@@ -5244,7 +5287,7 @@ class Editor extends import_eventemitter3.default {
5244
5287
  * @public
5245
5288
  */
5246
5289
  sendToBack(shapes) {
5247
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5290
+ const ids = toShapeIds(shapes);
5248
5291
  const changes = (0, import_reorderShapes.getReorderingShapesChanges)(this, "toBack", ids, {
5249
5292
  considerAllShapes: true
5250
5293
  });
@@ -5274,7 +5317,7 @@ class Editor extends import_eventemitter3.default {
5274
5317
  * @public
5275
5318
  */
5276
5319
  sendBackward(shapes, opts = {}) {
5277
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5320
+ const ids = toShapeIds(shapes);
5278
5321
  const changes = (0, import_reorderShapes.getReorderingShapesChanges)(this, "backward", ids, opts);
5279
5322
  if (changes) this.updateShapes(changes);
5280
5323
  return this;
@@ -5302,7 +5345,7 @@ class Editor extends import_eventemitter3.default {
5302
5345
  * @public
5303
5346
  */
5304
5347
  bringForward(shapes, opts = {}) {
5305
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5348
+ const ids = toShapeIds(shapes);
5306
5349
  const changes = (0, import_reorderShapes.getReorderingShapesChanges)(this, "forward", ids, opts);
5307
5350
  if (changes) this.updateShapes(changes);
5308
5351
  return this;
@@ -5321,7 +5364,7 @@ class Editor extends import_eventemitter3.default {
5321
5364
  * @public
5322
5365
  */
5323
5366
  bringToFront(shapes) {
5324
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5367
+ const ids = toShapeIds(shapes);
5325
5368
  const changes = (0, import_reorderShapes.getReorderingShapesChanges)(this, "toFront", ids);
5326
5369
  if (changes) this.updateShapes(changes);
5327
5370
  return this;
@@ -5333,7 +5376,7 @@ class Editor extends import_eventemitter3.default {
5333
5376
  * @internal
5334
5377
  */
5335
5378
  getShapeClusters(shapes, type, opts) {
5336
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5379
+ const ids = toShapeIds(shapes);
5337
5380
  let freshShapes = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5338
5381
  if (opts?.filterAxisAligned) {
5339
5382
  freshShapes = freshShapes.filter((s) => {
@@ -5358,7 +5401,7 @@ class Editor extends import_eventemitter3.default {
5358
5401
  const shapesMovingTogether = [shape];
5359
5402
  const boundsOfShapesMovingTogether = [shapePageBounds];
5360
5403
  this.collectShapesViaArrowBindings({
5361
- bindings: this.getBindingsToShape(shape.id, "arrow"),
5404
+ bindings: this.getBindingsInvolvingShape(shape.id, "arrow"),
5362
5405
  initialShapes: freshShapes,
5363
5406
  resultShapes: shapesMovingTogether,
5364
5407
  resultBounds: boundsOfShapesMovingTogether,
@@ -5414,7 +5457,7 @@ class Editor extends import_eventemitter3.default {
5414
5457
  */
5415
5458
  flipShapes(shapes, operation) {
5416
5459
  if (this.getIsReadonly()) return this;
5417
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5460
+ const ids = toShapeIds(shapes);
5418
5461
  const shapesToFlipFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5419
5462
  for (const shape of shapesToFlipFirstPass) {
5420
5463
  if (this.isShapeOfType(shape, "group")) {
@@ -5488,21 +5531,7 @@ class Editor extends import_eventemitter3.default {
5488
5531
  const { clusters: shapeClustersToStack } = this.getShapeClusters(shapes, "stack");
5489
5532
  const len = shapeClustersToStack.length;
5490
5533
  if (_gap === 0 && len < 3 || len < 2) return this;
5491
- let val;
5492
- let min;
5493
- let max;
5494
- let dim;
5495
- if (operation === "horizontal") {
5496
- val = "x";
5497
- min = "minX";
5498
- max = "maxX";
5499
- dim = "width";
5500
- } else {
5501
- val = "y";
5502
- min = "minY";
5503
- max = "maxY";
5504
- dim = "height";
5505
- }
5534
+ const { val, min, max, dim } = AXIS[operation];
5506
5535
  let shapeGap = 0;
5507
5536
  shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min]);
5508
5537
  if (_gap === 0) {
@@ -5541,13 +5570,7 @@ class Editor extends import_eventemitter3.default {
5541
5570
  const delta = new import_Vec.Vec();
5542
5571
  delta[val] = v + shapeGap - pageBounds[val];
5543
5572
  for (const shape of shapes2) {
5544
- const shapeDelta = delta.clone();
5545
- const parent = this.getShapeParent(shape);
5546
- if (parent) {
5547
- shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5548
- }
5549
- shapeDelta.add(shape);
5550
- changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5573
+ changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta));
5551
5574
  }
5552
5575
  v += pageBounds[dim] + shapeGap;
5553
5576
  }
@@ -5627,13 +5650,7 @@ class Editor extends import_eventemitter3.default {
5627
5650
  for (const { shapes: shapes2, pageBounds, nextPageBounds } of shapeClustersToPack) {
5628
5651
  const delta = import_Vec.Vec.Sub(nextPageBounds.point, pageBounds.point).add(centerDelta);
5629
5652
  for (const shape of shapes2) {
5630
- const shapeDelta = delta.clone();
5631
- const parent = this.getShapeParent(shape);
5632
- if (parent) {
5633
- shapeDelta.rot(-this.getShapeParentTransform(shape).rotation());
5634
- }
5635
- shapeDelta.add(shape);
5636
- changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5653
+ changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta));
5637
5654
  }
5638
5655
  }
5639
5656
  if (changes.length) {
@@ -5693,13 +5710,7 @@ class Editor extends import_eventemitter3.default {
5693
5710
  }
5694
5711
  }
5695
5712
  for (const shape of shapes2) {
5696
- const shapeDelta = delta.clone();
5697
- const parent = this.getShapeParent(shape);
5698
- if (parent) {
5699
- shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5700
- }
5701
- shapeDelta.add(shape);
5702
- changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5713
+ changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta));
5703
5714
  }
5704
5715
  });
5705
5716
  this.updateShapes(changes);
@@ -5723,27 +5734,13 @@ class Editor extends import_eventemitter3.default {
5723
5734
  if (this.getIsReadonly()) return this;
5724
5735
  const { clusters: shapeClustersToDistribute } = this.getShapeClusters(shapes, "distribute");
5725
5736
  if (shapeClustersToDistribute.length < 3) return this;
5726
- let val;
5727
- let min;
5728
- let max;
5729
- let dim;
5730
- if (operation === "horizontal") {
5731
- val = "x";
5732
- min = "minX";
5733
- max = "maxX";
5734
- dim = "width";
5735
- } else {
5736
- val = "y";
5737
- min = "minY";
5738
- max = "maxY";
5739
- dim = "height";
5740
- }
5737
+ const { val, min, max, dim } = AXIS[operation];
5741
5738
  const changes = [];
5742
5739
  const first = shapeClustersToDistribute.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])[0];
5743
5740
  const last2 = shapeClustersToDistribute.sort((a, b) => b.pageBounds[max] - a.pageBounds[max])[0];
5744
5741
  if (first === last2) {
5745
5742
  const excludedShapeIds = new Set(first.shapes.map((s) => s.id));
5746
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5743
+ const ids = toShapeIds(shapes);
5747
5744
  return this.distributeShapes(
5748
5745
  ids.filter((id) => !excludedShapeIds.has(id)),
5749
5746
  operation
@@ -5767,13 +5764,7 @@ class Editor extends import_eventemitter3.default {
5767
5764
  delta[val] = last2.pageBounds[max] - pageBounds[max] - 1;
5768
5765
  }
5769
5766
  for (const shape of shapes2) {
5770
- const shapeDelta = delta.clone();
5771
- const parent = this.getShapeParent(shape);
5772
- if (parent) {
5773
- shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5774
- }
5775
- shapeDelta.add(shape);
5776
- changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5767
+ changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta));
5777
5768
  }
5778
5769
  v += pageBounds[dim] + gap;
5779
5770
  }
@@ -5803,32 +5794,17 @@ class Editor extends import_eventemitter3.default {
5803
5794
  );
5804
5795
  if (shapeClustersToStretch.length < 2) return this;
5805
5796
  const commonBounds = import_Box.Box.Common(allBounds);
5806
- let val;
5807
- let min;
5808
- let dim;
5809
- if (operation === "horizontal") {
5810
- val = "x";
5811
- min = "minX";
5812
- dim = "width";
5813
- } else {
5814
- val = "y";
5815
- min = "minY";
5816
- dim = "height";
5817
- }
5797
+ const { val, min, dim } = AXIS[operation];
5818
5798
  this.run(() => {
5819
5799
  shapeClustersToStretch.forEach(({ shapes: shapes2, pageBounds }) => {
5820
- const localOffset = new import_Vec.Vec();
5821
- localOffset[val] = commonBounds[min] - pageBounds[min];
5800
+ const pageOffset = new import_Vec.Vec();
5801
+ pageOffset[val] = commonBounds[min] - pageBounds[min];
5822
5802
  const scaleOrigin = pageBounds.center.clone();
5823
5803
  scaleOrigin[val] = commonBounds[min];
5824
5804
  const scale = new import_Vec.Vec(1, 1);
5825
5805
  scale[val] = commonBounds[dim] / pageBounds[dim];
5826
5806
  for (const shape of shapes2) {
5827
- const shapeLocalOffset = localOffset.clone();
5828
- shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation());
5829
- shapeLocalOffset.add(shape);
5830
- const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5831
- this.updateShape(changes);
5807
+ this.updateShape(this.getChangesToTranslateShapeByPageDelta(shape, pageOffset));
5832
5808
  this.resizeShape(shape.id, scale, {
5833
5809
  initialBounds: this.getShapeGeometry(shape).bounds,
5834
5810
  scaleOrigin,
@@ -5871,7 +5847,7 @@ class Editor extends import_eventemitter3.default {
5871
5847
  const scaleY = targetBounds.height / commonBounds.height;
5872
5848
  const scale = new import_Vec.Vec(scaleX, scaleY);
5873
5849
  shapeClusters.forEach(({ shapes: shapes2, pageBounds }) => {
5874
- const localOffset = new import_Vec.Vec(
5850
+ const pageOffset = new import_Vec.Vec(
5875
5851
  targetBounds.minX - commonBounds.minX + (pageBounds.minX - commonBounds.minX) * (scaleX - 1),
5876
5852
  targetBounds.minY - commonBounds.minY + (pageBounds.minY - commonBounds.minY) * (scaleY - 1)
5877
5853
  );
@@ -5880,11 +5856,7 @@ class Editor extends import_eventemitter3.default {
5880
5856
  targetBounds.minY + (pageBounds.minY - commonBounds.minY) * scaleY
5881
5857
  );
5882
5858
  for (const shape of shapes2) {
5883
- const shapeLocalOffset = localOffset.clone();
5884
- shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation());
5885
- shapeLocalOffset.add(shape);
5886
- const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5887
- this.updateShape(changes);
5859
+ this.updateShape(this.getChangesToTranslateShapeByPageDelta(shape, pageOffset));
5888
5860
  this.resizeShape(shape.id, scale, {
5889
5861
  initialBounds: this.getShapeGeometry(shape).bounds,
5890
5862
  scaleOrigin,
@@ -6060,11 +6032,20 @@ class Editor extends import_eventemitter3.default {
6060
6032
  initialShape: options.initialShape,
6061
6033
  initialBounds: options.initialBounds,
6062
6034
  isAspectRatioLocked: options.isAspectRatioLocked,
6063
- initialPageTransform: options.initialPageTransform
6035
+ initialPageTransform: options.initialPageTransform,
6036
+ dragHandle: options.dragHandle,
6037
+ mode: options.mode,
6038
+ // this is one frame of the same resize, not a new one, so don't fire start/end again
6039
+ skipStartAndEndCallbacks: options.skipStartAndEndCallbacks
6064
6040
  });
6065
6041
  if (Math.sign(scale.x) * Math.sign(scale.y) < 0) {
6066
6042
  const parentRotation = this.getShapeParentTransform(id).rotation();
6067
- const rotation = -options.initialShape.rotation - 2 * parentRotation;
6043
+ const parentDelta = (0, import_utils2.shortAngleDist)(
6044
+ import_Mat.Mat.Cast(options.initialPageTransform).rotation() - options.initialShape.rotation,
6045
+ parentRotation
6046
+ );
6047
+ const initialParentRotation = parentRotation - parentDelta;
6048
+ const rotation = 2 * options.scaleAxisRotation - options.initialShape.rotation - 2 * initialParentRotation - parentDelta;
6068
6049
  this.updateShapes([{ id, type, rotation }]);
6069
6050
  }
6070
6051
  const preScaleShapePageCenter = import_Mat.Mat.applyToPoint(
@@ -6303,12 +6284,17 @@ class Editor extends import_eventemitter3.default {
6303
6284
  let remaining = duration;
6304
6285
  let t;
6305
6286
  const animations = [];
6287
+ const ignoreShapeLock = this._shouldIgnoreShapeLock;
6306
6288
  let partial, result;
6307
6289
  for (let i = 0, n = partials.length; i < n; i++) {
6308
6290
  partial = partials[i];
6309
6291
  if (!partial) continue;
6310
6292
  const shape = this.getShape(partial.id);
6311
6293
  if (!shape) continue;
6294
+ const unlocks = shape.isLocked && Object.hasOwn(partial, "isLocked") && !partial.isLocked;
6295
+ if (!ignoreShapeLock && !unlocks && this.isShapeOrAncestorLocked(shape)) {
6296
+ continue;
6297
+ }
6312
6298
  result = {
6313
6299
  start: (0, import_utils.structuredClone)(shape),
6314
6300
  end: applyPartialToRecordWithProps((0, import_utils.structuredClone)(shape), partial)
@@ -6324,7 +6310,7 @@ class Editor extends import_eventemitter3.default {
6324
6310
  (p) => p && animatingShapes2.get(p.id) === animationId
6325
6311
  );
6326
6312
  if (partialsToUpdate.length) {
6327
- this.updateShapes(partialsToUpdate);
6313
+ this.run(() => this.updateShapes(partialsToUpdate), { ignoreShapeLock });
6328
6314
  }
6329
6315
  this.off("tick", handleTick);
6330
6316
  return;
@@ -6357,13 +6343,13 @@ class Editor extends import_eventemitter3.default {
6357
6343
  throw Error("Editor.groupShapes: must provide an array of shapes or shape ids");
6358
6344
  }
6359
6345
  if (this.getIsReadonly()) return this;
6360
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
6361
- if (ids.length <= 1) return this;
6346
+ const ids = toShapeIds(shapes);
6362
6347
  const shapesToGroup = (0, import_utils.compact)(
6363
6348
  (this._shouldIgnoreShapeLock ? ids : this._getUnlockedShapeIds(ids)).map(
6364
6349
  (id) => this.getShape(id)
6365
6350
  )
6366
6351
  );
6352
+ if (shapesToGroup.length <= 1) return this;
6367
6353
  const sortedShapeIds = shapesToGroup.sort(import_utils.sortByIndex).map((s) => s.id);
6368
6354
  const childBounds = (0, import_utils.compact)(shapesToGroup.map((shape) => this.getShapePageBounds(shape)));
6369
6355
  const pageBounds = import_Box.Box.Common(childBounds);
@@ -6372,8 +6358,11 @@ class Editor extends import_eventemitter3.default {
6372
6358
  }
6373
6359
  const { x, y } = pageBounds.point;
6374
6360
  const parentId = this.findCommonAncestor(shapesToGroup) ?? this.getCurrentPageId();
6375
- if (this.getCurrentToolId() !== "select") return this;
6376
- if (!this.isIn("select.idle")) {
6361
+ if (!this.canCreateShapes([groupId])) {
6362
+ alertMaxShapes(this);
6363
+ return this;
6364
+ }
6365
+ if (this.isIn("select") && !this.isIn("select.idle")) {
6377
6366
  this.cancel();
6378
6367
  }
6379
6368
  const shapesWithRootParent = shapesToGroup.filter((shape) => shape.parentId === parentId).sort(import_utils.sortByIndex);
@@ -6401,15 +6390,14 @@ class Editor extends import_eventemitter3.default {
6401
6390
  ungroupShapes(shapes, opts = {}) {
6402
6391
  if (this.getIsReadonly()) return this;
6403
6392
  const { select = true } = opts;
6404
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
6393
+ const ids = toShapeIds(shapes);
6405
6394
  const shapesToUngroup = (0, import_utils.compact)(
6406
6395
  (this._shouldIgnoreShapeLock ? ids : this._getUnlockedShapeIds(ids)).map(
6407
6396
  (id) => this.getShape(id)
6408
6397
  )
6409
6398
  );
6410
6399
  if (shapesToUngroup.length === 0) return this;
6411
- if (this.getCurrentToolId() !== "select") return this;
6412
- if (!this.isIn("select.idle")) {
6400
+ if (this.isIn("select") && !this.isIn("select.idle")) {
6413
6401
  this.cancel();
6414
6402
  }
6415
6403
  const idsToSelect = /* @__PURE__ */ new Set();
@@ -6423,16 +6411,16 @@ class Editor extends import_eventemitter3.default {
6423
6411
  });
6424
6412
  if (groups.length === 0) return this;
6425
6413
  this.run(() => {
6426
- let group;
6427
6414
  for (let i = 0, n = groups.length; i < n; i++) {
6428
- group = groups[i];
6415
+ const group = this.getShape(groups[i].id);
6416
+ if (!group) continue;
6429
6417
  const childIds = this.getSortedChildIdsForParent(group.id);
6430
6418
  for (let j = 0, n2 = childIds.length; j < n2; j++) {
6431
6419
  idsToSelect.add(childIds[j]);
6432
6420
  }
6433
6421
  this.reparentShapes(childIds, group.parentId, group.index);
6434
6422
  }
6435
- this.deleteShapes(groups.map((group2) => group2.id));
6423
+ this.deleteShapes(groups.map((group) => group.id));
6436
6424
  if (select) {
6437
6425
  this.select(...idsToSelect);
6438
6426
  }
@@ -6513,14 +6501,14 @@ class Editor extends import_eventemitter3.default {
6513
6501
  }
6514
6502
  /** @internal */
6515
6503
  _getUnlockedShapeIds(ids) {
6516
- return ids.filter((id) => !this.getShape(id)?.isLocked);
6504
+ return ids.filter((id) => !this.isShapeOrAncestorLocked(id));
6517
6505
  }
6518
6506
  deleteShapes(_ids) {
6519
6507
  if (this.getIsReadonly()) return this;
6520
6508
  if (!Array.isArray(_ids)) {
6521
6509
  throw Error("Editor.deleteShapes: must provide an array of shapes or shapeIds");
6522
6510
  }
6523
- const shapeIds = typeof _ids[0] === "string" ? _ids : _ids.map((s) => s.id);
6511
+ const shapeIds = toShapeIds(_ids);
6524
6512
  const shapeIdsToDelete = this._shouldIgnoreShapeLock ? shapeIds : this._getUnlockedShapeIds(shapeIds);
6525
6513
  if (shapeIdsToDelete.length === 0) return this;
6526
6514
  const allShapeIdsToDelete = new Set(shapeIdsToDelete);
@@ -6865,7 +6853,7 @@ class Editor extends import_eventemitter3.default {
6865
6853
  * @public
6866
6854
  */
6867
6855
  getContentFromCurrentPage(shapes) {
6868
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
6856
+ const ids = toShapeIds(shapes);
6869
6857
  if (!ids) return;
6870
6858
  if (ids.length === 0) return;
6871
6859
  const shapeIds = this.getShapeAndDescendantIds(ids);
@@ -6933,21 +6921,29 @@ class Editor extends import_eventemitter3.default {
6933
6921
  await Promise.allSettled(
6934
6922
  content.assets.map(async (asset) => {
6935
6923
  if ((asset.type === "image" || asset.type === "video") && !asset.props.src?.startsWith("data:image") && !asset.props.src?.startsWith("data:video") && !asset.props.src?.startsWith("http")) {
6936
- const assetWithDataUrl = (0, import_utils.structuredClone)(asset);
6937
- const objectUrl = await this.store.props.assets.resolve(asset, {
6938
- screenScale: 1,
6939
- steppedScreenScale: 1,
6940
- dpr: 1,
6941
- networkEffectiveType: null,
6942
- shouldResolveToOriginal: true
6943
- });
6944
- assetWithDataUrl.props.src = await import_utils.FileHelpers.blobToDataUrl(
6945
- await (0, import_utils.fetch)(objectUrl).then((r) => r.blob())
6946
- );
6947
- assets.push(assetWithDataUrl);
6948
- } else {
6949
- assets.push(asset);
6924
+ try {
6925
+ const objectUrl = await this.store.props.assets.resolve(asset, {
6926
+ screenScale: 1,
6927
+ steppedScreenScale: 1,
6928
+ dpr: 1,
6929
+ networkEffectiveType: null,
6930
+ shouldResolveToOriginal: true
6931
+ });
6932
+ if (objectUrl) {
6933
+ const response = await (0, import_utils.fetch)(objectUrl);
6934
+ if (response.ok) {
6935
+ const assetWithDataUrl = (0, import_utils.structuredClone)(asset);
6936
+ assetWithDataUrl.props.src = await import_utils.FileHelpers.blobToDataUrl(await response.blob());
6937
+ assets.push(assetWithDataUrl);
6938
+ return;
6939
+ }
6940
+ console.warn(`Could not inline asset ${asset.id}: fetch returned ${response.status}`);
6941
+ }
6942
+ } catch (err) {
6943
+ console.warn(`Could not inline asset ${asset.id}`, err);
6944
+ }
6950
6945
  }
6946
+ assets.push(asset);
6951
6947
  })
6952
6948
  );
6953
6949
  content.assets = assets;
@@ -7241,7 +7237,7 @@ class Editor extends import_eventemitter3.default {
7241
7237
  * @public
7242
7238
  */
7243
7239
  async getSvgElement(shapes, opts = {}) {
7244
- const ids = shapes.length === 0 ? this.getCurrentPageShapeIdsSorted() : typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
7240
+ const ids = shapes.length === 0 ? this.getCurrentPageShapeIdsSorted() : toShapeIds(shapes);
7245
7241
  if (ids.length === 0) return void 0;
7246
7242
  await this.fonts.loadRequiredFontsForCurrentPage(this.options.maxFontsToLoadBeforeRender);
7247
7243
  return (0, import_exportToSvg.exportToSvg)(this, ids, opts);
@@ -7837,22 +7833,22 @@ class Editor extends import_eventemitter3.default {
7837
7833
  const releaseCtrl = this._ctrlKeyTimeout !== -1;
7838
7834
  const releaseMeta = this._metaKeyTimeout !== -1;
7839
7835
  if (releaseShift) {
7840
- clearTimeout(this._shiftKeyTimeout);
7836
+ this.timers.clearTimeout(this._shiftKeyTimeout);
7841
7837
  this._shiftKeyTimeout = -1;
7842
7838
  this.inputs.setShiftKey(false);
7843
7839
  }
7844
7840
  if (releaseAlt) {
7845
- clearTimeout(this._altKeyTimeout);
7841
+ this.timers.clearTimeout(this._altKeyTimeout);
7846
7842
  this._altKeyTimeout = -1;
7847
7843
  this.inputs.setAltKey(false);
7848
7844
  }
7849
7845
  if (releaseCtrl) {
7850
- clearTimeout(this._ctrlKeyTimeout);
7846
+ this.timers.clearTimeout(this._ctrlKeyTimeout);
7851
7847
  this._ctrlKeyTimeout = -1;
7852
7848
  this.inputs.setCtrlKey(false);
7853
7849
  }
7854
7850
  if (releaseMeta) {
7855
- clearTimeout(this._metaKeyTimeout);
7851
+ this.timers.clearTimeout(this._metaKeyTimeout);
7856
7852
  this._metaKeyTimeout = -1;
7857
7853
  this.inputs.setMetaKey(false);
7858
7854
  }
@@ -7966,28 +7962,28 @@ class Editor extends import_eventemitter3.default {
7966
7962
  return;
7967
7963
  }
7968
7964
  if (info.shiftKey) {
7969
- clearTimeout(this._shiftKeyTimeout);
7965
+ this.timers.clearTimeout(this._shiftKeyTimeout);
7970
7966
  this._shiftKeyTimeout = -1;
7971
7967
  inputs.setShiftKey(true);
7972
7968
  } else if (!info.shiftKey && inputs.getShiftKey() && this._shiftKeyTimeout === -1) {
7973
7969
  this._shiftKeyTimeout = this.timers.setTimeout(this._releaseShiftKey, 150);
7974
7970
  }
7975
7971
  if (info.altKey) {
7976
- clearTimeout(this._altKeyTimeout);
7972
+ this.timers.clearTimeout(this._altKeyTimeout);
7977
7973
  this._altKeyTimeout = -1;
7978
7974
  inputs.setAltKey(true);
7979
7975
  } else if (!info.altKey && inputs.getAltKey() && this._altKeyTimeout === -1) {
7980
7976
  this._altKeyTimeout = this.timers.setTimeout(this._releaseAltKey, 150);
7981
7977
  }
7982
7978
  if (info.ctrlKey) {
7983
- clearTimeout(this._ctrlKeyTimeout);
7979
+ this.timers.clearTimeout(this._ctrlKeyTimeout);
7984
7980
  this._ctrlKeyTimeout = -1;
7985
7981
  inputs.setCtrlKey(true);
7986
7982
  } else if (!info.ctrlKey && inputs.getCtrlKey() && this._ctrlKeyTimeout === -1) {
7987
7983
  this._ctrlKeyTimeout = this.timers.setTimeout(this._releaseCtrlKey, 150);
7988
7984
  }
7989
7985
  if (info.metaKey && info.name !== "key_up") {
7990
- clearTimeout(this._metaKeyTimeout);
7986
+ this.timers.clearTimeout(this._metaKeyTimeout);
7991
7987
  this._metaKeyTimeout = -1;
7992
7988
  inputs.setMetaKey(true);
7993
7989
  } else if (!info.metaKey && inputs.getMetaKey() && this._metaKeyTimeout === -1) {
@@ -8001,8 +7997,7 @@ class Editor extends import_eventemitter3.default {
8001
7997
  const cameraOptions = this._cameraOptions.__unsafe__getWithoutCapture();
8002
7998
  switch (type) {
8003
7999
  case "pinch": {
8004
- if (cameraOptions.isLocked) return;
8005
- clearTimeout(this._longPressTimeout);
8000
+ this.timers.clearTimeout(this._longPressTimeout);
8006
8001
  this.inputs.updateFromEvent(info);
8007
8002
  switch (info.name) {
8008
8003
  case "pinch_start": {
@@ -8023,6 +8018,7 @@ class Editor extends import_eventemitter3.default {
8023
8018
  }
8024
8019
  case "pinch": {
8025
8020
  if (!inputs.getIsPinching()) return;
8021
+ if (cameraOptions.isLocked) return;
8026
8022
  const {
8027
8023
  point: { z = 1 },
8028
8024
  delta: { x: dx, y: dy }
@@ -8078,7 +8074,7 @@ class Editor extends import_eventemitter3.default {
8078
8074
  const { panSpeed, zoomSpeed } = cameraOptions;
8079
8075
  let wheelBehavior = cameraOptions.wheelBehavior;
8080
8076
  const inputMode = this.user.getUserPreferences().inputMode;
8081
- if (inputMode !== null) {
8077
+ if (inputMode !== null && wheelBehavior !== "none") {
8082
8078
  wheelBehavior = inputMode === "trackpad" ? "pan" : "zoom";
8083
8079
  }
8084
8080
  if (wheelBehavior !== "none") {
@@ -8130,12 +8126,12 @@ class Editor extends import_eventemitter3.default {
8130
8126
  }
8131
8127
  case "pointer": {
8132
8128
  if (inputs.getIsPinching()) return;
8133
- this.inputs.updateFromEvent(info);
8134
8129
  const { isPen } = info;
8135
8130
  const { isPenMode } = instanceState;
8131
+ if (isPenMode && !isPen) return;
8132
+ this.inputs.updateFromEvent(info);
8136
8133
  switch (info.name) {
8137
8134
  case "pointer_down": {
8138
- if (isPenMode && !isPen) return;
8139
8135
  this._releaseDebouncedModifiers();
8140
8136
  if (!this.inputs.getIsPanning()) {
8141
8137
  this._longPressTimeout = this.timers.setTimeout(() => {
@@ -8159,11 +8155,12 @@ class Editor extends import_eventemitter3.default {
8159
8155
  inputs.buttons.add(info.button);
8160
8156
  inputs.setIsPointing(true);
8161
8157
  inputs.setIsDragging(false);
8158
+ this.stopCameraAnimation();
8162
8159
  if (!isPenMode && info.isPenDirect) {
8163
8160
  this.updateInstanceState({ isPenMode: true });
8164
8161
  this.interrupt();
8165
8162
  }
8166
- if (info.button === import_constants.STYLUS_ERASER_BUTTON) {
8163
+ if (info.button === import_constants.STYLUS_ERASER_BUTTON && this.getStateDescendant("eraser")) {
8167
8164
  this._restoreToolId = this.getCurrentToolId();
8168
8165
  this.complete();
8169
8166
  this.setCurrentTool("eraser");
@@ -8172,10 +8169,10 @@ class Editor extends import_eventemitter3.default {
8172
8169
  this._prevCursor = this.getInstanceState().cursor.type;
8173
8170
  }
8174
8171
  this.inputs.setIsPanning(true);
8175
- clearTimeout(this._longPressTimeout);
8172
+ this.timers.clearTimeout(this._longPressTimeout);
8176
8173
  } else if (info.button === import_constants.RIGHT_MOUSE_BUTTON && this.options.rightClickPanning) {
8177
8174
  this.inputs.setIsRightPointing(true);
8178
- clearTimeout(this._longPressTimeout);
8175
+ this.timers.clearTimeout(this._longPressTimeout);
8179
8176
  return this;
8180
8177
  }
8181
8178
  if (this.inputs.getIsPanning()) {
@@ -8186,7 +8183,6 @@ class Editor extends import_eventemitter3.default {
8186
8183
  break;
8187
8184
  }
8188
8185
  case "pointer_move": {
8189
- if (!isPen && isPenMode) return;
8190
8186
  const { x: cx, y: cy, z: cz } = (0, import_state.unsafe__withoutCapture)(() => this.getCamera());
8191
8187
  if (this.inputs.getIsRightPointing() && !this.inputs.getIsPanning()) {
8192
8188
  const currentScreenPoint = this.inputs.getCurrentScreenPoint();
@@ -8217,16 +8213,15 @@ class Editor extends import_eventemitter3.default {
8217
8213
  }
8218
8214
  if (inputs.getIsPointing() && !inputs.getIsDragging() && import_Vec.Vec.Dist2(inputs.getOriginPagePoint(), inputs.getCurrentPagePoint()) * this.getZoomLevel() > (instanceState.isCoarsePointer ? this.options.coarseDragDistanceSquared : this.options.dragDistanceSquared) / cz) {
8219
8215
  inputs.setIsDragging(true);
8220
- clearTimeout(this._longPressTimeout);
8216
+ this.timers.clearTimeout(this._longPressTimeout);
8221
8217
  }
8222
8218
  break;
8223
8219
  }
8224
8220
  case "pointer_up": {
8225
8221
  inputs.setIsDragging(false);
8226
8222
  inputs.setIsPointing(false);
8227
- clearTimeout(this._longPressTimeout);
8223
+ this.timers.clearTimeout(this._longPressTimeout);
8228
8224
  inputs.buttons.delete(info.button);
8229
- if (instanceState.isPenMode && !isPen) return;
8230
8225
  if (this.inputs.getIsRightPointing() && !this.inputs.getIsPanning()) {
8231
8226
  this.inputs.setIsRightPointing(false);
8232
8227
  this._selectedShapeIdsAtPointerDown = [];
@@ -8282,7 +8277,7 @@ class Editor extends import_eventemitter3.default {
8282
8277
  });
8283
8278
  }
8284
8279
  } else {
8285
- if (info.button === import_constants.STYLUS_ERASER_BUTTON) {
8280
+ if (info.button === import_constants.STYLUS_ERASER_BUTTON && this.getStateDescendant("eraser")) {
8286
8281
  this.complete();
8287
8282
  this.setCurrentTool(this._restoreToolId);
8288
8283
  }
@@ -8295,8 +8290,8 @@ class Editor extends import_eventemitter3.default {
8295
8290
  break;
8296
8291
  }
8297
8292
  case "keyboard": {
8298
- if (info.key === "ShiftRight") info.key = "ShiftLeft";
8299
- if (info.key === "AltRight") info.key = "AltLeft";
8293
+ if (info.code === "ShiftRight") info.code = "ShiftLeft";
8294
+ if (info.code === "AltRight") info.code = "AltLeft";
8300
8295
  if (info.code === "ControlRight") info.code = "ControlLeft";
8301
8296
  if (info.code === "MetaRight") info.code = "MetaLeft";
8302
8297
  switch (info.name) {
@@ -8309,7 +8304,7 @@ class Editor extends import_eventemitter3.default {
8309
8304
  }
8310
8305
  this.inputs.setIsPanning(true);
8311
8306
  this.inputs.setIsSpacebarPanning(true);
8312
- clearTimeout(this._longPressTimeout);
8307
+ this.timers.clearTimeout(this._longPressTimeout);
8313
8308
  this.setCursor({
8314
8309
  type: this.inputs.getIsPointing() ? "grabbing" : "grab",
8315
8310
  rotation: 0
@@ -8335,7 +8330,7 @@ class Editor extends import_eventemitter3.default {
8335
8330
  break;
8336
8331
  }
8337
8332
  }
8338
- if (offset) {
8333
+ if (offset && !cameraOptions.isLocked) {
8339
8334
  const bounds = this.getViewportPageBounds();
8340
8335
  const next = bounds.clone().translate(offset.mulV({ x: bounds.w, y: bounds.h }));
8341
8336
  this._animateToViewport(next, { animation: { duration: 320 } });
@@ -8375,7 +8370,7 @@ class Editor extends import_eventemitter3.default {
8375
8370
  this.setCurrentTool("select");
8376
8371
  }
8377
8372
  const { isPenMode } = this.store.unsafeGetWithoutCapture(import_tlschema.TLINSTANCE_ID);
8378
- if (info.isPen === isPenMode) {
8373
+ if (!isPenMode || info.isPen) {
8379
8374
  const clickInfo = this._clickManager.handlePointerEvent(info);
8380
8375
  if (info.name !== clickInfo.name) {
8381
8376
  this.root.handleEvent(info);
@@ -8397,7 +8392,7 @@ class Editor extends import_eventemitter3.default {
8397
8392
  maybeTrackPerformance(name) {
8398
8393
  if (import_debug_flags.debugFlags.measurePerformance.get()) {
8399
8394
  if (this.performanceTracker.isStarted()) {
8400
- clearTimeout(this.performanceTrackerTimeout);
8395
+ this.timers.clearTimeout(this.performanceTrackerTimeout);
8401
8396
  } else {
8402
8397
  this.performanceTracker.start(name);
8403
8398
  }
@@ -8666,6 +8661,9 @@ function pushShapeWithDescendants(editor, id, result) {
8666
8661
  pushShapeWithDescendants(editor, childIds[i], result);
8667
8662
  }
8668
8663
  }
8664
+ function toShapeIds(shapes) {
8665
+ return typeof shapes[0] === "string" ? shapes : shapes.map((shape) => shape.id);
8666
+ }
8669
8667
  function withIsolatedShapes(editor, shapeIds, callback) {
8670
8668
  let result;
8671
8669
  editor.run(
@@ -8688,7 +8686,7 @@ function withIsolatedShapes(editor, shapeIds, callback) {
8688
8686
  }
8689
8687
  }
8690
8688
  }
8691
- editor.deleteBindings([...bindingsToRemove], { isolateShapes: true });
8689
+ editor._deleteBindings([...bindingsToRemove], { isolateShapes: true });
8692
8690
  try {
8693
8691
  result = import_utils.Result.ok(callback(bindingsWithBoth));
8694
8692
  } catch (error) {