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,3 +1,4 @@
1
+ import { Vec } from '@tldraw/editor'
1
2
  import { vi } from 'vitest'
2
3
  import { HandTool } from '../lib/tools/HandTool/HandTool'
3
4
  import { TestEditor } from './TestEditor'
@@ -18,6 +19,43 @@ afterEach(() => {
18
19
  vi.useFakeTimers()
19
20
 
20
21
  describe(HandTool, () => {
22
+ it('Uses one-finger zoom when coarse pointer double-taps, holds, and drags', () => {
23
+ editor.setCurrentTool('hand')
24
+ editor.updateInstanceState({ isCoarsePointer: true })
25
+
26
+ // Double-tap, holding the second press down.
27
+ editor.pointerDown(0, 0).pointerUp(0, 0)
28
+ editor.pointerDown(0, 0)
29
+ // On the second press we're still just pointing — the zoom hasn't started yet.
30
+ editor.expectToBeIn('hand.pointing')
31
+
32
+ // One-finger zoom begins when the multi-click timer settles with the pointer
33
+ // still held down (the `settle-down` phase).
34
+ vi.advanceTimersByTime(editor.options.multiClickDurationMs)
35
+ editor.expectToBeIn('hand.one_finger_zooming')
36
+
37
+ const before = editor.getCamera().z
38
+ editor.pointerMove(0, 100).pointerUp(0, 100).forceTick()
39
+ expect(editor.getCamera().z).toBeGreaterThan(before)
40
+ })
41
+
42
+ it('Zooms in (not one-finger zoom) when a coarse pointer double-taps and releases', () => {
43
+ editor.setCurrentTool('hand')
44
+ editor.updateInstanceState({ isCoarsePointer: true })
45
+
46
+ // Double-tap and release the second press before the timer settles.
47
+ editor.pointerDown(0, 0).pointerUp(0, 0)
48
+ editor.pointerDown(0, 0).pointerUp(0, 0)
49
+ // Releasing means we never enter the one-finger zoom drag state.
50
+ editor.expectToBeIn('hand.idle')
51
+
52
+ // The `settle-up` phase zooms in by one step.
53
+ const before = editor.getZoomLevel()
54
+ vi.advanceTimersByTime(editor.options.multiClickDurationMs)
55
+ vi.advanceTimersByTime(600) // let the zoom animation finish
56
+ expect(editor.getZoomLevel()).toBeGreaterThan(before)
57
+ })
58
+
21
59
  it('Double taps to zoom in', () => {
22
60
  editor.setCurrentTool('hand')
23
61
  expect(editor.getZoomLevel()).toBe(1)
@@ -129,6 +167,16 @@ describe('When in the dragging state', () => {
129
167
  editor.pointerUp()
130
168
  })
131
169
 
170
+ it('Does not zoom when momentum panning on release', () => {
171
+ editor.setCurrentTool('hand')
172
+ editor.pointerDown(0, 0)
173
+ editor.pointerMove(50, 50)
174
+ editor.inputs.setPointerVelocity(new Vec(1, 1))
175
+ editor.pointerUp().forceTick()
176
+
177
+ expect(editor.getCamera().z).toBe(1)
178
+ })
179
+
132
180
  // it('Moves the camera with inertia on pointer up', () => {
133
181
  // Can't test this—x is set to Inifnity in tests
134
182
  // editor.setCurrentTool('hand')
@@ -160,4 +208,49 @@ describe('When in the dragging state', () => {
160
208
  editor.cancel()
161
209
  editor.expectToBeIn('hand.idle')
162
210
  })
211
+
212
+ it('Returns to the idle state on interrupt', () => {
213
+ editor.setCurrentTool('hand')
214
+ editor.pointerDown(50, 50)
215
+ editor.pointerMove(100, 100)
216
+ editor.expectToBeIn('hand.dragging')
217
+ editor.interrupt()
218
+ editor.expectToBeIn('hand.idle')
219
+ })
220
+
221
+ it('Ends the pan when a second touch starts, without jumping the camera', () => {
222
+ editor.setCurrentTool('hand')
223
+ editor.pointerDown(50, 50)
224
+ editor.pointerMove(100, 100)
225
+ editor.expectToBeIn('hand.dragging')
226
+ expect(editor.getCamera()).toMatchObject({ x: 50, y: 50 })
227
+
228
+ // A second finger lands far from the first, resetting the input origin.
229
+ editor.pointerDown(200, 0)
230
+ editor.expectToBeIn('hand.idle')
231
+
232
+ // Movement before pinch_start arrives no longer pans the camera.
233
+ editor.pointerMove(210, 10)
234
+ expect(editor.getCamera()).toMatchObject({ x: 50, y: 50 })
235
+ })
236
+
237
+ it('Does not resume the pan after a pinch ends', () => {
238
+ editor.setCurrentTool('hand')
239
+ editor.pointerDown(50, 50)
240
+ editor.pointerMove(100, 100)
241
+ editor.expectToBeIn('hand.dragging')
242
+
243
+ // A second finger lands and starts a pinch.
244
+ editor.pointerDown(200, 0)
245
+ editor.expectToBeIn('hand.idle')
246
+ editor.pinchStart(150, 50, 1, 0, 0, 0)
247
+ editor.pinchTo(150, 50, 2, 0, 0, 0)
248
+ editor.pinchEnd(150, 50, 2, 0, 0, 0)
249
+ editor.expectToBeIn('hand.idle')
250
+
251
+ // Moving the remaining finger does not pan the camera.
252
+ const { x, y } = editor.getCamera()
253
+ editor.pointerMove(120, 120)
254
+ expect(editor.getCamera()).toMatchObject({ x, y })
255
+ })
163
256
  })
@@ -1,6 +1,44 @@
1
1
  import { createShapeId, Ellipse2d, Rectangle2d, TLImageShape } from '@tldraw/editor'
2
2
  import { TestEditor } from './TestEditor'
3
3
 
4
+ const ids = {
5
+ image: createShapeId('image'),
6
+ box: createShapeId('box'),
7
+ }
8
+
9
+ function createCroppedImageWithSibling() {
10
+ editor.createShapes([
11
+ {
12
+ id: ids.image,
13
+ type: 'image',
14
+ x: 0,
15
+ y: 0,
16
+ props: {
17
+ w: 100,
18
+ h: 100,
19
+ assetId: null,
20
+ playing: true,
21
+ url: '',
22
+ // crop to the top-left quadrant of the source image
23
+ crop: {
24
+ topLeft: { x: 0, y: 0 },
25
+ bottomRight: { x: 0.5, y: 0.5 },
26
+ },
27
+ flipX: false,
28
+ flipY: false,
29
+ altText: '',
30
+ },
31
+ },
32
+ {
33
+ id: ids.box,
34
+ type: 'geo',
35
+ x: 300,
36
+ y: 0,
37
+ props: { w: 100, h: 100 },
38
+ },
39
+ ])
40
+ }
41
+
4
42
  let editor: TestEditor
5
43
 
6
44
  beforeEach(() => {
@@ -153,4 +191,54 @@ describe('ImageShapeUtil', () => {
153
191
  expect(geometry.isFilled).toBe(true)
154
192
  })
155
193
  })
194
+
195
+ describe('flipping a cropped image when its group is resized', () => {
196
+ it('mirrors the crop when a grouped cropped image is flipped by drag resize', () => {
197
+ createCroppedImageWithSibling()
198
+ editor.select(ids.image, ids.box)
199
+ editor.groupShapes(editor.getSelectedShapeIds())
200
+
201
+ // Flip the group horizontally by dragging a corner handle past the
202
+ // opposite edge. The drag scale is not exactly -1, which is the case
203
+ // that previously left the crop un-mirrored.
204
+ editor.resizeSelection({ scaleX: -2, scaleY: 1 }, 'top_left')
205
+
206
+ const image = editor.getShape<TLImageShape>(ids.image)!
207
+ expect(image.props.flipX).toBe(true)
208
+ // The crop should be mirrored horizontally and unchanged vertically.
209
+ expect(image.props.crop).toMatchObject({
210
+ topLeft: { x: 0.5, y: 0 },
211
+ bottomRight: { x: 1, y: 0.5 },
212
+ })
213
+ })
214
+
215
+ it('mirrors the crop vertically when a grouped cropped image is flipped by drag resize', () => {
216
+ createCroppedImageWithSibling()
217
+ editor.select(ids.image, ids.box)
218
+ editor.groupShapes(editor.getSelectedShapeIds())
219
+
220
+ editor.resizeSelection({ scaleX: 1, scaleY: -2 }, 'top_left')
221
+
222
+ const image = editor.getShape<TLImageShape>(ids.image)!
223
+ expect(image.props.flipY).toBe(true)
224
+ expect(image.props.crop).toMatchObject({
225
+ topLeft: { x: 0, y: 0.5 },
226
+ bottomRight: { x: 0.5, y: 1 },
227
+ })
228
+ })
229
+
230
+ it('matches the flipShapes command result for a grouped cropped image', () => {
231
+ createCroppedImageWithSibling()
232
+ editor.select(ids.image, ids.box)
233
+ editor.groupShapes(editor.getSelectedShapeIds())
234
+ editor.flipShapes(editor.getSelectedShapeIds(), 'horizontal')
235
+
236
+ const image = editor.getShape<TLImageShape>(ids.image)!
237
+ expect(image.props.flipX).toBe(true)
238
+ expect(image.props.crop).toMatchObject({
239
+ topLeft: { x: 0.5, y: 0 },
240
+ bottomRight: { x: 1, y: 0.5 },
241
+ })
242
+ })
243
+ })
156
244
  })
@@ -1,4 +1,11 @@
1
- import { IndexKey, ShapeUtil, TLFrameShape, createShapeId, toRichText } from '@tldraw/editor'
1
+ import {
2
+ IndexKey,
3
+ ShapeUtil,
4
+ TLArrowShape,
5
+ TLFrameShape,
6
+ createShapeId,
7
+ toRichText,
8
+ } from '@tldraw/editor'
2
9
  import { vi } from 'vitest'
3
10
  import { defaultHandleExternalTldrawContent } from '../lib/defaultExternalContentHandlers'
4
11
  import { defaultOverlayUtils } from '../lib/defaultOverlayUtils'
@@ -115,6 +122,44 @@ describe.skip('Edit on type', () => {
115
122
  })
116
123
  })
