@tldraw/editor 4.6.0-next.20de11b7e238 → 4.6.0-next.241e87d4700a

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 (156) hide show
  1. package/dist-cjs/index.d.ts +668 -96
  2. package/dist-cjs/index.js +16 -3
  3. package/dist-cjs/index.js.map +3 -3
  4. package/dist-cjs/lib/TldrawEditor.js +55 -12
  5. package/dist-cjs/lib/TldrawEditor.js.map +3 -3
  6. package/dist-cjs/lib/components/MenuClickCapture.js +16 -1
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js +3 -3
  9. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +2 -2
  10. package/dist-cjs/lib/config/createTLStore.js +7 -0
  11. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  12. package/dist-cjs/lib/config/defaultAssets.js +36 -0
  13. package/dist-cjs/lib/config/defaultAssets.js.map +7 -0
  14. package/dist-cjs/lib/editor/Editor.js +215 -5
  15. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  16. package/dist-cjs/lib/editor/assets/AssetUtil.js +66 -0
  17. package/dist-cjs/lib/editor/assets/AssetUtil.js.map +7 -0
  18. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.js +80 -0
  20. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.js.map +7 -0
  21. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js +466 -0
  22. package/dist-cjs/lib/editor/managers/PerformanceManager/PerformanceManager.js.map +7 -0
  23. package/dist-cjs/lib/editor/managers/PerformanceManager/perf-types.js +17 -0
  24. package/dist-cjs/lib/editor/managers/PerformanceManager/perf-types.js.map +7 -0
  25. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +106 -0
  26. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +7 -0
  27. package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js +586 -0
  28. package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js.map +7 -0
  29. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +6 -4
  30. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  31. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +11 -2
  32. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  33. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +1 -1
  34. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  35. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +6 -0
  36. package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
  37. package/dist-cjs/lib/editor/tools/StateNode.js +14 -17
  38. package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
  39. package/dist-cjs/lib/editor/types/SvgExportContext.js.map +2 -2
  40. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  41. package/dist-cjs/lib/exports/getSvgJsx.js +12 -7
  42. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  43. package/dist-cjs/lib/globals/environment.js +18 -1
  44. package/dist-cjs/lib/globals/environment.js.map +2 -2
  45. package/dist-cjs/lib/hooks/{useIsDarkMode.js → useColorMode.js} +14 -10
  46. package/dist-cjs/lib/hooks/useColorMode.js.map +7 -0
  47. package/dist-cjs/lib/hooks/useCursor.js +3 -7
  48. package/dist-cjs/lib/hooks/useCursor.js.map +2 -2
  49. package/dist-cjs/lib/hooks/useDarkMode.js +4 -4
  50. package/dist-cjs/lib/hooks/useDarkMode.js.map +2 -2
  51. package/dist-cjs/lib/utils/reparenting.js +2 -1
  52. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  53. package/dist-cjs/lib/utils/richText.js.map +2 -2
  54. package/dist-cjs/lib/utils/runtime.js +2 -1
  55. package/dist-cjs/lib/utils/runtime.js.map +2 -2
  56. package/dist-cjs/lib/utils/sync/hardReset.js +0 -8
  57. package/dist-cjs/lib/utils/sync/hardReset.js.map +2 -2
  58. package/dist-cjs/version.js +3 -3
  59. package/dist-cjs/version.js.map +1 -1
  60. package/dist-esm/index.d.mts +668 -96
  61. package/dist-esm/index.mjs +17 -6
  62. package/dist-esm/index.mjs.map +2 -2
  63. package/dist-esm/lib/TldrawEditor.mjs +58 -12
  64. package/dist-esm/lib/TldrawEditor.mjs.map +3 -3
  65. package/dist-esm/lib/components/MenuClickCapture.mjs +16 -1
  66. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  67. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +3 -3
  68. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +2 -2
  69. package/dist-esm/lib/config/createTLStore.mjs +10 -1
  70. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  71. package/dist-esm/lib/config/defaultAssets.mjs +16 -0
  72. package/dist-esm/lib/config/defaultAssets.mjs.map +7 -0
  73. package/dist-esm/lib/editor/Editor.mjs +215 -5
  74. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  75. package/dist-esm/lib/editor/assets/AssetUtil.mjs +46 -0
  76. package/dist-esm/lib/editor/assets/AssetUtil.mjs.map +7 -0
  77. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  78. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.mjs +60 -0
  79. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.mjs.map +7 -0
  80. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs +438 -0
  81. package/dist-esm/lib/editor/managers/PerformanceManager/PerformanceManager.mjs.map +7 -0
  82. package/dist-esm/lib/editor/managers/PerformanceManager/perf-types.mjs +1 -0
  83. package/dist-esm/lib/editor/managers/PerformanceManager/perf-types.mjs.map +7 -0
  84. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +88 -0
  85. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +7 -0
  86. package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs +568 -0
  87. package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs.map +7 -0
  88. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +6 -4
  89. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  90. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +11 -2
  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 +2 -2
  94. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +6 -0
  95. package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
  96. package/dist-esm/lib/editor/tools/StateNode.mjs +14 -17
  97. package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
  98. package/dist-esm/lib/editor/types/SvgExportContext.mjs.map +2 -2
  99. package/dist-esm/lib/exports/getSvgJsx.mjs +12 -10
  100. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  101. package/dist-esm/lib/globals/environment.mjs +18 -1
  102. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  103. package/dist-esm/lib/hooks/useColorMode.mjs +19 -0
  104. package/dist-esm/lib/hooks/useColorMode.mjs.map +7 -0
  105. package/dist-esm/lib/hooks/useCursor.mjs +3 -7
  106. package/dist-esm/lib/hooks/useCursor.mjs.map +2 -2
  107. package/dist-esm/lib/hooks/useDarkMode.mjs +4 -4
  108. package/dist-esm/lib/hooks/useDarkMode.mjs.map +2 -2
  109. package/dist-esm/lib/utils/reparenting.mjs +2 -1
  110. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  111. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  112. package/dist-esm/lib/utils/runtime.mjs +2 -1
  113. package/dist-esm/lib/utils/runtime.mjs.map +2 -2
  114. package/dist-esm/lib/utils/sync/hardReset.mjs +0 -8
  115. package/dist-esm/lib/utils/sync/hardReset.mjs.map +2 -2
  116. package/dist-esm/version.mjs +3 -3
  117. package/dist-esm/version.mjs.map +1 -1
  118. package/editor.css +0 -33
  119. package/package.json +7 -7
  120. package/src/index.ts +23 -6
  121. package/src/lib/TldrawEditor.tsx +90 -13
  122. package/src/lib/components/MenuClickCapture.tsx +20 -0
  123. package/src/lib/components/default-components/CanvasShapeIndicators.tsx +3 -3
  124. package/src/lib/config/createTLStore.ts +22 -1
  125. package/src/lib/config/defaultAssets.ts +19 -0
  126. package/src/lib/editor/Editor.ts +301 -27
  127. package/src/lib/editor/assets/AssetUtil.ts +85 -0
  128. package/src/lib/editor/managers/FontManager/FontManager.test.ts +9 -2
  129. package/src/lib/editor/managers/FontManager/FontManager.ts +1 -67
  130. package/src/lib/editor/managers/PerformanceManager/PerformanceApiAdapter.ts +82 -0
  131. package/src/lib/editor/managers/PerformanceManager/PerformanceManager.test.ts +522 -0
  132. package/src/lib/editor/managers/PerformanceManager/PerformanceManager.ts +583 -0
  133. package/src/lib/editor/managers/PerformanceManager/perf-types.ts +196 -0
  134. package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +116 -0
  135. package/src/lib/editor/managers/ThemeManager/defaultThemes.ts +605 -0
  136. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +23 -29
  137. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +5 -3
  138. package/src/lib/editor/shapes/ShapeUtil.ts +28 -3
  139. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +1 -1
  140. package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +7 -0
  141. package/src/lib/editor/tools/StateNode.ts +16 -18
  142. package/src/lib/editor/types/SvgExportContext.tsx +5 -0
  143. package/src/lib/editor/types/external-content.ts +1 -0
  144. package/src/lib/exports/getSvgJsx.tsx +21 -15
  145. package/src/lib/globals/environment.ts +18 -0
  146. package/src/lib/hooks/{useIsDarkMode.ts → useColorMode.ts} +9 -5
  147. package/src/lib/hooks/useCursor.ts +3 -7
  148. package/src/lib/hooks/useDarkMode.ts +4 -4
  149. package/src/lib/utils/reparenting.ts +6 -2
  150. package/src/lib/utils/richText.ts +1 -1
  151. package/src/lib/utils/runtime.ts +3 -1
  152. package/src/lib/utils/sync/hardReset.ts +0 -8
  153. package/src/version.ts +3 -3
  154. package/dist-cjs/lib/hooks/useIsDarkMode.js.map +0 -7
  155. package/dist-esm/lib/hooks/useIsDarkMode.mjs +0 -15
  156. package/dist-esm/lib/hooks/useIsDarkMode.mjs.map +0 -7
