tldraw 3.16.0-canary.5dac57cf9465 → 3.16.0-canary.5f82fb812214

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 (330) hide show
  1. package/dist-cjs/index.d.ts +170 -7
  2. package/dist-cjs/index.js +13 -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 +18 -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 +10 -2
  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/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +6 -6
  55. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +1 -1
  56. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js +4 -4
  57. package/dist-cjs/lib/ui/components/Minimap/MinimapManager.js.map +2 -2
  58. package/dist-cjs/lib/ui/components/MobileStylePanel.js +5 -3
  59. package/dist-cjs/lib/ui/components/MobileStylePanel.js.map +2 -2
  60. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js +1 -1
  61. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js.map +2 -2
  62. package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js +3 -2
  63. package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js.map +2 -2
  64. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js +39 -10
  65. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js.map +2 -2
  66. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js +66 -22
  67. package/dist-cjs/lib/ui/components/Toolbar/DefaultToolbar.js.map +3 -3
  68. package/dist-cjs/lib/ui/components/Toolbar/DefaultVideoToolbarContent.js +15 -3
  69. package/dist-cjs/lib/ui/components/Toolbar/DefaultVideoToolbarContent.js.map +2 -2
  70. package/dist-cjs/lib/ui/components/Toolbar/LinkEditor.js +3 -3
  71. package/dist-cjs/lib/ui/components/Toolbar/LinkEditor.js.map +2 -2
  72. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +188 -78
  73. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +3 -3
  74. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js +1 -1
  75. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js.map +2 -2
  76. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +10 -1
  77. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js.map +2 -2
  78. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +17 -4
  79. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
  80. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +15 -3
  81. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
  82. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +159 -160
  83. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  84. package/dist-cjs/lib/ui/components/primitives/layout.js +30 -5
  85. package/dist-cjs/lib/ui/components/primitives/layout.js.map +2 -2
  86. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuContext.js.map +2 -2
  87. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js +25 -12
  88. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuGroup.js.map +2 -2
  89. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +154 -19
  90. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  91. package/dist-cjs/lib/ui/context/actions.js +16 -2
  92. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  93. package/dist-cjs/lib/ui/context/components.js +2 -0
  94. package/dist-cjs/lib/ui/context/components.js.map +2 -2
  95. package/dist-cjs/lib/ui/context/events.js.map +2 -2
  96. package/dist-cjs/lib/ui/hooks/useTools.js +94 -9
  97. package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
  98. package/dist-cjs/lib/ui/hooks/useTranslation/TLUiTranslationKey.js.map +1 -1
  99. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js +2 -0
  100. package/dist-cjs/lib/ui/hooks/useTranslation/defaultTranslation.js.map +2 -2
  101. package/dist-cjs/lib/ui/kbd-utils.js +9 -3
  102. package/dist-cjs/lib/ui/kbd-utils.js.map +2 -2
  103. package/dist-cjs/lib/ui/version.js +3 -3
  104. package/dist-cjs/lib/ui/version.js.map +1 -1
  105. package/dist-esm/index.d.mts +170 -7
  106. package/dist-esm/index.mjs +20 -3
  107. package/dist-esm/index.mjs.map +2 -2
  108. package/dist-esm/lib/Tldraw.mjs +14 -4
  109. package/dist-esm/lib/Tldraw.mjs.map +2 -2
  110. package/dist-esm/lib/canvas/TldrawScribble.mjs +1 -1
  111. package/dist-esm/lib/canvas/TldrawScribble.mjs.map +2 -2
  112. package/dist-esm/lib/defaultExternalContentHandlers.mjs +5 -4
  113. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  114. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs +4 -3
  115. package/dist-esm/lib/shapes/arrow/ArrowShapeUtil.mjs.map +2 -2
  116. package/dist-esm/lib/shapes/arrow/elbow/ElbowArrowDebug.mjs +3 -3
  117. package/dist-esm/lib/shapes/arrow/elbow/ElbowArrowDebug.mjs.map +1 -1
  118. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs +4 -3
  119. package/dist-esm/lib/shapes/draw/DrawShapeUtil.mjs.map +2 -2
  120. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs +1 -1
  121. package/dist-esm/lib/shapes/embed/EmbedShapeUtil.mjs.map +1 -1
  122. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +19 -12
  123. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  124. package/dist-esm/lib/shapes/frame/components/FrameHeading.mjs +1 -1
  125. package/dist-esm/lib/shapes/frame/components/FrameHeading.mjs.map +2 -2
  126. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +3 -2
  127. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  128. package/dist-esm/lib/shapes/geo/components/GeoShapeBody.mjs +2 -1
  129. package/dist-esm/lib/shapes/geo/components/GeoShapeBody.mjs.map +2 -2
  130. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs +6 -1
  131. package/dist-esm/lib/shapes/highlight/HighlightShapeUtil.mjs.map +2 -2
  132. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +6 -3
  133. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
  134. package/dist-esm/lib/shapes/line/LineShapeUtil.mjs +6 -1
  135. package/dist-esm/lib/shapes/line/LineShapeUtil.mjs.map +2 -2
  136. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +5 -4
  137. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  138. package/dist-esm/lib/shapes/shared/ShapeFill.mjs +5 -4
  139. package/dist-esm/lib/shapes/shared/ShapeFill.mjs.map +2 -2
  140. package/dist-esm/lib/shapes/shared/freehand/svg.mjs.map +2 -2
  141. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs +3 -2
  142. package/dist-esm/lib/shapes/text/TextShapeUtil.mjs.map +2 -2
  143. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs +3 -3
  144. package/dist-esm/lib/shapes/video/VideoShapeUtil.mjs.map +1 -1
  145. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs +26 -1
  146. package/dist-esm/lib/tools/EraserTool/childStates/Erasing.mjs.map +2 -2
  147. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs +13 -0
  148. package/dist-esm/lib/tools/EraserTool/childStates/Pointing.mjs.map +2 -2
  149. package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
  150. package/dist-esm/lib/ui/TldrawUi.mjs +29 -14
  151. package/dist-esm/lib/ui/TldrawUi.mjs.map +3 -3
  152. package/dist-esm/lib/ui/assetUrls.mjs +13 -10
  153. package/dist-esm/lib/ui/assetUrls.mjs.map +2 -2
  154. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs +10 -2
  155. package/dist-esm/lib/ui/components/ActionsMenu/DefaultActionsMenu.mjs.map +2 -2
  156. package/dist-esm/lib/ui/components/{FollowingIndicator.mjs → DefaultFollowingIndicator.mjs} +3 -3
  157. package/dist-esm/lib/ui/components/DefaultFollowingIndicator.mjs.map +7 -0
  158. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +6 -6
  159. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +1 -1
  160. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs +4 -4
  161. package/dist-esm/lib/ui/components/Minimap/MinimapManager.mjs.map +2 -2
  162. package/dist-esm/lib/ui/components/MobileStylePanel.mjs +6 -3
  163. package/dist-esm/lib/ui/components/MobileStylePanel.mjs.map +2 -2
  164. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs +1 -1
  165. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +2 -2
  166. package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs +3 -2
  167. package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs.map +2 -2
  168. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs +39 -10
  169. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs.map +2 -2
  170. package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs +56 -22
  171. package/dist-esm/lib/ui/components/Toolbar/DefaultToolbar.mjs.map +2 -2
  172. package/dist-esm/lib/ui/components/Toolbar/DefaultVideoToolbarContent.mjs +15 -3
  173. package/dist-esm/lib/ui/components/Toolbar/DefaultVideoToolbarContent.mjs.map +2 -2
  174. package/dist-esm/lib/ui/components/Toolbar/LinkEditor.mjs +3 -3
  175. package/dist-esm/lib/ui/components/Toolbar/LinkEditor.mjs.map +2 -2
  176. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +192 -80
  177. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +3 -3
  178. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs +2 -1
  179. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs.map +2 -2
  180. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs +10 -1
  181. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs.map +2 -2
  182. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +17 -4
  183. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
  184. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +16 -4
  185. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
  186. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +168 -162
  187. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  188. package/dist-esm/lib/ui/components/primitives/layout.mjs +31 -6
  189. package/dist-esm/lib/ui/components/primitives/layout.mjs.map +2 -2
  190. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuContext.mjs.map +2 -2
  191. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs +25 -12
  192. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuGroup.mjs.map +2 -2
  193. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +162 -21
  194. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  195. package/dist-esm/lib/ui/context/actions.mjs +16 -2
  196. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  197. package/dist-esm/lib/ui/context/components.mjs +2 -0
  198. package/dist-esm/lib/ui/context/components.mjs.map +2 -2
  199. package/dist-esm/lib/ui/context/events.mjs.map +2 -2
  200. package/dist-esm/lib/ui/hooks/useTools.mjs +102 -10
  201. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  202. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +2 -0
  203. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  204. package/dist-esm/lib/ui/kbd-utils.mjs +9 -3
  205. package/dist-esm/lib/ui/kbd-utils.mjs.map +2 -2
  206. package/dist-esm/lib/ui/version.mjs +3 -3
  207. package/dist-esm/lib/ui/version.mjs.map +1 -1
  208. package/package.json +11 -34
  209. package/src/index.ts +13 -1
  210. package/src/lib/Tldraw.tsx +15 -2
  211. package/src/lib/canvas/TldrawScribble.tsx +1 -1
  212. package/src/lib/defaultExternalContentHandlers.ts +12 -4
  213. package/src/lib/shapes/arrow/ArrowShapeOptions.test.ts +2 -1
  214. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +4 -3
  215. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +7 -6
  216. package/src/lib/shapes/arrow/ArrowShapeUtil.tsx +4 -3
  217. package/src/lib/shapes/arrow/elbow/ElbowArrowDebug.tsx +3 -3
  218. package/src/lib/shapes/draw/DrawShapeTool.test.ts +0 -5
  219. package/src/lib/shapes/draw/DrawShapeUtil.tsx +4 -3
  220. package/src/lib/shapes/embed/EmbedShapeUtil.tsx +1 -1
  221. package/src/lib/shapes/frame/FrameShapeUtil.tsx +29 -14
  222. package/src/lib/shapes/frame/components/FrameHeading.tsx +1 -1
  223. package/src/lib/shapes/geo/GeoShapeUtil.tsx +3 -2
  224. package/src/lib/shapes/geo/components/GeoShapeBody.tsx +2 -2
  225. package/src/lib/shapes/highlight/HighlightShapeUtil.tsx +7 -1
  226. package/src/lib/shapes/image/ImageShapeUtil.tsx +6 -3
  227. package/src/lib/shapes/line/LineShapeUtil.test.tsx +4 -3
  228. package/src/lib/shapes/line/LineShapeUtil.tsx +6 -1
  229. package/src/lib/shapes/line/__snapshots__/LineShapeUtil.test.tsx.snap +2 -2
  230. package/src/lib/shapes/note/NoteShapeUtil.tsx +9 -4
  231. package/src/lib/shapes/shared/ShapeFill.tsx +5 -4
  232. package/src/lib/shapes/shared/freehand/svg.ts +2 -0
  233. package/src/lib/shapes/text/TextShapeTool.test.ts +6 -5
  234. package/src/lib/shapes/text/TextShapeUtil.tsx +3 -2
  235. package/src/lib/shapes/video/VideoShapeUtil.tsx +3 -3
  236. package/src/lib/tools/EraserTool/childStates/Erasing.ts +34 -1
  237. package/src/lib/tools/EraserTool/childStates/Pointing.ts +20 -0
  238. package/src/lib/tools/SelectTool/childStates/Translating.ts +0 -1
  239. package/src/lib/ui/TldrawUi.tsx +33 -12
  240. package/src/lib/ui/assetUrls.ts +13 -10
  241. package/src/lib/ui/components/ActionsMenu/DefaultActionsMenu.tsx +13 -2
  242. package/src/lib/ui/components/{FollowingIndicator.tsx → DefaultFollowingIndicator.tsx} +2 -1
  243. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +6 -6
  244. package/src/lib/ui/components/Minimap/MinimapManager.ts +4 -4
  245. package/src/lib/ui/components/MobileStylePanel.tsx +9 -6
  246. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +1 -1
  247. package/src/lib/ui/components/Toolbar/AltTextEditor.tsx +4 -3
  248. package/src/lib/ui/components/Toolbar/DefaultImageToolbarContent.tsx +33 -16
  249. package/src/lib/ui/components/Toolbar/DefaultToolbar.tsx +55 -24
  250. package/src/lib/ui/components/Toolbar/DefaultVideoToolbarContent.tsx +12 -4
  251. package/src/lib/ui/components/Toolbar/LinkEditor.tsx +5 -5
  252. package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +208 -56
  253. package/src/lib/ui/components/primitives/TldrawUiButtonPicker.tsx +3 -2
  254. package/src/lib/ui/components/primitives/TldrawUiContextualToolbar.tsx +6 -1
  255. package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +50 -30
  256. package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +22 -5
  257. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +202 -184
  258. package/src/lib/ui/components/primitives/layout.tsx +79 -5
  259. package/src/lib/ui/components/primitives/menus/TldrawUiMenuContext.tsx +0 -1
  260. package/src/lib/ui/components/primitives/menus/TldrawUiMenuGroup.tsx +29 -16
  261. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +221 -18
  262. package/src/lib/ui/context/actions.tsx +16 -2
  263. package/src/lib/ui/context/components.tsx +3 -0
  264. package/src/lib/ui/context/events.tsx +2 -1
  265. package/src/lib/ui/hooks/useTools.tsx +140 -10
  266. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +2 -0
  267. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +2 -0
  268. package/src/lib/ui/kbd-utils.ts +10 -3
  269. package/src/lib/ui/version.ts +3 -3
  270. package/src/lib/ui.css +349 -243
  271. package/src/lib/utils/excalidraw/__snapshots__/putExcalidrawContent.test.tsx.snap +5 -5
  272. package/src/lib/utils/tldr/__snapshots__/buildFromV1Document.test.ts.snap +4 -4
  273. package/src/test/A11y.test.tsx +3 -2
  274. package/src/test/ClickManager.test.ts +7 -6
  275. package/src/test/Editor.test.tsx +20 -19
  276. package/src/test/EraserTool.test.ts +184 -13
  277. package/src/test/HandTool.test.ts +10 -9
  278. package/src/test/HighlightShape.test.ts +2 -1
  279. package/src/test/SelectTool.test.ts +3 -2
  280. package/src/test/TLUserPreferences.test.ts +4 -3
  281. package/src/test/TestEditor.ts +13 -15
  282. package/src/test/TldrawEditor.test.tsx +11 -10
  283. package/src/test/ZoomTool.test.ts +7 -6
  284. package/src/test/__snapshots__/drawing.test.ts.snap +2 -2
  285. package/src/test/__snapshots__/groups.test.tsx.snap +6 -6
  286. package/src/test/__snapshots__/resizing.test.ts.snap +2 -2
  287. package/src/test/arrows-megabus.test.tsx +5 -4
  288. package/src/test/bindings.test.tsx +24 -37
  289. package/src/test/bookmark-shapes.test.ts +1 -8
  290. package/src/test/commands/__snapshots__/getSvgString.test.ts.snap +23 -7
  291. package/src/test/commands/__snapshots__/packShapes.test.ts.snap +8 -8
  292. package/src/test/commands/__snapshots__/zoomToFit.test.ts.snap +2 -2
  293. package/src/test/commands/alignShapes.test.tsx +25 -24
  294. package/src/test/commands/animationSpeed.test.ts +2 -1
  295. package/src/test/commands/centerOnPoint.test.ts +3 -2
  296. package/src/test/commands/clipboard.test.ts +3 -2
  297. package/src/test/commands/createShapes.test.ts +2 -1
  298. package/src/test/commands/deleteShapes.test.ts +2 -1
  299. package/src/test/commands/distributeShapes.test.tsx +11 -10
  300. package/src/test/commands/getSvgString.test.ts +2 -1
  301. package/src/test/commands/packShapes.test.ts +5 -4
  302. package/src/test/commands/resizeShape.test.ts +2 -1
  303. package/src/test/commands/rotateShapes.test.ts +7 -6
  304. package/src/test/commands/setCamera.test.ts +4 -3
  305. package/src/test/commands/setCurrentPage.test.ts +3 -2
  306. package/src/test/commands/stackShapes.test.ts +11 -10
  307. package/src/test/commands/stretch.test.tsx +13 -12
  308. package/src/test/createDeepLink.test.tsx +2 -1
  309. package/src/test/cropping.test.ts +3 -2
  310. package/src/test/custom-clipping.test.ts +436 -0
  311. package/src/test/drawing.test.ts +2 -1
  312. package/src/test/flipShapes.test.ts +4 -3
  313. package/src/test/frames.test.ts +25 -24
  314. package/src/test/getCulledShapes.test.tsx +3 -2
  315. package/src/test/groups.test.tsx +1 -1
  316. package/src/test/handleDeepLink.test.tsx +2 -1
  317. package/src/test/maxShapes.test.ts +3 -2
  318. package/src/test/modifiers.test.ts +5 -4
  319. package/src/test/navigation.test.ts +12 -11
  320. package/src/test/panning.test.ts +2 -1
  321. package/src/test/perf/perf.test.ts +2 -1
  322. package/src/test/registerDeepLinkListener.test.tsx +10 -9
  323. package/src/test/resizing.test.ts +39 -38
  324. package/src/test/select.test.tsx +4 -3
  325. package/src/test/selection-omnibus.test.ts +11 -10
  326. package/src/test/shapeutils.test.ts +4 -3
  327. package/src/test/translating.test.ts +9 -8
  328. package/tldraw.css +650 -533
  329. package/dist-cjs/lib/ui/components/FollowingIndicator.js.map +0 -7
  330. package/dist-esm/lib/ui/components/FollowingIndicator.mjs.map +0 -7
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  DefaultColorStyle,
3
+ getColorValue,
3
4
  SharedStyle,