117
124
 
125
+ describe('TLSelectTool.PointingShape when the shape is deleted mid-click', () => {
126
+ // Reproduces https://github.com/tldraw/tldraw/issues/8558: a remote user,
127
+ // undo, or other actor can delete the pointed-at shape between pointer down
128
+ // and pointer up. The tool should bail to idle instead of crashing.
129
+
130
+ it('does not crash on pointer up when an already-selected shape is deleted', () => {
131
+ // pre-selecting the shape means didSelectOnEnter is false, so pointer up
132
+ // runs the selection logic that dereferences the (now deleted) shape
133
+ editor.select(ids.box1)
134
+ const shape = editor.getShape(ids.box1)!
135
+
136
+ editor.pointerDown(shape.x + 10, shape.y + 10, { target: 'shape', shape })
137
+ editor.expectToBeIn('select.pointing_shape')
138
+
139
+ editor.deleteShapes([ids.box1])
140
+
141
+ expect(() => editor.pointerUp(shape.x + 10, shape.y + 10)).not.toThrow()
142
+ editor.expectToBeIn('select.idle')
143
+ })
144
+
145
+ it('does not crash on pointer up when a ctrl-clicked shape is deleted', () => {
146
+ // ctrl/accel on enter also leaves didSelectOnEnter false
147
+ const shape = editor.getShape(ids.box1)!
148
+
149
+ editor.pointerDown(shape.x + 10, shape.y + 10, {
150
+ target: 'shape',
151
+ shape,
152
+ accelKey: true,
153
+ })
154
+ editor.expectToBeIn('select.pointing_shape')
155
+
156
+ editor.deleteShapes([ids.box1])
157
+
158
+ expect(() => editor.pointerUp(shape.x + 10, shape.y + 10)).not.toThrow()
159
+ editor.expectToBeIn('select.idle')
160
+ })
161
+ })
162
+
118
163
  describe('TLSelectTool.Translating', () => {
119
164
  it('Enters from pointing and exits to idle', () => {
120
165
  const shape = editor.getShape(ids.box1)
@@ -510,6 +555,34 @@ describe('When pressing enter on a selected shape', () => {
510
555
  })
511
556
  })
512
557
 
558
+ describe('When undo/redo restores an invalid editing shape', () => {
559
+ // Regression: https://github.com/tldraw/tldraw/issues/9113
560
+ // Setting the editing shape is not recorded in history, but clearing it on delete is.
561
+ // When create + edit + delete of the same shape collapse into a single history entry,
562
+ // the shape's add/remove cancel out while the editingShapeId update survives. Undoing
563
+ // then restores editingShapeId pointing at a shape that no longer exists, which used to
564
+ // crash with "Entered editing state without an editing shape". The editor must never
565
+ // enter the editing state without a valid editing shape.
566
+ it('does not crash when undo restores editingShapeId for a deleted shape', () => {
567
+ const id = createShapeId()
568
+ editor.markHistoryStoppingPoint('start')
569
+ editor.createShape({ id, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } })
570
+ editor.setEditingShape(id)
571
+ editor.deleteShapes([id])
572
+
573
+ expect(() => editor.undo()).not.toThrow()
574
+
575
+ editor.expectToBeIn('select.idle')
576
+ expect(editor.getEditingShapeId()).toBe(null)
577
+ expect(editor.getShape(id)).toBeUndefined()
578
+
579
+ // Redoing back through the same history entry must also stay safe.
580
+ expect(() => editor.redo()).not.toThrow()
581
+ editor.expectToBeIn('select.idle')
582
+ expect(editor.getEditingShapeId()).toBe(null)
583
+ })
584
+ })
585
+
513
586
  // it('selects the child of a group', () => {
514
587
  // const id1 = createShapeId()
515
588
  // const id2 = createShapeId()
@@ -643,6 +716,34 @@ describe('When double clicking a selection handle that registers as a canvas eve
643
716
  expect(spy).toHaveBeenCalledTimes(1)
644
717
  expect(spy.mock.calls[0][1]).toMatchObject({ target: 'selection', handle: 'bottom_right' })
645
718
  })
