tldraw 3.16.0-canary.cf24aedcd577 → 3.16.0-canary.d3a23ebd1b0b

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 (226) hide show
  1. package/dist-cjs/index.d.ts +93 -1
  2. package/dist-cjs/index.js +9 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/Tldraw.js +12 -2
  5. package/dist-cjs/lib/Tldraw.js.map +2 -2
  6. package/dist-cjs/lib/canvas/TldrawScribble.js +1 -1
  7. package/dist-cjs/lib/canvas/TldrawScribble.js.map +2 -2
  8. package/dist-cjs/lib/defaultExternalContentHandlers.js +5 -4
  9. package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
  10. package/dist-cjs/lib/shapes/arrow/elbow/ElbowArrowDebug.js +3 -3
  11. package/dist-cjs/lib/shapes/arrow/elbow/ElbowArrowDebug.js.map +1 -1
  12. package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js +1 -1
  13. package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js.map +1 -1
  14. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +7 -4
  15. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  16. package/dist-cjs/lib/shapes/frame/components/FrameHeading.js +1 -1
  17. package/dist-cjs/lib/shapes/frame/components/FrameHeading.js.map +2 -2
  18. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +6 -3
  19. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js.map +2 -2
  20. package/dist-cjs/lib/shapes/shared/ShapeFill.js +1 -1
  21. package/dist-cjs/lib/shapes/shared/ShapeFill.js.map +2 -2
  22. package/dist-cjs/lib/shapes/shared/freehand/svg.js.map +2 -2
  23. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js +3 -3
  24. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js.map +1 -1
  25. package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js +25 -1
  26. package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js.map +2 -2
  27. package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js +12 -0
  28. package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js.map +2 -2
  29. package/dist-cjs/lib/ui/TldrawUi.js +13 -12
  30. package/dist-cjs/lib/ui/TldrawUi.js.map +2 -2
  31. package/dist-cjs/lib/ui/assetUrls.js +13 -10
  32. package/dist-cjs/lib/ui/assetUrls.js.map +2 -2
  33. package/dist-cjs/lib/ui/components/{FollowingIndicator.js → DefaultFollowingIndicator.js} +6 -6
  34. package/dist-cjs/lib/ui/components/DefaultFollowingIndicator.js.map +7 -0
  35. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +6 -6
  36. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +1 -1
  37. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js +4 -4
  38. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js.map +2 -2
  39. package/dist-cjs/lib/ui/components/MobileStylePanel.js +1 -1
  40. package/dist-cjs/lib/ui/components/MobileStylePanel.js.map +2 -2
  41. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js +1 -1
  42. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js.map +2 -2
  43. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js +1 -1
  44. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js.map +2 -2
  45. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +6 -4
  46. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
  47. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +94 -123
  48. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  49. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuContext.js.map +2 -2
  50. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js +0 -10
  51. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js.map +2 -2
  52. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +3 -19
  53. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  54. package/dist-cjs/lib/ui/context/actions.js +16 -2
  55. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  56. package/dist-cjs/lib/ui/context/components.js +2 -0
  57. package/dist-cjs/lib/ui/context/components.js.map +2 -2
  58. package/dist-cjs/lib/ui/hooks/useTools.js +21 -3
  59. package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
  60. package/dist-cjs/lib/ui/kbd-utils.js +9 -3
  61. package/dist-cjs/lib/ui/kbd-utils.js.map +2 -2
  62. package/dist-cjs/lib/ui/version.js +3 -3
  63. package/dist-cjs/lib/ui/version.js.map +1 -1
  64. package/dist-esm/index.d.mts +93 -1
  65. package/dist-esm/index.mjs +11 -2
  66. package/dist-esm/index.mjs.map +2 -2
  67. package/dist-esm/lib/Tldraw.mjs +14 -4
  68. package/dist-esm/lib/Tldraw.mjs.map +2 -2
  69. package/dist-esm/lib/canvas/TldrawScribble.mjs +1 -1
  70. package/dist-esm/lib/canvas/TldrawScribble.mjs.map +2 -2
  71. package/dist-esm/lib/defaultExternalContentHandlers.mjs +5 -4
  72. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  73. package/dist-esm/lib/shapes/arrow/elbow/ElbowArrowDebug.mjs +3 -3
  74. package/dist-esm/lib/shapes/arrow/elbow/ElbowArrowDebug.mjs.map +1 -1
  75. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs +1 -1
  76. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs.map +1 -1
  77. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +7 -4
  78. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  79. package/dist-esm/lib/shapes/frame/components/FrameHeading.mjs +1 -1
  80. package/dist-esm/lib/shapes/frame/components/FrameHeading.mjs.map +2 -2
  81. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +6 -3
  82. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
  83. package/dist-esm/lib/shapes/shared/ShapeFill.mjs +1 -1
  84. package/dist-esm/lib/shapes/shared/ShapeFill.mjs.map +2 -2
  85. package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
  86. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs +3 -3
  87. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs.map +1 -1
  88. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs +26 -1
  89. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs.map +2 -2
  90. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs +13 -0
  91. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs.map +2 -2
  92. package/dist-esm/lib/ui/TldrawUi.mjs +13 -12
  93. package/dist-esm/lib/ui/TldrawUi.mjs.map +2 -2
  94. package/dist-esm/lib/ui/assetUrls.mjs +13 -10
  95. package/dist-esm/lib/ui/assetUrls.mjs.map +2 -2
  96. package/dist-esm/lib/ui/components/{FollowingIndicator.mjs → DefaultFollowingIndicator.mjs} +3 -3
  97. package/dist-esm/lib/ui/components/DefaultFollowingIndicator.mjs.map +7 -0
  98. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +6 -6
  99. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +1 -1
  100. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs +4 -4
  101. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs.map +2 -2
  102. package/dist-esm/lib/ui/components/MobileStylePanel.mjs +1 -1
  103. package/dist-esm/lib/ui/components/MobileStylePanel.mjs.map +2 -2
  104. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs +1 -1
  105. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +2 -2
  106. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs +1 -1
  107. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs.map +2 -2
  108. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +6 -4
  109. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
  110. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +103 -125
  111. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  112. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuContext.mjs.map +2 -2
  113. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs +0 -10
  114. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs.map +2 -2
  115. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +3 -19
  116. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  117. package/dist-esm/lib/ui/context/actions.mjs +16 -2
  118. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  119. package/dist-esm/lib/ui/context/components.mjs +2 -0
  120. package/dist-esm/lib/ui/context/components.mjs.map +2 -2
  121. package/dist-esm/lib/ui/hooks/useTools.mjs +22 -3
  122. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  123. package/dist-esm/lib/ui/kbd-utils.mjs +9 -3
  124. package/dist-esm/lib/ui/kbd-utils.mjs.map +2 -2
  125. package/dist-esm/lib/ui/version.mjs +3 -3
  126. package/dist-esm/lib/ui/version.mjs.map +1 -1
  127. package/package.json +11 -34
  128. package/src/index.ts +6 -1
  129. package/src/lib/Tldraw.tsx +15 -2
  130. package/src/lib/canvas/TldrawScribble.tsx +1 -1
  131. package/src/lib/defaultExternalContentHandlers.ts +12 -4
  132. package/src/lib/shapes/arrow/ArrowShapeOptions.test.ts +2 -1
  133. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +4 -3
  134. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +7 -6
  135. package/src/lib/shapes/arrow/elbow/ElbowArrowDebug.tsx +3 -3
  136. package/src/lib/shapes/draw/DrawShapeTool.test.ts +0 -5
  137. package/src/lib/shapes/embed/EmbedShapeUtil.tsx +1 -1
  138. package/src/lib/shapes/frame/FrameShapeUtil.tsx +16 -4
  139. package/src/lib/shapes/frame/components/FrameHeading.tsx +1 -1
  140. package/src/lib/shapes/image/ImageShapeUtil.tsx +6 -3
  141. package/src/lib/shapes/line/LineShapeUtil.test.tsx +4 -3
  142. package/src/lib/shapes/line/__snapshots__/LineShapeUtil.test.tsx.snap +2 -2
  143. package/src/lib/shapes/shared/ShapeFill.tsx +1 -1
  144. package/src/lib/shapes/shared/freehand/svg.ts +2 -0
  145. package/src/lib/shapes/text/TextShapeTool.test.ts +6 -5
  146. package/src/lib/shapes/video/VideoShapeUtil.tsx +3 -3
  147. package/src/lib/tools/EraserTool/childStates/Erasing.ts +34 -1
  148. package/src/lib/tools/EraserTool/childStates/Pointing.ts +20 -0
  149. package/src/lib/ui/TldrawUi.tsx +16 -10
  150. package/src/lib/ui/assetUrls.ts +13 -10
  151. package/src/lib/ui/components/{FollowingIndicator.tsx → DefaultFollowingIndicator.tsx} +2 -1
  152. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +6 -6
  153. package/src/lib/ui/components/Minimap/MinimapManager.ts +4 -4
  154. package/src/lib/ui/components/MobileStylePanel.tsx +1 -1
  155. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +1 -1
  156. package/src/lib/ui/components/Toolbar/DefaultImageToolbarContent.tsx +1 -1
  157. package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +35 -30
  158. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +109 -127
  159. package/src/lib/ui/components/primitives/menus/TldrawUiMenuContext.tsx +0 -1
  160. package/src/lib/ui/components/primitives/menus/TldrawUiMenuGroup.tsx +0 -10
  161. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +5 -18
  162. package/src/lib/ui/context/actions.tsx +16 -2
  163. package/src/lib/ui/context/components.tsx +3 -0
  164. package/src/lib/ui/hooks/useTools.tsx +25 -3
  165. package/src/lib/ui/kbd-utils.ts +10 -3
  166. package/src/lib/ui/version.ts +3 -3
  167. package/src/lib/ui.css +230 -228
  168. package/src/lib/utils/excalidraw/__snapshots__/putExcalidrawContent.test.tsx.snap +5 -5
  169. package/src/lib/utils/tldr/__snapshots__/buildFromV1Document.test.ts.snap +4 -4
  170. package/src/test/A11y.test.tsx +3 -2
  171. package/src/test/ClickManager.test.ts +7 -6
  172. package/src/test/Editor.test.tsx +20 -19
  173. package/src/test/EraserTool.test.ts +184 -13
  174. package/src/test/HandTool.test.ts +10 -9
  175. package/src/test/HighlightShape.test.ts +2 -1
  176. package/src/test/SelectTool.test.ts +3 -2
  177. package/src/test/TLUserPreferences.test.ts +4 -3
  178. package/src/test/TestEditor.ts +13 -15
  179. package/src/test/TldrawEditor.test.tsx +11 -10
  180. package/src/test/ZoomTool.test.ts +7 -6
  181. package/src/test/__snapshots__/drawing.test.ts.snap +2 -2
  182. package/src/test/__snapshots__/groups.test.tsx.snap +6 -6
  183. package/src/test/__snapshots__/resizing.test.ts.snap +2 -2
  184. package/src/test/arrows-megabus.test.tsx +5 -4
  185. package/src/test/bindings.test.tsx +24 -37
  186. package/src/test/bookmark-shapes.test.ts +1 -8
  187. package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +23 -7
  188. package/src/test/commands/__snapshots__/packShapes.test.ts.snap +8 -8
  189. package/src/test/commands/__snapshots__/zoomToFit.test.ts.snap +2 -2
  190. package/src/test/commands/alignShapes.test.tsx +25 -24
  191. package/src/test/commands/animationSpeed.test.ts +2 -1
  192. package/src/test/commands/centerOnPoint.test.ts +3 -2
  193. package/src/test/commands/clipboard.test.ts +3 -2
  194. package/src/test/commands/createShapes.test.ts +2 -1
  195. package/src/test/commands/deleteShapes.test.ts +2 -1
  196. package/src/test/commands/distributeShapes.test.tsx +11 -10
  197. package/src/test/commands/getSvgString.test.ts +2 -1
  198. package/src/test/commands/packShapes.test.ts +5 -4
  199. package/src/test/commands/resizeShape.test.ts +2 -1
  200. package/src/test/commands/rotateShapes.test.ts +7 -6
  201. package/src/test/commands/setCamera.test.ts +4 -3
  202. package/src/test/commands/setCurrentPage.test.ts +3 -2
  203. package/src/test/commands/stackShapes.test.ts +11 -10
  204. package/src/test/commands/stretch.test.tsx +13 -12
  205. package/src/test/createDeepLink.test.tsx +2 -1
  206. package/src/test/cropping.test.ts +3 -2
  207. package/src/test/drawing.test.ts +2 -1
  208. package/src/test/flipShapes.test.ts +4 -3
  209. package/src/test/frames.test.ts +25 -24
  210. package/src/test/getCulledShapes.test.tsx +3 -2
  211. package/src/test/groups.test.tsx +1 -1
  212. package/src/test/handleDeepLink.test.tsx +2 -1
  213. package/src/test/maxShapes.test.ts +3 -2
  214. package/src/test/modifiers.test.ts +5 -4
  215. package/src/test/navigation.test.ts +12 -11
  216. package/src/test/panning.test.ts +2 -1
  217. package/src/test/perf/perf.test.ts +2 -1
  218. package/src/test/registerDeepLinkListener.test.tsx +10 -9
  219. package/src/test/resizing.test.ts +39 -38
  220. package/src/test/select.test.tsx +4 -3
  221. package/src/test/selection-omnibus.test.ts +11 -10
  222. package/src/test/shapeutils.test.ts +4 -3
  223. package/src/test/translating.test.ts +9 -8
  224. package/tldraw.css +531 -518
  225. package/dist-cjs/lib/ui/components/FollowingIndicator.js.map +0 -7
  226. package/dist-esm/lib/ui/components/FollowingIndicator.mjs.map +0 -7
