@tldraw/editor 3.15.0 → 3.16.0-canary.01f62b6d4455

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 (124) hide show
  1. package/dist-cjs/index.d.ts +185 -9
  2. package/dist-cjs/index.js +5 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +3 -1
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/MenuClickCapture.js +0 -5
  7. package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
  8. package/dist-cjs/lib/components/Shape.js +4 -26
  9. package/dist-cjs/lib/components/Shape.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
  11. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +1 -1
  12. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  13. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  14. package/dist-cjs/lib/components/default-components/DefaultScribble.js +1 -1
  15. package/dist-cjs/lib/components/default-components/DefaultScribble.js.map +2 -2
  16. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +9 -1
  17. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  18. package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js +53 -0
  19. package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js.map +7 -0
  20. package/dist-cjs/lib/config/TLUserPreferences.js +9 -3
  21. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  22. package/dist-cjs/lib/editor/Editor.js +110 -59
  23. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  24. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +9 -4
  25. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  26. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  27. package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
  28. package/dist-cjs/lib/exports/getSvgJsx.js +1 -2
  29. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  30. package/dist-cjs/lib/hooks/useCanvasEvents.js +24 -20
  31. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  32. package/dist-cjs/lib/hooks/useEditorComponents.js +2 -0
  33. package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
  34. package/dist-cjs/lib/hooks/useStateAttribute.js +35 -0
  35. package/dist-cjs/lib/hooks/useStateAttribute.js.map +7 -0
  36. package/dist-cjs/lib/license/Watermark.js +6 -6
  37. package/dist-cjs/lib/license/Watermark.js.map +1 -1
  38. package/dist-cjs/lib/options.js +7 -0
  39. package/dist-cjs/lib/options.js.map +2 -2
  40. package/dist-cjs/lib/utils/EditorAtom.js +45 -0
  41. package/dist-cjs/lib/utils/EditorAtom.js.map +7 -0
  42. package/dist-cjs/version.js +3 -3
  43. package/dist-cjs/version.js.map +1 -1
  44. package/dist-esm/index.d.mts +185 -9
  45. package/dist-esm/index.mjs +7 -1
  46. package/dist-esm/index.mjs.map +2 -2
  47. package/dist-esm/lib/TldrawEditor.mjs +3 -1
  48. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  49. package/dist-esm/lib/components/MenuClickCapture.mjs +0 -5
  50. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  51. package/dist-esm/lib/components/Shape.mjs +4 -26
  52. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  53. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +1 -1
  54. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +1 -1
  55. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  56. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  57. package/dist-esm/lib/components/default-components/DefaultScribble.mjs +1 -1
  58. package/dist-esm/lib/components/default-components/DefaultScribble.mjs.map +2 -2
  59. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +9 -1
  60. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  61. package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs +23 -0
  62. package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs.map +7 -0
  63. package/dist-esm/lib/config/TLUserPreferences.mjs +9 -3
  64. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  65. package/dist-esm/lib/editor/Editor.mjs +110 -59
  66. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  67. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +9 -4
  68. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  69. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  70. package/dist-esm/lib/exports/getSvgJsx.mjs +2 -2
  71. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  72. package/dist-esm/lib/hooks/useCanvasEvents.mjs +25 -21
  73. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  74. package/dist-esm/lib/hooks/useEditorComponents.mjs +4 -0
  75. package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
  76. package/dist-esm/lib/hooks/useStateAttribute.mjs +15 -0
  77. package/dist-esm/lib/hooks/useStateAttribute.mjs.map +7 -0
  78. package/dist-esm/lib/license/Watermark.mjs +6 -6
  79. package/dist-esm/lib/license/Watermark.mjs.map +1 -1
  80. package/dist-esm/lib/options.mjs +7 -0
  81. package/dist-esm/lib/options.mjs.map +2 -2
  82. package/dist-esm/lib/utils/EditorAtom.mjs +25 -0
  83. package/dist-esm/lib/utils/EditorAtom.mjs.map +7 -0
  84. package/dist-esm/version.mjs +3 -3
  85. package/dist-esm/version.mjs.map +1 -1
  86. package/editor.css +297 -311
  87. package/package.json +12 -36
  88. package/src/index.ts +7 -0
  89. package/src/lib/TldrawEditor.tsx +7 -5
  90. package/src/lib/components/MenuClickCapture.tsx +0 -8
  91. package/src/lib/components/Shape.tsx +6 -21
  92. package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -1
  93. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  94. package/src/lib/components/default-components/DefaultScribble.tsx +1 -1
  95. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +5 -1
  96. package/src/lib/components/default-components/DefaultShapeWrapper.tsx +35 -0
  97. package/src/lib/config/TLUserPreferences.ts +8 -1
  98. package/src/lib/editor/Editor.test.ts +12 -11
  99. package/src/lib/editor/Editor.ts +141 -82
  100. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +15 -14
  101. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +16 -15
  102. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +49 -48
  103. package/src/lib/editor/managers/FontManager/FontManager.test.ts +24 -23
  104. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +7 -6
  105. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +12 -11
  106. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +57 -50
  107. package/src/lib/editor/managers/TextManager/TextManager.test.ts +51 -26
  108. package/src/lib/editor/managers/TickManager/TickManager.test.ts +14 -13
  109. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +34 -26
  110. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +6 -1
  111. package/src/lib/editor/shapes/ShapeUtil.ts +57 -0
  112. package/src/lib/editor/types/misc-types.ts +73 -1
  113. package/src/lib/exports/getSvgJsx.tsx +2 -2
  114. package/src/lib/hooks/useCanvasEvents.ts +39 -32
  115. package/src/lib/hooks/useEditorComponents.tsx +7 -1
  116. package/src/lib/hooks/useStateAttribute.ts +15 -0
  117. package/src/lib/license/LicenseManager.test.ts +3 -1
  118. package/src/lib/license/Watermark.test.tsx +2 -1
  119. package/src/lib/license/Watermark.tsx +6 -6
  120. package/src/lib/options.ts +8 -0
  121. package/src/lib/utils/EditorAtom.ts +37 -0
  122. package/src/lib/utils/sync/LocalIndexedDb.test.ts +2 -1
  123. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +15 -15
  124. package/src/version.ts +3 -3
