tldraw 3.16.0-canary.aceca4c951a7 → 3.16.0-canary.b33662a7aefb

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 (184) hide show
  1. package/dist-cjs/index.d.ts +95 -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/ShapeFill.js +1 -1
  13. package/dist-cjs/lib/shapes/shared/ShapeFill.js.map +2 -2
  14. package/dist-cjs/lib/shapes/shared/freehand/svg.js.map +2 -2
  15. package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js +25 -1
  16. package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js.map +2 -2
  17. package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js +12 -0
  18. package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js.map +2 -2
  19. package/dist-cjs/lib/ui/TldrawUi.js +13 -12
  20. package/dist-cjs/lib/ui/TldrawUi.js.map +2 -2
  21. package/dist-cjs/lib/ui/assetUrls.js +13 -10
  22. package/dist-cjs/lib/ui/assetUrls.js.map +2 -2
  23. package/dist-cjs/lib/ui/components/{FollowingIndicator.js → DefaultFollowingIndicator.js} +6 -6
  24. package/dist-cjs/lib/ui/components/DefaultFollowingIndicator.js.map +7 -0
  25. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +6 -6
  26. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +1 -1
  27. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js +1 -1
  28. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js.map +2 -2
  29. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +52 -43
  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/context/events.js.map +1 -1
  38. package/dist-cjs/lib/ui/hooks/useTools.js +5 -5
  39. package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
  40. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  41. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +1 -0
  42. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  43. package/dist-cjs/lib/ui/kbd-utils.js +9 -3
  44. package/dist-cjs/lib/ui/kbd-utils.js.map +2 -2
  45. package/dist-cjs/lib/ui/version.js +3 -3
  46. package/dist-cjs/lib/ui/version.js.map +1 -1
  47. package/dist-esm/index.d.mts +95 -2
  48. package/dist-esm/index.mjs +11 -2
  49. package/dist-esm/index.mjs.map +2 -2
  50. package/dist-esm/lib/Tldraw.mjs +14 -4
  51. package/dist-esm/lib/Tldraw.mjs.map +2 -2
  52. package/dist-esm/lib/defaultExternalContentHandlers.mjs +5 -4
  53. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  54. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +6 -0
  55. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  56. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +3 -0
  57. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
  58. package/dist-esm/lib/shapes/shared/ShapeFill.mjs +1 -1
  59. package/dist-esm/lib/shapes/shared/ShapeFill.mjs.map +2 -2
  60. package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
  61. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs +26 -1
  62. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs.map +2 -2
  63. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs +13 -0
  64. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs.map +2 -2
  65. package/dist-esm/lib/ui/TldrawUi.mjs +13 -12
  66. package/dist-esm/lib/ui/TldrawUi.mjs.map +2 -2
  67. package/dist-esm/lib/ui/assetUrls.mjs +13 -10
  68. package/dist-esm/lib/ui/assetUrls.mjs.map +2 -2
  69. package/dist-esm/lib/ui/components/{FollowingIndicator.mjs → DefaultFollowingIndicator.mjs} +3 -3
  70. package/dist-esm/lib/ui/components/DefaultFollowingIndicator.mjs.map +7 -0
  71. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +6 -6
  72. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +1 -1
  73. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs +1 -1
  74. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +2 -2
  75. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +52 -43
  76. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  77. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +2 -1
  78. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  79. package/dist-esm/lib/ui/context/actions.mjs +16 -2
  80. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  81. package/dist-esm/lib/ui/context/components.mjs +2 -0
  82. package/dist-esm/lib/ui/context/components.mjs.map +2 -2
  83. package/dist-esm/lib/ui/context/events.mjs.map +1 -1
  84. package/dist-esm/lib/ui/hooks/useTools.mjs +5 -5
  85. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  86. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +1 -0
  87. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  88. package/dist-esm/lib/ui/kbd-utils.mjs +9 -3
  89. package/dist-esm/lib/ui/kbd-utils.mjs.map +2 -2
  90. package/dist-esm/lib/ui/version.mjs +3 -3
  91. package/dist-esm/lib/ui/version.mjs.map +1 -1
  92. package/package.json +11 -34
  93. package/src/index.ts +6 -1
  94. package/src/lib/Tldraw.tsx +15 -2
  95. package/src/lib/defaultExternalContentHandlers.ts +12 -4
  96. package/src/lib/shapes/arrow/ArrowShapeOptions.test.ts +2 -1
  97. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +4 -3
  98. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +7 -6
  99. package/src/lib/shapes/draw/DrawShapeTool.test.ts +0 -5
  100. package/src/lib/shapes/frame/FrameShapeUtil.tsx +8 -0
  101. package/src/lib/shapes/image/ImageShapeUtil.tsx +3 -0
  102. package/src/lib/shapes/line/LineShapeUtil.test.tsx +4 -3
  103. package/src/lib/shapes/line/__snapshots__/LineShapeUtil.test.tsx.snap +2 -2
  104. package/src/lib/shapes/shared/ShapeFill.tsx +1 -1
  105. package/src/lib/shapes/shared/freehand/svg.ts +2 -0
  106. package/src/lib/shapes/text/TextShapeTool.test.ts +6 -5
  107. package/src/lib/tools/EraserTool/childStates/Erasing.ts +34 -1
  108. package/src/lib/tools/EraserTool/childStates/Pointing.ts +20 -0
  109. package/src/lib/ui/TldrawUi.tsx +16 -10
  110. package/src/lib/ui/assetUrls.ts +13 -10
  111. package/src/lib/ui/components/{FollowingIndicator.tsx → DefaultFollowingIndicator.tsx} +2 -1
  112. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +6 -6
  113. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +1 -1
  114. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +48 -24
  115. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +3 -2
  116. package/src/lib/ui/context/actions.tsx +16 -2
  117. package/src/lib/ui/context/components.tsx +3 -0
  118. package/src/lib/ui/context/events.tsx +1 -1
  119. package/src/lib/ui/hooks/useTools.tsx +7 -5
  120. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +1 -0
  121. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +1 -0
  122. package/src/lib/ui/kbd-utils.ts +10 -3
  123. package/src/lib/ui/version.ts +3 -3
  124. package/src/lib/ui.css +3 -0
  125. package/src/lib/utils/excalidraw/__snapshots__/putExcalidrawContent.test.tsx.snap +5 -5
  126. package/src/lib/utils/tldr/__snapshots__/buildFromV1Document.test.ts.snap +4 -4
  127. package/src/test/A11y.test.tsx +3 -2
  128. package/src/test/ClickManager.test.ts +7 -6
  129. package/src/test/Editor.test.tsx +20 -19
  130. package/src/test/EraserTool.test.ts +184 -13
  131. package/src/test/HandTool.test.ts +10 -9
  132. package/src/test/HighlightShape.test.ts +2 -1
  133. package/src/test/SelectTool.test.ts +3 -2
  134. package/src/test/TLUserPreferences.test.ts +4 -3
  135. package/src/test/TestEditor.ts +13 -15
  136. package/src/test/TldrawEditor.test.tsx +11 -10
  137. package/src/test/ZoomTool.test.ts +7 -6
  138. package/src/test/__snapshots__/drawing.test.ts.snap +2 -2
  139. package/src/test/__snapshots__/groups.test.tsx.snap +6 -6
  140. package/src/test/__snapshots__/resizing.test.ts.snap +2 -2
  141. package/src/test/arrows-megabus.test.tsx +5 -4
  142. package/src/test/bindings.test.tsx +24 -37
  143. package/src/test/bookmark-shapes.test.ts +1 -8
  144. package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +23 -7
  145. package/src/test/commands/__snapshots__/packShapes.test.ts.snap +8 -8
  146. package/src/test/commands/__snapshots__/zoomToFit.test.ts.snap +2 -2
  147. package/src/test/commands/alignShapes.test.tsx +25 -24
  148. package/src/test/commands/animationSpeed.test.ts +2 -1
  149. package/src/test/commands/centerOnPoint.test.ts +3 -2
  150. package/src/test/commands/clipboard.test.ts +3 -2
  151. package/src/test/commands/createShapes.test.ts +2 -1
  152. package/src/test/commands/deleteShapes.test.ts +2 -1
  153. package/src/test/commands/distributeShapes.test.tsx +11 -10
  154. package/src/test/commands/getSvgString.test.ts +2 -1
  155. package/src/test/commands/packShapes.test.ts +5 -4
  156. package/src/test/commands/resizeShape.test.ts +2 -1
  157. package/src/test/commands/rotateShapes.test.ts +7 -6
  158. package/src/test/commands/setCamera.test.ts +4 -3
  159. package/src/test/commands/setCurrentPage.test.ts +3 -2
  160. package/src/test/commands/stackShapes.test.ts +11 -10
  161. package/src/test/commands/stretch.test.tsx +13 -12
  162. package/src/test/createDeepLink.test.tsx +2 -1
  163. package/src/test/cropping.test.ts +3 -2
  164. package/src/test/custom-clipping.test.ts +436 -0
  165. package/src/test/drawing.test.ts +2 -1
  166. package/src/test/flipShapes.test.ts +4 -3
  167. package/src/test/frames.test.ts +25 -24
  168. package/src/test/getCulledShapes.test.tsx +3 -2
  169. package/src/test/groups.test.tsx +1 -1
  170. package/src/test/handleDeepLink.test.tsx +2 -1
  171. package/src/test/maxShapes.test.ts +3 -2
  172. package/src/test/modifiers.test.ts +5 -4
  173. package/src/test/navigation.test.ts +12 -11
  174. package/src/test/panning.test.ts +2 -1
  175. package/src/test/perf/perf.test.ts +2 -1
  176. package/src/test/registerDeepLinkListener.test.tsx +10 -9
  177. package/src/test/resizing.test.ts +39 -38
  178. package/src/test/select.test.tsx +4 -3
  179. package/src/test/selection-omnibus.test.ts +11 -10
  180. package/src/test/shapeutils.test.ts +4 -3
  181. package/src/test/translating.test.ts +9 -8
  182. package/tldraw.css +11 -0
  183. package/dist-cjs/lib/ui/components/FollowingIndicator.js.map +0 -7
  184. package/dist-esm/lib/ui/components/FollowingIndicator.mjs.map +0 -7
