tldraw 3.16.0-canary.0ff72188cd53 → 3.16.0-canary.13ee933ed97f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist-cjs/index.d.ts +4 -4
  2. package/dist-cjs/index.js +1 -1
  3. package/dist-cjs/lib/defaultExternalContentHandlers.js +10 -0
  4. package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
  5. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js +4 -4
  6. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js.map +2 -2
  7. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +2 -1
  8. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  9. package/dist-cjs/lib/shapes/frame/components/FrameLabelInput.js +2 -2
  10. package/dist-cjs/lib/shapes/frame/components/FrameLabelInput.js.map +2 -2
  11. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +1 -0
  12. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
  13. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +2 -1
  14. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  15. package/dist-cjs/lib/shapes/shared/HyperlinkButton.js +4 -4
  16. package/dist-cjs/lib/shapes/shared/HyperlinkButton.js.map +2 -2
  17. package/dist-cjs/lib/shapes/shared/useEditablePlainText.js +3 -2
  18. package/dist-cjs/lib/shapes/shared/useEditablePlainText.js.map +2 -2
  19. package/dist-cjs/lib/shapes/text/PlainTextArea.js +2 -2
  20. package/dist-cjs/lib/shapes/text/PlainTextArea.js.map +2 -2
  21. package/dist-cjs/lib/shapes/text/RichTextArea.js +3 -3
  22. package/dist-cjs/lib/shapes/text/RichTextArea.js.map +2 -2
  23. package/dist-cjs/lib/ui/components/A11y.js +1 -1
  24. package/dist-cjs/lib/ui/components/A11y.js.map +2 -2
  25. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js +1 -1
  26. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js.map +2 -2
  27. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +1 -1
  28. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js.map +1 -1
  29. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js +2 -2
  30. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js.map +2 -2
  31. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +1 -1
  32. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
  33. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +26 -1
  34. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  35. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +5 -5
  36. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +1 -1
  37. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js +1 -1
  38. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js.map +2 -2
  39. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  40. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +3 -2
  41. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  42. package/dist-cjs/lib/ui/version.js +3 -3
  43. package/dist-cjs/lib/ui/version.js.map +1 -1
  44. package/dist-esm/index.d.mts +4 -4
  45. package/dist-esm/index.mjs +1 -1
  46. package/dist-esm/lib/defaultExternalContentHandlers.mjs +10 -0
  47. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  48. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs +5 -5
  49. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs.map +2 -2
  50. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +2 -1
  51. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  52. package/dist-esm/lib/shapes/frame/components/FrameLabelInput.mjs +3 -3
  53. package/dist-esm/lib/shapes/frame/components/FrameLabelInput.mjs.map +2 -2
  54. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +1 -0
  55. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  56. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +2 -1
  57. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  58. package/dist-esm/lib/shapes/shared/HyperlinkButton.mjs +5 -5
  59. package/dist-esm/lib/shapes/shared/HyperlinkButton.mjs.map +2 -2
  60. package/dist-esm/lib/shapes/shared/useEditablePlainText.mjs +4 -3
  61. package/dist-esm/lib/shapes/shared/useEditablePlainText.mjs.map +2 -2
  62. package/dist-esm/lib/shapes/text/PlainTextArea.mjs +3 -3
  63. package/dist-esm/lib/shapes/text/PlainTextArea.mjs.map +2 -2
  64. package/dist-esm/lib/shapes/text/RichTextArea.mjs +3 -4
  65. package/dist-esm/lib/shapes/text/RichTextArea.mjs.map +2 -2
  66. package/dist-esm/lib/ui/components/A11y.mjs +2 -2
  67. package/dist-esm/lib/ui/components/A11y.mjs.map +2 -2
  68. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs +2 -2
  69. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs.map +2 -2
  70. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs +2 -2
  71. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs.map +1 -1
  72. package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs +3 -3
  73. package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs.map +2 -2
  74. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +1 -1
  75. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
  76. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +27 -1
  77. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  78. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +5 -5
  79. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +1 -1
  80. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs +2 -2
  81. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs.map +2 -2
  82. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +3 -2
  83. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  84. package/dist-esm/lib/ui/version.mjs +3 -3
  85. package/dist-esm/lib/ui/version.mjs.map +1 -1
  86. package/package.json +3 -3
  87. package/src/lib/defaultExternalContentHandlers.ts +14 -0
  88. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +2 -2
  89. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +41 -0
  90. package/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx +5 -5
  91. package/src/lib/shapes/frame/FrameShapeUtil.tsx +1 -0
  92. package/src/lib/shapes/frame/components/FrameLabelInput.tsx +3 -3
  93. package/src/lib/shapes/geo/GeoShapeUtil.tsx +1 -0
  94. package/src/lib/shapes/note/NoteShapeUtil.tsx +1 -0
  95. package/src/lib/shapes/shared/HyperlinkButton.tsx +5 -5
  96. package/src/lib/shapes/shared/useEditablePlainText.ts +5 -3
  97. package/src/lib/shapes/text/PlainTextArea.tsx +3 -3
  98. package/src/lib/shapes/text/RichTextArea.tsx +3 -4
  99. package/src/lib/ui/components/A11y.tsx +2 -2
  100. package/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx +2 -2
  101. package/src/lib/ui/components/primitives/TldrawUiContextualToolbar.tsx +2 -2
  102. package/src/lib/ui/components/primitives/TldrawUiInput.tsx +3 -3
  103. package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +2 -2
  104. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +41 -11
  105. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +6 -6
  106. package/src/lib/ui/hooks/useClipboardEvents.ts +2 -2
  107. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +1 -0
  108. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +3 -2
  109. package/src/lib/ui/version.ts +3 -3
  110. package/src/test/getCulledShapes.test.tsx +71 -2
