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
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '5.4.0-next.ef99ab47e5ce'
4
+ export const version = '5.4.0'
5
5
  export const publishDates = {
6
6
  major: '2026-05-06T16:28:18.473Z',
7
- minor: '2026-08-05T11:52:36.664Z',
8
- patch: '2026-08-05T11:52:36.664Z',
7
+ minor: '2026-09-02T11:04:02.901Z',
8
+ patch: '2026-09-02T11:04:02.901Z',
9
9
  }
package/src/lib/ui.css CHANGED
@@ -2104,7 +2104,8 @@ tldraw? probably.
2104
2104
  align-items: center;
2105
2105
  }
2106
2106
 
2107
- .tlui-people-menu__user__color {
2107
+ .tlui-people-menu__user__color,
2108
+ .tlui-people-menu__user__edit {
2108
2109
  flex-shrink: 0;
2109
2110
  }
2110
2111
 
@@ -2150,7 +2151,8 @@ tldraw? probably.
2150
2151
  }
2151
2152
 
2152
2153
  .tlui-people-menu__item {
2153
- position: relative;
2154
+ display: flex;
2155
+ align-items: center;
2154
2156
  }
2155
2157
 
2156
2158
  .tlui-people-menu__item:last-of-type .tlui-button__menu {
@@ -2158,7 +2160,8 @@ tldraw? probably.
2158
2160
  }
2159
2161
 
2160
2162
  .tlui-people-menu__item__button {
2161
- padding: 0 11px;
2163
+ padding-inline-start: 12px;
2164
+ padding-inline-end: 0px;
2162
2165
  overflow: hidden;
2163
2166
  }
2164
2167
 
@@ -2168,7 +2171,7 @@ tldraw? probably.
2168
2171
  align-items: auto;
2169
2172
  justify-content: flex-start;
2170
2173
  flex-grow: 2;
2171
- gap: 11px;
2174
+ gap: 12px;
2172
2175
  }
2173
2176
 
2174
2177
  .tlui-people-menu__name {
@@ -2179,18 +2182,9 @@ tldraw? probably.
2179
2182
  }
2180
2183
 
2181
2184
  .tlui-people-menu__item__follow {
2182
- position: absolute;
2183
- top: 0px;
2184
- inset-inline-end: 0px;
2185
- max-width: 40px;
2186
2185
  flex-shrink: 0;
2187
2186
  }
2188
2187
 
2189
- .tlui-people-menu__item[data-follow='true'],
2190
- .tlui-people-menu__item:has(.tlui-button:focus-visible) {
2191
- padding-inline-end: 36px;
2192
- }
2193
-
2194
2188
  .tlui-people-menu__item[data-follow='true'] .tlui-people-menu__item__follow,
2195
2189
  .tlui-people-menu__item:has(.tlui-button:focus-visible) .tlui-people-menu__item__follow {
2196
2190
  opacity: 1;
@@ -2201,9 +2195,6 @@ tldraw? probably.
2201
2195
  opacity: 0;
2202
2196
  }
2203
2197
 
2204
- .tlui-people-menu__item:hover {
2205
- padding-right: 36px;
2206
- }
2207
2198
  .tlui-people-menu__item:hover .tlui-people-menu__item__follow {
2208
2199
  opacity: 1;
2209
2200
  }
@@ -263,6 +263,24 @@ it('Cancels when click moves', () => {
263
263
  expect(event.name).toBe('pointer_up')
264
264
  })
265
265
 
