tldraw 4.1.0-canary.9c36de6e611c → 4.1.0-canary.a152954244d2

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 (225) hide show
  1. package/dist-cjs/index.d.ts +46 -12
  2. package/dist-cjs/index.js +6 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/canvas/TldrawCropHandles.js +1 -1
  5. package/dist-cjs/lib/canvas/TldrawScribble.js +1 -1
  6. package/dist-cjs/lib/canvas/TldrawSelectionForeground.js +1 -1
  7. package/dist-cjs/lib/defaultEmbedDefinitions.js +25 -30
  8. package/dist-cjs/lib/defaultEmbedDefinitions.js.map +2 -2
  9. package/dist-cjs/lib/defaultExternalContentHandlers.js +10 -33
  10. package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
  11. package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js +3 -0
  12. package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js.map +2 -2
  13. package/dist-cjs/lib/shapes/arrow/curved-arrow.js +8 -2
  14. package/dist-cjs/lib/shapes/arrow/curved-arrow.js.map +2 -2
  15. package/dist-cjs/lib/shapes/arrow/straight-arrow.js +4 -1
  16. package/dist-cjs/lib/shapes/arrow/straight-arrow.js.map +2 -2
  17. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js +44 -102
  18. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js.map +2 -2
  19. package/dist-cjs/lib/shapes/bookmark/bookmarks.js +138 -0
  20. package/dist-cjs/lib/shapes/bookmark/bookmarks.js.map +7 -0
  21. package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js +25 -3
  22. package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js.map +2 -2
  23. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +1 -1
  24. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +1 -1
  25. package/dist-cjs/lib/shapes/line/LineShapeUtil.js +3 -0
  26. package/dist-cjs/lib/shapes/line/LineShapeUtil.js.map +2 -2
  27. package/dist-cjs/lib/shapes/shared/HyperlinkButton.js +1 -1
  28. package/dist-cjs/lib/shapes/shared/PlainTextLabel.js +1 -1
  29. package/dist-cjs/lib/shapes/shared/RichTextLabel.js +2 -2
  30. package/dist-cjs/lib/shapes/shared/RichTextLabel.js.map +2 -2
  31. package/dist-cjs/lib/shapes/shared/ShapeFill.js +1 -1
  32. package/dist-cjs/lib/shapes/text/PlainTextArea.js +1 -1
  33. package/dist-cjs/lib/shapes/text/RichTextArea.js +1 -1
  34. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js +1 -1
  35. package/dist-cjs/lib/tools/SelectTool/childStates/Crop/children/Cropping.js +20 -4
  36. package/dist-cjs/lib/tools/SelectTool/childStates/Crop/children/Cropping.js.map +2 -2
  37. package/dist-cjs/lib/tools/SelectTool/childStates/Crop/children/Idle.js +1 -1
  38. package/dist-cjs/lib/tools/SelectTool/childStates/Crop/children/Idle.js.map +2 -2
  39. package/dist-cjs/lib/tools/SelectTool/childStates/Crop/children/PointingCropHandle.js +23 -11
  40. package/dist-cjs/lib/tools/SelectTool/childStates/Crop/children/PointingCropHandle.js.map +2 -2
  41. package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js +27 -6
  42. package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js.map +2 -2
  43. package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js +1 -1
  44. package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js.map +2 -2
  45. package/dist-cjs/lib/tools/SelectTool/childStates/PointingArrowLabel.js +21 -9
  46. package/dist-cjs/lib/tools/SelectTool/childStates/PointingArrowLabel.js.map +2 -2
  47. package/dist-cjs/lib/tools/SelectTool/childStates/PointingResizeHandle.js +24 -8
  48. package/dist-cjs/lib/tools/SelectTool/childStates/PointingResizeHandle.js.map +2 -2
  49. package/dist-cjs/lib/tools/SelectTool/childStates/PointingRotateHandle.js +21 -9
  50. package/dist-cjs/lib/tools/SelectTool/childStates/PointingRotateHandle.js.map +2 -2
  51. package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js +23 -8
  52. package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js.map +2 -2
  53. package/dist-cjs/lib/tools/SelectTool/childStates/Rotating.js +21 -9
  54. package/dist-cjs/lib/tools/SelectTool/childStates/Rotating.js.map +2 -2
  55. package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js +26 -11
  56. package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
  57. package/dist-cjs/lib/ui/TldrawUi.js +2 -2
  58. package/dist-cjs/lib/ui/components/DebugMenu/DefaultDebugMenuContent.js +3 -3
  59. package/dist-cjs/lib/ui/components/DebugMenu/DefaultDebugMenuContent.js.map +1 -1
  60. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialog.js +1 -1
  61. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +1 -1
  62. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +1 -1
  63. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js +1 -1
  64. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js +5 -0
  65. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js.map +2 -2
  66. package/dist-cjs/lib/ui/components/OfflineIndicator/OfflineIndicator.js +1 -1
  67. package/dist-cjs/lib/ui/components/SharePanel/PeopleMenu.js +6 -2
  68. package/dist-cjs/lib/ui/components/SharePanel/PeopleMenu.js.map +2 -2
  69. package/dist-cjs/lib/ui/components/SharePanel/UserPresenceColorPicker.js +1 -1
  70. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +1 -1
  71. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +64 -56
  72. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
  73. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js +54 -47
  74. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js.map +3 -3
  75. package/dist-cjs/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.js +63 -56
  76. package/dist-cjs/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.js.map +2 -2
  77. package/dist-cjs/lib/ui/components/StylePanel/StylePanelDropdownPicker.js +13 -6
  78. package/dist-cjs/lib/ui/components/StylePanel/StylePanelDropdownPicker.js.map +2 -2
  79. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js +1 -1
  80. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +1 -1
  81. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +1 -1
  82. package/dist-cjs/lib/ui/components/menu-items.js +2 -2
  83. package/dist-cjs/lib/ui/components/menu-items.js.map +1 -1
  84. package/dist-cjs/lib/ui/components/primitives/Button/TldrawUiButton.js +2 -2
  85. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +1 -1
  86. package/dist-cjs/lib/ui/components/primitives/TldrawUiDialog.js +1 -1
  87. package/dist-cjs/lib/ui/components/primitives/TldrawUiDropdownMenu.js +1 -1
  88. package/dist-cjs/lib/ui/components/primitives/TldrawUiIcon.js +1 -1
  89. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js +2 -2
  90. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js +2 -2
  91. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +1 -1
  92. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +2 -2
  93. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +1 -1
  94. package/dist-cjs/lib/ui/components/primitives/layout.js +1 -1
  95. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js +1 -1
  96. package/dist-cjs/lib/ui/context/actions.js +44 -30
  97. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  98. package/dist-cjs/lib/ui/context/breakpoints.js +1 -1
  99. package/dist-cjs/lib/ui/context/events.js +1 -1
  100. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js +1 -1
  101. package/dist-cjs/lib/ui/hooks/useEditorEvents.js +1 -1
  102. package/dist-cjs/lib/ui/hooks/useEditorEvents.js.map +1 -1
  103. package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js +1 -1
  104. package/dist-cjs/lib/ui/hooks/useLocalStorageState.js +1 -1
  105. package/dist-cjs/lib/ui/hooks/useTools.js +1 -1
  106. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +4 -4
  107. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +1 -1
  108. package/dist-cjs/lib/ui/hooks/useTranslation/useTranslation.js +1 -1
  109. package/dist-cjs/lib/ui/version.js +3 -3
  110. package/dist-cjs/lib/ui/version.js.map +1 -1
  111. package/dist-cjs/lib/utils/text/richText.js +4 -4
  112. package/dist-esm/index.d.mts +46 -12
  113. package/dist-esm/index.mjs +12 -4
  114. package/dist-esm/index.mjs.map +2 -2
  115. package/dist-esm/lib/defaultEmbedDefinitions.mjs +25 -30
  116. package/dist-esm/lib/defaultEmbedDefinitions.mjs.map +2 -2
  117. package/dist-esm/lib/defaultExternalContentHandlers.mjs +10 -33
  118. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  119. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs +3 -0
  120. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs.map +2 -2
  121. package/dist-esm/lib/shapes/arrow/curved-arrow.mjs +8 -2
  122. package/dist-esm/lib/shapes/arrow/curved-arrow.mjs.map +2 -2
  123. package/dist-esm/lib/shapes/arrow/straight-arrow.mjs +4 -1
  124. package/dist-esm/lib/shapes/arrow/straight-arrow.mjs.map +2 -2
  125. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs +46 -101
  126. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs.map +2 -2
  127. package/dist-esm/lib/shapes/bookmark/bookmarks.mjs +124 -0
  128. package/dist-esm/lib/shapes/bookmark/bookmarks.mjs.map +7 -0
  129. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs +26 -3
  130. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs.map +2 -2
  131. package/dist-esm/lib/shapes/line/LineShapeUtil.mjs +3 -0
  132. package/dist-esm/lib/shapes/line/LineShapeUtil.mjs.map +2 -2
  133. package/dist-esm/lib/shapes/shared/RichTextLabel.mjs +1 -1
  134. package/dist-esm/lib/shapes/shared/RichTextLabel.mjs.map +2 -2
  135. package/dist-esm/lib/tools/SelectTool/childStates/Crop/children/Cropping.mjs +20 -4
  136. package/dist-esm/lib/tools/SelectTool/childStates/Crop/children/Cropping.mjs.map +2 -2
  137. package/dist-esm/lib/tools/SelectTool/childStates/Crop/children/Idle.mjs +1 -1
  138. package/dist-esm/lib/tools/SelectTool/childStates/Crop/children/Idle.mjs.map +2 -2
  139. package/dist-esm/lib/tools/SelectTool/childStates/Crop/children/PointingCropHandle.mjs +23 -11
  140. package/dist-esm/lib/tools/SelectTool/childStates/Crop/children/PointingCropHandle.mjs.map +2 -2
  141. package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs +29 -7
  142. package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs.map +2 -2
  143. package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs +1 -1
  144. package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs.map +2 -2
  145. package/dist-esm/lib/tools/SelectTool/childStates/PointingArrowLabel.mjs +21 -9
  146. package/dist-esm/lib/tools/SelectTool/childStates/PointingArrowLabel.mjs.map +2 -2
  147. package/dist-esm/lib/tools/SelectTool/childStates/PointingResizeHandle.mjs +24 -8
  148. package/dist-esm/lib/tools/SelectTool/childStates/PointingResizeHandle.mjs.map +2 -2
  149. package/dist-esm/lib/tools/SelectTool/childStates/PointingRotateHandle.mjs +21 -9
  150. package/dist-esm/lib/tools/SelectTool/childStates/PointingRotateHandle.mjs.map +2 -2
  151. package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs +23 -8
  152. package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs.map +2 -2
  153. package/dist-esm/lib/tools/SelectTool/childStates/Rotating.mjs +21 -9
  154. package/dist-esm/lib/tools/SelectTool/childStates/Rotating.mjs.map +2 -2
  155. package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs +26 -11
  156. package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
  157. package/dist-esm/lib/ui/components/DebugMenu/DefaultDebugMenuContent.mjs +2 -2
  158. package/dist-esm/lib/ui/components/DebugMenu/DefaultDebugMenuContent.mjs.map +1 -1
  159. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +1 -1
  160. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +1 -1
  161. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs +5 -0
  162. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs.map +2 -2
  163. package/dist-esm/lib/ui/components/SharePanel/PeopleMenu.mjs +6 -2
  164. package/dist-esm/lib/ui/components/SharePanel/PeopleMenu.mjs.map +2 -2
  165. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +68 -57
  166. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
  167. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs +54 -47
  168. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs.map +3 -3
  169. package/dist-esm/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.mjs +63 -56
  170. package/dist-esm/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.mjs.map +2 -2
  171. package/dist-esm/lib/ui/components/StylePanel/StylePanelDropdownPicker.mjs +12 -5
  172. package/dist-esm/lib/ui/components/StylePanel/StylePanelDropdownPicker.mjs.map +2 -2
  173. package/dist-esm/lib/ui/components/menu-items.mjs +2 -2
  174. package/dist-esm/lib/ui/components/menu-items.mjs.map +1 -1
  175. package/dist-esm/lib/ui/context/actions.mjs +43 -29
  176. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  177. package/dist-esm/lib/ui/hooks/useEditorEvents.mjs +1 -1
  178. package/dist-esm/lib/ui/hooks/useEditorEvents.mjs.map +1 -1
  179. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +4 -4
  180. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +1 -1
  181. package/dist-esm/lib/ui/version.mjs +3 -3
  182. package/dist-esm/lib/ui/version.mjs.map +1 -1
  183. package/package.json +11 -11
  184. package/src/index.ts +4 -0
  185. package/src/lib/defaultEmbedDefinitions.ts +20 -24
  186. package/src/lib/defaultExternalContentHandlers.ts +12 -37
  187. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +211 -1
  188. package/src/lib/shapes/arrow/ArrowShapeUtil.tsx +3 -0
  189. package/src/lib/shapes/arrow/curved-arrow.ts +8 -2
  190. package/src/lib/shapes/arrow/straight-arrow.ts +5 -1
  191. package/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx +51 -135
  192. package/src/lib/shapes/bookmark/bookmarks.ts +170 -0
  193. package/src/lib/shapes/embed/EmbedShapeUtil.tsx +28 -2
  194. package/src/lib/shapes/line/LineShapeUtil.tsx +3 -0
  195. package/src/lib/shapes/shared/RichTextLabel.tsx +1 -1
  196. package/src/lib/shapes/text/TextShapeTool.test.ts +74 -0
  197. package/src/lib/tools/SelectTool/childStates/Crop/children/Cropping.ts +23 -6
  198. package/src/lib/tools/SelectTool/childStates/Crop/children/Idle.ts +1 -1
  199. package/src/lib/tools/SelectTool/childStates/Crop/children/PointingCropHandle.ts +24 -12
  200. package/src/lib/tools/SelectTool/childStates/DraggingHandle.tsx +34 -11
  201. package/src/lib/tools/SelectTool/childStates/Idle.ts +1 -1
  202. package/src/lib/tools/SelectTool/childStates/PointingArrowLabel.ts +23 -11
  203. package/src/lib/tools/SelectTool/childStates/PointingResizeHandle.ts +26 -9
  204. package/src/lib/tools/SelectTool/childStates/PointingRotateHandle.ts +23 -10
  205. package/src/lib/tools/SelectTool/childStates/Resizing.ts +24 -9
  206. package/src/lib/tools/SelectTool/childStates/Rotating.ts +27 -11
  207. package/src/lib/tools/SelectTool/childStates/Translating.ts +28 -12
  208. package/src/lib/ui/components/DebugMenu/DefaultDebugMenuContent.tsx +2 -2
  209. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +1 -1
  210. package/src/lib/ui/components/Minimap/MinimapManager.ts +6 -0
  211. package/src/lib/ui/components/SharePanel/PeopleMenu.tsx +6 -2
  212. package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +60 -49
  213. package/src/lib/ui/components/StylePanel/StylePanelButtonPicker.tsx +70 -53
  214. package/src/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.tsx +105 -90
  215. package/src/lib/ui/components/StylePanel/StylePanelDropdownPicker.tsx +72 -51
  216. package/src/lib/ui/components/menu-items.tsx +2 -2
  217. package/src/lib/ui/context/actions.tsx +49 -31
  218. package/src/lib/ui/hooks/useEditorEvents.ts +1 -1
  219. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +4 -4
  220. package/src/lib/ui/version.ts +3 -3
  221. package/src/lib/utils/embeds/embeds.test.ts +16 -34
  222. package/src/test/SelectTool.test.ts +251 -0
  223. package/src/test/bookmark-shapes.test.ts +129 -7
  224. package/src/test/customSnapping.test.tsx +55 -11
  225. package/tldraw.css +7 -2
