@tldraw/editor 4.5.0-canary.034ea85352da → 4.5.0-canary.0d748e7df5db

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 (38) hide show
  1. package/dist-cjs/index.d.ts +24 -1
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +13 -16
  4. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  5. package/dist-cjs/lib/editor/Editor.js +206 -254
  6. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  7. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +15 -16
  8. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +3 -3
  9. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +1 -1
  10. package/dist-cjs/lib/hooks/useCanvasEvents.js +1 -1
  11. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  12. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +10 -0
  13. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  14. package/dist-cjs/version.js +3 -3
  15. package/dist-cjs/version.js.map +1 -1
  16. package/dist-esm/index.d.mts +24 -1
  17. package/dist-esm/index.mjs +1 -1
  18. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +13 -16
  19. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  20. package/dist-esm/lib/editor/Editor.mjs +207 -255
  21. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  22. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +15 -16
  23. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +3 -3
  24. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +1 -1
  25. package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -1
  26. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  27. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +10 -0
  28. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  29. package/dist-esm/version.mjs +3 -3
  30. package/dist-esm/version.mjs.map +1 -1
  31. package/package.json +7 -7
  32. package/src/lib/components/default-components/DefaultCanvas.tsx +19 -26
  33. package/src/lib/editor/Editor.ts +283 -393
  34. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +6 -5
  35. package/src/lib/editor/shapes/ShapeUtil.ts +1 -1
  36. package/src/lib/hooks/useCanvasEvents.ts +1 -1
  37. package/src/lib/primitives/geometry/Geometry2d.ts +12 -0
  38. package/src/version.ts +3 -3
@@ -3990,6 +3990,9 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
3990
3990
  if (geometry.isClosed) {
3991
3991
  if (distance <= outerMargin || hitInside && distance <= 0 && distance > -innerMargin) {
3992
3992
  if (geometry.isFilled || isGroup && geometry.children[0].isFilled) {
3993
+ if (geometry.ignoreHit(pointInShapeSpace)) {
3994
+ continue;
3995
+ }
3993
3996
  return inMarginClosestToEdgeHit || shape;
3994
3997
  } else {
3995
3998
  if (this.getShapePageBounds(shape).contains(viewportPageBounds)) continue;
@@ -4985,6 +4988,53 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
4985
4988
  if (changes) this.updateShapes(changes);
4986
4989
  return this;
4987
4990
  }
4991
+ /**
4992
+ * Shared clustering logic for layout methods. Resolves shapes, optionally filters to
4993
+ * axis-aligned shapes, checks canBeLaidOut, and groups shapes into clusters via arrow bindings.
4994
+ *
4995
+ * @internal
4996
+ */
4997
+ getShapeClusters(shapes, type, opts) {
4998
+ const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
4999
+ let freshShapes = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5000
+ if (opts?.filterAxisAligned) {
5001
+ freshShapes = freshShapes.filter(
5002
+ (s) => this.getShapePageTransform(s)?.rotation() % (import_utils2.PI / 2) === 0
5003
+ );
5004
+ }
5005
+ const clusters = [];
5006
+ const allBounds = [];
5007
+ const visited = /* @__PURE__ */ new Set();
5008
+ for (const shape of freshShapes) {
5009
+ if (visited.has(shape.id)) continue;
5010
+ visited.add(shape.id);
5011
+ const shapePageBounds = this.getShapePageBounds(shape);
5012
+ if (!shapePageBounds) continue;
5013
+ if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5014
+ type,
5015
+ shapes: freshShapes
5016
+ })) {
5017
+ continue;
5018
+ }
5019
+ const shapesMovingTogether = [shape];
5020
+ const boundsOfShapesMovingTogether = [shapePageBounds];
5021
+ this.collectShapesViaArrowBindings({
5022
+ bindings: this.getBindingsToShape(shape.id, "arrow"),
5023
+ initialShapes: freshShapes,
5024
+ resultShapes: shapesMovingTogether,
5025
+ resultBounds: boundsOfShapesMovingTogether,
5026
+ visited
5027
+ });
5028
+ const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
5029
+ if (!commonPageBounds) continue;
5030
+ clusters.push({
5031
+ shapes: shapesMovingTogether,
5032
+ pageBounds: commonPageBounds
5033
+ });
5034
+ allBounds.push(commonPageBounds);
5035
+ }
5036
+ return { clusters, allBounds };
5037
+ }
4988
5038
  /**
4989
5039
  * @internal
4990
5040
  */
