@tldraw/editor 3.9.0-canary.afd893c5d564 → 3.9.0-canary.b2b5d6e9ec54

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 (54) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/dist-cjs/index.d.ts +39 -7
  4. package/dist-cjs/index.js +1 -1
  5. package/dist-cjs/index.js.map +2 -2
  6. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  7. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  8. package/dist-cjs/lib/editor/Editor.js +431 -249
  9. package/dist-cjs/lib/editor/Editor.js.map +3 -3
  10. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +7 -2
  11. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  12. package/dist-cjs/lib/exports/getSvgAsImage.js +1 -1
  13. package/dist-cjs/lib/exports/getSvgAsImage.js.map +2 -2
  14. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  15. package/dist-cjs/lib/globals/environment.js +3 -1
  16. package/dist-cjs/lib/globals/environment.js.map +2 -2
  17. package/dist-cjs/lib/license/LicenseManager.js +1 -1
  18. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  19. package/dist-cjs/lib/utils/browserCanvasMaxSize.js +104 -28
  20. package/dist-cjs/lib/utils/browserCanvasMaxSize.js.map +3 -3
  21. package/dist-cjs/version.js +3 -3
  22. package/dist-cjs/version.js.map +1 -1
  23. package/dist-esm/index.d.mts +39 -7
  24. package/dist-esm/index.mjs +1 -1
  25. package/dist-esm/index.mjs.map +2 -2
  26. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  27. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  28. package/dist-esm/lib/editor/Editor.mjs +427 -245
  29. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  30. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +7 -2
  31. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  32. package/dist-esm/lib/exports/getSvgAsImage.mjs +1 -1
  33. package/dist-esm/lib/exports/getSvgAsImage.mjs.map +2 -2
  34. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  35. package/dist-esm/lib/globals/environment.mjs +3 -1
  36. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  37. package/dist-esm/lib/license/LicenseManager.mjs +1 -1
  38. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  39. package/dist-esm/lib/utils/browserCanvasMaxSize.mjs +104 -18
  40. package/dist-esm/lib/utils/browserCanvasMaxSize.mjs.map +2 -2
  41. package/dist-esm/version.mjs +3 -3
  42. package/dist-esm/version.mjs.map +1 -1
  43. package/package.json +7 -9
  44. package/src/index.ts +2 -0
  45. package/src/lib/components/default-components/DefaultErrorFallback.tsx +5 -3
  46. package/src/lib/editor/Editor.ts +556 -273
  47. package/src/lib/editor/shapes/ShapeUtil.ts +32 -5
  48. package/src/lib/exports/getSvgAsImage.ts +1 -1
  49. package/src/lib/exports/getSvgJsx.tsx +1 -0
  50. package/src/lib/globals/environment.ts +3 -0
  51. package/src/lib/license/LicenseManager.test.ts +16 -13
  52. package/src/lib/license/LicenseManager.ts +2 -2
  53. package/src/lib/utils/browserCanvasMaxSize.ts +121 -21
  54. package/src/version.ts +3 -3
@@ -46,7 +46,7 @@ var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use
46
46
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
47
47
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
48
48
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
49
- var __setMetaKeyTimeout_dec, __setCtrlKeyTimeout_dec, __setAltKeyTimeout_dec, __setShiftKeyTimeout_dec, _getIsReadonly_dec, _getIsFocused_dec, _getSharedOpacity_dec, _getSharedStyles_dec, __getSelectionSharedStyles_dec, __getBindingsIndexCache_dec, _getCurrentPageRenderingShapesSorted_dec, _getCurrentPageShapesSorted_dec, _getCurrentPageShapes_dec, _getCurrentPageBounds_dec, _getCulledShapes_dec, __notVisibleShapes_dec, __getShapeMaskedPageBoundsCache_dec, __getShapeMaskCache_dec, __getShapeClipPathCache_dec, __getShapePageBoundsCache_dec, __getShapePageTransformCache_dec, __getShapeHandlesCache_dec, __getShapeGeometryCache_dec, __getAllAssetsQuery_dec, _getCurrentPageShapeIdsSorted_dec, _getCurrentPageId_dec, _getPages_dec, __getAllPagesQuery_dec, _getRenderingShapes_dec, _getCollaboratorsOnCurrentPage_dec, _getCollaborators_dec, __getCollaboratorsQuery_dec, _getViewportPageBounds_dec, _getViewportScreenCenter_dec, _getViewportScreenBounds_dec, _getZoomLevel_dec, _getCameraForFollowing_dec, _getViewportPageBoundsForFollowing_dec, _getCamera_dec, __unsafe_getCameraId_dec, _getErasingShapes_dec, _getErasingShapeIds_dec, _getHintingShape_dec, _getHintingShapeIds_dec, _getHoveredShape_dec, _getHoveredShapeId_dec, _getEditingShape_dec, _getEditingShapeId_dec, _getFocusedGroup_dec, _getFocusedGroupId_dec, _getSelectionRotatedScreenBounds_dec, _getSelectionRotatedPageBounds_dec, _getSelectionRotation_dec, _getSelectionPageBounds_dec, _getOnlySelectedShape_dec, _getOnlySelectedShapeId_dec, _getSelectedShapes_dec, _getSelectedShapeIds_dec, __getCurrentPageStateId_dec, _getCurrentPageState_dec, __getPageStatesQuery_dec, _getPageStates_dec, _getIsMenuOpen_dec, _getOpenMenus_dec, _getInstanceState_dec, _getDocumentSettings_dec, _getCurrentToolId_dec, _getCurrentTool_dec, _getPath_dec, _getCanRedo_dec, _getCanUndo_dec, _getIsShapeHiddenCache_dec, _a, _init;
49
+ var __setMetaKeyTimeout_dec, __setCtrlKeyTimeout_dec, __setAltKeyTimeout_dec, __setShiftKeyTimeout_dec, _getIsReadonly_dec, _getIsFocused_dec, _getSharedOpacity_dec, _getSharedStyles_dec, __getSelectionSharedStyles_dec, __getBindingsIndexCache_dec, _getCurrentPageRenderingShapesSorted_dec, _getCurrentPageShapesSorted_dec, _getCurrentPageShapes_dec, _getCurrentPageBounds_dec, _getCulledShapes_dec, __notVisibleShapes_dec, __getShapeMaskedPageBoundsCache_dec, __getShapeMaskCache_dec, __getShapeClipPathCache_dec, __getShapePageBoundsCache_dec, __getShapePageTransformCache_dec, __getShapeHandlesCache_dec, __getAllAssetsQuery_dec, _getCurrentPageShapeIdsSorted_dec, _getCurrentPageId_dec, _getPages_dec, __getAllPagesQuery_dec, _getRenderingShapes_dec, _getCollaboratorsOnCurrentPage_dec, _getCollaborators_dec, __getCollaboratorsQuery_dec, _getViewportPageBounds_dec, _getViewportScreenCenter_dec, _getViewportScreenBounds_dec, _getZoomLevel_dec, _getCameraForFollowing_dec, _getViewportPageBoundsForFollowing_dec, _getCamera_dec, __unsafe_getCameraId_dec, _getErasingShapes_dec, _getErasingShapeIds_dec, _getHintingShape_dec, _getHintingShapeIds_dec, _getHoveredShape_dec, _getHoveredShapeId_dec, _getEditingShape_dec, _getEditingShapeId_dec, _getFocusedGroup_dec, _getFocusedGroupId_dec, _getSelectionRotatedScreenBounds_dec, _getSelectionRotatedPageBounds_dec, _getSelectionRotation_dec, _getSelectionPageBounds_dec, _getOnlySelectedShape_dec, _getOnlySelectedShapeId_dec, _getSelectedShapes_dec, _getSelectedShapeIds_dec, __getCurrentPageStateId_dec, _getCurrentPageState_dec, __getPageStatesQuery_dec, _getPageStates_dec, _getIsMenuOpen_dec, _getOpenMenus_dec, _getInstanceState_dec, _getDocumentSettings_dec, _getCurrentToolId_dec, _getCurrentTool_dec, _getPath_dec, _getCanRedo_dec, _getCanUndo_dec, _getIsShapeHiddenCache_dec, _a, _init;
50
50
  import { EMPTY_ARRAY, atom, computed, react, transact, unsafe__withoutCapture } from "@tldraw/state";
