@tldraw/editor 3.16.0-canary.ffdf566dd0a8 → 3.16.0-internal.51e99e128bd4
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.
- package/dist-cjs/index.d.ts +4 -0
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/TldrawEditor.js +1 -1
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +0 -5
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +1 -1
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultScribble.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultScribble.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +9 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/config/TLUserPreferences.js +1 -1
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +27 -15
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +1 -1
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/exports/getSvgJsx.js +1 -2
- package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +24 -20
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +6 -6
- package/dist-cjs/lib/license/Watermark.js.map +1 -1
- package/dist-cjs/lib/options.js +6 -0
- package/dist-cjs/lib/options.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +4 -0
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/TldrawEditor.mjs +1 -1
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +0 -5
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +1 -1
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultScribble.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultScribble.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +9 -1
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs +1 -1
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +27 -15
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +1 -1
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs +2 -2
- package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +25 -21
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +6 -6
- package/dist-esm/lib/license/Watermark.mjs.map +1 -1
- package/dist-esm/lib/options.mjs +6 -0
- package/dist-esm/lib/options.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +293 -290
- package/package.json +14 -37
- package/src/lib/TldrawEditor.tsx +5 -5
- package/src/lib/components/MenuClickCapture.tsx +0 -8
- package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -1
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
- package/src/lib/components/default-components/DefaultScribble.tsx +1 -1
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +5 -1
- package/src/lib/config/TLUserPreferences.ts +1 -1
- package/src/lib/editor/Editor.test.ts +12 -11
- package/src/lib/editor/Editor.ts +37 -20
- package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +15 -14
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +16 -15
- package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +49 -48
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +24 -23
- package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +7 -6
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +12 -11
- package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +57 -50
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +51 -26
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +14 -13
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +21 -26
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +1 -1
- package/src/lib/exports/getSvgJsx.tsx +2 -2
- package/src/lib/hooks/useCanvasEvents.ts +39 -32
- package/src/lib/license/LicenseManager.test.ts +3 -1
- package/src/lib/license/Watermark.test.tsx +2 -1
- package/src/lib/license/Watermark.tsx +6 -6
- package/src/lib/options.ts +6 -0
- package/src/lib/utils/sync/LocalIndexedDb.test.ts +2 -1
- package/src/lib/utils/sync/TLLocalSyncClient.test.ts +15 -15
- 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:
|
|
7
|
+
classList: { add: vi.fn() },
|
|
7
8
|
tabIndex: -1,
|
|
8
|
-
cloneNode:
|
|
9
|
+
cloneNode: vi.fn(),
|
|
9
10
|
innerHTML: '',
|
|
10
11
|
textContent: '',
|
|
11
|
-
setAttribute:
|
|
12
|
-
style: {
|
|
12
|
+
setAttribute: vi.fn(),
|
|
13
|
+
style: {
|
|
14
|
+
setProperty: vi.fn(),
|
|
15
|
+
getPropertyValue: vi.fn(() => ''),
|
|
16
|
+
},
|
|
13
17
|
scrollWidth: 100,
|
|
14
|
-
getBoundingClientRect:
|
|
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:
|
|
23
|
-
insertAdjacentElement:
|
|
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 =
|
|
32
|
+
const mockCreateElement = vi.fn(() => {
|
|
29
33
|
const element = { ...mockElement }
|
|
30
|
-
element.cloneNode =
|
|
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:
|
|
37
|
-
appendChild:
|
|
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 =
|
|
47
|
-
setStart:
|
|
48
|
-
setEnd:
|
|
49
|
-
getClientRects:
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
+
vi.mock('../../Editor')
|
|
7
8
|
|
|
8
9
|
// Mock Date.now to control time
|
|
9
|
-
const mockDateNow =
|
|
10
|
+
const mockDateNow = vi.fn()
|
|
10
11
|
Date.now = mockDateNow
|
|
11
12
|
|
|
12
13
|
// Mock requestAnimationFrame and cancelAnimationFrame
|
|
13
|
-
const mockRequestAnimationFrame =
|
|
14
|
-
const mockCancelAnimationFrame =
|
|
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:
|
|
20
|
+
let editor: Mocked<Editor>
|
|
20
21
|
let tickManager: TickManager
|
|
21
|
-
let mockEmit:
|
|
22
|
-
let mockDisposablesAdd:
|
|
22
|
+
let mockEmit: Mock
|
|
23
|
+
let mockDisposablesAdd: Mock
|
|
23
24
|
|
|
24
25
|
beforeEach(() => {
|
|
25
|
-
|
|
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 =
|
|
41
|
-
mockDisposablesAdd =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
8
|
-
|
|
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:
|
|
12
|
+
let mockUser: Mocked<TLUser>
|
|
15
13
|
let mockUserPreferences: TLUserPreferences
|
|
16
14
|
let userPreferencesAtom: any
|
|
17
15
|
let userPreferencesManager: UserPreferencesManager
|
|
@@ -36,14 +34,14 @@ describe('UserPreferencesManager', () => {
|
|
|
36
34
|
})
|
|
37
35
|
|
|
38
36
|
beforeEach(() => {
|
|
39
|
-
|
|
37
|
+
vi.clearAllMocks()
|
|
40
38
|
|
|
41
39
|
mockUserPreferences = createMockUserPreferences()
|
|
42
40
|
userPreferencesAtom = atom('userPreferences', mockUserPreferences)
|
|
43
41
|
|
|
44
42
|
mockUser = {
|
|
45
43
|
userPreferences: userPreferencesAtom,
|
|
46
|
-
setUserPreferences:
|
|
44
|
+
setUserPreferences: vi.fn((prefs) => {
|
|
47
45
|
userPreferencesAtom.set(prefs)
|
|
48
46
|
}),
|
|
49
47
|
}
|
|
@@ -51,8 +49,8 @@ describe('UserPreferencesManager', () => {
|
|
|
51
49
|
// Default matchMedia mock - no dark mode preference
|
|
52
50
|
mockMatchMedia.mockReturnValue({
|
|
53
51
|
matches: false,
|
|
54
|
-
addEventListener:
|
|
55
|
-
removeEventListener:
|
|
52
|
+
addEventListener: vi.fn(),
|
|
53
|
+
removeEventListener: vi.fn(),
|
|
56
54
|
})
|
|
57
55
|
})
|
|
58
56
|
|
|
@@ -66,17 +64,14 @@ describe('UserPreferencesManager', () => {
|
|
|
66
64
|
expect(userPreferencesManager.systemColorScheme.get()).toBe('light')
|
|
67
65
|
|
|
68
66
|
// Restore matchMedia
|
|
69
|
-
|
|
70
|
-
writable: true,
|
|
71
|
-
value: mockMatchMedia,
|
|
72
|
-
})
|
|
67
|
+
window.matchMedia = mockMatchMedia
|
|
73
68
|
})
|
|
74
69
|
|
|
75
70
|
it('should initialize with light system color scheme when dark mode not preferred', () => {
|
|
76
71
|
mockMatchMedia.mockReturnValue({
|
|
77
72
|
matches: false,
|
|
78
|
-
addEventListener:
|
|
79
|
-
removeEventListener:
|
|
73
|
+
addEventListener: vi.fn(),
|
|
74
|
+
removeEventListener: vi.fn(),
|
|
80
75
|
})
|
|
81
76
|
|
|
82
77
|
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
@@ -87,8 +82,8 @@ describe('UserPreferencesManager', () => {
|
|
|
87
82
|
it('should initialize with dark system color scheme when dark mode preferred', () => {
|
|
88
83
|
mockMatchMedia.mockReturnValue({
|
|
89
84
|
matches: true,
|
|
90
|
-
addEventListener:
|
|
91
|
-
removeEventListener:
|
|
85
|
+
addEventListener: vi.fn(),
|
|
86
|
+
removeEventListener: vi.fn(),
|
|
92
87
|
})
|
|
93
88
|
|
|
94
89
|
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
@@ -97,8 +92,8 @@ describe('UserPreferencesManager', () => {
|
|
|
97
92
|
})
|
|
98
93
|
|
|
99
94
|
it('should set up media query listener for color scheme changes', () => {
|
|
100
|
-
const mockAddEventListener =
|
|
101
|
-
const mockRemoveEventListener =
|
|
95
|
+
const mockAddEventListener = vi.fn()
|
|
96
|
+
const mockRemoveEventListener = vi.fn()
|
|
102
97
|
|
|
103
98
|
mockMatchMedia.mockReturnValue({
|
|
104
99
|
matches: false,
|
|
@@ -112,7 +107,7 @@ describe('UserPreferencesManager', () => {
|
|
|
112
107
|
})
|
|
113
108
|
|
|
114
109
|
it('should handle media query change events', () => {
|
|
115
|
-
const mockAddEventListener =
|
|
110
|
+
const mockAddEventListener = vi.fn()
|
|
116
111
|
let changeHandler: (e: MediaQueryListEvent) => void
|
|
117
112
|
|
|
118
113
|
mockMatchMedia.mockReturnValue({
|
|
@@ -123,7 +118,7 @@ describe('UserPreferencesManager', () => {
|
|
|
123
118
|
}
|
|
124
119
|
mockAddEventListener(event, handler)
|
|
125
120
|
},
|
|
126
|
-
removeEventListener:
|
|
121
|
+
removeEventListener: vi.fn(),
|
|
127
122
|
})
|
|
128
123
|
|
|
129
124
|
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
@@ -153,11 +148,11 @@ describe('UserPreferencesManager', () => {
|
|
|
153
148
|
|
|
154
149
|
describe('dispose', () => {
|
|
155
150
|
it('should remove media query listener on dispose', () => {
|
|
156
|
-
const mockRemoveEventListener =
|
|
151
|
+
const mockRemoveEventListener = vi.fn()
|
|
157
152
|
|
|
158
153
|
mockMatchMedia.mockReturnValue({
|
|
159
154
|
matches: false,
|
|
160
|
-
addEventListener:
|
|
155
|
+
addEventListener: vi.fn(),
|
|
161
156
|
removeEventListener: mockRemoveEventListener,
|
|
162
157
|
})
|
|
163
158
|
|
|
@@ -170,8 +165,8 @@ describe('UserPreferencesManager', () => {
|
|
|
170
165
|
it('should call all disposables', () => {
|
|
171
166
|
userPreferencesManager = new UserPreferencesManager(mockUser, false)
|
|
172
167
|
|
|
173
|
-
const mockDisposable1 =
|
|
174
|
-
const mockDisposable2 =
|
|
168
|
+
const mockDisposable1 = vi.fn()
|
|
169
|
+
const mockDisposable2 = vi.fn()
|
|
175
170
|
|
|
176
171
|
userPreferencesManager.disposables.add(mockDisposable1)
|
|
177
172
|
userPreferencesManager.disposables.add(mockDisposable2)
|
|
@@ -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' || !
|
|
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) {
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/lib/options.ts
CHANGED
|
@@ -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 {
|
|
@@ -98,6 +100,10 @@ export const defaultTldrawOptions = {
|
|
|
98
100
|
multiClickDurationMs: 200,
|
|
99
101
|
coarseDragDistanceSquared: 36, // 6 squared
|
|
100
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
|
|
101
107
|
defaultSvgPadding: 32,
|
|
102
108
|
cameraSlideFriction: 0.09,
|
|
103
109
|
gridSteps: [
|
|
@@ -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
|
-
|
|
10
|
+
vi.useRealTimers()
|
|
10
11
|
})
|
|
11
12
|
afterEach(async () => {
|
|
12
13
|
await hardReset({ shouldReload: false })
|