tldraw 3.16.0-canary.d04b7fc312b4 → 3.16.0-canary.d3a23ebd1b0b

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 (168) hide show
  1. package/dist-cjs/index.d.ts +91 -0
  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 +3 -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/primitives/TldrawUiSlider.js +6 -4
  28. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
  29. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +45 -40
  30. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  31. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +2 -1
  32. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  33. package/dist-cjs/lib/ui/context/actions.js +16 -2
  34. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  35. package/dist-cjs/lib/ui/context/components.js +2 -0
  36. package/dist-cjs/lib/ui/context/components.js.map +2 -2
  37. package/dist-cjs/lib/ui/kbd-utils.js +9 -3
  38. package/dist-cjs/lib/ui/kbd-utils.js.map +2 -2
  39. package/dist-cjs/lib/ui/version.js +3 -3
  40. package/dist-cjs/lib/ui/version.js.map +1 -1
  41. package/dist-esm/index.d.mts +91 -0
  42. package/dist-esm/index.mjs +11 -2
  43. package/dist-esm/index.mjs.map +2 -2
  44. package/dist-esm/lib/Tldraw.mjs +14 -4
  45. package/dist-esm/lib/Tldraw.mjs.map +2 -2
  46. package/dist-esm/lib/defaultExternalContentHandlers.mjs +5 -4
  47. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  48. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +3 -0
  49. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  50. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +3 -0
  51. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
  52. package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
  53. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs +26 -1
  54. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs.map +2 -2
  55. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs +13 -0
  56. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs.map +2 -2
  57. package/dist-esm/lib/ui/TldrawUi.mjs +13 -12
  58. package/dist-esm/lib/ui/TldrawUi.mjs.map +2 -2
  59. package/dist-esm/lib/ui/assetUrls.mjs +13 -10
  60. package/dist-esm/lib/ui/assetUrls.mjs.map +2 -2
  61. package/dist-esm/lib/ui/components/{FollowingIndicator.mjs → DefaultFollowingIndicator.mjs} +3 -3
  62. package/dist-esm/lib/ui/components/DefaultFollowingIndicator.mjs.map +7 -0
  63. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +6 -6
  64. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +1 -1
  65. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs +1 -1
  66. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +2 -2
  67. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +6 -4
  68. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
  69. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +45 -40
  70. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  71. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +2 -1
  72. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  73. package/dist-esm/lib/ui/context/actions.mjs +16 -2
  74. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  75. package/dist-esm/lib/ui/context/components.mjs +2 -0
  76. package/dist-esm/lib/ui/context/components.mjs.map +2 -2
  77. package/dist-esm/lib/ui/kbd-utils.mjs +9 -3
  78. package/dist-esm/lib/ui/kbd-utils.mjs.map +2 -2
  79. package/dist-esm/lib/ui/version.mjs +3 -3
  80. package/dist-esm/lib/ui/version.mjs.map +1 -1
  81. package/package.json +11 -34
  82. package/src/index.ts +6 -1
  83. package/src/lib/Tldraw.tsx +15 -2
  84. package/src/lib/defaultExternalContentHandlers.ts +12 -4
  85. package/src/lib/shapes/arrow/ArrowShapeOptions.test.ts +2 -1
  86. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +4 -3
  87. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +7 -6
  88. package/src/lib/shapes/draw/DrawShapeTool.test.ts +0 -5
  89. package/src/lib/shapes/frame/FrameShapeUtil.tsx +4 -0
  90. package/src/lib/shapes/image/ImageShapeUtil.tsx +3 -0
  91. package/src/lib/shapes/line/LineShapeUtil.test.tsx +4 -3
  92. package/src/lib/shapes/line/__snapshots__/LineShapeUtil.test.tsx.snap +2 -2
  93. package/src/lib/shapes/shared/freehand/svg.ts +2 -0
  94. package/src/lib/shapes/text/TextShapeTool.test.ts +6 -5
  95. package/src/lib/tools/EraserTool/childStates/Erasing.ts +34 -1
  96. package/src/lib/tools/EraserTool/childStates/Pointing.ts +20 -0
  97. package/src/lib/ui/TldrawUi.tsx +16 -10
  98. package/src/lib/ui/assetUrls.ts +13 -10
  99. package/src/lib/ui/components/{FollowingIndicator.tsx → DefaultFollowingIndicator.tsx} +2 -1
  100. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +6 -6
  101. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +1 -1
  102. package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +35 -30
  103. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +37 -20
  104. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +3 -2
  105. package/src/lib/ui/context/actions.tsx +16 -2
  106. package/src/lib/ui/context/components.tsx +3 -0
  107. package/src/lib/ui/kbd-utils.ts +10 -3
  108. package/src/lib/ui/version.ts +3 -3
  109. package/src/lib/ui.css +3 -0
  110. package/src/lib/utils/excalidraw/__snapshots__/putExcalidrawContent.test.tsx.snap +5 -5
  111. package/src/lib/utils/tldr/__snapshots__/buildFromV1Document.test.ts.snap +4 -4
  112. package/src/test/A11y.test.tsx +3 -2
  113. package/src/test/ClickManager.test.ts +7 -6
  114. package/src/test/Editor.test.tsx +20 -19
  115. package/src/test/EraserTool.test.ts +184 -13
  116. package/src/test/HandTool.test.ts +10 -9
  117. package/src/test/HighlightShape.test.ts +2 -1
  118. package/src/test/SelectTool.test.ts +3 -2
  119. package/src/test/TLUserPreferences.test.ts +4 -3
  120. package/src/test/TestEditor.ts +13 -15
  121. package/src/test/TldrawEditor.test.tsx +11 -10
  122. package/src/test/ZoomTool.test.ts +7 -6
  123. package/src/test/__snapshots__/drawing.test.ts.snap +2 -2
  124. package/src/test/__snapshots__/groups.test.tsx.snap +6 -6
  125. package/src/test/__snapshots__/resizing.test.ts.snap +2 -2
  126. package/src/test/arrows-megabus.test.tsx +5 -4
  127. package/src/test/bindings.test.tsx +24 -37
  128. package/src/test/bookmark-shapes.test.ts +1 -8
  129. package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +23 -7
  130. package/src/test/commands/__snapshots__/packShapes.test.ts.snap +8 -8
  131. package/src/test/commands/__snapshots__/zoomToFit.test.ts.snap +2 -2
  132. package/src/test/commands/alignShapes.test.tsx +25 -24
  133. package/src/test/commands/animationSpeed.test.ts +2 -1
  134. package/src/test/commands/centerOnPoint.test.ts +3 -2
  135. package/src/test/commands/clipboard.test.ts +3 -2
  136. package/src/test/commands/createShapes.test.ts +2 -1
  137. package/src/test/commands/deleteShapes.test.ts +2 -1
  138. package/src/test/commands/distributeShapes.test.tsx +11 -10
  139. package/src/test/commands/getSvgString.test.ts +2 -1
  140. package/src/test/commands/packShapes.test.ts +5 -4
  141. package/src/test/commands/resizeShape.test.ts +2 -1
  142. package/src/test/commands/rotateShapes.test.ts +7 -6
  143. package/src/test/commands/setCamera.test.ts +4 -3
  144. package/src/test/commands/setCurrentPage.test.ts +3 -2
  145. package/src/test/commands/stackShapes.test.ts +11 -10
  146. package/src/test/commands/stretch.test.tsx +13 -12
  147. package/src/test/createDeepLink.test.tsx +2 -1
  148. package/src/test/cropping.test.ts +3 -2
  149. package/src/test/drawing.test.ts +2 -1
  150. package/src/test/flipShapes.test.ts +4 -3
  151. package/src/test/frames.test.ts +25 -24
  152. package/src/test/getCulledShapes.test.tsx +3 -2
  153. package/src/test/groups.test.tsx +1 -1
  154. package/src/test/handleDeepLink.test.tsx +2 -1
  155. package/src/test/maxShapes.test.ts +3 -2
  156. package/src/test/modifiers.test.ts +5 -4
  157. package/src/test/navigation.test.ts +12 -11
  158. package/src/test/panning.test.ts +2 -1
  159. package/src/test/perf/perf.test.ts +2 -1
  160. package/src/test/registerDeepLinkListener.test.tsx +10 -9
  161. package/src/test/resizing.test.ts +39 -38
  162. package/src/test/select.test.tsx +4 -3
  163. package/src/test/selection-omnibus.test.ts +11 -10
  164. package/src/test/shapeutils.test.ts +4 -3
  165. package/src/test/translating.test.ts +9 -8
  166. package/tldraw.css +11 -0
  167. package/dist-cjs/lib/ui/components/FollowingIndicator.js.map +0 -7
  168. package/dist-esm/lib/ui/components/FollowingIndicator.mjs.map +0 -7