@@ -1,4 +1,4 @@
1
- import { TLFrameShape, TLShapeId, stopEventPropagation, useEditor } from '@tldraw/editor'
1
+ import { TLFrameShape, TLShapeId, markEventAsHandled, useEditor } from '@tldraw/editor'
2
2
  import { forwardRef, useCallback } from 'react'
3
3
  import { defaultEmptyAs } from '../FrameShapeUtil'
4
4
 
@@ -13,7 +13,7 @@ export const FrameLabelInput = forwardRef<
13
13
  if (e.key === 'Enter' && !e.nativeEvent.isComposing) {
14
14
  // need to prevent the enter keydown making it's way up to the Idle state
15
15
  // and sending us back into edit mode
16
- stopEventPropagation(e)
16
+ markEventAsHandled(e)
17
17
  e.currentTarget.blur()
18
18
  editor.setEditingShape(null)
19
19
  }
@@ -74,7 +74,7 @@ export const FrameLabelInput = forwardRef<
74
74
  onKeyDown={handleKeyDown}
75
75
  onBlur={handleBlur}
76
76
  onChange={handleChange}
77
- onPointerDown={isEditing ? stopEventPropagation : undefined}
77
+ onPointerDown={isEditing ? markEventAsHandled : undefined}
78
78
  draggable={false}
79
79
  />
80
80
  {defaultEmptyAs(name, 'Frame') + String.fromCharCode(8203)}
@@ -126,6 +126,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
126
126
  height: unscaledLabelHeight * shape.props.scale,
127
127
  isFilled: true,
128
128
  isLabel: true,
129
+ excludeFromShapeBounds: true,
129
130
  isEmptyLabel: isEmptyRichText(shape.props.richText),
130
131
  }),
131
132
  ],
@@ -147,6 +147,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
147
147
  height: lh,
148
148
  isFilled: true,
149
149
  isLabel: true,
150
+ excludeFromShapeBounds: true,
150
151
  }),
151
152
  ],
152
153
  })
@@ -1,4 +1,4 @@
1
- import { stopEventPropagation, useEditor, useValue } from '@tldraw/editor'
1
+ import { markEventAsHandled, useEditor, useValue } from '@tldraw/editor'
2
2
  import classNames from 'classnames'
3
3
  import { PointerEventHandler, useCallback } from 'react'
4
4
 
