tldraw 3.16.0-canary.67f438ba104b → 3.16.0-canary.856874107ebd

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 (217) hide show
  1. package/dist-cjs/index.d.ts +57 -3
  2. package/dist-cjs/index.js +10 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js +3 -3
  5. package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js.map +2 -2
  6. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js +1 -1
  7. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js.map +2 -2
  8. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js +3 -3
  9. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js.map +2 -2
  10. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +12 -12
  11. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  12. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +2 -2
  13. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
  14. package/dist-cjs/lib/shapes/geo/components/GeoShapeBody.js +2 -1
  15. package/dist-cjs/lib/shapes/geo/components/GeoShapeBody.js.map +2 -2
  16. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js +5 -1
  17. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js.map +2 -2
  18. package/dist-cjs/lib/shapes/line/LineShapeUtil.js +5 -1
  19. package/dist-cjs/lib/shapes/line/LineShapeUtil.js.map +2 -2
  20. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +4 -4
  21. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  22. package/dist-cjs/lib/shapes/shared/ShapeFill.js +5 -5
  23. package/dist-cjs/lib/shapes/shared/ShapeFill.js.map +2 -2
  24. package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js +10 -1
  25. package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js.map +2 -2
  26. package/dist-cjs/lib/shapes/text/TextShapeUtil.js +2 -2
  27. package/dist-cjs/lib/shapes/text/TextShapeUtil.js.map +2 -2
  28. package/dist-cjs/lib/ui/components/AccessibilityMenu.js +35 -0
  29. package/dist-cjs/lib/ui/components/AccessibilityMenu.js.map +7 -0
  30. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js +2 -1
  31. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js.map +2 -2
  32. package/dist-cjs/lib/ui/components/DefaultMenuPanel.js +3 -2
  33. package/dist-cjs/lib/ui/components/DefaultMenuPanel.js.map +2 -2
  34. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js +3 -3
  35. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js.map +2 -2
  36. package/dist-cjs/lib/ui/components/MobileStylePanel.js +1 -1
  37. package/dist-cjs/lib/ui/components/MobileStylePanel.js.map +2 -2
  38. package/dist-cjs/lib/ui/components/NavigationPanel/DefaultNavigationPanel.js +1 -1
  39. package/dist-cjs/lib/ui/components/NavigationPanel/DefaultNavigationPanel.js.map +2 -2
  40. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js +2 -1
  41. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js.map +2 -2
  42. package/dist-cjs/lib/ui/components/SharePanel/PeopleMenuItem.js +3 -2
  43. package/dist-cjs/lib/ui/components/SharePanel/PeopleMenuItem.js.map +2 -2
  44. package/dist-cjs/lib/ui/components/SharePanel/UserPresenceColorPicker.js +2 -2
  45. package/dist-cjs/lib/ui/components/SharePanel/UserPresenceColorPicker.js.map +2 -2
  46. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +2 -0
  47. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js.map +2 -2
  48. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +171 -140
  49. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
  50. package/dist-cjs/lib/ui/components/StylePanel/DoubleDropdownPicker.js +3 -3
  51. package/dist-cjs/lib/ui/components/StylePanel/DoubleDropdownPicker.js.map +2 -2
  52. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js +26 -25
  53. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js.map +3 -3
  54. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js +6 -5
  55. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js.map +2 -2
  56. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +9 -10
  57. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +2 -2
  58. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +5 -4
  59. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js.map +2 -2
  60. package/dist-cjs/lib/ui/components/menu-items.js +6 -0
  61. package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
  62. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js +5 -16
  63. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js.map +3 -3
  64. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +1 -1
  65. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js.map +2 -2
  66. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js +3 -2
  67. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js.map +3 -3
  68. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +18 -7
  69. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
  70. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +284 -0
  71. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +7 -0
  72. package/dist-cjs/lib/ui/components/primitives/layout.js +51 -0
  73. package/dist-cjs/lib/ui/components/primitives/layout.js.map +7 -0
  74. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +10 -8
  75. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  76. package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js +3 -2
  77. package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js.map +2 -2
  78. package/dist-cjs/lib/ui/context/actions.js +15 -0
  79. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  80. package/dist-cjs/lib/ui/context/events.js.map +2 -2
  81. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  82. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +3 -0
  83. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  84. package/dist-cjs/lib/ui/version.js +3 -3
  85. package/dist-cjs/lib/ui/version.js.map +1 -1
  86. package/dist-esm/index.d.mts +57 -3
  87. package/dist-esm/index.mjs +17 -1
  88. package/dist-esm/index.mjs.map +2 -2
  89. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs +4 -3
  90. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs.map +2 -2
  91. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs +1 -1
  92. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs.map +2 -2
  93. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs +4 -3
  94. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs.map +2 -2
  95. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +13 -12
  96. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  97. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +3 -2
  98. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  99. package/dist-esm/lib/shapes/geo/components/GeoShapeBody.mjs +2 -1
  100. package/dist-esm/lib/shapes/geo/components/GeoShapeBody.mjs.map +2 -2
  101. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs +6 -1
  102. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs.map +2 -2
  103. package/dist-esm/lib/shapes/line/LineShapeUtil.mjs +6 -1
  104. package/dist-esm/lib/shapes/line/LineShapeUtil.mjs.map +2 -2
  105. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +5 -4
  106. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  107. package/dist-esm/lib/shapes/shared/ShapeFill.mjs +6 -5
  108. package/dist-esm/lib/shapes/shared/ShapeFill.mjs.map +2 -2
  109. package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs +10 -1
  110. package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs.map +2 -2
  111. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs +3 -2
  112. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs.map +2 -2
  113. package/dist-esm/lib/ui/components/AccessibilityMenu.mjs +19 -0
  114. package/dist-esm/lib/ui/components/AccessibilityMenu.mjs.map +7 -0
  115. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs +2 -1
  116. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs.map +2 -2
  117. package/dist-esm/lib/ui/components/DefaultMenuPanel.mjs +3 -2
  118. package/dist-esm/lib/ui/components/DefaultMenuPanel.mjs.map +2 -2
  119. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs +3 -5
  120. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs.map +2 -2
  121. package/dist-esm/lib/ui/components/MobileStylePanel.mjs +2 -1
  122. package/dist-esm/lib/ui/components/MobileStylePanel.mjs.map +2 -2
  123. package/dist-esm/lib/ui/components/NavigationPanel/DefaultNavigationPanel.mjs +1 -1
  124. package/dist-esm/lib/ui/components/NavigationPanel/DefaultNavigationPanel.mjs.map +2 -2
  125. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs +2 -1
  126. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs.map +2 -2
  127. package/dist-esm/lib/ui/components/SharePanel/PeopleMenuItem.mjs +3 -2
  128. package/dist-esm/lib/ui/components/SharePanel/PeopleMenuItem.mjs.map +2 -2
  129. package/dist-esm/lib/ui/components/SharePanel/UserPresenceColorPicker.mjs +2 -2
  130. package/dist-esm/lib/ui/components/SharePanel/UserPresenceColorPicker.mjs.map +2 -2
  131. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs +3 -1
  132. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs.map +2 -2
  133. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +171 -140
  134. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
  135. package/dist-esm/lib/ui/components/StylePanel/DoubleDropdownPicker.mjs +3 -3
  136. package/dist-esm/lib/ui/components/StylePanel/DoubleDropdownPicker.mjs.map +2 -2
  137. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs +26 -25
  138. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +2 -2
  139. package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs +6 -5
  140. package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs.map +2 -2
  141. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +9 -10
  142. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +2 -2
  143. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs +5 -4
  144. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs.map +2 -2
  145. package/dist-esm/lib/ui/components/menu-items.mjs +6 -0
  146. package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
  147. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs +6 -6
  148. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs.map +2 -2
  149. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs +1 -1
  150. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs.map +2 -2
  151. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs +3 -2
  152. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs.map +2 -2
  153. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +18 -7
  154. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
  155. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +254 -0
  156. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +7 -0
  157. package/dist-esm/lib/ui/components/primitives/layout.mjs +21 -0
  158. package/dist-esm/lib/ui/components/primitives/layout.mjs.map +7 -0
  159. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +10 -8
  160. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  161. package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs +3 -2
  162. package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs.map +2 -2
  163. package/dist-esm/lib/ui/context/actions.mjs +15 -0
  164. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  165. package/dist-esm/lib/ui/context/events.mjs.map +2 -2
  166. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +3 -0
  167. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  168. package/dist-esm/lib/ui/version.mjs +3 -3
  169. package/dist-esm/lib/ui/version.mjs.map +1 -1
  170. package/package.json +3 -3
  171. package/src/index.ts +13 -0
  172. package/src/lib/shapes/arrow/ArrowShapeUtil.tsx +4 -3
  173. package/src/lib/shapes/arrow/arrowTargetState.ts +2 -1
  174. package/src/lib/shapes/draw/DrawShapeUtil.tsx +4 -3
  175. package/src/lib/shapes/frame/FrameShapeUtil.tsx +13 -14
  176. package/src/lib/shapes/geo/GeoShapeUtil.tsx +3 -2
  177. package/src/lib/shapes/geo/components/GeoShapeBody.tsx +2 -2
  178. package/src/lib/shapes/highlight/HighlightShapeUtil.tsx +7 -1
  179. package/src/lib/shapes/line/LineShapeUtil.tsx +6 -1
  180. package/src/lib/shapes/note/NoteShapeUtil.tsx +9 -4
  181. package/src/lib/shapes/shared/ShapeFill.tsx +6 -5
  182. package/src/lib/shapes/shared/usePrefersReducedMotion.tsx +11 -1
  183. package/src/lib/shapes/text/TextShapeUtil.tsx +3 -2
  184. package/src/lib/ui/components/AccessibilityMenu.tsx +20 -0
  185. package/src/lib/ui/components/ActionsMenu/DefaultActionsMenu.tsx +2 -1
  186. package/src/lib/ui/components/DefaultMenuPanel.tsx +4 -3
  187. package/src/lib/ui/components/MainMenu/DefaultMainMenuContent.tsx +4 -4
  188. package/src/lib/ui/components/MobileStylePanel.tsx +5 -3
  189. package/src/lib/ui/components/NavigationPanel/DefaultNavigationPanel.tsx +1 -1
  190. package/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx +3 -2
  191. package/src/lib/ui/components/SharePanel/PeopleMenuItem.tsx +4 -3
  192. package/src/lib/ui/components/SharePanel/UserPresenceColorPicker.tsx +3 -3
  193. package/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx +3 -1
  194. package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +146 -107
  195. package/src/lib/ui/components/StylePanel/DoubleDropdownPicker.tsx +3 -3
  196. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +7 -6
  197. package/src/lib/ui/components/Toolbar/DefaultToolbar.tsx +7 -6
  198. package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +10 -11
  199. package/src/lib/ui/components/Toolbar/ToggleToolLockedButton.tsx +14 -11
  200. package/src/lib/ui/components/menu-items.tsx +8 -0
  201. package/src/lib/ui/components/primitives/TldrawUiButtonPicker.tsx +40 -37
  202. package/src/lib/ui/components/primitives/TldrawUiContextualToolbar.tsx +1 -1
  203. package/src/lib/ui/components/primitives/TldrawUiPopover.tsx +4 -2
  204. package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +34 -12
  205. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +332 -0
  206. package/src/lib/ui/components/primitives/layout.tsx +33 -0
  207. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +13 -9
  208. package/src/lib/ui/context/TldrawUiContextProvider.tsx +23 -20
  209. package/src/lib/ui/context/actions.tsx +15 -0
  210. package/src/lib/ui/context/events.tsx +1 -0
  211. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +3 -0
  212. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +3 -0
  213. package/src/lib/ui/version.ts +3 -3
  214. package/src/lib/ui.css +80 -69
  215. package/src/test/arrows-megabus.test.tsx +12 -6
  216. package/src/test/inner-outer-margin.test.ts +315 -0
  217. package/tldraw.css +82 -69
