tldraw 3.16.0-next.eafb52d15064 → 3.16.0-next.fe14f1b4181f

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 (359) hide show
  1. package/dist-cjs/index.d.ts +193 -6
  2. package/dist-cjs/index.js +16 -1
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/Tldraw.js +12 -2
  5. package/dist-cjs/lib/Tldraw.js.map +2 -2
  6. package/dist-cjs/lib/canvas/TldrawScribble.js +1 -1
  7. package/dist-cjs/lib/canvas/TldrawScribble.js.map +2 -2
  8. package/dist-cjs/lib/defaultExternalContentHandlers.js +5 -4
  9. package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
  10. package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js +3 -3
  11. package/dist-cjs/lib/shapes/arrow/ArrowShapeUtil.js.map +2 -2
  12. package/dist-cjs/lib/shapes/arrow/elbow/ElbowArrowDebug.js +3 -3
  13. package/dist-cjs/lib/shapes/arrow/elbow/ElbowArrowDebug.js.map +1 -1
  14. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js +3 -3
  15. package/dist-cjs/lib/shapes/draw/DrawShapeUtil.js.map +2 -2
  16. package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js +1 -1
  17. package/dist-cjs/lib/shapes/embed/EmbedShapeUtil.js.map +1 -1
  18. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +15 -12
  19. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  20. package/dist-cjs/lib/shapes/frame/components/FrameHeading.js +1 -1
  21. package/dist-cjs/lib/shapes/frame/components/FrameHeading.js.map +2 -2
  22. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +2 -2
  23. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
  24. package/dist-cjs/lib/shapes/geo/components/GeoShapeBody.js +2 -1
  25. package/dist-cjs/lib/shapes/geo/components/GeoShapeBody.js.map +2 -2
  26. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js +5 -1
  27. package/dist-cjs/lib/shapes/highlight/HighlightShapeUtil.js.map +2 -2
  28. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +6 -3
  29. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js.map +2 -2
  30. package/dist-cjs/lib/shapes/line/LineShapeUtil.js +5 -1
  31. package/dist-cjs/lib/shapes/line/LineShapeUtil.js.map +2 -2
  32. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +4 -4
  33. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  34. package/dist-cjs/lib/shapes/shared/ShapeFill.js +4 -4
  35. package/dist-cjs/lib/shapes/shared/ShapeFill.js.map +2 -2
  36. package/dist-cjs/lib/shapes/shared/freehand/svg.js.map +2 -2
  37. package/dist-cjs/lib/shapes/text/TextShapeUtil.js +2 -2
  38. package/dist-cjs/lib/shapes/text/TextShapeUtil.js.map +2 -2
  39. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js +3 -3
  40. package/dist-cjs/lib/shapes/video/VideoShapeUtil.js.map +1 -1
  41. package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js +25 -1
  42. package/dist-cjs/lib/tools/EraserTool/childStates/Erasing.js.map +2 -2
  43. package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js +12 -0
  44. package/dist-cjs/lib/tools/EraserTool/childStates/Pointing.js.map +2 -2
  45. package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
  46. package/dist-cjs/lib/ui/TldrawUi.js +27 -12
  47. package/dist-cjs/lib/ui/TldrawUi.js.map +3 -3
  48. package/dist-cjs/lib/ui/assetUrls.js +13 -10
  49. package/dist-cjs/lib/ui/assetUrls.js.map +2 -2
  50. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js +12 -3
  51. package/dist-cjs/lib/ui/components/ActionsMenu/DefaultActionsMenu.js.map +2 -2
  52. package/dist-cjs/lib/ui/components/{FollowingIndicator.js → DefaultFollowingIndicator.js} +6 -6
  53. package/dist-cjs/lib/ui/components/DefaultFollowingIndicator.js.map +7 -0
  54. package/dist-cjs/lib/ui/components/DefaultMenuPanel.js +3 -2
  55. package/dist-cjs/lib/ui/components/DefaultMenuPanel.js.map +2 -2
  56. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +6 -6
  57. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +1 -1
  58. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js +4 -4
  59. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js.map +2 -2
  60. package/dist-cjs/lib/ui/components/MobileStylePanel.js +5 -3
  61. package/dist-cjs/lib/ui/components/MobileStylePanel.js.map +2 -2
  62. package/dist-cjs/lib/ui/components/NavigationPanel/DefaultNavigationPanel.js +1 -1
  63. package/dist-cjs/lib/ui/components/NavigationPanel/DefaultNavigationPanel.js.map +2 -2
  64. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js +2 -1
  65. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js.map +2 -2
  66. package/dist-cjs/lib/ui/components/SharePanel/PeopleMenuItem.js +3 -2
  67. package/dist-cjs/lib/ui/components/SharePanel/PeopleMenuItem.js.map +2 -2
  68. package/dist-cjs/lib/ui/components/SharePanel/UserPresenceColorPicker.js +2 -2
  69. package/dist-cjs/lib/ui/components/SharePanel/UserPresenceColorPicker.js.map +2 -2
  70. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +14 -14
  71. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
  72. package/dist-cjs/lib/ui/components/StylePanel/DoubleDropdownPicker.js +3 -3
  73. package/dist-cjs/lib/ui/components/StylePanel/DoubleDropdownPicker.js.map +2 -2
  74. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js +26 -25
  75. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js.map +3 -3
  76. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js +1 -1
  77. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js.map +2 -2
  78. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js +66 -21
  79. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js.map +3 -3
  80. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +189 -80
  81. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +3 -3
  82. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +2 -2
  83. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js.map +2 -2
  84. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js +5 -16
  85. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js.map +3 -3
  86. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +1 -1
  87. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js.map +2 -2
  88. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js +3 -2
  89. package/dist-cjs/lib/ui/components/primitives/TldrawUiPopover.js.map +3 -3
  90. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +6 -4
  91. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
  92. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +19 -4
  93. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
  94. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +155 -160
  95. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  96. package/dist-cjs/lib/ui/components/primitives/layout.js +76 -0
  97. package/dist-cjs/lib/ui/components/primitives/layout.js.map +7 -0
  98. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuContext.js.map +2 -2
  99. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js +25 -12
  100. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js.map +2 -2
  101. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +154 -20
  102. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  103. package/dist-cjs/lib/ui/context/actions.js +16 -2
  104. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  105. package/dist-cjs/lib/ui/context/components.js +2 -0
  106. package/dist-cjs/lib/ui/context/components.js.map +2 -2
  107. package/dist-cjs/lib/ui/context/events.js.map +2 -2
  108. package/dist-cjs/lib/ui/hooks/useTools.js +94 -9
  109. package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
  110. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  111. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +1 -0
  112. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  113. package/dist-cjs/lib/ui/kbd-utils.js +9 -3
  114. package/dist-cjs/lib/ui/kbd-utils.js.map +2 -2
  115. package/dist-cjs/lib/ui/version.js +3 -3
  116. package/dist-cjs/lib/ui/version.js.map +1 -1
  117. package/dist-esm/index.d.mts +193 -6
  118. package/dist-esm/index.mjs +25 -2
  119. package/dist-esm/index.mjs.map +2 -2
  120. package/dist-esm/lib/Tldraw.mjs +14 -4
  121. package/dist-esm/lib/Tldraw.mjs.map +2 -2
  122. package/dist-esm/lib/canvas/TldrawScribble.mjs +1 -1
  123. package/dist-esm/lib/canvas/TldrawScribble.mjs.map +2 -2
  124. package/dist-esm/lib/defaultExternalContentHandlers.mjs +5 -4
  125. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  126. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs +4 -3
  127. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs.map +2 -2
  128. package/dist-esm/lib/shapes/arrow/elbow/ElbowArrowDebug.mjs +3 -3
  129. package/dist-esm/lib/shapes/arrow/elbow/ElbowArrowDebug.mjs.map +1 -1
  130. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs +4 -3
  131. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs.map +2 -2
  132. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs +1 -1
  133. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs.map +1 -1
  134. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +16 -12
  135. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  136. package/dist-esm/lib/shapes/frame/components/FrameHeading.mjs +1 -1
  137. package/dist-esm/lib/shapes/frame/components/FrameHeading.mjs.map +2 -2
  138. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +3 -2
  139. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  140. package/dist-esm/lib/shapes/geo/components/GeoShapeBody.mjs +2 -1
  141. package/dist-esm/lib/shapes/geo/components/GeoShapeBody.mjs.map +2 -2
  142. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs +6 -1
  143. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs.map +2 -2
  144. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +6 -3
  145. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
  146. package/dist-esm/lib/shapes/line/LineShapeUtil.mjs +6 -1
  147. package/dist-esm/lib/shapes/line/LineShapeUtil.mjs.map +2 -2
  148. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +5 -4
  149. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  150. package/dist-esm/lib/shapes/shared/ShapeFill.mjs +5 -4
  151. package/dist-esm/lib/shapes/shared/ShapeFill.mjs.map +2 -2
  152. package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
  153. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs +3 -2
  154. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs.map +2 -2
  155. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs +3 -3
  156. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs.map +1 -1
  157. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs +26 -1
  158. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs.map +2 -2
  159. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs +13 -0
  160. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs.map +2 -2
  161. package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
  162. package/dist-esm/lib/ui/TldrawUi.mjs +29 -14
  163. package/dist-esm/lib/ui/TldrawUi.mjs.map +3 -3
  164. package/dist-esm/lib/ui/assetUrls.mjs +13 -10
  165. package/dist-esm/lib/ui/assetUrls.mjs.map +2 -2
  166. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs +12 -3
  167. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs.map +2 -2
  168. package/dist-esm/lib/ui/components/{FollowingIndicator.mjs → DefaultFollowingIndicator.mjs} +3 -3
  169. package/dist-esm/lib/ui/components/DefaultFollowingIndicator.mjs.map +7 -0
  170. package/dist-esm/lib/ui/components/DefaultMenuPanel.mjs +3 -2
  171. package/dist-esm/lib/ui/components/DefaultMenuPanel.mjs.map +2 -2
  172. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +6 -6
  173. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +1 -1
  174. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs +4 -4
  175. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs.map +2 -2
  176. package/dist-esm/lib/ui/components/MobileStylePanel.mjs +6 -3
  177. package/dist-esm/lib/ui/components/MobileStylePanel.mjs.map +2 -2
  178. package/dist-esm/lib/ui/components/NavigationPanel/DefaultNavigationPanel.mjs +1 -1
  179. package/dist-esm/lib/ui/components/NavigationPanel/DefaultNavigationPanel.mjs.map +2 -2
  180. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs +2 -1
  181. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs.map +2 -2
  182. package/dist-esm/lib/ui/components/SharePanel/PeopleMenuItem.mjs +3 -2
  183. package/dist-esm/lib/ui/components/SharePanel/PeopleMenuItem.mjs.map +2 -2
  184. package/dist-esm/lib/ui/components/SharePanel/UserPresenceColorPicker.mjs +2 -2
  185. package/dist-esm/lib/ui/components/SharePanel/UserPresenceColorPicker.mjs.map +2 -2
  186. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +14 -14
  187. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
  188. package/dist-esm/lib/ui/components/StylePanel/DoubleDropdownPicker.mjs +3 -3
  189. package/dist-esm/lib/ui/components/StylePanel/DoubleDropdownPicker.mjs.map +2 -2
  190. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs +26 -25
  191. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +2 -2
  192. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs +1 -1
  193. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs.map +2 -2
  194. package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs +56 -21
  195. package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs.map +2 -2
  196. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +192 -81
  197. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +3 -3
  198. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs +2 -2
  199. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs.map +2 -2
  200. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs +6 -6
  201. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs.map +2 -2
  202. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs +1 -1
  203. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs.map +2 -2
  204. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs +3 -2
  205. package/dist-esm/lib/ui/components/primitives/TldrawUiPopover.mjs.map +2 -2
  206. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +6 -4
  207. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
  208. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +19 -4
  209. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
  210. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +164 -162
  211. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  212. package/dist-esm/lib/ui/components/primitives/layout.mjs +46 -0
  213. package/dist-esm/lib/ui/components/primitives/layout.mjs.map +7 -0
  214. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuContext.mjs.map +2 -2
  215. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs +25 -12
  216. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs.map +2 -2
  217. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +162 -22
  218. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  219. package/dist-esm/lib/ui/context/actions.mjs +16 -2
  220. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  221. package/dist-esm/lib/ui/context/components.mjs +2 -0
  222. package/dist-esm/lib/ui/context/components.mjs.map +2 -2
  223. package/dist-esm/lib/ui/context/events.mjs.map +2 -2
  224. package/dist-esm/lib/ui/hooks/useTools.mjs +102 -10
  225. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  226. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +1 -0
  227. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  228. package/dist-esm/lib/ui/kbd-utils.mjs +9 -3
  229. package/dist-esm/lib/ui/kbd-utils.mjs.map +2 -2
  230. package/dist-esm/lib/ui/version.mjs +3 -3
  231. package/dist-esm/lib/ui/version.mjs.map +1 -1
  232. package/package.json +11 -34
  233. package/src/index.ts +18 -1
  234. package/src/lib/Tldraw.tsx +15 -2
  235. package/src/lib/canvas/TldrawScribble.tsx +1 -1
  236. package/src/lib/defaultExternalContentHandlers.ts +12 -4
  237. package/src/lib/shapes/arrow/ArrowShapeOptions.test.ts +2 -1
  238. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +4 -3
  239. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +7 -6
  240. package/src/lib/shapes/arrow/ArrowShapeUtil.tsx +4 -3
  241. package/src/lib/shapes/arrow/elbow/ElbowArrowDebug.tsx +3 -3
  242. package/src/lib/shapes/draw/DrawShapeTool.test.ts +0 -5
  243. package/src/lib/shapes/draw/DrawShapeUtil.tsx +4 -3
  244. package/src/lib/shapes/embed/EmbedShapeUtil.tsx +1 -1
  245. package/src/lib/shapes/frame/FrameShapeUtil.tsx +25 -14
  246. package/src/lib/shapes/frame/components/FrameHeading.tsx +1 -1
  247. package/src/lib/shapes/geo/GeoShapeUtil.tsx +3 -2
  248. package/src/lib/shapes/geo/components/GeoShapeBody.tsx +2 -2
  249. package/src/lib/shapes/highlight/HighlightShapeUtil.tsx +7 -1
  250. package/src/lib/shapes/image/ImageShapeUtil.tsx +6 -3
  251. package/src/lib/shapes/line/LineShapeUtil.test.tsx +4 -3
  252. package/src/lib/shapes/line/LineShapeUtil.tsx +6 -1
  253. package/src/lib/shapes/line/__snapshots__/LineShapeUtil.test.tsx.snap +2 -2
  254. package/src/lib/shapes/note/NoteShapeUtil.tsx +9 -4
  255. package/src/lib/shapes/shared/ShapeFill.tsx +5 -4
  256. package/src/lib/shapes/shared/freehand/svg.ts +2 -0
  257. package/src/lib/shapes/text/TextShapeTool.test.ts +6 -5
  258. package/src/lib/shapes/text/TextShapeUtil.tsx +3 -2
  259. package/src/lib/shapes/video/VideoShapeUtil.tsx +3 -3
  260. package/src/lib/tools/EraserTool/childStates/Erasing.ts +34 -1
  261. package/src/lib/tools/EraserTool/childStates/Pointing.ts +20 -0
  262. package/src/lib/tools/SelectTool/childStates/Translating.ts +0 -1
  263. package/src/lib/ui/TldrawUi.tsx +33 -12
  264. package/src/lib/ui/assetUrls.ts +13 -10
  265. package/src/lib/ui/components/ActionsMenu/DefaultActionsMenu.tsx +15 -3
  266. package/src/lib/ui/components/{FollowingIndicator.tsx → DefaultFollowingIndicator.tsx} +2 -1
  267. package/src/lib/ui/components/DefaultMenuPanel.tsx +4 -3
  268. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +6 -6
  269. package/src/lib/ui/components/Minimap/MinimapManager.ts +4 -4
  270. package/src/lib/ui/components/MobileStylePanel.tsx +9 -6
  271. package/src/lib/ui/components/NavigationPanel/DefaultNavigationPanel.tsx +1 -1
  272. package/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx +3 -2
  273. package/src/lib/ui/components/SharePanel/PeopleMenuItem.tsx +4 -3
  274. package/src/lib/ui/components/SharePanel/UserPresenceColorPicker.tsx +3 -3
  275. package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +39 -43
  276. package/src/lib/ui/components/StylePanel/DoubleDropdownPicker.tsx +3 -3
  277. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +7 -6
  278. package/src/lib/ui/components/Toolbar/DefaultImageToolbarContent.tsx +1 -1
  279. package/src/lib/ui/components/Toolbar/DefaultToolbar.tsx +55 -23
  280. package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +212 -61
  281. package/src/lib/ui/components/Toolbar/ToggleToolLockedButton.tsx +2 -2
  282. package/src/lib/ui/components/primitives/TldrawUiButtonPicker.tsx +40 -37
  283. package/src/lib/ui/components/primitives/TldrawUiContextualToolbar.tsx +1 -1
  284. package/src/lib/ui/components/primitives/TldrawUiPopover.tsx +4 -2
  285. package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +35 -30
  286. package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +32 -9
  287. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +196 -184
  288. package/src/lib/ui/components/primitives/layout.tsx +107 -0
  289. package/src/lib/ui/components/primitives/menus/TldrawUiMenuContext.tsx +0 -1
  290. package/src/lib/ui/components/primitives/menus/TldrawUiMenuGroup.tsx +29 -16
  291. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +221 -19
  292. package/src/lib/ui/context/actions.tsx +16 -2
  293. package/src/lib/ui/context/components.tsx +3 -0
  294. package/src/lib/ui/context/events.tsx +1 -0
  295. package/src/lib/ui/hooks/useTools.tsx +140 -10
  296. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +1 -0
  297. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +1 -0
  298. package/src/lib/ui/kbd-utils.ts +10 -3
  299. package/src/lib/ui/version.ts +3 -3
  300. package/src/lib/ui.css +366 -305
  301. package/src/lib/utils/excalidraw/__snapshots__/putExcalidrawContent.test.tsx.snap +5 -5
  302. package/src/lib/utils/tldr/__snapshots__/buildFromV1Document.test.ts.snap +4 -4
  303. package/src/test/A11y.test.tsx +3 -2
  304. package/src/test/ClickManager.test.ts +7 -6
  305. package/src/test/Editor.test.tsx +20 -19
  306. package/src/test/EraserTool.test.ts +184 -13
  307. package/src/test/HandTool.test.ts +10 -9
  308. package/src/test/HighlightShape.test.ts +2 -1
  309. package/src/test/SelectTool.test.ts +3 -2
  310. package/src/test/TLUserPreferences.test.ts +4 -3
  311. package/src/test/TestEditor.ts +13 -15
  312. package/src/test/TldrawEditor.test.tsx +11 -10
  313. package/src/test/ZoomTool.test.ts +7 -6
  314. package/src/test/__snapshots__/drawing.test.ts.snap +2 -2
  315. package/src/test/__snapshots__/groups.test.tsx.snap +6 -6
  316. package/src/test/__snapshots__/resizing.test.ts.snap +2 -2
  317. package/src/test/arrows-megabus.test.tsx +5 -4
  318. package/src/test/bindings.test.tsx +24 -37
  319. package/src/test/bookmark-shapes.test.ts +1 -8
  320. package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +23 -7
  321. package/src/test/commands/__snapshots__/packShapes.test.ts.snap +8 -8
  322. package/src/test/commands/__snapshots__/zoomToFit.test.ts.snap +2 -2
  323. package/src/test/commands/alignShapes.test.tsx +25 -24
  324. package/src/test/commands/animationSpeed.test.ts +2 -1
  325. package/src/test/commands/centerOnPoint.test.ts +3 -2
  326. package/src/test/commands/clipboard.test.ts +3 -2
  327. package/src/test/commands/createShapes.test.ts +2 -1
  328. package/src/test/commands/deleteShapes.test.ts +2 -1
  329. package/src/test/commands/distributeShapes.test.tsx +11 -10
  330. package/src/test/commands/getSvgString.test.ts +2 -1
  331. package/src/test/commands/packShapes.test.ts +5 -4
  332. package/src/test/commands/resizeShape.test.ts +2 -1
  333. package/src/test/commands/rotateShapes.test.ts +7 -6
  334. package/src/test/commands/setCamera.test.ts +4 -3
  335. package/src/test/commands/setCurrentPage.test.ts +3 -2
  336. package/src/test/commands/stackShapes.test.ts +11 -10
  337. package/src/test/commands/stretch.test.tsx +13 -12
  338. package/src/test/createDeepLink.test.tsx +2 -1
  339. package/src/test/cropping.test.ts +3 -2
  340. package/src/test/drawing.test.ts +2 -1
  341. package/src/test/flipShapes.test.ts +4 -3
  342. package/src/test/frames.test.ts +25 -24
  343. package/src/test/getCulledShapes.test.tsx +3 -2
  344. package/src/test/groups.test.tsx +1 -1
  345. package/src/test/handleDeepLink.test.tsx +2 -1
  346. package/src/test/maxShapes.test.ts +3 -2
  347. package/src/test/modifiers.test.ts +5 -4
  348. package/src/test/navigation.test.ts +12 -11
  349. package/src/test/panning.test.ts +2 -1
  350. package/src/test/perf/perf.test.ts +2 -1
  351. package/src/test/registerDeepLinkListener.test.tsx +10 -9
  352. package/src/test/resizing.test.ts +39 -38
  353. package/src/test/select.test.tsx +4 -3
  354. package/src/test/selection-omnibus.test.ts +11 -10
  355. package/src/test/shapeutils.test.ts +4 -3
  356. package/src/test/translating.test.ts +9 -8
  357. package/tldraw.css +667 -595
  358. package/dist-cjs/lib/ui/components/FollowingIndicator.js.map +0 -7
  359. package/dist-esm/lib/ui/components/FollowingIndicator.mjs.map +0 -7