@@ -20,6 +20,8 @@ export interface TldrawUiTooltipProps {
20
20
  side?: 'top' | 'right' | 'bottom' | 'left'
21
21
  sideOffset?: number
22
22
  disabled?: boolean
23
+ showOnMobile?: boolean
24
+ delayDuration?: number
23
25
  }
24
26
 
25
27
  // Singleton tooltip manager
@@ -30,10 +32,11 @@ class TooltipManager {
30
32
  content: ReactNode
31
33
  side: 'top' | 'right' | 'bottom' | 'left'
32
34
  sideOffset: number
35
+ showOnMobile: boolean
33
36
  targetElement: HTMLElement
37
+ delayDuration: number
34
38
  } | null>('current tooltip', null)
35
39
  private destroyTimeoutId: number | null = null
36
- private subscribers: Set<() => void> = new Set()
37
40
 
38
41
  static getInstance(): TooltipManager {
39
42
  if (!TooltipManager.instance) {
@@ -46,8 +49,10 @@ class TooltipManager {
46
49
  tooltipId: string,
47
50
  content: string | React.ReactNode,
48
51
  targetElement: HTMLElement,
49
- side: 'top' | 'right' | 'bottom' | 'left' = 'bottom',
50
- sideOffset: number = 5
52
+ side: 'top' | 'right' | 'bottom' | 'left',
53
+ sideOffset: number,
54
+ showOnMobile: boolean,
55
+ delayDuration: number
51
56
  ) {
52
57
  // Clear any existing destroy timeout
53
58
  if (this.destroyTimeoutId) {
@@ -61,7 +66,9 @@ class TooltipManager {
61
66
  content,
62
67
  side,
63
68
  sideOffset,
69
+ showOnMobile,
64
70
  targetElement,
71
+ delayDuration,
65
72
  })
66
73
  }
67
74
 
@@ -88,8 +95,10 @@ class TooltipManager {
88
95
  }
89
96
 
90
97
  getCurrentTooltipData() {
91
- if (!this.supportsHover()) return null
92
- return this.currentTooltip.get()
98
+ const currentTooltip = this.currentTooltip.get()
99
+ if (!currentTooltip) return null
100
+ if (!this.supportsHover() && !currentTooltip.showOnMobile) return null
101
+ return currentTooltip
93
102
  }
94
103
 
95
104
  private supportsHoverAtom: Atom<boolean> | null = null
@@ -130,11 +139,9 @@ export function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderPro
130
139
 
131
140
  // The singleton tooltip component that renders once
132
141
  function TooltipSingleton() {
133
- const editor = useMaybeEditor()
134
142
  const [isOpen, setIsOpen] = useState(false)
135
143
  const triggerRef = useRef<HTMLDivElement>(null)
136
144
  const isFirstShowRef = useRef(true)
137
- const showTimeoutRef = useRef<number | null>(null)
138
145
 
139
146
  const currentTooltip = useValue(
140
147
  'current tooltip',
@@ -144,12 +151,7 @@ function TooltipSingleton() {
144
151
 
145
152
  // Update open state and trigger position
146
153
  useEffect(() => {
147
- // Clear any existing show timeout
148
- if (showTimeoutRef.current) {
149
- clearTimeout(showTimeoutRef.current)
150
- showTimeoutRef.current = null
151
- }
152
-
154
+ let timer: ReturnType<typeof setTimeout> | null = null
153
155
  if (currentTooltip && triggerRef.current) {
154
156
  // Position the invisible trigger element over the active element
155
157
  const activeRect = currentTooltip.targetElement.getBoundingClientRect()
@@ -164,11 +166,12 @@ function TooltipSingleton() {
164
166
  trigger.style.zIndex = '9999'
165
167
 
166
168
  // Handle delay for first show
167
- if (isFirstShowRef.current && editor) {
168
- showTimeoutRef.current = editor.timers.setTimeout(() => {
169
+ if (isFirstShowRef.current) {
170
+ // eslint-disable-next-line no-restricted-globals
171
+ timer = setTimeout(() => {
169
172
  setIsOpen(true)
170
173
  isFirstShowRef.current = false
171
- }, editor.options.tooltipDelayMs)
174
+ }, currentTooltip.delayDuration)
172
175
  } else {
173
176
  // Subsequent tooltips show immediately
174
177
  setIsOpen(true)
@@ -179,7 +182,13 @@ function TooltipSingleton() {
179
182
  // Reset first show state after tooltip is hidden
180
183
  isFirstShowRef.current = true
181
184
  }
182
- }, [editor, currentTooltip])
185
+
186
+ return () => {
187
+ if (timer !== null) {
188
+ clearTimeout(timer)
189
+ }
190
+ }
191
+ }, [currentTooltip])
183
192
 
184
193
  if (!currentTooltip) {
185
194
  return null
@@ -207,7 +216,18 @@ function TooltipSingleton() {
207
216
 
208
217
  /** @public @react */
209
218
  export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(
210
- ({ children, content, side, sideOffset = 5, disabled = 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
+ ) => {
211
231
  const editor = useMaybeEditor()
212
232
  const tooltipId = useRef<string>(uniqueId())
213
233
  const hasProvider = useContext(TooltipSingletonContext)
@@ -229,13 +249,13 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
229
249
  return <>{children}</>
230
250
  }
231
251
 
252
+ const delayDurationToUse =
253
+ delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
254
+
232
255
  // Fallback to old behavior if no provider
233
256
  if (!hasProvider) {
234
257
  return (
235
- <_Tooltip.Root
236
- delayDuration={editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS}
237
- disableHoverableContent
238
- >
258
+ <_Tooltip.Root delayDuration={delayDurationToUse} disableHoverableContent>
239
259
  <_Tooltip.Trigger asChild ref={ref}>
240
260
  {children}
241
261
  </_Tooltip.Trigger>
@@ -264,7 +284,9 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
264
284
  content,
265
285
  event.currentTarget as HTMLElement,
266
286
  sideToUse,
267
- sideOffset
287
+ sideOffset,
288
+ showOnMobile,
289
+ delayDurationToUse
268
290
  )
269
291
  }
270
292
 
@@ -280,7 +302,9 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
280
302
  content,
281
303
  event.currentTarget as HTMLElement,
282
304
  sideToUse,
283
- sideOffset
305
+ sideOffset,
306
+ showOnMobile,
307
+ delayDurationToUse
284
308
  )
285
309
  }
286
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]
@@ -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
@@ -155,7 +155,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
155
155
  },
156
156
  onDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {
157
157
  onDragFromToolbarToCreateShape(editor, info, {
158
- createShape: (id) => editor.createShape({ id, type: 'geo', props: { geo } }),
158
+ createShape: (id) =>
159
+ editor.createShape({ id, type: 'geo', props: { w: 200, h: 200, geo } }),
159
160
  })
160
161
  trackEvent('drag-tool', { source, id: 'geo' })
161
162
  },
@@ -199,8 +200,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
199
200
  type: 'line',
200
201
  props: {
201
202
  points: {
202
- [start]: { id: start, index: start, x: 0, y: 0 },
203
- [end]: { id: end, index: end, x: 100, y: 100 },
203
+ [start]: { id: start, index: start, x: 0, y: 200 },
204
+ [end]: { id: end, index: end, x: 200, y: 0 },
204
205
  },
205
206
  },
206
207
  })
@@ -239,8 +240,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
239
240
  createShape: (id) =>
240
241
  editor.createShape({ id, type: 'text', props: { richText: toRichText('Text') } }),
241
242
  onDragEnd: (id) => {
242
- editor.emit('select-all-text', { shapeId: id })
243
243
  editor.setEditingShape(id)
244
+ editor.emit('select-all-text', { shapeId: id })
244
245
  },
245
246
  })
246
247
  trackEvent('drag-tool', { source, id: 'text' })
@@ -269,8 +270,8 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
269
270
  onDragFromToolbarToCreateShape(editor, info, {
270
271
  createShape: (id) => editor.createShape({ id, type: 'note' }),
271
272
  onDragEnd: (id) => {
272
- editor.emit('select-all-text', { shapeId: id })
273
273
  editor.setEditingShape(id)
274
+ editor.emit('select-all-text', { shapeId: id })
274
275
  },
275
276
  })
276
277
  trackEvent('drag-tool', { source, id: 'note' })
@@ -385,5 +386,6 @@ export function onDragFromToolbarToCreateShape(
385
386
  opts.onDragEnd?.(id)
386
387
  },
387
388
  })
389
+
388
390
  editor.getCurrentTool().setCurrentToolIdMask(shape.type)
389
391
  }
