@tldraw/editor 3.16.0-canary.6c77a180e58d → 3.16.0-canary.7379d3553d7e

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 (109) hide show
  1. package/dist-cjs/index.d.ts +53 -4
  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 +9 -3
  8. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  9. package/dist-cjs/lib/editor/Editor.js +44 -5
  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 +8 -3
  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/primitives/geometry/Geometry2d.js +24 -2
  32. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  33. package/dist-cjs/lib/primitives/geometry/Group2d.js +5 -1
  34. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  35. package/dist-cjs/lib/utils/dom.js.map +2 -2
  36. package/dist-cjs/lib/utils/getPointerInfo.js +2 -3
  37. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  38. package/dist-cjs/lib/utils/reparenting.js +5 -1
  39. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  40. package/dist-cjs/version.js +3 -3
  41. package/dist-cjs/version.js.map +1 -1
  42. package/dist-esm/index.d.mts +53 -4
  43. package/dist-esm/index.mjs +1 -1
  44. package/dist-esm/lib/TldrawEditor.mjs +0 -2
  45. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  46. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +11 -1
  47. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  48. package/dist-esm/lib/config/TLUserPreferences.mjs +9 -3
  49. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  50. package/dist-esm/lib/editor/Editor.mjs +44 -5
  51. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  52. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
  53. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  54. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +8 -3
  55. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  56. package/dist-esm/lib/hooks/useCanvasEvents.mjs +20 -22
  57. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  58. package/dist-esm/lib/hooks/useDocumentEvents.mjs +6 -6
  59. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  60. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +1 -2
  61. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  62. package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
  63. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  64. package/dist-esm/lib/hooks/useHandleEvents.mjs +6 -6
  65. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
  66. package/dist-esm/lib/hooks/useSelectionEvents.mjs +9 -14
  67. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
  68. package/dist-esm/lib/license/LicenseManager.mjs +24 -4
  69. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  70. package/dist-esm/lib/license/Watermark.mjs +98 -91
  71. package/dist-esm/lib/license/Watermark.mjs.map +2 -2
  72. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +24 -2
  73. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  74. package/dist-esm/lib/primitives/geometry/Group2d.mjs +5 -1
  75. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  76. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  77. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -3
  78. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  79. package/dist-esm/lib/utils/reparenting.mjs +5 -1
  80. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  81. package/dist-esm/version.mjs +3 -3
  82. package/dist-esm/version.mjs.map +1 -1
  83. package/package.json +7 -7
  84. package/src/lib/TldrawEditor.tsx +0 -2
  85. package/src/lib/components/default-components/DefaultCanvas.tsx +7 -1
  86. package/src/lib/config/TLUserPreferences.ts +8 -0
  87. package/src/lib/editor/Editor.test.ts +90 -0
  88. package/src/lib/editor/Editor.ts +57 -5
  89. package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
  90. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +18 -0
  91. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +5 -0
  92. package/src/lib/hooks/useCanvasEvents.ts +20 -20
  93. package/src/lib/hooks/useDocumentEvents.ts +6 -6
  94. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  95. package/src/lib/hooks/useGestureEvents.ts +2 -2
  96. package/src/lib/hooks/useHandleEvents.ts +6 -6
  97. package/src/lib/hooks/useSelectionEvents.ts +9 -14
  98. package/src/lib/license/LicenseManager.test.ts +78 -2
  99. package/src/lib/license/LicenseManager.ts +31 -5
  100. package/src/lib/license/Watermark.tsx +100 -92
  101. package/src/lib/primitives/geometry/Geometry2d.test.ts +420 -0
  102. package/src/lib/primitives/geometry/Geometry2d.ts +29 -2
  103. package/src/lib/primitives/geometry/Group2d.ts +6 -1
  104. package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
  105. package/src/lib/utils/dom.test.ts +103 -0
  106. package/src/lib/utils/dom.ts +8 -1
  107. package/src/lib/utils/getPointerInfo.ts +3 -2
  108. package/src/lib/utils/reparenting.ts +7 -1
  109. package/src/version.ts +3 -3
@@ -25,6 +25,7 @@ export interface TLUserPreferences {
25
25
  isDynamicSizeMode?: boolean | null
26
26
  isPasteAtCursorMode?: boolean | null
27
27
  showUiLabels?: boolean | null
28
+ inputMode?: 'trackpad' | 'mouse' | null
28
29
  }
29
30
 
30
31
  interface UserDataSnapshot {
@@ -54,6 +55,7 @@ export const userTypeValidator: T.Validator<TLUserPreferences> = T.object<TLUser
54
55
  isDynamicSizeMode: T.boolean.nullable().optional(),
55
56
  isPasteAtCursorMode: T.boolean.nullable().optional(),
56
57
  showUiLabels: T.boolean.nullable().optional(),
58
+ inputMode: T.literalEnum('trackpad', 'mouse').nullable().optional(),
57
59
  })
58
60
 
