@tldraw/editor 3.16.0-next.6611943ca24a → 3.16.0-next.8eb6d5c2d8f4

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 (160) hide show
  1. package/dist-cjs/index.d.ts +117 -109
  2. package/dist-cjs/index.js +3 -5
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +6 -8
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +12 -2
  7. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  8. package/dist-cjs/lib/config/TLUserPreferences.js +15 -4
  9. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  10. package/dist-cjs/lib/editor/Editor.js +75 -114
  11. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  12. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +4 -0
  13. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
  14. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +4 -2
  15. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  16. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +11 -6
  17. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  18. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +23 -0
  19. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  20. package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
  21. package/dist-cjs/lib/exports/getSvgJsx.js +34 -14
  22. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  23. package/dist-cjs/lib/hooks/useCanvasEvents.js +26 -21
  24. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  25. package/dist-cjs/lib/hooks/useDocumentEvents.js +5 -5
  26. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  27. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -2
  28. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  29. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  30. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  31. package/dist-cjs/lib/hooks/useHandleEvents.js +6 -6
  32. package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
  33. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js +4 -1
  34. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  35. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +4 -1
  36. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  37. package/dist-cjs/lib/hooks/useSelectionEvents.js +8 -8
  38. package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
  39. package/dist-cjs/lib/license/LicenseManager.js +143 -53
  40. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  41. package/dist-cjs/lib/license/LicenseProvider.js +39 -1
  42. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  43. package/dist-cjs/lib/license/Watermark.js +144 -75
  44. package/dist-cjs/lib/license/Watermark.js.map +3 -3
  45. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  46. package/dist-cjs/lib/primitives/Box.js +3 -0
  47. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  48. package/dist-cjs/lib/primitives/Vec.js +0 -4
  49. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  50. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +50 -20
  51. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  52. package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -1
  53. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  54. package/dist-cjs/lib/utils/dom.js.map +2 -2
  55. package/dist-cjs/lib/utils/getPointerInfo.js +2 -3
  56. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  57. package/dist-cjs/lib/utils/reparenting.js +7 -36
  58. package/dist-cjs/lib/utils/reparenting.js.map +3 -3
  59. package/dist-cjs/version.js +3 -3
  60. package/dist-cjs/version.js.map +1 -1
  61. package/dist-esm/index.d.mts +117 -109
  62. package/dist-esm/index.mjs +3 -5
  63. package/dist-esm/index.mjs.map +2 -2
  64. package/dist-esm/lib/TldrawEditor.mjs +6 -8
  65. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  66. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +12 -2
  67. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  68. package/dist-esm/lib/config/TLUserPreferences.mjs +15 -4
  69. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  70. package/dist-esm/lib/editor/Editor.mjs +75 -114
  71. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  72. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +4 -0
  73. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
  74. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
  75. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  76. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +11 -6
  77. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  78. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +23 -0
  79. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  80. package/dist-esm/lib/exports/getSvgJsx.mjs +34 -14
  81. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  82. package/dist-esm/lib/hooks/useCanvasEvents.mjs +27 -27
  83. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  84. package/dist-esm/lib/hooks/useDocumentEvents.mjs +6 -6
  85. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  86. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +1 -2
  87. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  88. package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
  89. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  90. package/dist-esm/lib/hooks/useHandleEvents.mjs +6 -6
  91. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
  92. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs +4 -1
  93. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  94. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +4 -1
  95. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  96. package/dist-esm/lib/hooks/useSelectionEvents.mjs +9 -14
  97. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
  98. package/dist-esm/lib/license/LicenseManager.mjs +144 -54
  99. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  100. package/dist-esm/lib/license/LicenseProvider.mjs +39 -2
  101. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  102. package/dist-esm/lib/license/Watermark.mjs +145 -76
  103. package/dist-esm/lib/license/Watermark.mjs.map +3 -3
  104. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  105. package/dist-esm/lib/primitives/Box.mjs +4 -1
  106. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  107. package/dist-esm/lib/primitives/Vec.mjs +0 -4
  108. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  109. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +53 -21
  110. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  111. package/dist-esm/lib/primitives/geometry/Group2d.mjs +8 -1
  112. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  113. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  114. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -3
  115. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  116. package/dist-esm/lib/utils/reparenting.mjs +8 -41
  117. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  118. package/dist-esm/version.mjs +3 -3
  119. package/dist-esm/version.mjs.map +1 -1
  120. package/editor.css +16 -3
  121. package/package.json +7 -7
  122. package/src/index.ts +2 -9
  123. package/src/lib/TldrawEditor.tsx +7 -16
  124. package/src/lib/components/default-components/DefaultCanvas.tsx +9 -1
  125. package/src/lib/config/TLUserPreferences.ts +16 -3
  126. package/src/lib/editor/Editor.test.ts +90 -0
  127. package/src/lib/editor/Editor.ts +95 -151
  128. package/src/lib/editor/derivations/notVisibleShapes.ts +6 -0
  129. package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
  130. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +30 -8
  131. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +10 -3
  132. package/src/lib/editor/shapes/ShapeUtil.ts +46 -0
  133. package/src/lib/editor/types/misc-types.ts +0 -6
  134. package/src/lib/exports/getSvgJsx.test.ts +868 -0
  135. package/src/lib/exports/getSvgJsx.tsx +76 -19
  136. package/src/lib/hooks/useCanvasEvents.ts +26 -26
  137. package/src/lib/hooks/useDocumentEvents.ts +6 -6
  138. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  139. package/src/lib/hooks/useGestureEvents.ts +2 -2
  140. package/src/lib/hooks/useHandleEvents.ts +6 -6
  141. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +4 -1
  142. package/src/lib/hooks/usePassThroughWheelEvents.ts +6 -1
  143. package/src/lib/hooks/useSelectionEvents.ts +9 -14
  144. package/src/lib/license/LicenseManager.test.ts +721 -382
  145. package/src/lib/license/LicenseManager.ts +204 -58
  146. package/src/lib/license/LicenseProvider.tsx +74 -2
  147. package/src/lib/license/Watermark.tsx +152 -77
  148. package/src/lib/license/useLicenseManagerState.ts +2 -2
  149. package/src/lib/primitives/Box.test.ts +126 -0
  150. package/src/lib/primitives/Box.ts +10 -1
  151. package/src/lib/primitives/Vec.ts +0 -5
  152. package/src/lib/primitives/geometry/Geometry2d.test.ts +420 -0
  153. package/src/lib/primitives/geometry/Geometry2d.ts +78 -21
  154. package/src/lib/primitives/geometry/Group2d.ts +10 -1
  155. package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
  156. package/src/lib/utils/dom.test.ts +103 -0
  157. package/src/lib/utils/dom.ts +8 -1
  158. package/src/lib/utils/getPointerInfo.ts +3 -2
  159. package/src/lib/utils/reparenting.ts +10 -70
  160. package/src/version.ts +3 -3
