tldraw 3.15.0-next.d30ed5ad740e → 3.15.0-next.f1dfcef63951

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 (71) hide show
  1. package/dist-cjs/index.d.ts +84 -77
  2. package/dist-cjs/index.js +30 -31
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +5 -5
  5. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  6. package/dist-cjs/lib/shapes/text/TextShapeUtil.js +11 -5
  7. package/dist-cjs/lib/shapes/text/TextShapeUtil.js.map +2 -2
  8. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +1 -11
  9. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +2 -2
  10. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js +0 -1
  11. package/dist-cjs/lib/ui/components/MainMenu/DefaultMainMenuContent.js.map +2 -2
  12. package/dist-cjs/lib/ui/components/menu-items.js +0 -16
  13. package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
  14. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +2 -0
  15. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  16. package/dist-cjs/lib/ui/context/actions.js +1 -28
  17. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  18. package/dist-cjs/lib/ui/context/events.js.map +2 -2
  19. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js +7 -24
  20. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js.map +2 -2
  21. package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js +2 -2
  22. package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js.map +2 -2
  23. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  24. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +0 -4
  25. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  26. package/dist-cjs/lib/ui/version.js +3 -3
  27. package/dist-cjs/lib/ui/version.js.map +1 -1
  28. package/dist-esm/index.d.mts +84 -77
  29. package/dist-esm/index.mjs +132 -134
  30. package/dist-esm/index.mjs.map +2 -2
  31. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +5 -5
  32. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  33. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs +11 -5
  34. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs.map +2 -2
  35. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +1 -11
  36. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +2 -2
  37. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs +0 -2
  38. package/dist-esm/lib/ui/components/MainMenu/DefaultMainMenuContent.mjs.map +2 -2
  39. package/dist-esm/lib/ui/components/menu-items.mjs +0 -16
  40. package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
  41. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +2 -0
  42. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  43. package/dist-esm/lib/ui/context/actions.mjs +1 -28
  44. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  45. package/dist-esm/lib/ui/context/events.mjs.map +2 -2
  46. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs +7 -24
  47. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs.map +2 -2
  48. package/dist-esm/lib/ui/hooks/useKeyboardShortcuts.mjs +2 -2
  49. package/dist-esm/lib/ui/hooks/useKeyboardShortcuts.mjs.map +2 -2
  50. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +0 -4
  51. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  52. package/dist-esm/lib/ui/version.mjs +3 -3
  53. package/dist-esm/lib/ui/version.mjs.map +1 -1
  54. package/package.json +3 -3
  55. package/src/index.ts +158 -159
  56. package/src/lib/shapes/frame/FrameShapeUtil.tsx +7 -5
  57. package/src/lib/shapes/text/TextShapeUtil.tsx +12 -5
  58. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +0 -8
  59. package/src/lib/ui/components/MainMenu/DefaultMainMenuContent.tsx +0 -2
  60. package/src/lib/ui/components/menu-items.tsx +0 -17
  61. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +2 -0
  62. package/src/lib/ui/context/actions.tsx +1 -29
  63. package/src/lib/ui/context/events.tsx +0 -2
  64. package/src/lib/ui/hooks/useClipboardEvents.ts +10 -31
  65. package/src/lib/ui/hooks/useKeyboardShortcuts.ts +2 -3
  66. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +0 -4
  67. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +0 -4
  68. package/src/lib/ui/version.ts +3 -3
  69. package/src/test/Editor.test.tsx +1 -68
  70. package/src/test/commands/clipboard.test.ts +1 -1
  71. package/src/test/editor.test.ts +77 -0
@@ -28,7 +28,6 @@ import { fitFrameToContent, removeFrame } from '../../utils/frames/frames'
28
28
  import { generateShapeAnnouncementMessage } from '../components/A11y'
29
29
  import { EditLinkDialog } from '../components/EditLinkDialog'
30
30
  import { EmbedDialog } from '../components/EmbedDialog'
31
- import { DefaultKeyboardShortcutsDialog } from '../components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialog'
32
31
  import { useShowCollaborationUi } from '../hooks/useCollaborationStatus'
33
32
  import { flattenShapesToImages } from '../hooks/useFlatten'
34
33
  import { TLUiTranslationKey } from '../hooks/useTranslation/TLUiTranslationKey'