@@ -5095,40 +5145,8 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
5095
5145
  */
5096
5146
  stackShapes(shapes, operation, gap) {
5097
5147
  const _gap = gap ?? this.options.adjacentShapeMargin;
5098
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5099
5148
  if (this.getIsReadonly()) return this;
5100
- const shapesToStackFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5101
- const shapeClustersToStack = [];
5102
- const allBounds = [];
5103
- const visited = /* @__PURE__ */ new Set();
5104
- for (const shape of shapesToStackFirstPass) {
5105
- if (visited.has(shape.id)) continue;
5106
- visited.add(shape.id);
5107
- const shapePageBounds = this.getShapePageBounds(shape);
5108
- if (!shapePageBounds) continue;
5109
- if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5110
- type: "stack",
5111
- shapes: shapesToStackFirstPass
5112
- })) {
5113
- continue;
5114
- }
5115
- const shapesMovingTogether = [shape];
5116
- const boundsOfShapesMovingTogether = [shapePageBounds];
5117
- this.collectShapesViaArrowBindings({
5118
- bindings: this.getBindingsToShape(shape.id, "arrow"),
5119
- initialShapes: shapesToStackFirstPass,
5120
- resultShapes: shapesMovingTogether,
5121
- resultBounds: boundsOfShapesMovingTogether,
5122
- visited
5123
- });
5124
- const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
5125
- if (!commonPageBounds) continue;
5126
- shapeClustersToStack.push({
5127
- shapes: shapesMovingTogether,
5128
- pageBounds: commonPageBounds
5129
- });
5130
- allBounds.push(commonPageBounds);
5131
- }
5149
+ const { clusters: shapeClustersToStack } = this.getShapeClusters(shapes, "stack");
5132
5150
  const len = shapeClustersToStack.length;
5133
5151
  if (_gap === 0 && len < 3 || len < 2) return this;
5134
5152
  let val;
@@ -5214,40 +5232,11 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
5214
5232
  packShapes(shapes, _gap) {
5215
5233
  if (this.getIsReadonly()) return this;
5216
5234
  const gap = _gap ?? this.options.adjacentShapeMargin;
5217
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5218
- const shapesToPackFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5219
- const shapeClustersToPack = [];
5220
- const allBounds = [];
5221
- const visited = /* @__PURE__ */ new Set();
5222
- for (const shape of shapesToPackFirstPass) {
5223
- if (visited.has(shape.id)) continue;
5224
- visited.add(shape.id);
5225
- const shapePageBounds = this.getShapePageBounds(shape);
5226
- if (!shapePageBounds) continue;
5227
- if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5228
- type: "pack",
5229
- shapes: shapesToPackFirstPass
5230
- })) {
5231
- continue;
5232
- }
5233
- const shapesMovingTogether = [shape];
5234
- const boundsOfShapesMovingTogether = [shapePageBounds];
5235
- this.collectShapesViaArrowBindings({
5236
- bindings: this.getBindingsToShape(shape.id, "arrow"),
5237
- initialShapes: shapesToPackFirstPass,
5238
- resultShapes: shapesMovingTogether,
5239
- resultBounds: boundsOfShapesMovingTogether,
5240
- visited
5241
- });
5242
- const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
5243
- if (!commonPageBounds) continue;
5244
- shapeClustersToPack.push({
5245
- shapes: shapesMovingTogether,
5246
- pageBounds: commonPageBounds,
5247
- nextPageBounds: commonPageBounds.clone()
5248
- });
5249
- allBounds.push(commonPageBounds);
5250
- }
5235
+ const { clusters, allBounds } = this.getShapeClusters(shapes, "pack");
5236
+ const shapeClustersToPack = clusters.map((cluster) => ({
5237
+ ...cluster,
5238
+ nextPageBounds: cluster.pageBounds.clone()
5239
+ }));
5251
5240
  if (shapeClustersToPack.length < 2) return this;