@@ -116,7 +116,6 @@ import {
116
116
  } from '../constants'
117
117
  import { exportToSvg } from '../exports/exportToSvg'
118
118
  import { getSvgAsImage } from '../exports/getSvgAsImage'
119
- import { tlenv } from '../globals/environment'
120
119
  import { tlmenus } from '../globals/menus'
121
120
  import { tltime } from '../globals/time'
122
121
  import { TldrawOptions, defaultTldrawOptions } from '../options'
@@ -244,16 +243,6 @@ export interface TLEditorOptions {
244
243
  options?: Partial<TldrawOptions>
245
244
  licenseKey?: string
246
245
  fontAssetUrls?: { [key: string]: string | undefined }
247
- /**
248
- * A predicate that should return true if the given shape should be hidden.
249
- *
250
- * @deprecated Use {@link Editor#getShapeVisibility} instead.
251
- *
252
- * @param shape - The shape to check.
253
- * @param editor - The editor instance.
254
- */
255
- isShapeHidden?(shape: TLShape, editor: Editor): boolean
256
-
257
246
  /**
258
247
  * Provides a way to hide shapes.
259
248
  *
@@ -309,21 +298,12 @@ export class Editor extends EventEmitter<TLEventMap> {
309
298
  autoFocus,
310
299
  inferDarkMode,
311
300
  options,
312
- // eslint-disable-next-line @typescript-eslint/no-deprecated
313
- isShapeHidden,
314
301
  getShapeVisibility,
315
302
  fontAssetUrls,
316
303
  }: TLEditorOptions) {
317
304
  super()
318
- assert(
319
- !(isShapeHidden && getShapeVisibility),
320
- 'Cannot use both isShapeHidden and getShapeVisibility'
321
- )
322
305
 
323
- this._getShapeVisibility = isShapeHidden
324
- ? // eslint-disable-next-line @typescript-eslint/no-deprecated
325
- (shape: TLShape, editor: Editor) => (isShapeHidden(shape, editor) ? 'hidden' : 'inherit')
326
- : getShapeVisibility
306
+ this._getShapeVisibility = getShapeVisibility
327
307
 
328
308
  this.options = { ...defaultTldrawOptions, ...options }
329
309
 
@@ -363,6 +343,8 @@ export class Editor extends EventEmitter<TLEventMap> {
363
343
  this.root = new NewRoot(this)
364
344
  this.root.children = {}
365
345
 
346
+ this.markEventAsHandled = this.markEventAsHandled.bind(this)
347
+
366
348
  const allShapeUtils = checkShapesAndAddCore(shapeUtils)
367
349
 
368
350
  const _shapeUtils = {} as Record<string, ShapeUtil<any>>
@@ -907,14 +889,6 @@ export class Editor extends EventEmitter<TLEventMap> {
907
889
  */
908
890
  readonly fonts: FontManager
909
891
 
910
- /**
911
- * A manager for the editor's environment.
912
- *
913
- * @deprecated This is deprecated and will be removed in a future version. Use the `tlenv` global export instead.
914
- * @public
915
- */
916
- readonly environment = tlenv
917
-
918
892
  /**
919
893
  * A manager for the editor's scribbles.
920
894
  *
@@ -1119,35 +1093,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1119
1093
  return this.history.getNumRedos() > 0
1120
1094
  }
1121
1095
 
1122
- /**
1123
- * Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
1124
- * any redos.
1125
- *
1126
- * @example
1127
- * ```ts
1128
- * editor.mark()
1129
- * editor.mark('flip shapes')
1130
- * ```
1131
- *
1132
- * @param markId - The mark's id, usually the reason for adding the mark.
1133
- *
1134
- * @public
1135
- * @deprecated use {@link Editor.markHistoryStoppingPoint} instead
1136
- */
1137
- mark(markId?: string): this {
1138
- if (typeof markId === 'string') {
1139
- console.warn(
1140
- `[tldraw] \`editor.history.mark("${markId}")\` is deprecated. Please use \`const myMarkId = editor.markHistoryStoppingPoint()\` instead.`
1141
- )
1142
- } else {
1143
- console.warn(
1144
- '[tldraw] `editor.mark()` is deprecated. Use `editor.markHistoryStoppingPoint()` instead.'
1145
- )
1146
- }
1147
- this.history._mark(markId ?? uniqueId())
1148
- return this
1149
- }
1150
-
1151
1096
  /**
1152
1097
  * Create a new "mark", or stopping point, in the undo redo history. Creating a mark will clear
1153
1098
  * any redos. You typically want to do this just before a user interaction begins or is handled.
@@ -1272,13 +1217,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1272
1217
  return this
1273
1218
  }
1274
1219
 
1275
- /**
1276
- * @deprecated Use `Editor.run` instead.
1277
- */
1278
- batch(fn: () => void, opts?: TLEditorRunOptions): this {
1279
- return this.run(fn, opts)
1280
- }
1281
-
1282
1220
  /* --------------------- Errors --------------------- */
1283
1221
 
1284
1222
  /** @internal */
@@ -1580,54 +1518,6 @@ export class Editor extends EventEmitter<TLEventMap> {
1580
1518
 
1581
1519
  menus = tlmenus.forContext(this.contextId)
1582
1520
 
1583
- /**
1584
- * @deprecated Use `editor.menus.getOpenMenus` instead.
1585
- *
1586
- * @public
1587
- */
1588
- @computed getOpenMenus(): string[] {
1589
- return this.menus.getOpenMenus()
1590
- }
1591
-
1592
- /**
1593
- * @deprecated Use `editor.menus.addOpenMenu` instead.
1594
- *
1595
- * @public
1596
- */
1597
- addOpenMenu(id: string): this {
1598
- this.menus.addOpenMenu(id)
1599
- return this
1600
- }
1601
-
1602
- /**
1603
- * @deprecated Use `editor.menus.deleteOpenMenu` instead.
1604
- *
1605
- * @public
1606
- */
1607
- deleteOpenMenu(id: string): this {
1608
- this.menus.deleteOpenMenu(id)
1609
- return this
1610
- }
1611
-
1612
- /**
1613
- * @deprecated Use `editor.menus.clearOpenMenus` instead.
1614
- *
1615
- * @public
1616
- */
1617
- clearOpenMenus(): this {
1618
- this.menus.clearOpenMenus()
1619
- return this
1620
- }
1621
-
1622
- /**
1623
- * @deprecated Use `editor.menus.hasAnyOpenMenus` instead.
1624
- *
1625
- * @public
1626
- */
1627
- @computed getIsMenuOpen(): boolean {
1628
- return this.menus.hasAnyOpenMenus()
1629
- }
1630
-
1631
1521
  /* --------------------- Cursor --------------------- */
1632
1522
 
1633
1523
  /**
@@ -4792,8 +4682,10 @@ export class Editor extends EventEmitter<TLEventMap> {
4792
4682
  return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
4793
4683
  const pageTransform = this.getShapePageTransform(shape)
4794
4684
  if (!pageTransform) return undefined
4795
- const geometry = this.getShapeGeometry(shape)
4796
- return Box.FromPoints(pageTransform.applyToPoints(geometry.vertices))
4685
+
4686
+ return Box.FromPoints(
4687
+ pageTransform.applyToPoints(this.getShapeGeometry(shape).boundsVertices)
4688
+ )
4797
4689
  })
4798
4690
  }
4799
4691
 
@@ -4860,27 +4752,25 @@ export class Editor extends EventEmitter<TLEventMap> {
4860
4752
  return this.store.createComputedCache('pageMaskCache', (shape) => {
4861
4753
  if (isPageId(shape.parentId)) return undefined
4862
4754
 
4863
- const frameAncestors = this.getShapeAncestors(shape.id).filter((shape) =>
4864
- this.isShapeOfType<TLFrameShape>(shape, 'frame')
4865
- )
4866
-
4867
- if (frameAncestors.length === 0) return undefined
4868
-
4869
- const pageMask = frameAncestors
4870
- .map<Vec[] | undefined>((s) => {
4871
- // Apply the frame transform to the frame outline to get the frame outline in the current page space
4872
- const geometry = this.getShapeGeometry(s.id)
4873
- const pageTransform = this.getShapePageTransform(s.id)
4874
- return pageTransform.applyToPoints(geometry.vertices)
4875
- })
4876
- .reduce((acc, b) => {
4877
- if (!(b && acc)) return undefined
4878
- const intersection = intersectPolygonPolygon(acc, b)
4879
- if (intersection) {
4880
- return intersection.map(Vec.Cast)
4881
- }
4882
- return []
4883
- })
4755
+ const clipPaths: Vec[][] = []
4756
+ // Get all ancestors that can potentially clip this shape
4757
+ for (const ancestor of this.getShapeAncestors(shape.id)) {
4758
+ const util = this.getShapeUtil(ancestor)
4759
+ const clipPath = util.getClipPath?.(ancestor)
4760
+ if (!clipPath) continue
4761
+ if (util.shouldClipChild?.(shape) === false) continue
4762
+ const pageTransform = this.getShapePageTransform(ancestor.id)
4763
+ clipPaths.push(pageTransform.applyToPoints(clipPath))
4764
+ }
4765
+ if (clipPaths.length === 0) return undefined
4766
+
4767
+ const pageMask = clipPaths.reduce((acc, b) => {
4768
+ const intersection = intersectPolygonPolygon(acc, b)
4769
+ if (intersection) {
4770
+ return intersection.map(Vec.Cast)
4771
+ }
4772
+ return []
4773
+ })
4884
4774
 
4885
4775
  return pageMask
4886
4776
  })
@@ -5841,11 +5731,6 @@ export class Editor extends EventEmitter<TLEventMap> {
5841
5731
  return shapeIds
5842
5732
  }
5843
5733
 
5844
- /** @deprecated Use {@link Editor.getDraggingOverShape} instead */
5845
- getDroppingOverShape(point: Vec, droppingShapes: TLShape[]): TLShape | undefined {
5846
- return this.getDraggingOverShape(point, droppingShapes)
5847
- }
5848
-
5849
5734
  /**
5850
5735
  * Get the shape that some shapes should be dropped on at a given point.
5851
5736
  *
@@ -8950,8 +8835,13 @@ export class Editor extends EventEmitter<TLEventMap> {
8950
8835
  * Handle external content, such as files, urls, embeds, or plain text which has been put into the app, for example by pasting external text or dropping external images onto canvas.
8951
8836
  *
8952
8837
  * @param info - Info about the external content.
8838
+ * @param opts - Options for handling external content, including force flag to bypass readonly checks.
8953
8839
  */
8954
- async putExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8840
+ async putExternalContent<E>(
8841
+ info: TLExternalContent<E>,
8842
+ opts = {} as { force?: boolean }
8843
+ ): Promise<void> {
8844
+ if (!opts.force && this.getIsReadonly()) return
8955
8845
  return this.externalContentHandlers[info.type]?.(info as any)
8956
8846
  }
8957
8847
 
@@ -8959,8 +8849,13 @@ export class Editor extends EventEmitter<TLEventMap> {
8959
8849
  * Handle replacing external content.
8960
8850
  *
8961
8851
  * @param info - Info about the external content.
8852
+ * @param opts - Options for handling external content, including force flag to bypass readonly checks.
8962
8853
  */
8963
- async replaceExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8854
+ async replaceExternalContent<E>(
8855
+ info: TLExternalContent<E>,
8856
+ opts = {} as { force?: boolean }
8857
+ ): Promise<void> {
8858
+ if (!opts.force && this.getIsReadonly()) return
8964
8859
  return this.externalContentHandlers[info.type]?.(info as any)
8965
8860
  }
8966
8861
 
@@ -9461,13 +9356,6 @@ export class Editor extends EventEmitter<TLEventMap> {
9461
9356
  }
9462
9357
  }
9463
9358
 
9464
- /** @deprecated Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead. */
9465
- async getSvg(shapes: TLShapeId[] | TLShape[], opts: TLSvgExportOptions = {}) {
9466
- const result = await this.getSvgElement(shapes, opts)
9467
- if (!result) return undefined
9468
- return result.svg
9469
- }
9470
-
9471
9359
  /**
9472
9360
  * Get an exported image of the given shapes.
9473
9361
  *
@@ -9519,6 +9407,24 @@ export class Editor extends EventEmitter<TLEventMap> {
9519
9407
  }
9520
9408
  }
9521
9409
 
9410
+ /**
9411
+ * Get an exported image of the given shapes as a data URL.
9412
+ *
9413
+ * @param shapes - The shapes (or shape ids) to export.
9414
+ * @param opts - Options for the export.
9415
+ *
9416
+ * @returns A data URL of the image.
9417
+ * @public
9418
+ */
9419
+ async toImageDataUrl(shapes: TLShapeId[] | TLShape[], opts: TLImageExportOptions = {}) {
9420
+ const { blob, width, height } = await this.toImage(shapes, opts)
9421
+ return {
9422
+ url: await FileHelpers.blobToDataUrl(blob),
9423
+ width,
9424
+ height,
9425
+ }
9426
+ }
9427
+
9522
9428
  /* --------------------- Events --------------------- */
9523
9429
 
9524
9430
  /**
@@ -10193,6 +10099,37 @@ export class Editor extends EventEmitter<TLEventMap> {
10193
10099
  /** @internal */
10194
10100
  private performanceTrackerTimeout = -1 as any
10195
10101
 
10102
+ /** @internal */
10103
+ private handledEvents = new WeakSet<Event>()
10104
+
10105
+ /**
10106
+ * In tldraw, events are sometimes handled by multiple components. For example, the shapes might
10107
+ * have events, but the canvas handles events too. The way that the canvas handles events can
10108
+ * interfere with the with the shapes event handlers - for example, it calls `.preventDefault()`
10109
+ * on `pointerDown`, which also prevents `click` events from firing on the shapes.
10110
+ *
10111
+ * You can use `.stopPropagation()` to prevent the event from propagating to the rest of the
10112
+ * DOM, but that can impact non-tldraw event handlers set up elsewhere. By using
10113
+ * `markEventAsHandled`, you'll stop other parts of tldraw from handling the event without
10114
+ * impacting other, non-tldraw event handlers. See also {@link Editor.wasEventAlreadyHandled}.
10115
+ *
10116
+ * @public
10117
+ */
10118
+ markEventAsHandled(e: Event | { nativeEvent: Event }) {
10119
+ const nativeEvent = 'nativeEvent' in e ? e.nativeEvent : e
10120
+ this.handledEvents.add(nativeEvent)
10121
+ }
10122
+
10123
+ /**
10124
+ * Checks if an event has already been handled. See {@link Editor.markEventAsHandled}.
10125
+ *
10126
+ * @public
10127
+ */
10128
+ wasEventAlreadyHandled(e: Event | { nativeEvent: Event }) {
10129
+ const nativeEvent = 'nativeEvent' in e ? e.nativeEvent : e
10130
+ return this.handledEvents.has(nativeEvent)
10131
+ }
10132
+
10196
10133
  /**
10197
10134
  * Dispatch an event to the editor.
10198
10135
  *
@@ -10397,7 +10334,14 @@ export class Editor extends EventEmitter<TLEventMap> {
10397
10334
 
10398
10335
  this._updateInputsFromEvent(info)
10399
10336
 
10400
- const { panSpeed, zoomSpeed, wheelBehavior } = cameraOptions
10337
+ const { panSpeed, zoomSpeed } = cameraOptions
10338
+ let wheelBehavior = cameraOptions.wheelBehavior
10339
+ const inputMode = this.user.getUserPreferences().inputMode
10340
+
10341
+ // If the user has set their input mode preference, then use that to determine the wheel behavior
10342
+ if (inputMode !== null) {
10343
+ wheelBehavior = inputMode === 'trackpad' ? 'pan' : 'zoom'
10344
+ }
10401
10345
 
10402
10346
  if (wheelBehavior !== 'none') {
10403
10347
  // Stop any camera animation
@@ -7,6 +7,12 @@ function fromScratch(editor: Editor): Set<TLShapeId> {
7
7
  const viewportPageBounds = editor.getViewportPageBounds()
8
8
  const notVisibleShapes = new Set<TLShapeId>()
9
9
  shapesIds.forEach((id) => {
10
+ const shape = editor.getShape(id)
11
+ if (!shape) return
12
+
13
+ const canCull = editor.getShapeUtil(shape.type).canCull(shape)
14
+ if (!canCull) return
15
+
10
16
  // If the shape is fully outside of the viewport page bounds, add it to the set.
11
17
  // We'll ignore masks here, since they're more expensive to compute and the overhead is not worth it.
12
18
  const pageBounds = editor.getShapePageBounds(id)
@@ -58,8 +58,12 @@ export class FocusManager {
58
58
 
59
59
  private handleKeyDown(keyEvent: KeyboardEvent) {
60
60
  const container = this.editor.getContainer()
61
- if (this.editor.isIn('select.editing_shape')) return
62
- if (document.activeElement === container && this.editor.getSelectedShapeIds().length > 0) return
61
+ const activeEl = document.activeElement
62
+ // Edit mode should remove the focus ring, however if the active element's
63
+ // parent is the contextual toolbar, then allow it.
64
+ if (this.editor.isIn('select.editing_shape') && !activeEl?.closest('.tlui-contextual-toolbar'))
65
+ return
66
+ if (activeEl === container && this.editor.getSelectedShapeIds().length > 0) return
63
67
  if (['Tab', 'ArrowUp', 'ArrowDown'].includes(keyEvent.key)) {
64
68
  container.classList.remove('tl-container__no-focus-ring')
65
69
  }
@@ -23,13 +23,14 @@ describe('UserPreferencesManager', () => {
23
23
  locale: 'en',
24
24
  animationSpeed: 1,
25
25
  areKeyboardShortcutsEnabled: true,
26
- showUiLabels: false,
26
+ enhancedA11yMode: false,
27
27
  edgeScrollSpeed: 1,
28
28
  colorScheme: 'light',
29
29
  isSnapMode: false,
30
30
  isWrapMode: false,
31
31
  isDynamicSizeMode: false,
32
32
  isPasteAtCursorMode: false,
33
+ inputMode: null,
33
34
  ...overrides,
34
35
  })
35
36
 
@@ -227,12 +228,13 @@ describe('UserPreferencesManager', () => {
227
228
  color: mockUserPreferences.color,
228
229
  animationSpeed: mockUserPreferences.animationSpeed,
229
230
  areKeyboardShortcutsEnabled: mockUserPreferences.areKeyboardShortcutsEnabled,
230
- showUiLabels: mockUserPreferences.showUiLabels,
231
+ enhancedA11yMode: mockUserPreferences.enhancedA11yMode,
231
232
  isSnapMode: mockUserPreferences.isSnapMode,
232
233
  colorScheme: mockUserPreferences.colorScheme,
233
234
  isDarkMode: false, // light mode
234
235
  isWrapMode: mockUserPreferences.isWrapMode,
235
236
  isDynamicResizeMode: mockUserPreferences.isDynamicSizeMode,
237
+ inputMode: mockUserPreferences.inputMode,
236
238
  })
237
239
  })
238
240
 
@@ -376,14 +378,18 @@ describe('UserPreferencesManager', () => {
376
378
  })
377
379
  })
378
380
 
379
- describe('getShowUiLabels', () => {
380
- it('should return user show ui labels setting', () => {
381
- expect(userPreferencesManager.getShowUiLabels()).toBe(mockUserPreferences.showUiLabels)
381
+ describe('getEnhancedA11yMode', () => {
382
+ it('should return user enhanced a11y mode setting', () => {
383
+ expect(userPreferencesManager.getEnhancedA11yMode()).toBe(
384
+ mockUserPreferences.enhancedA11yMode
385
+ )
382
386
  })
383
387
 
384
- it('should return default show ui labels when null', () => {
385
- userPreferencesAtom.set({ ...mockUserPreferences, showUiLabels: null })
386
- expect(userPreferencesManager.getShowUiLabels()).toBe(defaultUserPreferences.showUiLabels)
388
+ it('should return default enhanced a11y mode when null', () => {
389
+ userPreferencesAtom.set({ ...mockUserPreferences, enhancedA11yMode: null })
390
+ expect(userPreferencesManager.getEnhancedA11yMode()).toBe(
391
+ defaultUserPreferences.enhancedA11yMode
392
+ )
387
393
  })
388
394
  })
389
395
 
@@ -453,6 +459,22 @@ describe('UserPreferencesManager', () => {
453
459
  )
454
460
  })
455
461
  })
462
+
463
+ describe('getInputMode', () => {
464
+ it('should return user input mode setting', () => {
465
+ expect(userPreferencesManager.getInputMode()).toBe(null)
466
+ })
467
+
468
+ it('should return trackpad if input mode is trackpad', () => {
469
+ userPreferencesAtom.set({ ...mockUserPreferences, inputMode: 'trackpad' })
470
+ expect(userPreferencesManager.getInputMode()).toBe('trackpad')
471
+ })
472
+
473
+ it('should return mouse if input mode is mouse', () => {
474
+ userPreferencesAtom.set({ ...mockUserPreferences, inputMode: 'mouse' })
475
+ expect(userPreferencesManager.getInputMode()).toBe('mouse')
476
+ })
477
+ })
456
478
  })
457
479
 
458
480
  describe('reactive behavior', () => {
@@ -49,7 +49,8 @@ export class UserPreferencesManager {
49
49
  isDarkMode: this.getIsDarkMode(),
50
50
  isWrapMode: this.getIsWrapMode(),
51
51
  isDynamicResizeMode: this.getIsDynamicResizeMode(),
52
- showUiLabels: this.getShowUiLabels(),
52
+ enhancedA11yMode: this.getEnhancedA11yMode(),
53
+ inputMode: this.getInputMode(),
53
54
  }
54
55
  }
55
56
 
@@ -121,7 +122,13 @@ export class UserPreferencesManager {
121
122
  )
122
123
  }
123
124
 
124
- @computed getShowUiLabels() {
125
- return this.user.userPreferences.get().showUiLabels ?? defaultUserPreferences.showUiLabels
125
+ @computed getEnhancedA11yMode() {
126
+ return (
127
+ this.user.userPreferences.get().enhancedA11yMode ?? defaultUserPreferences.enhancedA11yMode
128
+ )
129
+ }
130
+
131
+ @computed getInputMode() {
132
+ return this.user.userPreferences.get().inputMode ?? defaultUserPreferences.inputMode
126
133
  }
127
134
  }
@@ -283,6 +283,17 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
283
283
  return true
284
284
  }
285
285
 
286
+ /**
287
+ * Whether this shape can be culled. By default, shapes are culled for
288
+ * performance reasons when they are outside of the viewport. Culled shapes are still rendered
289
+ * to the DOM, but have their `display` property set to `none`.
290
+ *
291
+ * @param shape - The shape.
292
+ */
293
+ canCull(_shape: Shape): boolean {
294
+ return true
295
+ }
296
+
286
297
  /**
287
298
  * Does this shape provide a background for its children? If this is true,
288
299
  * then any children with a `renderBackground` method will have their
@@ -296,6 +307,27 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
296
307
  return false
297
308
  }
298
309
 
310
+ /**
311
+ * Get the clip path to apply to this shape's children.
312
+ *
313
+ * @param shape - The shape to get the clip path for
314
+ * @returns Array of points defining the clipping polygon in local coordinates, or undefined if no clipping
315
+ * @public
316
+ */
317
+ getClipPath?(shape: Shape): Vec[] | undefined
318
+
319
+ /**
320
+ * Whether a specific child shape should be clipped by this shape.
321
+ * Only called if getClipPath returns a valid polygon.
322
+ *
323
+ * If not defined, the default behavior is to clip all children.
324
+ *
325
+ * @param child - The child shape to check
326
+ * @returns boolean indicating if this child should be clipped
327
+ * @public
328
+ */
329
+ shouldClipChild?(child: TLShape): boolean
330
+
299
331
  /**
300
332
  * Whether the shape should hide its resize handles when selected.
301
333
  *
@@ -341,6 +373,20 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
341
373
  return false
342
374
  }
343
375
 
376
+ /**
377
+ * By default, the bounds of an image export are the bounds of all the shapes it contains, plus
378
+ * some padding. If an export includes a shape where `isExportBoundsContainer` is true, then the
379
+ * padding is skipped _if the bounds of that shape contains all the other shapes_. This is
380
+ * useful in cases like annotating on top of an image, where you usually want to avoid extra
381
+ * padding around the image if you don't need it.
382
+ *
383
+ * @param _shape - The shape to check
384
+ * @returns True if this shape should be treated as an export bounds container
385
+ */
386
+ isExportBoundsContainer(_shape: Shape): boolean {
387
+ return false
388
+ }
389
+
344
390
  /**
345
391
  * Get a JSX element for the shape (as an HTML element) to be rendered as part of the canvas background - behind any other shape content.
346
392
  *
@@ -72,12 +72,6 @@ export interface TLImageExportOptions extends TLSvgExportOptions {
72
72
  format?: TLExportType
73
73
  }
74
74
 
75
- /**
76
- * @public
77
- * @deprecated use {@link TLImageExportOptions} instead
78
- */
79
- export type TLSvgOptions = TLImageExportOptions
80
-
81
75
  /** @public */
82
76
  export interface TLCameraMoveOptions {
83
77
  /** Whether to move the camera immediately, rather than on the next tick. */