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
@@ -3,6 +3,7 @@ import {
3
3
  DefaultColorStyle,
4
4
  DefaultFillStyle,
5
5
  Editor,
6
+ GeoShapeGeoStyle,
6
7
  HALF_PI,
7
8
  PageRecordType,
8
9
  Result,
@@ -583,10 +584,68 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
583
584
  }
584
585
  },
585
586
  },
587
+ {
588
+ id: 'frame-selection',
589
+ label: 'action.frame-selection',
590
+ kbd: 'cmd+alt+g',
591
+ onSelect(source) {
592
+ if (!canApplySelectionAction()) return
593
+ if (mustGoBackToSelectToolFirst()) return
594
+
595
+ const selectedShapes = editor.getSelectedShapes()
596
+
597
+ // If all selected shapes are frames, remove them (toggle behavior)
598
+ if (
599
+ selectedShapes.length > 0 &&
600
+ selectedShapes.every((shape) => editor.isShapeOfType(shape, 'frame'))
601
+ ) {
602
+ trackEvent('remove-frame', { source })
603
+ editor.markHistoryStoppingPoint('remove-frame')
604
+ removeFrame(
605
+ editor,
606
+ selectedShapes.map((shape) => shape.id)
607
+ )
608
+ return
609
+ }
610
+
611
+ const ids = editor.getSelectedShapeIds()
612
+ if (ids.length < 2) return
613
+
614
+ const shapes = compact(ids.map((id) => editor.getShape(id)))
615
+ const pageBounds = editor.getSelectionPageBounds()
616
+ if (!pageBounds) return
617
+
618
+ trackEvent('frame-selection', { source })
619
+ editor.markHistoryStoppingPoint('frame-selection')
620
+
621
+ const parentId = editor.findCommonAncestor(shapes) ?? editor.getCurrentPageId()
622
+
623
+ const frameId = createShapeId()
624
+ const padding = 25 / editor.getZoomLevel()
625
+
626
+ editor.run(() => {
627
+ editor.createShapes([
628
+ {
629
+ id: frameId,
630
+ type: 'frame',
631
+ parentId,
632
+ x: pageBounds.x,
633
+ y: pageBounds.y,
634
+ props: {
635
+ w: pageBounds.w,
636
+ h: pageBounds.h,
637
+ },
638
+ },
639
+ ])
640
+ editor.reparentShapes(ids, frameId)
641
+ fitFrameToContent(editor, frameId, { padding })
642
+ editor.select(frameId)
643
+ })
644
+ },
645
+ },
586
646
  {
587
647
  id: 'remove-frame',
588
648
  label: 'action.remove-frame',
589
- kbd: 'cmd+shift+f,ctrl+shift+f',
590
649
  onSelect(source) {
591
650
  if (!canApplySelectionAction()) return
592
651
 
@@ -1848,6 +1907,34 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
1848
1907
  trackEvent('download-original', { source })
1849
1908
  },
1850
1909
  },
1910
+ {
1911
+ id: 'copy-hovered-styles',
1912
+ label: 'action.copy-hovered-styles',
1913
+ kbd: 'shift+q',
1914
+ async onSelect(source) {
1915
+ const shape = editor.getShapeAtPoint(editor.inputs.getCurrentPagePoint(), {
1916
+ hitInside: false,
1917
+ hitLabels: false,
1918
+ hitLocked: editor.options.selectLockedShapes,
1919
+ margin: editor.options.hitTestMargin / editor.getZoomLevel(),
1920
+ })
1921
+
1922
+ const path = editor.getPath()
1923
+ if (!shape || !path.endsWith('.idle')) return
1924
+
1925
+ // Setting styles for the next shape is instance state, not document state, so it
1926
+ // isn't undoable and doesn't need a history stopping point.
1927
+ editor.run(() => {
1928
+ for (const style of editor.styleProps[shape.type].keys()) {
1929
+ const value = editor.getShapeStyleIfExists(shape, style)
1930
+ if (value === undefined || style === GeoShapeGeoStyle) continue
1931
+ editor.setStyleForNextShapes(style, value)
1932
+ }
1933
+ })
1934
+
1935
+ trackEvent('copy-hovered-styles', { source })
1936
+ },
1937
+ },
1851
1938
  ]
1852
1939
 
