@tldraw/editor 5.4.2 → 5.5.0-canary.003bf5951274

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 (200) hide show
  1. package/dist-cjs/index.d.ts +37 -14
  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/constants.js +2 -0
  14. package/dist-cjs/lib/constants.js.map +2 -2
  15. package/dist-cjs/lib/editor/Editor.js +147 -157
  16. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  17. package/dist-cjs/lib/editor/derivations/bindingsIndex.js +2 -0
  18. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +10 -6
  20. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +4 -4
  22. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +7 -3
  24. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +2 -2
  25. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +14 -6
  26. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +3 -3
  27. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  28. package/dist-cjs/lib/exports/ExportDelay.js +10 -2
  29. package/dist-cjs/lib/exports/ExportDelay.js.map +2 -2
  30. package/dist-cjs/lib/exports/FontEmbedder.js +25 -11
  31. package/dist-cjs/lib/exports/FontEmbedder.js.map +2 -2
  32. package/dist-cjs/lib/exports/StyleEmbedder.js +9 -4
  33. package/dist-cjs/lib/exports/StyleEmbedder.js.map +2 -2
  34. package/dist-cjs/lib/exports/fetchCache.js +3 -0
  35. package/dist-cjs/lib/exports/fetchCache.js.map +2 -2
  36. package/dist-cjs/lib/exports/getSvgAsImage.js +10 -4
  37. package/dist-cjs/lib/exports/getSvgAsImage.js.map +2 -2
  38. package/dist-cjs/lib/exports/getSvgJsx.js +18 -9
  39. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  40. package/dist-cjs/lib/exports/parseCss.js +1 -1
  41. package/dist-cjs/lib/exports/parseCss.js.map +1 -1
  42. package/dist-cjs/lib/hooks/EditorComponentsContext.js.map +2 -2
  43. package/dist-cjs/lib/hooks/useCanvasEvents.js +24 -0
  44. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  45. package/dist-cjs/lib/hooks/useIdentity.js +6 -2
  46. package/dist-cjs/lib/hooks/useIdentity.js.map +3 -3
  47. package/dist-cjs/lib/license/LicenseManager.js +9 -1
  48. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  49. package/dist-cjs/lib/license/LicenseProvider.js +12 -4
  50. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  51. package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
  52. package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
  53. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
  54. package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
  55. package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
  56. package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
  57. package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -5
  58. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  59. package/dist-cjs/lib/primitives/geometry/Point2d.js +1 -1
  60. package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
  61. package/dist-cjs/lib/utils/debug-flags.js +0 -6
  62. package/dist-cjs/lib/utils/debug-flags.js.map +2 -2
  63. package/dist-cjs/lib/utils/deepLinks.js +2 -1
  64. package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
  65. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js +22 -24
  66. package/dist-cjs/lib/utils/sync/LocalIndexedDb.js.map +2 -2
  67. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +81 -40
  68. package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
  69. package/dist-cjs/version.js +3 -3
  70. package/dist-cjs/version.js.map +1 -1
  71. package/dist-esm/index.d.mts +37 -14
  72. package/dist-esm/index.mjs +4 -2
  73. package/dist-esm/index.mjs.map +2 -2
  74. package/dist-esm/lib/TldrawEditor.mjs +17 -13
  75. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  76. package/dist-esm/lib/components/MenuClickCapture.mjs +42 -3
  77. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  78. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +5 -1
  79. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  80. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +2 -5
  81. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +3 -3
  82. package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
  83. package/dist-esm/lib/constants.mjs +2 -0
  84. package/dist-esm/lib/constants.mjs.map +2 -2
  85. package/dist-esm/lib/editor/Editor.mjs +148 -157
  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/EdgeScrollManager/EdgeScrollManager.mjs +10 -6
  90. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  91. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +4 -4
  92. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  93. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +7 -3
  94. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +2 -2
  95. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +14 -6
  96. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +3 -3
  97. package/dist-esm/lib/exports/ExportDelay.mjs +11 -3
  98. package/dist-esm/lib/exports/ExportDelay.mjs.map +2 -2
  99. package/dist-esm/lib/exports/FontEmbedder.mjs +25 -11
  100. package/dist-esm/lib/exports/FontEmbedder.mjs.map +2 -2
  101. package/dist-esm/lib/exports/StyleEmbedder.mjs +9 -4
  102. package/dist-esm/lib/exports/StyleEmbedder.mjs.map +2 -2
  103. package/dist-esm/lib/exports/fetchCache.mjs +3 -0
  104. package/dist-esm/lib/exports/fetchCache.mjs.map +2 -2
  105. package/dist-esm/lib/exports/getSvgAsImage.mjs +10 -4
  106. package/dist-esm/lib/exports/getSvgAsImage.mjs.map +2 -2
  107. package/dist-esm/lib/exports/getSvgJsx.mjs +18 -9
  108. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  109. package/dist-esm/lib/exports/parseCss.mjs +1 -1
  110. package/dist-esm/lib/exports/parseCss.mjs.map +1 -1
  111. package/dist-esm/lib/hooks/EditorComponentsContext.mjs.map +2 -2
  112. package/dist-esm/lib/hooks/useCanvasEvents.mjs +24 -0
  113. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  114. package/dist-esm/lib/hooks/useIdentity.mjs +7 -3
  115. package/dist-esm/lib/hooks/useIdentity.mjs.map +3 -3
  116. package/dist-esm/lib/license/LicenseManager.mjs +9 -1
  117. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  118. package/dist-esm/lib/license/LicenseProvider.mjs +13 -5
  119. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  120. package/dist-esm/lib/primitives/geometry/Arc2d.mjs +1 -1
  121. package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
  122. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
  123. package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
  124. package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
  125. package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
  126. package/dist-esm/lib/primitives/geometry/Group2d.mjs +12 -6
  127. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  128. package/dist-esm/lib/primitives/geometry/Point2d.mjs +1 -1
  129. package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
  130. package/dist-esm/lib/utils/debug-flags.mjs +0 -6
  131. package/dist-esm/lib/utils/debug-flags.mjs.map +2 -2
  132. package/dist-esm/lib/utils/deepLinks.mjs +2 -1
  133. package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
  134. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs +22 -24
  135. package/dist-esm/lib/utils/sync/LocalIndexedDb.mjs.map +2 -2
  136. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +81 -40
  137. package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
  138. package/dist-esm/version.mjs +3 -3
  139. package/dist-esm/version.mjs.map +1 -1
  140. package/editor.css +9 -1
  141. package/package.json +8 -11
  142. package/src/index.ts +5 -1
  143. package/src/lib/TldrawEditor.tsx +35 -21
  144. package/src/lib/components/MenuClickCapture.tsx +55 -4
  145. package/src/lib/components/default-components/DefaultCanvas.tsx +7 -1
  146. package/src/lib/components/default-components/DefaultErrorFallback.tsx +13 -5
  147. package/src/lib/components/default-components/DefaultSpinner.tsx +4 -1
  148. package/src/lib/constants.ts +8 -0
  149. package/src/lib/editor/Editor.test.ts +20 -0
  150. package/src/lib/editor/Editor.ts +228 -262
  151. package/src/lib/editor/derivations/bindingsIndex.ts +3 -0
  152. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +35 -4
  153. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +13 -7
  154. package/src/lib/editor/managers/TextManager/TextManager.test.ts +74 -0
  155. package/src/lib/editor/managers/TextManager/TextManager.ts +6 -5
  156. package/src/lib/editor/managers/ThemeManager/ThemeManager.test.ts +53 -0
  157. package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +9 -3
  158. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +23 -8
  159. package/src/lib/editor/types/external-content.ts +5 -0
  160. package/src/lib/exports/ExportDelay.tsx +16 -4
  161. package/src/lib/exports/FontEmbedder.test.ts +99 -0
  162. package/src/lib/exports/FontEmbedder.ts +32 -11
  163. package/src/lib/exports/StyleEmbedder.ts +13 -4
  164. package/src/lib/exports/fetchCache.test.ts +22 -0
  165. package/src/lib/exports/fetchCache.ts +4 -0
  166. package/src/lib/exports/getSvgAsImage.ts +13 -5
  167. package/src/lib/exports/getSvgJsx.test.ts +43 -1
  168. package/src/lib/exports/getSvgJsx.tsx +28 -16
  169. package/src/lib/exports/parseCss.test.ts +4 -0
  170. package/src/lib/exports/parseCss.ts +2 -2
  171. package/src/lib/hooks/EditorComponentsContext.tsx +2 -1
  172. package/src/lib/hooks/useCanvasEvents.ts +42 -0
  173. package/src/lib/hooks/useIdentity.tsx +12 -1
  174. package/src/lib/license/LicenseManager.ts +13 -1
  175. package/src/lib/license/LicenseProvider.test.tsx +76 -0
  176. package/src/lib/license/LicenseProvider.tsx +19 -5
  177. package/src/lib/primitives/geometry/Arc2d.test.ts +9 -0
  178. package/src/lib/primitives/geometry/Arc2d.ts +1 -1
  179. package/src/lib/primitives/geometry/CubicBezier2d.test.ts +13 -0
  180. package/src/lib/primitives/geometry/CubicBezier2d.ts +1 -1
  181. package/src/lib/primitives/geometry/Edge2d.test.ts +21 -3
  182. package/src/lib/primitives/geometry/Edge2d.ts +1 -1
  183. package/src/lib/primitives/geometry/Group2d.test.ts +26 -5
  184. package/src/lib/primitives/geometry/Group2d.ts +18 -7
  185. package/src/lib/primitives/geometry/Point2d.test.ts +14 -3
  186. package/src/lib/primitives/geometry/Point2d.ts +1 -1
  187. package/src/lib/test/preserveFocus.test.tsx +81 -0
  188. package/src/lib/utils/debug-flags.ts +0 -6
  189. package/src/lib/utils/deepLinks.test.ts +15 -0
  190. package/src/lib/utils/deepLinks.ts +8 -3
  191. package/src/lib/utils/sync/LocalIndexedDb.test.ts +95 -0
  192. package/src/lib/utils/sync/LocalIndexedDb.ts +31 -27
  193. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +257 -1
  194. package/src/lib/utils/sync/TLLocalSyncClient.ts +112 -54
  195. package/src/version.ts +3 -3
  196. package/dist-cjs/lib/primitives/geometry/geometry.bench.js +0 -133
  197. package/dist-cjs/lib/primitives/geometry/geometry.bench.js.map +0 -7
  198. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs +0 -132
  199. package/dist-esm/lib/primitives/geometry/geometry.bench.mjs.map +0 -7
  200. 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 }]);
