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
@@ -10,6 +10,7 @@ import {
10
10
  TLHighlightShapeProps,
11
11
  TLResizeInfo,
12
12
  VecLike,
13
+ b64Vecs,
13
14
  debugFlags,
14
15
  getColorValue,
15
16
  highlightShapeMigrations,
@@ -23,9 +24,8 @@ import {
23
24
  } from '@tldraw/editor'
24
25
  import { getHighlightFreehandSettings, getPointsFromDrawSegments } from '../draw/getPath'
25
26
  import { FONT_SIZES } from '../shared/default-shape-constants'
26
- import { getStrokeOutlinePoints } from '../shared/freehand/getStrokeOutlinePoints'
27
+ import { getStroke } from '../shared/freehand/getStroke'
27
28
  import { getStrokePoints } from '../shared/freehand/getStrokePoints'
28
- import { setStrokePointRadii } from '../shared/freehand/setStrokePointRadii'
29
29
  import { getSvgPathFromStrokePoints } from '../shared/freehand/svg'
30
30
  import type { ShapeOptionsWithDisplayValues } from '../shared/getDisplayValues'
31
31
  import { getDisplayValues } from '../shared/getDisplayValues'
@@ -113,12 +113,16 @@ export class HighlightShapeUtil extends ShapeUtil<TLHighlightShape> {
113
113
  })
114
114
  }
115
115
 
116
- const { strokePoints, sw } = getHighlightStrokePoints(shape, strokeWidth, true)
117
- const opts = getHighlightFreehandSettings({ strokeWidth: sw, showAsComplete: true })
118
- setStrokePointRadii(strokePoints, opts)
116
+ const allPointsFromSegments = getPointsFromDrawSegments(
117
+ shape.props.segments,
118
+ shape.props.scaleX,
119
+ shape.props.scaleY
120
+ )
121
+ const showAsComplete = shape.props.isComplete || last(shape.props.segments)?.type === 'straight'
122
+ const opts = getHighlightFreehandSettings({ strokeWidth, showAsComplete })
119
123
 
120
124
  return new Polygon2d({
121
- points: getStrokeOutlinePoints(strokePoints, opts),
125
+ points: getStroke(allPointsFromSegments, opts),
122
126
  isFilled: true,
123
127
  })
124
128
  }
@@ -286,11 +290,10 @@ function getHighlightStrokePoints(
286
290
  }
287
291
 
288
292
  function getIsDot(shape: TLHighlightShape) {
289
- // First point is 16 base64 chars (3 Float32s = 12 bytes)
290
- // Each delta point is 8 base64 chars (3 Float16s = 6 bytes)
291
- // 1 point = 16 chars, 2 points = 24 chars
292
- // Check if we have less than 2 points without decoding
293
- return shape.props.segments.length === 1 && shape.props.segments[0].path.length < 24
293
+ // A dot is a single-point segment. isSinglePoint knows the per-encoding length
294
+ // (and takes the segment's dim), so this stays correct for both 2D and 3D paths.
295
+ const segment = shape.props.segments[0]
296
+ return shape.props.segments.length === 1 && b64Vecs.isSinglePoint(segment.path, segment.dim)
294
297
  }
295
298
 
