@tldraw/editor 3.16.0-canary.e5e61b17cef3 → 3.16.0-canary.e618c2fbc95d
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 +50 -8
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/TldrawEditor.js +0 -2
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +11 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/config/TLUserPreferences.js +15 -4
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +41 -3
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +4 -2
- package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +11 -6
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +19 -16
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +5 -5
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -2
- package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
- package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useHandleEvents.js +6 -6
- package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useSelectionEvents.js +8 -8
- package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +24 -4
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +97 -90
- package/dist-cjs/lib/license/Watermark.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/getPointerInfo.js +2 -3
- package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
- package/dist-cjs/lib/utils/reparenting.js +5 -1
- package/dist-cjs/lib/utils/reparenting.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 +50 -8
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/TldrawEditor.mjs +0 -2
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +11 -1
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs +15 -4
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +41 -3
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
- package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +11 -6
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +20 -22
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +6 -6
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +1 -2
- package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
- package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useHandleEvents.mjs +6 -6
- package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useSelectionEvents.mjs +9 -14
- package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +24 -4
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +98 -91
- package/dist-esm/lib/license/Watermark.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/getPointerInfo.mjs +2 -3
- package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
- package/dist-esm/lib/utils/reparenting.mjs +5 -1
- package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/lib/TldrawEditor.tsx +0 -2
- package/src/lib/components/default-components/DefaultCanvas.tsx +7 -1
- package/src/lib/config/TLUserPreferences.ts +16 -3
- package/src/lib/editor/Editor.test.ts +90 -0
- package/src/lib/editor/Editor.ts +53 -3
- package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +30 -8
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +10 -3
- package/src/lib/hooks/useCanvasEvents.ts +20 -20
- package/src/lib/hooks/useDocumentEvents.ts +6 -6
- package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
- package/src/lib/hooks/useGestureEvents.ts +2 -2
- package/src/lib/hooks/useHandleEvents.ts +6 -6
- package/src/lib/hooks/useSelectionEvents.ts +9 -14
- package/src/lib/license/LicenseManager.test.ts +78 -2
- package/src/lib/license/LicenseManager.ts +31 -5
- package/src/lib/license/Watermark.tsx +100 -92
- package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
- package/src/lib/utils/dom.test.ts +103 -0
- package/src/lib/utils/dom.ts +8 -1
- package/src/lib/utils/getPointerInfo.ts +3 -2
- package/src/lib/utils/reparenting.ts +7 -1
- package/src/version.ts +3 -3
|
@@ -23,13 +23,14 @@ describe('UserPreferencesManager', () => {
|
|
|
23
23
|
locale: 'en',
|
|
24
24
|
animationSpeed: 1,
|
|
25
25
|
areKeyboardShortcutsEnabled: true,
|
|
26
|
-
|
|
26
|
+
enhancedA11yMode: false,
|
|
27
27
|
edgeScrollSpeed: 1,
|
|
28
28
|
colorScheme: 'light',
|
|
29
29
|
isSnapMode: false,
|
|
30
30
|
isWrapMode: false,
|
|
31
31
|
isDynamicSizeMode: false,
|
|
32
32
|
isPasteAtCursorMode: false,
|
|
33
|
+
inputMode: null,
|
|
33
34
|
...overrides,
|
|
34
35
|
})
|
|
35
36
|
|
|
@@ -227,12 +228,13 @@ describe('UserPreferencesManager', () => {
|
|
|
227
228
|
color: mockUserPreferences.color,
|
|
228
229
|
animationSpeed: mockUserPreferences.animationSpeed,
|
|
229
230
|
areKeyboardShortcutsEnabled: mockUserPreferences.areKeyboardShortcutsEnabled,
|
|
230
|
-
|
|
231
|
+
enhancedA11yMode: mockUserPreferences.enhancedA11yMode,
|
|
231
232
|
isSnapMode: mockUserPreferences.isSnapMode,
|
|
232
233
|
colorScheme: mockUserPreferences.colorScheme,
|
|
233
234
|
isDarkMode: false, // light mode
|
|
234
235
|
isWrapMode: mockUserPreferences.isWrapMode,
|
|
235
236
|
isDynamicResizeMode: mockUserPreferences.isDynamicSizeMode,
|
|
237
|
+
inputMode: mockUserPreferences.inputMode,
|
|
236
238
|
})
|
|
237
239
|
})
|
|
238
240
|
|
|
@@ -376,14 +378,18 @@ describe('UserPreferencesManager', () => {
|
|
|
376
378
|
})
|
|
377
379
|
})
|
|
378
380
|
|
|
379
|
-
describe('
|
|
380
|
-
it('should return user
|
|
381
|
-
expect(userPreferencesManager.
|
|
381
|
+
describe('getEnhancedA11yMode', () => {
|
|
382
|
+
it('should return user enhanced a11y mode setting', () => {
|
|
383
|
+
expect(userPreferencesManager.getEnhancedA11yMode()).toBe(
|
|
384
|
+
mockUserPreferences.enhancedA11yMode
|
|
385
|
+
)
|
|
382
386
|
})
|
|
383
387
|
|
|
384
|
-
it('should return default
|
|
385
|
-
userPreferencesAtom.set({ ...mockUserPreferences,
|
|
386
|
-
expect(userPreferencesManager.
|
|
388
|
+
it('should return default enhanced a11y mode when null', () => {
|
|
389
|
+
userPreferencesAtom.set({ ...mockUserPreferences, enhancedA11yMode: null })
|
|
390
|
+
expect(userPreferencesManager.getEnhancedA11yMode()).toBe(
|
|
391
|
+
defaultUserPreferences.enhancedA11yMode
|
|
392
|
+
)
|
|
387
393
|
})
|
|
388
394
|
})
|
|
389
395
|
|
|
@@ -453,6 +459,22 @@ describe('UserPreferencesManager', () => {
|
|
|
453
459
|
)
|
|
454
460
|
})
|
|
455
461
|
})
|
|
462
|
+
|
|
463
|
+
describe('getInputMode', () => {
|
|
464
|
+
it('should return user input mode setting', () => {
|
|
465
|
+
expect(userPreferencesManager.getInputMode()).toBe(null)
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it('should return trackpad if input mode is trackpad', () => {
|
|
469
|
+
userPreferencesAtom.set({ ...mockUserPreferences, inputMode: 'trackpad' })
|
|
470
|
+
expect(userPreferencesManager.getInputMode()).toBe('trackpad')
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
it('should return mouse if input mode is mouse', () => {
|
|
474
|
+
userPreferencesAtom.set({ ...mockUserPreferences, inputMode: 'mouse' })
|
|
475
|
+
expect(userPreferencesManager.getInputMode()).toBe('mouse')
|
|
476
|
+
})
|
|
477
|
+
})
|
|
456
478
|
})
|
|
457
479
|
|
|
458
480
|
describe('reactive behavior', () => {
|
|
@@ -49,7 +49,8 @@ export class UserPreferencesManager {
|
|
|
49
49
|
isDarkMode: this.getIsDarkMode(),
|
|
50
50
|
isWrapMode: this.getIsWrapMode(),
|
|
51
51
|
isDynamicResizeMode: this.getIsDynamicResizeMode(),
|
|
52
|
-
|
|
52
|
+
enhancedA11yMode: this.getEnhancedA11yMode(),
|
|
53
|
+
inputMode: this.getInputMode(),
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -121,7 +122,13 @@ export class UserPreferencesManager {
|
|
|
121
122
|
)
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
@computed
|
|
125
|
-
return
|
|
125
|
+
@computed getEnhancedA11yMode() {
|
|
126
|
+
return (
|
|
127
|
+
this.user.userPreferences.get().enhancedA11yMode ?? defaultUserPreferences.enhancedA11yMode
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@computed getInputMode() {
|
|
132
|
+
return this.user.userPreferences.get().inputMode ?? defaultUserPreferences.inputMode
|
|
126
133
|
}
|
|
127
134
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { useValue } from '@tldraw/state-react'
|
|
2
2
|
import React, { useEffect, useMemo } from 'react'
|
|
3
3
|
import { RIGHT_MOUSE_BUTTON } from '../constants'
|
|
4
|
-
import {
|
|
5
|
-
preventDefault,
|
|
6
|
-
releasePointerCapture,
|
|
7
|
-
setPointerCapture,
|
|
8
|
-
stopEventPropagation,
|
|
9
|
-
} from '../utils/dom'
|
|
4
|
+
import { preventDefault, releasePointerCapture, setPointerCapture } from '../utils/dom'
|
|
10
5
|
import { getPointerInfo } from '../utils/getPointerInfo'
|
|
11
6
|
import { useEditor } from './useEditor'
|
|
12
7
|
|
|
@@ -17,14 +12,14 @@ export function useCanvasEvents() {
|
|
|
17
12
|
const events = useMemo(
|
|
18
13
|
function canvasEvents() {
|
|
19
14
|
function onPointerDown(e: React.PointerEvent) {
|
|
20
|
-
if ((e
|
|
15
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
21
16
|
|
|
22
17
|
if (e.button === RIGHT_MOUSE_BUTTON) {
|
|
23
18
|
editor.dispatch({
|
|
24
19
|
type: 'pointer',
|
|
25
20
|
target: 'canvas',
|
|
26
21
|
name: 'right_click',
|
|
27
|
-
...getPointerInfo(e),
|
|
22
|
+
...getPointerInfo(editor, e),
|
|
28
23
|
})
|
|
29
24
|
return
|
|
30
25
|
}
|
|
@@ -37,12 +32,12 @@ export function useCanvasEvents() {
|
|
|
37
32
|
type: 'pointer',
|
|
38
33
|
target: 'canvas',
|
|
39
34
|
name: 'pointer_down',
|
|
40
|
-
...getPointerInfo(e),
|
|
35
|
+
...getPointerInfo(editor, e),
|
|
41
36
|
})
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
function onPointerUp(e: React.PointerEvent) {
|
|
45
|
-
if ((e
|
|
40
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
46
41
|
if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
|
|
47
42
|
|
|
48
43
|
releasePointerCapture(e.currentTarget, e)
|
|
@@ -51,31 +46,33 @@ export function useCanvasEvents() {
|
|
|
51
46
|
type: 'pointer',
|
|
52
47
|
target: 'canvas',
|
|
53
48
|
name: 'pointer_up',
|
|
54
|
-
...getPointerInfo(e),
|
|
49
|
+
...getPointerInfo(editor, e),
|
|
55
50
|
})
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
function onPointerEnter(e: React.PointerEvent) {
|
|
59
|
-
if ((e
|
|
54
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
60
55
|
if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
|
|
61
56
|
const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
|
|
62
57
|
editor.updateInstanceState({ isHoveringCanvas: canHover ? true : null })
|
|
63
58
|
}
|
|
64
59
|
|
|
65
60
|
function onPointerLeave(e: React.PointerEvent) {
|
|
66
|
-
if ((e
|
|
61
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
67
62
|
if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
|
|
68
63
|
const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
|
|
69
64
|
editor.updateInstanceState({ isHoveringCanvas: canHover ? false : null })
|
|
70
65
|
}
|
|
71
66
|
|
|
72
67
|
function onTouchStart(e: React.TouchEvent) {
|
|
73
|
-
|
|
68
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
69
|
+
editor.markEventAsHandled(e)
|
|
74
70
|
preventDefault(e)
|
|
75
71
|
}
|
|
76
72
|
|
|
77
73
|
function onTouchEnd(e: React.TouchEvent) {
|
|
78
|
-
|
|
74
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
75
|
+
editor.markEventAsHandled(e)
|
|
79
76
|
// check that e.target is an HTMLElement
|
|
80
77
|
if (!(e.target instanceof HTMLElement)) return
|
|
81
78
|
|
|
@@ -94,12 +91,14 @@ export function useCanvasEvents() {
|
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
function onDragOver(e: React.DragEvent<Element>) {
|
|
94
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
97
95
|
preventDefault(e)
|
|
98
96
|
}
|
|
99
97
|
|
|
100
98
|
async function onDrop(e: React.DragEvent<Element>) {
|
|
99
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
101
100
|
preventDefault(e)
|
|
102
|
-
|
|
101
|
+
e.stopPropagation()
|
|
103
102
|
|
|
104
103
|
if (e.dataTransfer?.files?.length) {
|
|
105
104
|
const files = Array.from(e.dataTransfer.files)
|
|
@@ -124,7 +123,8 @@ export function useCanvasEvents() {
|
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
function onClick(e: React.MouseEvent) {
|
|
127
|
-
|
|
126
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
127
|
+
e.stopPropagation()
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
return {
|
|
@@ -151,8 +151,8 @@ export function useCanvasEvents() {
|
|
|
151
151
|
let lastX: number, lastY: number
|
|
152
152
|
|
|
153
153
|
function onPointerMove(e: PointerEvent) {
|
|
154
|
-
if ((e
|
|
155
|
-
|
|
154
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
155
|
+
editor.markEventAsHandled(e)
|
|
156
156
|
|
|
157
157
|
if (e.clientX === lastX && e.clientY === lastY) return
|
|
158
158
|
lastX = e.clientX
|
|
@@ -168,7 +168,7 @@ export function useCanvasEvents() {
|
|
|
168
168
|
type: 'pointer',
|
|
169
169
|
target: 'canvas',
|
|
170
170
|
name: 'pointer_move',
|
|
171
|
-
...getPointerInfo(singleEvent),
|
|
171
|
+
...getPointerInfo(editor, singleEvent),
|
|
172
172
|
})
|
|
173
173
|
}
|
|
174
174
|
}
|
|
@@ -2,7 +2,7 @@ import { useValue } from '@tldraw/state-react'
|
|
|
2
2
|
import { useEffect } from 'react'
|
|
3
3
|
import { Editor } from '../editor/Editor'
|
|
4
4
|
import { TLKeyboardEventInfo } from '../editor/types/event-types'
|
|
5
|
-
import { activeElementShouldCaptureKeys, preventDefault
|
|
5
|
+
import { activeElementShouldCaptureKeys, preventDefault } from '../utils/dom'
|
|
6
6
|
import { isAccelKey } from '../utils/keyboard'
|
|
7
7
|
import { useContainer } from './useContainer'
|
|
8
8
|
import { useEditor } from './useEditor'
|
|
@@ -29,7 +29,7 @@ export function useDocumentEvents() {
|
|
|
29
29
|
// re-dispatched, which would lead to an infinite loop.
|
|
30
30
|
if ((e as any).isSpecialRedispatchedEvent) return
|
|
31
31
|
preventDefault(e)
|
|
32
|
-
|
|
32
|
+
e.stopPropagation()
|
|
33
33
|
const cvs = container.querySelector('.tl-canvas')
|
|
34
34
|
if (!cvs) return
|
|
35
35
|
const newEvent = new DragEvent(e.type, e)
|
|
@@ -103,8 +103,8 @@ export function useDocumentEvents() {
|
|
|
103
103
|
preventDefault(e)
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
if ((e
|
|
107
|
-
|
|
106
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
107
|
+
editor.markEventAsHandled(e)
|
|
108
108
|
const hasSelectedShapes = !!editor.getSelectedShapeIds().length
|
|
109
109
|
|
|
110
110
|
switch (e.key) {
|
|
@@ -211,8 +211,8 @@ export function useDocumentEvents() {
|
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
const handleKeyUp = (e: KeyboardEvent) => {
|
|
214
|
-
if ((e
|
|
215
|
-
|
|
214
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
215
|
+
editor.markEventAsHandled(e)
|
|
216
216
|
|
|
217
217
|
if (areShortcutsDisabled(editor)) {
|
|
218
218
|
return
|
|
@@ -19,7 +19,7 @@ export function useFixSafariDoubleTapZoomPencilEvents(ref: React.RefObject<HTMLE
|
|
|
19
19
|
|
|
20
20
|
const handleEvent = (e: PointerEvent | TouchEvent) => {
|
|
21
21
|
if (e instanceof PointerEvent && e.pointerType === 'pen') {
|
|
22
|
-
|
|
22
|
+
editor.markEventAsHandled(e)
|
|
23
23
|
const { target } = e
|
|
24
24
|
|
|
25
25
|
// Allow events to propagate if the app is editing a shape, or if the event is occurring in a text area or input
|
|
@@ -3,7 +3,7 @@ import { createUseGesture, pinchAction, wheelAction } from '@use-gesture/react'
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { TLWheelEventInfo } from '../editor/types/event-types'
|
|
5
5
|
import { Vec } from '../primitives/Vec'
|
|
6
|
-
import { preventDefault
|
|
6
|
+
import { preventDefault } from '../utils/dom'
|
|
7
7
|
import { isAccelKey } from '../utils/keyboard'
|
|
8
8
|
import { normalizeWheel } from '../utils/normalizeWheel'
|
|
9
9
|
import { useEditor } from './useEditor'
|
|
@@ -113,7 +113,7 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
preventDefault(event)
|
|
116
|
-
|
|
116
|
+
event.stopPropagation()
|
|
117
117
|
const delta = normalizeWheel(event)
|
|
118
118
|
|
|
119
119
|
if (delta.x === 0 && delta.y === 0) return
|
|
@@ -16,7 +16,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
|
|
|
16
16
|
|
|
17
17
|
return React.useMemo(() => {
|
|
18
18
|
const onPointerDown = (e: React.PointerEvent) => {
|
|
19
|
-
if ((e
|
|
19
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
20
20
|
|
|
21
21
|
// Must set pointer capture on an HTML element!
|
|
22
22
|
const target = loopToHtmlElement(e.currentTarget)
|
|
@@ -32,7 +32,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
|
|
|
32
32
|
handle,
|
|
33
33
|
shape,
|
|
34
34
|
name: 'pointer_down',
|
|
35
|
-
...getPointerInfo(e),
|
|
35
|
+
...getPointerInfo(editor, e),
|
|
36
36
|
})
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -40,7 +40,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
|
|
|
40
40
|
let lastX: number, lastY: number
|
|
41
41
|
|
|
42
42
|
const onPointerMove = (e: React.PointerEvent) => {
|
|
43
|
-
if ((e
|
|
43
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
44
44
|
if (e.clientX === lastX && e.clientY === lastY) return
|
|
45
45
|
lastX = e.clientX
|
|
46
46
|
lastY = e.clientY
|
|
@@ -55,12 +55,12 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
|
|
|
55
55
|
handle,
|
|
56
56
|
shape,
|
|
57
57
|
name: 'pointer_move',
|
|
58
|
-
...getPointerInfo(e),
|
|
58
|
+
...getPointerInfo(editor, e),
|
|
59
59
|
})
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const onPointerUp = (e: React.PointerEvent) => {
|
|
63
|
-
if ((e
|
|
63
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
64
64
|
|
|
65
65
|
const target = loopToHtmlElement(e.currentTarget)
|
|
66
66
|
releasePointerCapture(target, e)
|
|
@@ -75,7 +75,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
|
|
|
75
75
|
handle,
|
|
76
76
|
shape,
|
|
77
77
|
name: 'pointer_up',
|
|
78
|
-
...getPointerInfo(e),
|
|
78
|
+
...getPointerInfo(editor, e),
|
|
79
79
|
})
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import { RIGHT_MOUSE_BUTTON } from '../constants'
|
|
3
3
|
import { TLSelectionHandle } from '../editor/types/selection-types'
|
|
4
|
-
import {
|
|
5
|
-
loopToHtmlElement,
|
|
6
|
-
releasePointerCapture,
|
|
7
|
-
setPointerCapture,
|
|
8
|
-
stopEventPropagation,
|
|
9
|
-
} from '../utils/dom'
|
|
4
|
+
import { loopToHtmlElement, releasePointerCapture, setPointerCapture } from '../utils/dom'
|
|
10
5
|
import { getPointerInfo } from '../utils/getPointerInfo'
|
|
11
6
|
import { useEditor } from './useEditor'
|
|
12
7
|
|
|
@@ -17,7 +12,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
|
|
|
17
12
|
const events = useMemo(
|
|
18
13
|
function selectionEvents() {
|
|
19
14
|
const onPointerDown: React.PointerEventHandler = (e) => {
|
|
20
|
-
if ((e
|
|
15
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
21
16
|
|
|
22
17
|
if (e.button === RIGHT_MOUSE_BUTTON) {
|
|
23
18
|
editor.dispatch({
|
|
@@ -25,7 +20,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
|
|
|
25
20
|
target: 'selection',
|
|
26
21
|
handle,
|
|
27
22
|
name: 'right_click',
|
|
28
|
-
...getPointerInfo(e),
|
|
23
|
+
...getPointerInfo(editor, e),
|
|
29
24
|
})
|
|
30
25
|
return
|
|
31
26
|
}
|
|
@@ -52,16 +47,16 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
|
|
|
52
47
|
type: 'pointer',
|
|
53
48
|
target: 'selection',
|
|
54
49
|
handle,
|
|
55
|
-
...getPointerInfo(e),
|
|
50
|
+
...getPointerInfo(editor, e),
|
|
56
51
|
})
|
|
57
|
-
|
|
52
|
+
editor.markEventAsHandled(e)
|
|
58
53
|
}
|
|
59
54
|
|
|
60
55
|
// Track the last screen point
|
|
61
56
|
let lastX: number, lastY: number
|
|
62
57
|
|
|
63
58
|
function onPointerMove(e: React.PointerEvent) {
|
|
64
|
-
if ((e
|
|
59
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
65
60
|
if (e.button !== 0) return
|
|
66
61
|
if (e.clientX === lastX && e.clientY === lastY) return
|
|
67
62
|
lastX = e.clientX
|
|
@@ -72,12 +67,12 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
|
|
|
72
67
|
type: 'pointer',
|
|
73
68
|
target: 'selection',
|
|
74
69
|
handle,
|
|
75
|
-
...getPointerInfo(e),
|
|
70
|
+
...getPointerInfo(editor, e),
|
|
76
71
|
})
|
|
77
72
|
}
|
|
78
73
|
|
|
79
74
|
const onPointerUp: React.PointerEventHandler = (e) => {
|
|
80
|
-
if ((e
|
|
75
|
+
if (editor.wasEventAlreadyHandled(e)) return
|
|
81
76
|
if (e.button !== 0) return
|
|
82
77
|
|
|
83
78
|
editor.dispatch({
|
|
@@ -85,7 +80,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
|
|
|
85
80
|
type: 'pointer',
|
|
86
81
|
target: 'selection',
|
|
87
82
|
handle,
|
|
88
|
-
...getPointerInfo(e),
|
|
83
|
+
...getPointerInfo(editor, e),
|
|
89
84
|
})
|
|
90
85
|
}
|
|
91
86
|
|
|
@@ -266,7 +266,7 @@ describe('LicenseManager', () => {
|
|
|
266
266
|
delete window.location
|
|
267
267
|
// @ts-ignore
|
|
268
268
|
window.location = new URL(
|
|
269
|
-
'vscode-webview
|
|
269
|
+
'vscode-webview://1ipd8pun8ud7nd7hv9d112g7evi7m10vak9vviuvia66ou6aibp3/index.html?id=6ec2dc7a-afe9-45d9-bd71-1749f9568d28&origin=955b256f-37e1-4a72-a2f4-ad633e88239c&swVersion=4&extensionId=tldraw-org.tldraw-vscode&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app'
|
|
270
270
|
)
|
|
271
271
|
|
|
272
272
|
const permissiveHostsInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
@@ -286,7 +286,7 @@ describe('LicenseManager', () => {
|
|
|
286
286
|
delete window.location
|
|
287
287
|
// @ts-ignore
|
|
288
288
|
window.location = new URL(
|
|
289
|
-
'vscode-webview
|
|
289
|
+
'vscode-webview://1ipd8pun8ud7nd7hv9d112g7evi7m10vak9vviuvia66ou6aibp3/index.html?id=6ec2dc7a-afe9-45d9-bd71-1749f9568d28&origin=955b256f-37e1-4a72-a2f4-ad633e88239c&swVersion=4&extensionId=tldraw-org.tldraw-vscode&platform=electron&vscode-resource-base-authority=vscode-resource.vscode-cdn.net&parentOrigin=vscode-file%3A%2F%2Fvscode-app'
|
|
290
290
|
)
|
|
291
291
|
|
|
292
292
|
const permissiveHostsInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
@@ -300,6 +300,70 @@ describe('LicenseManager', () => {
|
|
|
300
300
|
)) as ValidLicenseKeyResult
|
|
301
301
|
expect(result.isDomainValid).toBe(false)
|
|
302
302
|
})
|
|
303
|
+
|
|
304
|
+
it('Succeeds if it is a native app', async () => {
|
|
305
|
+
// @ts-ignore
|
|
306
|
+
delete window.location
|
|
307
|
+
// @ts-ignore
|
|
308
|
+
window.location = new URL('app-bundle://app/index.html')
|
|
309
|
+
|
|
310
|
+
const nativeLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
311
|
+
nativeLicenseInfo[PROPERTIES.FLAGS] = FLAGS.NATIVE_LICENSE
|
|
312
|
+
nativeLicenseInfo[PROPERTIES.HOSTS] = ['app-bundle:']
|
|
313
|
+
const nativeLicenseKey = await generateLicenseKey(JSON.stringify(nativeLicenseInfo), keyPair)
|
|
314
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
315
|
+
nativeLicenseKey
|
|
316
|
+
)) as ValidLicenseKeyResult
|
|
317
|
+
expect(result.isDomainValid).toBe(true)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('Succeeds if it is a native app with a wildcard', async () => {
|
|
321
|
+
// @ts-ignore
|
|
322
|
+
delete window.location
|
|
323
|
+
// @ts-ignore
|
|
324
|
+
window.location = new URL('app-bundle://unique-id-123/index.html')
|
|
325
|
+
|
|
326
|
+
const nativeLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
327
|
+
nativeLicenseInfo[PROPERTIES.FLAGS] = FLAGS.NATIVE_LICENSE
|
|
328
|
+
nativeLicenseInfo[PROPERTIES.HOSTS] = ['^app-bundle://unique-id-123.*']
|
|
329
|
+
const nativeLicenseKey = await generateLicenseKey(JSON.stringify(nativeLicenseInfo), keyPair)
|
|
330
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
331
|
+
nativeLicenseKey
|
|
332
|
+
)) as ValidLicenseKeyResult
|
|
333
|
+
expect(result.isDomainValid).toBe(true)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('Succeeds if it is a native app with a wildcard and search param', async () => {
|
|
337
|
+
// @ts-ignore
|
|
338
|
+
delete window.location
|
|
339
|
+
// @ts-ignore
|
|
340
|
+
window.location = new URL('app-bundle://app/index.html?unique-id-123')
|
|
341
|
+
|
|
342
|
+
const nativeLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
343
|
+
nativeLicenseInfo[PROPERTIES.FLAGS] = FLAGS.NATIVE_LICENSE
|
|
344
|
+
nativeLicenseInfo[PROPERTIES.HOSTS] = ['^app-bundle://app.*unique-id-123.*']
|
|
345
|
+
const nativeLicenseKey = await generateLicenseKey(JSON.stringify(nativeLicenseInfo), keyPair)
|
|
346
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
347
|
+
nativeLicenseKey
|
|
348
|
+
)) as ValidLicenseKeyResult
|
|
349
|
+
expect(result.isDomainValid).toBe(true)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it('Fails if it is a native app with the wrong protocol', async () => {
|
|
353
|
+
// @ts-ignore
|
|
354
|
+
delete window.location
|
|
355
|
+
// @ts-ignore
|
|
356
|
+
window.location = new URL('blah-blundle://app/index.html')
|
|
357
|
+
|
|
358
|
+
const nativeLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
359
|
+
nativeLicenseInfo[PROPERTIES.FLAGS] = FLAGS.NATIVE_LICENSE
|
|
360
|
+
nativeLicenseInfo[PROPERTIES.HOSTS] = ['app-bundle:']
|
|
361
|
+
const nativeLicenseKey = await generateLicenseKey(JSON.stringify(nativeLicenseInfo), keyPair)
|
|
362
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
363
|
+
nativeLicenseKey
|
|
364
|
+
)) as ValidLicenseKeyResult
|
|
365
|
+
expect(result.isDomainValid).toBe(false)
|
|
366
|
+
})
|
|
303
367
|
})
|
|
304
368
|
|
|
305
369
|
describe('License types and flags', () => {
|
|
@@ -316,6 +380,17 @@ describe('LicenseManager', () => {
|
|
|
316
380
|
expect(result.isInternalLicense).toBe(true)
|
|
317
381
|
})
|
|
318
382
|
|
|
383
|
+
it('Checks for native license', async () => {
|
|
384
|
+
const nativeLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
385
|
+
nativeLicenseInfo[PROPERTIES.FLAGS] = FLAGS.NATIVE_LICENSE
|
|
386
|
+
const nativeLicenseKey = await generateLicenseKey(JSON.stringify(nativeLicenseInfo), keyPair)
|
|
387
|
+
|
|
388
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
389
|
+
nativeLicenseKey
|
|
390
|
+
)) as ValidLicenseKeyResult
|
|
391
|
+
expect(result.isNativeLicense).toBe(true)
|
|
392
|
+
})
|
|
393
|
+
|
|
319
394
|
it('Checks for license with watermark', async () => {
|
|
320
395
|
const withWatermarkLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
321
396
|
withWatermarkLicenseInfo[PROPERTIES.FLAGS] |= FLAGS.WITH_WATERMARK
|
|
@@ -553,6 +628,7 @@ function getDefaultLicenseResult(overrides: Partial<ValidLicenseKeyResult>): Val
|
|
|
553
628
|
isAnnualLicense: true,
|
|
554
629
|
isAnnualLicenseExpired: false,
|
|
555
630
|
isInternalLicense: false,
|
|
631
|
+
isNativeLicense: false,
|
|
556
632
|
isDevelopment: false,
|
|
557
633
|
isDomainValid: true,
|
|
558
634
|
isPerpetualLicense: false,
|
|
@@ -6,11 +6,22 @@ import { importPublicKey, str2ab } from '../utils/licensing'
|
|
|
6
6
|
const GRACE_PERIOD_DAYS = 30
|
|
7
7
|
|
|
8
8
|
export const FLAGS = {
|
|
9
|
+
// -- MUTUALLY EXCLUSIVE FLAGS --
|
|
10
|
+
// Annual means the license expires after a time period, usually 1 year.
|
|
9
11
|
ANNUAL_LICENSE: 1,
|
|
12
|
+
// Perpetual means the license never expires up to the max supported version.
|
|
10
13
|
PERPETUAL_LICENSE: 1 << 1,
|
|
14
|
+
|
|
15
|
+
// -- ADDITIVE FLAGS --
|
|
16
|
+
// Internal means the license is for internal use only.
|
|
11
17
|
INTERNAL_LICENSE: 1 << 2,
|
|
18
|
+
// Watermark means the product is watermarked.
|
|
12
19
|
WITH_WATERMARK: 1 << 3,
|
|
20
|
+
// Evaluation means the license is for evaluation purposes only.
|
|
13
21
|
EVALUATION_LICENSE: 1 << 4,
|
|
22
|
+
// Native means the license is for native apps which switches
|
|
23
|
+
// on special-case logic.
|
|
24
|
+
NATIVE_LICENSE: 1 << 5,
|
|
14
25
|
}
|
|
15
26
|
const HIGHEST_FLAG = Math.max(...Object.values(FLAGS))
|
|
16
27
|
|
|
@@ -69,6 +80,7 @@ export interface ValidLicenseKeyResult {
|
|
|
69
80
|
isPerpetualLicense: boolean
|
|
70
81
|
isPerpetualLicenseExpired: boolean
|
|
71
82
|
isInternalLicense: boolean
|
|
83
|
+
isNativeLicense: boolean
|
|
72
84
|
isLicensedWithWatermark: boolean
|
|
73
85
|
isEvaluationLicense: boolean
|
|
74
86
|
isEvaluationLicenseExpired: boolean
|
|
@@ -166,6 +178,9 @@ export class LicenseManager {
|
|
|
166
178
|
const url = new URL(WATERMARK_TRACK_SRC)
|
|
167
179
|
url.searchParams.set('version', version)
|
|
168
180
|
url.searchParams.set('license_type', trackType)
|
|
181
|
+
if ('license' in result) {
|
|
182
|
+
url.searchParams.set('license_id', result.license.id)
|
|
183
|
+
}
|
|
169
184
|
|
|
170
185
|
// eslint-disable-next-line no-restricted-globals
|
|
171
186
|
fetch(url.toString())
|
|
@@ -271,6 +286,7 @@ export class LicenseManager {
|
|
|
271
286
|
isPerpetualLicense,
|
|
272
287
|
isPerpetualLicenseExpired: isPerpetualLicense && this.isPerpetualLicenseExpired(expiryDate),
|
|
273
288
|
isInternalLicense: this.isFlagEnabled(licenseInfo.flags, FLAGS.INTERNAL_LICENSE),
|
|
289
|
+
isNativeLicense: this.isNativeLicense(licenseInfo),
|
|
274
290
|
isLicensedWithWatermark: this.isFlagEnabled(licenseInfo.flags, FLAGS.WITH_WATERMARK),
|
|
275
291
|
isEvaluationLicense,
|
|
276
292
|
isEvaluationLicenseExpired:
|
|
@@ -291,13 +307,13 @@ export class LicenseManager {
|
|
|
291
307
|
const currentHostname = window.location.hostname.toLowerCase()
|
|
292
308
|
|
|
293
309
|
return licenseInfo.hosts.some((host) => {
|
|
294
|
-
const
|
|
310
|
+
const normalizedHostOrUrlRegex = host.toLowerCase().trim()
|
|
295
311
|
|
|
296
312
|
// Allow the domain if listed and www variations, 'example.com' allows 'example.com' and 'www.example.com'
|
|
297
313
|
if (
|
|
298
|
-
|
|
299
|
-
`www.${
|
|
300
|
-
|
|
314
|
+
normalizedHostOrUrlRegex === currentHostname ||
|
|
315
|
+
`www.${normalizedHostOrUrlRegex}` === currentHostname ||
|
|
316
|
+
normalizedHostOrUrlRegex === `www.${currentHostname}`
|
|
301
317
|
) {
|
|
302
318
|
return true
|
|
303
319
|
}
|
|
@@ -308,6 +324,12 @@ export class LicenseManager {
|
|
|
308
324
|
return true
|
|
309
325
|
}
|
|
310
326
|
|
|
327
|
+
// Native license support
|
|
328
|
+
// In this case, `normalizedHost` is actually a protocol, e.g. `app-bundle:`
|
|
329
|
+
if (this.isNativeLicense(licenseInfo)) {
|
|
330
|
+
return new RegExp(normalizedHostOrUrlRegex).test(window.location.href)
|
|
331
|
+
}
|
|
332
|
+
|
|
311
333
|
// Glob testing, we only support '*.somedomain.com' right now.
|
|
312
334
|
if (host.includes('*')) {
|
|
313
335
|
const globToRegex = new RegExp(host.replace(/\*/g, '.*?'))
|
|
@@ -318,7 +340,7 @@ export class LicenseManager {
|
|
|
318
340
|
if (window.location.protocol === 'vscode-webview:') {
|
|
319
341
|
const currentUrl = new URL(window.location.href)
|
|
320
342
|
const extensionId = currentUrl.searchParams.get('extensionId')
|
|
321
|
-
if (
|
|
343
|
+
if (normalizedHostOrUrlRegex === extensionId) {
|
|
322
344
|
return true
|
|
323
345
|
}
|
|
324
346
|
}
|
|
@@ -327,6 +349,10 @@ export class LicenseManager {
|
|
|
327
349
|
})
|
|
328
350
|
}
|
|
329
351
|
|
|
352
|
+
private isNativeLicense(licenseInfo: LicenseInfo) {
|
|
353
|
+
return this.isFlagEnabled(licenseInfo.flags, FLAGS.NATIVE_LICENSE)
|
|
354
|
+
}
|
|
355
|
+
|
|
330
356
|
private getExpirationDateWithoutGracePeriod(expiryDate: Date) {
|
|
331
357
|
return new Date(expiryDate.getFullYear(), expiryDate.getMonth(), expiryDate.getDate())
|
|
332
358
|
}
|