@@ -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,14 +1615,18 @@ 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
- const adjacentIndex = (currentIndex + (direction === "next" ? 1 : -1) + shapeIds.length) % shapeIds.length;
1628
+ const startIndex = currentIndex === -1 && direction === "prev" ? 0 : currentIndex;
1629
+ const adjacentIndex = (startIndex + (direction === "next" ? 1 : -1) + shapeIds.length) % shapeIds.length;
1621
1630
  adjacentShapeId = shapeIds[adjacentIndex];
1622
1631
  } else {
1623
1632
  if (!currentShapeId) return;
@@ -1716,13 +1725,14 @@ class Editor extends import_eventemitter3.default {
1716
1725
  });
1717
1726
  if (shapesInDirection.length === 0) return currentShapeId;
1718
1727
  const lowestScoringShape = (0, import_utils.minBy)(shapesInDirection, ({ center }) => {
1719
- const distance = import_Vec.Vec.Dist2(currentCenter, center);
1728
+ const distance = import_Vec.Vec.Dist(currentCenter, center);
1720
1729
  const dirProp = ["left", "right"].includes(direction) ? "x" : "y";
1721
1730
  const directionalDistance = Math.abs(center[dirProp] - currentCenter[dirProp]);
1722
1731
  const offProp = ["left", "right"].includes(direction) ? "y" : "x";
1723
1732
  const offAxisDeviation = Math.abs(center[offProp] - currentCenter[offProp]);
1724
- const angle = Math.abs(import_Vec.Vec.Angle(currentCenter, center) * (180 / Math.PI));
1725
- 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);
1726
1736
  return distance * 1 + // Base distance
1727
1737
  offAxisDeviation * 2 + // Heavy penalty for off-axis deviation
1728
1738
  (distance - directionalDistance) * 1.5 + // Penalty for diagonal distance
@@ -2060,7 +2070,7 @@ class Editor extends import_eventemitter3.default {
2060
2070
  * @public
2061
2071
  */
2062
2072
  setHintingShapes(shapes) {
2063
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((shape) => shape.id);
2073
+ const ids = toShapeIds(shapes);
2064
2074
  this.run(
2065
2075
  () => {
2066
2076
  this._updateCurrentPageState({ hintingShapeIds: (0, import_utils.dedupe)(ids) });
@@ -2090,7 +2100,7 @@ class Editor extends import_eventemitter3.default {
2090
2100
  * @public
2091
2101
  */
2092
2102
  setErasingShapes(shapes) {
2093
- const ids = typeof shapes[0] === "string" ? shapes.slice() : shapes.map((shape) => shape.id);
2103
+ const ids = toShapeIds(shapes).slice();
2094
2104
  ids.sort();
2095
2105
  const erasingShapeIds = this.getErasingShapeIds();
2096
2106
  this.run(
@@ -2497,14 +2507,16 @@ class Editor extends import_eventemitter3.default {
2497
2507
  setCamera(point, opts) {
2498
2508
  const { isLocked } = this._cameraOptions.__unsafe__getWithoutCapture();
2499
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
+ }
2500
2516
  this.stopCameraAnimation();
2501
2517
  if (this.getInstanceState().followingUserId) {
2502
2518
  this.stopFollowingUser();
2503
2519
  }
2504
- const _point = import_Vec.Vec.Cast(point);
2505
- if (!Number.isFinite(_point.x)) _point.x = 0;
2506
- if (!Number.isFinite(_point.y)) _point.y = 0;
2507
- if (_point.z === void 0 || !Number.isFinite(_point.z)) point.z = this.getZoomLevel();
2508
2520
  const camera = this.getConstrainedCamera(_point, opts);
2509
2521
  if (opts?.animation) {
2510
2522
  const { width, height } = this.getViewportScreenBounds();
@@ -2789,11 +2801,14 @@ class Editor extends import_eventemitter3.default {
2789
2801
  _animateViewport(ms) {
2790
2802
  if (!this._viewportAnimation) return;
2791
2803
  this._viewportAnimation.elapsed += ms;
2792
- const { elapsed, easing, duration, start, end } = this._viewportAnimation;
2804
+ const { elapsed, easing, duration, start, end, opts } = this._viewportAnimation;
2793
2805
  if (elapsed > duration) {
2794
2806
  this.off("tick", this._animateViewport);
2795
2807
  this._viewportAnimation = null;
2796
- 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
+ );
2797
2812
  return;
2798
2813
  }
2799
2814
  const remaining = duration - elapsed;
@@ -2831,7 +2846,8 @@ class Editor extends import_eventemitter3.default {
2831
2846
  duration: duration / animationSpeed,
2832
2847
  easing,
2833
2848
  start: viewportPageBounds.clone(),
2834
- end: targetViewportPage.clone()
2849
+ end: targetViewportPage.clone(),
2850
+ opts: rest
2835
2851
  };
2836
2852
  this.once("stop-camera-animation", () => {
2837
2853
  this.off("tick", this._animateViewport);
@@ -2883,7 +2899,7 @@ class Editor extends import_eventemitter3.default {
2883
2899
  newCx += center.x / newCz - center.x / cz;
2884
2900
  newCy += center.y / newCz - center.y / cz;
2885
2901
  }
2886
- currentSpeed *= 1 - friction;
2902
+ currentSpeed *= (1 - friction) ** (elapsed / import_constants.FRAME_MS_60HZ);
2887
2903
  if (currentSpeed < speedThreshold) {
2888
2904
  cancel();
2889
2905
  } else {
@@ -2917,6 +2933,7 @@ class Editor extends import_eventemitter3.default {
2917
2933
  }
2918
2934
  const isOnSamePage = presence.currentPageId === this.getCurrentPageId();
2919
2935
  if (!isOnSamePage) {
2936
+ this.markHistoryStoppingPoint("change-page");
2920
2937
  this.setCurrentPage(presence.currentPageId);
2921
2938
  }
2922
2939
  if (opts && opts.animation && !isOnSamePage) {
@@ -3748,7 +3765,9 @@ class Editor extends import_eventemitter3.default {
3748
3765
  if (ids.length <= 0) return this;
3749
3766
  this.run(
3750
3767
  () => {
3751
- this.store.props.assets.remove?.(ids);
3768
+ Promise.resolve(this.store.props.assets.remove?.(ids)).catch(
3769
+ (err) => console.error("Error while removing assets from the asset store:", err)
3770
+ );
3752
3771
  this.store.remove(ids);
3753
3772
  },
3754
3773
  { history: "ignore" }
@@ -4130,7 +4149,7 @@ class Editor extends import_eventemitter3.default {
4130
4149
  if (shapes.length === 0) {
4131
4150
  return;
4132
4151
  }
4133
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4152
+ const ids = toShapeIds(shapes);
4134
4153
  const freshShapes = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
4135
4154
  if (freshShapes.length === 1) {
4136
4155
  const parentId = freshShapes[0].parentId;
@@ -4227,12 +4246,14 @@ class Editor extends import_eventemitter3.default {
4227
4246
  */
4228
4247
  getSelectedShapeAtPoint(point) {
4229
4248
  const selectedShapeIds = this.getSelectedShapeIds();
4230
- const margin = this.options.hitTestMargin / this.getZoomLevel();
4249
+ if (selectedShapeIds.length === 0) return void 0;
4250
+ const selectedShapeIdSet = new Set(selectedShapeIds);
4251
+ const margin = this.getHitTestMargin();
4231
4252
  const sortedShapes = this.getCurrentPageShapesSorted();
4232
4253
  for (let i = sortedShapes.length - 1; i >= 0; i--) {
4233
4254
  const shape = sortedShapes[i];
4234
4255
  if (shape.type === "group") continue;
4235
- if (!selectedShapeIds.includes(shape.id)) continue;
4256
+ if (!selectedShapeIdSet.has(shape.id)) continue;
4236
4257
  if (this.getShapeGeometry(shape).hitTestPoint(
4237
4258
  this.getPointInShapeSpace(shape, point),
4238
4259
  margin,
@@ -4330,7 +4351,6 @@ class Editor extends import_eventemitter3.default {
4330
4351
  }
4331
4352
  return inMarginClosestToEdgeHit || shape;
4332
4353
  } else {
4333
- if (this.getShapePageBounds(shape).contains(viewportPageBounds)) continue;
4334
4354
  if (hitInside ? (
4335
4355
  // On hitInside, the distance will be negative for hits inside
4336
4356
  // If the distance is positive, check against the outer margin
@@ -4347,6 +4367,7 @@ class Editor extends import_eventemitter3.default {
4347
4367
  inMarginClosestToEdgeHit = shape;
4348
4368
  }
4349
4369
  } else if (!inMarginClosestToEdgeHit) {
4370
+ if (this.getShapePageBounds(shape).contains(viewportPageBounds)) continue;
4350
4371
  const { area } = geometry;
4351
4372
  if (area < inHollowSmallestArea) {
4352
4373
  inHollowSmallestArea = area;
@@ -4357,6 +4378,9 @@ class Editor extends import_eventemitter3.default {
4357
4378
  }
4358
4379
  } else {
4359
4380
  if (distance < this.getHitTestMargin()) {
4381
+ if (inMarginClosestToEdgeHit && inMarginClosestToEdgeDistance <= distance) {
4382
+ return inMarginClosestToEdgeHit;
4383
+ }
4360
4384
  return shape;
4361
4385
  }
4362
4386
  }
@@ -4633,7 +4657,7 @@ class Editor extends import_eventemitter3.default {
4633
4657
  * @public
4634
4658
  */
4635
4659
  reparentShapes(shapes, parentId, insertIndex) {
4636
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4660
+ const ids = toShapeIds(shapes);
4637
4661
  if (ids.length === 0) return this;
4638
4662
  const changes = [];
4639
4663
  const parentTransform = (0, import_tlschema.isPageId)(parentId) ? import_Mat.Mat.Identity() : this.getShapePageTransform(parentId);
@@ -4862,6 +4886,7 @@ class Editor extends import_eventemitter3.default {
4862
4886
  * binding, but the `type`, `toId`, and `fromId` must all be provided.
4863
4887
  */
4864
4888
  createBindings(partials) {
4889
+ if (this.getIsReadonly()) return this;
4865
4890
  const bindings = [];
4866
4891
  for (const partial of partials) {
4867
4892
  const fromShape = this.getShape(partial.fromId);
@@ -4896,6 +4921,7 @@ class Editor extends import_eventemitter3.default {
4896
4921
  * binding is skipped. The changes from the partial are merged into the existing record.
4897
4922
  */
4898
4923
  updateBindings(partials) {
4924
+ if (this.getIsReadonly()) return this;
4899
4925
  const updated = [];
4900
4926
  for (const partial of partials) {
4901
4927
  if (!partial) continue;
@@ -4924,6 +4950,16 @@ class Editor extends import_eventemitter3.default {
4924
4950
  * Delete several bindings by their IDs. If a binding ID doesn't exist, it's ignored.
4925
4951
  */
4926
4952
  deleteBindings(bindings, { isolateShapes = false } = {}) {
4953
+ if (this.getIsReadonly()) return this;
4954
+ return this._deleteBindings(bindings, { isolateShapes });
4955
+ }
4956
+ /**
4957
+ * Unguarded so that withIsolatedShapes can transiently isolate shapes for copy and export in
4958
+ * readonly mode; the public deleteBindings is what readonly blocks.
4959
+ *
4960
+ * @internal
4961
+ */
4962
+ _deleteBindings(bindings, { isolateShapes = false } = {}) {
4927
4963
  const ids = bindings.map((binding) => typeof binding === "string" ? binding : binding.id);
4928
4964
  if (isolateShapes) {
4929
4965
  this.store.atomic(() => {
@@ -4982,7 +5018,7 @@ class Editor extends import_eventemitter3.default {
4982
5018
  * @param opts - The options for the rotation.
4983
5019
  */
4984
5020
  rotateShapesBy(shapes, delta, opts) {
4985
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5021
+ const ids = toShapeIds(shapes);
4986
5022
  if (ids.length <= 0) return this;
4987
5023
  const snapshot = (0, import_rotation.getRotationSnapshot)({ editor: this, ids });
4988
5024
  if (!snapshot) return this;
@@ -5019,6 +5055,14 @@ class Editor extends import_eventemitter3.default {
5019
5055
  }
5020
5056
  return workingShape;
5021
5057
  }
5058
+ // Counter-rotates the page-space delta into the parent's space; without that a child of a rotated
5059
+ // frame or group would move along its parent's axes instead of the page's.
5060
+ // todo: a shape laid out together with its own parent moves twice, once with the parent and once
5061
+ // on its own; the layout commands should skip shapes whose ancestors are also being laid out
5062
+ getChangesToTranslateShapeByPageDelta(shape, pageDelta) {
5063
+ const localDelta = import_Vec.Vec.From(pageDelta).rot(-this.getShapeParentTransform(shape).rotation());
5064
+ return this.getChangesToTranslateShape(shape, localDelta.add(shape));
5065
+ }
5022
5066
  /**
5023
5067
  * Move shapes by a delta.
5024
5068
  *
@@ -5031,14 +5075,12 @@ class Editor extends import_eventemitter3.default {
5031
5075
  * @param offset - The offset to apply to the shapes.
5032
5076
  */
5033
5077
  nudgeShapes(shapes, offset) {
5034
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5078
+ const ids = toShapeIds(shapes);
5035
5079
  if (ids.length <= 0) return this;
5036
5080
  const changes = [];
5037
5081
  for (const id of ids) {
5038
5082
  const shape = this.getShape(id);
5039
- const localDelta = import_Vec.Vec.From(offset);
5040
- localDelta.rot(-this.getShapeParentTransform(shape).rotation());
5041
- changes.push(this.getChangesToTranslateShape(shape, localDelta.add(shape)));
5083
+ changes.push(this.getChangesToTranslateShapeByPageDelta(shape, offset));
5042
5084
  }
5043
5085
  this.updateShapes(changes);
5044
5086
  return this;
@@ -5059,7 +5101,7 @@ class Editor extends import_eventemitter3.default {
5059
5101
  */
5060
5102
  duplicateShapes(shapes, offset) {
5061
5103
  this.run(() => {
5062
- const _ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5104
+ const _ids = toShapeIds(shapes);
5063
5105
  const ids = this._shouldIgnoreShapeLock ? _ids : this._getUnlockedShapeIds(_ids);
5064
5106
  if (ids.length <= 0) return this;
5065
5107
  const initialIds = new Set(ids);
@@ -5168,7 +5210,7 @@ class Editor extends import_eventemitter3.default {
5168
5210
  * @public
5169
5211
  */
5170
5212
  moveShapesToPage(shapes, pageId) {
5171
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5213
+ const ids = toShapeIds(shapes);
5172
5214
  if (ids.length === 0) return this;
5173
5215
  if (this.getIsReadonly()) return this;
5174
5216
  const currentPageId = this.getCurrentPageId();
@@ -5204,7 +5246,7 @@ class Editor extends import_eventemitter3.default {
5204
5246
  * @public
5205
5247
  */
5206
5248
  toggleLock(shapes) {
5207
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5249
+ const ids = toShapeIds(shapes);
5208
5250
  if (this.getIsReadonly() || ids.length === 0) return this;
5209
5251
  let allLocked = true;
5210
5252
  const shapesToToggle = [];
@@ -5243,7 +5285,7 @@ class Editor extends import_eventemitter3.default {
5243
5285
  * @public
5244
5286
  */
5245
5287
  sendToBack(shapes) {
5246
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5288
+ const ids = toShapeIds(shapes);
5247
5289
  const changes = (0, import_reorderShapes.getReorderingShapesChanges)(this, "toBack", ids, {
5248
5290
  considerAllShapes: true
5249
5291
  });
@@ -5273,7 +5315,7 @@ class Editor extends import_eventemitter3.default {
5273
5315
  * @public
5274
5316
  */
5275
5317
  sendBackward(shapes, opts = {}) {
5276
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5318
+ const ids = toShapeIds(shapes);
5277
5319
  const changes = (0, import_reorderShapes.getReorderingShapesChanges)(this, "backward", ids, opts);
5278
5320
  if (changes) this.updateShapes(changes);
5279
5321
  return this;
@@ -5301,7 +5343,7 @@ class Editor extends import_eventemitter3.default {
5301
5343
  * @public
5302
5344
  */
5303
5345
  bringForward(shapes, opts = {}) {
5304
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5346
+ const ids = toShapeIds(shapes);
5305
5347
  const changes = (0, import_reorderShapes.getReorderingShapesChanges)(this, "forward", ids, opts);
5306
5348
  if (changes) this.updateShapes(changes);
5307
5349
  return this;
@@ -5320,7 +5362,7 @@ class Editor extends import_eventemitter3.default {
5320
5362
  * @public
5321
5363
  */
5322
5364
  bringToFront(shapes) {
5323
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5365
+ const ids = toShapeIds(shapes);
5324
5366
  const changes = (0, import_reorderShapes.getReorderingShapesChanges)(this, "toFront", ids);
5325
5367
  if (changes) this.updateShapes(changes);
5326
5368
  return this;
@@ -5332,7 +5374,7 @@ class Editor extends import_eventemitter3.default {
5332
5374
  * @internal
5333
5375
  */
5334
5376
  getShapeClusters(shapes, type, opts) {
5335
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5377
+ const ids = toShapeIds(shapes);
5336
5378
  let freshShapes = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5337
5379
  if (opts?.filterAxisAligned) {
5338
5380
  freshShapes = freshShapes.filter((s) => {
@@ -5357,7 +5399,7 @@ class Editor extends import_eventemitter3.default {
5357
5399
  const shapesMovingTogether = [shape];
5358
5400
  const boundsOfShapesMovingTogether = [shapePageBounds];
5359
5401
  this.collectShapesViaArrowBindings({
5360
- bindings: this.getBindingsToShape(shape.id, "arrow"),
5402
+ bindings: this.getBindingsInvolvingShape(shape.id, "arrow"),
5361
5403
  initialShapes: freshShapes,
5362
5404
  resultShapes: shapesMovingTogether,
5363
5405
  resultBounds: boundsOfShapesMovingTogether,
@@ -5413,7 +5455,7 @@ class Editor extends import_eventemitter3.default {
5413
5455
  */
5414
5456
  flipShapes(shapes, operation) {
5415
5457
  if (this.getIsReadonly()) return this;
5416
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5458
+ const ids = toShapeIds(shapes);
5417
5459
  const shapesToFlipFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5418
5460
  for (const shape of shapesToFlipFirstPass) {
5419
5461
  if (this.isShapeOfType(shape, "group")) {
@@ -5487,21 +5529,7 @@ class Editor extends import_eventemitter3.default {
5487
5529
  const { clusters: shapeClustersToStack } = this.getShapeClusters(shapes, "stack");
5488
5530
  const len = shapeClustersToStack.length;
5489
5531
  if (_gap === 0 && len < 3 || len < 2) return this;
5490
- let val;
5491
- let min;
5492
- let max;
5493
- let dim;
5494
- if (operation === "horizontal") {
5495
- val = "x";
5496
- min = "minX";
5497
- max = "maxX";
5498
- dim = "width";
5499
- } else {
5500
- val = "y";
5501
- min = "minY";
5502
- max = "maxY";
5503
- dim = "height";
5504
- }
5532
+ const { val, min, max, dim } = AXIS[operation];
5505
5533
  let shapeGap = 0;
5506
5534
  shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min]);
5507
5535
  if (_gap === 0) {
@@ -5540,13 +5568,7 @@ class Editor extends import_eventemitter3.default {
5540
5568
  const delta = new import_Vec.Vec();
5541
5569
  delta[val] = v + shapeGap - pageBounds[val];
5542
5570
  for (const shape of shapes2) {
5543
- const shapeDelta = delta.clone();
5544
- const parent = this.getShapeParent(shape);
5545
- if (parent) {
5546
- shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5547
- }
5548
- shapeDelta.add(shape);
5549
- changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5571
+ changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta));
5550
5572
  }
5551
5573
  v += pageBounds[dim] + shapeGap;
5552
5574
  }
@@ -5626,13 +5648,7 @@ class Editor extends import_eventemitter3.default {
5626
5648
  for (const { shapes: shapes2, pageBounds, nextPageBounds } of shapeClustersToPack) {
5627
5649
  const delta = import_Vec.Vec.Sub(nextPageBounds.point, pageBounds.point).add(centerDelta);
5628
5650
  for (const shape of shapes2) {
5629
- const shapeDelta = delta.clone();
5630
- const parent = this.getShapeParent(shape);
5631
- if (parent) {
5632
- shapeDelta.rot(-this.getShapeParentTransform(shape).rotation());
5633
- }
5634
- shapeDelta.add(shape);
5635
- changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5651
+ changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta));
5636
5652
  }
5637
5653
  }
5638
5654
  if (changes.length) {
@@ -5692,13 +5708,7 @@ class Editor extends import_eventemitter3.default {
5692
5708
  }
5693
5709
  }
5694
5710
  for (const shape of shapes2) {
5695
- const shapeDelta = delta.clone();
5696
- const parent = this.getShapeParent(shape);
5697
- if (parent) {
5698
- shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5699
- }
5700
- shapeDelta.add(shape);
5701
- changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5711
+ changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta));
5702
5712
  }
5703
5713
  });
5704
5714
  this.updateShapes(changes);
@@ -5722,27 +5732,13 @@ class Editor extends import_eventemitter3.default {
5722
5732
  if (this.getIsReadonly()) return this;
5723
5733
  const { clusters: shapeClustersToDistribute } = this.getShapeClusters(shapes, "distribute");
5724
5734
  if (shapeClustersToDistribute.length < 3) return this;
5725
- let val;
5726
- let min;
5727
- let max;
5728
- let dim;
5729
- if (operation === "horizontal") {
5730
- val = "x";
5731
- min = "minX";
5732
- max = "maxX";
5733
- dim = "width";
5734
- } else {
5735
- val = "y";
5736
- min = "minY";
5737
- max = "maxY";
5738
- dim = "height";
5739
- }
5735
+ const { val, min, max, dim } = AXIS[operation];
5740
5736
  const changes = [];
5741
5737
  const first = shapeClustersToDistribute.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])[0];
5742
5738
  const last2 = shapeClustersToDistribute.sort((a, b) => b.pageBounds[max] - a.pageBounds[max])[0];
5743
5739
  if (first === last2) {
5744
5740
  const excludedShapeIds = new Set(first.shapes.map((s) => s.id));
5745
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5741
+ const ids = toShapeIds(shapes);
5746
5742
  return this.distributeShapes(
5747
5743
  ids.filter((id) => !excludedShapeIds.has(id)),
5748
5744
  operation
@@ -5766,13 +5762,7 @@ class Editor extends import_eventemitter3.default {
5766
5762
  delta[val] = last2.pageBounds[max] - pageBounds[max] - 1;
5767
5763
  }
5768
5764
  for (const shape of shapes2) {
5769
- const shapeDelta = delta.clone();
5770
- const parent = this.getShapeParent(shape);
5771
- if (parent) {
5772
- shapeDelta.rot(-this.getShapePageTransform(parent).rotation());
5773
- }
5774
- shapeDelta.add(shape);
5775
- changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5765
+ changes.push(this.getChangesToTranslateShapeByPageDelta(shape, delta));
5776
5766
  }
5777
5767
  v += pageBounds[dim] + gap;
5778
5768
  }
@@ -5802,32 +5792,17 @@ class Editor extends import_eventemitter3.default {
5802
5792
  );
5803
5793
  if (shapeClustersToStretch.length < 2) return this;
5804
5794
  const commonBounds = import_Box.Box.Common(allBounds);
5805
- let val;
5806
- let min;
5807
- let dim;
5808
- if (operation === "horizontal") {
5809
- val = "x";
5810
- min = "minX";
5811
- dim = "width";
5812
- } else {
5813
- val = "y";
5814
- min = "minY";
5815
- dim = "height";
5816
- }
5795
+ const { val, min, dim } = AXIS[operation];
5817
5796
  this.run(() => {
5818
5797
  shapeClustersToStretch.forEach(({ shapes: shapes2, pageBounds }) => {
5819
- const localOffset = new import_Vec.Vec();
5820
- localOffset[val] = commonBounds[min] - pageBounds[min];
5798
+ const pageOffset = new import_Vec.Vec();
5799
+ pageOffset[val] = commonBounds[min] - pageBounds[min];
5821
5800
  const scaleOrigin = pageBounds.center.clone();
5822
5801
  scaleOrigin[val] = commonBounds[min];
5823
5802
  const scale = new import_Vec.Vec(1, 1);
5824
5803
  scale[val] = commonBounds[dim] / pageBounds[dim];
5825
5804
  for (const shape of shapes2) {
5826
- const shapeLocalOffset = localOffset.clone();
5827
- shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation());
5828
- shapeLocalOffset.add(shape);
5829
- const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5830
- this.updateShape(changes);
5805
+ this.updateShape(this.getChangesToTranslateShapeByPageDelta(shape, pageOffset));
5831
5806
  this.resizeShape(shape.id, scale, {
5832
5807
  initialBounds: this.getShapeGeometry(shape).bounds,
5833
5808
  scaleOrigin,
@@ -5870,7 +5845,7 @@ class Editor extends import_eventemitter3.default {
5870
5845
  const scaleY = targetBounds.height / commonBounds.height;
5871
5846
  const scale = new import_Vec.Vec(scaleX, scaleY);
5872
5847
  shapeClusters.forEach(({ shapes: shapes2, pageBounds }) => {
5873
- const localOffset = new import_Vec.Vec(
5848
+ const pageOffset = new import_Vec.Vec(
5874
5849
  targetBounds.minX - commonBounds.minX + (pageBounds.minX - commonBounds.minX) * (scaleX - 1),
5875
5850
  targetBounds.minY - commonBounds.minY + (pageBounds.minY - commonBounds.minY) * (scaleY - 1)
5876
5851
  );
@@ -5879,11 +5854,7 @@ class Editor extends import_eventemitter3.default {
5879
5854
  targetBounds.minY + (pageBounds.minY - commonBounds.minY) * scaleY
5880
5855
  );
5881
5856
  for (const shape of shapes2) {
5882
- const shapeLocalOffset = localOffset.clone();
5883
- shapeLocalOffset.rot(-this.getShapeParentTransform(shape).rotation());
5884
- shapeLocalOffset.add(shape);
5885
- const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5886
- this.updateShape(changes);
5857
+ this.updateShape(this.getChangesToTranslateShapeByPageDelta(shape, pageOffset));
5887
5858
  this.resizeShape(shape.id, scale, {
5888
5859
  initialBounds: this.getShapeGeometry(shape).bounds,
5889
5860
  scaleOrigin,
@@ -6302,12 +6273,17 @@ class Editor extends import_eventemitter3.default {
6302
6273
  let remaining = duration;
6303
6274
  let t;
6304
6275
  const animations = [];
6276
+ const ignoreShapeLock = this._shouldIgnoreShapeLock;
6305
6277
  let partial, result;
6306
6278
  for (let i = 0, n = partials.length; i < n; i++) {
6307
6279
  partial = partials[i];
6308
6280
  if (!partial) continue;
6309
6281
  const shape = this.getShape(partial.id);
6310
6282
  if (!shape) continue;
6283
+ const unlocks = shape.isLocked && Object.hasOwn(partial, "isLocked") && !partial.isLocked;
6284
+ if (!ignoreShapeLock && !unlocks && this.isShapeOrAncestorLocked(shape)) {
6285
+ continue;
6286
+ }
6311
6287
  result = {
6312
6288
  start: (0, import_utils.structuredClone)(shape),
6313
6289
  end: applyPartialToRecordWithProps((0, import_utils.structuredClone)(shape), partial)
@@ -6323,7 +6299,7 @@ class Editor extends import_eventemitter3.default {
6323
6299
  (p) => p && animatingShapes2.get(p.id) === animationId
6324
6300
  );
6325
6301
  if (partialsToUpdate.length) {
6326
- this.updateShapes(partialsToUpdate);
6302
+ this.run(() => this.updateShapes(partialsToUpdate), { ignoreShapeLock });
6327
6303
  }
6328
6304
  this.off("tick", handleTick);
6329
6305
  return;
@@ -6356,13 +6332,13 @@ class Editor extends import_eventemitter3.default {
6356
6332
  throw Error("Editor.groupShapes: must provide an array of shapes or shape ids");
6357
6333
  }
6358
6334
  if (this.getIsReadonly()) return this;
6359
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
6360
- if (ids.length <= 1) return this;
6335
+ const ids = toShapeIds(shapes);
6361
6336
  const shapesToGroup = (0, import_utils.compact)(
6362
6337
  (this._shouldIgnoreShapeLock ? ids : this._getUnlockedShapeIds(ids)).map(
6363
6338
  (id) => this.getShape(id)
6364
6339
  )
6365
6340
  );
6341
+ if (shapesToGroup.length <= 1) return this;
6366
6342
  const sortedShapeIds = shapesToGroup.sort(import_utils.sortByIndex).map((s) => s.id);
6367
6343
  const childBounds = (0, import_utils.compact)(shapesToGroup.map((shape) => this.getShapePageBounds(shape)));
6368
6344
  const pageBounds = import_Box.Box.Common(childBounds);
@@ -6371,8 +6347,11 @@ class Editor extends import_eventemitter3.default {
6371
6347
  }
6372
6348
  const { x, y } = pageBounds.point;
6373
6349
  const parentId = this.findCommonAncestor(shapesToGroup) ?? this.getCurrentPageId();
6374
- if (this.getCurrentToolId() !== "select") return this;
6375
- if (!this.isIn("select.idle")) {
6350
+ if (!this.canCreateShapes([groupId])) {
6351
+ alertMaxShapes(this);
6352
+ return this;
6353
+ }
6354
+ if (this.isIn("select") && !this.isIn("select.idle")) {
6376
6355
  this.cancel();
6377
6356
  }
6378
6357
  const shapesWithRootParent = shapesToGroup.filter((shape) => shape.parentId === parentId).sort(import_utils.sortByIndex);
@@ -6400,15 +6379,14 @@ class Editor extends import_eventemitter3.default {
6400
6379
  ungroupShapes(shapes, opts = {}) {
6401
6380
  if (this.getIsReadonly()) return this;
6402
6381
  const { select = true } = opts;
6403
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
6382
+ const ids = toShapeIds(shapes);
6404
6383
  const shapesToUngroup = (0, import_utils.compact)(
6405
6384
  (this._shouldIgnoreShapeLock ? ids : this._getUnlockedShapeIds(ids)).map(
6406
6385
  (id) => this.getShape(id)
6407
6386
  )
6408
6387
  );
6409
6388
  if (shapesToUngroup.length === 0) return this;
6410
- if (this.getCurrentToolId() !== "select") return this;
6411
- if (!this.isIn("select.idle")) {
6389
+ if (this.isIn("select") && !this.isIn("select.idle")) {
6412
6390
  this.cancel();
6413
6391
  }
6414
6392
  const idsToSelect = /* @__PURE__ */ new Set();
@@ -6422,16 +6400,16 @@ class Editor extends import_eventemitter3.default {
6422
6400
  });
6423
6401
  if (groups.length === 0) return this;
6424
6402
  this.run(() => {
6425
- let group;
6426
6403
  for (let i = 0, n = groups.length; i < n; i++) {
6427
- group = groups[i];
6404
+ const group = this.getShape(groups[i].id);
6405
+ if (!group) continue;
6428
6406
  const childIds = this.getSortedChildIdsForParent(group.id);
6429
6407
  for (let j = 0, n2 = childIds.length; j < n2; j++) {
6430
6408
  idsToSelect.add(childIds[j]);
6431
6409
  }
6432
6410
  this.reparentShapes(childIds, group.parentId, group.index);
6433
6411
  }
6434
- this.deleteShapes(groups.map((group2) => group2.id));
6412
+ this.deleteShapes(groups.map((group) => group.id));
6435
6413
  if (select) {
6436
6414
  this.select(...idsToSelect);
6437
6415
  }
@@ -6512,14 +6490,14 @@ class Editor extends import_eventemitter3.default {
6512
6490
  }
6513
6491
  /** @internal */
6514
6492
  _getUnlockedShapeIds(ids) {
6515
- return ids.filter((id) => !this.getShape(id)?.isLocked);
6493
+ return ids.filter((id) => !this.isShapeOrAncestorLocked(id));
6516
6494
  }
6517
6495
  deleteShapes(_ids) {
6518
6496
  if (this.getIsReadonly()) return this;
6519
6497
  if (!Array.isArray(_ids)) {
6520
6498
  throw Error("Editor.deleteShapes: must provide an array of shapes or shapeIds");
6521
6499
  }
6522
- const shapeIds = typeof _ids[0] === "string" ? _ids : _ids.map((s) => s.id);
6500
+ const shapeIds = toShapeIds(_ids);
6523
6501
  const shapeIdsToDelete = this._shouldIgnoreShapeLock ? shapeIds : this._getUnlockedShapeIds(shapeIds);
6524
6502
  if (shapeIdsToDelete.length === 0) return this;
6525
6503
  const allShapeIdsToDelete = new Set(shapeIdsToDelete);
@@ -6864,7 +6842,7 @@ class Editor extends import_eventemitter3.default {
6864
6842
  * @public
6865
6843
  */
6866
6844
  getContentFromCurrentPage(shapes) {
6867
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
6845
+ const ids = toShapeIds(shapes);
6868
6846
  if (!ids) return;
6869
6847
  if (ids.length === 0) return;
6870
6848
  const shapeIds = this.getShapeAndDescendantIds(ids);
@@ -6932,21 +6910,29 @@ class Editor extends import_eventemitter3.default {
6932
6910
  await Promise.allSettled(
6933
6911
  content.assets.map(async (asset) => {
6934
6912
  if ((asset.type === "image" || asset.type === "video") && !asset.props.src?.startsWith("data:image") && !asset.props.src?.startsWith("data:video") && !asset.props.src?.startsWith("http")) {
6935
- const assetWithDataUrl = (0, import_utils.structuredClone)(asset);
6936
- const objectUrl = await this.store.props.assets.resolve(asset, {
6937
- screenScale: 1,
6938
- steppedScreenScale: 1,
6939
- dpr: 1,
6940
- networkEffectiveType: null,
6941
- shouldResolveToOriginal: true
6942
- });
6943
- assetWithDataUrl.props.src = await import_utils.FileHelpers.blobToDataUrl(
6944
- await (0, import_utils.fetch)(objectUrl).then((r) => r.blob())
6945
- );
6946
- assets.push(assetWithDataUrl);
6947
- } else {
6948
- assets.push(asset);
6913
+ try {
6914
+ const objectUrl = await this.store.props.assets.resolve(asset, {
6915
+ screenScale: 1,
6916
+ steppedScreenScale: 1,
6917
+ dpr: 1,
6918
+ networkEffectiveType: null,
6919
+ shouldResolveToOriginal: true
6920
+ });
6921
+ if (objectUrl) {
6922
+ const response = await (0, import_utils.fetch)(objectUrl);
6923
+ if (response.ok) {
6924
+ const assetWithDataUrl = (0, import_utils.structuredClone)(asset);
6925
+ assetWithDataUrl.props.src = await import_utils.FileHelpers.blobToDataUrl(await response.blob());
6926
+ assets.push(assetWithDataUrl);
6927
+ return;
6928
+ }
6929
+ console.warn(`Could not inline asset ${asset.id}: fetch returned ${response.status}`);
6930
+ }
6931
+ } catch (err) {
6932
+ console.warn(`Could not inline asset ${asset.id}`, err);
6933
+ }
6949
6934
  }
6935
+ assets.push(asset);
6950
6936
  })
6951
6937
  );
6952
6938
  content.assets = assets;
@@ -7240,7 +7226,7 @@ class Editor extends import_eventemitter3.default {
7240
7226
  * @public
7241
7227
  */
7242
7228
  async getSvgElement(shapes, opts = {}) {
7243
- const ids = shapes.length === 0 ? this.getCurrentPageShapeIdsSorted() : typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
7229
+ const ids = shapes.length === 0 ? this.getCurrentPageShapeIdsSorted() : toShapeIds(shapes);
7244
7230
  if (ids.length === 0) return void 0;
7245
7231
  await this.fonts.loadRequiredFontsForCurrentPage(this.options.maxFontsToLoadBeforeRender);
7246
7232
  return (0, import_exportToSvg.exportToSvg)(this, ids, opts);
@@ -8158,6 +8144,7 @@ class Editor extends import_eventemitter3.default {
8158
8144
  inputs.buttons.add(info.button);
8159
8145
  inputs.setIsPointing(true);
8160
8146
  inputs.setIsDragging(false);
8147
+ this.stopCameraAnimation();
8161
8148
  if (!isPenMode && info.isPenDirect) {
8162
8149
  this.updateInstanceState({ isPenMode: true });
8163
8150
  this.interrupt();
@@ -8294,8 +8281,8 @@ class Editor extends import_eventemitter3.default {
8294
8281
  break;
8295
8282
  }
8296
8283
  case "keyboard": {
8297
- if (info.key === "ShiftRight") info.key = "ShiftLeft";
8298
- if (info.key === "AltRight") info.key = "AltLeft";
8284
+ if (info.code === "ShiftRight") info.code = "ShiftLeft";
8285
+ if (info.code === "AltRight") info.code = "AltLeft";
8299
8286
  if (info.code === "ControlRight") info.code = "ControlLeft";
8300
8287
  if (info.code === "MetaRight") info.code = "MetaLeft";
8301
8288
  switch (info.name) {
@@ -8665,6 +8652,9 @@ function pushShapeWithDescendants(editor, id, result) {
8665
8652
  pushShapeWithDescendants(editor, childIds[i], result);
8666
8653
  }
8667
8654
  }
8655
+ function toShapeIds(shapes) {
8656
+ return typeof shapes[0] === "string" ? shapes : shapes.map((shape) => shape.id);
8657
+ }
8668
8658
  function withIsolatedShapes(editor, shapeIds, callback) {
8669
8659
  let result;
8670
8660
  editor.run(
@@ -8687,7 +8677,7 @@ function withIsolatedShapes(editor, shapeIds, callback) {
8687
8677
  }
8688
8678
  }
8689
8679
  }
8690
- editor.deleteBindings([...bindingsToRemove], { isolateShapes: true });
8680
+ editor._deleteBindings([...bindingsToRemove], { isolateShapes: true });
8691
8681
  try {
8692
8682
  result = import_utils.Result.ok(callback(bindingsWithBoth));
8693
8683
  } catch (error) {