@tldraw/editor 3.16.0-canary.aceca4c951a7 → 3.16.0-canary.b0fec0f5b729

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 (192) hide show
  1. package/dist-cjs/index.d.ts +113 -105
  2. package/dist-cjs/index.js +3 -5
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/TldrawEditor.js +6 -8
  5. package/dist-cjs/lib/TldrawEditor.js.map +2 -2
  6. package/dist-cjs/lib/components/Shape.js +7 -10
  7. package/dist-cjs/lib/components/Shape.js.map +2 -2
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +14 -23
  9. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  10. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
  11. package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +2 -2
  12. package/dist-cjs/lib/config/TLUserPreferences.js +1 -1
  13. package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
  14. package/dist-cjs/lib/editor/Editor.js +79 -114
  15. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  16. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +4 -0
  17. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +2 -2
  18. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js +4 -2
  19. package/dist-cjs/lib/editor/managers/FocusManager/FocusManager.js.map +2 -2
  20. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +1 -1
  21. package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
  22. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +23 -0
  23. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  24. package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
  25. package/dist-cjs/lib/exports/getSvgJsx.js +34 -14
  26. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  27. package/dist-cjs/lib/hooks/useCanvasEvents.js +26 -21
  28. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  29. package/dist-cjs/lib/hooks/useDocumentEvents.js +5 -5
  30. package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
  31. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js +1 -2
  32. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  33. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  34. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  35. package/dist-cjs/lib/hooks/useHandleEvents.js +6 -6
  36. package/dist-cjs/lib/hooks/useHandleEvents.js.map +2 -2
  37. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js +4 -1
  38. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  39. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +4 -1
  40. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  41. package/dist-cjs/lib/hooks/useSelectionEvents.js +8 -8
  42. package/dist-cjs/lib/hooks/useSelectionEvents.js.map +2 -2
  43. package/dist-cjs/lib/license/LicenseManager.js +143 -53
  44. package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
  45. package/dist-cjs/lib/license/LicenseProvider.js +39 -1
  46. package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
  47. package/dist-cjs/lib/license/Watermark.js +144 -75
  48. package/dist-cjs/lib/license/Watermark.js.map +3 -3
  49. package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
  50. package/dist-cjs/lib/options.js +6 -0
  51. package/dist-cjs/lib/options.js.map +2 -2
  52. package/dist-cjs/lib/primitives/Box.js +3 -0
  53. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  54. package/dist-cjs/lib/primitives/Vec.js +0 -4
  55. package/dist-cjs/lib/primitives/Vec.js.map +2 -2
  56. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +50 -20
  57. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  58. package/dist-cjs/lib/primitives/geometry/Group2d.js +8 -1
  59. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  60. package/dist-cjs/lib/utils/dom.js.map +2 -2
  61. package/dist-cjs/lib/utils/getPointerInfo.js +2 -3
  62. package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
  63. package/dist-cjs/lib/utils/reparenting.js +2 -35
  64. package/dist-cjs/lib/utils/reparenting.js.map +3 -3
  65. package/dist-cjs/version.js +3 -3
  66. package/dist-cjs/version.js.map +1 -1
  67. package/dist-esm/index.d.mts +113 -105
  68. package/dist-esm/index.mjs +3 -5
  69. package/dist-esm/index.mjs.map +2 -2
  70. package/dist-esm/lib/TldrawEditor.mjs +6 -8
  71. package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
  72. package/dist-esm/lib/components/Shape.mjs +7 -10
  73. package/dist-esm/lib/components/Shape.mjs.map +2 -2
  74. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +14 -23
  75. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  76. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
  77. package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +2 -2
  78. package/dist-esm/lib/config/TLUserPreferences.mjs +1 -1
  79. package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
  80. package/dist-esm/lib/editor/Editor.mjs +79 -114
  81. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  82. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +4 -0
  83. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +2 -2
  84. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs +4 -2
  85. package/dist-esm/lib/editor/managers/FocusManager/FocusManager.mjs.map +2 -2
  86. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +1 -1
  87. package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
  88. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +23 -0
  89. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  90. package/dist-esm/lib/exports/getSvgJsx.mjs +34 -14
  91. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  92. package/dist-esm/lib/hooks/useCanvasEvents.mjs +27 -27
  93. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  94. package/dist-esm/lib/hooks/useDocumentEvents.mjs +6 -6
  95. package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
  96. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs +1 -2
  97. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  98. package/dist-esm/lib/hooks/useGestureEvents.mjs +2 -2
  99. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  100. package/dist-esm/lib/hooks/useHandleEvents.mjs +6 -6
  101. package/dist-esm/lib/hooks/useHandleEvents.mjs.map +2 -2
  102. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs +4 -1
  103. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  104. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +4 -1
  105. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  106. package/dist-esm/lib/hooks/useSelectionEvents.mjs +9 -14
  107. package/dist-esm/lib/hooks/useSelectionEvents.mjs.map +2 -2
  108. package/dist-esm/lib/license/LicenseManager.mjs +144 -54
  109. package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
  110. package/dist-esm/lib/license/LicenseProvider.mjs +39 -2
  111. package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
  112. package/dist-esm/lib/license/Watermark.mjs +145 -76
  113. package/dist-esm/lib/license/Watermark.mjs.map +3 -3
  114. package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
  115. package/dist-esm/lib/options.mjs +6 -0
  116. package/dist-esm/lib/options.mjs.map +2 -2
  117. package/dist-esm/lib/primitives/Box.mjs +4 -1
  118. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  119. package/dist-esm/lib/primitives/Vec.mjs +0 -4
  120. package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
  121. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +53 -21
  122. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  123. package/dist-esm/lib/primitives/geometry/Group2d.mjs +8 -1
  124. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  125. package/dist-esm/lib/utils/dom.mjs.map +2 -2
  126. package/dist-esm/lib/utils/getPointerInfo.mjs +2 -3
  127. package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
  128. package/dist-esm/lib/utils/reparenting.mjs +3 -40
  129. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  130. package/dist-esm/version.mjs +3 -3
  131. package/dist-esm/version.mjs.map +1 -1
  132. package/editor.css +16 -3
  133. package/package.json +14 -37
  134. package/src/index.ts +2 -9
  135. package/src/lib/TldrawEditor.tsx +7 -16
  136. package/src/lib/components/Shape.tsx +6 -12
  137. package/src/lib/components/default-components/DefaultCanvas.tsx +11 -22
  138. package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
  139. package/src/lib/config/TLUserPreferences.ts +1 -1
  140. package/src/lib/editor/Editor.test.ts +102 -11
  141. package/src/lib/editor/Editor.ts +98 -151
  142. package/src/lib/editor/derivations/notVisibleShapes.ts +6 -0
  143. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +15 -14
  144. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +16 -15
  145. package/src/lib/editor/managers/FocusManager/FocusManager.test.ts +49 -48
  146. package/src/lib/editor/managers/FocusManager/FocusManager.ts +6 -2
  147. package/src/lib/editor/managers/FontManager/FontManager.test.ts +24 -23
  148. package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +7 -6
  149. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +12 -11
  150. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +57 -50
  151. package/src/lib/editor/managers/TextManager/TextManager.test.ts +51 -26
  152. package/src/lib/editor/managers/TickManager/TickManager.test.ts +14 -13
  153. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +21 -26
  154. package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +1 -1
  155. package/src/lib/editor/shapes/ShapeUtil.ts +46 -0
  156. package/src/lib/editor/types/misc-types.ts +0 -6
  157. package/src/lib/exports/getSvgJsx.test.ts +868 -0
  158. package/src/lib/exports/getSvgJsx.tsx +76 -19
  159. package/src/lib/hooks/useCanvasEvents.ts +26 -26
  160. package/src/lib/hooks/useDocumentEvents.ts +6 -6
  161. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  162. package/src/lib/hooks/useGestureEvents.ts +2 -2
  163. package/src/lib/hooks/useHandleEvents.ts +6 -6
  164. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +4 -1
  165. package/src/lib/hooks/usePassThroughWheelEvents.ts +6 -1
  166. package/src/lib/hooks/useSelectionEvents.ts +9 -14
  167. package/src/lib/license/LicenseManager.test.ts +724 -383
  168. package/src/lib/license/LicenseManager.ts +204 -58
  169. package/src/lib/license/LicenseProvider.tsx +74 -2
  170. package/src/lib/license/Watermark.test.tsx +2 -1
  171. package/src/lib/license/Watermark.tsx +152 -77
  172. package/src/lib/license/useLicenseManagerState.ts +2 -2
  173. package/src/lib/options.ts +6 -0
  174. package/src/lib/primitives/Box.test.ts +126 -0
  175. package/src/lib/primitives/Box.ts +10 -1
  176. package/src/lib/primitives/Vec.ts +0 -5
  177. package/src/lib/primitives/geometry/Geometry2d.test.ts +420 -0
  178. package/src/lib/primitives/geometry/Geometry2d.ts +78 -21
  179. package/src/lib/primitives/geometry/Group2d.ts +10 -1
  180. package/src/lib/test/InFrontOfTheCanvas.test.tsx +187 -0
  181. package/src/lib/utils/dom.test.ts +103 -0
  182. package/src/lib/utils/dom.ts +8 -1
  183. package/src/lib/utils/getPointerInfo.ts +3 -2
  184. package/src/lib/utils/reparenting.ts +3 -69
  185. package/src/lib/utils/sync/LocalIndexedDb.test.ts +2 -1
  186. package/src/lib/utils/sync/TLLocalSyncClient.test.ts +15 -15
  187. package/src/version.ts +3 -3
  188. package/dist-cjs/lib/utils/nearestMultiple.js +0 -34
  189. package/dist-cjs/lib/utils/nearestMultiple.js.map +0 -7
  190. package/dist-esm/lib/utils/nearestMultiple.mjs +0 -14
  191. package/dist-esm/lib/utils/nearestMultiple.mjs.map +0 -7
  192. package/src/lib/utils/nearestMultiple.ts +0 -13
