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
@@ -5,6 +5,7 @@ import {
5
5
  TLAssetId,
6
6
  TLBookmarkAsset,
7
7
  TLBookmarkShape,
8
+ TLShapeId,
8
9
  TLShapePartial,
9
10
  createShapeId,
10
11
  debounce,
@@ -84,6 +85,44 @@ export function updateBookmarkAssetOnUrlChange(editor: Editor, shape: TLBookmark
84
85
  }
85
86
  }
86
87
 
88
+ /**
89
+ * Resolve the asset id to render for a bookmark. Bookmark assets are keyed
90
+ * deterministically by URL, so if the shape has no `assetId` but an asset for
91
+ * its URL already exists in the store, use that.
92
+ *
93
+ * This keeps a bookmark from staying stuck on its placeholder after the shape
94
+ * is recreated outside of the hydration flow — e.g. on redo, where the
95
+ * placeholder is restored with a null `assetId` but its hydrated asset (created
96
+ * with `history: 'ignore'`) still lives in the store.
97
+ */
98
+ export function getResolvedBookmarkAssetId(
99
+ editor: Editor,
100
+ assetId: TLAssetId | null,
101
+ url: string
102
+ ): TLAssetId | null {
103
+ if (assetId) return assetId
104
+ if (!url) return null
105
+
106
+ const derivedId: TLAssetId = AssetRecordType.createId(getHashForString(url))
107
+ return editor.getAsset(derivedId) ? derivedId : null
108
+ }
109
+
110
+ /**
111
+ * The effective height to render and measure a bookmark at.
112
+ *
113
+ * Normally this is the stored `props.h`. But when the shape's `assetId` resolves
114
+ * to a different asset than it stores — e.g. a placeholder restored on redo with
115
+ * a null `assetId` whose asset already exists in the store — `props.h` is stale
116
+ * (it still holds the placeholder height). In that case recompute the height
117
+ * from the resolved asset so rendering, the indicator, and the geometry/selection
118
+ * bounds all stay in sync.
119
+ */
120
+ export function getBookmarkShapeHeight(editor: Editor, shape: TLBookmarkShape): number {
121
+ const resolvedAssetId = getResolvedBookmarkAssetId(editor, shape.props.assetId, shape.props.url)
122
+ if (resolvedAssetId === shape.props.assetId) return shape.props.h
123
+ return getBookmarkHeight(editor, resolvedAssetId)
124
+ }
125
+
87
126
  async function _createBookmarkAssetOnUrlChange(editor: Editor, shape: TLBookmarkShape) {
88
127
  if (editor.isDisposed) return
89
128
 
@@ -128,12 +167,17 @@ function createBookmarkAssetOnUrlChange(editor: Editor, shape: TLBookmarkShape)
128
167
  }
129
168
 
130
169
  /**
131
- * Creates a bookmark shape from a URL with unfurled metadata.
170
+ * Creates a bookmark shape from a URL.
171
+ *
172
+ * The shape is created immediately as a placeholder so the user gets visible
173
+ * feedback at the paste location, and the bookmark metadata (title, description,
174
+ * image, favicon) is fetched in the background. Once metadata resolves, the
175
+ * shape is patched with the resulting asset. If the fetch fails, the shape is
176
+ * left as a URL-only bookmark.
132
177
  *
133
178
  * @returns A Result containing the created bookmark shape or an error
134
179
  * @public
135
180
  */
