tldraw 3.16.0-canary.fa3749606e52 → 3.16.0-canary.ffdf566dd0a8
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.
- package/dist-cjs/index.d.ts +32 -34
- package/dist-cjs/index.js +7 -2
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/arrowTargetState.js +1 -1
- package/dist-cjs/lib/shapes/arrow/arrowTargetState.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js +10 -1
- package/dist-cjs/lib/shapes/shared/usePrefersReducedMotion.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
- package/dist-cjs/lib/ui/components/AccessibilityMenu.js +35 -0
- package/dist-cjs/lib/ui/components/AccessibilityMenu.js.map +7 -0
- package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js +3 -3
- package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +2 -0
- package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js.map +2 -2
- package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +168 -137
- package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +3 -3
- package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +3 -2
- package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js.map +2 -2
- package/dist-cjs/lib/ui/components/menu-items.js +6 -0
- package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +11 -3
- package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +267 -0
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +7 -0
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +1 -149
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
- package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js +3 -2
- package/dist-cjs/lib/ui/context/TldrawUiContextProvider.js.map +2 -2
- package/dist-cjs/lib/ui/context/actions.js +15 -0
- package/dist-cjs/lib/ui/context/actions.js.map +2 -2
- package/dist-cjs/lib/ui/context/events.js.map +1 -1
- package/dist-cjs/lib/ui/hooks/useTools.js +9 -76
- package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
- package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +3 -0
- package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
- package/dist-cjs/lib/ui/version.js +3 -3
- package/dist-cjs/lib/ui/version.js.map +1 -1
- package/dist-esm/index.d.mts +32 -34
- package/dist-esm/index.mjs +11 -3
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs +1 -1
- package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs +10 -1
- package/dist-esm/lib/shapes/shared/usePrefersReducedMotion.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
- package/dist-esm/lib/ui/components/AccessibilityMenu.mjs +19 -0
- package/dist-esm/lib/ui/components/AccessibilityMenu.mjs.map +7 -0
- package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs +3 -5
- package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs +3 -1
- package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs.map +2 -2
- package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +168 -137
- package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +3 -3
- package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs +3 -2
- package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs.map +2 -2
- package/dist-esm/lib/ui/components/menu-items.mjs +6 -0
- package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +11 -3
- package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +237 -0
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +7 -0
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +3 -157
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
- package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs +3 -2
- package/dist-esm/lib/ui/context/TldrawUiContextProvider.mjs.map +2 -2
- package/dist-esm/lib/ui/context/actions.mjs +15 -0
- package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
- package/dist-esm/lib/ui/context/events.mjs.map +1 -1
- package/dist-esm/lib/ui/hooks/useTools.mjs +10 -83
- package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +3 -0
- package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
- package/dist-esm/lib/ui/version.mjs +3 -3
- package/dist-esm/lib/ui/version.mjs.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +8 -2
- package/src/lib/shapes/arrow/arrowTargetState.ts +2 -1
- package/src/lib/shapes/shared/usePrefersReducedMotion.tsx +11 -1
- package/src/lib/tools/SelectTool/childStates/Translating.ts +1 -0
- package/src/lib/ui/components/AccessibilityMenu.tsx +20 -0
- package/src/lib/ui/components/MainMenu/DefaultMainMenuContent.tsx +4 -4
- package/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx +3 -1
- package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +171 -128
- package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +3 -3
- package/src/lib/ui/components/Toolbar/ToggleToolLockedButton.tsx +14 -11
- package/src/lib/ui/components/menu-items.tsx +8 -0
- package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +19 -3
- package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +313 -0
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +2 -213
- package/src/lib/ui/context/TldrawUiContextProvider.tsx +23 -20
- package/src/lib/ui/context/actions.tsx +15 -0
- package/src/lib/ui/context/events.tsx +1 -1
- package/src/lib/ui/hooks/useTools.tsx +10 -118
- package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +3 -0
- package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +3 -0
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/ui.css +57 -1
- package/src/test/arrows-megabus.test.tsx +12 -6
- package/src/test/inner-outer-margin.test.ts +315 -0
- 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 {
|
|
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
|
-
<
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
<
|
|
83
|
-
<
|
|
84
|
-
<
|
|
85
|
-
<
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|