@@ -1,7 +1,15 @@
1
- import { Editor, uniqueId, useMaybeEditor, Vec } from '@tldraw/editor'
1
+ import { assert, Atom, atom, Editor, uniqueId, useMaybeEditor, useValue } from '@tldraw/editor'
2
2
  import { Tooltip as _Tooltip } from 'radix-ui'
3
- import React, { createContext, useContext, useEffect, useRef, useState } from 'react'
4
- import { usePrefersReducedMotion } from '../../../shapes/shared/usePrefersReducedMotion'
3
+ import React, {
4
+ createContext,
5
+ forwardRef,
6
+ ReactNode,
7
+ useContext,
8
+ useEffect,
9
+ useRef,
10
+ useState,
11
+ } from 'react'
12
+ import { useTldrawUiOrientation } from './layout'
5
13
 
6
14
  const DEFAULT_TOOLTIP_DELAY_MS = 700
7
15
 
@@ -12,19 +20,23 @@ export interface TldrawUiTooltipProps {
12
20
  side?: 'top' | 'right' | 'bottom' | 'left'
13
21
  sideOffset?: number
14
22
  disabled?: boolean
23
+ showOnMobile?: boolean
24
+ delayDuration?: number
15
25
  }
16
26
 
17
27
  // Singleton tooltip manager