@@ -0,0 +1,332 @@
1
+ import { assert, Editor, uniqueId, useMaybeEditor, Vec } from '@tldraw/editor'
2
+ import { Tooltip as _Tooltip } from 'radix-ui'
3
+ import React, { createContext, useContext, useEffect, useRef, useState } from 'react'
4
+ import { usePrefersReducedMotion } from '../../../shapes/shared/usePrefersReducedMotion'
5
+
6
+ const DEFAULT_TOOLTIP_DELAY_MS = 700
7
+
8
+ /** @public */
9
+ export interface TldrawUiTooltipProps {
10
+ children: React.ReactNode
11
+ content?: string | React.ReactNode
12
+ side?: 'top' | 'right' | 'bottom' | 'left'
13
+ sideOffset?: number
14
+ disabled?: boolean
15
+ }
16
+
17
+ // Singleton tooltip manager
18
+ class TooltipManager {
19
+ private static instance: TooltipManager | null = null
20
+ private currentTooltipId: string | null = null
21
+ private currentContent: string | React.ReactNode = ''
22
+ private currentSide: 'top' | 'right' | 'bottom' | 'left' = 'bottom'
23
+ private currentSideOffset: number = 5
24
+ private destroyTimeoutId: number | null = null
25
+ private subscribers: Set<() => void> = new Set()
26
+ private activeElement: HTMLElement | null = null
27
+ private editor: Editor | null = null
28
+
29
+ static getInstance(): TooltipManager {
30
+ if (!TooltipManager.instance) {
31
+ TooltipManager.instance = new TooltipManager()
32
+ }
33
+ return TooltipManager.instance
34
+ }
35
+
36
+ setEditor(editor: Editor | null) {
37
+ this.editor = editor
38
+ }
39
+
40
+ subscribe(callback: () => void): () => void {
41
+ this.subscribers.add(callback)
42
+ return () => this.subscribers.delete(callback)
43
+ }
44
+
45
+ private notify() {
46
+ this.subscribers.forEach((callback) => callback())
47
+ }
48
+
49
+ showTooltip(
50
+ tooltipId: string,
51
+ content: string | React.ReactNode,
52
+ element: HTMLElement,
53
+ side: 'top' | 'right' | 'bottom' | 'left' = 'bottom',
54
+ sideOffset: number = 5
55
+ ) {
56
+ // Clear any existing destroy timeout
57
+ if (this.destroyTimeoutId) {
58
+ clearTimeout(this.destroyTimeoutId)
59
+ this.destroyTimeoutId = null
60
+ }
61
+
62
+ // Update current tooltip
63
+ this.currentTooltipId = tooltipId
64
+ this.currentContent = content
65
+ this.currentSide = side
66
+ this.currentSideOffset = sideOffset
67
+ this.activeElement = element
68
+
69
+ this.notify()
70
+ }
71
+
72
+ hideTooltip(tooltipId: string, instant: boolean = false) {
73
+ const hide = () => {
74
+ // Only hide if this is the current tooltip
75
+ if (this.currentTooltipId === tooltipId) {
76
+ this.currentTooltipId = null
77
+ this.currentContent = ''
78
+ this.activeElement = null
79
+ this.destroyTimeoutId = null
80
+ this.notify()
81
+ }
82
+ }
83
+
84
+ if (instant) {
85
+ hide()
86
+ } else if (this.editor) {
87
+ // Start destroy timeout (1 second)
88
+ this.destroyTimeoutId = this.editor.timers.setTimeout(hide, 300)
89
+ }
90
+ }
91
+
92
+ hideAllTooltips() {
93
+ this.currentTooltipId = null
94
+ this.currentContent = ''
95
+ this.activeElement = null
96
+ this.destroyTimeoutId = null
97
+ this.notify()
98
+ }
99
+
100
+ getCurrentTooltipData() {
101
+ return {
102
+ id: this.currentTooltipId,
103
+ content: this.currentContent,
104
+ side: this.currentSide,
105
+ sideOffset: this.currentSideOffset,
106
+ element: this.activeElement,
107
+ }
108
+ }
109
+ }
110
+
111
+ export const tooltipManager = TooltipManager.getInstance()
112
+
113
+ // Context for the tooltip singleton
114
+ const TooltipSingletonContext = createContext<boolean>(false)
115
+
116
+ /** @public */
117
+ export interface TldrawUiTooltipProviderProps {
118
+ children: React.ReactNode
119
+ }
120
+
121
+ /** @public @react */
122
+ export function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderProps) {
123
+ return (
124
+ <_Tooltip.Provider skipDelayDuration={700}>
125
+ <TooltipSingletonContext.Provider value={true}>
126
+ {children}
127
+ <TooltipSingleton />
128
+ </TooltipSingletonContext.Provider>
129
+ </_Tooltip.Provider>
130
+ )
131
+ }
132
+
133
+ // The singleton tooltip component that renders once
134
+ function TooltipSingleton() {
135
+ const editor = useMaybeEditor()
136
+ const [, forceUpdate] = useState({})
137
+ const [isOpen, setIsOpen] = useState(false)
138
+ const triggerRef = useRef<HTMLDivElement>(null)
139
+ const previousPositionRef = useRef<{ x: number; y: number } | null>(null)
140
+ const prefersReducedMotion = usePrefersReducedMotion()
141
+ const [shouldAnimate, setShouldAnimate] = useState(false)
142
+ const isFirstShowRef = useRef(true)
143
+ const showTimeoutRef = useRef<number | null>(null)
144
+
145
+ // Set editor in tooltip manager
146
+ useEffect(() => {
147
+ tooltipManager.setEditor(editor)
148
+ }, [editor])
149
+
150
+ // Subscribe to tooltip manager updates
151
+ useEffect(() => {
152
+ const unsubscribe = tooltipManager.subscribe(() => {
153
+ forceUpdate({})
154
+ })
155
+ return unsubscribe
156
+ }, [])
157
+
158
+ const tooltipData = tooltipManager.getCurrentTooltipData()
159
+
160
+ // Update open state and trigger position
161
+ useEffect(() => {
162
+ const shouldBeOpen = Boolean(tooltipData.id && tooltipData.element)
163
+
164
+ // Clear any existing show timeout
165
+ if (showTimeoutRef.current) {
166
+ clearTimeout(showTimeoutRef.current)
167
+ showTimeoutRef.current = null
168
+ }
169
+
170
+ if (shouldBeOpen && tooltipData.element && triggerRef.current) {
171
+ // Position the invisible trigger element over the active element
172
+ const activeRect = tooltipData.element.getBoundingClientRect()
173
+ const trigger = triggerRef.current
174
+
175
+ const newPosition = {
176
+ x: activeRect.left + activeRect.width / 2,
177
+ y: activeRect.top + activeRect.height / 2,
178
+ }
179
+
180
+ // Determine if we should animate
181
+ let shouldAnimateCheck = false
182
+ if (previousPositionRef.current) {
183
+ const isNearPrevious = Vec.DistMin(previousPositionRef.current, newPosition, 200)
184
+ // Only animate if the distance is less than 200px (nearby tooltips)
185
+ shouldAnimateCheck =
186
+ !prefersReducedMotion &&
187
+ isNearPrevious &&
188
+ Math.abs(newPosition.y - previousPositionRef.current.y) < 50
189
+ }
190
+ // Don't animate on initial show (previousPositionRef.current is null)
191
+
192
+ setShouldAnimate(isFirstShowRef.current ? false : shouldAnimateCheck)
193
+ previousPositionRef.current = newPosition
194
+
195
+ trigger.style.position = 'fixed'
196
+ trigger.style.left = `${activeRect.left}px`
197
+ trigger.style.top = `${activeRect.top}px`
198
+ trigger.style.width = `${activeRect.width}px`
199
+ trigger.style.height = `${activeRect.height}px`
200
+ trigger.style.pointerEvents = 'none'
201
+ trigger.style.zIndex = '9999'
202
+
203
+ // Handle delay for first show
204
+ if (isFirstShowRef.current && editor) {
205
+ showTimeoutRef.current = editor.timers.setTimeout(() => {
206
+ setIsOpen(true)
207
+ isFirstShowRef.current = false
208
+ }, editor.options.tooltipDelayMs)
209
+ } else {
210
+ // Subsequent tooltips show immediately
211
+ setIsOpen(true)
212
+ }
213
+ } else if (!shouldBeOpen) {
214
+ // Hide tooltip immediately
215
+ setIsOpen(false)
216
+ // Reset position tracking when tooltip closes
217
+ previousPositionRef.current = null
218
+ setShouldAnimate(false)
219
+ // Reset first show state after tooltip is hidden
220
+ isFirstShowRef.current = true
221
+ }
222
+ }, [tooltipData.id, tooltipData.element, editor, prefersReducedMotion])
223
+
224
+ if (!tooltipData.id) {
225
+ return null
226
+ }
227
+
228
+ return (
229
+ <_Tooltip.Root open={isOpen} delayDuration={0}>
230
+ <_Tooltip.Trigger asChild>
231
+ <div ref={triggerRef} />
232
+ </_Tooltip.Trigger>
233
+ <_Tooltip.Content
234
+ className="tlui-tooltip"
235
+ data-should-animate={shouldAnimate}
236
+ side={tooltipData.side}
237
+ sideOffset={tooltipData.sideOffset}
238
+ avoidCollisions
239
+ collisionPadding={8}
240
+ dir="ltr"
241
+ >
242
+ {tooltipData.content}
243
+ <_Tooltip.Arrow className="tlui-tooltip__arrow" />
244
+ </_Tooltip.Content>
245
+ </_Tooltip.Root>
246
+ )
247
+ }
248
+
249
+ /** @public @react */
250
+ export function TldrawUiTooltip({
251
+ children,
252
+ content,
253
+ side = 'bottom',
254
+ sideOffset = 5,
255
+ disabled = false,
256
+ }: TldrawUiTooltipProps) {
257
+ const editor = useMaybeEditor()
258
+ const tooltipId = useRef<string>(uniqueId())
259
+ const hasProvider = useContext(TooltipSingletonContext)
260
+
261
+ // Don't show tooltip if disabled, no content, or UI labels are disabled
262
+ if (disabled || !content) {
263
+ return <>{children}</>
264
+ }
265
+
266
+ // Fallback to old behavior if no provider
267
+ if (!hasProvider) {
268
+ return (
269
+ <_Tooltip.Root
270
+ delayDuration={editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS}
271
+ disableHoverableContent
272
+ >
273
+ <_Tooltip.Trigger asChild>{children}</_Tooltip.Trigger>
274
+ <_Tooltip.Content
275
+ className="tlui-tooltip"
276
+ side={side}
277
+ sideOffset={sideOffset}
278
+ avoidCollisions
279
+ collisionPadding={8}
280
+ dir="ltr"
281
+ >
282
+ {content}
283
+ <_Tooltip.Arrow className="tlui-tooltip__arrow" />
284
+ </_Tooltip.Content>
285
+ </_Tooltip.Root>
286
+ )
287
+ }
288
+
289
+ const child = React.Children.only(children)
290
+ assert(React.isValidElement(child), 'TldrawUiTooltip children must be a single element')
291
+
292
+ const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
293
+ child.props.onMouseEnter?.(event)
294
+ tooltipManager.showTooltip(
295
+ tooltipId.current,
296
+ content,
297
+ event.currentTarget as HTMLElement,
298
+ side,
299
+ sideOffset
300
+ )
301
+ }
302
+
303
+ const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
304
+ child.props.onMouseLeave?.(event)
305
+ tooltipManager.hideTooltip(tooltipId.current)
306
+ }
307
+
308
+ const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
309
+ child.props.onFocus?.(event)
310
+ tooltipManager.showTooltip(
311
+ tooltipId.current,
312
+ content,
313
+ event.currentTarget as HTMLElement,
314
+ side,
315
+ sideOffset
316
+ )
317
+ }
318
+
319
+ const handleBlur = (event: React.FocusEvent<HTMLElement>) => {
320
+ child.props.onBlur?.(event)
321
+ tooltipManager.hideTooltip(tooltipId.current)
322
+ }
323
+
324
+ const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
325
+ onMouseEnter: handleMouseEnter,
326
+ onMouseLeave: handleMouseLeave,
327
+ onFocus: handleFocus,
328
+ onBlur: handleBlur,
329
+ })
330
+
331
+ return childrenWithHandlers
332
+ }
@@ -0,0 +1,33 @@
1
+ import classNames from 'classnames'
2
+ import { Slot } from 'radix-ui'
3
+ import { HTMLAttributes, ReactNode, forwardRef } from 'react'
4
+
5
+ /** @public */
6
+ export interface TLUiLayoutProps extends HTMLAttributes<HTMLDivElement> {
7
+ children: ReactNode
8
+ asChild?: boolean
9
+ }
10
+
11
+ /**
12
+ * A row, usually of UI controls like buttons, select dropdown, checkboxes, etc.
13
+ *
14
+ * @public @react
15
+ */
16
+ export const TldrawUiRow = forwardRef<HTMLDivElement, TLUiLayoutProps>(
17
+ ({ asChild, className, ...props }, ref) => {
18
+ const Component = asChild ? Slot.Root : 'div'
19
+ return <Component ref={ref} className={classNames('tlui-row', className)} {...props} />
20
+ }
21
+ )
22
+
23
+ /**
24
+ * A tight grid 4 elements wide, usually of UI controls like buttons, select dropdown, checkboxes,
25
+ * etc.
26
+ *
27
+ * @public @react */
28
+ export const TldrawUiGrid = forwardRef<HTMLDivElement, TLUiLayoutProps>(
29
+ ({ asChild, className, ...props }, ref) => {
30
+ const Component = asChild ? Slot.Root : 'div'
31
+ return <Component ref={ref} className={classNames('tlui-grid', className)} {...props} />
32
+ }
33
+ )
@@ -5,6 +5,7 @@ import {
5
5
  TLPointerEventInfo,
6
6
  useEditor,
7
7
  Vec,
8
+ VecModel,
8
9
  } from '@tldraw/editor'
