tldraw 3.16.0-canary.6f3aedaa1c01 → 3.16.0-canary.7cd3b8a5bfd6
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 +45 -4
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/canvas/TldrawScribble.js +1 -1
- package/dist-cjs/lib/canvas/TldrawScribble.js.map +2 -2
- package/dist-cjs/lib/shapes/arrow/elbow/ElbowArrowDebug.js +3 -3
- package/dist-cjs/lib/shapes/arrow/elbow/ElbowArrowDebug.js.map +1 -1
- package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js +1 -1
- package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js.map +1 -1
- 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/frame/components/FrameHeading.js +1 -1
- package/dist-cjs/lib/shapes/frame/components/FrameHeading.js.map +2 -2
- package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +3 -3
- package/dist-cjs/lib/shapes/image/ImageShapeUtil.js.map +1 -1
- 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/video/VideoShapeUtil.js +3 -3
- package/dist-cjs/lib/shapes/video/VideoShapeUtil.js.map +1 -1
- package/dist-cjs/lib/ui/TldrawUi.js +14 -0
- package/dist-cjs/lib/ui/TldrawUi.js.map +3 -3
- package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js +10 -2
- package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js.map +2 -2
- package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js +4 -4
- package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js.map +2 -2
- package/dist-cjs/lib/ui/components/MobileStylePanel.js +4 -2
- package/dist-cjs/lib/ui/components/MobileStylePanel.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js +1 -1
- package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js.map +2 -2
- package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js +66 -22
- package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js.map +3 -3
- package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +188 -78
- package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +3 -3
- package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +15 -3
- package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +144 -160
- package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/layout.js +30 -5
- package/dist-cjs/lib/ui/components/primitives/layout.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 +25 -12
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +1 -18
- 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 +45 -4
- package/dist-esm/index.mjs +8 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/canvas/TldrawScribble.mjs +1 -1
- package/dist-esm/lib/canvas/TldrawScribble.mjs.map +2 -2
- package/dist-esm/lib/shapes/arrow/elbow/ElbowArrowDebug.mjs +3 -3
- package/dist-esm/lib/shapes/arrow/elbow/ElbowArrowDebug.mjs.map +1 -1
- package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs +1 -1
- package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs.map +1 -1
- 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/frame/components/FrameHeading.mjs +1 -1
- package/dist-esm/lib/shapes/frame/components/FrameHeading.mjs.map +2 -2
- package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +3 -3
- package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +1 -1
- 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/video/VideoShapeUtil.mjs +3 -3
- package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs.map +1 -1
- package/dist-esm/lib/ui/TldrawUi.mjs +16 -2
- package/dist-esm/lib/ui/TldrawUi.mjs.map +3 -3
- package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs +10 -2
- package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs +4 -4
- package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs.map +2 -2
- package/dist-esm/lib/ui/components/MobileStylePanel.mjs +4 -2
- package/dist-esm/lib/ui/components/MobileStylePanel.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs +1 -1
- package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs +56 -22
- package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs.map +2 -2
- package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +192 -80
- package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +3 -3
- package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +16 -4
- package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +153 -162
- package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/layout.mjs +31 -6
- package/dist-esm/lib/ui/components/primitives/layout.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 +25 -12
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +1 -18
- 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 +3 -3
- package/src/index.ts +5 -0
- package/src/lib/canvas/TldrawScribble.tsx +1 -1
- package/src/lib/shapes/arrow/elbow/ElbowArrowDebug.tsx +3 -3
- package/src/lib/shapes/embed/EmbedShapeUtil.tsx +1 -1
- package/src/lib/shapes/frame/FrameShapeUtil.tsx +12 -4
- package/src/lib/shapes/frame/components/FrameHeading.tsx +1 -1
- package/src/lib/shapes/image/ImageShapeUtil.tsx +3 -3
- package/src/lib/shapes/shared/ShapeFill.tsx +1 -1
- package/src/lib/shapes/video/VideoShapeUtil.tsx +3 -3
- package/src/lib/ui/TldrawUi.tsx +17 -2
- package/src/lib/ui/components/ActionsMenu/DefaultActionsMenu.tsx +13 -2
- package/src/lib/ui/components/Minimap/MinimapManager.ts +4 -4
- package/src/lib/ui/components/MobileStylePanel.tsx +4 -3
- package/src/lib/ui/components/Toolbar/DefaultImageToolbarContent.tsx +1 -1
- package/src/lib/ui/components/Toolbar/DefaultToolbar.tsx +55 -24
- package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +208 -56
- package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +22 -5
- package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +175 -180
- package/src/lib/ui/components/primitives/layout.tsx +79 -5
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuContext.tsx +0 -1
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuGroup.tsx +29 -16
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +2 -16
- package/src/lib/ui/hooks/useTools.tsx +25 -3
- package/src/lib/ui/version.ts +3 -3
- package/src/lib/ui.css +346 -243
- package/tldraw.css +639 -533
|
@@ -1,7 +1,15 @@
|
|
|
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'
|
|
12
|
+
import { useTldrawUiOrientation } from './layout'
|
|
5
13
|
|
|
6
14
|
const DEFAULT_TOOLTIP_DELAY_MS = 700
|
|
7
15
|
|
|
@@ -12,19 +20,23 @@ export interface TldrawUiTooltipProps {
|
|
|
12
20
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
13
21
|
sideOffset?: number
|
|
14
22
|
disabled?: boolean
|
|
23
|
+
showOnMobile?: boolean
|
|
24
|
+
delayDuration?: number
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
// Singleton tooltip manager
|
|
18
28
|
class TooltipManager {
|
|
19
29
|
private static instance: TooltipManager | null = null
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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)
|
|
24
39
|
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
40
|
|
|
29
41
|
static getInstance(): TooltipManager {
|
|
30
42
|
if (!TooltipManager.instance) {
|
|
@@ -33,25 +45,14 @@ class TooltipManager {
|
|
|
33
45
|
return TooltipManager.instance
|
|
34
46
|
}
|
|
35
47
|
|
|
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
48
|
showTooltip(
|
|
50
49
|
tooltipId: string,
|
|
51
50
|
content: string | React.ReactNode,
|
|
52
|
-
|
|
53
|
-
side: 'top' | 'right' | 'bottom' | 'left'
|
|
54
|
-
sideOffset: number
|
|
51
|
+
targetElement: HTMLElement,
|
|
52
|
+
side: 'top' | 'right' | 'bottom' | 'left',
|
|
53
|
+
sideOffset: number,
|
|
54
|
+
showOnMobile: boolean,
|
|
55
|
+
delayDuration: number | undefined
|
|
55
56
|
) {
|
|
56
57
|
// Clear any existing destroy timeout
|
|
57
58
|
if (this.destroyTimeoutId) {
|
|
@@ -60,51 +61,57 @@ class TooltipManager {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
// Update current tooltip
|
|
63
|
-
this.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
this.currentTooltip.set({
|
|
65
|
+
id: tooltipId,
|
|
66
|
+
content,
|
|
67
|
+
side,
|
|
68
|
+
sideOffset,
|
|
69
|
+
showOnMobile,
|
|
70
|
+
targetElement,
|
|
71
|
+
delayDuration,
|
|
72
|
+
})
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
hideTooltip(tooltipId: string, instant: boolean = false) {
|
|
75
|
+
hideTooltip(editor: Editor | null, tooltipId: string, instant: boolean = false) {
|
|
73
76
|
const hide = () => {
|
|
74
77
|
// Only hide if this is the current tooltip
|
|
75
|
-
if (this.
|
|
76
|
-
this.
|
|
77
|
-
this.currentContent = ''
|
|
78
|
-
this.activeElement = null
|
|
78
|
+
if (this.currentTooltip.get()?.id === tooltipId) {
|
|
79
|
+
this.currentTooltip.set(null)
|
|
79
80
|
this.destroyTimeoutId = null
|
|
80
|
-
this.notify()
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
if (instant) {
|
|
85
|
-
hide()
|
|
86
|
-
} else if (this.editor) {
|
|
84
|
+
if (editor && !instant) {
|
|
87
85
|
// Start destroy timeout (1 second)
|
|
88
|
-
this.destroyTimeoutId =
|
|
86
|
+
this.destroyTimeoutId = editor.timers.setTimeout(hide, 300)
|
|
87
|
+
} else {
|
|
88
|
+
hide()
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
hideAllTooltips() {
|
|
93
|
-
this.
|
|
94
|
-
this.currentContent = ''
|
|
95
|
-
this.activeElement = null
|
|
93
|
+
this.currentTooltip.set(null)
|
|
96
94
|
this.destroyTimeoutId = null
|
|
97
|
-
this.notify()
|
|
98
95
|
}
|
|
99
96
|
|
|
100
97
|
getCurrentTooltipData() {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
+
})
|
|
107
113
|
}
|
|
114
|
+
return this.supportsHoverAtom.get()
|
|
108
115
|
}
|
|
109
116
|
}
|
|
110
117
|
|
|
@@ -133,65 +140,30 @@ export function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderPro
|
|
|
133
140
|
// The singleton tooltip component that renders once
|
|
134
141
|
function TooltipSingleton() {
|
|
135
142
|
const editor = useMaybeEditor()
|
|
136
|
-
const [, forceUpdate] = useState({})
|
|
137
143
|
const [isOpen, setIsOpen] = useState(false)
|
|
138
144
|
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
145
|
const isFirstShowRef = useRef(true)
|
|
143
146
|
const showTimeoutRef = useRef<number | null>(null)
|
|
144
147
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
tooltipManager.
|
|
148
|
-
|
|
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()
|
|
148
|
+
const currentTooltip = useValue(
|
|
149
|
+
'current tooltip',
|
|
150
|
+
() => tooltipManager.getCurrentTooltipData(),
|
|
151
|
+
[]
|
|
152
|
+
)
|
|
159
153
|
|
|
160
154
|
// Update open state and trigger position
|
|
161
155
|
useEffect(() => {
|
|
162
|
-
const shouldBeOpen = Boolean(tooltipData.id && tooltipData.element)
|
|
163
|
-
|
|
164
156
|
// Clear any existing show timeout
|
|
165
157
|
if (showTimeoutRef.current) {
|
|
166
158
|
clearTimeout(showTimeoutRef.current)
|
|
167
159
|
showTimeoutRef.current = null
|
|
168
160
|
}
|
|
169
161
|
|
|
170
|
-
if (
|
|
162
|
+
if (currentTooltip && triggerRef.current) {
|
|
171
163
|
// Position the invisible trigger element over the active element
|
|
172
|
-
const activeRect =
|
|
164
|
+
const activeRect = currentTooltip.targetElement.getBoundingClientRect()
|
|
173
165
|
const trigger = triggerRef.current
|
|
174
166
|
|
|
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
167
|
trigger.style.position = 'fixed'
|
|
196
168
|
trigger.style.left = `${activeRect.left}px`
|
|
197
169
|
trigger.style.top = `${activeRect.top}px`
|
|
@@ -205,23 +177,20 @@ function TooltipSingleton() {
|
|
|
205
177
|
showTimeoutRef.current = editor.timers.setTimeout(() => {
|
|
206
178
|
setIsOpen(true)
|
|
207
179
|
isFirstShowRef.current = false
|
|
208
|
-
}, editor.options.tooltipDelayMs)
|
|
180
|
+
}, currentTooltip.delayDuration ?? editor.options.tooltipDelayMs)
|
|
209
181
|
} else {
|
|
210
182
|
// Subsequent tooltips show immediately
|
|
211
183
|
setIsOpen(true)
|
|
212
184
|
}
|
|
213
|
-
} else
|
|
185
|
+
} else {
|
|
214
186
|
// Hide tooltip immediately
|
|
215
187
|
setIsOpen(false)
|
|
216
|
-
// Reset position tracking when tooltip closes
|
|
217
|
-
previousPositionRef.current = null
|
|
218
|
-
setShouldAnimate(false)
|
|
219
188
|
// Reset first show state after tooltip is hidden
|
|
220
189
|
isFirstShowRef.current = true
|
|
221
190
|
}
|
|
222
|
-
}, [
|
|
191
|
+
}, [editor, currentTooltip])
|
|
223
192
|
|
|
224
|
-
if (!
|
|
193
|
+
if (!currentTooltip) {
|
|
225
194
|
return null
|
|
226
195
|
}
|
|
227
196
|
|
|
@@ -232,14 +201,13 @@ function TooltipSingleton() {
|
|
|
232
201
|
</_Tooltip.Trigger>
|
|
233
202
|
<_Tooltip.Content
|
|
234
203
|
className="tlui-tooltip"
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
sideOffset={tooltipData.sideOffset}
|
|
204
|
+
side={currentTooltip.side}
|
|
205
|
+
sideOffset={currentTooltip.sideOffset}
|
|
238
206
|
avoidCollisions
|
|
239
207
|
collisionPadding={8}
|
|
240
208
|
dir="ltr"
|
|
241
209
|
>
|
|
242
|
-
{
|
|
210
|
+
{currentTooltip.content}
|
|
243
211
|
<_Tooltip.Arrow className="tlui-tooltip__arrow" />
|
|
244
212
|
</_Tooltip.Content>
|
|
245
213
|
</_Tooltip.Root>
|
|
@@ -247,86 +215,113 @@ function TooltipSingleton() {
|
|
|
247
215
|
}
|
|
248
216
|
|
|
249
217
|
/** @public @react */
|
|
250
|
-
export
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
218
|
+
export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(
|
|
219
|
+
(
|
|
220
|
+
{
|
|
221
|
+
children,
|
|
222
|
+
content,
|
|
223
|
+
side,
|
|
224
|
+
sideOffset = 5,
|
|
225
|
+
disabled = false,
|
|
226
|
+
showOnMobile = false,
|
|
227
|
+
delayDuration,
|
|
228
|
+
},
|
|
229
|
+
ref
|
|
230
|
+
) => {
|
|
231
|
+
const editor = useMaybeEditor()
|
|
232
|
+
const tooltipId = useRef<string>(uniqueId())
|
|
233
|
+
const hasProvider = useContext(TooltipSingletonContext)
|
|
234
|
+
|
|
235
|
+
const orientationCtx = useTldrawUiOrientation()
|
|
236
|
+
const sideToUse = side ?? orientationCtx.tooltipSide
|
|
237
|
+
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
const currentTooltipId = tooltipId.current
|
|
240
|
+
return () => {
|
|
241
|
+
if (hasProvider) {
|
|
242
|
+
tooltipManager.hideTooltip(editor, currentTooltipId, true)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}, [editor, hasProvider])
|
|
260
246
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
247
|
+
// Don't show tooltip if disabled, no content, or UI labels are disabled
|
|
248
|
+
if (disabled || !content) {
|
|
249
|
+
return <>{children}</>
|
|
250
|
+
}
|
|
265
251
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
<_Tooltip.Content
|
|
275
|
-
className="tlui-tooltip"
|
|
276
|
-
side={side}
|
|
277
|
-
sideOffset={sideOffset}
|
|
278
|
-
avoidCollisions
|
|
279
|
-
collisionPadding={8}
|
|
280
|
-
dir="ltr"
|
|
252
|
+
// Fallback to old behavior if no provider
|
|
253
|
+
if (!hasProvider) {
|
|
254
|
+
return (
|
|
255
|
+
<_Tooltip.Root
|
|
256
|
+
delayDuration={
|
|
257
|
+
delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
|
|
258
|
+
}
|
|
259
|
+
disableHoverableContent
|
|
281
260
|
>
|
|
282
|
-
{
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
261
|
+
<_Tooltip.Trigger asChild ref={ref}>
|
|
262
|
+
{children}
|
|
263
|
+
</_Tooltip.Trigger>
|
|
264
|
+
<_Tooltip.Content
|
|
265
|
+
className="tlui-tooltip"
|
|
266
|
+
side={sideToUse}
|
|
267
|
+
sideOffset={sideOffset}
|
|
268
|
+
avoidCollisions
|
|
269
|
+
collisionPadding={8}
|
|
270
|
+
dir="ltr"
|
|
271
|
+
>
|
|
272
|
+
{content}
|
|
273
|
+
<_Tooltip.Arrow className="tlui-tooltip__arrow" />
|
|
274
|
+
</_Tooltip.Content>
|
|
275
|
+
</_Tooltip.Root>
|
|
276
|
+
)
|
|
277
|
+
}
|
|
291
278
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
279
|
+
const child = React.Children.only(children)
|
|
280
|
+
assert(React.isValidElement(child), 'TldrawUiTooltip children must be a single element')
|
|
281
|
+
|
|
282
|
+
const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
|
|
283
|
+
child.props.onMouseEnter?.(event)
|
|
284
|
+
tooltipManager.showTooltip(
|
|
285
|
+
tooltipId.current,
|
|
286
|
+
content,
|
|
287
|
+
event.currentTarget as HTMLElement,
|
|
288
|
+
sideToUse,
|
|
289
|
+
sideOffset,
|
|
290
|
+
showOnMobile,
|
|
291
|
+
delayDuration
|
|
292
|
+
)
|
|
293
|
+
}
|
|
302
294
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
295
|
+
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
|
|
296
|
+
child.props.onMouseLeave?.(event)
|
|
297
|
+
tooltipManager.hideTooltip(editor, tooltipId.current)
|
|
298
|
+
}
|
|
307
299
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
300
|
+
const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
|
|
301
|
+
child.props.onFocus?.(event)
|
|
302
|
+
tooltipManager.showTooltip(
|
|
303
|
+
tooltipId.current,
|
|
304
|
+
content,
|
|
305
|
+
event.currentTarget as HTMLElement,
|
|
306
|
+
sideToUse,
|
|
307
|
+
sideOffset,
|
|
308
|
+
showOnMobile,
|
|
309
|
+
delayDuration
|
|
310
|
+
)
|
|
311
|
+
}
|
|
318
312
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
313
|
+
const handleBlur = (event: React.FocusEvent<HTMLElement>) => {
|
|
314
|
+
child.props.onBlur?.(event)
|
|
315
|
+
tooltipManager.hideTooltip(editor, tooltipId.current)
|
|
316
|
+
}
|
|
323
317
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
318
|
+
const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
|
|
319
|
+
onMouseEnter: handleMouseEnter,
|
|
320
|
+
onMouseLeave: handleMouseLeave,
|
|
321
|
+
onFocus: handleFocus,
|
|
322
|
+
onBlur: handleBlur,
|
|
323
|
+
})
|
|
330
324
|
|
|
331
|
-
|
|
332
|
-
}
|
|
325
|
+
return childrenWithHandlers
|
|
326
|
+
}
|
|
327
|
+
)
|
|
@@ -1,10 +1,60 @@
|
|
|
1
1
|
import classNames from 'classnames'
|
|
2
2
|
import { Slot } from 'radix-ui'
|
|
3
|
-
import { HTMLAttributes, ReactNode, forwardRef } from 'react'
|
|
3
|
+
import { HTMLAttributes, ReactNode, createContext, forwardRef, useContext } from 'react'
|
|
4
|
+
|
|
5
|
+
/** @public */
|
|
6
|
+
export interface TldrawUiOrientationContext {
|
|
7
|
+
orientation: 'horizontal' | 'vertical'
|
|
8
|
+
tooltipSide: 'top' | 'right' | 'bottom' | 'left'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const TldrawUiOrientationContext = createContext<TldrawUiOrientationContext>({
|
|
12
|
+
orientation: 'horizontal',
|
|
13
|
+
tooltipSide: 'bottom',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
/** @public */
|
|
17
|
+
export interface TldrawUiOrientationProviderProps {
|
|
18
|
+
children: ReactNode
|
|
19
|
+
orientation: 'horizontal' | 'vertical'
|
|
20
|
+
tooltipSide?: 'top' | 'right' | 'bottom' | 'left'
|
|
21
|
+
}
|
|
22
|
+
/** @public @react */
|
|
23
|
+
export function TldrawUiOrientationProvider({
|
|
24
|
+
children,
|
|
25
|
+
orientation,
|
|
26
|
+
tooltipSide,
|
|
27
|
+
}: TldrawUiOrientationProviderProps) {
|
|
28
|
+
const prevContext = useTldrawUiOrientation()
|
|
29
|
+
// generally, we want tooltip side to cascade down through the layout - apart from when the
|
|
30
|
+
// orientation changes. If the tooltip side is "bottom", and then I include some vertical layout
|
|
31
|
+
// elements, keeping the tooltip side as bottom will cause the tooltip to overlap elements
|
|
32
|
+
// stacked on top of each other. In the absence of a tooltip side, we pick a default side based
|
|
33
|
+
// on the orientation whenever the orientation changes.
|
|
34
|
+
const tooltipSideToUse =
|
|
35
|
+
tooltipSide ??
|
|
36
|
+
(orientation === prevContext.orientation
|
|
37
|
+
? prevContext.tooltipSide
|
|
38
|
+
: orientation === 'horizontal'
|
|
39
|
+
? 'bottom'
|
|
40
|
+
: 'right')
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<TldrawUiOrientationContext.Provider value={{ orientation, tooltipSide: tooltipSideToUse }}>
|
|
44
|
+
{children}
|
|
45
|
+
</TldrawUiOrientationContext.Provider>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @public */
|
|
50
|
+
export function useTldrawUiOrientation() {
|
|
51
|
+
return useContext(TldrawUiOrientationContext)
|
|
52
|
+
}
|
|
4
53
|
|
|
5
54
|
/** @public */
|
|
6
55
|
export interface TLUiLayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
56
|
children: ReactNode
|
|
57
|
+
tooltipSide?: 'top' | 'right' | 'bottom' | 'left'
|
|
8
58
|
asChild?: boolean
|
|
9
59
|
}
|
|
10
60
|
|
|
@@ -14,9 +64,29 @@ export interface TLUiLayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
14
64
|
* @public @react
|
|
15
65
|
*/
|
|
16
66
|
export const TldrawUiRow = forwardRef<HTMLDivElement, TLUiLayoutProps>(
|
|
17
|
-
({ asChild, className, ...props }, ref) => {
|
|
67
|
+
({ asChild, className, tooltipSide, ...props }, ref) => {
|
|
68
|
+
const Component = asChild ? Slot.Root : 'div'
|
|
69
|
+
return (
|
|
70
|
+
<TldrawUiOrientationProvider orientation="horizontal" tooltipSide={tooltipSide}>
|
|
71
|
+
<Component ref={ref} className={classNames('tlui-row', className)} {...props} />
|
|
72
|
+
</TldrawUiOrientationProvider>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A column, usually of UI controls like buttons, select dropdown, checkboxes, etc.
|
|
79
|
+
*
|
|
80
|
+
* @public @react
|
|
81
|
+
*/
|
|
82
|
+
export const TldrawUiColumn = forwardRef<HTMLDivElement, TLUiLayoutProps>(
|
|
83
|
+
({ asChild, className, tooltipSide, ...props }, ref) => {
|
|
18
84
|
const Component = asChild ? Slot.Root : 'div'
|
|
19
|
-
return
|
|
85
|
+
return (
|
|
86
|
+
<TldrawUiOrientationProvider orientation="vertical" tooltipSide={tooltipSide}>
|
|
87
|
+
<Component ref={ref} className={classNames('tlui-column', className)} {...props} />
|
|
88
|
+
</TldrawUiOrientationProvider>
|
|
89
|
+
)
|
|
20
90
|
}
|
|
21
91
|
)
|
|
22
92
|
|
|
@@ -26,8 +96,12 @@ export const TldrawUiRow = forwardRef<HTMLDivElement, TLUiLayoutProps>(
|
|
|
26
96
|
*
|
|
27
97
|
* @public @react */
|
|
28
98
|
export const TldrawUiGrid = forwardRef<HTMLDivElement, TLUiLayoutProps>(
|
|
29
|
-
({ asChild, className, ...props }, ref) => {
|
|
99
|
+
({ asChild, className, tooltipSide, ...props }, ref) => {
|
|
30
100
|
const Component = asChild ? Slot.Root : 'div'
|
|
31
|
-
return
|
|
101
|
+
return (
|
|
102
|
+
<TldrawUiOrientationProvider orientation="horizontal" tooltipSide={tooltipSide}>
|
|
103
|
+
<Component ref={ref} className={classNames('tlui-grid', className)} {...props} />
|
|
104
|
+
</TldrawUiOrientationProvider>
|
|
105
|
+
)
|
|
32
106
|
}
|
|
33
107
|
)
|
|
@@ -3,6 +3,7 @@ import { ReactNode } from 'react'
|
|
|
3
3
|
import { unwrapLabel } from '../../../context/actions'
|
|
4
4
|
import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
|
|
5
5
|
import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
|
|
6
|
+
import { TldrawUiColumn, TldrawUiGrid, TldrawUiRow, useTldrawUiOrientation } from '../layout'
|
|
6
7
|
import { TldrawUiDropdownMenuGroup } from '../TldrawUiDropdownMenu'
|
|
7
8
|
import { useTldrawUiMenuContext } from './TldrawUiMenuContext'
|
|
8
9
|
|
|
@@ -19,25 +20,19 @@ export interface TLUiMenuGroupProps<TranslationKey extends string = string> {
|
|
|
19
20
|
|
|
20
21
|
/** @public @react */
|
|
21
22
|
export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGroupProps) {
|
|
22
|
-
const
|
|
23
|
+
const menu = useTldrawUiMenuContext()
|
|
24
|
+
const { orientation } = useTldrawUiOrientation()
|
|
23
25
|
const msg = useTranslation()
|
|
24
|
-
const labelToUse = unwrapLabel(label,
|
|
26
|
+
const labelToUse = unwrapLabel(label, menu.type)
|
|
25
27
|
const labelStr = labelToUse ? msg(labelToUse as TLUiTranslationKey) : undefined
|
|
26
28
|
|
|
27
|
-
switch (
|
|
28
|
-
case 'panel': {
|
|
29
|
-
return (
|
|
30
|
-
<div
|
|
31
|
-
className={classNames('tlui-menu__group', className)}
|
|
32
|
-
data-testid={`${sourceId}-group.${id}`}
|
|
33
|
-
>
|
|
34
|
-
{children}
|
|
35
|
-
</div>
|
|
36
|
-
)
|
|
37
|
-
}
|
|
29
|
+
switch (menu.type) {
|
|
38
30
|
case 'menu': {
|
|
39
31
|
return (
|
|
40
|
-
<TldrawUiDropdownMenuGroup
|
|
32
|
+
<TldrawUiDropdownMenuGroup
|
|
33
|
+
className={className}
|
|
34
|
+
data-testid={`${menu.sourceId}-group.${id}`}
|
|
35
|
+
>
|
|
41
36
|
{children}
|
|
42
37
|
</TldrawUiDropdownMenuGroup>
|
|
43
38
|
)
|
|
@@ -47,7 +42,7 @@ export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGr
|
|
|
47
42
|
<div
|
|
48
43
|
dir="ltr"
|
|
49
44
|
className={classNames('tlui-menu__group', className)}
|
|
50
|
-
data-testid={`${sourceId}-group.${id}`}
|
|
45
|
+
data-testid={`${menu.sourceId}-group.${id}`}
|
|
51
46
|
>
|
|
52
47
|
{children}
|
|
53
48
|
</div>
|
|
@@ -56,12 +51,30 @@ export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGr
|
|
|
56
51
|
case 'keyboard-shortcuts': {
|
|
57
52
|
// todo: if groups need a label, let's give em a label
|
|
58
53
|
return (
|
|
59
|
-
<div className="tlui-shortcuts-dialog__group" data-testid={`${sourceId}-group.${id}`}>
|
|
54
|
+
<div className="tlui-shortcuts-dialog__group" data-testid={`${menu.sourceId}-group.${id}`}>
|
|
60
55
|
<h2 className="tlui-shortcuts-dialog__group__title">{labelStr}</h2>
|
|
61
56
|
<div className="tlui-shortcuts-dialog__group__content">{children}</div>
|
|
62
57
|
</div>
|
|
63
58
|
)
|
|
64
59
|
}
|
|
60
|
+
case 'toolbar': {
|
|
61
|
+
const Layout = orientation === 'horizontal' ? TldrawUiRow : TldrawUiColumn
|
|
62
|
+
return (
|
|
63
|
+
<Layout className="tlui-main-toolbar__group" data-testid={`${menu.sourceId}-group.${id}`}>
|
|
64
|
+
{children}
|
|
65
|
+
</Layout>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
case 'toolbar-overflow': {
|
|
69
|
+
return (
|
|
70
|
+
<TldrawUiGrid
|
|
71
|
+
className="tlui-main-toolbar__group"
|
|
72
|
+
data-testid={`${menu.sourceId}-group.${id}`}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</TldrawUiGrid>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
65
78
|
default: {
|
|
66
79
|
return children
|
|
67
80
|
}
|