@tldraw/editor 3.14.0-canary.ff61ab6deaa2 → 3.14.0-internal.eada756f6aa0

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 (103) hide show
  1. package/dist-cjs/index.d.ts +166 -59
  2. package/dist-cjs/index.js +4 -3
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/config/TLSessionStateSnapshot.js +1 -12
  5. package/dist-cjs/lib/config/TLSessionStateSnapshot.js.map +3 -3
  6. package/dist-cjs/lib/editor/Editor.js +113 -45
  7. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  8. package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
  9. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +1 -2
  10. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  11. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +3 -1
  12. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
  13. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +73 -42
  14. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
  15. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +0 -10
  16. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  17. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
  18. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +1 -1
  19. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +10 -6
  20. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +3 -3
  21. package/dist-cjs/lib/editor/tools/StateNode.js +3 -3
  22. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  23. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  24. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  25. package/dist-cjs/lib/hooks/useCanvasEvents.js +1 -2
  26. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  27. package/dist-cjs/lib/primitives/Box.js +0 -6
  28. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  29. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +6 -2
  30. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  31. package/dist-cjs/lib/utils/areShapesContentEqual.js +1 -1
  32. package/dist-cjs/lib/utils/areShapesContentEqual.js.map +2 -2
  33. package/dist-cjs/lib/utils/dom.js +1 -1
  34. package/dist-cjs/lib/utils/dom.js.map +2 -2
  35. package/dist-cjs/lib/utils/reparenting.js +232 -0
  36. package/dist-cjs/lib/utils/reparenting.js.map +7 -0
  37. package/dist-cjs/lib/utils/richText.js +7 -2
  38. package/dist-cjs/lib/utils/richText.js.map +2 -2
  39. package/dist-cjs/version.js +3 -3
  40. package/dist-cjs/version.js.map +1 -1
  41. package/dist-esm/index.d.mts +166 -59
  42. package/dist-esm/index.mjs +4 -3
  43. package/dist-esm/index.mjs.map +2 -2
  44. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +1 -1
  45. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
  46. package/dist-esm/lib/editor/Editor.mjs +113 -45
  47. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  48. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  49. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +1 -2
  50. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  51. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +3 -1
  52. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
  53. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +73 -42
  54. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  55. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +0 -10
  56. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  57. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
  58. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +1 -1
  59. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +10 -6
  60. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
  61. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -3
  62. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  63. package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -2
  64. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  65. package/dist-esm/lib/primitives/Box.mjs +0 -6
  66. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  67. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +6 -2
  68. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  69. package/dist-esm/lib/utils/areShapesContentEqual.mjs +1 -1
  70. package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +2 -2
  71. package/dist-esm/lib/utils/dom.mjs +1 -1
  72. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  73. package/dist-esm/lib/utils/reparenting.mjs +216 -0
  74. package/dist-esm/lib/utils/reparenting.mjs.map +7 -0
  75. package/dist-esm/lib/utils/richText.mjs +8 -3
  76. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  77. package/dist-esm/version.mjs +3 -3
  78. package/dist-esm/version.mjs.map +1 -1
  79. package/editor.css +442 -492
  80. package/package.json +8 -9
  81. package/src/index.ts +7 -1
  82. package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
  83. package/src/lib/editor/Editor.test.ts +252 -3
  84. package/src/lib/editor/Editor.ts +129 -52
  85. package/src/lib/editor/bindings/BindingUtil.ts +6 -0
  86. package/src/lib/editor/managers/FontManager/FontManager.ts +1 -2
  87. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +3 -1
  88. package/src/lib/editor/managers/TextManager/TextManager.test.ts +1 -5
  89. package/src/lib/editor/managers/TextManager/TextManager.ts +118 -86
  90. package/src/lib/editor/shapes/ShapeUtil.ts +47 -15
  91. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
  92. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +22 -17
  93. package/src/lib/editor/tools/StateNode.ts +3 -3
  94. package/src/lib/editor/types/emit-types.ts +4 -0
  95. package/src/lib/editor/types/external-content.ts +11 -2
  96. package/src/lib/hooks/useCanvasEvents.ts +0 -1
  97. package/src/lib/primitives/Box.ts +0 -8
  98. package/src/lib/primitives/geometry/Geometry2d.ts +7 -2
  99. package/src/lib/utils/areShapesContentEqual.ts +1 -2
  100. package/src/lib/utils/dom.ts +1 -1
  101. package/src/lib/utils/reparenting.ts +383 -0
  102. package/src/lib/utils/richText.ts +9 -3
  103. package/src/version.ts +3 -3