@@ -1,17 +1,21 @@
1
+ import { vi } from 'vitest'
1
2
  import { Editor } from '../../Editor'
2
3
  import { TextManager, TLMeasureTextSpanOpts } from './TextManager'
3
4
 
4
5
  // Create a simple mock DOM environment
5
6
  const mockElement = {
6
- classList: { add: jest.fn() },
7
+ classList: { add: vi.fn() },
7
8
  tabIndex: -1,
8
- cloneNode: jest.fn(),
9
+ cloneNode: vi.fn(),
9
10
  innerHTML: '',
10
11
  textContent: '',
11
- setAttribute: jest.fn(),
12
- style: { setProperty: jest.fn() },
12
+ setAttribute: vi.fn(),
13
+ style: {
14
+ setProperty: vi.fn(),
15
+ getPropertyValue: vi.fn(() => ''),
16
+ },
13
17
  scrollWidth: 100,
14
- getBoundingClientRect: jest.fn(() => ({
18
+ getBoundingClientRect: vi.fn(() => ({
15
19
  width: 100,
16
20
  height: 20,
17
21
  left: 0,
@@ -19,22 +23,44 @@ const mockElement = {
19
23
  right: 100,
20
24
  bottom: 20,
21
25
  })),
22
- remove: jest.fn(),
23
- insertAdjacentElement: jest.fn(),
26
+ remove: vi.fn(),
27
+ insertAdjacentElement: vi.fn(),
24
28
  childNodes: [],
25
29
  }
26
30
 
27
31
  // Mock document.createElement to return our mock element
28
- const mockCreateElement = jest.fn(() => {
32
+ const mockCreateElement = vi.fn(() => {
29
33
  const element = { ...mockElement }
30
- element.cloneNode = jest.fn(() => ({ ...element }))
34
+ element.cloneNode = vi.fn(() => ({ ...element }))
35
+
36
+ // Make textContent and innerHTML reactive like real DOM elements
37
+ let _textContent = ''
38
+ let _innerHTML = ''
39
+
40
+ Object.defineProperty(element, 'textContent', {
41
+ get: () => _textContent,
42
+ set: (value) => {
43
+ _textContent = value || ''
44
+ // When textContent is set, innerHTML should be the escaped version
45
+ _innerHTML = _textContent
46
+ },
47
+ })
48
+
49
+ Object.defineProperty(element, 'innerHTML', {
50
+ get: () => _innerHTML,
51
+ set: (value) => {
52
+ _innerHTML = value || ''
53
+ _textContent = _innerHTML // Simple approximation
54
+ },
55
+ })
56
+
31
57
  return element
32
58
  })
33
59
 
34
60
  // Mock editor
35
61
  const mockEditor = {
36
- getContainer: jest.fn(() => ({
37
- appendChild: jest.fn(),
62
+ getContainer: vi.fn(() => ({
63
+ appendChild: vi.fn(),
38
64
  })),
39
65
  } as unknown as Editor
40
66
 
@@ -43,10 +69,10 @@ global.document = {
43
69
  createElement: mockCreateElement,
44
70
  } as any
45
71
 
46
- global.Range = jest.fn(() => ({
47
- setStart: jest.fn(),
48
- setEnd: jest.fn(),
49
- getClientRects: jest.fn(() => [
72
+ global.Range = vi.fn(() => ({
73
+ setStart: vi.fn(),
74
+ setEnd: vi.fn(),
75
+ getClientRects: vi.fn(() => [
50
76
  {
51
77
  width: 10,
52
78
  height: 16,
@@ -62,7 +88,7 @@ describe('TextManager', () => {
62
88
  let textManager: TextManager
63
89
 
64
90
  beforeEach(() => {
65
- jest.clearAllMocks()
91
+ vi.clearAllMocks()
66
92
  textManager = new TextManager(mockEditor)
67
93
  })
68
94
 
@@ -86,13 +112,13 @@ describe('TextManager', () => {
86
112
  }
87
113
 
88
114
  it('should call measureHtml with normalized text', () => {
89
- const spy = jest.spyOn(textManager, 'measureHtml')
115
+ const spy = vi.spyOn(textManager, 'measureHtml')
90
116
  textManager.measureText('Hello World', defaultOpts)
91
117
  expect(spy).toHaveBeenCalledWith('Hello World', defaultOpts)
92
118
  })
93
119
 
94
120
  it('should normalize line breaks', () => {
95
- const spy = jest.spyOn(textManager, 'measureHtml')
121
+ const spy = vi.spyOn(textManager, 'measureHtml')
96
122
  textManager.measureText('Hello\nWorld\r\nTest', defaultOpts)
97
123
  // The text should be normalized to use consistent line breaks
98
124
  expect(spy).toHaveBeenCalled()
@@ -247,7 +273,7 @@ describe('TextManager', () => {
247
273
 
248
274
  it('should return array of text spans for non-empty text', () => {
249
275
  // Mock measureElementTextNodeSpans to return some spans
250
- jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
276
+ vi.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
251
277
  spans: [
252
278
  {
253
279
  text: 'Hello World',
@@ -266,7 +292,7 @@ describe('TextManager', () => {
266
292
  })
267
293
 
268
294
  it('should handle wrap overflow', () => {
269
- jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
295
+ vi.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
270
296
  spans: [
271
297
  {
272
298
  text: 'Hello World',
@@ -284,8 +310,7 @@ describe('TextManager', () => {
284
310
 
285
311
  it('should handle truncate-ellipsis overflow', () => {
286
312
  // Mock the calls for ellipsis handling
287
- jest
288
- .spyOn(textManager, 'measureElementTextNodeSpans')
313
+ vi.spyOn(textManager, 'measureElementTextNodeSpans')
289
314
  .mockReturnValueOnce({
290
315
  spans: [
291
316
  {
@@ -321,7 +346,7 @@ describe('TextManager', () => {
321
346
  })
322
347
 
323
348
  it('should handle truncate-clip overflow', () => {
324
- jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
349
+ vi.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
325
350
  spans: [
326
351
  {
327
352
  text: 'Hello Wo',
@@ -338,7 +363,7 @@ describe('TextManager', () => {
338
363
  })
339
364
 
340
365
  it('should handle different text alignments', () => {
341
- jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
366
+ vi.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
342
367
  spans: [
343
368
  {
344
369
  text: 'Test',
@@ -358,7 +383,7 @@ describe('TextManager', () => {
358
383
  })
359
384
 
360
385
  it('should handle custom font properties', () => {
361
- jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
386
+ vi.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
362
387
  spans: [
363
388
  {
364
389
  text: 'Test',
@@ -382,7 +407,7 @@ describe('TextManager', () => {
382
407
  })
383
408
 
384
409
  it('should handle other styles', () => {
385
- jest.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
410
+ vi.spyOn(textManager, 'measureElementTextNodeSpans').mockReturnValue({
386
411
  spans: [
387
412
  {
388
413
  text: 'Test',
@@ -1,28 +1,29 @@
1
+ import { Mock, Mocked, vi } from 'vitest'
1
2
  import { Vec } from '../../../primitives/Vec'
2
3
  import { Editor } from '../../Editor'
3
4
  import { TickManager } from './TickManager'
4
5
 
5
6
  // Mock the Editor class
6
- jest.mock('../../Editor')
7
+ vi.mock('../../Editor')
7
8
 
8
9
  // Mock Date.now to control time
9
- const mockDateNow = jest.fn()
10
+ const mockDateNow = vi.fn()
10
11
  Date.now = mockDateNow
11
12
 
12
13
  // Mock requestAnimationFrame and cancelAnimationFrame
13
- const mockRequestAnimationFrame = jest.fn()
14
- const mockCancelAnimationFrame = jest.fn()
14
+ const mockRequestAnimationFrame = vi.fn()
15
+ const mockCancelAnimationFrame = vi.fn()
15
16
  global.requestAnimationFrame = mockRequestAnimationFrame
16
17
  global.cancelAnimationFrame = mockCancelAnimationFrame
17
18
 
18
19
  describe('TickManager', () => {
19
- let editor: jest.Mocked<Editor>
20
+ let editor: Mocked<Editor>
20
21
  let tickManager: TickManager
21
- let mockEmit: jest.Mock
22
- let mockDisposablesAdd: jest.Mock
22
+ let mockEmit: Mock
23
+ let mockDisposablesAdd: Mock
23
24
 
24
25
  beforeEach(() => {
25
- jest.clearAllMocks()
26
+ vi.clearAllMocks()
26
27
 
27
28
  // Reset time
28
29
  mockDateNow.mockReturnValue(1000)
@@ -37,8 +38,8 @@ describe('TickManager', () => {
37
38
 
38
39
  mockCancelAnimationFrame.mockImplementation(() => {})
39
40
 
40
- mockEmit = jest.fn()
41
- mockDisposablesAdd = jest.fn()
41
+ mockEmit = vi.fn()
42
+ mockDisposablesAdd = vi.fn()
42
43
 
43
44
  editor = {
44
45
  emit: mockEmit,
@@ -90,7 +91,7 @@ describe('TickManager', () => {
90
91
  })
91
92
 
92
93
  it('should cancel existing RAF before starting new one', () => {
93
- const mockCancel = jest.fn()
94
+ const mockCancel = vi.fn()
94
95
  tickManager.cancelRaf = mockCancel
95
96
 
96
97
  tickManager.start()
@@ -143,7 +144,7 @@ describe('TickManager', () => {
143
144
  })
144
145
 
145
146
  it('should update pointer velocity', () => {
146
- const updatePointerVelocitySpy = jest.spyOn(tickManager as any, 'updatePointerVelocity')
147
+ const updatePointerVelocitySpy = vi.spyOn(tickManager as any, 'updatePointerVelocity')
147
148
  tickManager.now = 1000
148
149
  mockDateNow.mockReturnValue(1016)
149
150
 
@@ -176,7 +177,7 @@ describe('TickManager', () => {
176
177
  })
177
178
 
178
179
  it('should cancel RAF if exists', () => {
179
- const mockCancel = jest.fn()
180
+ const mockCancel = vi.fn()
180
181
  tickManager.cancelRaf = mockCancel
181
182
 
182
183
  tickManager.dispose()
@@ -1,17 +1,15 @@
1
1
  import { atom } from '@tldraw/state'
2
+ import { Mocked, vi } from 'vitest'
2
3
  import { TLUserPreferences, defaultUserPreferences } from '../../../config/TLUserPreferences'
3
4
  import { TLUser } from '../../../config/createTLUser'
4
5
  import { UserPreferencesManager } from './UserPreferencesManager'
5
6
 
6
7
  // Mock window.matchMedia
7
- const mockMatchMedia = jest.fn()
8
- Object.defineProperty(window, 'matchMedia', {
9
- writable: true,
10
- value: mockMatchMedia,
11
- })
8
+ const mockMatchMedia = vi.fn()
9
+ window.matchMedia = mockMatchMedia
12
10
 
13
11
  describe('UserPreferencesManager', () => {
14
- let mockUser: jest.Mocked<TLUser>
12
+ let mockUser: Mocked<TLUser>
15
13
  let mockUserPreferences: TLUserPreferences
16
14
  let userPreferencesAtom: any
17
15
  let userPreferencesManager: UserPreferencesManager
@@ -25,6 +23,7 @@ describe('UserPreferencesManager', () => {
25
23
  locale: 'en',
26
24
  animationSpeed: 1,
27
25
  areKeyboardShortcutsEnabled: true,
26
+ showUiLabels: false,
28
27
  edgeScrollSpeed: 1,
29
28
  colorScheme: 'light',
30
29
  isSnapMode: false,
@@ -35,14 +34,14 @@ describe('UserPreferencesManager', () => {
35
34
  })
36
35
 
37
36
  beforeEach(() => {
38
- jest.clearAllMocks()
37
+ vi.clearAllMocks()
39
38
 
40
39
  mockUserPreferences = createMockUserPreferences()
41
40
  userPreferencesAtom = atom('userPreferences', mockUserPreferences)
42
41
 
43
42
  mockUser = {
44
43
  userPreferences: userPreferencesAtom,
45
- setUserPreferences: jest.fn((prefs) => {
44
+ setUserPreferences: vi.fn((prefs) => {
46
45
  userPreferencesAtom.set(prefs)
47
46
  }),
48
47
  }
@@ -50,8 +49,8 @@ describe('UserPreferencesManager', () => {
50
49
  // Default matchMedia mock - no dark mode preference
51
50
  mockMatchMedia.mockReturnValue({
52
51
  matches: false,
53
- addEventListener: jest.fn(),
54
- removeEventListener: jest.fn(),
52
+ addEventListener: vi.fn(),
53
+ removeEventListener: vi.fn(),
55
54
  })
56
55
  })
57
56
 
@@ -65,17 +64,14 @@ describe('UserPreferencesManager', () => {
65
64
  expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
66
65
 
67
66
  // Restore matchMedia
68
- Object.defineProperty(window, 'matchMedia', {
69
- writable: true,
70
- value: mockMatchMedia,
71
- })
67
+ window.matchMedia = mockMatchMedia
72
68
  })
73
69
 
74
70
  it('should initialize with light system color scheme when dark mode not preferred', () => {
75
71
  mockMatchMedia.mockReturnValue({
76
72
  matches: false,
77
- addEventListener: jest.fn(),
78
- removeEventListener: jest.fn(),
73
+ addEventListener: vi.fn(),
74
+ removeEventListener: vi.fn(),
79
75
  })
80
76
 
81
77
  userPreferencesManager = new UserPreferencesManager(mockUser, false)
@@ -86,8 +82,8 @@ describe('UserPreferencesManager', () => {
86
82
  it('should initialize with dark system color scheme when dark mode preferred', () => {
87
83
  mockMatchMedia.mockReturnValue({
88
84
  matches: true,
89
- addEventListener: jest.fn(),
90
- removeEventListener: jest.fn(),
85
+ addEventListener: vi.fn(),
86
+ removeEventListener: vi.fn(),
91
87
  })
92
88
 
93
89
  userPreferencesManager = new UserPreferencesManager(mockUser, false)
@@ -96,8 +92,8 @@ describe('UserPreferencesManager', () => {
96
92
  })
97
93
 
98
94
  it('should set up media query listener for color scheme changes', () => {
99
- const mockAddEventListener = jest.fn()
100
- const mockRemoveEventListener = jest.fn()
95
+ const mockAddEventListener = vi.fn()
96
+ const mockRemoveEventListener = vi.fn()
101
97
 
102
98
  mockMatchMedia.mockReturnValue({
103
99
  matches: false,
@@ -111,7 +107,7 @@ describe('UserPreferencesManager', () => {
111
107
  })
112
108
 
113
109
  it('should handle media query change events', () => {
114
- const mockAddEventListener = jest.fn()
110
+ const mockAddEventListener = vi.fn()
115
111
  let changeHandler: (e: MediaQueryListEvent) => void
116
112
 
117
113
  mockMatchMedia.mockReturnValue({
@@ -122,7 +118,7 @@ describe('UserPreferencesManager', () => {
122
118
  }
123
119
  mockAddEventListener(event, handler)
124
120
  },
125
- removeEventListener: jest.fn(),
121
+ removeEventListener: vi.fn(),
126
122
  })
127
123
 
128
124
  userPreferencesManager = new UserPreferencesManager(mockUser, false)
@@ -152,11 +148,11 @@ describe('UserPreferencesManager', () => {
152
148
 
153
149
  describe('dispose', () => {
154
150
  it('should remove media query listener on dispose', () => {
155
- const mockRemoveEventListener = jest.fn()
151
+ const mockRemoveEventListener = vi.fn()
156
152
 
157
153
  mockMatchMedia.mockReturnValue({
158
154
  matches: false,
159
- addEventListener: jest.fn(),
155
+ addEventListener: vi.fn(),
160
156
  removeEventListener: mockRemoveEventListener,
161
157
  })
162
158
 
@@ -169,8 +165,8 @@ describe('UserPreferencesManager', () => {
169
165
  it('should call all disposables', () => {
170
166
  userPreferencesManager = new UserPreferencesManager(mockUser, false)
171
167
 
172
- const mockDisposable1 = jest.fn()
173
- const mockDisposable2 = jest.fn()
168
+ const mockDisposable1 = vi.fn()
169
+ const mockDisposable2 = vi.fn()
174
170
 
175
171
  userPreferencesManager.disposables.add(mockDisposable1)
176
172
  userPreferencesManager.disposables.add(mockDisposable2)
@@ -231,6 +227,7 @@ describe('UserPreferencesManager', () => {
231
227
  color: mockUserPreferences.color,
232
228
  animationSpeed: mockUserPreferences.animationSpeed,
233
229
  areKeyboardShortcutsEnabled: mockUserPreferences.areKeyboardShortcutsEnabled,
230
+ showUiLabels: mockUserPreferences.showUiLabels,
234
231
  isSnapMode: mockUserPreferences.isSnapMode,
235
232
  colorScheme: mockUserPreferences.colorScheme,
236
233
  isDarkMode: false, // light mode
@@ -379,6 +376,17 @@ describe('UserPreferencesManager', () => {
379
376
  })
380
377
  })
381
378
 
379
+ describe('getShowUiLabels', () => {
380
+ it('should return user show ui labels setting', () => {
381
+ expect(userPreferencesManager.getShowUiLabels()).toBe(mockUserPreferences.showUiLabels)
382
+ })
383
+
384
+ it('should return default show ui labels when null', () => {
385
+ userPreferencesAtom.set({ ...mockUserPreferences, showUiLabels: null })
386
+ expect(userPreferencesManager.getShowUiLabels()).toBe(defaultUserPreferences.showUiLabels)
387
+ })
388
+ })
389
+
382
390
  describe('getEdgeScrollSpeed', () => {
383
391
  it('should return user edge scroll speed', () => {
384
392
  expect(userPreferencesManager.getEdgeScrollSpeed()).toBe(
@@ -13,7 +13,7 @@ export class UserPreferencesManager {
13
13
  private readonly user: TLUser,
14
14
  private readonly inferDarkMode: boolean
15
15
  ) {
16
- if (typeof window === 'undefined' || !('matchMedia' in window)) return
16
+ if (typeof window === 'undefined' || !window.matchMedia) return
17
17
 
18
18
  const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
19
19
  if (darkModeMediaQuery?.matches) {
@@ -49,6 +49,7 @@ export class UserPreferencesManager {
49
49
  isDarkMode: this.getIsDarkMode(),
50
50
  isWrapMode: this.getIsWrapMode(),
51
51
  isDynamicResizeMode: this.getIsDynamicResizeMode(),
52
+ showUiLabels: this.getShowUiLabels(),
52
53
  }
53
54
  }
54
55
 
@@ -119,4 +120,8 @@ export class UserPreferencesManager {
119
120
  defaultUserPreferences.isPasteAtCursorMode
120
121
  )
121
122
  }
123
+
124
+ @computed getShowUiLabels() {
125
+ return this.user.userPreferences.get().showUiLabels ?? defaultUserPreferences.showUiLabels
126
+ }
122
127
  }
@@ -584,6 +584,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
584
584
  */
585
585
  onResizeEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void
586
586
 
587
+ /**
588
+ * A callback called when a shape resize is cancelled.
589
+ *
590
+ * @param initial - The shape at the start of the resize.
591
+ * @param current - The current shape.
592
+ * @public
593
+ */
594
+ onResizeCancel?(initial: Shape, current: Shape): void
595
+
587
596
  /**
588
597
  * A callback called when a shape starts being translated.
589
598
  *
@@ -613,6 +622,25 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
613
622
  */
614
623
  onTranslateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void
615
624
 
625
+ /**
626
+ * A callback called when a shape translation is cancelled.
627
+ *
628
+ * @param initial - The shape at the start of the translation.
629
+ * @param current - The current shape.
630
+ * @public
631
+ */
632
+ onTranslateCancel?(initial: Shape, current: Shape): void
633
+
634
+ /**
635
+ * A callback called when a shape's handle starts being dragged.
636
+ *
637
+ * @param shape - The shape.
638
+ * @param info - An object containing the handle and whether the handle is 'precise' or not.
639
+ * @returns A change to apply to the shape, or void.
640
+ * @public
641
+ */
642
+ onHandleDragStart?(shape: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void
643
+
616
644
  /**
617
645
  * A callback called when a shape's handle changes.
618
646
  *
@@ -623,6 +651,25 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
623
651
  */
624
652
  onHandleDrag?(shape: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void
625
653
 
654
+ /**
655
+ * A callback called when a shape's handle finishes being dragged.
656
+ *
657
+ * @param current - The current shape.
658
+ * @param info - An object containing the handle and whether the handle is 'precise' or not.
659
+ * @returns A change to apply to the shape, or void.
660
+ * @public
661
+ */
662
+ onHandleDragEnd?(current: Shape, info: TLHandleDragInfo<Shape>): TLShapePartial<Shape> | void
663
+
664
+ /**
665
+ * A callback called when a shape's handle drag is cancelled.
666
+ *
667
+ * @param current - The current shape.
668
+ * @param info - An object containing the handle and whether the handle is 'precise' or not.
669
+ * @public
670
+ */
671
+ onHandleDragCancel?(current: Shape, info: TLHandleDragInfo<Shape>): void
672
+
626
673
  /**
627
674
  * A callback called when a shape starts being rotated.
628
675
  *
@@ -652,6 +699,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
652
699
  */
653
700
  onRotateEnd?(initial: Shape, current: Shape): TLShapePartial<Shape> | void
654
701
 
702
+ /**
703
+ * A callback called when a shape rotation is cancelled.
704
+ *
705
+ * @param initial - The shape at the start of the rotation.
706
+ * @param current - The current shape.
707
+ * @public
708
+ */
709
+ onRotateCancel?(initial: Shape, current: Shape): void
710
+
655
711
  /**
656
712
  * Not currently used.
657
713
  *
@@ -819,5 +875,6 @@ export interface TLResizeInfo<T extends TLShape> {
819
875
  export interface TLHandleDragInfo<T extends TLShape> {
820
876
  handle: TLHandle
821
877
  isPrecise: boolean
878
+ isCreatingShape: boolean
822
879
  initial?: T | undefined
823
880
  }
@@ -1,4 +1,4 @@
1
- import { BoxModel } from '@tldraw/tlschema'
1
+ import { BoxModel, TLShape } from '@tldraw/tlschema'
2
2
  import { Box } from '../../primitives/Box'
3
3
  import { VecLike } from '../../primitives/Vec'
4
4
 
@@ -187,3 +187,75 @@ export interface TLCameraConstraints {
187
187
  y: 'free' | 'fixed' | 'inside' | 'outside' | 'contain'
188
188
  }
189
189
  }
190
+
191
+ /** @public */
192
+ export interface TLUpdatePointerOptions {
193
+ /** Whether to update the pointer immediately, rather than on the next tick. */
194
+ immediate?: boolean
195
+ /**
196
+ * The point, in screen-space, to update the pointer to. Defaults to the position of the last
197
+ * pointer event.
198
+ */
199
+ point?: VecLike
200
+ pointerId?: number
201
+ ctrlKey?: boolean
202
+ altKey?: boolean
203
+ shiftKey?: boolean
204
+ metaKey?: boolean
205
+ accelKey?: boolean
206
+ isPen?: boolean
207
+ button?: number
208
+ }
209
+
210
+ /**
211
+ * Options to {@link Editor.getShapeAtPoint}.
212
+ *
213
+ * @public
214
+ */
215
+ export interface TLGetShapeAtPointOptions {
216
+ /**
217
+ * The margin to apply to the shape.
218
+ * If a number, it will be applied to both the inside and outside of the shape.
219
+ * If an array, the first element will be applied to the inside of the shape, and the second element will be applied to the outside.
220
+ *
221
+ * @example
222
+ * ```ts
223
+ * // Get the shape at the center of the screen
224
+ * const shape = editor.getShapeAtProps({
225
+ * margin: 10,
226
+ * })
227
+ *
228
+ * // Get the shape at the center of the screen with a 10px inner margin and a 5px outer margin
229
+ * const shape = editor.getShapeAtProps({
230
+ * margin: [10, 5],
231
+ * })
232
+ * ```
233
+ */
234
+ margin?: number | [number, number]
235
+ /**
236
+ * Whether to register hits inside of shapes (beyond the margin), such as the inside of a solid shape.
237
+ */
238
+ hitInside?: boolean
239
+ /**
240
+ * Whether to register hits on locked shapes.
241
+ */
242
+ hitLocked?: boolean
243
+ /**
244
+ * Whether to register hits on labels.
245
+ */
246
+ hitLabels?: boolean
247
+ /**
248
+ * Whether to only return hits on shapes that are currently being rendered.
249
+ * todo: rename this to hitCulled or hitNotRendering
250
+ */
251
+ renderingOnly?: boolean
252
+ /**
253
+ * Whether to register hits on the inside of frame shapes.
254
+ * todo: rename this to hitInsideFrames
255
+ */
256
+ hitFrameInside?: boolean
257
+ /**
258
+ * A filter function to apply to the shapes.
259
+ */
260
+ filter?(shape: TLShape): boolean
261
+ }
@@ -4,6 +4,7 @@ import {
4
4
  TLGroupShape,
5
5
  TLShape,
6
6
  TLShapeId,
7
+ getColorValue,
7
8
  getDefaultColorTheme,
8
9
  } from '@tldraw/tlschema'
9
10
  import { hasOwnProperty, promiseWithResolve, uniqueId } from '@tldraw/utils'
@@ -373,8 +374,7 @@ function SvgExport({
373
374
  | { options: { showColors: boolean } }
374
375
  if (frameShapeUtil?.options.showColors) {
375
376
  const shape = editor.getShape(singleFrameShapeId)! as TLFrameShape
376
- const color = theme[shape.props.color]
377
- backgroundColor = color.frame.fill
377
+ backgroundColor = getColorValue(theme, shape.props.color, 'frameFill')
378
378
  } else {
379
379
  backgroundColor = theme.solid
380
380
  }