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
@@ -0,0 +1,50 @@
1
+ import { VecModel } from '@tldraw/editor'
2
+ import { StrokeOptions } from '../../../lib/shapes/shared/freehand/types'
3
+ import {
4
+ highlightSettings,
5
+ realPressureSettings,
6
+ simulatePressureSettings,
7
+ solidSettings,
8
+ } from './presets'
9
+ import { REAL_STROKES } from './real'
10
+
11
+ /** A single comparison case: recorded input points plus the options tldraw would use. */
12
+ export interface CorpusCase {
13
+ id: string
14
+ /** What kind of tldraw stroke this represents. */
15
+ kind: 'draw' | 'pen' | 'solid' | 'highlight'
16
+ points: VecModel[]
17
+ options: StrokeOptions
18
+ }
19
+
20
+ function makeCases(): CorpusCase[] {
21
+ const cases: CorpusCase[] = []
22
+
23
+ const add = (
24
+ id: string,
25
+ kind: CorpusCase['kind'],
26
+ points: VecModel[],
27
+ options: StrokeOptions,
28
+ last = true
29
+ ) => {
30
+ cases.push({ id, kind, points, options: { ...options, last } })
31
+ }
32
+
33
+ // All strokes were drawn with a mouse at size 'xl' and scale 1, so tldraw passes
34
+ // strokeWidth = (theme.strokeWidth 2 * STROKE_SIZES.xl 5 + 1) * scale 1 = 11. Each
35
+ // stroke is also rendered mid-draw and through the other stroke kinds' settings, so
36
+ // every rendering path sees real input.
37
+ REAL_STROKES.forEach(({ points }, i) => {
38
+ const id = `real-${i + 1}`
39
+ add(id, 'draw', points, simulatePressureSettings(11))
40
+ add(`${id}-in-progress`, 'draw', points, simulatePressureSettings(11), false)
41
+ add(`${id}-pen`, 'pen', points, realPressureSettings(11))
42
+ add(`${id}-solid`, 'solid', points, solidSettings(11))
43
+ add(`${id}-highlight`, 'highlight', points, highlightSettings(22))
44
+ })
45
+
46
+ return cases
47
+ }
48
+
49
+ /** The full corpus of comparison cases: real hand-drawn strokes recorded in tldraw. */
50
+ export const CORPUS: CorpusCase[] = makeCases()
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { getSvgPathFromStrokePoints } from '../../lib/shapes/shared/freehand/svg'
3
+ import { StrokePoint } from '../../lib/shapes/shared/freehand/types'
4
+
5
+ function pt(x: number, y: number): StrokePoint {
6
+ return { point: { x, y } } as StrokePoint
7
+ }
8
+
9
+ // The corpus never produces a two-point stroke, so the parity gate doesn't
10
+ // cover this branch; keep a direct check on its output.
11
+ describe('getSvgPathFromStrokePoints', () => {
12
+ it('renders a two-point stroke as a single relative line', () => {
13
+ expect(getSvgPathFromStrokePoints([pt(0, 0), pt(10.5, 20.25)])).toBe('M0,0 l10.5,20.25 ')
14
+ })
15
+
16
+ it('renders nothing for fewer than two points', () => {
17
+ expect(getSvgPathFromStrokePoints([])).toBe('')
18
+ expect(getSvgPathFromStrokePoints([pt(5, 5)])).toBe('')
19
+ })
20
+ })
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { getStrokePoints } from '../../lib/shapes/shared/freehand/getStrokePoints'
3
+ import { getSvgPathFromStrokePoints } from '../../lib/shapes/shared/freehand/svg'
4
+ import { svgInk } from '../../lib/shapes/shared/freehand/svgInk'
5
+ import { CORPUS, CorpusCase } from './corpus/strokes'
6
+
7
+ /**
8
+ * Produce the svg path data tldraw would actually render for this case. Strokes with the 'draw'
9
+ * dash style (mouse or pen) are rendered as a filled outline via svgInk; solid strokes and the
10
+ * highlighter are rendered as a stroked centerline path.
11
+ */
12
+ function renderCase(c: CorpusCase): string {
13
+ if (c.kind === 'draw' || c.kind === 'pen') {
14
+ return svgInk(c.points, c.options)
15
+ }
16
+ return getSvgPathFromStrokePoints(getStrokePoints(c.points, c.options), false)
17
+ }
18
+
19
+ // The unit tests on the individual functions can't catch a malformed path string coming out of
20
+ // the full pipeline, so check the path data tldraw would render for every corpus case.
21
+ describe('svg path data is well formed', () => {
22
+ it.each(CORPUS.map((c) => [c.id, c] as const))('%s', (_id, c) => {
23
+ const svg = renderCase(c)
24
+ expect(svg.startsWith('M')).toBe(true)
25
+ expect(svg).not.toContain('NaN')
26
+ expect(svg).not.toContain('Infinity')
27
+ // only valid path characters
28
+ expect(svg).toMatch(/^[MLQTACZmlqtacz0-9 ,.-]+$/)
29
+ })
30
+ })
@@ -0,0 +1,102 @@
1
+ import { vi } from 'vitest'
2
+ import { TestEditor } from './TestEditor'
3
+
4
+ let editor: TestEditor
5
+
6
+ beforeEach(() => {
7
+ editor = new TestEditor()
8
+ })
9
+ afterEach(() => {
10
+ editor?.dispose()
11
+ })
12
+
13
+ vi.useFakeTimers()
14
+
15
+ // On a touch device a long press fires the browser `contextmenu` event. A discrete
16
+ // shape-creation tool (geo, note, line, text, arrow, frame) should respond by
17
+ // canceling any pending creation and returning to its own idle state, so the long
18
+ // press leaves no shape behind (#8277) and opens no menu — the context menu is a
19
+ // select-tool surface, reached on touch only from the select tool. The guard is
20
+ // `isCoarsePointer`, so fine-pointer (desktop) behavior must be preserved.
21
+ // Continuous-gesture tools (draw, highlight, eraser, laser) are excluded: their
22
+ // press is their own action, so a long press keeps that gesture going.
23
+ const CREATION_TOOLS = [
24
+ { tool: 'geo', pointingState: 'geo.pointing' },
25
+ { tool: 'note', pointingState: 'note.pointing' },
26
+ { tool: 'text', pointingState: 'text.pointing' },
27
+ { tool: 'line', pointingState: 'line.pointing' },
28
+ { tool: 'arrow', pointingState: 'arrow.pointing' },
29
+ { tool: 'frame', pointingState: 'frame.pointing' },
30
+ ] as const
31
+
32
+ describe('long press on shape-creation tools', () => {
33
+ describe('with a coarse pointer', () => {
34
+ beforeEach(() => {
35
+ editor.updateInstanceState({ isCoarsePointer: true })
36
+ })
37
+
38
+ it.each(CREATION_TOOLS)(
39
+ '$tool cancels creation and returns to its idle state, leaving no shape behind',
40
+ ({ tool }) => {
41
+ editor.setCurrentTool(tool)
42
+ editor.pointerDown(100, 100)
43
+ vi.advanceTimersByTime(editor.options.longPressDurationMs)
44
+
45
+ // back in the tool's own idle — not switched to select, no menu
46
+ editor.expectToBeIn(`${tool}.idle`)
47
+ expect(editor.getCurrentPageShapes()).toHaveLength(0)
48
+
49
+ // releasing the long press must not create a shape either
50
+ editor.pointerUp(100, 100)
51
+ expect(editor.getCurrentPageShapes()).toHaveLength(0)
52
+ }
53
+ )
54
+
55
+ it('continuous-gesture tools keep their gesture on long press', () => {
56
+ // Unlike the discrete tools, a freehand tool starts a stroke on
57
+ // pointer-down, so the long press is part of the stroke. It must not
58
+ // cancel — the stroke continues.
59
+ editor.setCurrentTool('draw')
60
+ editor.pointerDown(100, 100)
61
+ editor.expectToBeIn('draw.drawing')
62
+
63
+ vi.advanceTimersByTime(editor.options.longPressDurationMs)
64
+
65
+ editor.expectToBeIn('draw.drawing')
66
+ editor.pointerUp(100, 100)
67
+ expect(editor.getCurrentPageShapes()).toHaveLength(1)
68
+ })
69
+
70
+ it('defers note creation so a long press never flashes a note', () => {
71
+ // The note creates on press, so without deferral the long-press cancel
72
+ // would briefly show then remove it. On a coarse pointer it is created on
73
+ // release (or drag) instead, so nothing appears during the press.
74
+ editor.setCurrentTool('note')
75
+ editor.pointerDown(100, 100)
76
+ expect(editor.getCurrentPageShapes()).toHaveLength(0)
77
+
78
+ // a tap still creates the note on release
79
+ editor.pointerUp(100, 100)
80
+ const shapes = editor.getCurrentPageShapes()
81
+ expect(shapes).toHaveLength(1)
82
+ expect(shapes[0].type).toBe('note')
83
+ })
84
+ })
85
+
86
+ describe('with a fine pointer, the long press is ignored', () => {
87
+ beforeEach(() => {
88
+ editor.updateInstanceState({ isCoarsePointer: false })
89
+ })
90
+
91
+ it.each(CREATION_TOOLS)('$tool stays in $pointingState', ({ tool, pointingState }) => {
92
+ editor.setCurrentTool(tool)
93
+ editor.pointerDown(100, 100)
94
+ editor.expectToBeIn(pointingState)
95
+
96
+ vi.advanceTimersByTime(editor.options.longPressDurationMs)
97
+
98
+ // desktop behavior is preserved: the tool is still mid-creation
99
+ editor.expectToBeIn(pointingState)
100
+ })
101
+ })
102
+ })
@@ -1,4 +1,4 @@
1
- import { InstancePresenceRecordType } from '@tldraw/tlschema'
1
+ import { createUserId, InstancePresenceRecordType } from '@tldraw/tlschema'
2
2
  import { defaultOverlayUtils } from '../../lib/defaultOverlayUtils'
