tldraw 3.16.0-canary.cc5427cdff41 → 3.16.0-canary.cd822ae4ebee

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 +92 -9
  2. package/dist-cjs/index.js +5 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/defaultExternalContentHandlers.js +10 -0
  5. package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
  6. package/dist-cjs/lib/shapes/arrow/arrow-types.js.map +1 -1
  7. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js +3 -2
  8. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js.map +2 -2
  9. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js +1 -1
  10. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js.map +2 -2
  11. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js +4 -4
  12. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js.map +2 -2
  13. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +2 -1
  14. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  15. package/dist-cjs/lib/shapes/frame/components/FrameLabelInput.js +8 -2
  16. package/dist-cjs/lib/shapes/frame/components/FrameLabelInput.js.map +2 -2
  17. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +1 -0
  18. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
  19. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +2 -1
  20. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  21. package/dist-cjs/lib/shapes/shared/HyperlinkButton.js +4 -4
  22. package/dist-cjs/lib/shapes/shared/HyperlinkButton.js.map +2 -2
  23. package/dist-cjs/lib/shapes/shared/useEditablePlainText.js +3 -3
  24. package/dist-cjs/lib/shapes/shared/useEditablePlainText.js.map +2 -2
  25. package/dist-cjs/lib/shapes/text/PlainTextArea.js +3 -2
  26. package/dist-cjs/lib/shapes/text/PlainTextArea.js.map +2 -2
  27. package/dist-cjs/lib/shapes/text/RichTextArea.js +3 -3
  28. package/dist-cjs/lib/shapes/text/RichTextArea.js.map +2 -2
  29. package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js +3 -1
  30. package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js.map +2 -2
  31. package/dist-cjs/lib/ui/components/A11y.js +1 -1
  32. package/dist-cjs/lib/ui/components/A11y.js.map +2 -2
  33. package/dist-cjs/lib/ui/components/LanguageMenu.js +1 -0
  34. package/dist-cjs/lib/ui/components/LanguageMenu.js.map +2 -2
  35. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js +2 -1
  36. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js.map +2 -2
  37. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js +1 -1
  38. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js.map +2 -2
  39. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js +11 -2
  40. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js.map +2 -2
  41. package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js +3 -2
  42. package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js.map +2 -2
  43. package/dist-cjs/lib/ui/components/Toolbar/LinkEditor.js +5 -4
  44. package/dist-cjs/lib/ui/components/Toolbar/LinkEditor.js.map +2 -2
  45. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +1 -1
  46. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +2 -2
  47. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +6 -2
  48. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js.map +2 -2
  49. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +1 -1
  50. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js.map +2 -2
  51. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js +5 -3
  52. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js.map +2 -2
  53. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +1 -1
  54. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
  55. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +1 -0
  56. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
  57. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +40 -3
  58. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  59. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.js +3 -0
  60. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.js.map +2 -2
  61. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +8 -8
  62. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  63. package/dist-cjs/lib/ui/context/actions.js +6 -0
  64. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  65. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js +1 -1
  66. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js.map +2 -2
  67. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  68. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +4 -2
  69. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  70. package/dist-cjs/lib/ui/version.js +3 -3
  71. package/dist-cjs/lib/ui/version.js.map +1 -1
  72. package/dist-esm/index.d.mts +92 -9
  73. package/dist-esm/index.mjs +9 -1
  74. package/dist-esm/index.mjs.map +2 -2
  75. package/dist-esm/lib/defaultExternalContentHandlers.mjs +10 -0
  76. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  77. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs +3 -2
  78. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs.map +2 -2
  79. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs +1 -1
  80. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs.map +2 -2
  81. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs +4 -5
  82. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs.map +2 -2
  83. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +2 -1
  84. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  85. package/dist-esm/lib/shapes/frame/components/FrameLabelInput.mjs +9 -3
  86. package/dist-esm/lib/shapes/frame/components/FrameLabelInput.mjs.map +2 -2
  87. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +1 -0
  88. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  89. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +2 -1
  90. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  91. package/dist-esm/lib/shapes/shared/HyperlinkButton.mjs +5 -5
  92. package/dist-esm/lib/shapes/shared/HyperlinkButton.mjs.map +2 -2
  93. package/dist-esm/lib/shapes/shared/useEditablePlainText.mjs +3 -4
  94. package/dist-esm/lib/shapes/shared/useEditablePlainText.mjs.map +2 -2
  95. package/dist-esm/lib/shapes/text/PlainTextArea.mjs +4 -3
  96. package/dist-esm/lib/shapes/text/PlainTextArea.mjs.map +2 -2
  97. package/dist-esm/lib/shapes/text/RichTextArea.mjs +3 -4
  98. package/dist-esm/lib/shapes/text/RichTextArea.mjs.map +2 -2
  99. package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs +3 -1
  100. package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs.map +2 -2
  101. package/dist-esm/lib/ui/components/A11y.mjs +1 -2
  102. package/dist-esm/lib/ui/components/A11y.mjs.map +2 -2
  103. package/dist-esm/lib/ui/components/LanguageMenu.mjs +1 -0
  104. package/dist-esm/lib/ui/components/LanguageMenu.mjs.map +2 -2
  105. package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs +2 -1
  106. package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs.map +2 -2
  107. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs +1 -2
  108. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs.map +2 -2
  109. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs +11 -2
  110. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs.map +2 -2
  111. package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs +3 -2
  112. package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs.map +2 -2
  113. package/dist-esm/lib/ui/components/Toolbar/LinkEditor.mjs +5 -4
  114. package/dist-esm/lib/ui/components/Toolbar/LinkEditor.mjs.map +2 -2
  115. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +1 -1
  116. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +2 -2
  117. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs +6 -2
  118. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs.map +2 -2
  119. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs +1 -2
  120. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs.map +2 -2
  121. package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs +6 -4
  122. package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs.map +2 -2
  123. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +1 -1
  124. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
  125. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +1 -0
  126. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
  127. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +41 -3
  128. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  129. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.mjs +3 -0
  130. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.mjs.map +2 -2
  131. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +8 -8
  132. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  133. package/dist-esm/lib/ui/context/actions.mjs +6 -0
  134. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  135. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs +1 -2
  136. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs.map +2 -2
  137. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +4 -2
  138. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  139. package/dist-esm/lib/ui/version.mjs +3 -3
  140. package/dist-esm/lib/ui/version.mjs.map +1 -1
  141. package/package.json +3 -3
  142. package/src/index.ts +7 -0
  143. package/src/lib/defaultExternalContentHandlers.ts +14 -0
  144. package/src/lib/shapes/arrow/ArrowShapeOptions.test.ts +83 -13
  145. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +99 -5
  146. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +41 -0
  147. package/src/lib/shapes/arrow/arrow-types.ts +3 -5
  148. package/src/lib/shapes/arrow/arrowTargetState.ts +34 -3
  149. package/src/lib/shapes/arrow/toolStates/Pointing.tsx +1 -1
  150. package/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx +4 -5
  151. package/src/lib/shapes/frame/FrameShapeUtil.tsx +1 -0
  152. package/src/lib/shapes/frame/components/FrameLabelInput.tsx +10 -3
  153. package/src/lib/shapes/geo/GeoShapeUtil.tsx +1 -0
  154. package/src/lib/shapes/note/NoteShapeUtil.tsx +1 -0
  155. package/src/lib/shapes/shared/HyperlinkButton.tsx +5 -5
  156. package/src/lib/shapes/shared/useEditablePlainText.ts +3 -4
  157. package/src/lib/shapes/text/PlainTextArea.tsx +4 -3
  158. package/src/lib/shapes/text/RichTextArea.tsx +3 -4
  159. package/src/lib/tools/SelectTool/childStates/DraggingHandle.tsx +6 -2
  160. package/src/lib/ui/components/A11y.tsx +1 -2
  161. package/src/lib/ui/components/LanguageMenu.tsx +1 -0
  162. package/src/lib/ui/components/Minimap/DefaultMinimap.tsx +2 -1
  163. package/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx +1 -2
  164. package/src/lib/ui/components/StylePanel/StylePanelButtonPicker.tsx +9 -2
  165. package/src/lib/ui/components/Toolbar/AltTextEditor.tsx +4 -3
  166. package/src/lib/ui/components/Toolbar/LinkEditor.tsx +6 -5
  167. package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +1 -1
  168. package/src/lib/ui/components/Toolbar/ToggleToolLockedButton.tsx +9 -2
  169. package/src/lib/ui/components/primitives/TldrawUiContextualToolbar.tsx +1 -2
  170. package/src/lib/ui/components/primitives/TldrawUiInput.tsx +6 -3
  171. package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +2 -2
  172. package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +2 -1
  173. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +57 -13
  174. package/src/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.tsx +4 -0
  175. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +9 -9
  176. package/src/lib/ui/context/actions.tsx +13 -0
  177. package/src/lib/ui/hooks/useClipboardEvents.ts +1 -2
  178. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +2 -0
  179. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +4 -2
  180. package/src/lib/ui/version.ts +3 -3
  181. package/src/lib/ui.css +24 -1
  182. package/src/test/TestEditor.ts +8 -2
  183. package/src/test/getCulledShapes.test.tsx +71 -2
  184. package/tldraw.css +24 -1
