@tldraw/editor 4.3.0-canary.da35795ba8e2 → 4.3.0-canary.e1766dd4eab3

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 (89) hide show
  1. package/dist-cjs/index.d.ts +111 -37
  2. package/dist-cjs/index.js +2 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +3 -3
  5. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  6. package/dist-cjs/lib/editor/Editor.js +47 -15
  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.map +2 -2
  10. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js.map +2 -2
  11. package/dist-cjs/lib/editor/shapes/BaseBoxShapeUtil.js.map +1 -1
  12. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  13. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js +1 -1
  14. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js.map +2 -2
  15. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  16. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.js.map +2 -2
  17. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  18. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  19. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  20. package/dist-cjs/lib/globals/environment.js +45 -9
  21. package/dist-cjs/lib/globals/environment.js.map +2 -2
  22. package/dist-cjs/lib/globals/menus.js +1 -1
  23. package/dist-cjs/lib/globals/menus.js.map +2 -2
  24. package/dist-cjs/lib/hooks/useCoarsePointer.js +14 -29
  25. package/dist-cjs/lib/hooks/useCoarsePointer.js.map +2 -2
  26. package/dist-cjs/lib/hooks/useZoomCss.js +4 -8
  27. package/dist-cjs/lib/hooks/useZoomCss.js.map +2 -2
  28. package/dist-cjs/lib/options.js +3 -1
  29. package/dist-cjs/lib/options.js.map +2 -2
  30. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  31. package/dist-cjs/version.js +3 -3
  32. package/dist-cjs/version.js.map +1 -1
  33. package/dist-esm/index.d.mts +111 -37
  34. package/dist-esm/index.mjs +3 -2
  35. package/dist-esm/index.mjs.map +2 -2
  36. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +3 -3
  37. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  38. package/dist-esm/lib/editor/Editor.mjs +47 -15
  39. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  40. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  41. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  42. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +2 -2
  43. package/dist-esm/lib/editor/shapes/BaseBoxShapeUtil.mjs.map +1 -1
  44. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  45. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs +1 -1
  46. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs.map +2 -2
  47. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  48. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.mjs.map +2 -2
  49. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  50. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  51. package/dist-esm/lib/globals/environment.mjs +45 -9
  52. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  53. package/dist-esm/lib/globals/menus.mjs +1 -1
  54. package/dist-esm/lib/globals/menus.mjs.map +2 -2
  55. package/dist-esm/lib/hooks/useCoarsePointer.mjs +15 -30
  56. package/dist-esm/lib/hooks/useCoarsePointer.mjs.map +2 -2
  57. package/dist-esm/lib/hooks/useZoomCss.mjs +4 -8
  58. package/dist-esm/lib/hooks/useZoomCss.mjs.map +2 -2
  59. package/dist-esm/lib/options.mjs +3 -1
  60. package/dist-esm/lib/options.mjs.map +2 -2
  61. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  62. package/dist-esm/version.mjs +3 -3
  63. package/dist-esm/version.mjs.map +1 -1
  64. package/editor.css +8 -4
  65. package/package.json +10 -10
  66. package/src/index.ts +1 -1
  67. package/src/lib/components/default-components/DefaultCanvas.tsx +4 -3
  68. package/src/lib/editor/Editor.test.ts +10 -10
  69. package/src/lib/editor/Editor.ts +159 -63
  70. package/src/lib/editor/bindings/BindingUtil.ts +15 -9
  71. package/src/lib/editor/derivations/bindingsIndex.ts +2 -2
  72. package/src/lib/editor/managers/FontManager/FontManager.test.ts +14 -4
  73. package/src/lib/editor/managers/SnapManager/SnapManager.ts +3 -3
  74. package/src/lib/editor/shapes/BaseBoxShapeUtil.tsx +2 -2
  75. package/src/lib/editor/shapes/ShapeUtil.ts +5 -8
  76. package/src/lib/editor/shapes/group/DashedOutlineBox.tsx +1 -1
  77. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -3
  78. package/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts +2 -1
  79. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +3 -3
  80. package/src/lib/editor/types/emit-types.ts +3 -1
  81. package/src/lib/exports/getSvgJsx.test.ts +10 -19
  82. package/src/lib/exports/getSvgJsx.tsx +2 -5
  83. package/src/lib/globals/environment.ts +65 -10
  84. package/src/lib/globals/menus.ts +1 -1
  85. package/src/lib/hooks/useCoarsePointer.ts +16 -59
  86. package/src/lib/hooks/useZoomCss.ts +3 -8
  87. package/src/lib/options.ts +13 -0
  88. package/src/lib/utils/reparenting.ts +5 -5
  89. package/src/version.ts +3 -3
