tldraw 5.2.0-next.e2b8d10bf10e → 5.2.0

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 (420) hide show
  1. package/DOCS.md +20294 -0
  2. package/README.md +13 -1
  3. package/RELEASE_NOTES.md +2908 -0
  4. package/dist-cjs/index.d.ts +110 -23
  5. package/dist-cjs/index.js +3 -3
  6. package/dist-cjs/index.js.map +2 -2
  7. package/dist-cjs/lib/TldrawImage.js +0 -1
  8. package/dist-cjs/lib/TldrawImage.js.map +2 -2
  9. package/dist-cjs/lib/bindings/arrow/ArrowBindingUtil.js +2 -1
  10. package/dist-cjs/lib/bindings/arrow/ArrowBindingUtil.js.map +2 -2
  11. package/dist-cjs/lib/defaultEmbedDefinitions.js +3 -2
  12. package/dist-cjs/lib/defaultEmbedDefinitions.js.map +2 -2
  13. package/dist-cjs/lib/defaultOverlayUtils.js +11 -11
  14. package/dist-cjs/lib/defaultOverlayUtils.js.map +2 -2
  15. package/dist-cjs/lib/overlays/SelectionForegroundOverlayUtil.js +76 -64
  16. package/dist-cjs/lib/overlays/SelectionForegroundOverlayUtil.js.map +2 -2
  17. package/dist-cjs/lib/overlays/ShapeHandleOverlayUtil.js +1 -1
  18. package/dist-cjs/lib/overlays/ShapeHandleOverlayUtil.js.map +2 -2
  19. package/dist-cjs/lib/overlays/ShapeIndicatorOverlayUtil.js +107 -0
  20. package/dist-cjs/lib/overlays/ShapeIndicatorOverlayUtil.js.map +7 -0
  21. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js +3 -0
  22. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js.map +2 -2
  23. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js +12 -3
  24. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js.map +2 -2
  25. package/dist-cjs/lib/shapes/bookmark/bookmarks.js +54 -6
  26. package/dist-cjs/lib/shapes/bookmark/bookmarks.js.map +2 -2
  27. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js +2 -1
  28. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js.map +2 -2
  29. package/dist-cjs/lib/shapes/draw/getPath.js +1 -1
  30. package/dist-cjs/lib/shapes/draw/getPath.js.map +2 -2
  31. package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js +45 -47
  32. package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js.map +2 -2
  33. package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js +2 -1
  34. package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js.map +2 -2
  35. package/dist-cjs/lib/shapes/frame/FrameShapeTool.js +23 -18
  36. package/dist-cjs/lib/shapes/frame/FrameShapeTool.js.map +2 -2
  37. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +1 -1
  38. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
  39. package/dist-cjs/lib/shapes/geo/toolStates/Pointing.js +3 -0
  40. package/dist-cjs/lib/shapes/geo/toolStates/Pointing.js.map +2 -2
  41. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js +11 -7
  42. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js.map +2 -2
  43. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +3 -11
  44. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js.map +2 -2
  45. package/dist-cjs/lib/shapes/line/toolStates/Pointing.js +5 -1
  46. package/dist-cjs/lib/shapes/line/toolStates/Pointing.js.map +2 -2
  47. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +31 -41
  48. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  49. package/dist-cjs/lib/shapes/note/noteHelpers.js +1 -1
  50. package/dist-cjs/lib/shapes/note/noteHelpers.js.map +2 -2
  51. package/dist-cjs/lib/shapes/note/toolStates/Pointing.js +28 -6
  52. package/dist-cjs/lib/shapes/note/toolStates/Pointing.js.map +2 -2
  53. package/dist-cjs/lib/shapes/shared/PlainTextLabel.js +3 -2
  54. package/dist-cjs/lib/shapes/shared/PlainTextLabel.js.map +2 -2
  55. package/dist-cjs/lib/shapes/shared/RichTextLabel.js +3 -3
  56. package/dist-cjs/lib/shapes/shared/RichTextLabel.js.map +2 -2
  57. package/dist-cjs/lib/shapes/shared/defaultStyleDefs.js +17 -3
  58. package/dist-cjs/lib/shapes/shared/defaultStyleDefs.js.map +2 -2
  59. package/dist-cjs/lib/shapes/shared/freehand/core.js +424 -0
  60. package/dist-cjs/lib/shapes/shared/freehand/core.js.map +7 -0
  61. package/dist-cjs/lib/shapes/shared/freehand/fmt.js +106 -0
  62. package/dist-cjs/lib/shapes/shared/freehand/fmt.js.map +7 -0
  63. package/dist-cjs/lib/shapes/shared/freehand/getStroke.js +6 -6
  64. package/dist-cjs/lib/shapes/shared/freehand/getStroke.js.map +2 -2
  65. package/dist-cjs/lib/shapes/shared/freehand/getStrokeOutlinePoints.js +320 -86
  66. package/dist-cjs/lib/shapes/shared/freehand/getStrokeOutlinePoints.js.map +3 -3
  67. package/dist-cjs/lib/shapes/shared/freehand/getStrokePoints.js +20 -96
  68. package/dist-cjs/lib/shapes/shared/freehand/getStrokePoints.js.map +3 -3
  69. package/dist-cjs/lib/shapes/shared/freehand/svg.js +55 -19
  70. package/dist-cjs/lib/shapes/shared/freehand/svg.js.map +2 -2
  71. package/dist-cjs/lib/shapes/shared/freehand/svgInk.js +189 -97
  72. package/dist-cjs/lib/shapes/shared/freehand/svgInk.js.map +3 -3
  73. package/dist-cjs/lib/shapes/shared/freehand/types.js.map +1 -1
  74. package/dist-cjs/lib/shapes/shared/interpolate-props.js +6 -2
  75. package/dist-cjs/lib/shapes/shared/interpolate-props.js.map +2 -2
  76. package/dist-cjs/lib/shapes/text/toolStates/Pointing.js +3 -0
  77. package/dist-cjs/lib/shapes/text/toolStates/Pointing.js.map +2 -2
  78. package/dist-cjs/lib/tools/HandTool/HandTool.js +16 -6
  79. package/dist-cjs/lib/tools/HandTool/HandTool.js.map +2 -2
  80. package/dist-cjs/lib/tools/HandTool/childStates/Dragging.js +10 -1
  81. package/dist-cjs/lib/tools/HandTool/childStates/Dragging.js.map +2 -2
  82. package/dist-cjs/lib/tools/HandTool/childStates/OneFingerZooming.js +84 -0
  83. package/dist-cjs/lib/tools/HandTool/childStates/OneFingerZooming.js.map +7 -0
  84. package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js +12 -0
  85. package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js.map +2 -2
  86. package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js +4 -0
  87. package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js.map +2 -2
  88. package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js +107 -46
  89. package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js.map +2 -2
  90. package/dist-cjs/lib/ui/TldrawUi.js +4 -3
  91. package/dist-cjs/lib/ui/TldrawUi.js.map +2 -2
  92. package/dist-cjs/lib/ui/components/A11y.js +2 -0
  93. package/dist-cjs/lib/ui/components/A11y.js.map +2 -2
  94. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js +14 -1
  95. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js.map +2 -2
  96. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js +0 -6
  97. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js.map +2 -2
  98. package/dist-cjs/lib/ui/components/DefaultFollowingIndicator.js.map +2 -2
  99. package/dist-cjs/lib/ui/components/Dialogs.js +17 -37
  100. package/dist-cjs/lib/ui/components/Dialogs.js.map +2 -2
  101. package/dist-cjs/lib/ui/components/HelperButtons/BackToContent.js +19 -4
  102. package/dist-cjs/lib/ui/components/HelperButtons/BackToContent.js.map +2 -2
  103. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +12 -2
  104. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +2 -2
  105. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js +47 -70
  106. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js.map +2 -2
  107. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js +25 -46
  108. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js.map +2 -2
  109. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.js.map +2 -2
  110. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuContent.js.map +2 -2
  111. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.js.map +2 -2
  112. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js +1 -1
  113. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js.map +2 -2
  114. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js +1 -0
  115. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js.map +2 -2
  116. package/dist-cjs/lib/ui/components/menu-items.js +7 -0
  117. package/dist-cjs/lib/ui/components/menu-items.js.map +2 -2
  118. package/dist-cjs/lib/ui/components/primitives/TldrawUiDropdownMenu.js +2 -1
  119. package/dist-cjs/lib/ui/components/primitives/TldrawUiDropdownMenu.js.map +2 -2
  120. package/dist-cjs/lib/ui/components/primitives/TldrawUiIcon.js +14 -3
  121. package/dist-cjs/lib/ui/components/primitives/TldrawUiIcon.js.map +2 -2
  122. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js +2 -0
  123. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js.map +2 -2
  124. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js +2 -0
  125. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js.map +2 -2
  126. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.js +0 -2
  127. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.js.map +2 -2
  128. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.js +1 -2
  129. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.js.map +2 -2
  130. package/dist-cjs/lib/ui/context/actions.js +70 -1
  131. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  132. package/dist-cjs/lib/ui/context/asset-urls.js +5 -8
  133. package/dist-cjs/lib/ui/context/asset-urls.js.map +2 -2
  134. package/dist-cjs/lib/ui/context/events.js.map +2 -2
  135. package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js +13 -0
  136. package/dist-cjs/lib/ui/hooks/useKeyboardShortcuts.js.map +2 -2
  137. package/dist-cjs/lib/ui/hooks/useTools.js +1 -1
  138. package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
  139. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  140. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +2 -0
  141. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  142. package/dist-cjs/lib/ui/version.js +3 -3
  143. package/dist-cjs/lib/ui/version.js.map +1 -1
  144. package/dist-cjs/lib/utils/embeds/embeds.js +4 -4
  145. package/dist-cjs/lib/utils/embeds/embeds.js.map +2 -2
  146. package/dist-cjs/lib/utils/test-helpers.js +2 -2
  147. package/dist-cjs/lib/utils/test-helpers.js.map +2 -2
  148. package/dist-cjs/lib/utils/text/richText.js +3 -1
  149. package/dist-cjs/lib/utils/text/richText.js.map +2 -2
  150. package/dist-esm/index.d.mts +110 -23
  151. package/dist-esm/index.mjs +5 -3
  152. package/dist-esm/index.mjs.map +2 -2
  153. package/dist-esm/lib/TldrawImage.mjs +0 -1
  154. package/dist-esm/lib/TldrawImage.mjs.map +2 -2
  155. package/dist-esm/lib/bindings/arrow/ArrowBindingUtil.mjs +2 -1
  156. package/dist-esm/lib/bindings/arrow/ArrowBindingUtil.mjs.map +2 -2
  157. package/dist-esm/lib/defaultEmbedDefinitions.mjs +3 -2
  158. package/dist-esm/lib/defaultEmbedDefinitions.mjs.map +2 -2
  159. package/dist-esm/lib/defaultOverlayUtils.mjs +11 -11
  160. package/dist-esm/lib/defaultOverlayUtils.mjs.map +2 -2
  161. package/dist-esm/lib/overlays/SelectionForegroundOverlayUtil.mjs +77 -65
  162. package/dist-esm/lib/overlays/SelectionForegroundOverlayUtil.mjs.map +2 -2
  163. package/dist-esm/lib/overlays/ShapeHandleOverlayUtil.mjs +1 -1
  164. package/dist-esm/lib/overlays/ShapeHandleOverlayUtil.mjs.map +2 -2
  165. package/dist-esm/lib/overlays/ShapeIndicatorOverlayUtil.mjs +87 -0
  166. package/dist-esm/lib/overlays/ShapeIndicatorOverlayUtil.mjs.map +7 -0
  167. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs +9 -1
  168. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs.map +2 -2
  169. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs +15 -3
  170. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs.map +2 -2
  171. package/dist-esm/lib/shapes/bookmark/bookmarks.mjs +54 -6
  172. package/dist-esm/lib/shapes/bookmark/bookmarks.mjs.map +2 -2
  173. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs +3 -1
  174. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs.map +2 -2
  175. package/dist-esm/lib/shapes/draw/getPath.mjs +1 -1
  176. package/dist-esm/lib/shapes/draw/getPath.mjs.map +2 -2
  177. package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs +46 -47
  178. package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs.map +2 -2
  179. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs +2 -1
  180. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs.map +2 -2
  181. package/dist-esm/lib/shapes/frame/FrameShapeTool.mjs +23 -18
  182. package/dist-esm/lib/shapes/frame/FrameShapeTool.mjs.map +2 -2
  183. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +2 -1
  184. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  185. package/dist-esm/lib/shapes/geo/toolStates/Pointing.mjs +4 -0
  186. package/dist-esm/lib/shapes/geo/toolStates/Pointing.mjs.map +2 -2
  187. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs +12 -7
  188. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs.map +2 -2
  189. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +3 -11
  190. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
  191. package/dist-esm/lib/shapes/line/toolStates/Pointing.mjs +6 -1
  192. package/dist-esm/lib/shapes/line/toolStates/Pointing.mjs.map +2 -2
  193. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +32 -42
  194. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  195. package/dist-esm/lib/shapes/note/noteHelpers.mjs +1 -1
  196. package/dist-esm/lib/shapes/note/noteHelpers.mjs.map +2 -2
  197. package/dist-esm/lib/shapes/note/toolStates/Pointing.mjs +29 -6
  198. package/dist-esm/lib/shapes/note/toolStates/Pointing.mjs.map +2 -2
  199. package/dist-esm/lib/shapes/shared/PlainTextLabel.mjs +3 -2
  200. package/dist-esm/lib/shapes/shared/PlainTextLabel.mjs.map +2 -2
  201. package/dist-esm/lib/shapes/shared/RichTextLabel.mjs +4 -3
  202. package/dist-esm/lib/shapes/shared/RichTextLabel.mjs.map +2 -2
  203. package/dist-esm/lib/shapes/shared/defaultStyleDefs.mjs +17 -3
  204. package/dist-esm/lib/shapes/shared/defaultStyleDefs.mjs.map +2 -2
  205. package/dist-esm/lib/shapes/shared/freehand/core.mjs +404 -0
  206. package/dist-esm/lib/shapes/shared/freehand/core.mjs.map +7 -0
  207. package/dist-esm/lib/shapes/shared/freehand/fmt.mjs +86 -0
  208. package/dist-esm/lib/shapes/shared/freehand/fmt.mjs.map +7 -0
  209. package/dist-esm/lib/shapes/shared/freehand/getStroke.mjs +7 -7
  210. package/dist-esm/lib/shapes/shared/freehand/getStroke.mjs.map +2 -2
  211. package/dist-esm/lib/shapes/shared/freehand/getStrokeOutlinePoints.mjs +332 -86
  212. package/dist-esm/lib/shapes/shared/freehand/getStrokeOutlinePoints.mjs.map +3 -3
  213. package/dist-esm/lib/shapes/shared/freehand/getStrokePoints.mjs +31 -96
  214. package/dist-esm/lib/shapes/shared/freehand/getStrokePoints.mjs.map +3 -3
  215. package/dist-esm/lib/shapes/shared/freehand/svg.mjs +55 -19
  216. package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
  217. package/dist-esm/lib/shapes/shared/freehand/svgInk.mjs +214 -99
  218. package/dist-esm/lib/shapes/shared/freehand/svgInk.mjs.map +3 -3
  219. package/dist-esm/lib/shapes/shared/interpolate-props.mjs +6 -2
  220. package/dist-esm/lib/shapes/shared/interpolate-props.mjs.map +2 -2
  221. package/dist-esm/lib/shapes/text/toolStates/Pointing.mjs +4 -0
  222. package/dist-esm/lib/shapes/text/toolStates/Pointing.mjs.map +2 -2
  223. package/dist-esm/lib/tools/HandTool/HandTool.mjs +16 -6
  224. package/dist-esm/lib/tools/HandTool/HandTool.mjs.map +2 -2
  225. package/dist-esm/lib/tools/HandTool/childStates/Dragging.mjs +10 -1
  226. package/dist-esm/lib/tools/HandTool/childStates/Dragging.mjs.map +2 -2
  227. package/dist-esm/lib/tools/HandTool/childStates/OneFingerZooming.mjs +64 -0
  228. package/dist-esm/lib/tools/HandTool/childStates/OneFingerZooming.mjs.map +7 -0
  229. package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs +12 -0
  230. package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs.map +2 -2
  231. package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs +4 -0
  232. package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs.map +2 -2
  233. package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs +108 -46
  234. package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs.map +2 -2
  235. package/dist-esm/lib/ui/TldrawUi.mjs +4 -3
  236. package/dist-esm/lib/ui/TldrawUi.mjs.map +2 -2
  237. package/dist-esm/lib/ui/components/A11y.mjs +2 -0
  238. package/dist-esm/lib/ui/components/A11y.mjs.map +2 -2
  239. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs +21 -2
  240. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs.map +2 -2
  241. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs +0 -6
  242. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs.map +2 -2
  243. package/dist-esm/lib/ui/components/DefaultFollowingIndicator.mjs.map +2 -2
  244. package/dist-esm/lib/ui/components/Dialogs.mjs +19 -39
  245. package/dist-esm/lib/ui/components/Dialogs.mjs.map +2 -2
  246. package/dist-esm/lib/ui/components/HelperButtons/BackToContent.mjs +20 -5
  247. package/dist-esm/lib/ui/components/HelperButtons/BackToContent.mjs.map +2 -2
  248. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +12 -2
  249. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +2 -2
  250. package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs +47 -70
  251. package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs.map +2 -2
  252. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs +25 -46
  253. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs.map +2 -2
  254. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.mjs.map +2 -2
  255. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuContent.mjs.map +2 -2
  256. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.mjs.map +2 -2
  257. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs +1 -1
  258. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs.map +2 -2
  259. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs +1 -0
  260. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs.map +2 -2
  261. package/dist-esm/lib/ui/components/menu-items.mjs +7 -0
  262. package/dist-esm/lib/ui/components/menu-items.mjs.map +2 -2
  263. package/dist-esm/lib/ui/components/primitives/TldrawUiDropdownMenu.mjs +2 -1
  264. package/dist-esm/lib/ui/components/primitives/TldrawUiDropdownMenu.mjs.map +2 -2
  265. package/dist-esm/lib/ui/components/primitives/TldrawUiIcon.mjs +14 -3
  266. package/dist-esm/lib/ui/components/primitives/TldrawUiIcon.mjs.map +2 -2
  267. package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs +2 -0
  268. package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs.map +2 -2
  269. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs +2 -0
  270. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs.map +2 -2
  271. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.mjs +0 -2
  272. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.mjs.map +2 -2
  273. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.mjs +1 -2
  274. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.mjs.map +2 -2
  275. package/dist-esm/lib/ui/context/actions.mjs +71 -1
  276. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  277. package/dist-esm/lib/ui/context/asset-urls.mjs +5 -8
  278. package/dist-esm/lib/ui/context/asset-urls.mjs.map +2 -2
  279. package/dist-esm/lib/ui/context/events.mjs.map +2 -2
  280. package/dist-esm/lib/ui/hooks/useKeyboardShortcuts.mjs +13 -0
  281. package/dist-esm/lib/ui/hooks/useKeyboardShortcuts.mjs.map +2 -2
  282. package/dist-esm/lib/ui/hooks/useTools.mjs +1 -1
  283. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  284. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +2 -0
  285. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  286. package/dist-esm/lib/ui/version.mjs +3 -3
  287. package/dist-esm/lib/ui/version.mjs.map +1 -1
  288. package/dist-esm/lib/utils/embeds/embeds.mjs +4 -4
  289. package/dist-esm/lib/utils/embeds/embeds.mjs.map +2 -2
  290. package/dist-esm/lib/utils/test-helpers.mjs +2 -2
  291. package/dist-esm/lib/utils/test-helpers.mjs.map +2 -2
  292. package/dist-esm/lib/utils/text/richText.mjs +3 -1
  293. package/dist-esm/lib/utils/text/richText.mjs.map +2 -2
  294. package/package.json +11 -6
  295. package/src/index.ts +6 -1
  296. package/src/lib/TldrawImage.tsx +2 -2
  297. package/src/lib/bindings/arrow/ArrowBindingUtil.ts +5 -1
  298. package/src/lib/defaultEmbedDefinitions.ts +28 -6
  299. package/src/lib/defaultOverlayUtils.ts +11 -11
  300. package/src/lib/overlays/SelectionForegroundOverlayUtil.ts +91 -72
  301. package/src/lib/overlays/ShapeHandleOverlayUtil.ts +3 -1
  302. package/src/lib/overlays/ShapeIndicatorOverlayUtil.ts +133 -0
  303. package/src/lib/shapes/arrow/toolStates/Pointing.tsx +11 -1
  304. package/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx +16 -3
  305. package/src/lib/shapes/bookmark/bookmarks.ts +105 -15
  306. package/src/lib/shapes/draw/DrawShapeUtil.tsx +7 -5
  307. package/src/lib/shapes/draw/getPath.ts +1 -1
  308. package/src/lib/shapes/draw/toolStates/Drawing.ts +55 -48
  309. package/src/lib/shapes/embed/EmbedShapeUtil.tsx +14 -4
  310. package/src/lib/shapes/frame/FrameShapeTool.ts +35 -24
  311. package/src/lib/shapes/geo/GeoShapeUtil.test.tsx +46 -0
  312. package/src/lib/shapes/geo/GeoShapeUtil.tsx +2 -1
  313. package/src/lib/shapes/geo/toolStates/Pointing.ts +5 -0
  314. package/src/lib/shapes/highlight/HighlightShapeUtil.tsx +14 -11
  315. package/src/lib/shapes/image/ImageShapeUtil.tsx +7 -11
  316. package/src/lib/shapes/line/LineShapeTool.test.ts +41 -0
  317. package/src/lib/shapes/line/toolStates/Pointing.ts +12 -2
  318. package/src/lib/shapes/note/NoteShapeUtil.tsx +36 -51
  319. package/src/lib/shapes/note/noteCloning.test.ts +5 -5
  320. package/src/lib/shapes/note/noteHelpers.ts +1 -1
  321. package/src/lib/shapes/note/toolStates/Pointing.ts +40 -7
  322. package/src/lib/shapes/shared/PlainTextLabel.tsx +3 -3
  323. package/src/lib/shapes/shared/RichTextLabel.tsx +4 -3
  324. package/src/lib/shapes/shared/defaultStyleDefs.tsx +25 -3
  325. package/src/lib/shapes/shared/freehand/core.ts +547 -0
  326. package/src/lib/shapes/shared/freehand/fmt.ts +106 -0
  327. package/src/lib/shapes/shared/freehand/getStroke.ts +7 -7
  328. package/src/lib/shapes/shared/freehand/getStrokeOutlinePoints.ts +451 -139
  329. package/src/lib/shapes/shared/freehand/getStrokePoints.ts +33 -153
  330. package/src/lib/shapes/shared/freehand/svg.ts +72 -23
  331. package/src/lib/shapes/shared/freehand/svgInk.ts +281 -136
  332. package/src/lib/shapes/shared/freehand/types.ts +0 -1
  333. package/src/lib/shapes/shared/interpolate-props.ts +6 -2
  334. package/src/lib/shapes/text/toolStates/Pointing.ts +5 -0
  335. package/src/lib/tools/HandTool/HandTool.ts +19 -6
  336. package/src/lib/tools/HandTool/childStates/Dragging.ts +17 -1
  337. package/src/lib/tools/HandTool/childStates/OneFingerZooming.ts +79 -0
  338. package/src/lib/tools/SelectTool/childStates/Idle.ts +16 -2
  339. package/src/lib/tools/SelectTool/childStates/PointingShape.ts +8 -0
  340. package/src/lib/tools/SelectTool/childStates/Resizing.ts +137 -49
  341. package/src/lib/ui/TldrawUi.tsx +3 -3
  342. package/src/lib/ui/components/A11y.tsx +2 -0
  343. package/src/lib/ui/components/ContextMenu/DefaultContextMenu.tsx +32 -2
  344. package/src/lib/ui/components/ContextMenu/DefaultContextMenuContent.tsx +5 -6
  345. package/src/lib/ui/components/DefaultFollowingIndicator.tsx +2 -2
  346. package/src/lib/ui/components/Dialogs.tsx +20 -23
  347. package/src/lib/ui/components/HelperButtons/BackToContent.tsx +32 -4
  348. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +10 -0
  349. package/src/lib/ui/components/Minimap/DefaultMinimap.tsx +60 -74
  350. package/src/lib/ui/components/Minimap/MinimapManager.ts +35 -57
  351. package/src/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.tsx +2 -2
  352. package/src/lib/ui/components/SharePanel/DefaultPeopleMenuContent.tsx +2 -1
  353. package/src/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.tsx +2 -1
  354. package/src/lib/ui/components/SharePanel/DefaultPeopleMenuItem.tsx +3 -3
  355. package/src/lib/ui/components/StylePanel/StylePanelButtonPicker.tsx +5 -0
  356. package/src/lib/ui/components/menu-items.tsx +9 -0
  357. package/src/lib/ui/components/primitives/TldrawUiDropdownMenu.tsx +3 -1
  358. package/src/lib/ui/components/primitives/TldrawUiIcon.tsx +15 -3
  359. package/src/lib/ui/components/primitives/TldrawUiInput.tsx +4 -0
  360. package/src/lib/ui/components/primitives/TldrawUiPopover.tsx +7 -0
  361. package/src/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.tsx +0 -2
  362. package/src/lib/ui/components/primitives/menus/TldrawUiMenuSubmenu.tsx +0 -1
  363. package/src/lib/ui/context/actions.tsx +88 -1
  364. package/src/lib/ui/context/asset-urls.tsx +9 -8
  365. package/src/lib/ui/context/events.tsx +2 -0
  366. package/src/lib/ui/hooks/useKeyboardShortcuts.ts +45 -7
  367. package/src/lib/ui/hooks/useTools.tsx +7 -1
  368. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +2 -0
  369. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +2 -0
  370. package/src/lib/ui/version.ts +3 -3
  371. package/src/lib/ui.css +20 -9
  372. package/src/lib/utils/embeds/embeds.test.ts +14 -12
  373. package/src/lib/utils/embeds/embeds.ts +10 -4
  374. package/src/lib/utils/test-helpers.ts +3 -3
  375. package/src/lib/utils/text/richText.test.ts +36 -0
  376. package/src/lib/utils/text/richText.ts +9 -1
  377. package/src/test/HandTool.test.ts +93 -0
  378. package/src/test/ImageShapeUtil.test.ts +88 -0
  379. package/src/test/SelectTool.test.ts +102 -1
  380. package/src/test/__snapshots__/drawing.test.ts.snap +2 -1
  381. package/src/test/attribution.test.ts +40 -29
  382. package/src/test/bookmark-shapes.test.ts +256 -28
  383. package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +2 -2
  384. package/src/test/commands/clipboardCallbacks.test.ts +3 -1
  385. package/src/test/commands/getSvgString.test.ts +30 -0
  386. package/src/test/commands/penmode.test.ts +97 -1
  387. package/src/test/commands/setCamera.test.ts +12 -0
  388. package/src/test/dragFromToolbar.test.ts +67 -0
  389. package/src/test/drawing.test.ts +48 -7
  390. package/src/test/frames.test.ts +72 -0
  391. package/src/test/freehand/corpus/presets.ts +71 -0
  392. package/src/test/freehand/corpus/real.ts +370 -0
  393. package/src/test/freehand/corpus/strokes.ts +50 -0
  394. package/src/test/freehand/svg.test.ts +20 -0
  395. package/src/test/freehand/wellformed.test.ts +30 -0
  396. package/src/test/long-press-create.test.ts +102 -0
  397. package/src/test/overlays/CollaboratorBrushOverlayUtil.test.ts +4 -4
  398. package/src/test/overlays/CollaboratorCursorOverlayUtil.test.ts +18 -18
  399. package/src/test/overlays/CollaboratorHintOverlayUtil.test.ts +7 -7
  400. package/src/test/overlays/CollaboratorScribbleOverlayUtil.test.ts +3 -3
  401. package/src/test/overlays/OverlayManager.test.ts +1 -1
  402. package/src/test/overlays/SelectionForegroundOverlayUtil.test.ts +58 -0
  403. package/src/test/overlays/ShapeHandleOverlayUtil.test.ts +2 -2
  404. package/src/test/overlays/ShapeIndicatorOverlayUtil.test.ts +36 -0
  405. package/src/test/resizing.test.ts +250 -0
  406. package/src/test/right-click-context-menu.test.ts +90 -0
  407. package/src/test/rightClickPanning.test.ts +11 -0
  408. package/src/test/selection-omnibus.test.ts +15 -2
  409. package/src/test/spatialIndex.test.ts +132 -0
  410. package/src/test/translating.test.ts +0 -6
  411. package/src/test/ui/ContextMenu.test.tsx +146 -3
  412. package/src/test/ui/Dialogs.test.tsx +256 -0
  413. package/src/test/ui/copyHoveredStyles.test.tsx +154 -0
  414. package/src/test/ui/keyboardShortcuts.test.tsx +266 -0
  415. package/tldraw.css +35 -12
  416. package/dist-cjs/lib/shapes/shared/freehand/setStrokePointRadii.js +0 -100
  417. package/dist-cjs/lib/shapes/shared/freehand/setStrokePointRadii.js.map +0 -7
  418. package/dist-esm/lib/shapes/shared/freehand/setStrokePointRadii.mjs +0 -80
  419. package/dist-esm/lib/shapes/shared/freehand/setStrokePointRadii.mjs.map +0 -7
  420. package/src/lib/shapes/shared/freehand/setStrokePointRadii.ts +0 -127
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  GapsSnapIndicator,
3
+ Mat,
3
4
  PI,