@@ -348,6 +348,8 @@ export class Editor extends EventEmitter<TLEventMap> {
348
348
  this.getContainer = getContainer
349
349
 
350
350
  this.textMeasure = new TextManager(this)
351
+ this.disposables.add(() => this.textMeasure.dispose())
352
+
351
353
  this.fonts = new FontManager(this, fontAssetUrls)
352
354
 
353
355
  this._tickManager = new TickManager(this)
@@ -506,14 +508,13 @@ export class Editor extends EventEmitter<TLEventMap> {
506
508
  shape: {
507
509
  afterChange: (shapeBefore, shapeAfter) => {
508
510
  for (const binding of this.getBindingsInvolvingShape(shapeAfter)) {
509
- if (areShapesContentEqual(shapeBefore, shapeAfter)) continue
510
-
511
511
  invalidBindingTypes.add(binding.type)
512
512
  if (binding.fromId === shapeAfter.id) {
513
513
  this.getBindingUtil(binding).onAfterChangeFromShape?.({
514
514
  binding,
515
515
  shapeBefore,
516
516
  shapeAfter,
517
+ reason: 'self',
517
518
  })
518
519
  }
519
520
  if (binding.toId === shapeAfter.id) {
@@ -521,6 +522,7 @@ export class Editor extends EventEmitter<TLEventMap> {
521
522
  binding,
522
523
  shapeBefore,
523
524
  shapeAfter,
525
+ reason: 'self',
524
526
  })
525
527
  }
526
528
  }
@@ -539,6 +541,7 @@ export class Editor extends EventEmitter<TLEventMap> {
539
541
  binding,
540
542
  shapeBefore: descendantShape,
541
543
  shapeAfter: descendantShape,
544
+ reason: 'ancestry',
542
545
  })
543
546
  }
544
547
  if (binding.toId === descendantShape.id) {
@@ -546,6 +549,7 @@ export class Editor extends EventEmitter<TLEventMap> {
546
549
  binding,
547
550
  shapeBefore: descendantShape,
548
551
  shapeAfter: descendantShape,
552
+ reason: 'ancestry',
549
553
  })
550
554
  }
551
555
  }
@@ -2118,6 +2122,20 @@ export class Editor extends EventEmitter<TLEventMap> {
2118
2122
  return this.getShapesPageBounds(this.getSelectedShapeIds())
2119
2123
  }
2120
2124
 
2125
+ /**
2126
+ * The bounds of the selection bounding box in the current page space.
2127
+ *
2128
+ * @readonly
2129
+ * @public
2130
+ */
2131
+ getSelectionScreenBounds(): Box | undefined {
2132
+ const bounds = this.getSelectionPageBounds()
2133
+ if (!bounds) return undefined
2134
+ const { x, y } = this.pageToScreen(bounds.point)
2135
+ const zoom = this.getZoomLevel()
2136
+ return new Box(x, y, bounds.width * zoom, bounds.height * zoom)
2137
+ }
2138
+
2121
2139
  /**
2122
2140
  * @internal
2123
2141
  */
@@ -3644,7 +3662,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3644
3662
  * @public
3645
3663
  */