9
10
  import { ContextMenu as _ContextMenu } from 'radix-ui'
10
11
  import { useMemo, useState } from 'react'
@@ -23,6 +24,7 @@ import { TldrawUiDropdownMenuItem } from '../TldrawUiDropdownMenu'
23
24
  import { TLUiIconJsx } from '../TldrawUiIcon'
24
25
  import { TldrawUiKbd } from '../TldrawUiKbd'
25
26
  import { TldrawUiToolbarButton } from '../TldrawUiToolbar'
27
+ import { tooltipManager } from '../TldrawUiTooltip'
26
28
  import { useTldrawUiMenuContext } from './TldrawUiMenuContext'
27
29
 
28
30
  /** @public */
@@ -274,7 +276,6 @@ export function TldrawUiMenuItem<
274
276
  aria-label={labelStr}
275
277
  aria-pressed={isSelected ? 'true' : 'false'}
276
278
  isActive={isSelected}
277
- className="tlui-button-grid__button"
278
279
  data-testid={`tools.more.${id}`}
279
280
  data-value={id}
280
281
  disabled={disabled}
@@ -304,11 +305,11 @@ function useDraggableEvents(
304
305
  }
305
306
  | {
306
307
  name: 'pointing'
307
- start: Vec
308
+ screenSpaceStart: VecModel
308
309
  }
