@tldraw/editor 3.14.0-canary.cf19563e117d → 3.14.0-canary.d15c939851b6

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 (107) hide show
  1. package/dist-cjs/index.d.ts +155 -55
  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 +90 -30
  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 +13 -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/primitives/geometry/Group2d.js +11 -6
  32. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  33. package/dist-cjs/lib/utils/areShapesContentEqual.js +1 -1
  34. package/dist-cjs/lib/utils/areShapesContentEqual.js.map +2 -2
  35. package/dist-cjs/lib/utils/dom.js +1 -1
  36. package/dist-cjs/lib/utils/dom.js.map +2 -2
  37. package/dist-cjs/lib/utils/reparenting.js +232 -0
  38. package/dist-cjs/lib/utils/reparenting.js.map +7 -0
  39. package/dist-cjs/lib/utils/richText.js +7 -2
  40. package/dist-cjs/lib/utils/richText.js.map +2 -2
  41. package/dist-cjs/version.js +3 -3
  42. package/dist-cjs/version.js.map +1 -1
  43. package/dist-esm/index.d.mts +155 -55
  44. package/dist-esm/index.mjs +4 -3
  45. package/dist-esm/index.mjs.map +2 -2
  46. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +1 -1
  47. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
  48. package/dist-esm/lib/editor/Editor.mjs +90 -30
  49. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  50. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  51. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +1 -2
  52. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  53. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +3 -1
  54. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
  55. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +73 -42
  56. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
  57. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +0 -10
  58. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  59. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
  60. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +1 -1
  61. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +13 -6
  62. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
  63. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -3
  64. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  65. package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -2
  66. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  67. package/dist-esm/lib/primitives/Box.mjs +0 -6
  68. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  69. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +6 -2
  70. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  71. package/dist-esm/lib/primitives/geometry/Group2d.mjs +11 -6
  72. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  73. package/dist-esm/lib/utils/areShapesContentEqual.mjs +1 -1
  74. package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +2 -2
  75. package/dist-esm/lib/utils/dom.mjs +1 -1
  76. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  77. package/dist-esm/lib/utils/reparenting.mjs +216 -0
  78. package/dist-esm/lib/utils/reparenting.mjs.map +7 -0
  79. package/dist-esm/lib/utils/richText.mjs +8 -3
  80. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  81. package/dist-esm/version.mjs +3 -3
  82. package/dist-esm/version.mjs.map +1 -1
  83. package/editor.css +446 -489
  84. package/package.json +8 -9
  85. package/src/index.ts +7 -1
  86. package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
  87. package/src/lib/editor/Editor.ts +107 -38
  88. package/src/lib/editor/bindings/BindingUtil.ts +6 -0
  89. package/src/lib/editor/managers/FontManager/FontManager.ts +1 -2
  90. package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +3 -1
  91. package/src/lib/editor/managers/TextManager/TextManager.test.ts +1 -5
  92. package/src/lib/editor/managers/TextManager/TextManager.ts +118 -86
  93. package/src/lib/editor/shapes/ShapeUtil.ts +47 -15
  94. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
  95. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +25 -17
  96. package/src/lib/editor/tools/StateNode.ts +3 -3
  97. package/src/lib/editor/types/emit-types.ts +4 -0
  98. package/src/lib/editor/types/external-content.ts +11 -2
  99. package/src/lib/hooks/useCanvasEvents.ts +0 -1
  100. package/src/lib/primitives/Box.ts +0 -8
  101. package/src/lib/primitives/geometry/Geometry2d.ts +7 -2
  102. package/src/lib/primitives/geometry/Group2d.ts +11 -5
  103. package/src/lib/utils/areShapesContentEqual.ts +1 -2
  104. package/src/lib/utils/dom.ts +1 -1
  105. package/src/lib/utils/reparenting.ts +383 -0
  106. package/src/lib/utils/richText.ts +9 -3
  107. package/src/version.ts +3 -3
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tldraw/editor",
3
3
  "description": "A tiny little drawing app (editor).",
4
- "version": "3.14.0-canary.cf19563e117d",
4
+ "version": "3.14.0-canary.d15c939851b6",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -48,20 +48,19 @@
48
48
  "@tiptap/core": "^2.9.1",
49
49
  "@tiptap/pm": "^2.9.1",
50
50
  "@tiptap/react": "^2.9.1",
