tldraw 3.16.0-canary.1647ca5bba28 → 3.16.0-canary.1e91d2e19e07

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist-cjs/index.d.ts +122 -49
  2. package/dist-cjs/index.js +25 -9
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/shapes/arrow/arrowLabel.js +6 -0
  5. package/dist-cjs/lib/shapes/arrow/arrowLabel.js.map +3 -3
  6. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +2 -1
  7. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  8. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +1 -0
  9. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
  10. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +2 -1
  11. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  12. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +9 -4
  13. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js.map +2 -2
  14. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +255 -316
  15. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
  16. package/dist-cjs/lib/ui/components/{primitives/TldrawUiButtonPicker.js → StylePanel/StylePanelButtonPicker.js} +52 -45
  17. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js.map +7 -0
  18. package/dist-cjs/lib/ui/components/StylePanel/StylePanelContext.js +68 -0
  19. package/dist-cjs/lib/ui/components/StylePanel/StylePanelContext.js.map +7 -0
  20. package/dist-cjs/lib/ui/components/StylePanel/{DoubleDropdownPicker.js → StylePanelDoubleDropdownPicker.js} +23 -22
  21. package/dist-cjs/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.js.map +7 -0
  22. package/dist-cjs/lib/ui/components/StylePanel/{DropdownPicker.js → StylePanelDropdownPicker.js} +23 -20
  23. package/dist-cjs/lib/ui/components/StylePanel/StylePanelDropdownPicker.js.map +7 -0
  24. package/dist-cjs/lib/ui/components/StylePanel/StylePanelSubheading.js +28 -0
  25. package/dist-cjs/lib/ui/components/StylePanel/StylePanelSubheading.js.map +7 -0
  26. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +1 -1
  27. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
  28. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +2 -0
  29. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
  30. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +33 -5
  31. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  32. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +5 -5
  33. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +1 -1
  34. package/dist-cjs/lib/ui/hooks/useTools.js +1 -1
  35. package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
  36. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +2 -2
  37. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +1 -1
  38. package/dist-cjs/lib/ui/version.js +3 -3
  39. package/dist-cjs/lib/ui/version.js.map +1 -1
  40. package/dist-esm/index.d.mts +122 -49
  41. package/dist-esm/index.mjs +49 -17
  42. package/dist-esm/index.mjs.map +2 -2
  43. package/dist-esm/lib/shapes/arrow/arrowLabel.mjs +6 -0
  44. package/dist-esm/lib/shapes/arrow/arrowLabel.mjs.map +3 -3
  45. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +2 -1
  46. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  47. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +1 -0
  48. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  49. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +2 -1
  50. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  51. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs +14 -5
  52. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs.map +2 -2
  53. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +257 -320
  54. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
  55. package/dist-esm/lib/ui/components/{primitives/TldrawUiButtonPicker.mjs → StylePanel/StylePanelButtonPicker.mjs} +54 -43
  56. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs.map +7 -0
  57. package/dist-esm/lib/ui/components/StylePanel/StylePanelContext.mjs +48 -0
  58. package/dist-esm/lib/ui/components/StylePanel/StylePanelContext.mjs.map +7 -0
  59. package/dist-esm/lib/ui/components/StylePanel/{DoubleDropdownPicker.mjs → StylePanelDoubleDropdownPicker.mjs} +20 -19
  60. package/dist-esm/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.mjs.map +7 -0
  61. package/dist-esm/lib/ui/components/StylePanel/{DropdownPicker.mjs → StylePanelDropdownPicker.mjs} +20 -17
  62. package/dist-esm/lib/ui/components/StylePanel/StylePanelDropdownPicker.mjs.map +7 -0
  63. package/dist-esm/lib/ui/components/StylePanel/StylePanelSubheading.mjs +8 -0
  64. package/dist-esm/lib/ui/components/StylePanel/StylePanelSubheading.mjs.map +7 -0
  65. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +1 -1
  66. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
  67. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +2 -0
  68. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
  69. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +34 -5
  70. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  71. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +5 -5
  72. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +1 -1
  73. package/dist-esm/lib/ui/hooks/useTools.mjs +1 -1
  74. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  75. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +2 -2
  76. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +1 -1
  77. package/dist-esm/lib/ui/version.mjs +3 -3
  78. package/dist-esm/lib/ui/version.mjs.map +1 -1
  79. package/package.json +3 -3
  80. package/src/index.ts +36 -13
  81. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +2 -2
  82. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +41 -0
  83. package/src/lib/shapes/arrow/arrowLabel.ts +8 -0
  84. package/src/lib/shapes/frame/FrameShapeUtil.tsx +1 -0
  85. package/src/lib/shapes/geo/GeoShapeUtil.tsx +1 -0
  86. package/src/lib/shapes/note/NoteShapeUtil.tsx +1 -0
  87. package/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx +27 -13
  88. package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +260 -381
  89. package/src/lib/ui/components/{primitives/TldrawUiButtonPicker.tsx → StylePanel/StylePanelButtonPicker.tsx} +63 -50
  90. package/src/lib/ui/components/StylePanel/StylePanelContext.tsx +63 -0
  91. package/src/lib/ui/components/StylePanel/{DoubleDropdownPicker.tsx → StylePanelDoubleDropdownPicker.tsx} +28 -19
  92. package/src/lib/ui/components/StylePanel/StylePanelDropdownPicker.tsx +119 -0
  93. package/src/lib/ui/components/StylePanel/StylePanelSubheading.tsx +9 -0
  94. package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +2 -2
  95. package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +3 -0
  96. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +50 -16
  97. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +6 -6
  98. package/src/lib/ui/hooks/useTools.tsx +1 -1
  99. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +2 -2
  100. package/src/lib/ui/version.ts +3 -3
  101. package/src/lib/ui.css +16 -2
  102. package/src/test/getCulledShapes.test.tsx +71 -2
  103. package/tldraw.css +24 -5
  104. package/dist-cjs/lib/ui/components/StylePanel/DoubleDropdownPicker.js.map +0 -7
  105. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js.map +0 -7
  106. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js.map +0 -7
  107. package/dist-esm/lib/ui/components/StylePanel/DoubleDropdownPicker.mjs.map +0 -7
  108. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +0 -7
  109. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs.map +0 -7
  110. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +0 -110