@@ -49,7 +48,6 @@ export interface TLUiActionItem<
49
48
  label?: TransationKey | { [key: string]: TransationKey }
50
49
  readonlyOk?: boolean
51
50
  checkbox?: boolean
52
- isRequiredA11yAction?: boolean
53
51
  onSelect(source: TLUiEventSource): Promise<void> | void
54
52
  }
55
53
 
@@ -170,15 +168,6 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
170
168
  helpers.addDialog({ component: EmbedDialog })
171
169
  },
172
170
  },
173
- {
174
- id: 'open-kbd-shortcuts',
175
- label: 'action.open-kbd-shortcuts',
176
- kbd: 'cmd+alt+/,ctrl+alt+/',
177
- onSelect(source) {
178
- trackEvent('open-kbd-shortcuts', { source })
179
- helpers.addDialog({ component: DefaultKeyboardShortcutsDialog })
180
- },
181
- },
182
171
  {
183
172
  id: 'insert-media',
184
173
  label: 'action.insert-media',
@@ -513,6 +502,7 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
513
502
  }
514
503
  }
515
504
 
505
+ if (!editor.canCreateShapes(ids)) return
516
506
  editor.markHistoryStoppingPoint('duplicate shapes')
517
507
  editor.duplicateShapes(ids, offset)
518
508
 
@@ -1244,21 +1234,6 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1244
1234
  },
1245
1235
  checkbox: true,
1246
1236
  },