51
- "@tldraw/state": "3.14.0-canary.cf19563e117d",
52
- "@tldraw/state-react": "3.14.0-canary.cf19563e117d",
53
- "@tldraw/store": "3.14.0-canary.cf19563e117d",
54
- "@tldraw/tlschema": "3.14.0-canary.cf19563e117d",
55
- "@tldraw/utils": "3.14.0-canary.cf19563e117d",
56
- "@tldraw/validate": "3.14.0-canary.cf19563e117d",
51
+ "@tldraw/state": "3.14.0-canary.d15c939851b6",
52
+ "@tldraw/state-react": "3.14.0-canary.d15c939851b6",
53
+ "@tldraw/store": "3.14.0-canary.d15c939851b6",
54
+ "@tldraw/tlschema": "3.14.0-canary.d15c939851b6",
55
+ "@tldraw/utils": "3.14.0-canary.d15c939851b6",
56
+ "@tldraw/validate": "3.14.0-canary.d15c939851b6",
57
57
  "@types/core-js": "^2.5.8",
58
58
  "@use-gesture/react": "^10.3.1",
59
59
  "classnames": "^2.5.1",
60
60
  "core-js": "^3.40.0",
61
61
  "eventemitter3": "^4.0.7",
62
62
  "idb": "^7.1.1",
63
- "is-plain-object": "^5.0.0",
64
- "lodash.isequal": "^4.5.0"
63
+ "is-plain-object": "^5.0.0"
65
64
  },
