tldraw 4.1.0-canary.bdf9b3703a3d → 4.1.0-canary.e2133d922c9e

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 (39) hide show
  1. package/dist-cjs/index.js +1 -1
  2. package/dist-cjs/lib/defaultExternalContentHandlers.js +1 -1
  3. package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
  4. package/dist-cjs/lib/shapes/arrow/curved-arrow.js +8 -2
  5. package/dist-cjs/lib/shapes/arrow/curved-arrow.js.map +2 -2
  6. package/dist-cjs/lib/shapes/arrow/straight-arrow.js +4 -1
  7. package/dist-cjs/lib/shapes/arrow/straight-arrow.js.map +2 -2
  8. package/dist-cjs/lib/shapes/shared/RichTextLabel.js +1 -1
  9. package/dist-cjs/lib/shapes/shared/RichTextLabel.js.map +2 -2
  10. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +1 -1
  11. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +1 -1
  12. package/dist-cjs/lib/ui/context/actions.js +20 -0
  13. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  14. package/dist-cjs/lib/ui/version.js +3 -3
  15. package/dist-cjs/lib/ui/version.js.map +1 -1
  16. package/dist-esm/index.mjs +1 -1
  17. package/dist-esm/lib/defaultExternalContentHandlers.mjs +1 -1
  18. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  19. package/dist-esm/lib/shapes/arrow/curved-arrow.mjs +8 -2
  20. package/dist-esm/lib/shapes/arrow/curved-arrow.mjs.map +2 -2
  21. package/dist-esm/lib/shapes/arrow/straight-arrow.mjs +4 -1
  22. package/dist-esm/lib/shapes/arrow/straight-arrow.mjs.map +2 -2
  23. package/dist-esm/lib/shapes/shared/RichTextLabel.mjs +1 -1
  24. package/dist-esm/lib/shapes/shared/RichTextLabel.mjs.map +2 -2
  25. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +1 -1
  26. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +1 -1
  27. package/dist-esm/lib/ui/context/actions.mjs +20 -0
  28. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  29. package/dist-esm/lib/ui/version.mjs +3 -3
  30. package/dist-esm/lib/ui/version.mjs.map +1 -1
  31. package/package.json +3 -3
  32. package/src/lib/defaultExternalContentHandlers.ts +1 -1
  33. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +211 -1
  34. package/src/lib/shapes/arrow/curved-arrow.ts +8 -2
  35. package/src/lib/shapes/arrow/straight-arrow.ts +5 -1
  36. package/src/lib/shapes/shared/RichTextLabel.tsx +1 -1
  37. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +1 -1
  38. package/src/lib/ui/context/actions.tsx +22 -0
  39. package/src/lib/ui/version.ts +3 -3
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tldraw",
3
3
  "description": "A tiny little drawing editor.",
4
- "version": "4.1.0-canary.bdf9b3703a3d",
4
+ "version": "4.1.0-canary.e2133d922c9e",
5
5
  "author": {
6
6
  "name": "tldraw Inc.",
7
7
  "email": "hello@tldraw.com"
@@ -55,8 +55,8 @@
55
55
  "@tiptap/pm": "^2.9.1",
56
56
  "@tiptap/react": "^2.9.1",
57
57
  "@tiptap/starter-kit": "^2.9.1",
58
- "@tldraw/editor": "4.1.0-canary.bdf9b3703a3d",
59
- "@tldraw/store": "4.1.0-canary.bdf9b3703a3d",
58
+ "@tldraw/editor": "4.1.0-canary.e2133d922c9e",
59
+ "@tldraw/store": "4.1.0-canary.e2133d922c9e",
60
60
  "classnames": "^2.5.1",
61
61
  "hotkeys-js": "^3.13.9",
62
62
  "idb": "^7.1.1",
@@ -382,7 +382,7 @@ export async function defaultHandleExternalFileContent(
382
382
  ) {
383
383
  const { acceptedImageMimeTypes = DEFAULT_SUPPORTED_IMAGE_TYPES, toasts, msg } = options
384
384
  if (files.length > editor.options.maxFilesAtOnce) {
385
- toasts.addToast({ title: msg('assets.files.amount-too-big'), severity: 'error' })
385
+ toasts.addToast({ title: msg('assets.files.amount-too-many'), severity: 'error' })
386
386
  return
387
387
  }
388
388
 
@@ -1,7 +1,7 @@
1
1
  import { HALF_PI, TLArrowShape, TLShapeId, createShapeId, toRichText } from '@tldraw/editor'
2
2
  import { vi } from 'vitest'
3
3
  import { TestEditor } from '../../../test/TestEditor'
4
- import { createOrUpdateArrowBinding, getArrowBindings } from './shared'
4
+ import { createOrUpdateArrowBinding, getArrowBindings, getArrowInfo } from './shared'
5
5
 
6
6
  let editor: TestEditor
7
7
 
@@ -620,3 +620,213 @@ describe('Arrow export bounds', () => {
620
620
  expect(arrow.props.richText).not.toBeNull()
621
621
  })
622
622
  })