@@ -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'
@@ -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',
@@ -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.aceca4c951a7'
4
+ export const version = '3.16.0-canary.b33662a7aefb'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-08-13T14:52:04.311Z',
8
- patch: '2025-08-13T14:52:04.311Z',
7
+ minor: '2025-08-28T11:52:40.857Z',
8
+ patch: '2025-08-28T11:52:40.857Z',
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
 
@@ -14,6 +14,7 @@ import {
14
14
  loadSnapshot,
15
15
  react,
16
16
  } from '@tldraw/editor'
17
+ import { vi } from 'vitest'
17
18
  import { TestEditor } from './TestEditor'
18
19
  import { TL } from './test-jsx'
19
20
 
@@ -415,24 +416,24 @@ describe('isFocused', () => {
415
416
  })
416
417
 
417
418
  it('becomes true when the container div receives a focus event', () => {
418
- jest.advanceTimersByTime(100)
419
+ vi.advanceTimersByTime(100)
419
420
  expect(editor.getInstanceState().isFocused).toBe(false)
420
421
 
421
422
  editor.elm.focus()
422
423
 
423
- jest.advanceTimersByTime(100)
424
+ vi.advanceTimersByTime(100)
424
425
  expect(editor.getInstanceState().isFocused).toBe(true)
425
426
  })
