@tldraw/editor 3.14.0-canary.8ea0ff8489db → 3.14.0-canary.8f303431285f

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 (190) hide show
  1. package/dist-cjs/index.d.ts +166 -76
  2. package/dist-cjs/index.js +11 -10
  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 +132 -101
  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/derivations/bindingsIndex.js +22 -22
  10. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  11. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +16 -16
  12. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  13. package/dist-cjs/lib/editor/managers/{ClickManager.js → ClickManager/ClickManager.js} +1 -1
  14. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +7 -0
  15. package/dist-cjs/lib/editor/managers/{EdgeScrollManager.js → EdgeScrollManager/EdgeScrollManager.js} +2 -2
  16. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +7 -0
  17. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +7 -0
  18. package/dist-cjs/lib/editor/managers/{FontManager.js → FontManager/FontManager.js} +4 -1
  19. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +7 -0
  20. package/dist-cjs/lib/editor/managers/{HistoryManager.js → HistoryManager/HistoryManager.js} +67 -7
  21. package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +7 -0
  22. package/dist-cjs/lib/editor/managers/{ScribbleManager.js → ScribbleManager/ScribbleManager.js} +1 -1
  23. package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +7 -0
  24. package/dist-cjs/lib/editor/managers/{TextManager.js → TextManager/TextManager.js} +73 -42
  25. package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +7 -0
  26. package/dist-cjs/lib/editor/managers/{TickManager.js → TickManager/TickManager.js} +1 -1
  27. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +7 -0
  28. package/dist-cjs/lib/editor/managers/{UserPreferencesManager.js → UserPreferencesManager/UserPreferencesManager.js} +1 -1
  29. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +7 -0
  30. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +0 -10
  31. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  32. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
  33. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +1 -1
  34. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +10 -6
  35. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +3 -3
  36. package/dist-cjs/lib/editor/tools/StateNode.js +3 -3
  37. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  38. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  39. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  40. package/dist-cjs/lib/exports/getSvgJsx.js.map +1 -1
  41. package/dist-cjs/lib/hooks/useCanvasEvents.js +1 -2
  42. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  43. package/dist-cjs/lib/primitives/Box.js +33 -39
  44. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  45. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +6 -2
  46. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  47. package/dist-cjs/lib/primitives/geometry/Group2d.js +11 -6
  48. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  49. package/dist-cjs/lib/utils/areShapesContentEqual.js +1 -1
  50. package/dist-cjs/lib/utils/areShapesContentEqual.js.map +2 -2
  51. package/dist-cjs/lib/utils/dom.js +1 -1
  52. package/dist-cjs/lib/utils/dom.js.map +2 -2
  53. package/dist-cjs/lib/utils/reorderShapes.js +11 -10
  54. package/dist-cjs/lib/utils/reorderShapes.js.map +2 -2
  55. package/dist-cjs/lib/utils/reparenting.js +232 -0
  56. package/dist-cjs/lib/utils/reparenting.js.map +7 -0
  57. package/dist-cjs/lib/utils/richText.js +7 -2
  58. package/dist-cjs/lib/utils/richText.js.map +2 -2
  59. package/dist-cjs/version.js +3 -3
  60. package/dist-cjs/version.js.map +1 -1
  61. package/dist-esm/index.d.mts +166 -76
  62. package/dist-esm/index.mjs +15 -10
  63. package/dist-esm/index.mjs.map +2 -2
  64. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs +1 -1
  65. package/dist-esm/lib/config/TLSessionStateSnapshot.mjs.map +2 -2
  66. package/dist-esm/lib/editor/Editor.mjs +132 -101
  67. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  68. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  69. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs +22 -22
  70. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  71. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +16 -16
  72. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  73. package/dist-esm/lib/editor/managers/{ClickManager.mjs → ClickManager/ClickManager.mjs} +1 -1
  74. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +7 -0
  75. package/dist-esm/lib/editor/managers/{EdgeScrollManager.mjs → EdgeScrollManager/EdgeScrollManager.mjs} +2 -2
  76. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +7 -0
  77. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +7 -0
  78. package/dist-esm/lib/editor/managers/{FontManager.mjs → FontManager/FontManager.mjs} +4 -1
  79. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +7 -0
  80. package/dist-esm/lib/editor/managers/{HistoryManager.mjs → HistoryManager/HistoryManager.mjs} +63 -3
  81. package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +7 -0
  82. package/dist-esm/lib/editor/managers/{ScribbleManager.mjs → ScribbleManager/ScribbleManager.mjs} +1 -1
  83. package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +7 -0
  84. package/dist-esm/lib/editor/managers/{TextManager.mjs → TextManager/TextManager.mjs} +73 -42
  85. package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +7 -0
  86. package/dist-esm/lib/editor/managers/{TickManager.mjs → TickManager/TickManager.mjs} +1 -1
  87. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +7 -0
  88. package/dist-esm/lib/editor/managers/{UserPreferencesManager.mjs → UserPreferencesManager/UserPreferencesManager.mjs} +1 -1
  89. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +7 -0
  90. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +0 -10
  91. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  92. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +1 -1
  93. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +1 -1
  94. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +10 -6
  95. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +3 -3
  96. package/dist-esm/lib/editor/tools/StateNode.mjs +3 -3
  97. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  98. package/dist-esm/lib/exports/getSvgJsx.mjs.map +1 -1
  99. package/dist-esm/lib/hooks/useCanvasEvents.mjs +1 -2
  100. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  101. package/dist-esm/lib/primitives/Box.mjs +33 -39
  102. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  103. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +6 -2
  104. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  105. package/dist-esm/lib/primitives/geometry/Group2d.mjs +11 -6
  106. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  107. package/dist-esm/lib/utils/areShapesContentEqual.mjs +1 -1
  108. package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +2 -2
  109. package/dist-esm/lib/utils/dom.mjs +1 -1
  110. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  111. package/dist-esm/lib/utils/reorderShapes.mjs +11 -10
  112. package/dist-esm/lib/utils/reorderShapes.mjs.map +2 -2
  113. package/dist-esm/lib/utils/reparenting.mjs +216 -0
  114. package/dist-esm/lib/utils/reparenting.mjs.map +7 -0
  115. package/dist-esm/lib/utils/richText.mjs +8 -3
  116. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  117. package/dist-esm/version.mjs +3 -3
  118. package/dist-esm/version.mjs.map +1 -1
  119. package/editor.css +446 -489
  120. package/package.json +8 -9
  121. package/src/index.ts +20 -8
  122. package/src/lib/config/TLSessionStateSnapshot.ts +1 -1
  123. package/src/lib/editor/Editor.test.ts +252 -3
  124. package/src/lib/editor/Editor.ts +151 -111
  125. package/src/lib/editor/bindings/BindingUtil.ts +6 -0
  126. package/src/lib/editor/derivations/bindingsIndex.ts +27 -26
  127. package/src/lib/editor/derivations/parentsToChildren.ts +28 -25
  128. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +442 -0
  129. package/src/lib/editor/managers/{ClickManager.ts → ClickManager/ClickManager.ts} +3 -3
  130. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +374 -0
  131. package/src/lib/editor/managers/{EdgeScrollManager.ts → EdgeScrollManager/EdgeScrollManager.ts} +3 -3
  132. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +455 -0
  133. package/src/lib/editor/managers/{FocusManager.ts → FocusManager/FocusManager.ts} +1 -1
  134. package/src/lib/editor/managers/FontManager/FontManager.test.ts +263 -0
  135. package/src/lib/editor/managers/{FontManager.ts → FontManager/FontManager.ts} +5 -2
  136. package/src/lib/editor/managers/{HistoryManager.test.ts → HistoryManager/HistoryManager.test.ts} +388 -1
  137. package/src/lib/editor/managers/{HistoryManager.ts → HistoryManager/HistoryManager.ts} +76 -3
  138. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +624 -0
  139. package/src/lib/editor/managers/{ScribbleManager.ts → ScribbleManager/ScribbleManager.ts} +2 -2
  140. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +485 -0
  141. package/src/lib/editor/managers/TextManager/TextManager.test.ts +407 -0
  142. package/src/lib/editor/managers/{TextManager.ts → TextManager/TextManager.ts} +119 -87
  143. package/src/lib/editor/managers/TickManager/TickManager.test.ts +314 -0
  144. package/src/lib/editor/managers/{TickManager.ts → TickManager/TickManager.ts} +2 -2
  145. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +591 -0
  146. package/src/lib/editor/managers/{UserPreferencesManager.ts → UserPreferencesManager/UserPreferencesManager.ts} +2 -2
  147. package/src/lib/editor/shapes/ShapeUtil.ts +48 -16
  148. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
  149. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +22 -17
  150. package/src/lib/editor/tools/StateNode.ts +3 -3
  151. package/src/lib/editor/types/emit-types.ts +4 -0
  152. package/src/lib/editor/types/external-content.ts +11 -2
  153. package/src/lib/exports/getSvgJsx.tsx +1 -1
  154. package/src/lib/hooks/useCanvasEvents.ts +0 -1
  155. package/src/lib/primitives/Box.test.ts +588 -7
  156. package/src/lib/primitives/Box.ts +33 -41
  157. package/src/lib/primitives/geometry/Geometry2d.ts +7 -2
  158. package/src/lib/primitives/geometry/Group2d.ts +11 -5
  159. package/src/lib/utils/areShapesContentEqual.ts +1 -2
  160. package/src/lib/utils/dom.ts +1 -1
  161. package/src/lib/utils/reorderShapes.ts +10 -13
  162. package/src/lib/utils/reparenting.ts +383 -0
  163. package/src/lib/utils/richText.ts +10 -4
  164. package/src/version.ts +3 -3
  165. package/dist-cjs/lib/editor/managers/ClickManager.js.map +0 -7
  166. package/dist-cjs/lib/editor/managers/EdgeScrollManager.js.map +0 -7
  167. package/dist-cjs/lib/editor/managers/FocusManager.js.map +0 -7
  168. package/dist-cjs/lib/editor/managers/FontManager.js.map +0 -7
  169. package/dist-cjs/lib/editor/managers/HistoryManager.js.map +0 -7
  170. package/dist-cjs/lib/editor/managers/ScribbleManager.js.map +0 -7
  171. package/dist-cjs/lib/editor/managers/Stack.js +0 -82
  172. package/dist-cjs/lib/editor/managers/Stack.js.map +0 -7
  173. package/dist-cjs/lib/editor/managers/TextManager.js.map +0 -7
  174. package/dist-cjs/lib/editor/managers/TickManager.js.map +0 -7
  175. package/dist-cjs/lib/editor/managers/UserPreferencesManager.js.map +0 -7
  176. package/dist-esm/lib/editor/managers/ClickManager.mjs.map +0 -7
  177. package/dist-esm/lib/editor/managers/EdgeScrollManager.mjs.map +0 -7
  178. package/dist-esm/lib/editor/managers/FocusManager.mjs.map +0 -7
  179. package/dist-esm/lib/editor/managers/FontManager.mjs.map +0 -7
  180. package/dist-esm/lib/editor/managers/HistoryManager.mjs.map +0 -7
  181. package/dist-esm/lib/editor/managers/ScribbleManager.mjs.map +0 -7
  182. package/dist-esm/lib/editor/managers/Stack.mjs +0 -62
  183. package/dist-esm/lib/editor/managers/Stack.mjs.map +0 -7
  184. package/dist-esm/lib/editor/managers/TextManager.mjs.map +0 -7
  185. package/dist-esm/lib/editor/managers/TickManager.mjs.map +0 -7
  186. package/dist-esm/lib/editor/managers/UserPreferencesManager.mjs.map +0 -7
  187. package/src/lib/editor/managers/ScribbleManager.test.ts +0 -32
  188. package/src/lib/editor/managers/Stack.ts +0 -71
  189. /package/dist-cjs/lib/editor/managers/{FocusManager.js → FocusManager/FocusManager.js} +0 -0
  190. /package/dist-esm/lib/editor/managers/{FocusManager.mjs → FocusManager/FocusManager.mjs} +0 -0
