@tldraw/editor 3.16.0-canary.e372fad80b6d → 3.16.0-canary.e455ab838b8f

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 (79) hide show
  1. package/dist-cjs/index.d.ts +27 -28
  2. package/dist-cjs/index.js +2 -4
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +0 -2
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +4 -4
  7. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  8. package/dist-cjs/lib/editor/Editor.js +29 -0
  9. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  10. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +4 -2
  11. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  12. package/dist-cjs/lib/hooks/useCanvasEvents.js +17 -17
  13. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  14. package/dist-cjs/lib/hooks/useDocumentEvents.js +4 -4
  15. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  16. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -1
  17. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  18. package/dist-cjs/lib/hooks/useHandleEvents.js +6 -6
  19. package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
  20. package/dist-cjs/lib/hooks/useSelectionEvents.js +8 -8
  21. package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
  22. package/dist-cjs/lib/license/LicenseManager.js +3 -0
  23. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  24. package/dist-cjs/lib/license/Watermark.js +97 -90
  25. package/dist-cjs/lib/license/Watermark.js.map +2 -2
  26. package/dist-cjs/lib/utils/dom.js +1 -12
  27. package/dist-cjs/lib/utils/dom.js.map +2 -2
  28. package/dist-cjs/lib/utils/getPointerInfo.js +2 -3
  29. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  30. package/dist-cjs/version.js +3 -3
  31. package/dist-cjs/version.js.map +1 -1
  32. package/dist-esm/index.d.mts +27 -28
  33. package/dist-esm/index.mjs +3 -7
  34. package/dist-esm/index.mjs.map +2 -2
  35. package/dist-esm/lib/TldrawEditor.mjs +0 -2
  36. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  37. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +5 -5
  38. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  39. package/dist-esm/lib/editor/Editor.mjs +29 -0
  40. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  41. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
  42. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  43. package/dist-esm/lib/hooks/useCanvasEvents.mjs +18 -24
  44. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  45. package/dist-esm/lib/hooks/useDocumentEvents.mjs +5 -10
  46. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  47. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +2 -2
  48. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  49. package/dist-esm/lib/hooks/useHandleEvents.mjs +7 -12
  50. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
  51. package/dist-esm/lib/hooks/useSelectionEvents.mjs +9 -15
  52. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
  53. package/dist-esm/lib/license/LicenseManager.mjs +3 -0
  54. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  55. package/dist-esm/lib/license/Watermark.mjs +98 -91
  56. package/dist-esm/lib/license/Watermark.mjs.map +2 -2
  57. package/dist-esm/lib/utils/dom.mjs +1 -12
  58. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  59. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -3
  60. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  61. package/dist-esm/version.mjs +3 -3
  62. package/dist-esm/version.mjs.map +1 -1
  63. package/package.json +7 -7
  64. package/src/index.ts +0 -2
  65. package/src/lib/TldrawEditor.tsx +0 -2
  66. package/src/lib/components/default-components/DefaultCanvas.tsx +5 -5
  67. package/src/lib/editor/Editor.ts +33 -0
  68. package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
  69. package/src/lib/hooks/useCanvasEvents.ts +18 -24
  70. package/src/lib/hooks/useDocumentEvents.ts +5 -10
  71. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +2 -2
  72. package/src/lib/hooks/useHandleEvents.ts +7 -12
  73. package/src/lib/hooks/useSelectionEvents.ts +9 -15
  74. package/src/lib/license/LicenseManager.ts +3 -0
  75. package/src/lib/license/Watermark.tsx +100 -92
  76. package/src/lib/utils/dom.test.ts +33 -24
  77. package/src/lib/utils/dom.ts +1 -31
  78. package/src/lib/utils/getPointerInfo.ts +3 -3
  79. package/src/version.ts +3 -3
@@ -21,7 +21,7 @@ import { Mat } from '../../primitives/Mat'
21
21
  import { Vec } from '../../primitives/Vec'
