tldraw 3.16.0-canary.fa3749606e52 → 3.16.0-next.34fddf633325

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 (105) hide show
  1. package/dist-cjs/index.d.ts +32 -34
  2. package/dist-cjs/index.js +7 -2
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js +1 -1
  5. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js.map +2 -2
  6. package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js +10 -1
  7. package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js.map +2 -2
  8. package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
  9. package/dist-cjs/lib/ui/components/AccessibilityMenu.js +35 -0
  10. package/dist-cjs/lib/ui/components/AccessibilityMenu.js.map +7 -0
  11. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js +3 -3
  12. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js.map +2 -2
  13. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +2 -0
  14. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js.map +2 -2
  15. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +168 -137
  16. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
  17. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +3 -3
  18. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +2 -2
  19. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +3 -2
  20. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js.map +2 -2
  21. package/dist-cjs/lib/ui/components/menu-items.js +6 -0
  22. package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
  23. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +11 -3
  24. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
  25. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +267 -0
  26. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +7 -0
  27. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +1 -149
  28. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  29. package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js +3 -2
  30. package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js.map +2 -2
  31. package/dist-cjs/lib/ui/context/actions.js +15 -0
  32. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  33. package/dist-cjs/lib/ui/context/events.js.map +1 -1
  34. package/dist-cjs/lib/ui/hooks/useTools.js +9 -76
  35. package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
  36. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  37. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +3 -0
  38. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  39. package/dist-cjs/lib/ui/version.js +3 -3
  40. package/dist-cjs/lib/ui/version.js.map +1 -1
  41. package/dist-esm/index.d.mts +32 -34
  42. package/dist-esm/index.mjs +11 -3
  43. package/dist-esm/index.mjs.map +2 -2
  44. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs +1 -1
  45. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs.map +2 -2
  46. package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs +10 -1
  47. package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs.map +2 -2
  48. package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
  49. package/dist-esm/lib/ui/components/AccessibilityMenu.mjs +19 -0
  50. package/dist-esm/lib/ui/components/AccessibilityMenu.mjs.map +7 -0
  51. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs +3 -5
  52. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs.map +2 -2
  53. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs +3 -1
  54. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs.map +2 -2
  55. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +168 -137
  56. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
  57. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +3 -3
  58. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +2 -2
  59. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs +3 -2
  60. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs.map +2 -2
  61. package/dist-esm/lib/ui/components/menu-items.mjs +6 -0
  62. package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
  63. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +11 -3
  64. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
  65. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +237 -0
  66. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +7 -0
  67. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +3 -157
  68. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  69. package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs +3 -2
  70. package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs.map +2 -2
  71. package/dist-esm/lib/ui/context/actions.mjs +15 -0
  72. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  73. package/dist-esm/lib/ui/context/events.mjs.map +1 -1
  74. package/dist-esm/lib/ui/hooks/useTools.mjs +10 -83
  75. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  76. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +3 -0
  77. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  78. package/dist-esm/lib/ui/version.mjs +3 -3
  79. package/dist-esm/lib/ui/version.mjs.map +1 -1
  80. package/package.json +3 -3
  81. package/src/index.ts +8 -2
  82. package/src/lib/shapes/arrow/arrowTargetState.ts +2 -1
  83. package/src/lib/shapes/shared/usePrefersReducedMotion.tsx +11 -1
  84. package/src/lib/tools/SelectTool/childStates/Translating.ts +1 -0
  85. package/src/lib/ui/components/AccessibilityMenu.tsx +20 -0
  86. package/src/lib/ui/components/MainMenu/DefaultMainMenuContent.tsx +4 -4
  87. package/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx +3 -1
  88. package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +171 -128
  89. package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +3 -3
  90. package/src/lib/ui/components/Toolbar/ToggleToolLockedButton.tsx +14 -11
  91. package/src/lib/ui/components/menu-items.tsx +8 -0
  92. package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +19 -3
  93. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +313 -0
  94. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +2 -213
  95. package/src/lib/ui/context/TldrawUiContextProvider.tsx +23 -20
  96. package/src/lib/ui/context/actions.tsx +15 -0
  97. package/src/lib/ui/context/events.tsx +1 -1
  98. package/src/lib/ui/hooks/useTools.tsx +10 -118
  99. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +3 -0
  100. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +3 -0
  101. package/src/lib/ui/version.ts +3 -3
  102. package/src/lib/ui.css +57 -1
  103. package/src/test/arrows-megabus.test.tsx +12 -6
  104. package/src/test/inner-outer-margin.test.ts +315 -0
  105. package/tldraw.css +59 -1