296
299
  function HighlightRenderer({
@@ -170,7 +170,7 @@ export class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {
170
170
  override onResize(shape: TLImageShape, info: TLResizeInfo<TLImageShape>) {
171
171
  let resized: TLImageShape = resizeBox(shape, info)
172
172
  const { flipX, flipY } = info.initialShape.props
173
- const { scaleX, scaleY, mode } = info
173
+ const { scaleX, scaleY } = info
174
174
 
175
175
  resized = {
176
176
  ...resized,
@@ -182,16 +182,12 @@ export class ImageShapeUtil extends BaseBoxShapeUtil<TLImageShape> {
182
182
  }
183
183
  if (!shape.props.crop) return resized
184
184
 
185
- const flipCropHorizontally =
186
- // We used the flip horizontally feature
187
- (mode === 'scale_shape' && scaleX === -1) ||
188
- // We resized the shape past it's bounds, so it flipped
189
- (mode === 'resize_bounds' && flipX !== resized.props.flipX)
190
- const flipCropVertically =
191
- // We used the flip vertically feature
192
- (mode === 'scale_shape' && scaleY === -1) ||
193
- // We resized the shape past it's bounds, so it flipped
194
- (mode === 'resize_bounds' && flipY !== resized.props.flipY)
185
+ // Mirror the crop whenever the shape is flipped along an axis. This happens both when
186
+ // using the flip command and when dragging a resize handle (including a group's handle)
187
+ // past the opposite edge. We can't check for an exact scale of -1 here because a group
188
+ // flip resizes its children by an arbitrary negative scale, not just -1.
189
+ const flipCropHorizontally = scaleX < 0
190
+ const flipCropVertically = scaleY < 0
195
191
 
196
192
  const { topLeft, bottomRight } = shape.props.crop
197
193
  resized.props.crop = {
@@ -205,6 +205,47 @@ describe('When extending the line with the shift-key in tool-lock mode', () => {
205
205
  })
206
206
  })
207
207
 
208
+ describe('minimum bend distance for shift-clicked handles is measured in screen space', () => {
209
+ // regression for #7661: minimum bend distance for shift-clicked handles is measured in screen space
210
+ // Creates a two-point line whose end handle sits at screen point (200, 200), the
211
+ // camera being static so the handle's screen position matches the pointer-up location.
212
+ function createTwoPointLine() {
213
+ editor
214
+ .setCurrentTool('line')
215
+ .pointerDown(100, 100, { target: 'canvas' })
216
+ .pointerMove(200, 200)
217
+ .pointerUp(200, 200)
218
+ }
219
+
220
+ beforeEach(() => {
221
+ editor.updateInstanceState({ isToolLocked: true })
222
+ })
223
+
224
+ it('adds a point when the click is more than 2 screen pixels away while zoomed in', () => {
225
+ editor.setCamera({ x: 0, y: 0, z: 4 })
226
+ createTwoPointLine()
227
+ // 4 screen pixels from the end handle: past the 2px screen threshold, but only 1
228
+ // page unit — under the old page-space threshold this would have merged.
229
+ editor.keyDown('Shift').pointerDown(204, 200, { target: 'canvas' }).pointerUp(204, 200)
230
+
231
+ const line = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1]
232
+ assert(editor.isShapeOfType(line, 'line'))
233
+ expect(Object.keys(line.props.points).length).toBe(3)
234
+ })
235
+
236
+ it('merges the click when within 2 screen pixels while zoomed out', () => {
237
+ editor.setCamera({ x: 0, y: 0, z: 0.25 })
238
+ createTwoPointLine()
239
+ // 1 screen pixel from the end handle: within the 2px screen threshold, but 4 page
240
+ // units — under the old page-space threshold this would have added a point.
241
+ editor.keyDown('Shift').pointerDown(201, 200, { target: 'canvas' }).pointerUp(201, 200)
242
+
243
+ const line = editor.getCurrentPageShapes()[editor.getCurrentPageShapes().length - 1]
244
+ assert(editor.isShapeOfType(line, 'line'))
245
+ expect(Object.keys(line.props.points).length).toBe(2)
246
+ })
247
+ })
248
+
208
249
  describe('tool lock bug', () => {
209
250
  it('works as expected when tool lock is on but shift is off', () => {
210
251
  expect(editor.getCurrentPageShapes().length).toBe(0)
@@ -6,12 +6,14 @@ import {
6
6
  Vec,
7
7
  createShapeId,
8
8
  getIndexAbove,
9
+ cancelShapeCreationOnLongPress,
9
10
  last,
10
11
  maybeSnapToGrid,
11
12
  sortByIndex,
12
13
  structuredClone,
13
14
  } from '@tldraw/editor'
14
15
 
16
+ // Minimum distance, in screen pixels, between two shift-clicked handles before we merge them
15
17
  const MINIMUM_DISTANCE_BETWEEN_SHIFT_CLICKED_HANDLES = 2
16
18
 
17
19
  export class Pointing extends StateNode {
@@ -51,9 +53,13 @@ export class Pointing extends StateNode {
51
53
  const nextPoint = maybeSnapToGrid(nudgedPoint, this.editor)
52
54
  const points = structuredClone(this.shape.props.points)
53
55
 
56
+ // compare in screen space
57
+ const minDistance =
58
+ MINIMUM_DISTANCE_BETWEEN_SHIFT_CLICKED_HANDLES / this.editor.getZoomLevel()
59
+
54
60
  if (
55
- Vec.DistMin(endHandle, prevEndHandle, MINIMUM_DISTANCE_BETWEEN_SHIFT_CLICKED_HANDLES) ||
56
- Vec.DistMin(nextPoint, endHandle, MINIMUM_DISTANCE_BETWEEN_SHIFT_CLICKED_HANDLES)
61
+ Vec.DistMin(endHandle, prevEndHandle, minDistance) ||
62
+ Vec.DistMin(nextPoint, endHandle, minDistance)
57
63
  ) {
58
64
  // Don't add a new point if the distance between the last two points is too small
59
65
  points[endHandle.id] = {
@@ -136,6 +142,10 @@ export class Pointing extends StateNode {
136
142
  this.complete()
137
143
  }
138
144
 
145
+ override onLongPress() {
146
+ cancelShapeCreationOnLongPress(this.editor, () => this.cancel())
147
+ }
148
+
139
149
  override onCancel() {
140
150
  this.cancel()
141
151
  }
@@ -1,9 +1,7 @@
1
1
  /* eslint-disable react-hooks/rules-of-hooks */
2
2
  import {
3
3
  Box,
4
- DefaultFontFamilies,
5
4
  EMPTY_ARRAY,
6
- Editor,
7
5
  Group2d,
8
6
  IndexKey,
9
7
  Rectangle2d,
@@ -26,6 +24,7 @@ import {
26
24
  noteShapeMigrations,
27
25
  noteShapeProps,
28
26
  resizeScaled,
27
+ resolveLineHeightPx,
29
28
  rng,
30
29
  toRichText,
31
30
  useColorMode,
@@ -179,7 +178,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
179
178
  fontSizeAdjustment: 1,
180
179
  url: '',
181
180
  scale: 1,
182
- textFirstEditedBy: null,
181
+ textLastEditedBy: null,
183
182
  }
184
183
  }
185
184
 
@@ -297,7 +296,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
297
296
  }
298
297
 
299
298
  override getReferencedUserIds(shape: TLNoteShape) {
300
- return shape.props.textFirstEditedBy ? [shape.props.textFirstEditedBy] : []
299
+ return shape.props.textLastEditedBy ? [shape.props.textLastEditedBy] : []
301
300
  }
302
301
 
303
302
  override getFontFaces(shape: TLNoteShape) {
@@ -309,7 +308,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
309
308
  style: 'normal',
310
309
  })
311
310
 
312
- if (shape.props.textFirstEditedBy && !isEmptyRichText(shape.props.richText)) {
311
+ if (shape.props.textLastEditedBy && !isEmptyRichText(shape.props.richText)) {
313
312
  return [...fonts, DefaultFontFaces.tldraw_sans.normal.normal]
314
313
  }
315
314
  const themeFaces = getThemeFontFaces(this.editor.getCurrentTheme(), shape.props.font)
@@ -320,7 +319,7 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
320
319
 
321
320
  component(shape: TLNoteShape) {
322
321
  const { id, type, props } = shape
323
- const { scale, richText, fontSizeAdjustment, textFirstEditedBy } = props
322
+ const { scale, richText, fontSizeAdjustment, textLastEditedBy } = props
324
323
 
325
324
  const handleKeyDown = useNoteKeydownHandler(id)
326
325
 
@@ -347,12 +346,12 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
347
346
  const attribution = useValue(
348
347
  'attribution',
349
348
  () => {
350
- if (!textFirstEditedBy || isEmpty) return null
351
- const name = this.editor.getAttributionDisplayName(textFirstEditedBy)
349
+ if (!textLastEditedBy || isEmpty) return null
350
+ const name = this.editor.getAttributionDisplayName(textLastEditedBy)
352
351
  if (!name) return null
353
352
  return { short: name.split(' ')[0], full: name }
354
353
  },
355
- [textFirstEditedBy, isEmpty, this.editor]
354
+ [textLastEditedBy, isEmpty, this.editor]
356
355
  )
357
356
 
358
357
  return (
@@ -459,14 +458,11 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
459
458
  ),
460
459
  })
461
460
 
462
- const { textFirstEditedBy } = shape.props
463
- const attributionFirstName =
464
- textFirstEditedBy && !isEmptyRichText(shape.props.richText)
465
- ? this.editor.getAttributionDisplayName(textFirstEditedBy)?.split(' ')[0]
461
+ const { textLastEditedBy } = shape.props
462
+ const attributionName =
463
+ textLastEditedBy && !isEmptyRichText(shape.props.richText)
464
+ ? (this.editor.getAttributionDisplayName(textLastEditedBy)?.split(' ')[0] ?? null)
466
465
  : null
467
- const attributionName = attributionFirstName
468
- ? truncateAttributionForSvg(this.editor, attributionFirstName, dv.noteWidth)
469
- : null
470
466
 
471
467
  return (
472
468
  <>
@@ -493,17 +489,26 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
493
489
  showTextOutline={false}
494
490
  />
495
491
  {attributionName && (
496
- <text
497
- x={dv.noteWidth - 8}
498
- y={bounds.h - 6}
499
- textAnchor="end"
500
- fontFamily={DefaultFontFamilies['sans']}
501
- fontSize={11}
502
- fill={dv.labelColor}
503
- opacity={0.6}
492
+ <foreignObject
493
+ x={0}
494
+ y={0}
495
+ width={dv.noteWidth}
496
+ height={bounds.h}
497
+ className="tl-export-embed-styles"
504
498
  >
505
- {attributionName}
506
- </text>
499
+ <div style={{ position: 'relative', width: '100%', height: '100%' }}>
500
+ <div
501
+ className="tl-note__attribution"
502
+ style={{
503
+ fontSize: 11,
504
+ color: dv.labelColor,
505
+ opacity: 0.6,
506
+ }}
507
+ >
508
+ {attributionName}
509
+ </div>
510
+ </div>
511
+ </foreignObject>
507
512
  )}
508
513
  </>
509
514
  )
@@ -529,12 +534,12 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
529
534
  if (isEmptyRichText(next.props.richText)) {
530
535
  shape = {
531
536
  ...shape,
532
- props: { ...shape.props, textFirstEditedBy: null },
537
+ props: { ...shape.props, textLastEditedBy: null },
533
538
  }
534
- } else if (!prev.props.textFirstEditedBy) {
539
+ } else {
535
540
  shape = {
536
541
  ...shape,
537
- props: { ...shape.props, textFirstEditedBy: this.editor.getAttributionUserId() },
542
+ props: { ...shape.props, textLastEditedBy: this.editor.getAttributionUserId() },
538
543
  }
539
544
  }
540
545
  }
@@ -596,7 +601,8 @@ export class NoteShapeUtil extends ShapeUtil<TLNoteShape> {
596
601
  const { richText } = shape.props
597
602
 
598
603
  if (isEmptyRichText(richText)) {
599
- const minHeight = dv.labelFontSize * dv.labelLineHeight + dv.labelPadding * 2
604
+ const minHeight =
605
+ resolveLineHeightPx(dv.labelFontSize, dv.labelLineHeight) + dv.labelPadding * 2
600
606
  return { labelHeight: minHeight, labelWidth: 100, fontSizeAdjustment: 1 }
601
607
  }
602
608
 
@@ -737,27 +743,6 @@ function getNoteHeight(shape: TLNoteShape, noteHeight: number) {
737
743
  return (noteHeight + shape.props.growY) * shape.props.scale
738
744
  }
739
745
 
740
- // Matches `.tl-note__attribution { max-width: 60% }` so SVG export truncates the same way.
741
- const ATTRIBUTION_MAX_WIDTH_RATIO = 0.6
742
-
743
- function truncateAttributionForSvg(editor: Editor, name: string, noteWidth: number) {
744
- if (process.env.NODE_ENV === 'test') return name
745
- const spans = editor.textMeasure.measureTextSpans(name, {
746
- fontSize: 11,
747
- fontFamily: DefaultFontFamilies['sans'],
748
- textAlign: 'end',
749
- width: noteWidth * ATTRIBUTION_MAX_WIDTH_RATIO,
750
- height: 16,
751
- padding: 0,
752
- lineHeight: 1,
753
- fontStyle: 'normal',
754
- fontWeight: 'normal',
755
- overflow: 'truncate-ellipsis',
756
- })
757
- if (spans.length === 0) return name
758
- return spans.map((s) => s.text).join('')
759
- }
760
-
761
746
  function getNoteShadow(id: string, rotation: number, scale: number) {
762
747
  const random = rng(id) // seeded based on id
763
748
  const lift = Math.abs(random()) + 0.5 // 0 to 1.5
@@ -241,11 +241,11 @@ describe('Clone handles and attribution', () => {
241
241
  })
242
242
  const updated = editor.getShape<TLNoteShape>(shape.id)!
243
243
  // Sanity check - the source note has attribution set
244
- expect(updated.props.textFirstEditedBy).not.toBeNull()
244
+ expect(updated.props.textLastEditedBy).not.toBeNull()
245
245
  return updated
246
246
  }
247
247
 
248
- it('Does not preserve textFirstEditedBy when creating an adjacent note via click', () => {
248
+ it('Does not preserve textLastEditedBy when creating an adjacent note via click', () => {
249
249
  const shape = createNoteWithAttribution()
250
250
 
251
251
  editor.select(shape.id)
@@ -261,10 +261,10 @@ describe('Clone handles and attribution', () => {
261
261
 
262
262
  const newShape = editor.getLastCreatedShape<TLNoteShape>()
263
263
  expect(newShape.id).not.toBe(shape.id)
264
- expect(newShape.props.textFirstEditedBy).toBeNull()
264
+ expect(newShape.props.textLastEditedBy).toBeNull()
265
265
  })
266
266
 
267
- it('Does not preserve textFirstEditedBy when creating an adjacent note via drag', () => {
267
+ it('Does not preserve textLastEditedBy when creating an adjacent note via drag', () => {
268
268
  const shape = createNoteWithAttribution()
269
269
 
270
270
  editor.select(shape.id)
@@ -282,7 +282,7 @@ describe('Clone handles and attribution', () => {
282
282
 
283
283
  const newShape = editor.getLastCreatedShape<TLNoteShape>()
284
284
  expect(newShape.id).not.toBe(shape.id)
285
- expect(newShape.props.textFirstEditedBy).toBeNull()
285
+ expect(newShape.props.textLastEditedBy).toBeNull()
286
286
 
287
287
  editor.pointerUp()
288
288
  })
@@ -232,7 +232,7 @@ export function getNoteShapeForAdjacentPosition(
232
232
  growY: 0,
233
233
  fontSizeAdjustment: 1,
234
234
  url: '',
235
- textFirstEditedBy: null,
235
+ textLastEditedBy: null,
236
236
  },
237
237
  })
238
238
 
@@ -6,6 +6,7 @@ import {
6
6
  TLShapeId,
7
7
  Vec,
8
8
  createShapeId,
9
+ cancelShapeCreationOnLongPress,
9
10
  maybeSnapToGrid,
10
11
  } from '@tldraw/editor'
11
12
  import { startEditingShapeWithRichText } from '../../../tools/SelectTool/selectHelpers'
@@ -27,33 +28,57 @@ export class Pointing extends StateNode {
27
28
 
28
29
  shape = {} as TLNoteShape
29
30
 
31
+ private shapeId!: TLShapeId
32
+
33
+ private shapeCenter = new Vec()
34
+
35
+ private hasCreatedShape = false
36
+
30
37
  override onEnter() {
31
38
  const { editor } = this
32
39
 
33
- const id = createShapeId()
34
- this.markId = editor.markHistoryStoppingPoint(`creating_note:${id}`)
40
+ this.hasCreatedShape = false
41
+ this.shapeId = createShapeId()
42
+ this.markId = editor.markHistoryStoppingPoint(`creating_note:${this.shapeId}`)
35
43
 
36
44
  // Resolve note dimensions from the util's defaults
37
45
  const noteUtil = editor.getShapeUtil('note') as NoteShapeUtil
38
46
  const dv = getDisplayValues(noteUtil, { props: noteUtil.getDefaultProps() } as TLNoteShape)
39
47
 
40
48
  // Check for note pits; if the pointer is close to one, place the note centered on the pit
41
- const center = this.editor.inputs.getOriginPagePoint().clone()
49
+ const center = editor.inputs.getOriginPagePoint().clone()
42
50
  const offset = getNoteShapeAdjacentPositionOffset(
43
- this.editor,
51
+ editor,
44
52
  center,
45
- this.editor.getResizeScaleFactor(),
53
+ editor.getResizeScaleFactor(),
46
54
  dv.noteWidth,
47
55
  dv.noteHeight
48
56
  )
49
57
  if (offset) {
50
58
  center.sub(offset)
51
59
  }
60
+ this.shapeCenter = center
61
+
62
+ // On a coarse pointer, defer creating the note until the gesture commits (a
63
+ // drag or a release). The note is created on press, and a long-press cancels
64
+ // the pending creation, so without deferral the note appears on press and
65
+ // vanishes at the long-press mark — a visible flash. On a fine pointer there
66
+ // is no long-press cancel, so create immediately to keep the press-and-drag feel.
67
+ if (!editor.getInstanceState().isCoarsePointer) {
68
+ this.ensureShapeCreated()
69
+ }
70
+ }
71
+
72
+ // Create the note now if it hasn't been created yet (idempotent). The deferred
73
+ // drag and release paths route through here; a max-shapes failure cancels the
74
+ // gesture, matching the original onEnter behaviour.
75
+ private ensureShapeCreated() {
76
+ if (this.hasCreatedShape) return
52
77
 
53
- // Allow this to trigger the max shapes reached alert
54
- const shape = createNoteShape(this.editor, id, center)
78
+ const shape = createNoteShape(this.editor, this.shapeId, this.shapeCenter)
55
79
  if (shape) {
56
80
  this.shape = shape
81
+ this.hasCreatedShape = true
57
82
  } else {
58
83
  this.cancel()
59
84
  }
@@ -61,6 +86,8 @@ export class Pointing extends StateNode {
61
86
 
62
87
  override onPointerMove(info: TLPointerEventInfo) {
63
88
  if (this.editor.inputs.getIsDragging()) {
89
+ this.ensureShapeCreated()
90
+ if (!this.hasCreatedShape) return
64
91
  this.editor.setCurrentTool('select.translating', {
65
92
  ...info,
66
93
  target: 'shape',
@@ -79,6 +106,10 @@ export class Pointing extends StateNode {
79
106
  this.complete()
80
107
  }
81
108
 
109
+ override onLongPress() {
110
+ cancelShapeCreationOnLongPress(this.editor, () => this.cancel())
111
+ }
112
+
82
113
  override onInterrupt() {
83
114
  this.cancel()
84
115
  }
@@ -92,6 +123,8 @@ export class Pointing extends StateNode {
92
123
  }
93
124
 
94
125
  private complete() {
126
+ this.ensureShapeCreated()
127
+ if (!this.hasCreatedShape) return
95
128
  if (this.editor.getInstanceState().isToolLocked) {
96
129
  this.parent.transition('idle')
97
130
  } else {
@@ -1,4 +1,4 @@
1
- import { Box, ExtractShapeByProps, TLShapeId } from '@tldraw/editor'
1
+ import { Box, ExtractShapeByProps, TLShapeId, resolveLineHeightPx } from '@tldraw/editor'
2
2
  import classNames from 'classnames'
3
3
  import React from 'react'
4
4
  import { PlainTextArea } from '../text/PlainTextArea'
@@ -105,8 +105,8 @@ export const PlainTextLabel = React.memo(function PlainTextLabel({
105
105
  className={`${cssPrefix}-label__inner tl-text-content__wrapper`}
106
106
  style={{
107
107
  fontSize,
108
- lineHeight: lineHeight.toString(),
109
- minHeight: Math.floor(fontSize * lineHeight) + 'px',
108
+ lineHeight: `${resolveLineHeightPx(fontSize, lineHeight)}px`,
109
+ minHeight: `${resolveLineHeightPx(fontSize, lineHeight)}px`,
110
110
  minWidth: Math.ceil(textWidth || 0),
111
111
  color: labelColor,
112
112
  width: textWidth ? Math.ceil(textWidth) : undefined,
@@ -6,6 +6,7 @@ import {
6
6
  TLShapeId,
7
7
  openWindow,
8
8
  preventDefault,
9
+ resolveLineHeightPx,
9
10
  useEditor,
10
11
  useReactor,
11
12
  useValue,
@@ -161,8 +162,8 @@ export const RichTextLabel = React.memo(function RichTextLabel({
161
162
  className={`${cssPrefix}-label__inner tl-text-content__wrapper`}
162
163
  style={{
163
164
  fontSize,
164
- lineHeight: lineHeight.toString(),
165
- minHeight: Math.floor(fontSize * lineHeight) + 'px',
165
+ lineHeight: `${resolveLineHeightPx(fontSize, lineHeight)}px`,
166
+ minHeight: `${resolveLineHeightPx(fontSize, lineHeight)}px`,
166
167
  minWidth: Math.ceil(textWidth || 0),
167
168
  color: labelColor,
168
169
  width: textWidth ? Math.ceil(textWidth) : undefined,
@@ -254,7 +255,7 @@ export function RichTextSVG({
254
255
  fontSize: `${fontSize}px`,
255
256
  wrap: wrap ? 'wrap' : 'nowrap',
256
257
  color: labelColor,
257
- lineHeight,
258
+ lineHeight: `${resolveLineHeightPx(fontSize, lineHeight)}px`,
258
259
  textAlign,
259
260
  width: '100%',
260
261
  wordWrap: 'break-word' as const,
@@ -74,7 +74,10 @@ const generateImage = (dpr: number, currentZoom: number, solid: string) => {
74
74
  canvasEl.height = size
75
75
 
76
76
  const ctx = canvasEl.getContext('2d')
77
- if (!ctx) return
77
+ if (!ctx) {
78
+ reject()
79
+ return
80
+ }
78
81
 
79
82
  ctx.fillStyle = solid
80
83
  ctx.fillRect(0, 0, size, size)
@@ -109,6 +112,25 @@ const generateImage = (dpr: number, currentZoom: number, solid: string) => {
109
112
  })
110
113
  }
111
114
 
115
+ // Generating the pattern images is expensive (canvas draw + encode), but the result only depends
116
+ // on the device pixel ratio, zoom level, and solid color. Cache the blobs at the module level so
117
+ // the work is done once and reused across editor mounts and instances. Object URLs are still
118
+ // created and revoked per-instance; only the underlying blobs are shared.
119
+ const patternImageBlobCache = new Map<string, Promise<Blob>>()
120
+
121
+ function getPatternImageBlob(dpr: number, zoom: number, solid: string): Promise<Blob> {
122
+ const key = `${dpr}_${zoom}_${solid}`
123
+ let blob = patternImageBlobCache.get(key)
124
+ if (!blob) {
125
+ blob = generateImage(dpr, zoom, solid)
126
+ // Don't keep a rejected promise around (e.g. the throwToBlob debug flag), so a later call
127
+ // can retry once the failure condition is gone.
128
+ blob.catch(() => patternImageBlobCache.delete(key))
129
+ patternImageBlobCache.set(key, blob)
130
+ }
131
+ return blob
132
+ }
133
+
112
134
  const canvasBlob = (size: [number, number], fn: (ctx: CanvasRenderingContext2D) => void) => {
113
135
  const canvas = getGlobalDocument().createElement('canvas')
114
136
  canvas.width = size[0]
@@ -208,12 +230,12 @@ function usePattern() {
208
230
 
209
231
  const promise = Promise.all(
210
232
  getPatternLodsToGenerate(maxEffectiveZoom).flatMap<Promise<PatternDef>>((zoom) => [
211
- generateImage(dpr, zoom, lightSolid).then((blob) => ({
233
+ getPatternImageBlob(dpr, zoom, lightSolid).then((blob) => ({
212
234
  zoom,
213
235
  theme: 'light',
214
236
  url: URL.createObjectURL(blob),
215
237
  })),
216
- generateImage(dpr, zoom, darkSolid).then((blob) => ({
238
+ getPatternImageBlob(dpr, zoom, darkSolid).then((blob) => ({
217
239
  zoom,
218
240
  theme: 'dark',
219
241
  url: URL.createObjectURL(blob),