@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.
Files changed (98) hide show
  1. package/dist-cjs/index.d.ts +50 -8
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/lib/TldrawEditor.js +0 -2
  4. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  5. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +11 -1
  6. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  7. package/dist-cjs/lib/config/TLUserPreferences.js +15 -4
  8. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  9. package/dist-cjs/lib/editor/Editor.js +41 -3
  10. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  11. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +4 -2
  12. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  13. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +11 -6
  14. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  15. package/dist-cjs/lib/hooks/useCanvasEvents.js +19 -16
  16. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  17. package/dist-cjs/lib/hooks/useDocumentEvents.js +5 -5
  18. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  19. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -2
  20. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  21. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  22. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  23. package/dist-cjs/lib/hooks/useHandleEvents.js +6 -6
  24. package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
  25. package/dist-cjs/lib/hooks/useSelectionEvents.js +8 -8
  26. package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
  27. package/dist-cjs/lib/license/LicenseManager.js +24 -4
  28. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  29. package/dist-cjs/lib/license/Watermark.js +97 -90
  30. package/dist-cjs/lib/license/Watermark.js.map +2 -2
  31. package/dist-cjs/lib/utils/dom.js.map +2 -2
  32. package/dist-cjs/lib/utils/getPointerInfo.js +2 -3
  33. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  34. package/dist-cjs/lib/utils/reparenting.js +5 -1
  35. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  36. package/dist-cjs/version.js +3 -3
  37. package/dist-cjs/version.js.map +1 -1
  38. package/dist-esm/index.d.mts +50 -8
  39. package/dist-esm/index.mjs +1 -1
  40. package/dist-esm/lib/TldrawEditor.mjs +0 -2
  41. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  42. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +11 -1
  43. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  44. package/dist-esm/lib/config/TLUserPreferences.mjs +15 -4
  45. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  46. package/dist-esm/lib/editor/Editor.mjs +41 -3
  47. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  48. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
  49. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  50. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +11 -6
  51. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  52. package/dist-esm/lib/hooks/useCanvasEvents.mjs +20 -22
  53. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  54. package/dist-esm/lib/hooks/useDocumentEvents.mjs +6 -6
  55. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  56. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +1 -2
  57. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  58. package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
  59. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  60. package/dist-esm/lib/hooks/useHandleEvents.mjs +6 -6
  61. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
  62. package/dist-esm/lib/hooks/useSelectionEvents.mjs +9 -14
  63. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
  64. package/dist-esm/lib/license/LicenseManager.mjs +24 -4
  65. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  66. package/dist-esm/lib/license/Watermark.mjs +98 -91
  67. package/dist-esm/lib/license/Watermark.mjs.map +2 -2
  68. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  69. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -3
  70. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  71. package/dist-esm/lib/utils/reparenting.mjs +5 -1
  72. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  73. package/dist-esm/version.mjs +3 -3
  74. package/dist-esm/version.mjs.map +1 -1
  75. package/package.json +7 -7
  76. package/src/lib/TldrawEditor.tsx +0 -2
  77. package/src/lib/components/default-components/DefaultCanvas.tsx +7 -1
  78. package/src/lib/config/TLUserPreferences.ts +16 -3
  79. package/src/lib/editor/Editor.test.ts +90 -0
  80. package/src/lib/editor/Editor.ts +53 -3
  81. package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
  82. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +30 -8
  83. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +10 -3
  84. package/src/lib/hooks/useCanvasEvents.ts +20 -20
  85. package/src/lib/hooks/useDocumentEvents.ts +6 -6
  86. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  87. package/src/lib/hooks/useGestureEvents.ts +2 -2
  88. package/src/lib/hooks/useHandleEvents.ts +6 -6
  89. package/src/lib/hooks/useSelectionEvents.ts +9 -14
  90. package/src/lib/license/LicenseManager.test.ts +78 -2
  91. package/src/lib/license/LicenseManager.ts +31 -5
  92. package/src/lib/license/Watermark.tsx +100 -92
  93. package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
  94. package/src/lib/utils/dom.test.ts +103 -0
  95. package/src/lib/utils/dom.ts +8 -1
  96. package/src/lib/utils/getPointerInfo.ts +3 -2
  97. package/src/lib/utils/reparenting.ts +7 -1
  98. 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
- showUiLabels: false,
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
- showUiLabels: mockUserPreferences.showUiLabels,
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('getShowUiLabels', () => {
380
- it('should return user show ui labels setting', () => {
381
- expect(userPreferencesManager.getShowUiLabels()).toBe(mockUserPreferences.showUiLabels)
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 show ui labels when null', () => {
385
- userPreferencesAtom.set({ ...mockUserPreferences, showUiLabels: null })
386
- expect(userPreferencesManager.getShowUiLabels()).toBe(defaultUserPreferences.showUiLabels)
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
- showUiLabels: this.getShowUiLabels(),
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 getShowUiLabels() {
125
- return this.user.userPreferences.get().showUiLabels ?? defaultUserPreferences.showUiLabels
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 as any).isKilled) return
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 as any).isKilled) return
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 as any).isKilled) return
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 as any).isKilled) return
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
- ;(e as any).isKilled = true
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
- ;(e as any).isKilled = true
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
- stopEventPropagation(e)
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
- stopEventPropagation(e)
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 as any).isKilled) return
155
- ;(e as any).isKilled = true
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, stopEventPropagation } from '../utils/dom'
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
- stopEventPropagation(e)
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 as any).isKilled) return
107
- ;(e as any).isKilled = true
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 as any).isKilled) return
215
- ;(e as any).isKilled = true
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
- ;(e as any).isKilled = true
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, stopEventPropagation } from '../utils/dom'
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
- stopEventPropagation(event)
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 as any).isKilled) return
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 as any).isKilled) return
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 as any).isKilled) return
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 as any).isKilled) return
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
- stopEventPropagation(e)
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 as any).isKilled) return
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 as any).isKilled) return
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: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'
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: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'
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 normalizedHost = host.toLowerCase().trim()
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
- normalizedHost === currentHostname ||
299
- `www.${normalizedHost}` === currentHostname ||
300
- normalizedHost === `www.${currentHostname}`
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 (normalizedHost === extensionId) {
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
  }