66
65
  "peerDependencies": {
67
66
  "react": "^18.2.0 || ^19.0.0",
package/src/index.ts CHANGED
@@ -4,7 +4,6 @@ import 'core-js/stable/array/flat-map.js'
4
4
  import 'core-js/stable/array/flat.js'
5
5
  import 'core-js/stable/string/at.js'
6
6
  import 'core-js/stable/string/replace-all.js'
7
- export { areShapesContentEqual } from './lib/utils/areShapesContentEqual'
8
7
 
9
8
  // eslint-disable-next-line local/no-export-star
10
9
  export * from '@tldraw/state'
@@ -175,6 +174,7 @@ export {
175
174
  } from './lib/editor/managers/SnapManager/SnapManager'
176
175
  export {
177
176
  TextManager,
177
+ type TLMeasureTextOpts,
178
178
  type TLMeasureTextSpanOpts,
179
179
  } from './lib/editor/managers/TextManager/TextManager'
180
180
  export { UserPreferencesManager } from './lib/editor/managers/UserPreferencesManager/UserPreferencesManager'
@@ -182,6 +182,10 @@ export { BaseBoxShapeUtil, type TLBaseBoxShape } from './lib/editor/shapes/BaseB
182
182
  export {
183
183
  ShapeUtil,
184
184
  type TLCropInfo,
185
+ type TLDragShapesInInfo,
186
+ type TLDragShapesOutInfo,
187
+ type TLDragShapesOverInfo,
188
+ type TLDropShapesOverInfo,
185
189
  type TLGeometryOpts,
186
190
  type TLHandleDragInfo,
187
191
  type TLResizeInfo,
@@ -253,6 +257,7 @@ export {
253
257
  type TLExternalContent,
254
258
  type TLExternalContentSource,
255
259
  type TLFileExternalAsset,
260
+ type TLFileReplaceExternalContent,
256
261
  type TLFilesExternalContent,
257
262
  type TLSvgTextExternalContent,
258
263
  type TLTextExternalContent,
@@ -445,6 +450,7 @@ export { hardResetEditor } from './lib/utils/hardResetEditor'
445
450
  export { isAccelKey } from './lib/utils/keyboard'
446
451
  export { normalizeWheel } from './lib/utils/normalizeWheel'
447
452
  export { refreshPage } from './lib/utils/refreshPage'
453
+ export { getDroppedShapesToNewParents, kickoutOccludedShapes } from './lib/utils/reparenting'
448
454
  export {
449
455
  getFontsFromRichText,
450
456
  type RichTextFontVisitor,
@@ -14,12 +14,12 @@ import {
14
14
  import {
15
15
  deleteFromSessionStorage,
16
16
  getFromSessionStorage,
17
+ isEqual,
17
18
  setInSessionStorage,
18
19
  structuredClone,
19
20
  uniqueId,
20
21
  } from '@tldraw/utils'
21
22
  import { T } from '@tldraw/validate'
22
- import isEqual from 'lodash.isequal'
23
23
  import { tlenv } from '../globals/environment'
24
24
 
25
25
  const tabIdKey = 'TLDRAW_TAB_ID_v2' as const
@@ -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,
@@ -5510,7 +5528,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5510
5528
  if (!id) return undefined
5511
5529
  const freshShape = this.getShape(id)
5512
5530
  if (freshShape === undefined || !isShapeId(freshShape.parentId)) return undefined
5513
- return this.store.get(freshShape.parentId)
5531
+ return this.getShape(freshShape.parentId)
5514
5532
  }
5515
5533
 
5516
5534
  /**
@@ -5693,6 +5711,10 @@ export class Editor extends EventEmitter<TLEventMap> {
5693
5711
  const newPoint = invertedParentTransform.applyToPoint(pagePoint)
5694
5712
  const newRotation = pageTransform.rotation() - parentPageRotation
5695
5713
 
5714
+ if (shape.id === parentId) {
5715
+ throw Error('Attempted to reparent a shape to itself!')
5716
+ }
5717
+
5696
5718
  changes.push({
5697
5719
  id: shape.id,
5698
5720
  type: shape.type,
@@ -5796,6 +5818,11 @@ export class Editor extends EventEmitter<TLEventMap> {
5796
5818
  return shapeIds
5797
5819
  }
5798
5820
 
5821
+ /** @deprecated Use {@link Editor.getDraggingOverShape} instead */
5822
+ getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5823
+ return this.getDraggingOverShape(point, droppingShapes)
5824
+ }
5825
+
5799
5826
  /**
5800
5827
  * Get the shape that some shapes should be dropped on at a given point.
5801
5828
  *
@@ -5806,35 +5833,33 @@ export class Editor extends EventEmitter<TLEventMap> {
5806
5833
  *
5807
5834
  * @public
5808
5835
  */
5809
- getDroppingOverShape(point: VecLike, droppingShapes: TLShape[] = []) {
5810
- // starting from the top...
5811
- const currentPageShapesSorted = this.getCurrentPageShapesSorted()
5812
- for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
5813
- const shape = currentPageShapesSorted[i]
5814
-
5815
- if (
5816
- // ignore hidden shapes
5817
- this.isShapeHidden(shape) ||
5818
- // don't allow dropping on selected shapes
5819
- this.getSelectedShapeIds().includes(shape.id) ||
5820
- // only allow shapes that can receive children
5821
- !this.getShapeUtil(shape).canDropShapes(shape, droppingShapes) ||
5822
- // don't allow dropping a shape on itself or one of it's children
5823
- droppingShapes.find((s) => s.id === shape.id || this.hasAncestor(shape, s.id))
5824
- ) {
5825
- continue
5826
- }
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
+ )
5827
5841
 
5828
- // Only allow dropping into the masked page bounds of the shape, e.g. when a frame is
5829
- // partially clipped by its own parent frame
5830
- 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
+ )
5831
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
5832
5856
  if (
5833
- maskedPageBounds &&
5834
- maskedPageBounds.containsPoint(point) &&
5835
- this.getShapeGeometry(shape).hitTestPoint(this.getPointInShapeSpace(shape, point), 0, true)
5857
+ shapeUtil.onDragShapesOver ||
5858
+ shapeUtil.onDragShapesIn ||
5859
+ shapeUtil.onDragShapesOut ||
5860
+ shapeUtil.onDropShapesOver
5836
5861
  ) {
5837
- return shape
5862
+ return maybeDraggingOverShape
5838
5863
  }
5839
5864
  }
5840
5865
  }