5252
5241
  let area = 0;
5253
5242
  for (const { pageBounds } of shapeClustersToPack) {
@@ -5331,39 +5320,7 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
5331
5320
  */
5332
5321
  alignShapes(shapes, operation) {
5333
5322
  if (this.getIsReadonly()) return this;
5334
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5335
- const shapesToAlignFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5336
- const shapeClustersToAlign = [];
5337
- const allBounds = [];
5338
- const visited = /* @__PURE__ */ new Set();
5339
- for (const shape of shapesToAlignFirstPass) {
5340
- if (visited.has(shape.id)) continue;
5341
- visited.add(shape.id);
5342
- const shapePageBounds = this.getShapePageBounds(shape);
5343
- if (!shapePageBounds) continue;
5344
- if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5345
- type: "align",
5346
- shapes: shapesToAlignFirstPass
5347
- })) {
5348
- continue;
5349
- }
5350
- const shapesMovingTogether = [shape];
5351
- const boundsOfShapesMovingTogether = [shapePageBounds];
5352
- this.collectShapesViaArrowBindings({
5353
- bindings: this.getBindingsToShape(shape.id, "arrow"),
5354
- initialShapes: shapesToAlignFirstPass,
5355
- resultShapes: shapesMovingTogether,
5356
- resultBounds: boundsOfShapesMovingTogether,
5357
- visited
5358
- });
5359
- const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
5360
- if (!commonPageBounds) continue;
5361
- shapeClustersToAlign.push({
5362
- shapes: shapesMovingTogether,
5363
- pageBounds: commonPageBounds
5364
- });
5365
- allBounds.push(commonPageBounds);
5366
- }
5323
+ const { clusters: shapeClustersToAlign, allBounds } = this.getShapeClusters(shapes, "align");
5367
5324
  if (shapeClustersToAlign.length < 2) return this;
5368
5325
  const commonBounds = import_Box.Box.Common(allBounds);
5369
5326
  const changes = [];
@@ -5425,39 +5382,7 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
5425
5382
  */
5426
5383
  distributeShapes(shapes, operation) {
5427
5384
  if (this.getIsReadonly()) return this;
5428
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5429
- const shapesToDistributeFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id)));
5430
- const shapeClustersToDistribute = [];
5431
- const allBounds = [];
5432
- const visited = /* @__PURE__ */ new Set();
5433
- for (const shape of shapesToDistributeFirstPass) {
5434
- if (visited.has(shape.id)) continue;
5435
- visited.add(shape.id);
5436
- const shapePageBounds = this.getShapePageBounds(shape);
5437
- if (!shapePageBounds) continue;
5438
- if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5439
- type: "distribute",
5440
- shapes: shapesToDistributeFirstPass
5441
- })) {
5442
- continue;
5443
- }
5444
- const shapesMovingTogether = [shape];
5445
- const boundsOfShapesMovingTogether = [shapePageBounds];
5446
- this.collectShapesViaArrowBindings({
5447
- bindings: this.getBindingsToShape(shape.id, "arrow"),
5448
- initialShapes: shapesToDistributeFirstPass,
5449
- resultShapes: shapesMovingTogether,
5450
- resultBounds: boundsOfShapesMovingTogether,
5451
- visited
5452
- });
5453
- const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
5454
- if (!commonPageBounds) continue;
5455
- shapeClustersToDistribute.push({
5456
- shapes: shapesMovingTogether,
5457
- pageBounds: commonPageBounds
5458
- });
5459
- allBounds.push(commonPageBounds);
5460
- }
5385
+ const { clusters: shapeClustersToDistribute } = this.getShapeClusters(shapes, "distribute");
5461
5386
  if (shapeClustersToDistribute.length < 3) return this;