59
61
  const Versions = {
@@ -67,6 +69,7 @@ const Versions = {
67
69
  AddPasteAtCursor: 8,
68
70
  AddKeyboardShortcuts: 9,
69
71
  AddShowUiLabels: 10,
72
+ AddPointerPeripheral: 11,
70
73
  } as const
71
74
 
72
75
  const CURRENT_VERSION = Math.max(...Object.values(Versions))
@@ -109,6 +112,10 @@ function migrateSnapshot(data: { version: number; user: any }) {
109
112
  data.user.showUiLabels = false
110
113
  }
111
114
 
115
+ if (data.version < Versions.AddPointerPeripheral) {
116
+ data.user.inputMode = null
117
+ }
118
+
112
119
  // finally
113
120
  data.version = CURRENT_VERSION
114
121
  }
@@ -158,6 +165,7 @@ export const defaultUserPreferences = Object.freeze({
158
165
  isPasteAtCursorMode: false,
159
166
  showUiLabels: false,
160
167
  colorScheme: 'light',
168
+ inputMode: null,
161
169
  }) satisfies Readonly<Omit<TLUserPreferences, 'id'>>
162
170
 
163
171
  /** @public */
@@ -833,3 +833,93 @@ describe('selectAll', () => {
833
833
  setSelectedShapesSpy.mockRestore()
834
834
  })
835
835
  })
836
+
837
+ describe('putExternalContent', () => {
838
+ let mockHandler: any
839
+
840
+ beforeEach(() => {
841
+ mockHandler = vi.fn()
842
+ editor.registerExternalContentHandler('text', mockHandler)
843
+ })
844
+
845
+ it('calls external content handler when not readonly', async () => {
846
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
847
+
848
+ const info = { type: 'text' as const, text: 'test-data' }
849
+ await editor.putExternalContent(info)
850
+
851
+ expect(mockHandler).toHaveBeenCalledWith(info)
852
+ })
853
+
854
+ it('does not call external content handler when readonly', async () => {
855
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
856
+
857
+ const info = { type: 'text' as const, text: 'test-data' }
858
+ await editor.putExternalContent(info)
859
+
860
+ expect(mockHandler).not.toHaveBeenCalled()
861
+ })
862
+
863
+ it('calls external content handler when readonly but force is true', async () => {
864
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
865
+
866
+ const info = { type: 'text' as const, text: 'test-data' }
867
+ await editor.putExternalContent(info, { force: true })
868
+
869
+ expect(mockHandler).toHaveBeenCalledWith(info)
870
+ })
871
+
872
+ it('calls external content handler when force is false and not readonly', async () => {
873
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
874
+
875
+ const info = { type: 'text' as const, text: 'test-data' }
876
+ await editor.putExternalContent(info, { force: false })
877
+
878
+ expect(mockHandler).toHaveBeenCalledWith(info)
879
+ })
880
+ })
881
+
882
+ describe('replaceExternalContent', () => {
883
+ let mockHandler: any
884
+
885
+ beforeEach(() => {
886
+ mockHandler = vi.fn()
887
+ editor.registerExternalContentHandler('text', mockHandler)
888
+ })
889
+
890
+ it('calls external content handler when not readonly', async () => {
891
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
892
+
893
+ const info = { type: 'text' as const, text: 'test-data' }
894
+ await editor.replaceExternalContent(info)
895
+
896
+ expect(mockHandler).toHaveBeenCalledWith(info)
897
+ })
898
+
899
+ it('does not call external content handler when readonly', async () => {
900
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
901
+
902
+ const info = { type: 'text' as const, text: 'test-data' }
903
+ await editor.replaceExternalContent(info)
904
+
905
+ expect(mockHandler).not.toHaveBeenCalled()
906
+ })
907
+
908
+ it('calls external content handler when readonly but force is true', async () => {
909
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(true)
910
+
911
+ const info = { type: 'text' as const, text: 'test-data' }
912
+ await editor.replaceExternalContent(info, { force: true })
913
+
914
+ expect(mockHandler).toHaveBeenCalledWith(info)
915
+ })
916
+
917
+ it('calls external content handler when force is false and not readonly', async () => {
918
+ vi.spyOn(editor, 'getIsReadonly').mockReturnValue(false)
919
+
920
+ const info = { type: 'text' as const, text: 'test-data' }
921
+ await editor.replaceExternalContent(info, { force: false })
922
+
923
+ expect(mockHandler).toHaveBeenCalledWith(info)
924
+ })
925
+ })
@@ -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>>
@@ -4680,8 +4682,10 @@ export class Editor extends EventEmitter<TLEventMap> {
4680
4682
  return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
4681
4683
  const pageTransform = this.getShapePageTransform(shape)
4682
4684
  if (!pageTransform) return undefined
4683
- const geometry = this.getShapeGeometry(shape)
4684
- return Box.FromPoints(pageTransform.applyToPoints(geometry.vertices))
4685
+
4686
+ return Box.FromPoints(
4687
+ pageTransform.applyToPoints(this.getShapeGeometry(shape).boundsVertices)
4688
+ )
4685
4689
  })
4686
4690
  }
4687
4691
 
