@tldraw/editor 3.16.0-canary.0a84defb63a4 → 3.16.0-canary.0b7bf4d7feb8

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 (115) hide show
  1. package/dist-cjs/index.d.ts +98 -9
  2. package/dist-cjs/index.js +3 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +6 -2
  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 +7 -10
  9. package/dist-cjs/lib/components/Shape.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +2 -21
  11. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  12. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
  13. package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +1 -1
  14. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  15. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  16. package/dist-cjs/lib/components/default-components/DefaultScribble.js +1 -1
  17. package/dist-cjs/lib/components/default-components/DefaultScribble.js.map +2 -2
  18. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +9 -1
  19. package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
  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 +63 -24
  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/types/misc-types.js.map +1 -1
  27. package/dist-cjs/lib/exports/getSvgJsx.js +1 -2
  28. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  29. package/dist-cjs/lib/hooks/useCanvasEvents.js +24 -20
  30. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  31. package/dist-cjs/lib/license/Watermark.js +6 -6
  32. package/dist-cjs/lib/license/Watermark.js.map +1 -1
  33. package/dist-cjs/lib/options.js +7 -0
  34. package/dist-cjs/lib/options.js.map +2 -2
  35. package/dist-cjs/lib/utils/{nearestMultiple.js → EditorAtom.js} +25 -14
  36. package/dist-cjs/lib/utils/EditorAtom.js.map +7 -0
  37. package/dist-cjs/version.js +3 -3
  38. package/dist-cjs/version.js.map +1 -1
  39. package/dist-esm/index.d.mts +98 -9
  40. package/dist-esm/index.mjs +3 -1
  41. package/dist-esm/index.mjs.map +2 -2
  42. package/dist-esm/lib/TldrawEditor.mjs +6 -2
  43. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  44. package/dist-esm/lib/components/MenuClickCapture.mjs +0 -5
  45. package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
  46. package/dist-esm/lib/components/Shape.mjs +7 -10
  47. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  48. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +2 -21
  49. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  50. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +1 -1
  51. package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +1 -1
  52. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  53. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  54. package/dist-esm/lib/components/default-components/DefaultScribble.mjs +1 -1
  55. package/dist-esm/lib/components/default-components/DefaultScribble.mjs.map +2 -2
  56. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +9 -1
  57. package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
  58. package/dist-esm/lib/config/TLUserPreferences.mjs +9 -3
  59. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  60. package/dist-esm/lib/editor/Editor.mjs +63 -24
  61. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  62. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +9 -4
  63. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  64. package/dist-esm/lib/exports/getSvgJsx.mjs +2 -2
  65. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  66. package/dist-esm/lib/hooks/useCanvasEvents.mjs +25 -21
  67. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  68. package/dist-esm/lib/license/Watermark.mjs +6 -6
  69. package/dist-esm/lib/license/Watermark.mjs.map +1 -1
  70. package/dist-esm/lib/options.mjs +7 -0
  71. package/dist-esm/lib/options.mjs.map +2 -2
  72. package/dist-esm/lib/utils/EditorAtom.mjs +25 -0
  73. package/dist-esm/lib/utils/EditorAtom.mjs.map +7 -0
  74. package/dist-esm/version.mjs +3 -3
  75. package/dist-esm/version.mjs.map +1 -1
  76. package/editor.css +293 -288
  77. package/package.json +14 -37
  78. package/src/index.ts +2 -0
  79. package/src/lib/TldrawEditor.tsx +11 -6
  80. package/src/lib/components/MenuClickCapture.tsx +0 -8
  81. package/src/lib/components/Shape.tsx +6 -12
  82. package/src/lib/components/default-components/DefaultCanvas.tsx +2 -21
  83. package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -1
  84. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  85. package/src/lib/components/default-components/DefaultScribble.tsx +1 -1
  86. package/src/lib/components/default-components/DefaultShapeIndicator.tsx +5 -1
  87. package/src/lib/config/TLUserPreferences.ts +8 -1
  88. package/src/lib/editor/Editor.test.ts +12 -11
  89. package/src/lib/editor/Editor.ts +88 -47
  90. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +15 -14
  91. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +16 -15
  92. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +49 -48
  93. package/src/lib/editor/managers/FontManager/FontManager.test.ts +24 -23
  94. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +7 -6
  95. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +12 -11
  96. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +57 -50
  97. package/src/lib/editor/managers/TextManager/TextManager.test.ts +51 -26
  98. package/src/lib/editor/managers/TickManager/TickManager.test.ts +14 -13
  99. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +34 -26
  100. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +6 -1
  101. package/src/lib/editor/types/misc-types.ts +54 -1
  102. package/src/lib/exports/getSvgJsx.tsx +2 -2
  103. package/src/lib/hooks/useCanvasEvents.ts +39 -32
  104. package/src/lib/license/LicenseManager.test.ts +3 -1
  105. package/src/lib/license/Watermark.test.tsx +2 -1
  106. package/src/lib/license/Watermark.tsx +6 -6
  107. package/src/lib/options.ts +8 -0
  108. package/src/lib/utils/EditorAtom.ts +37 -0
  109. package/src/lib/utils/sync/LocalIndexedDb.test.ts +2 -1
  110. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +15 -15
  111. package/src/version.ts +3 -3
  112. package/dist-cjs/lib/utils/nearestMultiple.js.map +0 -7
  113. package/dist-esm/lib/utils/nearestMultiple.mjs +0 -14
  114. package/dist-esm/lib/utils/nearestMultiple.mjs.map +0 -7
  115. package/src/lib/utils/nearestMultiple.ts +0 -13