623
+
624
+ describe('Arrow terminal positioning bug fix', () => {
625
+ const data = {
626
+ document: {
627
+ store: {
628
+ 'shape:1Hm61DGAsY0uqEO-kt75l': {
629
+ x: 637.2890625,
630
+ y: 383.9296875,
631
+ rotation: 0,
632
+ isLocked: false,
633
+ opacity: 1,
634
+ meta: {},
635
+ id: 'shape:1Hm61DGAsY0uqEO-kt75l',
636
+ type: 'geo',
637
+ props: {
638
+ w: 230.66796875,
639
+ h: 114.796875,
640
+ geo: 'rectangle',
641
+ dash: 'draw',
642
+ growY: 0,
643
+ url: '',
644
+ scale: 1,
645
+ color: 'black',
646
+ labelColor: 'black',
647
+ fill: 'none',
648
+ size: 'm',
649
+ font: 'draw',
650
+ align: 'middle',
651
+ verticalAlign: 'middle',
652
+ richText: {
653
+ type: 'doc',
654
+ content: [
655
+ {
656
+ type: 'paragraph',
657
+ },
658
+ ],
659
+ },
660
+ },
661
+ parentId: 'page:page',
662
+ index: 'a1',
663
+ typeName: 'shape',
664
+ },
665
+ 'binding:nekxhMCGaoEJO98DEqWgo': {
666
+ meta: {},
667
+ id: 'binding:nekxhMCGaoEJO98DEqWgo',
668
+ type: 'arrow',
669
+ fromId: 'shape:j0HKQihjBXqMqgVhfRhDS',
670
+ toId: 'shape:1Hm61DGAsY0uqEO-kt75l',
671
+ props: {
672
+ isPrecise: true,
673
+ isExact: false,
674
+ normalizedAnchor: {
675
+ x: 0.13182672605036325,
676
+ y: 0.8036953858717844,
677
+ },
678
+ snap: 'none',
679
+ terminal: 'start',
680
+ },
681
+ typeName: 'binding',
682
+ },
683
+ 'binding:kWamalL_QSq_kFPZDgp7Z': {
684
+ meta: {},
685
+ id: 'binding:kWamalL_QSq_kFPZDgp7Z',
686
+ type: 'arrow',
687
+ fromId: 'shape:j0HKQihjBXqMqgVhfRhDS',
688
+ toId: 'shape:1Hm61DGAsY0uqEO-kt75l',
689
+ props: {
690
+ isPrecise: true,
691
+ isExact: false,
692
+ normalizedAnchor: {
693
+ x: 0.7138744475114731,
694
+ y: 0.45797604464407243,
695
+ },
696
+ snap: 'none',
697
+ terminal: 'end',
698
+ },
699
+ typeName: 'binding',
700
+ },
701
+ 'shape:j0HKQihjBXqMqgVhfRhDS': {
702
+ x: 665.296875,
703
+ y: 477.59765625,
704
+ rotation: 0,
705
+ isLocked: false,
706
+ opacity: 1,
707
+ meta: {},
708
+ id: 'shape:j0HKQihjBXqMqgVhfRhDS',
709
+ type: 'arrow',
710
+ props: {
711
+ kind: 'arc',
712
+ elbowMidPoint: 0.5,
713
+ dash: 'draw',
714
+ size: 'm',
715
+ fill: 'none',
716
+ color: 'black',
717
+ labelColor: 'black',
718
+ bend: 0,
719
+ start: {
720
+ x: 0,
721
+ y: 0,
722
+ },
723
+ end: {
724
+ x: 2,
725
+ y: 0,
726
+ },
727
+ arrowheadStart: 'none',
728
+ arrowheadEnd: 'arrow',
729
+ richText: {
730
+ type: 'doc',
731
+ content: [
732
+ {
733
+ type: 'paragraph',
734
+ },
735
+ ],
736
+ },
737
+ labelPosition: 0.5,
738
+ font: 'draw',
739
+ scale: 1,
740
+ },
741
+ parentId: 'page:page',
742
+ index: 'a2lbpzZG',
743
+ typeName: 'shape',
744
+ },
745
+ 'page:page': {
746
+ meta: {},
747
+ id: 'page:page',
748
+ name: 'Page 1',
749
+ index: 'a1',
750
+ typeName: 'page',
751
+ },
752
+ 'document:document': {
753
+ gridSize: 10,
754
+ name: '',
755
+ meta: {},
756
+ id: 'document:document',
757
+ typeName: 'document',
758
+ },
759
+ },
760
+ schema: {
761
+ schemaVersion: 2,
762
+ sequences: {
763
+ 'com.tldraw.store': 5,
764
+ 'com.tldraw.asset': 1,
765
+ 'com.tldraw.camera': 1,
766
+ 'com.tldraw.document': 2,
767
+ 'com.tldraw.instance': 25,
768
+ 'com.tldraw.instance_page_state': 5,
769
+ 'com.tldraw.page': 1,
770
+ 'com.tldraw.instance_presence': 6,
771
+ 'com.tldraw.pointer': 1,
772
+ 'com.tldraw.shape': 4,
773
+ 'com.tldraw.asset.bookmark': 2,
774
+ 'com.tldraw.asset.image': 5,
775
+ 'com.tldraw.asset.video': 5,
776
+ 'com.tldraw.shape.group': 0,
777
+ 'com.tldraw.shape.text': 3,
778
+ 'com.tldraw.shape.bookmark': 2,
779
+ 'com.tldraw.shape.draw': 2,
780
+ 'com.tldraw.shape.geo': 10,
781
+ 'com.tldraw.shape.note': 9,
782
+ 'com.tldraw.shape.line': 5,
783
+ 'com.tldraw.shape.frame': 1,
784
+ 'com.tldraw.shape.arrow': 7,
785
+ 'com.tldraw.shape.highlight': 1,
786
+ 'com.tldraw.shape.embed': 4,
787
+ 'com.tldraw.shape.image': 5,
788
+ 'com.tldraw.shape.video': 4,
789
+ 'com.tldraw.binding.arrow': 1,
790
+ },
791
+ },
792
+ },
793
+ session: {
794
+ version: 0,
795
+ currentPageId: 'page:page',
796
+ exportBackground: true,
797
+ isFocusMode: false,
798
+ isDebugMode: false,
799
+ isToolLocked: false,
800
+ isGridMode: false,
801
+ pageStates: [
802
+ {
803
+ pageId: 'page:page',
804
+ camera: {
805
+ x: 0,
806
+ y: 0,
807
+ z: 1,
808
+ },
809
+ selectedShapeIds: ['shape:j0HKQihjBXqMqgVhfRhDS'],
810
+ focusedGroupId: null,
811
+ },
812
+ ],
813
+ },
814
+ }
815
+
816
+ it('should position straight arrow terminals on shape boundary, not text label boundary', () => {
817
+ // Create a geo shape with text label
818
+ editor.loadSnapshot(data as any)
819
+
820
+ const arrow = editor.getShape('shape:j0HKQihjBXqMqgVhfRhDS' as TLShapeId) as TLArrowShape
821
+ expect(arrow).toBeDefined()
822
+
823
+ expect(getArrowBindings(editor, arrow)).toMatchObject({
824
+ end: { props: { isPrecise: true } },
825
+ start: { props: { isPrecise: true } },
826
+ })
827
+
828
+ const info = getArrowInfo(editor, arrow)
829
+ expect(info?.start.handle).toEqual(info?.start.point)
830
+ expect(info?.end.handle).toEqual(info?.end.point)
831
+ })
832
+ })
@@ -154,7 +154,10 @@ export function getCurvedArrowInfo(
154
154
  }