5462
5387
  let val;
5463
5388
  let min;
@@ -5479,6 +5404,7 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
5479
5404
  const last2 = shapeClustersToDistribute.sort((a, b) => b.pageBounds[max] - a.pageBounds[max])[0];
5480
5405
  if (first === last2) {
5481
5406
  const excludedShapeIds = new Set(first.shapes.map((s) => s.id));
5407
+ const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5482
5408
  return this.distributeShapes(
5483
5409
  ids.filter((id) => !excludedShapeIds.has(id)),
5484
5410
  operation
@@ -5531,41 +5457,12 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
5531
5457
  * @public
5532
5458
  */
5533
5459
  stretchShapes(shapes, operation) {
5534
- const ids = typeof shapes[0] === "string" ? shapes : shapes.map((s) => s.id);
5535
5460
  if (this.getIsReadonly()) return this;
5536
- const shapesToStretchFirstPass = (0, import_utils.compact)(ids.map((id) => this.getShape(id))).filter(
5537
- (s) => this.getShapePageTransform(s)?.rotation() % (import_utils2.PI / 2) === 0
5461
+ const { clusters: shapeClustersToStretch, allBounds } = this.getShapeClusters(
5462
+ shapes,
5463
+ "stretch",
5464
+ { filterAxisAligned: true }
5538
5465
  );
5539
- const shapeClustersToStretch = [];
5540
- const allBounds = [];
5541
- const visited = /* @__PURE__ */ new Set();
5542
- for (const shape of shapesToStretchFirstPass) {
5543
- if (visited.has(shape.id)) continue;
5544
- visited.add(shape.id);
5545
- const shapePageBounds = this.getShapePageBounds(shape);
5546
- if (!shapePageBounds) continue;
5547
- const shapesMovingTogether = [shape];
5548
- const boundsOfShapesMovingTogether = [shapePageBounds];
5549
- if (!this.getShapeUtil(shape).canBeLaidOut?.(shape, {
5550
- type: "stretch"
5551
- })) {
5552
- continue;
5553
- }
5554
- this.collectShapesViaArrowBindings({
5555
- bindings: this.getBindingsToShape(shape.id, "arrow"),
5556
- initialShapes: shapesToStretchFirstPass,
5557
- resultShapes: shapesMovingTogether,
5558
- resultBounds: boundsOfShapesMovingTogether,
5559
- visited
5560
- });
5561
- const commonPageBounds = import_Box.Box.Common(boundsOfShapesMovingTogether);
5562
- if (!commonPageBounds) continue;
5563
- shapeClustersToStretch.push({
5564
- shapes: shapesMovingTogether,
5565
- pageBounds: commonPageBounds
5566
- });
5567
- allBounds.push(commonPageBounds);
5568
- }
5569
5466
  if (shapeClustersToStretch.length < 2) return this;
5570
5467
  const commonBounds = import_Box.Box.Common(allBounds);
5571
5468
  let val;
@@ -5591,7 +5488,7 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
5591
5488
  for (const shape of shapes2) {
5592
5489
  const shapeLocalOffset = localOffset.clone();
5593
5490
  const parentTransform = this.getShapeParentTransform(shape);
5594
- if (parentTransform) localOffset.rot(-parentTransform.rotation());
5491
+ if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation());
5595
5492
  shapeLocalOffset.add(shape);
5596
5493
  const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5597
5494
  this.updateShape(changes);
@@ -5606,6 +5503,62 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
5606
5503
  });
5607
5504
  return this;
5608
5505
  }
5506
+ /**
5507
+ * Resize and reposition a set of shapes so that their combined page bounds matches the given
5508
+ * target bounds.
5509
+ *
5510
+ * @example
5511
+ * ```ts
5512
+ * editor.resizeToBounds([box1, box2], { x: 0, y: 0, w: 500, h: 500 })
5513
+ * editor.resizeToBounds(editor.getSelectedShapeIds(), new Box(0, 0, 500, 500))
5514
+ * ```
5515
+ *
5516
+ * @param shapes - The shapes (or shape ids) to resize.
5517
+ * @param bounds - The target bounding box.
5518
+ *
5519
+ * @public
5520
+ */
5521
+ resizeToBounds(shapes, bounds) {
5522
+ if (this.getIsReadonly()) return this;
5523
+ const targetBounds = import_Box.Box.From(bounds);
5524
+ const { clusters: shapeClusters, allBounds } = this.getShapeClusters(
5525
+ shapes,
5526
+ "resize_to_bounds",
5527
+ { filterAxisAligned: true }
5528
+ );
5529
+ if (shapeClusters.length === 0) return this;
5530
+ const commonBounds = import_Box.Box.Common(allBounds);
5531
+ if (!commonBounds) return this;
5532
+ if (commonBounds.width === 0 || commonBounds.height === 0) return this;
5533
+ const scaleX = targetBounds.width / commonBounds.width;
5534
+ const scaleY = targetBounds.height / commonBounds.height;
5535
+ const scale = new import_Vec.Vec(scaleX, scaleY);
5536
+ shapeClusters.forEach(({ shapes: shapes2, pageBounds }) => {
5537
+ const localOffset = new import_Vec.Vec(
5538
+ targetBounds.minX - commonBounds.minX + (pageBounds.minX - commonBounds.minX) * (scaleX - 1),
5539
+ targetBounds.minY - commonBounds.minY + (pageBounds.minY - commonBounds.minY) * (scaleY - 1)
5540
+ );
5541
+ const scaleOrigin = new import_Vec.Vec(
5542
+ targetBounds.minX + (pageBounds.minX - commonBounds.minX) * scaleX,
5543
+ targetBounds.minY + (pageBounds.minY - commonBounds.minY) * scaleY
5544
+ );
5545
+ for (const shape of shapes2) {
5546
+ const shapeLocalOffset = localOffset.clone();
5547
+ const parentTransform = this.getShapeParentTransform(shape);
5548
+ if (parentTransform) shapeLocalOffset.rot(-parentTransform.rotation());
5549
+ shapeLocalOffset.add(shape);
5550
+ const changes = this.getChangesToTranslateShape(shape, shapeLocalOffset);
5551
+ this.updateShape(changes);
5552
+ this.resizeShape(shape.id, scale, {
5553
+ initialBounds: this.getShapeGeometry(shape).bounds,
5554
+ scaleOrigin,
5555
+ isAspectRatioLocked: this.getShapeUtil(shape).isAspectRatioLocked(shape),
5556
+ scaleAxisRotation: 0
5557
+ });
5558
+ }
5559
+ });
5560
+ return this;
5561
+ }
5609
5562
  /**
5610
5563
  * Resize a shape.
5611
5564
  *
@@ -6725,38 +6678,10 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
6725
6678
  const bindingIdMap = new Map(
6726
6679
  preserveIds ? bindings.map((binding) => [binding.id, binding.id]) : bindings.map((binding) => [binding.id, (0, import_tlschema.createBindingId)()])
6727
6680
  );
6728
- let pasteParentId = this.getCurrentPageId();
6729
- let lowestDepth = Infinity;
6730
- let lowestAncestors = [];
6731
- for (const shape of this.getSelectedShapes()) {
6732
- if (lowestDepth === 0) break;
6733
- const isFrame = this.isShapeOfType(shape, "frame");
6734
- const ancestors = this.getShapeAncestors(shape);
6735
- if (isFrame) ancestors.push(shape);
6736
- const depth = isFrame ? ancestors.length + 1 : ancestors.length;
6737
- if (depth < lowestDepth) {
6738
- lowestDepth = depth;
6739
- lowestAncestors = ancestors;
6740
- pasteParentId = isFrame ? shape.id : shape.parentId;
6741
- } else if (depth === lowestDepth) {
6742
- if (lowestAncestors.length !== ancestors.length) {
6743
- throw Error(`Ancestors: ${lowestAncestors.length} !== ${ancestors.length}`);
6744
- }
6745
- if (lowestAncestors.length === 0) {
6746
- pasteParentId = currentPageId;
6747
- break;
6748
- } else {
6749
- pasteParentId = currentPageId;
6750
- for (let i = 0; i < lowestAncestors.length; i++) {
6751
- if (ancestors[i] !== lowestAncestors[i]) break;
6752
- pasteParentId = ancestors[i].id;
6753
- }
6754
- }
6755
- }
6756
- }
6681
+ let pasteParentId = currentPageId;
6682
+ const shapesById = new Map(shapes.map((s) => [s.id, s]));
6683
+ const rootShapesFromContent = (0, import_utils.compact)(rootShapeIds.map((id) => shapesById.get(id)));
6757
6684
  if (point) {
6758
- const shapesById = new Map(shapes.map((shape) => [shape.id, shape]));
6759
- const rootShapesFromContent = (0, import_utils.compact)(rootShapeIds.map((id) => shapesById.get(id)));
6760
6685
  if (rootShapesFromContent.length > 0) {
6761
6686
  const targetParent = this.getShapeAtPoint(point, {
6762
6687
  hitInside: true,
@@ -6764,38 +6689,55 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
6764
6689
  hitLocked: true,
6765
6690
  filter: (shape) => {
6766
6691
  const util = this.getShapeUtil(shape);
6767
- if (!util.canReceiveNewChildrenOfType) return false;
6768
6692
  return rootShapesFromContent.every(
6769
- (rootShape) => util.canReceiveNewChildrenOfType(shape, rootShape.type)
6693
+ (rootShape) => util.canReceiveNewChildrenOfType?.(shape, rootShape.type)
6770
6694
  );
6771
6695
  }
6772
6696
  });
6773
- pasteParentId = targetParent ? targetParent.id : currentPageId;
6774
- }
6775
- }
6776
- let isDuplicating = false;
6777
- if (!(0, import_tlschema.isPageId)(pasteParentId)) {
6778
- const parent = this.getShape(pasteParentId);
6779
- if (parent) {
6780
- if (!this.getViewportPageBounds().includes(this.getShapePageBounds(parent))) {
6781
- pasteParentId = currentPageId;
6782
- } else {
6783
- if (rootShapeIds.length === 1) {
6784
- const rootShape = shapes.find((s) => s.id === rootShapeIds[0]);
6785
- if (this.isShapeOfType(parent, "frame") && this.isShapeOfType(rootShape, "frame") && rootShape.props.w === parent?.props.w && rootShape.props.h === parent?.props.h) {
6786
- isDuplicating = true;
6697
+ pasteParentId = targetParent?.id ?? currentPageId;
6698
+ }
6699
+ } else if (!preservePosition) {
6700
+ const selectedShapes = this.getSelectedShapes();
6701
+ let selectedParent = null;
6702
+ const canAcceptAll = (candidate) => {
6703
+ const util = this.getShapeUtil(candidate);
6704
+ return rootShapesFromContent.every(
6705
+ (rs) => util.canReceiveNewChildrenOfType?.(candidate, rs.type)
6706
+ );
6707
+ };
6708
+ for (const shape of selectedShapes) {
6709
+ const candidate = canAcceptAll(shape) ? shape : this.findShapeAncestor(shape, canAcceptAll) ?? ((0, import_tlschema.isShapeId)(shape.parentId) ? this.getShape(shape.parentId) : null);
6710
+ if (!candidate) {
6711
+ selectedParent = null;
6712
+ break;
6713
+ }
6714
+ if (!selectedParent) {
6715
+ selectedParent = candidate;
6716
+ } else if (selectedParent.id !== candidate.id) {
6717
+ const spAncestors = this.getShapeAncestors(selectedParent);
6718
+ if (canAcceptAll(selectedParent)) spAncestors.push(selectedParent);
6719
+ const acceptingAncestors = spAncestors.filter(canAcceptAll);
6720
+ const candidateAncestorIds = /* @__PURE__ */ new Set([
6721
+ candidate.id,
6722
+ ...this.getShapeAncestors(candidate).map((a) => a.id)
6723
+ ]);
6724
+ let common = null;
6725
+ for (let i = acceptingAncestors.length - 1; i >= 0; i--) {
6726
+ if (candidateAncestorIds.has(acceptingAncestors[i].id)) {
6727
+ common = acceptingAncestors[i];
6728
+ break;
6787
6729
  }
6788
6730
  }
6731
+ selectedParent = common;
6732
+ if (!selectedParent) break;
6789
6733
  }