4
5
  StyleProp,
5
6
  TLDefaultColorStyle,
6
7
  TLDefaultColorTheme,
7
8
  useEditor,
8
9
  } from '@tldraw/editor'
9
- import { ReactElement, memo, useMemo, useRef } from 'react'
10
+ import { memo, ReactElement, useMemo, useRef } from 'react'
10
11
  import { StyleValuesForUi } from '../../../styles'
11
12
  import { PORTRAIT_BREAKPOINT } from '../../constants'
12
13
  import { useBreakpoint } from '../../context/breakpoints'
@@ -140,7 +141,7 @@ export const TldrawUiButtonPicker = memo(function TldrawUiButtonPicker<T extends
140
141
  title={label}
141
142
  style={
142
143
  style === (DefaultColorStyle as StyleProp<unknown>)
143
- ? { color: theme[item.value as TLDefaultColorStyle].solid }
144
+ ? { color: getColorValue(theme, item.value as TLDefaultColorStyle, 'solid') }
144
145
  : undefined
145
146
  }
146
147
  onPointerEnter={handleButtonPointerEnter}
@@ -172,7 +172,12 @@ export const TldrawUiContextualToolbar = ({
172
172
  className={classNames('tlui-contextual-toolbar', className)}
173
173
  onPointerDown={stopEventPropagation}
174
174
  >
175
- <TldrawUiToolbar orientation="horizontal" className="tlui-menu" label={label}>
175
+ <TldrawUiToolbar
176
+ orientation="horizontal"
177
+ className="tlui-menu"
178
+ label={label}
179
+ tooltipSide="top"
180
+ >
176
181
  {children}
177
182
  </TldrawUiToolbar>
178
183
  </div>
@@ -1,7 +1,9 @@
1
+ import { tltime } from '@tldraw/editor'
1
2
  import { Slider as _Slider } from 'radix-ui'
2
3
  import React, { useCallback, useEffect, useState } from 'react'
3
4
  import { TLUiTranslationKey } from '../../hooks/useTranslation/TLUiTranslationKey'
4
5
  import { useTranslation } from '../../hooks/useTranslation/useTranslation'
6
+ import { TldrawUiTooltip, tooltipManager } from './TldrawUiTooltip'
5
7
 
6
8
  /** @public */
7
9
  export interface TLUiSliderProps {
@@ -32,6 +34,7 @@ export const TldrawUiSlider = React.forwardRef<HTMLDivElement, TLUiSliderProps>(
32
34
  ref
33
35
  ) {
34
36
  const msg = useTranslation()
37
+ const [titleAndLabel, setTitleAndLabel] = useState('')
35
38
 
36
39
  // XXX: Radix starts out our slider with a tabIndex of 0
37
40
  // This causes some tab focusing issues, most prevelant in MobileStylePanel,
@@ -49,9 +52,25 @@ export const TldrawUiSlider = React.forwardRef<HTMLDivElement, TLUiSliderProps>(
49
52
  )
50
53
 
51
54
  const handlePointerDown = useCallback(() => {
55
+ tooltipManager.hideAllTooltips()
52
56
  onHistoryMark('click slider')
53
57
  }, [onHistoryMark])
54
58
 
59
+ // N.B. This is a bit silly. The Radix slider auto-focuses which
60
+ // triggers TldrawUiTooltip handleFocus when we dbl-click to edit an image,
61
+ // which in turn makes the tooltip display prematurely.
62
+ // This makes it wait until we've focused to show the tooltip.
63
+ useEffect(() => {
64
+ const timeout = tltime.setTimeout(
65
+ 'set title and label',
66
+ () => {
67
+ setTitleAndLabel(title + ' — ' + msg(label as TLUiTranslationKey))
68
+ },
69
+ 0
70
+ )
71
+ return () => clearTimeout(timeout)
72
+ }, [label, msg, title])
73
+
55
74
  // N.B. Annoying. For a11y purposes, we need Tab to work.
56
75
  // For some reason, Radix has some custom behavior here
57
76
  // that interferes with tabbing past the slider and then
@@ -64,36 +83,37 @@ export const TldrawUiSlider = React.forwardRef<HTMLDivElement, TLUiSliderProps>(
64
83
 
65
84
  return (
66
85
  <div className="tlui-slider__container">
67
- <_Slider.Root
68
- data-testid={testId}
69
- className="tlui-slider"
70
- dir="ltr"
71
- min={min ?? 0}
72
- max={steps}
73
- step={1}
74
- value={value !== null ? [value] : undefined}
75
- onPointerDown={handlePointerDown}
76
- onValueChange={handleValueChange}
77
- onKeyDownCapture={handleKeyEvent}
78
- onKeyUpCapture={handleKeyEvent}
79
- title={title + ' — ' + msg(label as TLUiTranslationKey)}
80
- >
81
- <_Slider.Track className="tlui-slider__track" dir="ltr">
82
- {value !== null && <_Slider.Range className="tlui-slider__range" dir="ltr" />}
83
- </_Slider.Track>
84
- {value !== null && (
85
- <_Slider.Thumb
86
- aria-valuemin={(min ?? 0) * ariaValueModifier}
87
- aria-valuenow={value * ariaValueModifier}
88
- aria-valuemax={steps * ariaValueModifier}
89
- aria-label={title + ' — ' + msg(label as TLUiTranslationKey)}
90
- className="tlui-slider__thumb"
91
- dir="ltr"
92
- ref={ref}
93
- tabIndex={tabIndex}
94
- />
95
- )}
96
- </_Slider.Root>
86
+ <TldrawUiTooltip content={titleAndLabel}>
87
+ <_Slider.Root
88
+ data-testid={testId}
89
+ className="tlui-slider"
90
+ dir="ltr"
91
+ min={min ?? 0}
92
+ max={steps}
93
+ step={1}
94
+ value={value !== null ? [value] : undefined}
95
+ onPointerDown={handlePointerDown}
96
+ onValueChange={handleValueChange}
97
+ onKeyDownCapture={handleKeyEvent}
98
+ onKeyUpCapture={handleKeyEvent}
99
+ >
100
+ <_Slider.Track className="tlui-slider__track" dir="ltr">
101
+ {value !== null && <_Slider.Range className="tlui-slider__range" dir="ltr" />}
102
+ </_Slider.Track>
103
+ {value !== null && (
104
+ <_Slider.Thumb
105
+ aria-valuemin={(min ?? 0) * ariaValueModifier}
106
+ aria-valuenow={value * ariaValueModifier}
107
+ aria-valuemax={steps * ariaValueModifier}
108
+ aria-label={titleAndLabel}
109
+ className="tlui-slider__thumb"
110
+ dir="ltr"
111
+ ref={ref}
112
+ tabIndex={tabIndex}
113
+ />
114
+ )}
115
+ </_Slider.Root>
116
+ </TldrawUiTooltip>
97
117
  </div>
98
118
  )
99
119
  })
@@ -1,7 +1,7 @@
1
1
  import classnames from 'classnames'
2
2
  import { Toolbar as _Toolbar } from 'radix-ui'
3
3
  import React from 'react'
4
- import { TldrawUiGrid, TldrawUiRow } from './layout'
4
+ import { TldrawUiColumn, TldrawUiGrid, TldrawUiRow } from './layout'
5
5
  import { TldrawUiTooltip } from './TldrawUiTooltip'
6
6
 
7
7
  /** @public */
@@ -10,15 +10,32 @@ export interface TLUiToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
10
10
  className?: string
11
11
  dir?: 'ltr' | 'rtl'
12
12
  label: string
13
- orientation?: 'horizontal' | 'grid'
13
+ orientation?: 'horizontal' | 'vertical' | 'grid'
14
+ tooltipSide?: 'top' | 'right' | 'bottom' | 'left'
15
+ }
16
+
17
+ const LayoutByOrientation = {
18
+ horizontal: TldrawUiRow,
19
+ vertical: TldrawUiColumn,
20
+ grid: TldrawUiGrid,
14
21
  }
15
22
 
16
23
  /** @public @react */
17
24
  export const TldrawUiToolbar = React.forwardRef<HTMLDivElement, TLUiToolbarProps>(
18
- ({ children, className, label, orientation = 'horizontal', ...props }: TLUiToolbarProps, ref) => {
19
- const Layout = orientation === 'grid' ? TldrawUiGrid : TldrawUiRow
25
+ (
26
+ {
27
+ children,
28
+ className,
29
+ label,
30
+ orientation = 'horizontal',
31
+ tooltipSide,
32
+ ...props
33
+ }: TLUiToolbarProps,
34
+ ref
35
+ ) => {
36
+ const Layout = LayoutByOrientation[orientation]
20
37
  return (
21
- <Layout asChild>
38
+ <Layout asChild tooltipSide={tooltipSide}>
22
39
  <_Toolbar.Root
23
40
  ref={ref}
24
41
  {...props}
@@ -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,117 @@ 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
+ const camera = useValue('camera', () => editor?.getCamera(), [])
239
+
240
+ useEffect(() => {
241
+ const currentTooltipId = tooltipId.current
242
+ return () => {
243
+ if (hasProvider) {
244
+ tooltipManager.hideTooltip(editor, currentTooltipId, true)
245
+ }
246
+ }
247
+ }, [editor, hasProvider])
286
248
 
287
- const handleMouseLeave = () => {
288
- tooltipManager.hideTooltip(tooltipId.current)
289
- }
249
+ useEffect(() => {
250
+ tooltipManager.hideTooltip(editor, tooltipId.current, true)
251
+ }, [editor, camera])
290
252
 
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
- }
253
+ // Don't show tooltip if disabled, no content, or UI labels are disabled
254
+ if (disabled || !content) {
255
+ return <>{children}</>
256
+ }
300
257
 
301
- const handleBlur = () => {
302
- tooltipManager.hideTooltip(tooltipId.current)
303
- }
258
+ const delayDurationToUse =
259
+ delayDuration ?? (editor?.options.tooltipDelayMs || DEFAULT_TOOLTIP_DELAY_MS)
260
+
261
+ // Fallback to old behavior if no provider
262
+ if (!hasProvider) {
263
+ return (
264
+ <_Tooltip.Root delayDuration={delayDurationToUse} disableHoverableContent>
265
+ <_Tooltip.Trigger asChild ref={ref}>
266
+ {children}
267
+ </_Tooltip.Trigger>
268
+ <_Tooltip.Content
269
+ className="tlui-tooltip"
270
+ side={sideToUse}
271
+ sideOffset={sideOffset}
272
+ avoidCollisions
273
+ collisionPadding={8}
274
+ dir="ltr"
275
+ >
276
+ {content}
277
+ <_Tooltip.Arrow className="tlui-tooltip__arrow" />
278
+ </_Tooltip.Content>
279
+ </_Tooltip.Root>
280
+ )
281
+ }
304
282
 
305
- const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
306
- onMouseEnter: handleMouseEnter,
307
- onMouseLeave: handleMouseLeave,
308
- onFocus: handleFocus,
309
- onBlur: handleBlur,
310
- })
283
+ const child = React.Children.only(children)
284
+ assert(React.isValidElement(child), 'TldrawUiTooltip children must be a single element')
285
+
286
+ const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
287
+ child.props.onMouseEnter?.(event)
288
+ tooltipManager.showTooltip(
289
+ tooltipId.current,
290
+ content,
291
+ event.currentTarget as HTMLElement,
292
+ sideToUse,
293
+ sideOffset,
294
+ showOnMobile,
295
+ delayDurationToUse
296
+ )
297
+ }
311
298
 
312
- return childrenWithHandlers
313
- }
299
+ const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
300
+ child.props.onMouseLeave?.(event)
301
+ tooltipManager.hideTooltip(editor, tooltipId.current)
302
+ }
303
+
304
+ const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
305
+ child.props.onFocus?.(event)
306
+ tooltipManager.showTooltip(
307
+ tooltipId.current,
308
+ content,
309
+ event.currentTarget as HTMLElement,
310
+ sideToUse,
311
+ sideOffset,
312
+ showOnMobile,
313
+ delayDurationToUse
314
+ )
315
+ }
316
+
317
+ const handleBlur = (event: React.FocusEvent<HTMLElement>) => {
318
+ child.props.onBlur?.(event)
319
+ tooltipManager.hideTooltip(editor, tooltipId.current)
320
+ }
321
+
322
+ const childrenWithHandlers = React.cloneElement(children as React.ReactElement, {
323
+ onMouseEnter: handleMouseEnter,
324
+ onMouseLeave: handleMouseLeave,
325
+ onFocus: handleFocus,
326
+ onBlur: handleBlur,
327
+ })
328
+
329
+ return childrenWithHandlers
330
+ }
331
+ )