@@ -4,46 +4,52 @@ import {
4
4
  SharedStyle,
5
5
  StyleProp,
6
6
  TLDefaultColorStyle,
7
- TLDefaultColorTheme,
8
7
  useEditor,
9
8
  } from '@tldraw/editor'
10
9
  import { memo, ReactElement, useMemo, useRef } from 'react'
10
+ import { useDefaultColorTheme } from '../../../shapes/shared/useDefaultColorTheme'
11
11
  import { StyleValuesForUi } from '../../../styles'
12
12
  import { PORTRAIT_BREAKPOINT } from '../../constants'
13
13
  import { useBreakpoint } from '../../context/breakpoints'
14
14
  import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
15
15
  import { useTranslation } from '../../hooks/useTranslation/useTranslation'
16
- import { TldrawUiButtonIcon } from './Button/TldrawUiButtonIcon'
17
- import { TldrawUiToolbarToggleGroup, TldrawUiToolbarToggleItem } from './TldrawUiToolbar'
18
- import { TldrawUiGrid, TldrawUiRow } from './layout'
16
+ import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
17
+ import {
18
+ TldrawUiToolbar,
19
+ TldrawUiToolbarToggleGroup,
20
+ TldrawUiToolbarToggleItem,
21
+ } from '../primitives/TldrawUiToolbar'
22
+ import { TldrawUiGrid, TldrawUiRow } from '../primitives/layout'
23
+ import { useStylePanelContext } from './StylePanelContext'
24
+ import { StylePanelSubheading } from './StylePanelSubheading'
19
25
 
20
26
  /** @public */