22
22
  import { toDomPrecision } from '../../primitives/utils'
23
23
  import { debugFlags } from '../../utils/debug-flags'
24
- import { markEventAsHandled, setStyleProperty } from '../../utils/dom'
24
+ import { setStyleProperty } from '../../utils/dom'
25
25
  import { GeometryDebuggingView } from '../GeometryDebuggingView'
26
26
  import { LiveCollaborators } from '../LiveCollaborators'
27
27
  import { MenuClickCapture } from '../MenuClickCapture'
@@ -174,10 +174,10 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
174
174
  </div>
175
175
  <div
176
176
  className="tl-canvas__in-front"
177
- onPointerDown={markEventAsHandled}
178
- onPointerUp={markEventAsHandled}
179
- onTouchStart={markEventAsHandled}
180
- onTouchEnd={markEventAsHandled}
177
+ onPointerDown={editor.markEventAsHandled}
178
+ onPointerUp={editor.markEventAsHandled}
179
+ onTouchStart={editor.markEventAsHandled}
180
+ onTouchEnd={editor.markEventAsHandled}
181
181
  >
182
182
  <InFrontOfTheCanvasWrapper />
183
183
  </div>
@@ -343,6 +343,8 @@ export class Editor extends EventEmitter<TLEventMap> {
343
343
  this.root = new NewRoot(this)
344
344
  this.root.children = {}
345
345
 
346
+ this.markEventAsHandled = this.markEventAsHandled.bind(this)
347
+
346
348
  const allShapeUtils = checkShapesAndAddCore(shapeUtils)
347
349
 
348
350
  const _shapeUtils = {} as Record<string, ShapeUtil<any>>
@@ -10097,6 +10099,37 @@ export class Editor extends EventEmitter<TLEventMap> {
10097
10099
  /** @internal */
10098
10100
  private performanceTrackerTimeout = -1 as any
10099
10101
 
10102
+ /** @internal */
10103
+ private handledEvents = new WeakSet<Event>()
10104
+
10105
+ /**
10106
+ * In tldraw, events are sometimes handled by multiple components. For example, the shapes might
10107
+ * have events, but the canvas handles events too. The way that the canvas handles events can
10108
+ * interfere with the with the shapes event handlers - for example, it calls `.preventDefault()`
10109
+ * on `pointerDown`, which also prevents `click` events from firing on the shapes.
10110
+ *
10111
+ * You can use `.stopPropagation()` to prevent the event from propagating to the rest of the
10112
+ * DOM, but that can impact non-tldraw event handlers set up elsewhere. By using
10113
+ * `markEventAsHandled`, you'll stop other parts of tldraw from handling the event without
10114
+ * impacting other, non-tldraw event handlers. See also {@link Editor.wasEventAlreadyHandled}.
10115
+ *
10116
+ * @public
10117
+ */
10118
+ markEventAsHandled(e: Event | { nativeEvent: Event }) {
10119
+ const nativeEvent = 'nativeEvent' in e ? e.nativeEvent : e
10120
+ this.handledEvents.add(nativeEvent)
10121
+ }
10122
+
10123
+ /**
10124
+ * Checks if an event has already been handled. See {@link Editor.markEventAsHandled}.
10125
+ *
10126
+ * @public
10127
+ */
10128
+ wasEventAlreadyHandled(e: Event | { nativeEvent: Event }) {
10129
+ const nativeEvent = 'nativeEvent' in e ? e.nativeEvent : e
10130
+ return this.handledEvents.has(nativeEvent)
10131
+ }
10132
+
10100
10133
  /**
10101
10134
  * Dispatch an event to the editor.
10102
10135
  *
@@ -58,8 +58,12 @@ export class FocusManager {
58
58
 
59
59
  private handleKeyDown(keyEvent: KeyboardEvent) {
60
60
  const container = this.editor.getContainer()
61
- if (this.editor.isIn('select.editing_shape')) return
62
- if (document.activeElement === container && this.editor.getSelectedShapeIds().length > 0) return
61
+ const activeEl = document.activeElement
62
+ // Edit mode should remove the focus ring, however if the active element's
63
+ // parent is the contextual toolbar, then allow it.
64
+ if (this.editor.isIn('select.editing_shape') && !activeEl?.closest('.tlui-contextual-toolbar'))
65
+ return
66
+ if (activeEl === container && this.editor.getSelectedShapeIds().length > 0) return
63
67
  if (['Tab', 'ArrowUp', 'ArrowDown'].includes(keyEvent.key)) {
64
68
  container.classList.remove('tl-container__no-focus-ring')
65
69
  }
@@ -1,13 +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
- markEventAsHandled,
6
- preventDefault,
7
- releasePointerCapture,
8
- setPointerCapture,
9
- wasEventAlreadyHandled,
10
- } from '../utils/dom'
4
+ import { preventDefault, releasePointerCapture, setPointerCapture } from '../utils/dom'
11
5
  import { getPointerInfo } from '../utils/getPointerInfo'
12
6
  import { useEditor } from './useEditor'
13
7
 
@@ -18,14 +12,14 @@ export function useCanvasEvents() {
18
12
  const events = useMemo(
19
13
  function canvasEvents() {
20
14
  function onPointerDown(e: React.PointerEvent) {
21
- if (wasEventAlreadyHandled(e)) return
15
+ if (editor.wasEventAlreadyHandled(e)) return
22
16
 
23
17
  if (e.button === RIGHT_MOUSE_BUTTON) {
24
18
  editor.dispatch({
25
19
  type: 'pointer',
26
20
  target: 'canvas',
27
21
  name: 'right_click',
28
- ...getPointerInfo(e),
22
+ ...getPointerInfo(editor, e),
29
23
  })
30
24
  return
31
25
  }
@@ -38,12 +32,12 @@ export function useCanvasEvents() {
38
32
  type: 'pointer',
39
33
  target: 'canvas',
40
34
  name: 'pointer_down',
41
- ...getPointerInfo(e),
35
+ ...getPointerInfo(editor, e),
42
36
  })
43
37
  }
44
38
 
45
39
  function onPointerUp(e: React.PointerEvent) {
46
- if (wasEventAlreadyHandled(e)) return
40
+ if (editor.wasEventAlreadyHandled(e)) return
47
41
  if (e.button !== 0 && e.button !== 1 && e.button !== 2 && e.button !== 5) return
48
42
 
49
43
  releasePointerCapture(e.currentTarget, e)
@@ -52,33 +46,33 @@ export function useCanvasEvents() {
52
46
  type: 'pointer',
53
47
  target: 'canvas',
54
48
  name: 'pointer_up',
55
- ...getPointerInfo(e),
49
+ ...getPointerInfo(editor, e),
56
50
  })
57
51
  }
58
52
 
59
53
  function onPointerEnter(e: React.PointerEvent) {
60
- if (wasEventAlreadyHandled(e)) return
54
+ if (editor.wasEventAlreadyHandled(e)) return
61
55
  if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
62
56
  const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
63
57
  editor.updateInstanceState({ isHoveringCanvas: canHover ? true : null })
64
58
  }
65
59
 
66
60
  function onPointerLeave(e: React.PointerEvent) {
67
- if (wasEventAlreadyHandled(e)) return
61
+ if (editor.wasEventAlreadyHandled(e)) return
68
62
  if (editor.getInstanceState().isPenMode && e.pointerType !== 'pen') return
69
63
  const canHover = e.pointerType === 'mouse' || e.pointerType === 'pen'
70
64
  editor.updateInstanceState({ isHoveringCanvas: canHover ? false : null })
71
65
  }
72
66
 
73
67
  function onTouchStart(e: React.TouchEvent) {
74
- if (wasEventAlreadyHandled(e)) return
75
- markEventAsHandled(e)
68
+ if (editor.wasEventAlreadyHandled(e)) return
69
+ editor.markEventAsHandled(e)
76
70
  preventDefault(e)
77
71
  }
78
72
 
79
73
  function onTouchEnd(e: React.TouchEvent) {
80
- if (wasEventAlreadyHandled(e)) return
81
- markEventAsHandled(e)
74
+ if (editor.wasEventAlreadyHandled(e)) return
75
+ editor.markEventAsHandled(e)
82
76
  // check that e.target is an HTMLElement
83
77
  if (!(e.target instanceof HTMLElement)) return
84
78
 
@@ -97,12 +91,12 @@ export function useCanvasEvents() {
97
91
  }
98
92
 
99
93
  function onDragOver(e: React.DragEvent<Element>) {
100
- if (wasEventAlreadyHandled(e)) return
94
+ if (editor.wasEventAlreadyHandled(e)) return
101
95
  preventDefault(e)
102
96
  }
103
97
 
104
98
  async function onDrop(e: React.DragEvent<Element>) {
105
- if (wasEventAlreadyHandled(e)) return
99
+ if (editor.wasEventAlreadyHandled(e)) return
106
100
  preventDefault(e)
107
101
  e.stopPropagation()
108
102
 
@@ -129,7 +123,7 @@ export function useCanvasEvents() {
129
123
  }
130
124
 
131
125
  function onClick(e: React.MouseEvent) {
132
- if (wasEventAlreadyHandled(e)) return
126
+ if (editor.wasEventAlreadyHandled(e)) return
133
127
  e.stopPropagation()
134
128
  }
135
129
 
@@ -157,8 +151,8 @@ export function useCanvasEvents() {
157
151
  let lastX: number, lastY: number
158
152
 
159
153
  function onPointerMove(e: PointerEvent) {
160
- if (wasEventAlreadyHandled(e)) return
161
- markEventAsHandled(e)
154
+ if (editor.wasEventAlreadyHandled(e)) return
155
+ editor.markEventAsHandled(e)
162
156
 
163
157
  if (e.clientX === lastX && e.clientY === lastY) return
164
158
  lastX = e.clientX
@@ -174,7 +168,7 @@ export function useCanvasEvents() {
174
168
  type: 'pointer',
175
169
  target: 'canvas',
176
170
  name: 'pointer_move',
177
- ...getPointerInfo(singleEvent),
171
+ ...getPointerInfo(editor, singleEvent),
178
172
  })
179
173
  }
180
174
  }
@@ -2,12 +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 {
6
- activeElementShouldCaptureKeys,
7
- markEventAsHandled,
8
- preventDefault,
9
- wasEventAlreadyHandled,
10
- } from '../utils/dom'
5
+ import { activeElementShouldCaptureKeys, preventDefault } from '../utils/dom'
11
6
  import { isAccelKey } from '../utils/keyboard'
12
7
  import { useContainer } from './useContainer'
13
8
  import { useEditor } from './useEditor'
@@ -108,8 +103,8 @@ export function useDocumentEvents() {
108
103
  preventDefault(e)
109
104
  }
110
105
 
111
- if (wasEventAlreadyHandled(e)) return
112
- markEventAsHandled(e)
106
+ if (editor.wasEventAlreadyHandled(e)) return
107
+ editor.markEventAsHandled(e)
113
108
  const hasSelectedShapes = !!editor.getSelectedShapeIds().length
114
109
 
115
110
  switch (e.key) {
@@ -216,8 +211,8 @@ export function useDocumentEvents() {
216
211
  }
217
212
 
218
213
  const handleKeyUp = (e: KeyboardEvent) => {
219
- if (wasEventAlreadyHandled(e)) return
220
- markEventAsHandled(e)
214
+ if (editor.wasEventAlreadyHandled(e)) return
215
+ editor.markEventAsHandled(e)
221
216
 
222
217
  if (areShortcutsDisabled(editor)) {
223
218
  return
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from 'react'
2
- import { markEventAsHandled, preventDefault } from '../utils/dom'
2
+ import { preventDefault } from '../utils/dom'
3
3
  import { useEditor } from './useEditor'
4
4
 
5
5
  const IGNORED_TAGS = ['textarea', 'input']
@@ -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
- markEventAsHandled(e)
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
@@ -1,12 +1,7 @@
1
1
  import { TLArrowShape, TLLineShape, TLShapeId } from '@tldraw/tlschema'
2
2
  import * as React from 'react'
3
3
  import { Editor } from '../editor/Editor'
4
- import {
5
- loopToHtmlElement,
6
- releasePointerCapture,
7
- setPointerCapture,
8
- wasEventAlreadyHandled,
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
 
@@ -21,7 +16,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
21
16
 
22
17
  return React.useMemo(() => {
23
18
  const onPointerDown = (e: React.PointerEvent) => {
24
- if (wasEventAlreadyHandled(e)) return
19
+ if (editor.wasEventAlreadyHandled(e)) return
25
20
 
26
21
  // Must set pointer capture on an HTML element!
27
22
  const target = loopToHtmlElement(e.currentTarget)
@@ -37,7 +32,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
37
32
  handle,
38
33
  shape,
39
34
  name: 'pointer_down',
40
- ...getPointerInfo(e),
35
+ ...getPointerInfo(editor, e),
41
36
  })
42
37
  }
43
38
 
@@ -45,7 +40,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
45
40
  let lastX: number, lastY: number
46
41
 
47
42
  const onPointerMove = (e: React.PointerEvent) => {
48
- if (wasEventAlreadyHandled(e)) return
43
+ if (editor.wasEventAlreadyHandled(e)) return
49
44
  if (e.clientX === lastX && e.clientY === lastY) return
50
45
  lastX = e.clientX
51
46
  lastY = e.clientY
@@ -60,12 +55,12 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
60
55
  handle,
61
56
  shape,
62
57
  name: 'pointer_move',
63
- ...getPointerInfo(e),
58
+ ...getPointerInfo(editor, e),
64
59
  })
65
60
  }
66
61
 
67
62
  const onPointerUp = (e: React.PointerEvent) => {
68
- if (wasEventAlreadyHandled(e)) return
63
+ if (editor.wasEventAlreadyHandled(e)) return
69
64
 
70
65
  const target = loopToHtmlElement(e.currentTarget)
71
66
  releasePointerCapture(target, e)
@@ -80,7 +75,7 @@ export function useHandleEvents(id: TLShapeId, handleId: string) {
80
75
  handle,
81
76
  shape,
82
77
  name: 'pointer_up',
83
- ...getPointerInfo(e),
78
+ ...getPointerInfo(editor, e),
84
79
  })
85
80
  }
86
81
 
@@ -1,13 +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
- markEventAsHandled,
7
- releasePointerCapture,
8
- setPointerCapture,
9
- wasEventAlreadyHandled,
10
- } from '../utils/dom'
4
+ import { loopToHtmlElement, releasePointerCapture, setPointerCapture } from '../utils/dom'
11
5
  import { getPointerInfo } from '../utils/getPointerInfo'
12
6
  import { useEditor } from './useEditor'
13
7
 
@@ -18,7 +12,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
18
12
  const events = useMemo(
19
13
  function selectionEvents() {
20
14
  const onPointerDown: React.PointerEventHandler = (e) => {
21
- if (wasEventAlreadyHandled(e)) return
15
+ if (editor.wasEventAlreadyHandled(e)) return
22
16
 
23
17
  if (e.button === RIGHT_MOUSE_BUTTON) {
24
18
  editor.dispatch({
@@ -26,7 +20,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
26
20
  target: 'selection',
27
21
  handle,
28
22
  name: 'right_click',
29
- ...getPointerInfo(e),
23
+ ...getPointerInfo(editor, e),
30
24
  })
31
25
  return
32
26
  }
@@ -53,16 +47,16 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
53
47
  type: 'pointer',
54
48
  target: 'selection',
55
49
  handle,
56
- ...getPointerInfo(e),
50
+ ...getPointerInfo(editor, e),
57
51
  })
58
- markEventAsHandled(e)
52
+ editor.markEventAsHandled(e)
59
53
  }
60
54
 
61
55
  // Track the last screen point
62
56
  let lastX: number, lastY: number
63
57
 
64
58
  function onPointerMove(e: React.PointerEvent) {
65
- if (wasEventAlreadyHandled(e)) return
59
+ if (editor.wasEventAlreadyHandled(e)) return
66
60
  if (e.button !== 0) return
67
61
  if (e.clientX === lastX && e.clientY === lastY) return
68
62
  lastX = e.clientX
@@ -73,12 +67,12 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
73
67
  type: 'pointer',
74
68
  target: 'selection',
75
69
  handle,
76
- ...getPointerInfo(e),
70
+ ...getPointerInfo(editor, e),
77
71
  })
78
72
  }
79
73
 
80
74
  const onPointerUp: React.PointerEventHandler = (e) => {
81
- if (wasEventAlreadyHandled(e)) return
75
+ if (editor.wasEventAlreadyHandled(e)) return
82
76
  if (e.button !== 0) return
83
77
 
84
78
  editor.dispatch({
@@ -86,7 +80,7 @@ export function useSelectionEvents(handle: TLSelectionHandle) {
86
80
  type: 'pointer',
87
81
  target: 'selection',
88
82
  handle,
89
- ...getPointerInfo(e),
83
+ ...getPointerInfo(editor, e),
90
84
  })
91
85
  }
92
86
 
@@ -178,6 +178,9 @@ export class LicenseManager {
178
178
  const url = new URL(WATERMARK_TRACK_SRC)
179
179
  url.searchParams.set('version', version)
180
180
  url.searchParams.set('license_type', trackType)
181
+ if ('license' in result) {
182
+ url.searchParams.set('license_id', result.license.id)
183
+ }
181
184
 
182
185
  // eslint-disable-next-line no-restricted-globals
183
186
  fetch(url.toString())
@@ -3,7 +3,7 @@ import { memo, useRef } from 'react'
3
3
  import { useCanvasEvents } from '../hooks/useCanvasEvents'
4
4
  import { useEditor } from '../hooks/useEditor'
5
5
  import { usePassThroughWheelEvents } from '../hooks/usePassThroughWheelEvents'
6
- import { markEventAsHandled, preventDefault } from '../utils/dom'
6
+ import { preventDefault } from '../utils/dom'
7
7
  import { runtime } from '../utils/runtime'
8
8
  import { watermarkDesktopSvg, watermarkMobileSvg } from '../watermarks'
9
9
  import { LicenseManager } from './LicenseManager'
@@ -43,11 +43,13 @@ const UnlicensedWatermark = memo(function UnlicensedWatermark({
43
43
  isDebugMode: boolean
44
44
  isMobile: boolean
45
45
  }) {
46
+ const editor = useEditor()
46
47
  const events = useCanvasEvents()
47
48
  const ref = useRef<HTMLDivElement>(null)
48
49
  usePassThroughWheelEvents(ref)
49
50
 
50
- const url = 'https://tldraw.dev/?utm_source=dotcom&utm_medium=organic&utm_campaign=watermark'
51
+ const url =
52
+ 'https://tldraw.dev/pricing?utm_source=dotcom&utm_medium=organic&utm_campaign=watermark'
51
53
 
52
54
  return (
53
55
  <div
@@ -64,26 +66,13 @@ const UnlicensedWatermark = memo(function UnlicensedWatermark({
64
66
  draggable={false}
65
67
  role="button"
66
68
  onPointerDown={(e) => {
67
- markEventAsHandled(e)
69
+ editor.markEventAsHandled(e)
68
70
  preventDefault(e)
69
71
  }}
70
- title="Unlicensed - click to get a license"
72
+ title="The tldraw SDK requires a license key to work in production. You can get a free 100-day trial license at tldraw.dev/pricing."
71
73
  onClick={() => runtime.openWindow(url, '_blank')}
72
- style={{
73
- position: 'absolute',
74
- pointerEvents: 'all',
75
- cursor: 'pointer',
76
- color: 'var(--tl-color-text)',
77
- opacity: 0.8,
78
- border: 0,
79
- padding: 0,
80
- backgroundColor: 'transparent',
81
- fontSize: '11px',
82
- fontWeight: '600',
83
- textAlign: 'center',
84
- }}
85
74
  >
86
- Unlicensed
75
+ Get a license for production
87
76
  </button>
88
77
  </div>
89
78
  )
@@ -127,10 +116,10 @@ const WatermarkInner = memo(function WatermarkInner({
127
116
  draggable={false}
128
117
  role="button"
129
118
  onPointerDown={(e) => {
130
- markEventAsHandled(e)
119
+ editor.markEventAsHandled(e)
131
120
  preventDefault(e)
132
121
  }}
133
- title="made with tldraw"
122
+ title="Build infinite canvas applications with the tldraw SDK. Learn more at https://tldraw.dev."
134
123
  onClick={() => runtime.openWindow(url, '_blank')}
135
124
  style={{ mask: maskCss, WebkitMask: maskCss }}
136
125
  />
@@ -142,7 +131,8 @@ const LicenseStyles = memo(function LicenseStyles() {
142
131
  const editor = useEditor()
143
132
  const className = LicenseManager.className
144
133
 
145
- const CSS = `/* ------------------- SEE LICENSE -------------------
134
+ const CSS = `
135
+ /* ------------------- SEE LICENSE -------------------
146
136
  The tldraw watermark is part of tldraw's license. It is shown for unlicensed
147
137
  or "licensed-with-watermark" users. By using this library, you agree to
148
138
  preserve the watermark's behavior, keeping it visible, unobscured, and
@@ -151,87 +141,105 @@ available to user-interaction.
151
141
  To remove the watermark, please purchase a license at tldraw.dev.
152
142
  */
153
143
 
154
- .${className} {
155
- position: absolute;
156
- bottom: max(var(--tl-space-2), env(safe-area-inset-bottom));
157
- right: max(var(--tl-space-2), env(safe-area-inset-right));
158
- width: 96px;
159
- height: 32px;
160
- display: flex;
161
- align-items: center;
162
- justify-content: center;
163
- z-index: var(--tl-layer-watermark) !important;
164
- background-color: color-mix(in srgb, var(--tl-color-background) 62%, transparent);
165
- opacity: 1;
166
- border-radius: 5px;
167
- pointer-events: all;
168
- padding: 2px;
169
- box-sizing: content-box;
144
+ .${className} {
145
+ position: absolute;
146
+ bottom: max(var(--tl-space-2), env(safe-area-inset-bottom));
147
+ right: max(var(--tl-space-2), env(safe-area-inset-right));
148
+ width: 96px;
149
+ height: 32px;
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ z-index: var(--tl-layer-watermark) !important;
154
+ background-color: color-mix(in srgb, var(--tl-color-background) 62%, transparent);
155
+ opacity: 1;
156
+ border-radius: 5px;
157
+ pointer-events: all;
158
+ padding: 2px;
159
+ box-sizing: content-box;
160
+ }
161
+
162
+ .${className} > button {
163
+ position: absolute;
164
+ width: 96px;
165
+ height: 32px;
166
+ pointer-events: all;
167
+ cursor: inherit;
168
+ color: var(--tl-color-text);
169
+ opacity: .38;
170
+ border: 0;
171
+ padding: 0;
172
+ background-color: currentColor;
173
+ }
174
+
175
+ .${className}[data-debug='true'] {
176
+ bottom: max(46px, env(safe-area-inset-bottom));
177
+ }
178
+
179
+ .${className}[data-mobile='true'] {
180
+ border-radius: 4px 0px 0px 4px;
181
+ right: max(-2px, calc(env(safe-area-inset-right) - 2px));
182
+ width: 8px;
183
+ height: 48px;
184
+ }
185
+
186
+ .${className}[data-mobile='true'] > button {
187
+ width: 8px;
188
+ height: 32px;
189
+ }
190
+
191
+ .${className}[data-unlicensed='true'] > button {
192
+ font-size: 100px;
193
+ position: absolute;
194
+ pointer-events: all;
195
+ cursor: pointer;
196
+ color: var(--tl-color-text);
197
+ opacity: 0.8;
198
+ border: 0;
199
+ padding: 0;
200
+ background-color: transparent;
201
+ font-size: 11px;
202
+ font-weight: 600;
203
+ text-align: center;
204
+ }
205
+
206
+ .${className}[data-mobile='true'][data-unlicensed='true'] > button {
207
+ display: none;
208
+ }
209
+
210
+ @media (hover: hover) {
211
+ .${className}[data-licensed='false'] > button {
212
+ pointer-events: none;
170
213
  }
171
214
 
172
- .${className} > button {
173
- position: absolute;
174
- width: 96px;
175
- height: 32px;
176
- pointer-events: all;
177
- cursor: inherit;
178
- color: var(--tl-color-text);
179
- opacity: .38;
180
- border: 0;
181
- padding: 0;
182
- background-color: currentColor;
215
+ .${className}[data-licensed='false']:hover {
216
+ background-color: var(--tl-color-background);
217
+ transition: background-color 0.2s ease-in-out;
218
+ transition-delay: 0.32s;
183
219
  }
184
220
 
185
- .${className}[data-debug='true'] {
186
- bottom: max(46px, env(safe-area-inset-bottom));
221
+ .${className}[data-licensed='false']:hover > button {
222
+ animation: ${className}_delayed_link 0.2s forwards ease-in-out;
223
+ animation-delay: 0.32s;
187
224
  }
188
225
 
189
- .${className}[data-mobile='true'] {
190
- border-radius: 4px 0px 0px 4px;
191
- right: max(-2px, calc(env(safe-area-inset-right) - 2px));
192
- width: 8px;
193
- height: 48px;
226
+ .${className}[data-licensed='false'] > button:focus-visible {
227
+ opacity: 1;
194
228
  }
229
+ }
195
230
 
196
- .${className}[data-mobile='true'] > button {
197
- width: 8px;
198
- height: 32px;
231
+ @keyframes ${className}_delayed_link {
232
+ 0% {
233
+ cursor: inherit;
234
+ opacity: .38;
235
+ pointer-events: none;
199
236
  }
200
-
201
- @media (hover: hover) {
202
- .${className} > button {
203
- pointer-events: none;
204
- }
205
-
206
- .${className}:hover {
207
- background-color: var(--tl-color-background);
208
- transition: background-color 0.2s ease-in-out;
209
- transition-delay: 0.32s;
210
- }
211
-
212
- .${className}:hover > button {
213
- animation: ${className}_delayed_link 0.2s forwards ease-in-out;
214
- animation-delay: 0.32s;
215
- }
216
-
217
- .${className} > button:focus-visible {
218
- opacity: 1;
219
- }
237
+ 100% {
238
+ cursor: pointer;
239
+ opacity: 1;
240
+ pointer-events: all;
220
241
  }
221
-
222
-
223
- @keyframes ${className}_delayed_link {
224
- 0% {
225
- cursor: inherit;
226
- opacity: .38;
227
- pointer-events: none;
228
- }
229
- 100% {
230
- cursor: pointer;
231
- opacity: 1;
232
- pointer-events: all;
233
- }
234
- }`
242
+ }`
235
243
 
236
244
  return <style nonce={editor.options.nonce}>{CSS}</style>
237
245
  })