4
5
  PI2,
5
6
  PointsSnapIndicator,
6
7
  RotateCorner,
8
+ TLArrowShape,
7
9
  TLGeoShape,
8
10
  TLSelectionHandle,
9
11
  TLShapeId,
@@ -16,6 +18,7 @@ import {
16
18
  toRichText,
17
19
  } from '@tldraw/editor'
18
20
  import { vi } from 'vitest'
21
+ import { getArrowBindings, getArrowTerminalsInArrowSpace } from '../lib/shapes/arrow/shared'
19
22
  import { NoteShapeUtil } from '../lib/shapes/note/NoteShapeUtil'
20
23
  import { createDrawSegments } from '../lib/utils/test-helpers'
21
24
  import { getSnapLines } from './getSnapLines'
@@ -867,6 +870,253 @@ describe('When resizing a shape with children', () => {
867
870
  })
868
871
  })
869
872
 
873
+ describe('When resizing nested shapes', () => {
874
+ // A document with the common kinds of nesting: a geo shape on the page, a
875
+ // group of two geo shapes, a frame with a geo child, and arrows bound from
876
+ // the page shape to a group child and from a group child to the frame child.
877
+ //
878
+ // 0 100 200 500
879
+ //
880
+ // 0 ┌─group1─────────┐
881
+ // │ ┌─────┐ │
882
+ // │ │ A │ ┌─────┐│
883
+ // 100 │ └─────┘ │ B ││
884
+ // 200 ┌─────┐ └─────────┴─────┘│
885
+ // │page │
886
+ // 300 └─────┘
887
+ // 400 ┌─frame1─────────┐
888
+ // │ ┌─────┐ │
889
+ // │ │ C │ │
890
+ // 600 └─┴─────┴────────┘
891
+ const nestedIds = {
892
+ pageBox: createShapeId('pageBox'),
893
+ group1: createShapeId('group1'),
894
+ groupBoxA: createShapeId('groupBoxA'),
895
+ groupBoxB: createShapeId('groupBoxB'),
896
+ frame1: createShapeId('frame1'),
897
+ frameBox: createShapeId('frameBox'),
898
+ arrowPageToGroup: createShapeId('arrowPageToGroup'),
899
+ arrowGroupToFrame: createShapeId('arrowGroupToFrame'),
900
+ }
901
+
902
+ beforeEach(() => {
903
+ editor.selectAll().deleteShapes(editor.getSelectedShapeIds())
904
+ editor
905
+ .createShapes([
906
+ { id: nestedIds.pageBox, type: 'geo', x: 0, y: 200, props: { w: 100, h: 100 } },
907
+ { id: nestedIds.groupBoxA, type: 'geo', x: 200, y: 0, props: { w: 100, h: 100 } },
908
+ { id: nestedIds.groupBoxB, type: 'geo', x: 400, y: 100, props: { w: 100, h: 100 } },
909
+ { id: nestedIds.frame1, type: 'frame', x: 200, y: 400, props: { w: 300, h: 200 } },
910
+ {
911
+ id: nestedIds.frameBox,
912
+ type: 'geo',
913
+ parentId: nestedIds.frame1,
914
+ x: 50,
915
+ y: 50,
916
+ props: { w: 100, h: 100 },
917
+ },
918
+ ])
919
+ .groupShapes([nestedIds.groupBoxA, nestedIds.groupBoxB], { groupId: nestedIds.group1 })
920
+ .createShapes([
921
+ {
922
+ id: nestedIds.arrowPageToGroup,
923
+ type: 'arrow',
924
+ x: 150,
925
+ y: 200,
926
+ props: { start: { x: 0, y: 0 }, end: { x: 50, y: -100 } },
927
+ },
928
+ {
929
+ id: nestedIds.arrowGroupToFrame,
930
+ type: 'arrow',
931
+ x: 450,
932
+ y: 250,
933
+ props: { start: { x: 0, y: 0 }, end: { x: -150, y: 250 } },
934
+ },
935
+ ])
936
+ .createBindings([
937
+ {
938
+ type: 'arrow',
939
+ fromId: nestedIds.arrowPageToGroup,
940
+ toId: nestedIds.pageBox,
941
+ props: {
942
+ terminal: 'start',
943
+ isExact: false,
944
+ isPrecise: true,
945
+ normalizedAnchor: { x: 0.5, y: 0.5 },
946
+ },
947
+ },
948
+ {
949
+ type: 'arrow',
950
+ fromId: nestedIds.arrowPageToGroup,
951
+ toId: nestedIds.groupBoxA,
952
+ props: {
953
+ terminal: 'end',
954
+ isExact: false,
955
+ isPrecise: true,
956
+ normalizedAnchor: { x: 0.5, y: 0.5 },
957
+ },
958
+ },
959
+ {
960
+ type: 'arrow',
961
+ fromId: nestedIds.arrowGroupToFrame,
962
+ toId: nestedIds.groupBoxB,
963
+ props: {
964
+ terminal: 'start',
965
+ isExact: false,
966
+ isPrecise: true,
967
+ normalizedAnchor: { x: 0.5, y: 0.5 },
968
+ },
969
+ },
970
+ {
971
+ type: 'arrow',
972
+ fromId: nestedIds.arrowGroupToFrame,
973
+ toId: nestedIds.frameBox,
974
+ props: {
975
+ terminal: 'end',
976
+ isExact: false,
977
+ isPrecise: true,
978
+ normalizedAnchor: { x: 0.5, y: 0.5 },
979
+ },
980
+ },
981
+ ])
982
+ .selectNone()
983
+ })
984
+
985
+ const getArrowTerminalPagePoints = (arrowId: TLShapeId) => {
986
+ const arrow = editor.getShape<TLArrowShape>(arrowId)!
987
+ const bindings = getArrowBindings(editor, arrow)
988
+ const terminals = getArrowTerminalsInArrowSpace(editor, arrow, bindings)
989
+ const pageTransform = editor.getShapePageTransform(arrowId)!
990
+ return {
991
+ start: Mat.applyToPoint(pageTransform, terminals.start),
992
+ end: Mat.applyToPoint(pageTransform, terminals.end),
993
+ }
994
+ }
995
+
996
+ it('resizes the children of a group', () => {
997
+ editor
998
+ .select(nestedIds.group1)
999
+ // the group's page bounds are (200,0) -> (500,200); double them
1000
+ .pointerDownOnHandle('bottom_right')
1001
+ .pointerMove(800, 400)
1002
+
1003
+ expect(roundedPageBounds(nestedIds.group1)).toMatchObject({ x: 200, y: 0, w: 600, h: 400 })
1004
+ expect(roundedPageBounds(nestedIds.groupBoxA)).toMatchObject({ x: 200, y: 0, w: 200, h: 200 })
1005
+ expect(roundedPageBounds(nestedIds.groupBoxB)).toMatchObject({
1006
+ x: 600,
1007
+ y: 200,
1008
+ w: 200,
1009
+ h: 200,
1010
+ })
1011
+
1012
+ // shapes outside of the group are unaffected
1013
+ expect(roundedPageBounds(nestedIds.pageBox)).toMatchObject({ x: 0, y: 200, w: 100, h: 100 })
1014
+ expect(roundedPageBounds(nestedIds.frameBox)).toMatchObject({ x: 250, y: 450, w: 100, h: 100 })
1015
+ })
1016
+
1017
+ it('resizes the children of nested groups on every pointer move', () => {
1018
+ const boxP = createShapeId('boxP')
1019
+ const boxQ = createShapeId('boxQ')
1020
+ const boxR = createShapeId('boxR')
1021
+ const innerGroup = createShapeId('innerGroup')
1022
+ const outerGroup = createShapeId('outerGroup')
1023
+
1024
+ editor
1025
+ .selectAll()
1026
+ .deleteShapes(editor.getSelectedShapeIds())
1027
+ .createShapes([
1028
+ { id: boxP, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } },
1029
+ { id: boxQ, type: 'geo', x: 200, y: 200, props: { w: 100, h: 100 } },
1030
+ ])
1031
+ .groupShapes([boxP, boxQ], { groupId: innerGroup })
1032
+ .createShapes([{ id: boxR, type: 'geo', x: 400, y: 400, props: { w: 100, h: 100 } }])
1033
+ .groupShapes([innerGroup, boxR], { groupId: outerGroup })
1034
+ .select(outerGroup)
1035
+
1036
+ expect(roundedPageBounds(outerGroup)).toMatchObject({ x: 0, y: 0, w: 500, h: 500 })
1037
+
1038
+ // grow to double the size; each pointer move must update the outer
1039
+ // group, then the inner group, then the innermost children, since each
1040
+ // level's new position depends on its parent's committed transform
1041
+ editor.pointerDownOnHandle('bottom_right').pointerMove(1000, 1000)
1042
+
1043
+ expect(roundedPageBounds(outerGroup)).toMatchObject({ x: 0, y: 0, w: 1000, h: 1000 })
1044
+ expect(roundedPageBounds(innerGroup)).toMatchObject({ x: 0, y: 0, w: 600, h: 600 })
1045
+ expect(roundedPageBounds(boxP)).toMatchObject({ x: 0, y: 0, w: 200, h: 200 })
1046
+ expect(roundedPageBounds(boxQ)).toMatchObject({ x: 400, y: 400, w: 200, h: 200 })
1047
+ expect(roundedPageBounds(boxR)).toMatchObject({ x: 800, y: 800, w: 200, h: 200 })
1048
+
1049
+ // then shrink to half the original size in the same gesture
1050
+ editor.pointerMove(250, 250)
1051
+
1052
+ expect(roundedPageBounds(outerGroup)).toMatchObject({ x: 0, y: 0, w: 250, h: 250 })
1053
+ expect(roundedPageBounds(innerGroup)).toMatchObject({ x: 0, y: 0, w: 150, h: 150 })
1054
+ expect(roundedPageBounds(boxP)).toMatchObject({ x: 0, y: 0, w: 50, h: 50 })
1055
+ expect(roundedPageBounds(boxQ)).toMatchObject({ x: 100, y: 100, w: 50, h: 50 })
1056
+ expect(roundedPageBounds(boxR)).toMatchObject({ x: 200, y: 200, w: 50, h: 50 })
1057
+
1058
+ editor.pointerUp()
1059
+
1060
+ expect(roundedPageBounds(boxP)).toMatchObject({ x: 0, y: 0, w: 50, h: 50 })
1061
+ expect(roundedPageBounds(boxQ)).toMatchObject({ x: 100, y: 100, w: 50, h: 50 })
1062
+ expect(roundedPageBounds(boxR)).toMatchObject({ x: 200, y: 200, w: 50, h: 50 })
1063
+ })
1064
+
1065
+ it('keeps arrows bound to shapes inside a resized group', () => {
1066
+ editor
1067
+ .select(nestedIds.group1)
1068
+ .pointerDownOnHandle('bottom_right')
1069
+ .pointerMove(800, 400)
1070
+ .pointerUp()
1071
+
1072
+ const bindings = getArrowBindings(
1073
+ editor,
1074
+ editor.getShape<TLArrowShape>(nestedIds.arrowPageToGroup)!
1075
+ )
1076
+ expect(bindings.start).toMatchObject({ toId: nestedIds.pageBox })
1077
+ expect(bindings.end).toMatchObject({ toId: nestedIds.groupBoxA })
1078
+
1079
+ const terminals = getArrowTerminalPagePoints(nestedIds.arrowPageToGroup)
1080
+ // the start terminal stays at the center of the unselected page shape
1081
+ expect(terminals.start).toCloselyMatchObject({ x: 50, y: 250 })
1082
+ // the end terminal follows the center of the resized group child
1083
+ expect(terminals.end).toCloselyMatchObject({ x: 300, y: 100 })
1084
+ })
1085
+
1086
+ it('resizes a mixed selection of page, group, and frame shapes', () => {
1087
+ editor
1088
+ .selectAll()
1089
+ // the selection's page bounds are (0,0) -> (500,600); double them
1090
+ .pointerDownOnHandle('bottom_right')
1091
+ .pointerMove(1000, 1200)
1092
+ .pointerUp()
1093
+
1094
+ expect(roundedPageBounds(nestedIds.pageBox)).toMatchObject({ x: 0, y: 400, w: 200, h: 200 })
1095
+ expect(roundedPageBounds(nestedIds.group1)).toMatchObject({ x: 400, y: 0, w: 600, h: 400 })
1096
+ expect(roundedPageBounds(nestedIds.groupBoxA)).toMatchObject({ x: 400, y: 0, w: 200, h: 200 })
1097
+ expect(roundedPageBounds(nestedIds.groupBoxB)).toMatchObject({
1098
+ x: 800,
1099
+ y: 200,
1100
+ w: 200,
1101
+ h: 200,
1102
+ })
1103
+ expect(roundedPageBounds(nestedIds.frame1)).toMatchObject({ x: 400, y: 800, w: 600, h: 400 })
1104
+
1105
+ // the frame's child is not resized; it keeps its position within the frame
1106
+ expect(editor.getShape(nestedIds.frameBox)).toMatchObject({ x: 50, y: 50 })
1107
+ expect(roundedPageBounds(nestedIds.frameBox)).toMatchObject({ x: 450, y: 850, w: 100, h: 100 })
1108
+
1109
+ // the arrow terminals follow the shapes they are bound to
1110
+ const pageToGroup = getArrowTerminalPagePoints(nestedIds.arrowPageToGroup)
1111
+ expect(pageToGroup.start).toCloselyMatchObject({ x: 100, y: 500 })
1112
+ expect(pageToGroup.end).toCloselyMatchObject({ x: 500, y: 100 })
1113
+
1114
+ const groupToFrame = getArrowTerminalPagePoints(nestedIds.arrowGroupToFrame)
1115
+ expect(groupToFrame.start).toCloselyMatchObject({ x: 900, y: 300 })
1116
+ expect(groupToFrame.end).toCloselyMatchObject({ x: 500, y: 900 })
1117
+ })
1118
+ })
1119
+
870
1120
  function getGapAndPointLines() {
871
1121
  const gapLines = editor.snaps
872
1122
  .getIndicators()
@@ -0,0 +1,90 @@
1
+ import { createShapeId } from '@tldraw/editor'
2
+ import { TestEditor } from './TestEditor'
3
+
4
+ let editor: TestEditor
5
+
6
+ beforeEach(() => {
7
+ editor = new TestEditor()
8
+ })
9
+ afterEach(() => {
10
+ editor?.dispose()
11
+ })
12
+
13
+ // A right-click opens the context menu, which is a select-tool surface — every
14
+ // item acts on the current selection. The editor routes every right-click
15
+ // through the select tool before the event reaches the state chart, so the
16
+ // select tool's idle resolves the selection at the click point (selecting the
17
+ // shape under the pointer, or clearing the selection) regardless of which tool
18
+ // was active. This keeps the menu consistent with the click in any tool, and
19
+ // never creates a shape from a creation tool. See #8277 and #8828.
20
+
21
+ // Shape-creation tools: right-click must switch to select and never create a shape.
22
+ const CREATION_TOOLS = ['geo', 'note', 'text', 'line', 'arrow', 'draw', 'frame'] as const
23
+
24
+ // Other non-select tools: right-click must still switch to select so the menu
25
+ // reflects the click rather than a stale selection.
26
+ const OTHER_TOOLS = ['hand', 'eraser', 'laser', 'zoom'] as const
27
+
28
+ describe('right click routes through the select tool', () => {
29
+ it.each(CREATION_TOOLS)('%s switches to the select tool and creates no shape', (tool) => {
30
+ editor.setCurrentTool(tool)
31
+
32
+ editor.rightClick(100, 100)
33
+
34
+ editor.expectToBeIn('select.idle')
35
+ expect(editor.getCurrentPageShapes()).toHaveLength(0)
36
+ })
37
+
38
+ it.each(OTHER_TOOLS)('%s switches to the select tool', (tool) => {
39
+ editor.setCurrentTool(tool)
40
+
41
+ editor.rightClick(100, 100)
42
+
43
+ editor.expectToBeIn('select.idle')
44
+ })
45
+
46
+ it('stays in the select tool when already active', () => {
47
+ editor.setCurrentTool('select')
48
+
49
+ editor.rightClick(100, 100)
50
+
51
+ editor.expectToBeIn('select.idle')
52
+ })
53
+
54
+ it('selects the shape under the pointer so the menu reflects it', () => {
55
+ const id = createShapeId()
56
+ editor.createShape({
57
+ id,
58
+ type: 'geo',
59
+ x: 0,
60
+ y: 0,
61
+ props: { w: 100, h: 100, geo: 'rectangle' },
62
+ })
63
+ editor.selectNone()
64
+
65
+ editor.setCurrentTool('geo')
66
+ editor.rightClick(50, 50)
67
+
68
+ editor.expectToBeIn('select.idle')
69
+ expect(editor.getSelectedShapeIds()).toEqual([id])
70
+ })
71
+
72
+ it('clears a stale selection when right-clicking empty canvas from another tool', () => {
73
+ const id = createShapeId()
74
+ editor.createShape({
75
+ id,
76
+ type: 'geo',
77
+ x: 0,
78
+ y: 0,
79
+ props: { w: 100, h: 100, geo: 'rectangle' },
80
+ })
81
+ editor.select(id)
82
+
83
+ // right-click far from the selected shape, while a non-select tool is active
84
+ editor.setCurrentTool('hand')
85
+ editor.rightClick(500, 500)
86
+
87
+ editor.expectToBeIn('select.idle')
88
+ expect(editor.getSelectedShapeIds()).toEqual([])
89
+ })
90
+ })
@@ -1,3 +1,4 @@
1
+ import { Vec } from '@tldraw/editor'
1
2
  import { TestEditor } from './TestEditor'
2
3
 
3
4
  describe('with rightClickPanning enabled (default)', () => {
@@ -17,6 +18,16 @@ describe('with rightClickPanning enabled (default)', () => {
17
18
  expect(editor.inputs.getIsPanning()).toBe(false)
18
19
  })
19
20
 
21
+ it('does not zoom when momentum panning on release', () => {
22
+ editor.pointerDown(100, 100, { button: 2 })
23
+ editor.pointerMove(200, 200)
24
+ expect(editor.inputs.getIsPanning()).toBe(true)
25
+ editor.inputs.setPointerVelocity(new Vec(1, 1))
26
+ editor.pointerUp(200, 200, { button: 2 }).forceTick()
27
+
28
+ expect(editor.getCamera().z).toBe(1)
29
+ })
30
+
20
31
  it('does not pan on a static right-click', () => {
21
32
  editor.pointerDown(100, 100, { button: 2 })
22
33
  editor.pointerUp(100, 100, { button: 2 })
@@ -1785,7 +1785,7 @@ describe('right clicking', () => {
1785
1785
  expect(editor.getSelectedShapeIds()).toEqual([ids.box1])
1786
1786
  })
1787
1787
 
1788
- it('keeps selection when right-clicking a selection background', () => {
1788
+ it('keeps selection when right-clicking inside the selection bounds', () => {
1789
1789
  editor.createShapes([{ id: ids.box1, type: 'geo' }])
1790
1790
  editor.selectAll()
1791
1791
  editor.pointerMove(30, 30)
@@ -1817,7 +1817,7 @@ describe('right clicking', () => {
1817
1817
  expect(editor.getSelectedShapeIds()).toEqual([ids.box2, ids.box3])
1818
1818
  })
1819
1819
 
1820
- it('keeps selection when right-clicking a selection background', () => {
1820
+ it('clears selection when right-clicking outside the selected arrow', () => {
1821
1821
  editor
1822
1822
  .selectAll()
1823
1823
  .deleteShapes(editor.getSelectedShapeIds())
@@ -2188,6 +2188,19 @@ describe('control pointing', () => {
2188
2188
  // ...and expect menu to be open, but that's a native thing
2189
2189
  })
2190
2190
 
2191
+ it('selects locked shapes on ctrl click when on a mac', () => {
2192
+ tlenv.isDarwin = true
2193
+
2194
+ editor.createShape({ id: ids.box4, type: 'geo', x: 600, isLocked: true })
2195
+
2196
+ expect(editor.getSelectedShapeIds()).toEqual([ids.box1])
2197
+ editor.keyDown('Control')
2198
+ editor.pointerMove(650, 50) // inside of locked box 4
2199
+ editor.pointerDown()
2200
+ editor.pointerUp()
2201
+ expect(editor.getSelectedShapeIds()).toEqual([ids.box4])
2202
+ })
2203
+
2191
2204
  it('selects on ctrl click when on a pc or other device', () => {
2192
2205
  tlenv.isDarwin = false
2193
2206
 
@@ -223,6 +223,138 @@ describe('SpatialIndexManager - step-2 transitive bounds sweep', () => {
223
223
  })
224
224
  })
225
225
 
226
+ describe('SpatialIndexManager - binding-only diffs', () => {
227
+ it('refreshes arrow bounds when bindings are created in a separate step from the shapes', () => {
228
+ // Regression: an arrow's bounds derive from its bindings, but a
229
+ // binding-only transaction contains no shape diff. If the index was
230
+ // queried between createShapes and createBindings (the hover side
231
+ // effect does this on every store change), the arrow stayed indexed
232
+ // at its unbound bounds and parts of the bound body were
233
+ // un-hoverable.
234
+ const boxAId = createShapeId('boxA')
235
+ const boxBId = createShapeId('boxB')
236
+ const arrowId = createShapeId('arrow')
237
+
238
+ editor.createShapes([
239
+ { id: boxAId, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100, fill: 'none' } },
240
+ { id: boxBId, type: 'geo', x: 500, y: 0, props: { w: 100, h: 100, fill: 'none' } },
241
+ // The arrow's own terminals are far from the shapes it will be
242
+ // bound to, as happens when content is created via the API.
243
+ {
244
+ id: arrowId,
245
+ type: 'arrow',
246
+ x: 2000,
247
+ y: 2000,
248
+ props: { kind: 'arc', bend: -50, start: { x: 0, y: 0 }, end: { x: 100, y: 0 } },
249
+ },
250
+ ])
251
+
252
+ const unboundBounds = editor.getShapePageBounds(arrowId)!
253
+
254
+ // Pull the spatial index so the arrow is indexed at its unbound
255
+ // bounds before the bindings exist.
256
+ expect(editor.getShapeIdsInsideBounds(unboundBounds).has(arrowId)).toBe(true)
257
+
258
+ editor.createBindings([
259
+ {
260
+ type: 'arrow',
261
+ fromId: arrowId,
262
+ toId: boxAId,
263
+ props: {
264
+ terminal: 'start',
265
+ isExact: false,
266
+ isPrecise: false,
267
+ normalizedAnchor: { x: 0.5, y: 0.5 },
268
+ },
269
+ },
270
+ {
271
+ type: 'arrow',
272
+ fromId: arrowId,
273
+ toId: boxBId,
274
+ props: {
275
+ terminal: 'end',
276
+ isExact: false,
277
+ isPrecise: false,
278
+ normalizedAnchor: { x: 0.5, y: 0.5 },
279
+ },
280
+ },
281
+ ])
282
+
283
+ const boundBounds = editor.getShapePageBounds(arrowId)!
284
+ expect(boundBounds.equals(unboundBounds)).toBe(false)
285
+
286
+ // The index must reflect the bound bounds...
287
+ expect(editor.getShapeIdsInsideBounds(boundBounds).has(arrowId)).toBe(true)
288
+
289
+ // ...and the arrow must be hit-testable on its bound body.
290
+ const apex = { x: boundBounds.midX, y: boundBounds.minY + 1 }
291
+ expect(editor.getShapeAtPoint(apex, { hitInside: false, margin: 8 })?.id).toBe(arrowId)
292
+ })
293
+
294
+ it('ticks the epoch and refreshes arrow bounds when a binding update moves the arrow', () => {
295
+ const boxAId = createShapeId('boxA')
296
+ const boxBId = createShapeId('boxB')
297
+ const arrowId = createShapeId('arrow')
298
+
299
+ editor.createShapes([
300
+ { id: boxAId, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100, fill: 'none' } },
301
+ { id: boxBId, type: 'geo', x: 500, y: 500, props: { w: 100, h: 100, fill: 'none' } },
302
+ {
303
+ id: arrowId,
304
+ type: 'arrow',
305
+ x: 0,
306
+ y: 0,
307
+ props: { start: { x: 0, y: 0 }, end: { x: 100, y: 0 } },
308
+ },
309
+ ])
310
+ editor.createBindings([
311
+ {
312
+ type: 'arrow',
313
+ fromId: arrowId,
314
+ toId: boxAId,
315
+ props: {
316
+ terminal: 'start',
317
+ isExact: false,
318
+ isPrecise: true,
319
+ normalizedAnchor: { x: 0, y: 0 },
320
+ },
321
+ },
322
+ {
323
+ type: 'arrow',
324
+ fromId: arrowId,
325
+ toId: boxBId,
326
+ props: {
327
+ terminal: 'end',
328
+ isExact: false,
329
+ isPrecise: true,
330
+ normalizedAnchor: { x: 0, y: 0 },
331
+ },
332
+ },
333
+ ])
334
+
335
+ const boundsBefore = editor.getShapePageBounds(arrowId)!
336
+ const tracker = trackSpatialIndexInvalidations(editor, boundsBefore.clone())
337
+
338
+ // Move the end terminal across the target shape with a binding-only
339
+ // update; the arrow record itself is untouched.
340
+ const binding = editor
341
+ .getBindingsFromShape(arrowId, 'arrow')
342
+ .find((b) => b.props.terminal === 'end')!
343
+ editor.updateBinding({
344
+ ...binding,
345
+ props: { ...binding.props, normalizedAnchor: { x: 0.5, y: 1 } },
346
+ })
347
+
348
+ const boundsAfter = editor.getShapePageBounds(arrowId)!
349
+ expect(boundsAfter.equals(boundsBefore)).toBe(false)
350
+ expect(tracker.delta).toBe(1)
351
+
352
+ const probe = probeOnlyInAfter(boundsBefore, boundsAfter)
353
+ expect(editor.getShapeIdsInsideBounds(probe).has(arrowId)).toBe(true)
354
+ tracker.stop()
355
+ })
356
+ })
357
+
226
358
  describe('SpatialIndexManager - basic queries', () => {
227
359
  it('returns shapes inside the queried bounds', () => {
228
360
  const insideId = createShapeId('inside')
@@ -783,9 +783,6 @@ describe('snapping with multiple shapes', () => {
783
783
  })
784
784
 
785
785
  describe('Snap-between behavior', () => {
786
- beforeEach(() => {
787
- editor?.dispose()
788
- })
789
786
  it('snaps a shape horizontally between two others', () => {
790
787
  // ┌─────┐ ┌─────┐
791
788
  // │ │ │ │
@@ -1160,9 +1157,6 @@ describe('Snap-between behavior', () => {
1160
1157
  })
1161
1158
 
1162
1159
  describe('Snap-next-to behavior', () => {
1163
- beforeEach(() => {
1164
- editor?.dispose()
1165
- })
1166
1160
  it('snaps a shape to the left of two others, matching the gap size', () => {
1167
1161
  // ┌───┐
1168
1162
  // │ X │