1853
1940
  if (showCollaborationUi) {
@@ -21,16 +21,17 @@ export interface AssetUrlsProviderProps {
21
21
  */
22
22
  export function AssetUrlsProvider({ assetUrls, children }: AssetUrlsProviderProps) {
23
23
  useEffect(() => {
24
- for (const src of Object.values(assetUrls.icons)) {
24
+ // Many icon URLs point at the same sprite sheet and differ only by their
25
+ // `#fragment` identifier (e.g. `0_merged.svg#tool-arrow`). The fragment
26
+ // doesn't change the underlying resource, so preload each unique resource
27
+ // once rather than creating hundreds of redundant Image decodes.
28
+ const preloaded = new Set<string>()
29
+ for (const src of [...Object.values(assetUrls.icons), ...Object.values(assetUrls.embedIcons)]) {
25
30
  if (!src) continue
26
31
 
27
- const image = Image()
28
- image.crossOrigin = 'anonymous'
29
- image.src = src
30
- image.decode().catch(noop)
31
- }
32
- for (const src of Object.values(assetUrls.embedIcons)) {
33
- if (!src) continue
32
+ const resource = src.split('#')[0]
33
+ if (preloaded.has(resource)) continue
34
+ preloaded.add(resource)
34
35
 
35
36
  const image = Image()
36
37
  image.crossOrigin = 'anonymous'
@@ -44,6 +44,7 @@ export interface TLUiEventMap {
44
44
  'rename-document': null
45
45
  'group-shapes': null
46
46
  'ungroup-shapes': null
47
+ 'frame-selection': null
47
48
  'remove-frame': null
48
49
  'fit-frame-to-content': null
49
50
  'convert-to-embed': null
@@ -145,6 +146,7 @@ export interface TLUiEventMap {
145
146
  | 'bulletList'
146
147
  }
147
148
  edit: null
149
+ 'copy-hovered-styles': null
148
150
  }
149
151
 
150
152
  /** @public */
@@ -141,12 +141,35 @@ export function useKeyboardShortcuts() {
141
141
 
142
142
  const body = editor.getContainerDocument().body
143
143
 
144
+ // Track which registration each physically-held key first triggered, keyed by
145
+ // `event.code`. While a key is held down, releasing a modifier should not let the
146
+ // auto-repeat keydown events trigger an adjacent shortcut (e.g. releasing shift while
147
+ // still holding shift+q shouldn't start firing the plain `q` shortcut). The same
148
+ // registration is still allowed to repeat (e.g. holding `=` to keep zooming).
149
+ const heldKeyRegistrations = new Map<string, Registration>()
150
+
144
151
  const handleKeyDown = (e: KeyboardEvent) => {
145
152
  if (shouldSkipEvent(e)) return
153
+ const code = e.code
146
154
  for (const reg of registry) {
147
155
  if (!reg.onKeyDown) continue
148
156
  for (const p of reg.parsed) {
149
157
  if (matchesEvent(e, p)) {
158
+ if (code) {
159
+ // We only guard auto-repeat events. A fresh keypress (`e.repeat` is
160
+ // false) is always free to trigger whatever it matches, even on the same
161
+ // physical key — e.g. cmd+z (undo) then cmd+shift+z (redo), where macOS
162
+ // swallows the `z` keyup while cmd stays held, so we can't rely on keyup
163
+ // to clear the previous registration.
164
+ if (e.repeat) {
165
+ const prev = heldKeyRegistrations.get(code)
166
+ // The held key already triggered a different shortcut; don't fall back
167
+ // to this one (or anything else) just because a modifier was released.
168
+ if (prev && prev !== reg) return
169
+ } else {
170
+ heldKeyRegistrations.set(code, reg)
171
+ }
172
+ }
150
173
  reg.onKeyDown(e)
151
174
  break
152
175
  }
@@ -155,6 +178,10 @@ export function useKeyboardShortcuts() {
155
178
  }
156
179
 
157
180
  const handleKeyUp = (e: KeyboardEvent) => {
181
+ // Always release the held-key tracking, even for events we'd otherwise skip (e.g. the
182
+ // key was released after focus moved into a text input), so a stale entry can't block
183
+ // later shortcuts.
184
+ if (e.code) heldKeyRegistrations.delete(e.code)
158
185
  if (shouldSkipEvent(e)) return
159
186
  for (const reg of registry) {
160
187
  if (!reg.onKeyUp) continue
@@ -195,7 +222,10 @@ export function areShortcutsDisabled(editor: Editor) {
195
222
  // non-Latin layouts (Cyrillic, Greek, etc.) and macOS Option-letter dead-key combinations,
196
223
  // where `event.key` is a non-ASCII glyph.
197
224
 
198
- interface ParsedKbd {
225
+ /**
226
+ * @internal
227
+ */
228
+ export interface ParsedKbd {
199
229
  key: string
200
230
  shift: boolean
201
231
  alt: boolean
@@ -319,7 +349,10 @@ const PHYSICAL_KEY_MAP: Record<string, string> = {
319
349
  Backquote: '`',
320
350
  }
321
351
 
322
- function parseKbd(kbd: string): ParsedKbd[] {
352
+ /**
353
+ * @internal
354
+ */
355
+ export function parseKbd(kbd: string): ParsedKbd[] {
323
356
  const out: ParsedKbd[] = []
324
357
  for (const shortcut of getKeys(kbd)) {
325
358
  const parsed = parseShortcut(shortcut)
@@ -411,11 +444,16 @@ function shouldSkipEvent(e: KeyboardEvent): boolean {
411
444
  return false
412
445
  }
413
446
 
414
- // The "raw" kbd here will look something like "a" or a combination of keys "del,backspace".
415
- // We need to first split them up by comma, then parse each key to ensure backwards compatibility
416
- // with the old kbd format. We used to have symbols to denote cmd/alt/shift,
417
- // using ! for shift, $ for cmd, and ? for alt.
418
- function getHotkeysStringFromKbd(kbd: string) {
447
+ /**
448
+ * The "raw" kbd here will look something like "a" or a combination of keys
449
+ * "del,backspace". We need to first split them up by comma, then parse each
450
+ * key to ensure backwards compatibility with the old kbd format. We used to
451
+ * have symbols to denote cmd/alt/shift, using ! for shift, $ for cmd, and ?
452
+ * for alt.
453
+ *
454
+ * @internal
455
+ */
456
+ export function getHotkeysStringFromKbd(kbd: string) {
419
457
  return getKeys(kbd)
420
458
  .map((kbd) => {
421
459
  let str = ''
@@ -367,6 +367,12 @@ export interface OnDragFromToolbarToCreateShapesOpts {
367
367
  * Called once the drag interaction has finished.
368
368
  */
369
369
  onDragEnd?(id: TLShapeId): void
370
+ /**
371
+ * The id of the toolbar tool to keep highlighted while dragging. Defaults to the created shape's
372
+ * type. Set this when the dragged shape's type differs from the tool it's dragged from — for
373
+ * example, dragging a `draw` shape out from the select tool.
374
+ */
375
+ maskedToolId?: string
370
376
  }
371
377
 
372
378
  /**
@@ -405,5 +411,5 @@ export function onDragFromToolbarToCreateShape(
405
411
  },
406
412
  })
407
413
 
408
- editor.getCurrentTool().setCurrentToolIdMask(shape.type)
414
+ editor.getCurrentTool().setCurrentToolIdMask(opts.maskedToolId ?? shape.type)
409
415
  }
@@ -29,6 +29,7 @@ export type TLUiTranslationKey =
29
29
  | 'action.copy-as-svg.short'
30
30
  | 'action.copy-as-svg'
31
31
  | 'action.copy'
32
+ | 'action.copy-hovered-styles'
32
33
  | 'action.cut'
33
34
  | 'action.delete'
34
35
  | 'action.unlock-all'
@@ -55,6 +56,7 @@ export type TLUiTranslationKey =
55
56
  | 'action.flip-vertical.short'
56
57
  | 'action.fork-project'
57
58
  | 'action.fork-project-on-tldraw'
59
+ | 'action.frame-selection'
58
60
  | 'action.group'
59
61
  | 'action.insert-embed'
60
62
  | 'action.insert-media'
@@ -29,6 +29,7 @@ export const DEFAULT_TRANSLATION = {
29
29
  'action.copy-as-svg.short': 'SVG',
30
30
  'action.copy-as-svg': 'Copy as SVG',
31
31
  'action.copy': 'Copy',
32
+ 'action.copy-hovered-styles': 'Copy hovered styles',
32
33
  'action.cut': 'Cut',
33
34
  'action.delete': 'Delete',
34
35
  'action.unlock-all': 'Unlock all',
@@ -55,6 +56,7 @@ export const DEFAULT_TRANSLATION = {
55
56
  'action.flip-vertical.short': 'Flip V',
56
57
  'action.fork-project': 'Fork this project',
57
58
  'action.fork-project-on-tldraw': 'Fork project on tldraw',
59
+ 'action.frame-selection': 'Frame selection',
58
60
  'action.group': 'Group',
59
61
  'action.insert-embed': 'Insert embed…',
60
62
  'action.insert-media': 'Upload media…',
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '5.2.0-next.e2b8d10bf10e'
4
+ export const version = '5.2.0'
5
5
  export const publishDates = {
6
6
  major: '2026-05-06T16:28:18.473Z',
7
- minor: '2026-06-05T16:06:49.927Z',
8
- patch: '2026-06-05T16:06:49.927Z',
7
+ minor: '2026-07-01T09:20:01.735Z',
8
+ patch: '2026-07-01T09:20:01.735Z',
9
9
  }
package/src/lib/ui.css CHANGED
@@ -12,9 +12,8 @@
12
12
  * (which creates its own stacking context at z-index 300), so their
13
13
  * z-indices are relative to the layout, not the container.
14
14
  *
15
- * cursor and following-indicator are rendered inside canvas-in-front
16
- * (stacking context at z-index 250), so their z-indices are relative
17
- * to that context.
15
+ * cursor is rendered inside canvas-in-front (stacking context at
16
+ * z-index 250), so their z-indices are relative to that context.
18
17
  */
19
18
  --tl-layer-above: 1;
20
19
  --tl-layer-focused-input: 10;
@@ -24,7 +23,6 @@
24
23
  --tl-layer-toasts: 650;
25
24
  --tl-layer-cursor: 700;
26
25
  --tl-layer-header-footer: 999;
27
- --tl-layer-following-indicator: 1000;
28
26
  }
29
27
 
30
28
  /* Button */
@@ -54,14 +52,14 @@
54
52
 
55
53
  .tlui-button:disabled,
56
54
  .tlui-button[data-disabled] {
57
- color: var(--tl-color-text-3);
55
+ color: var(--tl-color-text-disabled);
58
56
  text-shadow: none;
59
57
  cursor: default;
60
58
  }
61
59
 
62
60
  .tlui-button:disabled .tlui-kbd,
63
61
  .tlui-button[data-disabled] .tlui-kbd {
64
- color: var(--tl-color-text-3);
62
+ color: var(--tl-color-text-disabled);
65
63
  }
66
64
 
67
65
  .tlui-button > * {
@@ -430,6 +428,11 @@
430
428
  height: 15px;
431
429
  }
432
430
 
431
+ .tlui-icon__tiny {
432
+ width: 12px;
433
+ height: 12px;
434
+ }
435
+
433
436
  /* --------------------- Slider --------------------- */
434
437
 
435
438
  .tlui-slider__container {
@@ -1043,7 +1046,7 @@
1043
1046
  left: 8px;
1044
1047
  }
1045
1048
 
1046
- /*
1049
+ /*
1047
1050
  * focusing skip-to-main-content hide top ui buttons to make sure we avoid
1048
1051
  * z-index clashes on hover (see issue #8328)
1049
1052
  */
@@ -1690,8 +1693,17 @@ tldraw? probably.
1690
1693
  height: 100%;
1691
1694
  z-index: var(--tl-layer-canvas-overlays);
1692
1695
  background-color: var(--tl-color-overlay);
1693
- pointer-events: all;
1694
1696
  animation: tl-fade-in 0.12s ease-out;
1697
+ }
1698
+
1699
+ .tlui-dialog__positioner {
1700
+ position: absolute;
1701
+ top: 0px;
1702
+ left: 0px;
1703
+ width: 100%;
1704
+ height: 100%;
1705
+ z-index: var(--tl-layer-canvas-overlays);
1706
+ pointer-events: all;
1695
1707
  display: grid;
1696
1708
  place-items: center;
1697
1709
  overflow-y: auto;
@@ -2181,7 +2193,6 @@ tldraw? probably.
2181
2193
  inset: 0px;
2182
2194
  border-width: 2px;
2183
2195
  border-style: solid;
2184
- z-index: var(--tl-layer-following-indicator);
2185
2196
  pointer-events: none;
2186
2197
  }
2187
2198
 
@@ -5,6 +5,9 @@ import {
5
5
  } from '../../defaultEmbedDefinitions'
6
6
  import { getEmbedInfo, matchEmbedUrl, matchUrl } from './embeds'
7
7
 
8
+ const GOOGLE_MAPS_API_KEY = 'test-google-maps-api-key'
9
+ const TEST_EMBED_CONFIG = { google_maps: { apiKey: GOOGLE_MAPS_API_KEY } }
10
+
8
11
  describe('embed sandbox permissions', () => {
9
12
  function getSandboxString(permissions: Record<string, boolean | undefined>): string {
10
13
  return Object.entries(permissions)
@@ -250,7 +253,7 @@ const MATCH_URL_TEST_URLS: (MatchUrlTestNoMatchDef | MatchUrlTestMatchDef)[] = [
250
253
  match: true,
251
254
  output: {
252
255
  type: 'google_maps',
253
- embedUrl: `https://google.com/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}&center=52.2449313,0.0813192&zoom=14&maptype=roadmap`,
256
+ embedUrl: `https://google.com/maps/embed/v1/view?key=${GOOGLE_MAPS_API_KEY}&center=52.2449313,0.0813192&zoom=14&maptype=roadmap`,
254
257
  },
255
258
  },
256
259
  {
@@ -258,7 +261,7 @@ const MATCH_URL_TEST_URLS: (MatchUrlTestNoMatchDef | MatchUrlTestMatchDef)[] = [
258
261
  match: true,
259
262
  output: {
260
263
  type: 'google_maps',
261
- embedUrl: `https://google.co.uk/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}&center=52.2449313,0.0813192&zoom=14&maptype=roadmap`,
264
+ embedUrl: `https://google.co.uk/maps/embed/v1/view?key=${GOOGLE_MAPS_API_KEY}&center=52.2449313,0.0813192&zoom=14&maptype=roadmap`,
262
265
  },
263
266
  },
264
267
  {
@@ -266,7 +269,7 @@ const MATCH_URL_TEST_URLS: (MatchUrlTestNoMatchDef | MatchUrlTestMatchDef)[] = [
266
269
  match: true,
267
270
  output: {
268
271
  type: 'google_maps',
269
- embedUrl: `https://google.com/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}&center=51.5041626,-0.2468738&zoom=14&maptype=roadmap`,
272
+ embedUrl: `https://google.com/maps/embed/v1/view?key=${GOOGLE_MAPS_API_KEY}&center=51.5041626,-0.2468738&zoom=14&maptype=roadmap`,
270
273
  },
271
274
  },
272
275
  {
@@ -282,7 +285,7 @@ const MATCH_URL_TEST_URLS: (MatchUrlTestNoMatchDef | MatchUrlTestMatchDef)[] = [
282
285
  match: true,
283
286
  output: {
284
287
  type: 'google_maps',
285
- embedUrl: `https://google.com/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}&center=52.2449313,0.0813192&zoom=17.313261121327326&maptype=satellite`,
288
+ embedUrl: `https://google.com/maps/embed/v1/view?key=${GOOGLE_MAPS_API_KEY}&center=52.2449313,0.0813192&zoom=17.313261121327326&maptype=satellite`,
286
289
  },
287
290
  },
288
291
  {
@@ -290,7 +293,7 @@ const MATCH_URL_TEST_URLS: (MatchUrlTestNoMatchDef | MatchUrlTestMatchDef)[] = [
290
293
  match: true,
291
294
  output: {
292
295
  type: 'google_maps',
293
- embedUrl: `https://google.co.uk/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}&center=51.5074,0.1278&zoom=16.984468114035085&maptype=satellite`,
296
+ embedUrl: `https://google.co.uk/maps/embed/v1/view?key=${GOOGLE_MAPS_API_KEY}&center=51.5074,0.1278&zoom=16.984468114035085&maptype=satellite`,
294
297
  },
295
298
  },
296
299
  {
@@ -604,7 +607,7 @@ const MATCH_EMBED_TEST_URLS: (MatchEmbedTestMatchDef | MatchEmbedTestNoMatchDef)
604
607
  },
605
608
  // google_maps
606
609
  {
607
- embedUrl: `https://google.com/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}&center=52.2449313,0.0813192&zoom=14`,
610
+ embedUrl: `https://google.com/maps/embed/v1/view?key=${GOOGLE_MAPS_API_KEY}&center=52.2449313,0.0813192&zoom=14`,
608
611
  match: true,
609
612
  output: {
610
613
  type: 'google_maps',
@@ -612,7 +615,7 @@ const MATCH_EMBED_TEST_URLS: (MatchEmbedTestMatchDef | MatchEmbedTestNoMatchDef)
612
615
  },
613
616
  },
614
617
  {
615
- embedUrl: `https://google.com/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}&center=52.2449313,0.0813192&zoom=14&maptype=satellite`,
618
+ embedUrl: `https://google.com/maps/embed/v1/view?key=${GOOGLE_MAPS_API_KEY}&center=52.2449313,0.0813192&zoom=14&maptype=satellite`,
616
619
  match: true,
617
620
  output: {
618
621
  type: 'google_maps',
@@ -620,7 +623,7 @@ const MATCH_EMBED_TEST_URLS: (MatchEmbedTestMatchDef | MatchEmbedTestNoMatchDef)
620
623
  },
621
624
  },
622
625
  {
623
- embedUrl: `https://google.co.uk/maps/embed/v1/view?key=${process.env.NEXT_PUBLIC_GC_API_KEY}&center=51.5074,0.1278&zoom=12&maptype=roadmap`,
626
+ embedUrl: `https://google.co.uk/maps/embed/v1/view?key=${GOOGLE_MAPS_API_KEY}&center=51.5074,0.1278&zoom=12&maptype=roadmap`,
624
627
  match: true,
625
628
  output: {
626
629
  type: 'google_maps',
@@ -628,8 +631,7 @@ const MATCH_EMBED_TEST_URLS: (MatchEmbedTestMatchDef | MatchEmbedTestNoMatchDef)
628
631
  },
629
632
  },
630
633
  {
631
- embedUrl:
632
- 'https://google.com/maps/embed?key=${process.env.NEXT_PUBLIC_GC_API_KEY}&center=52.2449313,0.0813192&zoom=14',
634
+ embedUrl: 'https://google.com/maps/embed?key=KEY&center=52.2449313,0.0813192&zoom=14',
633
635
  match: false,
634
636
  },
635
637
  // google_calendar
@@ -786,7 +788,7 @@ const MATCH_EMBED_TEST_URLS: (MatchEmbedTestMatchDef | MatchEmbedTestNoMatchDef)
786
788
 
787
789
  for (const testDef of MATCH_URL_TEST_URLS) {
788
790
  test(`matchUrl("${testDef.url}")`, () => {
789
- const result = matchUrl(DEFAULT_EMBED_DEFINITIONS, testDef.url)
791
+ const result = matchUrl(DEFAULT_EMBED_DEFINITIONS, testDef.url, TEST_EMBED_CONFIG)
790
792
  if (testDef.match) {
791
793
  expect(result).toBeDefined()
792
794
  expect(result?.definition.type).toBe(testDef.output.type)
@@ -797,7 +799,7 @@ for (const testDef of MATCH_URL_TEST_URLS) {
797
799
  })
798
800
 
799
801
  test(`getEmbedInfo("${testDef.url}")`, () => {
800
- const result = getEmbedInfo(DEFAULT_EMBED_DEFINITIONS, testDef.url)
802
+ const result = getEmbedInfo(DEFAULT_EMBED_DEFINITIONS, testDef.url, TEST_EMBED_CONFIG)
801
803
  if (testDef.match) {
802
804
  expect(result).toBeDefined()
803
805
  expect(result?.definition.type).toBe(testDef.output.type)
@@ -47,13 +47,17 @@ const checkHostnames = (hostnames: readonly string[], targetHostname: string) =>
47
47
  }
48
48
 
49
49
  /** @public */
50
- export function matchUrl(definitions: readonly TLEmbedDefinition[], url: string) {
50
+ export function matchUrl(
51
+ definitions: readonly TLEmbedDefinition[],
52
+ url: string,
53
+ embedConfig?: Record<string, unknown>
54
+ ) {
51
55
  const parsed = safeParseUrl(url)
52
56
  if (!parsed) return undefined
53
57
  const host = parsed.host.replace('www.', '')
54
58
  for (const localEmbedDef of definitions) {
55
59
  if (checkHostnames(localEmbedDef.hostnames, host)) {
56
- const embedUrl = localEmbedDef.toEmbedUrl(url)
60
+ const embedUrl = localEmbedDef.toEmbedUrl(url, embedConfig?.[localEmbedDef.type])
57
61
 
58
62
  if (embedUrl) {
59
63
  return {
@@ -81,14 +85,16 @@ export type TLEmbedResult =
81
85
  * return undefined.
82
86
  *
83
87
  * @param inputUrl - The URL to match
88
+ * @param embedConfig - Optional per-embed config, keyed by embed type, passed to `toEmbedUrl`
84
89
  * @public
85
90
  */
86
91
  export function getEmbedInfo(
87
92
  definitions: readonly TLEmbedDefinition[],
88
- inputUrl: string
93
+ inputUrl: string,
94
+ embedConfig?: Record<string, unknown>
89
95
  ): TLEmbedResult {
90
96
  try {
91
- return matchUrl(definitions, inputUrl) ?? matchEmbedUrl(definitions, inputUrl)
97
+ return matchUrl(definitions, inputUrl, embedConfig) ?? matchEmbedUrl(definitions, inputUrl)
92
98
  } catch {
93
99
  return undefined
94
100
  }
@@ -24,14 +24,14 @@ export function pointsToBase64(points: VecModel[]): string {
24
24
  *
25
25
  * @example
26
26
  * ```ts
27
- * const points = base64ToPoints(shape.props.segments[0].path)
27
+ * const points = base64ToPoints(shape.props.segments[0].path, shape.props.segments[0].dim)
28
28
  * expect(points[0].x).toBe(0)
29
29
  * ```
30
30
  *
31
31
  * @public
32
32
  */
33
- export function base64ToPoints(base64: string): VecModel[] {
34
- return b64Vecs.decodePoints(base64)
33
+ export function base64ToPoints(base64: string, dim?: 2 | 3): VecModel[] {
34
+ return b64Vecs.decodePoints(base64, dim)
35
35
  }
36
36
 
37
37
  /**
@@ -0,0 +1,36 @@
1
+ import { TLRichText, toRichText } from '@tldraw/editor'
2
+ import { isEmptyRichText } from './richText'
3
+
4
+ describe('isEmptyRichText', () => {
5
+ it('treats a paragraph with no content key as empty (interactive editor output)', () => {
6
+ expect(isEmptyRichText(toRichText(''))).toBe(true)
7
+ })
8
+
9
+ it('treats a paragraph with an empty content array as empty (programmatic authoring)', () => {
10
+ const richText: TLRichText = {
11
+ type: 'doc',
12
+ content: [{ type: 'paragraph', attrs: { dir: 'auto' }, content: [] }],
13
+ }
14
+ expect(isEmptyRichText(richText)).toBe(true)
15
+ })
16
+
17
+ it('treats a doc with an empty content array as empty (hand-authored / importer form)', () => {
18
+ const richText: TLRichText = { type: 'doc', content: [] }
19
+ expect(isEmptyRichText(richText)).toBe(true)
20
+ })
21
+
22
+ it('treats a paragraph with text as non-empty', () => {
23
+ expect(isEmptyRichText(toRichText('Hello'))).toBe(false)
24
+ })
25
+
26
+ it('treats multiple paragraphs as non-empty', () => {
27
+ const richText: TLRichText = {
28
+ type: 'doc',
29
+ content: [
30
+ { type: 'paragraph', content: [] },
31
+ { type: 'paragraph', content: [] },
32
+ ],
33
+ }
34
+ expect(isEmptyRichText(richText)).toBe(false)
35
+ })
36
+ })
@@ -107,8 +107,16 @@ export function renderHtmlFromRichTextForMeasurement(editor: Editor, richText: T
107
107
  const plainTextFromRichTextCache = new WeakCache<TLRichText, string>()
108
108
 
109
109
  export function isEmptyRichText(richText: TLRichText) {
110
+ // An empty document has no text. It can be encoded several equally-valid ways:
111
+ // an empty `content` array at the doc level, or a single paragraph whose own
112
+ // `content` is missing or an empty array. The interactive editor emits the
113
+ // single-paragraph / missing-`content` form; programmatic authoring (snapshot
114
+ // loads, and agents/importers emitting tldraw JSON) commonly emits the
115
+ // empty-array forms. Treat them all as empty.
116
+ if (richText.content.length === 0) return true
110
117
  if (richText.content.length === 1) {
111
- if (!(richText.content[0] as any).content) return true
118
+ const node = richText.content[0] as any
119
+ if (!node.content || node.content.length === 0) return true
112
120
  }
113
121
  return false
114
122
  }