tldraw 3.16.0-canary.f55016ece635 → 3.16.0-canary.f5bf2b535ea7

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 (293) hide show
  1. package/dist-cjs/index.d.ts +227 -110
  2. package/dist-cjs/index.js +29 -14
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/defaultExternalContentHandlers.js +10 -0
  5. package/dist-cjs/lib/defaultExternalContentHandlers.js.map +2 -2
  6. package/dist-cjs/lib/shapes/arrow/arrow-types.js.map +1 -1
  7. package/dist-cjs/lib/shapes/arrow/arrowLabel.js +6 -0
  8. package/dist-cjs/lib/shapes/arrow/arrowLabel.js.map +3 -3
  9. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js +3 -2
  10. package/dist-cjs/lib/shapes/arrow/arrowTargetState.js.map +2 -2
  11. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js +1 -1
  12. package/dist-cjs/lib/shapes/arrow/toolStates/Pointing.js.map +2 -2
  13. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js +4 -4
  14. package/dist-cjs/lib/shapes/bookmark/BookmarkShapeUtil.js.map +2 -2
  15. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js +8 -1
  16. package/dist-cjs/lib/shapes/frame/FrameShapeUtil.js.map +2 -2
  17. package/dist-cjs/lib/shapes/frame/components/FrameLabelInput.js +8 -2
  18. package/dist-cjs/lib/shapes/frame/components/FrameLabelInput.js.map +2 -2
  19. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js +1 -0
  20. package/dist-cjs/lib/shapes/geo/GeoShapeUtil.js.map +2 -2
  21. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js +3 -0
  22. package/dist-cjs/lib/shapes/image/ImageShapeUtil.js.map +2 -2
  23. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js +2 -1
  24. package/dist-cjs/lib/shapes/note/NoteShapeUtil.js.map +2 -2
  25. package/dist-cjs/lib/shapes/shared/HyperlinkButton.js +4 -4
  26. package/dist-cjs/lib/shapes/shared/HyperlinkButton.js.map +2 -2
  27. package/dist-cjs/lib/shapes/shared/PlainTextLabel.js +1 -3
  28. package/dist-cjs/lib/shapes/shared/PlainTextLabel.js.map +2 -2
  29. package/dist-cjs/lib/shapes/shared/useEditablePlainText.js +3 -5
  30. package/dist-cjs/lib/shapes/shared/useEditablePlainText.js.map +2 -2
  31. package/dist-cjs/lib/shapes/shared/useImageOrVideoAsset.js +0 -2
  32. package/dist-cjs/lib/shapes/shared/useImageOrVideoAsset.js.map +2 -2
  33. package/dist-cjs/lib/shapes/text/PlainTextArea.js +3 -2
  34. package/dist-cjs/lib/shapes/text/PlainTextArea.js.map +2 -2
  35. package/dist-cjs/lib/shapes/text/RichTextArea.js +3 -3
  36. package/dist-cjs/lib/shapes/text/RichTextArea.js.map +2 -2
  37. package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js +3 -1
  38. package/dist-cjs/lib/tools/SelectTool/childStates/DraggingHandle.js.map +2 -2
  39. package/dist-cjs/lib/ui/components/A11y.js +1 -1
  40. package/dist-cjs/lib/ui/components/A11y.js.map +2 -2
  41. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js +5 -5
  42. package/dist-cjs/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.js.map +1 -1
  43. package/dist-cjs/lib/ui/components/LanguageMenu.js +1 -0
  44. package/dist-cjs/lib/ui/components/LanguageMenu.js.map +2 -2
  45. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js +2 -1
  46. package/dist-cjs/lib/ui/components/Minimap/DefaultMinimap.js.map +2 -2
  47. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js +1 -1
  48. package/dist-cjs/lib/ui/components/PageMenu/DefaultPageMenu.js.map +2 -2
  49. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js +9 -4
  50. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanel.js.map +2 -2
  51. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js +255 -316
  52. package/dist-cjs/lib/ui/components/StylePanel/DefaultStylePanelContent.js.map +2 -2
  53. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js +147 -0
  54. package/dist-cjs/lib/ui/components/StylePanel/StylePanelButtonPicker.js.map +7 -0
  55. package/dist-cjs/lib/ui/components/StylePanel/StylePanelContext.js +68 -0
  56. package/dist-cjs/lib/ui/components/StylePanel/StylePanelContext.js.map +7 -0
  57. package/dist-cjs/lib/ui/components/StylePanel/{DoubleDropdownPicker.js → StylePanelDoubleDropdownPicker.js} +23 -22
  58. package/dist-cjs/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.js.map +7 -0
  59. package/dist-cjs/lib/ui/components/StylePanel/{DropdownPicker.js → StylePanelDropdownPicker.js} +23 -20
  60. package/dist-cjs/lib/ui/components/StylePanel/StylePanelDropdownPicker.js.map +7 -0
  61. package/dist-cjs/lib/ui/components/StylePanel/StylePanelSubheading.js +28 -0
  62. package/dist-cjs/lib/ui/components/StylePanel/StylePanelSubheading.js.map +7 -0
  63. package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js +2 -0
  64. package/dist-cjs/lib/ui/components/Toolbar/AltTextEditor.js.map +2 -2
  65. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js +38 -9
  66. package/dist-cjs/lib/ui/components/Toolbar/DefaultImageToolbarContent.js.map +2 -2
  67. package/dist-cjs/lib/ui/components/Toolbar/DefaultVideoToolbarContent.js +15 -3
  68. package/dist-cjs/lib/ui/components/Toolbar/DefaultVideoToolbarContent.js.map +2 -2
  69. package/dist-cjs/lib/ui/components/Toolbar/LinkEditor.js +2 -1
  70. package/dist-cjs/lib/ui/components/Toolbar/LinkEditor.js.map +2 -2
  71. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js +1 -1
  72. package/dist-cjs/lib/ui/components/Toolbar/OverflowingToolbar.js.map +2 -2
  73. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js +6 -2
  74. package/dist-cjs/lib/ui/components/Toolbar/ToggleToolLockedButton.js.map +2 -2
  75. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js +11 -2
  76. package/dist-cjs/lib/ui/components/primitives/TldrawUiContextualToolbar.js.map +2 -2
  77. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js +5 -3
  78. package/dist-cjs/lib/ui/components/primitives/TldrawUiInput.js.map +2 -2
  79. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js +18 -5
  80. package/dist-cjs/lib/ui/components/primitives/TldrawUiSlider.js.map +2 -2
  81. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js +3 -0
  82. package/dist-cjs/lib/ui/components/primitives/TldrawUiToolbar.js.map +2 -2
  83. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js +47 -3
  84. package/dist-cjs/lib/ui/components/primitives/TldrawUiTooltip.js.map +2 -2
  85. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.js +3 -0
  86. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.js.map +2 -2
  87. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +8 -8
  88. package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
  89. package/dist-cjs/lib/ui/context/actions.js +13 -8
  90. package/dist-cjs/lib/ui/context/actions.js.map +2 -2
  91. package/dist-cjs/lib/ui/context/events.js.map +1 -1
  92. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js +1 -1
  93. package/dist-cjs/lib/ui/hooks/useClipboardEvents.js.map +2 -2
  94. package/dist-cjs/lib/ui/hooks/useExportAs.js +3 -2
  95. package/dist-cjs/lib/ui/hooks/useExportAs.js.map +2 -2
  96. package/dist-cjs/lib/ui/hooks/useTools.js +1 -1
  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 +6 -2
  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-cjs/lib/utils/export/copyAs.js +1 -2
  106. package/dist-cjs/lib/utils/export/copyAs.js.map +2 -2
  107. package/dist-cjs/lib/utils/export/export.js +0 -20
  108. package/dist-cjs/lib/utils/export/export.js.map +2 -2
  109. package/dist-cjs/lib/utils/export/exportAs.js +1 -2
  110. package/dist-cjs/lib/utils/export/exportAs.js.map +2 -2
  111. package/dist-esm/index.d.mts +227 -110
  112. package/dist-esm/index.mjs +59 -28
  113. package/dist-esm/index.mjs.map +2 -2
  114. package/dist-esm/lib/defaultExternalContentHandlers.mjs +10 -0
  115. package/dist-esm/lib/defaultExternalContentHandlers.mjs.map +2 -2
  116. package/dist-esm/lib/shapes/arrow/arrowLabel.mjs +6 -0
  117. package/dist-esm/lib/shapes/arrow/arrowLabel.mjs.map +3 -3
  118. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs +3 -2
  119. package/dist-esm/lib/shapes/arrow/arrowTargetState.mjs.map +2 -2
  120. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs +1 -1
  121. package/dist-esm/lib/shapes/arrow/toolStates/Pointing.mjs.map +2 -2
  122. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs +4 -5
  123. package/dist-esm/lib/shapes/bookmark/BookmarkShapeUtil.mjs.map +2 -2
  124. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs +8 -1
  125. package/dist-esm/lib/shapes/frame/FrameShapeUtil.mjs.map +2 -2
  126. package/dist-esm/lib/shapes/frame/components/FrameLabelInput.mjs +9 -3
  127. package/dist-esm/lib/shapes/frame/components/FrameLabelInput.mjs.map +2 -2
  128. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs +1 -0
  129. package/dist-esm/lib/shapes/geo/GeoShapeUtil.mjs.map +2 -2
  130. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs +3 -0
  131. package/dist-esm/lib/shapes/image/ImageShapeUtil.mjs.map +2 -2
  132. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs +2 -1
  133. package/dist-esm/lib/shapes/note/NoteShapeUtil.mjs.map +2 -2
  134. package/dist-esm/lib/shapes/shared/HyperlinkButton.mjs +5 -5
  135. package/dist-esm/lib/shapes/shared/HyperlinkButton.mjs.map +2 -2
  136. package/dist-esm/lib/shapes/shared/PlainTextLabel.mjs +1 -3
  137. package/dist-esm/lib/shapes/shared/PlainTextLabel.mjs.map +2 -2
  138. package/dist-esm/lib/shapes/shared/useEditablePlainText.mjs +3 -6
  139. package/dist-esm/lib/shapes/shared/useEditablePlainText.mjs.map +2 -2
  140. package/dist-esm/lib/shapes/shared/useImageOrVideoAsset.mjs +0 -2
  141. package/dist-esm/lib/shapes/shared/useImageOrVideoAsset.mjs.map +2 -2
  142. package/dist-esm/lib/shapes/text/PlainTextArea.mjs +4 -3
  143. package/dist-esm/lib/shapes/text/PlainTextArea.mjs.map +2 -2
  144. package/dist-esm/lib/shapes/text/RichTextArea.mjs +3 -4
  145. package/dist-esm/lib/shapes/text/RichTextArea.mjs.map +2 -2
  146. package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs +3 -1
  147. package/dist-esm/lib/tools/SelectTool/childStates/DraggingHandle.mjs.map +2 -2
  148. package/dist-esm/lib/ui/components/A11y.mjs +1 -2
  149. package/dist-esm/lib/ui/components/A11y.mjs.map +2 -2
  150. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs +5 -5
  151. package/dist-esm/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.mjs.map +1 -1
  152. package/dist-esm/lib/ui/components/LanguageMenu.mjs +1 -0
  153. package/dist-esm/lib/ui/components/LanguageMenu.mjs.map +2 -2
  154. package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs +2 -1
  155. package/dist-esm/lib/ui/components/Minimap/DefaultMinimap.mjs.map +2 -2
  156. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs +1 -2
  157. package/dist-esm/lib/ui/components/PageMenu/DefaultPageMenu.mjs.map +2 -2
  158. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs +14 -5
  159. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanel.mjs.map +2 -2
  160. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs +257 -320
  161. package/dist-esm/lib/ui/components/StylePanel/DefaultStylePanelContent.mjs.map +2 -2
  162. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs +135 -0
  163. package/dist-esm/lib/ui/components/StylePanel/StylePanelButtonPicker.mjs.map +7 -0
  164. package/dist-esm/lib/ui/components/StylePanel/StylePanelContext.mjs +48 -0
  165. package/dist-esm/lib/ui/components/StylePanel/StylePanelContext.mjs.map +7 -0
  166. package/dist-esm/lib/ui/components/StylePanel/{DoubleDropdownPicker.mjs → StylePanelDoubleDropdownPicker.mjs} +20 -19
  167. package/dist-esm/lib/ui/components/StylePanel/StylePanelDoubleDropdownPicker.mjs.map +7 -0
  168. package/dist-esm/lib/ui/components/StylePanel/{DropdownPicker.mjs → StylePanelDropdownPicker.mjs} +20 -17
  169. package/dist-esm/lib/ui/components/StylePanel/StylePanelDropdownPicker.mjs.map +7 -0
  170. package/dist-esm/lib/ui/components/StylePanel/StylePanelSubheading.mjs +8 -0
  171. package/dist-esm/lib/ui/components/StylePanel/StylePanelSubheading.mjs.map +7 -0
  172. package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs +2 -0
  173. package/dist-esm/lib/ui/components/Toolbar/AltTextEditor.mjs.map +2 -2
  174. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs +38 -9
  175. package/dist-esm/lib/ui/components/Toolbar/DefaultImageToolbarContent.mjs.map +2 -2
  176. package/dist-esm/lib/ui/components/Toolbar/DefaultVideoToolbarContent.mjs +15 -3
  177. package/dist-esm/lib/ui/components/Toolbar/DefaultVideoToolbarContent.mjs.map +2 -2
  178. package/dist-esm/lib/ui/components/Toolbar/LinkEditor.mjs +2 -1
  179. package/dist-esm/lib/ui/components/Toolbar/LinkEditor.mjs.map +2 -2
  180. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs +1 -1
  181. package/dist-esm/lib/ui/components/Toolbar/OverflowingToolbar.mjs.map +2 -2
  182. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs +6 -2
  183. package/dist-esm/lib/ui/components/Toolbar/ToggleToolLockedButton.mjs.map +2 -2
  184. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs +11 -3
  185. package/dist-esm/lib/ui/components/primitives/TldrawUiContextualToolbar.mjs.map +2 -2
  186. package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs +6 -4
  187. package/dist-esm/lib/ui/components/primitives/TldrawUiInput.mjs.map +2 -2
  188. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs +18 -5
  189. package/dist-esm/lib/ui/components/primitives/TldrawUiSlider.mjs.map +2 -2
  190. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs +3 -0
  191. package/dist-esm/lib/ui/components/primitives/TldrawUiToolbar.mjs.map +2 -2
  192. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs +48 -3
  193. package/dist-esm/lib/ui/components/primitives/TldrawUiTooltip.mjs.map +2 -2
  194. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.mjs +3 -0
  195. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.mjs.map +2 -2
  196. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +8 -8
  197. package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
  198. package/dist-esm/lib/ui/context/actions.mjs +13 -8
  199. package/dist-esm/lib/ui/context/actions.mjs.map +2 -2
  200. package/dist-esm/lib/ui/context/events.mjs.map +1 -1
  201. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs +1 -2
  202. package/dist-esm/lib/ui/hooks/useClipboardEvents.mjs.map +2 -2
  203. package/dist-esm/lib/ui/hooks/useExportAs.mjs +3 -2
  204. package/dist-esm/lib/ui/hooks/useExportAs.mjs.map +2 -2
  205. package/dist-esm/lib/ui/hooks/useTools.mjs +1 -1
  206. package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
  207. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs +6 -2
  208. package/dist-esm/lib/ui/hooks/useTranslation/defaultTranslation.mjs.map +2 -2
  209. package/dist-esm/lib/ui/kbd-utils.mjs +9 -3
  210. package/dist-esm/lib/ui/kbd-utils.mjs.map +2 -2
  211. package/dist-esm/lib/ui/version.mjs +3 -3
  212. package/dist-esm/lib/ui/version.mjs.map +1 -1
  213. package/dist-esm/lib/utils/export/copyAs.mjs +1 -2
  214. package/dist-esm/lib/utils/export/copyAs.mjs.map +2 -2
  215. package/dist-esm/lib/utils/export/export.mjs +0 -20
  216. package/dist-esm/lib/utils/export/export.mjs.map +2 -2
  217. package/dist-esm/lib/utils/export/exportAs.mjs +1 -2
  218. package/dist-esm/lib/utils/export/exportAs.mjs.map +2 -2
  219. package/package.json +3 -3
  220. package/src/index.ts +45 -21
  221. package/src/lib/defaultExternalContentHandlers.ts +14 -0
  222. package/src/lib/shapes/arrow/ArrowShapeOptions.test.ts +83 -13
  223. package/src/lib/shapes/arrow/ArrowShapeTool.test.ts +99 -5
  224. package/src/lib/shapes/arrow/ArrowShapeUtil.test.ts +41 -0
  225. package/src/lib/shapes/arrow/arrow-types.ts +3 -5
  226. package/src/lib/shapes/arrow/arrowLabel.ts +8 -0
  227. package/src/lib/shapes/arrow/arrowTargetState.ts +34 -3
  228. package/src/lib/shapes/arrow/toolStates/Pointing.tsx +1 -1
  229. package/src/lib/shapes/bookmark/BookmarkShapeUtil.tsx +4 -5
  230. package/src/lib/shapes/frame/FrameShapeUtil.tsx +9 -0
  231. package/src/lib/shapes/frame/components/FrameLabelInput.tsx +10 -3
  232. package/src/lib/shapes/geo/GeoShapeUtil.tsx +1 -0
  233. package/src/lib/shapes/image/ImageShapeUtil.tsx +3 -0
  234. package/src/lib/shapes/note/NoteShapeUtil.tsx +1 -0
  235. package/src/lib/shapes/shared/HyperlinkButton.tsx +5 -5
  236. package/src/lib/shapes/shared/PlainTextLabel.tsx +0 -6
  237. package/src/lib/shapes/shared/useEditablePlainText.ts +3 -10
  238. package/src/lib/shapes/shared/useImageOrVideoAsset.ts +0 -7
  239. package/src/lib/shapes/text/PlainTextArea.tsx +4 -3
  240. package/src/lib/shapes/text/RichTextArea.tsx +3 -4
  241. package/src/lib/tools/SelectTool/childStates/DraggingHandle.tsx +6 -2
  242. package/src/lib/ui/components/A11y.tsx +1 -2
  243. package/src/lib/ui/components/KeyboardShortcutsDialog/DefaultKeyboardShortcutsDialogContent.tsx +5 -5
  244. package/src/lib/ui/components/LanguageMenu.tsx +1 -0
  245. package/src/lib/ui/components/Minimap/DefaultMinimap.tsx +2 -1
  246. package/src/lib/ui/components/PageMenu/DefaultPageMenu.tsx +1 -2
  247. package/src/lib/ui/components/StylePanel/DefaultStylePanel.tsx +27 -13
  248. package/src/lib/ui/components/StylePanel/DefaultStylePanelContent.tsx +260 -381
  249. package/src/lib/ui/components/{primitives/TldrawUiButtonPicker.tsx → StylePanel/StylePanelButtonPicker.tsx} +70 -50
  250. package/src/lib/ui/components/StylePanel/StylePanelContext.tsx +63 -0
  251. package/src/lib/ui/components/StylePanel/{DoubleDropdownPicker.tsx → StylePanelDoubleDropdownPicker.tsx} +28 -19
  252. package/src/lib/ui/components/StylePanel/StylePanelDropdownPicker.tsx +119 -0
  253. package/src/lib/ui/components/StylePanel/StylePanelSubheading.tsx +9 -0
  254. package/src/lib/ui/components/Toolbar/AltTextEditor.tsx +2 -0
  255. package/src/lib/ui/components/Toolbar/DefaultImageToolbarContent.tsx +32 -15
  256. package/src/lib/ui/components/Toolbar/DefaultVideoToolbarContent.tsx +12 -4
  257. package/src/lib/ui/components/Toolbar/LinkEditor.tsx +1 -0
  258. package/src/lib/ui/components/Toolbar/OverflowingToolbar.tsx +1 -1
  259. package/src/lib/ui/components/Toolbar/ToggleToolLockedButton.tsx +9 -2
  260. package/src/lib/ui/components/primitives/TldrawUiContextualToolbar.tsx +7 -3
  261. package/src/lib/ui/components/primitives/TldrawUiInput.tsx +6 -3
  262. package/src/lib/ui/components/primitives/TldrawUiSlider.tsx +52 -32
  263. package/src/lib/ui/components/primitives/TldrawUiToolbar.tsx +5 -1
  264. package/src/lib/ui/components/primitives/TldrawUiTooltip.tsx +67 -13
  265. package/src/lib/ui/components/primitives/menus/TldrawUiMenuCheckboxItem.tsx +4 -0
  266. package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +9 -9
  267. package/src/lib/ui/context/actions.tsx +20 -8
  268. package/src/lib/ui/context/events.tsx +1 -1
  269. package/src/lib/ui/hooks/useClipboardEvents.ts +1 -2
  270. package/src/lib/ui/hooks/useExportAs.ts +3 -2
  271. package/src/lib/ui/hooks/useTools.tsx +1 -1
  272. package/src/lib/ui/hooks/useTranslation/TLUiTranslationKey.ts +4 -0
  273. package/src/lib/ui/hooks/useTranslation/defaultTranslation.ts +6 -2
  274. package/src/lib/ui/kbd-utils.ts +10 -3
  275. package/src/lib/ui/version.ts +3 -3
  276. package/src/lib/ui.css +40 -3
  277. package/src/lib/utils/export/copyAs.ts +1 -24
  278. package/src/lib/utils/export/export.ts +0 -36
  279. package/src/lib/utils/export/exportAs.ts +1 -32
  280. package/src/test/TestEditor.ts +8 -2
  281. package/src/test/custom-clipping.test.ts +436 -0
  282. package/src/test/frames.test.ts +15 -0
  283. package/src/test/getCulledShapes.test.tsx +71 -2
  284. package/tldraw.css +48 -6
  285. package/dist-cjs/lib/ui/components/StylePanel/DoubleDropdownPicker.js.map +0 -7
  286. package/dist-cjs/lib/ui/components/StylePanel/DropdownPicker.js.map +0 -7
  287. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js +0 -131
  288. package/dist-cjs/lib/ui/components/primitives/TldrawUiButtonPicker.js.map +0 -7
  289. package/dist-esm/lib/ui/components/StylePanel/DoubleDropdownPicker.mjs.map +0 -7
  290. package/dist-esm/lib/ui/components/StylePanel/DropdownPicker.mjs.map +0 -7
  291. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs +0 -115
  292. package/dist-esm/lib/ui/components/primitives/TldrawUiButtonPicker.mjs.map +0 -7
  293. package/src/lib/ui/components/StylePanel/DropdownPicker.tsx +0 -110