@@ -1,7 +1,14 @@
1
- import { assert, Editor, uniqueId, useMaybeEditor, Vec } from '@tldraw/editor'
1
+ import { assert, Atom, atom, Editor, uniqueId, useMaybeEditor, useValue } from '@tldraw/editor'
2
2
  import { Tooltip as _Tooltip } from 'radix-ui'
3
- import React, { createContext, forwardRef, useContext, useEffect, useRef, useState } from 'react'
4
- import { usePrefersReducedMotion } from '../../../shapes/shared/usePrefersReducedMotion'
3
+ import React, {
4
+ createContext,
5
+ forwardRef,
6
+ ReactNode,
7
+ useContext,
8
+ useEffect,
9
+ useRef,
10
+ useState,
11
+ } from 'react'
5
12
  import { useTldrawUiOrientation } from './layout'
6
13
 
7
14
  const DEFAULT_TOOLTIP_DELAY_MS = 700
@@ -13,19 +20,23 @@ export interface TldrawUiTooltipProps {
13
20
  side?: 'top' | 'right' | 'bottom' | 'left'
14
21
  sideOffset?: number
15
22
  disabled?: boolean
23
+ showOnMobile?: boolean
24
+ delayDuration?: number
16
25
  }
17
26
 
18
27
  // Singleton tooltip manager