426
427
 
427
428
  it('becomes false when the container div receives a blur event', () => {
428
429
  editor.elm.focus()
429
430
 
430
- jest.advanceTimersByTime(100)
431
+ vi.advanceTimersByTime(100)
431
432
  expect(editor.getInstanceState().isFocused).toBe(true)
432
433
 
433
434
  editor.elm.blur()
434
435
 
435
- jest.advanceTimersByTime(100)
436
+ vi.advanceTimersByTime(100)
436
437
  expect(editor.getInstanceState().isFocused).toBe(false)
437
438
  })
438
439
 
@@ -444,13 +445,13 @@ describe('isFocused', () => {
444
445
  editor.elm.blur()
445
446
  const child = document.createElement('div')
446
447
  editor.elm.appendChild(child)
447
- jest.advanceTimersByTime(100)
448
+ vi.advanceTimersByTime(100)
448
449
  expect(editor.getInstanceState().isFocused).toBe(false)
449
450
  child.dispatchEvent(new FocusEvent('focusin', { bubbles: true }))
450
- jest.advanceTimersByTime(100)
451
+ vi.advanceTimersByTime(100)
451
452
  expect(editor.getInstanceState().isFocused).toBe(true)
452
453
  child.dispatchEvent(new FocusEvent('focusout', { bubbles: true }))
453
- jest.advanceTimersByTime(100)
454
+ vi.advanceTimersByTime(100)
454
455
  expect(editor.getInstanceState().isFocused).toBe(false)
455
456
  })
