@tldraw/editor 3.9.0-canary.d799df28e99e → 3.9.0-canary.ef20f7307209

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 (33) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/dist-cjs/index.d.ts +23 -6
  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 +413 -237
  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/getSvgJsx.js.map +2 -2
  13. package/dist-cjs/version.js +3 -3
  14. package/dist-cjs/version.js.map +1 -1
  15. package/dist-esm/index.d.mts +23 -6
  16. package/dist-esm/index.mjs +1 -1
  17. package/dist-esm/index.mjs.map +2 -2
  18. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  19. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  20. package/dist-esm/lib/editor/Editor.mjs +409 -233
  21. package/dist-esm/lib/editor/Editor.mjs.map +3 -3
  22. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +7 -2
  23. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  24. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  25. package/dist-esm/version.mjs +3 -3
  26. package/dist-esm/version.mjs.map +1 -1
  27. package/package.json +7 -7
  28. package/src/index.ts +1 -0
  29. package/src/lib/components/default-components/DefaultErrorFallback.tsx +5 -3
  30. package/src/lib/editor/Editor.ts +540 -262
  31. package/src/lib/editor/shapes/ShapeUtil.ts +21 -5
  32. package/src/lib/exports/getSvgJsx.tsx +1 -0
  33. package/src/version.ts +3 -3
@@ -80,6 +80,7 @@ var import_state = require("@tldraw/state");
80
80
  var import_store = require("@tldraw/store");
81
81
  var import_tlschema = require("@tldraw/tlschema");
82
82
  var import_utils = require("@tldraw/utils");
83
+ var import_core_js = require("core-js");
83
84
  var import_eventemitter3 = __toESM(require("eventemitter3"));
84
85
  var import_TLEditorSnapshot = require("../config/TLEditorSnapshot");
85
86
  var import_createTLUser = require("../config/createTLUser");
@@ -2094,9 +2095,9 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
2094
2095
  this.stopFollowingUser();
2095
2096
  }
2096
2097
  const _point = import_Vec.Vec.Cast(point);
2097
- if (!Number.isFinite(_point.x)) _point.x = 0;
2098
- if (!Number.isFinite(_point.y)) _point.y = 0;
2099
- if (_point.z === void 0 || !Number.isFinite(_point.z)) point.z = this.getZoomLevel();
2098
+ if (!import_core_js.Number.isFinite(_point.x)) _point.x = 0;
2099
+ if (!import_core_js.Number.isFinite(_point.y)) _point.y = 0;
2100
+ if (_point.z === void 0 || !import_core_js.Number.isFinite(_point.z)) point.z = this.getZoomLevel();
2100
2101
  const camera = this.getConstrainedCamera(_point, opts);
2101
2102
  if (opts?.animation) {
2102
2103
  const { width, height } = this.getViewportScreenBounds();
@@ -3218,7 +3219,7 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
3218
3219
  this._shapeGeometryCaches[context] = this.store.createComputedCache(
3219
3220
  "bounds",
3220
3221
  (shape2) => this.getShapeUtil(shape2).getGeometry(shape2, opts),
3221
- (a, b) => a.props === b.props
3222
+ { areRecordsEqual: (a, b) => a.props === b.props }
3222
3223
  );
3223
3224
  }
3224
3225
  return this._shapeGeometryCaches[context].get(
@@ -4289,27 +4290,28 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
4289
4290
  });
4290
4291
  return this;
4291
4292
  }
4293
+ // Gets a shape partial that includes life cycle changes: on translate start, on translate, on translate end
4292
4294
  getChangesToTranslateShape(initialShape, newShapeCoords) {
4293
4295
  let workingShape = initialShape;
4294
4296
  const util = this.getShapeUtil(initialShape);
4295
- workingShape = applyPartialToRecordWithProps(
4296
- workingShape,
4297
- util.onTranslateStart?.(workingShape) ?? void 0
4298
- );
4297
+ const afterTranslateStart = util.onTranslateStart?.(workingShape);
4298
+ if (afterTranslateStart) {
4299
+ workingShape = applyPartialToRecordWithProps(workingShape, afterTranslateStart);
4300
+ }
4299
4301
  workingShape = applyPartialToRecordWithProps(workingShape, {
4300
4302
  id: initialShape.id,
4301
4303
  type: initialShape.type,
4302
4304
  x: newShapeCoords.x,
4303
4305
  y: newShapeCoords.y
4304
4306
  });
4305
- workingShape = applyPartialToRecordWithProps(
4306
- workingShape,
4307
- util.onTranslate?.(initialShape, workingShape) ?? void 0
4308
- );
4309
- workingShape = applyPartialToRecordWithProps(
4310
- workingShape,
4311
- util.onTranslateEnd?.(initialShape, workingShape) ?? void 0
4312
- );
4307
+ const afterTranslate = util.onTranslate?.(initialShape, workingShape);
4308
+ if (afterTranslate) {
4309
+ workingShape = applyPartialToRecordWithProps(workingShape, afterTranslate);
4310
+ }
4311
+ const afterTranslateEnd = util.onTranslateEnd?.(initialShape, workingShape);
4312
+ if (afterTranslateEnd) {
4313
+ workingShape = applyPartialToRecordWithProps(workingShape, afterTranslateEnd);
4314
+ }
4313
4315
  return workingShape;
4314
4316
  }
