tldraw 3.16.0-canary.614a556981b7 → 3.16.0-canary.654b4007a087
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 +74 -1
- package/dist-cjs/index.js +5 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +4 -4
- package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/ShapeFill.js +1 -1
- package/dist-cjs/lib/shapes/shared/ShapeFill.js.map +2 -2
- package/dist-cjs/lib/shapes/shared/freehand/svg.js.map +2 -2
- package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js +25 -1
- package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js.map +2 -2
- package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js +12 -0
- package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +68 -91
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuContext.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js +0 -10
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +3 -19
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useTools.js +21 -3
- package/dist-cjs/lib/ui/hooks/useTools.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 +74 -1
- package/dist-esm/index.mjs +5 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +4 -4
- package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/ShapeFill.mjs +1 -1
- package/dist-esm/lib/shapes/shared/ShapeFill.mjs.map +2 -2
- package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
- package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs +26 -1
- package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs.map +2 -2
- package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs +13 -0
- package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +77 -93
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuContext.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs +0 -10
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +3 -19
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useTools.mjs +22 -3
- package/dist-esm/lib/ui/hooks/useTools.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 +9 -33
- package/src/index.ts +3 -0
- package/src/lib/shapes/arrow/ArrowShapeOptions.test.ts +2 -1
- package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +4 -3
- package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +7 -6
- package/src/lib/shapes/draw/DrawShapeTool.test.ts +0 -5
- package/src/lib/shapes/frame/FrameShapeUtil.tsx +12 -4
- package/src/lib/shapes/line/LineShapeUtil.test.tsx +4 -3
- package/src/lib/shapes/line/__snapshots__/LineShapeUtil.test.tsx.snap +2 -2
- package/src/lib/shapes/shared/ShapeFill.tsx +1 -1
- package/src/lib/shapes/shared/freehand/svg.ts +2 -0
- package/src/lib/shapes/text/TextShapeTool.test.ts +6 -5
- package/src/lib/tools/EraserTool/childStates/Erasing.ts +34 -1
- package/src/lib/tools/EraserTool/childStates/Pointing.ts +20 -0
- package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +98 -114
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuContext.tsx +0 -1
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuGroup.tsx +0 -10
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +5 -18
- package/src/lib/ui/hooks/useTools.tsx +25 -3
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/ui.css +5 -6
- package/src/lib/utils/excalidraw/__snapshots__/putExcalidrawContent.test.tsx.snap +5 -5
- package/src/lib/utils/tldr/__snapshots__/buildFromV1Document.test.ts.snap +4 -4
- package/src/test/A11y.test.tsx +3 -2
- package/src/test/ClickManager.test.ts +7 -6
- package/src/test/Editor.test.tsx +20 -19
- package/src/test/EraserTool.test.ts +184 -13
- package/src/test/HandTool.test.ts +10 -9
- package/src/test/HighlightShape.test.ts +2 -1
- package/src/test/SelectTool.test.ts +3 -2
- package/src/test/TLUserPreferences.test.ts +4 -3
- package/src/test/TestEditor.ts +13 -15
- package/src/test/TldrawEditor.test.tsx +11 -10
- package/src/test/ZoomTool.test.ts +7 -6
- package/src/test/__snapshots__/drawing.test.ts.snap +2 -2
- package/src/test/__snapshots__/groups.test.tsx.snap +6 -6
- package/src/test/__snapshots__/resizing.test.ts.snap +2 -2
- package/src/test/arrows-megabus.test.tsx +5 -4
- package/src/test/bindings.test.tsx +24 -37
- package/src/test/bookmark-shapes.test.ts +1 -8
- package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +23 -7
- package/src/test/commands/__snapshots__/packShapes.test.ts.snap +8 -8
- package/src/test/commands/__snapshots__/zoomToFit.test.ts.snap +2 -2
- package/src/test/commands/alignShapes.test.tsx +25 -24
- package/src/test/commands/animationSpeed.test.ts +2 -1
- package/src/test/commands/centerOnPoint.test.ts +3 -2
- package/src/test/commands/clipboard.test.ts +3 -2
- package/src/test/commands/createShapes.test.ts +2 -1
- package/src/test/commands/deleteShapes.test.ts +2 -1
- package/src/test/commands/distributeShapes.test.tsx +11 -10
- package/src/test/commands/getSvgString.test.ts +2 -1
- package/src/test/commands/packShapes.test.ts +5 -4
- package/src/test/commands/resizeShape.test.ts +2 -1
- package/src/test/commands/rotateShapes.test.ts +7 -6
- package/src/test/commands/setCamera.test.ts +4 -3
- package/src/test/commands/setCurrentPage.test.ts +3 -2
- package/src/test/commands/stackShapes.test.ts +11 -10
- package/src/test/commands/stretch.test.tsx +13 -12
- package/src/test/createDeepLink.test.tsx +2 -1
- package/src/test/cropping.test.ts +3 -2
- package/src/test/drawing.test.ts +2 -1
- package/src/test/flipShapes.test.ts +4 -3
- package/src/test/frames.test.ts +25 -24
- package/src/test/getCulledShapes.test.tsx +3 -2
- package/src/test/groups.test.tsx +1 -1
- package/src/test/handleDeepLink.test.tsx +2 -1
- package/src/test/maxShapes.test.ts +3 -2
- package/src/test/modifiers.test.ts +5 -4
- package/src/test/navigation.test.ts +12 -11
- package/src/test/panning.test.ts +2 -1
- package/src/test/perf/perf.test.ts +2 -1
- package/src/test/registerDeepLinkListener.test.tsx +10 -9
- package/src/test/resizing.test.ts +39 -38
- package/src/test/select.test.tsx +4 -3
- package/src/test/selection-omnibus.test.ts +11 -10
- package/src/test/shapeutils.test.ts +4 -3
- package/src/test/translating.test.ts +9 -8
- package/tldraw.css +5 -6
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { assert, Editor, uniqueId, useMaybeEditor,
|
|
1
|
+
import { assert, Atom, atom, Editor, uniqueId, useMaybeEditor, useValue } from '@tldraw/editor'
|
|
2
2
|
import { Tooltip as _Tooltip } from 'radix-ui'
|
|
3
|
-
import React, {
|
|
4
|
-
|
|
3
|
+
import React, {
|
|
4
|
+
createContext,
|
|
5
|
+
forwardRef,
|
|
6
|
+
ReactNode,
|
|
7
|
+
useContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from 'react'
|
|
5
12
|
import { useTldrawUiOrientation } from './layout'
|
|
6
13
|
|
|
7
14
|
const DEFAULT_TOOLTIP_DELAY_MS = 700
|
|
@@ -13,19 +20,23 @@ export interface TldrawUiTooltipProps {
|
|
|
13
20
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
14
21
|
sideOffset?: number
|
|
15
22
|
disabled?: boolean
|
|
23
|
+
showOnMobile?: boolean
|
|
24
|
+
delayDuration?: number
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
// Singleton tooltip manager
|
|
19
28
|
class TooltipManager {
|
|
20
29
|
private static instance: TooltipManager | null = null
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
private currentTooltip = atom<{
|
|
31
|
+
id: string
|
|
32
|
+
content: ReactNode
|
|
33
|
+
side: 'top' | 'right' | 'bottom' | 'left'
|
|
34
|
+
sideOffset: number
|
|
35
|
+
showOnMobile: boolean
|
|
36
|
+
targetElement: HTMLElement
|
|
37
|
+
delayDuration: number | undefined
|
|
38
|
+
} | null>('current tooltip', null)
|
|
25
39
|
private destroyTimeoutId: number | null = null
|
|
26
|
-
private subscribers: Set<() => void> = new Set()
|
|
27
|
-
private activeElement: HTMLElement | null = null
|
|
28
|
-
private editor: Editor | null = null
|
|
29
40
|
|
|
30
41
|
static getInstance(): TooltipManager {
|
|
31
42
|
if (!TooltipManager.instance) {
|
|
@@ -34,25 +45,14 @@ class TooltipManager {
|
|
|
34
45
|
return TooltipManager.instance
|
|
35
46
|
}
|
|
36
47
|
|
|
37
|
-
setEditor(editor: Editor | null) {
|
|
38
|
-
this.editor = editor
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
subscribe(callback: () => void): () => void {
|
|
42
|
-
this.subscribers.add(callback)
|
|
43
|
-
return () => this.subscribers.delete(callback)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private notify() {
|
|
47
|
-
this.subscribers.forEach((callback) => callback())
|
|
48
|
-
}
|
|
49
|
-
|
|
50
48
|
showTooltip(
|
|
51
49
|
tooltipId: string,
|
|
52
50
|
content: string | React.ReactNode,
|
|
53
|
-
|
|
54
|
-
side: 'top' | 'right' | 'bottom' | 'left'
|
|
55
|
-
sideOffset: number
|
|
51
|
+
targetElement: HTMLElement,
|
|
52
|
+
side: 'top' | 'right' | 'bottom' | 'left',
|
|
53
|
+
sideOffset: number,
|
|
54
|
+
showOnMobile: boolean,
|
|
55
|
+
delayDuration: number | undefined
|
|
56
56
|
) {
|
|
57
57
|
// Clear any existing destroy timeout
|
|
58
58
|
if (this.destroyTimeoutId) {
|
|
@@ -61,51 +61,57 @@ class TooltipManager {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
// Update current tooltip
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
this.currentTooltip.set({
|
|
65
|
+
id: tooltipId,
|
|
66
|
+
content,
|
|
67
|
+
side,
|
|
68
|
+
sideOffset,
|
|
69
|
+
showOnMobile,
|
|
70
|
+
targetElement,
|
|
71
|
+
delayDuration,
|
|
72
|
+
})
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
hideTooltip(tooltipId: string, instant: boolean = false) {
|
|
75
|
+
hideTooltip(editor: Editor | null, tooltipId: string, instant: boolean = false) {
|
|
74
76
|
const hide = () => {
|
|
75
77
|
// Only hide if this is the current tooltip
|
|
76
|
-
if (this.
|
|
77
|
-
this.
|
|
78
|
-
this.currentContent = ''
|
|
79
|
-
this.activeElement = null
|
|
78
|
+
if (this.currentTooltip.get()?.id === tooltipId) {
|
|
79
|
+
this.currentTooltip.set(null)
|
|
80
80
|
this.destroyTimeoutId = null
|
|
81
|
-
this.notify()
|
|
82
81
|
}
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
if (instant) {
|
|
86
|
-
hide()
|
|
87
|
-
} else if (this.editor) {
|
|
84
|
+
if (editor && !instant) {
|
|
88
85
|
// Start destroy timeout (1 second)
|
|
89
|
-
this.destroyTimeoutId =
|
|
86
|
+
this.destroyTimeoutId = editor.timers.setTimeout(hide, 300)
|
|
87
|
+
} else {
|
|
88
|
+
hide()
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
hideAllTooltips() {
|
|
94
|
-
this.
|
|
95
|
-
this.currentContent = ''
|
|
96
|
-
this.activeElement = null
|
|
93
|
+
this.currentTooltip.set(null)
|
|
97
94
|
this.destroyTimeoutId = null
|
|
98
|
-
this.notify()
|
|
99
95
|
}
|
|
100
96
|
|
|
101
97
|
getCurrentTooltipData() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
98
|
+
const currentTooltip = this.currentTooltip.get()
|
|
99
|
+
if (!currentTooltip) return null
|
|
100
|
+
if (!this.supportsHover() && !currentTooltip.showOnMobile) return null
|
|
101
|
+
return currentTooltip
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private supportsHoverAtom: Atom<boolean> | null = null
|
|
105
|
+
supportsHover() {
|
|
106
|
+
if (!this.supportsHoverAtom) {
|
|
107
|
+
const mediaQuery = window.matchMedia('(hover: hover)')
|
|
108
|
+
const supportsHover = atom('has hover', mediaQuery.matches)
|
|
109
|
+
this.supportsHoverAtom = supportsHover
|
|
110
|
+
mediaQuery.addEventListener('change', (e) => {
|
|
111
|
+
supportsHover.set(e.matches)
|
|
112
|
+
})
|
|
108
113
|
}
|
|
114
|
+
return this.supportsHoverAtom.get()
|
|
109
115
|
}
|
|
110
116
|
}
|
|
111
117
|
|
|
@@ -134,65 +140,30 @@ export function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderPro
|
|
|
134
140
|
// The singleton tooltip component that renders once
|
|
135
141
|
function TooltipSingleton() {
|
|
136
142
|
const editor = useMaybeEditor()
|
|
137
|
-
const [, forceUpdate] = useState({})
|
|
138
143
|
const [isOpen, setIsOpen] = useState(false)
|
|
139
144
|
const triggerRef = useRef<HTMLDivElement>(null)
|
|
140
|
-
const previousPositionRef = useRef<{ x: number; y: number } | null>(null)
|
|
141
|
-
const prefersReducedMotion = usePrefersReducedMotion()
|
|
142
|
-
const [shouldAnimate, setShouldAnimate] = useState(false)
|
|
143
145
|
const isFirstShowRef = useRef(true)
|
|
144
146
|
const showTimeoutRef = useRef<number | null>(null)
|
|
145
147
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
tooltipManager.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// Subscribe to tooltip manager updates
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
const unsubscribe = tooltipManager.subscribe(() => {
|
|
154
|
-
forceUpdate({})
|
|
155
|
-
})
|
|
156
|
-
return unsubscribe
|
|
157
|
-
}, [])
|
|
158
|
-
|
|
159
|
-
const tooltipData = tooltipManager.getCurrentTooltipData()
|
|
148
|
+
const currentTooltip = useValue(
|
|
149
|
+
'current tooltip',
|
|
150
|
+
() => tooltipManager.getCurrentTooltipData(),
|
|
151
|
+
[]
|
|
152
|
+
)
|
|
160
153
|
|
|
161
154
|
// Update open state and trigger position
|
|
162
155
|
useEffect(() => {
|
|
163
|
-
const shouldBeOpen = Boolean(tooltipData.id && tooltipData.element)
|
|
164
|
-
|
|
165
156
|
// Clear any existing show timeout
|
|
166
157
|
if (showTimeoutRef.current) {
|
|
167
158
|
clearTimeout(showTimeoutRef.current)
|
|
168
159
|
showTimeoutRef.current = null
|
|
169
160
|
}
|
|
170
161
|
|
|
171
|
-
if (
|
|
162
|
+
if (currentTooltip && triggerRef.current) {
|
|
172
163
|
// Position the invisible trigger element over the active element
|
|
173
|
-
const activeRect =
|
|
164
|
+
const activeRect = currentTooltip.targetElement.getBoundingClientRect()
|
|
174
165
|
const trigger = triggerRef.current
|
|
175
166
|
|
|
176
|
-
const newPosition = {
|
|
177
|
-
x: activeRect.left + activeRect.width / 2,
|
|
178
|
-
y: activeRect.top + activeRect.height / 2,
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Determine if we should animate
|
|
182
|
-
let shouldAnimateCheck = false
|
|
183
|
-
if (previousPositionRef.current) {
|
|
184
|
-
const isNearPrevious = Vec.DistMin(previousPositionRef.current, newPosition, 200)
|
|
185
|
-
// Only animate if the distance is less than 200px (nearby tooltips)
|
|
186
|
-
shouldAnimateCheck =
|
|
187
|
-
!prefersReducedMotion &&
|
|
188
|
-
isNearPrevious &&
|
|
189
|
-
Math.abs(newPosition.y - previousPositionRef.current.y) < 50
|
|
190
|
-
}
|
|
191
|
-
// Don't animate on initial show (previousPositionRef.current is null)
|
|
192
|
-
|
|
193
|
-
setShouldAnimate(isFirstShowRef.current ? false : shouldAnimateCheck)
|
|
194
|
-
previousPositionRef.current = newPosition
|
|
195
|
-
|
|
196
167
|
trigger.style.position = 'fixed'
|
|
197
168
|
trigger.style.left = `${activeRect.left}px`
|
|
198
169
|
trigger.style.top = `${activeRect.top}px`
|
|
@@ -206,23 +177,20 @@ function TooltipSingleton() {
|
|
|
206
177
|
showTimeoutRef.current = editor.timers.setTimeout(() => {
|
|
207
178
|
setIsOpen(true)
|
|
208
179
|
isFirstShowRef.current = false
|
|
209
|
-
}, editor.options.tooltipDelayMs)
|
|
180
|
+
}, currentTooltip.delayDuration ?? editor.options.tooltipDelayMs)
|
|
210
181
|
} else {
|
|
211
182
|
// Subsequent tooltips show immediately
|
|
212
183
|
setIsOpen(true)
|
|
213
184
|
}
|
|
214
|
-
} else
|
|
185
|
+
} else {
|
|
215
186
|
// Hide tooltip immediately
|
|
216
187
|
setIsOpen(false)
|
|
217
|
-
// Reset position tracking when tooltip closes
|
|
218
|
-
previousPositionRef.current = null
|
|
219
|
-
setShouldAnimate(false)
|
|
220
188
|
// Reset first show state after tooltip is hidden
|
|
221
189
|
isFirstShowRef.current = true
|
|
222
190
|
}
|
|
223
|
-
}, [
|
|
191
|
+
}, [editor, currentTooltip])
|
|
224
192
|
|
|
225
|
-
if (!
|
|
193
|
+
if (!currentTooltip) {
|
|
226
194
|
return null
|
|
227
195
|
}
|
|
228
196
|
|
|
@@ -233,14 +201,13 @@ function TooltipSingleton() {
|
|
|
233
201
|
</_Tooltip.Trigger>
|
|
234
202
|
<_Tooltip.Content
|
|
235
203
|
className="tlui-tooltip"
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
sideOffset={tooltipData.sideOffset}
|
|
204
|
+
side={currentTooltip.side}
|
|
205
|
+
sideOffset={currentTooltip.sideOffset}
|
|
239
206
|
avoidCollisions
|
|
240
207
|
collisionPadding={8}
|
|
241
208
|
dir="ltr"
|
|
242
209
|
>
|
|
243
|
-
{
|
|
210
|
+
{currentTooltip.content}
|
|
244
211
|
<_Tooltip.Arrow className="tlui-tooltip__arrow" />
|
|
245
212
|
</_Tooltip.Content>
|
|
246
213
|
</_Tooltip.Root>
|
|
@@ -249,7 +216,18 @@ function TooltipSingleton() {
|
|
|
249
216
|
|
|
250
217
|
/** @public @react */
|
|
251
218
|
export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(
|
|
252
|
-
(
|
|
219
|
+
(
|
|
220
|
+
{
|
|
221
|
+
children,
|
|
222
|
+
content,
|
|
223
|
+
side,
|
|
224
|
+
sideOffset = 5,
|
|
225
|
+
disabled = false,
|
|
226
|
+
showOnMobile = false,
|
|
227
|
+
delayDuration,
|
|
228
|
+
},
|
|
229
|
+
ref
|
|
230
|
+
) => {
|
|
253
231
|
const editor = useMaybeEditor()
|
|
254
232
|
const tooltipId = useRef<string>(uniqueId())
|
|
255
233
|
const hasProvider = useContext(TooltipSingletonContext)
|
|
@@ -261,10 +239,10 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
|
|
|
261
239
|
const currentTooltipId = tooltipId.current
|
|
262
240
|
return () => {
|
|
263
241
|
if (hasProvider) {
|
|
264
|
-
tooltipManager.hideTooltip(currentTooltipId, true)
|
|
242
|
+
tooltipManager.hideTooltip(editor, currentTooltipId, true)
|
|
265
243
|
}
|
|
266
244
|
}
|
|
267
|
-
}, [hasProvider])
|
|
245
|
+
}, [editor, hasProvider])
|
|
268
246
|
|
|
269
247
|
// Don't show tooltip if disabled, no content, or UI labels are disabled
|
|
270
248
|
if (disabled || !content) {
|
|
@@ -275,7 +253,9 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
|
|
|
275
253
|
if (!hasProvider) {
|
|
276
254
|
return (
|
|
277
255
|
<_Tooltip.Root
|
|
278
|
-
delayDuration={
|
|
256
|
+
delayDuration={
|
|
257
|
+
delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
|
|
258
|
+
}
|
|
279
259
|
disableHoverableContent
|
|
280
260
|
>
|
|
281
261
|
<_Tooltip.Trigger asChild ref={ref}>
|
|
@@ -306,13 +286,15 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
|
|
|
306
286
|
content,
|
|
307
287
|
event.currentTarget as HTMLElement,
|
|
308
288
|
sideToUse,
|
|
309
|
-
sideOffset
|
|
289
|
+
sideOffset,
|
|
290
|
+
showOnMobile,
|
|
291
|
+
delayDuration
|
|
310
292
|
)
|
|
311
293
|
}
|
|
312
294
|
|
|
313
295
|
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
|
|
314
296
|
child.props.onMouseLeave?.(event)
|
|
315
|
-
tooltipManager.hideTooltip(tooltipId.current)
|
|
297
|
+
tooltipManager.hideTooltip(editor, tooltipId.current)
|
|
316
298
|
}
|
|
317
299
|
|
|
318
300
|
const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
|
|
@@ -322,13 +304,15 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
|
|
|
322
304
|
content,
|
|
323
305
|
event.currentTarget as HTMLElement,
|
|
324
306
|
sideToUse,
|
|
325
|
-
sideOffset
|
|
307
|
+
sideOffset,
|
|
308
|
+
showOnMobile,
|
|
309
|
+
delayDuration
|
|
326
310
|
)
|
|
327
311
|
}
|
|
328
312
|
|
|
329
313
|
const handleBlur = (event: React.FocusEvent<HTMLElement>) => {
|
|
330
314
|
child.props.onBlur?.(event)
|
|
331
|
-
tooltipManager.hideTooltip(tooltipId.current)
|
|
315
|
+
tooltipManager.hideTooltip(editor, tooltipId.current)
|
|
332
316
|
}
|
|
333
317
|
|
|
334
318
|
const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
|
|
@@ -27,16 +27,6 @@ export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGr
|
|
|
27
27
|
const labelStr = labelToUse ? msg(labelToUse as TLUiTranslationKey) : undefined
|
|
28
28
|
|
|
29
29
|
switch (menu.type) {
|
|
30
|
-
case 'panel': {
|
|
31
|
-
return (
|
|
32
|
-
<div
|
|
33
|
-
className={classNames('tlui-menu__group', className)}
|
|
34
|
-
data-testid={`${menu.sourceId}-group.${id}`}
|
|
35
|
-
>
|
|
36
|
-
{children}
|
|
37
|
-
</div>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
30
|
case 'menu': {
|
|
41
31
|
return (
|
|
42
32
|
<TldrawUiDropdownMenuGroup
|
|
@@ -120,7 +120,6 @@ export function TldrawUiMenuItem<
|
|
|
120
120
|
type="menu"
|
|
121
121
|
data-testid={`${sourceId}.${id}`}
|
|
122
122
|
disabled={disabled}
|
|
123
|
-
title={titleStr}
|
|
124
123
|
onClick={(e) => {
|
|
125
124
|
if (noClose) {
|
|
126
125
|
preventDefault(e)
|
|
@@ -146,7 +145,6 @@ export function TldrawUiMenuItem<
|
|
|
146
145
|
return (
|
|
147
146
|
<_ContextMenu.Item
|
|
148
147
|
dir="ltr"
|
|
149
|
-
title={titleStr}
|
|
150
148
|
draggable={false}
|
|
151
149
|
className="tlui-button tlui-button__menu"
|
|
152
150
|
data-testid={`${sourceId}.${id}`}
|
|
@@ -168,20 +166,6 @@ export function TldrawUiMenuItem<
|
|
|
168
166
|
</_ContextMenu.Item>
|
|
169
167
|
)
|
|
170
168
|
}
|
|
171
|
-
case 'panel': {
|
|
172
|
-
return (
|
|
173
|
-
<TldrawUiButton
|
|
174
|
-
data-testid={`${sourceId}.${id}`}
|
|
175
|
-
type="menu"
|
|
176
|
-
title={titleStr}
|
|
177
|
-
disabled={disabled}
|
|
178
|
-
onClick={() => onSelect(sourceId)}
|
|
179
|
-
>
|
|
180
|
-
<TldrawUiButtonLabel>{labelStr}</TldrawUiButtonLabel>
|
|
181
|
-
{spinner ? <Spinner /> : icon && <TldrawUiButtonIcon icon={icon} />}
|
|
182
|
-
</TldrawUiButton>
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
169
|
case 'small-icons':
|
|
186
170
|
case 'icons': {
|
|
187
171
|
return (
|
|
@@ -332,8 +316,8 @@ function useDraggableEvents(
|
|
|
332
316
|
if (
|
|
333
317
|
distanceSq >
|
|
334
318
|
(editor.getInstanceState().isCoarsePointer
|
|
335
|
-
? editor.options.
|
|
336
|
-
: editor.options.
|
|
319
|
+
? editor.options.uiCoarseDragDistanceSquared
|
|
320
|
+
: editor.options.uiDragDistanceSquared)
|
|
337
321
|
) {
|
|
338
322
|
const screenSpaceStart = state.screenSpaceStart
|
|
339
323
|
state = {
|
|
@@ -342,6 +326,8 @@ function useDraggableEvents(
|
|
|
342
326
|
}
|
|
343
327
|
|
|
344
328
|
editor.run(() => {
|
|
329
|
+
editor.setCurrentTool('select')
|
|
330
|
+
|
|
345
331
|
// Set origin point
|
|
346
332
|
editor.dispatch({
|
|
347
333
|
type: 'pointer',
|
|
@@ -364,6 +350,7 @@ function useDraggableEvents(
|
|
|
364
350
|
})
|
|
365
351
|
|
|
366
352
|
tooltipManager.hideAllTooltips()
|
|
353
|
+
editor.getContainer().focus()
|
|
367
354
|
})
|
|
368
355
|
}
|
|
369
356
|
}
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
createShapeId,
|
|
4
4
|
Editor,
|
|
5
5
|
GeoShapeGeoStyle,
|
|
6
|
+
getIndicesBetween,
|
|
7
|
+
TLLineShape,
|
|
6
8
|
TLPointerEventInfo,
|
|
7
9
|
TLShapeId,
|
|
8
10
|
toRichText,
|
|
@@ -153,7 +155,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
153
155
|
},
|
|
154
156
|
onDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {
|
|
155
157
|
onDragFromToolbarToCreateShape(editor, info, {
|
|
156
|
-
createShape: (id) =>
|
|
158
|
+
createShape: (id) =>
|
|
159
|
+
editor.createShape({ id, type: 'geo', props: { w: 200, h: 200, geo } }),
|
|
157
160
|
})
|
|
158
161
|
trackEvent('drag-tool', { source, id: 'geo' })
|
|
159
162
|
},
|
|
@@ -188,6 +191,24 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
188
191
|
editor.setCurrentTool('line')
|
|
189
192
|
onToolSelect(source, this)
|
|
190
193
|
},
|
|
194
|
+
onDragStart(source, info) {
|
|
195
|
+
onDragFromToolbarToCreateShape(editor, info, {
|
|
196
|
+
createShape: (id) => {
|
|
197
|
+
const [start, end] = getIndicesBetween(null, null, 2)
|
|
198
|
+
editor.createShape<TLLineShape>({
|
|
199
|
+
id,
|
|
200
|
+
type: 'line',
|
|
201
|
+
props: {
|
|
202
|
+
points: {
|
|
203
|
+
[start]: { id: start, index: start, x: 0, y: 200 },
|
|
204
|
+
[end]: { id: end, index: end, x: 200, y: 0 },
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
},
|
|
209
|
+
})
|
|
210
|
+
trackEvent('drag-tool', { source, id: 'line' })
|
|
211
|
+
},
|
|
191
212
|
},
|
|
192
213
|
{
|
|
193
214
|
id: 'frame',
|
|
@@ -219,8 +240,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
219
240
|
createShape: (id) =>
|
|
220
241
|
editor.createShape({ id, type: 'text', props: { richText: toRichText('Text') } }),
|
|
221
242
|
onDragEnd: (id) => {
|
|
222
|
-
editor.emit('select-all-text', { shapeId: id })
|
|
223
243
|
editor.setEditingShape(id)
|
|
244
|
+
editor.emit('select-all-text', { shapeId: id })
|
|
224
245
|
},
|
|
225
246
|
})
|
|
226
247
|
trackEvent('drag-tool', { source, id: 'text' })
|
|
@@ -249,8 +270,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
249
270
|
onDragFromToolbarToCreateShape(editor, info, {
|
|
250
271
|
createShape: (id) => editor.createShape({ id, type: 'note' }),
|
|
251
272
|
onDragEnd: (id) => {
|
|
252
|
-
editor.emit('select-all-text', { shapeId: id })
|
|
253
273
|
editor.setEditingShape(id)
|
|
274
|
+
editor.emit('select-all-text', { shapeId: id })
|
|
254
275
|
},
|
|
255
276
|
})
|
|
256
277
|
trackEvent('drag-tool', { source, id: 'note' })
|
|
@@ -365,5 +386,6 @@ export function onDragFromToolbarToCreateShape(
|
|
|
365
386
|
opts.onDragEnd?.(id)
|
|
366
387
|
},
|
|
367
388
|
})
|
|
389
|
+
|
|
368
390
|
editor.getCurrentTool().setCurrentToolIdMask(shape.type)
|
|
369
391
|
}
|
package/src/lib/ui/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '3.16.0-canary.
|
|
4
|
+
export const version = '3.16.0-canary.654b4007a087'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-08-
|
|
8
|
-
patch: '2025-08-
|
|
7
|
+
minor: '2025-08-15T13:47:42.351Z',
|
|
8
|
+
patch: '2025-08-15T13:47:42.351Z',
|
|
9
9
|
}
|
package/src/lib/ui.css
CHANGED
|
@@ -1078,7 +1078,6 @@
|
|
|
1078
1078
|
.tlui-layout__bottom {
|
|
1079
1079
|
grid-row: 2;
|
|
1080
1080
|
width: 100%;
|
|
1081
|
-
overflow: hidden;
|
|
1082
1081
|
}
|
|
1083
1082
|
|
|
1084
1083
|
.tlui-layout__bottom__main {
|
|
@@ -1270,6 +1269,10 @@
|
|
|
1270
1269
|
opacity: 1;
|
|
1271
1270
|
}
|
|
1272
1271
|
|
|
1272
|
+
.tlui-main-toolbar__overflow-content {
|
|
1273
|
+
touch-action: none;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1273
1276
|
.tlui-main-toolbar__tools [data-toolbar-visible='false'],
|
|
1274
1277
|
.tlui-main-toolbar__overflow-content [data-toolbar-visible='false'] {
|
|
1275
1278
|
display: none;
|
|
@@ -1319,7 +1322,6 @@
|
|
|
1319
1322
|
max-width: 400px;
|
|
1320
1323
|
width: fit-content;
|
|
1321
1324
|
text-align: center;
|
|
1322
|
-
pointer-events: none;
|
|
1323
1325
|
will-change: transform, opacity;
|
|
1324
1326
|
z-index: 2;
|
|
1325
1327
|
}
|
|
@@ -1331,10 +1333,7 @@
|
|
|
1331
1333
|
|
|
1332
1334
|
[data-radix-popper-content-wrapper]:has(.tlui-tooltip) {
|
|
1333
1335
|
z-index: var(--tl-layer-toasts) !important;
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
[data-radix-popper-content-wrapper]:has(.tlui-tooltip[data-should-animate='true']) {
|
|
1337
|
-
transition: all 0.1s ease-out;
|
|
1336
|
+
pointer-events: none;
|
|
1338
1337
|
}
|
|
1339
1338
|
|
|
1340
1339
|
/* ------------------- Debug panel ------------------ */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`putExcalidrawContent test fixtures bound-arrows.json 1`] = `
|
|
3
|
+
exports[`putExcalidrawContent test fixtures > bound-arrows.json 1`] = `
|
|
4
4
|
{
|
|
5
5
|
"binding:8": {
|
|
6
6
|
"fromId": "shape:7",
|
|
@@ -173,7 +173,7 @@ exports[`putExcalidrawContent test fixtures bound-arrows.json 1`] = `
|
|
|
173
173
|
}
|
|
174
174
|
`;
|
|
175
175
|
|
|
176
|
-
exports[`putExcalidrawContent test fixtures bound-elbow-arrows.json 1`] = `
|
|
176
|
+
exports[`putExcalidrawContent test fixtures > bound-elbow-arrows.json 1`] = `
|
|
177
177
|
{
|
|
178
178
|
"binding:7": {
|
|
179
179
|
"fromId": "shape:6",
|
|
@@ -346,7 +346,7 @@ exports[`putExcalidrawContent test fixtures bound-elbow-arrows.json 1`] = `
|
|
|
346
346
|
}
|
|
347
347
|
`;
|
|
348
348
|
|
|
349
|
-
exports[`putExcalidrawContent test fixtures image.json 1`] = `
|
|
349
|
+
exports[`putExcalidrawContent test fixtures > image.json 1`] = `
|
|
350
350
|
{
|
|
351
351
|
"asset:5": {
|
|
352
352
|
"id": "asset:5",
|
|
@@ -404,7 +404,7 @@ exports[`putExcalidrawContent test fixtures image.json 1`] = `
|
|
|
404
404
|
}
|
|
405
405
|
`;
|
|
406
406
|
|
|
407
|
-
exports[`putExcalidrawContent test fixtures line-drawing.json 1`] = `
|
|
407
|
+
exports[`putExcalidrawContent test fixtures > line-drawing.json 1`] = `
|
|
408
408
|
{
|
|
409
409
|
"document:document": {
|
|
410
410
|
"gridSize": 10,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`buildFromV1Document test fixtures arrow-binding.tldr 1`] = `
|
|
3
|
+
exports[`buildFromV1Document test fixtures > arrow-binding.tldr 1`] = `
|
|
4
4
|
{
|
|
5
5
|
"binding:12": {
|
|
6
6
|
"fromId": "shape:11",
|
|
@@ -173,7 +173,7 @@ exports[`buildFromV1Document test fixtures arrow-binding.tldr 1`] = `
|
|
|
173
173
|
}
|
|
174
174
|
`;
|
|
175
175
|
|
|
176
|
-
exports[`buildFromV1Document test fixtures exact-arrow-binding.tldr 1`] = `
|
|
176
|
+
exports[`buildFromV1Document test fixtures > exact-arrow-binding.tldr 1`] = `
|
|
177
177
|
{
|
|
178
178
|
"binding:11": {
|
|
179
179
|
"fromId": "shape:10",
|
|
@@ -346,7 +346,7 @@ exports[`buildFromV1Document test fixtures exact-arrow-binding.tldr 1`] = `
|
|
|
346
346
|
}
|
|
347
347
|
`;
|
|
348
348
|
|
|
349
|
-
exports[`buildFromV1Document test fixtures incorrect-arrow-binding.tldr 1`] = `
|
|
349
|
+
exports[`buildFromV1Document test fixtures > incorrect-arrow-binding.tldr 1`] = `
|
|
350
350
|
{
|
|
351
351
|
"binding:11": {
|
|
352
352
|
"fromId": "shape:10",
|
package/src/test/A11y.test.tsx
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { createShapeId, toRichText } from '@tldraw/editor'
|
|
2
|
+
import { Mock, vi } from 'vitest'
|
|
2
3
|
import { generateShapeAnnouncementMessage } from '../lib/ui/components/A11y'
|
|
3
4
|
import { TestEditor } from './TestEditor'
|
|
4
5
|
|
|
5
6
|
describe('A11y Shape Announcements', () => {
|
|
6
7
|
let editor: TestEditor
|
|
7
|
-
let mockTranslate:
|
|
8
|
+
let mockTranslate: Mock
|
|
8
9
|
|
|
9
10
|
beforeEach(() => {
|
|
10
11
|
editor = new TestEditor()
|
|
11
12
|
|
|
12
13
|
// Create a simple translation mock
|
|
13
|
-
mockTranslate =
|
|
14
|
+
mockTranslate = vi.fn((key) => {
|
|
14
15
|
if (key === 'a11y.multiple-shapes') return '{num} shapes selected'
|
|
15
16
|
if (key === 'a11y.shape') return 'Shape'
|
|
16
17
|
if (key === 'a11y.text') return 'Text'
|