@@ -31,9 +31,16 @@ export function kbd(str: string) {
31
31
  )
32
32
  .flat()
33
33
  .map((sub, index) => {
34
- if (sub === '__CTRL__') return 'Ctrl'
35
- if (sub === '__ALT__') return 'Alt'
36
- const modifiedKey = sub[0].toUpperCase() + sub.slice(1)
34
+ if (sub[0] === '+') return []
35
+
36
+ let modifiedKey
37
+ if (sub === '__CTRL__') {
38
+ modifiedKey = 'Ctrl'
39
+ } else if (sub === '__ALT__') {
40
+ modifiedKey = 'Alt'
41
+ } else {
42
+ modifiedKey = sub[0].toUpperCase() + sub.slice(1)
43
+ }
37
44
  return tlenv.isDarwin || !index ? modifiedKey : ['+', modifiedKey]
38
45
  })
39
46
  .flat()
@@ -1,9 +1,9 @@
1
1
  // This file is automatically generated by internal/scripts/refresh-assets.ts.
2
2
  // Do not edit manually. Or do, I'm a comment, not a cop.
3
3
 
4
- export const version = '3.16.0-canary.f55016ece635'
4
+ export const version = '3.16.0-canary.f5bf2b535ea7'
5
5
  export const publishDates = {
6
6
  major: '2024-09-13T14:36:29.063Z',
7
- minor: '2025-08-26T12:45:19.828Z',
8
- patch: '2025-08-26T12:45:19.828Z',
7
+ minor: '2025-09-18T13:10:54.219Z',
8
+ patch: '2025-09-18T13:10:54.219Z',
9
9
  }