@@ -0,0 +1,313 @@
1
+ import { 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) {
73
+ // Only hide if this is the current tooltip
74
+ if (this.currentTooltipId === tooltipId) {
75
+ // Start destroy timeout (1 second)
76
+ if (this.editor) {
77
+ this.destroyTimeoutId = this.editor.timers.setTimeout(() => {
78
+ this.currentTooltipId = null
79
+ this.currentContent = ''
80
+ this.activeElement = null
81
+ this.destroyTimeoutId = null
82
+ this.notify()
83
+ }, 300)
84
+ }
85
+ }
86
+ }
87
+
88
+ getCurrentTooltipData() {
89
+ return {
90
+ id: this.currentTooltipId,
91
+ content: this.currentContent,
92
+ side: this.currentSide,
93
+ sideOffset: this.currentSideOffset,
94
+ element: this.activeElement,
95
+ }
96
+ }
97
+ }
98
+
99
+ const tooltipManager = TooltipManager.getInstance()
100
+
101
+ // Context for the tooltip singleton
102
+ const TooltipSingletonContext = createContext<boolean>(false)
103
+
104
+ /** @public */
105
+ export interface TldrawUiTooltipProviderProps {
106
+ children: React.ReactNode
107
+ }
108
+
109
+ /** @public @react */
110
+ export function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderProps) {
111
+ return (
112
+ <_Tooltip.Provider skipDelayDuration={700}>
113
+ <TooltipSingletonContext.Provider value={true}>
114
+ {children}
115
+ <TooltipSingleton />
116
+ </TooltipSingletonContext.Provider>
117
+ </_Tooltip.Provider>
118
+ )
119
+ }
120
+
121
+ // The singleton tooltip component that renders once
122
+ function TooltipSingleton() {
123
+ const editor = useMaybeEditor()
124
+ const [, forceUpdate] = useState({})
125
+ const [isOpen, setIsOpen] = useState(false)
126
+ const triggerRef = useRef<HTMLDivElement>(null)
127
+ const previousPositionRef = useRef<{ x: number; y: number } | null>(null)
128
+ const prefersReducedMotion = usePrefersReducedMotion()
129
+ const [shouldAnimate, setShouldAnimate] = useState(false)
130
+ const isFirstShowRef = useRef(true)
131
+ const showTimeoutRef = useRef<number | null>(null)
132
+
133
+ // Set editor in tooltip manager
134
+ useEffect(() => {
135
+ tooltipManager.setEditor(editor)
136
+ }, [editor])
137
+
138
+ // Subscribe to tooltip manager updates
139
+ useEffect(() => {
140
+ const unsubscribe = tooltipManager.subscribe(() => {
141
+ forceUpdate({})
142
+ })
143
+ return unsubscribe
144
+ }, [])
145
+
146
+ const tooltipData = tooltipManager.getCurrentTooltipData()
147
+
148
+ // Update open state and trigger position
149
+ useEffect(() => {
150
+ const shouldBeOpen = Boolean(tooltipData.id && tooltipData.element)
151
+
152
+ // Clear any existing show timeout
153
+ if (showTimeoutRef.current) {
154
+ clearTimeout(showTimeoutRef.current)
155
+ showTimeoutRef.current = null
156
+ }
157
+
158
+ if (shouldBeOpen && tooltipData.element && triggerRef.current) {
159
+ // Position the invisible trigger element over the active element
160
+ const activeRect = tooltipData.element.getBoundingClientRect()
161
+ const trigger = triggerRef.current
162
+
163
+ const newPosition = {
164
+ x: activeRect.left + activeRect.width / 2,
165
+ y: activeRect.top + activeRect.height / 2,
166
+ }
167
+
168
+ // Determine if we should animate
169
+ let shouldAnimateCheck = false
170
+ if (previousPositionRef.current) {
171
+ const isNearPrevious = Vec.DistMin(previousPositionRef.current, newPosition, 200)
172
+ // Only animate if the distance is less than 200px (nearby tooltips)
173
+ shouldAnimateCheck =
174
+ !prefersReducedMotion &&
175
+ isNearPrevious &&
176
+ Math.abs(newPosition.y - previousPositionRef.current.y) < 50
177
+ }
178
+ // Don't animate on initial show (previousPositionRef.current is null)
179
+
180
+ setShouldAnimate(isFirstShowRef.current ? false : shouldAnimateCheck)
181
+ previousPositionRef.current = newPosition
182
+
183
+ trigger.style.position = 'fixed'
184
+ trigger.style.left = `${activeRect.left}px`
185
+ trigger.style.top = `${activeRect.top}px`
186
+ trigger.style.width = `${activeRect.width}px`
187
+ trigger.style.height = `${activeRect.height}px`
188
+ trigger.style.pointerEvents = 'none'
189
+ trigger.style.zIndex = '9999'
190
+
191
+ // Handle delay for first show
192
+ if (isFirstShowRef.current && editor) {
193
+ showTimeoutRef.current = editor.timers.setTimeout(() => {
194
+ setIsOpen(true)
195
+ isFirstShowRef.current = false
196
+ }, editor.options.tooltipDelayMs)
197
+ } else {
198
+ // Subsequent tooltips show immediately
199
+ setIsOpen(true)
200
+ }
201
+ } else if (!shouldBeOpen) {
202
+ // Hide tooltip immediately
203
+ setIsOpen(false)
204
+ // Reset position tracking when tooltip closes
205
+ previousPositionRef.current = null
206
+ setShouldAnimate(false)
207
+ // Reset first show state after tooltip is hidden
208
+ isFirstShowRef.current = true
209
+ }
210
+ }, [tooltipData.id, tooltipData.element, editor, prefersReducedMotion])
211
+
212
+ if (!tooltipData.id) {
213
+ return null
214
+ }
215
+
216
+ return (
217
+ <_Tooltip.Root open={isOpen} delayDuration={0}>
218
+ <_Tooltip.Trigger asChild>
219
+ <div ref={triggerRef} />
220
+ </_Tooltip.Trigger>
221
+ <_Tooltip.Content
222
+ className="tlui-tooltip"
223
+ data-should-animate={shouldAnimate}
224
+ side={tooltipData.side}
225
+ sideOffset={tooltipData.sideOffset}
226
+ avoidCollisions
227
+ collisionPadding={8}
228
+ dir="ltr"
229
+ >
230
+ {tooltipData.content}
231
+ <_Tooltip.Arrow className="tlui-tooltip__arrow" />
232
+ </_Tooltip.Content>
233
+ </_Tooltip.Root>
234
+ )
235
+ }
236
+
237
+ /** @public @react */
238
+ export function TldrawUiTooltip({
239
+ children,
240
+ content,
241
+ side = 'bottom',
242
+ sideOffset = 5,
243
+ disabled = false,
244
+ }: TldrawUiTooltipProps) {
245
+ const editor = useMaybeEditor()
246
+ const tooltipId = useRef<string>(uniqueId())
247
+ const hasProvider = useContext(TooltipSingletonContext)
248
+
249
+ // Don't show tooltip if disabled, no content, or UI labels are disabled
250
+ if (disabled || !content) {
251
+ return <>{children}</>
252
+ }
253
+
254
+ // Fallback to old behavior if no provider
255
+ if (!hasProvider) {
256
+ return (
257
+ <_Tooltip.Root
258
+ delayDuration={editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS}
259
+ disableHoverableContent
260
+ >
261
+ <_Tooltip.Trigger asChild>{children}</_Tooltip.Trigger>
262
+ <_Tooltip.Content
263
+ className="tlui-tooltip"
264
+ side={side}
265
+ sideOffset={sideOffset}
266
+ avoidCollisions
267
+ collisionPadding={8}
268
+ dir="ltr"
269
+ >
270
+ {content}
271
+ <_Tooltip.Arrow className="tlui-tooltip__arrow" />
272
+ </_Tooltip.Content>
273
+ </_Tooltip.Root>
274
+ )
275
+ }
276
+
277
+ const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
278
+ tooltipManager.showTooltip(
279
+ tooltipId.current,
280
+ content,
281
+ event.currentTarget as HTMLElement,
282
+ side,
283
+ sideOffset
284
+ )
285
+ }
286
+
287
+ const handleMouseLeave = () => {
288
+ tooltipManager.hideTooltip(tooltipId.current)
289
+ }
290
+
291
+ const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
292
+ tooltipManager.showTooltip(
293
+ tooltipId.current,
294
+ content,
295
+ event.currentTarget as HTMLElement,
296
+ side,
297
+ sideOffset
298
+ )
299
+ }
300
+
301
+ const handleBlur = () => {
302
+ tooltipManager.hideTooltip(tooltipId.current)
303
+ }
304
+
305
+ const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
306
+ onMouseEnter: handleMouseEnter,
307
+ onMouseLeave: handleMouseLeave,
308
+ onFocus: handleFocus,
309
+ onBlur: handleBlur,
310
+ })
311
+
312
+ return childrenWithHandlers
313
+ }
@@ -1,17 +1,9 @@
1
- import {
2
- exhaustiveSwitchError,
3
- getPointerInfo,
4
- preventDefault,
5
- TLPointerEventInfo,
6
- useEditor,
7
- Vec,
8
- } from '@tldraw/editor'
1
+ import { exhaustiveSwitchError, preventDefault } from '@tldraw/editor'
9
2
  import { ContextMenu as _ContextMenu } from 'radix-ui'
