tldraw 3.16.0-canary.e455ab838b8f → 3.16.0-canary.e618c2fbc95d

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 (97) hide show
  1. package/dist-cjs/index.d.ts +89 -9
  2. package/dist-cjs/index.js +6 -2
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/shapes/arrow/arrow-types.js.map +1 -1
  5. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js +3 -2
  6. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js.map +2 -2
  7. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js +1 -1
  8. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js.map +2 -2
  9. package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js +3 -1
  10. package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js.map +2 -2
  11. package/dist-cjs/lib/ui/components/AccessibilityMenu.js +1 -1
  12. package/dist-cjs/lib/ui/components/AccessibilityMenu.js.map +2 -2
  13. package/dist-cjs/lib/ui/components/InputModeMenu.js +77 -0
  14. package/dist-cjs/lib/ui/components/InputModeMenu.js.map +7 -0
  15. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js +2 -0
  16. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js.map +2 -2
  17. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +4 -2
  18. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js.map +2 -2
  19. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +2 -2
  20. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
  21. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js +1 -1
  22. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js.map +2 -2
  23. package/dist-cjs/lib/ui/components/StylePanel/StylePanelContext.js +4 -2
  24. package/dist-cjs/lib/ui/components/StylePanel/StylePanelContext.js.map +2 -2
  25. package/dist-cjs/lib/ui/components/menu-items.js +6 -4
  26. package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
  27. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +32 -21
  28. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  29. package/dist-cjs/lib/ui/context/actions.js +5 -25
  30. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  31. package/dist-cjs/lib/ui/context/events.js.map +2 -2
  32. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  33. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +8 -2
  34. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  35. package/dist-cjs/lib/ui/version.js +3 -3
  36. package/dist-cjs/lib/ui/version.js.map +1 -1
  37. package/dist-esm/index.d.mts +89 -9
  38. package/dist-esm/index.mjs +11 -3
  39. package/dist-esm/index.mjs.map +2 -2
  40. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs +3 -2
  41. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs.map +2 -2
  42. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs +1 -1
  43. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs.map +2 -2
  44. package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs +3 -1
  45. package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs.map +2 -2
  46. package/dist-esm/lib/ui/components/AccessibilityMenu.mjs +3 -3
  47. package/dist-esm/lib/ui/components/AccessibilityMenu.mjs.map +2 -2
  48. package/dist-esm/lib/ui/components/InputModeMenu.mjs +57 -0
  49. package/dist-esm/lib/ui/components/InputModeMenu.mjs.map +7 -0
  50. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs +2 -0
  51. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs.map +2 -2
  52. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs +4 -2
  53. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs.map +2 -2
  54. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +2 -2
  55. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
  56. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs +1 -1
  57. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs.map +2 -2
  58. package/dist-esm/lib/ui/components/StylePanel/StylePanelContext.mjs +4 -2
  59. package/dist-esm/lib/ui/components/StylePanel/StylePanelContext.mjs.map +2 -2
  60. package/dist-esm/lib/ui/components/menu-items.mjs +6 -4
  61. package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
  62. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +32 -21
  63. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  64. package/dist-esm/lib/ui/context/actions.mjs +5 -25
  65. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  66. package/dist-esm/lib/ui/context/events.mjs.map +2 -2
  67. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +8 -2
  68. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  69. package/dist-esm/lib/ui/version.mjs +3 -3
  70. package/dist-esm/lib/ui/version.mjs.map +1 -1
  71. package/package.json +3 -3
  72. package/src/index.ts +8 -1
  73. package/src/lib/shapes/arrow/ArrowShapeOptions.test.ts +83 -13
  74. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +97 -3
  75. package/src/lib/shapes/arrow/arrow-types.ts +3 -5
  76. package/src/lib/shapes/arrow/arrowTargetState.ts +34 -3
  77. package/src/lib/shapes/arrow/toolStates/Pointing.tsx +1 -1
  78. package/src/lib/tools/SelectTool/childStates/DraggingHandle.tsx +6 -2
  79. package/src/lib/ui/components/AccessibilityMenu.tsx +2 -2
  80. package/src/lib/ui/components/InputModeMenu.tsx +65 -0
  81. package/src/lib/ui/components/MainMenu/DefaultMainMenuContent.tsx +4 -0
  82. package/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx +4 -2
  83. package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +4 -2
  84. package/src/lib/ui/components/StylePanel/StylePanelButtonPicker.tsx +1 -1
  85. package/src/lib/ui/components/StylePanel/StylePanelContext.tsx +5 -3
  86. package/src/lib/ui/components/menu-items.tsx +5 -3
  87. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +12 -5
  88. package/src/lib/ui/context/actions.tsx +5 -27
  89. package/src/lib/ui/context/events.tsx +2 -1
  90. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +8 -2
  91. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +8 -2
  92. package/src/lib/ui/version.ts +3 -3
  93. package/src/lib/ui.css +15 -2
  94. package/src/test/TestEditor.ts +8 -2
  95. package/src/test/commands/setCamera.test.ts +13 -0
  96. package/src/test/frames.test.ts +15 -0
  97. package/tldraw.css +15 -2