@@ -148,16 +148,16 @@ import { bindingsIndex } from './derivations/bindingsIndex'
148
148
  import { notVisibleShapes } from './derivations/notVisibleShapes'
149
149
  import { parentsToChildren } from './derivations/parentsToChildren'
150
150
  import { deriveShapeIdsInCurrentPage } from './derivations/shapeIdsInCurrentPage'
151
- import { ClickManager } from './managers/ClickManager'
152
- import { EdgeScrollManager } from './managers/EdgeScrollManager'
153
- import { FocusManager } from './managers/FocusManager'
154
- import { FontManager } from './managers/FontManager'
155
- import { HistoryManager } from './managers/HistoryManager'
156
- import { ScribbleManager } from './managers/ScribbleManager'
151
+ import { ClickManager } from './managers/ClickManager/ClickManager'
152
+ import { EdgeScrollManager } from './managers/EdgeScrollManager/EdgeScrollManager'
153
+ import { FocusManager } from './managers/FocusManager/FocusManager'
154
+ import { FontManager } from './managers/FontManager/FontManager'
155
+ import { HistoryManager } from './managers/HistoryManager/HistoryManager'
156
+ import { ScribbleManager } from './managers/ScribbleManager/ScribbleManager'
157
157
  import { SnapManager } from './managers/SnapManager/SnapManager'