51
51
  import {
52
52
  reverseRecordsDiff
@@ -92,6 +92,7 @@ import {
92
92
  structuredClone,
93
93
  uniqueId
94
94
  } from "@tldraw/utils";
95
+ import { Number } from "core-js";
95
96
  import EventEmitter from "eventemitter3";
96
97
  import {
97
98
  getSnapshot,
@@ -122,7 +123,7 @@ import { Vec } from "../primitives/Vec.mjs";
122
123
  import { EASINGS } from "../primitives/easings.mjs";
123
124
  import { Group2d } from "../primitives/geometry/Group2d.mjs";
124
125
  import { intersectPolygonPolygon } from "../primitives/intersect.mjs";
125
- import { PI2, approximately, areAnglesCompatible, clamp, pointInPolygon } from "../primitives/utils.mjs";
126
+ import { PI, approximately, areAnglesCompatible, clamp, pointInPolygon } from "../primitives/utils.mjs";
126
127
  import { SharedStyleMap } from "../utils/SharedStylesMap.mjs";
127
128
  import { dataUrlToFile } from "../utils/assets.mjs";
128
129
  import { debugFlags } from "../utils/debug-flags.mjs";
@@ -148,7 +149,7 @@ import { TextManager } from "./managers/TextManager.mjs";
148
149
  import { TickManager } from "./managers/TickManager.mjs";
149
150
  import { UserPreferencesManager } from "./managers/UserPreferencesManager.mjs";
150
151
  import { RootState } from "./tools/RootState.mjs";
151
- class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed], _getCanUndo_dec = [computed], _getCanRedo_dec = [computed], _getPath_dec = [computed], _getCurrentTool_dec = [computed], _getCurrentToolId_dec = [computed], _getDocumentSettings_dec = [computed], _getInstanceState_dec = [computed], _getOpenMenus_dec = [computed], _getIsMenuOpen_dec = [computed], _getPageStates_dec = [computed], __getPageStatesQuery_dec = [computed], _getCurrentPageState_dec = [computed], __getCurrentPageStateId_dec = [computed], _getSelectedShapeIds_dec = [computed], _getSelectedShapes_dec = [computed], _getOnlySelectedShapeId_dec = [computed], _getOnlySelectedShape_dec = [computed], _getSelectionPageBounds_dec = [computed], _getSelectionRotation_dec = [computed], _getSelectionRotatedPageBounds_dec = [computed], _getSelectionRotatedScreenBounds_dec = [computed], _getFocusedGroupId_dec = [computed], _getFocusedGroup_dec = [computed], _getEditingShapeId_dec = [computed], _getEditingShape_dec = [computed], _getHoveredShapeId_dec = [computed], _getHoveredShape_dec = [computed], _getHintingShapeIds_dec = [computed], _getHintingShape_dec = [computed], _getErasingShapeIds_dec = [computed], _getErasingShapes_dec = [computed], __unsafe_getCameraId_dec = [computed], _getCamera_dec = [computed], _getViewportPageBoundsForFollowing_dec = [computed], _getCameraForFollowing_dec = [computed], _getZoomLevel_dec = [computed], _getViewportScreenBounds_dec = [computed], _getViewportScreenCenter_dec = [computed], _getViewportPageBounds_dec = [computed], __getCollaboratorsQuery_dec = [computed], _getCollaborators_dec = [computed], _getCollaboratorsOnCurrentPage_dec = [computed], _getRenderingShapes_dec = [computed], __getAllPagesQuery_dec = [computed], _getPages_dec = [computed], _getCurrentPageId_dec = [computed], _getCurrentPageShapeIdsSorted_dec = [computed], __getAllAssetsQuery_dec = [computed], __getShapeGeometryCache_dec = [computed], __getShapeHandlesCache_dec = [computed], __getShapePageTransformCache_dec = [computed], __getShapePageBoundsCache_dec = [computed], __getShapeClipPathCache_dec = [computed], __getShapeMaskCache_dec = [computed], __getShapeMaskedPageBoundsCache_dec = [computed], __notVisibleShapes_dec = [computed], _getCulledShapes_dec = [computed], _getCurrentPageBounds_dec = [computed], _getCurrentPageShapes_dec = [computed], _getCurrentPageShapesSorted_dec = [computed], _getCurrentPageRenderingShapesSorted_dec = [computed], __getBindingsIndexCache_dec = [computed], __getSelectionSharedStyles_dec = [computed], _getSharedStyles_dec = [computed({ isEqual: (a, b) => a.equals(b) })], _getSharedOpacity_dec = [computed], _getIsFocused_dec = [computed], _getIsReadonly_dec = [computed], __setShiftKeyTimeout_dec = [bind], __setAltKeyTimeout_dec = [bind], __setCtrlKeyTimeout_dec = [bind], __setMetaKeyTimeout_dec = [bind], _a) {
152
+ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed], _getCanUndo_dec = [computed], _getCanRedo_dec = [computed], _getPath_dec = [computed], _getCurrentTool_dec = [computed], _getCurrentToolId_dec = [computed], _getDocumentSettings_dec = [computed], _getInstanceState_dec = [computed], _getOpenMenus_dec = [computed], _getIsMenuOpen_dec = [computed], _getPageStates_dec = [computed], __getPageStatesQuery_dec = [computed], _getCurrentPageState_dec = [computed], __getCurrentPageStateId_dec = [computed], _getSelectedShapeIds_dec = [computed], _getSelectedShapes_dec = [computed], _getOnlySelectedShapeId_dec = [computed], _getOnlySelectedShape_dec = [computed], _getSelectionPageBounds_dec = [computed], _getSelectionRotation_dec = [computed], _getSelectionRotatedPageBounds_dec = [computed], _getSelectionRotatedScreenBounds_dec = [computed], _getFocusedGroupId_dec = [computed], _getFocusedGroup_dec = [computed], _getEditingShapeId_dec = [computed], _getEditingShape_dec = [computed], _getHoveredShapeId_dec = [computed], _getHoveredShape_dec = [computed], _getHintingShapeIds_dec = [computed], _getHintingShape_dec = [computed], _getErasingShapeIds_dec = [computed], _getErasingShapes_dec = [computed], __unsafe_getCameraId_dec = [computed], _getCamera_dec = [computed], _getViewportPageBoundsForFollowing_dec = [computed], _getCameraForFollowing_dec = [computed], _getZoomLevel_dec = [computed], _getViewportScreenBounds_dec = [computed], _getViewportScreenCenter_dec = [computed], _getViewportPageBounds_dec = [computed], __getCollaboratorsQuery_dec = [computed], _getCollaborators_dec = [computed], _getCollaboratorsOnCurrentPage_dec = [computed], _getRenderingShapes_dec = [computed], __getAllPagesQuery_dec = [computed], _getPages_dec = [computed], _getCurrentPageId_dec = [computed], _getCurrentPageShapeIdsSorted_dec = [computed], __getAllAssetsQuery_dec = [computed], __getShapeHandlesCache_dec = [computed], __getShapePageTransformCache_dec = [computed], __getShapePageBoundsCache_dec = [computed], __getShapeClipPathCache_dec = [computed], __getShapeMaskCache_dec = [computed], __getShapeMaskedPageBoundsCache_dec = [computed], __notVisibleShapes_dec = [computed], _getCulledShapes_dec = [computed], _getCurrentPageBounds_dec = [computed], _getCurrentPageShapes_dec = [computed], _getCurrentPageShapesSorted_dec = [computed], _getCurrentPageRenderingShapesSorted_dec = [computed], __getBindingsIndexCache_dec = [computed], __getSelectionSharedStyles_dec = [computed], _getSharedStyles_dec = [computed({ isEqual: (a, b) => a.equals(b) })], _getSharedOpacity_dec = [computed], _getIsFocused_dec = [computed], _getIsReadonly_dec = [computed], __setShiftKeyTimeout_dec = [bind], __setAltKeyTimeout_dec = [bind], __setCtrlKeyTimeout_dec = [bind], __setMetaKeyTimeout_dec = [bind], _a) {
152
153
  constructor({
153
154
  store,
154
155
  user,
@@ -310,6 +311,8 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
310
311
  __publicField(this, "_cameraStateTimeoutRemaining", 0);
311
312
  /* @internal */
312
313
  __publicField(this, "_currentPageShapeIds");
314
+ /* --------------------- Shapes --------------------- */
315
+ __publicField(this, "_shapeGeometryCaches", {});
313
316
  // Parents and children
314
317
  /**
315
318
  * A cache of parents to children.
@@ -3221,13 +3224,6 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
3221
3224
  async uploadAsset(asset, file, abortSignal) {
3222
3225
  return await this.store.props.assets.upload(asset, file, abortSignal);
3223
3226
  }
3224
- _getShapeGeometryCache() {
3225
- return this.store.createComputedCache(
3226
- "bounds",
3227
- (shape) => this.getShapeUtil(shape).getGeometry(shape),
3228
- (a, b) => a.props === b.props
3229
- );
3230
- }
3231
3227
  /**
3232
3228
  * Get the geometry of a shape.
3233
3229
  *
@@ -3235,14 +3231,26 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
3235
3231
  * ```ts
3236
3232
  * editor.getShapeGeometry(myShape)
3237
3233
  * editor.getShapeGeometry(myShapeId)
3234
+ * editor.getShapeGeometry(myShapeId, { context: "arrow" })
3238
3235
  * ```
3239
3236
  *
3240
3237
  * @param shape - The shape (or shape id) to get the geometry for.
3238
+ * @param opts - Additional options about the request for geometry. Passed to {@link ShapeUtil.getGeometry}.
3241
3239
  *
3242
3240
  * @public
3243
3241
  */
3244
- getShapeGeometry(shape) {
3245
- return this._getShapeGeometryCache().get(typeof shape === "string" ? shape : shape.id);
3242
+ getShapeGeometry(shape, opts) {
3243
+ const context = opts?.context ?? "none";
3244
+ if (!this._shapeGeometryCaches[context]) {
3245
+ this._shapeGeometryCaches[context] = this.store.createComputedCache(
3246
+ "bounds",
3247
+ (shape2) => this.getShapeUtil(shape2).getGeometry(shape2, opts),
3248
+ { areRecordsEqual: (a, b) => a.props === b.props }
3249
+ );
3250
+ }
3251
+ return this._shapeGeometryCaches[context].get(
3252
+ typeof shape === "string" ? shape : shape.id
3253
+ );
3246
3254
  }
3247
3255
  _getShapeHandlesCache() {
3248
3256
  return this.store.createComputedCache("handles", (shape) => {
@@ -4308,27 +4316,28 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
4308
4316
  });
4309
4317
  return this;
4310
4318
  }
4319
+ // Gets a shape partial that includes life cycle changes: on translate start, on translate, on translate end
4311
4320
  getChangesToTranslateShape(initialShape, newShapeCoords) {
4312
4321
  let workingShape = initialShape;
4313
4322
  const util = this.getShapeUtil(initialShape);
4314
- workingShape = applyPartialToRecordWithProps(
4315
- workingShape,
4316
- util.onTranslateStart?.(workingShape) ?? void 0
4317
- );
4323
+ const afterTranslateStart = util.onTranslateStart?.(workingShape);
4324
+ if (afterTranslateStart) {
4325
+ workingShape = applyPartialToRecordWithProps(workingShape, afterTranslateStart);
4326
+ }
4318
4327
  workingShape = applyPartialToRecordWithProps(workingShape, {
4319
4328
  id: initialShape.id,
4320
4329
  type: initialShape.type,
4321
4330
  x: newShapeCoords.x,
4322
4331
  y: newShapeCoords.y
4323
4332
  });
4324
- workingShape = applyPartialToRecordWithProps(
4325
- workingShape,
4326
- util.onTranslate?.(initialShape, workingShape) ?? void 0
4327
- );
4328
- workingShape = applyPartialToRecordWithProps(
4329
- workingShape,
4330
- util.onTranslateEnd?.(initialShape, workingShape) ?? void 0
4331
- );
4333
+ const afterTranslate = util.onTranslate?.(initialShape, workingShape);
4334
+ if (afterTranslate) {
4335
+ workingShape = applyPartialToRecordWithProps(workingShape, afterTranslate);
4336
+ }
4337
+ const afterTranslateEnd = util.onTranslateEnd?.(initialShape, workingShape);
4338
+ if (afterTranslateEnd) {
4339
+ workingShape = applyPartialToRecordWithProps(workingShape, afterTranslateEnd);
4340
+ }
4332
4341
  return workingShape;
4333
4342
  }
4334
4343
  /**
@@ -4635,6 +4644,30 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
4635
4644
  if (changes) this.updateShapes(changes);
4636
4645
  return this;
4637
4646
  }
4647
+ /**
4648
+ * @internal
4649
+ */
4650
+ collectShapesViaArrowBindings(info) {
4651
+ const { initialShapes, resultShapes, resultBounds, bindings, visited } = info;
4652
+ for (const binding of bindings) {
4653
+ for (const id of [binding.fromId, binding.toId]) {
4654
+ if (!visited.has(id)) {
4655
+ const aligningShape = initialShapes.find((s) => s.id === id);
4656
+ if (aligningShape && !visited.has(aligningShape.id)) {
4657
+ visited.add(aligningShape.id);
4658
+ const shapePageBounds = this.getShapePageBounds(aligningShape);
4659
+ if (!shapePageBounds) continue;
4660
+ resultShapes.push(aligningShape);
4661
+ resultBounds.push(shapePageBounds);
4662
+ this.collectShapesViaArrowBindings({
4663
+ ...info,
4664
+ bindings: this.getBindingsInvolvingShape(aligningShape, "arrow")
4665
+ });
4666
+ }
4667
+ }
4668
+ }
4669
+ }
4670
+ }
4638
4671
  /**
4639
4672
  * Flip shape positions.
4640
4673
  *
@@ -4650,35 +4683,52 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
4650
4683
  * @public
4651
4684
  */
4652
4685
  flipShapes(shapes, operation) {
4653
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4654
4686
  if (this.getIsReadonly()) return this;
4655
- let shapesToFlip = compact(ids.map((id) => this.getShape(id)));
4687
+ const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4688
+ const shapesToFlipFirstPass = compact(ids.map((id) => this.getShape(id)));
4689
+ for (const shape of shapesToFlipFirstPass) {
4690
+ if (this.isShapeOfType(shape, "group")) {
4691
+ const childrenOfGroups = compact(
4692
+ this.getSortedChildIdsForParent(shape.id).map((id) => this.getShape(id))
4693
+ );
4694
+ shapesToFlipFirstPass.push(...childrenOfGroups);
4695
+ }
4696
+ }
4697
+ const shapesToFlip = [];
4698
+ const allBounds = [];
4699
+ for (const shape of shapesToFlipFirstPass) {
4700
+ const util = this.getShapeUtil(shape);
4701
+ if (!util.canBeLaidOut(shape, {
4702
+ type: "flip",
4703
+ shapes: shapesToFlipFirstPass
4704
+ })) {
4705
+ continue;
4706
+ }
4707
+ const pageBounds = this.getShapePageBounds(shape);
4708
+ const localBounds = this.getShapeGeometry(shape).bounds;
4709
+ const pageTransform = this.getShapePageTransform(shape.id);
4710
+ if (!(pageBounds && localBounds && pageTransform)) continue;
4711
+ shapesToFlip.push({
4712
+ shape,
4713
+ localBounds,
4714
+ pageTransform,
4715
+ isAspectRatioLocked: util.isAspectRatioLocked(shape)
4716
+ });
4717
+ allBounds.push(pageBounds);
4718
+ }
4656
4719
  if (!shapesToFlip.length) return this;
4657
- shapesToFlip = compact(
4658
- shapesToFlip.map((shape) => {
4659
- if (this.isShapeOfType(shape, "group")) {
4660
- return this.getSortedChildIdsForParent(shape.id).map((id) => this.getShape(id));
4661
- }
4662
- return shape;
4663
- }).flat()
4664
- );
4665
- const scaleOriginPage = Box.Common(
4666
- compact(shapesToFlip.map((id) => this.getShapePageBounds(id)))
4667
- ).center;
4720
+ const scaleOriginPage = Box.Common(allBounds).center;
4668
4721
  this.run(() => {
4669
- for (const shape of shapesToFlip) {
4670
- const bounds = this.getShapeGeometry(shape).bounds;
4671
- const initialPageTransform = this.getShapePageTransform(shape.id);
4672
- if (!initialPageTransform) continue;
4722
+ for (const { shape, localBounds, pageTransform, isAspectRatioLocked } of shapesToFlip) {
4673
4723
  this.resizeShape(
4674
4724
  shape.id,
4675
4725
  { x: operation === "horizontal" ? -1 : 1, y: operation === "vertical" ? -1 : 1 },
4676
4726
  {
4677
- initialBounds: bounds,
4678
- initialPageTransform,
4727
+ initialBounds: localBounds,
4728
+ initialPageTransform: pageTransform,
4679
4729
  initialShape: shape,
4730
+ isAspectRatioLocked,
4680
4731
  mode: "scale_shape",
4681
- isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
4682
4732
  scaleOrigin: scaleOriginPage,
4683
4733
  scaleAxisRotation: 0
4684
4734
  }
@@ -4705,15 +4755,40 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
4705
4755
  stackShapes(shapes, operation, gap) {
4706
4756
  const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4707
4757
  if (this.getIsReadonly()) return this;
4708
- const shapesToStack = ids.map((id) => this.getShape(id)).filter((shape) => {
4709
- if (!shape) return false;
4710
- return this.getShapeUtil(shape).canBeLaidOut(shape);
4711
- });
4712
- const len = shapesToStack.length;
4758
+ const shapesToStackFirstPass = compact(ids.map((id) => this.getShape(id)));
4759
+ const shapeClustersToStack = [];
4760
+ const allBounds = [];
4761
+ const visited = /* @__PURE__ */ new Set();
4762
+ for (const shape of shapesToStackFirstPass) {
4763
+ if (visited.has(shape.id)) continue;
4764
+ visited.add(shape.id);
4765
+ const shapePageBounds = this.getShapePageBounds(shape);
4766
+ if (!shapePageBounds) continue;
4767
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
4768
+ type: "stack",
4769
+ shapes: shapesToStackFirstPass
4770
+ })) {
4771
+ continue;
4772
+ }
4773
+ const shapesMovingTogether = [shape];
4774
+ const boundsOfShapesMovingTogether = [shapePageBounds];
4775
+ this.collectShapesViaArrowBindings({
4776
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
4777
+ initialShapes: shapesToStackFirstPass,
4778
+ resultShapes: shapesMovingTogether,
4779
+ resultBounds: boundsOfShapesMovingTogether,
4780
+ visited
4781
+ });
4782
+ const commonPageBounds = Box.Common(boundsOfShapesMovingTogether);
4783
+ if (!commonPageBounds) continue;
4784
+ shapeClustersToStack.push({
4785
+ shapes: shapesMovingTogether,
4786
+ pageBounds: commonPageBounds
4787
+ });
4788
+ allBounds.push(commonPageBounds);
4789
+ }
4790
+ const len = shapeClustersToStack.length;
4713
4791
  if (gap === 0 && len < 3 || len < 2) return this;
4714
- const pageBounds = Object.fromEntries(
4715
- shapesToStack.map((shape) => [shape.id, this.getShapePageBounds(shape)])
4716
- );
4717
4792
  let val;
4718
4793
  let min;
4719
4794
  let max;
@@ -4729,57 +4804,55 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
4729
4804
  max = "maxY";
4730
4805
  dim = "height";
4731
4806
  }
4732
- let shapeGap;
4807
+ let shapeGap = 0;
4733
4808
  if (gap === 0) {
4734
- const gaps = [];
4735
- shapesToStack.sort((a, b) => pageBounds[a.id][min] - pageBounds[b.id][min]);
4809
+ const gaps = {};
4810
+ shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min]);
4736
4811
  for (let i = 0; i < len - 1; i++) {
4737
- const shape = shapesToStack[i];
4738
- const nextShape = shapesToStack[i + 1];
4739
- const bounds = pageBounds[shape.id];
4740
- const nextBounds = pageBounds[nextShape.id];
4741
- const gap2 = nextBounds[min] - bounds[max];
4742
- const current = gaps.find((g) => g.gap === gap2);
4743
- if (current) {
4744
- current.count++;
4745
- } else {
4746
- gaps.push({ gap: gap2, count: 1 });
4812
+ const currCluster = shapeClustersToStack[i];
4813
+ const nextCluster = shapeClustersToStack[i + 1];
4814
+ const gap2 = nextCluster.pageBounds[min] - currCluster.pageBounds[max];
4815
+ if (!gaps[gap2]) {
4816
+ gaps[gap2] = 0;
4747
4817
  }
4818
+ gaps[gap2]++;
4748
4819
  }
4749
- let maxCount = 0;
4750
- gaps.forEach((g) => {
4751
- if (g.count > maxCount) {
4752
- maxCount = g.count;
4753
- shapeGap = g.gap;
4820
+ let maxCount = 1;
4821
+ for (const [gap2, count] of Object.entries(gaps)) {
4822
+ if (count > maxCount) {
4823
+ maxCount = count;
4824
+ shapeGap = parseFloat(gap2);
4754
4825
  }
4755
- });
4826
+ }
4756
4827
  if (maxCount === 1) {
4757
- shapeGap = Math.max(0, gaps.reduce((a, c) => a + c.gap * c.count, 0) / (len - 1));
4828
+ let totalCount = 0;
4829
+ for (const [gap2, count] of Object.entries(gaps)) {
4830
+ shapeGap += parseFloat(gap2) * count;
4831
+ totalCount += count;
4832
+ }
4833
+ shapeGap /= totalCount;
4758
4834
  }
4759
4835
  } else {
4760
4836
  shapeGap = gap;
4761
4837
  }
4762
4838
  const changes = [];
4763
- let v = pageBounds[shapesToStack[0].id][max];
4764
- shapesToStack.forEach((shape, i) => {
4765
- if (i === 0) return;
4766
- const delta = { x: 0, y: 0 };
4767
- delta[val] = v + shapeGap - pageBounds[shape.id][val];
4768
- const parent = this.getShapeParent(shape);
4769
- const localDelta = parent ? Vec.Rot(delta, -this.getShapePageTransform(parent).decompose().rotation) : delta;
4770
- const translateStartChanges = this.getShapeUtil(shape).onTranslateStart?.(shape);
4771
- changes.push(
4772
- translateStartChanges ? {
4773
- ...translateStartChanges,
4774
- [val]: shape[val] + localDelta[val]
4775
- } : {
4776
- id: shape.id,
4777
- type: shape.type,
4778
- [val]: shape[val] + localDelta[val]
4839
+ let v = shapeClustersToStack[0].pageBounds[max];
4840
+ for (let i = 1; i < shapeClustersToStack.length; i++) {
4841
+ const { shapes: shapes2, pageBounds } = shapeClustersToStack[i];
4842
+ const delta = new Vec();
4843
+ delta[val] = v + shapeGap - pageBounds[val];
4844
+ for (const shape of shapes2) {
4845
+ const shapeDelta = delta.clone();
4846
+ const parent = this.getShapeParent(shape);
4847
+ if (parent) {
4848
+ const parentTransform = this.getShapePageTransform(parent);
4849
+ if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
4779
4850
  }
4780
- );
4781
- v += pageBounds[shape.id][dim] + shapeGap;
4782
- });
4851
+ shapeDelta.add(shape);
4852
+ changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
4853
+ }
4854
+ v += pageBounds[dim] + shapeGap;
4855
+ }
4783
4856
  this.updateShapes(changes);
4784
4857
  return this;
4785
4858
  }
@@ -4797,91 +4870,101 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
4797
4870
  * @param gap - The padding to apply to the packed shapes. Defaults to 16.
4798
4871
  */
4799
4872
  packShapes(shapes, gap) {
4800
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4801
4873
  if (this.getIsReadonly()) return this;
4802
- if (ids.length < 2) return this;
4803
- const shapesToPack = ids.map((id) => this.getShape(id)).filter((shape2) => {
4804
- if (!shape2) return false;
4805
- return this.getShapeUtil(shape2).canBeLaidOut(shape2);
4806
- });
4807
- const shapePageBounds = {};
4808
- const nextShapePageBounds = {};
4809
- let shape, bounds, area = 0;
4810
- for (let i = 0; i < shapesToPack.length; i++) {
4811
- shape = shapesToPack[i];
4812
- bounds = this.getShapePageBounds(shape);
4813
- shapePageBounds[shape.id] = bounds;
4814
- nextShapePageBounds[shape.id] = bounds.clone();
4815
- area += bounds.width * bounds.height;
4816
- }
4817
- const commonBounds = Box.Common(compact(Object.values(shapePageBounds)));
4874
+ const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4875
+ const shapesToPackFirstPass = compact(ids.map((id) => this.getShape(id)));
4876
+ const shapeClustersToPack = [];
4877
+ const allBounds = [];
4878
+ const visited = /* @__PURE__ */ new Set();
4879
+ for (const shape of shapesToPackFirstPass) {
4880
+ if (visited.has(shape.id)) continue;
4881
+ visited.add(shape.id);
4882
+ const shapePageBounds = this.getShapePageBounds(shape);
4883
+ if (!shapePageBounds) continue;
4884
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
4885
+ type: "pack",
4886
+ shapes: shapesToPackFirstPass
4887
+ })) {
4888
+ continue;
4889
+ }
4890
+ const shapesMovingTogether = [shape];
4891
+ const boundsOfShapesMovingTogether = [shapePageBounds];
4892
+ this.collectShapesViaArrowBindings({
4893
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
4894
+ initialShapes: shapesToPackFirstPass,
4895
+ resultShapes: shapesMovingTogether,
4896
+ resultBounds: boundsOfShapesMovingTogether,
4897
+ visited
4898
+ });
4899
+ const commonPageBounds = Box.Common(boundsOfShapesMovingTogether);
4900
+ if (!commonPageBounds) continue;
4901
+ shapeClustersToPack.push({
4902
+ shapes: shapesMovingTogether,
4903
+ pageBounds: commonPageBounds,
4904
+ nextPageBounds: commonPageBounds.clone()
4905
+ });
4906
+ allBounds.push(commonPageBounds);
4907
+ }
4908
+ if (shapeClustersToPack.length < 2) return this;
4909
+ let area = 0;
4910
+ for (const { pageBounds } of shapeClustersToPack) {
4911
+ area += pageBounds.width * pageBounds.height;
4912
+ }
4913
+ const commonBounds = Box.Common(allBounds);
4818
4914
  const maxWidth = commonBounds.width;
4819
- shapesToPack.sort((a, b) => shapePageBounds[b.id].height - shapePageBounds[a.id].height);
4915
+ shapeClustersToPack.sort((a, b) => a.pageBounds.width - b.pageBounds.width).sort((a, b) => a.pageBounds.height - b.pageBounds.height);
4820
4916
  const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);
4821
4917
  const spaces = [new Box(commonBounds.x, commonBounds.y, startWidth, Infinity)];
4822
4918
  let width = 0;
4823
4919
  let height = 0;
4824
4920
  let space;
4825
4921
  let last2;
4826
- for (let i = 0; i < shapesToPack.length; i++) {
4827
- shape = shapesToPack[i];
4828
- bounds = nextShapePageBounds[shape.id];
4829
- for (let i2 = spaces.length - 1; i2 >= 0; i2--) {
4830
- space = spaces[i2];
4831
- if (bounds.width > space.width || bounds.height > space.height) continue;
4832
- bounds.x = space.x;
4833
- bounds.y = space.y;
4834
- height = Math.max(height, bounds.maxY);
4835
- width = Math.max(width, bounds.maxX);
4836
- if (bounds.width === space.width && bounds.height === space.height) {
4922
+ for (const { nextPageBounds } of shapeClustersToPack) {
4923
+ for (let i = spaces.length - 1; i >= 0; i--) {
4924
+ space = spaces[i];
4925
+ if (nextPageBounds.width > space.width || nextPageBounds.height > space.height) continue;
4926
+ nextPageBounds.x = space.x;
4927
+ nextPageBounds.y = space.y;
4928
+ height = Math.max(height, nextPageBounds.maxY);
4929
+ width = Math.max(width, nextPageBounds.maxX);
4930
+ if (nextPageBounds.width === space.width && nextPageBounds.height === space.height) {
4837
4931
  last2 = spaces.pop();
4838
- if (i2 < spaces.length) spaces[i2] = last2;
4839
- } else if (bounds.height === space.height) {
4840
- space.x += bounds.width + gap;
4841
- space.width -= bounds.width + gap;
4842
- } else if (bounds.width === space.width) {
4843
- space.y += bounds.height + gap;
4844
- space.height -= bounds.height + gap;
4932
+ if (i < spaces.length) spaces[i] = last2;
4933
+ } else if (nextPageBounds.height === space.height) {
4934
+ space.x += nextPageBounds.width + gap;
4935
+ space.width -= nextPageBounds.width + gap;
4936
+ } else if (nextPageBounds.width === space.width) {
4937
+ space.y += nextPageBounds.height + gap;
4938
+ space.height -= nextPageBounds.height + gap;
4845
4939
  } else {
4846
4940
  spaces.push(
4847
4941
  new Box(
4848
- space.x + (bounds.width + gap),
4942
+ space.x + (nextPageBounds.width + gap),
4849
4943
  space.y,
4850
- space.width - (bounds.width + gap),
4851
- bounds.height
4944
+ space.width - (nextPageBounds.width + gap),
4945
+ nextPageBounds.height
4852
4946
  )
4853
4947
  );
4854
- space.y += bounds.height + gap;
4855
- space.height -= bounds.height + gap;
4948
+ space.y += nextPageBounds.height + gap;
4949
+ space.height -= nextPageBounds.height + gap;
4856
4950
  }
4857
4951
  break;
4858
4952
  }
4859
4953
  }
4860
- const commonAfter = Box.Common(Object.values(nextShapePageBounds));
4954
+ const commonAfter = Box.Common(shapeClustersToPack.map((s) => s.nextPageBounds));
4861
4955
  const centerDelta = Vec.Sub(commonBounds.center, commonAfter.center);
4862
- let nextBounds;
4863
4956
  const changes = [];
4864
- for (let i = 0; i < shapesToPack.length; i++) {
4865
- shape = shapesToPack[i];
4866
- bounds = shapePageBounds[shape.id];
4867
- nextBounds = nextShapePageBounds[shape.id];
4868
- const delta = Vec.Sub(nextBounds.point, bounds.point).add(centerDelta);
4869
- const parentTransform = this.getShapeParentTransform(shape);
4870
- if (parentTransform) delta.rot(-parentTransform.rotation());
4871
- const change = {
4872
- id: shape.id,
4873
- type: shape.type,
4874
- x: shape.x + delta.x,
4875
- y: shape.y + delta.y
4876
- };
4877
- const translateStartChange = this.getShapeUtil(shape).onTranslateStart?.({
4878
- ...shape,
4879
- ...change
4880
- });
4881
- if (translateStartChange) {
4882
- changes.push({ ...change, ...translateStartChange });
4883
- } else {
4884
- changes.push(change);
4957
+ for (const { shapes: shapes2, pageBounds, nextPageBounds } of shapeClustersToPack) {
4958
+ const delta = Vec.Sub(nextPageBounds.point, pageBounds.point).add(centerDelta);
4959
+ for (const shape of shapes2) {
4960
+ const shapeDelta = delta.clone();
4961
+ const parent = this.getShapeParent(shape);
4962
+ if (parent) {
4963
+ const parentTransform = this.getShapeParentTransform(shape);
4964
+ if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
4965
+ }
4966
+ shapeDelta.add(shape);
4967
+ changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
4885
4968
  }
4886
4969
  }
4887
4970
  if (changes.length) {
@@ -4904,19 +4987,45 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
4904
4987
  * @public
4905
4988
  */
4906
4989
  alignShapes(shapes, operation) {
4907
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4908
4990
  if (this.getIsReadonly()) return this;
4909
- if (ids.length < 2) return this;
4910
- const shapesToAlign = compact(ids.map((id) => this.getShape(id)));
4911
- const shapePageBounds = Object.fromEntries(
4912
- shapesToAlign.map((shape) => [shape.id, this.getShapePageBounds(shape)])
4913
- );
4914
- const commonBounds = Box.Common(compact(Object.values(shapePageBounds)));
4991
+ const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4992
+ const shapesToAlignFirstPass = compact(ids.map((id) => this.getShape(id)));
4993
+ const shapeClustersToAlign = [];
4994
+ const allBounds = [];
4995
+ const visited = /* @__PURE__ */ new Set();
4996
+ for (const shape of shapesToAlignFirstPass) {
4997
+ if (visited.has(shape.id)) continue;
4998
+ visited.add(shape.id);
4999
+ const shapePageBounds = this.getShapePageBounds(shape);
5000
+ if (!shapePageBounds) continue;
5001
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5002
+ type: "align",
5003
+ shapes: shapesToAlignFirstPass
5004
+ })) {
5005
+ continue;
5006
+ }
5007
+ const shapesMovingTogether = [shape];
5008
+ const boundsOfShapesMovingTogether = [shapePageBounds];
5009
+ this.collectShapesViaArrowBindings({
5010
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
5011
+ initialShapes: shapesToAlignFirstPass,
5012
+ resultShapes: shapesMovingTogether,
5013
+ resultBounds: boundsOfShapesMovingTogether,
5014
+ visited
5015
+ });
5016
+ const commonPageBounds = Box.Common(boundsOfShapesMovingTogether);
5017
+ if (!commonPageBounds) continue;
5018
+ shapeClustersToAlign.push({
5019
+ shapes: shapesMovingTogether,
5020
+ pageBounds: commonPageBounds
5021
+ });
5022
+ allBounds.push(commonPageBounds);
5023
+ }
5024
+ if (shapeClustersToAlign.length < 2) return this;
5025
+ const commonBounds = Box.Common(allBounds);
4915
5026
  const changes = [];
4916
- shapesToAlign.forEach((shape) => {
4917
- const pageBounds = shapePageBounds[shape.id];
4918
- if (!pageBounds) return;
4919
- const delta = { x: 0, y: 0 };
5027
+ shapeClustersToAlign.forEach(({ shapes: shapes2, pageBounds }) => {
5028
+ const delta = new Vec();
4920
5029
  switch (operation) {
4921
5030
  case "top": {
4922
5031
  delta.y = commonBounds.minY - pageBounds.minY;
@@ -4943,9 +5052,16 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
4943
5052
  break;
4944
5053
  }
4945
5054
  }
4946
- const parent = this.getShapeParent(shape);
4947
- const localDelta = parent ? Vec.Rot(delta, -this.getShapePageTransform(parent).decompose().rotation) : delta;
4948
- changes.push(this.getChangesToTranslateShape(shape, Vec.Add(shape, localDelta)));
5055
+ for (const shape of shapes2) {
5056
+ const shapeDelta = delta.clone();
5057
+ const parent = this.getShapeParent(shape);
5058
+ if (parent) {
5059
+ const parentTransform = this.getShapePageTransform(parent);
5060
+ if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5061
+ }
5062
+ shapeDelta.add(shape);
5063
+ changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5064
+ }
4949
5065
  });
4950
5066
  this.updateShapes(changes);
4951
5067
  return this;
@@ -4965,47 +5081,95 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
4965
5081
  * @public
4966
5082
  */
4967
5083
  distributeShapes(shapes, operation) {
4968
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4969
5084
  if (this.getIsReadonly()) return this;
4970
- if (ids.length < 3) return this;
4971
- const len = ids.length;
4972
- const shapesToDistribute = compact(ids.map((id) => this.getShape(id)));
4973
- const pageBounds = Object.fromEntries(
4974
- shapesToDistribute.map((shape) => [shape.id, this.getShapePageBounds(shape)])
4975
- );
5085
+ const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5086
+ const shapesToDistributeFirstPass = compact(ids.map((id) => this.getShape(id)));
5087
+ const shapeClustersToDistribute = [];
5088
+ const allBounds = [];
5089
+ const visited = /* @__PURE__ */ new Set();
5090
+ for (const shape of shapesToDistributeFirstPass) {
5091
+ if (visited.has(shape.id)) continue;
5092
+ visited.add(shape.id);
5093
+ const shapePageBounds = this.getShapePageBounds(shape);
5094
+ if (!shapePageBounds) continue;
5095
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5096
+ type: "distribute",
5097
+ shapes: shapesToDistributeFirstPass
5098
+ })) {
5099
+ continue;
5100
+ }
5101
+ const shapesMovingTogether = [shape];
5102
+ const boundsOfShapesMovingTogether = [shapePageBounds];
5103
+ this.collectShapesViaArrowBindings({
5104
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
5105
+ initialShapes: shapesToDistributeFirstPass,
5106
+ resultShapes: shapesMovingTogether,
5107
+ resultBounds: boundsOfShapesMovingTogether,
5108
+ visited
5109
+ });
5110
+ const commonPageBounds = Box.Common(boundsOfShapesMovingTogether);
5111
+ if (!commonPageBounds) continue;
5112
+ shapeClustersToDistribute.push({
5113
+ shapes: shapesMovingTogether,
5114
+ pageBounds: commonPageBounds
5115
+ });
5116
+ allBounds.push(commonPageBounds);
5117
+ }
5118
+ if (shapeClustersToDistribute.length < 3) return this;
4976
5119
  let val;
4977
5120
  let min;
4978
5121
  let max;
4979
- let mid;
4980
5122
  let dim;
4981
5123
  if (operation === "horizontal") {
4982
5124
  val = "x";
4983
5125
  min = "minX";
4984
5126
  max = "maxX";
4985
- mid = "midX";
4986
5127
  dim = "width";
4987
5128
  } else {
4988
5129
  val = "y";
4989
5130
  min = "minY";
4990
5131
  max = "maxY";
4991
- mid = "midY";
4992
5132
  dim = "height";
4993
5133
  }
4994
5134
  const changes = [];
4995
- const first = shapesToDistribute.sort(
4996
- (a, b) => pageBounds[a.id][min] - pageBounds[b.id][min]
4997
- )[0];
4998
- const last2 = shapesToDistribute.sort((a, b) => pageBounds[b.id][max] - pageBounds[a.id][max])[0];
4999
- const midFirst = pageBounds[first.id][mid];
5000
- const step = (pageBounds[last2.id][mid] - midFirst) / (len - 1);
5001
- const v = midFirst + step;
5002
- shapesToDistribute.filter((shape) => shape !== first && shape !== last2).sort((a, b) => pageBounds[a.id][mid] - pageBounds[b.id][mid]).forEach((shape, i) => {
5003
- const delta = { x: 0, y: 0 };
5004
- delta[val] = v + step * i - pageBounds[shape.id][dim] / 2 - pageBounds[shape.id][val];
5005
- const parent = this.getShapeParent(shape);
5006
- const localDelta = parent ? Vec.Rot(delta, -this.getShapePageTransform(parent).rotation()) : delta;
5007
- changes.push(this.getChangesToTranslateShape(shape, Vec.Add(shape, localDelta)));
5135
+ const first = shapeClustersToDistribute.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])[0];
5136
+ const last2 = shapeClustersToDistribute.sort((a, b) => b.pageBounds[max] - a.pageBounds[max])[0];
5137
+ if (first === last2) {
5138
+ const excludedShapeIds = new Set(first.shapes.map((s) => s.id));
5139
+ return this.distributeShapes(
5140
+ ids.filter((id) => !excludedShapeIds.has(id)),
5141
+ operation
5142
+ );
5143
+ }
5144
+ const shapeClustersToMove = shapeClustersToDistribute.filter((shape) => shape !== first && shape !== last2).sort((a, b) => {
5145
+ if (a.pageBounds[min] === b.pageBounds[min]) {
5146
+ return a.shapes[0].id < b.shapes[0].id ? -1 : 1;
5147
+ }
5148
+ return a.pageBounds[min] - b.pageBounds[min];
5008
5149
  });
5150
+ const maxFirst = first.pageBounds[max];
5151
+ const range = last2.pageBounds[min] - maxFirst;
5152
+ const summedShapeDimensions = shapeClustersToMove.reduce((acc, s) => acc + s.pageBounds[dim], 0);
5153
+ const gap = (range - summedShapeDimensions) / (shapeClustersToMove.length + 1);
5154
+ for (let v = maxFirst + gap, i = 0; i < shapeClustersToMove.length; i++) {
5155
+ const { shapes: shapes2, pageBounds } = shapeClustersToMove[i];
5156
+ const delta = new Vec();
5157
+ delta[val] = v - pageBounds[val];
5158
+ if (v + pageBounds[dim] > last2.pageBounds[max] - 1) {
5159
+ delta[val] = last2.pageBounds[max] - pageBounds[max] - 1;
5160
+ }
5161
+ for (const shape of shapes2) {
5162
+ const shapeDelta = delta.clone();
5163
+ const parent = this.getShapeParent(shape);
5164
+ if (parent) {
5165
+ const parentTransform = this.getShapePageTransform(parent);
5166
+ if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5167
+ }
5168
+ shapeDelta.add(shape);
5169
+ changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5170
+ }
5171
+ v += pageBounds[dim] + gap;
5172
+ }
5009
5173
  this.updateShapes(changes);
5010
5174
  return this;
5011
5175
  }
@@ -5026,59 +5190,78 @@ class Editor extends (_a = EventEmitter, _getIsShapeHiddenCache_dec = [computed]
5026
5190
  stretchShapes(shapes, operation) {
5027
5191
  const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5028
5192
  if (this.getIsReadonly()) return this;
5029
- if (ids.length < 2) return this;
5030
- const shapesToStretch = compact(ids.map((id) => this.getShape(id)));
5031
- const shapeBounds = Object.fromEntries(ids.map((id) => [id, this.getShapeGeometry(id).bounds]));
5032
- const shapePageBounds = Object.fromEntries(ids.map((id) => [id, this.getShapePageBounds(id)]));
5033
- const commonBounds = Box.Common(compact(Object.values(shapePageBounds)));
5034
- switch (operation) {
5035
- case "vertical": {
5036
- this.run(() => {
5037
- for (const shape of shapesToStretch) {
5038
- const pageRotation = this.getShapePageTransform(shape).rotation();
5039
- if (pageRotation % PI2) continue;
5040
- const bounds = shapeBounds[shape.id];
5041
- const pageBounds = shapePageBounds[shape.id];
5042
- const localOffset = new Vec(0, commonBounds.minY - pageBounds.minY);
5043
- const parentTransform = this.getShapeParentTransform(shape);
5044
- if (parentTransform) localOffset.rot(-parentTransform.rotation());
5045
- const { x, y } = Vec.Add(localOffset, shape);
5046
- this.updateShapes([{ id: shape.id, type: shape.type, x, y }]);
5047
- const scale = new Vec(1, commonBounds.height / pageBounds.height);
5048
- this.resizeShape(shape.id, scale, {
5049
- initialBounds: bounds,
5050
- scaleOrigin: new Vec(pageBounds.center.x, commonBounds.minY),
5051
- isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
5052
- scaleAxisRotation: 0
5053
- });
5054
- }
5055
- });
5056
- break;
5057
- }
5058
- case "horizontal": {
5059
- this.run(() => {
5060
- for (const shape of shapesToStretch) {
5061
- const bounds = shapeBounds[shape.id];
5062
- const pageBounds = shapePageBounds[shape.id];
5063
- const pageRotation = this.getShapePageTransform(shape).rotation();
5064
- if (pageRotation % PI2) continue;
5065
- const localOffset = new Vec(commonBounds.minX - pageBounds.minX, 0);
5066
- const parentTransform = this.getShapeParentTransform(shape);
5067
- if (parentTransform) localOffset.rot(-parentTransform.rotation());
5068
- const { x, y } = Vec.Add(localOffset, shape);
5069
- this.updateShapes([{ id: shape.id, type: shape.type, x, y }]);
5070
- const scale = new Vec(commonBounds.width / pageBounds.width, 1);
5071
- this.resizeShape(shape.id, scale, {
5072
- initialBounds: bounds,
5073
- scaleOrigin: new Vec(commonBounds.minX, pageBounds.center.y),
5074
- isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
5075
- scaleAxisRotation: 0
5076
- });
5077
- }
5078
- });
5079
- break;
5193
+ const shapesToStretchFirstPass = compact(ids.map((id) => this.getShape(id))).filter(
5194
+ (s) => this.getShapePageTransform(s)?.rotation() % (PI / 2) === 0
5195
+ );
5196
+ const shapeClustersToStretch = [];
5197
+ const allBounds = [];
5198
+ const visited = /* @__PURE__ */ new Set();
5199
+ for (const shape of shapesToStretchFirstPass) {
5200
+ if (visited.has(shape.id)) continue;
5201
+ visited.add(shape.id);
5202
+ const shapePageBounds = this.getShapePageBounds(shape);
5203
+ if (!shapePageBounds) continue;
5204
+ const shapesMovingTogether = [shape];
5205
+ const boundsOfShapesMovingTogether = [shapePageBounds];
5206
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5207
+ type: "stretch",
5208
+ shapes: shapesToStretchFirstPass
5209
+ })) {
5210
+ continue;
5080
5211
  }
5212
+ this.collectShapesViaArrowBindings({
5213
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
5214
+ initialShapes: shapesToStretchFirstPass,
5215
+ resultShapes: shapesMovingTogether,
5216
+ resultBounds: boundsOfShapesMovingTogether,
5217
+ visited
5218
+ });
5219
+ const commonPageBounds = Box.Common(boundsOfShapesMovingTogether);
5220
+ if (!commonPageBounds) continue;
5221
+ shapeClustersToStretch.push({
5222
+ shapes: shapesMovingTogether,
5223
+ pageBounds: commonPageBounds
5224
+ });
5225
+ allBounds.push(commonPageBounds);
5226
+ }
5227
+ if (shapeClustersToStretch.length < 2) return this;
5228
+ const commonBounds = Box.Common(allBounds);
5229
+ let val;
5230
+ let min;
5231
+ let dim;
5232
+ if (operation === "horizontal") {
5233
+ val = "x";
5234
+ min = "minX";
5235
+ dim = "width";
5236
+ } else {
5237
+ val = "y";
5238
+ min = "minY";
5239
+ dim = "height";
5081
5240
  }
5241
+ this.run(() => {
5242
+ shapeClustersToStretch.forEach(({ shapes: shapes2, pageBounds }) => {
5243
+ const localOffset = new Vec();
5244
+ localOffset[val] = commonBounds[min] - pageBounds[min];
5245
+ const scaleOrigin = pageBounds.center.clone();
5246
+ scaleOrigin[val] = commonBounds[min];
5247
+ const scale = new Vec(1, 1);
5248
+ scale[val] = commonBounds[dim] / pageBounds[dim];
5249
+ for (const shape of shapes2) {
5250
+ const shapeLocalOffset = localOffset.clone();
5251
+ const parentTransform = this.getShapeParentTransform(shape);
5252
+ if (parentTransform) localOffset.rot(-parentTransform.rotation());
5253
+ shapeLocalOffset.add(shape);
5254
+ const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5255
+ this.updateShape(changes);
5256
+ this.resizeShape(shape.id, scale, {
5257
+ initialBounds: this.getShapeGeometry(shape).bounds,
5258
+ scaleOrigin,
5259
+ isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
5260
+ scaleAxisRotation: 0
5261
+ });
5262
+ }
5263
+ });
5264
+ });
5082
5265
  return this;
5083
5266
  }
5084
5267
  /**
@@ -7327,7 +7510,6 @@ __decorateElement(_init, 1, "getPages", _getPages_dec, Editor);
7327
7510
  __decorateElement(_init, 1, "getCurrentPageId", _getCurrentPageId_dec, Editor);
7328
7511
  __decorateElement(_init, 1, "getCurrentPageShapeIdsSorted", _getCurrentPageShapeIdsSorted_dec, Editor);
7329
7512
  __decorateElement(_init, 1, "_getAllAssetsQuery", __getAllAssetsQuery_dec, Editor);
7330
- __decorateElement(_init, 1, "_getShapeGeometryCache", __getShapeGeometryCache_dec, Editor);
7331
7513
  __decorateElement(_init, 1, "_getShapeHandlesCache", __getShapeHandlesCache_dec, Editor);
7332
7514
  __decorateElement(_init, 1, "_getShapePageTransformCache", __getShapePageTransformCache_dec, Editor);
7333
7515
  __decorateElement(_init, 1, "_getShapePageBoundsCache", __getShapePageBoundsCache_dec, Editor);
@@ -7417,7 +7599,7 @@ function withIsolatedShapes(editor, shapeIds, callback) {
7417
7599
  result = Result.err(error);
7418
7600
  }
7419
7601
  });
7420
- editor.store.applyDiff(reverseRecordsDiff(changes));
7602
+ editor.store.applyDiff(reverseRecordsDiff(changes), { runCallbacks: false });
7421
7603
  },
7422
7604
  { history: "ignore" }
7423
7605
  );