@@ -1,4 +1,5 @@
1
1
  import { LANGUAGES, RecursivePartial, getDefaultCdnBaseUrl } from '@tldraw/editor'
2
+ import { useMemo } from 'react'
2
3
  import { DEFAULT_EMBED_DEFINITIONS } from '../defaultEmbedDefinitions'
3
4
  import { TLEditorAssetUrls, defaultEditorAssetUrls } from '../utils/static-assets/assetUrls'
4
5
  import { TLUiIconType, iconTypes } from './icon-types'
@@ -41,15 +42,17 @@ export function setDefaultUiAssetUrls(urls: TLUiAssetUrls) {
41
42
  export function useDefaultUiAssetUrlsWithOverrides(
42
43
  overrides?: TLUiAssetUrlOverrides
43
44
  ): TLUiAssetUrls {
44
- if (!overrides) return defaultUiAssetUrls
45
+ return useMemo(() => {
46
+ if (!overrides) return defaultUiAssetUrls
45
47
 
46
- return {
47
- fonts: Object.assign({ ...defaultUiAssetUrls.fonts }, { ...overrides?.fonts }),
48
- icons: Object.assign({ ...defaultUiAssetUrls.icons }, { ...overrides?.icons }),
49
- embedIcons: Object.assign({ ...defaultUiAssetUrls.embedIcons }, { ...overrides?.embedIcons }),
50
- translations: Object.assign(
51
- { ...defaultUiAssetUrls.translations },
52
- { ...overrides?.translations }
53
- ),
54
- }
48
+ return {
49
+ fonts: Object.assign({ ...defaultUiAssetUrls.fonts }, { ...overrides?.fonts }),
50
+ icons: Object.assign({ ...defaultUiAssetUrls.icons }, { ...overrides?.icons }),
51
+ embedIcons: Object.assign({ ...defaultUiAssetUrls.embedIcons }, { ...overrides?.embedIcons }),
52
+ translations: Object.assign(
53
+ { ...defaultUiAssetUrls.translations },
54
+ { ...overrides?.translations }
55
+ ),
56
+ }
57
+ }, [overrides])
55
58
  }
@@ -1,6 +1,7 @@
1
1
  import { useEditor, usePresence, useValue } from '@tldraw/editor'
2
2
 
3
- export function FollowingIndicator() {
3
+ /** @public @react */
4
+ export function DefaultFollowingIndicator() {
4
5
  const editor = useEditor()
5
6
  const followingUserId = useValue('follow', () => editor.getInstanceState().followingUserId, [
6
7
  editor,
@@ -123,7 +123,7 @@ export function DefaultKeyboardShortcutsDialogContent() {
123
123
  <TldrawUiMenuItem
124
124
  id="text-header"
125
125
  label="tool.rich-text-header"
126
- kbd="cmd+shift+[[1-6]]"
126
+ kbd="cmd+alt+[[1-6]]"
127
127
  onSelect={() => {
128
128
  /* do nothing */
129
129
  }}
@@ -157,7 +157,7 @@ export function DefaultKeyboardShortcutsDialogContent() {
157
157
  <TldrawUiMenuItem
158
158
  id="a11y-select-next-shape-direction"
159
159
  label="a11y.select-shape-direction"
160
- kbd="cmd+↑→↓←"
160
+ kbd="cmd+[[↑→↓←]]"
161
161
  onSelect={() => {
162
162
  /* do nothing */
163
163
  }}
@@ -165,7 +165,7 @@ export function DefaultKeyboardShortcutsDialogContent() {
165
165
  <TldrawUiMenuItem
166
166
  id="a11y-select-next-shape-container"
167
167
  label="a11y.enter-leave-container"
168
- kbd="cmd+shift+↑→"
168
+ kbd="cmd+shift+[[↑→]]"
169
169
  onSelect={() => {
170
170
  /* do nothing */
171
171
  }}
@@ -173,7 +173,7 @@ export function DefaultKeyboardShortcutsDialogContent() {
173
173
  <TldrawUiMenuItem
174
174
  id="a11y-pan-camera"
175
175
  label="a11y.pan-camera"
176
- kbd="[[Space]]+↑→↓←"
176
+ kbd="[[Space]]+[[↑→↓←]]"
177
177
  onSelect={() => {
178
178
  /* do nothing */
179
179
  }}
@@ -197,7 +197,7 @@ export function DefaultKeyboardShortcutsDialogContent() {
197
197
  <TldrawUiMenuItem
198
198
  id="a11y-move-shape"
199
199
  label="a11y.move-shape"
200
- kbd="↑→↓←"
200
+ kbd="[[↑→↓←]]"
201
201
  onSelect={() => {
202
202
  /* do nothing */
203
203
  }}
@@ -205,7 +205,7 @@ export function DefaultKeyboardShortcutsDialogContent() {
205
205
  <TldrawUiMenuItem
206
206
  id="a11y-move-shape-faster"
207
207
  label="a11y.move-shape-faster"
208
- kbd="shift+↑→↓←"
208
+ kbd="shift+[[↑→↓←]]"
209
209
  onSelect={() => {
210
210
  /* do nothing */
211
211
  }}
@@ -74,7 +74,7 @@ function DropdownPickerInner<T extends string>({
74
74
  </TldrawUiToolbarButton>
75
75
  </TldrawUiPopoverTrigger>
76
76
  <TldrawUiPopoverContent side="left" align="center">
77
- <TldrawUiToolbar orientation="grid" label={labelStr}>
77
+ <TldrawUiToolbar orientation={items.length > 4 ? 'grid' : 'horizontal'} label={labelStr}>
78
78
  <TldrawUiMenuContextProvider type="icons" sourceId="style-panel">
79
79
  {items.map((item) => {
80
80
  return (
@@ -2,6 +2,7 @@ import { Slider as _Slider } from 'radix-ui'
2
2
  import React, { useCallback, useEffect, useState } from 'react'
3
3
  import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
4
4
  import { useTranslation } from '../../hooks/useTranslation/useTranslation'
5
+ import { TldrawUiTooltip, tooltipManager } from './TldrawUiTooltip'
5
6
 
6
7
  /** @public */
7
8
  export interface TLUiSliderProps {
@@ -49,6 +50,7 @@ export const TldrawUiSlider = React.forwardRef<HTMLDivElement, TLUiSliderProps>(
49
50
  )
50
51
 
51
52
  const handlePointerDown = useCallback(() => {
53
+ tooltipManager.hideAllTooltips()
52
54
  onHistoryMark('click slider')
53
55
  }, [onHistoryMark])
54
56
 
@@ -62,38 +64,41 @@ export const TldrawUiSlider = React.forwardRef<HTMLDivElement, TLUiSliderProps>(
62
64
  }
63
65
  }, [])
64
66
 
67
+ const titleAndLabel = title + ' — ' + msg(label as TLUiTranslationKey)
68
+
65
69
  return (
66
70
  <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>
71
+ <TldrawUiTooltip content={titleAndLabel}>
72
+ <_Slider.Root
73
+ data-testid={testId}
74
+ className="tlui-slider"
75
+ dir="ltr"
76
+ min={min ?? 0}
77
+ max={steps}
78
+ step={1}
79
+ value={value !== null ? [value] : undefined}
80
+ onPointerDown={handlePointerDown}
81
+ onValueChange={handleValueChange}
82
+ onKeyDownCapture={handleKeyEvent}
83
+ onKeyUpCapture={handleKeyEvent}
84
+ >
85
+ <_Slider.Track className="tlui-slider__track" dir="ltr">
86
+ {value !== null && <_Slider.Range className="tlui-slider__range" dir="ltr" />}
87
+ </_Slider.Track>
88
+ {value !== null && (
89
+ <_Slider.Thumb
90
+ aria-valuemin={(min ?? 0) * ariaValueModifier}
91
+ aria-valuenow={value * ariaValueModifier}
92
+ aria-valuemax={steps * ariaValueModifier}
93
+ aria-label={titleAndLabel}
94
+ className="tlui-slider__thumb"
95
+ dir="ltr"
96
+ ref={ref}
97
+ tabIndex={tabIndex}
98
+ />
99
+ )}
100
+ </_Slider.Root>
101
+ </TldrawUiTooltip>
97
102
  </div>
98
103
  )
99
104
  })
@@ -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)
@@ -234,13 +249,13 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
234
249
  return <>{children}</>
235
250
  }
236
251
 
252
+ const delayDurationToUse =
253
+ delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
254
+
237
255
  // Fallback to old behavior if no provider
238
256
  if (!hasProvider) {
239
257
  return (
240
- <_Tooltip.Root
241
- delayDuration={editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS}
242
- disableHoverableContent
243
- >
258
+ <_Tooltip.Root delayDuration={delayDurationToUse} disableHoverableContent>
244
259
  <_Tooltip.Trigger asChild ref={ref}>
245
260
  {children}
246
261
  </_Tooltip.Trigger>
@@ -270,7 +285,8 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
270
285
  event.currentTarget as HTMLElement,
271
286
  sideToUse,
272
287
  sideOffset,
273
- showOnMobile
288
+ showOnMobile,
289
+ delayDurationToUse
274
290
  )
275
291
  }
276
292
 
@@ -287,7 +303,8 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
287
303
  event.currentTarget as HTMLElement,
288
304
  sideToUse,
289
305
  sideOffset,
290
- showOnMobile
306
+ showOnMobile,
307
+ delayDurationToUse
291
308
  )
292
309
  }
293
310
 
@@ -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]
@@ -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.d3a23ebd1b0b'
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-08-26T15:17:10.156Z',
8
+ patch: '2025-08-26T15:17:10.156Z',
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