@tldraw/editor 4.6.0-next.793daa9fe264 → 4.6.0-next.a31a8a6b83f2

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 (110) hide show
  1. package/dist-cjs/index.d.ts +376 -98
  2. package/dist-cjs/index.js +12 -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/default-components/CanvasShapeIndicators.js +3 -3
  7. package/dist-cjs/lib/components/default-components/CanvasShapeIndicators.js.map +2 -2
  8. package/dist-cjs/lib/config/createTLStore.js +7 -0
  9. package/dist-cjs/lib/config/createTLStore.js.map +2 -2
  10. package/dist-cjs/lib/config/defaultAssets.js +36 -0
  11. package/dist-cjs/lib/config/defaultAssets.js.map +7 -0
  12. package/dist-cjs/lib/editor/Editor.js +200 -5
  13. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  14. package/dist-cjs/lib/editor/assets/AssetUtil.js +66 -0
  15. package/dist-cjs/lib/editor/assets/AssetUtil.js.map +7 -0
  16. package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
  17. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +106 -0
  18. package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +7 -0
  19. package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js +586 -0
  20. package/dist-cjs/lib/editor/managers/ThemeManager/defaultThemes.js.map +7 -0
  21. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +6 -4
  22. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  23. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +9 -0
  24. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  25. package/dist-cjs/lib/editor/types/SvgExportContext.js.map +2 -2
  26. package/dist-cjs/lib/editor/types/external-content.js.map +1 -1
  27. package/dist-cjs/lib/exports/getSvgJsx.js +12 -7
  28. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  29. package/dist-cjs/lib/globals/environment.js +18 -1
  30. package/dist-cjs/lib/globals/environment.js.map +2 -2
  31. package/dist-cjs/lib/hooks/{useIsDarkMode.js → useColorMode.js} +14 -10
  32. package/dist-cjs/lib/hooks/useColorMode.js.map +7 -0
  33. package/dist-cjs/lib/hooks/useCursor.js +3 -7
  34. package/dist-cjs/lib/hooks/useCursor.js.map +2 -2
  35. package/dist-cjs/lib/hooks/useDarkMode.js +4 -4
  36. package/dist-cjs/lib/hooks/useDarkMode.js.map +2 -2
  37. package/dist-cjs/lib/utils/reparenting.js +2 -1
  38. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  39. package/dist-cjs/lib/utils/richText.js.map +2 -2
  40. package/dist-cjs/version.js +3 -3
  41. package/dist-cjs/version.js.map +1 -1
  42. package/dist-esm/index.d.mts +376 -98
  43. package/dist-esm/index.mjs +13 -6
  44. package/dist-esm/index.mjs.map +2 -2
  45. package/dist-esm/lib/TldrawEditor.mjs +58 -12
  46. package/dist-esm/lib/TldrawEditor.mjs.map +3 -3
  47. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs +3 -3
  48. package/dist-esm/lib/components/default-components/CanvasShapeIndicators.mjs.map +2 -2
  49. package/dist-esm/lib/config/createTLStore.mjs +10 -1
  50. package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
  51. package/dist-esm/lib/config/defaultAssets.mjs +16 -0
  52. package/dist-esm/lib/config/defaultAssets.mjs.map +7 -0
  53. package/dist-esm/lib/editor/Editor.mjs +200 -5
  54. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  55. package/dist-esm/lib/editor/assets/AssetUtil.mjs +46 -0
  56. package/dist-esm/lib/editor/assets/AssetUtil.mjs.map +7 -0
  57. package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
  58. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +88 -0
  59. package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +7 -0
  60. package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs +568 -0
  61. package/dist-esm/lib/editor/managers/ThemeManager/defaultThemes.mjs.map +7 -0
  62. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +6 -4
  63. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  64. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +9 -0
  65. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  66. package/dist-esm/lib/editor/types/SvgExportContext.mjs.map +2 -2
  67. package/dist-esm/lib/exports/getSvgJsx.mjs +12 -10
  68. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  69. package/dist-esm/lib/globals/environment.mjs +18 -1
  70. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  71. package/dist-esm/lib/hooks/useColorMode.mjs +19 -0
  72. package/dist-esm/lib/hooks/useColorMode.mjs.map +7 -0
  73. package/dist-esm/lib/hooks/useCursor.mjs +3 -7
  74. package/dist-esm/lib/hooks/useCursor.mjs.map +2 -2
  75. package/dist-esm/lib/hooks/useDarkMode.mjs +4 -4
  76. package/dist-esm/lib/hooks/useDarkMode.mjs.map +2 -2
  77. package/dist-esm/lib/utils/reparenting.mjs +2 -1
  78. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  79. package/dist-esm/lib/utils/richText.mjs.map +2 -2
  80. package/dist-esm/version.mjs +3 -3
  81. package/dist-esm/version.mjs.map +1 -1
  82. package/editor.css +0 -33
  83. package/package.json +7 -7
  84. package/src/index.ts +8 -6
  85. package/src/lib/TldrawEditor.tsx +90 -13
  86. package/src/lib/components/default-components/CanvasShapeIndicators.tsx +3 -3
  87. package/src/lib/config/createTLStore.ts +22 -1
  88. package/src/lib/config/defaultAssets.ts +19 -0
  89. package/src/lib/editor/Editor.ts +285 -27
  90. package/src/lib/editor/assets/AssetUtil.ts +85 -0
  91. package/src/lib/editor/managers/FontManager/FontManager.test.ts +9 -2
  92. package/src/lib/editor/managers/FontManager/FontManager.ts +1 -67
  93. package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +116 -0
  94. package/src/lib/editor/managers/ThemeManager/defaultThemes.ts +605 -0
  95. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +23 -29
  96. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +5 -3
  97. package/src/lib/editor/shapes/ShapeUtil.ts +26 -1
  98. package/src/lib/editor/types/SvgExportContext.tsx +5 -0
  99. package/src/lib/editor/types/external-content.ts +1 -0
  100. package/src/lib/exports/getSvgJsx.tsx +21 -15
  101. package/src/lib/globals/environment.ts +18 -0
  102. package/src/lib/hooks/{useIsDarkMode.ts → useColorMode.ts} +9 -5
  103. package/src/lib/hooks/useCursor.ts +3 -7
  104. package/src/lib/hooks/useDarkMode.ts +4 -4
  105. package/src/lib/utils/reparenting.ts +6 -2
  106. package/src/lib/utils/richText.ts +1 -1
  107. package/src/version.ts +3 -3
  108. package/dist-cjs/lib/hooks/useIsDarkMode.js.map +0 -7
  109. package/dist-esm/lib/hooks/useIsDarkMode.mjs +0 -15
  110. 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'