266
+ it('Does not cancel a double click on a small move when the container is offset', () => {
267
+ // click points are client coordinates; comparing them against the container-relative
268
+ // screen point used to cancel on any movement at all in an offset container
269
+ editor.setScreenBounds({ x: 300, y: 100, w: 1080, h: 720 })
270
+ const events: any[] = []
271
+ editor.addListener('event', (info) => events.push(info))
272
+
273
+ editor.pointerDown(500, 400).pointerMove(501, 400).pointerUp(501, 400).pointerDown(501, 400)
274
+
275
+ expect(events).toMatchObject([
276
+ { name: 'pointer_down' },
277
+ { name: 'pointer_move' },
278
+ { name: 'pointer_up' },
279
+ { name: 'pointer_down' },
280
+ { name: 'double_click', type: 'click', phase: 'down' },
281
+ ])
282
+ })
283
+
266
284
  it('Resets when the focus layer changes', () => {
267
285
  const boxId = editor.testShapeID('box')
268
286
  const otherBoxId = editor.testShapeID('otherBox')
@@ -913,6 +913,9 @@ describe('instance.isReadonly', () => {
913
913
  expect(editor.getIsReadonly()).toBe(false)
914
914
  mode.set('readonly')
915
915
  expect(editor.getIsReadonly()).toBe(true)
916
+
917
+ // syncing the mode is not a user edit, so it must not show up in the undo history
918
+ expect(editor.getCanUndo()).toBe(false)
916
919
  })
917
920
  })
918
921
 
@@ -411,6 +411,22 @@ describe('When clicking and dragging', () => {
411
411
  expect(editor.getShape(ids.box3)).not.toBeDefined()
412
412
  })
413
413
 
