tldraw 3.16.0-canary.2b8b5023f0a5 → 3.16.0-canary.396b6a2c3eec
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 +44 -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 +132 -159
- 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 +44 -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 +141 -161
- 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 +155 -179
- 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,21 @@ export interface TldrawUiTooltipProps {
|
|
|
12
20
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
13
21
|
sideOffset?: number
|
|
14
22
|
disabled?: boolean
|
|
23
|
+
showOnMobile?: boolean
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
// Singleton tooltip manager
|
|
18
27
|
class TooltipManager {
|
|
19
28
|
private static instance: TooltipManager | null = null
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
private currentTooltip = atom<{
|
|
30
|
+
id: string
|
|
31
|
+
content: ReactNode
|
|
32
|
+
side: 'top' | 'right' | 'bottom' | 'left'
|
|
33
|
+
sideOffset: number
|
|
34
|
+
showOnMobile: boolean
|
|
35
|
+
targetElement: HTMLElement
|
|
36
|
+
} | null>('current tooltip', null)
|
|
24
37
|
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
38
|
|
|
29
39
|
static getInstance(): TooltipManager {
|
|
30
40
|
if (!TooltipManager.instance) {
|
|
@@ -33,25 +43,13 @@ class TooltipManager {
|
|
|
33
43
|
return TooltipManager.instance
|
|
34
44
|
}
|
|
35
45
|
|
|
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
46
|
showTooltip(
|
|
50
47
|
tooltipId: string,
|
|
51
48
|
content: string | React.ReactNode,
|
|
52
|
-
|
|
53
|
-
side: 'top' | 'right' | 'bottom' | 'left'
|
|
54
|
-
sideOffset: number
|
|
49
|
+
targetElement: HTMLElement,
|
|
50
|
+
side: 'top' | 'right' | 'bottom' | 'left',
|
|
51
|
+
sideOffset: number,
|
|
52
|
+
showOnMobile: boolean
|
|
55
53
|
) {
|
|
56
54
|
// Clear any existing destroy timeout
|
|
57
55
|
if (this.destroyTimeoutId) {
|
|
@@ -60,51 +58,56 @@ class TooltipManager {
|
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
// Update current tooltip
|
|
63
|
-
this.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
this.currentTooltip.set({
|
|
62
|
+
id: tooltipId,
|
|
63
|
+
content,
|
|
64
|
+
side,
|
|
65
|
+
sideOffset,
|
|
66
|
+
showOnMobile,
|
|
67
|
+
targetElement,
|
|
68
|
+
})
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
hideTooltip(tooltipId: string, instant: boolean = false) {
|
|
71
|
+
hideTooltip(editor: Editor | null, tooltipId: string, instant: boolean = false) {
|
|
73
72
|
const hide = () => {
|
|
74
73
|
// Only hide if this is the current tooltip
|
|
75
|
-
if (this.
|
|
76
|
-
this.
|
|
77
|
-
this.currentContent = ''
|
|
78
|
-
this.activeElement = null
|
|
74
|
+
if (this.currentTooltip.get()?.id === tooltipId) {
|
|
75
|
+
this.currentTooltip.set(null)
|
|
79
76
|
this.destroyTimeoutId = null
|
|
80
|
-
this.notify()
|
|
81
77
|
}
|
|
82
78
|
}
|
|
83
79
|
|
|
84
|
-
if (instant) {
|
|
85
|
-
hide()
|
|
86
|
-
} else if (this.editor) {
|
|
80
|
+
if (editor && !instant) {
|
|
87
81
|
// Start destroy timeout (1 second)
|
|
88
|
-
this.destroyTimeoutId =
|
|
82
|
+
this.destroyTimeoutId = editor.timers.setTimeout(hide, 300)
|
|
83
|
+
} else {
|
|
84
|
+
hide()
|
|
89
85
|
}
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
hideAllTooltips() {
|
|
93
|
-
this.
|
|
94
|
-
this.currentContent = ''
|
|
95
|
-
this.activeElement = null
|
|
89
|
+
this.currentTooltip.set(null)
|
|
96
90
|
this.destroyTimeoutId = null
|
|
97
|
-
this.notify()
|
|
98
91
|
}
|
|
99
92
|
|
|
100
93
|
getCurrentTooltipData() {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
const currentTooltip = this.currentTooltip.get()
|
|
95
|
+
if (!currentTooltip) return null
|
|
96
|
+
if (!this.supportsHover() && !currentTooltip.showOnMobile) return null
|
|
97
|
+
return currentTooltip
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private supportsHoverAtom: Atom<boolean> | null = null
|
|
101
|
+
supportsHover() {
|
|
102
|
+
if (!this.supportsHoverAtom) {
|
|
103
|
+
const mediaQuery = window.matchMedia('(hover: hover)')
|
|
104
|
+
const supportsHover = atom('has hover', mediaQuery.matches)
|
|
105
|
+
this.supportsHoverAtom = supportsHover
|
|
106
|
+
mediaQuery.addEventListener('change', (e) => {
|
|
107
|
+
supportsHover.set(e.matches)
|
|
108
|
+
})
|
|
107
109
|
}
|
|
110
|
+
return this.supportsHoverAtom.get()
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
|
|
@@ -133,65 +136,30 @@ export function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderPro
|
|
|
133
136
|
// The singleton tooltip component that renders once
|
|
134
137
|
function TooltipSingleton() {
|
|
135
138
|
const editor = useMaybeEditor()
|
|
136
|
-
const [, forceUpdate] = useState({})
|
|
137
139
|
const [isOpen, setIsOpen] = useState(false)
|
|
138
140
|
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
141
|
const isFirstShowRef = useRef(true)
|
|
143
142
|
const showTimeoutRef = useRef<number | null>(null)
|
|
144
143
|
|
|
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()
|
|
144
|
+
const currentTooltip = useValue(
|
|
145
|
+
'current tooltip',
|
|
146
|
+
() => tooltipManager.getCurrentTooltipData(),
|
|
147
|
+
[]
|
|
148
|
+
)
|
|
159
149
|
|
|
160
150
|
// Update open state and trigger position
|
|
161
151
|
useEffect(() => {
|
|
162
|
-
const shouldBeOpen = Boolean(tooltipData.id && tooltipData.element)
|
|
163
|
-
|
|
164
152
|
// Clear any existing show timeout
|
|
165
153
|
if (showTimeoutRef.current) {
|
|
166
154
|
clearTimeout(showTimeoutRef.current)
|
|
167
155
|
showTimeoutRef.current = null
|
|
168
156
|
}
|
|
169
157
|
|
|
170
|
-
if (
|
|
158
|
+
if (currentTooltip && triggerRef.current) {
|
|
171
159
|
// Position the invisible trigger element over the active element
|
|
172
|
-
const activeRect =
|
|
160
|
+
const activeRect = currentTooltip.targetElement.getBoundingClientRect()
|
|
173
161
|
const trigger = triggerRef.current
|
|
174
162
|
|
|
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
163
|
trigger.style.position = 'fixed'
|
|
196
164
|
trigger.style.left = `${activeRect.left}px`
|
|
197
165
|
trigger.style.top = `${activeRect.top}px`
|
|
@@ -210,18 +178,15 @@ function TooltipSingleton() {
|
|
|
210
178
|
// Subsequent tooltips show immediately
|
|
211
179
|
setIsOpen(true)
|
|
212
180
|
}
|
|
213
|
-
} else
|
|
181
|
+
} else {
|
|
214
182
|
// Hide tooltip immediately
|
|
215
183
|
setIsOpen(false)
|
|
216
|
-
// Reset position tracking when tooltip closes
|
|
217
|
-
previousPositionRef.current = null
|
|
218
|
-
setShouldAnimate(false)
|
|
219
184
|
// Reset first show state after tooltip is hidden
|
|
220
185
|
isFirstShowRef.current = true
|
|
221
186
|
}
|
|
222
|
-
}, [
|
|
187
|
+
}, [editor, currentTooltip])
|
|
223
188
|
|
|
224
|
-
if (!
|
|
189
|
+
if (!currentTooltip) {
|
|
225
190
|
return null
|
|
226
191
|
}
|
|
227
192
|
|
|
@@ -232,14 +197,13 @@ function TooltipSingleton() {
|
|
|
232
197
|
</_Tooltip.Trigger>
|
|
233
198
|
<_Tooltip.Content
|
|
234
199
|
className="tlui-tooltip"
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
sideOffset={tooltipData.sideOffset}
|
|
200
|
+
side={currentTooltip.side}
|
|
201
|
+
sideOffset={currentTooltip.sideOffset}
|
|
238
202
|
avoidCollisions
|
|
239
203
|
collisionPadding={8}
|
|
240
204
|
dir="ltr"
|
|
241
205
|
>
|
|
242
|
-
{
|
|
206
|
+
{currentTooltip.content}
|
|
243
207
|
<_Tooltip.Arrow className="tlui-tooltip__arrow" />
|
|
244
208
|
</_Tooltip.Content>
|
|
245
209
|
</_Tooltip.Root>
|
|
@@ -247,86 +211,98 @@ function TooltipSingleton() {
|
|
|
247
211
|
}
|
|
248
212
|
|
|
249
213
|
/** @public @react */
|
|
250
|
-
export
|
|
251
|
-
children,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
214
|
+
export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(
|
|
215
|
+
({ children, content, side, sideOffset = 5, disabled = false, showOnMobile = false }, ref) => {
|
|
216
|
+
const editor = useMaybeEditor()
|
|
217
|
+
const tooltipId = useRef<string>(uniqueId())
|
|
218
|
+
const hasProvider = useContext(TooltipSingletonContext)
|
|
219
|
+
|
|
220
|
+
const orientationCtx = useTldrawUiOrientation()
|
|
221
|
+
const sideToUse = side ?? orientationCtx.tooltipSide
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
const currentTooltipId = tooltipId.current
|
|
225
|
+
return () => {
|
|
226
|
+
if (hasProvider) {
|
|
227
|
+
tooltipManager.hideTooltip(editor, currentTooltipId, true)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}, [editor, hasProvider])
|
|
260
231
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
232
|
+
// Don't show tooltip if disabled, no content, or UI labels are disabled
|
|
233
|
+
if (disabled || !content) {
|
|
234
|
+
return <>{children}</>
|
|
235
|
+
}
|
|
265
236
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
>
|
|
273
|
-
<_Tooltip.Trigger asChild>{children}</_Tooltip.Trigger>
|
|
274
|
-
<_Tooltip.Content
|
|
275
|
-
className="tlui-tooltip"
|
|
276
|
-
side={side}
|
|
277
|
-
sideOffset={sideOffset}
|
|
278
|
-
avoidCollisions
|
|
279
|
-
collisionPadding={8}
|
|
280
|
-
dir="ltr"
|
|
237
|
+
// Fallback to old behavior if no provider
|
|
238
|
+
if (!hasProvider) {
|
|
239
|
+
return (
|
|
240
|
+
<_Tooltip.Root
|
|
241
|
+
delayDuration={editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS}
|
|
242
|
+
disableHoverableContent
|
|
281
243
|
>
|
|
282
|
-
{
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
244
|
+
<_Tooltip.Trigger asChild ref={ref}>
|
|
245
|
+
{children}
|
|
246
|
+
</_Tooltip.Trigger>
|
|
247
|
+
<_Tooltip.Content
|
|
248
|
+
className="tlui-tooltip"
|
|
249
|
+
side={sideToUse}
|
|
250
|
+
sideOffset={sideOffset}
|
|
251
|
+
avoidCollisions
|
|
252
|
+
collisionPadding={8}
|
|
253
|
+
dir="ltr"
|
|
254
|
+
>
|
|
255
|
+
{content}
|
|
256
|
+
<_Tooltip.Arrow className="tlui-tooltip__arrow" />
|
|
257
|
+
</_Tooltip.Content>
|
|
258
|
+
</_Tooltip.Root>
|
|
259
|
+
)
|
|
260
|
+
}
|
|
291
261
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
262
|
+
const child = React.Children.only(children)
|
|
263
|
+
assert(React.isValidElement(child), 'TldrawUiTooltip children must be a single element')
|
|
264
|
+
|
|
265
|
+
const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
|
|
266
|
+
child.props.onMouseEnter?.(event)
|
|
267
|
+
tooltipManager.showTooltip(
|
|
268
|
+
tooltipId.current,
|
|
269
|
+
content,
|
|
270
|
+
event.currentTarget as HTMLElement,
|
|
271
|
+
sideToUse,
|
|
272
|
+
sideOffset,
|
|
273
|
+
showOnMobile
|
|
274
|
+
)
|
|
275
|
+
}
|
|
302
276
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
277
|
+
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
|
|
278
|
+
child.props.onMouseLeave?.(event)
|
|
279
|
+
tooltipManager.hideTooltip(editor, tooltipId.current)
|
|
280
|
+
}
|
|
307
281
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
282
|
+
const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
|
|
283
|
+
child.props.onFocus?.(event)
|
|
284
|
+
tooltipManager.showTooltip(
|
|
285
|
+
tooltipId.current,
|
|
286
|
+
content,
|
|
287
|
+
event.currentTarget as HTMLElement,
|
|
288
|
+
sideToUse,
|
|
289
|
+
sideOffset,
|
|
290
|
+
showOnMobile
|
|
291
|
+
)
|
|
292
|
+
}
|
|
318
293
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
294
|
+
const handleBlur = (event: React.FocusEvent<HTMLElement>) => {
|
|
295
|
+
child.props.onBlur?.(event)
|
|
296
|
+
tooltipManager.hideTooltip(editor, tooltipId.current)
|
|
297
|
+
}
|
|
323
298
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
299
|
+
const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
|
|
300
|
+
onMouseEnter: handleMouseEnter,
|
|
301
|
+
onMouseLeave: handleMouseLeave,
|
|
302
|
+
onFocus: handleFocus,
|
|
303
|
+
onBlur: handleBlur,
|
|
304
|
+
})
|
|
330
305
|
|
|
331
|
-
|
|
332
|
-
}
|
|
306
|
+
return childrenWithHandlers
|
|
307
|
+
}
|
|
308
|
+
)
|
|
@@ -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
|
}
|