309
310
  | {
310
311
  name: 'dragging'
311
- start: Vec
312
+ screenSpaceStart: VecModel
312
313
  }
313
314
  | {
314
315
  name: 'dragged'
@@ -317,7 +318,7 @@ function useDraggableEvents(
317
318
  function handlePointerDown(e: React.PointerEvent<HTMLButtonElement>) {
318
319
  state = {
319
320
  name: 'pointing',
320
- start: editor.inputs.currentPagePoint.clone(),
321
+ screenSpaceStart: { x: e.clientX, y: e.clientY },
321
322
  }
322
323
 
323
324
  e.currentTarget.setPointerCapture(e.pointerId)
@@ -327,17 +328,17 @@ function useDraggableEvents(
327
328
  if ((e as any).isSpecialRedispatchedEvent) return
328
329
 
329
330
  if (state.name === 'pointing') {
330
- const distance = Vec.Dist2(state.start, editor.inputs.currentPagePoint)
331
+ const distanceSq = Vec.Dist2(state.screenSpaceStart, { x: e.clientX, y: e.clientY })
331
332
  if (
332
- distance >
333
+ distanceSq >
333
334
  (editor.getInstanceState().isCoarsePointer
334
335
  ? editor.options.coarseDragDistanceSquared
335
336
  : editor.options.dragDistanceSquared)
336
337
  ) {
337
- const start = state.start
338
+ const screenSpaceStart = state.screenSpaceStart
338
339
  state = {
339
340
  name: 'dragging',
340
- start,
341
+ screenSpaceStart,
341
342
  }
342
343
 
343
344
  editor.run(() => {
@@ -347,7 +348,7 @@ function useDraggableEvents(
347
348
  target: 'canvas',
348
349
  name: 'pointer_down',
349
350
  ...getPointerInfo(e),
350
- point: start,
351
+ point: screenSpaceStart,
351
352
  })
352
353
 
353
354
  // Pointer down potentially selects shapes, so we need to deselect them.
@@ -359,7 +360,10 @@ function useDraggableEvents(
359
360
  target: 'canvas',
360
361
  name: 'pointer_move',
361
362
  ...getPointerInfo(e),
363
+ point: screenSpaceStart,
362
364
  })
365
+
366
+ tooltipManager.hideAllTooltips()
363
367
  })
364
368
  }
365
369
  }
@@ -1,6 +1,7 @@
1
1
  import { RecursivePartial, defaultUserPreferences, track, useMaybeEditor } from '@tldraw/editor'
2
2
  import { ReactNode } from 'react'
3
3
  import { TLUiAssetUrls, useDefaultUiAssetUrlsWithOverrides } from '../assetUrls'
4
+ import { TldrawUiTooltipProvider } from '../components/primitives/TldrawUiTooltip'
4
5
  import { ToolsProvider } from '../hooks/useTools'
5
6
  import { TldrawUiTranslationProvider } from '../hooks/useTranslation/useTranslation'
6
7
  import {
@@ -72,26 +73,28 @@ export const TldrawUiContextProvider = track(function TldrawUiContextProvider({
72
73
  const editor = useMaybeEditor()
73
74
  return (
74
75
  <MimeTypeContext.Provider value={mediaMimeTypes}>
75
- <AssetUrlsProvider assetUrls={useDefaultUiAssetUrlsWithOverrides(assetUrls)}>
76
- <TldrawUiTranslationProvider
77
- overrides={useMergedTranslationOverrides(overrides)}
78
- locale={editor?.user.getLocale() ?? defaultUserPreferences.locale}
79
- >
80
- <TldrawUiEventsProvider onEvent={onUiEvent}>
81
- <TldrawUiToastsProvider>
82
- <TldrawUiDialogsProvider context={'tla'}>
83
- <TldrawUiA11yProvider>
84
- <BreakPointProvider forceMobile={forceMobile}>
85
- <TldrawUiComponentsProvider overrides={components}>
86
- <InternalProviders overrides={overrides}>{children}</InternalProviders>
87
- </TldrawUiComponentsProvider>
88
- </BreakPointProvider>
89
- </TldrawUiA11yProvider>
90
- </TldrawUiDialogsProvider>
91
- </TldrawUiToastsProvider>
92
- </TldrawUiEventsProvider>
93
- </TldrawUiTranslationProvider>
94
- </AssetUrlsProvider>
76
+ <TldrawUiTooltipProvider>
77
+ <AssetUrlsProvider assetUrls={useDefaultUiAssetUrlsWithOverrides(assetUrls)}>
78
+ <TldrawUiTranslationProvider
79
+ overrides={useMergedTranslationOverrides(overrides)}
80
+ locale={editor?.user.getLocale() ?? defaultUserPreferences.locale}
81
+ >
82
+ <TldrawUiEventsProvider onEvent={onUiEvent}>
83
+ <TldrawUiToastsProvider>
84
+ <TldrawUiDialogsProvider context={'tla'}>
85
+ <TldrawUiA11yProvider>
86
+ <BreakPointProvider forceMobile={forceMobile}>
87
+ <TldrawUiComponentsProvider overrides={components}>
88
+ <InternalProviders overrides={overrides}>{children}</InternalProviders>
89
+ </TldrawUiComponentsProvider>
90
+ </BreakPointProvider>
91
+ </TldrawUiA11yProvider>
92
+ </TldrawUiDialogsProvider>
93
+ </TldrawUiToastsProvider>
94
+ </TldrawUiEventsProvider>
95
+ </TldrawUiTranslationProvider>
96
+ </AssetUrlsProvider>
97
+ </TldrawUiTooltipProvider>
95
98
  </MimeTypeContext.Provider>
96
99
  )
97
100
  })
@@ -1266,6 +1266,21 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1266
1266
  },
1267
1267
  checkbox: true,
1268
1268
  },
1269
+ {
1270
+ id: 'toggle-ui-labels',
1271
+ label: {
1272
+ default: 'action.toggle-ui-labels',
1273
+ menu: 'action.toggle-ui-labels.menu',
1274
+ },
1275
+ readonlyOk: true,
1276
+ onSelect(source) {
1277
+ trackEvent('toggle-ui-labels', { source })
1278
+ editor.user.updateUserPreferences({
1279
+ showUiLabels: !editor.user.getShowUiLabels(),
1280
+ })
1281
+ },
1282
+ checkbox: true,
1283
+ },
1269
1284
  {
1270
1285
  id: 'toggle-edge-scrolling',
1271
1286
  label: {
@@ -108,6 +108,7 @@ export interface TLUiEventMap {
108
108
  'toggle-lock': null
109
109
  'toggle-reduce-motion': null
110
110
  'toggle-keyboard-shortcuts': null
111
+ 'toggle-ui-labels': null
111
112
  'toggle-edge-scrolling': null
112
113
  'color-scheme': { value: string }
113
114
  'exit-pen-mode': null
@@ -93,6 +93,8 @@ export type TLUiTranslationKey =
93
93
  | 'action.toggle-reduce-motion'
94
94
  | 'action.toggle-keyboard-shortcuts.menu'
95
95
  | 'action.toggle-keyboard-shortcuts'
96
+ | 'action.toggle-ui-labels.menu'
97
+ | 'action.toggle-ui-labels'
96
98
  | 'action.toggle-edge-scrolling.menu'
97
99
  | 'action.toggle-edge-scrolling'
98
100
  | 'action.toggle-debug-mode.menu'
@@ -298,6 +300,7 @@ export type TLUiTranslationKey =
298
300
  | 'a11y.open-keyboard-shortcuts'
299
301
  | 'menu.title'
300
302
  | 'menu.theme'
303
+ | 'menu.accessibility'
301
304
  | 'menu.copy-as'
302
305
  | 'menu.edit'
303
306
  | 'menu.export-as'
@@ -94,6 +94,8 @@ export const DEFAULT_TRANSLATION = {
94
94
  'action.toggle-reduce-motion': 'Toggle reduce motion',
95
95
  'action.toggle-keyboard-shortcuts.menu': 'Keyboard shortcuts',
96
96
  'action.toggle-keyboard-shortcuts': 'Toggle keyboard shortcuts',
97
+ 'action.toggle-ui-labels.menu': 'UI labels',
98
+ 'action.toggle-ui-labels': 'Toggle UI labels',
97
99
  'action.toggle-edge-scrolling.menu': 'Edge scrolling',
98
100
  'action.toggle-edge-scrolling': 'Toggle edge scrolling',
99
101
  'action.toggle-debug-mode.menu': 'Debug mode',
@@ -299,6 +301,7 @@ export const DEFAULT_TRANSLATION = {
299
301
  'a11y.open-keyboard-shortcuts': 'Open keyboard shortcuts',
300
302
  'menu.title': 'Menu',
301
303
  'menu.theme': 'Theme',
304
+ 'menu.accessibility': 'Accessibility',
302
305
  'menu.copy-as': 'Copy as',
303
306
  'menu.edit': 'Edit',
304
307
  'menu.export-as': 'Export as',
@@ -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.67f438ba104b'
4
+ export const version = '3.16.0-canary.856874107ebd'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-08-06T08:50:41.768Z',
8
- patch: '2025-08-06T08:50:41.768Z',
7
+ minor: '2025-08-11T11:27:08.471Z',
8
+ patch: '2025-08-11T11:27:08.471Z',
9
9
  }