@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
@@ -61,7 +61,7 @@ describe('UserPreferencesManager', () => {
61
61
  // Test when window.matchMedia is not available
62
62
  delete (window as any).matchMedia
63
63
 
64
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
64
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
65
65
 
66
66
  expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
67
67
 
@@ -76,7 +76,7 @@ describe('UserPreferencesManager', () => {
76
76
  removeEventListener: vi.fn(),
77
77
  })
78
78
 
79
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
79
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
80
80
 
81
81
  expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
82
82
  })
@@ -88,7 +88,7 @@ describe('UserPreferencesManager', () => {
88
88
  removeEventListener: vi.fn(),
89
89
  })
90
90
 
91
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
91
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'system')
92
92
 
93
93
  expect(userPreferencesManager.systemColorScheme.get()).toBe('dark')
94
94
  })
@@ -103,7 +103,7 @@ describe('UserPreferencesManager', () => {
103
103
  removeEventListener: mockRemoveEventListener,
104
104
  })
105
105
 
106
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
106
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'system')
107
107
 
108
108
  expect(mockAddEventListener).toHaveBeenCalledWith('change', expect.any(Function))
109
109
  })
@@ -123,7 +123,7 @@ describe('UserPreferencesManager', () => {
123
123
  removeEventListener: vi.fn(),
124
124
  })
125
125
 
126
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
126
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'system')
127
127
 
128
128
  expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
129
129
 
@@ -141,7 +141,7 @@ describe('UserPreferencesManager', () => {
141
141
  delete (global as any).window
142
142
 
143
143
  expect(() => {
144
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
144
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
145
145
  }).not.toThrow()
146
146
 
147
147
  global.window = originalWindow
@@ -158,14 +158,14 @@ describe('UserPreferencesManager', () => {
158
158
  removeEventListener: mockRemoveEventListener,
159
159
  })
160
160
 
161
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
161
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'system')
162
162
  userPreferencesManager.dispose()
163
163
 
164
164
  expect(mockRemoveEventListener).toHaveBeenCalledWith('change', expect.any(Function))
165
165
  })
166
166
 