21
- export interface TLUiButtonPickerProps<T extends string> {
27
+ export interface StylePanelButtonPickerProps<T extends string> {
22
28
  title: string
23
29
  uiType: string
24
30
  style: StyleProp<T>
25
31
  value: SharedStyle<T>
26
32
  items: StyleValuesForUi<T>
27
- theme: TLDefaultColorTheme
28
- onValueChange(style: StyleProp<T>, value: T): void
33
+ onValueChange?(style: StyleProp<T>, value: T): void
29
34
  onHistoryMark?(id: string): void
30
35
  }
31
36
 
32
37
  /** @public */
33
- export const TldrawUiButtonPicker = memo(function TldrawUiButtonPicker<T extends string>(
34
- props: TLUiButtonPickerProps<T>
38
+ export const StylePanelButtonPicker = memo(function StylePanelButtonPicker<T extends string>(
39
+ props: StylePanelButtonPickerProps<T>
35
40
  ) {
41
+ const ctx = useStylePanelContext()
42
+
36
43
  const {
37
44
  uiType,
38
45
  items,
39
46
  title,
40
47
  style,
41
48
  value,
42
- // columns = clamp(items.length, 2, 4),
43
- onValueChange,
44
- onHistoryMark,
45
- theme,
49
+ onValueChange = ctx.onValueChange,
50
+ onHistoryMark = ctx.onHistoryMark,
46
51
  } = props
52
+ const theme = useDefaultColorTheme()
47
53
  const editor = useEditor()
48
54
  const msg = useTranslation()
49
55
  const breakpoint = useBreakpoint()
@@ -117,43 +123,50 @@ export const TldrawUiButtonPicker = memo(function TldrawUiButtonPicker<T extends
117
123
  }
118
124
  }, [editor, breakpoint, value, onHistoryMark, onValueChange, style])
119
125
 
120
- const Wrapper = items.length > 4 ? TldrawUiGrid : TldrawUiRow
126
+ const Layout = items.length > 4 ? TldrawUiGrid : TldrawUiRow
121
127
 
122
128
  return (
123
- <Wrapper asChild>
124
- <TldrawUiToolbarToggleGroup
125
- data-testid={`style.${uiType}`}
126
- type="single"
127
- value={value.type === 'shared' ? value.value : undefined}
128
- >
129
- {items.map((item) => {
130
- const label = title + ' — ' + msg(`${uiType}-style.${item.value}` as TLUiTranslationKey)
131
- return (
132
- <TldrawUiToolbarToggleItem
133
- type="icon"
134
- key={item.value}
135
- data-id={item.value}
136
- data-testid={`style.${uiType}.${item.value}`}
137
- aria-label={label}
138
- value={item.value}
139
- data-state={value.type === 'shared' && value.value === item.value ? 'on' : 'off'}
140
- data-isactive={value.type === 'shared' && value.value === item.value}
141
- title={label}
142
- style={
143
- style === (DefaultColorStyle as StyleProp<unknown>)
144
- ? { color: getColorValue(theme, item.value as TLDefaultColorStyle, 'solid') }
145
- : undefined
146
- }
147
- onPointerEnter={handleButtonPointerEnter}
148
- onPointerDown={handleButtonPointerDown}
149
- onPointerUp={handleButtonPointerUp}
150
- onClick={handleButtonClick}
151
- >
152
- <TldrawUiButtonIcon icon={item.icon} />
153
- </TldrawUiToolbarToggleItem>
154
- )
155
- })}
156
- </TldrawUiToolbarToggleGroup>
157
- </Wrapper>
129
+ <>
130
+ {ctx.showUiLabels && <StylePanelSubheading>{title}</StylePanelSubheading>}
131
+ <TldrawUiToolbar label={title}>
132
+ <TldrawUiToolbarToggleGroup
133
+ data-testid={`style.${uiType}`}
134
+ type="single"
135
+ value={value.type === 'shared' ? value.value : undefined}
136
+ asChild
137
+ >
138
+ <Layout>
139
+ {items.map((item) => {
140
+ const label =
141
+ title + ' — ' + msg(`${uiType}-style.${item.value}` as TLUiTranslationKey)
142
+ return (
143
+ <TldrawUiToolbarToggleItem
144
+ type="icon"
145
+ key={item.value}
146
+ data-id={item.value}
147
+ data-testid={`style.${uiType}.${item.value}`}
148
+ aria-label={label}
149
+ value={item.value}
150
+ data-state={value.type === 'shared' && value.value === item.value ? 'on' : 'off'}
151
+ data-isactive={value.type === 'shared' && value.value === item.value}
152
+ title={label}
153
+ style={
154
+ style === (DefaultColorStyle as StyleProp<unknown>)
155
+ ? { color: getColorValue(theme, item.value as TLDefaultColorStyle, 'solid') }
156
+ : undefined
157
+ }
158
+ onPointerEnter={handleButtonPointerEnter}
159
+ onPointerDown={handleButtonPointerDown}
160
+ onPointerUp={handleButtonPointerUp}
161
+ onClick={handleButtonClick}
162
+ >
163
+ <TldrawUiButtonIcon icon={item.icon} />
164
+ </TldrawUiToolbarToggleItem>
165
+ )
166
+ })}
167
+ </Layout>
168
+ </TldrawUiToolbarToggleGroup>
169
+ </TldrawUiToolbar>
170
+ </>
158
171
  )
159
- }) as <T extends string>(props: TLUiButtonPickerProps<T>) => ReactElement
172
+ }) as <T extends string>(props: StylePanelButtonPickerProps<T>) => ReactElement
@@ -0,0 +1,63 @@
1
+ import { ReadonlySharedStyleMap, StyleProp, useEditor, useValue } from '@tldraw/editor'
2
+ import { createContext, useCallback, useContext } from 'react'
3
+ import { useUiEvents } from '../../context/events'
4
+
5
+ /** @public */
6
+ export interface StylePanelContext {
7
+ styles: ReadonlySharedStyleMap
8
+ showUiLabels: boolean
9
+ onHistoryMark(id: string): void
10
+ onValueChange<T>(style: StyleProp<T>, value: T): void
11
+ }
12
+ const StylePanelContext = createContext<null | StylePanelContext>(null)
13
+
14
+ /** @public */
15
+ export interface StylePanelContextProviderProps {
16
+ children: React.ReactNode
17
+ styles: ReadonlySharedStyleMap
18
+ }
19
+
20
+ /** @public @react */
21
+ export function StylePanelContextProvider({ children, styles }: StylePanelContextProviderProps) {
22
+ const editor = useEditor()
23
+ const trackEvent = useUiEvents()
24
+
25
+ const onHistoryMark = useCallback((id: string) => editor.markHistoryStoppingPoint(id), [editor])
26
+ const showUiLabels = useValue('showUiLabels', () => editor.user.getShowUiLabels(), [editor])
27
+ const onValueChange = useCallback(
28
+ function <T>(style: StyleProp<T>, value: T) {
29
+ editor.run(() => {
30
+ if (editor.isIn('select')) {
31
+ editor.setStyleForSelectedShapes(style, value)
32
+ }
33
+ editor.setStyleForNextShapes(style, value)
34
+ editor.updateInstanceState({ isChangingStyle: true })
35
+ })
36
+
37
+ trackEvent('set-style', { source: 'style-panel', id: style.id, value: value as string })
38
+ },
39
+ [editor, trackEvent]
40
+ )
41
+
42
+ return (
43
+ <StylePanelContext.Provider
44
+ value={{
45
+ styles: styles,
46
+ showUiLabels,
47
+ onHistoryMark,
48
+ onValueChange,
49
+ }}
50
+ >
51
+ {children}
52
+ </StylePanelContext.Provider>
53
+ )
54
+ }
55
+
56
+ /** @public */
57
+ export function useStylePanelContext() {
58
+ const context = useContext(StylePanelContext)
59
+ if (!context) {
60
+ throw new Error('useStylePanelContext must be used within a StylePanelContextProvider')
61
+ }
62
+ return context
63
+ }
@@ -11,8 +11,10 @@ import {
11
11
  } from '../primitives/TldrawUiPopover'