@@ -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
+ })
@@ -144,6 +144,9 @@ export class ArrowShapeUtil extends ShapeUtil<TLArrowShape> {
144
144
  override hideSelectionBoundsFg() {
145
145
  return true
146
146
  }
147
+ override hideInMinimap() {
148
+ return true
149
+ }
147
150
 
148
151
  override canBeLaidOut(shape: TLArrowShape, info: TLShapeUtilCanBeLaidOutOpts) {
149
152
  if (info.type === 'flip') {
@@ -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
  }
@@ -1,7 +1,5 @@
1
1
  import {
2
- AssetRecordType,
3
2
  BaseBoxShapeUtil,
4
- Editor,
5
3
  HTMLContainer,
6
4
  T,
7
5
  TLAssetId,
@@ -10,8 +8,6 @@ import {
10
8
  TLBookmarkShapeProps,
11
9
  bookmarkShapeMigrations,
12
10
  bookmarkShapeProps,
13
- debounce,
14
- getHashForString,
15
11
  lerp,
16
12
  tlenv,
17
13
  toDomPrecision,
@@ -24,11 +20,13 @@ import { convertCommonTitleHTMLEntities } from '../../utils/text/text'
24
20
  import { HyperlinkButton } from '../shared/HyperlinkButton'
25
21
  import { LINK_ICON } from '../shared/icons-editor'
26
22
  import { getRotatedBoxShadow } from '../shared/rotated-box-shadow'
27
-
28
- const BOOKMARK_WIDTH = 300
29
- const BOOKMARK_HEIGHT = 320
30
- const BOOKMARK_JUST_URL_HEIGHT = 46
31
- const SHORT_BOOKMARK_HEIGHT = 101
23
+ import {
24
+ BOOKMARK_HEIGHT,
25
+ BOOKMARK_WIDTH,
26
+ getHumanReadableAddress,
27
+ setBookmarkHeight,
28
+ updateBookmarkAssetOnUrlChange,
29
+ } from './bookmarks'
32
30
 
33
31
  /** @public */
34
32
  export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
@@ -71,22 +69,18 @@ export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
71
69
  }
72
70
 
73
71
  override component(shape: TLBookmarkShape) {
74
- return <BookmarkShapeComponent shape={shape} />
72
+ const { assetId, url, h } = shape.props
73
+ const rotation = this.editor.getShapePageTransform(shape)!.rotation()
74
+
75
+ return <BookmarkShapeComponent assetId={assetId} url={url} h={h} rotation={rotation} />
75
76
  }
76
77
 
77
78
  override indicator(shape: TLBookmarkShape) {
78
- return (
79
- <rect
80
- width={toDomPrecision(shape.props.w)}
81
- height={toDomPrecision(shape.props.h)}
82
- rx="6"
83
- ry="6"
84
- />
85
- )
79
+ return <BookmarkIndicatorComponent w={shape.props.w} h={shape.props.h} />
86
80
  }
87
81
 
88
82
  override onBeforeCreate(next: TLBookmarkShape) {
89
- return getBookmarkSize(this.editor, next)
83
+ return setBookmarkHeight(this.editor, next)
90
84
  }
91
85
 
92
86
  override onBeforeUpdate(prev: TLBookmarkShape, shape: TLBookmarkShape) {
@@ -99,7 +93,7 @@ export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
99
93
  }
100
94
 
101
95
  if (prev.props.assetId !== shape.props.assetId) {
102
- return getBookmarkSize(this.editor, shape)
96
+ return setBookmarkHeight(this.editor, shape)
103
97
  }
104
98
  }
105
99
  override getInterpolatedProps(
@@ -115,18 +109,30 @@ export class BookmarkShapeUtil extends BaseBoxShapeUtil<TLBookmarkShape> {
115
109
  }
116
110
  }
117
111
 
118
- function BookmarkShapeComponent({ shape }: { shape: TLBookmarkShape }) {
112
+ export function BookmarkIndicatorComponent({ w, h }: { w: number; h: number }) {
113
+ return <rect width={toDomPrecision(w)} height={toDomPrecision(h)} rx="6" ry="6" />
114
+ }
115
+
116
+ export function BookmarkShapeComponent({
117
+ assetId,
118
+ rotation,
119
+ url,
120
+ h,
121
+ showImageContainer = true,
122
+ }: {
123
+ assetId: TLAssetId | null
124
+ rotation: number
125
+ h: number
126
+ url: string
127
+ showImageContainer?: boolean
128
+ }) {
119
129
  const editor = useEditor()
120
130
 
121
- const asset = (
122
- shape.props.assetId ? editor.getAsset(shape.props.assetId) : null
123
- ) as TLBookmarkAsset
131
+ const asset = assetId ? (editor.getAsset(assetId) as TLBookmarkAsset) : null
124
132
 
125
133
  const isSafariExport = !!useSvgExportContext() && tlenv.isSafari
126
134
 
127
- const pageRotation = editor.getShapePageTransform(shape)!.rotation()
128
-
129
- const address = getHumanReadableAddress(shape)
135
+ const address = getHumanReadableAddress(url)
130
136
 
131
137
  const [isFaviconValid, setIsFaviconValid] = useState(true)
132
138
  const onFaviconError = () => setIsFaviconValid(false)
@@ -146,11 +152,11 @@ function BookmarkShapeComponent({ shape }: { shape: TLBookmarkShape }) {
146
152
  isSafariExport && 'tl-bookmark__container--safariExport'
147
153
  )}
148
154
  style={{
149
- boxShadow: isSafariExport ? undefined : getRotatedBoxShadow(pageRotation),
150
- maxHeight: shape.props.h,
155
+ boxShadow: isSafariExport ? undefined : getRotatedBoxShadow(rotation),
156
+ maxHeight: h,
151
157
  }}
152
158
  >
153
- {(!asset || asset.props.image) && (
159
+ {showImageContainer && (!asset || asset.props.image) && (
154
160
  <div className="tl-bookmark__image_container">
155
161
  {asset ? (
156
162
  <img
@@ -163,21 +169,31 @@ function BookmarkShapeComponent({ shape }: { shape: TLBookmarkShape }) {
163
169
  ) : (
164
170
  <div className="tl-bookmark__placeholder" />
165
171
  )}
166
- {asset?.props.image && <HyperlinkButton url={shape.props.url} />}
172
+ {asset?.props.image && <HyperlinkButton url={url} />}
167
173
  </div>
168
174
  )}
169
175
  <div className="tl-bookmark__copy_container">
170
176
  {asset?.props.title ? (
171
- <h2 className="tl-bookmark__heading">
172
- {convertCommonTitleHTMLEntities(asset.props.title)}
173
- </h2>
177
+ <a
178
+ className="tl-bookmark__link"
179
+ href={url || ''}
180
+ target="_blank"
181
+ rel="noopener noreferrer"
182
+ draggable={false}
183
+ onPointerDown={markAsHandledOnShiftKey}
184
+ onPointerUp={markAsHandledOnShiftKey}
185
+ >
186
+ <h2 className="tl-bookmark__heading">
187
+ {convertCommonTitleHTMLEntities(asset.props.title)}
188
+ </h2>
189
+ </a>
174
190
  ) : null}
175
191
  {asset?.props.description && asset?.props.image ? (
176
192
  <p className="tl-bookmark__description">{asset.props.description}</p>
177
193
  ) : null}
178
194
  <a
179
195
  className="tl-bookmark__link"
180
- href={shape.props.url || ''}
196
+ href={url || ''}
181
197
  target="_blank"
182
198
  rel="noopener noreferrer"
183
199
  draggable={false}
@@ -208,103 +224,3 @@ function BookmarkShapeComponent({ shape }: { shape: TLBookmarkShape }) {
208
224
  </HTMLContainer>
209
225
  )
210
226
  }
211
-
212
- function getBookmarkSize(editor: Editor, shape: TLBookmarkShape) {
213
- const asset = (
214
- shape.props.assetId ? editor.getAsset(shape.props.assetId) : null
215
- ) as TLBookmarkAsset
216
-
217
- let h = BOOKMARK_HEIGHT
218
-
219
- if (asset) {
220
- if (!asset.props.image) {
221
- if (!asset.props.title) {
222
- h = BOOKMARK_JUST_URL_HEIGHT
223
- } else {
224
- h = SHORT_BOOKMARK_HEIGHT
225
- }
226
- }
227
- }
228
-
229
- return {
230
- ...shape,
231
- props: {
232
- ...shape.props,
233
- h,
234
- },
235
- }
236
- }
237
-
238
- /** @internal */
239
- export const getHumanReadableAddress = (shape: TLBookmarkShape) => {
240
- try {
241
- const url = new URL(shape.props.url)
242
- // we want the hostname without any www
243
- return url.hostname.replace(/^www\./, '')
244
- } catch {
245
- return shape.props.url
246
- }
247
- }
248
-
249
- function updateBookmarkAssetOnUrlChange(editor: Editor, shape: TLBookmarkShape) {
250
- const { url } = shape.props
251
-
252
- // Derive the asset id from the URL
253
- const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url))
254
-
255
- if (editor.getAsset(assetId)) {
256
- // Existing asset for this URL?
257
- if (shape.props.assetId !== assetId) {
258
- editor.updateShapes<TLBookmarkShape>([
259
- {
260
- id: shape.id,
261
- type: shape.type,
262
- props: { assetId },
263
- },
264
- ])
265
- }
266
- } else {
267
- // No asset for this URL?
268
-
269
- // First, clear out the existing asset reference
270
- editor.updateShapes<TLBookmarkShape>([
271
- {
272
- id: shape.id,
273
- type: shape.type,
274
- props: { assetId: null },
275
- },
276
- ])
277
-
278
- // Then try to asyncronously create a new one
279
- createBookmarkAssetOnUrlChange(editor, shape)
280
- }
281
- }
282
-
283
- const createBookmarkAssetOnUrlChange = debounce(async (editor: Editor, shape: TLBookmarkShape) => {
284
- if (editor.isDisposed) return
285
-
286
- const { url } = shape.props
287
-
288
- // Create the asset using the external content manager's createAssetFromUrl method.
289
- // This may be overwritten by the user (for example, we overwrite it on tldraw.com)
290
- const asset = await editor.getAssetForExternalContent({ type: 'url', url })
291
-
292
- if (!asset) {
293
- // No asset? Just leave the bookmark as a null assetId.
294
- return
295
- }
296
-
297
- editor.run(() => {
298
- // Create the new asset
299
- editor.createAssets([asset])
300
-
301
- // And update the shape
302
- editor.updateShapes<TLBookmarkShape>([
303
- {
304
- id: shape.id,
305
- type: shape.type,
306
- props: { assetId: asset.id },
307
- },
308
- ])
309
- })
310
- }, 500)
@@ -0,0 +1,170 @@
1
+ import {
2
+ AssetRecordType,
3
+ Editor,
4
+ Result,
5
+ TLAssetId,
6
+ TLBookmarkAsset,
7
+ TLBookmarkShape,
8
+ TLShapePartial,
9
+ createShapeId,
10
+ debounce,
11
+ getHashForString,
12
+ } from '@tldraw/editor'
13
+
14
+ export const BOOKMARK_WIDTH = 300
15
+ export const BOOKMARK_HEIGHT = 320
16
+ export const BOOKMARK_JUST_URL_HEIGHT = 46
17
+ const SHORT_BOOKMARK_HEIGHT = 101
18
+
19
+ export function getBookmarkHeight(editor: Editor, assetId?: TLAssetId | null) {
20
+ const asset = (assetId ? editor.getAsset(assetId) : null) as TLBookmarkAsset | null
21
+
22
+ if (asset) {
23
+ if (!asset.props.image) {
24
+ if (!asset.props.title) {
25
+ return BOOKMARK_JUST_URL_HEIGHT
26
+ } else {
27
+ return SHORT_BOOKMARK_HEIGHT
28
+ }
29
+ }
30
+ }
31
+
32
+ return BOOKMARK_HEIGHT
33
+ }
34
+
35
+ export function setBookmarkHeight(editor: Editor, shape: TLBookmarkShape) {
36
+ return {
37
+ ...shape,
38
+ props: { ...shape.props, h: getBookmarkHeight(editor, shape.props.assetId) },
39
+ }
40
+ }
41
+
42
+ /** @internal */
43
+ export const getHumanReadableAddress = (url: string) => {
44
+ try {
45
+ const objUrl = new URL(url)
46
+ // we want the hostname without any www
47
+ return objUrl.hostname.replace(/^www\./, '')
48
+ } catch {
49
+ return url
50
+ }
51
+ }
52
+
53
+ export function updateBookmarkAssetOnUrlChange(editor: Editor, shape: TLBookmarkShape) {
54
+ const { url } = shape.props
55
+
56
+ // Derive the asset id from the URL
57
+ const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url))
58
+
59
+ if (editor.getAsset(assetId)) {
60
+ // Existing asset for this URL?
61
+ if (shape.props.assetId !== assetId) {
62
+ editor.updateShapes<TLBookmarkShape>([
63
+ {
64
+ id: shape.id,
65
+ type: shape.type,
66
+ props: { assetId },
67
+ },
68
+ ])
69
+ }
70
+ } else {
71
+ // No asset for this URL?
72
+
73
+ // First, clear out the existing asset reference
74
+ editor.updateShapes<TLBookmarkShape>([
75
+ {
76
+ id: shape.id,
77
+ type: shape.type,
78
+ props: { assetId: null },
79
+ },
80
+ ])
81
+
82
+ // Then try to asyncronously create a new one
83
+ createBookmarkAssetOnUrlChange(editor, shape)
84
+ }
85
+ }
86
+
87
+ const createBookmarkAssetOnUrlChange = debounce(async (editor: Editor, shape: TLBookmarkShape) => {
88
+ if (editor.isDisposed) return
89
+
90
+ const { url } = shape.props
91
+
92
+ // Create the asset using the external content manager's createAssetFromUrl method.
93
+ // This may be overwritten by the user (for example, we overwrite it on tldraw.com)
94
+ const asset = await editor.getAssetForExternalContent({ type: 'url', url })
95
+
96
+ if (!asset) {
97
+ // No asset? Just leave the bookmark as a null assetId.
98
+ return
99
+ }
100
+
101
+ editor.run(() => {
102
+ // Create the new asset
103
+ editor.createAssets([asset])
104
+
105
+ // And update the shape
106
+ editor.updateShapes<TLBookmarkShape>([
107
+ {
108
+ id: shape.id,
109
+ type: shape.type,
110
+ props: { assetId: asset.id },
111
+ },
112
+ ])
113
+ })
114
+ }, 500)
115
+
116
+ /**
117
+ * Creates a bookmark shape from a URL with unfurled metadata.
118
+ *
119
+ * @returns A Result containing the created bookmark shape or an error
120
+ * @public
121
+ */
122
+
123
+ export async function createBookmarkFromUrl(
124
+ editor: Editor,
125
+ {
126
+ url,
127
+ center = editor.getViewportPageBounds().center,
128
+ }: {
129
+ url: string
130
+ center?: { x: number; y: number }
131
+ }
132
+ ): Promise<Result<TLBookmarkShape, string>> {
133
+ try {
134
+ // Create the bookmark asset with unfurled metadata
135
+ const asset = await editor.getAssetForExternalContent({ type: 'url', url })
136
+
137
+ // Create the bookmark shape
138
+ const shapeId = createShapeId()
139
+ const shapePartial: TLShapePartial<TLBookmarkShape> = {
140
+ id: shapeId,
141
+ type: 'bookmark',
142
+ x: center.x - BOOKMARK_WIDTH / 2,
143
+ y: center.y - BOOKMARK_HEIGHT / 2,
144
+ rotation: 0,
145
+ opacity: 1,
146
+ props: {
147
+ url,
148
+ assetId: asset?.id || null,
149
+ w: BOOKMARK_WIDTH,
150
+ h: getBookmarkHeight(editor, asset?.id),
151
+ },
152
+ }
153
+
154
+ editor.run(() => {
155
+ // Create the asset if we have one
156
+ if (asset) {
157
+ editor.createAssets([asset])
158
+ }
159
+
160
+ // Create the shape
161
+ editor.createShapes([shapePartial])
162
+ })
163
+
164
+ // Get the created shape
165
+ const createdShape = editor.getShape(shapeId) as TLBookmarkShape
166
+ return Result.ok(createdShape)
167
+ } catch (error) {
168
+ return Result.err(error instanceof Error ? error.message : 'Failed to create bookmark')
169
+ }
170
+ }