4315
4317
  /**
@@ -4616,6 +4618,30 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
4616
4618
  if (changes) this.updateShapes(changes);
4617
4619
  return this;
4618
4620
  }
4621
+ /**
4622
+ * @internal
4623
+ */
4624
+ collectShapesViaArrowBindings(info) {
4625
+ const { initialShapes, resultShapes, resultBounds, bindings, visited } = info;
4626
+ for (const binding of bindings) {
4627
+ for (const id of [binding.fromId, binding.toId]) {
4628
+ if (!visited.has(id)) {
4629
+ const aligningShape = initialShapes.find((s) => s.id === id);
4630
+ if (aligningShape && !visited.has(aligningShape.id)) {
4631
+ visited.add(aligningShape.id);
4632
+ const shapePageBounds = this.getShapePageBounds(aligningShape);
4633
+ if (!shapePageBounds) continue;
4634
+ resultShapes.push(aligningShape);
4635
+ resultBounds.push(shapePageBounds);
4636
+ this.collectShapesViaArrowBindings({
4637
+ ...info,
4638
+ bindings: this.getBindingsInvolvingShape(aligningShape, "arrow")
4639
+ });
4640
+ }
4641
+ }
4642
+ }
4643
+ }
4644
+ }
4619
4645
  /**
4620
4646
  * Flip shape positions.
4621
4647
  *
@@ -4631,35 +4657,52 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
4631
4657
  * @public
4632
4658
  */
