tldraw 5.4.0-next.ef99ab47e5ce → 5.4.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 (316) hide show
  1. package/DOCS.md +2826 -3226
  2. package/RELEASE_NOTES.md +190 -2
  3. package/dist-cjs/index.d.ts +264 -275
  4. package/dist-cjs/index.js +1 -1
  5. package/dist-cjs/lib/defaultExternalContentHandlers.js +32 -16
  6. package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
  7. package/dist-cjs/lib/shapes/arrow/curved-arrow.js +2 -2
  8. package/dist-cjs/lib/shapes/arrow/curved-arrow.js.map +2 -2
  9. package/dist-cjs/lib/shapes/arrow/elbow/getElbowArrowInfo.js +2 -2
  10. package/dist-cjs/lib/shapes/arrow/elbow/getElbowArrowInfo.js.map +2 -2
  11. package/dist-cjs/lib/shapes/arrow/getArrowInfo.js +1 -1
  12. package/dist-cjs/lib/shapes/arrow/getArrowInfo.js.map +2 -2
  13. package/dist-cjs/lib/shapes/arrow/straight-arrow.js +2 -2
  14. package/dist-cjs/lib/shapes/arrow/straight-arrow.js.map +2 -2
  15. package/dist-cjs/lib/shapes/arrow/toolStates/Idle.js +2 -3
  16. package/dist-cjs/lib/shapes/arrow/toolStates/Idle.js.map +2 -2
  17. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js +7 -5
  18. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js.map +2 -2
  19. package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js +8 -6
  20. package/dist-cjs/lib/shapes/draw/toolStates/Drawing.js.map +2 -2
  21. package/dist-cjs/lib/shapes/geo/getGeoShapePath.js +3 -1
  22. package/dist-cjs/lib/shapes/geo/getGeoShapePath.js.map +2 -2
  23. package/dist-cjs/lib/shapes/geo/toolStates/Idle.js +2 -3
  24. package/dist-cjs/lib/shapes/geo/toolStates/Idle.js.map +2 -2
  25. package/dist-cjs/lib/shapes/geo/toolStates/Pointing.js +1 -1
  26. package/dist-cjs/lib/shapes/geo/toolStates/Pointing.js.map +2 -2
  27. package/dist-cjs/lib/shapes/line/toolStates/Pointing.js +2 -6
  28. package/dist-cjs/lib/shapes/line/toolStates/Pointing.js.map +2 -2
  29. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +4 -4
  30. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  31. package/dist-cjs/lib/shapes/note/noteHelpers.js +6 -5
  32. package/dist-cjs/lib/shapes/note/noteHelpers.js.map +2 -2
  33. package/dist-cjs/lib/shapes/note/toolStates/Pointing.js +1 -1
  34. package/dist-cjs/lib/shapes/note/toolStates/Pointing.js.map +2 -2
  35. package/dist-cjs/lib/shapes/shared/crop.js +1 -1
  36. package/dist-cjs/lib/shapes/shared/crop.js.map +2 -2
  37. package/dist-cjs/lib/shapes/text/toolStates/Idle.js +1 -1
  38. package/dist-cjs/lib/shapes/text/toolStates/Idle.js.map +2 -2
  39. package/dist-cjs/lib/shapes/text/toolStates/Pointing.js +1 -0
  40. package/dist-cjs/lib/shapes/text/toolStates/Pointing.js.map +2 -2
  41. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js +3 -2
  42. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js.map +2 -2
  43. package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js +2 -0
  44. package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js.map +2 -2
  45. package/dist-cjs/lib/tools/HandTool/childStates/OneFingerZooming.js +6 -0
  46. package/dist-cjs/lib/tools/HandTool/childStates/OneFingerZooming.js.map +2 -2
  47. package/dist-cjs/lib/tools/SelectTool/SelectTool.js +3 -0
  48. package/dist-cjs/lib/tools/SelectTool/SelectTool.js.map +2 -2
  49. package/dist-cjs/lib/tools/SelectTool/childStates/Brushing.js +1 -0
  50. package/dist-cjs/lib/tools/SelectTool/childStates/Brushing.js.map +2 -2
  51. package/dist-cjs/lib/tools/SelectTool/childStates/Crop/Crop.js +1 -0
  52. package/dist-cjs/lib/tools/SelectTool/childStates/Crop/Crop.js.map +2 -2
  53. package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js +1 -3
  54. package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js.map +2 -2
  55. package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js +6 -3
  56. package/dist-cjs/lib/tools/SelectTool/childStates/Idle.js.map +2 -2
  57. package/dist-cjs/lib/tools/SelectTool/childStates/PointingArrowLabel.js +1 -1
  58. package/dist-cjs/lib/tools/SelectTool/childStates/PointingArrowLabel.js.map +2 -2
  59. package/dist-cjs/lib/tools/SelectTool/childStates/PointingCanvas.js +3 -0
  60. package/dist-cjs/lib/tools/SelectTool/childStates/PointingCanvas.js.map +2 -2
  61. package/dist-cjs/lib/tools/SelectTool/childStates/PointingHandle.js +1 -0
  62. package/dist-cjs/lib/tools/SelectTool/childStates/PointingHandle.js.map +2 -2
  63. package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js +1 -1
  64. package/dist-cjs/lib/tools/SelectTool/childStates/PointingShape.js.map +2 -2
  65. package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js +22 -30
  66. package/dist-cjs/lib/tools/SelectTool/childStates/Resizing.js.map +2 -2
  67. package/dist-cjs/lib/tools/SelectTool/childStates/ScribbleBrushing.js +1 -3
  68. package/dist-cjs/lib/tools/SelectTool/childStates/ScribbleBrushing.js.map +2 -2
  69. package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js +4 -1
  70. package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
  71. package/dist-cjs/lib/tools/SelectTool/selectHelpers.js +10 -0
  72. package/dist-cjs/lib/tools/SelectTool/selectHelpers.js.map +2 -2
  73. package/dist-cjs/lib/ui/TldrawUi.js +2 -3
  74. package/dist-cjs/lib/ui/TldrawUi.js.map +2 -2
  75. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js +1 -11
  76. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js.map +2 -2
  77. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js +6 -1
  78. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenu.js.map +2 -2
  79. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js +1 -1
  80. package/dist-cjs/lib/ui/components/ContextMenu/DefaultContextMenuContent.js.map +2 -2
  81. package/dist-cjs/lib/ui/components/Dialogs.js +8 -0
  82. package/dist-cjs/lib/ui/components/Dialogs.js.map +2 -2
  83. package/dist-cjs/lib/ui/components/EditLinkDialog.js +5 -5
  84. package/dist-cjs/lib/ui/components/EditLinkDialog.js.map +2 -2
  85. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js +4 -20
  86. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js.map +2 -2
  87. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js +1 -2
  88. package/dist-cjs/lib/ui/components/SharePanel/DefaultPeopleMenuItem.js.map +2 -2
  89. package/dist-cjs/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.js +2 -0
  90. package/dist-cjs/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.js.map +2 -2
  91. package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js +1 -0
  92. package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js.map +2 -2
  93. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js +10 -8
  94. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js.map +2 -2
  95. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +20 -1
  96. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
  97. package/dist-cjs/lib/ui/context/actions.js +8 -8
  98. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  99. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js +2 -0
  100. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js.map +2 -2
  101. package/dist-cjs/lib/ui/hooks/useCommentingEnabled.js +1 -1
  102. package/dist-cjs/lib/ui/hooks/useCommentingEnabled.js.map +2 -2
  103. package/dist-cjs/lib/ui/hooks/useFlatten.js +42 -23
  104. package/dist-cjs/lib/ui/hooks/useFlatten.js.map +2 -2
  105. package/dist-cjs/lib/ui/version.js +3 -3
  106. package/dist-cjs/lib/ui/version.js.map +1 -1
  107. package/dist-esm/index.d.mts +264 -275
  108. package/dist-esm/index.mjs +1 -1
  109. package/dist-esm/lib/defaultExternalContentHandlers.mjs +32 -16
  110. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  111. package/dist-esm/lib/shapes/arrow/curved-arrow.mjs +2 -2
  112. package/dist-esm/lib/shapes/arrow/curved-arrow.mjs.map +2 -2
  113. package/dist-esm/lib/shapes/arrow/elbow/getElbowArrowInfo.mjs +2 -2
  114. package/dist-esm/lib/shapes/arrow/elbow/getElbowArrowInfo.mjs.map +2 -2
  115. package/dist-esm/lib/shapes/arrow/getArrowInfo.mjs +1 -1
  116. package/dist-esm/lib/shapes/arrow/getArrowInfo.mjs.map +2 -2
  117. package/dist-esm/lib/shapes/arrow/straight-arrow.mjs +2 -2
  118. package/dist-esm/lib/shapes/arrow/straight-arrow.mjs.map +2 -2
  119. package/dist-esm/lib/shapes/arrow/toolStates/Idle.mjs +3 -4
  120. package/dist-esm/lib/shapes/arrow/toolStates/Idle.mjs.map +2 -2
  121. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs +7 -5
  122. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs.map +2 -2
  123. package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs +8 -6
  124. package/dist-esm/lib/shapes/draw/toolStates/Drawing.mjs.map +2 -2
  125. package/dist-esm/lib/shapes/geo/getGeoShapePath.mjs +3 -1
  126. package/dist-esm/lib/shapes/geo/getGeoShapePath.mjs.map +2 -2
  127. package/dist-esm/lib/shapes/geo/toolStates/Idle.mjs +3 -4
  128. package/dist-esm/lib/shapes/geo/toolStates/Idle.mjs.map +2 -2
  129. package/dist-esm/lib/shapes/geo/toolStates/Pointing.mjs +1 -1
  130. package/dist-esm/lib/shapes/geo/toolStates/Pointing.mjs.map +2 -2
  131. package/dist-esm/lib/shapes/line/toolStates/Pointing.mjs +2 -7
  132. package/dist-esm/lib/shapes/line/toolStates/Pointing.mjs.map +2 -2
  133. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +4 -4
  134. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  135. package/dist-esm/lib/shapes/note/noteHelpers.mjs +6 -5
  136. package/dist-esm/lib/shapes/note/noteHelpers.mjs.map +2 -2
  137. package/dist-esm/lib/shapes/note/toolStates/Pointing.mjs +1 -1
  138. package/dist-esm/lib/shapes/note/toolStates/Pointing.mjs.map +2 -2
  139. package/dist-esm/lib/shapes/shared/crop.mjs +1 -1
  140. package/dist-esm/lib/shapes/shared/crop.mjs.map +2 -2
  141. package/dist-esm/lib/shapes/text/toolStates/Idle.mjs +2 -2
  142. package/dist-esm/lib/shapes/text/toolStates/Idle.mjs.map +2 -2
  143. package/dist-esm/lib/shapes/text/toolStates/Pointing.mjs +1 -0
  144. package/dist-esm/lib/shapes/text/toolStates/Pointing.mjs.map +2 -2
  145. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs +3 -2
  146. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs.map +2 -2
  147. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs +2 -0
  148. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs.map +2 -2
  149. package/dist-esm/lib/tools/HandTool/childStates/OneFingerZooming.mjs +6 -0
  150. package/dist-esm/lib/tools/HandTool/childStates/OneFingerZooming.mjs.map +2 -2
  151. package/dist-esm/lib/tools/SelectTool/SelectTool.mjs +3 -0
  152. package/dist-esm/lib/tools/SelectTool/SelectTool.mjs.map +2 -2
  153. package/dist-esm/lib/tools/SelectTool/childStates/Brushing.mjs +1 -0
  154. package/dist-esm/lib/tools/SelectTool/childStates/Brushing.mjs.map +2 -2
  155. package/dist-esm/lib/tools/SelectTool/childStates/Crop/Crop.mjs +1 -0
  156. package/dist-esm/lib/tools/SelectTool/childStates/Crop/Crop.mjs.map +2 -2
  157. package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs +1 -3
  158. package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs.map +2 -2
  159. package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs +6 -3
  160. package/dist-esm/lib/tools/SelectTool/childStates/Idle.mjs.map +2 -2
  161. package/dist-esm/lib/tools/SelectTool/childStates/PointingArrowLabel.mjs +1 -1
  162. package/dist-esm/lib/tools/SelectTool/childStates/PointingArrowLabel.mjs.map +2 -2
  163. package/dist-esm/lib/tools/SelectTool/childStates/PointingCanvas.mjs +3 -0
  164. package/dist-esm/lib/tools/SelectTool/childStates/PointingCanvas.mjs.map +2 -2
  165. package/dist-esm/lib/tools/SelectTool/childStates/PointingHandle.mjs +5 -1
  166. package/dist-esm/lib/tools/SelectTool/childStates/PointingHandle.mjs.map +2 -2
  167. package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs +1 -1
  168. package/dist-esm/lib/tools/SelectTool/childStates/PointingShape.mjs.map +2 -2
  169. package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs +22 -31
  170. package/dist-esm/lib/tools/SelectTool/childStates/Resizing.mjs.map +2 -2
  171. package/dist-esm/lib/tools/SelectTool/childStates/ScribbleBrushing.mjs +1 -3
  172. package/dist-esm/lib/tools/SelectTool/childStates/ScribbleBrushing.mjs.map +2 -2
  173. package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs +4 -1
  174. package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
  175. package/dist-esm/lib/tools/SelectTool/selectHelpers.mjs +10 -0
  176. package/dist-esm/lib/tools/SelectTool/selectHelpers.mjs.map +2 -2
  177. package/dist-esm/lib/ui/TldrawUi.mjs +2 -3
  178. package/dist-esm/lib/ui/TldrawUi.mjs.map +2 -2
  179. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs +2 -12
  180. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs.map +2 -2
  181. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs +7 -2
  182. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenu.mjs.map +2 -2
  183. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs +1 -1
  184. package/dist-esm/lib/ui/components/ContextMenu/DefaultContextMenuContent.mjs.map +2 -2
  185. package/dist-esm/lib/ui/components/Dialogs.mjs +9 -1
  186. package/dist-esm/lib/ui/components/Dialogs.mjs.map +2 -2
  187. package/dist-esm/lib/ui/components/EditLinkDialog.mjs +5 -5
  188. package/dist-esm/lib/ui/components/EditLinkDialog.mjs.map +2 -2
  189. package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs +5 -22
  190. package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs.map +2 -2
  191. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs +1 -2
  192. package/dist-esm/lib/ui/components/SharePanel/DefaultPeopleMenuItem.mjs.map +2 -2
  193. package/dist-esm/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.mjs +2 -0
  194. package/dist-esm/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.mjs.map +2 -2
  195. package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs +1 -0
  196. package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs.map +2 -2
  197. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs +10 -8
  198. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs.map +2 -2
  199. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +20 -1
  200. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
  201. package/dist-esm/lib/ui/context/actions.mjs +8 -8
  202. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  203. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs +2 -0
  204. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs.map +2 -2
  205. package/dist-esm/lib/ui/hooks/useCommentingEnabled.mjs +2 -2
  206. package/dist-esm/lib/ui/hooks/useCommentingEnabled.mjs.map +2 -2
  207. package/dist-esm/lib/ui/hooks/useFlatten.mjs +42 -23
  208. package/dist-esm/lib/ui/hooks/useFlatten.mjs.map +2 -2
  209. package/dist-esm/lib/ui/version.mjs +3 -3
  210. package/dist-esm/lib/ui/version.mjs.map +1 -1
  211. package/package.json +12 -6
  212. package/src/lib/defaultExternalContentHandlers.test.ts +43 -0
  213. package/src/lib/defaultExternalContentHandlers.ts +40 -18
  214. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +17 -0
  215. package/src/lib/shapes/arrow/curved-arrow.ts +2 -2
  216. package/src/lib/shapes/arrow/elbow/getElbowArrowInfo.tsx +5 -3
  217. package/src/lib/shapes/arrow/getArrowInfo.ts +2 -1
  218. package/src/lib/shapes/arrow/straight-arrow.ts +2 -2
  219. package/src/lib/shapes/arrow/toolStates/Idle.tsx +3 -4
  220. package/src/lib/shapes/arrow/toolStates/Pointing.tsx +10 -6
  221. package/src/lib/shapes/draw/DrawShapeTool.test.ts +19 -0
  222. package/src/lib/shapes/draw/toolStates/Drawing.ts +12 -6
  223. package/src/lib/shapes/frame/FrameShapeTool.test.ts +17 -0
  224. package/src/lib/shapes/geo/GeoShapeTool.test.ts +28 -0
  225. package/src/lib/shapes/geo/getGeoShapePath.ts +3 -1
  226. package/src/lib/shapes/geo/toolStates/Idle.ts +3 -4
  227. package/src/lib/shapes/geo/toolStates/Pointing.ts +2 -1
  228. package/src/lib/shapes/line/LineShapeTool.test.ts +30 -1
  229. package/src/lib/shapes/line/toolStates/Pointing.ts +6 -7
  230. package/src/lib/shapes/note/NoteShapeTool.test.ts +45 -0
  231. package/src/lib/shapes/note/NoteShapeUtil.tsx +7 -4
  232. package/src/lib/shapes/note/noteHelpers.ts +18 -8
  233. package/src/lib/shapes/note/toolStates/Pointing.ts +2 -1
  234. package/src/lib/shapes/shared/crop.ts +7 -1
  235. package/src/lib/shapes/text/TextShapeTool.test.ts +33 -0
  236. package/src/lib/shapes/text/toolStates/Idle.ts +2 -2
  237. package/src/lib/shapes/text/toolStates/Pointing.ts +3 -0
  238. package/src/lib/shapes/video/VideoShapeUtil.tsx +4 -2
  239. package/src/lib/tools/EraserTool/childStates/Erasing.ts +5 -0
  240. package/src/lib/tools/HandTool/childStates/OneFingerZooming.ts +10 -0
  241. package/src/lib/tools/SelectTool/SelectTool.ts +4 -0
  242. package/src/lib/tools/SelectTool/childStates/Brushing.ts +4 -0
  243. package/src/lib/tools/SelectTool/childStates/Crop/Crop.ts +4 -0
  244. package/src/lib/tools/SelectTool/childStates/DraggingHandle.tsx +3 -4
  245. package/src/lib/tools/SelectTool/childStates/Idle.ts +10 -3
  246. package/src/lib/tools/SelectTool/childStates/PointingArrowLabel.ts +1 -1
  247. package/src/lib/tools/SelectTool/childStates/PointingCanvas.ts +4 -0
  248. package/src/lib/tools/SelectTool/childStates/PointingHandle.ts +6 -1
  249. package/src/lib/tools/SelectTool/childStates/PointingShape.ts +4 -1
  250. package/src/lib/tools/SelectTool/childStates/Resizing.ts +30 -33
  251. package/src/lib/tools/SelectTool/childStates/ScribbleBrushing.ts +3 -3
  252. package/src/lib/tools/SelectTool/childStates/Translating.ts +6 -0
  253. package/src/lib/tools/SelectTool/selectHelpers.ts +21 -0
  254. package/src/lib/ui/TldrawUi.tsx +1 -2
  255. package/src/lib/ui/components/ActionsMenu/DefaultActionsMenu.tsx +4 -14
  256. package/src/lib/ui/components/ContextMenu/DefaultContextMenu.tsx +8 -2
  257. package/src/lib/ui/components/ContextMenu/DefaultContextMenuContent.tsx +5 -1
  258. package/src/lib/ui/components/Dialogs.tsx +17 -1
  259. package/src/lib/ui/components/EditLinkDialog.tsx +8 -7
  260. package/src/lib/ui/components/Minimap/DefaultMinimap.tsx +5 -23
  261. package/src/lib/ui/components/SharePanel/DefaultPeopleMenuItem.tsx +2 -3
  262. package/src/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.tsx +2 -0
  263. package/src/lib/ui/components/Toolbar/AltTextEditor.tsx +1 -0
  264. package/src/lib/ui/components/Toolbar/DefaultImageToolbarContent.tsx +13 -8
  265. package/src/lib/ui/components/primitives/TldrawUiSlider.test.tsx +92 -0
  266. package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +25 -1
  267. package/src/lib/ui/context/actions.tsx +25 -8
  268. package/src/lib/ui/hooks/useClipboardEvents.ts +2 -0
  269. package/src/lib/ui/hooks/useCommentingEnabled.ts +5 -3
  270. package/src/lib/ui/hooks/useFlatten.ts +56 -23
  271. package/src/lib/ui/version.ts +3 -3
  272. package/src/lib/ui.css +7 -16
  273. package/src/test/ClickManager.test.ts +18 -0
  274. package/src/test/Editor.test.tsx +3 -0
  275. package/src/test/EraserTool.test.ts +18 -2
  276. package/src/test/HandTool.test.ts +21 -0
  277. package/src/test/LaserTool.test.ts +46 -0
  278. package/src/test/ScribbleManager.test.ts +19 -0
  279. package/src/test/SelectTool.test.ts +110 -0
  280. package/src/test/TldrawEditor.test.tsx +18 -0
  281. package/src/test/VideoShapeUtil.test.tsx +126 -0
  282. package/src/test/actions-menu-readonly.test.tsx +37 -0
  283. package/src/test/bookmark-shapes.test.ts +110 -0
  284. package/src/test/commands/__snapshots__/packShapes.test.ts.snap +6 -6
  285. package/src/test/commands/clipboard.test.ts +36 -1
  286. package/src/test/commands/lockShapes.test.ts +11 -0
  287. package/src/test/commands/packShapes.test.ts +20 -0
  288. package/src/test/commands/putContent.test.ts +35 -1
  289. package/src/test/commands/setCamera.test.ts +69 -0
  290. package/src/test/commands/stackShapes.test.ts +12 -0
  291. package/src/test/commands/updateViewportPageBounds.test.ts +8 -0
  292. package/src/test/cropping.test.ts +162 -0
  293. package/src/test/customSnapping.test.tsx +32 -0
  294. package/src/test/defaultHandleExternalFileContent.test.ts +86 -0
  295. package/src/test/drawing.test.ts +60 -1
  296. package/src/test/duplicate.test.ts +22 -0
  297. package/src/test/flatten.test.ts +135 -0
  298. package/src/test/frames.test.ts +51 -0
  299. package/src/test/getShapeAtPoint.test.ts +44 -1
  300. package/src/test/handleDeepLink.test.tsx +15 -0
  301. package/src/test/minimap-wheel.test.tsx +48 -0
  302. package/src/test/resizing.test.ts +46 -0
  303. package/src/test/selection-omnibus.test.ts +50 -0
  304. package/src/test/spacebarPanning.test.ts +58 -0
  305. package/src/test/translating.test.ts +42 -0
  306. package/src/test/ui/AltTextEditor.test.tsx +51 -0
  307. package/src/test/ui/ContextMenu.test.tsx +61 -2
  308. package/src/test/ui/EditLinkDialog.test.tsx +77 -0
  309. package/src/test/ui/ImageToolbar.test.tsx +58 -0
  310. package/src/test/ui/StylePanel.test.tsx +33 -2
  311. package/src/test/ui/keyboardShortcuts.test.tsx +27 -0
  312. package/src/test/ui/presenceActivity.test.tsx +44 -0
  313. package/src/test/ui/unlockAll.test.tsx +55 -0
  314. package/src/test/ui-outside-editor.test.tsx +74 -0
  315. package/src/test/viewport-following.test.ts +29 -1
  316. package/tldraw.css +7 -16