@@ -21,7 +21,6 @@ import {
21
21
  PageRecordType,
22
22
  StyleProp,
23
23
  StylePropValue,
24
- TLArrowShape,
25
24
  TLAsset,
26
25
  TLAssetId,
27
26
  TLAssetPartial,
@@ -30,12 +29,12 @@ import {
30
29
  TLBindingId,
31
30
  TLBindingUpdate,
32
31
  TLCamera,
32
+ TLCreateShapePartial,
33
33
  TLCursor,
34
34
  TLCursorType,
35
35
  TLDOCUMENT_ID,
36
36
  TLDocument,
37
37
  TLFrameShape,
38
- TLGeoShape,
39
38
  TLGroupShape,
40
39
  TLHandle,
41
40
  TLINSTANCE_ID,
@@ -43,7 +42,6 @@ import {
43
42
  TLInstance,
44
43
  TLInstancePageState,
45
44
  TLInstancePresence,
46
- TLNoteShape,
47
45
  TLPOINTER_ID,
48
46
  TLPage,
49
47
  TLPageId,
@@ -54,8 +52,6 @@ import {
54
52
  TLShapePartial,
55
53
  TLStore,
56
54
  TLStoreSnapshot,
57
- TLUnknownBinding,
58
- TLUnknownShape,
59
55
  TLVideoAsset,
60
56
  createBindingId,
61
57
  createShapeId,
@@ -447,7 +443,7 @@ export class Editor extends EventEmitter<TLEventMap> {
447
443
  let deletedBindings = new Map<TLBindingId, BindingOnDeleteOptions<any>>()
448
444
  const deletedShapeIds = new Set<TLShapeId>()
449
445
  const invalidParents = new Set<TLShapeId>()
450
- let invalidBindingTypes = new Set<string>()
446
+ let invalidBindingTypes = new Set<TLBinding['type']>()
451
447
  this.disposables.add(
452
448
  this.sideEffects.registerOperationCompleteHandler(() => {
453
449
  // this needs to be cleared here because further effects may delete more shapes
@@ -710,7 +706,7 @@ export class Editor extends EventEmitter<TLEventMap> {
710
706
  if (filtered.length > 0) {
711
707
  const commonGroupAncestor = this.findCommonAncestor(
712
708
  compact(filtered.map((id) => this.getShape(id))),
713
- (shape) => this.isShapeOfType<TLGroupShape>(shape, 'group')
709
+ (shape) => this.isShapeOfType(shape, 'group')
714
710
  )
715
711
 
716
712
  if (commonGroupAncestor) {
@@ -973,6 +969,7 @@ export class Editor extends EventEmitter<TLEventMap> {
973
969
  this.disposables.clear()
974
970
  this.store.dispose()
975
971
  this.isDisposed = true
972
+ this.emit('dispose')
976
973
  }
977
974
 
978
975
  /* ------------------- Shape Utils ------------------ */
@@ -982,7 +979,7 @@ export class Editor extends EventEmitter<TLEventMap> {
982
979
  *
983
980
  * @public
984
981
  */
985
- shapeUtils: { readonly [K in string]?: ShapeUtil<TLUnknownShape> }
982
+ shapeUtils: { readonly [K in string]?: ShapeUtil<TLShape> }
986
983
 
987
984
  styleProps: { [key: string]: Map<StyleProp<any>, string> }
988
985
 
@@ -1001,8 +998,8 @@ export class Editor extends EventEmitter<TLEventMap> {
1001
998
  *
1002
999
  * @public
1003
1000
  */
1004
- getShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): ShapeUtil<S>
1005
- getShapeUtil<S extends TLUnknownShape>(type: S['type']): ShapeUtil<S>
1001
+ getShapeUtil<K extends TLShape['type']>(type: K): ShapeUtil<Extract<TLShape, { type: K }>>
1002
+ getShapeUtil<S extends TLShape>(shape: S | TLShapePartial<S> | S['type']): ShapeUtil<S>
1006
1003
  getShapeUtil<T extends ShapeUtil>(type: T extends ShapeUtil<infer R> ? R['type'] : string): T
1007
1004
  getShapeUtil(arg: string | { type: string }) {
1008
1005
  const type = typeof arg === 'string' ? arg : arg.type
@@ -1016,8 +1013,8 @@ export class Editor extends EventEmitter<TLEventMap> {
1016
1013
  *
1017
1014
  * @param shape - A shape, shape partial, or shape type.
1018
1015
  */
1019
- hasShapeUtil<S extends TLUnknownShape>(shape: S | TLShapePartial<S>): boolean
1020
- hasShapeUtil<S extends TLUnknownShape>(type: S['type']): boolean
1016
+ hasShapeUtil(shape: TLShape | TLShapePartial<TLShape>): boolean
1017
+ hasShapeUtil(type: TLShape['type']): boolean
1021
1018
  hasShapeUtil<T extends ShapeUtil>(
1022
1019
  type: T extends ShapeUtil<infer R> ? R['type'] : string
1023
1020
  ): boolean
@@ -1032,7 +1029,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1032
1029
  *
1033
1030
  * @public
1034
1031
  */
1035
- bindingUtils: { readonly [K in string]?: BindingUtil<TLUnknownBinding> }
1032
+ bindingUtils: { readonly [K in string]?: BindingUtil<TLBinding> }
1036
1033
 
1037
1034
  /**
1038
1035
  * Get a binding util from a binding itself.
@@ -1049,8 +1046,8 @@ export class Editor extends EventEmitter<TLEventMap> {
1049
1046
  *
1050
1047
  * @public
1051
1048
  */
1052
- getBindingUtil<S extends TLUnknownBinding>(binding: S | { type: S['type'] }): BindingUtil<S>
1053
- getBindingUtil<S extends TLUnknownBinding>(type: S['type']): BindingUtil<S>
1049
+ getBindingUtil<K extends TLBinding['type']>(type: K): BindingUtil<Extract<TLBinding, { type: K }>>
1050
+ getBindingUtil<S extends TLBinding>(binding: S | { type: S['type'] }): BindingUtil<S>
1054
1051
  getBindingUtil<T extends BindingUtil>(
1055
1052
  type: T extends BindingUtil<infer R> ? R['type'] : string
1056
1053
  ): T
@@ -2220,7 +2217,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2220
2217
  throw Error(`Editor.setFocusedGroup: Shape with id ${id} does not exist`)
2221
2218
  }
2222
2219
 
2223
- if (!this.isShapeOfType<TLGroupShape>(shape, 'group')) {
2220
+ if (!this.isShapeOfType(shape, 'group')) {
2224
2221
  throw Error(
2225
2222
  `Editor.setFocusedGroup: Cannot set focused group to shape of type ${shape.type}`
2226
2223
  )
@@ -2248,7 +2245,7 @@ export class Editor extends EventEmitter<TLEventMap> {
2248
2245
  if (focusedGroup) {
2249
2246
  // If we have a focused layer, look for an ancestor of the focused shape that is a group
2250
2247
  const match = this.findShapeAncestor(focusedGroup, (shape) =>
2251
- this.isShapeOfType<TLGroupShape>(shape, 'group')
2248
+ this.isShapeOfType(shape, 'group')
2252
2249
  )
2253
2250
  // If we have an ancestor that can become a focused layer, set it as the focused layer
2254
2251
  this.setFocusedGroup(match?.id ?? null)
@@ -2673,6 +2670,52 @@ export class Editor extends EventEmitter<TLEventMap> {
2673
2670
  return this.getCamera().z
2674
2671
  }
2675
2672
 
2673
+ private _debouncedZoomLevel = atom('debounced zoom level', 1)
2674
+
2675
+ /**
2676
+ * Get the debounced zoom level. When the camera is moving, this returns the zoom level
2677
+ * from when the camera started moving rather than the current zoom level. This can be
2678
+ * used to avoid expensive re-renders during camera movements.
2679
+ *
2680
+ * This behavior is controlled by the `useDebouncedZoom` option. When `useDebouncedZoom`
2681
+ * is `false`, this method always returns the current zoom level.
2682
+ *
2683
+ * @public
2684
+ */
2685
+ @computed getDebouncedZoomLevel() {
2686
+ if (this.options.debouncedZoom) {
2687
+ if (this.getCameraState() === 'idle') {
2688
+ return this.getZoomLevel()
2689
+ } else {
2690
+ return this._debouncedZoomLevel.get()
2691
+ }
2692
+ }
2693
+
2694
+ return this.getZoomLevel()
2695
+ }
2696
+
2697
+ @computed private _getAboveDebouncedZoomThreshold() {
2698
+ return this.getCurrentPageShapeIds().size > this.options.debouncedZoomThreshold
2699
+ }
2700
+
2701
+ /**
2702
+ * Get the efficient zoom level. This returns the current zoom level if there are less than 300 shapes on the page,
2703
+ * otherwise it returns the debounced zoom level. This can be used to avoid expensive re-renders during camera movements.
2704
+ *
2705
+ * @public
2706
+ * @example
2707
+ * ```ts
2708
+ * editor.getEfficientZoomLevel()
2709
+ * ```
2710
+ *
2711
+ * @public
2712
+ */
2713
+ @computed getEfficientZoomLevel() {
2714
+ return this._getAboveDebouncedZoomThreshold()
2715
+ ? this.getDebouncedZoomLevel()
2716
+ : this.getZoomLevel()
2717
+ }
2718
+
2676
2719
  /**
2677
2720
  * Get the camera's initial or reset zoom level.
2678
2721
  *
@@ -3635,22 +3678,23 @@ export class Editor extends EventEmitter<TLEventMap> {
3635
3678
  if (_willSetInitialBounds) {
3636
3679
  // If we have just received the initial bounds, don't center the camera.
3637
3680
  this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
3681
+ this.emit('resize', screenBounds.toJson())
3638
3682
  this.setCamera(this.getCamera())
3639
3683
  } else {
3640
3684
  if (center && !this.getInstanceState().followingUserId) {
3641
3685
  // Get the page center before the change, make the change, and restore it
3642
3686
  const before = this.getViewportPageBounds().center
3643
3687
  this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
3688
+ this.emit('resize', screenBounds.toJson())
3644
3689
  this.centerOnPoint(before)
3645
3690
  } else {
3646
3691
  // Otherwise,
3647
3692
  this.updateInstanceState({ screenBounds: screenBounds.toJson(), insets })
3693
+ this.emit('resize', screenBounds.toJson())
3648
3694
  this._setCamera(Vec.From({ ...this.getCamera() }))
3649
3695
  }
3650
3696
  }
3651
3697
 
3652
- this._tickCameraState()
3653
-
3654
3698
  return this
3655
3699
  }
3656
3700
 
@@ -4056,18 +4100,19 @@ export class Editor extends EventEmitter<TLEventMap> {
4056
4100
  // box just for rendering, and we only update after the camera stops moving.
4057
4101
  private _cameraState = atom('camera state', 'idle' as 'idle' | 'moving')
4058
4102
  private _cameraStateTimeoutRemaining = 0
4059
- _decayCameraStateTimeout(elapsed: number) {
4103
+ private _decayCameraStateTimeout(elapsed: number) {
4060
4104
  this._cameraStateTimeoutRemaining -= elapsed
4061
4105
  if (this._cameraStateTimeoutRemaining > 0) return
4062
4106
  this.off('tick', this._decayCameraStateTimeout)
4063
4107
  this._cameraState.set('idle')
4064
4108
  }
4065
- _tickCameraState() {
4109
+ private _tickCameraState() {
4066
4110
  // always reset the timeout
4067
4111
  this._cameraStateTimeoutRemaining = this.options.cameraMovingTimeoutMs
4068
4112
  // If the state is idle, then start the tick
4069
4113
  if (this._cameraState.__unsafe__getWithoutCapture() !== 'idle') return
4070
4114
  this._cameraState.set('moving')
4115
+ this._debouncedZoomLevel.set(unsafe__withoutCapture(() => this.getCamera().z))
4071
4116
  this.on('tick', this._decayCameraStateTimeout)
4072
4117
  }
4073
4118
 
@@ -5127,10 +5172,10 @@ export class Editor extends EventEmitter<TLEventMap> {
5127
5172
 
5128
5173
  // Check labels first
5129
5174
  if (
5130
- this.isShapeOfType<TLFrameShape>(shape, 'frame') ||
5131
- ((this.isShapeOfType<TLNoteShape>(shape, 'note') ||
5132
- this.isShapeOfType<TLArrowShape>(shape, 'arrow') ||
5133
- (this.isShapeOfType<TLGeoShape>(shape, 'geo') && shape.props.fill === 'none')) &&
5175
+ this.isShapeOfType(shape, 'frame') ||
5176
+ ((this.isShapeOfType(shape, 'note') ||
5177
+ this.isShapeOfType(shape, 'arrow') ||
5178
+ (this.isShapeOfType(shape, 'geo') && shape.props.fill === 'none')) &&
5134
5179
  this.getShapeUtil(shape).getText(shape)?.trim())
5135
5180
  ) {
5136
5181
  for (const childGeometry of (geometry as Group2d).children) {
@@ -5140,7 +5185,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5140
5185
  }
5141
5186
  }
5142
5187
 
5143
- if (this.isShapeOfType<TLFrameShape>(shape, 'frame')) {
5188
+ if (this.isShapeOfType(shape, 'frame')) {
5144
5189
  // On the rare case that we've hit a frame (not its label), test again hitInside to be forced true;
5145
5190
  // this prevents clicks from passing through the body of a frame to shapes behind it.
5146
5191
 
@@ -5421,7 +5466,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5421
5466
  *
5422
5467
  * @example
5423
5468
  * ```ts
5424
- * const isArrowShape = isShapeOfType<TLArrowShape>(someShape, 'arrow')
5469
+ * const isArrowShape = isShapeOfType(someShape, 'arrow')
5425
5470
  * ```
5426
5471
  *
5427
5472
  * @param util - the TLShapeUtil constructor to test against
@@ -5429,15 +5474,16 @@ export class Editor extends EventEmitter<TLEventMap> {
5429
5474
  *
5430
5475
  * @public
5431
5476
  */
5432
- isShapeOfType<T extends TLUnknownShape>(shape: TLUnknownShape, type: T['type']): shape is T
5433
- isShapeOfType<T extends TLUnknownShape>(
5434
- shapeId: TLUnknownShape['id'],
5435
- type: T['type']
5436
- ): shapeId is T['id']
5437
- isShapeOfType<T extends TLUnknownShape>(
5438
- arg: TLUnknownShape | TLUnknownShape['id'],
5477
+ isShapeOfType<K extends TLShape['type']>(
5478
+ shape: TLShape,
5479
+ type: K
5480
+ ): shape is Extract<TLShape, { type: K }>
5481
+ isShapeOfType<T extends TLShape>(
5482
+ shape: TLShape,
5439
5483
  type: T['type']
5440
- ) {
5484
+ ): shape is Extract<TLShape, { type: T['type'] }>
5485
+ isShapeOfType<T extends TLShape = TLShape>(shapeId: TLShapeId, type: T['type']): boolean
5486
+ isShapeOfType(arg: TLShape | TLShapeId, type: TLShape['type']) {
5441
5487
  const shape = typeof arg === 'string' ? this.getShape(arg) : arg
5442
5488
  if (!shape) return false
5443
5489
  return shape.type === type
@@ -5833,7 +5879,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5833
5879
 
5834
5880
  while (node) {
5835
5881
  if (
5836
- this.isShapeOfType<TLGroupShape>(node, 'group') &&
5882
+ this.isShapeOfType(node, 'group') &&
5837
5883
  focusedGroup?.id !== node.id &&
5838
5884
  !this.hasAncestor(focusedGroup, node.id) &&
5839
5885
  (filter?.(node) ?? true)
@@ -5875,7 +5921,15 @@ export class Editor extends EventEmitter<TLEventMap> {
5875
5921
  * Get all bindings of a certain type _from_ a particular shape. These are the bindings whose
5876
5922
  * `fromId` matched the shape's ID.
5877
5923
  */
5878
- getBindingsFromShape<Binding extends TLUnknownBinding = TLBinding>(
5924
+ getBindingsFromShape<K extends TLBinding['type']>(
5925
+ shape: TLShape | TLShapeId,
5926
+ type: K
5927
+ ): Extract<TLBinding, { type: K }>[]
5928
+ getBindingsFromShape<Binding extends TLBinding = TLBinding>(
5929
+ shape: TLShape | TLShapeId,
5930
+ type: Binding['type']
5931
+ ): Binding[]
5932
+ getBindingsFromShape<Binding extends TLBinding = TLBinding>(
5879
5933
  shape: TLShape | TLShapeId,
5880
5934
  type: Binding['type']
5881
5935
  ): Binding[] {
@@ -5889,7 +5943,15 @@ export class Editor extends EventEmitter<TLEventMap> {
5889
5943
  * Get all bindings of a certain type _to_ a particular shape. These are the bindings whose
5890
5944
  * `toId` matches the shape's ID.
5891
5945
  */
5892
- getBindingsToShape<Binding extends TLUnknownBinding = TLBinding>(
5946
+ getBindingsToShape<K extends TLBinding['type']>(
5947
+ shape: TLShape | TLShapeId,
5948
+ type: K
5949
+ ): Extract<TLBinding, { type: K }>[]
5950
+ getBindingsToShape<Binding extends TLBinding = TLBinding>(
5951
+ shape: TLShape | TLShapeId,
5952
+ type: Binding['type']
5953
+ ): Binding[]
5954
+ getBindingsToShape<Binding extends TLBinding = TLBinding>(
5893
5955
  shape: TLShape | TLShapeId,
5894
5956
  type: Binding['type']
5895
5957
  ): Binding[] {
@@ -5903,7 +5965,15 @@ export class Editor extends EventEmitter<TLEventMap> {
5903
5965
  * Get all bindings involving a particular shape. This includes bindings where the shape is the
5904
5966
  * `fromId` or `toId`. If a type is provided, only bindings of that type are returned.
5905
5967
  */
5906
- getBindingsInvolvingShape<Binding extends TLUnknownBinding = TLBinding>(
5968
+ getBindingsInvolvingShape<K extends TLBinding['type']>(
5969
+ shape: TLShape | TLShapeId,
5970
+ type: K
5971
+ ): Extract<TLBinding, { type: K }>[]
5972
+ getBindingsInvolvingShape<Binding extends TLBinding = TLBinding>(
5973
+ shape: TLShape | TLShapeId,
5974
+ type?: Binding['type']
5975
+ ): Binding[]
5976
+ getBindingsInvolvingShape<Binding extends TLBinding = TLBinding>(
5907
5977
  shape: TLShape | TLShapeId,
5908
5978
  type?: Binding['type']
5909
5979
  ): Binding[] {
@@ -5925,7 +5995,7 @@ export class Editor extends EventEmitter<TLEventMap> {
5925
5995
  if (!fromShape || !toShape) continue
5926
5996
  if (!this.canBindShapes({ fromShape, toShape, binding: partial })) continue
5927
5997
 
5928
- const util = this.getBindingUtil<TLUnknownBinding>(partial.type)
5998
+ const util = this.getBindingUtil(partial.type)
5929
5999
  const defaultProps = util.getDefaultProps()
5930
6000
  const binding = this.store.schema.types.binding.create({
5931
6001
  ...partial,
@@ -6030,7 +6100,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6030
6100
  const toShapeType = typeof toShape === 'string' ? toShape : toShape.type
6031
6101
  const bindingType = typeof binding === 'string' ? binding : binding.type
6032
6102
 
6033
- const canBindOpts = { fromShapeType, toShapeType, bindingType }
6103
+ const canBindOpts = { fromShapeType, toShapeType, bindingType } as const
6034
6104
 
6035
6105
  if (fromShapeType === toShapeType) {
6036
6106
  return this.getShapeUtil(fromShapeType).canBind(canBindOpts)
@@ -6571,7 +6641,7 @@ export class Editor extends EventEmitter<TLEventMap> {
6571
6641
  const shapesToFlipFirstPass = compact(ids.map((id) => this.getShape(id)))
6572
6642
 
6573
6643
  for (const shape of shapesToFlipFirstPass) {
6574
- if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
6644
+ if (this.isShapeOfType(shape, 'group')) {
6575
6645
  const childrenOfGroups = compact(
6576
6646
  this.getSortedChildIdsForParent(shape.id).map((id) => this.getShape(id))
6577
6647
  )
@@ -7692,9 +7762,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7692
7762
  *
7693
7763
  * @public
7694
7764
  */
7695
- canCreateShape<T extends TLUnknownShape>(
7696
- shape: OptionalKeys<TLShapePartial<T>, 'id'> | T['id']
7697
- ): boolean {
7765
+ canCreateShape(shape: OptionalKeys<TLShapePartial<TLShape>, 'id'> | TLShape['id']): boolean {
7698
7766
  return this.canCreateShapes([shape])
7699
7767
  }
7700
7768
 
@@ -7705,8 +7773,8 @@ export class Editor extends EventEmitter<TLEventMap> {
7705
7773
  *
7706
7774
  * @public
7707
7775
  */
7708
- canCreateShapes<T extends TLUnknownShape>(
7709
- shapes: (T['id'] | OptionalKeys<TLShapePartial<T>, 'id'>)[]
7776
+ canCreateShapes(
7777
+ shapes: (TLShape['id'] | OptionalKeys<TLShapePartial<TLShape>, 'id'>)[]
7710
7778
  ): boolean {
7711
7779
  return shapes.length + this.getCurrentPageShapeIds().size <= this.options.maxShapesPerPage
7712
7780
  }
@@ -7724,7 +7792,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7724
7792
  *
7725
7793
  * @public
7726
7794
  */
7727
- createShape<T extends TLUnknownShape>(shape: OptionalKeys<TLShapePartial<T>, 'id'>): this {
7795
+ createShape<TShape extends TLShape>(shape: TLCreateShapePartial<TShape>): this {
7728
7796
  this.createShapes([shape])
7729
7797
  return this
7730
7798
  }
@@ -7742,7 +7810,7 @@ export class Editor extends EventEmitter<TLEventMap> {
7742
7810
  *
7743
7811
  * @public
7744
7812
  */
7745
- createShapes<T extends TLUnknownShape>(shapes: OptionalKeys<TLShapePartial<T>, 'id'>[]): this {
7813
+ createShapes<TShape extends TLShape = TLShape>(shapes: TLCreateShapePartial<TShape>[]): this {
7746
7814
  if (!Array.isArray(shapes)) {
7747
7815
  throw Error('Editor.createShapes: must provide an array of shapes or shape partials')
7748
7816
  }
@@ -8123,7 +8191,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8123
8191
  const highestIndex = shapesWithRootParent[shapesWithRootParent.length - 1]?.index
8124
8192
 
8125
8193
  this.run(() => {
8126
- this.createShapes<TLGroupShape>([
8194
+ this.createShapes([
8127
8195
  {
8128
8196
  id: groupId,
8129
8197
  type: 'group',
@@ -8193,7 +8261,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8193
8261
  const groups: TLGroupShape[] = []
8194
8262
 
8195
8263
  shapesToUngroup.forEach((shape) => {
8196
- if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
8264
+ if (this.isShapeOfType(shape, 'group')) {
8197
8265
  groups.push(shape)
8198
8266
  } else {
8199
8267
  idsToSelect.add(shape.id)
@@ -8239,7 +8307,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8239
8307
  *
8240
8308
  * @public
8241
8309
  */
8242
- updateShape<T extends TLUnknownShape>(partial: TLShapePartial<T> | null | undefined) {
8310
+ updateShape<T extends TLShape = TLShape>(partial: TLShapePartial<T> | null | undefined) {
8243
8311
  this.updateShapes([partial])
8244
8312
  return this
8245
8313
  }
@@ -8256,7 +8324,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8256
8324
  *
8257
8325
  * @public
8258
8326
  */
8259
- updateShapes<T extends TLUnknownShape>(partials: (TLShapePartial<T> | null | undefined)[]) {
8327
+ updateShapes<T extends TLShape>(partials: (TLShapePartial<T> | null | undefined)[]) {
8260
8328
  const compactedPartials: TLShapePartial<T>[] = Array(partials.length)
8261
8329
 
8262
8330
  for (let i = 0, n = partials.length; i < n; i++) {
@@ -8408,7 +8476,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8408
8476
  * @internal
8409
8477
  */
8410
8478
  private _extractSharedStyles(shape: TLShape, sharedStyleMap: SharedStyleMap) {
8411
- if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
8479
+ if (this.isShapeOfType(shape, 'group')) {
8412
8480
  // For groups, ignore the styles of the group shape and instead include the styles of the
8413
8481
  // group's children. These are the shapes that would have their styles changed if the
8414
8482
  // user called `setStyle` on the current selection.
@@ -8528,7 +8596,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8528
8596
  // For groups, ignore the opacity of the group shape and instead include
8529
8597
  // the opacity of the group's children. These are the shapes that would have
8530
8598
  // their opacity changed if the user called `setOpacity` on the current selection.
8531
- if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
8599
+ if (this.isShapeOfType(shape, 'group')) {
8532
8600
  for (const childId of this.getSortedChildIdsForParent(shape.id)) {
8533
8601
  addShape(childId)
8534
8602
  }
@@ -8589,7 +8657,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8589
8657
  // We can have many deep levels of grouped shape
8590
8658
  // Making a recursive function to look through all the levels
8591
8659
  const addShapeById = (shape: TLShape) => {
8592
- if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
8660
+ if (this.isShapeOfType(shape, 'group')) {
8593
8661
  const childIds = this.getSortedChildIdsForParent(shape)
8594
8662
  for (const childId of childIds) {
8595
8663
  addShapeById(this.getShape(childId)!)
@@ -8673,7 +8741,7 @@ export class Editor extends EventEmitter<TLEventMap> {
8673
8741
  // We can have many deep levels of grouped shape
8674
8742
  // Making a recursive function to look through all the levels
8675
8743
  const addShapeById = (shape: TLShape) => {
8676
- if (this.isShapeOfType<TLGroupShape>(shape, 'group')) {
8744
+ if (this.isShapeOfType(shape, 'group')) {
8677
8745
  const childIds = this.getSortedChildIdsForParent(shape.id)
8678
8746
  for (const childId of childIds) {
8679
8747
  addShapeById(this.getShape(childId)!)
@@ -9098,7 +9166,7 @@ export class Editor extends EventEmitter<TLEventMap> {
9098
9166
  for (const shape of this.getSelectedShapes()) {
9099
9167
  if (lowestDepth === 0) break
9100
9168
 
9101
- const isFrame = this.isShapeOfType<TLFrameShape>(shape, 'frame')
9169
+ const isFrame = this.isShapeOfType(shape, 'frame')
9102
9170
  const ancestors = this.getShapeAncestors(shape)
9103
9171
  if (isFrame) ancestors.push(shape)
9104
9172
 
@@ -9126,6 +9194,30 @@ export class Editor extends EventEmitter<TLEventMap> {
9126
9194
  }
9127
9195
  }
9128
9196
 
9197
+ if (point) {
9198
+ const shapesById = new Map<TLShapeId, TLShape>(shapes.map((shape) => [shape.id, shape]))
9199
+ const rootShapesFromContent = compact(rootShapeIds.map((id) => shapesById.get(id)))
9200
+ if (rootShapesFromContent.length > 0) {
9201
+ const targetParent = this.getShapeAtPoint(point, {
9202
+ hitInside: true,
9203
+ hitFrameInside: true,
9204
+ hitLocked: true,
9205
+ filter: (shape) => {
9206
+ const util = this.getShapeUtil(shape)
9207
+ if (!util.canReceiveNewChildrenOfType) return false
9208
+ return rootShapesFromContent.every((rootShape) =>
9209
+ util.canReceiveNewChildrenOfType!(shape, rootShape.type)
9210
+ )
9211
+ },
9212
+ })
9213
+
9214
+ // When pasting at a specific point (e.g. paste-at-cursor) prefer the
9215
+ // parent under the pointer so that we don't keep using the original
9216
+ // selection's parent (which can keep shapes clipped inside frames).
9217
+ pasteParentId = targetParent ? targetParent.id : currentPageId
9218
+ }
9219
+ }
9220
+
9129
9221
  let isDuplicating = false
9130
9222
 
9131
9223
  if (!isPageId(pasteParentId)) {
@@ -9137,8 +9229,8 @@ export class Editor extends EventEmitter<TLEventMap> {
9137
9229
  if (rootShapeIds.length === 1) {
9138
9230
  const rootShape = shapes.find((s) => s.id === rootShapeIds[0])!
9139
9231
  if (
9140
- this.isShapeOfType<TLFrameShape>(parent, 'frame') &&
9141
- this.isShapeOfType<TLFrameShape>(rootShape, 'frame') &&
9232
+ this.isShapeOfType(parent, 'frame') &&
9233
+ this.isShapeOfType(rootShape, 'frame') &&
9142
9234
  rootShape.props.w === parent?.props.w &&
9143
9235
  rootShape.props.h === parent?.props.h
9144
9236
  ) {
@@ -9313,11 +9405,11 @@ export class Editor extends EventEmitter<TLEventMap> {
9313
9405
  const onlyRoot = rootShapes[0] as TLFrameShape
9314
9406
  // If the old bounds are in the viewport...
9315
9407
  // todo: replace frame references with shapes that can accept children
9316
- if (this.isShapeOfType<TLFrameShape>(onlyRoot, 'frame')) {
9408
+ if (this.isShapeOfType(onlyRoot, 'frame')) {
9317
9409
  while (
9318
9410
  this.getShapesAtPoint(point).some(
9319
9411
  (shape) =>
9320
- this.isShapeOfType<TLFrameShape>(shape, 'frame') &&
9412
+ this.isShapeOfType(shape, 'frame') &&
9321
9413
  shape.props.w === onlyRoot.props.w &&
9322
9414
  shape.props.h === onlyRoot.props.h
9323
9415
  )
@@ -10233,6 +10325,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10233
10325
  }
10234
10326
 
10235
10327
  this.root.handleEvent(info)
10328
+ this.emit('event', info)
10236
10329
  return
10237
10330
  }
10238
10331
 
@@ -10730,7 +10823,10 @@ function alertMaxShapes(editor: Editor, pageId = editor.getCurrentPageId()) {
10730
10823
 
10731
10824
  function applyPartialToRecordWithProps<
10732
10825
  T extends UnknownRecord & { type: string; props: object; meta: object },
10733
- >(prev: T, partial?: Partial<T> & { props?: Partial<T['props']> }): T {
10826
+ >(
10827
+ prev: T,
10828
+ partial?: T extends T ? Omit<Partial<T>, 'props'> & { props?: Partial<T['props']> } : never
10829
+ ): T {
10734
10830
  if (!partial) return prev
10735
10831
  let next = null as null | T
10736
10832
  const entries = Object.entries(partial)
@@ -1,9 +1,15 @@
1
- import { RecordProps, TLPropsMigrations, TLShape, TLUnknownBinding } from '@tldraw/tlschema'
1
+ import {
2
+ RecordProps,
3
+ TLBinding,
4
+ TLPropsMigrations,
5
+ TLShape,
6
+ TLUnknownBinding,
7
+ } from '@tldraw/tlschema'
2
8
  import { Editor } from '../Editor'
3
9
 
4
10
  /** @public */
5
11
  export interface TLBindingUtilConstructor<
6
- T extends TLUnknownBinding,
12
+ T extends TLBinding,
7
13
  U extends BindingUtil<T> = BindingUtil<T>,
8
14
  > {
9
15
  new (editor: Editor): U
@@ -20,7 +26,7 @@ export interface TLBindingUtilConstructor<
20
26
  *
21
27
  * @public
22
28
  */
23
- export interface BindingOnCreateOptions<Binding extends TLUnknownBinding> {
29
+ export interface BindingOnCreateOptions<Binding extends TLBinding = TLBinding> {
24
30
  /** The binding being created. */
25
31
  binding: Binding
26
32
  }
@@ -31,7 +37,7 @@ export interface BindingOnCreateOptions<Binding extends TLUnknownBinding> {
31
37
  *
32
38
  * @public
33
39
  */
34
- export interface BindingOnChangeOptions<Binding extends TLUnknownBinding> {
40
+ export interface BindingOnChangeOptions<Binding extends TLBinding = TLBinding> {
35
41
  /** The binding record before the change is made. */
36
42
  bindingBefore: Binding
37
43
  /** The binding record after the change is made. */
@@ -44,7 +50,7 @@ export interface BindingOnChangeOptions<Binding extends TLUnknownBinding> {
44
50
  *
45
51
  * @public
46
52
  */
47
- export interface BindingOnDeleteOptions<Binding extends TLUnknownBinding> {
53
+ export interface BindingOnDeleteOptions<Binding extends TLBinding = TLBinding> {
48
54
  /** The binding being deleted. */
49
55
  binding: Binding
50
56
  }
@@ -55,7 +61,7 @@ export interface BindingOnDeleteOptions<Binding extends TLUnknownBinding> {
55
61
  *
56
62
  * @public
57
63
  */
58
- export interface BindingOnShapeChangeOptions<Binding extends TLUnknownBinding> {
64
+ export interface BindingOnShapeChangeOptions<Binding extends TLBinding = TLBinding> {
59
65
  /** The binding record linking these two shapes. */
60
66
  binding: Binding
61
67
  /** The shape record before the change is made. */
@@ -95,7 +101,7 @@ export interface BindingOnShapeChangeOptions<Binding extends TLUnknownBinding> {
95
101
  *
96
102
  * @public
97
103
  */
98
- export interface BindingOnShapeIsolateOptions<Binding extends TLUnknownBinding> {
104
+ export interface BindingOnShapeIsolateOptions<Binding extends TLBinding = TLBinding> {
99
105
  /** The binding record that refers to the shape in question. */
100
106
  binding: Binding
101
107
  /**
@@ -114,7 +120,7 @@ export interface BindingOnShapeIsolateOptions<Binding extends TLUnknownBinding>
114
120
  *
115
121
  * @public
116
122
  */
117
- export interface BindingOnShapeDeleteOptions<Binding extends TLUnknownBinding> {
123
+ export interface BindingOnShapeDeleteOptions<Binding extends TLBinding = TLBinding> {
118
124
  /** The binding record that refers to the shape in question. */
119
125
  binding: Binding
120
126
  /** The shape that is about to be deleted. */
@@ -122,7 +128,7 @@ export interface BindingOnShapeDeleteOptions<Binding extends TLUnknownBinding> {
122
128
  }
123
129
 
124
130
  /** @public */
125
- export abstract class BindingUtil<Binding extends TLUnknownBinding = TLUnknownBinding> {
131
+ export abstract class BindingUtil<Binding extends TLBinding = TLBinding> {
126
132
  constructor(public editor: Editor) {}
127
133
  static props?: RecordProps<TLUnknownBinding>
128
134
  static migrations?: TLPropsMigrations
@@ -1,11 +1,11 @@
1
1
  import { Computed, RESET_VALUE, computed, isUninitialized } from '@tldraw/state'
2
- import { TLArrowBinding, TLBinding, TLShapeId, TLUnknownBinding } from '@tldraw/tlschema'
2
+ import { TLBinding, TLShapeId } 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
- function fromScratch(bindingsQuery: Computed<(TLArrowBinding | TLUnknownBinding)[], unknown>) {
8
+ function fromScratch(bindingsQuery: Computed<TLBinding[], unknown>) {
9
9
  const allBindings = bindingsQuery.get() as TLBinding[]
10
10
 
11
11
  const shapesToBindings: TLBindingsIndex = new Map()