3646
3664
  updateViewportScreenBounds(screenBounds: Box | HTMLElement, center = false): this {
3647
- if (screenBounds instanceof HTMLElement) {
3665
+ if (!(screenBounds instanceof Box)) {
3648
3666
  const rect = screenBounds.getBoundingClientRect()
3649
3667
  screenBounds = new Box(
3650
3668
  rect.left || rect.x,
@@ -5035,28 +5053,33 @@ export class Editor extends EventEmitter<TLEventMap> {
5035
5053
  *
5036
5054
  * @public
5037
5055
  */
5038
- isShapeOrAncestorLocked(shape?: TLShape): boolean
5039
- isShapeOrAncestorLocked(id?: TLShapeId): boolean
5040
- isShapeOrAncestorLocked(arg?: TLShape | TLShapeId): boolean {
5041
- const shape = typeof arg === 'string' ? this.getShape(arg) : arg
5042
- if (shape === undefined) return false
5043
- if (shape.isLocked) return true
5044
- return this.isShapeOrAncestorLocked(this.getShapeParent(shape))
5056
+ isShapeOrAncestorLocked(shape?: TLShape | TLShapeId): boolean {
5057
+ const _shape = shape && this.getShape(shape)
5058
+ if (_shape === undefined) return false
5059
+ if (_shape.isLocked) return true
5060
+ return this.isShapeOrAncestorLocked(this.getShapeParent(_shape))
5045
5061
  }
5046
5062
 
5063
+ /**
5064
+ * Get shapes that are outside of the viewport.
5065
+ *
5066
+ * @public
5067
+ */
5047
5068
  @computed
5048
- private _notVisibleShapes() {
5049
- return notVisibleShapes(this)
5069
+ getNotVisibleShapes() {
5070
+ return this._notVisibleShapes.get()
5050
5071
  }
5051
5072
 
5073
+ private _notVisibleShapes = notVisibleShapes(this)
5074
+
5052
5075
  /**
5053
- * Get culled shapes.
5076
+ * Get culled shapes (those that should not render), taking into account which shapes are selected or editing.
5054
5077
  *
5055
5078
  * @public
5056
5079
  */
5057
5080
  @computed
5058
5081
  getCulledShapes() {
5059
- const notVisibleShapes = this._notVisibleShapes().get()
5082
+ const notVisibleShapes = this.getNotVisibleShapes()
5060
5083
  const selectedShapeIds = this.getSelectedShapeIds()
5061
5084
  const editingId = this.getEditingShapeId()
5062
5085
  const culledShapes = new Set<TLShapeId>(notVisibleShapes)
@@ -5305,21 +5328,23 @@ export class Editor extends EventEmitter<TLEventMap> {
5305
5328
  * @example
5306
5329
  * ```ts
5307
5330
  * editor.getShapesAtPoint({ x: 100, y: 100 })
5308
- * editor.getShapesAtPoint({ x: 100, y: 100 }, { hitInside: true, exact: true })
5331
+ * editor.getShapesAtPoint({ x: 100, y: 100 }, { hitInside: true, margin: 8 })
5309
5332
  * ```
5310
5333
  *
5311
5334
  * @param point - The page point to test.
5312
5335
  * @param opts - The options for the hit point testing.
5313
5336
  *
5337
+ * @returns An array of shapes at the given point, sorted in reverse order of their absolute z-index (top-most shape first).
5338
+ *
5314
5339
  * @public
5315
5340
  */
5316
5341
  getShapesAtPoint(
5317
5342
  point: VecLike,
5318
5343
  opts = {} as { margin?: number; hitInside?: boolean }
5319
5344
  ): TLShape[] {
5320
- return this.getCurrentPageShapes().filter(
5321
- (shape) => !this.isShapeHidden(shape) && this.isPointInShape(shape, point, opts)
5322
- )
5345
+ return this.getCurrentPageShapesSorted()
5346
+ .filter((shape) => !this.isShapeHidden(shape) && this.isPointInShape(shape, point, opts))
5347
+ .reverse()
5323
5348
  }
5324
5349
 
5325
5350
  /**
@@ -5503,7 +5528,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5503
5528
  if (!id) return undefined
5504
5529
  const freshShape = this.getShape(id)
5505
5530
  if (freshShape === undefined || !isShapeId(freshShape.parentId)) return undefined
5506
- return this.store.get(freshShape.parentId)
5531
+ return this.getShape(freshShape.parentId)
5507
5532
  }
5508
5533
 
5509
5534
  /**
@@ -5686,6 +5711,10 @@ export class Editor extends EventEmitter<TLEventMap> {
5686
5711
  const newPoint = invertedParentTransform.applyToPoint(pagePoint)
5687
5712
  const newRotation = pageTransform.rotation() - parentPageRotation
5688
5713
 
5714
+ if (shape.id === parentId) {
5715
+ throw Error('Attempted to reparent a shape to itself!')
5716
+ }
5717
+
5689
5718
  changes.push({
5690
5719
  id: shape.id,
5691
5720
  type: shape.type,
@@ -5789,6 +5818,11 @@ export class Editor extends EventEmitter<TLEventMap> {
5789
5818
  return shapeIds
5790
5819
  }
5791
5820
 
5821
+ /** @deprecated Use {@link Editor.getDraggingOverShape} instead */
5822
+ getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5823
+ return this.getDraggingOverShape(point, droppingShapes)
5824
+ }
5825
+
5792
5826
  /**
5793
5827
  * Get the shape that some shapes should be dropped on at a given point.
5794
5828
  *
@@ -5799,35 +5833,33 @@ export class Editor extends EventEmitter<TLEventMap> {
5799
5833
  *
5800
5834
  * @public
5801
5835
  */
5802
- getDroppingOverShape(point: VecLike, droppingShapes: TLShape[] = []) {
5803
- // starting from the top...
5804
- const currentPageShapesSorted = this.getCurrentPageShapesSorted()
5805
- for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
5806
- const shape = currentPageShapesSorted[i]
5807
-
5808
- if (
5809
- // ignore hidden shapes
5810
- this.isShapeHidden(shape) ||
5811
- // don't allow dropping on selected shapes
5812
- this.getSelectedShapeIds().includes(shape.id) ||
5813
- // only allow shapes that can receive children
5814
- !this.getShapeUtil(shape).canDropShapes(shape, droppingShapes) ||
5815
- // don't allow dropping a shape on itself or one of it's children
5816
- droppingShapes.find((s) => s.id === shape.id || this.hasAncestor(shape, s.id))
5817
- ) {
5818
- continue
5819
- }
5836
+ getDraggingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5837
+ // get fresh moving shapes
5838
+ const draggingShapes = compact(droppingShapes.map((s) => this.getShape(s))).filter(
5839
+ (s) => !s.isLocked && !this.isShapeHidden(s)
5840
+ )
5820
5841
 
5821
- // Only allow dropping into the masked page bounds of the shape, e.g. when a frame is
5822
- // partially clipped by its own parent frame
5823
- const maskedPageBounds = this.getShapeMaskedPageBounds(shape.id)
5842
+ const maybeDraggingOverShapes = this.getShapesAtPoint(point, {
5843
+ hitInside: true,
5844
+ margin: 0,
5845
+ }).filter(
5846
+ (s) =>
5847
+ !droppingShapes.includes(s) &&
5848
+ !s.isLocked &&
5849
+ !this.isShapeHidden(s) &&
5850
+ !draggingShapes.includes(s)
5851
+ )
5824
5852
 
5853
+ for (const maybeDraggingOverShape of maybeDraggingOverShapes) {
5854
+ const shapeUtil = this.getShapeUtil(maybeDraggingOverShape)
5855
+ // Any shape that can handle any dragging interactions is a valid target
5825
5856
  if (
5826
- maskedPageBounds &&
5827
- maskedPageBounds.containsPoint(point) &&
5828
- this.getShapeGeometry(shape).hitTestPoint(this.getPointInShapeSpace(shape, point), 0, true)
5857
+ shapeUtil.onDragShapesOver ||
5858
+ shapeUtil.onDragShapesIn ||
5859
+ shapeUtil.onDragShapesOut ||
5860
+ shapeUtil.onDropShapesOver
5829
5861
  ) {
5830
- return shape
5862
+ return maybeDraggingOverShape
5831
5863
  }
5832
5864
  }
5833
5865
  }
@@ -6186,11 +6218,12 @@ export class Editor extends EventEmitter<TLEventMap> {
6186
6218
  */
6187
6219
  duplicateShapes(shapes: TLShapeId[] | TLShape[], offset?: VecLike): this {
6188
6220
  this.run(() => {
6189
- const ids =
6221
+ const _ids =
6190
6222
  typeof shapes[0] === 'string'
6191
6223
  ? (shapes as TLShapeId[])
6192
6224
  : (shapes as TLShape[]).map((s) => s.id)
6193
6225
 
6226
+ const ids = this._shouldIgnoreShapeLock ? _ids : this._getUnlockedShapeIds(_ids)
6194
6227
  if (ids.length <= 0) return this
6195
6228
 
6196
6229
  const initialIds = new Set(ids)
@@ -6270,10 +6303,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6270
6303
  })
6271
6304
  const shapesToCreate = shapesToCreateWithOriginals.map(({ shape }) => shape)
6272
6305
 
6273
- const maxShapesReached =
6274
- shapesToCreate.length + this.getCurrentPageShapeIds().size > this.options.maxShapesPerPage
6275
-
6276
- if (maxShapesReached) {
6306
+ if (!this.canCreateShapes(shapesToCreate)) {
6277
6307
  alertMaxShapes(this)
6278
6308
  return
6279
6309
  }
@@ -7702,6 +7732,32 @@ export class Editor extends EventEmitter<TLEventMap> {
7702
7732
  return {}
7703
7733
  }
7704
7734
 
7735
+ /**
7736
+ * Get whether the provided shape can be created.
7737
+ *
7738
+ * @param shape - The shape or shape IDs to check.
7739
+ *
7740
+ * @public
7741
+ */
7742
+ canCreateShape<T extends TLUnknownShape>(
7743
+ shape: OptionalKeys<TLShapePartial<T>, 'id'> | T['id']
7744
+ ): boolean {
7745
+ return this.canCreateShapes([shape])
7746
+ }
7747
+
7748
+ /**
7749
+ * Get whether the provided shapes can be created.
7750
+ *
7751
+ * @param shapes - The shapes or shape IDs to create.
7752
+ *
7753
+ * @public
7754
+ */
7755
+ canCreateShapes<T extends TLUnknownShape>(
7756
+ shapes: (T['id'] | OptionalKeys<TLShapePartial<T>, 'id'>)[]
7757
+ ): boolean {
7758
+ return shapes.length + this.getCurrentPageShapeIds().size <= this.options.maxShapesPerPage
7759
+ }
7760
+
7705
7761
  /**
7706
7762
  * Create a single shape.
7707
7763
  *
@@ -7748,6 +7804,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7748
7804
  if (maxShapesReached) {
7749
7805
  // can't create more shapes than fit on the page
7750
7806
  alertMaxShapes(this)
7807
+ // todo: throw an error here? Otherwise we'll need to check every time whether the shapes were actually created
7751
7808
  return this
7752
7809
  }
7753
7810
 
@@ -7780,9 +7837,10 @@ export class Editor extends EventEmitter<TLEventMap> {
7780
7837
 
7781
7838
  for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
7782
7839
  const parent = currentPageShapesSorted[i]
7840
+ const util = this.getShapeUtil(parent)
7783
7841
  if (
7842
+ util.canReceiveNewChildrenOfType(parent, partial.type) &&
7784
7843
  !this.isShapeHidden(parent) &&
7785
- this.getShapeUtil(parent).canReceiveNewChildrenOfType(parent, partial.type) &&
7786
7844
  this.isPointInShape(
7787
7845
  parent,
7788
7846
  // If no parent is provided, then we can treat the
@@ -7911,6 +7969,8 @@ export class Editor extends EventEmitter<TLEventMap> {
7911
7969
  }
7912
7970
  })
7913
7971
 
7972
+ this.emit('created-shapes', shapeRecordsToCreate)
7973
+ this.emit('edit')
7914
7974
  this.store.put(shapeRecordsToCreate)
7915
7975
  })
7916
7976
 
@@ -8305,6 +8365,8 @@ export class Editor extends EventEmitter<TLEventMap> {
8305
8365
  updates.push(updated)
8306
8366
  }
8307
8367
 
8368
+ this.emit('edited-shapes', updates)
8369
+ this.emit('edit')
8308
8370
  this.store.put(updates)
8309
8371
  })
8310
8372
  }
@@ -8354,6 +8416,8 @@ export class Editor extends EventEmitter<TLEventMap> {
8354
8416
  })
8355
8417
  }
8356
8418
 
8419
+ this.emit('deleted-shapes', [...allShapeIdsToDelete])
8420
+ this.emit('edit')
8357
8421
  return this.run(() => this.store.remove([...allShapeIdsToDelete]))
8358
8422
  }
8359
8423
 
@@ -8802,6 +8866,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8802
8866
  } = {
8803
8867
  text: null,
8804
8868
  files: null,
8869
+ 'file-replace': null,
8805
8870
  embed: null,
8806
8871
  'svg-text': null,
8807
8872
  url: null,
@@ -8851,6 +8916,15 @@ export class Editor extends EventEmitter<TLEventMap> {
8851
8916
  return this.externalContentHandlers[info.type]?.(info as any)
8852
8917
  }
8853
8918
 
8919
+ /**
8920
+ * Handle replacing external content.
8921
+ *
8922
+ * @param info - Info about the external content.
8923
+ */
8924
+ async replaceExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8925
+ return this.externalContentHandlers[info.type]?.(info as any)
8926
+ }
8927
+
8854
8928
  /**
8855
8929
  * Get content that can be exported for the given shape ids.
8856
8930
  *
@@ -9268,6 +9342,7 @@ export class Editor extends EventEmitter<TLEventMap> {
9268
9342
  if (rootShapes.length === 1) {
9269
9343
  const onlyRoot = rootShapes[0] as TLFrameShape
9270
9344
  // If the old bounds are in the viewport...
9345
+ // todo: replace frame references with shapes that can accept children
9271
9346
  if (this.isShapeOfType<TLFrameShape>(onlyRoot, 'frame')) {
9272
9347
  while (
9273
9348
  this.getShapesAtPoint(point).some(
@@ -9469,6 +9544,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9469
9544
  previousPagePoint,
9470
9545
  currentScreenPoint,
9471
9546
  currentPagePoint,
9547
+ originScreenPoint,
9548
+ originPagePoint,
9472
9549
  } = this.inputs
9473
9550
 
9474
9551
  const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
@@ -9497,8 +9574,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9497
9574
  // Reset velocity on pointer down, or when a pinch starts or ends
9498
9575
  if (info.name === 'pointer_down' || this.inputs.isPinching) {
9499
9576
  pointerVelocity.set(0, 0)
9500
- this.inputs.originScreenPoint.setTo(currentScreenPoint)
9501
- this.inputs.originPagePoint.setTo(currentPagePoint)
9577
+ originScreenPoint.setTo(currentScreenPoint)
9578
+ originPagePoint.setTo(currentPagePoint)
9502
9579
  }
9503
9580
 
9504
9581
  // todo: We only have to do this if there are multiple users in the document
@@ -62,6 +62,12 @@ export interface BindingOnShapeChangeOptions<Binding extends TLUnknownBinding> {
62
62
  shapeBefore: TLShape
63
63
  /** The shape record after the change is made. */
64
64
  shapeAfter: TLShape
65
+ /**
66
+ * Why did this shape change?
67
+ * - 'self': the shape itself changed
68
+ * - 'ancestry': the ancestry of the shape changed, but the shape itself may not have done
69
+ */
70
+ reason: 'self' | 'ancestry'
65
71
  }
66
72
 
67
73
  /**
@@ -96,8 +96,7 @@ export class FontManager {
96
96
  },
97
97
  {
98
98
  areResultsEqual: areArraysShallowEqual,
99
- // @ts-expect-error
100
- areRecordsEqual: (a, b) => a.props.richText === b.props.richText,
99
+ areRecordsEqual: (a, b) => a.props === b.props && a.meta === b.meta,
101
100
  }
102
101
  )
103
102
 
@@ -241,7 +241,9 @@ export class HistoryManager<R extends UnknownRecord> {
241
241
  }
242
242
 
243
243
  bailToMark(id: string) {
244
- this._undo({ pushToRedoStack: false, toMark: id })
244
+ if (id) {
245
+ this._undo({ pushToRedoStack: false, toMark: id })
246
+ }
245
247
 
246
248
  return this
247
249
  }
@@ -99,7 +99,7 @@ describe('TextManager', () => {
99
99
  })
100
100
 
101
101
  it('should handle empty text', () => {
102
- const result = textManager.measureText('', defaultOpts)
102
+ const result = textManager.measureText('', { ...defaultOpts, measureScrollWidth: true })
103
103
  expect(result).toHaveProperty('x', 0)
104
104
  expect(result).toHaveProperty('y', 0)
105
105
  expect(result).toHaveProperty('w')
@@ -128,7 +128,6 @@ describe('TextManager', () => {
128
128
  y: 0,
129
129
  w: expect.any(Number),
130
130
  h: expect.any(Number),
131
- scrollWidth: expect.any(Number),
132
131
  })
133
132
  })
134
133
 
@@ -141,7 +140,6 @@ describe('TextManager', () => {
141
140
  y: 0,
142
141
  w: expect.any(Number),
143
142
  h: expect.any(Number),
144
- scrollWidth: expect.any(Number),
145
143
  })
146
144
  })
147
145
 
@@ -154,7 +152,6 @@ describe('TextManager', () => {
154
152
  y: 0,
155
153
  w: expect.any(Number),
156
154
  h: expect.any(Number),
157
- scrollWidth: expect.any(Number),
158
155
  })
159
156
  })
160
157
 
@@ -173,7 +170,6 @@ describe('TextManager', () => {
173
170
  y: 0,
174
171
  w: expect.any(Number),
175
172
  h: expect.any(Number),
176
- scrollWidth: expect.any(Number),
177
173
  })
178
174
  })
179
175
  })