719
+
720
+ it('Routes a canvas-targeted double-click on an arrow handle to onDoubleClickHandle', () => {
721
+ const id = createShapeId()
722
+ overlayEditor
723
+ .createShapes([
724
+ {
725
+ id,
726
+ type: 'arrow',
727
+ x: 100,
728
+ y: 100,
729
+ props: { start: { x: 0, y: 0 }, end: { x: 100, y: 100 } },
730
+ },
731
+ ])
732
+ .select(id)
733
+
734
+ expect(overlayEditor.getShape<TLArrowShape>(id)!.props.arrowheadEnd).toBe('arrow')
735
+
736
+ // Double-click on the end handle without specifying target — defaults to
737
+ // target: 'canvas', the payload a real DOM double-click produces when the
738
+ // press lands on the handle overlay. This should toggle the arrowhead.
739
+ overlayEditor.doubleClick(200, 200)
740
+
741
+ expect(overlayEditor.getShape<TLArrowShape>(id)!.props.arrowheadEnd).toBe('none')
742
+
743
+ overlayEditor.doubleClick(200, 200)
744
+
745
+ expect(overlayEditor.getShape<TLArrowShape>(id)!.props.arrowheadEnd).toBe('arrow')
746
+ })
646
747
  })
647
748
 
648
749
  describe('When editing shapes', () => {
@@ -19,7 +19,8 @@ exports[`Draws a bunch > draw shape 1`] = `
19
19
  "scaleY": 1,
20
20
  "segments": [
21
21
  {
22
- "path": "AAAAAAAAAAAAAAA/ADwAAAAAAEIAAAAAAEYAvAAAgEgAwgAAgEkAxgAAAEwAyQAAgEsAyQAAgEqAygAAgEsAzAAAgEqAzAAAAEaAygAAAESAygAAAEUAzAAAADwAygAAADwAxgAAAAAAyAAAADwAxwAAALwAxgAAAMAAxQAAAMIAxAAAAMUAwAAAAMYAvAAAAMYAAAAAAMgAAAAAgMgARAAAgMgARwAAgMiASAAAAMgASgAAAMQASgAAAMKASgAAALwASwAAAABATAAAADxATQAAAEYATQAAAEdATAAAAEhATAAAgEnATAAAgEgATQAAAEiATAAAAEfATAAAAEJATAAAADwATAAAAACATAAAAMUATAAAAMoASQAAAMwASAAAwM+ASAAAgMkAPAAAgMkAAAAAAMkAAAAAgMgAAAAAgMgAwAAAAMYAwgAAAMAAxgAAAAAAxgAAAESAyAAAAEqAyQAAAEuAywAAwEwAzAAAgE1AzAAAQE5AzQAAgE5AzgAAwExAzQAAwEyAzQAAgEsAzQAAAEoAzQAAgErAzQAAgEgAzQAAAEXAzAAAAEKAywAAADyAyQAAAAAAyAAAAMAAxgAAAMUAwgAAAMUAvAAAAMUAvAAAAMgAPAAAAMgARgAAAMYASQAAAMYASgAAAMeATAAAAMdATQAAAMVATgAAAMQATwAAAMJATwAAAABATwAAAADATgAAADxATgAAAEfATQAAAEgATAAAAEiASQAAAEmASgAAgEgASAAAAEgARQAAAEgAQAAAAEYAAAAAAEgAvAAAgEgAxQAAgEgAyQAAAEeAyQAAAEaAygAAAEZAzAAAAEVAzAAAAEJAzAAAAECAywAAADyAyAAAAAAAxgAAAAAAxwAAAMAAxAAAAMIAxAAAAMAAAAAAAMIAAAAAAMIAPAAAAMAAQAAAAMAARwAAAMAASAAAALyASQAAALyASQAAAAAASQAAAEKASgAAAEYASgAAAEaASAAAAEiASAAAAEUARAAAAEYAQgAAAEgAQAAAAEYAAAAAAEYAwgAAAEUAyQAAAEUAygAAAEAAyQAAAEKAygAAAEAAygAAAEAAyQAAADwAyQAAADwAxwAAADwAxgAAAAAAwgAAAAAAwAAAAAAAvAAAAAAAQgAAAAAARQAAAAAASAAAAACASAAAAAAARwAAAAAARQAAAAAARQAAADwARQAAAEIARQAAAEAAQgAAAEIAPAAAAEIAPAAAAEQAAAAAAEUAwgAAAEQAxAAAAEIAwgAAAEIAxAAAAEIAwgAAAEAAwAAAAEAAwAAAAEAAAAAAAAAAAAAAADwAAAAAAAAAPAAAADwAPAAAADwAQAAAAAAAQAAAADwAPAAAAAAAPAAAADwAPAAAADwAAAAAADwAAAAAADwAxgAAAECAyAAAAECAyAAAAEIAyAAAAEAAxgAAAEAAxwAAAEAAxgAAAEQAxgAAAEIAxAAAAEQAvAAAAEUAAAAAAEUAAAAAAEcAPAAAgEoASAAAAEsASgAAAEmASQAAAEqASwAAAEmASwAAAEhATAAAAEQASwAAAEBATQAAAECATgAAAAAATQAAAMQATAAAAMlATAAAAMyATAAAwMxATAAAAMwASQAAAMsARQAAAMwARQAAgMoAQAAAAMkAAAAAAMgAxAAAAMaAyAAAALwAywAAAADAzgAAAEkg0AAAwE1g0AAAwE8g0QAAQE9g0AAAYFDA0AAAoFCg0AAA4FHg0QAAwFHg0QAAgE3AzQAAgE0AzgAAgE2AzgAAAEqAywAAgEgAzAAAAEiAzAAAADwAxwAAAMAAxgAAgMgAxAAAgMwAwAAAgM0AAAAAQM0AQgAAANCASgAAYNDATAAAAM8ATQAAAM7ATAAAgMxATAAAQMzATAAAgMmASwAAAMaASQAAAMAASAAAAAAARQAAADwAQgAAAEIAPAAAAEYAPAAAAEQAAAAAAEIAvAAAAEUAvAAAAEAAvAAAAEAAvAAAADwAAAAAADwAvAAAAAAAAAAAADwAAAAA",
22
+ "dim": 2,
23
+ "path": "AAAAAAAAAAAAPAAAAEIAAABGALyASADCgEkAxgBMAMmASwDJgEqAyoBLAMyASoDMAEaAygBEgMoARQDMADwAygA8AMYAAADIADwAxwC8AMYAwADFAMIAxADFAMAAxgC8AMYAAADIAACAyABEgMgAR4DIgEgAyABKAMQASgDCgEoAvABLAABATAA8QE0ARgBNAEdATABIQEyAScBMgEgATQBIgEwAR8BMAEJATAA8AEwAAIBMAMUATADKAEkAzABIwM+ASIDJADyAyQAAAMkAAIDIAACAyADAAMYAwgDAAMYAAADGAESAyABKgMkAS4DLwEwAzIBNQMxATkDNgE5AzsBMQM3ATIDNgEsAzQBKAM2ASsDNgEgAzQBFwMwAQoDLADyAyQAAAMgAwADGAMUAwgDFALwAxQC8AMgAPADIAEYAxgBJAMYASgDHgEwAx0BNAMVATgDEAE8AwkBPAABATwAAwE4APEBOAEfATQBIAEwASIBJAEmASoBIAEgASABFAEgAQABGAAAASAC8gEgAxYBIAMkAR4DJAEaAygBGQMwARUDMAEJAzABAgMsAPIDIAAAAxgAAAMcAwADEAMIAxADAAAAAwgAAAMIAPADAAEAAwABHAMAASAC8gEkAvIBJAAAASQBCgEoARgBKAEaASABIgEgARQBEAEYAQgBIAEAARgAAAEYAwgBFAMkARQDKAEAAyQBCgMoAQADKAEAAyQA8AMkAPADHADwAxgAAAMIAAADAAAAAvAAAAEIAAABFAAAASAAAgEgAAABHAAAARQAAAEUAPABFAEIARQBAAEIAQgA8AEIAPABEAAAARQDCAEQAxABCAMIAQgDEAEIAwgBAAMAAQADAAEAAAAAAAAAAPAAAAAAAPAA8ADwAPABAAAAAQAA8ADwAAAA8ADwAPAA8AAAAPAAAADwAxgBAgMgAQIDIAEIAyABAAMYAQADHAEAAxgBEAMYAQgDEAEQAvABFAAAARQAAAEcAPIBKAEgASwBKAEmASQBKgEsASYBLAEhATABEAEsAQEBNAECATgAAAE0AxABMAMlATADMgEzAzEBMAMwASQDLAEUAzABFgMoAQADJAAAAyADEAMaAyAC8AMsAAMDOAEkg0MBNYNDATyDRQE9g0GBQwNCgUKDQ4FHg0cBR4NGATcDNgE0AzoBNgM4ASoDLgEgAzABIgMwAPADHAMAAxoDIAMSAzADAgM0AAEDNAEIA0IBKYNDATADPAE0AzsBMgMxATEDMwEyAyYBLAMaASQDAAEgAAABFADwAQgBCADwARgA8AEQAAABCALwARQC8AEAAvABAALwAPAAAADwAvAAAAAAAPAAA",
23
24
  "type": "free",
24
25
  },
25
26
  ],
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  TLNoteShape,
3
3
  UserRecordType,
4
+ atom,
4
5
  computed,
5
6
  createShapeId,
6
7
  createUserId,
@@ -25,7 +26,7 @@ const ids = {
25
26
 
26
27
  describe('getAttributionDisplayName', () => {
27
28
  it('returns current user name for own userId', () => {
28
- const userId = editor.user.getId()
29
+ const userId = editor.user.getExternalId()
29
30
  const name = editor.getAttributionDisplayName(userId)
30
31
  expect(name).toBe(editor.user.getName())
31
32
  })
@@ -40,8 +41,8 @@ describe('getAttributionDisplayName', () => {
40
41
  })
41
42
  })
42
43
 
43
- describe('note shape textFirstEditedBy', () => {
44
- it('sets textFirstEditedBy as user ID string when richText changes', () => {
44
+ describe('note shape textLastEditedBy', () => {
45
+ it('sets textLastEditedBy as user ID string when richText changes', () => {
45
46
  editor.createShapes([
46
47
  {
47
48
  id: ids.note1,
@@ -51,7 +52,7 @@ describe('note shape textFirstEditedBy', () => {
51
52
  },
52
53
  ])
53
54
 
54
- const userId = editor.user.getId()
55
+ const userId = editor.user.getExternalId()
55
56
 
56
57
  editor.updateShape<TLNoteShape>({
57
58
  id: ids.note1,
@@ -60,41 +61,51 @@ describe('note shape textFirstEditedBy', () => {
60
61
  })
61
62
 
62
63
  const note = editor.getShape<TLNoteShape>(ids.note1)!
63
- expect(note.props.textFirstEditedBy).toBe(userId)
64
+ expect(note.props.textLastEditedBy).toBe(userId)
64
65
  })
65
66
 
66
- it('does not overwrite textFirstEditedBy on subsequent edits', () => {
67
- editor.createShapes([
67
+ it('updates textLastEditedBy to the most recent editor', () => {
68
+ const alice = UserRecordType.create({ id: createUserId('user-1'), name: 'Alice' })
69
+ const bob = UserRecordType.create({ id: createUserId('user-2'), name: 'Bob' })
70
+ const currentUser = atom('currentUser', alice)
71
+ const customEditor = new TestEditor(
72
+ {},
68
73
  {
69
- id: ids.note1,
70
- type: 'note',
71
- x: 0,
72
- y: 0,
73
- },
74
- ])
74
+ users: {
75
+ currentUser,
76
+ resolve: (userId) =>
77
+ computed('resolve-' + userId, () => {
78
+ if (userId === 'user-1') return alice
79
+ if (userId === 'user-2') return bob
80
+ return null
81
+ }),
82
+ },
83
+ }
84
+ )
75
85
 
76
- const userId = editor.user.getId()
86
+ customEditor.createShapes([{ id: ids.note1, type: 'note', x: 0, y: 0 }])
77
87
 
78
- editor.updateShape<TLNoteShape>({
88
+ // Alice edits the note first
89
+ customEditor.updateShape<TLNoteShape>({
79
90
  id: ids.note1,
80
91
  type: 'note',
81
92
  props: { richText: toRichText('Hello') },
82
93
  })
94
+ expect(customEditor.getShape<TLNoteShape>(ids.note1)!.props.textLastEditedBy).toBe('user-1')
83
95
 
84
- const note1 = editor.getShape<TLNoteShape>(ids.note1)!
85
- expect(note1.props.textFirstEditedBy).toBe(userId)
86
-
87
- editor.updateShape<TLNoteShape>({
96
+ // Bob edits the note next — the latest editor wins
97
+ currentUser.set(bob)
98
+ customEditor.updateShape<TLNoteShape>({
88
99
  id: ids.note1,
89
100
  type: 'note',
90
101
  props: { richText: toRichText('Hello world') },
91
102
  })
103
+ expect(customEditor.getShape<TLNoteShape>(ids.note1)!.props.textLastEditedBy).toBe('user-2')
92
104
 
93
- const note2 = editor.getShape<TLNoteShape>(ids.note1)!
94
- expect(note2.props.textFirstEditedBy).toBe(userId)
105
+ customEditor.dispose()
95
106
  })
96
107
 
97
- it('resets textFirstEditedBy to null when text is deleted', () => {
108
+ it('resets textLastEditedBy to null when text is deleted', () => {
98
109
  editor.createShapes([
99
110
  {
100
111
  id: ids.note1,
@@ -111,7 +122,7 @@ describe('note shape textFirstEditedBy', () => {
111
122
  })
112
123
 
113
124
  const note1 = editor.getShape<TLNoteShape>(ids.note1)!
114
- expect(note1.props.textFirstEditedBy).not.toBeNull()
125
+ expect(note1.props.textLastEditedBy).not.toBeNull()
115
126
 
116
127
  editor.updateShape<TLNoteShape>({
117
128
  id: ids.note1,
@@ -120,10 +131,10 @@ describe('note shape textFirstEditedBy', () => {
120
131
  })
121
132
 
122
133
  const note2 = editor.getShape<TLNoteShape>(ids.note1)!
123
- expect(note2.props.textFirstEditedBy).toBeNull()
134
+ expect(note2.props.textLastEditedBy).toBeNull()
124
135
  })
125
136
 
126
- it('does not set textFirstEditedBy when only position changes', () => {
137
+ it('does not set textLastEditedBy when only position changes', () => {
127
138
  editor.createShapes([
128
139
  {
129
140
  id: ids.note1,
@@ -136,10 +147,10 @@ describe('note shape textFirstEditedBy', () => {
136
147
  editor.updateShape({ id: ids.note1, type: 'note', x: 100 })
137
148
 
138
149
  const note = editor.getShape<TLNoteShape>(ids.note1)!
139
- expect(note.props.textFirstEditedBy).toBeNull()
150
+ expect(note.props.textLastEditedBy).toBeNull()
140
151
  })
141
152
 
142
- it('does not set textFirstEditedBy when color changes', () => {
153
+ it('does not set textLastEditedBy when color changes', () => {
143
154
  editor.createShapes([
144
155
  {
145
156
  id: ids.note1,
@@ -156,7 +167,7 @@ describe('note shape textFirstEditedBy', () => {
156
167
  })
157
168
 
158
169
  const note = editor.getShape<TLNoteShape>(ids.note1)!
159
- expect(note.props.textFirstEditedBy).toBeNull()
170
+ expect(note.props.textLastEditedBy).toBeNull()
160
171
  })
161
172
  })
162
173
 
@@ -186,7 +197,7 @@ describe('TLUserStore', () => {
186
197
  })
187
198
 
188
199
  it('falls back to default user store when none provided', () => {
189
- const userId = editor.user.getId()
200
+ const userId = editor.user.getExternalId()
190
201
  expect(editor.getAttributionUserId()).toBe(userId)
191
202
  expect(editor.getAttributionDisplayName(userId)).toBe(editor.user.getName())
192
203
  })