10
- import { useMemo, useState } from 'react'
3
+ import { useState } from 'react'
11
4
  import { unwrapLabel } from '../../../context/actions'
12
5
  import { TLUiEventSource } from '../../../context/events'
13
6
  import { useReadonly } from '../../../hooks/useReadonly'
14
- import { TLUiToolItem } from '../../../hooks/useTools'
15
7
  import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
16
8
  import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
17
9
  import { kbdStr } from '../../../kbd-utils'
@@ -71,10 +63,6 @@ export interface TLUiMenuItemProps<
71
63
  * Whether the item is selected.
72
64
  */
73
65
  isSelected?: boolean
74
- /**
75
- * The function to call when the item is dragged. If this is provided, the item will be draggable.
76
- */
77
- onDragStart?(source: TLUiEventSource, info: TLPointerEventInfo): void
78
66
  }
79
67
 
80
68
  /** @public @react */
@@ -93,7 +81,6 @@ export function TldrawUiMenuItem<
93
81
  onSelect,
94
82
  noClose,
95
83
  isSelected,
96
- onDragStart,
97
84
  }: TLUiMenuItemProps<TranslationKey, IconType>) {
98
85
  const { type: menuType, sourceId } = useTldrawUiMenuContext()
99
86
 
@@ -220,20 +207,6 @@ export function TldrawUiMenuItem<
220
207
  )