136
-
137
181
  export async function createBookmarkFromUrl(
138
182
  editor: Editor,
139
183
  {
@@ -145,10 +189,12 @@ export async function createBookmarkFromUrl(
145
189
  }
146
190
  ): Promise<Result<TLBookmarkShape, string>> {
147
191
  try {
148
- // Create the bookmark asset with unfurled metadata
149
- const asset = await editor.getAssetForExternalContent({ type: 'url', url })
192
+ // If we already have a bookmark asset for this URL (e.g. another bookmark
193
+ // shape was created from the same URL earlier), use it immediately rather
194
+ // than re-fetching.
195
+ const expectedAssetId: TLAssetId = AssetRecordType.createId(getHashForString(url))
196
+ const existingAsset = editor.getAsset(expectedAssetId) as TLBookmarkAsset | null
150
197
 
151
- // Create the bookmark shape
152
198
  const shapeId = createShapeId()
153
199
  const shapePartial: TLShapePartial<TLBookmarkShape> = {
154
200
  id: shapeId,
@@ -159,26 +205,70 @@ export async function createBookmarkFromUrl(
159
205
  opacity: 1,
160
206
  props: {
161
207
  url,
162
- assetId: asset?.id || null,
208
+ assetId: existingAsset?.id ?? null,
163
209
  w: BOOKMARK_WIDTH,
164
- h: getBookmarkHeight(editor, asset?.id),
210
+ h: getBookmarkHeight(editor, existingAsset?.id),
165
211
  },
166
212
  }
167
213
 
168
214
  editor.run(() => {
169
- // Create the asset if we have one
170
- if (asset) {
171
- editor.createAssets([asset])
172
- }
173
-
174
- // Create the shape
175
215
  editor.createShapes([shapePartial])
176
216
  })
177
217
 
178
- // Get the created shape
179
- const createdShape = editor.getShape(shapeId) as TLBookmarkShape
218
+ const createdShape = editor.getShape<TLBookmarkShape>(shapeId)
219
+ if (!createdShape) {
220
+ return Result.err('Failed to create bookmark shape')
221
+ }
222
+
223
+ // If we already had the asset, we're done — no metadata fetch needed.
224
+ if (existingAsset) {
225
+ return Result.ok(createdShape)
226
+ }
227
+
228
+ // Otherwise kick off the metadata fetch in the background. The shape is
229
+ // already visible as a placeholder; once the asset resolves we'll patch
230
+ // the shape with its assetId.
231
+ void hydrateBookmarkShape(editor, shapeId, url)
232
+
180
233
  return Result.ok(createdShape)
181
234
  } catch (error) {
182
235
  return Result.err(error instanceof Error ? error.message : 'Failed to create bookmark')
183
236
  }
184
237
  }
238
+
239
+ async function hydrateBookmarkShape(editor: Editor, shapeId: TLShapeId, url: string) {
240
+ let asset
241
+ try {
242
+ asset = await editor.getAssetForExternalContent({ type: 'url', url })
243
+ } catch (error) {
244
+ console.error(error)
245
+ return
246
+ }
247
+
248
+ if (editor.isDisposed) return
249
+ if (!asset) return
250
+
251
+ // The shape may have been deleted (e.g. via undo), had its URL changed, or
252
+ // had its asset replaced before the fetch resolved. In any of those cases we
253
+ // don't want to attach metadata for the old URL.
254
+ const shape = editor.getShape<TLBookmarkShape>(shapeId)
255
+ if (!shape) return
256
+ if (shape.props.url !== url) return
257
+ if (shape.props.assetId) return
258
+
259
+ editor.run(
260
+ () => {
261
+ if (!editor.getAsset(asset.id)) {
262
+ editor.createAssets([asset])
263
+ }
264
+ editor.updateShapes([
265
+ {
266
+ id: shapeId,
267
+ type: 'bookmark',
268
+ props: { assetId: asset.id },
269
+ },
270
+ ])
271
+ },
272
+ { history: 'ignore' }
273
+ )
274
+ }
@@ -11,6 +11,7 @@ import {
11
11
  TLResizeInfo,
12
12
  TLShapeUtilCanvasSvgDef,
13
13
  VecLike,
14
+ b64Vecs,
14
15
  drawShapeMigrations,
15
16
  drawShapeProps,
16
17
  getColorValue,
@@ -266,11 +267,12 @@ function getDot(point: VecLike, sw: number) {
266
267
  }
267
268
 
268
269
  function getIsDot(shape: TLDrawShape) {
269
- // First point is 16 base64 chars (3 Float32s = 12 bytes)
270
- // Each delta point is 8 base64 chars (3 Float16s = 6 bytes)
271
- // 1 point = 16 chars, 2 points = 24 chars
272
- // Check if we have less than 2 points without decoding
273
- return shape.props.segments.length === 1 && shape.props.segments[0].path.length < 24
270
+ // A dot is a single-point segment. isSinglePoint knows the per-encoding length
271
+ // (and takes the segment's dim), so this stays correct for both 2D and 3D paths.
272
+ // A bare length threshold can't: a 2-point 2D path is the same length as a 1-point
273
+ // 3D path, so only the dim disambiguates them.
274
+ const segment = shape.props.segments[0]
275
+ return shape.props.segments.length === 1 && b64Vecs.isSinglePoint(segment.path, segment.dim)
274
276
  }
275
277
 
276
278
  function DrawShapeSvg({
@@ -109,7 +109,7 @@ export function getPointsFromDrawSegment(
109
109
  scaleY: number,
110
110
  points: Vec[] = []
111
111
  ) {
112
- const _points = b64Vecs.decodePoints(segment.path)
112
+ const _points = b64Vecs.decodePoints(segment.path, segment.dim)
113
113
 
114
114
  // Apply scale factors (used for lazy resize and flipping)
115
115
  if (scaleX !== 1 || scaleY !== 1) {
@@ -8,6 +8,7 @@ import {
8
8
  TLKeyboardEventInfo,
9
9
  TLPointerEventInfo,
10
10
  TLShapePartial,
11
+ DIM_2D,
11
12
  Vec,
12
13
  VecModel,
13
14
  b64Vecs,
@@ -40,6 +41,11 @@ export class Drawing extends StateNode {
40
41
  isPen = false
41
42
  isPenOrStylus = false
42
43
 
44
+ // Encoding dimension for segments created during this stroke. `2` drops the
45
+ // constant pressure value (non-pressure devices), `undefined` keeps full 3D.
46
+ // Decided once in startShape from the input device — see the note there.
47
+ segmentDim: 2 | undefined = undefined
48
+
43
49
  segmentMode = 'free' as 'free' | 'straight' | 'starting_straight' | 'starting_free'
44
50
 
45
51
  didJustShiftClickToExtendPreviousShapeLine = false
@@ -157,9 +163,9 @@ export class Drawing extends StateNode {
157
163
  const theme = this.editor.getCurrentTheme()
158
164
  strokeWidth = theme.strokeWidth * STROKE_SIZES[size]
159
165
  }
160
- const firstPoint = b64Vecs.decodeFirstPoint(segments[0].path)
166
+ const firstPoint = b64Vecs.decodeFirstPoint(segments[0].path, segments[0].dim)
161
167
  const lastSegment = segments[segments.length - 1]
162
- const lastPoint = b64Vecs.decodeLastPoint(lastSegment.path)
168
+ const lastPoint = b64Vecs.decodeLastPoint(lastSegment.path, lastSegment.dim)
163
169
 
164
170
  const isDynamicResizingEnabled = this.editor.user.getIsDynamicResizeMode()
165
171
 
@@ -180,6 +186,16 @@ export class Drawing extends StateNode {
180
186
  )
181
187
  }
182
188
 
189
+ /**
190
+ * Build a segment from points, encoding `path` at this stroke's `segmentDim` and
191
+ * attaching `dim: 2` only when z was dropped. 3D segments omit `dim` so they
192
+ * serialize byte-identically to pre-#8879 data.
193
+ */
194
+ private makeSegment(type: TLDrawShapeSegment['type'], points: VecModel[]): TLDrawShapeSegment {
195
+ const path = b64Vecs.encodePoints(points, this.segmentDim)
196
+ return this.segmentDim === DIM_2D ? { type, path, dim: DIM_2D } : { type, path }
197
+ }
198
+
183
199
  private startShape() {
184
200
  const inputs = this.editor.inputs
185
201
  const originPagePoint = inputs.getOriginPagePoint()
@@ -199,6 +215,13 @@ export class Drawing extends StateNode {
199
215
  // or a broken OS.
200
216
  this.isPenOrStylus = (isPen && z !== 0) || (z > 0 && z < 0.5) || (z > 0.5 && z < 1)
201
217
 
218
+ // Decide the encoding dimension once, here, from the input device — not by
219
+ // scanning point data (content-driven). Device-driven keeps the encoded bytes
220
+ // deterministic and is O(1) at stroke start. Non-pressure devices report a
221
+ // constant z (0 / 0.5 / 1) that we normalize to 0.5 and omit entirely (2D);
222
+ // pen/stylus input keeps the full 3D encoding.
223
+ this.segmentDim = this.isPenOrStylus ? undefined : DIM_2D
224
+
202
225
  const pressure = this.isPenOrStylus ? z * 1.25 : 0.5
203
226
 
204
227
  this.segmentMode = this.editor.inputs.getShiftKey() ? 'straight' : 'free'
@@ -217,18 +240,15 @@ export class Drawing extends StateNode {
217
240
 
218
241
  const prevSegment = last(shape.props.segments)
219
242
  if (!prevSegment) throw Error('Expected a previous segment!')
220
- const prevPoint = b64Vecs.decodeLastPoint(prevSegment.path)
243
+ const prevPoint = b64Vecs.decodeLastPoint(prevSegment.path, prevSegment.dim)
221
244
  if (!prevPoint) throw Error('Expected a previous point!')
222
245
 
223
246
  const { x, y } = this.editor.getPointInShapeSpace(shape, originPagePoint).toFixed()
224
247
 
225
- const newSegment: TLDrawShapeSegment = {
226
- type: this.segmentMode,
227
- path: b64Vecs.encodePoints([
228
- { x: prevPoint.x, y: prevPoint.y, z: +pressure.toFixed(2) },
229
- { x, y, z: +pressure.toFixed(2) },
230
- ]),
231
- }
248
+ const newSegment = this.makeSegment(this.segmentMode, [
249
+ { x: prevPoint.x, y: prevPoint.y, z: +pressure.toFixed(2) },
250
+ { x, y, z: +pressure.toFixed(2) },
251
+ ])
232
252
 
233
253
  // Convert prevPoint to page space
234
254
  const prevPointPageSpace = Mat.applyToPoint(
@@ -285,12 +305,7 @@ export class Drawing extends StateNode {
285
305
  props: {
286
306
  isPen: this.isPenOrStylus,
287
307
  scale: this.editor.getResizeScaleFactor(),
288
- segments: [
289
- {
290
- type: this.segmentMode,
291
- path: b64Vecs.encodePoints([initialPoint]),
292
- },
293
- ],
308
+ segments: [this.makeSegment(this.segmentMode, [initialPoint])],
294
309
  },
295
310
  })
296
311
  const shape = this.editor.getShape<DrawableShape>(id)
@@ -350,7 +365,7 @@ export class Drawing extends StateNode {
350
365
  const prevSegment = last(segments)
351
366
  if (!prevSegment) throw Error('Expected a previous segment!')
352
367
 
353
- const prevLastPoint = b64Vecs.decodeLastPoint(prevSegment.path)
368
+ const prevLastPoint = b64Vecs.decodeLastPoint(prevSegment.path, prevSegment.dim)
354
369
  if (!prevLastPoint) throw Error('Expected a previous last point!')
355
370
 
356
371
  let newSegment: TLDrawShapeSegment
@@ -363,19 +378,13 @@ export class Drawing extends StateNode {
363
378
  if (prevSegment.type === 'straight') {
364
379
  this.currentLineLength += Vec.Dist(prevLastPoint, newLastPoint)
365
380
 
366
- newSegment = {
367
- type: 'straight',
368
- path: b64Vecs.encodePoints([prevLastPoint, newLastPoint]),
369
- }
381
+ newSegment = this.makeSegment('straight', [prevLastPoint, newLastPoint])
370
382
 
371
383
  const transform = this.editor.getShapePageTransform(shape)!
372
384
 
373
385
  this.pagePointWhereCurrentSegmentChanged = Mat.applyToPoint(transform, prevLastPoint)
374
386
  } else {
375
- newSegment = {
376
- type: 'straight',
377
- path: b64Vecs.encodePoints([newLastPoint, newPoint]),
378
- }
387
+ newSegment = this.makeSegment('straight', [newLastPoint, newPoint])
379
388
  }
380
389
 
381
390
  const shapePartial: TLShapePartial<DrawableShape> = {
@@ -422,7 +431,10 @@ export class Drawing extends StateNode {
422
431
 
423
432
  const newSegments = segments.slice()
424
433
  const prevStraightSegment = newSegments[newSegments.length - 1]
425
- const prevPoint = b64Vecs.decodeLastPoint(prevStraightSegment.path)
434
+ const prevPoint = b64Vecs.decodeLastPoint(
435
+ prevStraightSegment.path,
436
+ prevStraightSegment.dim
437
+ )
426
438
 
427
439
  if (!prevPoint) {
428
440
  throw Error('No previous point!')
@@ -436,10 +448,7 @@ export class Drawing extends StateNode {
436
448
  // Initialize cache for the new free segment
437
449
  this.currentSegmentPoints = interpolatedPoints
438
450
 
439
- const newFreeSegment: TLDrawShapeSegment = {
440
- type: 'free',
441
- path: b64Vecs.encodePoints(interpolatedPoints),
442
- }
451
+ const newFreeSegment = this.makeSegment('free', interpolatedPoints)
443
452
 
444
453
  const finalSegments = [...newSegments, newFreeSegment]
445
454
 
@@ -516,8 +525,8 @@ export class Drawing extends StateNode {
516
525
  if (!segment) break
517
526
  if (segment.type === 'free') continue
518
527
 
519
- const first = b64Vecs.decodeFirstPoint(segment.path)
520
- const lastPoint = b64Vecs.decodeLastPoint(segment.path)
528
+ const first = b64Vecs.decodeFirstPoint(segment.path, segment.dim)
529
+ const lastPoint = b64Vecs.decodeLastPoint(segment.path, segment.dim)
521
530
  if (!(first && lastPoint)) continue
522
531
 
523
532
  // Snap to the nearest point on the segment, if it's closer than the previous snapped point
@@ -544,8 +553,8 @@ export class Drawing extends StateNode {
544
553
 
545
554
  if (didSnap && snapSegment) {
546
555
  const transform = this.editor.getShapePageTransform(shape)!
547
- const first = b64Vecs.decodeFirstPoint(snapSegment.path)
548
- const lastPoint = b64Vecs.decodeLastPoint(snapSegment.path)
556
+ const first = b64Vecs.decodeFirstPoint(snapSegment.path, snapSegment.dim)
557
+ const lastPoint = b64Vecs.decodeLastPoint(snapSegment.path, snapSegment.dim)
549
558
  if (!first || !lastPoint) throw Error('Expected a last point!')
550
559
 
551
560
  const A = Mat.applyToPoint(transform, first)
@@ -587,17 +596,20 @@ export class Drawing extends StateNode {
587
596
  // without continuing the previous line. In this case, we want to remove the previous segment.
588
597
 
589
598
  this.currentLineLength +=
590
- newSegments.length && b64Vecs.decodeFirstPoint(newSegment.path)
591
- ? Vec.Dist(b64Vecs.decodeFirstPoint(newSegment.path)!, Vec.From(newPoint))
599
+ newSegments.length && b64Vecs.decodeFirstPoint(newSegment.path, newSegment.dim)
600
+ ? Vec.Dist(
601
+ b64Vecs.decodeFirstPoint(newSegment.path, newSegment.dim)!,
602
+ Vec.From(newPoint)
603
+ )
592
604
  : 0
593
605
 
594
606
  newSegments[newSegments.length - 1] = {
595
607
  ...newSegment,
596
608
  type: 'straight',
597
- path: b64Vecs.encodePoints([
598
- b64Vecs.decodeFirstPoint(newSegment.path)!,
599
- Vec.From(newPoint),
600
- ]),
609
+ path: b64Vecs.encodePoints(
610
+ [b64Vecs.decodeFirstPoint(newSegment.path, newSegment.dim)!, Vec.From(newPoint)],
611
+ newSegment.dim
612
+ ),
601
613
  }
602
614
 
603
615
  const shapePartial: TLShapePartial<DrawableShape> = {
@@ -642,7 +654,7 @@ export class Drawing extends StateNode {
642
654
  const newSegment = newSegments[newSegments.length - 1]
643
655
  newSegments[newSegments.length - 1] = {
644
656
  ...newSegment,
645
- path: b64Vecs.encodePoints(cachedPoints),
657
+ path: b64Vecs.encodePoints(cachedPoints, newSegment.dim),
646
658
  }
647
659
 
648
660
  const dvStrokeWidth = (getDisplayValues(this.util as any, shape) as { strokeWidth: number })
@@ -692,12 +704,7 @@ export class Drawing extends StateNode {
692
704
  props: {
693
705
  isPen: this.isPenOrStylus,
694
706
  scale: props.scale,
695
- segments: [
696
- {
697
- type: 'free',
698
- path: b64Vecs.encodePoints([initialPoint]),
699
- },
700
- ],
707
+ segments: [this.makeSegment('free', [initialPoint])],
701
708
  },
702
709
  })
703
710
 
@@ -724,7 +731,7 @@ export class Drawing extends StateNode {
724
731
  let length = 0
725
732
 
726
733
  for (let j = 0; j < segments.length; j++) {
727
- const points = b64Vecs.decodePoints(segments[j].path)
734
+ const points = b64Vecs.decodePoints(segments[j].path, segments[j].dim)
728
735
  for (let i = 0; i < points.length - 1; i++) {
729
736
  length += Vec.Dist2(points[i], points[i + 1])
730
737
  }
@@ -18,7 +18,7 @@ import {
18
18
  } from '@tldraw/editor'
19
19
  import {
20
20
  DEFAULT_EMBED_DEFINITIONS,
21
- EmbedDefinition,
21
+ DefaultEmbedConfig,
22
22
  TLEmbedDefinition,
23
23
  TLEmbedShapePermissions,
24
24
  embedShapePermissionDefaults,
@@ -41,6 +41,15 @@ export interface EmbedShapeOptions extends ShapeOptionsWithDisplayValues<
41
41
  > {
42
42
  /** The embed definitions to use for this shape util. */
43
43
  readonly embedDefinitions: readonly TLEmbedDefinition[]
44
+ /**
45
+ * Per-embed configuration, keyed by embed type. Passed to each definition's `toEmbedUrl` when
46
+ * building its embed URL — for example, an API key for the default Google Maps embed:
47
+ *
48
+ * ```ts
49
+ * EmbedShapeUtil.configure({ embedConfig: { google_maps: { apiKey: '...' } } })
50
+ * ```
51
+ */
52
+ readonly embedConfig?: DefaultEmbedConfig & Record<string, unknown>
44
53
  }
45
54
 
46
55
  const getSandboxPermissions = (permissions: TLEmbedShapePermissions) => {
@@ -58,6 +67,7 @@ export class EmbedShapeUtil extends BaseBoxShapeUtil<TLEmbedShape> {
58
67
 
59
68
  override options: EmbedShapeOptions = {
60
69
  embedDefinitions: DEFAULT_EMBED_DEFINITIONS,
70
+ embedConfig: {},
61
71
  getDefaultDisplayValues(): EmbedShapeUtilDisplayValues {
62
72
  return {
63
73
  showShadow: true,
@@ -74,10 +84,10 @@ export class EmbedShapeUtil extends BaseBoxShapeUtil<TLEmbedShape> {
74
84
  return result.definition.canEditWhileLocked ?? true
75
85
  }
76
86
 
77
- private static legacyEmbedDefinitions: readonly EmbedDefinition[] | null = null
87
+ private static legacyEmbedDefinitions: readonly TLEmbedDefinition[] | null = null
78
88
 
79
89
  /** @deprecated - Use `EmbedShapeUtil.configure({ embedDefinitions: [...] })` instead. */
80
- static setEmbedDefinitions(embedDefinitions: readonly EmbedDefinition[]) {
90
+ static setEmbedDefinitions(embedDefinitions: readonly TLEmbedDefinition[]) {
81
91
  EmbedShapeUtil.legacyEmbedDefinitions = embedDefinitions
82
92
  }
83
93
 
@@ -90,7 +100,7 @@ export class EmbedShapeUtil extends BaseBoxShapeUtil<TLEmbedShape> {
90
100
  }
91
101
 
92
102
  getEmbedDefinition(url: string): TLEmbedResult {
93
- return getEmbedInfo(this.getEmbedDefs(), url)
103
+ return getEmbedInfo(this.getEmbedDefs(), url, this.options.embedConfig)
94
104
  }
95
105
 
96
106
  override getText(shape: TLEmbedShape) {
@@ -1,4 +1,4 @@
1
- import { BaseBoxShapeTool, TLShape, TLShapeId } from '@tldraw/editor'
1
+ import { BaseBoxShapeTool, Editor, TLShape, TLShapeId } from '@tldraw/editor'
2
2
 
3
3
  /** @public */
4
4
  export class FrameShapeTool extends BaseBoxShapeTool {
@@ -9,29 +9,7 @@ export class FrameShapeTool extends BaseBoxShapeTool {
9
9
  override onCreate(shape: TLShape | null): void {
10
10
  if (!shape) return
11
11
 
12
- const bounds = this.editor.getShapePageBounds(shape)!
13
- const shapesToAddToFrame: TLShapeId[] = []
14
- const ancestorIds = this.editor.getShapeAncestors(shape).map((shape) => shape.id)
15
-
16
- this.editor.getSortedChildIdsForParent(shape.parentId).map((siblingShapeId) => {
17
- const siblingShape = this.editor.getShape(siblingShapeId)
18
- if (!siblingShape) return
19
- // We don't want to frame the frame itself
20
- if (siblingShape.id === shape.id) return
21
- if (siblingShape.isLocked) return
22
-
23
- const pageShapeBounds = this.editor.getShapePageBounds(siblingShape)
24
- if (!pageShapeBounds) return
25
-
26
- // Frame shape encloses page shape
27
- if (bounds.contains(pageShapeBounds)) {
28
- if (canEnclose(siblingShape, ancestorIds, shape)) {
29
- shapesToAddToFrame.push(siblingShape.id)
30
- }
31
- }
32
- })
33
-
34
- this.editor.reparentShapes(shapesToAddToFrame, shape.id)
12
+ this.editor.reparentShapes(getEnclosedShapeIds(this.editor, shape), shape.id)
35
13
 
36
14
  if (this.editor.getInstanceState().isToolLocked) {
37
15
  this.editor.setCurrentTool('frame')
@@ -41,6 +19,39 @@ export class FrameShapeTool extends BaseBoxShapeTool {
41
19
  }
42
20
  }
43
21
 
22
+ /**
23
+ * Get the ids of the sibling shapes that a frame would enclose at its current page bounds.
24
+ *
25
+ * @internal
26
+ */
27
+ export function getEnclosedShapeIds(editor: Editor, shape: TLShape): TLShapeId[] {
28
+ const bounds = editor.getShapePageBounds(shape)
29
+ if (!bounds) return []
30
+
31
+ const enclosedShapeIds: TLShapeId[] = []
32
+ const ancestorIds = editor.getShapeAncestors(shape).map((shape) => shape.id)
33
+
34
+ editor.getSortedChildIdsForParent(shape.parentId).map((siblingShapeId) => {
35
+ const siblingShape = editor.getShape(siblingShapeId)
36
+ if (!siblingShape) return
37
+ // We don't want to frame the frame itself
38
+ if (siblingShape.id === shape.id) return
39
+ if (siblingShape.isLocked) return
40
+
41
+ const pageShapeBounds = editor.getShapePageBounds(siblingShape)
42
+ if (!pageShapeBounds) return
43
+
44
+ // Frame shape encloses page shape
45
+ if (bounds.contains(pageShapeBounds)) {
46
+ if (canEnclose(siblingShape, ancestorIds, shape)) {
47
+ enclosedShapeIds.push(siblingShape.id)
48
+ }
49
+ }
50
+ })
51
+
52
+ return enclosedShapeIds
53
+ }
54
+
44
55
  /** @internal */
45
56
  function canEnclose(shape: TLShape, ancestorIds: TLShapeId[], frame: TLShape): boolean {
46
57
  // We don't want to pull in shapes that are ancestors of the frame (can create a cycle)
@@ -182,6 +182,52 @@ describe('Resizing geo shapes with labels', () => {
182
182
  })
183
183
  })
184
184
 
185
+ describe('Geo shapes with programmatically-authored empty rich text', () => {
186
+ const geoId = createShapeId('geo')
187
+
188
+ function getGeo() {
189
+ return editor.getShape<TLGeoShape>(geoId)!
190
+ }
191
+
192
+ // An empty paragraph can be encoded two equally-valid ways. The interactive
193
+ // editor emits the first (no `content` key); programmatic authoring (snapshot
194
+ // load, agents emitting tldraw JSON) commonly emits the second (`content: []`).
195
+ // Both mean "no label", so neither should grow the shape to fit a phantom line.
196
+ const emptyForms = [
197
+ {
198
+ label: 'paragraph with no content key (toRichText output)',
199
+ richText: toRichText(''),
200
+ },
201
+ {
202
+ label: 'paragraph with empty content array',
203
+ richText: {
204
+ type: 'doc',
205
+ content: [{ type: 'paragraph', attrs: { dir: 'auto' }, content: [] }],
206
+ } as TLGeoShape['props']['richText'],
207
+ },
208
+ {
209
+ label: 'doc with empty content array',
210
+ richText: { type: 'doc', content: [] } as TLGeoShape['props']['richText'],
211
+ },
212
+ ]
213
+
214
+ for (const { label, richText } of emptyForms) {
215
+ test(`does not grow height for an empty label: ${label}`, () => {
216
+ editor.createShapes([
217
+ {
218
+ id: geoId,
219
+ type: 'geo',
220
+ props: { w: 110, h: 24, geo: 'rectangle', size: 's', richText },
221
+ },
222
+ ])
223
+
224
+ const geo = getGeo()
225
+ expect(geo.props.h).toBe(24)
226
+ expect(geo.props.growY).toBe(0)
227
+ })
228
+ }
229
+ })
230
+
185
231
  describe('GeoShapeUtil.configure with customGeoTypes', () => {
186
232
  // Snapshot the built-in geo values so we can clean up any custom keys added
187
233
  // during these tests. `GeoShapeUtil.configure({ customGeoTypes })` mutates
@@ -30,6 +30,7 @@ import {
30
30
  getFontsFromRichText,
31
31
  isEqual,
32
32
  lerp,
33
+ resolveLineHeightPx,
33
34
  toRichText,
34
35
  useColorMode,
35
36
  useValue,
@@ -271,7 +272,7 @@ export class GeoShapeUtil extends BaseBoxShapeUtil<TLGeoShape> {
271
272
  // Calculate minimum label dimensions based on font size and shape size
272
273
  const unscaledMinWidth = Math.min(100, unscaledShapeW / 2)
273
274
  const unscaledMinHeight = Math.min(
274
- dv.labelFontSize * dv.labelLineHeight + dv.labelPadding * 2,
275
+ resolveLineHeightPx(dv.labelFontSize, dv.labelLineHeight) + dv.labelPadding * 2,
275
276
  unscaledShapeH / 2
276
277
  )
277
278
 
@@ -5,6 +5,7 @@ import {
5
5
  TLPointerEventInfo,
6
6
  Vec,
7
7
  createShapeId,
8
+ cancelShapeCreationOnLongPress,
8
9
  maybeSnapToGrid,
9
10
  } from '@tldraw/editor'
10
11
  import { GeoShapeUtil } from '../GeoShapeUtil'
@@ -17,6 +18,10 @@ export class Pointing extends StateNode {
17
18
  this.complete()
18
19
  }
19
20
 
21
+ override onLongPress() {
22
+ cancelShapeCreationOnLongPress(this.editor, () => this.cancel())
23
+ }
24
+
20
25
  override onPointerMove(info: TLPointerEventInfo) {
21
26
  if (this.editor.inputs.getIsDragging()) {
22
27
  const originPagePoint = this.editor.inputs.getOriginPagePoint()