@@ -50,6 +50,9 @@ import {
50
50
  TLShapePartial,
51
51
  TLStore,
52
52
  TLStoreSnapshot,
53
+ TLTheme,
54
+ TLThemeId,
55
+ TLThemes,
53
56
  TLUser,
54
57
  TLUserId,
55
58
  TLVideoAsset,
@@ -95,6 +98,7 @@ import {
95
98
  } from '@tldraw/utils'
96
99
  import EventEmitter from 'eventemitter3'
97
100
  import { TLCurrentUser, createTLCurrentUser } from '../config/createTLCurrentUser'
101
+ import { TLAnyAssetUtilConstructor, checkAssets } from '../config/defaultAssets'
98
102
  import { TLAnyBindingUtilConstructor, checkBindings } from '../config/defaultBindings'
99
103
  import { TLAnyShapeUtilConstructor, checkShapesAndAddCore } from '../config/defaultShapes'
100
104
  import {
@@ -141,6 +145,7 @@ import { getDroppedShapesToNewParents, kickoutOccludedShapes } from '../utils/re
141
145
  import { TLTextOptions, TiptapEditor } from '../utils/richText'
142
146
  import { applyRotationToSnapshotShapes, getRotationSnapshot } from '../utils/rotation'
143
147
  import { ReadonlySharedStyleMap, SharedStyle, SharedStyleMap } from '../utils/SharedStylesMap'
148
+ import { AssetUtil } from './assets/AssetUtil'
144
149
  import { BindingOnDeleteOptions, BindingUtil } from './bindings/BindingUtil'
145
150
  import { bindingsIndex } from './derivations/bindingsIndex'
146
151
  import { notVisibleShapes } from './derivations/notVisibleShapes'
@@ -152,10 +157,12 @@ import { FocusManager } from './managers/FocusManager/FocusManager'
152
157
  import { FontManager } from './managers/FontManager/FontManager'
153
158
  import { HistoryManager } from './managers/HistoryManager/HistoryManager'
154
159
  import { InputsManager } from './managers/InputsManager/InputsManager'
160
+ import { PerformanceManager } from './managers/PerformanceManager/PerformanceManager'
155
161
  import { ScribbleManager } from './managers/ScribbleManager/ScribbleManager'
156
162
  import { SnapManager } from './managers/SnapManager/SnapManager'
157
163
  import { SpatialIndexManager } from './managers/SpatialIndexManager/SpatialIndexManager'
158
164
  import { TextManager } from './managers/TextManager/TextManager'
165
+ import { resolveThemes, ThemeManager } from './managers/ThemeManager/ThemeManager'
159
166
  import { TickManager } from './managers/TickManager/TickManager'
160
167
  import { UserPreferencesManager } from './managers/UserPreferencesManager/UserPreferencesManager'
161
168
  import {
@@ -214,14 +221,13 @@ export interface TLEditorOptions {
214
221
  */
215
222
  bindingUtils: readonly TLAnyBindingUtilConstructor[]
216
223
  /**
217
- * An array of tools to use in the editor. These will be used to handle events and manage user interactions in the editor.
224
+ * An array of asset utils to use in the editor. These will be used to handle asset-type-specific behavior.
218
225
  */
219
- tools: readonly TLStateNodeConstructor[]
226
+ assetUtils?: readonly TLAnyAssetUtilConstructor[]
220
227
  /**
221
- * Should return a containing html element which has all the styles applied to the editor. If not
222
- * given, the body element will be used.
228
+ * An array of tools to use in the editor. These will be used to handle events and manage user interactions in the editor.
223
229
  */
224
- getContainer(): HTMLElement
230
+ tools: readonly TLStateNodeConstructor[]
225
231
  /**
226
232
  * A user defined externally to replace the default user.
227
233
  */
@@ -234,25 +240,13 @@ export interface TLEditorOptions {
234
240
  * Whether to automatically focus the editor when it mounts.
235
241
  */
236
242
  autoFocus?: boolean
237
- /**
238
- * Whether to infer dark mode from the user's system preferences. Defaults to false.
239
- */
240
- inferDarkMode?: boolean
241
- /**
242
- * Options for the editor's camera.
243
- *
244
- * @deprecated Use `options.cameraOptions` instead. This will be removed in a future release.
245
- */
246
- cameraOptions?: Partial<TLCameraOptions>
247
- options?: Partial<TldrawOptions>
248
- /**
249
- * Text options for the editor.
250
- *
251
- * @deprecated Use `options.text` instead. This prop will be removed in a future release.
252
- */
253
- textOptions?: TLTextOptions
254
243
  licenseKey?: string
255
244
  fontAssetUrls?: { [key: string]: string | undefined }
245
+ /**
246
+ * Should return a containing html element which has all the styles applied to the editor. If not
247
+ * given, the body element will be used.
248
+ */
249
+ getContainer(): HTMLElement
256
250
  /**
257
251
  * Provides a way to hide shapes.
258
252
  *
@@ -272,6 +266,41 @@ export interface TLEditorOptions {
272
266
  shape: TLShape,
273
267
  editor: Editor
274
268
  ): 'visible' | 'hidden' | 'inherit' | null | undefined
269
+ /**
270
+ * Named theme definitions for the editor. Each theme contains shared
271
+ * properties (font size, line height, stroke width) and color palettes
272
+ * for both light and dark modes.
273
+ */
274
+ themes?: Partial<TLThemes>
275
+ /**
276
+ * The id of the initially active theme. Defaults to `'default'`.
277
+ */
278
+ initialTheme?: TLThemeId
279
+ /**
280
+ * The editor's color scheme preference, controls the default color mode. Defaults to `'light'`.
281
+ *
282
+ * - `'light'` - Always use light mode.
283
+ * - `'dark'` - Always use dark mode.
284
+ * - `'system'` - Follow the OS color scheme preference.
285
+ */
286
+ colorScheme?: 'light' | 'dark' | 'system'
287
+ /**
288
+ * Additional configuration options for the tldraw editor.
289
+ */
290
+ options?: Partial<TldrawOptions>
291
+ // --- Deprecated ----
292
+ /**
293
+ * Options for the editor's camera.
294
+ *
295
+ * @deprecated Use `options.cameraOptions` instead. This will be removed in a future release.
296
+ */
297
+ cameraOptions?: Partial<TLCameraOptions>
298
+ /**
299
+ * Text options for the editor.
300
+ *
301
+ * @deprecated Use `options.text` instead. This prop will be removed in a future release.
302
+ */
303
+ textOptions?: TLTextOptions
275
304
  }
276
305
 
277
306
  /**
@@ -300,6 +329,7 @@ export class Editor extends EventEmitter<TLEventMap> {
300
329
  user,
301
330
  shapeUtils,
302
331
  bindingUtils,
332
+ assetUtils: assetUtilConstructors,
303
333
  tools,
304
334
  getContainer,
305
335
  // needs to be here for backwards compatibility with TldrawEditor
@@ -307,13 +337,15 @@ export class Editor extends EventEmitter<TLEventMap> {
307
337
  cameraOptions,
308
338
  initialState,
309
339
  autoFocus,
310
- inferDarkMode,
311
340
  options: _options,
312
341
  // needs to be here for backwards compatibility with TldrawEditor
313
342
  // eslint-disable-next-line @typescript-eslint/no-deprecated
314
343
  textOptions: _textOptions,
315
344
  getShapeVisibility,
345
+ colorScheme,
316
346
  fontAssetUrls,
347
+ themes,
348
+ initialTheme,
317
349
  }: TLEditorOptions) {
318
350
  super()
319
351
 
@@ -348,19 +380,24 @@ export class Editor extends EventEmitter<TLEventMap> {
348
380
  ...options?.camera,
349
381
  })
350
382
 
383
+ this.getContainer = getContainer
384
+
351
385
  this._textOptions = atom('text options', options?.text ?? null)
352
386
 
353
- this.user = new UserPreferencesManager(user ?? createTLCurrentUser(), inferDarkMode ?? false)
387
+ this.user = new UserPreferencesManager(user ?? createTLCurrentUser(), colorScheme ?? 'light')
354
388
  this.disposables.add(() => this.user.dispose())
355
389
 
356
- this.getContainer = getContainer
357
-
358
390
  this.textMeasure = new TextManager(this)
359
391
  this.disposables.add(() => this.textMeasure.dispose())
360
392
 
361
- this.fonts = new FontManager(this, fontAssetUrls)
393
+ this._themeManager = new ThemeManager(this, {
394
+ themes: resolveThemes(themes),
395
+ initial: initialTheme ?? 'default',
396
+ })
397
+ this.disposables.add(() => this._themeManager.dispose())
362
398
 
363
399
  this._tickManager = new TickManager(this)
400
+ this.disposables.add(() => this._tickManager.dispose())
364
401
  this.disposables.add(() => {
365
402
  // Reset camera state to 'idle' so the store isn't left stuck at 'moving'
366
403
  // when tick events stop (e.g. React strict mode disposes while camera is moving)
@@ -368,7 +405,11 @@ export class Editor extends EventEmitter<TLEventMap> {
368
405
  this._setCameraState('idle')
369
406
  })
370
407
 
408
+ this.fonts = new FontManager(this, fontAssetUrls)
409
+
371
410
  this.inputs = new InputsManager(this)
411
+ this.performance = new PerformanceManager(this)
412
+ this.disposables.add(() => this.performance.dispose())
372
413
 
373
414
  class NewRoot extends RootState {
374
415
  static override initial = initialState ?? ''
@@ -406,6 +447,17 @@ export class Editor extends EventEmitter<TLEventMap> {
406
447
  this.shapeUtils = _shapeUtils
407
448
  this.styleProps = _styleProps
408
449
 
450
+ const _shapeUtilsByAssetType = {} as Record<string, ShapeUtil<any>>
451
+ for (const Util of allShapeUtils) {
452
+ const assetTypes = Util.handledAssetTypes
453
+ if (assetTypes) {
454
+ for (const assetType of assetTypes) {
455
+ _shapeUtilsByAssetType[assetType] = _shapeUtils[Util.type]
456
+ }
457
+ }
458
+ }
459
+ this._shapeUtilsByAssetType = _shapeUtilsByAssetType
460
+
409
461
  const allBindingUtils = checkBindings(bindingUtils)
410
462
  const _bindingUtils = {} as Record<string, BindingUtil<any>>
411
463
  for (const Util of allBindingUtils) {
@@ -414,6 +466,17 @@ export class Editor extends EventEmitter<TLEventMap> {
414
466
  }
415
467
  this.bindingUtils = _bindingUtils
416
468
 
469
+ // Asset utils
470
+ if (assetUtilConstructors) {
471
+ const allAssetUtils = checkAssets(assetUtilConstructors)
472
+ const _assetUtils = {} as Record<string, AssetUtil<any>>
473
+ for (const Util of allAssetUtils) {
474
+ const util = new Util(this)
475
+ _assetUtils[Util.type] = util
476
+ }
477
+ this.assetUtils = _assetUtils
478
+ }
479
+
417
480
  // Tools.
418
481
  // Accept tools from constructor parameters which may not conflict with the root note's default or
419
482
  // "baked in" tools, select and zoom.
@@ -948,6 +1011,18 @@ export class Editor extends EventEmitter<TLEventMap> {
948
1011
  */
949
1012
  readonly snaps: SnapManager
950
1013
 
1014
+ /**
1015
+ * A manager for performance measurement hooks.
1016
+ *
1017
+ * @public
1018
+ */
1019
+ readonly performance: PerformanceManager
1020
+
1021
+ /**
1022
+ * A manager for the spatial index, tracking where shapes exist on the canvas.
1023
+ *
1024
+ * @internal
1025
+ */
951
1026
  private readonly _spatialIndex: SpatialIndexManager
952
1027
 
953
1028
  /**
@@ -965,6 +1040,13 @@ export class Editor extends EventEmitter<TLEventMap> {
965
1040
  */
966
1041
  readonly user: UserPreferencesManager
967
1042
 
1043
+ /**
1044
+ * A manager for the editor's themes.
1045
+ *
1046
+ * @internal
1047
+ */
1048
+ private readonly _themeManager: ThemeManager
1049
+
968
1050
  /**
969
1051
  * A helper for measuring text.
970
1052
  *
@@ -1045,13 +1127,134 @@ export class Editor extends EventEmitter<TLEventMap> {
1045
1127
  * @public
1046
1128
  */
1047
1129
  dispose() {
1130
+ // Stop any in-progress camera animations and following before
1131
+ // running disposables, so their cleanup listeners fire first
1132
+ this.stopCameraAnimation()
1133
+ if (this.getInstanceState().followingUserId) {
1134
+ this.stopFollowingUser()
1135
+ }
1136
+
1048
1137
  this.disposables.forEach((dispose) => dispose())
1049
1138
  this.disposables.clear()
1139
+
1140
+ // Clear any open menus for this editor's context
1141
+ this.menus.clearOpenMenus()
1142
+
1050
1143
  this.store.dispose()
1051
1144
  this.isDisposed = true
1052
1145
  this.emit('dispose')
1053
1146
  }
1054
1147
 
1148
+ /* ------------------ Themes (shadowing the theme manager) ------------------ */
1149
+
1150
+ /**
1151
+ * Get the current color mode (`'light'` or `'dark'`), based on the user's dark mode preference.
1152
+ *
1153
+ * @public
1154
+ */
1155
+ getColorMode(): 'light' | 'dark' {
1156
+ return this._themeManager.getColorMode()
1157
+ }
1158
+
1159
+ /**
1160
+ * Set the color mode. Note that this is a convenience method that passes the mode to
1161
+ * `user.updateUserPreferences`, which is the source of truth for the user's color mode preference.
1162
+ *
1163
+ * @public
1164
+ */
1165
+ setColorMode(mode: 'light' | 'dark') {
1166
+ this.user.updateUserPreferences({ colorScheme: mode })
1167
+ return this
1168
+ }
1169
+
1170
+ /**
1171
+ * Get the id of the current theme.
1172
+ *
1173
+ * @public
1174
+ */
1175
+ getCurrentThemeId(): TLThemeId {
1176
+ return this._themeManager.getCurrentThemeId()
1177
+ }
1178
+
1179
+ /**
1180
+ * Get the current theme definition.
1181
+ *
1182
+ * @public
1183
+ */
1184
+ getCurrentTheme(): TLTheme {
1185
+ return this._themeManager.getCurrentTheme()
1186
+ }
1187
+
1188
+ /**
1189
+ * Set the current theme by id.
1190
+ *
1191
+ * @public
1192
+ */
1193
+ setCurrentTheme(id: TLThemeId) {
1194
+ this._themeManager.setCurrentTheme(id)
1195
+ return this
1196
+ }
1197
+
1198
+ /**
1199
+ * Get all registered theme definitions.
1200
+ *
1201
+ * @public
1202
+ */
1203
+ getThemes(): TLThemes {
1204
+ return this._themeManager.getThemes()
1205
+ }
1206
+
1207
+ /**
1208
+ * Get a single theme definition by id.
1209
+ *
1210
+ * @public
1211
+ */
1212
+ getTheme(id: TLThemeId): TLTheme | undefined {
1213
+ return this._themeManager.getTheme(id)
1214
+ }
1215
+
1216
+ /**
1217
+ * Replace all theme definitions, or update them via a callback that receives a deep copy.
1218
+ * The `'default'` theme must always be present in the result.
1219
+ *
1220
+ * @example
1221
+ * ```ts
1222
+ * // Replace all themes
1223
+ * editor.updateThemes({ default: myDefaultTheme, ocean: myOceanTheme })
1224
+ *
1225
+ * // Update via callback
1226
+ * editor.updateThemes((themes) => {
1227
+ * delete themes.ocean
1228
+ * return themes
1229
+ * })
1230
+ * ```
1231
+ *
1232
+ * @public
1233
+ */
1234
+ updateThemes(themes: TLThemes | ((themes: TLThemes) => TLThemes)) {
1235
+ this._themeManager.updateThemes(themes)
1236
+ return this
1237
+ }
1238
+
1239
+ /**
1240
+ * Register or update a single theme definition. The theme is keyed by its `id` property.
1241
+ *
1242
+ * @example
1243
+ * ```ts
1244
+ * // Override a property on the default theme
1245
+ * editor.updateTheme({ ...editor.getTheme('default')!, fontSize: 24 })
1246
+ *
1247
+ * // Register a new theme
1248
+ * editor.updateTheme({ id: 'ocean', ...myOceanTheme })
1249
+ * ```
1250
+ *
1251
+ * @public
1252
+ */
1253
+ updateTheme(theme: TLTheme) {
1254
+ this._themeManager.updateTheme(theme)
1255
+ return this
1256
+ }
1257
+
1055
1258
  /* ------------------- Shape Utils ------------------ */
1056
1259
 
1057
1260
  /**
@@ -1061,6 +1264,9 @@ export class Editor extends EventEmitter<TLEventMap> {
1061
1264
  */
1062
1265
  shapeUtils: { readonly [K in string]?: ShapeUtil<TLShape> }
1063
1266
 
1267
+ /** @internal */
1268
+ private _shapeUtilsByAssetType: { readonly [K in string]?: ShapeUtil<TLShape> } = {}
1269
+
1064
1270
  styleProps: { [key: string]: Map<StyleProp<any>, string> }
1065
1271
 
1066
1272
  /**
@@ -1103,6 +1309,18 @@ export class Editor extends EventEmitter<TLEventMap> {
1103
1309
  return hasOwnProperty(this.shapeUtils, type)
1104
1310
  }
1105
1311
 
1312
+ /**
1313
+ * Get the shape util that handles the given asset type.
1314
+ * Returns the shape util whose {@link ShapeUtil.handledAssetTypes} includes
1315
+ * the given asset type, or undefined if none matches.
1316
+ *
1317
+ * @param assetType - The asset type string.
1318
+ * @public
1319
+ */
1320
+ getShapeUtilForAssetType(assetType: string): ShapeUtil | undefined {
1321
+ return getOwnProperty(this._shapeUtilsByAssetType, assetType)
1322
+ }
1323
+
1106
1324
  /* ------------------- Binding Utils ------------------ */
1107
1325
  /**
1108
1326
  * A map of shape utility classes (TLShapeUtils) by shape type.
@@ -1138,6 +1356,56 @@ export class Editor extends EventEmitter<TLEventMap> {
1138
1356
  return bindingUtil
1139
1357
  }
1140
1358
 
1359
+ /* ------------------- Asset Utils ------------------ */
1360
+
1361
+ /**
1362
+ * A map of asset utility classes by asset type.
1363
+ *
1364
+ * @public
1365
+ */
1366
+ assetUtils: { readonly [K in string]?: AssetUtil<TLAsset> } = {}
1367
+
1368
+ /**
1369
+ * Get an asset util from an asset or asset type.
1370
+ *
1371
+ * @param arg - An asset, asset type string, or object with type.
1372
+ *
1373
+ * @public
1374
+ */
1375
+ getAssetUtil<S extends TLAsset>(asset: S | { type: S['type'] }): AssetUtil<S>
1376
+ getAssetUtil(type: string): AssetUtil
1377
+ getAssetUtil(arg: string | { type: string }) {
1378
+ const type = typeof arg === 'string' ? arg : arg.type
1379
+ const assetUtil = getOwnProperty(this.assetUtils, type)
1380
+ assert(assetUtil, `No asset util found for type "${type}"`)
1381
+ return assetUtil
1382
+ }
1383
+
1384
+ /**
1385
+ * Returns true if the editor has an asset util for the given asset type.
1386
+ *
1387
+ * @public
1388
+ */
1389
+ hasAssetUtil(arg: string | { type: string }): boolean {
1390
+ const type = typeof arg === 'string' ? arg : arg.type
1391
+ return hasOwnProperty(this.assetUtils, type)
1392
+ }
1393
+
1394
+ /**
1395
+ * Get the asset util that accepts the given MIME type.
1396
+ * Returns null if no registered asset util accepts the MIME type.
1397
+ *
1398
+ * @public
1399
+ */
1400
+ getAssetUtilForMimeType(mimeType: string): AssetUtil | null {
1401
+ for (const util of Object.values(this.assetUtils)) {
1402
+ if (util && util.acceptsMimeType(mimeType)) {
1403
+ return util
1404
+ }
1405
+ }
1406
+ return null
1407
+ }
1408
+
1141
1409
  /* --------------------- History -------------------- */
1142
1410
 
1143
1411
  /**
@@ -1161,6 +1429,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1161
1429
  this._flushEventsForTick(0)
1162
1430
  this.complete()
1163
1431
  this.history.undo()
1432
+ this.performance._notifyUndoRedo('undo', this.history.getNumUndos(), this.history.getNumRedos())
1164
1433
  return this
1165
1434
  }
1166
1435
 
@@ -1191,6 +1460,7 @@ export class Editor extends EventEmitter<TLEventMap> {
1191
1460
  this._flushEventsForTick(0)
1192
1461
  this.complete()
1193
1462
  this.history.redo()
1463
+ this.performance._notifyUndoRedo('redo', this.history.getNumUndos(), this.history.getNumRedos())
1194
1464
  return this
1195
1465
  }
1196
1466
 
@@ -10595,6 +10865,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10595
10865
  { immediate: true }
10596
10866
  )
10597
10867
 
10868
+ this.performance._notifyCameraOperation('zooming')
10598
10869
  this.emit('event', info)
10599
10870
  return // Stop here!
10600
10871
  }
@@ -10687,6 +10958,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10687
10958
  immediate: true,
10688
10959
  })
10689
10960
  this.maybeTrackPerformance('Zooming')
10961
+ this.performance._notifyCameraOperation('zooming')
10690
10962
  this.root.handleEvent(info)
10691
10963
  this.emit('event', info)
10692
10964
  return
@@ -10697,6 +10969,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10697
10969
  immediate: true,
10698
10970
  })
10699
10971
  this.maybeTrackPerformance('Panning')
10972
+ this.performance._notifyCameraOperation('panning')
10700
10973
  this.root.handleEvent(info)
10701
10974
  this.emit('event', info)
10702
10975
  return
@@ -10791,6 +11064,7 @@ export class Editor extends EventEmitter<TLEventMap> {
10791
11064
  immediate: true,
10792
11065
  })
10793
11066
  this.maybeTrackPerformance('Panning')
11067
+ this.performance._notifyCameraOperation('panning')
10794
11068
  return
10795
11069
  }
10796
11070
 
@@ -0,0 +1,85 @@
1
+ import { LegacyMigrations, MigrationSequence } from '@tldraw/store'
2
+ import {
3
+ RecordProps,
4
+ TLAsset,
5
+ TLAssetId,
6
+ TLPropsMigrations,
7
+ TLUnknownAsset,
8
+ } from '@tldraw/tlschema'
9
+ import type { Editor } from '../Editor'
10
+
11
+ /** @public */
12
+ export interface TLAssetUtilConstructor<
13
+ T extends TLAsset = TLAsset,
14
+ U extends AssetUtil<T> = AssetUtil<T>,
15
+ > {
16
+ new (editor: Editor): U
17
+ type: T['type']
18
+ props?: RecordProps<T>
19
+ migrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence
20
+ }
21
+
22
+ /**
23
+ * Abstract base class for defining asset-type-specific behavior.
24
+ *
25
+ * Each asset type (image, video, bookmark, etc.) has a corresponding AssetUtil that handles
26
+ * type-specific operations like determining supported MIME types and creating assets from files.
27
+ *
28
+ * @public
29
+ */
30
+ export abstract class AssetUtil<Asset extends TLAsset = TLAsset> {
31
+ /** Configure this asset util's {@link AssetUtil.options | `options`}. */
32
+ static configure<T extends TLAssetUtilConstructor<any, any>>(
33
+ this: T,
34
+ options: T extends new (...args: any[]) => { options: infer Options } ? Partial<Options> : never
35
+ ): T {
36
+ // @ts-expect-error -- typescript has no idea what's going on here but it's fine
37
+ return class extends this {
38
+ // @ts-expect-error
39
+ options = { ...this.options, ...options }
40
+ }
41
+ }
42
+
43
+ constructor(public editor: Editor) {}
44
+
45
+ /**
46
+ * Options for this asset util. Override this to provide customization options for your asset.
47
+ * Use {@link AssetUtil.configure} to customize existing asset utils.
48
+ */
49
+ options = {}
50
+
51
+ static props?: RecordProps<TLUnknownAsset>
52
+ static migrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence
53
+
54
+ /**
55
+ * The type of the asset util, which should match the asset's type.
56
+ */
57
+ static type: string
58
+
59
+ /**
60
+ * Get the default props for an asset of this type.
61
+ */
62
+ abstract getDefaultProps(): Asset['props']
63
+
64
+ /**
65
+ * Get the MIME types that this asset type supports.
66
+ * Return an empty array if this asset type doesn't support files (e.g. bookmarks).
67
+ */
68
+ getSupportedMimeTypes(): readonly string[] {
69
+ return []
70
+ }
71
+
72
+ /**
73
+ * Check whether this asset type accepts a given MIME type.
74
+ */
75
+ acceptsMimeType(mimeType: string): boolean {
76
+ return this.getSupportedMimeTypes().includes(mimeType)
77
+ }
78
+
79
+ /**
80
+ * Create an asset from a file. Return null if this asset type can't handle the file.
81
+ */
82
+ async getAssetFromFile(_file: File, _assetId: TLAssetId): Promise<Asset | null> {
83
+ return null
84
+ }
85
+ }
@@ -1,8 +1,15 @@
1
- import { TLParentId, TLShape, TLShapeId, createShapeId, toRichText } from '@tldraw/tlschema'
1
+ import {
2
+ TLFontFace,
3
+ TLParentId,
4
+ TLShape,
5
+ TLShapeId,
6
+ createShapeId,
7
+ toRichText,
8
+ } from '@tldraw/tlschema'
2
9
  import { IndexKey } from '@tldraw/utils'
3
10
  import { Mock, Mocked, vi } from 'vitest'
4
11
  import { Editor } from '../../Editor'
5
- import { FontManager, TLFontFace } from './FontManager'
12
+ import { FontManager } from './FontManager'
6
13
 
7
14
  // Mock the Editor class
8
15
  vi.mock('../../Editor')