221
208
  }
222
209
  case 'toolbar': {
223
- if (onDragStart) {
224
- return (
225
- <DraggableToolbarButton
226
- id={id}
227
- icon={icon}
228
- onSelect={onSelect}
229
- onDragStart={onDragStart}
230
- labelToUse={labelToUse}
231
- titleStr={titleStr}
232
- disabled={disabled}
233
- isSelected={isSelected}
234
- />
235
- )
236
- }
237
210
  return (
238
211
  <TldrawUiToolbarButton
239
212
  aria-label={labelStr}
@@ -254,21 +227,6 @@ export function TldrawUiMenuItem<
254
227
  )
255
228
  }
256
229
  case 'toolbar-overflow': {
257
- if (onDragStart) {
258
- return (
259
- <DraggableToolbarButton
260
- id={id}
261
- icon={icon}
262
- onSelect={onSelect}
263
- onDragStart={onDragStart}
264
- labelToUse={labelToUse}
265
- titleStr={titleStr}
266
- disabled={disabled}
267
- isSelected={isSelected}
268
- overflow
269
- />
270
- )
271
- }
272
230
  return (
273
231
  <TldrawUiToolbarButton
274
232
  aria-label={labelStr}
@@ -291,172 +249,3 @@ export function TldrawUiMenuItem<
291
249
  }
292
250
  }
293
251
  }