@@ -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
  }
@@ -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
 
@@ -206,3 +206,56 @@ export interface TLUpdatePointerOptions {
206
206
  isPen?: boolean
207
207
  button?: number
208
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
  }
@@ -1,5 +1,5 @@
1
1
  import { useValue } from '@tldraw/state-react'
2
- import React, { useMemo } from 'react'
2
+ import React, { useEffect, useMemo } from 'react'
3
3
  import { RIGHT_MOUSE_BUTTON } from '../constants'
4
4
  import {
5
5
  preventDefault,
@@ -16,9 +16,6 @@ export function useCanvasEvents() {
16
16
 
17
17
  const events = useMemo(
18
18
  function canvasEvents() {
19
- // Track the last screen point
20
- let lastX: number, lastY: number
21
-
22
19
  function onPointerDown(e: React.PointerEvent) {
23
20
  if ((e as any).isKilled) return
24
21
 
@@ -44,35 +41,9 @@ export function useCanvasEvents() {
44
41
  })
45
42
  }
46
43
 
47
- function onPointerMove(e: React.PointerEvent) {
48
- if ((e as any).isKilled) return
49
-
50
- if (e.clientX === lastX && e.clientY === lastY) return
51
- lastX = e.clientX
52
- lastY = e.clientY
53
-
54
- // For tools that benefit from a higher fidelity of events,
55
- // we dispatch the coalesced events.
56
- // N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.
57
- const events =
58
- currentTool.useCoalescedEvents && e.nativeEvent.getCoalescedEvents
59
- ? e.nativeEvent.getCoalescedEvents()
60
- : [e]
61
- for (const singleEvent of events) {
62
- editor.dispatch({
63
- type: 'pointer',
64
- target: 'canvas',
65
- name: 'pointer_move',
66
- ...getPointerInfo(singleEvent),
67
- })
68
- }
69
- }
70
-
71
44
  function onPointerUp(e: React.PointerEvent) {
72
45
  if ((e as any).isKilled) return
73
46
  if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
74
- lastX = e.clientX
75
- lastY = e.clientY
76
47
 
77
48
  releasePointerCapture(e.currentTarget, e)
78
49
 
@@ -158,7 +129,6 @@ export function useCanvasEvents() {
158
129
 
159
130
  return {
160
131
  onPointerDown,
161
- onPointerMove,
162
132
  onPointerUp,
163
133
  onPointerEnter,
164
134
  onPointerLeave,
@@ -169,8 +139,45 @@ export function useCanvasEvents() {
169
139
  onClick,
170
140
  }
171
141
  },
172
- [editor, currentTool]
142
+ [editor]
173
143
  )
174
144
 
145
+ // onPointerMove is special: where we're only interested in the other events when they're
146
+ // happening _on_ the canvas (as opposed to outside of it, or on UI floating over it), we want
147
+ // the pointer position to be up to date regardless of whether it's over the tldraw canvas or
148
+ // not. So instead of returning a listener to be attached to the canvas, we directly attach a
149
+ // listener to the whole document instead.
150
+ useEffect(() => {
151
+ let lastX: number, lastY: number
152
+
153
+ function onPointerMove(e: PointerEvent) {
154
+ if ((e as any).isKilled) return
155
+ ;(e as any).isKilled = true
156
+
157
+ if (e.clientX === lastX && e.clientY === lastY) return
158
+ lastX = e.clientX
159
+ lastY = e.clientY
160
+
161
+ // For tools that benefit from a higher fidelity of events,
162
+ // we dispatch the coalesced events.
163
+ // N.B. Sometimes getCoalescedEvents isn't present on iOS, ugh.
164
+ const events =
165
+ currentTool.useCoalescedEvents && e.getCoalescedEvents ? e.getCoalescedEvents() : [e]
166
+ for (const singleEvent of events) {
167
+ editor.dispatch({
168
+ type: 'pointer',
169
+ target: 'canvas',
170
+ name: 'pointer_move',
171
+ ...getPointerInfo(singleEvent),
172
+ })
173
+ }
174
+ }
175
+
176
+ document.body.addEventListener('pointermove', onPointerMove)
177
+ return () => {
178
+ document.body.removeEventListener('pointermove', onPointerMove)
179
+ }
180
+ }, [editor, currentTool])
181
+
175
182
  return events
176
183
  }
@@ -1,4 +1,5 @@
1
1
  import crypto from 'crypto'
2
+ import { vi } from 'vitest'
2
3
  import { publishDates } from '../../version'
3
4
  import { str2ab } from '../utils/licensing'
4
5
  import {
@@ -9,8 +10,9 @@ import {
9
10
  ValidLicenseKeyResult,
10
11
  } from './LicenseManager'
11
12
 
12
- jest.mock('../../version', () => {
13
+ vi.mock('../../version', () => {
13
14
  return {
15
+ version: '3.15.1',
14
16
  publishDates: {
15
17
  major: '2024-06-28T10:56:07.893Z',
16
18
  minor: '2024-07-02T16:49:50.397Z',
@@ -1,10 +1,11 @@
1
1
  import { act, render, waitFor } from '@testing-library/react'
2
+ import { vi } from 'vitest'
2
3
  import { TldrawEditor } from '../TldrawEditor'
3
4
  import { LicenseManager } from './LicenseManager'
4
5
 
5
6
  let mockLicenseState = 'unlicensed'
6
7
 
7
- jest.mock('./useLicenseManagerState', () => ({
8
+ vi.mock('./useLicenseManagerState', () => ({
8
9
  useLicenseManagerState: () => mockLicenseState,
9
10
  }))
10
11
 
@@ -86,15 +86,15 @@ To remove the watermark, please purchase a license at tldraw.dev.
86
86
 
87
87
  .${className} {
88
88
  position: absolute;
89
- bottom: var(--space-2);
90
- right: var(--space-2);
89
+ bottom: var(--tl-space-2);
90
+ right: var(--tl-space-2);
91
91
  width: 96px;
92
92
  height: 32px;
93
93
  display: flex;
94
94
  align-items: center;
95
95
  justify-content: center;
96
- z-index: var(--layer-watermark) !important;
97
- background-color: color-mix(in srgb, var(--color-background) 62%, transparent);
96
+ z-index: var(--tl-layer-watermark) !important;
97
+ background-color: color-mix(in srgb, var(--tl-color-background) 62%, transparent);
98
98
  opacity: 1;
99
99
  border-radius: 5px;
100
100
  pointer-events: all;
@@ -108,7 +108,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
108
108
  height: 32px;
109
109
  pointer-events: all;
110
110
  cursor: inherit;
111
- color: var(--color-text);
111
+ color: var(--tl-color-text);
112
112
  opacity: .38;
113
113
  border: 0;
114
114
  padding: 0;
@@ -137,7 +137,7 @@ To remove the watermark, please purchase a license at tldraw.dev.
137
137
  }
138
138
 
139
139
  .${className}:hover {
140
- background-color: var(--color-background);
140
+ background-color: var(--tl-color-background);
141
141
  transition: background-color 0.2s ease-in-out;
142
142
  transition-delay: 0.32s;
143
143
  }
@@ -27,6 +27,8 @@ export interface TldrawOptions {
27
27
  readonly multiClickDurationMs: number
28
28
  readonly coarseDragDistanceSquared: number
29
29
  readonly dragDistanceSquared: number
30
+ readonly uiDragDistanceSquared: number
31
+ readonly uiCoarseDragDistanceSquared: number
30
32
  readonly defaultSvgPadding: number
31
33
  readonly cameraSlideFriction: number
32
34
  readonly gridSteps: readonly {
@@ -53,6 +55,7 @@ export interface TldrawOptions {
53
55
  readonly flattenImageBoundsPadding: number
54
56
  readonly laserDelayMs: number
55
57
  readonly maxExportDelayMs: number
58
+ readonly tooltipDelayMs: number
56
59
  /**
57
60
  * How long should previews created by {@link Editor.createTemporaryAssetPreview} last before
58
61
  * they expire? Defaults to 3 minutes.
@@ -97,6 +100,10 @@ export const defaultTldrawOptions = {
97
100
  multiClickDurationMs: 200,
98
101
  coarseDragDistanceSquared: 36, // 6 squared
99
102
  dragDistanceSquared: 16, // 4 squared
103
+ uiDragDistanceSquared: 16, // 4 squared
104
+ // it's really easy to accidentally drag from the toolbar on mobile, so we use a much larger
105
+ // threshold than usual here to try and prevent accidental drags.
106
+ uiCoarseDragDistanceSquared: 625, // 25 squared
100
107
  defaultSvgPadding: 32,
101
108
  cameraSlideFriction: 0.09,
102
109
  gridSteps: [
@@ -124,6 +131,7 @@ export const defaultTldrawOptions = {
124
131
  flattenImageBoundsPadding: 16,
125
132
  laserDelayMs: 1200,
126
133
  maxExportDelayMs: 5000,
134
+ tooltipDelayMs: 700,
127
135
  temporaryAssetPreviewLifetimeMs: 180000,
128
136
  actionShortcutsLocation: 'swap',
129
137
  createTextOnCanvasDoubleClick: true,
@@ -0,0 +1,37 @@
1
+ import { atom, Atom } from '@tldraw/state'
2
+ import { WeakCache } from '@tldraw/utils'
3
+ import { Editor } from '../editor/Editor'
4
+
5
+ /**
6
+ * An Atom that is scoped to the lifetime of an Editor.
7
+ *
8
+ * This is useful for storing UI state for tldraw applications. Keeping state scoped to an editor
9
+ * instead of stored in a global atom can prevent issues with state being shared between editors
10
+ * when navigating between pages, or when multiple editor instances are used on the same page.
11
+ *
12
+ * @public
13
+ */
14
+ export class EditorAtom<T> {
15
+ private states = new WeakCache<Editor, Atom<T>>()
16
+
17
+ constructor(
18
+ private name: string,
19
+ private getInitialState: (editor: Editor) => T
20
+ ) {}
21
+
22
+ getAtom(editor: Editor): Atom<T> {
23
+ return this.states.get(editor, () => atom(this.name, this.getInitialState(editor)))
24
+ }
25
+
26
+ get(editor: Editor): T {
27
+ return this.getAtom(editor).get()
28
+ }
29
+
30
+ update(editor: Editor, update: (state: T) => T): T {
31
+ return this.getAtom(editor).update(update)
32
+ }
33
+
34
+ set(editor: Editor, state: T): T {
35
+ return this.getAtom(editor).set(state)
36
+ }
37
+ }
@@ -1,12 +1,13 @@
1
1
  import { createTLSchema } from '@tldraw/tlschema'
2
2
  import { openDB } from 'idb'
3
+ import { vi } from 'vitest'
3
4
  import { hardReset } from './hardReset'
4
5
  import { getAllIndexDbNames, LocalIndexedDb } from './LocalIndexedDb'
5
6
 
6
7
  const schema = createTLSchema({ shapes: {}, bindings: {} })
7
8
  describe('LocalIndexedDb', () => {
8
9
  beforeEach(() => {
9
- jest.useRealTimers()
10
+ vi.useRealTimers()
10
11
  })
11
12
  afterEach(async () => {
12
13
  await hardReset({ shouldReload: false })