@@ -13,6 +13,7 @@ import {
13
13
  sortByIndex,
14
14
  structuredClone,
15
15
  } from '@tldraw/editor'
16
+ import { ArrowShapeUtil } from '../../../shapes/arrow/ArrowShapeUtil'
16
17
  import { clearArrowTargetState } from '../../../shapes/arrow/arrowTargetState'
17
18
  import { getArrowBindings } from '../../../shapes/arrow/shared'
18
19
 
@@ -135,10 +136,13 @@ export class DraggingHandle extends StateNode {
135
136
  }
136
137
 
137
138
  // Only relevant to arrows
138
- private exactTimeout = -1 as any
139
+ private exactTimeout = -1
139
140
 
140
141
  // Only relevant to arrows
141
142
  private resetExactTimeout() {
143
+ const arrowUtil = this.editor.getShapeUtil<ArrowShapeUtil>('arrow')
144
+ const timeoutValue = arrowUtil.options.pointingPreciseTimeout
145
+
142
146
  if (this.exactTimeout !== -1) {
143
147
  this.clearExactTimeout()
144
148
  }
@@ -150,7 +154,7 @@ export class DraggingHandle extends StateNode {
150
154
  this.update()
151
155
  }
152
156
  this.exactTimeout = -1
153
- }, 750)
157
+ }, timeoutValue)
154
158
  }