294
-
295
- function useDraggableEvents(
296
- onDragStart: TLUiToolItem['onDragStart'],
297
- onSelect: TLUiToolItem['onSelect']
298
- ) {
299
- const editor = useEditor()
300
- const events = useMemo(() => {
301
- let state = { name: 'idle' } as
302
- | {
303
- name: 'idle'
304
- }
305
- | {
306
- name: 'pointing'
307
- start: Vec
308
- }
309
- | {
310
- name: 'dragging'
311
- start: Vec
312
- }
313
- | {
314
- name: 'dragged'
315
- }
316
-
317
- function handlePointerDown(e: React.PointerEvent<HTMLButtonElement>) {
318
- state = {
319
- name: 'pointing',
320
- start: editor.inputs.currentPagePoint.clone(),
321
- }
322
-
323
- e.currentTarget.setPointerCapture(e.pointerId)
324
- }
325
-
326
- function handlePointerMove(e: React.PointerEvent<HTMLButtonElement>) {
327
- if ((e as any).isSpecialRedispatchedEvent) return
328
-
329
- if (state.name === 'pointing') {
330
- const distance = Vec.Dist2(state.start, editor.inputs.currentPagePoint)
331
- if (
332
- distance >
333
- (editor.getInstanceState().isCoarsePointer
334
- ? editor.options.coarseDragDistanceSquared
335
- : editor.options.dragDistanceSquared)
336
- ) {
337
- const start = state.start
338
- state = {
339
- name: 'dragging',
340
- start,
341
- }
342
-
343
- editor.run(() => {
344
- // Set origin point
345
- editor.dispatch({
346
- type: 'pointer',
347
- target: 'canvas',
348
- name: 'pointer_down',
349
- ...getPointerInfo(e),
350
- point: start,
351
- })
352
-
353
- // Pointer down potentially selects shapes, so we need to deselect them.
354
- editor.selectNone()
355
-
356
- // start drag
357
- onDragStart?.('toolbar', {
358
- type: 'pointer',
359
- target: 'canvas',
360
- name: 'pointer_move',
361
- ...getPointerInfo(e),
362
- })
363
- })
364
- }
365
- }
366
- }
367
-
368
- function handlePointerUp(e: React.PointerEvent<HTMLButtonElement>) {
369
- if ((e as any).isSpecialRedispatchedEvent) return
370
-
371
- e.currentTarget.releasePointerCapture(e.pointerId)
372
-
373
- editor.dispatch({
374
- type: 'pointer',
375
- target: 'canvas',
376
- name: 'pointer_up',
377
- ...getPointerInfo(e),
378
- })
379
- }
380
-
381
- function handleClick() {
382
- if (state.name === 'dragging' || state.name === 'dragged') {
383
- state = { name: 'idle' }
384
- return true
385
- }
386
-
387
- state = { name: 'idle' }
388
- onSelect?.('toolbar')
389
- }
390
-
391
- return {
392
- onPointerDown: handlePointerDown,
393
- onPointerMove: handlePointerMove,
394
- onPointerUp: handlePointerUp,
395
- onClick: handleClick,
396
- }
397
- }, [onDragStart, editor, onSelect])
398
-
399
- return events
400
- }
401
-
402
- function DraggableToolbarButton({
403
- id,
404
- labelToUse,
405
- titleStr,
406
- disabled,
407
- isSelected,
408
- icon,
409
- onSelect,
410
- onDragStart,
411
- overflow,
412
- }: {
413
- id: string
414
- disabled: boolean
415
- labelToUse?: string
416
- titleStr?: string
417
- isSelected?: boolean
418
- icon: TLUiMenuItemProps['icon']
419
- onSelect: TLUiMenuItemProps['onSelect']
420
- onDragStart: TLUiMenuItemProps['onDragStart']
421
- overflow?: boolean
422
- }) {
423
- const events = useDraggableEvents(onDragStart, onSelect)
424
-
425
- if (overflow) {
426
- return (
427
- <TldrawUiToolbarButton
428
- aria-label={labelToUse}
429
- aria-pressed={isSelected ? 'true' : 'false'}
430
- isActive={isSelected}
431
- className="tlui-button-grid__button"
432
- data-testid={`tools.more.${id}`}
433
- data-value={id}
434
- disabled={disabled}
435
- title={titleStr}
436
- type="icon"
437
- {...events}
438
- >
439
- <TldrawUiButtonIcon icon={icon!} />
440
- </TldrawUiToolbarButton>
441
- )
442
- }
443
-
444
- return (
445
- <TldrawUiToolbarButton
446
- aria-label={labelToUse}
447
- aria-pressed={isSelected ? 'true' : 'false'}
448
- data-testid={`tools.${id}`}
449
- data-value={id}
450
- disabled={disabled}
451
- onTouchStart={(e) => {
452
- preventDefault(e)
453
- onSelect('toolbar')
454
- }}
455
- title={titleStr}
456
- type="tool"
457
- {...events}
458
- >
459
- <TldrawUiButtonIcon icon={icon!} />
460
- </TldrawUiToolbarButton>
461
- )
462
- }
@@ -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
@@ -126,7 +127,6 @@ export interface TLUiEventMap {
126
127
  'open-context-menu': null
127
128
  'adjust-shape-styles': null
128
129
  'copy-link': null
129
- 'drag-tool': { id: string }
130
130
  'image-replace': null
131
131
  'video-replace': null
132
132
  'open-kbd-shortcuts': null