158
- import { TextManager } from './managers/TextManager'
159
- import { TickManager } from './managers/TickManager'
160
- import { UserPreferencesManager } from './managers/UserPreferencesManager'
158
+ import { TextManager } from './managers/TextManager/TextManager'
159
+ import { TickManager } from './managers/TickManager/TickManager'
160
+ import { UserPreferencesManager } from './managers/UserPreferencesManager/UserPreferencesManager'
161
161
  import { ShapeUtil, TLGeometryOpts, TLResizeMode } from './shapes/ShapeUtil'
162
162
  import { RootState } from './tools/RootState'
163
163
  import { StateNode, TLStateNodeConstructor } from './tools/StateNode'
@@ -328,7 +328,7 @@ export class Editor extends EventEmitter<TLEventMap> {
328
328
  this.store = store
329
329
  this.history = new HistoryManager<TLRecord>({
330
330
  store,
331
- annotateError: (error) => {
331
+ annotateError: (error: any) => {
332
332
  this.annotateError(error, { origin: 'history.batch', willCrashApp: true })
333
333
  this.crash(error)
334
334
  },
@@ -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,
@@ -3717,10 +3735,7 @@ export class Editor extends EventEmitter<TLEventMap> {
3717
3735
  */
3718
3736
  @computed getViewportScreenCenter() {
3719
3737
  const viewportScreenBounds = this.getViewportScreenBounds()
3720
- return new Vec(
3721
- viewportScreenBounds.midX - viewportScreenBounds.minX,
3722
- viewportScreenBounds.midY - viewportScreenBounds.minY
3723
- )
3738
+ return new Vec(viewportScreenBounds.w / 2, viewportScreenBounds.h / 2)
3724
3739
  }
3725
3740
 
3726
3741
  /**
@@ -4646,44 +4661,6 @@ export class Editor extends EventEmitter<TLEventMap> {
4646
4661
  )! as T
4647
4662
  }
4648
4663
 
4649
- private _shapePageGeometryCaches: Record<string, ComputedCache<Geometry2d, TLShape>> = {}
4650
-
4651
- /**
4652
- * Get the geometry of a shape in page-space.
4653
- *
4654
- * @example
4655
- * ```ts
4656
- * editor.getShapePageGeometry(myShape)
4657
- * editor.getShapePageGeometry(myShapeId)
4658
- * editor.getShapePageGeometry(myShapeId, { context: "arrow" })
4659
- * ```
4660
- *
4661
- * @param shape - The shape (or shape id) to get the geometry for.
4662
- * @param opts - Additional options about the request for geometry. Passed to {@link ShapeUtil.getGeometry}.
4663
- *
4664
- * @public
4665
- */
4666
- getShapePageGeometry<T extends Geometry2d>(shape: TLShape | TLShapeId, opts?: TLGeometryOpts): T {
4667
- const context = opts?.context ?? 'none'
4668
- if (!this._shapePageGeometryCaches[context]) {
4669
- this._shapePageGeometryCaches[context] = this.store.createComputedCache(
4670
- 'bounds',
4671
- (shape) => {
4672
- const geometry = this.getShapeGeometry(shape.id, opts)
4673
- const pageTransform = this.getShapePageTransform(shape.id)
4674
- return geometry.transform(pageTransform)
4675
- },
4676
- {
4677
- // we only depend directly on the shape id, and changing geometry/transform will update us anyway
4678
- areRecordsEqual: () => true,
4679
- }
4680
- )
4681
- }
4682
- return this._shapePageGeometryCaches[context].get(
4683
- typeof shape === 'string' ? shape : shape.id
4684
- )! as T
4685
- }
4686
-
4687
4664
  /** @internal */
4688
4665
  @computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
4689
4666
  return this.store.createComputedCache(
@@ -4796,7 +4773,10 @@ export class Editor extends EventEmitter<TLEventMap> {
4796
4773
  /** @internal */
4797
4774
  @computed private _getShapePageBoundsCache(): ComputedCache<Box, TLShape> {
4798
4775
  return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
4799
- return this.getShapePageGeometry(shape).bounds
4776
+ const pageTransform = this.getShapePageTransform(shape)
4777
+ if (!pageTransform) return undefined
4778
+ const geometry = this.getShapeGeometry(shape)
4779
+ return Box.FromPoints(pageTransform.applyToPoints(geometry.vertices))
4800
4780
  })
4801
4781
  }
4802
4782
 
@@ -4870,11 +4850,12 @@ export class Editor extends EventEmitter<TLEventMap> {
4870
4850
  if (frameAncestors.length === 0) return undefined
4871
4851
 
4872
4852
  const pageMask = frameAncestors
4873
- .map<Vec[] | undefined>(
4874
- (s) =>
4875
- // Apply the frame transform to the frame outline to get the frame outline in the current page space
4876
- this.getShapePageGeometry(s.id).vertices
4877
- )
4853
+ .map<Vec[] | undefined>((s) => {
4854
+ // Apply the frame transform to the frame outline to get the frame outline in the current page space
4855
+ const geometry = this.getShapeGeometry(s.id)
4856
+ const pageTransform = this.getShapePageTransform(s.id)
4857
+ return pageTransform.applyToPoints(geometry.vertices)
4858
+ })
4878
4859
  .reduce((acc, b) => {
4879
4860
  if (!(b && acc)) return undefined
4880
4861
  const intersection = intersectPolygonPolygon(acc, b)
@@ -5072,28 +5053,33 @@ export class Editor extends EventEmitter<TLEventMap> {
5072
5053
  *
5073
5054
  * @public
5074
5055
  */
5075
- isShapeOrAncestorLocked(shape?: TLShape): boolean
5076
- isShapeOrAncestorLocked(id?: TLShapeId): boolean
5077
- isShapeOrAncestorLocked(arg?: TLShape | TLShapeId): boolean {
5078
- const shape = typeof arg === 'string' ? this.getShape(arg) : arg
5079
- if (shape === undefined) return false
5080
- if (shape.isLocked) return true
5081
- 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))
5082
5061
  }
5083
5062
 
5063
+ /**
5064
+ * Get shapes that are outside of the viewport.
5065
+ *
5066
+ * @public
5067
+ */
5084
5068
  @computed
5085
- private _notVisibleShapes() {
5086
- return notVisibleShapes(this)
5069
+ getNotVisibleShapes() {
5070
+ return this._notVisibleShapes.get()
5087
5071
  }
5088
5072
 
5073
+ private _notVisibleShapes = notVisibleShapes(this)
5074
+
5089
5075
  /**
5090
- * Get culled shapes.
5076
+ * Get culled shapes (those that should not render), taking into account which shapes are selected or editing.
5091
5077
  *
5092
5078
  * @public
5093
5079
  */
5094
5080
  @computed
5095
5081
  getCulledShapes() {
5096
- const notVisibleShapes = this._notVisibleShapes().get()
5082
+ const notVisibleShapes = this.getNotVisibleShapes()
5097
5083
  const selectedShapeIds = this.getSelectedShapeIds()
5098
5084
  const editingId = this.getEditingShapeId()
5099
5085
  const culledShapes = new Set<TLShapeId>(notVisibleShapes)
@@ -5342,21 +5328,23 @@ export class Editor extends EventEmitter<TLEventMap> {
5342
5328
  * @example
5343
5329
  * ```ts
5344
5330
  * editor.getShapesAtPoint({ x: 100, y: 100 })
5345
- * editor.getShapesAtPoint({ x: 100, y: 100 }, { hitInside: true, exact: true })
5331
+ * editor.getShapesAtPoint({ x: 100, y: 100 }, { hitInside: true, margin: 8 })
5346
5332
  * ```
5347
5333
  *
5348
5334
  * @param point - The page point to test.
5349
5335
  * @param opts - The options for the hit point testing.
5350
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
+ *
5351
5339
  * @public
5352
5340
  */
5353
5341
  getShapesAtPoint(
5354
5342
  point: VecLike,
5355
5343
  opts = {} as { margin?: number; hitInside?: boolean }
5356
5344
  ): TLShape[] {
5357
- return this.getCurrentPageShapes().filter(
5358
- (shape) => !this.isShapeHidden(shape) && this.isPointInShape(shape, point, opts)
5359
- )
5345
+ return this.getCurrentPageShapesSorted()
5346
+ .filter((shape) => !this.isShapeHidden(shape) && this.isPointInShape(shape, point, opts))
5347
+ .reverse()
5360
5348
  }
5361
5349
 
5362
5350
  /**
@@ -5540,7 +5528,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5540
5528
  if (!id) return undefined
5541
5529
  const freshShape = this.getShape(id)
5542
5530
  if (freshShape === undefined || !isShapeId(freshShape.parentId)) return undefined
5543
- return this.store.get(freshShape.parentId)
5531
+ return this.getShape(freshShape.parentId)
5544
5532
  }
5545
5533
 
5546
5534
  /**
@@ -5723,6 +5711,10 @@ export class Editor extends EventEmitter<TLEventMap> {
5723
5711
  const newPoint = invertedParentTransform.applyToPoint(pagePoint)
5724
5712
  const newRotation = pageTransform.rotation() - parentPageRotation
5725
5713
 
5714
+ if (shape.id === parentId) {
5715
+ throw Error('Attempted to reparent a shape to itself!')
5716
+ }
5717
+
5726
5718
  changes.push({
5727
5719
  id: shape.id,
5728
5720
  type: shape.type,
@@ -5826,6 +5818,11 @@ export class Editor extends EventEmitter<TLEventMap> {
5826
5818
  return shapeIds
5827
5819
  }
5828
5820
 
5821
+ /** @deprecated Use {@link Editor.getDraggingOverShape} instead */
5822
+ getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5823
+ return this.getDraggingOverShape(point, droppingShapes)
5824
+ }
5825
+
5829
5826
  /**
5830
5827
  * Get the shape that some shapes should be dropped on at a given point.
5831
5828
  *
@@ -5836,35 +5833,33 @@ export class Editor extends EventEmitter<TLEventMap> {
5836
5833
  *
5837
5834
  * @public
5838
5835
  */
5839
- getDroppingOverShape(point: VecLike, droppingShapes: TLShape[] = []) {
5840
- // starting from the top...
5841
- const currentPageShapesSorted = this.getCurrentPageShapesSorted()
5842
- for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
5843
- const shape = currentPageShapesSorted[i]
5844
-
5845
- if (
5846
- // ignore hidden shapes
5847
- this.isShapeHidden(shape) ||
5848
- // don't allow dropping on selected shapes
5849
- this.getSelectedShapeIds().includes(shape.id) ||
5850
- // only allow shapes that can receive children
5851
- !this.getShapeUtil(shape).canDropShapes(shape, droppingShapes) ||
5852
- // don't allow dropping a shape on itself or one of it's children
5853
- droppingShapes.find((s) => s.id === shape.id || this.hasAncestor(shape, s.id))
5854
- ) {
5855
- continue
5856
- }
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
+ )
5857
5841
 
5858
- // Only allow dropping into the masked page bounds of the shape, e.g. when a frame is
5859
- // partially clipped by its own parent frame
5860
- 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
+ )
5861
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
5862
5856
  if (
5863
- maskedPageBounds &&
5864
- maskedPageBounds.containsPoint(point) &&
5865
- this.getShapeGeometry(shape).hitTestPoint(this.getPointInShapeSpace(shape, point), 0, true)
5857
+ shapeUtil.onDragShapesOver ||
5858
+ shapeUtil.onDragShapesIn ||
5859
+ shapeUtil.onDragShapesOut ||
5860
+ shapeUtil.onDropShapesOver
5866
5861
  ) {
5867
- return shape
5862
+ return maybeDraggingOverShape
5868
5863
  }
5869
5864
  }
5870
5865
  }
@@ -6223,11 +6218,12 @@ export class Editor extends EventEmitter<TLEventMap> {
6223
6218
  */
6224
6219
  duplicateShapes(shapes: TLShapeId[] | TLShape[], offset?: VecLike): this {
6225
6220
  this.run(() => {
6226
- const ids =
6221
+ const _ids =
6227
6222
  typeof shapes[0] === 'string'
6228
6223
  ? (shapes as TLShapeId[])
6229
6224
  : (shapes as TLShape[]).map((s) => s.id)
6230
6225
 
6226
+ const ids = this._shouldIgnoreShapeLock ? _ids : this._getUnlockedShapeIds(_ids)
6231
6227
  if (ids.length <= 0) return this
6232
6228
 
6233
6229
  const initialIds = new Set(ids)
@@ -6307,10 +6303,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6307
6303
  })
6308
6304
  const shapesToCreate = shapesToCreateWithOriginals.map(({ shape }) => shape)
6309
6305
 
6310
- const maxShapesReached =
6311
- shapesToCreate.length + this.getCurrentPageShapeIds().size > this.options.maxShapesPerPage
6312
-
6313
- if (maxShapesReached) {
6306
+ if (!this.canCreateShapes(shapesToCreate)) {
6314
6307
  alertMaxShapes(this)
6315
6308
  return
6316
6309
  }
@@ -7739,6 +7732,32 @@ export class Editor extends EventEmitter<TLEventMap> {
7739
7732
  return {}
7740
7733
  }
7741
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
+
7742
7761
  /**
7743
7762
  * Create a single shape.
7744
7763
  *
@@ -7785,6 +7804,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7785
7804
  if (maxShapesReached) {
7786
7805
  // can't create more shapes than fit on the page
7787
7806
  alertMaxShapes(this)
7807
+ // todo: throw an error here? Otherwise we'll need to check every time whether the shapes were actually created
7788
7808
  return this
7789
7809
  }
7790
7810
 
@@ -7817,9 +7837,10 @@ export class Editor extends EventEmitter<TLEventMap> {
7817
7837
 
7818
7838
  for (let i = currentPageShapesSorted.length - 1; i >= 0; i--) {
7819
7839
  const parent = currentPageShapesSorted[i]
7840
+ const util = this.getShapeUtil(parent)
7820
7841
  if (
7842
+ util.canReceiveNewChildrenOfType(parent, partial.type) &&
7821
7843
  !this.isShapeHidden(parent) &&
7822
- this.getShapeUtil(parent).canReceiveNewChildrenOfType(parent, partial.type) &&
7823
7844
  this.isPointInShape(
7824
7845
  parent,
7825
7846
  // If no parent is provided, then we can treat the
@@ -7838,7 +7859,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7838
7859
 
7839
7860
  const prevParentId = partial.parentId
7840
7861
 
7841
- // 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.
7842
7863
  if (parentId === partial.id) {
7843
7864
  parentId = focusedGroupId
7844
7865
  }
@@ -7948,6 +7969,8 @@ export class Editor extends EventEmitter<TLEventMap> {
7948
7969
  }
7949
7970
  })
7950
7971
 
7972
+ this.emit('created-shapes', shapeRecordsToCreate)
7973
+ this.emit('edit')
7951
7974
  this.store.put(shapeRecordsToCreate)
7952
7975
  })
7953
7976
 
@@ -8342,6 +8365,8 @@ export class Editor extends EventEmitter<TLEventMap> {
8342
8365
  updates.push(updated)
8343
8366
  }
8344
8367
 
8368
+ this.emit('edited-shapes', updates)
8369
+ this.emit('edit')
8345
8370
  this.store.put(updates)
8346
8371
  })
8347
8372
  }
@@ -8391,6 +8416,8 @@ export class Editor extends EventEmitter<TLEventMap> {
8391
8416
  })
8392
8417
  }
8393
8418
 
8419
+ this.emit('deleted-shapes', [...allShapeIdsToDelete])
8420
+ this.emit('edit')
8394
8421
  return this.run(() => this.store.remove([...allShapeIdsToDelete]))
8395
8422
  }
8396
8423
 
@@ -8839,6 +8866,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8839
8866
  } = {
8840
8867
  text: null,
8841
8868
  files: null,
8869
+ 'file-replace': null,
8842
8870
  embed: null,
8843
8871
  'svg-text': null,
8844
8872
  url: null,
@@ -8888,6 +8916,15 @@ export class Editor extends EventEmitter<TLEventMap> {
8888
8916
  return this.externalContentHandlers[info.type]?.(info as any)
8889
8917
  }
8890
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
+
8891
8928
  /**
8892
8929
  * Get content that can be exported for the given shape ids.
8893
8930
  *
@@ -9305,6 +9342,7 @@ export class Editor extends EventEmitter<TLEventMap> {
9305
9342
  if (rootShapes.length === 1) {
9306
9343
  const onlyRoot = rootShapes[0] as TLFrameShape
9307
9344
  // If the old bounds are in the viewport...
9345
+ // todo: replace frame references with shapes that can accept children
9308
9346
  if (this.isShapeOfType<TLFrameShape>(onlyRoot, 'frame')) {
9309
9347
  while (
9310
9348
  this.getShapesAtPoint(point).some(
@@ -9506,6 +9544,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9506
9544
  previousPagePoint,
9507
9545
  currentScreenPoint,
9508
9546
  currentPagePoint,
9547
+ originScreenPoint,
9548
+ originPagePoint,
9509
9549
  } = this.inputs
9510
9550
 
9511
9551
  const { screenBounds } = this.store.unsafeGetWithoutCapture(TLINSTANCE_ID)!
@@ -9534,8 +9574,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9534
9574
  // Reset velocity on pointer down, or when a pinch starts or ends
9535
9575
  if (info.name === 'pointer_down' || this.inputs.isPinching) {
9536
9576
  pointerVelocity.set(0, 0)
9537
- this.inputs.originScreenPoint.setTo(currentScreenPoint)
9538
- this.inputs.originPagePoint.setTo(currentPagePoint)
9577
+ originScreenPoint.setTo(currentScreenPoint)
9578
+ originPagePoint.setTo(currentPagePoint)
9539
9579
  }
9540
9580
 
9541
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
  /**
@@ -1,41 +1,42 @@
1
1
  import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'
2
- import { TLBinding, TLShapeId } from '@tldraw/tlschema'
2
+ import { TLArrowBinding, TLBinding, TLShapeId, TLUnknownBinding } from '@tldraw/tlschema'
3
3
  import { objectMapValues } from '@tldraw/utils'
4
4
  import { Editor } from '../Editor'
5
5
 
6
6
  type TLBindingsIndex = Map<TLShapeId, TLBinding[]>
7
7
 
8
- export const bindingsIndex = (editor: Editor): Computed<TLBindingsIndex> => {
9
- const { store } = editor
10
- const bindingsHistory = store.query.filterHistory('binding')
11
- const bindingsQuery = store.query.records('binding')
12
- function fromScratch() {
13
- const allBindings = bindingsQuery.get() as TLBinding[]
8
+ function fromScratch(bindingsQuery: Computed<(TLArrowBinding | TLUnknownBinding)[], unknown>) {
9
+ const allBindings = bindingsQuery.get() as TLBinding[]
14
10
 
15
- const shape2Binding: TLBindingsIndex = new Map()
11
+ const shapesToBindings: TLBindingsIndex = new Map()
16
12
 
17
- for (const binding of allBindings) {
18
- const { fromId, toId } = binding
19
- const bindingsForFromShape = shape2Binding.get(fromId)
20
- if (!bindingsForFromShape) {
21
- shape2Binding.set(fromId, [binding])
22
- } else {
23
- bindingsForFromShape.push(binding)
24
- }
25
- const bindingsForToShape = shape2Binding.get(toId)
26
- if (!bindingsForToShape) {
27
- shape2Binding.set(toId, [binding])
28
- } else {
29
- bindingsForToShape.push(binding)
30
- }
13
+ for (const binding of allBindings) {
14
+ const { fromId, toId } = binding
15
+ const bindingsForFromShape = shapesToBindings.get(fromId)
16
+ if (!bindingsForFromShape) {
17
+ shapesToBindings.set(fromId, [binding])
18
+ } else {
19
+ bindingsForFromShape.push(binding)
20
+ }
21
+ const bindingsForToShape = shapesToBindings.get(toId)
22
+ if (!bindingsForToShape) {
23
+ shapesToBindings.set(toId, [binding])
24
+ } else {
25
+ bindingsForToShape.push(binding)
31
26
  }
32
-
33
- return shape2Binding
34
27
  }
35
28
 
29
+ return shapesToBindings
30
+ }
31
+
32
+ export const bindingsIndex = (editor: Editor): Computed<TLBindingsIndex> => {
33
+ const { store } = editor
34
+ const bindingsHistory = store.query.filterHistory('binding')
35
+ const bindingsQuery = store.query.records('binding')
36
+
36
37
  return computed<TLBindingsIndex>('arrowBindingsIndex', (_lastValue, lastComputedEpoch) => {
37
38
  if (isUninitialized(_lastValue)) {
38
- return fromScratch()
39
+ return fromScratch(bindingsQuery)
39
40
  }
40
41
 
41
42
  const lastValue = _lastValue
@@ -43,7 +44,7 @@ export const bindingsIndex = (editor: Editor): Computed<TLBindingsIndex> => {
43
44
  const diff = bindingsHistory.getDiffSince(lastComputedEpoch)
44
45
 
45
46
  if (diff === RESET_VALUE) {
46
- return fromScratch()
47
+ return fromScratch(bindingsQuery)
47
48
  }
48
49
 
49
50
  let nextValue: TLBindingsIndex | undefined = undefined