@tldraw/editor 4.2.2 → 4.2.3

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 (200) hide show
  1. package/dist-cjs/index.d.ts +155 -498
  2. package/dist-cjs/index.js +1 -6
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/components/ErrorBoundary.js.map +1 -1
  5. package/dist-cjs/lib/components/GeometryDebuggingView.js +17 -1
  6. package/dist-cjs/lib/components/GeometryDebuggingView.js.map +2 -2
  7. package/dist-cjs/lib/components/default-components/DefaultCanvas.js +3 -3
  8. package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
  9. package/dist-cjs/lib/constants.js +3 -1
  10. package/dist-cjs/lib/constants.js.map +2 -2
  11. package/dist-cjs/lib/editor/Editor.js +286 -292
  12. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  13. package/dist-cjs/lib/editor/bindings/BindingUtil.js.map +2 -2
  14. package/dist-cjs/lib/editor/derivations/bindingsIndex.js.map +2 -2
  15. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +17 -18
  16. package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
  17. package/dist-cjs/lib/editor/derivations/parentsToChildren.js +3 -12
  18. package/dist-cjs/lib/editor/derivations/parentsToChildren.js.map +2 -2
  19. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +1 -1
  20. package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
  21. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +6 -5
  22. package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
  23. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js +1 -1
  24. package/dist-cjs/lib/editor/managers/SnapManager/SnapManager.js.map +2 -2
  25. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +22 -1
  26. package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
  27. package/dist-cjs/lib/editor/shapes/BaseBoxShapeUtil.js.map +1 -1
  28. package/dist-cjs/lib/editor/shapes/ShapeUtil.js +23 -31
  29. package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
  30. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js +1 -1
  31. package/dist-cjs/lib/editor/shapes/group/DashedOutlineBox.js.map +2 -2
  32. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  33. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.js.map +2 -2
  34. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +3 -3
  35. package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
  36. package/dist-cjs/lib/editor/types/emit-types.js.map +1 -1
  37. package/dist-cjs/lib/exports/getSvgJsx.js.map +2 -2
  38. package/dist-cjs/lib/exports/parseCss.js +1 -1
  39. package/dist-cjs/lib/exports/parseCss.js.map +2 -2
  40. package/dist-cjs/lib/globals/environment.js +9 -45
  41. package/dist-cjs/lib/globals/environment.js.map +2 -2
  42. package/dist-cjs/lib/globals/menus.js +1 -1
  43. package/dist-cjs/lib/globals/menus.js.map +2 -2
  44. package/dist-cjs/lib/hooks/useCanvasEvents.js +3 -4
  45. package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
  46. package/dist-cjs/lib/hooks/useCoarsePointer.js +29 -14
  47. package/dist-cjs/lib/hooks/useCoarsePointer.js.map +2 -2
  48. package/dist-cjs/lib/hooks/useEvent.js +1 -1
  49. package/dist-cjs/lib/hooks/useEvent.js.map +2 -2
  50. package/dist-cjs/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.js.map +2 -2
  51. package/dist-cjs/lib/hooks/useGestureEvents.js +1 -1
  52. package/dist-cjs/lib/hooks/useGestureEvents.js.map +2 -2
  53. package/dist-cjs/lib/hooks/usePassThroughMouseOverEvents.js.map +2 -2
  54. package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
  55. package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
  56. package/dist-cjs/lib/hooks/useStateAttribute.js +1 -4
  57. package/dist-cjs/lib/hooks/useStateAttribute.js.map +2 -2
  58. package/dist-cjs/lib/hooks/useTransform.js.map +1 -1
  59. package/dist-cjs/lib/hooks/useZoomCss.js +8 -4
  60. package/dist-cjs/lib/hooks/useZoomCss.js.map +2 -2
  61. package/dist-cjs/lib/options.js +1 -6
  62. package/dist-cjs/lib/options.js.map +2 -2
  63. package/dist-cjs/lib/primitives/Box.js +0 -3
  64. package/dist-cjs/lib/primitives/Box.js.map +2 -2
  65. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +0 -1
  66. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +2 -2
  67. package/dist-cjs/lib/utils/reparenting.js.map +2 -2
  68. package/dist-cjs/lib/utils/rotation.js +1 -1
  69. package/dist-cjs/lib/utils/rotation.js.map +2 -2
  70. package/dist-cjs/version.js +3 -3
  71. package/dist-cjs/version.js.map +1 -1
  72. package/dist-esm/index.d.mts +155 -498
  73. package/dist-esm/index.mjs +2 -7
  74. package/dist-esm/index.mjs.map +2 -2
  75. package/dist-esm/lib/components/ErrorBoundary.mjs.map +1 -1
  76. package/dist-esm/lib/components/GeometryDebuggingView.mjs +17 -1
  77. package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +2 -2
  78. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +3 -3
  79. package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
  80. package/dist-esm/lib/constants.mjs +3 -1
  81. package/dist-esm/lib/constants.mjs.map +2 -2
  82. package/dist-esm/lib/editor/Editor.mjs +289 -293
  83. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  84. package/dist-esm/lib/editor/bindings/BindingUtil.mjs.map +2 -2
  85. package/dist-esm/lib/editor/derivations/bindingsIndex.mjs.map +2 -2
  86. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +17 -18
  87. package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
  88. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs +4 -13
  89. package/dist-esm/lib/editor/derivations/parentsToChildren.mjs.map +2 -2
  90. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +1 -1
  91. package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
  92. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +6 -5
  93. package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
  94. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs +1 -1
  95. package/dist-esm/lib/editor/managers/SnapManager/SnapManager.mjs.map +2 -2
  96. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +22 -1
  97. package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
  98. package/dist-esm/lib/editor/shapes/BaseBoxShapeUtil.mjs.map +1 -1
  99. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +23 -31
  100. package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
  101. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs +1 -1
  102. package/dist-esm/lib/editor/shapes/group/DashedOutlineBox.mjs.map +2 -2
  103. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  104. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.mjs.map +2 -2
  105. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +3 -3
  106. package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
  107. package/dist-esm/lib/exports/getSvgJsx.mjs.map +2 -2
  108. package/dist-esm/lib/exports/parseCss.mjs +1 -1
  109. package/dist-esm/lib/exports/parseCss.mjs.map +2 -2
  110. package/dist-esm/lib/globals/environment.mjs +9 -45
  111. package/dist-esm/lib/globals/environment.mjs.map +2 -2
  112. package/dist-esm/lib/globals/menus.mjs +1 -1
  113. package/dist-esm/lib/globals/menus.mjs.map +2 -2
  114. package/dist-esm/lib/hooks/useCanvasEvents.mjs +3 -4
  115. package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
  116. package/dist-esm/lib/hooks/useCoarsePointer.mjs +30 -15
  117. package/dist-esm/lib/hooks/useCoarsePointer.mjs.map +2 -2
  118. package/dist-esm/lib/hooks/useEvent.mjs +1 -1
  119. package/dist-esm/lib/hooks/useEvent.mjs.map +2 -2
  120. package/dist-esm/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.mjs.map +2 -2
  121. package/dist-esm/lib/hooks/useGestureEvents.mjs +1 -1
  122. package/dist-esm/lib/hooks/useGestureEvents.mjs.map +2 -2
  123. package/dist-esm/lib/hooks/usePassThroughMouseOverEvents.mjs.map +2 -2
  124. package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
  125. package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
  126. package/dist-esm/lib/hooks/useStateAttribute.mjs +1 -4
  127. package/dist-esm/lib/hooks/useStateAttribute.mjs.map +2 -2
  128. package/dist-esm/lib/hooks/useTransform.mjs.map +1 -1
  129. package/dist-esm/lib/hooks/useZoomCss.mjs +8 -4
  130. package/dist-esm/lib/hooks/useZoomCss.mjs.map +2 -2
  131. package/dist-esm/lib/options.mjs +1 -6
  132. package/dist-esm/lib/options.mjs.map +2 -2
  133. package/dist-esm/lib/primitives/Box.mjs +0 -3
  134. package/dist-esm/lib/primitives/Box.mjs.map +2 -2
  135. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +0 -1
  136. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  137. package/dist-esm/lib/utils/reparenting.mjs.map +2 -2
  138. package/dist-esm/lib/utils/rotation.mjs +1 -1
  139. package/dist-esm/lib/utils/rotation.mjs.map +2 -2
  140. package/dist-esm/version.mjs +3 -3
  141. package/dist-esm/version.mjs.map +1 -1
  142. package/editor.css +12 -14
  143. package/package.json +16 -18
  144. package/src/index.ts +1 -4
  145. package/src/lib/components/ErrorBoundary.tsx +1 -1
  146. package/src/lib/components/GeometryDebuggingView.tsx +19 -1
  147. package/src/lib/components/default-components/DefaultCanvas.tsx +3 -4
  148. package/src/lib/constants.ts +2 -0
  149. package/src/lib/editor/Editor.test.ts +10 -150
  150. package/src/lib/editor/Editor.ts +379 -459
  151. package/src/lib/editor/bindings/BindingUtil.ts +9 -15
  152. package/src/lib/editor/derivations/bindingsIndex.ts +2 -2
  153. package/src/lib/editor/derivations/notVisibleShapes.ts +23 -37
  154. package/src/lib/editor/derivations/parentsToChildren.ts +7 -18
  155. package/src/lib/editor/managers/ClickManager/ClickManager.test.ts +31 -17
  156. package/src/lib/editor/managers/ClickManager/ClickManager.ts +1 -1
  157. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +79 -129
  158. package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +6 -10
  159. package/src/lib/editor/managers/FontManager/FontManager.test.ts +4 -14
  160. package/src/lib/editor/managers/ScribbleManager/ScribbleManager.test.ts +4 -0
  161. package/src/lib/editor/managers/SnapManager/SnapManager.test.ts +0 -12
  162. package/src/lib/editor/managers/SnapManager/SnapManager.ts +4 -4
  163. package/src/lib/editor/managers/TickManager/TickManager.test.ts +107 -40
  164. package/src/lib/editor/managers/TickManager/TickManager.ts +32 -2
  165. package/src/lib/editor/shapes/BaseBoxShapeUtil.tsx +2 -2
  166. package/src/lib/editor/shapes/ShapeUtil.ts +32 -72
  167. package/src/lib/editor/shapes/group/DashedOutlineBox.tsx +1 -1
  168. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +3 -1
  169. package/src/lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool.ts +1 -2
  170. package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +6 -6
  171. package/src/lib/editor/types/emit-types.ts +1 -3
  172. package/src/lib/exports/getSvgJsx.test.ts +19 -10
  173. package/src/lib/exports/getSvgJsx.tsx +5 -2
  174. package/src/lib/exports/parseCss.test.ts +0 -1
  175. package/src/lib/exports/parseCss.ts +1 -1
  176. package/src/lib/globals/environment.ts +10 -65
  177. package/src/lib/globals/menus.ts +1 -1
  178. package/src/lib/hooks/useCanvasEvents.ts +3 -4
  179. package/src/lib/hooks/useCoarsePointer.ts +59 -16
  180. package/src/lib/hooks/useEvent.tsx +1 -1
  181. package/src/lib/hooks/useFixSafariDoubleTapZoomPencilEvents.ts +1 -1
  182. package/src/lib/hooks/useGestureEvents.ts +2 -2
  183. package/src/lib/hooks/usePassThroughMouseOverEvents.ts +1 -1
  184. package/src/lib/hooks/usePassThroughWheelEvents.ts +1 -1
  185. package/src/lib/hooks/useScreenBounds.ts +1 -1
  186. package/src/lib/hooks/useStateAttribute.ts +1 -4
  187. package/src/lib/hooks/useTransform.ts +1 -1
  188. package/src/lib/hooks/useZoomCss.ts +8 -3
  189. package/src/lib/options.ts +0 -32
  190. package/src/lib/primitives/Box.ts +0 -9
  191. package/src/lib/primitives/geometry/Geometry2d.ts +0 -1
  192. package/src/lib/utils/reparenting.ts +5 -5
  193. package/src/lib/utils/rotation.ts +1 -1
  194. package/src/version.ts +3 -3
  195. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +0 -591
  196. package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +0 -7
  197. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +0 -573
  198. package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +0 -7
  199. package/src/lib/config/TLUserPreferences.test.ts +0 -40
  200. package/src/lib/editor/managers/InputsManager/InputsManager.ts +0 -566