155
155
  if (!point) {
156
156
  if (isClosed) {
157
- const nearestPoint = startShapeInfo.geometry.nearestPoint(startInStartShapeLocalSpace)
157
+ const nearestPoint = startShapeInfo.geometry.nearestPoint(startInStartShapeLocalSpace, {
158
+ includeInternal: false,
159
+ includeLabels: false,
160
+ })
158
161
  if (Vec.DistMin(nearestPoint, startInStartShapeLocalSpace, 1)) {
159
162
  point = nearestPoint
160
163
  }
@@ -233,7 +236,10 @@ export function getCurvedArrowInfo(
233
236
  }
234
237
  if (!point) {
235
238
  if (isClosed) {
236
- const nearestPoint = endShapeInfo.geometry.nearestPoint(endInEndShapeLocalSpace)
239
+ const nearestPoint = endShapeInfo.geometry.nearestPoint(endInEndShapeLocalSpace, {
240
+ includeInternal: false,
241
+ includeLabels: false,
242
+ })
237
243
  if (Vec.DistMin(nearestPoint, endInEndShapeLocalSpace, 1)) {
238
244
  point = nearestPoint
239
245
  }
@@ -248,7 +248,11 @@ function updateArrowheadPointWithBoundShape(
248
248
  if (targetInt === undefined) {
249
249
  // No intersection? The arrowhead point will be at the arrow terminal.
250
250
  // if we _almost_ hit the target, just put the arrowhead at the target.
251
- targetInt = targetShapeInfo.geometry.nearestPoint(targetTo)
251
+ targetInt = targetShapeInfo.geometry.nearestPoint(targetTo, {
252
+ includeLabels: false,
253
+ includeInternal: false,
254
+ })
255
+
252
256
  if (!Vec.DistMin(targetInt, targetTo, 1)) {
253
257
  return
254
258
  }
@@ -109,7 +109,7 @@ export const RichTextLabel = React.memo(function RichTextLabel({
109
109
  // We don't get the mouseup event later because we preventDefault
110
110
  // so we have to do it manually.
111
111
  const handlePointerUp = (e: TLEventInfo) => {
112
- if (e.name !== 'pointer_up') return
112
+ if (e.name !== 'pointer_up' || !link) return
113
113
 
114
114
  if (!isDragging.current) {
115
115
  window.open(link, '_blank', 'noopener, noreferrer')
@@ -165,7 +165,7 @@ export function DefaultKeyboardShortcutsDialogContent() {
165
165
  <TldrawUiMenuItem
166
166
  id="a11y-select-next-shape-container"
167
167
  label="a11y.enter-leave-container"
168
- kbd="cmd+shift+[[↑→]]"
168
+ kbd="cmd+shift+[[↑↓]]"
169
169
  onSelect={() => {
170
170
  /* do nothing */
171
171
  }}
@@ -1344,6 +1344,28 @@ 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
+ },
1347
1369
  {
1348
1370
  id: 'toggle-grid',
1349
1371
  label: {
@@ -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 = '4.1.0-canary.bdf9b3703a3d'
4
+ export const version = '4.1.0-canary.e2133d922c9e'
5
5
  export const publishDates = {
6
6
  major: '2025-09-18T14:39:22.803Z',
7
- minor: '2025-09-18T18:11:43.980Z',
8
- patch: '2025-09-18T18:11:43.980Z',
7
+ minor: '2025-09-29T21:29:51.533Z',
8
+ patch: '2025-09-29T21:29:51.533Z',
9
9
  }