@@ -163,7 +163,7 @@ export class Pointing extends StateNode {
163
163
  const endHandle = handles.find((h) => h.id === 'end')!
164
164
  const change = util.onHandleDrag?.(this.editor.getShape(shape)!, {
165
165
  handle: { ...endHandle, x: point.x, y: point.y },
166
- isPrecise: false,
166
+ isPrecise: this.isPrecise,
167
167
  isCreatingShape: true,
168
168
  initial: initial,
169
169
  })
@@ -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,7 @@
1
1
  import {
2
+ ToggleEnhancedA11yModeItem,
2
3
  ToggleKeyboardShortcutsItem,
3
4
  ToggleReduceMotionItem,
4
- ToggleUiLabelsItem,
5
5
  } from './menu-items'
6
6
  import { TldrawUiMenuGroup } from './primitives/menus/TldrawUiMenuGroup'
7
7
  import { TldrawUiMenuSubmenu } from './primitives/menus/TldrawUiMenuSubmenu'
@@ -13,7 +13,7 @@ export function AccessibilityMenu() {
13
13
  <TldrawUiMenuGroup id="accessibility">
14
14
  <ToggleReduceMotionItem />
15
15
  <ToggleKeyboardShortcutsItem />
16
- <ToggleUiLabelsItem />
16
+ <ToggleEnhancedA11yModeItem />
17
17
  </TldrawUiMenuGroup>
18
18
  </TldrawUiMenuSubmenu>
19
19
  )
@@ -0,0 +1,65 @@
1
+ import { useEditor, useValue } from '@tldraw/editor'
2
+ import { useUiEvents } from '../context/events'
3
+ import { TldrawUiMenuCheckboxItem } from './primitives/menus/TldrawUiMenuCheckboxItem'
4
+ import { TldrawUiMenuGroup } from './primitives/menus/TldrawUiMenuGroup'
5
+ import { TldrawUiMenuSubmenu } from './primitives/menus/TldrawUiMenuSubmenu'
6
+
7
+ const MODES = ['auto', 'trackpad', 'mouse'] as const
8
+
9
+ /** @public @react */
10
+ export function InputModeMenu() {
11
+ const editor = useEditor()
12
+ const trackEvent = useUiEvents()
13
+
14
+ const inputMode = useValue('inputMode', () => editor.user.getUserPreferences().inputMode, [
15
+ editor,
16
+ ])
17
+ const wheelBehavior = useValue('wheelBehavior', () => editor.getCameraOptions().wheelBehavior, [
18
+ editor,
19
+ ])
20
+
21
+ const isModeChecked = (mode: string) => {
22
+ if (mode === 'auto') {
23
+ return inputMode === null
24
+ }
25
+ return inputMode === mode
26
+ }
27
+
28
+ const getLabel = (mode: string, wheelBehavior: 'zoom' | 'pan' | 'none') => {
29
+ if (mode === 'auto') {
30
+ return `action.toggle-auto-${wheelBehavior}`
31
+ }
32
+
33
+ return mode === 'trackpad' ? 'action.toggle-trackpad' : 'action.toggle-mouse'
34
+ }
35
+
36
+ return (
37
+ <TldrawUiMenuSubmenu id="help menu input-mode" label="menu.input-mode">
38
+ <TldrawUiMenuGroup id="peripheral-mode">
39
+ {MODES.map((mode) => (
40
+ <TldrawUiMenuCheckboxItem
41
+ id={`peripheral-mode-${mode}`}
42
+ key={mode}
43
+ label={getLabel(mode, wheelBehavior)}
44
+ checked={isModeChecked(mode)}
45
+ readonlyOk
46
+ onSelect={() => {
47
+ trackEvent('input-mode', { source: 'menu', value: mode })
48
+ switch (mode) {
49
+ case 'auto':
50
+ editor.user.updateUserPreferences({ inputMode: null })
51
+ break
52
+ case 'trackpad':
53
+ editor.user.updateUserPreferences({ inputMode: 'trackpad' })
54
+ break
55
+ case 'mouse':
56
+ editor.user.updateUserPreferences({ inputMode: 'mouse' })
57
+ break
58
+ }
59
+ }}
60
+ />
61
+ ))}
62
+ </TldrawUiMenuGroup>
63
+ </TldrawUiMenuSubmenu>
64
+ )
65
+ }
@@ -2,6 +2,7 @@ import { useCanRedo, useCanUndo } from '../../hooks/menu-hooks'
2
2
  import { AccessibilityMenu } from '../AccessibilityMenu'
3
3
  import { ColorSchemeMenu } from '../ColorSchemeMenu'
4
4
  import { KeyboardShortcutsMenuItem } from '../HelpMenu/DefaultHelpMenuContent'
5
+ import { InputModeMenu } from '../InputModeMenu'
5
6
  import { LanguageMenu } from '../LanguageMenu'
6
7
  import {
7
8
  ClipboardMenuGroup,
@@ -164,6 +165,9 @@ export function PreferencesGroup() {
164
165
  <TogglePasteAtCursorItem />
165
166
  <ToggleDebugModeItem />
166
167
  </TldrawUiMenuGroup>
168
+ <TldrawUiMenuGroup id="input-mode">
169
+ <InputModeMenu />
170
+ </TldrawUiMenuGroup>
167
171
  <TldrawUiMenuGroup id="color-scheme">
168
172
  <ColorSchemeMenu />
169
173
  </TldrawUiMenuGroup>
@@ -24,7 +24,9 @@ export const DefaultStylePanel = memo(function DefaultStylePanel({
24
24
  children,
25
25
  }: TLUiStylePanelProps) {
26
26
  const editor = useEditor()
27
- const showUiLabels = useValue('showUiLabels', () => editor.user.getShowUiLabels(), [editor])
27
+ const enhancedA11yMode = useValue('enhancedA11yMode', () => editor.user.getEnhancedA11yMode(), [
28
+ editor,
29
+ ])
28
30
 
29
31
  const ref = useRef<HTMLDivElement>(null)
30
32
  usePassThroughWheelEvents(ref)
@@ -62,7 +64,7 @@ export const DefaultStylePanel = memo(function DefaultStylePanel({
62
64
  data-testid="style.panel"
63
65
  className={classNames('tlui-style-panel', { 'tlui-style-panel__wrapper': !isMobile })}
64
66
  data-ismobile={isMobile}
65
- data-show-ui-labels={showUiLabels}
67
+ data-enhanced-a11y-mode={enhancedA11yMode}
66
68
  onPointerLeave={handlePointerOut}
67
69
  >
68
70
  <StylePanelContextProvider styles={styles}>
@@ -91,7 +91,7 @@ const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1] as const
91
91
  /** @public @react */
92
92
  export function StylePanelOpacityPicker() {
93
93
  const editor = useEditor()
94
- const { onHistoryMark, showUiLabels } = useStylePanelContext()
94
+ const { onHistoryMark, enhancedA11yMode } = useStylePanelContext()
95
95
 
96
96
  const opacity = useValue('opacity', () => editor.getSharedOpacity(), [editor])
97
97
  const trackEvent = useUiEvents()
@@ -126,7 +126,9 @@ export function StylePanelOpacityPicker() {
126
126
 
127
127
  return (
128
128
  <>
129
- {showUiLabels && <StylePanelSubheading>{msg('style-panel.opacity')}</StylePanelSubheading>}
129
+ {enhancedA11yMode && (
130
+ <StylePanelSubheading>{msg('style-panel.opacity')}</StylePanelSubheading>
131
+ )}
130
132
  <TldrawUiSlider
131
133
  data-testid="style.opacity"
132
134
  value={opacityIndex >= 0 ? opacityIndex : tldrawSupportedOpacities.length - 1}
@@ -127,7 +127,7 @@ export const StylePanelButtonPicker = memo(function StylePanelButtonPicker<T ext
127
127
 
128
128
  return (
129
129
  <>
130
- {ctx.showUiLabels && <StylePanelSubheading>{title}</StylePanelSubheading>}
130
+ {ctx.enhancedA11yMode && <StylePanelSubheading>{title}</StylePanelSubheading>}
131
131
  <TldrawUiToolbar label={title}>
132
132
  <TldrawUiToolbarToggleGroup
133
133
  data-testid={`style.${uiType}`}
@@ -5,7 +5,7 @@ import { useUiEvents } from '../../context/events'
5
5
  /** @public */
6
6
  export interface StylePanelContext {
7
7
  styles: ReadonlySharedStyleMap
8
- showUiLabels: boolean
8
+ enhancedA11yMode: boolean
9
9
  onHistoryMark(id: string): void
10
10
  onValueChange<T>(style: StyleProp<T>, value: T): void
11
11
  }
@@ -23,7 +23,9 @@ export function StylePanelContextProvider({ children, styles }: StylePanelContex
23
23
  const trackEvent = useUiEvents()
24
24
 
25
25
  const onHistoryMark = useCallback((id: string) => editor.markHistoryStoppingPoint(id), [editor])
26
- const showUiLabels = useValue('showUiLabels', () => editor.user.getShowUiLabels(), [editor])
26
+ const enhancedA11yMode = useValue('enhancedA11yMode', () => editor.user.getEnhancedA11yMode(), [
27
+ editor,
28
+ ])
27
29
  const onValueChange = useCallback(
28
30
  function <T>(style: StyleProp<T>, value: T) {
29
31
  editor.run(() => {
@@ -43,7 +45,7 @@ export function StylePanelContextProvider({ children, styles }: StylePanelContex
43
45
  <StylePanelContext.Provider
44
46
  value={{
45
47
  styles: styles,
46
- showUiLabels,
48
+ enhancedA11yMode,
47
49
  onHistoryMark,
48
50
  onValueChange,
49
51
  }}
@@ -652,11 +652,13 @@ export function ToggleKeyboardShortcutsItem() {
652
652
  }
653
653
 
654
654
  /** @public @react */
655
- export function ToggleUiLabelsItem() {
655
+ export function ToggleEnhancedA11yModeItem() {
656
656
  const editor = useEditor()
657
- const showUiLabels = useValue('showUiLabels', () => editor.user.getShowUiLabels(), [editor])
657
+ const enhancedA11yMode = useValue('enhancedA11yMode', () => editor.user.getEnhancedA11yMode(), [
658
+ editor,
659
+ ])
658
660
 
659
- return <TldrawUiMenuActionCheckboxItem actionId="toggle-ui-labels" checked={showUiLabels} />
661
+ return <TldrawUiMenuActionCheckboxItem actionId="enhanced-a11y-mode" checked={enhancedA11yMode} />
660
662
  }
661
663
 
662
664
  /** @public @react */
@@ -267,7 +267,11 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
267
267
  const editor = useMaybeEditor()
268
268
  const tooltipId = useRef<string>(uniqueId())
269
269
  const hasProvider = useContext(TooltipSingletonContext)
270
- const showUiLabels = useValue('showUiLabels', () => editor?.user.getShowUiLabels(), [editor])
270
+ const enhancedA11yMode = useValue(
271
+ 'enhancedA11yMode',
272
+ () => editor?.user.getEnhancedA11yMode(),
273
+ [editor]
274
+ )
271
275
 
272
276
  const orientationCtx = useTldrawUiOrientation()
273
277
  const sideToUse = side ?? orientationCtx.tooltipSide
@@ -293,13 +297,13 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
293
297
  }
294
298
  }, [content, sideToUse, sideOffset, showOnMobile, hasProvider])
295
299
 
296
- // Don't show tooltip if disabled, no content, or UI labels are disabled
300
+ // Don't show tooltip if disabled, no content, or enhanced accessibility mode is disabled
297
301
  if (disabled || !content) {
298
302
  return <>{children}</>
299
303
  }
300
304
 
301
305
  let delayDurationToUse
302
- if (showUiLabels) {
306
+ if (enhancedA11yMode) {
303
307
  delayDurationToUse = 0
304
308
  } else {
305
309
  delayDurationToUse =
@@ -307,9 +311,12 @@ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProp
307
311
  }
308
312
 
309
313
  // Fallback to old behavior if no provider
310
- if (!hasProvider || showUiLabels) {
314
+ if (!hasProvider || enhancedA11yMode) {
311
315
  return (
312
- <_Tooltip.Root delayDuration={delayDurationToUse} disableHoverableContent={!showUiLabels}>
316
+ <_Tooltip.Root
317
+ delayDuration={delayDurationToUse}
318
+ disableHoverableContent={!enhancedA11yMode}
319
+ >
313
320
  <_Tooltip.Trigger asChild ref={ref}>
314
321
  {children}
315
322
  </_Tooltip.Trigger>
@@ -1270,16 +1270,16 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1270
1270
  checkbox: true,
1271
1271
  },
1272
1272
  {
1273
- id: 'toggle-ui-labels',
1273
+ id: 'enhanced-a11y-mode',
1274
1274
  label: {
1275
- default: 'action.toggle-ui-labels',
1276
- menu: 'action.toggle-ui-labels.menu',
1275
+ default: 'action.enhanced-a11y-mode',
1276
+ menu: 'action.enhanced-a11y-mode.menu',
1277
1277
  },
1278
1278
  readonlyOk: true,
1279
1279
  onSelect(source) {
1280
- trackEvent('toggle-ui-labels', { source })
1280
+ trackEvent('enhanced-a11y-mode', { source })
1281
1281
  editor.user.updateUserPreferences({
1282
- showUiLabels: !editor.user.getShowUiLabels(),
1282
+ enhancedA11yMode: !editor.user.getEnhancedA11yMode(),
1283
1283
  })
1284
1284
  },
1285
1285
  checkbox: true,
@@ -1344,28 +1344,6 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1344
1344
  }
1345
1345
  },
1346
1346
  },
1347
- {
1348
- id: 'toggle-focus-mode',
1349
- label: {
1350
- default: 'action.toggle-focus-mode',
1351
- menu: 'action.toggle-focus-mode.menu',
1352
- },
1353
- readonlyOk: true,
1354
- kbd: 'cmd+.,ctrl+.',
1355
- checkbox: true,
1356
- onSelect(source) {
1357
- // this needs to be deferred because it causes the menu
1358
- // UI to unmount which puts us in a dodgy state
1359
- editor.timers.requestAnimationFrame(() => {
1360
- editor.run(() => {
1361
- trackEvent('toggle-focus-mode', { source })
1362
- helpers.clearDialogs()
1363
- helpers.clearToasts()
1364
- editor.updateInstanceState({ isFocusMode: !editor.getInstanceState().isFocusMode })
1365
- })
1366
- })
1367
- },
1368
- },
1369
1347
  {
1370
1348
  id: 'toggle-grid',
1371
1349
  label: {
@@ -102,13 +102,14 @@ export interface TLUiEventMap {
102
102
  'toggle-grid-mode': null
103
103
  'toggle-wrap-mode': null
104
104
  'toggle-focus-mode': null
105
+ 'input-mode': { value: string }
105
106
  'toggle-debug-mode': null
106
107
  'toggle-dynamic-size-mode': null
107
108
  'toggle-paste-at-cursor': null
108
109
  'toggle-lock': null
109
110
  'toggle-reduce-motion': null
110
111
  'toggle-keyboard-shortcuts': null
111
- 'toggle-ui-labels': null
112
+ 'enhanced-a11y-mode': null
112
113
  'toggle-edge-scrolling': null
113
114
  'color-scheme': { value: string }
114
115
  'exit-pen-mode': null
@@ -3,6 +3,11 @@
3
3
 
4
4
  /** @public */
5
5
  export type TLUiTranslationKey =
6
+ | 'action.toggle-auto-pan'
7
+ | 'action.toggle-auto-zoom'
8
+ | 'action.toggle-auto-none'
9
+ | 'action.toggle-mouse'
10
+ | 'action.toggle-trackpad'
6
11
  | 'action.convert-to-bookmark'
7
12
  | 'action.convert-to-embed'
8
13
  | 'action.open-embed-link'
@@ -93,8 +98,8 @@ export type TLUiTranslationKey =
93
98
  | 'action.toggle-reduce-motion'
94
99
  | 'action.toggle-keyboard-shortcuts.menu'
95
100
  | 'action.toggle-keyboard-shortcuts'
96
- | 'action.toggle-ui-labels.menu'
97
- | 'action.toggle-ui-labels'
101
+ | 'action.enhanced-a11y-mode.menu'
102
+ | 'action.enhanced-a11y-mode'
98
103
  | 'action.toggle-edge-scrolling.menu'
99
104
  | 'action.toggle-edge-scrolling'
100
105
  | 'action.toggle-debug-mode.menu'
@@ -311,6 +316,7 @@ export type TLUiTranslationKey =
311
316
  | 'menu.language'
312
317
  | 'menu.preferences'
313
318
  | 'menu.view'
319
+ | 'menu.input-mode'
314
320
  | 'context-menu.title'
315
321
  | 'context-menu.edit'
316
322
  | 'context-menu.arrange'
@@ -3,6 +3,11 @@
3
3
 
4
4
  /** @internal */
5
5
  export const DEFAULT_TRANSLATION = {
6
+ 'action.toggle-auto-pan': 'Auto (trackpad)',
7
+ 'action.toggle-auto-zoom': 'Auto (mouse)',
8
+ 'action.toggle-auto-none': 'Auto',
9
+ 'action.toggle-mouse': 'Mouse',
10
+ 'action.toggle-trackpad': 'Trackpad',
6
11
  'action.convert-to-bookmark': 'Convert to Bookmark',
7
12
  'action.convert-to-embed': 'Convert to Embed',
8
13
  'action.open-embed-link': 'Open link',
@@ -94,8 +99,8 @@ export const DEFAULT_TRANSLATION = {
94
99
  'action.toggle-reduce-motion': 'Toggle reduce motion',
95
100
  'action.toggle-keyboard-shortcuts.menu': 'Enable keyboard shortcuts',
96
101
  'action.toggle-keyboard-shortcuts': 'Toggle keyboard shortcuts',
97
- 'action.toggle-ui-labels.menu': 'Enable UI labels',
98
- 'action.toggle-ui-labels': 'Toggle UI labels',
102
+ 'action.enhanced-a11y-mode.menu': 'Enhanced accessibility mode',
103
+ 'action.enhanced-a11y-mode': 'Toggle enhanced accessibility mode',
99
104
  'action.toggle-edge-scrolling.menu': 'Edge scrolling',
100
105
  'action.toggle-edge-scrolling': 'Toggle edge scrolling',
101
106
  'action.toggle-debug-mode.menu': 'Debug mode',
@@ -312,6 +317,7 @@ export const DEFAULT_TRANSLATION = {
312
317
  'menu.language': 'Language',
313
318
  'menu.preferences': 'Preferences',
314
319
  'menu.view': 'View',
320
+ 'menu.input-mode': 'Input mode',
315
321
  'context-menu.title': 'Context menu',
316
322
  'context-menu.edit': 'Edit',
317
323
  'context-menu.arrange': 'Arrange',
@@ -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.e455ab838b8f'
4
+ export const version = '3.16.0-canary.e618c2fbc95d'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-09-17T16:11:14.790Z',
8
- patch: '2025-09-17T16:11:14.790Z',
7
+ minor: '2025-09-18T14:34:09.025Z',
8
+ patch: '2025-09-18T14:34:09.025Z',
9
9
  }
package/src/lib/ui.css CHANGED
@@ -168,7 +168,7 @@
168
168
  min-height: 40px;
169
169
  width: 100%;
170
170
  gap: 8px;
171
- margin: -4px 0px;
171
+ margin-top: -4px;
172
172
  }
173
173
 
174
174
  .tlui-button__menu::after {
@@ -997,7 +997,7 @@
997
997
  max-width: 148px;
998
998
  }
999
999
 
1000
- .tlui-style-panel[data-show-ui-labels='true'] .tlui-button[data-isactive='true'] {
1000
+ .tlui-style-panel[data-enhanced-a11y-mode='true'] .tlui-button[data-isactive='true'] {
1001
1001
  border-radius: 10px;
1002
1002
  outline: 2px solid var(--tl-color-text);
1003
1003
  outline-offset: -5px;
@@ -1039,6 +1039,19 @@ tldraw? probably.
1039
1039
  display: none;
1040
1040
  }
1041
1041
 
1042
+ /*
1043
+ * This is used in a couple places, like Align and Vertical Align.
1044
+ * It's because we have a toolbar with a Toggle Group but then an adjacent button
1045
+ * next to it that opens a popup.
1046
+ */
1047
+ .tlui-style-panel__section .tlui-toolbar:has(.tlui-toolbar) {
1048
+ flex-wrap: wrap;
1049
+ }
1050
+
1051
+ .tlui-style-panel__section .tlui-toolbar:has(.tlui-toolbar) .tlui-style-panel__subheading {
1052
+ margin-left: -2px;
1053
+ }
1054
+
1042
1055
  .tlui-style-panel__section__common:not(:only-child) {
1043
1056
  margin-bottom: 7px;
1044
1057
  border-bottom: 1px solid var(--tl-color-divider);
@@ -86,8 +86,14 @@ export class TestEditor extends Editor {
86
86
  elm.tabIndex = 0
87
87
  elm.getBoundingClientRect = () => bounds as DOMRect
88
88
 
89
- const shapeUtilsWithDefaults = [...defaultShapeUtils, ...(options.shapeUtils ?? [])]
90
- const bindingUtilsWithDefaults = [...defaultBindingUtils, ...(options.bindingUtils ?? [])]
89
+ const shapeUtilsWithDefaults = [
90
+ ...defaultShapeUtils.filter((s) => !options.shapeUtils?.some((su) => su.type === s.type)),
91
+ ...(options.shapeUtils ?? []),
92
+ ]
93
+ const bindingUtilsWithDefaults = [
94
+ ...defaultBindingUtils.filter((b) => !options.bindingUtils?.some((bu) => bu.type === b.type)),
95
+ ...(options.bindingUtils ?? []),
96
+ ]
91
97
 
92
98
  super({
93
99
  ...options,
@@ -12,6 +12,7 @@ beforeEach(() => {
12
12
  },
13
13
  })
14
14
  editor.updateViewportScreenBounds(new Box(0, 0, 1600, 900))
15
+ editor.user.updateUserPreferences({ inputMode: null })
15
16
  })
16
17
 
17
18
  const wheelEvent = {
@@ -204,6 +205,18 @@ describe('CameraOptions.wheelBehavior', () => {
204
205
  .forceTick()
205
206
  expect(editor.getCamera()).toMatchObject({ x: 0, y: 5, z: 1 })
206
207
  })
208
+
209
+ it('When input mode is set, camera wheel behavior is ignored', () => {
210
+ editor.user.updateUserPreferences({ inputMode: 'trackpad' })
211
+ editor
212
+ .setCameraOptions({ ...DEFAULT_CAMERA_OPTIONS, wheelBehavior: 'zoom' })
213
+ .dispatch({
214
+ ...wheelEvent,
215
+ delta: new Vec(0, 5, 0.01),
216
+ })
217
+ .forceTick()
218
+ expect(editor.getCamera()).toMatchObject({ x: 0, y: 5, z: 1 })
219
+ })
207
220
  })
208
221
 
209
222
  describe('CameraOptions.panSpeed', () => {
@@ -1680,3 +1680,18 @@ it('drops into the top-most frame, if there is one', () => {
1680
1680
 
1681
1681
  expect(editor.getShape(rect)?.parentId).toBe(editor.getCurrentPageId())
1682
1682
  })
1683
+
1684
+ it('does not get drop children of nested frame if they are occluded from the outer frame', () => {
1685
+ const frame1Id = dragCreateFrame({ down: [100, 100], move: [300, 300], up: [300, 300] })
1686
+ const frame2Id = dragCreateFrame({ down: [150, 150], move: [290, 290], up: [290, 290] })
1687
+
1688
+ const rect1 = createRect({ pos: [280, 160], size: [10, 30] })
1689
+
1690
+ expect(editor.getShape(rect1)?.parentId).toBe(frame2Id)
1691
+ expect(editor.getShape(frame2Id)?.parentId).toBe(frame1Id)
1692
+
1693
+ editor.select(frame2Id)
1694
+ editor.translateSelection(30, 0)
1695
+
1696
+ expect(editor.getShape(rect1)?.parentId).toBe(frame2Id)
1697
+ })
package/tldraw.css CHANGED
@@ -1964,7 +1964,7 @@ it from receiving any pointer events or affecting the cursor. */
1964
1964
  min-height: 40px;
1965
1965
  width: 100%;
1966
1966
  gap: 8px;
1967
- margin: -4px 0px;
1967
+ margin-top: -4px;
1968
1968
  }
1969
1969
 
1970
1970
  .tlui-button__menu::after {
@@ -2793,7 +2793,7 @@ it from receiving any pointer events or affecting the cursor. */
2793
2793
  max-width: 148px;
2794
2794
  }
2795
2795
 
2796
- .tlui-style-panel[data-show-ui-labels='true'] .tlui-button[data-isactive='true'] {
2796
+ .tlui-style-panel[data-enhanced-a11y-mode='true'] .tlui-button[data-isactive='true'] {
2797
2797
  border-radius: 10px;
2798
2798
  outline: 2px solid var(--tl-color-text);
2799
2799
  outline-offset: -5px;
@@ -2835,6 +2835,19 @@ tldraw? probably.
2835
2835
  display: none;
2836
2836
  }
2837
2837
 
2838
+ /*
2839
+ * This is used in a couple places, like Align and Vertical Align.
2840
+ * It's because we have a toolbar with a Toggle Group but then an adjacent button
2841
+ * next to it that opens a popup.
2842
+ */
2843
+ .tlui-style-panel__section .tlui-toolbar:has(.tlui-toolbar) {
2844
+ flex-wrap: wrap;
2845
+ }
2846
+
2847
+ .tlui-style-panel__section .tlui-toolbar:has(.tlui-toolbar) .tlui-style-panel__subheading {
2848
+ margin-left: -2px;
2849
+ }
2850
+
2838
2851
  .tlui-style-panel__section__common:not(:only-child) {
2839
2852
  margin-bottom: 7px;
2840
2853
  border-bottom: 1px solid var(--tl-color-divider);