@@ -6193,11 +6218,12 @@ export class Editor extends EventEmitter<TLEventMap> {
6193
6218
  */
6194
6219
  duplicateShapes(shapes: TLShapeId[] | TLShape[], offset?: VecLike): this {
6195
6220
  this.run(() => {
6196
- const ids =
6221
+ const _ids =
6197
6222
  typeof shapes[0] === 'string'
6198
6223
  ? (shapes as TLShapeId[])
6199
6224
  : (shapes as TLShape[]).map((s) => s.id)
6200
6225
 
6226
+ const ids = this._shouldIgnoreShapeLock ? _ids : this._getUnlockedShapeIds(_ids)
6201
6227
  if (ids.length <= 0) return this
6202
6228
 
6203
6229
  const initialIds = new Set(ids)
@@ -6277,10 +6303,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6277
6303
  })
6278
6304
  const shapesToCreate = shapesToCreateWithOriginals.map(({ shape }) => shape)
6279
6305
 
6280
- const maxShapesReached =
6281
- shapesToCreate.length + this.getCurrentPageShapeIds().size > this.options.maxShapesPerPage
6282
-
6283
- if (maxShapesReached) {
6306
+ if (!this.canCreateShapes(shapesToCreate)) {
6284
6307
  alertMaxShapes(this)
6285
6308
  return
6286
6309
  }
@@ -7709,6 +7732,32 @@ export class Editor extends EventEmitter<TLEventMap> {
7709
7732
  return {}
7710
7733
  }
7711
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
+
7712
7761
  /**
7713
7762
  * Create a single shape.
7714
7763
  *
@@ -7755,6 +7804,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7755
7804
  if (maxShapesReached) {
7756
7805
  // can't create more shapes than fit on the page
7757
7806
  alertMaxShapes(this)
7807
+ // todo: throw an error here? Otherwise we'll need to check every time whether the shapes were actually created
7758
7808
  return this
7759
7809
  }
7760
7810
 
@@ -7787,9 +7837,10 @@ export class Editor extends EventEmitter<TLEventMap> {
7787
7837
 
7788
7838
  for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
7789
7839
  const parent = currentPageShapesSorted[i]
7840
+ const util = this.getShapeUtil(parent)
7790
7841
  if (
7842
+ util.canReceiveNewChildrenOfType(parent, partial.type) &&
7791
7843
  !this.isShapeHidden(parent) &&
7792
- this.getShapeUtil(parent).canReceiveNewChildrenOfType(parent, partial.type) &&
7793
7844
  this.isPointInShape(
7794
7845
  parent,
7795
7846
  // If no parent is provided, then we can treat the
@@ -7808,7 +7859,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7808
7859
 
7809
7860
  const prevParentId = partial.parentId
7810
7861
 
7811
- // a shape cannot be it's own parent. This was a rare issue with frames/groups in the syncFuzz tests.
7862
+ // a shape cannot be its own parent. This was a rare issue with frames/groups in the syncFuzz tests.
7812
7863
  if (parentId === partial.id) {
7813
7864
  parentId = focusedGroupId
7814
7865
  }
@@ -7918,6 +7969,8 @@ export class Editor extends EventEmitter<TLEventMap> {
7918
7969
  }
7919
7970
  })
7920
7971
 
7972
+ this.emit('created-shapes', shapeRecordsToCreate)
7973
+ this.emit('edit')
7921
7974
  this.store.put(shapeRecordsToCreate)
7922
7975
  })
7923
7976
 
@@ -8312,6 +8365,8 @@ export class Editor extends EventEmitter<TLEventMap> {
8312
8365
  updates.push(updated)
8313
8366
  }
8314
8367
 
8368
+ this.emit('edited-shapes', updates)
8369
+ this.emit('edit')
8315
8370
  this.store.put(updates)
8316
8371
  })
8317
8372
  }
@@ -8361,6 +8416,8 @@ export class Editor extends EventEmitter<TLEventMap> {
8361
8416
  })
8362
8417
  }
8363
8418
 
8419
+ this.emit('deleted-shapes', [...allShapeIdsToDelete])
8420
+ this.emit('edit')
8364
8421
  return this.run(() => this.store.remove([...allShapeIdsToDelete]))
8365
8422
  }
8366
8423
 
@@ -8809,6 +8866,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8809
8866
  } = {
8810
8867
  text: null,
8811
8868
  files: null,
8869
+ 'file-replace': null,
8812
8870
  embed: null,
8813
8871
  'svg-text': null,
8814
8872
  url: null,
@@ -8858,6 +8916,15 @@ export class Editor extends EventEmitter<TLEventMap> {
8858
8916
  return this.externalContentHandlers[info.type]?.(info as any)
8859
8917
  }
8860
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
+
8861
8928
  /**
8862
8929
  * Get content that can be exported for the given shape ids.
8863
8930
  *
@@ -9477,6 +9544,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9477
9544
  previousPagePoint,
9478
9545
  currentScreenPoint,
9479
9546
  currentPagePoint,
9547
+ originScreenPoint,
9548
+ originPagePoint,
9480
9549
  } = this.inputs
9481
9550
 
9482
9551
  const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
@@ -9505,8 +9574,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9505
9574
  // Reset velocity on pointer down, or when a pinch starts or ends
9506
9575
  if (info.name === 'pointer_down' || this.inputs.isPinching) {
9507
9576
  pointerVelocity.set(0, 0)
9508
- this.inputs.originScreenPoint.setTo(currentScreenPoint)
9509
- this.inputs.originPagePoint.setTo(currentPagePoint)
9577
+ originScreenPoint.setTo(currentScreenPoint)
9578
+ originPagePoint.setTo(currentPagePoint)
9510
9579
  }
9511
9580
 
9512
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
  })