tldraw 3.16.0-canary.d04b7fc312b4 → 3.16.0-canary.d3f0c2d5313c

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 (204) hide show
  1. package/dist-cjs/index.d.ts +94 -2
  2. package/dist-cjs/index.js +9 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/Tldraw.js +12 -2
  5. package/dist-cjs/lib/Tldraw.js.map +2 -2
  6. package/dist-cjs/lib/defaultExternalContentHandlers.js +5 -4
  7. package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
  8. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +6 -0
  9. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  10. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +3 -0
  11. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js.map +2 -2
  12. package/dist-cjs/lib/shapes/shared/freehand/svg.js.map +2 -2
  13. package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js +25 -1
  14. package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js.map +2 -2
  15. package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js +12 -0
  16. package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js.map +2 -2
  17. package/dist-cjs/lib/ui/TldrawUi.js +13 -12
  18. package/dist-cjs/lib/ui/TldrawUi.js.map +2 -2
  19. package/dist-cjs/lib/ui/assetUrls.js +13 -10
  20. package/dist-cjs/lib/ui/assetUrls.js.map +2 -2
  21. package/dist-cjs/lib/ui/components/{FollowingIndicator.js → DefaultFollowingIndicator.js} +6 -6
  22. package/dist-cjs/lib/ui/components/DefaultFollowingIndicator.js.map +7 -0
  23. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +6 -6
  24. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +1 -1
  25. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js +1 -1
  26. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js.map +2 -2
  27. package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js +3 -2
  28. package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js.map +2 -2
  29. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js +38 -9
  30. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js.map +2 -2
  31. package/dist-cjs/lib/ui/components/Toolbar/DefaultVideoToolbarContent.js +15 -3
  32. package/dist-cjs/lib/ui/components/Toolbar/DefaultVideoToolbarContent.js.map +2 -2
  33. package/dist-cjs/lib/ui/components/Toolbar/LinkEditor.js +3 -3
  34. package/dist-cjs/lib/ui/components/Toolbar/LinkEditor.js.map +2 -2
  35. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +10 -1
  36. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js.map +2 -2
  37. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +17 -4
  38. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
  39. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +49 -40
  40. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  41. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +2 -1
  42. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  43. package/dist-cjs/lib/ui/context/actions.js +16 -2
  44. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  45. package/dist-cjs/lib/ui/context/components.js +2 -0
  46. package/dist-cjs/lib/ui/context/components.js.map +2 -2
  47. package/dist-cjs/lib/ui/context/events.js.map +1 -1
  48. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  49. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +2 -0
  50. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  51. package/dist-cjs/lib/ui/kbd-utils.js +9 -3
  52. package/dist-cjs/lib/ui/kbd-utils.js.map +2 -2
  53. package/dist-cjs/lib/ui/version.js +3 -3
  54. package/dist-cjs/lib/ui/version.js.map +1 -1
  55. package/dist-esm/index.d.mts +94 -2
  56. package/dist-esm/index.mjs +11 -2
  57. package/dist-esm/index.mjs.map +2 -2
  58. package/dist-esm/lib/Tldraw.mjs +14 -4
  59. package/dist-esm/lib/Tldraw.mjs.map +2 -2
  60. package/dist-esm/lib/defaultExternalContentHandlers.mjs +5 -4
  61. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  62. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +6 -0
  63. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  64. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +3 -0
  65. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
  66. package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
  67. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs +26 -1
  68. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs.map +2 -2
  69. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs +13 -0
  70. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs.map +2 -2
  71. package/dist-esm/lib/ui/TldrawUi.mjs +13 -12
  72. package/dist-esm/lib/ui/TldrawUi.mjs.map +2 -2
  73. package/dist-esm/lib/ui/assetUrls.mjs +13 -10
  74. package/dist-esm/lib/ui/assetUrls.mjs.map +2 -2
  75. package/dist-esm/lib/ui/components/{FollowingIndicator.mjs → DefaultFollowingIndicator.mjs} +3 -3
  76. package/dist-esm/lib/ui/components/DefaultFollowingIndicator.mjs.map +7 -0
  77. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +6 -6
  78. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +1 -1
  79. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs +1 -1
  80. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +2 -2
  81. package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs +3 -2
  82. package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs.map +2 -2
  83. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs +38 -9
  84. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs.map +2 -2
  85. package/dist-esm/lib/ui/components/Toolbar/DefaultVideoToolbarContent.mjs +15 -3
  86. package/dist-esm/lib/ui/components/Toolbar/DefaultVideoToolbarContent.mjs.map +2 -2
  87. package/dist-esm/lib/ui/components/Toolbar/LinkEditor.mjs +3 -3
  88. package/dist-esm/lib/ui/components/Toolbar/LinkEditor.mjs.map +2 -2
  89. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs +10 -1
  90. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs.map +2 -2
  91. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +17 -4
  92. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
  93. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +49 -40
  94. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  95. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +2 -1
  96. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  97. package/dist-esm/lib/ui/context/actions.mjs +16 -2
  98. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  99. package/dist-esm/lib/ui/context/components.mjs +2 -0
  100. package/dist-esm/lib/ui/context/components.mjs.map +2 -2
  101. package/dist-esm/lib/ui/context/events.mjs.map +1 -1
  102. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +2 -0
  103. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  104. package/dist-esm/lib/ui/kbd-utils.mjs +9 -3
  105. package/dist-esm/lib/ui/kbd-utils.mjs.map +2 -2
  106. package/dist-esm/lib/ui/version.mjs +3 -3
  107. package/dist-esm/lib/ui/version.mjs.map +1 -1
  108. package/package.json +11 -34
  109. package/src/index.ts +6 -1
  110. package/src/lib/Tldraw.tsx +15 -2
  111. package/src/lib/defaultExternalContentHandlers.ts +12 -4
  112. package/src/lib/shapes/arrow/ArrowShapeOptions.test.ts +2 -1
  113. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +4 -3
  114. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +7 -6
  115. package/src/lib/shapes/draw/DrawShapeTool.test.ts +0 -5
  116. package/src/lib/shapes/frame/FrameShapeUtil.tsx +8 -0
  117. package/src/lib/shapes/image/ImageShapeUtil.tsx +3 -0
  118. package/src/lib/shapes/line/LineShapeUtil.test.tsx +4 -3
  119. package/src/lib/shapes/line/__snapshots__/LineShapeUtil.test.tsx.snap +2 -2
  120. package/src/lib/shapes/shared/freehand/svg.ts +2 -0
  121. package/src/lib/shapes/text/TextShapeTool.test.ts +6 -5
  122. package/src/lib/tools/EraserTool/childStates/Erasing.ts +34 -1
  123. package/src/lib/tools/EraserTool/childStates/Pointing.ts +20 -0
  124. package/src/lib/ui/TldrawUi.tsx +16 -10
  125. package/src/lib/ui/assetUrls.ts +13 -10
  126. package/src/lib/ui/components/{FollowingIndicator.tsx → DefaultFollowingIndicator.tsx} +2 -1
  127. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +6 -6
  128. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +1 -1
  129. package/src/lib/ui/components/Toolbar/AltTextEditor.tsx +4 -3
  130. package/src/lib/ui/components/Toolbar/DefaultImageToolbarContent.tsx +32 -15
  131. package/src/lib/ui/components/Toolbar/DefaultVideoToolbarContent.tsx +12 -4
  132. package/src/lib/ui/components/Toolbar/LinkEditor.tsx +5 -5
  133. package/src/lib/ui/components/primitives/TldrawUiContextualToolbar.tsx +6 -1
  134. package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +50 -30
  135. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +43 -20
  136. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +3 -2
  137. package/src/lib/ui/context/actions.tsx +16 -2
  138. package/src/lib/ui/context/components.tsx +3 -0
  139. package/src/lib/ui/context/events.tsx +1 -1
  140. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +2 -0
  141. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +2 -0
  142. package/src/lib/ui/kbd-utils.ts +10 -3
  143. package/src/lib/ui/version.ts +3 -3
  144. package/src/lib/ui.css +3 -0
  145. package/src/lib/utils/excalidraw/__snapshots__/putExcalidrawContent.test.tsx.snap +5 -5
  146. package/src/lib/utils/tldr/__snapshots__/buildFromV1Document.test.ts.snap +4 -4
  147. package/src/test/A11y.test.tsx +3 -2
  148. package/src/test/ClickManager.test.ts +7 -6
  149. package/src/test/Editor.test.tsx +20 -19
  150. package/src/test/EraserTool.test.ts +184 -13
  151. package/src/test/HandTool.test.ts +10 -9
  152. package/src/test/HighlightShape.test.ts +2 -1
  153. package/src/test/SelectTool.test.ts +3 -2
  154. package/src/test/TLUserPreferences.test.ts +4 -3
  155. package/src/test/TestEditor.ts +13 -15
  156. package/src/test/TldrawEditor.test.tsx +11 -10
  157. package/src/test/ZoomTool.test.ts +7 -6
  158. package/src/test/__snapshots__/drawing.test.ts.snap +2 -2
  159. package/src/test/__snapshots__/groups.test.tsx.snap +6 -6
  160. package/src/test/__snapshots__/resizing.test.ts.snap +2 -2
  161. package/src/test/arrows-megabus.test.tsx +5 -4
  162. package/src/test/bindings.test.tsx +24 -37
  163. package/src/test/bookmark-shapes.test.ts +1 -8
  164. package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +23 -7
  165. package/src/test/commands/__snapshots__/packShapes.test.ts.snap +8 -8
  166. package/src/test/commands/__snapshots__/zoomToFit.test.ts.snap +2 -2
  167. package/src/test/commands/alignShapes.test.tsx +25 -24
  168. package/src/test/commands/animationSpeed.test.ts +2 -1
  169. package/src/test/commands/centerOnPoint.test.ts +3 -2
  170. package/src/test/commands/clipboard.test.ts +3 -2
  171. package/src/test/commands/createShapes.test.ts +2 -1
  172. package/src/test/commands/deleteShapes.test.ts +2 -1
  173. package/src/test/commands/distributeShapes.test.tsx +11 -10
  174. package/src/test/commands/getSvgString.test.ts +2 -1
  175. package/src/test/commands/packShapes.test.ts +5 -4
  176. package/src/test/commands/resizeShape.test.ts +2 -1
  177. package/src/test/commands/rotateShapes.test.ts +7 -6
  178. package/src/test/commands/setCamera.test.ts +4 -3
  179. package/src/test/commands/setCurrentPage.test.ts +3 -2
  180. package/src/test/commands/stackShapes.test.ts +11 -10
  181. package/src/test/commands/stretch.test.tsx +13 -12
  182. package/src/test/createDeepLink.test.tsx +2 -1
  183. package/src/test/cropping.test.ts +3 -2
  184. package/src/test/custom-clipping.test.ts +436 -0
  185. package/src/test/drawing.test.ts +2 -1
  186. package/src/test/flipShapes.test.ts +4 -3
  187. package/src/test/frames.test.ts +25 -24
  188. package/src/test/getCulledShapes.test.tsx +3 -2
  189. package/src/test/groups.test.tsx +1 -1
  190. package/src/test/handleDeepLink.test.tsx +2 -1
  191. package/src/test/maxShapes.test.ts +3 -2
  192. package/src/test/modifiers.test.ts +5 -4
  193. package/src/test/navigation.test.ts +12 -11
  194. package/src/test/panning.test.ts +2 -1
  195. package/src/test/perf/perf.test.ts +2 -1
  196. package/src/test/registerDeepLinkListener.test.tsx +10 -9
  197. package/src/test/resizing.test.ts +39 -38
  198. package/src/test/select.test.tsx +4 -3
  199. package/src/test/selection-omnibus.test.ts +11 -10
  200. package/src/test/shapeutils.test.ts +4 -3
  201. package/src/test/translating.test.ts +9 -8
  202. package/tldraw.css +11 -0
  203. package/dist-cjs/lib/ui/components/FollowingIndicator.js.map +0 -7
  204. package/dist-esm/lib/ui/components/FollowingIndicator.mjs.map +0 -7