456
457
 
@@ -466,7 +467,7 @@ describe('isFocused', () => {
466
467
 
467
468
  child.dispatchEvent(new FocusEvent('focusout', { bubbles: true }))
468
469
 
469
- jest.advanceTimersByTime(100)
470
+ vi.advanceTimersByTime(100)
470
471
  expect(editor.getInstanceState().isFocused).toBe(false)
471
472
  })
472
473
  })
@@ -520,13 +521,13 @@ describe('getShapeUtil', () => {
520
521
  it('throws if that shape type isnt registered', () => {
521
522
  const myMissingShape = { type: 'missing' } as TLShape
522
523
  expect(() => editor.getShapeUtil(myMissingShape)).toThrowErrorMatchingInlineSnapshot(
523
- `"No shape util found for type "missing""`
524
+ `[Error: No shape util found for type "missing"]`
524
525
  )
525
526
  })
526
527
 
527
528
  it('throws if that type isnt registered', () => {
528
529
  expect(() => editor.getShapeUtil('missing')).toThrowErrorMatchingInlineSnapshot(
529
- `"No shape util found for type "missing""`
530
+ `[Error: No shape util found for type "missing"]`
530
531
  )
531
532
  })
532
533
  })
@@ -603,14 +604,14 @@ describe('snapshots', () => {
603
604
 
604
605
  describe('when the user prefers dark UI', () => {
605
606
  beforeEach(() => {
606
- window.matchMedia = jest.fn().mockImplementation((query) => {
607
+ window.matchMedia = vi.fn().mockImplementation((query) => {
607
608
  return {
608
609
  matches: query === '(prefers-color-scheme: dark)',
609
610
  media: query,
610
611
  onchange: null,
611
- addEventListener: jest.fn(),
612
- removeEventListener: jest.fn(),
613
- dispatchEvent: jest.fn(),
612
+ addEventListener: vi.fn(),
613
+ removeEventListener: vi.fn(),
614
+ dispatchEvent: vi.fn(),
614
615
  }
615
616
  })
616
617
  })
@@ -630,14 +631,14 @@ describe('when the user prefers dark UI', () => {
630
631
 
631
632
  describe('when the user prefers light UI', () => {
632
633
  beforeEach(() => {
633
- window.matchMedia = jest.fn().mockImplementation((query) => {
634
+ window.matchMedia = vi.fn().mockImplementation((query) => {
634
635
  return {
635
636
  matches: false,
636
637
  media: query,
637
638
  onchange: null,
638
- addEventListener: jest.fn(),
639
- removeEventListener: jest.fn(),
640
- dispatchEvent: jest.fn(),
639
+ addEventListener: vi.fn(),
640
+ removeEventListener: vi.fn(),
641
+ dispatchEvent: vi.fn(),
641
642
  }
642
643
  })
643
644
  })
@@ -722,7 +723,7 @@ describe('dragging', () => {
722
723
  })
723
724
 
724
725
  describe('getShapeVisibility', () => {
725
- const getShapeVisibility = jest.fn(((shape: TLShape) => {
726
+ const getShapeVisibility = vi.fn(((shape: TLShape) => {
726
727
  return shape.meta.visibility as any
727
728
  }) satisfies TldrawEditorProps['getShapeVisibility'])
728
729