@@ -57,33 +57,21 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
57
57
  .filter(({ id }) => shapeIdsToInclude.has(id))
58
58
 
59
59
  // --- Common bounding box of all shapes
60
+ const singleFrameShapeId =
61
+ ids.length === 1 && editor.isShapeOfType<TLFrameShape>(editor.getShape(ids[0])!, 'frame')
62
+ ? ids[0]
63
+ : null
64
+
60
65
  let bbox: null | Box = null
61
66
  if (opts.bounds) {
62
- bbox = opts.bounds
67
+ bbox = opts.bounds.clone().expandBy(padding)
63
68
  } else {
64
- for (const { id } of renderingShapes) {
65
- const maskedPageBounds = editor.getShapeMaskedPageBounds(id)
66
- if (!maskedPageBounds) continue
67
- if (bbox) {
68
- bbox.union(maskedPageBounds)
69
- } else {
70
- bbox = maskedPageBounds.clone()
71
- }
72
- }
69
+ bbox = getExportDefaultBounds(editor, renderingShapes, padding, singleFrameShapeId)
73
70
  }
74
71
 
75
72
  // no unmasked shapes to export
76
73
  if (!bbox) return
77
74
 
78
- const singleFrameShapeId =
79
- ids.length === 1 && editor.isShapeOfType<TLFrameShape>(editor.getShape(ids[0])!, 'frame')
80
- ? ids[0]
81
- : null
82
- if (!singleFrameShapeId) {
83
- // Expand by an extra 32 pixels
84
- bbox.expandBy(padding)
85
- }
86
-
87
75
  // We want the svg image to be BIGGER THAN USUAL to account for image quality