1247
- {
1248
- id: 'toggle-keyboard-shortcuts',
1249
- label: {
1250
- default: 'action.toggle-keyboard-shortcuts',
1251
- menu: 'action.toggle-keyboard-shortcuts.menu',
1252
- },
1253
- readonlyOk: true,
1254
- onSelect(source) {
1255
- trackEvent('toggle-keyboard-shortcuts', { source })
1256
- editor.user.updateUserPreferences({
1257
- areKeyboardShortcutsEnabled: !editor.user.getAreKeyboardShortcutsEnabled(),
1258
- })
1259
- },
1260
- checkbox: true,
1261
- },
1262
1237
  {
1263
1238
  id: 'toggle-edge-scrolling',
1264
1239
  label: {
@@ -1555,7 +1530,6 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1555
1530
  id: 'adjust-shape-styles',
1556
1531
  label: 'a11y.adjust-shape-styles',
1557
1532
  kbd: 'cmd+Enter,ctrl+Enter',
1558
- isRequiredA11yAction: true,
1559
1533
  onSelect: async (source) => {
1560
1534
  if (!canApplySelectionAction()) return
1561
1535
 
@@ -1569,7 +1543,6 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1569
1543
  {
1570
1544
  id: 'a11y-open-context-menu',
1571
1545
  kbd: 'cmd+shift+Enter,ctrl+shift+Enter',
1572
- isRequiredA11yAction: true,
1573
1546
  readonlyOk: true,
1574
1547
  onSelect: async (source) => {
1575
1548
  if (!canApplySelectionAction()) return
@@ -1622,7 +1595,6 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1622
1595
  id: 'a11y-repeat-shape-announce',
1623
1596
  kbd: 'alt+r',
1624
1597
  label: 'a11y.repeat-shape',
1625
- isRequiredA11yAction: true,
1626
1598
  readonlyOk: true,
1627
1599
  onSelect: async (source) => {
1628
1600
  const selectedShapeIds = editor.getSelectedShapeIds()
@@ -107,7 +107,6 @@ export interface TLUiEventMap {
107
107
  'toggle-paste-at-cursor': null
108
108
  'toggle-lock': null
109
109
  'toggle-reduce-motion': null
110
- 'toggle-keyboard-shortcuts': null
111
110
  'toggle-edge-scrolling': null
112
111
  'color-scheme': { value: string }
113
112
  'exit-pen-mode': null
@@ -128,7 +127,6 @@ export interface TLUiEventMap {
128
127
  'copy-link': null
129
128
  'image-replace': null
130
129
  'video-replace': null
131
- 'open-kbd-shortcuts': null
132
130
  'rich-text': {
133
131
  operation:
134
132
  | 'bold'
@@ -352,7 +352,7 @@ async function handleClipboardThings(editor: Editor, things: ClipboardThing[], p
352
352
 
353
353
  if (tldrawHtmlComment) {
354
354
  try {
355
- // First try parsing as plain JSON (version 2/3 formats)
355
+ // First try parsing as plain JSON (version 2 format)
356
356
  let json
357
357
  try {
358
358
  json = JSON.parse(tldrawHtmlComment)
@@ -380,32 +380,19 @@ async function handleClipboardThings(editor: Editor, things: ClipboardThing[], p
380
380
  }
381
381
 
382
382
  // Handle versioned clipboard format
383
- if (json.version === 3) {
384
- // Version 3: Assets are plain, decompress only other data
383
+ if (json.version === 2) {
384
+ // Version 2: Assets are plain, decompress only other data
385
385
  try {
386
- const otherData = JSON.parse(
387
- lz.decompressFromBase64(json.data.otherCompressed) || '{}'
388
- )
389
- const reconstructedData = {
390
- assets: json.data.assets || [],
391
- ...otherData,
392
- }
393
-
394
- r({ type: 'tldraw', data: reconstructedData })
386
+ r({ type: 'tldraw', data: json.data })
395
387
  return
396
388
  } catch (error) {
397
389
  r({
398
390
  type: 'error',
399
391
  data: json,
400
- reason: `failed to decompress version 2 clipboard data: ${error}`,
392
+ reason: `failed to parse version 2 clipboard data: ${error}`,
401
393
  })
402
394
  return
403
395
  }
404
- }
405
- if (json.version === 2) {
406
- // Version 2: Everything is plain, this had issues with encoding... :-/
407
- // TODO: nix this support after some time.
408
- r({ type: 'tldraw', data: json.data })
409
396
  } else {
410
397
  // Version 1 or no version: Legacy format
411
398
  if (typeof json.data === 'string') {
@@ -597,21 +584,13 @@ const handleNativeOrMenuCopy = async (editor: Editor) => {
597
584
  return
598
585
  }
599
586
 
600
- // Use versioned clipboard format for better compression
601
- // Version 3: Don't compress assets, only compress other data
602
- const { assets, ...otherData } = content
603
- const clipboardData = {
587
+ // Version 2: Don't compress anything.
588
+ const stringifiedClipboard = JSON.stringify({
604
589
  type: 'application/tldraw',
605
590
  kind: 'content',
606
- version: 3,
607
- data: {
608
- assets: assets || [], // Plain JSON, no compression
609
- otherCompressed: lz.compressToBase64(JSON.stringify(otherData)), // Only compress non-asset data
610
- },
611
- }
612
-
613
- // Don't compress the final structure - just use plain JSON
614
- const stringifiedClipboard = JSON.stringify(clipboardData)
591
+ version: 2,
592
+ data: content,
593
+ })
615
594
 
616
595
  if (typeof navigator === 'undefined') {
617
596
  return
@@ -61,7 +61,7 @@ export function useKeyboardShortcuts() {
61
61
  if (SKIP_KBDS.includes(action.id)) continue
62
62
 
63
63
  hot(getHotkeysStringFromKbd(action.kbd), (event) => {
64
- if (areShortcutsDisabled(editor) && !action.isRequiredA11yAction) return
64
+ if (areShortcutsDisabled(editor)) return
65
65
  preventDefault(event)
66
66
  action.onSelect('kbd')
67
67
  })
@@ -149,8 +149,7 @@ export function areShortcutsDisabled(editor: Editor) {
149
149
  return (
150
150
  editor.menus.hasAnyOpenMenus() ||
151
151
  editor.getEditingShapeId() !== null ||
152
- editor.getCrashingError() ||
153
- !editor.user.getAreKeyboardShortcutsEnabled()
152
+ editor.getCrashingError()
154
153
  )
155
154
  }
156
155
 
@@ -55,7 +55,6 @@ export type TLUiTranslationKey =
55
55
  | 'action.new-project'
56
56
  | 'action.new-shared-project'
57
57
  | 'action.open-cursor-chat'
58
- | 'action.open-kbd-shortcuts'
59
58
  | 'action.open-file'
60
59
  | 'action.pack'
61
60
  | 'action.paste'
@@ -91,8 +90,6 @@ export type TLUiTranslationKey =
91
90
  | 'action.toggle-wrap-mode'
92
91
  | 'action.toggle-reduce-motion.menu'
93
92
  | 'action.toggle-reduce-motion'
94
- | 'action.toggle-keyboard-shortcuts.menu'
95
- | 'action.toggle-keyboard-shortcuts'
96
93
  | 'action.toggle-edge-scrolling.menu'
97
94
  | 'action.toggle-edge-scrolling'
98
95
  | 'action.toggle-debug-mode.menu'
@@ -291,7 +288,6 @@ export type TLUiTranslationKey =
291
288
  | 'a11y.pan-camera'
292
289
  | 'a11y.adjust-shape-styles'
293
290
  | 'a11y.open-context-menu'
294
- | 'a11y.open-keyboard-shortcuts'
295
291
  | 'menu.title'
296
292
  | 'menu.theme'
297
293
  | 'menu.copy-as'
@@ -55,7 +55,6 @@ export const DEFAULT_TRANSLATION = {
55
55
  'action.new-project': 'New project',
56
56
  'action.new-shared-project': 'New shared project',
57
57
  'action.open-cursor-chat': 'Cursor chat',
58
- 'action.open-kbd-shortcuts': 'Open keyboard shortcuts',
59
58
  'action.open-file': 'Open file',
60
59
  'action.pack': 'Pack',
61
60
  'action.paste': 'Paste',
@@ -92,8 +91,6 @@ export const DEFAULT_TRANSLATION = {
92
91
  'action.toggle-wrap-mode': 'Toggle Select on wrap',
93
92
  'action.toggle-reduce-motion.menu': 'Reduce motion',
94
93
  'action.toggle-reduce-motion': 'Toggle reduce motion',
95
- 'action.toggle-keyboard-shortcuts.menu': 'Keyboard shortcuts',
96
- 'action.toggle-keyboard-shortcuts': 'Toggle keyboard shortcuts',
97
94
  'action.toggle-edge-scrolling.menu': 'Edge scrolling',
98
95
  'action.toggle-edge-scrolling': 'Toggle edge scrolling',
99
96
  'action.toggle-debug-mode.menu': 'Debug mode',
@@ -292,7 +289,6 @@ export const DEFAULT_TRANSLATION = {
292
289
  'a11y.pan-camera': 'Pan camera',
293
290
  'a11y.adjust-shape-styles': 'Adjust shape styles',
294
291
  'a11y.open-context-menu': 'Open context menu',
295
- 'a11y.open-keyboard-shortcuts': 'Open keyboard shortcuts',
296
292
  'menu.title': 'Menu',
297
293
  'menu.theme': 'Theme',
298
294
  'menu.copy-as': 'Copy as',
@@ -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.15.0-next.d30ed5ad740e'
4
+ export const version = '3.15.0-next.f1dfcef63951'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-07-10T10:00:49.217Z',
8
- patch: '2025-07-10T10:00:49.217Z',
7
+ minor: '2025-07-03T10:22:18.515Z',
8
+ patch: '2025-07-03T10:22:18.515Z',
9
9
  }
@@ -49,7 +49,7 @@ beforeEach(() => {
49
49
  })
50
50
 
51
51
  const moveShapesToPage2 = () => {
52
- // directly manipulate parentId like would happen in multiplayer situations
52
+ // directly maniuplate parentId like would happen in multiplayer situations
53
53
 
54
54
  editor.updateShapes([
55
55
  { id: ids.box1, type: 'geo', parentId: ids.page2 },
@@ -899,70 +899,3 @@ describe('the geometry cache', () => {
899
899
  expect(editor.getShapePageBounds(A)!.width).toBe(200)
900
900
  })
901
901
  })
902
- describe('editor.getShapePageBounds', () => {
903
- it('calculates axis aligned bounds correctly', () => {
904
- editor.createShape({
905
- type: 'geo',
906
- x: 99,
907
- y: 88,
908
- props: {
909
- w: 199,
910
- h: 188,
911
- },
912
- })
913
- const shape = editor.getLastCreatedShape()
914
- expect(editor.getShapePageBounds(shape)!).toMatchInlineSnapshot(`
915
- Box {
916
- "h": 188,
917
- "w": 199,
918
- "x": 99,
919
- "y": 88,
920
- }
921
- `)
922
- })
923
-
924
- it('calculates rotated bounds correctly', () => {
925
- editor.createShape({
926
- type: 'geo',
927
- x: 99,
928
- y: 88,
929
- rotation: Math.PI / 4,
930
- props: {
931
- w: 199,
932
- h: 188,
933
- },
934
- })
935
- const shape = editor.getLastCreatedShape()
936
- expect(editor.getShapePageBounds(shape)!).toMatchInlineSnapshot(`
937
- Box {
938
- "h": 273.65032431919394,
939
- "w": 273.6503243191939,
940
- "x": -33.93607486307093,
941
- "y": 88,
942
- }
943
- `)
944
- })
945
-
946
- it('calculates bounds based on vertices, not corners', () => {
947
- editor.createShape({
948
- type: 'geo',
949
- x: 99,
950
- y: 88,
951
- rotation: Math.PI / 4,
952
- props: {
953
- geo: 'ellipse',
954
- w: 199,
955
- h: 188,
956
- },
957
- })
958
- const shape = editor.getLastCreatedShape()
959
- expect(editor.getShapePageBounds(shape)!).toMatchInlineSnapshot(`
960
- Box {
961
- "h": 193.49999999999997,
962
- "w": 193.50000000000003,
963
- "x": 6.139087296526014,
964
- "y": 128.07516215959694,
965
- }
966
- `)
967
- })
968
- })
@@ -33,7 +33,7 @@ const doMockClipboard = () => {
33
33
  },
34
34
  })
35
35
 
36
- globalThis.ClipboardItem = jest.fn((payload: any) => payload) as any
36
+ globalThis.ClipboardItem = jest.fn((payload: any) => payload)
37
37
 
38
38
  return context
39
39
  }
@@ -0,0 +1,77 @@
1
+ import { TestEditor } from './TestEditor'
2
+
3
+ describe('editor', () => {
4
+ let editor: TestEditor
5
+
6
+ beforeEach(() => {
7
+ editor = new TestEditor()
8
+ })
9
+
10
+ describe('editor.getShapePageBounds', () => {
11
+ it('calculates axis aligned bounds correctly', () => {
12
+ editor.createShape({
13
+ type: 'geo',
14
+ x: 99,
15
+ y: 88,
16
+ props: {
17
+ w: 199,
18
+ h: 188,
19
+ },
20
+ })
21
+ const shape = editor.getLastCreatedShape()
22
+ expect(editor.getShapePageBounds(shape)!).toMatchInlineSnapshot(`
23
+ Box {
24
+ "h": 188,
25
+ "w": 199,
26
+ "x": 99,
27
+ "y": 88,
28
+ }
29
+ `)
30
+ })
31
+
32
+ it('calculates rotated bounds correctly', () => {
33
+ editor.createShape({
34
+ type: 'geo',
35
+ x: 99,
36
+ y: 88,
37
+ rotation: Math.PI / 4,
38
+ props: {
39
+ w: 199,
40
+ h: 188,
41
+ },
42
+ })
43
+ const shape = editor.getLastCreatedShape()
44
+ expect(editor.getShapePageBounds(shape)!).toMatchInlineSnapshot(`
45
+ Box {
46
+ "h": 273.65032431919394,
47
+ "w": 273.6503243191939,
48
+ "x": -33.93607486307093,
49
+ "y": 88,
50
+ }
51
+ `)
52
+ })
53
+
54
+ it('calculates bounds based on vertices, not corners', () => {
55
+ editor.createShape({
56
+ type: 'geo',
57
+ x: 99,
58
+ y: 88,
59
+ rotation: Math.PI / 4,
60
+ props: {
61
+ geo: 'ellipse',
62
+ w: 199,
63
+ h: 188,
64
+ },
65
+ })
66
+ const shape = editor.getLastCreatedShape()
67
+ expect(editor.getShapePageBounds(shape)!).toMatchInlineSnapshot(`
68
+ Box {
69
+ "h": 193.49999999999997,
70
+ "w": 193.50000000000003,
71
+ "x": 6.139087296526014,
72
+ "y": 128.07516215959694,
73
+ }
74
+ `)
75
+ })
76
+ })
77
+ })