@@ -156,6 +161,7 @@ import { ScribbleManager } from './managers/ScribbleManager/ScribbleManager'
156
161
  import { SnapManager } from './managers/SnapManager/SnapManager'
157
162
  import { SpatialIndexManager } from './managers/SpatialIndexManager/SpatialIndexManager'
158
163
  import { TextManager } from './managers/TextManager/TextManager'
164
+ import { resolveThemes, ThemeManager } from './managers/ThemeManager/ThemeManager'
159
165
  import { TickManager } from './managers/TickManager/TickManager'
160
166
  import { UserPreferencesManager } from './managers/UserPreferencesManager/UserPreferencesManager'
161
167
  import {
@@ -214,14 +220,13 @@ export interface TLEditorOptions {
214
220
  */
215
221
  bindingUtils: readonly TLAnyBindingUtilConstructor[]
216
222
  /**
217
- * An array of tools to use in the editor. These will be used to handle events and manage user interactions in the editor.
223
+ * An array of asset utils to use in the editor. These will be used to handle asset-type-specific behavior.
218
224
  */
219
- tools: readonly TLStateNodeConstructor[]
225
+ assetUtils?: readonly TLAnyAssetUtilConstructor[]
220
226
  /**
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.
227
+ * An array of tools to use in the editor. These will be used to handle events and manage user interactions in the editor.
223
228
  */
224
- getContainer(): HTMLElement
229
+ tools: readonly TLStateNodeConstructor[]
225
230
  /**
226
231
  * A user defined externally to replace the default user.
227
232
  */
@@ -234,25 +239,13 @@ export interface TLEditorOptions {
234
239
  * Whether to automatically focus the editor when it mounts.
235
240
  */
236
241
  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
242
  licenseKey?: string
255
243
  fontAssetUrls?: { [key: string]: string | undefined }
244
+ /**
245
+ * Should return a containing html element which has all the styles applied to the editor. If not
246
+ * given, the body element will be used.
247
+ */
248
+ getContainer(): HTMLElement
256
249
  /**
257
250
  * Provides a way to hide shapes.
258
251
  *
@@ -272,6 +265,41 @@ export interface TLEditorOptions {
272
265
  shape: TLShape,
273
266
  editor: Editor
274
267
  ): 'visible' | 'hidden' | 'inherit' | null | undefined
268
+ /**
269
+ * Named theme definitions for the editor. Each theme contains shared
270
+ * properties (font size, line height, stroke width) and color palettes
271
+ * for both light and dark modes.
272
+ */
273
+ themes?: Partial<TLThemes>
274
+ /**
275
+ * The id of the initially active theme. Defaults to `'default'`.
276
+ */
277
+ initialTheme?: TLThemeId
278
+ /**
279
+ * The editor's color scheme preference, controls the default color mode. Defaults to `'light'`.
280
+ *
281
+ * - `'light'` - Always use light mode.
282
+ * - `'dark'` - Always use dark mode.
283
+ * - `'system'` - Follow the OS color scheme preference.
284
+ */
285
+ colorScheme?: 'light' | 'dark' | 'system'
286
+ /**
287
+ * Additional configuration options for the tldraw editor.
288
+ */
289
+ options?: Partial<TldrawOptions>
290
+ // --- Deprecated ----
291
+ /**
292
+ * Options for the editor's camera.
293
+ *
294
+ * @deprecated Use `options.cameraOptions` instead. This will be removed in a future release.
295
+ */
296
+ cameraOptions?: Partial<TLCameraOptions>
297
+ /**
298
+ * Text options for the editor.
299
+ *
300
+ * @deprecated Use `options.text` instead. This prop will be removed in a future release.
301
+ */
302
+ textOptions?: TLTextOptions
275
303
  }
276
304
 
277
305
  /**
@@ -300,6 +328,7 @@ export class Editor extends EventEmitter<TLEventMap> {
300
328
  user,
301
329
  shapeUtils,
302
330
  bindingUtils,
331
+ assetUtils: assetUtilConstructors,
303
332
  tools,
304
333
  getContainer,
305
334
  // needs to be here for backwards compatibility with TldrawEditor
@@ -307,13 +336,15 @@ export class Editor extends EventEmitter<TLEventMap> {
307
336
  cameraOptions,
308
337
  initialState,
309
338
  autoFocus,
310
- inferDarkMode,
311
339
  options: _options,
312
340
  // needs to be here for backwards compatibility with TldrawEditor
313
341
  // eslint-disable-next-line @typescript-eslint/no-deprecated
314
342
  textOptions: _textOptions,
315
343
  getShapeVisibility,
344
+ colorScheme,
316
345
  fontAssetUrls,
346
+ themes,
347
+ initialTheme,
317
348
  }: TLEditorOptions) {
318
349
  super()
319
350
 
@@ -348,19 +379,24 @@ export class Editor extends EventEmitter<TLEventMap> {
348
379
  ...options?.camera,
349
380
  })
350
381
 
382
+ this.getContainer = getContainer
383
+
351
384
  this._textOptions = atom('text options', options?.text ?? null)
352
385
 
353
- this.user = new UserPreferencesManager(user ?? createTLCurrentUser(), inferDarkMode ?? false)
386
+ this.user = new UserPreferencesManager(user ?? createTLCurrentUser(), colorScheme ?? 'light')
354
387
  this.disposables.add(() => this.user.dispose())
355
388
 
356
- this.getContainer = getContainer
357
-
358
389
  this.textMeasure = new TextManager(this)
359
390
  this.disposables.add(() => this.textMeasure.dispose())
360
391
 
361
- this.fonts = new FontManager(this, fontAssetUrls)
392
+ this._themeManager = new ThemeManager(this, {
393
+ themes: resolveThemes(themes),
394
+ initial: initialTheme ?? 'default',
395
+ })
396
+ this.disposables.add(() => this._themeManager.dispose())
362
397
 
363
398
  this._tickManager = new TickManager(this)
399
+ this.disposables.add(() => this._tickManager.dispose())
364
400
  this.disposables.add(() => {
365
401
  // Reset camera state to 'idle' so the store isn't left stuck at 'moving'
366
402
  // when tick events stop (e.g. React strict mode disposes while camera is moving)
@@ -368,6 +404,8 @@ export class Editor extends EventEmitter<TLEventMap> {
368
404
  this._setCameraState('idle')
369
405
  })
370
406
 
407
+ this.fonts = new FontManager(this, fontAssetUrls)
408
+
371
409
  this.inputs = new InputsManager(this)
372
410
 
373
411
  class NewRoot extends RootState {
@@ -406,6 +444,17 @@ export class Editor extends EventEmitter<TLEventMap> {
406
444
  this.shapeUtils = _shapeUtils
407
445
  this.styleProps = _styleProps
408
446
 
447
+ const _shapeUtilsByAssetType = {} as Record<string, ShapeUtil<any>>
448
+ for (const Util of allShapeUtils) {
449
+ const assetTypes = Util.handledAssetTypes
450
+ if (assetTypes) {
451
+ for (const assetType of assetTypes) {
452
+ _shapeUtilsByAssetType[assetType] = _shapeUtils[Util.type]
453
+ }
454
+ }
455
+ }
456
+ this._shapeUtilsByAssetType = _shapeUtilsByAssetType
457
+
409
458
  const allBindingUtils = checkBindings(bindingUtils)
410
459
  const _bindingUtils = {} as Record<string, BindingUtil<any>>
411
460
  for (const Util of allBindingUtils) {
@@ -414,6 +463,17 @@ export class Editor extends EventEmitter<TLEventMap> {
414
463
  }
415
464
  this.bindingUtils = _bindingUtils
416
465
 
466
+ // Asset utils
467
+ if (assetUtilConstructors) {
468
+ const allAssetUtils = checkAssets(assetUtilConstructors)
469
+ const _assetUtils = {} as Record<string, AssetUtil<any>>
470
+ for (const Util of allAssetUtils) {
471
+ const util = new Util(this)
472
+ _assetUtils[Util.type] = util
473
+ }
474
+ this.assetUtils = _assetUtils
475
+ }
476
+
417
477
  // Tools.
418
478
  // Accept tools from constructor parameters which may not conflict with the root note's default or
419
479
  // "baked in" tools, select and zoom.
@@ -948,6 +1008,11 @@ export class Editor extends EventEmitter<TLEventMap> {
948
1008
  */
949
1009
  readonly snaps: SnapManager
950
1010
 
1011
+ /**
1012
+ * A manager for the spatial index, tracking where shapes exist on the canvas.
1013
+ *
1014
+ * @internal
1015
+ */
951
1016
  private readonly _spatialIndex: SpatialIndexManager
952
1017
 
953
1018
  /**
@@ -965,6 +1030,13 @@ export class Editor extends EventEmitter<TLEventMap> {
965
1030
  */
966
1031
  readonly user: UserPreferencesManager
967
1032
 
1033
+ /**
1034
+ * A manager for the editor's themes.
1035
+ *
1036
+ * @internal
1037
+ */
1038
+ private readonly _themeManager: ThemeManager
1039
+
968
1040
  /**
969
1041
  * A helper for measuring text.
970
1042
  *
@@ -1045,13 +1117,134 @@ export class Editor extends EventEmitter<TLEventMap> {
1045
1117
  * @public
1046
1118
  */
1047
1119
  dispose() {
1120
+ // Stop any in-progress camera animations and following before
1121
+ // running disposables, so their cleanup listeners fire first
1122
+ this.stopCameraAnimation()
1123
+ if (this.getInstanceState().followingUserId) {
1124
+ this.stopFollowingUser()
1125
+ }
1126
+
1048
1127
  this.disposables.forEach((dispose) => dispose())
1049
1128
  this.disposables.clear()
1129
+
1130
+ // Clear any open menus for this editor's context
1131
+ this.menus.clearOpenMenus()
1132
+
1050
1133
  this.store.dispose()
1051
1134
  this.isDisposed = true
1052
1135
  this.emit('dispose')
1053
1136
  }
1054
1137
 
1138
+ /* ------------------ Themes (shadowing the theme manager) ------------------ */
1139
+
1140
+ /**
1141
+ * Get the current color mode (`'light'` or `'dark'`), based on the user's dark mode preference.
1142
+ *
1143
+ * @public
1144
+ */
1145
+ getColorMode(): 'light' | 'dark' {
1146
+ return this._themeManager.getColorMode()
1147
+ }
1148
+
1149
+ /**
1150
+ * Set the color mode. Note that this is a convenience method that passes the mode to
1151
+ * `user.updateUserPreferences`, which is the source of truth for the user's color mode preference.
1152
+ *
1153
+ * @public
1154
+ */
1155
+ setColorMode(mode: 'light' | 'dark') {
1156
+ this.user.updateUserPreferences({ colorScheme: mode })
1157
+ return this
1158
+ }
1159
+
1160
+ /**
1161
+ * Get the id of the current theme.
1162
+ *
1163
+ * @public
1164
+ */
1165
+ getCurrentThemeId(): TLThemeId {
1166
+ return this._themeManager.getCurrentThemeId()
1167
+ }
1168
+
1169
+ /**
1170
+ * Get the current theme definition.
1171
+ *
1172
+ * @public
1173
+ */
1174
+ getCurrentTheme(): TLTheme {
1175
+ return this._themeManager.getCurrentTheme()
1176
+ }
1177
+
1178
+ /**
1179
+ * Set the current theme by id.
1180
+ *
1181
+ * @public
1182
+ */
1183
+ setCurrentTheme(id: TLThemeId) {
1184
+ this._themeManager.setCurrentTheme(id)
1185
+ return this
1186
+ }
1187
+
1188
+ /**
1189
+ * Get all registered theme definitions.
1190
+ *
1191
+ * @public
1192
+ */
1193
+ getThemes(): TLThemes {
1194
+ return this._themeManager.getThemes()
1195
+ }
1196
+
1197
+ /**
1198
+ * Get a single theme definition by id.
1199
+ *
1200
+ * @public
1201
+ */
1202
+ getTheme(id: TLThemeId): TLTheme | undefined {
1203
+ return this._themeManager.getTheme(id)
1204
+ }
1205
+
1206
+ /**
1207
+ * Replace all theme definitions, or update them via a callback that receives a deep copy.
1208
+ * The `'default'` theme must always be present in the result.
1209
+ *
1210
+ * @example
1211
+ * ```ts
1212
+ * // Replace all themes
1213
+ * editor.updateThemes({ default: myDefaultTheme, ocean: myOceanTheme })
1214
+ *
1215
+ * // Update via callback
1216
+ * editor.updateThemes((themes) => {
1217
+ * delete themes.ocean
1218
+ * return themes
1219
+ * })
1220
+ * ```
1221
+ *
1222
+ * @public
1223
+ */
1224
+ updateThemes(themes: TLThemes | ((themes: TLThemes) => TLThemes)) {
1225
+ this._themeManager.updateThemes(themes)
1226
+ return this
1227
+ }
1228
+
1229
+ /**
1230
+ * Register or update a single theme definition. The theme is keyed by its `id` property.
1231
+ *
1232
+ * @example
1233
+ * ```ts
1234
+ * // Override a property on the default theme
1235
+ * editor.updateTheme({ ...editor.getTheme('default')!, fontSize: 24 })
1236
+ *
1237
+ * // Register a new theme
1238
+ * editor.updateTheme({ id: 'ocean', ...myOceanTheme })
1239
+ * ```
1240
+ *
1241
+ * @public
1242
+ */
1243
+ updateTheme(theme: TLTheme) {
1244
+ this._themeManager.updateTheme(theme)
1245
+ return this
1246
+ }
1247
+
1055
1248
  /* ------------------- Shape Utils ------------------ */
1056
1249
 
1057
1250
  /**
@@ -1061,6 +1254,9 @@ export class Editor extends EventEmitter<TLEventMap> {
1061
1254
  */
1062
1255
  shapeUtils: { readonly [K in string]?: ShapeUtil<TLShape> }
1063
1256
 
1257
+ /** @internal */
1258
+ private _shapeUtilsByAssetType: { readonly [K in string]?: ShapeUtil<TLShape> } = {}
1259
+
1064
1260
  styleProps: { [key: string]: Map<StyleProp<any>, string> }
1065
1261
 
1066
1262
  /**
@@ -1103,6 +1299,18 @@ export class Editor extends EventEmitter<TLEventMap> {
1103
1299
  return hasOwnProperty(this.shapeUtils, type)
1104
1300
  }
1105
1301
 
1302
+ /**
1303
+ * Get the shape util that handles the given asset type.
1304
+ * Returns the shape util whose {@link ShapeUtil.handledAssetTypes} includes
1305
+ * the given asset type, or undefined if none matches.
1306
+ *
1307
+ * @param assetType - The asset type string.
1308
+ * @public
1309
+ */
1310
+ getShapeUtilForAssetType(assetType: string): ShapeUtil | undefined {
1311
+ return getOwnProperty(this._shapeUtilsByAssetType, assetType)
1312
+ }
1313
+
1106
1314
  /* ------------------- Binding Utils ------------------ */
1107
1315
  /**
1108
1316
  * A map of shape utility classes (TLShapeUtils) by shape type.
@@ -1138,6 +1346,56 @@ export class Editor extends EventEmitter<TLEventMap> {
1138
1346
  return bindingUtil
1139
1347
  }
1140
1348
 
1349
+ /* ------------------- Asset Utils ------------------ */
1350
+
1351
+ /**
1352
+ * A map of asset utility classes by asset type.
1353
+ *
1354
+ * @public
1355
+ */
1356
+ assetUtils: { readonly [K in string]?: AssetUtil<TLAsset> } = {}
1357
+
1358
+ /**
1359
+ * Get an asset util from an asset or asset type.
1360
+ *
1361
+ * @param arg - An asset, asset type string, or object with type.
1362
+ *
1363
+ * @public
1364
+ */
1365
+ getAssetUtil<S extends TLAsset>(asset: S | { type: S['type'] }): AssetUtil<S>
1366
+ getAssetUtil(type: string): AssetUtil
1367
+ getAssetUtil(arg: string | { type: string }) {
1368
+ const type = typeof arg === 'string' ? arg : arg.type
1369
+ const assetUtil = getOwnProperty(this.assetUtils, type)
1370
+ assert(assetUtil, `No asset util found for type "${type}"`)
1371
+ return assetUtil
1372
+ }
1373
+
1374
+ /**
1375
+ * Returns true if the editor has an asset util for the given asset type.
1376
+ *
1377
+ * @public
1378
+ */
1379
+ hasAssetUtil(arg: string | { type: string }): boolean {
1380
+ const type = typeof arg === 'string' ? arg : arg.type
1381
+ return hasOwnProperty(this.assetUtils, type)
1382
+ }
1383
+
1384
+ /**
1385
+ * Get the asset util that accepts the given MIME type.
1386
+ * Returns null if no registered asset util accepts the MIME type.
1387
+ *
1388
+ * @public
1389
+ */
1390
+ getAssetUtilForMimeType(mimeType: string): AssetUtil | null {
1391
+ for (const util of Object.values(this.assetUtils)) {
1392
+ if (util && util.acceptsMimeType(mimeType)) {
1393
+ return util
1394
+ }
1395
+ }
1396
+ return null
1397
+ }
1398
+
1141
1399
  /* --------------------- History -------------------- */
1142
1400
 
1143
1401
  /**
@@ -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')
@@ -1,6 +1,6 @@
1
1
  import { computed, EMPTY_ARRAY, transact } from '@tldraw/state'
2
2
  import { AtomMap } from '@tldraw/store'
3
- import { TLShape, TLShapeId } from '@tldraw/tlschema'
3
+ import { TLFontFace, TLShape, TLShapeId } from '@tldraw/tlschema'
4
4
  import {
5
5
  areArraysShallowEqual,
6
6
  compact,
@@ -10,72 +10,6 @@ import {
10
10
  } from '@tldraw/utils'
11
11
  import type { Editor } from '../../Editor'
12
12
 
13
- /**
14
- * Represents the `src` property of a {@link TLFontFace}.
15
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src | `src`} for details of the properties here.
16
- * @public
17
- */
18
- export interface TLFontFaceSource {
19
- /**
20
- * A URL from which to load the font. If the value here is a key in
21
- * {@link tldraw#TLEditorAssetUrls.fonts}, the value from there will be used instead.
22
- */
23
- url: string
24
- format?: string
25
- tech?: string
26
- }
27
-
28
- /**
29
- * A font face that can be used in the editor. The properties of this are largely the same as the
30
- * ones in the
31
- * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face | css `@font-face` rule}.
32
- * @public
33
- */
34
- export interface TLFontFace {
35
- /**
36
- * How this font can be referred to in CSS.
37
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-family | `font-family`}.
38
- */
39
- readonly family: string
40
- /**
41
- * The source of the font. This
42
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src | `src`}.
43
- */
44
- readonly src: TLFontFaceSource
45
- /**
46
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/ascent-override | `ascent-override`}.
47
- */
48
- readonly ascentOverride?: string
49
- /**
50
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/descent-override | `descent-override`}.
51
- */
52
- readonly descentOverride?: string
53
- /**
54
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-stretch | `font-stretch`}.
55
- */
56
- readonly stretch?: string
57
- /**
58
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-style | `font-style`}.
59
- */
60
- readonly style?: string
61
- /**
62
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-weight | `font-weight`}.
63
- */
64
- readonly weight?: string
65
- /**
66
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-feature-settings | `font-feature-settings`}.
67
- */
68
- readonly featureSettings?: string
69
- /**
70
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/line-gap-override | `line-gap-override`}.
71
- */
72
- readonly lineGapOverride?: string
73
- /**
74
- * See {@link https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range | `unicode-range`}.
75
- */
76
- readonly unicodeRange?: string
77
- }
78
-
79
13
  interface FontState {
80
14
  readonly state: 'loading' | 'ready' | 'error'
81
15
  readonly instance: FontFace