88
76
  const w = bbox.width * scale
89
77
  const h = bbox.height * scale
@@ -120,6 +108,75 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
120
108
  return { jsx: svg, width: w, height: h, exportDelay }
121
109
  }
122
110
 
111
+ /**
112
+ * Calculates the default bounds for an SVG export. This function handles:
113
+ * 1. Computing masked page bounds for each shape
114
+ * 2. Container logic: if a shape is marked as an export bounds container and it
115
+ * contains all other shapes, use its bounds and skip padding
116
+ * 3. Otherwise, create a union of all shape bounds and apply padding
117
+ *
118
+ * The container logic is useful for cases like annotating on an image - if the image
119
+ * contains all annotations, we want to export exactly the image bounds without extra padding.
120
+ *
121
+ * @param editor - The editor instance
122
+ * @param renderingShapes - The shapes to include in the export
123
+ * @param padding - Padding to add around the bounds (only applied if no container bounds)
124
+ * @param singleFrameShapeId - If exporting a single frame, this is its ID (skips padding)
125
+ * @returns The calculated bounds box, or null if no shapes to export
126
+ */
127
+ export function getExportDefaultBounds(
128
+ editor: Editor,
129
+ renderingShapes: TLRenderingShape[],
130
+ padding: number,
131
+ singleFrameShapeId: TLShapeId | null
132
+ ) {
133
+ let isBoundedByContainer = false
134
+ let bbox: null | Box = null
135
+
136
+ for (const { id } of renderingShapes) {
137
+ const maskedPageBounds = editor.getShapeMaskedPageBounds(id)
138
+ if (!maskedPageBounds) continue
139
+
140
+ // Check if this shape is an export bounds container (e.g., an image being annotated)
141
+ const shape = editor.getShape(id)!
142
+ const isContainer = editor.getShapeUtil(shape).isExportBoundsContainer(shape)
143
+
144
+ if (bbox) {
145
+ // Container logic: if this is a container and it contains all shapes processed so far,
146
+ // use the container's bounds instead of the union. This prevents extra padding around
147
+ // things like annotated images.
148
+ if (isContainer && Box.ContainsApproximately(maskedPageBounds, bbox)) {
149
+ isBoundedByContainer = true
150
+ bbox = maskedPageBounds.clone()
151
+ } else {
152
+ // If we were previously bounded by a container but this shape extends outside it,
153
+ // we're no longer bounded by a container
154
+ if (isBoundedByContainer && !Box.ContainsApproximately(bbox, maskedPageBounds)) {
155
+ isBoundedByContainer = false
156
+ }
157
+ // Expand the bounding box to include this shape
158
+ bbox.union(maskedPageBounds)
159
+ }
160
+ } else {
161
+ // First shape sets the initial bounds
162
+ isBoundedByContainer = isContainer
163
+ bbox = maskedPageBounds.clone()
164
+ }
165
+ }
166
+
167
+ // No unmasked shapes to export
168
+ if (!bbox) return null
169
+
170
+ // Only apply padding if:
171
+ // - Not exporting a single frame (frames have their own padding rules)
172
+ // - Not bounded by a container (containers define their own bounds precisely)
173
+ if (!singleFrameShapeId && !isBoundedByContainer) {
174
+ bbox.expandBy(padding)
175
+ }
176
+
177
+ return bbox
178
+ }
179
+
123
180
  function SvgExport({
124
181
  editor,
125
182
  preserveAspectRatio,
@@ -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,55 +46,59 @@ 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
 
79
+ const editingShapeId = editor.getEditingShape()?.id
82
80
  if (
81
+ // if the target is not inside the editing shape
82
+ !(editingShapeId && e.target.closest(`[data-shape-id="${editingShapeId}"]`)) &&
83
+ // and the target is not an clickable element
83
84
  e.target.tagName !== 'A' &&
85
+ // or a TextArea.tsx ?
84
86
  e.target.tagName !== 'TEXTAREA' &&
85
- !e.target.isContentEditable &&
86
- // When in EditingShape state, we are actually clicking on a 'DIV'
87
- // not A/TEXTAREA/contenteditable element yet. So, to preserve cursor position
88
- // for edit mode on mobile we need to not preventDefault.
89
- // TODO: Find out if we still need this preventDefault in general though.
90
- !(editor.getEditingShape() && e.target.className.includes('tl-text-content'))
87
+ !e.target.isContentEditable
91
88
  ) {
92
89
  preventDefault(e)
93
90
  }
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,14 +1,17 @@
1
1
  import { RefObject, useEffect } from 'react'
2
2
  import { preventDefault } from '../utils/dom'
3
3
  import { useContainer } from './useContainer'
4
+ import { useMaybeEditor } from './useEditor'
4
5
 
5
6
  /** @public */
6
7
  export function usePassThroughMouseOverEvents(ref: RefObject<HTMLElement>) {
7
8
  if (!ref) throw Error('usePassThroughWheelEvents must be passed a ref')
8
9
  const container = useContainer()
10
+ const editor = useMaybeEditor()
9
11
 
10
12
  useEffect(() => {
11
13
  function onMouseOver(e: MouseEvent) {
14
+ if (!editor?.getInstanceState().isFocused) return
12
15
  if ((e as any).isSpecialRedispatchedEvent) return
13
16
  preventDefault(e)
14
17
  const cvs = container.querySelector('.tl-canvas')
@@ -25,5 +28,5 @@ export function usePassThroughMouseOverEvents(ref: RefObject<HTMLElement>) {
25
28
  return () => {
26
29
  elm.removeEventListener('mouseover', onMouseOver)
27
30
  }
28
- }, [container, ref])
31
+ }, [container, editor, ref])
29
32
  }
@@ -1,14 +1,19 @@
1
1
  import { RefObject, useEffect } from 'react'
2
2
  import { preventDefault } from '../utils/dom'
3
3
  import { useContainer } from './useContainer'
4
+ import { useMaybeEditor } from './useEditor'
4
5
 
5
6
  /** @public */
6
7
  export function usePassThroughWheelEvents(ref: RefObject<HTMLElement>) {
7
8
  if (!ref) throw Error('usePassThroughWheelEvents must be passed a ref')
8
9
  const container = useContainer()
10
+ const editor = useMaybeEditor()
9
11
 
10
12
  useEffect(() => {
11
13
  function onWheel(e: WheelEvent) {
14
+ // Only pass through wheel events if the editor is focused
15
+ if (!editor?.getInstanceState().isFocused) return
16
+
12
17
  if ((e as any).isSpecialRedispatchedEvent) return
13
18
 
14
19
  // if the element is scrollable, don't redispatch the event
@@ -32,5 +37,5 @@ export function usePassThroughWheelEvents(ref: RefObject<HTMLElement>) {
32
37
  return () => {
33
38
  elm.removeEventListener('wheel', onWheel)
34
39
  }
35
- }, [container, ref])
40
+ }, [container, editor, ref])
36
41
  }
@@ -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