12
12
  import { TldrawUiToolbar, TldrawUiToolbarButton } from '../primitives/TldrawUiToolbar'
13
13
  import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
14
+ import { useStylePanelContext } from './StylePanelContext'
14
15
 
15
- interface DoubleDropdownPickerProps<T extends string> {
16
+ /** @public */
17
+ export interface StylePanelDoubleDropdownPickerProps<T extends string> {
16
18
  uiTypeA: string
17
19
  uiTypeB: string
18
20
  label: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
@@ -24,23 +26,27 @@ interface DoubleDropdownPickerProps<T extends string> {
24
26
  styleB: StyleProp<T>
25
27
  valueA: SharedStyle<T>
26
28
  valueB: SharedStyle<T>
27
- onValueChange(style: StyleProp<T>, value: T): void
29
+ onValueChange?(style: StyleProp<T>, value: T): void
28
30
  }
29
31
 
30
- function DoubleDropdownPickerInner<T extends string>({
31
- label,
32
- uiTypeA,
33
- uiTypeB,
34
- labelA,
35
- labelB,
36
- itemsA,
37
- itemsB,
38
- styleA,
39
- styleB,
40
- valueA,
41
- valueB,
42
- onValueChange,
43
- }: DoubleDropdownPickerProps<T>) {
32
+ function DoubleDropdownPickerInner<T extends string>(
33
+ props: StylePanelDoubleDropdownPickerProps<T>
34
+ ) {
35
+ const ctx = useStylePanelContext()
36
+ const {
37
+ label,
38
+ uiTypeA,
39
+ uiTypeB,
40
+ labelA,
41
+ labelB,
42
+ itemsA,
43
+ itemsB,
44
+ styleA,
45
+ styleB,
46
+ valueA,
47
+ valueB,
48
+ onValueChange = ctx.onValueChange,
49
+ } = props
44
50
  const editor = useEditor()
45
51
  const msg = useTranslation()
46
52
  const [isOpenA, setIsOpenA] = React.useState(false)
@@ -155,6 +161,9 @@ function DoubleDropdownPickerInner<T extends string>({
155
161
  }
156
162
 
157
163
  // need to memo like this to get generics
158
- export const DoubleDropdownPicker = React.memo(
159
- DoubleDropdownPickerInner
160
- ) as typeof DoubleDropdownPickerInner
164
+ /** @public @react */
165
+ export const StylePanelDoubleDropdownPicker = React.memo(DoubleDropdownPickerInner) as <
166
+ T extends string,
167
+ >(
168
+ props: StylePanelDoubleDropdownPickerProps<T>
169
+ ) => React.JSX.Element
@@ -0,0 +1,119 @@
1
+ import { SharedStyle, StyleProp, tlmenus, useEditor } from '@tldraw/editor'
2
+ import * as React from 'react'
3
+ import { StyleValuesForUi } from '../../../styles'
4
+ import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
5
+ import { useTranslation } from '../../hooks/useTranslation/useTranslation'
6
+ import { TLUiIconType } from '../../icon-types'
7
+ import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
8
+ import { TldrawUiButtonLabel } from '../primitives/Button/TldrawUiButtonLabel'
9
+ import {
10
+ TldrawUiPopover,
11
+ TldrawUiPopoverContent,
12
+ TldrawUiPopoverTrigger,
13
+ } from '../primitives/TldrawUiPopover'
14
+ import { TldrawUiToolbar, TldrawUiToolbarButton } from '../primitives/TldrawUiToolbar'
15
+ import { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'
16
+ import { useStylePanelContext } from './StylePanelContext'
17
+
18
+ /** @public */
19
+ export interface StylePanelDropdownPickerProps<T extends string> {
20
+ id: string
21
+ label?: TLUiTranslationKey | Exclude<string, TLUiTranslationKey>
22
+ uiType: string
23
+ stylePanelType: string
24
+ style: StyleProp<T>
25
+ value: SharedStyle<T>
26
+ items: StyleValuesForUi<T>
27
+ type: 'icon' | 'tool' | 'menu'
28
+ onValueChange?(style: StyleProp<T>, value: T): void
29
+ }
30
+
31
+ function DropdownPickerInner<T extends string>(props: StylePanelDropdownPickerProps<T>) {
32
+ const ctx = useStylePanelContext()
33
+ const {
34
+ id,
35
+ label,
36
+ uiType,
37
+ stylePanelType,
38
+ style,
39
+ items,
40
+ type,
41
+ value,
42
+ onValueChange = ctx.onValueChange,
43
+ } = props
44
+ const msg = useTranslation()
45
+ const editor = useEditor()
46
+ const [isOpen, setIsOpen] = React.useState(false)
47
+
48
+ const icon = React.useMemo(
49
+ () => items.find((item) => value.type === 'shared' && item.value === value.value)?.icon,
50
+ [items, value]
51
+ )
52
+
53
+ const stylePanelName = msg(`style-panel.${stylePanelType}` as TLUiTranslationKey)
54
+
55
+ const titleStr =
56
+ value.type === 'mixed'
57
+ ? msg('style-panel.mixed')
58
+ : stylePanelName + ' — ' + msg(`${uiType}-style.${value.value}` as TLUiTranslationKey)
59
+ const labelStr = label ? msg(label) : ''
60
+
61
+ const popoverId = `style panel ${id}`
62
+ return (
63
+ <TldrawUiToolbar label={stylePanelName}>
64
+ <TldrawUiPopover
65
+ id={popoverId}
66
+ open={isOpen}
67
+ onOpenChange={setIsOpen}
68
+ className="tlui-style-panel__dropdown-picker"
69
+ >
70
+ <TldrawUiPopoverTrigger>
71
+ <TldrawUiToolbarButton
72
+ type={type}
73
+ data-testid={`style.${uiType}`}
74
+ data-direction="left"
75
+ title={titleStr}
76
+ >
77
+ {labelStr && <TldrawUiButtonLabel>{labelStr}</TldrawUiButtonLabel>}
78
+ <TldrawUiButtonIcon icon={(icon as TLUiIconType) ?? 'mixed'} />
79
+ </TldrawUiToolbarButton>
80
+ </TldrawUiPopoverTrigger>
81
+ <TldrawUiPopoverContent side="left" align="center">
82
+ <TldrawUiToolbar orientation={items.length > 4 ? 'grid' : 'horizontal'} label={labelStr}>
83
+ <TldrawUiMenuContextProvider type="icons" sourceId="style-panel">
84
+ {items.map((item) => {
85
+ return (
86
+ <TldrawUiToolbarButton
87
+ key={item.value}
88
+ type="icon"
89
+ data-testid={`style.${uiType}.${item.value}`}
90
+ title={
91
+ stylePanelName +
92
+ ' — ' +
93
+ msg(`${uiType}-style.${item.value}` as TLUiTranslationKey)
94
+ }
95
+ isActive={icon === item.icon}
96
+ onClick={() => {
97
+ ctx.onHistoryMark('select style dropdown item')
98
+ onValueChange(style, item.value)
99
+ tlmenus.deleteOpenMenu(popoverId, editor.contextId)
100
+ setIsOpen(false)
101
+ }}
102
+ >
103
+ <TldrawUiButtonIcon icon={item.icon} />
104
+ </TldrawUiToolbarButton>
105
+ )
106
+ })}
107
+ </TldrawUiMenuContextProvider>
108
+ </TldrawUiToolbar>
109
+ </TldrawUiPopoverContent>
110
+ </TldrawUiPopover>
111
+ </TldrawUiToolbar>
112
+ )
113
+ }
114
+
115
+ // need to export like this to get generics
116
+ /** @public @react */
117
+ export const StylePanelDropdownPicker = React.memo(DropdownPickerInner) as <T extends string>(
118
+ props: StylePanelDropdownPickerProps<T>
119
+ ) => React.JSX.Element
@@ -0,0 +1,9 @@
1
+ /** @public */
2
+ export interface StylePanelSubheadingProps {
3
+ children: React.ReactNode
4
+ }
5
+
6
+ /** @public @react */
7
+ export function StylePanelSubheading({ children }: StylePanelSubheadingProps) {
8
+ return <h3 className="tlui-style-panel__subheading">{children}</h3>
9
+ }
@@ -13,7 +13,7 @@ export interface TLUiSliderProps {
13
13
  label: string
14
14
  title: string
15
15
  onValueChange(value: number): void
16
- onHistoryMark(id: string): void
16
+ onHistoryMark?(id: string): void
17
17
  'data-testid'?: string
18
18
  ariaValueModifier?: number
19
19
  }
@@ -53,7 +53,7 @@ export const TldrawUiSlider = React.forwardRef<HTMLDivElement, TLUiSliderProps>(
53
53
 
54
54
  const handlePointerDown = useCallback(() => {
55
55
  tooltipManager.hideAllTooltips()
56
- onHistoryMark('click slider')
56
+ onHistoryMark?.('click slider')
57
57
  }, [onHistoryMark])
58
58
 
59
59
  // N.B. This is a bit silly. The Radix slider auto-focuses which
@@ -94,6 +94,7 @@ export interface TLUiToolbarToggleGroupProps extends React.HTMLAttributes<HTMLDi
94
94
  // TODO: fix up this type later
95
95
  defaultValue?: any
96
96
  type: 'single' | 'multiple'
97
+ asChild?: boolean
97
98
  }
98
99
 
99
100
  /** @public @react */
@@ -101,10 +102,12 @@ export const TldrawUiToolbarToggleGroup = ({
101
102
  children,
102
103
  className,
103
104
  type,
105
+ asChild,
104
106
  ...props
105
107
  }: TLUiToolbarToggleGroupProps) => {
106
108
  return (
107
109
  <_Toolbar.ToggleGroup
110
+ asChild={asChild}
108
111
  type={type}
109
112
  {...props}
110
113
  // TODO: this fixes a bug in Radix until they fix it.
@@ -6,6 +6,7 @@ import React, {
6
6
  ReactNode,
7
7
  useContext,
8
8
  useEffect,
9
+ useLayoutEffect,
9
10
  useRef,
10
11
  useState,
11
12
  } from 'react'
@@ -24,18 +25,20 @@ export interface TldrawUiTooltipProps {
24
25
  delayDuration?: number
25
26
  }
26
27
 
28
+ interface CurrentTooltip {
29
+ id: string
30
+ content: ReactNode
31
+ side: 'top' | 'right' | 'bottom' | 'left'
32
+ sideOffset: number
33
+ showOnMobile: boolean
34
+ targetElement: HTMLElement
35
+ delayDuration: number
36
+ }
37
+
27
38
  // Singleton tooltip manager
28
39
  class TooltipManager {
29
40
  private static instance: TooltipManager | null = null
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
38
- } | null>('current tooltip', null)
41
+ private currentTooltip = atom<CurrentTooltip | null>('current tooltip', null)
39
42
  private destroyTimeoutId: number | null = null
40
43
 
41
44
  static getInstance(): TooltipManager {
@@ -72,6 +75,15 @@ class TooltipManager {
72
75
  })
73
76
  }
74
77
 
78
+ updateCurrentTooltip(tooltipId: string, update: (tooltip: CurrentTooltip) => CurrentTooltip) {
79
+ this.currentTooltip.update((tooltip) => {
80
+ if (tooltip?.id === tooltipId) {
81
+ return update(tooltip)
82
+ }
83
+ return tooltip
84
+ })
85
+ }
86
+
75
87
  hideTooltip(editor: Editor | null, tooltipId: string, instant: boolean = false) {
76
88
  const hide = () => {
77
89
  // Only hide if this is the current tooltip
@@ -142,6 +154,7 @@ function TooltipSingleton() {
142
154
  const [isOpen, setIsOpen] = useState(false)
143
155
  const triggerRef = useRef<HTMLDivElement>(null)
144
156
  const isFirstShowRef = useRef(true)
157
+ const editor = useMaybeEditor()
145
158
 
146
159
  const currentTooltip = useValue(
147
160
  'current tooltip',
@@ -149,6 +162,15 @@ function TooltipSingleton() {
149
162
  []
150
163
  )
151
164
 
165
+ const cameraState = useValue('camera state', () => editor?.getCameraState(), [editor])
166
+
167
+ // Hide tooltip when camera is moving (panning/zooming)
168
+ useEffect(() => {
169
+ if (cameraState === 'moving' && isOpen && currentTooltip) {
170
+ tooltipManager.hideTooltip(editor, currentTooltip.id, true)
171
+ }
172
+ }, [cameraState, isOpen, currentTooltip, editor])
173
+
152
174
  // Update open state and trigger position
153
175
  useEffect(() => {
154
176
  let timer: ReturnType<typeof setTimeout> | null = null
@@ -231,12 +253,11 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
231
253
  const editor = useMaybeEditor()
232
254
  const tooltipId = useRef<string>(uniqueId())
233
255
  const hasProvider = useContext(TooltipSingletonContext)
256
+ const showUiLabels = useValue('showUiLabels', () => editor?.user.getShowUiLabels(), [editor])
234
257
 
235
258
  const orientationCtx = useTldrawUiOrientation()
236
259
  const sideToUse = side ?? orientationCtx.tooltipSide
237
260
 
238
- const camera = useValue('camera', () => editor?.getCamera(), [])
239
-
240
261
  useEffect(() => {
241
262
  const currentTooltipId = tooltipId.current
242
263
  return () => {
@@ -246,17 +267,30 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
246
267
  }
247
268
  }, [editor, hasProvider])
248
269
 
249
- useEffect(() => {
250
- tooltipManager.hideTooltip(editor, tooltipId.current, true)
251
- }, [editor, camera])
270
+ useLayoutEffect(() => {
271
+ if (hasProvider && tooltipManager.getCurrentTooltipData()?.id === tooltipId.current) {
272
+ tooltipManager.updateCurrentTooltip(tooltipId.current, (tooltip) => ({
273
+ ...tooltip,
274
+ content,
275
+ side: sideToUse,
276
+ sideOffset,
277
+ showOnMobile,
278
+ }))
279
+ }
280
+ }, [content, sideToUse, sideOffset, showOnMobile, hasProvider])
252
281
 
253
282
  // Don't show tooltip if disabled, no content, or UI labels are disabled
254
283
  if (disabled || !content) {
255
284
  return <>{children}</>
256
285
  }
257
286
 
258
- const delayDurationToUse =
259
- delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
287
+ let delayDurationToUse
288
+ if (showUiLabels) {
289
+ delayDurationToUse = 0
290
+ } else {
291
+ delayDurationToUse =
292
+ delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
293
+ }
260
294
 
261
295
  // Fallback to old behavior if no provider
262
296
  if (!hasProvider) {
@@ -213,7 +213,7 @@ export function TldrawUiMenuItem<
213
213
  icon={icon}
214
214
  onSelect={onSelect}
215
215
  onDragStart={onDragStart}
216
- labelToUse={labelToUse}
216
+ labelStr={labelStr}
217
217
  titleStr={titleStr}
218
218
  disabled={disabled}
219
219
  isSelected={isSelected}
@@ -247,7 +247,7 @@ export function TldrawUiMenuItem<
247
247
  icon={icon}
248
248
  onSelect={onSelect}
249
249
  onDragStart={onDragStart}
250
- labelToUse={labelToUse}
250
+ labelStr={labelStr}
251
251
  titleStr={titleStr}
252
252
  disabled={disabled}
253
253
  isSelected={isSelected}
@@ -392,7 +392,7 @@ function useDraggableEvents(
392
392
 
393
393
  function DraggableToolbarButton({
394
394
  id,
395
- labelToUse,
395
+ labelStr,
396
396
  titleStr,
397
397
  disabled,
398
398
  isSelected,
@@ -403,7 +403,7 @@ function DraggableToolbarButton({
403
403
  }: {
404
404
  id: string
405
405
  disabled: boolean
406
- labelToUse?: string
406
+ labelStr?: string
407
407
  titleStr?: string
408
408
  isSelected?: boolean
409
409
  icon: TLUiMenuItemProps['icon']
@@ -416,7 +416,7 @@ function DraggableToolbarButton({
416
416
  if (overflow) {
417
417
  return (
418
418
  <TldrawUiToolbarButton
419
- aria-label={labelToUse}
419
+ aria-label={labelStr}
420
420
  aria-pressed={isSelected ? 'true' : 'false'}
421
421
  isActive={isSelected}
422
422
  className="tlui-button-grid__button"
@@ -434,7 +434,7 @@ function DraggableToolbarButton({
434
434
 
435
435
  return (
436
436
  <TldrawUiToolbarButton
437
- aria-label={labelToUse}
437
+ aria-label={labelStr}
438
438
  aria-pressed={isSelected ? 'true' : 'false'}
439
439
  data-testid={`tools.${id}`}
440
440
  data-value={id}
@@ -176,7 +176,7 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
176
176
  editor.createShape({
177
177
  id,
178
178
  type: 'arrow',
179
- props: { start: { x: 0, y: 0 }, end: { x: 200, y: 0 } },
179
+ props: { start: { x: 0, y: 200 }, end: { x: 200, y: 0 } },
180
180
  }),
181
181
  })
182
182
  trackEvent('drag-tool', { source, id: 'arrow' })
@@ -92,9 +92,9 @@ export const DEFAULT_TRANSLATION = {
92
92
  'action.toggle-wrap-mode': 'Toggle Select on wrap',
93
93
  'action.toggle-reduce-motion.menu': 'Reduce motion',
94
94
  'action.toggle-reduce-motion': 'Toggle reduce motion',
95
- 'action.toggle-keyboard-shortcuts.menu': 'Keyboard shortcuts',
95
+ 'action.toggle-keyboard-shortcuts.menu': 'Enable keyboard shortcuts',
96
96
  'action.toggle-keyboard-shortcuts': 'Toggle keyboard shortcuts',
97
- 'action.toggle-ui-labels.menu': 'UI labels',
97
+ 'action.toggle-ui-labels.menu': 'Enable UI labels',
98
98
  'action.toggle-ui-labels': 'Toggle UI labels',
99
99
  'action.toggle-edge-scrolling.menu': 'Edge scrolling',
100
100
  'action.toggle-edge-scrolling': 'Toggle edge scrolling',
@@ -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.1647ca5bba28'
4
+ export const version = '3.16.0-canary.1e91d2e19e07'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-09-02T07:48:08.981Z',
8
- patch: '2025-09-02T07:48:08.981Z',
7
+ minor: '2025-09-12T17:16:34.637Z',
8
+ patch: '2025-09-12T17:16:34.637Z',
9
9
  }