414
+ it.each([
415
+ ['exactly on the outline', 300],
416
+ ['a pixel inside the outline', 301],
417
+ ['a pixel outside the outline', 299],
418
+ ])('Keeps erasing a frame pressed %s when the drag begins', (_label, x) => {
419
+ editor.setCurrentTool('eraser')
420
+ editor.pointerDown(x, 50) // on frame1's left edge, not its children
421
+ expect(editor.getErasingShapeIds()).toEqual([ids.frame1])
422
+ editor.pointerMove(325, 50) // drag inward, still in the frame, clear of box4
423
+ vi.advanceTimersByTime(16)
424
+ editor.expectToBeIn('eraser.erasing')
425
+ expect(editor.getErasingShapeIds()).toEqual([ids.frame1])
426
+ editor.pointerUp()
427
+ expect(editor.getShape(ids.frame1)).not.toBeDefined()
428
+ })
429
+
414
430
  it('Only erases masked shapes when pointer is inside the mask', () => {
415
431
  editor.setCurrentTool('eraser')
416
432
  editor.pointerMove(425, 0)
@@ -423,8 +439,8 @@ describe('When clicking and dragging', () => {
423
439
  editor.pointerUp()
424
440
  expect(editor.getShape(ids.box3)).toBeDefined()
425
441
 
426
- editor.pointerMove(375, 0)
427
- editor.pointerDown() // Above the not-masked part of box3
442
+ editor.pointerMove(375, 20)
443
+ editor.pointerDown() // Inside frame1 (clear of its outline), above the not-masked part of box3
428
444
  editor.pointerMove(375, 500) // Through the masked part of box3
429
445
  expect(editor.getInstanceState().scribbles.length).toBe(1)
430
446
  expect(editor.getErasingShapeIds()).toEqual([ids.box3])
@@ -39,6 +39,27 @@ describe(HandTool, () => {
39
39
  expect(editor.getCamera().z).toBeGreaterThan(before)
40
40
  })
41
41
 
42
+ it('Ends one-finger zoom when a second touch starts, instead of jumping the zoom', () => {
43
+ editor.setCurrentTool('hand')
44
+ editor.updateInstanceState({ isCoarsePointer: true })
45
+
46
+ editor.pointerDown(100, 100).pointerUp(100, 100)
47
+ editor.pointerDown(100, 100)
48
+ vi.advanceTimersByTime(editor.options.multiClickDurationMs)
49
+ editor.expectToBeIn('hand.one_finger_zooming')
50
+
51
+ editor.pointerMove(100, 120)
52
+ const zoomBeforeSecondTouch = editor.getCamera().z
53
+ expect(zoomBeforeSecondTouch).toBeGreaterThan(1)
54
+
55
+ // A second finger lands far away and moves; the pan/zoom must yield rather than
56
+ // treat the new pointer as a continuation of the first one.
57
+ editor.pointerDown(300, 400, { pointerId: 2 })
58
+ editor.expectToBeIn('hand.idle')
59
+ editor.pointerMove(300, 420, { pointerId: 2 })
60
+ expect(editor.getCamera().z).toBe(zoomBeforeSecondTouch)
61
+ })
62
+
42
63
  it('Zooms in (not one-finger zoom) when a coarse pointer double-taps and releases', () => {
43
64
  editor.setCurrentTool('hand')
44
65
  editor.updateInstanceState({ isCoarsePointer: true })
@@ -1,3 +1,4 @@
1
+ import { PageRecordType } from '@tldraw/editor'
1
2
  import { vi } from 'vitest'
2
3
  import { TestEditor } from './TestEditor'
3
4
 
@@ -385,6 +386,51 @@ describe('LaserTool', () => {
385
386
  })
386
387
  })
387
388
 
389
+ describe('Page changes', () => {
390
+ it('clears the trail when the current page changes', () => {
391
+ editor.setCurrentTool('laser')
392
+
393
+ editor.pointerDown(0, 0)
394
+ for (let i = 1; i <= 10; i++) {
395
+ editor.pointerMove(i * 5, i * 5)
396
+ vi.advanceTimersByTime(16)
397
+ }
398
+ editor.pointerUp()
399
+ expect(editor.getInstanceState().scribbles.length).toBeGreaterThan(0)
400
+
401
+ const page2Id = PageRecordType.createId('page2')
402
+ editor.createPage({ id: page2Id, name: 'Page 2' })
403
+ editor.setCurrentPage(page2Id)
404
+ vi.advanceTimersByTime(16)
405
+
406
+ expect(editor.getInstanceState().scribbles).toEqual([])
407
+ })
408
+
409
+ it('starts a fresh session on the new page after a page change', () => {
410
+ editor.setCurrentTool('laser')
411
+
412
+ editor.pointerDown(0, 0)
413
+ editor.pointerMove(10, 10)
414
+ vi.advanceTimersByTime(16)
415
+ editor.pointerUp()
416
+
417
+ const page2Id = PageRecordType.createId('page2')
418
+ editor.createPage({ id: page2Id, name: 'Page 2' })
419
+ editor.setCurrentPage(page2Id)
420
+ vi.advanceTimersByTime(16)
421
+
422
+ editor.pointerDown(0, 50)
423
+ for (let i = 1; i <= 10; i++) {
424
+ editor.pointerMove(i * 5, 50 + i * 5)
425
+ vi.advanceTimersByTime(16)
426
+ }
427
+ editor.pointerUp()
428
+
429
+ // Only the stroke drawn on the new page is visible
430
+ expect(editor.getInstanceState().scribbles.length).toBe(1)
431
+ })
432
+ })
433
+
388
434
  describe('Edge cases', () => {
389
435
  it('handles rapid pointer up/down', () => {
390
436
  editor.setCurrentTool('laser')
@@ -1,3 +1,4 @@
1
+ import { PageRecordType } from '@tldraw/editor'
1
2
  import { vi } from 'vitest'
2
3
  import { TestEditor } from './TestEditor'
3
4
 
@@ -341,6 +342,24 @@ describe('ScribbleManager', () => {
341
342
 
342
343
  expect(editor.scribbles.isSessionActive(sessionId)).toBe(false)
343
344
  })
345
+
346
+ it('clears sessions started on another page when the current page changes', () => {
347
+ const sessionId = editor.scribbles.startSession({ selfConsume: false })
348
+ const item = editor.scribbles.addScribbleToSession(sessionId, {})
349
+ for (let i = 0; i < 10; i++) {
350
+ editor.scribbles.addPointToSession(sessionId, item.id, i * 5, i * 5)
351
+ editor.scribbles.tick(16)
352
+ }
353
+ expect(editor.getInstanceState().scribbles.length).toBe(1)
354
+
355
+ const page2Id = PageRecordType.createId('page2')
356
+ editor.createPage({ id: page2Id, name: 'Page 2' })
357
+ editor.setCurrentPage(page2Id)
358
+ editor.scribbles.tick(16)
359
+
360
+ expect(editor.getInstanceState().scribbles).toEqual([])
361
+ expect(editor.scribbles.isSessionActive(sessionId)).toBe(false)
362
+ })
344
363
  })
345
364
  })
346
365
 
@@ -3,6 +3,7 @@ import {
3
3
  ShapeUtil,
4
4
  TLArrowShape,
5
5
  TLFrameShape,
6
+ TLGeoShape,
6
7
  createShapeId,
7
8
  toRichText,
8
9
  } from '@tldraw/editor'
@@ -47,6 +48,13 @@ describe('TLSelectTool.Idle', () => {
47
48
  editor.expectToBeIn('select.pointing_canvas')
48
49
  })
49
50
 
51
+ it('Returns to idle when a canvas press is cancelled', () => {
52
+ editor.pointerDown(10, 10, { target: 'canvas' })
53
+ editor.expectToBeIn('select.pointing_canvas')
54
+ editor.cancel()
55
+ editor.expectToBeIn('select.idle')
56
+ })
57
+
50
58
  it('Nudges selected shapes on arrow key down', () => {
51
59
  const shape = editor.getShape(ids.box1)!
52
60
  editor.select(shape.id)
@@ -56,6 +64,47 @@ describe('TLSelectTool.Idle', () => {
56
64
  expect(nudgedShape).toBeDefined()
57
65
  expect(nudgedShape?.x).toBe(101)
58
66
  })
67
+
68
+ it('Does not nudge selected shapes on arrow key down while spacebar panning', () => {
69
+ const shape = editor.getShape(ids.box1)!
70
+ editor.select(shape.id)
71
+ editor.keyDown(' ')
72
+ expect(editor.inputs.getIsSpacebarPanning()).toBe(true)
73
+ editor.keyDown('ArrowRight')
74
+ editor.keyRepeat('ArrowRight')
75
+ editor.keyUp('ArrowRight')
76
+ editor.keyUp(' ')
77
+ expect(editor.getShape(shape.id)).toMatchObject({ x: 100, y: 100 })
78
+ })
79
+
80
+ it('Does not nudge selected shapes on arrow key down while alt is held', () => {
81
+ const shape = editor.getShape(ids.box1)!
82
+ editor.select(shape.id)
83
+ editor.keyDown('Alt')
84
+ editor.keyDown('ArrowRight')
85
+ editor.keyRepeat('ArrowRight')
86
+ editor.keyUp('ArrowRight')
87
+ editor.keyUp('Alt')
88
+ expect(editor.getShape(shape.id)).toMatchObject({ x: 100, y: 100 })
89
+ })
90
+
91
+ it('Makes a shape double click change its own undo step', () => {
92
+ const shape = editor.getShape<TLGeoShape>(ids.box1)!
93
+ expect(shape.props.geo).toBe('rectangle')
94
+
95
+ // Alt+double-click swaps a rectangle to a check-box via GeoShapeUtil.onDoubleClick
96
+ editor.keyDown('Alt')
97
+ editor.doubleClick(150, 150, { target: 'shape', shape }, { altKey: true })
98
+ editor.keyUp('Alt')
99
+
100
+ expect(editor.getShape<TLGeoShape>(ids.box1)!.props.geo).toBe('check-box')
101
+ expect(editor.getOnlySelectedShapeId()).toBe(ids.box1)
102
+
103
+ editor.undo()
104
+
105
+ expect(editor.getShape<TLGeoShape>(ids.box1)!.props.geo).toBe('rectangle')
106
+ expect(editor.getOnlySelectedShapeId()).toBe(ids.box1)
107
+ })
59
108
  })
60
109
 
61
110
  // todo: turn on feature flag for these tests or remove them
@@ -501,6 +550,42 @@ describe('PointingLabel', () => {
501
550
  editor.expectToBeIn('select.idle')
502
551
  })
503
552
 
553
+ it('Keeps the dragged label position on complete', () => {
554
+ editor.createShapes([
555
+ {
556
+ id: ids.arrow1,
557
+ type: 'arrow',
558
+ x: 100,
559
+ y: 100,
560
+ props: {
561
+ richText: toRichText('Test Label'),
562
+ start: { x: 0, y: 0 },
563
+ end: { x: 100, y: 0 },
564
+ },
565
+ },
566
+ ])
567
+ const shape = editor.getShape<TLArrowShape>(ids.arrow1)!
568
+ const initialLabelPosition = shape.props.labelPosition
569
+
570
+ editor.pointerDown(150, 100, {
571
+ target: 'shape',
572
+ shape,
573
+ })
574
+ editor.pointerMove(160, 100)
575
+ editor.expectToBeIn('select.pointing_arrow_label')
576
+ editor.pointerMove(190, 100)
577
+
578
+ const draggedLabelPosition = editor.getShape<TLArrowShape>(ids.arrow1)!.props.labelPosition
579
+ expect(draggedLabelPosition).not.toBe(initialLabelPosition)
580
+
581
+ // A menu opening or an undo keypress mid-drag completes the interaction
582
+ editor.complete()
583
+ editor.expectToBeIn('select.idle')
584
+ expect(editor.getShape<TLArrowShape>(ids.arrow1)!.props.labelPosition).toBe(
585
+ draggedLabelPosition
586
+ )
587
+ })
588
+
504
589
  it('Doesnt go into pointing_arrow_label mode if not selecting the arrow shape', () => {
505
590
  editor.createShapes([
506
591
  {
@@ -744,6 +829,31 @@ describe('When double clicking a selection handle that registers as a canvas eve
744
829
 
745
830
  expect(overlayEditor.getShape<TLArrowShape>(id)!.props.arrowheadEnd).toBe('arrow')
746
831
  })
832
+
833
+ it('Undoes an arrowhead toggled by handle double-click without undoing the selection', () => {
834
+ const id = createShapeId()
835
+ overlayEditor
836
+ .createShapes([
837
+ {
838
+ id,
839
+ type: 'arrow',
840
+ x: 100,
841
+ y: 100,
842
+ props: { start: { x: 0, y: 0 }, end: { x: 100, y: 100 } },
843
+ },
844
+ ])
845
+ .selectNone()
846
+ overlayEditor.markHistoryStoppingPoint('before selecting arrow')
847
+ overlayEditor.select(id)
848
+
849
+ overlayEditor.doubleClick(200, 200)
850
+ expect(overlayEditor.getShape<TLArrowShape>(id)!.props.arrowheadEnd).toBe('none')
851
+
852
+ overlayEditor.undo()
853
+
854
+ expect(overlayEditor.getShape<TLArrowShape>(id)!.props.arrowheadEnd).toBe('arrow')
855
+ expect(overlayEditor.getSelectedShapeIds()).toEqual([id])
856
+ })
747
857
  })
748
858
 
749
859
  describe('When editing shapes', () => {
@@ -13,6 +13,7 @@ import {
13
13
  createTLStore,
14
14
  noop,
15
15
  react,
16
+ tleditors,
16
17
  toRichText,
17
18
  } from '@tldraw/editor'
18
19
  import { StrictMode } from 'react'
@@ -95,6 +96,21 @@ describe('<TldrawEditor />', () => {
95
96
  )
96
97
  })
97
98
 
99
+ it('runs the teardown returned by the store onMount option when unmounted', async () => {
100
+ const teardown = vi.fn()
101
+ const storeOnMount = vi.fn(() => teardown)
102
+ const store = createTLStore({ shapeUtils: [], bindingUtils: [], onMount: storeOnMount })
103
+ const rendered = await renderTldrawComponent(
104
+ <TldrawEditor store={store} tools={defaultTools} initialState="select" />,
105
+ { waitForPatterns: false }
106
+ )
107
+ expect(storeOnMount).toHaveBeenCalledTimes(1)
108
+ expect(teardown).not.toHaveBeenCalled()
109
+
110
+ act(() => rendered.unmount())
111
+ expect(teardown).toHaveBeenCalledTimes(1)
112
+ })
113
+
98
114
  it('throws if the store has different shapes to the ones passed in', async () => {
99
115
  const spy = vi.spyOn(console, 'error').mockImplementation(noop)
100
116
  // expect(() =>
@@ -181,6 +197,7 @@ describe('<TldrawEditor />', () => {
181
197
 
182
198
  // mounted after render:
183
199
  expect(editor.getIsMounted()).toBe(true)
200
+ expect(tleditors.getMounted()).toEqual([editor])
184
201
 
185
202
  // getIsMounted is reactive:
186
203
  const mountedValues: boolean[] = []
@@ -191,6 +208,7 @@ describe('<TldrawEditor />', () => {
191
208
  // the unmount event fired and the mounted state flipped back to false:
192
209
  expect(onUnmount).toHaveBeenCalledTimes(1)
193
210
  expect(editor.getIsMounted()).toBe(false)
211
+ expect(tleditors.getMounted()).toEqual([])
194
212
  expect(mountedValues).toEqual([true, false])
195
213
 
196
214
  stop()
@@ -0,0 +1,126 @@
1
+ import { act, fireEvent } from '@testing-library/react'
2
+ import { AssetRecordType, Editor, TLAssetId, TLShapeId, createShapeId } from '@tldraw/editor'
3
+ import { Tldraw } from '../lib/Tldraw'
4
+ import { renderTldrawComponentWithEditor } from './testutils/renderTldrawComponent'
5
+
6
+ const LOADING_BACKGROUND = 'var(--tl-color-low)'
7
+ const LOADING_BORDER = '1px solid var(--tl-color-low-border)'
8
+
9
+ let editor: Editor
10
+ let videoShapeId: TLShapeId
11
+ let videoAssetId: TLAssetId
12
+
13
+ function getShapeContainer() {
14
+ const container = document.getElementById(videoShapeId)
15
+ expect(container).toBeTruthy()
16
+ return container!
17
+ }
18
+
19
+ beforeEach(async () => {
20
+ videoShapeId = createShapeId('video')
21
+ videoAssetId = AssetRecordType.createId('videoAsset')
22
+
23
+ const result = await renderTldrawComponentWithEditor((onMount) => <Tldraw onMount={onMount} />, {
24
+ waitForPatterns: false,
25
+ })
26
+ editor = result.editor
27
+ })
28
+
29
+ describe('VideoShapeUtil loading state', () => {
30
+ it('shows the loading plate when the asset record is missing', async () => {
31
+ await act(async () => {
32
+ editor.createShapes([
33
+ { id: videoShapeId, type: 'video', x: 0, y: 0, props: { w: 100, h: 100 } },
34
+ ])
35
+ })
36
+
37
+ const container = getShapeContainer()
38
+ expect(container.style.backgroundColor).toBe(LOADING_BACKGROUND)
39
+ expect(container.style.border).toBe(LOADING_BORDER)
40
+ })
41
+
42
+ it('shows the loading plate while the asset is uploading (no src)', async () => {
43
+ await act(async () => {
44
+ editor.createAssets([
45
+ {
46
+ id: videoAssetId,
47
+ type: 'video',
48
+ typeName: 'asset',
49
+ props: {
50
+ w: 100,
51
+ h: 100,
52
+ name: 'video.mp4',
53
+ isAnimated: true,
54
+ mimeType: 'video/mp4',
55
+ src: '',
56
+ },
57
+ meta: {},
58
+ },
59
+ ])
60
+ editor.createShapes([
61
+ {
62
+ id: videoShapeId,
63
+ type: 'video',
64
+ x: 0,
65
+ y: 0,
66
+ props: { w: 100, h: 100, assetId: videoAssetId },
67
+ },
68
+ ])
69
+ })
70
+
71
+ const container = getShapeContainer()
72
+ expect(container.style.backgroundColor).toBe(LOADING_BACKGROUND)
73
+ expect(container.style.border).toBe(LOADING_BORDER)
74
+ })
75
+
76
+ it('shows the loading plate until the video loads, then goes transparent', async () => {
77
+ await act(async () => {
78
+ editor.createAssets([
79
+ {
80
+ id: videoAssetId,
81
+ type: 'video',
82
+ typeName: 'asset',
83
+ props: {
84
+ w: 100,
85
+ h: 100,
86
+ name: 'video.mp4',
87
+ isAnimated: true,
88
+ mimeType: 'video/mp4',
89
+ src: 'http://localhost/video.mp4',
90
+ },
91
+ meta: {},
92
+ },
93
+ ])
94
+ editor.createShapes([
95
+ {
96
+ id: videoShapeId,
97
+ type: 'video',
98
+ x: 0,
99
+ y: 0,
100
+ props: { w: 100, h: 100, assetId: videoAssetId },
101
+ },
102
+ ])
103
+ })
104
+
105
+ // Wait for the async asset URL resolution so the <video> element renders
106
+ await act(async () => {
107
+ await new Promise((resolve) => setTimeout(resolve, 0))
108
+ })
109
+
110
+ const container = getShapeContainer()
111
+ const video = container.querySelector('video')
112
+ expect(video).toBeTruthy()
113
+
114
+ // The video element exists but hasn't fired loadeddata yet
115
+ expect(container.style.backgroundColor).toBe(LOADING_BACKGROUND)
116
+ expect(container.style.border).toBe(LOADING_BORDER)
117
+
118
+ await act(async () => {
119
+ fireEvent.loadedData(video!)
120
+ })
121
+
122
+ expect(container.style.backgroundColor).toBe('transparent')
123
+ // jsdom doesn't round-trip the `border: none` shorthand, so check the style directly
124
+ expect(container.style.borderStyle).toBe('none')
125
+ })
126
+ })
@@ -0,0 +1,37 @@
1
+ import { act } from '@testing-library/react'
2
+ import { Editor } from '@tldraw/editor'
3
+ import { Tldraw } from '../lib/Tldraw'
4
+ import { renderTldrawComponentWithEditor } from './testutils/renderTldrawComponent'
5
+
6
+ let editor: Editor
7
+ let rendered: Awaited<ReturnType<typeof renderTldrawComponentWithEditor>>['rendered']
8
+
9
+ beforeEach(async () => {
10
+ // Pin the quick actions into the menu panel so the test does not depend on jsdom's viewport width
11
+ ;({ editor, rendered } = await renderTldrawComponentWithEditor(
12
+ (onMount) => <Tldraw onMount={onMount} options={{ actionShortcutsLocation: 'menu' }} />,
13
+ { waitForPatterns: true }
14
+ ))
15
+ })
16
+
17
+ afterEach(() => {
18
+ editor?.dispose()
19
+ })
20
+
21
+ describe('actions menu in readonly mode', () => {
22
+ it('shows the actions menu button when not readonly', async () => {
23
+ expect(await rendered.findByTestId('actions-menu.button')).toBeTruthy()
24
+ })
25
+
26
+ it.each(['select', 'hand', 'zoom', 'eraser'])(
27
+ 'hides the actions menu button in readonly mode with the %s tool',
28
+ async (toolId) => {
29
+ act(() => {
30
+ editor.updateInstanceState({ isReadonly: true })
31
+ editor.setCurrentTool(toolId)
32
+ })
33
+ expect(editor.getCurrentToolId()).toBe(toolId)
34
+ expect(rendered.queryByTestId('actions-menu.button')).toBeNull()
35
+ }
36
+ )
37
+ })