package/src/lib/ui.css CHANGED
@@ -168,7 +168,7 @@
168
168
  min-height: 40px;
169
169
  width: 100%;
170
170
  gap: 8px;
171
- margin: -4px 0px;
171
+ margin-top: -4px;
172
172
  }
173
173
 
174
174
  .tlui-button__menu::after {
@@ -494,6 +494,10 @@
494
494
  -webkit-user-select: auto !important;
495
495
  }
496
496
 
497
+ .tlui-input::placeholder {
498
+ color: var(--tl-color-text-3);
499
+ }
500
+
497
501
  .tlui-input__wrapper {
498
502
  width: 100%;
499
503
  height: 44px;
@@ -578,6 +582,12 @@
578
582
  box-shadow: var(--tl-shadow-3);
579
583
  }
580
584
 
585
+ @media (max-height: 600px) {
586
+ .tlui-menu {
587
+ max-height: 70vh;
588
+ }
589
+ }
590
+
581
591
  .tlui-menu::-webkit-scrollbar {
582
592
  display: none;
583
593
  }
@@ -1007,21 +1017,48 @@
1007
1017
  flex-direction: column;
1008
1018
  }
1009
1019
 
1010
- .tlui-style-panel__section:nth-of-type(n + 2):not(:last-child) {
1020
+ /*
1021
+ add a border to the bottom of all but the last section. we have to handle empty sections too, which
1022
+ are hidden and shouldn't be counted
1023
+ */
1024
+ .tlui-style-panel__section:not(:nth-last-child(-n + 1 of .tlui-style-panel__section:not(:empty))) {
1011
1025
  border-bottom: 1px solid var(--tl-color-divider);
1012
1026
  }
1027
+ /*
1028
+ if a section ends with a slider and we're adding a border, we need some extra space for visual
1029
+ balance. we need to handle empty sections as above. is this the most complex css selector in all of
1030
+ tldraw? probably.
1031
+ */
1032
+ .tlui-style-panel__section:has(.tlui-slider__container:last-child):not(
1033
+ :nth-last-child(-n + 1 of .tlui-style-panel__section:not(:empty))
1034
+ ) {
1035
+ margin-bottom: 7px;
1036
+ }
1013
1037
 
1014
1038
  .tlui-style-panel__section:empty {
1015
1039
  display: none;
1016
1040
  }
1017
1041
 
1042
+ /*
1043
+ * This is used in a couple places, like Align and Vertical Align.
1044
+ * It's because we have a toolbar with a Toggle Group but then an adjacent button
1045
+ * next to it that opens a popup.
1046
+ */
1047
+ .tlui-style-panel__section .tlui-toolbar:has(.tlui-toolbar) {
1048
+ flex-wrap: wrap;
1049
+ }
1050
+
1051
+ .tlui-style-panel__section .tlui-toolbar:has(.tlui-toolbar) .tlui-style-panel__subheading {
1052
+ margin-left: -2px;
1053
+ }
1054
+
1018
1055
  .tlui-style-panel__section__common:not(:only-child) {
1019
1056
  margin-bottom: 7px;
1020
1057
  border-bottom: 1px solid var(--tl-color-divider);
1021
1058
  }
1022
1059
 
1023
1060
  .tlui-style-panel__dropdown-picker:only-child {
1024
- width: 100%;
1061
+ flex: 1;
1025
1062
  }
1026
1063
 
1027
1064
  .tlui-style-panel__double-select-picker {
@@ -31,30 +31,7 @@ export interface CopyAsOptions extends TLImageExportOptions {
31
31
  *
32
32
  * @public
33
33
  */
34
- export function copyAs(editor: Editor, ids: TLShapeId[], opts: CopyAsOptions): Promise<void>
35
- /**
36
- * @deprecated The format parameter is now part of the opts object.
37
- * @public
38
- */
39
- export function copyAs(
40
- editor: Editor,
41
- ids: TLShapeId[],
42
- format: TLCopyType,
43
- opts?: TLImageExportOptions & { format?: undefined }
44
- ): Promise<void>
45
- export function copyAs(
46
- ...args:
47
- | [editor: Editor, ids: TLShapeId[], opts: TLImageExportOptions & { format: TLCopyType }]
48
- | [
49
- editor: Editor,
50
- ids: TLShapeId[],
51
- format: TLCopyType,
52
- opts?: TLImageExportOptions & { format?: undefined },
53
- ]
54
- ) {
55
- const [editor, ids, opts] =
56
- typeof args[2] === 'string' ? [args[0], args[1], { ...args[3], format: args[2] }] : args
57
-
34
+ export function copyAs(editor: Editor, ids: TLShapeId[], opts: CopyAsOptions): Promise<void> {
58
35
  // Note: it's important that this function itself isn't async and doesn't really use promises -
59
36
  // we need to create the relevant `ClipboardItem`s and call navigator.clipboard.write
60
37
  // synchronously to make sure safari knows that the user _wants_ to copy See
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  Editor,
3
3
  FileHelpers,
4
- TLExportType,
5
4
  TLImageExportOptions,
6
5
  TLShapeId,
7
6
  exhaustiveSwitchError,
@@ -35,41 +34,6 @@ export async function exportToString(
35
34
  }
36
35
  }
37
36
 
38
- /**
39
- * Export the given shapes as a blob.
40
- * @param editor - The editor instance.
41
- * @param ids - The ids of the shapes to export.
42
- * @param format - The format to export as.
43
- * @param opts - Rendering options.
44
- * @returns A promise that resolves to a blob.
45
- * @deprecated Use {@link @tldraw/editor#Editor.toImage} instead.
46
- * @public
47
- */
48
- export async function exportToBlob({
49
- editor,
50
- ids,
51
- format,
52
- opts = {},
53
- }: {
54
- editor: Editor
55
- ids: TLShapeId[]
56
- format: TLExportType
57
- opts?: TLImageExportOptions
58
- }): Promise<Blob> {
59
- const idsToUse = ids?.length ? ids : [...editor.getCurrentPageShapeIds()]
60
- switch (format) {
61
- case 'jpeg':
62
- case 'png':
63
- case 'webp':
64
- case 'svg': {
65
- return (await editor.toImage(idsToUse, { ...opts, format })).blob
66
- }
67
- default: {
68
- exhaustiveSwitchError(format)
69
- }
70
- }
71
- }
72
-
73
37
  const clipboardMimeTypesByFormat = {
74
38
  jpeg: 'image/jpeg',
75
39
  png: 'image/png',
@@ -28,38 +28,7 @@ export async function exportAs(
28
28
  editor: Editor,
29
29
  ids: TLShapeId[],
30
30
  opts: ExportAsOptions
31
- ): Promise<void>
32
- /**
33
- * @deprecated The format & name parameters are now part of the opts object.
34
- * @public
35
- */
36
- export async function exportAs(
37
- editor: Editor,
38
- ids: TLShapeId[],
39
- format?: TLExportType,
40
- name?: string,
41
- opts?: TLImageExportOptions
42
- ): Promise<void>
43
- export async function exportAs(
44
- ...args:
45
- | [
46
- editor: Editor,
47
- ids: TLShapeId[],
48
- opts: TLImageExportOptions & { format: TLExportType; name?: string },
49
- ]
50
- | [
51
- editor: Editor,
52
- ids: TLShapeId[],
53
- format?: TLExportType,
54
- name?: string,
55
- opts?: TLImageExportOptions,
56
- ]
57
- ) {
58
- const [editor, ids, opts] =
59
- typeof args[2] === 'object'
60
- ? args
61
- : [args[0], args[1], { ...args[4], format: args[2] ?? 'png', name: args[3] }]
62
-
31
+ ): Promise<void> {
63
32
  // If we don't get name then use a predefined one
64
33
  let name = opts.name
65
34
  if (!name) {
@@ -86,8 +86,14 @@ export class TestEditor extends Editor {
86
86
  elm.tabIndex = 0
87
87
  elm.getBoundingClientRect = () => bounds as DOMRect
88
88
 
89
- const shapeUtilsWithDefaults = [...defaultShapeUtils, ...(options.shapeUtils ?? [])]
90
- const bindingUtilsWithDefaults = [...defaultBindingUtils, ...(options.bindingUtils ?? [])]
89
+ const shapeUtilsWithDefaults = [
90
+ ...defaultShapeUtils.filter((s) => !options.shapeUtils?.some((su) => su.type === s.type)),
91
+ ...(options.shapeUtils ?? []),
92
+ ]
93
+ const bindingUtilsWithDefaults = [
94
+ ...defaultBindingUtils.filter((b) => !options.bindingUtils?.some((bu) => bu.type === b.type)),
95
+ ...(options.bindingUtils ?? []),
96
+ ]
91
97
 
92
98
  super({
93
99
  ...options,
@@ -0,0 +1,436 @@
1
+ import {
2
+ atom,
3
+ BaseBoxShapeUtil,
4
+ Circle2d,
5
+ createShapeId,
6
+ Geometry2d,
7
+ RecordProps,
8
+ resizeBox,
9
+ StateNode,
10
+ T,
11
+ TLBaseShape,
12
+ TLEventHandlers,
13
+ TLGeoShape,
14
+ TLResizeInfo,
15
+ TLShape,
16
+ TLTextShape,
17
+ toRichText,
18
+ Vec,
19
+ } from '@tldraw/editor'
20
+ import { TestEditor } from './TestEditor'
21
+
22
+ // Custom Circle Clip Shape Definition
23
+ export type CircleClipShape = TLBaseShape<
24
+ 'circle-clip',
25
+ {
26
+ w: number
27
+ h: number
28
+ }
29
+ >
30
+
31
+ export const isClippingEnabled$ = atom('isClippingEnabled', true)
32
+
33
+ export class CircleClipShapeUtil extends BaseBoxShapeUtil<CircleClipShape> {
34
+ static override type = 'circle-clip' as const
35
+ static override props: RecordProps<CircleClipShape> = {
36
+ w: T.number,
37
+ h: T.number,
38
+ }
39
+
40
+ override canBind() {
41
+ return false
42
+ }
43
+
44
+ override canReceiveNewChildrenOfType(shape: TLShape) {
45
+ return !shape.isLocked
46
+ }
47
+
48
+ override getDefaultProps(): CircleClipShape['props'] {
49
+ return {
50
+ w: 200,
51
+ h: 200,
52
+ }
53
+ }
54
+
55
+ override getGeometry(shape: CircleClipShape): Geometry2d {
56
+ const radius = Math.min(shape.props.w, shape.props.h) / 2
57
+ return new Circle2d({
58
+ radius,
59
+ x: shape.props.w / 2 - radius,
60
+ y: shape.props.h / 2 - radius,
61
+ isFilled: true,
62
+ })
63
+ }
64
+
65
+ override getClipPath(shape: CircleClipShape): Vec[] | undefined {
66
+ // Generate a polygon approximation of the circle
67
+ const centerX = shape.props.w / 2
68
+ const centerY = shape.props.h / 2
69
+ const radius = Math.min(shape.props.w, shape.props.h) / 2
70
+ const segments = 48 // More segments = smoother circle
71
+
72
+ const points: Vec[] = []
73
+ for (let i = 0; i < segments; i++) {
74
+ const angle = (i / segments) * Math.PI * 2
75
+ const x = centerX + Math.cos(angle) * radius
76
+ const y = centerY + Math.sin(angle) * radius
77
+ points.push(new Vec(x, y))
78
+ }
79
+
80
+ return points
81
+ }
82
+
83
+ override shouldClipChild(_child: TLShape): boolean {
84
+ // For now, clip all children - we removed the onlyClipText feature for simplicity
85
+ return isClippingEnabled$.get()
86
+ }
87
+
88
+ override component(_shape: CircleClipShape) {
89
+ // For testing purposes, we'll just return null
90
+ // In a real implementation, this would return JSX
91
+ return null as any
92
+ }
93
+
94
+ override indicator(_shape: CircleClipShape) {
95
+ // For testing purposes, we'll just return null
96
+ // In a real implementation, this would return JSX
97
+ return null as any
98
+ }
99
+
100
+ override onResize(shape: CircleClipShape, info: TLResizeInfo<CircleClipShape>) {
101
+ return resizeBox(shape, info)
102
+ }
103
+ }
104
+
105
+ export class CircleClipShapeTool extends StateNode {
106
+ static override id = 'circle-clip'
107
+
108
+ override onEnter(): void {
109
+ this.editor.setCursor({ type: 'cross', rotation: 0 })
110
+ }
111
+
112
+ override onPointerDown(info: Parameters<TLEventHandlers['onPointerDown']>[0]) {
113
+ if (info.target === 'canvas') {
114
+ const { originPagePoint } = this.editor.inputs
115
+
116
+ this.editor.createShape<CircleClipShape>({
117
+ type: 'circle-clip',
118
+ x: originPagePoint.x - 100,
119
+ y: originPagePoint.y - 100,
120
+ props: {
121
+ w: 200,
122
+ h: 200,
123
+ },
124
+ })
125
+ }
126
+ }
127
+ }
128
+
129
+ let editor: TestEditor
130
+
131
+ afterEach(() => {
132
+ editor?.dispose()
133
+ })
134
+
135
+ const ids = {
136
+ circleClip1: createShapeId('circleClip1'),
137
+ circleClip2: createShapeId('circleClip2'),
138
+ text1: createShapeId('text1'),
139
+ geo1: createShapeId('geo1'),
140
+ geo2: createShapeId('geo2'),
141
+ }
142
+
143
+ beforeEach(() => {
144
+ editor = new TestEditor({
145
+ shapeUtils: [CircleClipShapeUtil],
146
+ tools: [CircleClipShapeTool],
147
+ })
148
+
149
+ // Reset clipping state
150
+ isClippingEnabled$.set(true)
151
+ })
152
+
153
+ describe('CircleClipShapeUtil', () => {
154
+ describe('shape creation and properties', () => {
155
+ it('should create a circle clip shape with default properties', () => {
156
+ editor.createShape<CircleClipShape>({
157
+ id: ids.circleClip1,
158
+ type: 'circle-clip',
159
+ x: 100,
160
+ y: 100,
161
+ props: {
162
+ w: 200,
163
+ h: 200,
164
+ },
165
+ })
166
+
167
+ const shape = editor.getShape<CircleClipShape>(ids.circleClip1)
168
+ expect(shape).toBeDefined()
169
+ expect(shape!.type).toBe('circle-clip')
170
+ expect(shape!.props.w).toBe(200)
171
+ expect(shape!.props.h).toBe(200)
172
+ })
173
+
174
+ it('should use default props when not specified', () => {
175
+ editor.createShape<CircleClipShape>({
176
+ id: ids.circleClip1,
177
+ type: 'circle-clip',
178
+ x: 100,
179
+ y: 100,
180
+ props: {},
181
+ })
182
+
183
+ const shape = editor.getShape<CircleClipShape>(ids.circleClip1)
184
+ expect(shape!.props.w).toBe(200) // default from getDefaultProps
185
+ expect(shape!.props.h).toBe(200) // default from getDefaultProps
186
+ })
187
+ })
188
+
189
+ describe('geometry and clipping', () => {
190
+ it('should generate correct circle geometry', () => {
191
+ editor.createShape<CircleClipShape>({
192
+ id: ids.circleClip1,
193
+ type: 'circle-clip',
194
+ x: 100,
195
+ y: 100,
196
+ props: {
197
+ w: 200,
198
+ h: 200,
199
+ },
200
+ })
201
+
202
+ const shape = editor.getShape<CircleClipShape>(ids.circleClip1)
203
+ const util = editor.getShapeUtil<CircleClipShape>('circle-clip')
204
+ const geometry = util.getGeometry(shape!)
205
+
206
+ expect(geometry).toBeDefined()
207
+ expect(geometry.bounds).toBeDefined()
208
+ expect(geometry.bounds.width).toBe(200)
209
+ expect(geometry.bounds.height).toBe(200)
210
+ })
211
+
212
+ it('should generate clip path for circle', () => {
213
+ editor.createShape<CircleClipShape>({
214
+ id: ids.circleClip1,
215
+ type: 'circle-clip',
216
+ x: 100,
217
+ y: 100,
218
+ props: {
219
+ w: 200,
220
+ h: 200,
221
+ },
222
+ })
223
+
224
+ const shape = editor.getShape<CircleClipShape>(ids.circleClip1)
225
+ const util = editor.getShapeUtil<CircleClipShape>('circle-clip')
226
+ const clipPath = util.getClipPath?.(shape!)
227
+ if (!clipPath) throw new Error('Clip path is undefined')
228
+
229
+ expect(clipPath).toBeDefined()
230
+ expect(Array.isArray(clipPath)).toBe(true)
231
+ expect(clipPath.length).toBeGreaterThan(0)
232
+
233
+ // Should be a polygon approximation of a circle
234
+ // Check that points are roughly in a circle pattern
235
+ const centerX = 100 // shape.x
236
+ const centerY = 100 // shape.y
237
+ const radius = 100 // min(w, h) / 2
238
+
239
+ clipPath.forEach((point) => {
240
+ const distance = Math.sqrt(Math.pow(point.x - centerX, 2) + Math.pow(point.y - centerY, 2))
241
+ expect(distance).toBeCloseTo(radius, 0)
242
+ })
243
+ })
244
+ })
245
+
246
+ describe('child clipping behavior', () => {
247
+ it('should clip children when clipping is enabled', () => {
248
+ editor.createShape<CircleClipShape>({
249
+ id: ids.circleClip1,
250
+ type: 'circle-clip',
251
+ x: 100,
252
+ y: 100,
253
+ props: {
254
+ w: 200,
255
+ h: 200,
256
+ },
257
+ })
258
+
259
+ editor.createShape<TLTextShape>({
260
+ id: ids.text1,
261
+ type: 'text',
262
+ x: 0,
263
+ y: 0,
264
+ parentId: ids.circleClip1,
265
+ props: {
266
+ richText: toRichText('Test text'),
267
+ },
268
+ })
269
+
270
+ const util = editor.getShapeUtil<CircleClipShape>('circle-clip')
271
+ const textShape = editor.getShape<TLTextShape>(ids.text1)
272
+
273
+ // Clipping should be enabled by default
274
+ expect(isClippingEnabled$.get()).toBe(true)
275
+ expect(util.shouldClipChild?.(textShape!)).toBe(true)
276
+ expect(editor.getShapeClipPath(ids.text1)).toBeDefined()
277
+ })
278
+
279
+ it('should not clip children when clipping is disabled', () => {
280
+ isClippingEnabled$.set(false)
281
+
282
+ editor.createShape<CircleClipShape>({
283
+ id: ids.circleClip1,
284
+ type: 'circle-clip',
285
+ x: 100,
286
+ y: 100,
287
+ props: {
288
+ w: 200,
289
+ h: 200,
290
+ },
291
+ })
292
+
293
+ editor.createShape<TLTextShape>({
294
+ id: ids.text1,
295
+ type: 'text',
296
+ x: 0,
297
+ y: 0,
298
+ parentId: ids.circleClip1,
299
+ props: {
300
+ richText: toRichText('Test text'),
301
+ },
302
+ })
303
+
304
+ const util = editor.getShapeUtil<CircleClipShape>('circle-clip')
305
+ const textShape = editor.getShape<TLTextShape>(ids.text1)
306
+
307
+ expect(isClippingEnabled$.get()).toBe(false)
308
+ expect(util.shouldClipChild?.(textShape!)).toBe(false)
309
+ expect(editor.getShapeClipPath(ids.text1)).toBeUndefined()
310
+ })
311
+ })
312
+ })
313
+
314
+ describe('Integration tests', () => {
315
+ it('should create and manage circle clip shapes with children', () => {
316
+ // Create circle clip shape
317
+ editor.createShape<CircleClipShape>({
318
+ id: ids.circleClip1,
319
+ type: 'circle-clip',
320
+ x: 100,
321
+ y: 100,
322
+ props: {
323
+ w: 200,
324
+ h: 200,
325
+ },
326
+ })
327
+
328
+ // Add text child
329
+ editor.createShape<TLTextShape>({
330
+ id: ids.text1,
331
+ type: 'text',
332
+ x: 50,
333
+ y: 50,
334
+ parentId: ids.circleClip1,
335
+ props: {
336
+ richText: toRichText('Clipped text'),
337
+ },
338
+ })
339
+
340
+ // Add geo child
341
+ editor.createShape<TLGeoShape>({
342
+ id: ids.geo1,
343
+ type: 'geo',
344
+ x: 150,
345
+ y: 150,
346
+ parentId: ids.circleClip1,
347
+ props: {
348
+ w: 50,
349
+ h: 50,
350
+ },
351
+ })
352
+
353
+ const circleClipShape = editor.getShape<CircleClipShape>(ids.circleClip1)
354
+ const textShape = editor.getShape<TLTextShape>(ids.text1)
355
+ const geoShape = editor.getShape<TLGeoShape>(ids.geo1)
356
+
357
+ expect(circleClipShape).toBeDefined()
358
+ expect(textShape!.parentId).toBe(ids.circleClip1)
359
+ expect(geoShape!.parentId).toBe(ids.circleClip1)
360
+
361
+ // Verify clipping behavior
362
+ const util = editor.getShapeUtil<CircleClipShape>('circle-clip')
363
+ expect(util.shouldClipChild?.(textShape!)).toBe(true)
364
+ expect(util.shouldClipChild?.(geoShape!)).toBe(true)
365
+ expect(editor.getShapeClipPath(ids.text1)).toBeDefined()
366
+ expect(editor.getShapeClipPath(ids.geo1)).toBeDefined()
367
+
368
+ // Test clipping toggle
369
+ isClippingEnabled$.set(false)
370
+ expect(util.shouldClipChild?.(textShape!)).toBe(false)
371
+ expect(util.shouldClipChild?.(geoShape!)).toBe(false)
372
+ expect(editor.getShapeClipPath(ids.text1)).toBeUndefined()
373
+ expect(editor.getShapeClipPath(ids.geo1)).toBeUndefined()
374
+ })
375
+
376
+ it('should handle multiple circle clip shapes independently', () => {
377
+ // Create two circle clip shapes
378
+ editor.createShape<CircleClipShape>({
379
+ id: ids.circleClip1,
380
+ type: 'circle-clip',
381
+ x: 100,
382
+ y: 100,
383
+ props: {
384
+ w: 200,
385
+ h: 200,
386
+ },
387
+ })
388
+
389
+ editor.createShape<CircleClipShape>({
390
+ id: ids.circleClip2,
391
+ type: 'circle-clip',
392
+ x: 400,
393
+ y: 100,
394
+ props: {
395
+ w: 150,
396
+ h: 150,
397
+ },
398
+ })
399
+
400
+ // Add children to both
401
+ editor.createShape<TLTextShape>({
402
+ id: ids.text1,
403
+ type: 'text',
404
+ x: 0,
405
+ y: 0,
406
+ parentId: ids.circleClip1,
407
+ props: {
408
+ richText: toRichText('First clip'),
409
+ },
410
+ })
411
+
412
+ editor.createShape<TLTextShape>({
413
+ id: ids.geo1,
414
+ type: 'text',
415
+ x: 0,
416
+ y: 0,
417
+ parentId: ids.circleClip2,
418
+ props: {
419
+ richText: toRichText('Second clip'),
420
+ },
421
+ })
422
+
423
+ const util = editor.getShapeUtil<CircleClipShape>('circle-clip')
424
+ const text1 = editor.getShape<TLTextShape>(ids.text1)
425
+ const text2 = editor.getShape<TLTextShape>(ids.geo1)
426
+
427
+ // Both should be clipped when enabled
428
+ expect(util.shouldClipChild?.(text1!)).toBe(true)
429
+ expect(util.shouldClipChild?.(text2!)).toBe(true)
430
+
431
+ // Both should not be clipped when disabled
432
+ isClippingEnabled$.set(false)
433
+ expect(util.shouldClipChild?.(text1!)).toBe(false)
434
+ expect(util.shouldClipChild?.(text2!)).toBe(false)
435
+ })
436
+ })
@@ -1680,3 +1680,18 @@ it('drops into the top-most frame, if there is one', () => {
1680
1680
 
1681
1681
  expect(editor.getShape(rect)?.parentId).toBe(editor.getCurrentPageId())
1682
1682
  })
1683
+
1684
+ it('does not get drop children of nested frame if they are occluded from the outer frame', () => {
1685
+ const frame1Id = dragCreateFrame({ down: [100, 100], move: [300, 300], up: [300, 300] })
1686
+ const frame2Id = dragCreateFrame({ down: [150, 150], move: [290, 290], up: [290, 290] })
1687
+
1688
+ const rect1 = createRect({ pos: [280, 160], size: [10, 30] })
1689
+
1690
+ expect(editor.getShape(rect1)?.parentId).toBe(frame2Id)
1691
+ expect(editor.getShape(frame2Id)?.parentId).toBe(frame1Id)
1692
+
1693
+ editor.select(frame2Id)
1694
+ editor.translateSelection(30, 0)
1695
+
1696
+ expect(editor.getShape(rect1)?.parentId).toBe(frame2Id)
1697
+ })