@@ -8831,8 +8835,13 @@ export class Editor extends EventEmitter<TLEventMap> {
8831
8835
  * Handle external content, such as files, urls, embeds, or plain text which has been put into the app, for example by pasting external text or dropping external images onto canvas.
8832
8836
  *
8833
8837
  * @param info - Info about the external content.
8838
+ * @param opts - Options for handling external content, including force flag to bypass readonly checks.
8834
8839
  */
8835
- async putExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8840
+ async putExternalContent<E>(
8841
+ info: TLExternalContent<E>,
8842
+ opts = {} as { force?: boolean }
8843
+ ): Promise<void> {
8844
+ if (!opts.force && this.getIsReadonly()) return
8836
8845
  return this.externalContentHandlers[info.type]?.(info as any)
8837
8846
  }
8838
8847
 
@@ -8840,8 +8849,13 @@ export class Editor extends EventEmitter<TLEventMap> {
8840
8849
  * Handle replacing external content.
8841
8850
  *
8842
8851
  * @param info - Info about the external content.
8852
+ * @param opts - Options for handling external content, including force flag to bypass readonly checks.
8843
8853
  */
8844
- async replaceExternalContent<E>(info: TLExternalContent<E>): Promise<void> {
8854
+ async replaceExternalContent<E>(
8855
+ info: TLExternalContent<E>,
8856
+ opts = {} as { force?: boolean }
8857
+ ): Promise<void> {
8858
+ if (!opts.force && this.getIsReadonly()) return
8845
8859
  return this.externalContentHandlers[info.type]?.(info as any)
8846
8860
  }
8847
8861
 
@@ -10085,6 +10099,37 @@ export class Editor extends EventEmitter<TLEventMap> {
10085
10099
  /** @internal */
10086
10100
  private performanceTrackerTimeout = -1 as any
10087
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
+
10088
10133
  /**
10089
10134
  * Dispatch an event to the editor.
10090
10135
  *
@@ -10289,7 +10334,14 @@ export class Editor extends EventEmitter<TLEventMap> {
10289
10334
 
10290
10335
  this._updateInputsFromEvent(info)
10291
10336
 
10292
- const { panSpeed, zoomSpeed, wheelBehavior } = cameraOptions
10337
+ const { panSpeed, zoomSpeed } = cameraOptions
10338
+ let wheelBehavior = cameraOptions.wheelBehavior
10339
+ const inputMode = this.user.getUserPreferences().inputMode
10340
+
10341
+ // If the user has set their input mode preference, then use that to determine the wheel behavior
10342
+ if (inputMode !== null) {
10343
+ wheelBehavior = inputMode === 'trackpad' ? 'pan' : 'zoom'
10344
+ }
10293
10345
 
10294
10346
  if (wheelBehavior !== 'none') {
10295
10347
  // Stop any camera animation
@@ -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
  }
@@ -30,6 +30,7 @@ describe('UserPreferencesManager', () => {
30
30
  isWrapMode: false,
31
31
  isDynamicSizeMode: false,
32
32
  isPasteAtCursorMode: false,
33
+ inputMode: null,
33
34
  ...overrides,
34
35
  })
35
36
 
@@ -233,6 +234,7 @@ describe('UserPreferencesManager', () => {
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
 
@@ -453,6 +455,22 @@ describe('UserPreferencesManager', () => {
453
455
  )
454
456
  })
455
457
  })
458
+
459
+ describe('getInputMode', () => {
460
+ it('should return user input mode setting', () => {
461
+ expect(userPreferencesManager.getInputMode()).toBe(null)
462
+ })
463
+
464
+ it('should return trackpad if input mode is trackpad', () => {
465
+ userPreferencesAtom.set({ ...mockUserPreferences, inputMode: 'trackpad' })
466
+ expect(userPreferencesManager.getInputMode()).toBe('trackpad')
467
+ })
468
+
469
+ it('should return mouse if input mode is mouse', () => {
470
+ userPreferencesAtom.set({ ...mockUserPreferences, inputMode: 'mouse' })
471
+ expect(userPreferencesManager.getInputMode()).toBe('mouse')
472
+ })
473
+ })
456
474
  })
457
475
 
458
476
  describe('reactive behavior', () => {
@@ -50,6 +50,7 @@ export class UserPreferencesManager {
50
50
  isWrapMode: this.getIsWrapMode(),
51
51
  isDynamicResizeMode: this.getIsDynamicResizeMode(),
52
52
  showUiLabels: this.getShowUiLabels(),
53
+ inputMode: this.getInputMode(),
53
54
  }
54
55
  }
55
56
 
@@ -124,4 +125,8 @@ export class UserPreferencesManager {
124
125
  @computed getShowUiLabels() {
125
126
  return this.user.userPreferences.get().showUiLabels ?? defaultUserPreferences.showUiLabels
126
127
  }
128
+
129
+ @computed getInputMode() {
130
+ return this.user.userPreferences.get().inputMode ?? defaultUserPreferences.inputMode
131
+ }
127
132
  }
@@ -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