6790
- } else {
6791
- pasteParentId = currentPageId;
6792
6734
  }
6793
- }
6794
- if (!isDuplicating) {
6795
- isDuplicating = shapeIdMap.has(pasteParentId);
6796
- }
6797
- if (isDuplicating) {
6798
- pasteParentId = this.getShape(pasteParentId).parentId;
6735
+ if (selectedParent && shapeIdMap.has(selectedParent.id)) {
6736
+ selectedParent = null;
6737
+ }
6738
+ if (selectedParent) {
6739
+ pasteParentId = selectedParent.id;
6740
+ }
6799
6741
  }
6800
6742
  let index = this.getHighestIndexForParent(pasteParentId);
6801
6743
  const rootShapes = [];
@@ -6859,22 +6801,17 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
6859
6801
  })
6860
6802
  );
6861
6803
  this.run(() => {
6862
- if (assetsToCreate.length > 0) {
6863
- this.createAssets(assetsToCreate);
6864
- }
6804
+ if (assetsToCreate.length > 0) this.createAssets(assetsToCreate);
6865
6805
  this.createShapes(newShapes);
6866
6806
  this.createBindings(newBindings);
6867
- if (select) {
6868
- this.select(...rootShapes.map((s) => s.id));
6869
- }
6807
+ if (select) this.select(...rootShapes.map((s) => s.id));
6870
6808
  if (pasteParentId !== currentPageId) {
6871
6809
  this.reparentShapes(
6872
6810
  rootShapes.map((s) => s.id),
6873
6811
  pasteParentId
6874
6812
  );
6875
6813
  }
6876
- const newCreatedShapes = newShapes.map((s) => this.getShape(s.id));
6877
- const bounds = import_Box.Box.Common(newCreatedShapes.map((s) => this.getShapePageBounds(s)));
6814
+ const rootBounds = import_Box.Box.Common((0, import_utils.compact)(rootShapes.map((s) => this.getShapePageBounds(s.id))));
6878
6815
  if (point === void 0) {
6879
6816
  if (!(0, import_tlschema.isPageId)(pasteParentId)) {
6880
6817
  const shape = this.getShape(pasteParentId);
@@ -6882,40 +6819,44 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
6882
6819
  this.getShapePageTransform(shape),
6883
6820
  this.getShapeGeometry(shape).bounds.center
6884
6821
  );
6822
+ } else if (preservePosition) {
6823
+ point = rootBounds.center;
6885
6824
  } else {
6886
6825
  const viewportPageBounds = this.getViewportPageBounds();
6887
- if (preservePosition || viewportPageBounds.includes(import_Box.Box.From(bounds))) {
6888
- point = bounds.center;
6889
- } else {
6890
- point = viewportPageBounds.center;
6891
- }
6892
- }
6893
- }
6894
- if (rootShapes.length === 1) {
6895
- const onlyRoot = rootShapes[0];
6896
- if (this.isShapeOfType(onlyRoot, "frame")) {
6897
- while (this.getShapesAtPoint(point).some(
6898
- (shape) => this.isShapeOfType(shape, "frame") && shape.props.w === onlyRoot.props.w && shape.props.h === onlyRoot.props.h
6899
- )) {
6900
- point.x += bounds.w + 16;
6901
- }
6826
+ const anyOverlap = rootShapes.some((s) => {
6827
+ const b = this.getShapePageBounds(s.id);
6828
+ return b && viewportPageBounds.collides(b);
6829
+ });
6830
+ point = anyOverlap ? rootBounds.center : viewportPageBounds.center;
6902
6831
  }
6903
6832
  }
6904
6833
  const pageCenter = import_Box.Box.Common(
6905
6834
  (0, import_utils.compact)(rootShapes.map(({ id }) => this.getShapePageBounds(id)))
6906
6835
  ).center;
6907
6836
  const offset = import_Vec.Vec.Sub(point, pageCenter);
6908
- this.updateShapes(
6909
- rootShapes.map(({ id }) => {
6910
- const s = this.getShape(id);
6911
- const localRotation = this.getShapeParentTransform(id).decompose().rotation;
6912
- const localDelta = import_Vec.Vec.Rot(offset, -localRotation);
6913
- return { id: s.id, type: s.type, x: s.x + localDelta.x, y: s.y + localDelta.y };
6914
- })
6915
- );
6837
+ if (offset.x !== 0 || offset.y !== 0) {
6838
+ this.updateShapes(
6839
+ rootShapes.map(({ id }) => {
6840
+ const s = this.getShape(id);
6841
+ const localRotation = this.getShapeParentTransform(id).decompose().rotation;
6842
+ const localDelta = import_Vec.Vec.Rot(offset, -localRotation);
6843
+ return { id: s.id, type: s.type, x: s.x + localDelta.x, y: s.y + localDelta.y };
6844
+ })
6845
+ );
6846
+ }
6916
6847
  if ((0, import_tlschema.isPageId)(pasteParentId)) {
6917
6848
  const currentRootShapes = (0, import_utils.compact)(rootShapes.map((s) => this.getShape(s.id)));
6918
- const { reparenting } = (0, import_reparenting.getDroppedShapesToNewParents)(this, currentRootShapes);
6849
+ const { reparenting } = (0, import_reparenting.getDroppedShapesToNewParents)(
6850
+ this,
6851
+ currentRootShapes,
6852
+ (shape, parent) => {
6853
+ if (shapeIdMap.has(parent.id)) return false;
6854
+ const shapeBounds = this.getShapePageBounds(shape);
6855
+ const parentBounds = this.getShapePageBounds(parent);
6856
+ if (!shapeBounds || !parentBounds) return false;
6857
+ return parentBounds.containsPoint(shapeBounds.center);
6858
+ }
6859
+ );
6919
6860
  reparenting.forEach((childrenToReparent, newParentId) => {
6920
6861
  if (childrenToReparent.length === 0) return;
6921
6862
  this.reparentShapes(
@@ -6924,6 +6865,17 @@ class Editor extends (_a = import_eventemitter3.default, _getIsShapeHiddenCache_
6924
6865
  );
6925
6866
  });
6926
6867
  }
6868
+ const newShapeIdSet = new Set(newShapes.map((s) => s.id));
6869
+ const shapesToKickout = rootShapes.map((s) => s.id).filter((id) => {
6870
+ const shape = this.getShape(id);
6871
+ if (!shape) return false;
6872
+ if ((0, import_tlschema.isPageId)(shape.parentId)) return false;
6873
+ const children = this.getSortedChildIdsForParent(id);
6874
+ return !children.some((childId) => newShapeIdSet.has(childId));
6875
+ });
6876
+ if (shapesToKickout.length > 0) {
6877
+ (0, import_reparenting.kickoutOccludedShapes)(this, shapesToKickout);
6878
+ }
6927
6879
  });
6928
6880
  return this;
6929
6881
  }