4633
4659
  flipShapes(shapes, operation) {
4634
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4635
4660
  if (this.getIsReadonly()) return this;
4636
- let shapesToFlip = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
4661
+ const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4662
+ const shapesToFlipFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
4663
+ for (const shape of shapesToFlipFirstPass) {
4664
+ if (this.isShapeOfType(shape, "group")) {
4665
+ const childrenOfGroups = (0, import_utils.compact)(
4666
+ this.getSortedChildIdsForParent(shape.id).map((id) => this.getShape(id))
4667
+ );
4668
+ shapesToFlipFirstPass.push(...childrenOfGroups);
4669
+ }
4670
+ }
4671
+ const shapesToFlip = [];
4672
+ const allBounds = [];
4673
+ for (const shape of shapesToFlipFirstPass) {
4674
+ const util = this.getShapeUtil(shape);
4675
+ if (!util.canBeLaidOut(shape, {
4676
+ type: "flip",
4677
+ shapes: shapesToFlipFirstPass
4678
+ })) {
4679
+ continue;
4680
+ }
4681
+ const pageBounds = this.getShapePageBounds(shape);
4682
+ const localBounds = this.getShapeGeometry(shape).bounds;
4683
+ const pageTransform = this.getShapePageTransform(shape.id);
4684
+ if (!(pageBounds && localBounds && pageTransform)) continue;
4685
+ shapesToFlip.push({
4686
+ shape,
4687
+ localBounds,
4688
+ pageTransform,
4689
+ isAspectRatioLocked: util.isAspectRatioLocked(shape)
4690
+ });
4691
+ allBounds.push(pageBounds);
4692
+ }
4637
4693
  if (!shapesToFlip.length) return this;
4638
- shapesToFlip = (0, import_utils.compact)(
4639
- shapesToFlip.map((shape) => {
4640
- if (this.isShapeOfType(shape, "group")) {
4641
- return this.getSortedChildIdsForParent(shape.id).map((id) => this.getShape(id));
4642
- }
4643
- return shape;
4644
- }).flat()
4645
- );
4646
- const scaleOriginPage = import_Box.Box.Common(
4647
- (0, import_utils.compact)(shapesToFlip.map((id) => this.getShapePageBounds(id)))
4648
- ).center;
4694
+ const scaleOriginPage = import_Box.Box.Common(allBounds).center;
4649
4695
  this.run(() => {
4650
- for (const shape of shapesToFlip) {
4651
- const bounds = this.getShapeGeometry(shape).bounds;
4652
- const initialPageTransform = this.getShapePageTransform(shape.id);
4653
- if (!initialPageTransform) continue;
4696
+ for (const { shape, localBounds, pageTransform, isAspectRatioLocked } of shapesToFlip) {
4654
4697
  this.resizeShape(
4655
4698
  shape.id,
4656
4699
  { x: operation === "horizontal" ? -1 : 1, y: operation === "vertical" ? -1 : 1 },
4657
4700
  {
4658
- initialBounds: bounds,
4659
- initialPageTransform,
4701
+ initialBounds: localBounds,
4702
+ initialPageTransform: pageTransform,
4660
4703
  initialShape: shape,
4704
+ isAspectRatioLocked,
4661
4705
  mode: "scale_shape",
4662
- isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
4663
4706
  scaleOrigin: scaleOriginPage,
4664
4707
  scaleAxisRotation: 0
4665
4708
  }
@@ -4686,15 +4729,40 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
4686
4729
  stackShapes(shapes, operation, gap) {
4687
4730
  const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4688
4731
  if (this.getIsReadonly()) return this;
4689
- const shapesToStack = ids.map((id) => this.getShape(id)).filter((shape) => {
4690
- if (!shape) return false;
4691
- return this.getShapeUtil(shape).canBeLaidOut(shape);
4692
- });
4693
- const len = shapesToStack.length;
4732
+ const shapesToStackFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
4733
+ const shapeClustersToStack = [];
4734
+ const allBounds = [];
4735
+ const visited = /* @__PURE__ */ new Set();
4736
+ for (const shape of shapesToStackFirstPass) {
4737
+ if (visited.has(shape.id)) continue;
4738
+ visited.add(shape.id);
4739
+ const shapePageBounds = this.getShapePageBounds(shape);
4740
+ if (!shapePageBounds) continue;
4741
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
4742
+ type: "stack",
4743
+ shapes: shapesToStackFirstPass
4744
+ })) {
4745
+ continue;
4746
+ }
4747
+ const shapesMovingTogether = [shape];
4748
+ const boundsOfShapesMovingTogether = [shapePageBounds];
4749
+ this.collectShapesViaArrowBindings({
4750
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
4751
+ initialShapes: shapesToStackFirstPass,
4752
+ resultShapes: shapesMovingTogether,
4753
+ resultBounds: boundsOfShapesMovingTogether,
4754
+ visited
4755
+ });
4756
+ const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
4757
+ if (!commonPageBounds) continue;
4758
+ shapeClustersToStack.push({
4759
+ shapes: shapesMovingTogether,
4760
+ pageBounds: commonPageBounds
4761
+ });
4762
+ allBounds.push(commonPageBounds);
4763
+ }
4764
+ const len = shapeClustersToStack.length;
4694
4765
  if (gap === 0 && len < 3 || len < 2) return this;
4695
- const pageBounds = Object.fromEntries(
4696
- shapesToStack.map((shape) => [shape.id, this.getShapePageBounds(shape)])
4697
- );
4698
4766
  let val;
4699
4767
  let min;
4700
4768
  let max;
@@ -4710,57 +4778,55 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
4710
4778
  max = "maxY";
4711
4779
  dim = "height";
4712
4780
  }
4713
- let shapeGap;
4781
+ let shapeGap = 0;
4714
4782
  if (gap === 0) {
4715
- const gaps = [];
4716
- shapesToStack.sort((a, b) => pageBounds[a.id][min] - pageBounds[b.id][min]);
4783
+ const gaps = {};
4784
+ shapeClustersToStack.sort((a, b) => a.pageBounds[min] - b.pageBounds[min]);
4717
4785
  for (let i = 0; i < len - 1; i++) {
4718
- const shape = shapesToStack[i];
4719
- const nextShape = shapesToStack[i + 1];
4720
- const bounds = pageBounds[shape.id];
4721
- const nextBounds = pageBounds[nextShape.id];
4722
- const gap2 = nextBounds[min] - bounds[max];
4723
- const current = gaps.find((g) => g.gap === gap2);
4724
- if (current) {
4725
- current.count++;
4726
- } else {
4727
- gaps.push({ gap: gap2, count: 1 });
4786
+ const currCluster = shapeClustersToStack[i];
4787
+ const nextCluster = shapeClustersToStack[i + 1];
4788
+ const gap2 = nextCluster.pageBounds[min] - currCluster.pageBounds[max];
4789
+ if (!gaps[gap2]) {
4790
+ gaps[gap2] = 0;
4728
4791
  }
4792
+ gaps[gap2]++;
4729
4793
  }
4730
- let maxCount = 0;
4731
- gaps.forEach((g) => {
4732
- if (g.count > maxCount) {
4733
- maxCount = g.count;
4734
- shapeGap = g.gap;
4794
+ let maxCount = 1;
4795
+ for (const [gap2, count] of Object.entries(gaps)) {
4796
+ if (count > maxCount) {
4797
+ maxCount = count;
4798
+ shapeGap = parseFloat(gap2);
4735
4799
  }
4736
- });
4800
+ }
4737
4801
  if (maxCount === 1) {
4738
- shapeGap = Math.max(0, gaps.reduce((a, c) => a + c.gap * c.count, 0) / (len - 1));
4802
+ let totalCount = 0;
4803
+ for (const [gap2, count] of Object.entries(gaps)) {
4804
+ shapeGap += parseFloat(gap2) * count;
4805
+ totalCount += count;
4806
+ }
4807
+ shapeGap /= totalCount;
4739
4808
  }
4740
4809
  } else {
4741
4810
  shapeGap = gap;
4742
4811
  }
4743
4812
  const changes = [];
4744
- let v = pageBounds[shapesToStack[0].id][max];
4745
- shapesToStack.forEach((shape, i) => {
4746
- if (i === 0) return;
4747
- const delta = { x: 0, y: 0 };
4748
- delta[val] = v + shapeGap - pageBounds[shape.id][val];
4749
- const parent = this.getShapeParent(shape);
4750
- const localDelta = parent ? import_Vec.Vec.Rot(delta, -this.getShapePageTransform(parent).decompose().rotation) : delta;
4751
- const translateStartChanges = this.getShapeUtil(shape).onTranslateStart?.(shape);
4752
- changes.push(
4753
- translateStartChanges ? {
4754
- ...translateStartChanges,
4755
- [val]: shape[val] + localDelta[val]
4756
- } : {
4757
- id: shape.id,
4758
- type: shape.type,
4759
- [val]: shape[val] + localDelta[val]
4813
+ let v = shapeClustersToStack[0].pageBounds[max];
4814
+ for (let i = 1; i < shapeClustersToStack.length; i++) {
4815
+ const { shapes: shapes2, pageBounds } = shapeClustersToStack[i];
4816
+ const delta = new import_Vec.Vec();
4817
+ delta[val] = v + shapeGap - pageBounds[val];
4818
+ for (const shape of shapes2) {
4819
+ const shapeDelta = delta.clone();
4820
+ const parent = this.getShapeParent(shape);
4821
+ if (parent) {
4822
+ const parentTransform = this.getShapePageTransform(parent);
4823
+ if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
4760
4824
  }
4761
- );
4762
- v += pageBounds[shape.id][dim] + shapeGap;
4763
- });
4825
+ shapeDelta.add(shape);
4826
+ changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
4827
+ }
4828
+ v += pageBounds[dim] + shapeGap;
4829
+ }
4764
4830
  this.updateShapes(changes);
4765
4831
  return this;
4766
4832
  }
@@ -4778,91 +4844,101 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
4778
4844
  * @param gap - The padding to apply to the packed shapes. Defaults to 16.
4779
4845
  */
4780
4846
  packShapes(shapes, gap) {
4781
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4782
4847
  if (this.getIsReadonly()) return this;
4783
- if (ids.length < 2) return this;
4784
- const shapesToPack = ids.map((id) => this.getShape(id)).filter((shape2) => {
4785
- if (!shape2) return false;
4786
- return this.getShapeUtil(shape2).canBeLaidOut(shape2);
4787
- });
4788
- const shapePageBounds = {};
4789
- const nextShapePageBounds = {};
4790
- let shape, bounds, area = 0;
4791
- for (let i = 0; i < shapesToPack.length; i++) {
4792
- shape = shapesToPack[i];
4793
- bounds = this.getShapePageBounds(shape);
4794
- shapePageBounds[shape.id] = bounds;
4795
- nextShapePageBounds[shape.id] = bounds.clone();
4796
- area += bounds.width * bounds.height;
4797
- }
4798
- const commonBounds = import_Box.Box.Common((0, import_utils.compact)(Object.values(shapePageBounds)));
4848
+ const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4849
+ const shapesToPackFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
4850
+ const shapeClustersToPack = [];
4851
+ const allBounds = [];
4852
+ const visited = /* @__PURE__ */ new Set();
4853
+ for (const shape of shapesToPackFirstPass) {
4854
+ if (visited.has(shape.id)) continue;
4855
+ visited.add(shape.id);
4856
+ const shapePageBounds = this.getShapePageBounds(shape);
4857
+ if (!shapePageBounds) continue;
4858
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
4859
+ type: "pack",
4860
+ shapes: shapesToPackFirstPass
4861
+ })) {
4862
+ continue;
4863
+ }
4864
+ const shapesMovingTogether = [shape];
4865
+ const boundsOfShapesMovingTogether = [shapePageBounds];
4866
+ this.collectShapesViaArrowBindings({
4867
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
4868
+ initialShapes: shapesToPackFirstPass,
4869
+ resultShapes: shapesMovingTogether,
4870
+ resultBounds: boundsOfShapesMovingTogether,
4871
+ visited
4872
+ });
4873
+ const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
4874
+ if (!commonPageBounds) continue;
4875
+ shapeClustersToPack.push({
4876
+ shapes: shapesMovingTogether,
4877
+ pageBounds: commonPageBounds,
4878
+ nextPageBounds: commonPageBounds.clone()
4879
+ });
4880
+ allBounds.push(commonPageBounds);
4881
+ }
4882
+ if (shapeClustersToPack.length < 2) return this;
4883
+ let area = 0;
4884
+ for (const { pageBounds } of shapeClustersToPack) {
4885
+ area += pageBounds.width * pageBounds.height;
4886
+ }
4887
+ const commonBounds = import_Box.Box.Common(allBounds);
4799
4888
  const maxWidth = commonBounds.width;
4800
- shapesToPack.sort((a, b) => shapePageBounds[b.id].height - shapePageBounds[a.id].height);
4889
+ shapeClustersToPack.sort((a, b) => a.pageBounds.width - b.pageBounds.width).sort((a, b) => a.pageBounds.height - b.pageBounds.height);
4801
4890
  const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);
4802
4891
  const spaces = [new import_Box.Box(commonBounds.x, commonBounds.y, startWidth, Infinity)];
4803
4892
  let width = 0;
4804
4893
  let height = 0;
4805
4894
  let space;
4806
4895
  let last2;
4807
- for (let i = 0; i < shapesToPack.length; i++) {
4808
- shape = shapesToPack[i];
4809
- bounds = nextShapePageBounds[shape.id];
4810
- for (let i2 = spaces.length - 1; i2 >= 0; i2--) {
4811
- space = spaces[i2];
4812
- if (bounds.width > space.width || bounds.height > space.height) continue;
4813
- bounds.x = space.x;
4814
- bounds.y = space.y;
4815
- height = Math.max(height, bounds.maxY);
4816
- width = Math.max(width, bounds.maxX);
4817
- if (bounds.width === space.width && bounds.height === space.height) {
4896
+ for (const { nextPageBounds } of shapeClustersToPack) {
4897
+ for (let i = spaces.length - 1; i >= 0; i--) {
4898
+ space = spaces[i];
4899
+ if (nextPageBounds.width > space.width || nextPageBounds.height > space.height) continue;
4900
+ nextPageBounds.x = space.x;
4901
+ nextPageBounds.y = space.y;
4902
+ height = Math.max(height, nextPageBounds.maxY);
4903
+ width = Math.max(width, nextPageBounds.maxX);
4904
+ if (nextPageBounds.width === space.width && nextPageBounds.height === space.height) {
4818
4905
  last2 = spaces.pop();
4819
- if (i2 < spaces.length) spaces[i2] = last2;
4820
- } else if (bounds.height === space.height) {
4821
- space.x += bounds.width + gap;
4822
- space.width -= bounds.width + gap;
4823
- } else if (bounds.width === space.width) {
4824
- space.y += bounds.height + gap;
4825
- space.height -= bounds.height + gap;
4906
+ if (i < spaces.length) spaces[i] = last2;
4907
+ } else if (nextPageBounds.height === space.height) {
4908
+ space.x += nextPageBounds.width + gap;
4909
+ space.width -= nextPageBounds.width + gap;
4910
+ } else if (nextPageBounds.width === space.width) {
4911
+ space.y += nextPageBounds.height + gap;
4912
+ space.height -= nextPageBounds.height + gap;
4826
4913
  } else {
4827
4914
  spaces.push(
4828
4915
  new import_Box.Box(
4829
- space.x + (bounds.width + gap),
4916
+ space.x + (nextPageBounds.width + gap),
4830
4917
  space.y,
4831
- space.width - (bounds.width + gap),
4832
- bounds.height
4918
+ space.width - (nextPageBounds.width + gap),
4919
+ nextPageBounds.height
4833
4920
  )
4834
4921
  );
4835
- space.y += bounds.height + gap;
4836
- space.height -= bounds.height + gap;
4922
+ space.y += nextPageBounds.height + gap;
4923
+ space.height -= nextPageBounds.height + gap;
4837
4924
  }
4838
4925
  break;
4839
4926
  }
4840
4927
  }
4841
- const commonAfter = import_Box.Box.Common(Object.values(nextShapePageBounds));
4928
+ const commonAfter = import_Box.Box.Common(shapeClustersToPack.map((s) => s.nextPageBounds));
4842
4929
  const centerDelta = import_Vec.Vec.Sub(commonBounds.center, commonAfter.center);
4843
- let nextBounds;
4844
4930
  const changes = [];
4845
- for (let i = 0; i < shapesToPack.length; i++) {
4846
- shape = shapesToPack[i];
4847
- bounds = shapePageBounds[shape.id];
4848
- nextBounds = nextShapePageBounds[shape.id];
4849
- const delta = import_Vec.Vec.Sub(nextBounds.point, bounds.point).add(centerDelta);
4850
- const parentTransform = this.getShapeParentTransform(shape);
4851
- if (parentTransform) delta.rot(-parentTransform.rotation());
4852
- const change = {
4853
- id: shape.id,
4854
- type: shape.type,
4855
- x: shape.x + delta.x,
4856
- y: shape.y + delta.y
4857
- };
4858
- const translateStartChange = this.getShapeUtil(shape).onTranslateStart?.({
4859
- ...shape,
4860
- ...change
4861
- });
4862
- if (translateStartChange) {
4863
- changes.push({ ...change, ...translateStartChange });
4864
- } else {
4865
- changes.push(change);
4931
+ for (const { shapes: shapes2, pageBounds, nextPageBounds } of shapeClustersToPack) {
4932
+ const delta = import_Vec.Vec.Sub(nextPageBounds.point, pageBounds.point).add(centerDelta);
4933
+ for (const shape of shapes2) {
4934
+ const shapeDelta = delta.clone();
4935
+ const parent = this.getShapeParent(shape);
4936
+ if (parent) {
4937
+ const parentTransform = this.getShapeParentTransform(shape);
4938
+ if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
4939
+ }
4940
+ shapeDelta.add(shape);
4941
+ changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
4866
4942
  }
4867
4943
  }
4868
4944
  if (changes.length) {
@@ -4885,19 +4961,45 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
4885
4961
  * @public
4886
4962
  */
4887
4963
  alignShapes(shapes, operation) {
4888
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4889
4964
  if (this.getIsReadonly()) return this;
4890
- if (ids.length < 2) return this;
4891
- const shapesToAlign = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
4892
- const shapePageBounds = Object.fromEntries(
4893
- shapesToAlign.map((shape) => [shape.id, this.getShapePageBounds(shape)])
4894
- );
4895
- const commonBounds = import_Box.Box.Common((0, import_utils.compact)(Object.values(shapePageBounds)));
4965
+ const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4966
+ const shapesToAlignFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
4967
+ const shapeClustersToAlign = [];
4968
+ const allBounds = [];
4969
+ const visited = /* @__PURE__ */ new Set();
4970
+ for (const shape of shapesToAlignFirstPass) {
4971
+ if (visited.has(shape.id)) continue;
4972
+ visited.add(shape.id);
4973
+ const shapePageBounds = this.getShapePageBounds(shape);
4974
+ if (!shapePageBounds) continue;
4975
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
4976
+ type: "align",
4977
+ shapes: shapesToAlignFirstPass
4978
+ })) {
4979
+ continue;
4980
+ }
4981
+ const shapesMovingTogether = [shape];
4982
+ const boundsOfShapesMovingTogether = [shapePageBounds];
4983
+ this.collectShapesViaArrowBindings({
4984
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
4985
+ initialShapes: shapesToAlignFirstPass,
4986
+ resultShapes: shapesMovingTogether,
4987
+ resultBounds: boundsOfShapesMovingTogether,
4988
+ visited
4989
+ });
4990
+ const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
4991
+ if (!commonPageBounds) continue;
4992
+ shapeClustersToAlign.push({
4993
+ shapes: shapesMovingTogether,
4994
+ pageBounds: commonPageBounds
4995
+ });
4996
+ allBounds.push(commonPageBounds);
4997
+ }
4998
+ if (shapeClustersToAlign.length < 2) return this;
4999
+ const commonBounds = import_Box.Box.Common(allBounds);
4896
5000
  const changes = [];
4897
- shapesToAlign.forEach((shape) => {
4898
- const pageBounds = shapePageBounds[shape.id];
4899
- if (!pageBounds) return;
4900
- const delta = { x: 0, y: 0 };
5001
+ shapeClustersToAlign.forEach(({ shapes: shapes2, pageBounds }) => {
5002
+ const delta = new import_Vec.Vec();
4901
5003
  switch (operation) {
4902
5004
  case "top": {
4903
5005
  delta.y = commonBounds.minY - pageBounds.minY;
@@ -4924,9 +5026,16 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
4924
5026
  break;
4925
5027
  }
4926
5028
  }
4927
- const parent = this.getShapeParent(shape);
4928
- const localDelta = parent ? import_Vec.Vec.Rot(delta, -this.getShapePageTransform(parent).decompose().rotation) : delta;
4929
- changes.push(this.getChangesToTranslateShape(shape, import_Vec.Vec.Add(shape, localDelta)));
5029
+ for (const shape of shapes2) {
5030
+ const shapeDelta = delta.clone();
5031
+ const parent = this.getShapeParent(shape);
5032
+ if (parent) {
5033
+ const parentTransform = this.getShapePageTransform(parent);
5034
+ if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5035
+ }
5036
+ shapeDelta.add(shape);
5037
+ changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5038
+ }
4930
5039
  });
4931
5040
  this.updateShapes(changes);
4932
5041
  return this;
@@ -4946,47 +5055,95 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
4946
5055
  * @public
4947
5056
  */
4948
5057
  distributeShapes(shapes, operation) {
4949
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4950
5058
  if (this.getIsReadonly()) return this;
4951
- if (ids.length < 3) return this;
4952
- const len = ids.length;
4953
- const shapesToDistribute = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
4954
- const pageBounds = Object.fromEntries(
4955
- shapesToDistribute.map((shape) => [shape.id, this.getShapePageBounds(shape)])
4956
- );
5059
+ const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5060
+ const shapesToDistributeFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5061
+ const shapeClustersToDistribute = [];
5062
+ const allBounds = [];
5063
+ const visited = /* @__PURE__ */ new Set();
5064
+ for (const shape of shapesToDistributeFirstPass) {
5065
+ if (visited.has(shape.id)) continue;
5066
+ visited.add(shape.id);
5067
+ const shapePageBounds = this.getShapePageBounds(shape);
5068
+ if (!shapePageBounds) continue;
5069
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5070
+ type: "distribute",
5071
+ shapes: shapesToDistributeFirstPass
5072
+ })) {
5073
+ continue;
5074
+ }
5075
+ const shapesMovingTogether = [shape];
5076
+ const boundsOfShapesMovingTogether = [shapePageBounds];
5077
+ this.collectShapesViaArrowBindings({
5078
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
5079
+ initialShapes: shapesToDistributeFirstPass,
5080
+ resultShapes: shapesMovingTogether,
5081
+ resultBounds: boundsOfShapesMovingTogether,
5082
+ visited
5083
+ });
5084
+ const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
5085
+ if (!commonPageBounds) continue;
5086
+ shapeClustersToDistribute.push({
5087
+ shapes: shapesMovingTogether,
5088
+ pageBounds: commonPageBounds
5089
+ });
5090
+ allBounds.push(commonPageBounds);
5091
+ }
5092
+ if (shapeClustersToDistribute.length < 3) return this;
4957
5093
  let val;
4958
5094
  let min;
4959
5095
  let max;
4960
- let mid;
4961
5096
  let dim;
4962
5097
  if (operation === "horizontal") {
4963
5098
  val = "x";
4964
5099
  min = "minX";
4965
5100
  max = "maxX";
4966
- mid = "midX";
4967
5101
  dim = "width";
4968
5102
  } else {
4969
5103
  val = "y";
4970
5104
  min = "minY";
4971
5105
  max = "maxY";
4972
- mid = "midY";
4973
5106
  dim = "height";
4974
5107
  }
4975
5108
  const changes = [];
4976
- const first = shapesToDistribute.sort(
4977
- (a, b) => pageBounds[a.id][min] - pageBounds[b.id][min]
4978
- )[0];
4979
- const last2 = shapesToDistribute.sort((a, b) => pageBounds[b.id][max] - pageBounds[a.id][max])[0];
4980
- const midFirst = pageBounds[first.id][mid];
4981
- const step = (pageBounds[last2.id][mid] - midFirst) / (len - 1);
4982
- const v = midFirst + step;
4983
- shapesToDistribute.filter((shape) => shape !== first && shape !== last2).sort((a, b) => pageBounds[a.id][mid] - pageBounds[b.id][mid]).forEach((shape, i) => {
4984
- const delta = { x: 0, y: 0 };
4985
- delta[val] = v + step * i - pageBounds[shape.id][dim] / 2 - pageBounds[shape.id][val];
4986
- const parent = this.getShapeParent(shape);
4987
- const localDelta = parent ? import_Vec.Vec.Rot(delta, -this.getShapePageTransform(parent).rotation()) : delta;
4988
- changes.push(this.getChangesToTranslateShape(shape, import_Vec.Vec.Add(shape, localDelta)));
5109
+ const first = shapeClustersToDistribute.sort((a, b) => a.pageBounds[min] - b.pageBounds[min])[0];
5110
+ const last2 = shapeClustersToDistribute.sort((a, b) => b.pageBounds[max] - a.pageBounds[max])[0];
5111
+ if (first === last2) {
5112
+ const excludedShapeIds = new Set(first.shapes.map((s) => s.id));
5113
+ return this.distributeShapes(
5114
+ ids.filter((id) => !excludedShapeIds.has(id)),
5115
+ operation
5116
+ );
5117
+ }
5118
+ const shapeClustersToMove = shapeClustersToDistribute.filter((shape) => shape !== first && shape !== last2).sort((a, b) => {
5119
+ if (a.pageBounds[min] === b.pageBounds[min]) {
5120
+ return a.shapes[0].id < b.shapes[0].id ? -1 : 1;
5121
+ }
5122
+ return a.pageBounds[min] - b.pageBounds[min];
4989
5123
  });
5124
+ const maxFirst = first.pageBounds[max];
5125
+ const range = last2.pageBounds[min] - maxFirst;
5126
+ const summedShapeDimensions = shapeClustersToMove.reduce((acc, s) => acc + s.pageBounds[dim], 0);
5127
+ const gap = (range - summedShapeDimensions) / (shapeClustersToMove.length + 1);
5128
+ for (let v = maxFirst + gap, i = 0; i < shapeClustersToMove.length; i++) {
5129
+ const { shapes: shapes2, pageBounds } = shapeClustersToMove[i];
5130
+ const delta = new import_Vec.Vec();
5131
+ delta[val] = v - pageBounds[val];
5132
+ if (v + pageBounds[dim] > last2.pageBounds[max] - 1) {
5133
+ delta[val] = last2.pageBounds[max] - pageBounds[max] - 1;
5134
+ }
5135
+ for (const shape of shapes2) {
5136
+ const shapeDelta = delta.clone();
5137
+ const parent = this.getShapeParent(shape);
5138
+ if (parent) {
5139
+ const parentTransform = this.getShapePageTransform(parent);
5140
+ if (parentTransform) shapeDelta.rot(-parentTransform.rotation());
5141
+ }
5142
+ shapeDelta.add(shape);
5143
+ changes.push(this.getChangesToTranslateShape(shape, shapeDelta));
5144
+ }
5145
+ v += pageBounds[dim] + gap;
5146
+ }
4990
5147
  this.updateShapes(changes);
4991
5148
  return this;
4992
5149
  }
@@ -5007,59 +5164,78 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
5007
5164
  stretchShapes(shapes, operation) {
5008
5165
  const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5009
5166
  if (this.getIsReadonly()) return this;
5010
- if (ids.length < 2) return this;
5011
- const shapesToStretch = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5012
- const shapeBounds = Object.fromEntries(ids.map((id) => [id, this.getShapeGeometry(id).bounds]));
5013
- const shapePageBounds = Object.fromEntries(ids.map((id) => [id, this.getShapePageBounds(id)]));
5014
- const commonBounds = import_Box.Box.Common((0, import_utils.compact)(Object.values(shapePageBounds)));
5015
- switch (operation) {
5016
- case "vertical": {
5017
- this.run(() => {
5018
- for (const shape of shapesToStretch) {
5019
- const pageRotation = this.getShapePageTransform(shape).rotation();
5020
- if (pageRotation % import_utils2.PI2) continue;
5021
- const bounds = shapeBounds[shape.id];
5022
- const pageBounds = shapePageBounds[shape.id];
5023
- const localOffset = new import_Vec.Vec(0, commonBounds.minY - pageBounds.minY);
5024
- const parentTransform = this.getShapeParentTransform(shape);
5025
- if (parentTransform) localOffset.rot(-parentTransform.rotation());
5026
- const { x, y } = import_Vec.Vec.Add(localOffset, shape);
5027
- this.updateShapes([{ id: shape.id, type: shape.type, x, y }]);
5028
- const scale = new import_Vec.Vec(1, commonBounds.height / pageBounds.height);
5029
- this.resizeShape(shape.id, scale, {
5030
- initialBounds: bounds,
5031
- scaleOrigin: new import_Vec.Vec(pageBounds.center.x, commonBounds.minY),
5032
- isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
5033
- scaleAxisRotation: 0
5034
- });
5035
- }
5036
- });
5037
- break;
5038
- }
5039
- case "horizontal": {
5040
- this.run(() => {
5041
- for (const shape of shapesToStretch) {
5042
- const bounds = shapeBounds[shape.id];
5043
- const pageBounds = shapePageBounds[shape.id];
5044
- const pageRotation = this.getShapePageTransform(shape).rotation();
5045
- if (pageRotation % import_utils2.PI2) continue;
5046
- const localOffset = new import_Vec.Vec(commonBounds.minX - pageBounds.minX, 0);
5047
- const parentTransform = this.getShapeParentTransform(shape);
5048
- if (parentTransform) localOffset.rot(-parentTransform.rotation());
5049
- const { x, y } = import_Vec.Vec.Add(localOffset, shape);
5050
- this.updateShapes([{ id: shape.id, type: shape.type, x, y }]);
5051
- const scale = new import_Vec.Vec(commonBounds.width / pageBounds.width, 1);
5052
- this.resizeShape(shape.id, scale, {
5053
- initialBounds: bounds,
5054
- scaleOrigin: new import_Vec.Vec(commonBounds.minX, pageBounds.center.y),
5055
- isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
5056
- scaleAxisRotation: 0
5057
- });
5058
- }
5059
- });
5060
- break;
5167
+ const shapesToStretchFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id))).filter(
5168
+ (s) => this.getShapePageTransform(s)?.rotation() % (import_utils2.PI / 2) === 0
5169
+ );
5170
+ const shapeClustersToStretch = [];
5171
+ const allBounds = [];
5172
+ const visited = /* @__PURE__ */ new Set();
5173
+ for (const shape of shapesToStretchFirstPass) {
5174
+ if (visited.has(shape.id)) continue;
5175
+ visited.add(shape.id);
5176
+ const shapePageBounds = this.getShapePageBounds(shape);
5177
+ if (!shapePageBounds) continue;
5178
+ const shapesMovingTogether = [shape];
5179
+ const boundsOfShapesMovingTogether = [shapePageBounds];
5180
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5181
+ type: "stretch",
5182
+ shapes: shapesToStretchFirstPass
5183
+ })) {
5184
+ continue;
5061
5185
  }
5186
+ this.collectShapesViaArrowBindings({
5187
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
5188
+ initialShapes: shapesToStretchFirstPass,
5189
+ resultShapes: shapesMovingTogether,
5190
+ resultBounds: boundsOfShapesMovingTogether,
5191
+ visited
5192
+ });
5193
+ const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
5194
+ if (!commonPageBounds) continue;
5195
+ shapeClustersToStretch.push({
5196
+ shapes: shapesMovingTogether,
5197
+ pageBounds: commonPageBounds
5198
+ });
5199
+ allBounds.push(commonPageBounds);
5200
+ }
5201
+ if (shapeClustersToStretch.length < 2) return this;
5202
+ const commonBounds = import_Box.Box.Common(allBounds);
5203
+ let val;
5204
+ let min;
5205
+ let dim;
5206
+ if (operation === "horizontal") {
5207
+ val = "x";
5208
+ min = "minX";
5209
+ dim = "width";
5210
+ } else {
5211
+ val = "y";
5212
+ min = "minY";
5213
+ dim = "height";
5062
5214
  }
5215
+ this.run(() => {
5216
+ shapeClustersToStretch.forEach(({ shapes: shapes2, pageBounds }) => {
5217
+ const localOffset = new import_Vec.Vec();
5218
+ localOffset[val] = commonBounds[min] - pageBounds[min];
5219
+ const scaleOrigin = pageBounds.center.clone();
5220
+ scaleOrigin[val] = commonBounds[min];
5221
+ const scale = new import_Vec.Vec(1, 1);
5222
+ scale[val] = commonBounds[dim] / pageBounds[dim];
5223
+ for (const shape of shapes2) {
5224
+ const shapeLocalOffset = localOffset.clone();
5225
+ const parentTransform = this.getShapeParentTransform(shape);
5226
+ if (parentTransform) localOffset.rot(-parentTransform.rotation());
5227
+ shapeLocalOffset.add(shape);
5228
+ const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5229
+ this.updateShape(changes);
5230
+ this.resizeShape(shape.id, scale, {
5231
+ initialBounds: this.getShapeGeometry(shape).bounds,
5232
+ scaleOrigin,
5233
+ isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
5234
+ scaleAxisRotation: 0
5235
+ });
5236
+ }
5237
+ });
5238
+ });
5063
5239
  return this;
5064
5240
  }
5065
5241
  /**
@@ -5074,8 +5250,8 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
5074
5250
  resizeShape(shape, scale, opts = {}) {
5075
5251
  const id = typeof shape === "string" ? shape : shape.id;
5076
5252
  if (this.getIsReadonly()) return this;
5077
- if (!Number.isFinite(scale.x)) scale = new import_Vec.Vec(1, scale.y);
5078
- if (!Number.isFinite(scale.y)) scale = new import_Vec.Vec(scale.x, 1);
5253
+ if (!import_core_js.Number.isFinite(scale.x)) scale = new import_Vec.Vec(1, scale.y);
5254
+ if (!import_core_js.Number.isFinite(scale.y)) scale = new import_Vec.Vec(scale.x, 1);
5079
5255
  const initialShape = opts.initialShape ?? this.getShape(id);
5080
5256
  if (!initialShape) return this;
5081
5257
  const scaleOrigin = opts.scaleOrigin ?? this.getShapePageBounds(id)?.center;