@@ -3,6 +3,7 @@ import {
3
3
  IndexKey,
4
4
  TLNoteShape,
5
5
  TLShape,
6
+ TLShapeId,
6
7
  Vec,
7
8
  compact,
8
9
  createShapeId,
@@ -94,29 +95,34 @@ export interface AvailableNoteAdjacentPositionsOpts {
94
95
  extraHeight: number
95
96
  noteWidth: number
96
97
  noteHeight: number
98
+ /**
99
+ * Notes whose adjacent positions should not be offered, e.g. the notes being dragged,
100
+ * which would otherwise snap to their own slots.
101
+ */
102
+ excludeShapeIds?: TLShapeId[]
97
103
  }
98
104
 
99
105
  /**
100
- * Get all of the available note adjacent positions, excluding the selected shapes.
106
+ * Get all of the available note adjacent positions, excluding those around `excludeShapeIds`.
101
107
  *
102
108
  * @internal */
103
109
  export function getAvailableNoteAdjacentPositions(
104
110
  editor: Editor,
105
111
  opts: AvailableNoteAdjacentPositionsOpts
106
112
  ) {
107
- const { rotation, scale, extraHeight, noteWidth, noteHeight } = opts
108
- const selectedShapeIds = new Set(editor.getSelectedShapeIds())
113
+ const { rotation, scale, extraHeight, noteWidth, noteHeight, excludeShapeIds = [] } = opts
114
+ const excludedShapeIds = new Set(excludeShapeIds)
109
115
  const minSize =
110
116
  (Math.max(noteWidth, noteHeight) + editor.options.adjacentShapeMargin + extraHeight) ** 2
111
117
  const allCenters = new Map<TLNoteShape, Vec>()
112
118
  const positions: (Vec | undefined)[] = []
113
119
 
114
- // Get all the positions that are adjacent to the selected note shapes
120
+ // Get all the positions that are adjacent to the candidate note shapes
115
121
  for (const shape of editor.getCurrentPageShapes()) {
116
122
  if (
117
123
  !editor.isShapeOfType(shape, 'note') ||
118
124
  scale !== shape.props.scale ||
119
- selectedShapeIds.has(shape.id)
125
+ excludedShapeIds.has(shape.id)
120
126
  ) {
121
127
  continue
122
128
  }
@@ -126,7 +132,7 @@ export function getAvailableNoteAdjacentPositions(
126
132
  // If the note has a different rotation, we can't use its adjacent positions
127
133
  if (rotation !== transform.rotation()) continue
128
134
 
129
- // Save the unselected note shape's center
135
+ // Save the candidate note shape's center
130
136
  allCenters.set(shape, editor.getShapePageBounds(shape)!.center)
131
137
 
132
138
  // And push its position to the positions array
@@ -135,7 +141,8 @@ export function getAvailableNoteAdjacentPositions(
135
141
  getNoteAdjacentPositions(editor, {
136
142
  pagePoint: transform.point(),
137
143
  pageRotation: rotation,
138
- growY: shape.props.growY,
144
+ // page units, like the base positions (see PointingHandle)
145
+ growY: shape.props.growY * shape.props.scale,
139
146
  extraHeight,
140
147
  scale,
141
148
  noteWidth,
@@ -191,8 +198,11 @@ export function getNoteShapeForAdjacentPosition(
191
198
  // Start from the top of the stack, and work our way down
192
199
  const allShapesOnPage = editor.getCurrentPageShapesSorted()
193
200
 
201
+ // Squared, in page units, to compare against Dist2 below. A neighbour that has grown
202
+ // (growY) sits further from the ideal slot centre, so the radius is a full note plus margin.
194
203
  const minDistance =
195
- (Math.max(noteWidth, noteHeight) + editor.options.adjacentShapeMargin ** 2) ** shape.props.scale
204
+ ((Math.max(noteWidth, noteHeight) + editor.options.adjacentShapeMargin) * shape.props.scale) **
205
+ 2
196
206
 
197
207
  for (let i = allShapesOnPage.length - 1; i >= 0; i--) {
198
208
  const otherNote = allShapesOnPage[i]
@@ -115,7 +115,8 @@ export class Pointing extends StateNode {
115
115
  }
116
116
 
117
117
  override onComplete() {
118
- this.complete()
118
+ // Not a release: see BaseBoxShapeTool's Pointing state for why this cancels.
119
+ this.cancel()
119
120
  }
120
121
 
121
122
  override onCancel() {
@@ -551,7 +551,13 @@ export function getCroppedImageDataForReplacedImage(
551
551
  let crop = defaultCrop
552
552
  let newDisplayW = origDisplayW
553
553
  let newDisplayH = origDisplayH
554
- const isOriginalCrop = isEqual(imageShape.props.crop, defaultCrop)
554
+ // Compare the bounds, not the whole object: an explicit `isCircle: false` must still count
555
+ // as the original crop.
556
+ const isOriginalCrop =
557
+ !!imageShape.props.crop &&
558
+ isEqual(imageShape.props.crop.topLeft, defaultCrop.topLeft) &&
559
+ isEqual(imageShape.props.crop.bottomRight, defaultCrop.bottomRight) &&
560
+ !imageShape.props.crop.isCircle
555
561
 
556
562
  if (isOriginalCrop) {
557
563
  newDisplayW = origDisplayW
@@ -55,6 +55,24 @@ describe(TextShapeTool, () => {
55
55
  props: { richText: toRichText('Hello') },
56
56
  })
57
57
  })
58
+
59
+ it('Records the deletion of an empty text shape so redo cannot resurrect it', () => {
60
+ editor.setCurrentTool('text')
61
+ editor.pointerDown(0, 0)
62
+ editor.pointerUp()
63
+ editor.expectToBeIn('select.editing_shape')
64
+ expect(editor.getCurrentPageShapes().length).toBe(1)
65
+
66
+ // Exiting without typing deletes the empty shape (TextShapeUtil.onEditEnd)
67
+ editor.cancel()
68
+ expect(editor.getCurrentPageShapes().length).toBe(0)
69
+
70
+ // The creation and the deletion squash to nothing, so neither undo nor redo brings it back
71
+ editor.undo()
72
+ expect(editor.getCurrentPageShapes().length).toBe(0)
73
+ editor.redo()
74
+ expect(editor.getCurrentPageShapes().length).toBe(0)
75
+ })
58
76
  })
59
77
 
60
78
  describe('When selecting the tool', () => {
@@ -280,6 +298,21 @@ describe('When resizing', () => {
280
298
  expect(editor.getCurrentPageShapes().length).toBe(1)
281
299
  })
282
300
 
301
+ it('removes the pending text shape when another tool is selected mid-drag', () => {
302
+ editor.setCurrentTool('text')
303
+ editor.pointerDown(0, 0)
304
+ vi.advanceTimersByTime(200)
305
+ editor.pointerMove(100, 100)
306
+ editor.expectToBeIn('select.resizing')
307
+ expect(editor.getCurrentPageShapes().length).toBe(1)
308
+ editor.setCurrentTool('geo')
309
+ editor.expectToBeIn('geo.idle')
310
+ expect(editor.getCurrentPageShapes().length).toBe(0)
311
+ expect(editor.getSelectedShapeIds()).toEqual([])
312
+ editor.pointerUp(100, 100)
313
+ expect(editor.getCurrentPageShapes().length).toBe(0)
314
+ })
315
+
283
316
  it('preserves the top left when the text has a fixed width', () => {
284
317
  editor.setCurrentTool('text')
285
318
  const x = 0
@@ -3,7 +3,7 @@ import {
3
3
  cancelUpdateHoveredShapeId,
4
4
  updateHoveredShapeId,
5
5
  } from '../../../tools/selection-logic/updateHoveredShapeId'
6
- import { startEditingShapeWithRichText } from '../../../tools/SelectTool/selectHelpers'
6
+ import { startEditingShape } from '../../../tools/SelectTool/selectHelpers'
7
7
 
8
8
  export class Idle extends StateNode {
9
9
  static override id = 'idle'
@@ -34,7 +34,7 @@ export class Idle extends StateNode {
34
34
  const onlySelectedShape = this.editor.getOnlySelectedShape()
35
35
  if (!this.editor.canEditShape(onlySelectedShape)) return
36
36
  this.editor.setCurrentTool('select')
37
- startEditingShapeWithRichText(this.editor, onlySelectedShape.id, { info })
37
+ startEditingShape(this.editor, onlySelectedShape, { info })
38
38
  }
39
39
  }
40
40
 
@@ -21,6 +21,9 @@ export class Pointing extends StateNode {
21
21
  enterTime = 0
22
22
  override onEnter(): void {
23
23
  this.enterTime = Date.now()
24
+ // Only the drag-to-create path sets a mark. Without this reset, a later cancel
25
+ // would bail to a mark left over from a previous text shape and undo unrelated work.
26
+ this.markId = ''
24
27
  }
25
28
 
26
29
  override onExit() {
@@ -175,14 +175,16 @@ const VideoShape = memo(function VideoShape({ shape }: { shape: TLVideoShape })
175
175
  }
176
176
  }, [isEditing, isLoaded])
177
177
 
178
+ const hasVideo = !!asset?.props.src && !!url && isLoaded
179
+
178
180
  return (
179
181
  <>
180
182
  <HTMLContainer
181
183
  id={shape.id}
182
184
  style={{
183
185
  color: 'var(--tl-color-text-3)',
184
- backgroundColor: asset ? 'transparent' : 'var(--tl-color-low)',
185
- border: asset ? 'none' : '1px solid var(--tl-color-low-border)',
186
+ backgroundColor: hasVideo ? 'transparent' : 'var(--tl-color-low)',
187
+ border: hasVideo ? 'none' : '1px solid var(--tl-color-low-border)',
186
188
  }}
187
189
  >
188
190
  <div className="tl-counter-scaled">
@@ -16,12 +16,17 @@ export class Erasing extends StateNode {
16
16
  this.info = info
17
17
 
18
18
  const originPagePoint = this.editor.inputs.getOriginPagePoint()
19
+ const pressedShapeIds = new Set(this.editor.getErasingShapeIds())
19
20
  this.excludedShapeIds = new Set(
20
21
  this.editor
21
22
  .getCurrentPageShapes()
22
23
  .filter((shape) => {
23
24
  //If the shape is locked, we shouldn't erase it
24
25
  if (this.editor.isShapeOrAncestorLocked(shape)) return true
26
+ // A frame the press hit on its outline stays marked: a press just inside the outline
27
+ // is within the frame's bounds, so the containment check below would otherwise
28
+ // unmark it on the first move while a press just outside would erase it
29
+ if (this.editor.isShapeFrameLike(shape) && pressedShapeIds.has(shape.id)) return false
25
30
  //If the shape is a group or frame-like, check we're inside it when we start erasing
26
31
  if (this.editor.isShapeOfType(shape, 'group') || this.editor.isShapeFrameLike(shape)) {
27
32
  const pointInShapeShape = this.editor.getPointInShapeSpace(shape, originPagePoint)
@@ -47,6 +47,16 @@ export class OneFingerZooming extends StateNode {
47
47
  this.complete()
48
48
  }
49
49
 
50
+ override onPointerDown() {
51
+ // A second touch resets the input origin to the new pointer, so continuing
52
+ // the zoom would jump the camera. Yield before the pinch begins.
53
+ this.parent.transition('idle')
54
+ }
55
+
56
+ override onComplete() {
57
+ this.parent.transition('idle')
58
+ }
59
+
50
60
  override onCancel() {
51
61
  this.parent.transition('idle')
52
62
  }
@@ -84,5 +84,9 @@ export class SelectTool extends StateNode {
84
84
  if (this.editor.getCurrentPageState().editingShapeId) {
85
85
  this.editor.setEditingShape(null)
86
86
  }
87
+ // Leaving crop mode for another tool would otherwise keep the image in its cropping state
88
+ if (this.editor.getCroppingShapeId()) {
89
+ this.editor.setCroppingShape(null)
90
+ }
87
91
  }
88
92
  }
@@ -36,6 +36,10 @@ export class Brushing extends StateNode {
36
36
 
37
37
  this.isWrapMode = editor.user.getIsWrapMode()
38
38
 
39
+ // Accel-pressing a resize or rotate handle brushes instead of resizing or rotating,
40
+ // and the handle's hover cursor would otherwise stay up for the whole brush.
41
+ editor.setCursor({ type: 'default', rotation: 0 })
42
+
39
43
  this.viewportDidChange = false
40
44
 
41
45
  let isInitialCheck = true
@@ -28,6 +28,10 @@ export class Crop extends StateNode {
28
28
  }
29
29
  }
30
30
  override onCancel() {
31
+ // Parents handle events before children, so a cancel during a child's drag would unwind
32
+ // the whole session here before the child bails to its own mark. Only idle's escape ends
33
+ // the session; mid-drag children revert just their own change and return to idle.
34
+ if (this.getCurrent()?.id !== 'idle') return
31
35
  if (!this.didExit) {
32
36
  this.didExit = true
33
37
  this.editor.bailToMark(this.markId)
@@ -333,13 +333,12 @@ export class DraggingHandle extends StateNode {
333
333
 
334
334
  if (canSnap && (isSnapMode ? !ctrlKey : ctrlKey)) {
335
335
  // We're snapping
336
- const pageTransform = editor.getShapePageTransform(shape.id)
337
- if (!pageTransform) throw Error('Expected a page transform')
338
-
339
336
  const snap = snaps.handles.snapHandle({ currentShapeId: shapeId, handle: nextHandle })
340
337
 
341
338
  if (snap) {
342
- snap.nudge.rot(-editor.getShapeParentTransform(shape)!.rotation())
339
+ // The nudge is in page space and `point` is in the shape's own space, so the shape's
340
+ // full page rotation (not just its parent's) has to come off
341
+ snap.nudge.rot(-editor.getShapePageTransform(shape.id).rotation())
343
342
  point.add(snap.nudge)
344
343
  nextHandle = { ...initialHandle, x: point.x, y: point.y }
345
344
  }
@@ -440,6 +440,7 @@ export class Idle extends StateNode {
440
440
  // Call the shape's double click handler
441
441
  const change = util.onDoubleClick?.(shape)
442
442
  if (change) {
443
+ this.editor.markHistoryStoppingPoint('double click shape')
443
444
  this.editor.updateShapes([change])
444
445
  return
445
446
  }
@@ -472,6 +473,7 @@ export class Idle extends StateNode {
472
473
  const changes = util.onDoubleClickHandle?.(shape, handle)
473
474
 
474
475
  if (changes) {
476
+ this.editor.markHistoryStoppingPoint('double click handle')
475
477
  this.editor.updateShapes([changes])
476
478
  } else {
477
479
  // If the shape's double click handler has not created a change,
@@ -589,7 +591,7 @@ export class Idle extends StateNode {
589
591
  }
590
592
  return
591
593
  }
592
- this.nudgeSelectedShapes(false)
594
+ this.nudgeSelectedShapes(info, false)
593
595
  return
594
596
  }
595
597
  }
@@ -635,7 +637,7 @@ export class Idle extends StateNode {
635
637
  )
636
638
  return
637
639
  }
638
- this.nudgeSelectedShapes(true)
640
+ this.nudgeSelectedShapes(info, true)
639
641
  break
640
642
  }
641
643
  case 'Tab': {
@@ -754,13 +756,18 @@ export class Idle extends StateNode {
754
756
  startEditingShapeWithRichText(this.editor, id, { info })
755
757
  }
756
758
 
757
- private nudgeSelectedShapes(ephemeral = false) {
759
+ private nudgeSelectedShapes(info: TLKeyboardEventInfo, ephemeral = false) {
758
760
  const {
759
761
  editor: {
760
762
  inputs: { keys },
761
763
  },
762
764
  } = this
763
765
 
766
+ // Space+arrow pages the camera and Alt+arrow is the change-page shortcut; both
767
+ // events still reach this state, so without this guard the selection also
768
+ // moves one unit (#10397)
769
+ if (info.altKey || this.editor.inputs.getIsSpacebarPanning()) return
770
+
764
771
  // We want to use the "actual" shift key state,
765
772
  // not the one that's in the editor.inputs.shiftKey,
766
773
  // because that one uses a short timeout on release
@@ -148,7 +148,7 @@ export class PointingArrowLabel extends StateNode {
148
148
  }
149
149
 
150
150
  override onComplete() {
151
- this.cancel()
151
+ this.complete()
152
152
  }
153
153
 
154
154
  override onInterrupt() {
@@ -45,6 +45,10 @@ export class PointingCanvas extends StateNode {
45
45
  this.complete()
46
46
  }
47
47
 
48
+ override onCancel() {
49
+ this.parent.transition('idle')
50
+ }
51
+
48
52
  override onInterrupt() {
49
53
  this.parent.transition('idle')
50
54
  }
@@ -7,7 +7,10 @@ import {
7
7
  TLPointerEventInfo,
8
8
  Vec,
9
9
  } from '@tldraw/editor'
10
- import { updateArrowTargetState } from '../../../shapes/arrow/arrowTargetState'
10
+ import {
11
+ clearArrowTargetState,
12
+ updateArrowTargetState,
13
+ } from '../../../shapes/arrow/arrowTargetState'
11
14
  import { getArrowBindings } from '../../../shapes/arrow/shared'
12
15
  import {
13
16
  getNoteAdjacentPositions,
@@ -55,6 +58,8 @@ export class PointingHandle extends StateNode {
55
58
 
56
59
  override onExit() {
57
60
  this.editor.setHintingShapes([])
61
+ // onEnter shows the arrow's binding target; a click without a drag would leave it showing
62
+ clearArrowTargetState(this.editor)
58
63
  this.editor.setCursor({ type: 'default', rotation: 0 })
59
64
  }
60
65
 
@@ -65,7 +65,10 @@ export class PointingShape extends StateNode {
65
65
  const focusedGroupId = this.editor.getFocusedGroupId()
66
66
  const currentPagePoint = this.editor.inputs.getCurrentPagePoint()
67
67
 
68
- const additiveSelectionKey = info.shiftKey || info.accelKey
68
+ // Alt suppresses additive selection here as it does on enter and in
69
+ // selectOnCanvasPointerUp; otherwise shift+alt or ctrl+alt releasing over
70
+ // a selected shape would toggle it out of the selection.
71
+ const additiveSelectionKey = (info.shiftKey || info.accelKey) && !info.altKey
69
72
 
70
73
  const hitShape =
71
74
  this.editor.getShapeAtPoint(currentPagePoint, {
@@ -2,8 +2,6 @@ import {
2
2
  Box,
3
3
  HALF_PI,
4
4
  Mat,
5
- PI,
6
- PI2,
7
5
  SelectionCorner,
8
6
  SelectionEdge,
9
7
  StateNode,
@@ -19,6 +17,7 @@ import {
19
17
  isAccelKey,
20
18
  isShapeId,
21
19
  kickoutOccludedShapes,
20
+ rotateSelectionHandle,
22
21
  } from '@tldraw/editor'
23
22
  import { getEnclosedShapeIds } from '../../../shapes/frame/FrameShapeTool'
24
23
  import { batchMeasureGeoLabels, setBatchLabelSizeCache } from '../../../shapes/geo/GeoShapeUtil'
@@ -44,6 +43,9 @@ export class Resizing extends StateNode {
44
43
  // A switch to detect when the user is holding ctrl
45
44
  private didHoldCommand = false
46
45
 
46
+ // Set by complete() and cancel(); see onExit for what happens when neither ran
47
+ private didFinish = false
48
+
47
49
  // we transition into the resizing state from the geo pointing state, which starts with a shape of size w: 1, h: 1,
48
50
  // so if the user drags x: +50, y: +50 after mouseDown, the shape will be w: 51, h: 51, which is too many pixels, alas
49
51
  // so we allow passing a further offset into this state to negate such issues
@@ -56,6 +58,7 @@ export class Resizing extends StateNode {
56
58
 
57
59
  this.info = info
58
60
  this.didHoldCommand = false
61
+ this.didFinish = false
59
62
 
60
63
  if (typeof info.onInteractionEnd === 'string') {
61
64
  this.parent.setCurrentToolIdMask(info.onInteractionEnd)
@@ -127,6 +130,8 @@ export class Resizing extends StateNode {
127
130
  }
128
131
 
129
132
  private cancel() {
133
+ this.didFinish = true
134
+
130
135
  // Call onResizeCancel callback before resetting
131
136
  const { shapeSnapshots } = this.snapshot
132
137
 
@@ -134,6 +139,7 @@ export class Resizing extends StateNode {
134
139
  const current = this.editor.getShape(shape.id)
135
140
  if (current) {
136
141
  const util = this.editor.getShapeUtil(shape)
142
+ if (!util.canResize(shape)) return
137
143
  util.onResizeCancel?.(shape, current)
138
144
  }
139
145
  })
@@ -153,6 +159,8 @@ export class Resizing extends StateNode {
153
159
  }
154
160
 
155
161
  private complete() {
162
+ this.didFinish = true
163
+
156
164
  kickoutOccludedShapes(this.editor, this.snapshot.selectedShapeIds)
157
165
 
158
166
  this.handleResizeEnd()
@@ -179,12 +187,15 @@ export class Resizing extends StateNode {
179
187
  }
180
188
 
181
189
  private handleResizeStart() {
190
+ // Non-resizable shapes are in the snapshot only to be repositioned, so they don't get the
191
+ // resize lifecycle callbacks (matching Editor.resizeShape).
182
192
  const { shapeSnapshots } = this.snapshot
183
193
 
184
194
  const changes: TLShapePartial[] = []
185
195
 
186
196
  shapeSnapshots.forEach(({ shape }) => {
187
197
  const util = this.editor.getShapeUtil(shape)
198
+ if (!util.canResize(shape)) return
188
199
  const change = util.onResizeStart?.(shape)
189
200
  if (change) {
190
201
  changes.push(change)
@@ -204,6 +215,7 @@ export class Resizing extends StateNode {
204
215
  shapeSnapshots.forEach(({ shape }) => {
205
216
  const current = this.editor.getShape(shape.id)!
206
217
  const util = this.editor.getShapeUtil(shape)
218
+ if (!util.canResize(shape)) return
207
219
  const change = util.onResizeEnd?.(shape, current)
208
220
  if (change) {
209
221
  changes.push(change)
@@ -545,6 +557,12 @@ export class Resizing extends StateNode {
545
557
  if (this.info.isCreating && this.editor.getHintingShapeIds().length > 0) {
546
558
  this.editor.setHintingShapes([])
547
559
  }
560
+ // Exited mid-creation without complete() or cancel(), e.g. a tool shortcut pressed while
561
+ // dragging out a text box: the shape created at the drag threshold was never committed and
562
+ // would otherwise be left behind, empty and invisible (#10401)
563
+ if (this.info.isCreating && !this.didFinish) {
564
+ this.editor.bailToMark(this.markId)
565
+ }
548
566
  }
549
567
 
550
568
  private _createSnapshot() {
@@ -583,17 +601,16 @@ export class Resizing extends StateNode {
583
601
 
584
602
  const util = editor.getShapeUtil(shape)
585
603
 
586
- // If the shape can resize, add it to the resizing shapes snapshots
587
- if (util.canResize(shape)) {
588
- const pageTransform = editor.getShapePageTransform(shape)!
589
- shapeSnapshots.set(shape.id, {
590
- shape,
591
- bounds: editor.getShapeGeometry(shape).bounds,
592
- pageTransform,
593
- pageRotation: Mat.Decompose(pageTransform).rotation,
594
- isAspectRatioLocked: util.isAspectRatioLocked(shape),
595
- })
596
- }
604
+ // Shapes that can't resize are snapshotted too: getResizeShapePartial repositions them
605
+ // so they keep their place in the selection instead of being left behind.
606
+ const pageTransform = editor.getShapePageTransform(shape)!
607
+ shapeSnapshots.set(shape.id, {
608
+ shape,
609
+ bounds: editor.getShapeGeometry(shape).bounds,
610
+ pageTransform,
611
+ pageRotation: Mat.Decompose(pageTransform).rotation,
612
+ isAspectRatioLocked: util.isAspectRatioLocked(shape),
613
+ })
597
614
 
598
615
  // Special case:
599
616
  // For frames, we don't want to resize children but we DO want to get a snapshot of their children so that we can restore their
@@ -670,23 +687,3 @@ export class Resizing extends StateNode {
670
687
  }
671
688
 
672
689
  type Snapshot = ReturnType<Resizing['_createSnapshot']>
673
-
674
- const ORDERED_SELECTION_HANDLES: (SelectionEdge | SelectionCorner)[] = [
675
- 'top',
676
- 'top_right',
677
- 'right',
678
- 'bottom_right',
679
- 'bottom',
680
- 'bottom_left',
681
- 'left',
682
- 'top_left',
683
- ]
684
-
685
- export function rotateSelectionHandle(handle: SelectionEdge | SelectionCorner, rotation: number) {
686
- // first find out how many tau we need to rotate by
687
- rotation = rotation % PI2
688
- const numSteps = Math.round(rotation / (PI / 4))
689
-
690
- const currentIndex = ORDERED_SELECTION_HANDLES.indexOf(handle)
691
- return ORDERED_SELECTION_HANDLES[(currentIndex + numSteps) % ORDERED_SELECTION_HANDLES.length]
692
- }
@@ -23,9 +23,9 @@ export class ScribbleBrushing extends StateNode {
23
23
  newlySelectedShapeIds = new Set<TLShapeId>()
24
24
 
25
25
  override onEnter() {
26
- this.initialSelectedShapeIds = new Set<TLShapeId>(
27
- this.editor.inputs.getShiftKey() ? this.editor.getSelectedShapeIds() : []
28
- )
26
+ // Captured regardless of shift so cancel() can restore it like the rectangle
27
+ // brush does (#10429); shift only decides whether it merges into the live selection.
28
+ this.initialSelectedShapeIds = new Set<TLShapeId>(this.editor.getSelectedShapeIds())
29
29
  this.newlySelectedShapeIds = new Set<TLShapeId>()
30
30
  this.size = 0
31
31
  this.hits.clear()
@@ -170,6 +170,9 @@ export class Translating extends StateNode {
170
170
  this.editor.duplicateShapes(Array.from(this.editor.getSelectedShapeIds()))
171
171
 
172
172
  this.snapshot = getTranslatingSnapshot(this.editor)
173
+ // The manager ignores startDraggingShapes while it is running, so it has to be
174
+ // cleared or it keeps reparenting the originals instead of the clones
175
+ this.dragAndDropManager.clear()
173
176
  this.handleStart()
174
177
  this.updateShapes()
175
178
  }
@@ -179,6 +182,8 @@ export class Translating extends StateNode {
179
182
  this.snapshot = this.selectionSnapshot
180
183
  this.reset()
181
184
  this.markId = this.editor.markHistoryStoppingPoint('translate')
185
+ // Same as in startCloning: the manager is still tracking the (now deleted) clones
186
+ this.dragAndDropManager.clear()
182
187
  this.updateShapes()
183
188
  }
184
189
 
@@ -439,6 +444,7 @@ function getTranslatingSnapshot(editor: Editor) {
439
444
  extraHeight: noteSnapshot.shape.props.growY ?? 0,
440
445
  noteWidth: dv.noteWidth,
441
446
  noteHeight: dv.noteHeight,
447
+ excludeShapeIds: selectedShapeIds,
442
448
  })
443
449
  noteCenterOffset = new Vec(dv.noteWidth / 2, dv.noteHeight / 2)
444
450
  }
@@ -48,3 +48,24 @@ export function startEditingShapeWithRichText(
48
48
  editor.emit('select-all-text', { shapeId: shape.id })
49
49
  }
50
50
  }
51
+
52
+ /**
53
+ * Start editing any editable shape. Shapes with rich text go through
54
+ * `startEditingShapeWithRichText`; editable shapes without it (frame, video, embed) would
55
+ * make that helper throw, so they enter the editing state directly.
56
+ *
57
+ * @internal
58
+ */
59
+ export function startEditingShape(
60
+ editor: Editor,
61
+ shape: TLShape,
62
+ options: { selectAll?: boolean; info?: TLEventInfo } = {}
63
+ ) {
64
+ if (!editor.canEditShape(shape)) return
65
+ if (hasRichText(shape)) {
66
+ startEditingShapeWithRichText(editor, shape, options)
67
+ return
68
+ }
69
+ editor.setEditingShape(shape)
70
+ editor.setCurrentTool('select.editing_shape', { ...options.info, target: 'shape', shape })
71
+ }
@@ -75,7 +75,6 @@ interface TldrawUiContentProps {
75
75
  const TldrawUiInner = React.memo(function TldrawUiInner({
76
76
  children,
77
77
  hideUi,
78
- ...rest
79
78
  }: TldrawUiContentProps & { children: ReactNode }) {
80
79
  // The hideUi prop should prevent the UI from mounting.
81
80
  // If we ever need want the UI to mount and preserve state, then
@@ -89,7 +88,7 @@ const TldrawUiInner = React.memo(function TldrawUiInner({
89
88
  return (
90
89
  <>
91
90
  {children}
92
- {hideUi ? null : <TldrawUiContent {...rest} />}
91
+ {hideUi ? null : <TldrawUiContent />}
93
92
  </>
94
93
  )
95
94
  })