@@ -1,7 +1,9 @@
1
+ import { tltime } from '@tldraw/editor'
1
2
  import { Slider as _Slider } from 'radix-ui'
2
3
  import React, { useCallback, useEffect, useState } from 'react'
3
4
  import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
4
5
  import { useTranslation } from '../../hooks/useTranslation/useTranslation'
6
+ import { TldrawUiTooltip, tooltipManager } from './TldrawUiTooltip'
5
7
 
6
8
  /** @public */
7
9
  export interface TLUiSliderProps {
@@ -32,6 +34,7 @@ export const TldrawUiSlider = React.forwardRef<HTMLDivElement, TLUiSliderProps>(
32
34
  ref
33
35
  ) {
34
36
  const msg = useTranslation()
37
+ const [titleAndLabel, setTitleAndLabel] = useState('')
35
38
 
36
39
  // XXX: Radix starts out our slider with a tabIndex of 0
37
40
  // This causes some tab focusing issues, most prevelant in MobileStylePanel,
@@ -49,9 +52,25 @@ export const TldrawUiSlider = React.forwardRef<HTMLDivElement, TLUiSliderProps>(
49
52
  )
50
53
 
51
54
  const handlePointerDown = useCallback(() => {
55
+ tooltipManager.hideAllTooltips()
52
56
  onHistoryMark('click slider')
53
57
  }, [onHistoryMark])
54
58
 
59
+ // N.B. This is a bit silly. The Radix slider auto-focuses which
60
+ // triggers TldrawUiTooltip handleFocus when we dbl-click to edit an image,
61
+ // which in turn makes the tooltip display prematurely.
62
+ // This makes it wait until we've focused to show the tooltip.
63
+ useEffect(() => {
64
+ const timeout = tltime.setTimeout(
65
+ 'set title and label',
66
+ () => {
67
+ setTitleAndLabel(title + ' — ' + msg(label as TLUiTranslationKey))
68
+ },
69
+ 0
70
+ )
71
+ return () => clearTimeout(timeout)
72
+ }, [label, msg, title])
73
+
55
74
  // N.B. Annoying. For a11y purposes, we need Tab to work.
56
75
  // For some reason, Radix has some custom behavior here
57
76
  // that interferes with tabbing past the slider and then
@@ -64,36 +83,37 @@ export const TldrawUiSlider = React.forwardRef<HTMLDivElement, TLUiSliderProps>(
64
83
 
65
84
  return (
66
85
  <div className="tlui-slider__container">
67
- <_Slider.Root
68
- data-testid={testId}
69
- className="tlui-slider"
70
- dir="ltr"
71
- min={min ?? 0}
72
- max={steps}
73
- step={1}
74
- value={value !== null ? [value] : undefined}
75
- onPointerDown={handlePointerDown}
76
- onValueChange={handleValueChange}
77
- onKeyDownCapture={handleKeyEvent}
78
- onKeyUpCapture={handleKeyEvent}
79
- title={title + ' — ' + msg(label as TLUiTranslationKey)}
80
- >
81
- <_Slider.Track className="tlui-slider__track" dir="ltr">
82
- {value !== null && <_Slider.Range className="tlui-slider__range" dir="ltr" />}
83
- </_Slider.Track>
84
- {value !== null && (
85
- <_Slider.Thumb
86
- aria-valuemin={(min ?? 0) * ariaValueModifier}
87
- aria-valuenow={value * ariaValueModifier}
88
- aria-valuemax={steps * ariaValueModifier}
89
- aria-label={title + ' — ' + msg(label as TLUiTranslationKey)}
90
- className="tlui-slider__thumb"
91
- dir="ltr"
92
- ref={ref}
93
- tabIndex={tabIndex}
94
- />
95
- )}
96
- </_Slider.Root>
86
+ <TldrawUiTooltip content={titleAndLabel}>
87
+ <_Slider.Root
88
+ data-testid={testId}
89
+ className="tlui-slider"
90
+ dir="ltr"
91
+ min={min ?? 0}
92
+ max={steps}
93
+ step={1}
94
+ value={value !== null ? [value] : undefined}
95
+ onPointerDown={handlePointerDown}
96
+ onValueChange={handleValueChange}
97
+ onKeyDownCapture={handleKeyEvent}
98
+ onKeyUpCapture={handleKeyEvent}
99
+ >
100
+ <_Slider.Track className="tlui-slider__track" dir="ltr">
101
+ {value !== null && <_Slider.Range className="tlui-slider__range" dir="ltr" />}
102
+ </_Slider.Track>
103
+ {value !== null && (
104
+ <_Slider.Thumb
105
+ aria-valuemin={(min ?? 0) * ariaValueModifier}
106
+ aria-valuenow={value * ariaValueModifier}
107
+ aria-valuemax={steps * ariaValueModifier}
108
+ aria-label={titleAndLabel}
109
+ className="tlui-slider__thumb"
110
+ dir="ltr"
111
+ ref={ref}
112
+ tabIndex={tabIndex}
113
+ />
114
+ )}
115
+ </_Slider.Root>
116
+ </TldrawUiTooltip>
97
117
  </div>
98
118
  )
99
119
  })
@@ -21,6 +21,7 @@ export interface TldrawUiTooltipProps {
21
21
  sideOffset?: number
22
22
  disabled?: boolean
23
23
  showOnMobile?: boolean
24
+ delayDuration?: number
24
25
  }
25
26
 
26
27
  // Singleton tooltip manager
@@ -33,6 +34,7 @@ class TooltipManager {
33
34
  sideOffset: number
34
35
  showOnMobile: boolean
35
36
  targetElement: HTMLElement
37
+ delayDuration: number
36
38
  } | null>('current tooltip', null)
37
39
  private destroyTimeoutId: number | null = null
38
40
 
@@ -49,7 +51,8 @@ class TooltipManager {
49
51
  targetElement: HTMLElement,
50
52
  side: 'top' | 'right' | 'bottom' | 'left',
51
53
  sideOffset: number,
52
- showOnMobile: boolean
54
+ showOnMobile: boolean,
55
+ delayDuration: number
53
56
  ) {
54
57
  // Clear any existing destroy timeout
55
58
  if (this.destroyTimeoutId) {
@@ -65,6 +68,7 @@ class TooltipManager {
65
68
  sideOffset,
66
69
  showOnMobile,
67
70
  targetElement,
71
+ delayDuration,
68
72
  })
69
73
  }
70
74
 
@@ -135,11 +139,9 @@ export function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderPro
135
139
 
136
140
  // The singleton tooltip component that renders once
137
141
  function TooltipSingleton() {
138
- const editor = useMaybeEditor()
139
142
  const [isOpen, setIsOpen] = useState(false)
140
143
  const triggerRef = useRef<HTMLDivElement>(null)
141
144
  const isFirstShowRef = useRef(true)
142
- const showTimeoutRef = useRef<number | null>(null)
143
145
 
144
146
  const currentTooltip = useValue(
145
147
  'current tooltip',
@@ -149,12 +151,7 @@ function TooltipSingleton() {
149
151
 
150
152
  // Update open state and trigger position
151
153
  useEffect(() => {
152
- // Clear any existing show timeout
153
- if (showTimeoutRef.current) {
154
- clearTimeout(showTimeoutRef.current)
155
- showTimeoutRef.current = null
156
- }
157
-
154
+ let timer: ReturnType<typeof setTimeout> | null = null
158
155
  if (currentTooltip && triggerRef.current) {
159
156
  // Position the invisible trigger element over the active element
160
157
  const activeRect = currentTooltip.targetElement.getBoundingClientRect()
@@ -169,11 +166,12 @@ function TooltipSingleton() {
169
166
  trigger.style.zIndex = '9999'
170
167
 
171
168
  // Handle delay for first show
172
- if (isFirstShowRef.current && editor) {
173
- showTimeoutRef.current = editor.timers.setTimeout(() => {
169
+ if (isFirstShowRef.current) {
170
+ // eslint-disable-next-line no-restricted-globals
171
+ timer = setTimeout(() => {
174
172
  setIsOpen(true)
175
173
  isFirstShowRef.current = false
176
- }, editor.options.tooltipDelayMs)
174
+ }, currentTooltip.delayDuration)
177
175
  } else {
178
176
  // Subsequent tooltips show immediately
179
177
  setIsOpen(true)
@@ -184,7 +182,13 @@ function TooltipSingleton() {
184
182
  // Reset first show state after tooltip is hidden
185
183
  isFirstShowRef.current = true
186
184
  }
187
- }, [editor, currentTooltip])
185
+
186
+ return () => {
187
+ if (timer !== null) {
188
+ clearTimeout(timer)
189
+ }
190
+ }
191
+ }, [currentTooltip])
188
192
 
189
193
  if (!currentTooltip) {
190
194
  return null
@@ -212,7 +216,18 @@ function TooltipSingleton() {
212
216
 
213
217
  /** @public @react */
214
218
  export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(
215
- ({ children, content, side, sideOffset = 5, disabled = false, showOnMobile = false }, ref) => {
219
+ (
220
+ {
221
+ children,
222
+ content,
223
+ side,
224
+ sideOffset = 5,
225
+ disabled = false,
226
+ showOnMobile = false,
227
+ delayDuration,
228
+ },
229
+ ref
230
+ ) => {
216
231
  const editor = useMaybeEditor()
217
232
  const tooltipId = useRef<string>(uniqueId())
218
233
  const hasProvider = useContext(TooltipSingletonContext)
@@ -220,6 +235,8 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
220
235
  const orientationCtx = useTldrawUiOrientation()
221
236
  const sideToUse = side ?? orientationCtx.tooltipSide
222
237
 
238
+ const camera = useValue('camera', () => editor?.getCamera(), [])
239
+
223
240
  useEffect(() => {
224
241
  const currentTooltipId = tooltipId.current
225
242
  return () => {
@@ -229,18 +246,22 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
229
246
  }
230
247
  }, [editor, hasProvider])
231
248
 
249
+ useEffect(() => {
250
+ tooltipManager.hideTooltip(editor, tooltipId.current, true)
251
+ }, [editor, camera])
252
+
232
253
  // Don't show tooltip if disabled, no content, or UI labels are disabled
233
254
  if (disabled || !content) {
234
255
  return <>{children}</>
235
256
  }
236
257
 
258
+ const delayDurationToUse =
259
+ delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
260
+
237
261
  // Fallback to old behavior if no provider
238
262
  if (!hasProvider) {
239
263
  return (
240
- <_Tooltip.Root
241
- delayDuration={editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS}
242
- disableHoverableContent
243
- >
264
+ <_Tooltip.Root delayDuration={delayDurationToUse} disableHoverableContent>
244
265
  <_Tooltip.Trigger asChild ref={ref}>
245
266
  {children}
246
267
  </_Tooltip.Trigger>
@@ -270,7 +291,8 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
270
291
  event.currentTarget as HTMLElement,
271
292
  sideToUse,
272
293
  sideOffset,
273
- showOnMobile
294
+ showOnMobile,
295
+ delayDurationToUse
274
296
  )
275
297
  }
276
298
 
@@ -287,7 +309,8 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
287
309
  event.currentTarget as HTMLElement,
288
310
  sideToUse,
289
311
  sideOffset,
290
- showOnMobile
312
+ showOnMobile,
313
+ delayDurationToUse
291
314
  )
292
315
  }
293
316
 
@@ -316,8 +316,8 @@ function useDraggableEvents(
316
316
  if (
317
317
  distanceSq >
318
318
  (editor.getInstanceState().isCoarsePointer
319
- ? editor.options.coarseDragDistanceSquared
320
- : editor.options.dragDistanceSquared)
319
+ ? editor.options.uiCoarseDragDistanceSquared
320
+ : editor.options.uiDragDistanceSquared)
321
321
  ) {
322
322
  const screenSpaceStart = state.screenSpaceStart
323
323
  state = {
@@ -350,6 +350,7 @@ function useDraggableEvents(
350
350
  })
351
351
 
352
352
  tooltipManager.hideAllTooltips()
353
+ editor.getContainer().focus()
353
354
  })
354
355
  }
355
356
  }
@@ -36,6 +36,7 @@ import { useTranslation } from '../hooks/useTranslation/useTranslation'
36
36
  import { TLUiIconType } from '../icon-types'
37
37
  import { TLUiOverrideHelpers, useDefaultHelpers } from '../overrides'
38
38
  import { useA11y } from './a11y'
39
+ import { useTldrawUiComponents } from './components'
39
40
  import { TLUiEventSource, useUiEvents } from './events'
40
41
 
41
42
  /** @public */
@@ -98,6 +99,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
98
99
  const _editor = useMaybeEditor()
99
100
  const showCollaborationUi = useShowCollaborationUi()
100
101
  const helpers = useDefaultHelpers()
102
+ const components = useTldrawUiComponents()
101
103
  const trackEvent = useUiEvents()
102
104
  const a11y = useA11y()
103
105
  const msg = useTranslation()
@@ -176,7 +178,9 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
176
178
  kbd: 'cmd+alt+/,ctrl+alt+/',
177
179
  onSelect(source) {
178
180
  trackEvent('open-kbd-shortcuts', { source })
179
- helpers.addDialog({ component: DefaultKeyboardShortcutsDialog })
181
+ helpers.addDialog({
182
+ component: components.KeyboardShortcutsDialog ?? DefaultKeyboardShortcutsDialog,
183
+ })
180
184
  },
181
185
  },
182
186
  {
@@ -1755,7 +1759,17 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1755
1759
  }
1756
1760
 
1757
1761
  return actions
1758
- }, [helpers, _editor, trackEvent, overrides, defaultDocumentName, showCollaborationUi, msg, a11y])
1762
+ }, [
1763
+ helpers,
1764
+ _editor,
1765
+ trackEvent,
1766
+ overrides,
1767
+ defaultDocumentName,
1768
+ showCollaborationUi,
1769
+ msg,
1770
+ a11y,
1771
+ components,
1772
+ ])
1759
1773
 
1760
1774
  return <ActionsContext.Provider value={asActions(actions)}>{children}</ActionsContext.Provider>
1761
1775
  }