167
167
  it('should call all disposables', () => {
168
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
168
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
169
169
 
170
170
  const mockDisposable1 = vi.fn()
171
171
  const mockDisposable2 = vi.fn()
@@ -182,7 +182,7 @@ describe('UserPreferencesManager', () => {
182
182
 
183
183
  describe('updateUserPreferences', () => {
184
184
  beforeEach(() => {
185
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
185
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
186
186
  })
187
187
 
188
188
  it('should update user preferences with partial data', () => {
@@ -216,7 +216,7 @@ describe('UserPreferencesManager', () => {
216
216
 
217
217
  describe('getUserPreferences', () => {
218
218
  beforeEach(() => {
219
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
219
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
220
220
  })
221
221
 
222
222
  it('should return complete user preferences with computed values', () => {
@@ -255,7 +255,7 @@ describe('UserPreferencesManager', () => {
255
255
 
256
256
  describe('getIsDarkMode', () => {
257
257
  beforeEach(() => {
258
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
258
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
259
259
  })
260
260
 
261
261
  it('should return true when colorScheme is dark', () => {
@@ -282,24 +282,18 @@ describe('UserPreferencesManager', () => {
282
282
  expect(userPreferencesManager.getIsDarkMode()).toBe(true)
283
283
  })
284
284
 
285
- it('should use inferDarkMode when colorScheme is undefined', () => {
285
+ it('should default to light when colorScheme is undefined', () => {
286
286
  userPreferencesAtom.set({ ...mockUserPreferences, colorScheme: undefined })
287
287
 
288
- // With inferDarkMode = true
289
- const managerWithInfer = new UserPreferencesManager(mockUser, true)
290
- managerWithInfer.systemColorScheme.set('dark')
291
- expect(managerWithInfer.getIsDarkMode()).toBe(true)
292
-
293
- // With inferDarkMode = false
294
- const managerWithoutInfer = new UserPreferencesManager(mockUser, false)
295
- managerWithoutInfer.systemColorScheme.set('dark')
296
- expect(managerWithoutInfer.getIsDarkMode()).toBe(false)
288
+ const manager = new UserPreferencesManager(mockUser, 'light')
289
+ manager.systemColorScheme.set('dark')
290
+ expect(manager.getIsDarkMode()).toBe(false)
297
291
  })
298
292
  })
299
293
 
300
294
  describe('individual preference getters', () => {
301
295
  beforeEach(() => {
302
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
296
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
303
297
  })
304
298
 
305
299
  describe('getId', () => {
@@ -499,7 +493,7 @@ describe('UserPreferencesManager', () => {
499
493
 
500
494
  describe('reactive behavior', () => {
501
495
  beforeEach(() => {
502
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
496
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
503
497
  })
504
498
 
505
499
  it('should react to user preferences changes', () => {
@@ -533,7 +527,7 @@ describe('UserPreferencesManager', () => {
533
527
 
534
528
  describe('edge cases and error handling', () => {
535
529
  beforeEach(() => {
536
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
530
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
537
531
  })
538
532
 
539
533
  it('should handle undefined user preferences gracefully', () => {
@@ -589,14 +583,14 @@ describe('UserPreferencesManager', () => {
589
583
  mockMatchMedia.mockReturnValue(null)
590
584
 
591
585
  expect(() => {
592
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
586
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
593
587
  }).not.toThrow()
594
588
 
595
589
  expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
596
590
  })
597
591
 
598
592
  it('should handle dispose gracefully in all cases', () => {
599
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
593
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
600
594
 
601
595
  // Should not throw even if dispose is called multiple times
602
596
  expect(() => userPreferencesManager.dispose()).not.toThrow()
@@ -608,7 +602,7 @@ describe('UserPreferencesManager', () => {
608
602
  const originalWindow = global.window
609
603
  delete (global as any).window
610
604
 
611
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
605
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
612
606
 
613
607
  expect(() => userPreferencesManager.dispose()).not.toThrow()
614
608
  expect(userPreferencesManager.disposables.size).toBe(0)
@@ -619,7 +613,7 @@ describe('UserPreferencesManager', () => {
619
613
 
620
614
  describe('integration scenarios', () => {
621
615
  it('should work with real-world preference scenarios', () => {
622
- userPreferencesManager = new UserPreferencesManager(mockUser, true)
616
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
623
617
 
624
618
  // User starts with system preference
625
619
  userPreferencesManager.updateUserPreferences({ colorScheme: 'system' })
@@ -641,7 +635,7 @@ describe('UserPreferencesManager', () => {
641
635
  })
642
636
 
643
637
  it('should handle preference updates with multiple fields', () => {
644
- userPreferencesManager = new UserPreferencesManager(mockUser, false)
638
+ userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
645
639
 
646
640
  const updates = {
647
641
  name: 'New User',
@@ -12,7 +12,7 @@ export class UserPreferencesManager {
12
12
  }
13
13
  constructor(
14
14
  private readonly user: TLCurrentUser,
15
- private readonly inferDarkMode: boolean
15
+ private readonly colorScheme: 'light' | 'dark' | 'system'
16
16
  ) {
17
17
  if (typeof window === 'undefined' || !getGlobalWindow().matchMedia) return
18
18
 
@@ -57,7 +57,9 @@ export class UserPreferencesManager {
57
57
  }
58
58
 
59
59
  @computed getIsDarkMode() {
60
- switch (this.user.userPreferences.get().colorScheme) {
60
+ const userColorScheme = this.user.userPreferences.get().colorScheme
61
+ const scheme = userColorScheme ?? this.colorScheme
62
+ switch (scheme) {
61
63
  case 'dark':
62
64
  return true
63
65
  case 'light':
@@ -65,7 +67,7 @@ export class UserPreferencesManager {
65
67
  case 'system':
66
68
  return this.systemColorScheme.get() === 'dark'
67
69
  default:
68
- return this.inferDarkMode ? this.systemColorScheme.get() === 'dark' : false
70
+ return false
69
71
  }
70
72
  }
71
73
 
@@ -3,6 +3,7 @@ import { EMPTY_ARRAY } from '@tldraw/state'
3
3
  import { LegacyMigrations, MigrationSequence } from '@tldraw/store'
4
4
  import {
5
5
  RecordProps,
6
+ TLAsset,
6
7
  TLHandle,
7
8
  TLParentId,
8
9
  TLPropsMigrations,
@@ -11,14 +12,15 @@ import {
11
12
  TLShapeId,
12
13
  TLShapePartial,
13
14
  TLUnknownShape,
15
+ VecModel,
14
16
  } from '@tldraw/tlschema'
17
+ import { TLFontFace } from '@tldraw/tlschema'
15
18
  import { IndexKey } from '@tldraw/utils'
16
19
  import { ReactElement } from 'react'
17
20
  import { Box, SelectionHandle } from '../../primitives/Box'
18
21
  import { Geometry2d } from '../../primitives/geometry/Geometry2d'
19
22
  import { Vec } from '../../primitives/Vec'
20
23
  import type { Editor } from '../Editor'
21
- import { TLFontFace } from '../managers/FontManager/FontManager'
22
24
  import { BoundsSnapGeometry } from '../managers/SnapManager/BoundsSnaps'
23
25
  import { HandleSnapGeometry } from '../managers/SnapManager/HandleSnaps'
24
26
  import { TLClickEventInfo } from '../types/event-types'
@@ -31,6 +33,7 @@ export interface TLShapeUtilConstructor<T extends TLShape, U extends ShapeUtil<T
31
33
  type: T['type']
32
34
  props?: RecordProps<T>
33
35
  migrations?: LegacyMigrations | TLPropsMigrations | MigrationSequence
36
+ handledAssetTypes?: readonly string[]
34
37
  }
35
38
 
36
39
  /**
@@ -168,6 +171,16 @@ export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
168
171
  */
169
172
  static type: string
170
173
 
174
+ /**
175
+ * The asset types that this shape can be created from.
176
+ * When a file is dropped on the canvas, the editor finds the shape util
177
+ * whose `handledAssetTypes` includes the asset's type and calls
178
+ * {@link ShapeUtil.createShapeForAsset} to produce the shape.
179
+ *
180
+ * @public
181
+ */
182
+ static handledAssetTypes?: readonly string[]
183
+
171
184
  /**
172
185
  * Get the default props for a shape.
173
186
  *
@@ -175,6 +188,18 @@ export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
175
188
  */
176
189
  abstract getDefaultProps(): Shape['props']
177
190
 
191
+ /**
192
+ * Create a shape partial for placing an asset on the canvas.
193
+ * Only called for shapes whose constructor declares matching
194
+ * {@link ShapeUtil.handledAssetTypes | `handledAssetTypes`}.
195
+ *
196
+ * @param asset - The asset to create a shape for.
197
+ * @param position - Where to place the shape.
198
+ * @returns A shape partial, or null if this shape can't be created for the asset.
199
+ * @public
200
+ */
201
+ createShapeForAsset?(asset: TLAsset, position: VecModel): TLShapePartial | null
202
+
178
203
  /**
179
204
  * Get the shape's geometry.
180
205
  *
@@ -276,7 +301,7 @@ export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
276
301
  *
277
302
  * @public
278
303
  */
279
- canBind(_opts: TLShapeUtilCanBindOpts): boolean {
304
+ canBind(opts: TLShapeUtilCanBindOpts): boolean {
280
305
  return true
281
306
  }
282
307
 
@@ -526,7 +551,7 @@ export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
526
551
  * @param type - The shape type.
527
552
  * @public
528
553
  */
529
- canReceiveNewChildrenOfType(shape: Shape, _type: TLShape['type']) {
554
+ canReceiveNewChildrenOfType(shape: Shape, type: TLShape['type']) {
530
555
  return false
531
556
  }
532
557
 
@@ -12,7 +12,7 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
12
12
  static override props = groupShapeProps
13
13
  static override migrations = groupShapeMigrations
14
14
 
15
- override hideSelectionBoundsFg() {
15
+ override hideSelectionBoundsFg(shape: TLGroupShape) {
16
16
  return true
17
17
  }
18
18
 
@@ -43,6 +43,13 @@ export function getPerfectDashProps(
43
43
  }
44
44
  }
45
45
 
46
+ if (style === 'none') {
47
+ return {
48
+ strokeDasharray: 'none',
49
+ strokeDashoffset: 'none',
50
+ }
51
+ }
52
+
46
53
  switch (style) {
47
54
  case 'dashed': {
48
55
  ratio = 1
@@ -17,20 +17,6 @@ import {
17
17
  TLWheelEventInfo,
18
18
  } from '../types/event-types'
19
19
 
20
- const STATE_NODES_TO_MEASURE = [
21
- 'brushing',
22
- 'cropping',
23
- 'dragging',
24
- 'dragging_handle',
25
- 'drawing',
26
- 'erasing',
27
- 'lasering',
28
- 'resizing',
29
- 'rotating',
30
- 'scribble_brushing',
31
- 'translating',
32
- ]
33
-
34
20
  /** @public */
35
21
  export interface TLStateNodeConstructor {
36
22
  new (editor: Editor, parent?: StateNode): StateNode
@@ -39,6 +25,7 @@ export interface TLStateNodeConstructor {
39
25
  children?(): TLStateNodeConstructor[]
40
26
  isLockable: boolean
41
27
  useCoalescedEvents: boolean
28
+ trackPerformance: boolean
42
29
  }
43
30
 
44
31
  /** @public */
@@ -94,6 +81,8 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
94
81
  static children?: () => TLStateNodeConstructor[]
95
82
  static isLockable = true
96
83
  static useCoalescedEvents = false
84
+ /** Set to `true` in subclasses to emit interaction-start/end performance events when this state is entered/exited. */
85
+ static trackPerformance = false
97
86
 
98
87
  id: string
99
88
  type: 'branch' | 'leaf' | 'root'
@@ -191,8 +180,12 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
191
180
 
192
181
  // todo: move this logic into transition
193
182
  enter(info: any, from: string) {
194
- if (debugFlags.measurePerformance.get() && STATE_NODES_TO_MEASURE.includes(this.id)) {
195
- this.performanceTracker.start(this.id)
183
+ const track = (this.constructor as TLStateNodeConstructor).trackPerformance
184
+ if (track) {
185
+ if (debugFlags.measurePerformance.get()) {
186
+ this.performanceTracker.start(this.id)
187
+ }
188
+ this.editor.performance._notifyInteractionStart(this.id, this.getPath())
196
189
  }
197
190
 
198
191
  this._isActive.set(true)
@@ -207,9 +200,14 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
207
200
 
208
201
  // todo: move this logic into transition
209
202
  exit(info: any, to: string) {
210
- if (debugFlags.measurePerformance.get() && this.performanceTracker.isStarted()) {
211
- this.performanceTracker.stop()
203
+ const track = (this.constructor as TLStateNodeConstructor).trackPerformance
204
+ if (track) {
205
+ if (debugFlags.measurePerformance.get() && this.performanceTracker.isStarted()) {
206
+ this.performanceTracker.stop()
207
+ }
208
+ this.editor.performance._notifyInteractionEnd()
212
209
  }
210
+
213
211
  this._isActive.set(false)
214
212
  this.onExit?.(info, to)
215
213
 
@@ -42,6 +42,11 @@ export interface SvgExportContext {
42
42
  */
43
43
  readonly isDarkMode: boolean
44
44
 
45
+ /**
46
+ * The color mode to use for this export.
47
+ */
48
+ readonly colorMode: 'light' | 'dark'
49
+
45
50
  /**
46
51
  * The scale of the export - how much CSS pixels will be scaled up/down by.
47
52
  */
@@ -60,6 +60,7 @@ export interface TLFileReplaceExternalContent extends TLBaseExternalContent {
60
60
  type: 'file-replace'
61
61
  file: File
62
62
  shapeId: TLShapeId
63
+ /** @deprecated This field is no longer used by the default handler. It may be removed in a future version. */
63
64
  isImage: boolean
64
65
  }
65
66
 
@@ -1,11 +1,6 @@
1
1
  import { useAtom, useValue } from '@tldraw/state-react'
2
- import {
3
- TLFrameShape,
4
- TLShape,
5
- TLShapeId,
6
- getColorValue,
7
- getDefaultColorTheme,
8
- } from '@tldraw/tlschema'
2
+ import { TLFrameShape, TLShape, TLShapeId } from '@tldraw/tlschema'
3
+ import { TLFontFace } from '@tldraw/tlschema'
9
4
  import { hasOwnProperty, promiseWithResolve, uniqueId } from '@tldraw/utils'
10
5
  import {
11
6
  ComponentType,
@@ -21,7 +16,7 @@ import { flushSync } from 'react-dom'
21
16
  import { ErrorBoundary } from '../components/ErrorBoundary'
22
17
  import { InnerShape, InnerShapeBackground } from '../components/Shape'
23
18
  import type { Editor, TLRenderingShape } from '../editor/Editor'
24
- import { TLFontFace } from '../editor/managers/FontManager/FontManager'
19
+ import { getColorValue } from '../editor/managers/ThemeManager/defaultThemes'
25
20
  import { ShapeUtil } from '../editor/shapes/ShapeUtil'
26
21
  import { TLImageExportOptions } from '../editor/types/misc-types'
27
22
  import {
@@ -54,7 +49,9 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
54
49
  const renderPadding =
55
50
  typeof opts.padding === 'number' ? opts.padding : editor.options.defaultSvgPadding
56
51
 
57
- const isDarkMode = opts.darkMode ?? editor.user.getIsDarkMode()
52
+ const colorMode: 'light' | 'dark' =
53
+ opts.darkMode !== undefined ? (opts.darkMode ? 'dark' : 'light') : editor.getColorMode()
54
+ const isDarkMode = colorMode === 'dark'
58
55
 
59
56
  // ---Figure out which shapes we need to include
60
57
  const shapeIdsToInclude = editor.getShapeAndDescendantIds(ids)
@@ -116,6 +113,7 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
116
113
  background={background}
117
114
  singleFrameShapeId={singleFrameShapeId}
118
115
  isDarkMode={isDarkMode}
116
+ colorMode={colorMode}
119
117
  renderingShapes={renderingShapes}
120
118
  onMount={initialEffectPromise.resolve}
121
119
  waitUntil={exportDelay.waitUntil}
@@ -206,6 +204,7 @@ function SvgExport({
206
204
  background,
207
205
  singleFrameShapeId,
208
206
  isDarkMode,
207
+ colorMode,
209
208
  renderingShapes,
210
209
  onMount,
211
210
  waitUntil,
@@ -218,12 +217,13 @@ function SvgExport({
218
217
  background: boolean
219
218
  singleFrameShapeId: TLShapeId | null
220
219
  isDarkMode: boolean
220
+ colorMode: 'light' | 'dark'
221
221
  renderingShapes: TLRenderingShape[]
222
222
  onMount(): void
223
223
  waitUntil(promise: Promise<void>): void
224
224
  }) {
225
225
  const masksId = useUniqueSafeId()
226
- const theme = getDefaultColorTheme({ isDarkMode })
226
+ const theme = editor.getCurrentTheme()
227
227
 
228
228
  const stateAtom = useAtom<{
229
229
  defsById: Record<
@@ -257,6 +257,7 @@ function SvgExport({
257
257
  const exportContext = useMemo(
258
258
  (): SvgExportContext => ({
259
259
  isDarkMode,
260
+ colorMode,
260
261
  waitUntil,
261
262
  addExportDef,
262
263
  scale,
@@ -272,7 +273,7 @@ function SvgExport({
272
273
  })
273
274
  },
274
275
  }),
275
- [isDarkMode, waitUntil, addExportDef, scale, pixelRatio, editor]
276
+ [isDarkMode, colorMode, waitUntil, addExportDef, scale, pixelRatio, editor]
276
277
  )
277
278
 
278
279
  const didRenderRef = useRef(false)
@@ -443,17 +444,22 @@ function SvgExport({
443
444
  onMount()
444
445
  }, [onMount, shapeElements])
445
446
 
446
- let backgroundColor = background ? theme.background : 'transparent'
447
+ const colors = theme.colors[colorMode]
448
+ let backgroundColor = background ? colors.background : 'transparent'
447
449
 
448
450
  if (singleFrameShapeId && background) {
449
451
  const frameShapeUtil = editor.getShapeUtil('frame') as any as
450
452
  | undefined
451
- | { options: { showColors: boolean } }
453
+ | {
454
+ options: {
455
+ showColors: boolean
456
+ }
457
+ }
452
458
  if (frameShapeUtil?.options.showColors) {
453
459
  const shape = editor.getShape(singleFrameShapeId)! as TLFrameShape
454
- backgroundColor = getColorValue(theme, shape.props.color, 'frameFill')
460
+ backgroundColor = getColorValue(colors, shape.props.color, 'frameFill')
455
461
  } else {
456
- backgroundColor = theme.solid
462
+ backgroundColor = colors.solid
457
463
  }
458
464
  }
459
465
 
@@ -46,8 +46,26 @@ const tlenvReactive = atom('tlenvReactive', {
46
46
  // on touch-screen laptops, which will become "coarse" if the user touches the screen.
47
47
  // See https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/pointer#coarse
48
48
  isCoarsePointer: false,
49
+ // Whether the user's display supports P3 color space. This is dynamic because a window can
50
+ // move between displays with different color gamut support.
51
+ supportsP3ColorSpace: false,
49
52
  })
50
53
 
54
+ if (typeof window !== 'undefined') {
55
+ const canRenderP3 = typeof CSS !== 'undefined' && CSS.supports('color', 'color(display-p3 1 1 1)')
56
+ if (canRenderP3) {
57
+ const p3mql = window.matchMedia('(color-gamut: p3)')
58
+ const updateSupportsP3 = () => {
59
+ const supportsP3 = p3mql.matches
60
+ if (supportsP3 !== tlenvReactive.__unsafe__getWithoutCapture().supportsP3ColorSpace) {
61
+ tlenvReactive.update((prev) => ({ ...prev, supportsP3ColorSpace: supportsP3 }))
62
+ }
63
+ }
64
+ updateSupportsP3()
65
+ p3mql.addEventListener('change', updateSupportsP3)
66
+ }
67
+ }
68
+
51
69
  if (typeof window !== 'undefined' && !isForcedFinePointer) {
52
70
  const mql = getGlobalWindow().matchMedia && getGlobalWindow().matchMedia('(any-pointer: coarse)')
53
71
 
@@ -3,11 +3,15 @@ import { useSvgExportContext } from '../editor/types/SvgExportContext'
3
3
  import { useEditor } from './useEditor'
4
4
 
5
5
  /** @public */
6
- export function useIsDarkMode() {
6
+ export function useColorMode(): 'light' | 'dark' {
7
7
  const editor = useEditor()
8
8
  const exportContext = useSvgExportContext()
9
- return useValue('isDarkMode', () => exportContext?.isDarkMode ?? editor.user.getIsDarkMode(), [
10
- exportContext,
11
- editor,
12
- ])
9
+ return useValue(
10
+ 'colorMode',
11
+ () => {
12
+ if (exportContext) return exportContext.colorMode
13
+ return editor.getColorMode()
14
+ },
15
+ [exportContext, editor]
16
+ )
13
17
  }
@@ -3,7 +3,6 @@ import { TLCursorType } from '@tldraw/tlschema'
3
3
  import { PI, radiansToDegrees } from '../primitives/utils'
4
4
  import { useContainer } from './useContainer'
5
5
  import { useEditor } from './useEditor'
6
- import { useIsDarkMode } from './useIsDarkMode'
7
6
 
8
7
  const CORNER_SVG = `<path d='m19.7432 17.0869-4.072 4.068 2.829 2.828-8.473-.013-.013-8.47 2.841 2.842 4.075-4.068 1.414-1.415-2.844-2.842h8.486v8.484l-2.83-2.827z' fill='%23fff'/><path d='m18.6826 16.7334-4.427 4.424 1.828 1.828-5.056-.016-.014-5.054 1.842 1.841 4.428-4.422 2.474-2.475-1.844-1.843h5.073v5.071l-1.83-1.828z' fill='%23000'/>`
9
8
  const EDGE_SVG = `<path d='m9 17.9907v.005l5.997 5.996.001-3.999h1.999 2.02v4l5.98-6.001-5.98-5.999.001 4.019-2.021.002h-2l.001-4.022zm1.411.003 3.587-3.588-.001 2.587h3.5 2.521v-2.585l3.565 3.586-3.564 3.585-.001-2.585h-2.521l-3.499-.001-.001 2.586z' fill='%23fff'/><path d='m17.4971 18.9932h2.521v2.586l3.565-3.586-3.565-3.585v2.605h-2.521-3.5v-2.607l-3.586 3.587 3.586 3.586v-2.587z' fill='%23000'/>`
@@ -67,7 +66,6 @@ export function getCursor(cursor: TLCursorType, rotation = 0, color = 'black') {
67
66
  export function useCursor() {
68
67
  const editor = useEditor()
69
68
  const container = useContainer()
70
- const isDarkMode = useIsDarkMode()
71
69
 
72
70
  useQuickReactor(
73
71
  'useCursor',
@@ -79,11 +77,9 @@ export function useCursor() {
79
77
  return
80
78
  }
81
79
 
82
- container.style.setProperty(
83
- '--tl-cursor',
84
- getCursor(type, rotation, isDarkMode ? 'white' : 'black')
85
- )
80
+ const { cursor } = editor.getCurrentTheme().colors[editor.getColorMode()]
81
+ container.style.setProperty('--tl-cursor', getCursor(type, rotation, cursor))
86
82
  },
87
- [editor, container, isDarkMode]
83
+ [editor, container]
88
84
  )
89
85
  }
@@ -1,18 +1,18 @@
1
1
  import { useValue } from '@tldraw/state-react'
2
2
  import React from 'react'
3
3
  import { debugFlags } from '../utils/debug-flags'
4
+ import { useColorMode } from './useColorMode'
4
5
  import { useContainer } from './useContainer'
5
6
  import { useEditor } from './useEditor'
6
- import { useIsDarkMode } from './useIsDarkMode'
7
7
 
8
8
  export function useDarkMode() {
9
9
  const editor = useEditor()
10
10
  const container = useContainer()
11
- const isDarkMode = useIsDarkMode()
11
+ const colorMode = useColorMode()
12
12
  const forceSrgb = useValue(debugFlags.forceSrgb)
13
13
 
14
14
  React.useEffect(() => {
15
- if (isDarkMode) {
15
+ if (colorMode === 'dark') {
16
16
  container.setAttribute('data-color-mode', 'dark')
17
17
  container.classList.remove('tl-theme__light')
18
18
  container.classList.add('tl-theme__dark')
@@ -26,5 +26,5 @@ export function useDarkMode() {
26
26
  } else {
27
27
  container.classList.remove('tl-theme__force-sRGB')
28
28
  }
29
- }, [editor, container, forceSrgb, isDarkMode])
29
+ }, [editor, container, forceSrgb, colorMode])
30
30
  }
@@ -103,9 +103,13 @@ export function kickoutOccludedShapes(
103
103
  if (oldParentIndex > -1) {
104
104
  // If the old parent is a direct child of the new parent, then we'll add them above the old parent but below the next sibling.
105
105
  const siblingsIndexAbove = oldParentSiblingIds[oldParentIndex + 1]
106
- const indexKeyAbove = siblingsIndexAbove
106
+ const siblingAboveIndex = siblingsIndexAbove
107
107
  ? editor.getShape(siblingsIndexAbove)!.index
108
- : getIndexAbove(prevParent.index)
108
+ : undefined
109
+ const indexKeyAbove =
110
+ siblingAboveIndex && siblingAboveIndex > prevParent.index
111
+ ? siblingAboveIndex
112
+ : getIndexAbove(prevParent.index)
109
113
  insertIndexKey = getIndexBetween(prevParent.index, indexKeyAbove)
110
114
  } else {
111
115
  // If the old parent is not a direct child of the new parent, then we'll add them to the "top" of the new parent's children.
@@ -2,9 +2,9 @@ import { getSchema, JSONContent, Editor as TTEditor } from '@tiptap/core'
2
2
  import { Node, Schema } from '@tiptap/pm/model'
3
3
  import { EditorProviderProps } from '@tiptap/react'
4
4
  import { TLRichText } from '@tldraw/tlschema'
5
+ import { TLFontFace } from '@tldraw/tlschema'
5
6
  import { assert, WeakCache } from '@tldraw/utils'
6
7
  import type { Editor } from '../editor/Editor'
7
- import { TLFontFace } from '../editor/managers/FontManager/FontManager'
8
8
 
9
9
  /**
10
10
  * This is the TipTap editor! Docs are {@link https://tiptap.dev/docs}.