18
28
  class TooltipManager {
19
29
  private static instance: TooltipManager | null = null
20
- private currentTooltipId: string | null = null
21
- private currentContent: string | React.ReactNode = ''
22
- private currentSide: 'top' | 'right' | 'bottom' | 'left' = 'bottom'
23
- private currentSideOffset: number = 5
30
+ private currentTooltip = atom<{
31
+ id: string
32
+ content: ReactNode
33
+ side: 'top' | 'right' | 'bottom' | 'left'
34
+ sideOffset: number
35
+ showOnMobile: boolean
36
+ targetElement: HTMLElement
37
+ delayDuration: number
38
+ } | null>('current tooltip', null)
24
39
  private destroyTimeoutId: number | null = null
25
- private subscribers: Set<() => void> = new Set()
26
- private activeElement: HTMLElement | null = null
27
- private editor: Editor | null = null
28
40
 
29
41
  static getInstance(): TooltipManager {
30
42
  if (!TooltipManager.instance) {
@@ -33,25 +45,14 @@ class TooltipManager {
33
45
  return TooltipManager.instance
34
46
  }
35
47
 
36
- setEditor(editor: Editor | null) {
37
- this.editor = editor
38
- }
39
-
40
- subscribe(callback: () => void): () => void {
41
- this.subscribers.add(callback)
42
- return () => this.subscribers.delete(callback)
43
- }
44
-
45
- private notify() {
46
- this.subscribers.forEach((callback) => callback())
47
- }
48
-
49
48
  showTooltip(
50
49
  tooltipId: string,
51
50
  content: string | React.ReactNode,
52
- element: HTMLElement,
53
- side: 'top' | 'right' | 'bottom' | 'left' = 'bottom',
54
- sideOffset: number = 5
51
+ targetElement: HTMLElement,
52
+ side: 'top' | 'right' | 'bottom' | 'left',
53
+ sideOffset: number,
54
+ showOnMobile: boolean,
55
+ delayDuration: number
55
56
  ) {
56
57
  // Clear any existing destroy timeout
57
58
  if (this.destroyTimeoutId) {
@@ -60,43 +61,61 @@ class TooltipManager {
60
61
  }
61
62
 
62
63
  // Update current tooltip
63
- this.currentTooltipId = tooltipId
64
- this.currentContent = content
65
- this.currentSide = side
66
- this.currentSideOffset = sideOffset
67
- this.activeElement = element
68
-
69
- this.notify()
64
+ this.currentTooltip.set({
65
+ id: tooltipId,
66
+ content,
67
+ side,
68
+ sideOffset,
69
+ showOnMobile,
70
+ targetElement,
71
+ delayDuration,
72
+ })
70
73
  }
71
74
 
72
- hideTooltip(tooltipId: string) {
73
- // Only hide if this is the current tooltip
74
- if (this.currentTooltipId === tooltipId) {
75
- // Start destroy timeout (1 second)
76
- if (this.editor) {
77
- this.destroyTimeoutId = this.editor.timers.setTimeout(() => {
78
- this.currentTooltipId = null
79
- this.currentContent = ''
80
- this.activeElement = null
81
- this.destroyTimeoutId = null
82
- this.notify()
83
- }, 300)
75
+ hideTooltip(editor: Editor | null, tooltipId: string, instant: boolean = false) {
76
+ const hide = () => {
77
+ // Only hide if this is the current tooltip
78
+ if (this.currentTooltip.get()?.id === tooltipId) {
79
+ this.currentTooltip.set(null)
80
+ this.destroyTimeoutId = null
84
81
  }
85
82
  }
83
+
84
+ if (editor && !instant) {
85
+ // Start destroy timeout (1 second)
86
+ this.destroyTimeoutId = editor.timers.setTimeout(hide, 300)
87
+ } else {
88
+ hide()
89
+ }
90
+ }
91
+
92
+ hideAllTooltips() {
93
+ this.currentTooltip.set(null)
94
+ this.destroyTimeoutId = null
86
95
  }
87
96
 
88
97
  getCurrentTooltipData() {
89
- return {
90
- id: this.currentTooltipId,
91
- content: this.currentContent,
92
- side: this.currentSide,
93
- sideOffset: this.currentSideOffset,
94
- element: this.activeElement,
98
+ const currentTooltip = this.currentTooltip.get()
99
+ if (!currentTooltip) return null
100
+ if (!this.supportsHover() && !currentTooltip.showOnMobile) return null
101
+ return currentTooltip
102
+ }
103
+
104
+ private supportsHoverAtom: Atom<boolean> | null = null
105
+ supportsHover() {
106
+ if (!this.supportsHoverAtom) {
107
+ const mediaQuery = window.matchMedia('(hover: hover)')
108
+ const supportsHover = atom('has hover', mediaQuery.matches)
109
+ this.supportsHoverAtom = supportsHover
110
+ mediaQuery.addEventListener('change', (e) => {
111
+ supportsHover.set(e.matches)
112
+ })
95
113
  }
114
+ return this.supportsHoverAtom.get()
96
115
  }
97
116
  }
98
117
 
99
- const tooltipManager = TooltipManager.getInstance()
118
+ export const tooltipManager = TooltipManager.getInstance()
100
119
 
101
120
  // Context for the tooltip singleton
102
121
  const TooltipSingletonContext = createContext<boolean>(false)
@@ -120,66 +139,24 @@ export function TldrawUiTooltipProvider({ children }: TldrawUiTooltipProviderPro
120
139
 
121
140
  // The singleton tooltip component that renders once
122
141
  function TooltipSingleton() {
123
- const editor = useMaybeEditor()
124
- const [, forceUpdate] = useState({})
125
142
  const [isOpen, setIsOpen] = useState(false)
126
143
  const triggerRef = useRef<HTMLDivElement>(null)
127
- const previousPositionRef = useRef<{ x: number; y: number } | null>(null)
128
- const prefersReducedMotion = usePrefersReducedMotion()
129
- const [shouldAnimate, setShouldAnimate] = useState(false)
130
144
  const isFirstShowRef = useRef(true)
131
- const showTimeoutRef = useRef<number | null>(null)
132
-
133
- // Set editor in tooltip manager
134
- useEffect(() => {
135
- tooltipManager.setEditor(editor)
136
- }, [editor])
137
-
138
- // Subscribe to tooltip manager updates
139
- useEffect(() => {
140
- const unsubscribe = tooltipManager.subscribe(() => {
141
- forceUpdate({})
142
- })
143
- return unsubscribe
144
- }, [])
145
145
 
146
- const tooltipData = tooltipManager.getCurrentTooltipData()
146
+ const currentTooltip = useValue(
147
+ 'current tooltip',
148
+ () => tooltipManager.getCurrentTooltipData(),
149
+ []
150
+ )
147
151
 
148
152
  // Update open state and trigger position
149
153
  useEffect(() => {
150
- const shouldBeOpen = Boolean(tooltipData.id && tooltipData.element)
151
-
152
- // Clear any existing show timeout
153
- if (showTimeoutRef.current) {
154
- clearTimeout(showTimeoutRef.current)
155
- showTimeoutRef.current = null
156
- }
157
-
158
- if (shouldBeOpen && tooltipData.element && triggerRef.current) {
154
+ let timer: ReturnType<typeof setTimeout> | null = null
155
+ if (currentTooltip && triggerRef.current) {
159
156
  // Position the invisible trigger element over the active element
160
- const activeRect = tooltipData.element.getBoundingClientRect()
157
+ const activeRect = currentTooltip.targetElement.getBoundingClientRect()
161
158
  const trigger = triggerRef.current
162
159
 
163
- const newPosition = {
164
- x: activeRect.left + activeRect.width / 2,
165
- y: activeRect.top + activeRect.height / 2,
166
- }
167
-
168
- // Determine if we should animate
169
- let shouldAnimateCheck = false
170
- if (previousPositionRef.current) {
171
- const isNearPrevious = Vec.DistMin(previousPositionRef.current, newPosition, 200)
172
- // Only animate if the distance is less than 200px (nearby tooltips)
173
- shouldAnimateCheck =
174
- !prefersReducedMotion &&
175
- isNearPrevious &&
176
- Math.abs(newPosition.y - previousPositionRef.current.y) < 50
177
- }
178
- // Don't animate on initial show (previousPositionRef.current is null)
179
-
180
- setShouldAnimate(isFirstShowRef.current ? false : shouldAnimateCheck)
181
- previousPositionRef.current = newPosition
182
-
183
160
  trigger.style.position = 'fixed'
184
161
  trigger.style.left = `${activeRect.left}px`
185
162
  trigger.style.top = `${activeRect.top}px`
@@ -189,27 +166,31 @@ function TooltipSingleton() {
189
166
  trigger.style.zIndex = '9999'
190
167
 
191
168
  // Handle delay for first show
192
- if (isFirstShowRef.current && editor) {
193
- showTimeoutRef.current = editor.timers.setTimeout(() => {
169
+ if (isFirstShowRef.current) {
170
+ // eslint-disable-next-line no-restricted-globals
171
+ timer = setTimeout(() => {
194
172
  setIsOpen(true)
195
173
  isFirstShowRef.current = false
196
- }, editor.options.tooltipDelayMs)
174
+ }, currentTooltip.delayDuration)
197
175
  } else {
198
176
  // Subsequent tooltips show immediately
199
177
  setIsOpen(true)
200
178
  }
201
- } else if (!shouldBeOpen) {
179
+ } else {
202
180
  // Hide tooltip immediately
203
181
  setIsOpen(false)
204
- // Reset position tracking when tooltip closes
205
- previousPositionRef.current = null
206
- setShouldAnimate(false)
207
182
  // Reset first show state after tooltip is hidden
208
183
  isFirstShowRef.current = true
209
184
  }
210
- }, [tooltipData.id, tooltipData.element, editor, prefersReducedMotion])
211
185
 
212
- if (!tooltipData.id) {
186
+ return () => {
187
+ if (timer !== null) {
188
+ clearTimeout(timer)
189
+ }
190
+ }
191
+ }, [currentTooltip])
192
+
193
+ if (!currentTooltip) {
213
194
  return null
214
195
  }
215
196
 
@@ -220,14 +201,13 @@ function TooltipSingleton() {
220
201
  </_Tooltip.Trigger>
221
202
  <_Tooltip.Content
222
203
  className="tlui-tooltip"
223
- data-should-animate={shouldAnimate}
224
- side={tooltipData.side}
225
- sideOffset={tooltipData.sideOffset}
204
+ side={currentTooltip.side}
205
+ sideOffset={currentTooltip.sideOffset}
226
206
  avoidCollisions
227
207
  collisionPadding={8}
228
208
  dir="ltr"
229
209
  >
230
- {tooltipData.content}
210
+ {currentTooltip.content}
231
211
  <_Tooltip.Arrow className="tlui-tooltip__arrow" />
232
212
  </_Tooltip.Content>
233
213
  </_Tooltip.Root>
@@ -235,79 +215,111 @@ function TooltipSingleton() {
235
215
  }
236
216
 
237
217
  /** @public @react */
238
- export function TldrawUiTooltip({
239
- children,
240
- content,
241
- side = 'bottom',
242
- sideOffset = 5,
243
- disabled = false,
244
- }: TldrawUiTooltipProps) {
245
- const editor = useMaybeEditor()
246
- const tooltipId = useRef<string>(uniqueId())
247
- const hasProvider = useContext(TooltipSingletonContext)
248
-
249
- // Don't show tooltip if disabled, no content, or UI labels are disabled
250
- if (disabled || !content) {
251
- return <>{children}</>
252
- }
253
-
254
- // Fallback to old behavior if no provider
255
- if (!hasProvider) {
256
- return (
257
- <_Tooltip.Root
258
- delayDuration={editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS}
259
- disableHoverableContent
260
- >
261
- <_Tooltip.Trigger asChild>{children}</_Tooltip.Trigger>
262
- <_Tooltip.Content
263
- className="tlui-tooltip"
264
- side={side}
265
- sideOffset={sideOffset}
266
- avoidCollisions
267
- collisionPadding={8}
268
- dir="ltr"
269
- >
270
- {content}
271
- <_Tooltip.Arrow className="tlui-tooltip__arrow" />
272
- </_Tooltip.Content>
273
- </_Tooltip.Root>
274
- )
275
- }
276
-
277
- const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
278
- tooltipManager.showTooltip(
279
- tooltipId.current,
218
+ export const TldrawUiTooltip = forwardRef<HTMLButtonElement, TldrawUiTooltipProps>(
219
+ (
220
+ {
221
+ children,
280
222
  content,
281
- event.currentTarget as HTMLElement,
282
223
  side,
283
- sideOffset
284
- )
285
- }
224
+ sideOffset = 5,
225
+ disabled = false,
226
+ showOnMobile = false,
227
+ delayDuration,
228
+ },
229
+ ref
230
+ ) => {
231
+ const editor = useMaybeEditor()
232
+ const tooltipId = useRef<string>(uniqueId())
233
+ const hasProvider = useContext(TooltipSingletonContext)
234
+
235
+ const orientationCtx = useTldrawUiOrientation()
236
+ const sideToUse = side ?? orientationCtx.tooltipSide
237
+
238
+ useEffect(() => {
239
+ const currentTooltipId = tooltipId.current
240
+ return () => {
241
+ if (hasProvider) {
242
+ tooltipManager.hideTooltip(editor, currentTooltipId, true)
243
+ }
244
+ }
245
+ }, [editor, hasProvider])
286
246
 
287
- const handleMouseLeave = () => {
288
- tooltipManager.hideTooltip(tooltipId.current)
289
- }
247
+ // Don't show tooltip if disabled, no content, or UI labels are disabled
248
+ if (disabled || !content) {
249
+ return <>{children}</>
250
+ }
290
251
 
291
- const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
292
- tooltipManager.showTooltip(
293
- tooltipId.current,
294
- content,
295
- event.currentTarget as HTMLElement,
296
- side,
297
- sideOffset
298
- )
299
- }
252
+ const delayDurationToUse =
253
+ delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
254
+
255
+ // Fallback to old behavior if no provider
256
+ if (!hasProvider) {
257
+ return (
258
+ <_Tooltip.Root delayDuration={delayDurationToUse} disableHoverableContent>
259
+ <_Tooltip.Trigger asChild ref={ref}>
260
+ {children}
261
+ </_Tooltip.Trigger>
262
+ <_Tooltip.Content
263
+ className="tlui-tooltip"
264
+ side={sideToUse}
265
+ sideOffset={sideOffset}
266
+ avoidCollisions
267
+ collisionPadding={8}
268
+ dir="ltr"
269
+ >
270
+ {content}
271
+ <_Tooltip.Arrow className="tlui-tooltip__arrow" />
272
+ </_Tooltip.Content>
273
+ </_Tooltip.Root>
274
+ )
275
+ }
300
276
 
301
- const handleBlur = () => {
302
- tooltipManager.hideTooltip(tooltipId.current)
303
- }
277
+ const child = React.Children.only(children)
278
+ assert(React.isValidElement(child), 'TldrawUiTooltip children must be a single element')
279
+
280
+ const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
281
+ child.props.onMouseEnter?.(event)
282
+ tooltipManager.showTooltip(
283
+ tooltipId.current,
284
+ content,
285
+ event.currentTarget as HTMLElement,
286
+ sideToUse,
287
+ sideOffset,
288
+ showOnMobile,
289
+ delayDurationToUse
290
+ )
291
+ }
304
292
 
305
- const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
306
- onMouseEnter: handleMouseEnter,
307
- onMouseLeave: handleMouseLeave,
308
- onFocus: handleFocus,
309
- onBlur: handleBlur,
310
- })
293
+ const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
294
+ child.props.onMouseLeave?.(event)
295
+ tooltipManager.hideTooltip(editor, tooltipId.current)
296
+ }
311
297
 
312
- return childrenWithHandlers
313
- }
298
+ const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
299
+ child.props.onFocus?.(event)
300
+ tooltipManager.showTooltip(
301
+ tooltipId.current,
302
+ content,
303
+ event.currentTarget as HTMLElement,
304
+ sideToUse,
305
+ sideOffset,
306
+ showOnMobile,
307
+ delayDurationToUse
308
+ )
309
+ }
310
+
311
+ const handleBlur = (event: React.FocusEvent<HTMLElement>) => {
312
+ child.props.onBlur?.(event)
313
+ tooltipManager.hideTooltip(editor, tooltipId.current)
314
+ }
315
+
316
+ const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
317
+ onMouseEnter: handleMouseEnter,
318
+ onMouseLeave: handleMouseLeave,
319
+ onFocus: handleFocus,
320
+ onBlur: handleBlur,
321
+ })
322
+
323
+ return childrenWithHandlers
324
+ }
325
+ )
@@ -0,0 +1,107 @@
1
+ import classNames from 'classnames'
2
+ import { Slot } from 'radix-ui'
3
+ import { HTMLAttributes, ReactNode, createContext, forwardRef, useContext } from 'react'
4
+
5
+ /** @public */
6
+ export interface TldrawUiOrientationContext {
7
+ orientation: 'horizontal' | 'vertical'
8
+ tooltipSide: 'top' | 'right' | 'bottom' | 'left'
9
+ }
10
+
11
+ const TldrawUiOrientationContext = createContext<TldrawUiOrientationContext>({
12
+ orientation: 'horizontal',
13
+ tooltipSide: 'bottom',
14
+ })
15
+
16
+ /** @public */
17
+ export interface TldrawUiOrientationProviderProps {
18
+ children: ReactNode
19
+ orientation: 'horizontal' | 'vertical'
20
+ tooltipSide?: 'top' | 'right' | 'bottom' | 'left'
21
+ }
22
+ /** @public @react */
23
+ export function TldrawUiOrientationProvider({
24
+ children,
25
+ orientation,
26
+ tooltipSide,
27
+ }: TldrawUiOrientationProviderProps) {
28
+ const prevContext = useTldrawUiOrientation()
29
+ // generally, we want tooltip side to cascade down through the layout - apart from when the
30
+ // orientation changes. If the tooltip side is "bottom", and then I include some vertical layout
31
+ // elements, keeping the tooltip side as bottom will cause the tooltip to overlap elements
32
+ // stacked on top of each other. In the absence of a tooltip side, we pick a default side based
33
+ // on the orientation whenever the orientation changes.
34
+ const tooltipSideToUse =
35
+ tooltipSide ??
36
+ (orientation === prevContext.orientation
37
+ ? prevContext.tooltipSide
38
+ : orientation === 'horizontal'
39
+ ? 'bottom'
40
+ : 'right')
41
+
42
+ return (
43
+ <TldrawUiOrientationContext.Provider value={{ orientation, tooltipSide: tooltipSideToUse }}>
44
+ {children}
45
+ </TldrawUiOrientationContext.Provider>
46
+ )
47
+ }
48
+
49
+ /** @public */
50
+ export function useTldrawUiOrientation() {
51
+ return useContext(TldrawUiOrientationContext)
52
+ }
53
+
54
+ /** @public */
55
+ export interface TLUiLayoutProps extends HTMLAttributes<HTMLDivElement> {
56
+ children: ReactNode
57
+ tooltipSide?: 'top' | 'right' | 'bottom' | 'left'
58
+ asChild?: boolean
59
+ }
60
+
61
+ /**
62
+ * A row, usually of UI controls like buttons, select dropdown, checkboxes, etc.
63
+ *
64
+ * @public @react
65
+ */
66
+ export const TldrawUiRow = forwardRef<HTMLDivElement, TLUiLayoutProps>(
67
+ ({ asChild, className, tooltipSide, ...props }, ref) => {
68
+ const Component = asChild ? Slot.Root : 'div'
69
+ return (
70
+ <TldrawUiOrientationProvider orientation="horizontal" tooltipSide={tooltipSide}>
71
+ <Component ref={ref} className={classNames('tlui-row', className)} {...props} />
72
+ </TldrawUiOrientationProvider>
73
+ )
74
+ }
75
+ )
76
+
77
+ /**
78
+ * A column, usually of UI controls like buttons, select dropdown, checkboxes, etc.
79
+ *
80
+ * @public @react
81
+ */
82
+ export const TldrawUiColumn = forwardRef<HTMLDivElement, TLUiLayoutProps>(
83
+ ({ asChild, className, tooltipSide, ...props }, ref) => {
84
+ const Component = asChild ? Slot.Root : 'div'
85
+ return (
86
+ <TldrawUiOrientationProvider orientation="vertical" tooltipSide={tooltipSide}>
87
+ <Component ref={ref} className={classNames('tlui-column', className)} {...props} />
88
+ </TldrawUiOrientationProvider>
89
+ )
90
+ }
91
+ )
92
+
93
+ /**
94
+ * A tight grid 4 elements wide, usually of UI controls like buttons, select dropdown, checkboxes,
95
+ * etc.
96
+ *
97
+ * @public @react */
98
+ export const TldrawUiGrid = forwardRef<HTMLDivElement, TLUiLayoutProps>(
99
+ ({ asChild, className, tooltipSide, ...props }, ref) => {
100
+ const Component = asChild ? Slot.Root : 'div'
101
+ return (
102
+ <TldrawUiOrientationProvider orientation="horizontal" tooltipSide={tooltipSide}>
103
+ <Component ref={ref} className={classNames('tlui-grid', className)} {...props} />
104
+ </TldrawUiOrientationProvider>
105
+ )
106
+ }
107
+ )
@@ -3,7 +3,6 @@ import { TLUiEventSource } from '../../../context/events'
3
3
 
4
4
  /** @public */
5
5
  export type TLUiMenuContextType =
6
- | 'panel'
7
6
  | 'menu'
8
7
  | 'small-icons'
9
8
  | 'context-menu'
@@ -3,6 +3,7 @@ import { ReactNode } from 'react'
3
3
  import { unwrapLabel } from '../../../context/actions'
4
4
  import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
5
5
  import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
6
+ import { TldrawUiColumn, TldrawUiGrid, TldrawUiRow, useTldrawUiOrientation } from '../layout'
6
7
  import { TldrawUiDropdownMenuGroup } from '../TldrawUiDropdownMenu'
7
8
  import { useTldrawUiMenuContext } from './TldrawUiMenuContext'
8
9
 
@@ -19,25 +20,19 @@ export interface TLUiMenuGroupProps<TranslationKey extends string = string> {
19
20
 
20
21
  /** @public @react */
21
22
  export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGroupProps) {
22
- const { type: menuType, sourceId } = useTldrawUiMenuContext()
23
+ const menu = useTldrawUiMenuContext()
24
+ const { orientation } = useTldrawUiOrientation()
23
25
  const msg = useTranslation()
24
- const labelToUse = unwrapLabel(label, menuType)
26
+ const labelToUse = unwrapLabel(label, menu.type)
25
27
  const labelStr = labelToUse ? msg(labelToUse as TLUiTranslationKey) : undefined
26
28
 
27
- switch (menuType) {
28
- case 'panel': {
29
- return (
30
- <div
31
- className={classNames('tlui-menu__group', className)}
32
- data-testid={`${sourceId}-group.${id}`}
33
- >
34
- {children}
35
- </div>
36
- )
37
- }
29
+ switch (menu.type) {
38
30
  case 'menu': {
39
31
  return (
40
- <TldrawUiDropdownMenuGroup className={className} data-testid={`${sourceId}-group.${id}`}>
32
+ <TldrawUiDropdownMenuGroup
33
+ className={className}
34
+ data-testid={`${menu.sourceId}-group.${id}`}
35
+ >
41
36
  {children}
42
37
  </TldrawUiDropdownMenuGroup>
43
38
  )
@@ -47,7 +42,7 @@ export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGr
47
42
  <div
48
43
  dir="ltr"
49
44
  className={classNames('tlui-menu__group', className)}
50
- data-testid={`${sourceId}-group.${id}`}
45
+ data-testid={`${menu.sourceId}-group.${id}`}
51
46
  >
52
47
  {children}
53
48
  </div>
@@ -56,12 +51,30 @@ export function TldrawUiMenuGroup({ id, label, className, children }: TLUiMenuGr
56
51
  case 'keyboard-shortcuts': {
57
52
  // todo: if groups need a label, let's give em a label
58
53
  return (
59
- <div className="tlui-shortcuts-dialog__group" data-testid={`${sourceId}-group.${id}`}>
54
+ <div className="tlui-shortcuts-dialog__group" data-testid={`${menu.sourceId}-group.${id}`}>
60
55
  <h2 className="tlui-shortcuts-dialog__group__title">{labelStr}</h2>
61
56
  <div className="tlui-shortcuts-dialog__group__content">{children}</div>
62
57
  </div>
63
58
  )
64
59
  }
60
+ case 'toolbar': {
61
+ const Layout = orientation === 'horizontal' ? TldrawUiRow : TldrawUiColumn
62
+ return (
63
+ <Layout className="tlui-main-toolbar__group" data-testid={`${menu.sourceId}-group.${id}`}>
64
+ {children}
65
+ </Layout>
66
+ )
67
+ }
68
+ case 'toolbar-overflow': {
69
+ return (
70
+ <TldrawUiGrid
71
+ className="tlui-main-toolbar__group"
72
+ data-testid={`${menu.sourceId}-group.${id}`}
73
+ >
74
+ {children}
75
+ </TldrawUiGrid>
76
+ )
77
+ }
65
78
  default: {
66
79
  return children
67
80
  }