155
159
 
156
160
  // Only relevant to arrows
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  debugFlags,
3
3
  Editor,
4
- stopEventPropagation,
5
4
  TLGeoShape,
6
5
  TLShapeId,
7
6
  unsafe__withoutCapture,
@@ -23,7 +22,7 @@ export function SkipToMainContent() {
23
22
 
24
23
  const handleNavigateToFirstShape = useCallback(
25
24
  (e: MouseEvent | KeyboardEvent) => {
26
- stopEventPropagation(e)
25
+ editor.markEventAsHandled(e)
27
26
  button.current?.blur()
28
27
  const shapes = editor.getCurrentPageShapesInReadingOrder()
29
28
  if (!shapes.length) return
@@ -18,6 +18,7 @@ export function LanguageMenu() {
18
18
  {LANGUAGES.map(({ locale, label }) => (
19
19
  <TldrawUiMenuCheckboxItem
20
20
  id={`language-${locale}`}
21
+ lang={locale}
21
22
  key={locale}
22
23
  title={locale}
23
24
  label={label}
@@ -159,7 +159,7 @@ export function DefaultMinimap() {
159
159
  type: 'pointer',
160
160
  target: 'canvas',
161
161
  name: 'pointer_move',
162
- ...getPointerInfo(e),
162
+ ...getPointerInfo(editor, e),
163
163
  point: screenPoint,
164
164
  isPen: editor.getInstanceState().isPenMode,
165
165
  }
@@ -204,6 +204,7 @@ export function DefaultMinimap() {
204
204
  <canvas
205
205
  role="img"
206
206
  aria-label={msg('navigation-zone.minimap')}
207
+ data-testid="minimap.canvas"
207
208
  ref={rCanvas}
208
209
  className="tlui-minimap__canvas"
209
210
  onDoubleClick={onDoubleClick}
@@ -3,7 +3,6 @@ import {
3
3
  TLPageId,
4
4
  releasePointerCapture,
5
5
  setPointerCapture,
6
- stopEventPropagation,
7
6
  tlenv,
8
7
  useEditor,
9
8
  useValue,
@@ -451,7 +450,7 @@ export const DefaultPageMenu = memo(function DefaultPageMenu() {
451
450
  if (e.key === 'Enter') {
452
451
  if (page.id === currentPage.id) {
453
452
  toggleEditing()
454
- stopEventPropagation(e)
453
+ editor.markEventAsHandled(e)
455
454
  }
456
455
  }
457
456
  }}
@@ -137,6 +137,7 @@ export const StylePanelButtonPicker = memo(function StylePanelButtonPicker<T ext
137
137
  >
138
138
  <Layout>
139
139
  {items.map((item) => {
140
+ const isActive = value.type === 'shared' && value.value === item.value
140
141
  const label =
141
142
  title + ' — ' + msg(`${uiType}-style.${item.value}` as TLUiTranslationKey)
142
143
  return (
@@ -145,10 +146,16 @@ export const StylePanelButtonPicker = memo(function StylePanelButtonPicker<T ext
145
146
  key={item.value}
146
147
  data-id={item.value}
147
148
  data-testid={`style.${uiType}.${item.value}`}
148
- aria-label={label}
149
+ aria-label={label + (isActive ? ` (${msg('style-panel.selected')})` : '')}
150
+ tooltip={
151
+ <>
152
+ <div>{label}</div>
153
+ {isActive ? <div>({msg('style-panel.selected')})</div> : null}
154
+ </>
155
+ }
149
156
  value={item.value}
150
157
  data-state={value.type === 'shared' && value.value === item.value ? 'on' : 'off'}
151
- data-isactive={value.type === 'shared' && value.value === item.value}
158
+ data-isactive={isActive}
152
159
  title={label}
153
160
  style={
154
161
  style === (DefaultColorStyle as StyleProp<unknown>)
@@ -2,9 +2,9 @@ import { preventDefault, TLShape, TLShapeId, useEditor } from '@tldraw/editor'
2
2
  import { useCallback, useEffect, useRef, useState } from 'react'
3
3
  import { useUiEvents } from '../../context/events'
4
4
  import { useTranslation } from '../../hooks/useTranslation/useTranslation'
5
+ import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
5
6
  import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
6
7
  import { TldrawUiInput } from '../primitives/TldrawUiInput'
7
- import { TldrawUiToolbarButton } from '../primitives/TldrawUiToolbar'
8
8
 
9
9
  /** @public */
10
10
  export interface AltTextEditorProps {
@@ -70,13 +70,14 @@ export function AltTextEditor({ shapeId, onClose, source }: AltTextEditorProps)
70
70
  data-testid="media-toolbar.alt-text-input"
71
71
  value={altText}
72
72
  placeholder={msg('tool.media-alt-text-desc')}
73
+ aria-label={msg('tool.media-alt-text-desc')}
73
74
  onValueChange={handleValueChange}
74
75
  onComplete={handleComplete}
75
76
  onCancel={handleAltTextCancel}
76
77
  disabled={isReadonly}
77
78
  />
78
79
  {!isReadonly && (
79
- <TldrawUiToolbarButton
80
+ <TldrawUiButton
80
81
  title={msg('tool.media-alt-text-confirm')}
81
82
  data-testid="tool.media-alt-text-confirm"
82
83
  type="icon"
@@ -84,7 +85,7 @@ export function AltTextEditor({ shapeId, onClose, source }: AltTextEditorProps)
84
85
  onClick={handleConfirm}
85
86
  >
86
87
  <TldrawUiButtonIcon small icon="check" />
87
- </TldrawUiToolbarButton>
88
+ </TldrawUiButton>
88
89
  )}
89
90
  </>
90
91
  )
@@ -2,9 +2,9 @@ import { preventDefault, TiptapEditor, useEditor } from '@tldraw/editor'
2
2
  import { useEffect, useRef, useState } from 'react'
3
3
  import { useUiEvents } from '../../context/events'
4
4
  import { useTranslation } from '../../hooks/useTranslation/useTranslation'
5
+ import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
5
6
  import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
6
7
  import { TldrawUiInput } from '../primitives/TldrawUiInput'
7
- import { TldrawUiToolbarButton } from '../primitives/TldrawUiToolbar'
8
8
 
9
9
  /** @public */
10
10
  export interface LinkEditorProps {
@@ -75,8 +75,9 @@ export function LinkEditor({ textEditor, value: initialValue, onClose }: LinkEdi
75
75
  onComplete={handleLinkComplete}
76
76
  onCancel={handleLinkCancel}
77
77
  placeholder="example.com"
78
+ aria-label="example.com"
78
79
  />
79
- <TldrawUiToolbarButton
80
+ <TldrawUiButton
80
81
  className="tlui-rich-text__toolbar-link-visit"
81
82
  title={msg('tool.rich-text-link-visit')}
82
83
  type="icon"
@@ -85,8 +86,8 @@ export function LinkEditor({ textEditor, value: initialValue, onClose }: LinkEdi
85
86
  disabled={!value}
86
87
  >
87
88
  <TldrawUiButtonIcon small icon="external-link" />
88
- </TldrawUiToolbarButton>
89
- <TldrawUiToolbarButton
89
+ </TldrawUiButton>
90
+ <TldrawUiButton
90
91
  className="tlui-rich-text__toolbar-link-remove"
91
92
  title={msg('tool.rich-text-link-remove')}
92
93
  data-testid="rich-text.link-remove"
@@ -95,7 +96,7 @@ export function LinkEditor({ textEditor, value: initialValue, onClose }: LinkEdi
95
96
  onClick={handleRemoveLink}
96
97
  >
97
98
  <TldrawUiButtonIcon small icon="trash" />
98
- </TldrawUiToolbarButton>
99
+ </TldrawUiButton>
99
100
  </>
100
101
  )
101
102
  }
@@ -101,7 +101,7 @@ export function OverflowingToolbar({
101
101
  items: collectItems(child.children),
102
102
  element: child as HTMLElement,
103
103
  })
104
- } else {
104
+ } else if (!child.hasAttribute('data-radix-popper-content-wrapper')) {
105
105
  items.push({ type: 'item', element: child as HTMLElement })
106
106
  }
107
107
  }
@@ -1,8 +1,10 @@
1
1
  import { useEditor, useValue } from '@tldraw/editor'
2
2
  import classNames from 'classnames'
3
3
  import { PORTRAIT_BREAKPOINT } from '../../constants'
4
+ import { useActions } from '../../context/actions'
4
5
  import { useBreakpoint } from '../../context/breakpoints'
5
6
  import { useTranslation } from '../../hooks/useTranslation/useTranslation'
7
+ import { kbdStr } from '../../kbd-utils'
6
8
  import { TldrawUiButton } from '../primitives/Button/TldrawUiButton'
7
9
  import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'
8
10
  import { TldrawUiTooltip } from '../primitives/TldrawUiTooltip'
@@ -17,6 +19,7 @@ export function ToggleToolLockedButton({ activeToolId }: ToggleToolLockedButtonP
17
19
  const editor = useEditor()
18
20
  const breakpoint = useBreakpoint()
19
21
  const msg = useTranslation()
22
+ const actions = useActions()
20
23
 
21
24
  const isToolLocked = useValue('is tool locked', () => editor.getInstanceState().isToolLocked, [
22
25
  editor,
@@ -25,11 +28,15 @@ export function ToggleToolLockedButton({ activeToolId }: ToggleToolLockedButtonP
25
28
 
26
29
  if (!activeToolId || !tool.isLockable) return null
27
30
 
31
+ const toggleLockAction = actions['toggle-tool-lock']
32
+ const tooltipContent = toggleLockAction?.kbd
33
+ ? `${msg('action.toggle-tool-lock')} ${kbdStr(toggleLockAction.kbd)}`
34
+ : msg('action.toggle-tool-lock')
35
+
28
36
  return (
29
- <TldrawUiTooltip content={msg('action.toggle-tool-lock')}>
37
+ <TldrawUiTooltip content={tooltipContent}>
30
38
  <TldrawUiButton
31
39
  type="normal"
32
- title={msg('action.toggle-tool-lock')}
33
40
  data-testid="tool-lock"
34
41
  className={classNames('tlui-main-toolbar__lock-button', {
35
42
  'tlui-main-toolbar__lock-button__mobile': breakpoint < PORTRAIT_BREAKPOINT.TABLET_SM,
@@ -4,7 +4,6 @@ import {
4
4
  clamp,
5
5
  Editor,
6
6
  react,
7
- stopEventPropagation,
8
7
  useAtom,
9
8
  useEditor,
10
9
  usePassThroughMouseOverEvents,
@@ -170,7 +169,7 @@ export const TldrawUiContextualToolbar = ({
170
169
  data-visible={false}
171
170
  data-testid="contextual-toolbar"
172
171
  className={classNames('tlui-contextual-toolbar', className)}
173
- onPointerDown={stopEventPropagation}
172
+ onPointerDown={editor.markEventAsHandled}
174
173
  >
175
174
  <TldrawUiToolbar
176
175
  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'
@@ -35,6 +35,7 @@ export interface TLUiInputProps {
35
35
  shouldManuallyMaintainScrollPositionWhenFocused?: boolean
36
36
  value?: string
37
37
  'data-testid'?: string
38
+ 'aria-label'?: string
38
39
  }
39
40
 
40
41
  /** @public @react */
@@ -60,6 +61,7 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
60
61
  value,
61
62
  'data-testid': dataTestId,
62
63
  disabled,
64
+ 'aria-label': ariaLabel,
63
65
  },
64
66
  ref
65
67
  ) {
@@ -118,7 +120,7 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
118
120
  // `onChange` with a duplicated text value.
119
121
  if (isComposing.current) return
120
122
  e.currentTarget.blur()
121
- stopEventPropagation(e)
123
+ e.stopPropagation()
122
124
  onComplete?.(e.currentTarget.value)
123
125
  break
124
126
  }
@@ -126,7 +128,7 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
126
128
  e.currentTarget.value = rInitialValue.current
127
129
  onCancel?.(e.currentTarget.value)
128
130
  e.currentTarget.blur()
129
- stopEventPropagation(e)
131
+ e.stopPropagation()
130
132
  break
131
133
  }
132
134
  }
@@ -198,6 +200,7 @@ export const TldrawUiInput = React.forwardRef<HTMLInputElement, TLUiInputProps>(
198
200
  onCompositionStart={handleCompositionStart}
199
201
  onCompositionEnd={handleCompositionEnd}
200
202
  autoFocus={autoFocus}
203
+ aria-label={ariaLabel}
201
204
  placeholder={placeholder}
202
205
  value={value}
203
206
  data-testid={dataTestId}
@@ -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
@@ -71,6 +71,7 @@ export const TldrawUiToolbarButton = React.forwardRef<HTMLButtonElement, TLUiToo
71
71
  draggable={false}
72
72
  data-isactive={isActive}
73
73
  {...props}
74
+ aria-label={props.title}
74
75
  // The tooltip takes care of this.
75
76
  title={undefined}
76
77
  className={classnames('tlui-button', `tlui-button__${type}`, props.className)}
@@ -127,7 +128,7 @@ export interface TLUiToolbarToggleItemProps extends React.HTMLAttributes<HTMLBut
127
128
  className?: string
128
129
  type: 'icon' | 'tool'
129
130
  value: string
130
- tooltip?: string
131
+ tooltip?: React.ReactNode
131
132
  }
132
133
 
133
134
  /** @public @react */
@@ -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
@@ -159,6 +171,20 @@ function TooltipSingleton() {
159
171
  }
160
172
  }, [cameraState, isOpen, currentTooltip, editor])
161
173
 
174
+ useEffect(() => {
175
+ function handleKeyDown(event: KeyboardEvent) {
176
+ if (event.key === 'Escape' && currentTooltip && isOpen) {
177
+ tooltipManager.hideTooltip(editor, currentTooltip.id)
178
+ event.stopPropagation()
179
+ }
180
+ }
181
+
182
+ document.addEventListener('keydown', handleKeyDown, { capture: true })
183
+ return () => {
184
+ document.removeEventListener('keydown', handleKeyDown, { capture: true })
185
+ }
186
+ }, [editor, currentTooltip, isOpen])
187
+
162
188
  // Update open state and trigger position
163
189
  useEffect(() => {
164
190
  let timer: ReturnType<typeof setTimeout> | null = null
@@ -241,6 +267,7 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
241
267
  const editor = useMaybeEditor()
242
268
  const tooltipId = useRef<string>(uniqueId())
243
269
  const hasProvider = useContext(TooltipSingletonContext)
270
+ const showUiLabels = useValue('showUiLabels', () => editor?.user.getShowUiLabels(), [editor])
244
271
 
245
272
  const orientationCtx = useTldrawUiOrientation()
246
273
  const sideToUse = side ?? orientationCtx.tooltipSide
@@ -254,18 +281,35 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
254
281
  }
255
282
  }, [editor, hasProvider])
256
283
 
284
+ useLayoutEffect(() => {
285
+ if (hasProvider && tooltipManager.getCurrentTooltipData()?.id === tooltipId.current) {
286
+ tooltipManager.updateCurrentTooltip(tooltipId.current, (tooltip) => ({
287
+ ...tooltip,
288
+ content,
289
+ side: sideToUse,
290
+ sideOffset,
291
+ showOnMobile,
292
+ }))
293
+ }
294
+ }, [content, sideToUse, sideOffset, showOnMobile, hasProvider])
295
+
257
296
  // Don't show tooltip if disabled, no content, or UI labels are disabled
258
297
  if (disabled || !content) {
259
298
  return <>{children}</>
260
299
  }
261
300
 
262
- const delayDurationToUse =
263
- delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
301
+ let delayDurationToUse
302
+ if (showUiLabels) {
303
+ delayDurationToUse = 0
304
+ } else {
305
+ delayDurationToUse =
306
+ delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
307
+ }
264
308
 
265
309
  // Fallback to old behavior if no provider
266
- if (!hasProvider) {
310
+ if (!hasProvider || showUiLabels) {
267
311
  return (
268
- <_Tooltip.Root delayDuration={delayDurationToUse} disableHoverableContent>
312
+ <_Tooltip.Root delayDuration={delayDurationToUse} disableHoverableContent={!showUiLabels}>
269
313
  <_Tooltip.Trigger asChild ref={ref}>
270
314
  {children}
271
315
  </_Tooltip.Trigger>
@@ -19,6 +19,7 @@ export interface TLUiMenuCheckboxItemProps<
19
19
  kbd?: string
20
20
  title?: string
21
21
  label?: TranslationKey | { [key: string]: TranslationKey }
22
+ lang?: string
22
23
  readonlyOk?: boolean
23
24
  onSelect(source: TLUiEventSource): Promise<void> | void
24
25
  toggle?: boolean
@@ -34,6 +35,7 @@ export function TldrawUiMenuCheckboxItem<
34
35
  id,
35
36
  kbd,
36
37
  label,
38
+ lang,
37
39
  readonlyOk,
38
40
  onSelect,
39
41
  toggle = false,
@@ -55,6 +57,7 @@ export function TldrawUiMenuCheckboxItem<
55
57
  return (
56
58
  <_DropdownMenu.CheckboxItem
57
59
  dir="ltr"
60
+ lang={lang}
58
61
  className="tlui-button tlui-button__menu tlui-button__checkbox"
59
62
  title={labelStr}
60
63
  onSelect={(e) => {
@@ -84,6 +87,7 @@ export function TldrawUiMenuCheckboxItem<
84
87
  key={id}
85
88
  className="tlui-button tlui-button__menu tlui-button__checkbox"
86
89
  dir="ltr"
90
+ lang={lang}
87
91
  title={labelStr}
88
92
  onSelect={(e) => {
89
93
  onSelect(sourceId)
@@ -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}
@@ -333,7 +333,7 @@ function useDraggableEvents(
333
333
  type: 'pointer',
334
334
  target: 'canvas',
335
335
  name: 'pointer_down',
336
- ...getPointerInfo(e),
336
+ ...getPointerInfo(editor, e),
337
337
  point: screenSpaceStart,
338
338
  })
339
339
 
@@ -345,7 +345,7 @@ function useDraggableEvents(
345
345
  type: 'pointer',
346
346
  target: 'canvas',
347
347
  name: 'pointer_move',
348
- ...getPointerInfo(e),
348
+ ...getPointerInfo(editor, e),
349
349
  point: screenSpaceStart,
350
350
  })
351
351
 
@@ -365,7 +365,7 @@ function useDraggableEvents(
365
365
  type: 'pointer',
366
366
  target: 'canvas',
367
367
  name: 'pointer_up',
368
- ...getPointerInfo(e),
368
+ ...getPointerInfo(editor, e),
369
369
  })
370
370
  }
371
371
 
@@ -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}
@@ -1584,6 +1584,19 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1584
1584
  onSelect: async (source) => {
1585
1585
  if (!canApplySelectionAction()) return
1586
1586
 
1587
+ const onlySelectedShape = editor.getOnlySelectedShape()
1588
+ if (
1589
+ onlySelectedShape &&
1590
+ (editor.isShapeOfType<TLImageShape>(onlySelectedShape, 'image') ||
1591
+ editor.isShapeOfType<TLVideoShape>(onlySelectedShape, 'video'))
1592
+ ) {
1593
+ const firstToolbarButton = editor
1594
+ .getContainer()
1595
+ .querySelector('.tlui-contextual-toolbar button:first-child') as HTMLElement | null
1596
+ firstToolbarButton?.focus()
1597
+ return
1598
+ }
1599
+
1587
1600
  const firstButton = editor
1588
1601
  .getContainer()
1589
1602
  .querySelector('.tlui-style-panel button') as HTMLElement | null
@@ -8,7 +8,6 @@ import {
8
8
  compact,
9
9
  isDefined,
10
10
  preventDefault,
11
- stopEventPropagation,
12
11
  uniq,
13
12
  useEditor,
14
13
  useMaybeEditor,
@@ -763,7 +762,7 @@ export function useNativeClipboardEvents() {
763
762
 
764
763
  const paste = (e: ClipboardEvent) => {
765
764
  if (disablingMiddleClickPaste) {
766
- stopEventPropagation(e)
765
+ editor.markEventAsHandled(e)
767
766
  return
768
767
  }
769
768
 
@@ -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'
@@ -411,6 +412,7 @@ export type TLUiTranslationKey =
411
412
  | 'style-panel.opacity'
412
413
  | 'style-panel.size'
413
414
  | 'style-panel.spline'
415
+ | 'style-panel.selected'
414
416
  | 'tool-panel.title'
415
417
  | 'tool-panel.more'
416
418
  | 'navigation-zone.title'
@@ -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',
@@ -414,6 +415,7 @@ export const DEFAULT_TRANSLATION = {
414
415
  'style-panel.opacity': 'Opacity',
415
416
  'style-panel.size': 'Size',
416
417
  'style-panel.spline': 'Spline',
418
+ 'style-panel.selected': 'selected',
417
419
  'tool-panel.title': 'Tools',
418
420
  'tool-panel.more': 'More',
419
421
  'navigation-zone.title': 'Navigation',