3
3
  import { CollaboratorBrushOverlayUtil } from '../../lib/overlays/CollaboratorBrushOverlayUtil'
4
4
  import { TestEditor } from '../TestEditor'
@@ -22,7 +22,7 @@ describe('CollaboratorBrushOverlayUtil', () => {
22
22
  editor.store.put([
23
23
  InstancePresenceRecordType.create({
24
24
  id: InstancePresenceRecordType.createId('peer1'),
25
- userId: 'peer1',
25
+ userId: createUserId('peer1'),
26
26
  userName: 'Peer 1',
27
27
  currentPageId: pageId,
28
28
  brush: { x: 1, y: 2, w: 3, h: 4 },
@@ -46,7 +46,7 @@ describe('CollaboratorBrushOverlayUtil', () => {
46
46
  editor.store.put([
47
47
  InstancePresenceRecordType.create({
48
48
  id: InstancePresenceRecordType.createId('peer1'),
49
- userId: 'peer1',
49
+ userId: createUserId('peer1'),
50
50
  userName: 'Peer 1',
51
51
  currentPageId: pageId,
52
52
  color: '#123456',
@@ -54,7 +54,7 @@ describe('CollaboratorBrushOverlayUtil', () => {
54
54
  }),
55
55
  InstancePresenceRecordType.create({
56
56
  id: InstancePresenceRecordType.createId('peer2'),
57
- userId: 'peer2',
57
+ userId: createUserId('peer2'),
58
58
  userName: 'Peer 2',
59
59
  currentPageId: pageId,
60
60
  color: '#abcdef',
@@ -1,4 +1,4 @@
1
- import { InstancePresenceRecordType } from '@tldraw/tlschema'
1
+ import { createUserId, InstancePresenceRecordType } from '@tldraw/tlschema'
2
2
  import { vi } from 'vitest'
3
3
  import { defaultOverlayUtils } from '../../lib/defaultOverlayUtils'
4
4
  import { CollaboratorCursorOverlayUtil } from '../../lib/overlays/CollaboratorCursorOverlayUtil'
@@ -73,7 +73,7 @@ describe('CollaboratorCursorOverlayUtil', () => {
73
73
  editor.store.put([
74
74
  InstancePresenceRecordType.create({
75
75
  id: InstancePresenceRecordType.createId('peer1'),
76
- userId: 'peer1',
76
+ userId: createUserId('peer1'),
77
77
  userName: 'Peer 1',
78
78
  currentPageId: pageId,
79
79
  cursor: { type: 'default', x: 10, y: 10, rotation: 0 },
@@ -98,7 +98,7 @@ describe('CollaboratorCursorOverlayUtil', () => {
98
98
  editor.store.put([
99
99
  InstancePresenceRecordType.create({
100
100
  id: InstancePresenceRecordType.createId('peer1'),
101
- userId: 'peer1',
101
+ userId: createUserId('peer1'),
102
102
  userName: 'Alice',
103
103
  currentPageId: pageId,
104
104
  color: '#a',
@@ -107,7 +107,7 @@ describe('CollaboratorCursorOverlayUtil', () => {
107
107
  }),
108
108
  InstancePresenceRecordType.create({
109
109
  id: InstancePresenceRecordType.createId('peer2'),
110
- userId: 'peer2',
110
+ userId: createUserId('peer2'),
111
111
  userName: 'Bob',
112
112
  currentPageId: pageId,
113
113
  color: '#b',
@@ -130,7 +130,7 @@ describe('CollaboratorCursorOverlayUtil', () => {
130
130
  editor.store.put([
131
131
  InstancePresenceRecordType.create({
132
132
  id: InstancePresenceRecordType.createId('peer3'),
133
- userId: 'peer3',
133
+ userId: createUserId('peer3'),
134
134
  userName: 'Carol',
135
135
  currentPageId: pageId,
136
136
  color: '#c',
@@ -150,7 +150,7 @@ describe('CollaboratorCursorOverlayUtil', () => {
150
150
  // inactive (way past timeout) — not shown
151
151
  InstancePresenceRecordType.create({
152
152
  id: InstancePresenceRecordType.createId('p_inactive'),
153
- userId: 'p_inactive',
153
+ userId: createUserId('p_inactive'),
154
154
  userName: 'Inactive',
155
155
  currentPageId: pageId,
156
156
  cursor: { type: 'default', x: 10, y: 10, rotation: 0 },
@@ -159,7 +159,7 @@ describe('CollaboratorCursorOverlayUtil', () => {
159
159
  // idle (between thresholds) — shown
160
160
  InstancePresenceRecordType.create({
161
161
  id: InstancePresenceRecordType.createId('p_idle'),
162
- userId: 'p_idle',
162
+ userId: createUserId('p_idle'),
163
163
  userName: 'Idle',
164
164
  currentPageId: pageId,
165
165
  cursor: { type: 'default', x: 15, y: 15, rotation: 0 },
@@ -168,7 +168,7 @@ describe('CollaboratorCursorOverlayUtil', () => {
168
168
  // active — shown
169
169
  InstancePresenceRecordType.create({
170
170
  id: InstancePresenceRecordType.createId('p_active'),
171
- userId: 'p_active',
171
+ userId: createUserId('p_active'),
172
172
  userName: 'Active',
173
173
  currentPageId: pageId,
174
174
  cursor: { type: 'default', x: 20, y: 20, rotation: 0 },
@@ -184,12 +184,12 @@ describe('CollaboratorCursorOverlayUtil', () => {
184
184
 
185
185
  it('shows inactive collaborators when following them', () => {
186
186
  const pageId = editor.getCurrentPageId()
187
- editor.updateInstanceState({ followingUserId: 'peerF' })
187
+ editor.updateInstanceState({ followingUserId: createUserId('peerF') })
188
188
  const now = Date.now()
189
189
  editor.store.put([
190
190
  InstancePresenceRecordType.create({
191
191
  id: InstancePresenceRecordType.createId('presenceF'),
192
- userId: 'peerF',
192
+ userId: createUserId('peerF'),
193
193
  userName: 'Followed',
194
194
  currentPageId: pageId,
195
195
  cursor: { type: 'default', x: 10, y: 10, rotation: 0 },
@@ -204,12 +204,12 @@ describe('CollaboratorCursorOverlayUtil', () => {
204
204
 
205
205
  it('shows inactive collaborators when they are highlighted', () => {
206
206
  const pageId = editor.getCurrentPageId()
207
- editor.updateInstanceState({ highlightedUserIds: ['peerH'] })
207
+ editor.updateInstanceState({ highlightedUserIds: [createUserId('peerH')] })
208
208
  const now = Date.now()
209
209
  editor.store.put([
210
210
  InstancePresenceRecordType.create({
211
211
  id: InstancePresenceRecordType.createId('presenceH'),
212
- userId: 'peerH',
212
+ userId: createUserId('peerH'),
213
213
  userName: 'Highlighted',
214
214
  currentPageId: pageId,
215
215
  cursor: { type: 'default', x: 20, y: 20, rotation: 0 },
@@ -228,12 +228,12 @@ describe('CollaboratorCursorOverlayUtil', () => {
228
228
  editor.store.put([
229
229
  InstancePresenceRecordType.create({
230
230
  id: InstancePresenceRecordType.createId('presenceFU'),
231
- userId: 'peerFU',
231
+ userId: createUserId('peerFU'),
232
232
  userName: 'Follower',
233
233
  currentPageId: pageId,
234
234
  cursor: { type: 'default', x: 30, y: 30, rotation: 0 },
235
235
  lastActivityTimestamp: now - 5 * 1000,
236
- followingUserId: editor.user.getId(),
236
+ followingUserId: editor.user.getRecordId(),
237
237
  }),
238
238
  ])
239
239
  const overlays = editor.overlays
@@ -248,12 +248,12 @@ describe('CollaboratorCursorOverlayUtil', () => {
248
248
  editor.store.put([
249
249
  InstancePresenceRecordType.create({
250
250
  id: InstancePresenceRecordType.createId('presenceChat'),
251
- userId: 'peerChat',
251
+ userId: createUserId('peerChat'),
252
252
  userName: 'Follower Chat',
253
253
  currentPageId: pageId,
254
254
  cursor: { type: 'default', x: 40, y: 40, rotation: 0 },
255
255
  lastActivityTimestamp: now - 5 * 1000,
256
- followingUserId: editor.user.getId(),
256
+ followingUserId: editor.user.getRecordId(),
257
257
  chatMessage: 'Hello',
258
258
  }),
259
259
  ])
@@ -268,7 +268,7 @@ describe('CollaboratorCursorOverlayUtil', () => {
268
268
  editor.store.put([
269
269
  InstancePresenceRecordType.create({
270
270
  id: InstancePresenceRecordType.createId('peer4'),
271
- userId: 'peer4',
271
+ userId: createUserId('peer4'),
272
272
  userName: 'New User',
273
273
  currentPageId: pageId,
274
274
  cursor: { type: 'default', x: 10, y: 10, rotation: 0 },
@@ -290,7 +290,7 @@ describe('CollaboratorCursorOverlayUtil', () => {
290
290
  timedEditor.store.put([
291
291
  InstancePresenceRecordType.create({
292
292
  id: InstancePresenceRecordType.createId('peer_timed'),
293
- userId: 'peer_timed',
293
+ userId: createUserId('peer_timed'),
294
294
  userName: 'Timed Peer',
295
295
  currentPageId: pageId,
296
296
  cursor: { type: 'default', x: 10, y: 10, rotation: 0 },
@@ -1,4 +1,4 @@
1
- import { InstancePresenceRecordType } from '@tldraw/tlschema'
1
+ import { createUserId, InstancePresenceRecordType } from '@tldraw/tlschema'
2
2
  import { defaultOverlayUtils } from '../../lib/defaultOverlayUtils'
3
3
  import { CollaboratorHintOverlayUtil } from '../../lib/overlays/CollaboratorHintOverlayUtil'
4
4
  import { TestEditor } from '../TestEditor'
@@ -22,7 +22,7 @@ describe('CollaboratorHintOverlayUtil', () => {
22
22
  editor.store.put([
23
23
  InstancePresenceRecordType.create({
24
24
  id: InstancePresenceRecordType.createId('peer1'),
25
- userId: 'peer1',
25
+ userId: createUserId('peer1'),
26
26
  userName: 'Peer 1',
27
27
  currentPageId: pageId,
28
28
  cursor: { type: 'default', x: 100000, y: 100000, rotation: 0 },
@@ -46,7 +46,7 @@ describe('CollaboratorHintOverlayUtil', () => {
46
46
  editor.store.put([
47
47
  InstancePresenceRecordType.create({
48
48
  id: InstancePresenceRecordType.createId('peer1'),
49
- userId: 'peer1',
49
+ userId: createUserId('peer1'),
50
50
  userName: 'Peer 1',
51
51
  currentPageId: pageId,
52
52
  color: '#c00',
@@ -55,7 +55,7 @@ describe('CollaboratorHintOverlayUtil', () => {
55
55
  }),
56
56
  InstancePresenceRecordType.create({
57
57
  id: InstancePresenceRecordType.createId('peer2'),
58
- userId: 'peer2',
58
+ userId: createUserId('peer2'),
59
59
  userName: 'Peer 2',
60
60
  currentPageId: pageId,
61
61
  color: '#0c0',
@@ -77,7 +77,7 @@ describe('CollaboratorHintOverlayUtil', () => {
77
77
  editor.store.put([
78
78
  InstancePresenceRecordType.create({
79
79
  id: InstancePresenceRecordType.createId('peer1'),
80
- userId: 'peer1',
80
+ userId: createUserId('peer1'),
81
81
  userName: 'Peer 1',
82
82
  currentPageId: pageId,
83
83
  color: '#c00',
@@ -95,13 +95,13 @@ describe('CollaboratorHintOverlayUtil', () => {
95
95
  editor.store.put([
96
96
  InstancePresenceRecordType.create({
97
97
  id: InstancePresenceRecordType.createId('peer1'),
98
- userId: 'peer1',
98
+ userId: createUserId('peer1'),
99
99
  userName: 'Peer 1',
100
100
  currentPageId: pageId,
101
101
  color: '#c00',
102
102
  cursor: { type: 'default', x: 100000, y: 100000, rotation: 0 },
103
103
  lastActivityTimestamp: now - 5 * 1000,
104
- followingUserId: editor.user.getId(),
104
+ followingUserId: editor.user.getRecordId(),
105
105
  }),
106
106
  ])
107
107
  const util = editor.overlays.getOverlayUtil<CollaboratorHintOverlayUtil>('collaborator_hint')
@@ -1,4 +1,4 @@
1
- import { InstancePresenceRecordType } from '@tldraw/tlschema'
1
+ import { createUserId, InstancePresenceRecordType } from '@tldraw/tlschema'
2
2
  import { defaultOverlayUtils } from '../../lib/defaultOverlayUtils'
3
3
  import { CollaboratorScribbleOverlayUtil } from '../../lib/overlays/CollaboratorScribbleOverlayUtil'
4
4
  import { TestEditor } from '../TestEditor'
@@ -22,7 +22,7 @@ describe('CollaboratorScribbleOverlayUtil', () => {
22
22
  editor.store.put([
23
23
  InstancePresenceRecordType.create({
24
24
  id: InstancePresenceRecordType.createId('peer1'),
25
- userId: 'peer1',
25
+ userId: createUserId('peer1'),
26
26
  userName: 'Peer 1',
27
27
  currentPageId: pageId,
28
28
  scribbles: [
@@ -58,7 +58,7 @@ describe('CollaboratorScribbleOverlayUtil', () => {
58
58
  editor.store.put([
59
59
  InstancePresenceRecordType.create({
60
60
  id: InstancePresenceRecordType.createId('peer1'),
61
- userId: 'peer1',
61
+ userId: createUserId('peer1'),
62
62
  userName: 'Peer 1',
63
63
  currentPageId: pageId,
64
64
  color: '#00FF00',
@@ -101,7 +101,7 @@ describe('OverlayManager', () => {
101
101
  it('hits the bounded edge handle hit area regardless of margin', () => {
102
102
  editor.createShapes([{ id: ids.box1, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } }])
103
103
  editor.select(ids.box1)
104
- // Edge handles are filled polygons of half-thickness targetSizeY (~4.5px at zoom 1);
104
+ // Edge handles are filled polygons of half-thickness hitTargetSizeY (~4.5px at zoom 1);
105
105
  // a point inside that band hits with margin 0, and a point outside misses.
106
106
  const insideTopEdge = { x: 50, y: -3 }
107
107
  const outsideTopEdge = { x: 50, y: -20 }
@@ -93,6 +93,22 @@ describe('SelectionForegroundOverlayUtil', () => {
93
93
  expect(idsSet.has('selection_fg:bottom_right_rotate')).toBe(true)
94
94
  })
95
95
 
96
+ it.each(['top_left', 'top', 'top_left_rotate'] as const)(
97
+ 'keeps selection foreground overlays visible while pointing the %s handle',
98
+ (handle) => {
99
+ editor.updateInstanceState({ isCoarsePointer: false })
100
+ editor.createShapes([{ id: ids.box1, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } }])
101
+ editor.select(ids.box1)
102
+ editor.pointerDown(0, 0, { target: 'selection', handle })
103
+
104
+ const overlays = editor.overlays.getCurrentOverlays()
105
+ const idsSet = new Set(overlays.map((o) => o.id))
106
+ expect(idsSet.has('selection_fg:top_left')).toBe(true)
107
+ expect(idsSet.has('selection_fg:top')).toBe(true)
108
+ expect(idsSet.has('selection_fg:top_left_rotate')).toBe(true)
109
+ }
110
+ )
111
+
96
112
  it('includes mobile rotate handle for coarse pointer', () => {
97
113
  editor.updateInstanceState({ isCoarsePointer: true })
98
114
  editor.createShapes([{ id: ids.box1, type: 'geo', x: 0, y: 0, props: { w: 60, h: 60 } }])
@@ -189,6 +205,48 @@ describe('SelectionForegroundOverlayUtil', () => {
189
205
  expect(idsSet.has('selection_fg:bottom_right')).toBe(true)
190
206
  })
191
207
 
208
+ it('keeps resize handles while pressing a resize edge', () => {
209
+ editor.createShapes([{ id: ids.box1, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } }])
210
+ editor.select(ids.box1)
211
+ editor.pointerDownOnHandle('top')
212
+ expect(editor.getPath()).toBe('select.pointing_resize_handle')
213
+
214
+ const util =
215
+ editor.overlays.getOverlayUtil<SelectionForegroundOverlayUtil>('selection_foreground')
216
+ const idsSet = new Set(util.getOverlays().map((o) => o.id))
217
+ expect(idsSet.has('selection_fg:top_left')).toBe(true)
218
+ expect(idsSet.has('selection_fg:top_right')).toBe(true)
219
+ expect(idsSet.has('selection_fg:bottom_left')).toBe(true)
220
+ expect(idsSet.has('selection_fg:bottom_right')).toBe(true)
221
+
222
+ // Once an actual resize begins, the handles hide
223
+ editor.pointerMoveBy(0, -30)
224
+ expect(editor.getPath()).toBe('select.resizing')
225
+ expect(util.getOverlays()).toEqual([])
226
+ })
227
+
228
+ it('keeps resize handles while pressing a rotate handle', () => {
229
+ editor.updateInstanceState({ isCoarsePointer: false })
230
+ editor.createShapes([{ id: ids.box1, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } }])
231
+ editor.select(ids.box1)
232
+ editor.pointerDownOnHandle('top_left_rotate')
233
+ expect(editor.getPath()).toBe('select.pointing_rotate_handle')
234
+
235
+ const util =
236
+ editor.overlays.getOverlayUtil<SelectionForegroundOverlayUtil>('selection_foreground')
237
+ expect(util.isActive()).toBe(true)
238
+ const idsSet = new Set(util.getOverlays().map((o) => o.id))
239
+ expect(idsSet.has('selection_fg:top_left')).toBe(true)
240
+ expect(idsSet.has('selection_fg:top_right')).toBe(true)
241
+ expect(idsSet.has('selection_fg:bottom_left')).toBe(true)
242
+ expect(idsSet.has('selection_fg:bottom_right')).toBe(true)
243
+
244
+ // Once an actual rotation begins, the selection foreground hides
245
+ editor.pointerMoveBy(-30, -30)
246
+ expect(editor.getPath()).toBe('select.rotating')
247
+ expect(util.isActive()).toBe(false)
248
+ })
249
+
192
250
  it('hides edge handles on coarse pointer', () => {
193
251
  editor.updateInstanceState({ isCoarsePointer: true })
194
252
  editor.createShapes([{ id: ids.box1, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } }])
@@ -64,13 +64,13 @@ describe('ShapeHandleOverlayUtil', () => {
64
64
  editor.pointerUp()
65
65
  })
66
66
 
67
- it('returns true in select.pointing_shape with a handle shape selected', () => {
67
+ it('returns false in select.pointing_shape with a handle shape selected', () => {
68
68
  editor.createShapes([{ id: ids.line1, type: 'line', x: 120, y: 120 }])
69
69
  const shape = editor.getShape(ids.line1)!
70
70
  editor.pointerDown(shape.x + 1, shape.y + 1, { target: 'shape', shape })
71
71
  editor.expectToBeIn('select.pointing_shape')
72
72
  const util = editor.overlays.getOverlayUtil<ShapeHandleOverlayUtil>('shape_handle')
73
- expect(util.isActive()).toBe(true)
73
+ expect(util.isActive()).toBe(false)
74
74
  editor.pointerUp()
75
75
  })
76
76
 
@@ -0,0 +1,36 @@
1
+ import { createShapeId } from '@tldraw/editor'
2
+ import { defaultOverlayUtils } from '../../lib/defaultOverlayUtils'
3
+ import { ShapeIndicatorOverlayUtil } from '../../lib/overlays/ShapeIndicatorOverlayUtil'
4
+ import { TestEditor } from '../TestEditor'
5
+
6
+ let editor: TestEditor
7
+
8
+ const ids = {
9
+ box1: createShapeId('box1'),
10
+ }
11
+
12
+ beforeEach(() => {
13
+ editor = new TestEditor({ overlayUtils: defaultOverlayUtils })
14
+ })
15
+
16
+ describe('ShapeIndicatorOverlayUtil', () => {
17
+ describe('getOverlays', () => {
18
+ it.each(['top_left', 'top_left_rotate'] as const)(
19
+ 'keeps selected shape indicators visible while pointing the %s selection handle',
20
+ (handle) => {
21
+ editor.createShapes([{ id: ids.box1, type: 'geo', x: 0, y: 0, props: { w: 100, h: 100 } }])
22
+ editor.select(ids.box1)
23
+ editor.pointerDown(0, 0, { target: 'selection', handle })
24
+
25
+ const util = editor.overlays.getOverlayUtil<ShapeIndicatorOverlayUtil>('shape_indicator')
26
+ expect(util.getOverlays()).toMatchObject([
27
+ {
28
+ id: 'shape_indicator',
29
+ type: 'shape_indicator',
30
+ props: { idsToDisplay: [ids.box1] },
31
+ },
32
+ ])
33
+ }
34
+ )
35
+ })
36
+ })