@@ -1,6 +1,7 @@
1
1
  import { useAtom, useValue } from '@tldraw/state-react'
2
2
  import {
3
3
  TLFrameShape,
4
+ TLGroupShape,
4
5
  TLShape,
5
6
  TLShapeId,
6
7
  getColorValue,
@@ -57,7 +58,9 @@ export function getSvgJsx(editor: Editor, ids: TLShapeId[], opts: TLImageExportO
57
58
 
58
59
  // --- Common bounding box of all shapes
59
60
  const singleFrameShapeId =
60
- ids.length === 1 && editor.isShapeOfType(editor.getShape(ids[0])!, 'frame') ? ids[0] : null
61
+ ids.length === 1 && editor.isShapeOfType<TLFrameShape>(editor.getShape(ids[0])!, 'frame')
62
+ ? ids[0]
63
+ : null
61
64
 
62
65
  let bbox: null | Box = null
63
66
  if (opts.bounds) {
@@ -269,7 +272,7 @@ function SvgExport({
269
272
 
270
273
  const shape = editor.getShape(id)!
271
274
 
272
- if (editor.isShapeOfType(shape, 'group')) return []
275
+ if (editor.isShapeOfType<TLGroupShape>(shape, 'group')) return []
273
276
 
274
277
  const elements = []
275
278
  const util = editor.getShapeUtil(shape)
@@ -364,5 +364,4 @@ test('parseCssValueUrls', () => {
364
364
  },
365
365
  ]
366
366
  `)
367
- expect(parseCssValueUrls(`url(#arrowhead)`)).toMatchInlineSnapshot(`[]`)
368
367
  })
@@ -108,5 +108,5 @@ export function parseCssValueUrls(value: string) {
108
108
  return Array.from(value.matchAll(urlsRegex), (m) => ({
109
109
  original: m[0],
110
110
  url: m[1] || m[2] || m[3],
111
- })).filter((m) => !m.url.startsWith('#'))
111
+ }))
112
112
  }
@@ -1,9 +1,5 @@
1
- import { atom } from '@tldraw/state'
2
-
3
1
  /**
4
2
  * An object that contains information about the current device and environment.
5
- * This object is not reactive and will not update automatically when the environment changes,
6
- * so only include values that are fixed, such as the user's browser and operating system.
7
3
  *
8
4
  * @public
9
5
  */
@@ -18,66 +14,15 @@ const tlenv = {
18
14
  hasCanvasSupport: false,
19
15
  }
20
16
 
21
- let isForcedFinePointer = false
22
-
23
- if (typeof window !== 'undefined') {
24
- if ('navigator' in window) {
25
- tlenv.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
26
- tlenv.isIos = !!navigator.userAgent.match(/iPad/i) || !!navigator.userAgent.match(/iPhone/i)
27
- tlenv.isChromeForIos = /crios.*safari/i.test(navigator.userAgent)
28
- tlenv.isFirefox = /firefox/i.test(navigator.userAgent)
29
- tlenv.isAndroid = /android/i.test(navigator.userAgent)
30
- tlenv.isDarwin = window.navigator.userAgent.toLowerCase().indexOf('mac') > -1
31
- }
32
- tlenv.hasCanvasSupport = 'Promise' in window && 'HTMLCanvasElement' in window
33
- isForcedFinePointer = tlenv.isFirefox && !tlenv.isAndroid && !tlenv.isIos
34
- }
35
-
36
- /**
37
- * An atom that contains information about the current device and environment.
38
- * This object is reactive and will update automatically when the environment changes.
39
- * Use it for values that may change over time, such as the pointer type.
40
- *
41
- * @public
42
- */
43
- const tlenvReactive = atom('tlenvReactive', {
44
- // Whether the user's device has a coarse pointer. This is dynamic on many systems, especially
45
- // on touch-screen laptops, which will become "coarse" if the user touches the screen.
46
- // See https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/pointer#coarse
47
- isCoarsePointer: false,
48
- })
49
-
50
- if (typeof window !== 'undefined' && !isForcedFinePointer) {
51
- const mql = window.matchMedia && window.matchMedia('(any-pointer: coarse)')
52
-
53
- const isCurrentCoarsePointer = () => tlenvReactive.__unsafe__getWithoutCapture().isCoarsePointer
54
-
55
- if (mql) {
56
- // 1. Update the coarse pointer automatically when the media query changes
57
- const updateIsCoarsePointer = () => {
58
- const isCoarsePointer = mql.matches
59
- if (isCoarsePointer !== isCurrentCoarsePointer()) {
60
- tlenvReactive.update((prev) => ({ ...prev, isCoarsePointer: isCoarsePointer }))
61
- }
62
- }
63
- updateIsCoarsePointer()
64
- mql.addEventListener('change', updateIsCoarsePointer)
65
- }
66
-
67
- // 2. Also update the coarse pointer state when a pointer down event occurs. We need `capture: true`
68
- // here because the tldraw component itself stops propagation on pointer events it receives.
69
- window.addEventListener(
70
- 'pointerdown',
71
- (e: PointerEvent) => {
72
- // when the user interacts with a mouse, we assume they have a fine pointer.
73
- // otherwise, we assume they have a coarse pointer.
74
- const isCoarseEvent = e.pointerType !== 'mouse'
75
- if (isCoarseEvent !== isCurrentCoarsePointer()) {
76
- tlenvReactive.update((prev) => ({ ...prev, isCoarsePointer: isCoarseEvent }))
77
- }
78
- },
79
- { capture: true }
80
- )
17
+ if (typeof window !== 'undefined' && 'navigator' in window) {
18
+ tlenv.isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
19
+ tlenv.isIos = !!navigator.userAgent.match(/iPad/i) || !!navigator.userAgent.match(/iPhone/i)
20
+ tlenv.isChromeForIos = /crios.*safari/i.test(navigator.userAgent)
21
+ tlenv.isFirefox = /firefox/i.test(navigator.userAgent)
22
+ tlenv.isAndroid = /android/i.test(navigator.userAgent)
23
+ tlenv.isDarwin = window.navigator.userAgent.toLowerCase().indexOf('mac') > -1
24
+ tlenv.hasCanvasSupport =
25
+ typeof window !== 'undefined' && 'Promise' in window && 'HTMLCanvasElement' in window
81
26
  }
82
27
 
83
- export { tlenv, tlenvReactive }
28
+ export { tlenv }
@@ -148,7 +148,7 @@ export const tlmenus = {
148
148
  * @public
149
149
  */
150
150
  isMenuOpen(id: string, contextId?: string): boolean {
151
- return this.getOpenMenus(contextId).includes(`${id}-${contextId}`)
151
+ return this.getOpenMenus(contextId).includes(id)
152
152
  },
153
153
 
154
154
  /**
@@ -8,7 +8,6 @@ import { useEditor } from './useEditor'
8
8
 
9
9
  export function useCanvasEvents() {
10
10
  const editor = useEditor()
11
- const ownerDocument = editor.getContainer().ownerDocument
12
11
  const currentTool = useValue('current tool', () => editor.getCurrentTool(), [editor])
13
12
 
14
13
  const events = useMemo(
@@ -181,11 +180,11 @@ export function useCanvasEvents() {
181
180
  }
182
181
  }
183
182
 
184
- ownerDocument.body.addEventListener('pointermove', onPointerMove)
183
+ document.body.addEventListener('pointermove', onPointerMove)
185
184
  return () => {
186
- ownerDocument.body.removeEventListener('pointermove', onPointerMove)
185
+ document.body.removeEventListener('pointermove', onPointerMove)
187
186
  }
188
- }, [editor, currentTool, ownerDocument])
187
+ }, [editor, currentTool])
189
188
 
190
189
  return events
191
190
  }
@@ -1,23 +1,66 @@
1
- import { unsafe__withoutCapture } from '@tldraw/state'
2
- import { useReactor } from '@tldraw/state-react'
3
- import { tlenvReactive } from '../globals/environment'
1
+ import { useEffect } from 'react'
2
+ import { tlenv } from '../globals/environment'
4
3
  import { useEditor } from './useEditor'
5
4
 
6
5
  /** @internal */
7
6
  export function useCoarsePointer() {
8
7
  const editor = useEditor()
9
8
 
10
- // When the coarse pointer state changes, update the instance state
11
- useReactor(
12
- 'coarse pointer change',
13
- () => {
14
- const isCoarsePointer = tlenvReactive.get().isCoarsePointer
15
- const isInstanceStateCoarsePointer = unsafe__withoutCapture(
16
- () => editor.getInstanceState().isCoarsePointer
17
- )
18
- if (isCoarsePointer === isInstanceStateCoarsePointer) return
19
- editor.updateInstanceState({ isCoarsePointer: isCoarsePointer })
20
- },
21
- [editor]
22
- )
9
+ useEffect(() => {
10
+ // We'll track our own state for the pointer type
11
+ let isCoarse = editor.getInstanceState().isCoarsePointer
12
+
13
+ // 1.
14
+ // We'll use pointer events to detect coarse pointer.
15
+
16
+ const handlePointerDown = (e: PointerEvent) => {
17
+ // when the user interacts with a mouse, we assume they have a fine pointer.
18
+ // otherwise, we assume they have a coarse pointer.
19
+ const isCoarseEvent = e.pointerType !== 'mouse'
20
+ if (isCoarse === isCoarseEvent) return
21
+ isCoarse = isCoarseEvent
22
+ editor.updateInstanceState({ isCoarsePointer: isCoarseEvent })
23
+ }
24
+
25
+ // we need `capture: true` here because the tldraw component itself stops propagation on
26
+ // pointer events it receives.
27
+ window.addEventListener('pointerdown', handlePointerDown, { capture: true })
28
+
29
+ // 2.
30
+ // We can also use the media query to detect / set the initial pointer type
31
+ // and update the state if the pointer type changes.
32
+
33
+ // We want the touch / mouse events to run even if the browser does not
34
+ // support matchMedia. We'll have to handle the media query changes
35
+ // conditionally in the code below.
36
+ const mql = window.matchMedia && window.matchMedia('(any-pointer: coarse)')
37
+
38
+ // This is a workaround for a Firefox bug where we don't correctly
39
+ // detect coarse VS fine pointer. For now, let's assume that you have a fine
40
+ // pointer if you're on Firefox on desktop.
41
+ const isForcedFinePointer = tlenv.isFirefox && !tlenv.isAndroid && !tlenv.isIos
42
+
43
+ const handleMediaQueryChange = () => {
44
+ const next = isForcedFinePointer ? false : mql.matches // get the value from the media query
45
+ if (isCoarse !== next) return // bail if the value hasn't changed
46
+ isCoarse = next // update the local value
47
+ editor.updateInstanceState({ isCoarsePointer: next }) // update the value in state
48
+ }
49
+
50
+ if (mql) {
51
+ // set up the listener
52
+ mql.addEventListener('change', handleMediaQueryChange)
53
+
54
+ // and run the handler once to set the initial value
55
+ handleMediaQueryChange()
56
+ }
57
+
58
+ return () => {
59
+ window.removeEventListener('pointerdown', handlePointerDown, { capture: true })
60
+
61
+ if (mql) {
62
+ mql.removeEventListener('change', handleMediaQueryChange)
63
+ }
64
+ }
65
+ }, [editor])
23
66
  }
@@ -27,7 +27,7 @@ import { useCallback, useDebugValue, useLayoutEffect, useRef } from 'react'
27
27
  export function useEvent<Args extends Array<unknown>, Result>(
28
28
  handler: (...args: Args) => Result
29
29
  ): (...args: Args) => Result {
30
- const handlerRef = useRef<((...args: Args) => Result) | undefined>(undefined)
30
+ const handlerRef = useRef<(...args: Args) => Result>()
31
31
 
32
32
  // In a real implementation, this would run before layout effects
33
33
  useLayoutEffect(() => {
@@ -9,7 +9,7 @@ const IGNORED_TAGS = ['textarea', 'input']
9
9
  * want this for drawing operations and can disable it by setting 'disableDoubleTapZoom' in the main
10
10
  * editor.
11
11
  */
12
- export function useFixSafariDoubleTapZoomPencilEvents(ref: React.RefObject<HTMLElement | null>) {
12
+ export function useFixSafariDoubleTapZoomPencilEvents(ref: React.RefObject<HTMLElement>) {
13
13
  const editor = useEditor()
14
14
 
15
15
  useEffect(() => {
@@ -75,7 +75,7 @@ const isWheelEndEvent = (time: number) => {
75
75
  return false
76
76
  }
77
77
 
78
- export function useGestureEvents(ref: React.RefObject<HTMLDivElement | null>) {
78
+ export function useGestureEvents(ref: React.RefObject<HTMLDivElement>) {
79
79
  const editor = useEditor()
80
80
 
81
81
  const events = React.useMemo(() => {
@@ -105,7 +105,7 @@ export function useGestureEvents(ref: React.RefObject<HTMLDivElement | null>) {
105
105
  const util = editor.getShapeUtil(shape)
106
106
  if (util.canScroll(shape)) {
107
107
  const bounds = editor.getShapePageBounds(editingShapeId)
108
- if (bounds?.containsPoint(editor.inputs.getCurrentPagePoint())) {
108
+ if (bounds?.containsPoint(editor.inputs.currentPagePoint)) {
109
109
  return
110
110
  }
111
111
  }
@@ -4,7 +4,7 @@ import { useContainer } from './useContainer'
4
4
  import { useMaybeEditor } from './useEditor'
5
5
 
6
6
  /** @public */
7
- export function usePassThroughMouseOverEvents(ref: RefObject<HTMLElement | null>) {
7
+ export function usePassThroughMouseOverEvents(ref: RefObject<HTMLElement>) {
8
8
  if (!ref) throw Error('usePassThroughWheelEvents must be passed a ref')
9
9
  const container = useContainer()
10
10
  const editor = useMaybeEditor()
@@ -4,7 +4,7 @@ import { useContainer } from './useContainer'
4
4
  import { useMaybeEditor } from './useEditor'
5
5
 
6
6
  /** @public */
7
- export function usePassThroughWheelEvents(ref: RefObject<HTMLElement | null>) {
7
+ export function usePassThroughWheelEvents(ref: RefObject<HTMLElement>) {
8
8
  if (!ref) throw Error('usePassThroughWheelEvents must be passed a ref')
9
9
  const container = useContainer()
10
10
  const editor = useMaybeEditor()
@@ -2,7 +2,7 @@ import { throttle } from '@tldraw/utils'
2
2
  import { useLayoutEffect } from 'react'
3
3
  import { useEditor } from './useEditor'
4
4
 
5
- export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
5
+ export function useScreenBounds(ref: React.RefObject<HTMLElement>) {
6
6
  const editor = useEditor()
7
7
 
8
8
  useLayoutEffect(() => {
@@ -9,10 +9,7 @@ export function useStateAttribute() {
9
9
  // editor mounting and this attribute being applied, because styles may depend on it:
10
10
  useLayoutEffect(() => {
11
11
  return react('stateAttribute', () => {
12
- const container = editor.getContainer()
13
- const instanceState = editor.getInstanceState()
14
- container.setAttribute('data-state', editor.getPath())
15
- container.setAttribute('data-coarse', String(instanceState.isCoarsePointer))
12
+ editor.getContainer().setAttribute('data-state', editor.getPath())
16
13
  })
17
14
  }, [editor])
18
15
  }
@@ -3,7 +3,7 @@ import { VecLike } from '../primitives/Vec'
3
3
 
4
4
  /** @public */
5
5
  export function useTransform(
6
- ref: React.RefObject<HTMLElement | SVGElement | null>,
6
+ ref: React.RefObject<HTMLElement | SVGElement>,
7
7
  x?: number,
8
8
  y?: number,
9
9
  scale?: number,
@@ -12,9 +12,14 @@ export function useZoomCss() {
12
12
  const setScale = (s: number) => container.style.setProperty('--tl-zoom', s.toString())
13
13
  const setScaleDebounced = debounce(setScale, 100)
14
14
 
15
- const scheduler = new EffectScheduler('useZoomCss', () =>
16
- setScale(editor.getEfficientZoomLevel())
17
- )
15
+ const scheduler = new EffectScheduler('useZoomCss', () => {
16
+ const numShapes = editor.getCurrentPageShapeIds().size
17
+ if (numShapes < 300) {
18
+ setScale(editor.getZoomLevel())
19
+ } else {
20
+ setScaleDebounced(editor.getZoomLevel())
21
+ }
22
+ })
18
23
 
19
24
  scheduler.attach()
20
25
  scheduler.execute()
@@ -87,33 +87,6 @@ export interface TldrawOptions {
87
87
  * Branding name of the app, currently only used for adding aria-label for the application.
88
88
  */
89
89
  readonly branding?: string
90
- /**
91
- * Whether to use debounced zoom level for certain rendering optimizations. When true,
92
- * `editor.getDebouncedZoomLevel()` returns a cached zoom value while the camera is moving,
93
- * reducing re-renders. When false, it always returns the current zoom level.
94
- */
95
- readonly debouncedZoom: boolean
96
- /**
97
- * The number of shapes that must be on the page for the debounced zoom level to be used.
98
- * Defaults to 300 shapes.
99
- */
100
- readonly debouncedZoomThreshold: number
101
- /**
102
- * Whether to allow spacebar panning. When true, the spacebar will pan the camera when held down.
103
- * When false, the spacebar will not pan the camera.
104
- */
105
- readonly spacebarPanning: boolean
106
- /**
107
- * The default padding (in pixels) used when zooming to fit content in the viewport.
108
- * This affects methods like `zoomToFit()`, `zoomToSelection()`, and `zoomToBounds()`.
109
- * The actual padding used is the minimum of this value and 28% of the viewport width.
110
- * Defaults to 128 pixels.
111
- */
112
- readonly zoomToFitPadding: number
113
- /**
114
- * The distance (in screen pixels) at which shapes snap to guides and other shapes.
115
- */
116
- readonly snapThreshold: number
117
90
  }
118
91
 
119
92
  /** @public */
@@ -166,9 +139,4 @@ export const defaultTldrawOptions = {
166
139
  enableToolbarKeyboardShortcuts: true,
167
140
  maxFontsToLoadBeforeRender: Infinity,
168
141
  nonce: undefined,
169
- debouncedZoom: true,
170
- debouncedZoomThreshold: 500,
171
- spacebarPanning: true,
172
- zoomToFitPadding: 128,
173
- snapThreshold: 8,
174
142
  } as const satisfies TldrawOptions
@@ -180,15 +180,6 @@ export class Box {
180
180
  return new Vec(this.w, this.h)
181
181
  }
182
182
 
183
- isValid() {
184
- return (
185
- Number.isFinite(this.x) &&
186
- Number.isFinite(this.y) &&
187
- Number.isFinite(this.w) &&
188
- Number.isFinite(this.h)
189
- )
190
- }
191
-
192
183
  toFixed() {
193
184
  this.x = toPrecision(this.x)
194
185
  this.y = toPrecision(this.y)
@@ -140,7 +140,6 @@ export abstract class Geometry2d {
140
140
  }
141
141
  }
142
142
  if (!nearest) throw Error('nearest point not found')
143
- dist = Math.sqrt(dist) // return the actual distance, not the squared distance
144
143
  return this.isClosed && this.isFilled && pointInPolygon(nearest, this.vertices) ? -dist : dist
145
144
  }
146
145
 
@@ -95,8 +95,8 @@ export function kickoutOccludedShapes(
95
95
  if (remainingShapesToReparent.size > 0) {
96
96
  // The remaining shapes are going to be reparented to the old parent's containing group, if there was one, or else to the page
97
97
  const newParentId =
98
- editor.findShapeAncestor(prevParent, (s) => editor.isShapeOfType(s, 'group'))?.id ??
99
- editor.getCurrentPageId()
98
+ editor.findShapeAncestor(prevParent, (s) => editor.isShapeOfType<TLGroupShape>(s, 'group'))
99
+ ?.id ?? editor.getCurrentPageId()
100
100
 
101
101
  remainingShapesToReparent.forEach((shape) => {
102
102
  if (!parentsToNewChildren[newParentId]) {
@@ -211,7 +211,7 @@ export function getDroppedShapesToNewParents(
211
211
 
212
212
  for (const shape of shapes) {
213
213
  const parent = editor.getShapeParent(shape)
214
- if (parent && editor.isShapeOfType(parent, 'group')) {
214
+ if (parent && editor.isShapeOfType<TLGroupShape>(parent, 'group')) {
215
215
  if (!movingGroups.has(parent)) {
216
216
  movingGroups.add(parent)
217
217
  }
@@ -248,7 +248,7 @@ export function getDroppedShapesToNewParents(
248
248
  parentCheck: for (let i = potentialParentShapes.length - 1; i >= 0; i--) {
249
249
  const parentShape = potentialParentShapes[i]
250
250
  const parentShapeContainingGroupId = editor.findShapeAncestor(parentShape, (s) =>
251
- editor.isShapeOfType(s, 'group')
251
+ editor.isShapeOfType<TLGroupShape>(s, 'group')
252
252
  )?.id
253
253
 
254
254
  const parentGeometry = editor.getShapeGeometry(parentShape)
@@ -274,7 +274,7 @@ export function getDroppedShapesToNewParents(
274
274
  if (!shapeGroupIds.has(shape.id)) {
275
275
  shapeGroupIds.set(
276
276
  shape.id,
277
- editor.findShapeAncestor(shape, (s) => editor.isShapeOfType(s, 'group'))?.id
277
+ editor.findShapeAncestor(shape, (s) => editor.isShapeOfType<TLGroupShape>(s, 'group'))?.id
278
278
  )
279
279
  }
280
280
 
@@ -32,7 +32,7 @@ export function getRotationSnapshot({
32
32
 
33
33
  return {
34
34
  initialPageCenter,
35
- initialCursorAngle: initialPageCenter.angle(editor.inputs.getOriginPagePoint()),
35
+ initialCursorAngle: initialPageCenter.angle(editor.inputs.originPagePoint),
36
36
  initialShapesRotation: rotation,
37
37
  shapeSnapshots: shapes.map((shape) => ({
38
38
  shape,
package/src/version.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '4.2.2'
4
+ export const version = '4.2.3'
5
5
  export const publishDates = {
6
6
  major: '2025-09-18T14:39:22.803Z',
7
- minor: '2025-11-19T11:53:58.692Z',
8
- patch: '2026-01-07T11:19:27.083Z',
7
+ minor: '2025-11-19T11:47:45.748Z',
8
+ patch: '2026-01-08T10:11:20.530Z',
9
9
  }