@@ -12,6 +12,7 @@ import {
12
12
  import { CursorChatBubble } from '../components/CursorChatBubble'
13
13
  import { DefaultDebugMenu } from '../components/DebugMenu/DefaultDebugMenu'
14
14
  import { DefaultDebugPanel } from '../components/DefaultDebugPanel'
15
+ import { DefaultFollowingIndicator } from '../components/DefaultFollowingIndicator'
15
16
  import { DefaultMenuPanel } from '../components/DefaultMenuPanel'
16
17
  import { DefaultDialogs } from '../components/Dialogs'
17
18
  import { TLUiHelpMenuProps } from '../components/HelpMenu/DefaultHelpMenu'
@@ -72,6 +73,7 @@ export interface TLUiComponents {
72
73
  Dialogs?: ComponentType | null
73
74
  Toasts?: ComponentType | null
74
75
  A11y?: ComponentType | null
76
+ FollowingIndicator?: ComponentType | null
75
77
  }
76
78
 
77
79
  const TldrawUiComponentsContext = createContext<TLUiComponents | null>(null)
@@ -119,6 +121,7 @@ export function TldrawUiComponentsProvider({
119
121
  Dialogs: DefaultDialogs,
120
122
  Toasts: DefaultToasts,
121
123
  A11y: DefaultA11yAnnouncer,
124
+ FollowingIndicator: DefaultFollowingIndicator,
122
125
  ..._overrides,
123
126
  }),
124
127
  [_overrides, showCollaborationUi]
@@ -123,7 +123,7 @@ export interface TLUiEventMap {
123
123
  'shrink-shapes': null
124
124
  'flatten-to-image': null
125
125
  'a11y-repeat-shape-announce': null
126
- 'open-url': { url: string }
126
+ 'open-url': { destinationUrl: string }
127
127
  'open-context-menu': null
128
128
  'adjust-shape-styles': null
129
129
  'copy-link': null
@@ -186,6 +186,7 @@ export type TLUiTranslationKey =
186
186
  | 'geo-style.pentagon'
187
187
  | 'geo-style.rectangle'
188
188
  | 'geo-style.rhombus'
189
+ | 'geo-style.rhombus-2'
189
190
  | 'geo-style.star'
190
191
  | 'geo-style.trapezoid'
191
192
  | 'geo-style.triangle'
@@ -260,6 +261,7 @@ export type TLUiTranslationKey =
260
261
  | 'tool.aspect-ratio.wide'
261
262
  | 'tool.image-toolbar-title'
262
263
  | 'tool.image-crop'
264
+ | 'tool.image-crop-confirm'
263
265
  | 'tool.media-alt-text'
264
266
  | 'tool.media-alt-text-desc'
265
267
  | 'tool.media-alt-text-confirm'
@@ -187,6 +187,7 @@ export const DEFAULT_TRANSLATION = {
187
187
  'geo-style.pentagon': 'Pentagon',
188
188
  'geo-style.rectangle': 'Rectangle',
189
189
  'geo-style.rhombus': 'Rhombus',
190
+ 'geo-style.rhombus-2': 'Rhombus left',
190
191
  'geo-style.star': 'Star',
191
192
  'geo-style.trapezoid': 'Trapezoid',
192
193
  'geo-style.triangle': 'Triangle',
@@ -261,6 +262,7 @@ export const DEFAULT_TRANSLATION = {
261
262
  'tool.aspect-ratio.wide': 'Wide (16:9)',
262
263
  'tool.image-toolbar-title': 'Image tools',
263
264
  'tool.image-crop': 'Crop image',
265
+ 'tool.image-crop-confirm': 'Confirm',
264
266
  'tool.media-alt-text': 'Alternative text',
265
267
  'tool.media-alt-text-desc': 'Give a description…',
266
268
  'tool.media-alt-text-confirm': 'Confirm',
@@ -31,9 +31,16 @@ export function kbd(str: string) {
31
31
  )
32
32
  .flat()
33
33
  .map((sub, index) => {
34
- if (sub === '__CTRL__') return 'Ctrl'
35
- if (sub === '__ALT__') return 'Alt'
36
- const modifiedKey = sub[0].toUpperCase() + sub.slice(1)
34
+ if (sub[0] === '+') return []
35
+
36
+ let modifiedKey
37
+ if (sub === '__CTRL__') {
38
+ modifiedKey = 'Ctrl'
39
+ } else if (sub === '__ALT__') {
40
+ modifiedKey = 'Alt'
41
+ } else {
42
+ modifiedKey = sub[0].toUpperCase() + sub.slice(1)
43
+ }
37
44
  return tlenv.isDarwin || !index ? modifiedKey : ['+', modifiedKey]
38
45
  })
39
46
  .flat()
@@ -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.d04b7fc312b4'
4
+ export const version = '3.16.0-canary.d3f0c2d5313c'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-08-14T09:26:55.295Z',
8
- patch: '2025-08-14T09:26:55.295Z',
7
+ minor: '2025-09-01T15:21:11.358Z',
8
+ patch: '2025-09-01T15:21:11.358Z',
9
9
  }
package/src/lib/ui.css CHANGED
@@ -1047,6 +1047,9 @@
1047
1047
  }
1048
1048
 
1049
1049
  @media (hover: hover) {
1050
+ .tlui-style-panel .tlui-button[aria-expanded='true'] {
1051
+ background: none;
1052
+ }
1050
1053
  .tlui-style-panel .tlui-button[data-state='open']:not(:hover)::after {
1051
1054
  opacity: 1;
1052
1055
  background: linear-gradient(270deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);
@@ -1,6 +1,6 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`putExcalidrawContent test fixtures bound-arrows.json 1`] = `
3
+ exports[`putExcalidrawContent test fixtures > bound-arrows.json 1`] = `
4
4
  {
5
5
  "binding:8": {
6
6
  "fromId": "shape:7",
@@ -173,7 +173,7 @@ exports[`putExcalidrawContent test fixtures bound-arrows.json 1`] = `
173
173
  }
174
174
  `;
175
175
 
176
- exports[`putExcalidrawContent test fixtures bound-elbow-arrows.json 1`] = `
176
+ exports[`putExcalidrawContent test fixtures > bound-elbow-arrows.json 1`] = `
177
177
  {
178
178
  "binding:7": {
179
179
  "fromId": "shape:6",
@@ -346,7 +346,7 @@ exports[`putExcalidrawContent test fixtures bound-elbow-arrows.json 1`] = `
346
346
  }
347
347
  `;
348
348
 
349
- exports[`putExcalidrawContent test fixtures image.json 1`] = `
349
+ exports[`putExcalidrawContent test fixtures > image.json 1`] = `
350
350
  {
351
351
  "asset:5": {
352
352
  "id": "asset:5",
@@ -404,7 +404,7 @@ exports[`putExcalidrawContent test fixtures image.json 1`] = `
404
404
  }
405
405
  `;
406
406
 
407
- exports[`putExcalidrawContent test fixtures line-drawing.json 1`] = `
407
+ exports[`putExcalidrawContent test fixtures > line-drawing.json 1`] = `
408
408
  {
409
409
  "document:document": {
410
410
  "gridSize": 10,
@@ -1,6 +1,6 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`buildFromV1Document test fixtures arrow-binding.tldr 1`] = `
3
+ exports[`buildFromV1Document test fixtures > arrow-binding.tldr 1`] = `
4
4
  {
5
5
  "binding:12": {
6
6
  "fromId": "shape:11",
@@ -173,7 +173,7 @@ exports[`buildFromV1Document test fixtures arrow-binding.tldr 1`] = `
173
173
  }
174
174
  `;
175
175
 
176
- exports[`buildFromV1Document test fixtures exact-arrow-binding.tldr 1`] = `
176
+ exports[`buildFromV1Document test fixtures > exact-arrow-binding.tldr 1`] = `
177
177
  {
178
178
  "binding:11": {
179
179
  "fromId": "shape:10",
@@ -346,7 +346,7 @@ exports[`buildFromV1Document test fixtures exact-arrow-binding.tldr 1`] = `
346
346
  }
347
347
  `;
348
348
 
349
- exports[`buildFromV1Document test fixtures incorrect-arrow-binding.tldr 1`] = `
349
+ exports[`buildFromV1Document test fixtures > incorrect-arrow-binding.tldr 1`] = `
350
350
  {
351
351
  "binding:11": {
352
352
  "fromId": "shape:10",
@@ -1,16 +1,17 @@
1
1
  import { createShapeId, toRichText } from '@tldraw/editor'
2
+ import { Mock, vi } from 'vitest'
2
3
  import { generateShapeAnnouncementMessage } from '../lib/ui/components/A11y'
3
4
  import { TestEditor } from './TestEditor'
4
5
 
5
6
  describe('A11y Shape Announcements', () => {
6
7
  let editor: TestEditor
7
- let mockTranslate: jest.Mock
8
+ let mockTranslate: Mock
8
9
 
9
10
  beforeEach(() => {
10
11
  editor = new TestEditor()
11
12
 
12
13
  // Create a simple translation mock
13
- mockTranslate = jest.fn((key) => {
14
+ mockTranslate = vi.fn((key) => {
14
15
  if (key === 'a11y.multiple-shapes') return '{num} shapes selected'
15
16
  if (key === 'a11y.shape') return 'Shape'
16
17
  if (key === 'a11y.text') return 'Text'
@@ -1,3 +1,4 @@
1
+ import { vi } from 'vitest'
1
2
  import { TestEditor } from './TestEditor'
2
3
 
3
4
  let editor: TestEditor
@@ -10,7 +11,7 @@ beforeEach(() => {
10
11
  editor._transformPointerUpSpy.mockRestore()
11
12
  })
12
13
 
13
- jest.useFakeTimers()
14
+ vi.useFakeTimers()
14
15
 
15
16
  describe('Handles events', () => {
16
17
  it('Emits single click events', () => {
@@ -23,7 +24,7 @@ describe('Handles events', () => {
23
24
  const eventsBeforeSettle = [{ name: 'pointer_down' }, { name: 'pointer_up' }]
24
25
 
25
26
  // allow time for settle
26
- jest.advanceTimersByTime(500)
27
+ vi.advanceTimersByTime(500)
27
28
 
28
29
  // nothing should have settled
29
30
  expect(events).toMatchObject(eventsBeforeSettle)
@@ -64,7 +65,7 @@ describe('Handles events', () => {
64
65
  }
65
66
 
66
67
  // allow double click to settle
67
- jest.advanceTimersByTime(500)
68
+ vi.advanceTimersByTime(500)
68
69
 
69
70
  expect(events).toMatchObject([
70
71
  ...eventsBeforeSettle,
@@ -110,7 +111,7 @@ describe('Handles events', () => {
110
111
  expect(eventsBeforeSettle).toMatchObject(eventsBeforeSettle)
111
112
 
112
113
  // allow double click to settle
113
- jest.advanceTimersByTime(500)
114
+ vi.advanceTimersByTime(500)
114
115
 
115
116
  expect(events).toMatchObject([
116
117
  ...eventsBeforeSettle,
@@ -162,7 +163,7 @@ describe('Handles events', () => {
162
163
  expect(events).toMatchObject(eventsBeforeSettle)
163
164
 
164
165
  // allow double click to settle
165
- jest.advanceTimersByTime(500)
166
+ vi.advanceTimersByTime(500)
166
167
 
167
168
  expect(events).toMatchObject([
168
169
  ...eventsBeforeSettle,
@@ -218,7 +219,7 @@ describe('Handles events', () => {
218
219
  expect(events).toMatchObject(eventsBeforeSettle)
219
220
 
220
221
  // allow double click to settle
221
- jest.advanceTimersByTime(500)
222
+ vi.advanceTimersByTime(500)
222
223
 
223
224
  expect(events).toMatchObject(eventsBeforeSettle)
224
225