@@ -8,9 +8,9 @@ const LINK_ICON =
8
8
  export function HyperlinkButton({ url }: { url: string }) {
9
9
  const editor = useEditor()
10
10
  const hideButton = useValue('zoomLevel', () => editor.getZoomLevel() < 0.32, [editor])
11
- const useStopPropagationOnShiftKey = useCallback<PointerEventHandler>(
11
+ const markAsHandledOnShiftKey = useCallback<PointerEventHandler>(
12
12
  (e) => {
13
- if (!editor.inputs.shiftKey) stopEventPropagation(e)
13
+ if (!editor.inputs.shiftKey) markEventAsHandled(e)
14
14
  },
15
15
  [editor]
16
16
  )
@@ -22,8 +22,8 @@ export function HyperlinkButton({ url }: { url: string }) {
22
22
  href={url}
23
23
  target="_blank"
24
24
  rel="noopener noreferrer"
25
- onPointerDown={useStopPropagationOnShiftKey}
26
- onPointerUp={useStopPropagationOnShiftKey}
25
+ onPointerDown={markAsHandledOnShiftKey}
26
+ onPointerUp={markAsHandledOnShiftKey}
27
27
  title={url}
28
28
  draggable={false}
29
29
  >
@@ -3,9 +3,9 @@ import {
3
3
  TLShapeId,
4
4
  TLUnknownShape,
5
5
  getPointerInfo,
6
+ markEventAsHandled,
6
7
  noop,
7
8
  preventDefault,
8
- stopEventPropagation,
9
9
  tlenv,
10
10
  useEditor,
11
11
  useValue,
@@ -136,7 +136,7 @@ export function useEditableTextCommon(shapeId: TLShapeId) {
136
136
  shape: editor.getShape(shapeId)!,
137
137
  })
138
138
 
139
- stopEventPropagation(e) // we need to prevent blurring the input
139
+ e.stopPropagation() // we need to prevent blurring the input
140
140
  },
141
141
  [editor, shapeId]
142
142
  )
@@ -157,11 +157,13 @@ export function useEditableTextCommon(shapeId: TLShapeId) {
157
157
  [editor, shapeId]
158
158
  )
159
159
 
160
+ const handleDoubleClick: (e: React.MouseEvent) => void = markEventAsHandled
161
+
160
162
  return {
161
163
  handleFocus: noop,
162
164
  handleBlur: noop,
163
165
  handleInputPointerDown,
164
- handleDoubleClick: stopEventPropagation,
166
+ handleDoubleClick,
165
167
  handlePaste,
166
168
  isEditing,
167
169
  isReadyForEditing,
@@ -1,4 +1,4 @@
1
- import { preventDefault, stopEventPropagation } from '@tldraw/editor'
1
+ import { markEventAsHandled, preventDefault } from '@tldraw/editor'
2
2
  import React from 'react'
3
3
  import { TextAreaProps } from './RichTextArea'
4
4
 
@@ -46,8 +46,8 @@ export const PlainTextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps
46
46
  onChange={onChange}
47
47
  onKeyDown={(e) => handleKeyDown(e.nativeEvent)}
48
48
  onBlur={handleBlur}
49
- onTouchEnd={stopEventPropagation}
50
- onContextMenu={isEditing ? stopEventPropagation : undefined}
49
+ onTouchEnd={markEventAsHandled}
50
+ onContextMenu={isEditing ? (e) => e.stopPropagation() : undefined}
51
51
  onPointerDown={handleInputPointerDown}
52
52
  onPaste={handlePaste}
53
53
  onDoubleClick={handleDoubleClick}
@@ -10,7 +10,6 @@ import {
10
10
  TLRichText,
11
11
  TLShapeId,
12
12
  preventDefault,
13
- stopEventPropagation,
14
13
  useEditor,
15
14
  useEvent,
16
15
  useUniqueSafeId,
@@ -233,13 +232,13 @@ export const RichTextArea = React.forwardRef<HTMLDivElement, TextAreaProps>(func
233
232
  tabIndex={-1}
234
233
  data-testid="rich-text-area"
235
234
  className="tl-rich-text tl-text tl-text-input"
236
- onContextMenu={isEditing ? stopEventPropagation : undefined}
235
+ onContextMenu={isEditing ? (e) => e.stopPropagation() : undefined}
237
236
  // N.B. When PointerStateExtension was introduced, this was moved there.
238
237
  // However, that caused selecting over list items to break.
239
238
  // The handleDOMEvents in TipTap don't seem to support the pointerDownCapture event.
240
- onPointerDownCapture={stopEventPropagation}
239
+ onPointerDownCapture={(e) => e.stopPropagation()}
241
240
  // This onTouchEnd is important for Android to be able to change selection on text.
242
- onTouchEnd={stopEventPropagation}
241
+ onTouchEnd={(e) => e.stopPropagation()}
243
242
  // On FF, there's a behavior where dragging a selection will grab that selection into
244
243
  // the drag event. However, once the drag is over, and you select away from the textarea,
245
244
  // starting a drag over the textarea will restart a selection drag instead of a shape drag.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  debugFlags,
3
3
  Editor,
4
- stopEventPropagation,
4
+ markEventAsHandled,
5
5
  TLGeoShape,
6
6
  TLShapeId,
7
7
  unsafe__withoutCapture,
@@ -23,7 +23,7 @@ export function SkipToMainContent() {
23
23
 
24
24
  const handleNavigateToFirstShape = useCallback(
25
25
  (e: MouseEvent | KeyboardEvent) => {
26
- stopEventPropagation(e)
26
+ markEventAsHandled(e)
27
27
  button.current?.blur()
28
28
  const shapes = editor.getCurrentPageShapesInReadingOrder()
29
29
  if (!shapes.length) return
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  PageRecordType,
3
3
  TLPageId,
4
+ markEventAsHandled,
4
5
  releasePointerCapture,
5
6
  setPointerCapture,
6
- stopEventPropagation,
7
7
  tlenv,
8
8
  useEditor,
9
9
  useValue,
@@ -451,7 +451,7 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
451
451
  if (e.key === 'Enter') {
452
452
  if (page.id === currentPage.id) {
453
453
  toggleEditing()
454
- stopEventPropagation(e)
454
+ markEventAsHandled(e)
455
455
  }
456
456
  }
457
457
  }}
@@ -3,8 +3,8 @@ import {
3
3
  Box,
4
4
  clamp,
5
5
  Editor,
6
+ markEventAsHandled,
6
7
  react,
7
- stopEventPropagation,
8
8
  useAtom,
9
9
  useEditor,
10
10
  usePassThroughMouseOverEvents,
@@ -170,7 +170,7 @@ export const TldrawUiContextualToolbar = ({
170
170
  data-visible={false}
171
171
  data-testid="contextual-toolbar"
172
172
  className={classNames('tlui-contextual-toolbar', className)}
173
- onPointerDown={stopEventPropagation}
173
+ onPointerDown={markEventAsHandled}
174
174
  >
175
175
  <TldrawUiToolbar
176
176
  orientation="horizontal"
@@ -1,4 +1,4 @@
1
- import { stopEventPropagation, tlenv, tltime, useMaybeEditor } from '@tldraw/editor'
1
+ import { tlenv, tltime, useMaybeEditor } from '@tldraw/editor'
2
2
  import classNames from 'classnames'
3
3
  import * as React from 'react'
4
4
  import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
@@ -118,7 +118,7 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
118
118
  // `onChange` with a duplicated text value.
119
119
  if (isComposing.current) return
120
120
  e.currentTarget.blur()
121
- stopEventPropagation(e)
121
+ e.stopPropagation()
122
122
  onComplete?.(e.currentTarget.value)
123
123
  break
124
124
  }
@@ -126,7 +126,7 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
126
126
  e.currentTarget.value = rInitialValue.current
127
127
  onCancel?.(e.currentTarget.value)
128
128
  e.currentTarget.blur()
129
- stopEventPropagation(e)
129
+ e.stopPropagation()
130
130
  break
131
131
  }
132
132
  }
@@ -13,7 +13,7 @@ export interface TLUiSliderProps {
13
13
  label: string
14
14
  title: string
15
15
  onValueChange(value: number): void
16
- onHistoryMark(id: string): void
16
+ onHistoryMark?(id: string): void
17
17
  'data-testid'?: string
18
18
  ariaValueModifier?: number
19
19
  }
@@ -53,7 +53,7 @@ export const TldrawUiSlider = React.forwardRef<HTMLDivElement, TLUiSliderProps>(
53
53
 
54
54
  const handlePointerDown = useCallback(() => {
55
55
  tooltipManager.hideAllTooltips()
56
- onHistoryMark('click slider')
56
+ onHistoryMark?.('click slider')
57
57
  }, [onHistoryMark])
58
58
 
59
59
  // N.B. This is a bit silly. The Radix slider auto-focuses which
@@ -6,6 +6,7 @@ import React, {
6
6
  ReactNode,
7
7
  useContext,
8
8
  useEffect,
9
+ useLayoutEffect,
9
10
  useRef,
10
11
  useState,
11
12
  } from 'react'
@@ -24,18 +25,20 @@ export interface TldrawUiTooltipProps {
24
25
  delayDuration?: number
25
26
  }
26
27
 
28
+ interface CurrentTooltip {
29
+ id: string
30
+ content: ReactNode
31
+ side: 'top' | 'right' | 'bottom' | 'left'
32
+ sideOffset: number
33
+ showOnMobile: boolean
34
+ targetElement: HTMLElement
35
+ delayDuration: number
36
+ }
37
+
27
38
  // Singleton tooltip manager
28
39
  class TooltipManager {
29
40
  private static instance: TooltipManager | null = null
30
- private currentTooltip = atom<{
31
- id: string
32
- content: ReactNode
33
- side: 'top' | 'right' | 'bottom' | 'left'
34
- sideOffset: number
35
- showOnMobile: boolean
36
- targetElement: HTMLElement
37
- delayDuration: number
38
- } | null>('current tooltip', null)
41
+ private currentTooltip = atom<CurrentTooltip | null>('current tooltip', null)
39
42
  private destroyTimeoutId: number | null = null
40
43
 
41
44
  static getInstance(): TooltipManager {
@@ -72,6 +75,15 @@ class TooltipManager {
72
75
  })
73
76
  }
74
77
 
78
+ updateCurrentTooltip(tooltipId: string, update: (tooltip: CurrentTooltip) => CurrentTooltip) {
79
+ this.currentTooltip.update((tooltip) => {
80
+ if (tooltip?.id === tooltipId) {
81
+ return update(tooltip)
82
+ }
83
+ return tooltip
84
+ })
85
+ }
86
+
75
87
  hideTooltip(editor: Editor | null, tooltipId: string, instant: boolean = false) {
76
88
  const hide = () => {
77
89
  // Only hide if this is the current tooltip
@@ -241,6 +253,7 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
241
253
  const editor = useMaybeEditor()
242
254
  const tooltipId = useRef<string>(uniqueId())
243
255
  const hasProvider = useContext(TooltipSingletonContext)
256
+ const showUiLabels = useValue('showUiLabels', () => editor?.user.getShowUiLabels(), [editor])
244
257
 
245
258
  const orientationCtx = useTldrawUiOrientation()
246
259
  const sideToUse = side ?? orientationCtx.tooltipSide
@@ -254,13 +267,30 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
254
267
  }
255
268
  }, [editor, hasProvider])
256
269
 
270
+ useLayoutEffect(() => {
271
+ if (hasProvider && tooltipManager.getCurrentTooltipData()?.id === tooltipId.current) {
272
+ tooltipManager.updateCurrentTooltip(tooltipId.current, (tooltip) => ({
273
+ ...tooltip,
274
+ content,
275
+ side: sideToUse,
276
+ sideOffset,
277
+ showOnMobile,
278
+ }))
279
+ }
280
+ }, [content, sideToUse, sideOffset, showOnMobile, hasProvider])
281
+
257
282
  // Don't show tooltip if disabled, no content, or UI labels are disabled
258
283
  if (disabled || !content) {
259
284
  return <>{children}</>
260
285
  }
261
286
 
262
- const delayDurationToUse =
263
- delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
287
+ let delayDurationToUse
288
+ if (showUiLabels) {
289
+ delayDurationToUse = 0
290
+ } else {
291
+ delayDurationToUse =
292
+ delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
293
+ }
264
294
 
265
295
  // Fallback to old behavior if no provider
266
296
  if (!hasProvider) {
@@ -213,7 +213,7 @@ export function TldrawUiMenuItem<
213
213
  icon={icon}
214
214
  onSelect={onSelect}
215
215
  onDragStart={onDragStart}
216
- labelToUse={labelToUse}
216
+ labelStr={labelStr}
217
217
  titleStr={titleStr}
218
218
  disabled={disabled}
219
219
  isSelected={isSelected}
@@ -247,7 +247,7 @@ export function TldrawUiMenuItem<
247
247
  icon={icon}
248
248
  onSelect={onSelect}
249
249
  onDragStart={onDragStart}
250
- labelToUse={labelToUse}
250
+ labelStr={labelStr}
251
251
  titleStr={titleStr}
252
252
  disabled={disabled}
253
253
  isSelected={isSelected}
@@ -392,7 +392,7 @@ function useDraggableEvents(
392
392
 
393
393
  function DraggableToolbarButton({
394
394
  id,
395
- labelToUse,
395
+ labelStr,
396
396
  titleStr,
397
397
  disabled,
398
398
  isSelected,
@@ -403,7 +403,7 @@ function DraggableToolbarButton({
403
403
  }: {
404
404
  id: string
405
405
  disabled: boolean
406
- labelToUse?: string
406
+ labelStr?: string
407
407
  titleStr?: string
408
408
  isSelected?: boolean
409
409
  icon: TLUiMenuItemProps['icon']
@@ -416,7 +416,7 @@ function DraggableToolbarButton({
416
416
  if (overflow) {
417
417
  return (
418
418
  <TldrawUiToolbarButton
419
- aria-label={labelToUse}
419
+ aria-label={labelStr}
420
420
  aria-pressed={isSelected ? 'true' : 'false'}
421
421
  isActive={isSelected}
422
422
  className="tlui-button-grid__button"
@@ -434,7 +434,7 @@ function DraggableToolbarButton({
434
434
 
435
435
  return (
436
436
  <TldrawUiToolbarButton
437
- aria-label={labelToUse}
437
+ aria-label={labelStr}
438
438
  aria-pressed={isSelected ? 'true' : 'false'}
439
439
  data-testid={`tools.${id}`}
440
440
  data-value={id}
@@ -7,8 +7,8 @@ import {
7
7
  assert,
8
8
  compact,
9
9
  isDefined,
10
+ markEventAsHandled,
10
11
  preventDefault,
11
- stopEventPropagation,
12
12
  uniq,
13
13
  useEditor,
14
14
  useMaybeEditor,
@@ -763,7 +763,7 @@ export function useNativeClipboardEvents() {
763
763
 
764
764
  const paste = (e: ClipboardEvent) => {
765
765
  if (disablingMiddleClickPaste) {
766
- stopEventPropagation(e)
766
+ markEventAsHandled(e)
767
767
  return
768
768
  }
769
769
 
@@ -122,6 +122,7 @@ export type TLUiTranslationKey =
122
122
  | 'action.zoom-to-fit'
123
123
  | 'action.zoom-to-selection'
124
124
  | 'assets.files.size-too-big'
125
+ | 'assets.files.maximum-size'
125
126
  | 'assets.files.type-not-allowed'
126
127
  | 'assets.files.upload-failed'
127
128
  | 'assets.files.amount-too-many'
@@ -92,9 +92,9 @@ export const DEFAULT_TRANSLATION = {
92
92
  'action.toggle-wrap-mode': 'Toggle Select on wrap',
93
93
  'action.toggle-reduce-motion.menu': 'Reduce motion',
94
94
  'action.toggle-reduce-motion': 'Toggle reduce motion',
95
- 'action.toggle-keyboard-shortcuts.menu': 'Keyboard shortcuts',
95
+ 'action.toggle-keyboard-shortcuts.menu': 'Enable keyboard shortcuts',
96
96
  'action.toggle-keyboard-shortcuts': 'Toggle keyboard shortcuts',
97
- 'action.toggle-ui-labels.menu': 'UI labels',
97
+ 'action.toggle-ui-labels.menu': 'Enable UI labels',
98
98
  'action.toggle-ui-labels': 'Toggle UI labels',
99
99
  'action.toggle-edge-scrolling.menu': 'Edge scrolling',
100
100
  'action.toggle-edge-scrolling': 'Toggle edge scrolling',
@@ -123,6 +123,7 @@ export const DEFAULT_TRANSLATION = {
123
123
  'action.zoom-to-fit': 'Zoom to fit',
124
124
  'action.zoom-to-selection': 'Zoom to selection',
125
125
  'assets.files.size-too-big': 'File size is too big',
126
+ 'assets.files.maximum-size': 'Maximum file size is {size}',
126
127
  'assets.files.type-not-allowed': 'File type is not allowed',
127
128
  'assets.files.upload-failed': 'Upload failed',
128
129
  'assets.files.amount-too-many': 'Too many files',
@@ -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.0ff72188cd53'
4
+ export const version = '3.16.0-canary.13ee933ed97f'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-09-08T07:34:47.834Z',
8
- patch: '2025-09-08T07:34:47.834Z',
7
+ minor: '2025-09-16T09:21:06.120Z',
8
+ patch: '2025-09-16T09:21:06.120Z',
9
9
  }
@@ -1,12 +1,50 @@
1
- import { Box, TLShapeId, createShapeId } from '@tldraw/editor'
1
+ import {
2
+ BaseBoxShapeUtil,
3
+ Box,
4
+ RecordProps,
5
+ T,
6
+ TLBaseShape,
7
+ TLShapeId,
8
+ createShapeId,
9
+ } from '@tldraw/editor'
2
10
  import { vi } from 'vitest'
3
11
  import { TestEditor } from './TestEditor'
4
12
  import { TL } from './test-jsx'
5
13
 
14
+ // Custom uncullable shape type for testing canCull override
15
+ type UncullableShape = TLBaseShape<'uncullable', { w: number; h: number }>
16
+
17
+ class UncullableShapeUtil extends BaseBoxShapeUtil<UncullableShape> {
18
+ static override type = 'uncullable' as const
19
+ static override props: RecordProps<UncullableShape> = {
20
+ w: T.number,
21
+ h: T.number,
22
+ }
23
+
24
+ override canCull() {
25
+ return false
26
+ }
27
+
28
+ override getDefaultProps(): UncullableShape['props'] {
29
+ return {
30
+ w: 100,
31
+ h: 100,
32
+ }
33
+ }
34
+
35
+ override component() {
36
+ return <div>Uncullable shape</div>
37
+ }
38
+
39
+ override indicator() {
40
+ return <div>Uncullable shape</div>
41
+ }
42
+ }
43
+
6
44
  let editor: TestEditor
7
45
 
8
46
  beforeEach(() => {
9
- editor = new TestEditor()
47
+ editor = new TestEditor({ shapeUtils: [UncullableShapeUtil] })
10
48
  editor.setScreenBounds({ x: 0, y: 0, w: 1800, h: 900 })
11
49
  })
12
50
 
@@ -203,3 +241,34 @@ it('works for shapes that are outside of the viewport, but are then moved inside
203
241
  // Arrow should also not be culled
204
242
  expect(editor.getCulledShapes()).toEqual(new Set())
205
243
  })
244
+
245
+ it('respects canCull override - shapes that cannot be culled are never culled', () => {
246
+ const cullableShapeId = createShapeId()
247
+ const uncullableShapeId = createShapeId()
248
+
249
+ // Create both shapes outside the viewport
250
+ editor.createShapes([
251
+ {
252
+ id: cullableShapeId,
253
+ type: 'geo',
254
+ x: -2000, // Way outside viewport
255
+ y: -2000,
256
+ props: { w: 100, h: 100 },
257
+ },
258
+ {
259
+ id: uncullableShapeId,
260
+ type: 'uncullable',
261
+ x: -2000, // Way outside viewport
262
+ y: -2000,
263
+ props: { w: 100, h: 100 },
264
+ },
265
+ ])
266
+
267
+ const culledShapes = editor.getCulledShapes()
268
+
269
+ // The regular geo shape should be culled since it's outside the viewport
270
+ expect(culledShapes).toContain(cullableShapeId)
271
+
272
+ // The uncullable shape should NOT be culled even though it's outside the viewport
273
+ expect(culledShapes).not.toContain(uncullableShapeId)
274
+ })