19
28
  class TooltipManager {
20
29
  private static instance: TooltipManager | null = null
21
- private currentTooltipId: string | null = null
22
- private currentContent: string | React.ReactNode = ''
23
- private currentSide: 'top' | 'right' | 'bottom' | 'left' = 'bottom'
24
- private currentSideOffset: number = 5
30
+ private currentTooltip = atom<{
31
+ id: string
32
+ content: ReactNode
33
+ side: 'top' | 'right' | 'bottom' | 'left'
34
+ sideOffset: number
35
+ showOnMobile: boolean
36
+ targetElement: HTMLElement
37
+ delayDuration: number
38
+ } | null>('current tooltip', null)
25
39
  private destroyTimeoutId: number | null = null
26
- private subscribers: Set<() => void> = new Set()
27
- private activeElement: HTMLElement | null = null
28
- private editor: Editor | null = null
29
40
 
30
41
  static getInstance(): TooltipManager {
31
42
  if (!TooltipManager.instance) {
@@ -34,25 +45,14 @@ class TooltipManager {
34
45
  return TooltipManager.instance
35
46
  }
36
47
 
37
- setEditor(editor: Editor | null) {
38
- this.editor = editor
39
- }
40
-
41
- subscribe(callback: () => void): () => void {
42
- this.subscribers.add(callback)
43
- return () => this.subscribers.delete(callback)
44
- }
45
-
46
- private notify() {
47
- this.subscribers.forEach((callback) => callback())
48
- }
49
-
50
48
  showTooltip(
51
49
  tooltipId: string,
52
50
  content: string | React.ReactNode,
53
- element: HTMLElement,
54
- side: 'top' | 'right' | 'bottom' | 'left' = 'bottom',
55
- sideOffset: number = 5
51
+ targetElement: HTMLElement,
52
+ side: 'top' | 'right' | 'bottom' | 'left',
53
+ sideOffset: number,
54
+ showOnMobile: boolean,
55
+ delayDuration: number
56
56
  ) {
57
57
  // Clear any existing destroy timeout
58
58
  if (this.destroyTimeoutId) {
@@ -61,51 +61,57 @@ class TooltipManager {
61
61
  }
62
62
 
63
63
  // Update current tooltip
64
- this.currentTooltipId = tooltipId
65
- this.currentContent = content
66
- this.currentSide = side
67
- this.currentSideOffset = sideOffset
68
- this.activeElement = element
69
-
70
- this.notify()
64
+ this.currentTooltip.set({
65
+ id: tooltipId,
66
+ content,
67
+ side,
68
+ sideOffset,
69
+ showOnMobile,
70
+ targetElement,
71
+ delayDuration,
72
+ })
71
73
  }
72
74
 
73
- hideTooltip(tooltipId: string, instant: boolean = false) {
75
+ hideTooltip(editor: Editor | null, tooltipId: string, instant: boolean = false) {
74
76
  const hide = () => {
75
77
  // Only hide if this is the current tooltip
76
- if (this.currentTooltipId === tooltipId) {
77
- this.currentTooltipId = null
78
- this.currentContent = ''
79
- this.activeElement = null
78
+ if (this.currentTooltip.get()?.id === tooltipId) {
79
+ this.currentTooltip.set(null)
80
80
  this.destroyTimeoutId = null
81
- this.notify()
82
81
  }
83
82
  }
84
83
 
85
- if (instant) {
86
- hide()
87
- } else if (this.editor) {
84
+ if (editor && !instant) {
88
85
  // Start destroy timeout (1 second)
89
- this.destroyTimeoutId = this.editor.timers.setTimeout(hide, 300)
86
+ this.destroyTimeoutId = editor.timers.setTimeout(hide, 300)
87
+ } else {
88
+ hide()
90
89
  }
91
90
  }
92
91
 
93
92
  hideAllTooltips() {
94
- this.currentTooltipId = null
95
- this.currentContent = ''
96
- this.activeElement = null
93
+ this.currentTooltip.set(null)
97
94
  this.destroyTimeoutId = null
98
- this.notify()
99
95
  }
100
96
 
101
97
  getCurrentTooltipData() {
102
- return {
103
- id: this.currentTooltipId,
104
- content: this.currentContent,
105
- side: this.currentSide,
106
- sideOffset: this.currentSideOffset,
107
- element: this.activeElement,
98
+ const currentTooltip = this.currentTooltip.get()
99
+ if (!currentTooltip) return null
100
+ if (!this.supportsHover() && !currentTooltip.showOnMobile) return null
101
+ return currentTooltip
102
+ }
103
+
104
+ private supportsHoverAtom: Atom<boolean> | null = null
105
+ supportsHover() {
106
+ if (!this.supportsHoverAtom) {
107
+ const mediaQuery = window.matchMedia('(hover: hover)')
108
+ const supportsHover = atom('has hover', mediaQuery.matches)
109
+ this.supportsHoverAtom = supportsHover
110
+ mediaQuery.addEventListener('change', (e) => {
111
+ supportsHover.set(e.matches)
112
+ })
108
113
  }
114
+ return this.supportsHoverAtom.get()
109
115
  }
110
116
  }
111
117
 
@@ -133,66 +139,24 @@ export function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderPro
133
139
 
134
140
  // The singleton tooltip component that renders once
135
141
  function TooltipSingleton() {
136
- const editor = useMaybeEditor()
137
- const [, forceUpdate] = useState({})
138
142
  const [isOpen, setIsOpen] = useState(false)
139
143
  const triggerRef = useRef<HTMLDivElement>(null)
140
- const previousPositionRef = useRef<{ x: number; y: number } | null>(null)
141
- const prefersReducedMotion = usePrefersReducedMotion()
142
- const [shouldAnimate, setShouldAnimate] = useState(false)
143
144
  const isFirstShowRef = useRef(true)
144
- const showTimeoutRef = useRef<number | null>(null)
145
-
146
- // Set editor in tooltip manager
147
- useEffect(() => {
148
- tooltipManager.setEditor(editor)
149
- }, [editor])
150
-
151
- // Subscribe to tooltip manager updates
152
- useEffect(() => {
153
- const unsubscribe = tooltipManager.subscribe(() => {
154
- forceUpdate({})
155
- })
156
- return unsubscribe
157
- }, [])
158
145
 
159
- const tooltipData = tooltipManager.getCurrentTooltipData()
146
+ const currentTooltip = useValue(
147
+ 'current tooltip',
148
+ () => tooltipManager.getCurrentTooltipData(),
149
+ []
150
+ )
160
151
 
161
152
  // Update open state and trigger position
162
153
  useEffect(() => {
163
- const shouldBeOpen = Boolean(tooltipData.id && tooltipData.element)
164
-
165
- // Clear any existing show timeout
166
- if (showTimeoutRef.current) {
167
- clearTimeout(showTimeoutRef.current)
168
- showTimeoutRef.current = null
169
- }
170
-
171
- if (shouldBeOpen && tooltipData.element && triggerRef.current) {
154
+ let timer: ReturnType<typeof setTimeout> | null = null
155
+ if (currentTooltip && triggerRef.current) {
172
156
  // Position the invisible trigger element over the active element
173
- const activeRect = tooltipData.element.getBoundingClientRect()
157
+ const activeRect = currentTooltip.targetElement.getBoundingClientRect()
174
158
  const trigger = triggerRef.current
175
159
 
176
- const newPosition = {
177
- x: activeRect.left + activeRect.width / 2,
178
- y: activeRect.top + activeRect.height / 2,
179
- }
180
-
181
- // Determine if we should animate
182
- let shouldAnimateCheck = false
183
- if (previousPositionRef.current) {
184
- const isNearPrevious = Vec.DistMin(previousPositionRef.current, newPosition, 200)
185
- // Only animate if the distance is less than 200px (nearby tooltips)
186
- shouldAnimateCheck =
187
- !prefersReducedMotion &&
188
- isNearPrevious &&
189
- Math.abs(newPosition.y - previousPositionRef.current.y) < 50
190
- }
191
- // Don't animate on initial show (previousPositionRef.current is null)
192
-
193
- setShouldAnimate(isFirstShowRef.current ? false : shouldAnimateCheck)
194
- previousPositionRef.current = newPosition
195
-
196
160
  trigger.style.position = 'fixed'
197
161
  trigger.style.left = `${activeRect.left}px`
198
162
  trigger.style.top = `${activeRect.top}px`
@@ -202,27 +166,31 @@ function TooltipSingleton() {
202
166
  trigger.style.zIndex = '9999'
203
167
 
204
168
  // Handle delay for first show
205
- if (isFirstShowRef.current && editor) {
206
- showTimeoutRef.current = editor.timers.setTimeout(() => {
169
+ if (isFirstShowRef.current) {
170
+ // eslint-disable-next-line no-restricted-globals
171
+ timer = setTimeout(() => {
207
172
  setIsOpen(true)
208
173
  isFirstShowRef.current = false
209
- }, editor.options.tooltipDelayMs)
174
+ }, currentTooltip.delayDuration)
210
175
  } else {
211
176
  // Subsequent tooltips show immediately
212
177
  setIsOpen(true)
213
178
  }
214
- } else if (!shouldBeOpen) {
179
+ } else {
215
180
  // Hide tooltip immediately
216
181
  setIsOpen(false)
217
- // Reset position tracking when tooltip closes
218
- previousPositionRef.current = null
219
- setShouldAnimate(false)
220
182
  // Reset first show state after tooltip is hidden
221
183
  isFirstShowRef.current = true
222
184
  }
223
- }, [tooltipData.id, tooltipData.element, editor, prefersReducedMotion])
224
185
 
225
- if (!tooltipData.id) {
186
+ return () => {
187
+ if (timer !== null) {
188
+ clearTimeout(timer)
189
+ }
190
+ }
191
+ }, [currentTooltip])
192
+
193
+ if (!currentTooltip) {
226
194
  return null
227
195
  }
228
196
 
@@ -233,14 +201,13 @@ function TooltipSingleton() {
233
201
  </_Tooltip.Trigger>
234
202
  <_Tooltip.Content
235
203
  className="tlui-tooltip"
236
- data-should-animate={shouldAnimate}
237
- side={tooltipData.side}
238
- sideOffset={tooltipData.sideOffset}
204
+ side={currentTooltip.side}
205
+ sideOffset={currentTooltip.sideOffset}
239
206
  avoidCollisions
240
207
  collisionPadding={8}
241
208
  dir="ltr"
242
209
  >
243
- {tooltipData.content}
210
+ {currentTooltip.content}
244
211
  <_Tooltip.Arrow className="tlui-tooltip__arrow" />
245
212
  </_Tooltip.Content>
246
213
  </_Tooltip.Root>
@@ -249,7 +216,18 @@ function TooltipSingleton() {
249
216
 
250
217
  /** @public @react */
251
218
  export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(
252
- ({ children, content, side, sideOffset = 5, disabled = false }, ref) => {
219
+ (
220
+ {
221
+ children,
222
+ content,
223
+ side,
224
+ sideOffset = 5,
225
+ disabled = false,
226
+ showOnMobile = false,
227
+ delayDuration,
228
+ },
229
+ ref
230
+ ) => {
253
231
  const editor = useMaybeEditor()
254
232
  const tooltipId = useRef<string>(uniqueId())
255
233
  const hasProvider = useContext(TooltipSingletonContext)
@@ -261,23 +239,23 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
261
239
  const currentTooltipId = tooltipId.current
262
240
  return () => {
263
241
  if (hasProvider) {
264
- tooltipManager.hideTooltip(currentTooltipId, true)
242
+ tooltipManager.hideTooltip(editor, currentTooltipId, true)
265
243
  }
266
244
  }
267
- }, [hasProvider])
245
+ }, [editor, hasProvider])
268
246
 
269
247
  // Don't show tooltip if disabled, no content, or UI labels are disabled
270
248
  if (disabled || !content) {
271
249
  return <>{children}</>
272
250
  }
273
251
 
252
+ const delayDurationToUse =
253
+ delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
254
+
274
255
  // Fallback to old behavior if no provider
275
256
  if (!hasProvider) {
276
257
  return (
277
- <_Tooltip.Root
278
- delayDuration={editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS}
279
- disableHoverableContent
280
- >
258
+ <_Tooltip.Root delayDuration={delayDurationToUse} disableHoverableContent>
281
259
  <_Tooltip.Trigger asChild ref={ref}>
282
260
  {children}
283
261
  </_Tooltip.Trigger>
@@ -306,13 +284,15 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
306
284
  content,
307
285
  event.currentTarget as HTMLElement,
308
286
  sideToUse,
309
- sideOffset
287
+ sideOffset,
288
+ showOnMobile,
289
+ delayDurationToUse
310
290
  )
311
291
  }
312
292
 
313
293
  const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
314
294
  child.props.onMouseLeave?.(event)
315
- tooltipManager.hideTooltip(tooltipId.current)
295
+ tooltipManager.hideTooltip(editor, tooltipId.current)
316
296
  }
317
297
 
318
298
  const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
@@ -322,13 +302,15 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
322
302
  content,
323
303
  event.currentTarget as HTMLElement,
324
304
  sideToUse,
325
- sideOffset
305
+ sideOffset,
306
+ showOnMobile,
307
+ delayDurationToUse
326
308
  )
327
309
  }
328
310
 
329
311
  const handleBlur = (event: React.FocusEvent<HTMLElement>) => {
330
312
  child.props.onBlur?.(event)
331
- tooltipManager.hideTooltip(tooltipId.current)
313
+ tooltipManager.hideTooltip(editor, tooltipId.current)
332
314
  }
333
315
 
334
316
  const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
@@ -3,7 +3,6 @@ import { TLUiEventSource } from '../../../context/events'
3
3
 
4
4
  /** @public */
5
5
  export type TLUiMenuContextType =
6
- | 'panel'
7
6
  | 'menu'
8
7
  | 'small-icons'
9
8
  | 'context-menu'
@@ -27,16 +27,6 @@ export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGr
27
27
  const labelStr = labelToUse ? msg(labelToUse as TLUiTranslationKey) : undefined
28
28
 
29
29
  switch (menu.type) {
30
- case 'panel': {
31
- return (
32
- <div
33
- className={classNames('tlui-menu__group', className)}
34
- data-testid={`${menu.sourceId}-group.${id}`}
35
- >
36
- {children}
37
- </div>
38
- )
39
- }
40
30
  case 'menu': {
41
31
  return (
42
32
  <TldrawUiDropdownMenuGroup
@@ -120,7 +120,6 @@ export function TldrawUiMenuItem<
120
120
  type="menu"
121
121
  data-testid={`${sourceId}.${id}`}
122
122
  disabled={disabled}
123
- title={titleStr}
124
123
  onClick={(e) => {
125
124
  if (noClose) {
126
125
  preventDefault(e)
@@ -146,7 +145,6 @@ export function TldrawUiMenuItem<
146
145
  return (
147
146
  <_ContextMenu.Item
148
147
  dir="ltr"
149
- title={titleStr}
150
148
  draggable={false}
151
149
  className="tlui-button tlui-button__menu"
152
150
  data-testid={`${sourceId}.${id}`}
@@ -168,20 +166,6 @@ export function TldrawUiMenuItem<
168
166
  </_ContextMenu.Item>
169
167
  )
170
168
  }
171
- case 'panel': {
172
- return (
173
- <TldrawUiButton
174
- data-testid={`${sourceId}.${id}`}
175
- type="menu"
176
- title={titleStr}
177
- disabled={disabled}
178
- onClick={() => onSelect(sourceId)}
179
- >
180
- <TldrawUiButtonLabel>{labelStr}</TldrawUiButtonLabel>
181
- {spinner ? <Spinner /> : icon && <TldrawUiButtonIcon icon={icon} />}
182
- </TldrawUiButton>
183
- )
184
- }
185
169
  case 'small-icons':
186
170
  case 'icons': {
187
171
  return (
@@ -332,8 +316,8 @@ function useDraggableEvents(
332
316
  if (
333
317
  distanceSq >
334
318
  (editor.getInstanceState().isCoarsePointer
335
- ? editor.options.coarseDragDistanceSquared
336
- : editor.options.dragDistanceSquared)
319
+ ? editor.options.uiCoarseDragDistanceSquared
320
+ : editor.options.uiDragDistanceSquared)
337
321
  ) {
338
322
  const screenSpaceStart = state.screenSpaceStart
339
323
  state = {
@@ -342,6 +326,8 @@ function useDraggableEvents(
342
326
  }
343
327
 
344
328
  editor.run(() => {
329
+ editor.setCurrentTool('select')
330
+
345
331
  // Set origin point
346
332
  editor.dispatch({
347
333
  type: 'pointer',
@@ -364,6 +350,7 @@ function useDraggableEvents(
364
350
  })
365
351
 
366
352
  tooltipManager.hideAllTooltips()
353
+ editor.getContainer().focus()
367
354
  })
368
355
  }
369
356
  }
@@ -36,6 +36,7 @@ import { useTranslation } from '../hooks/useTranslation/useTranslation'
36
36
  import { TLUiIconType } from '../icon-types'
37
37
  import { TLUiOverrideHelpers, useDefaultHelpers } from '../overrides'
38
38
  import { useA11y } from './a11y'
39
+ import { useTldrawUiComponents } from './components'
39
40
  import { TLUiEventSource, useUiEvents } from './events'
40
41
 
41
42
  /** @public */
@@ -98,6 +99,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
98
99
  const _editor = useMaybeEditor()
99
100
  const showCollaborationUi = useShowCollaborationUi()
100
101
  const helpers = useDefaultHelpers()
102
+ const components = useTldrawUiComponents()
101
103
  const trackEvent = useUiEvents()
102
104
  const a11y = useA11y()
103
105
  const msg = useTranslation()
@@ -176,7 +178,9 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
176
178
  kbd: 'cmd+alt+/,ctrl+alt+/',
177
179
  onSelect(source) {
178
180
  trackEvent('open-kbd-shortcuts', { source })
179
- helpers.addDialog({ component: DefaultKeyboardShortcutsDialog })
181
+ helpers.addDialog({
182
+ component: components.KeyboardShortcutsDialog ?? DefaultKeyboardShortcutsDialog,
183
+ })
180
184
  },
181
185
  },
182
186
  {
@@ -1755,7 +1759,17 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1755
1759
  }
1756
1760
 
1757
1761
  return actions
1758
- }, [helpers, _editor, trackEvent, overrides, defaultDocumentName, showCollaborationUi, msg, a11y])
1762
+ }, [
1763
+ helpers,
1764
+ _editor,
1765
+ trackEvent,
1766
+ overrides,
1767
+ defaultDocumentName,
1768
+ showCollaborationUi,
1769
+ msg,
1770
+ a11y,
1771
+ components,
1772
+ ])
1759
1773
 
1760
1774
  return <ActionsContext.Provider value={asActions(actions)}>{children}</ActionsContext.Provider>
1761
1775
  }
@@ -12,6 +12,7 @@ import {
12
12
  import { CursorChatBubble } from '../components/CursorChatBubble'
13
13
  import { DefaultDebugMenu } from '../components/DebugMenu/DefaultDebugMenu'
14
14
  import { DefaultDebugPanel } from '../components/DefaultDebugPanel'
15
+ import { DefaultFollowingIndicator } from '../components/DefaultFollowingIndicator'
15
16
  import { DefaultMenuPanel } from '../components/DefaultMenuPanel'
16
17
  import { DefaultDialogs } from '../components/Dialogs'
17
18
  import { TLUiHelpMenuProps } from '../components/HelpMenu/DefaultHelpMenu'
@@ -72,6 +73,7 @@ export interface TLUiComponents {
72
73
  Dialogs?: ComponentType | null
73
74
  Toasts?: ComponentType | null
74
75
  A11y?: ComponentType | null
76
+ FollowingIndicator?: ComponentType | null
75
77
  }
76
78
 
77
79
  const TldrawUiComponentsContext = createContext<TLUiComponents | null>(null)
@@ -119,6 +121,7 @@ export function TldrawUiComponentsProvider({
119
121
  Dialogs: DefaultDialogs,
120
122
  Toasts: DefaultToasts,
121
123
  A11y: DefaultA11yAnnouncer,
124
+ FollowingIndicator: DefaultFollowingIndicator,
122
125
  ..._overrides,
123
126
  }),
124
127
  [_overrides, showCollaborationUi]
@@ -3,6 +3,8 @@ import {
3
3
  createShapeId,
4
4
  Editor,
5
5
  GeoShapeGeoStyle,
6
+ getIndicesBetween,
7
+ TLLineShape,
6
8
  TLPointerEventInfo,
7
9
  TLShapeId,
8
10
  toRichText,
@@ -153,7 +155,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
153
155
  },
154
156
  onDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {
155
157
  onDragFromToolbarToCreateShape(editor, info, {
156
- createShape: (id) => editor.createShape({ id, type: 'geo', props: { geo } }),
158
+ createShape: (id) =>
159
+ editor.createShape({ id, type: 'geo', props: { w: 200, h: 200, geo } }),
157
160
  })
158
161
  trackEvent('drag-tool', { source, id: 'geo' })
159
162
  },
@@ -188,6 +191,24 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
188
191
  editor.setCurrentTool('line')
189
192
  onToolSelect(source, this)
190
193
  },
194
+ onDragStart(source, info) {
195
+ onDragFromToolbarToCreateShape(editor, info, {
196
+ createShape: (id) => {
197
+ const [start, end] = getIndicesBetween(null, null, 2)
198
+ editor.createShape<TLLineShape>({
199
+ id,
200
+ type: 'line',
201
+ props: {
202
+ points: {
203
+ [start]: { id: start, index: start, x: 0, y: 200 },
204
+ [end]: { id: end, index: end, x: 200, y: 0 },
205
+ },
206
+ },
207
+ })
208
+ },
209
+ })
210
+ trackEvent('drag-tool', { source, id: 'line' })
211
+ },
191
212
  },
192
213
  {
193
214
  id: 'frame',
@@ -219,8 +240,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
219
240
  createShape: (id) =>
220
241
  editor.createShape({ id, type: 'text', props: { richText: toRichText('Text') } }),
221
242
  onDragEnd: (id) => {
222
- editor.emit('select-all-text', { shapeId: id })
223
243
  editor.setEditingShape(id)
244
+ editor.emit('select-all-text', { shapeId: id })
224
245
  },
225
246
  })
226
247
  trackEvent('drag-tool', { source, id: 'text' })
@@ -249,8 +270,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
249
270
  onDragFromToolbarToCreateShape(editor, info, {
250
271
  createShape: (id) => editor.createShape({ id, type: 'note' }),
251
272
  onDragEnd: (id) => {
252
- editor.emit('select-all-text', { shapeId: id })
253
273
  editor.setEditingShape(id)
274
+ editor.emit('select-all-text', { shapeId: id })
254
275
  },
255
276
  })
256
277
  trackEvent('drag-tool', { source, id: 'note' })
@@ -365,5 +386,6 @@ export function onDragFromToolbarToCreateShape(
365
386
  opts.onDragEnd?.(id)
366
387
  },
367
388
  })
389
+
368
390
  editor.getCurrentTool().setCurrentToolIdMask(shape.type)
369
391
  }
@@ -31,9 +31,16 @@ export function kbd(str: string) {
31
31
  )
32
32
  .flat()
33
33
  .map((sub, index) => {
34
- if (sub === '__CTRL__') return 'Ctrl'
35
- if (sub === '__ALT__') return 'Alt'
36
- const modifiedKey = sub[0].toUpperCase() + sub.slice(1)
34
+ if (sub[0] === '+') return []
35
+
36
+ let modifiedKey
37
+ if (sub === '__CTRL__') {
38
+ modifiedKey = 'Ctrl'
39
+ } else if (sub === '__ALT__') {
40
+ modifiedKey = 'Alt'
41
+ } else {
42
+ modifiedKey = sub[0].toUpperCase() + sub.slice(1)
43
+ }
37
44
  return tlenv.isDarwin || !index ? modifiedKey : ['+', modifiedKey]
38
45
  })
39
46
  .flat()
@@ -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 = '3.16.0-canary.cf24aedcd577'
4
+ export const version = '3.16.0-canary.d3a23ebd1b0b'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-08-11T15:30:09.531Z',
8
- patch: '2025-08-11T15:30:09.531Z',
7
+ minor: '2025-08-26T15:17:10.156Z',
8
+ patch: '2025-08-26T15:17:10.156Z',
9
9
  }