@keboola/design 11.3.0 → 11.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/AGENTS.md +208 -1
  2. package/CHANGELOG.md +528 -0
  3. package/COMPONENT-API.md +68 -46
  4. package/dist/{AIKit-DaFT7p92.js → AIKit-CNX6aiKm.js} +338 -113
  5. package/dist/AIKit-CNX6aiKm.js.map +1 -0
  6. package/dist/{Alert-CseY3yAj.js → Alert-Ba5b2YPq.js} +3 -3
  7. package/dist/{Alert-CseY3yAj.js.map → Alert-Ba5b2YPq.js.map} +1 -1
  8. package/dist/{AlertModal-DQAGpsEy.js → AlertModal-BGJdaRRj.js} +3 -3
  9. package/dist/{AlertModal-DQAGpsEy.js.map → AlertModal-BGJdaRRj.js.map} +1 -1
  10. package/dist/{AppShell-BUoxM3fv.js → AppShell-DRgJxpXW.js} +2 -2
  11. package/dist/{AppShell-BUoxM3fv.js.map → AppShell-DRgJxpXW.js.map} +1 -1
  12. package/dist/{Badge-Ch26CXoP.js → Badge-D0pXdekt.js} +2 -2
  13. package/dist/Badge-D0pXdekt.js.map +1 -0
  14. package/dist/{BrandEditor-BnTLMRMp.js → BrandEditor-CSANsxUz.js} +8 -8
  15. package/dist/{BrandEditor-BnTLMRMp.js.map → BrandEditor-CSANsxUz.js.map} +1 -1
  16. package/dist/{BrandLogo-V2pRUAeO.js → BrandLogo-CoKBk4MI.js} +5 -5
  17. package/dist/BrandLogo-CoKBk4MI.js.map +1 -0
  18. package/dist/{Breadcrumb-CQTPn_KB.js → Breadcrumb-g_xn_-Nf.js} +3 -3
  19. package/dist/{Breadcrumb-CQTPn_KB.js.map → Breadcrumb-g_xn_-Nf.js.map} +1 -1
  20. package/dist/{Button-CPsEQuTN.js → Button-DsRrU5F1.js} +6 -5
  21. package/dist/Button-DsRrU5F1.js.map +1 -0
  22. package/dist/{Card-Cd5HSBmv.js → Card-Dyo7055Q.js} +2 -2
  23. package/dist/{Card-Cd5HSBmv.js.map → Card-Dyo7055Q.js.map} +1 -1
  24. package/dist/{Checkbox-5VNCcV7E.js → Checkbox-CUIn2jap.js} +3 -3
  25. package/dist/{Checkbox-5VNCcV7E.js.map → Checkbox-CUIn2jap.js.map} +1 -1
  26. package/dist/{Clipboard-B7rckqV_.js → Clipboard-BTW6C-b_.js} +2 -2
  27. package/dist/{Clipboard-B7rckqV_.js.map → Clipboard-BTW6C-b_.js.map} +1 -1
  28. package/dist/{ClipboardButton-CQ6Aewpg.js → ClipboardButton-CwZJdRaz.js} +3 -3
  29. package/dist/{ClipboardButton-CQ6Aewpg.js.map → ClipboardButton-CwZJdRaz.js.map} +1 -1
  30. package/dist/{CodeEditor-CUse8znx.js → CodeEditor-C9zy7PxK.js} +16 -27
  31. package/dist/CodeEditor-C9zy7PxK.js.map +1 -0
  32. package/dist/{ColorInput-Q1D9Vcc_.js → ColorInput-BZV-u_94.js} +3 -3
  33. package/dist/{ColorInput-Q1D9Vcc_.js.map → ColorInput-BZV-u_94.js.map} +1 -1
  34. package/dist/{ColorModeSwitch-B5PXCVqB.js → ColorModeSwitch-CsWLiTBX.js} +3 -3
  35. package/dist/{ColorModeSwitch-B5PXCVqB.js.map → ColorModeSwitch-CsWLiTBX.js.map} +1 -1
  36. package/dist/{Combobox-CyoRA7oY.js → Combobox-BfIfaVMA.js} +285 -109
  37. package/dist/Combobox-BfIfaVMA.js.map +1 -0
  38. package/dist/{Command-Ds68v2rX.js → Command-Bfa1kpbt.js} +6 -6
  39. package/dist/{Command-Ds68v2rX.js.map → Command-Bfa1kpbt.js.map} +1 -1
  40. package/dist/{CopyField-DI66uY_g.js → CopyField-DpT0Ngz1.js} +2 -2
  41. package/dist/{CopyField-DI66uY_g.js.map → CopyField-DpT0Ngz1.js.map} +1 -1
  42. package/dist/{DateInput-BPj2pzsn.js → DateInput-DiEd5uCL.js} +4 -4
  43. package/dist/{DateInput-BPj2pzsn.js.map → DateInput-DiEd5uCL.js.map} +1 -1
  44. package/dist/{DatePicker-DyEGuW9m.js → DatePicker-nA8YdE84.js} +2 -2
  45. package/dist/{DatePicker-DyEGuW9m.js.map → DatePicker-nA8YdE84.js.map} +1 -1
  46. package/dist/{DateRangeInput-u9DxNbHW.js → DateRangeInput-DPfVYchl.js} +4 -4
  47. package/dist/{DateRangeInput-u9DxNbHW.js.map → DateRangeInput-DPfVYchl.js.map} +1 -1
  48. package/dist/{DateRangePicker-CYYhX6S7.js → DateRangePicker-DjSUFjh4.js} +5 -5
  49. package/dist/{DateRangePicker-CYYhX6S7.js.map → DateRangePicker-DjSUFjh4.js.map} +1 -1
  50. package/dist/{DropdownMenu-DpMEcyCq.js → DropdownMenu-CASr-jgl.js} +2 -2
  51. package/dist/{DropdownMenu-DpMEcyCq.js.map → DropdownMenu-CASr-jgl.js.map} +1 -1
  52. package/dist/{FieldItem-qQKGU9lF.js → FieldItem-B7XMmZSz.js} +3 -3
  53. package/dist/{FieldItem-qQKGU9lF.js.map → FieldItem-B7XMmZSz.js.map} +1 -1
  54. package/dist/{FileUpload-C336MVnU.js → FileUpload-ABThVZyg.js} +2 -2
  55. package/dist/{FileUpload-C336MVnU.js.map → FileUpload-ABThVZyg.js.map} +1 -1
  56. package/dist/{Form-CTygENDc.js → Form-CsttTHn4.js} +8 -8
  57. package/dist/Form-CsttTHn4.js.map +1 -0
  58. package/dist/{FormGroup-BnwP0jNu.js → FormGroup-DHvTFdno.js} +3 -3
  59. package/dist/{FormGroup-BnwP0jNu.js.map → FormGroup-DHvTFdno.js.map} +1 -1
  60. package/dist/{FullScreenModal-j-M4aPFd.js → FullScreenModal-BvPuYibe.js} +3 -3
  61. package/dist/{FullScreenModal-j-M4aPFd.js.map → FullScreenModal-BvPuYibe.js.map} +1 -1
  62. package/dist/{HoverCard-yQq-O49c.js → HoverCard-B9ZMJxjn.js} +2 -2
  63. package/dist/{HoverCard-yQq-O49c.js.map → HoverCard-B9ZMJxjn.js.map} +1 -1
  64. package/dist/{InputGroup-D7zlmqPB.js → InputGroup-DcZRlVnN.js} +24 -16
  65. package/dist/InputGroup-DcZRlVnN.js.map +1 -0
  66. package/dist/{Kbd-D6LpR7G2.js → Kbd-Cvvt91Q8.js} +14 -4
  67. package/dist/Kbd-Cvvt91Q8.js.map +1 -0
  68. package/dist/MarkedText-DNGEUVWc.js +67 -0
  69. package/dist/MarkedText-DNGEUVWc.js.map +1 -0
  70. package/dist/{MermaidDiagram-JlvuGcY-.js → MermaidDiagram-CXG5tC6J.js} +19 -18
  71. package/dist/MermaidDiagram-CXG5tC6J.js.map +1 -0
  72. package/dist/{Modal-o4ATIexE.js → Modal-Coj-H02o.js} +16 -4
  73. package/dist/Modal-Coj-H02o.js.map +1 -0
  74. package/dist/{MultiActionButtonGroup-GTeFk-L8.js → MultiActionButtonGroup-DokHbPgM.js} +3 -3
  75. package/dist/{MultiActionButtonGroup-GTeFk-L8.js.map → MultiActionButtonGroup-DokHbPgM.js.map} +1 -1
  76. package/dist/{Navbar-CAQs4BZz.js → Navbar-Liy1TPI7.js} +7 -7
  77. package/dist/{Navbar-CAQs4BZz.js.map → Navbar-Liy1TPI7.js.map} +1 -1
  78. package/dist/PageHeader-Q3IpCnQd.js.map +1 -1
  79. package/dist/{PageToolbar-bG2YtSa3.js → PageToolbar-DExqEYGl.js} +4 -4
  80. package/dist/{PageToolbar-bG2YtSa3.js.map → PageToolbar-DExqEYGl.js.map} +1 -1
  81. package/dist/{Pagination-BB3X7ooY.js → Pagination-C_b7Xhey.js} +2 -2
  82. package/dist/{Pagination-BB3X7ooY.js.map → Pagination-C_b7Xhey.js.map} +1 -1
  83. package/dist/{Popconfirm-C2Ky5hJO.js → Popconfirm-5pBzT_HQ.js} +12 -13
  84. package/dist/{Popconfirm-C2Ky5hJO.js.map → Popconfirm-5pBzT_HQ.js.map} +1 -1
  85. package/dist/{Popover-DsbI2HKu.js → Popover-BEbQrC_P.js} +22 -19
  86. package/dist/Popover-BEbQrC_P.js.map +1 -0
  87. package/dist/{RadioGroup-BMxuZEbT.js → RadioGroup-DXPbSj8P.js} +3 -3
  88. package/dist/{RadioGroup-BMxuZEbT.js.map → RadioGroup-DXPbSj8P.js.map} +1 -1
  89. package/dist/{Search-DFeECt5D.js → Search-5uQAiNIP.js} +2 -2
  90. package/dist/{Search-DFeECt5D.js.map → Search-5uQAiNIP.js.map} +1 -1
  91. package/dist/{SearchInput-CCpdUego.js → SearchInput-D4UgAXJK.js} +3 -3
  92. package/dist/{SearchInput-CCpdUego.js.map → SearchInput-D4UgAXJK.js.map} +1 -1
  93. package/dist/{SectionNav-Bw_MVL0_.js → SectionNav-OkKCjSA-.js} +5 -5
  94. package/dist/{SectionNav-Bw_MVL0_.js.map → SectionNav-OkKCjSA-.js.map} +1 -1
  95. package/dist/{Select-D6IZVvwL.js → Select-DV3Hak_l.js} +31 -21
  96. package/dist/Select-DV3Hak_l.js.map +1 -0
  97. package/dist/{Sheet-BIzs2K2H.js → Sheet-Chf0kX1E.js} +3 -3
  98. package/dist/{Sheet-BIzs2K2H.js.map → Sheet-Chf0kX1E.js.map} +1 -1
  99. package/dist/{Sidebar-r1TW4g_b.js → Sidebar-D3cY1kha.js} +48 -22
  100. package/dist/Sidebar-D3cY1kha.js.map +1 -0
  101. package/dist/{Slider-CjxyfEty.js → Slider-CRf-bB4y.js} +3 -3
  102. package/dist/{Slider-CjxyfEty.js.map → Slider-CRf-bB4y.js.map} +1 -1
  103. package/dist/{Switch-BueTV03C.js → Switch-B0WdGSjz.js} +8 -8
  104. package/dist/{Switch-BueTV03C.js.map → Switch-B0WdGSjz.js.map} +1 -1
  105. package/dist/{Tabs-l72-WuhK.js → Tabs-eB974xJf.js} +2 -2
  106. package/dist/{Tabs-l72-WuhK.js.map → Tabs-eB974xJf.js.map} +1 -1
  107. package/dist/{TextInput-DhdRk8qm.js → TextInput-DH5SkbsE.js} +3 -3
  108. package/dist/{TextInput-DhdRk8qm.js.map → TextInput-DH5SkbsE.js.map} +1 -1
  109. package/dist/{ThemeProvider-DZfGdf1v.js → ThemeProvider-2RMdVGxO.js} +95 -15
  110. package/dist/ThemeProvider-2RMdVGxO.js.map +1 -0
  111. package/dist/{TimePicker-B6d2rAbX.js → TimePicker-BGeqc-Ed.js} +2 -2
  112. package/dist/{TimePicker-B6d2rAbX.js.map → TimePicker-BGeqc-Ed.js.map} +1 -1
  113. package/dist/{Timeline-6btyJl4D.js → Timeline-BG5Xogxh.js} +11 -11
  114. package/dist/{Timeline-6btyJl4D.js.map → Timeline-BG5Xogxh.js.map} +1 -1
  115. package/dist/{Toast-CxXUulYN.js → Toast-Dr6Uw7ao.js} +2 -2
  116. package/dist/{Toast-CxXUulYN.js.map → Toast-Dr6Uw7ao.js.map} +1 -1
  117. package/dist/{Workbench-BzE-Vvd9.js → Workbench-UR2FBC7I.js} +3 -1
  118. package/dist/Workbench-UR2FBC7I.js.map +1 -0
  119. package/dist/{ZoomableSvg-CNIzRHKH.js → ZoomableSvg-pooPb1Cu.js} +4 -4
  120. package/dist/{ZoomableSvg-CNIzRHKH.js.map → ZoomableSvg-pooPb1Cu.js.map} +1 -1
  121. package/dist/{context-BrNtMc1H.js → context-D7X470dy.js} +4 -3
  122. package/dist/context-D7X470dy.js.map +1 -0
  123. package/dist/{defaults-CTfvj2KB.js → defaults-i_xG2F_1.js} +2 -4
  124. package/dist/defaults-i_xG2F_1.js.map +1 -0
  125. package/dist/editors.js +5 -5
  126. package/dist/findLiteralMatches-BGBw8Vbl.js +62 -0
  127. package/dist/findLiteralMatches-BGBw8Vbl.js.map +1 -0
  128. package/dist/flint.js +134 -8
  129. package/dist/flint.js.map +1 -1
  130. package/dist/index.d.ts +1118 -566
  131. package/dist/index.js +64 -62
  132. package/dist/internal/blocks/AIKit.js +2 -2
  133. package/dist/internal/blocks/AppShell.js +1 -1
  134. package/dist/internal/blocks/AuthLayout.js +118 -0
  135. package/dist/internal/blocks/AuthLayout.js.map +1 -0
  136. package/dist/internal/blocks/BrandEditor.js +1 -1
  137. package/dist/internal/blocks/ChartPanel.js +1 -1
  138. package/dist/internal/blocks/ClipboardButton.js +1 -1
  139. package/dist/internal/blocks/ColorInput.js +1 -1
  140. package/dist/internal/blocks/ColorModeSwitch.js +1 -1
  141. package/dist/internal/blocks/Command.js +1 -1
  142. package/dist/internal/blocks/CopyField.js +1 -1
  143. package/dist/internal/blocks/DateInput.js +1 -1
  144. package/dist/internal/blocks/DateRangeInput.js +1 -1
  145. package/dist/internal/blocks/EmptyConfigurationActionCard.js +1 -1
  146. package/dist/internal/blocks/ErrorState.js +1 -1
  147. package/dist/internal/blocks/ExportMenu.js +5 -5
  148. package/dist/internal/blocks/ExportMenu.js.map +1 -1
  149. package/dist/internal/blocks/Form/connectors/MultiComboboxField.js +1 -1
  150. package/dist/internal/blocks/Form.js +1 -1
  151. package/dist/internal/blocks/FormGroup.js +1 -1
  152. package/dist/internal/blocks/MultiActionButtonGroup.js +1 -1
  153. package/dist/internal/blocks/MultiCombobox.js +116 -54
  154. package/dist/internal/blocks/MultiCombobox.js.map +1 -1
  155. package/dist/internal/blocks/NumberInput.js +1 -1
  156. package/dist/internal/blocks/PageToolbar.js +1 -1
  157. package/dist/internal/blocks/PasswordInput.js +2 -2
  158. package/dist/internal/blocks/SearchInput.js +1 -1
  159. package/dist/internal/blocks/SearchInputWithNavigation.js +3 -3
  160. package/dist/internal/blocks/SectionNav.js +1 -1
  161. package/dist/internal/blocks/SplitButton.js +2 -2
  162. package/dist/internal/blocks/StatCard.js +1 -1
  163. package/dist/internal/blocks/TextInput.js +1 -1
  164. package/dist/internal/blocks/Textarea.js +1 -1
  165. package/dist/internal/blocks/TimeInput.js +3 -3
  166. package/dist/internal/components/Alert.js +1 -1
  167. package/dist/internal/components/AlertModal.js +1 -1
  168. package/dist/internal/components/Avatar.js +1 -1
  169. package/dist/internal/components/Badge.js +1 -1
  170. package/dist/internal/components/BrandLogo.js +1 -1
  171. package/dist/internal/components/Breadcrumb.js +1 -1
  172. package/dist/internal/components/Button.js +1 -1
  173. package/dist/internal/components/Card.js +1 -1
  174. package/dist/internal/components/Checkbox.js +1 -1
  175. package/dist/internal/components/Clipboard.js +1 -1
  176. package/dist/internal/components/CollapsiblePanel.js +1 -1
  177. package/dist/internal/components/Combobox.js +2 -2
  178. package/dist/internal/components/DatePicker.js +1 -1
  179. package/dist/internal/components/DateRangePicker.js +1 -1
  180. package/dist/internal/components/DropdownMenu.js +1 -1
  181. package/dist/internal/components/FileUpload.js +1 -1
  182. package/dist/internal/components/FullScreenModal.js +1 -1
  183. package/dist/internal/components/HoverCard.js +1 -1
  184. package/dist/internal/components/InputGroup.js +1 -1
  185. package/dist/internal/components/Kbd.js +1 -1
  186. package/dist/internal/components/MarkedText.js +1 -1
  187. package/dist/internal/components/Modal.js +1 -1
  188. package/dist/internal/components/Navbar.js +1 -1
  189. package/dist/internal/components/Pagination.js +1 -1
  190. package/dist/internal/components/Popconfirm.js +1 -1
  191. package/dist/internal/components/Popover.js +1 -1
  192. package/dist/internal/components/RadioGroup.js +1 -1
  193. package/dist/internal/components/Search.js +1 -1
  194. package/dist/internal/components/Select.js +1 -1
  195. package/dist/internal/components/Sheet.js +1 -1
  196. package/dist/internal/components/Sidebar.js +1 -1
  197. package/dist/internal/components/Slider.js +1 -1
  198. package/dist/internal/components/Switch.js +1 -1
  199. package/dist/internal/components/Tabs.js +1 -1
  200. package/dist/internal/components/ThemeProvider.js +4 -4
  201. package/dist/internal/components/TimePicker.js +1 -1
  202. package/dist/internal/components/Timeline.js +1 -1
  203. package/dist/internal/components/Toast.js +2 -2
  204. package/dist/internal/components/Toggle.js +3 -3
  205. package/dist/internal/components/Toggle.js.map +1 -1
  206. package/dist/internal/components/Tooltip.js +1 -1
  207. package/dist/internal/components/Truncated.js +1 -1
  208. package/dist/internal/components/Workbench.js +1 -1
  209. package/dist/internal/components/ZoomableSvg.js +1 -1
  210. package/dist/internal/utils.js +3 -3
  211. package/dist/markdown-editor.js +32 -26
  212. package/dist/markdown-editor.js.map +1 -1
  213. package/dist/mermaid.js +1 -1
  214. package/dist/rjsf.js +24 -25
  215. package/dist/rjsf.js.map +1 -1
  216. package/dist/styles.css +1 -1
  217. package/dist/testing.js +4 -4
  218. package/dist/testing.js.map +1 -1
  219. package/dist/{useColorSchemeState-BJEk6esG.js → themeScopeContext-D7fSyb06.js} +17 -4
  220. package/dist/themeScopeContext-D7fSyb06.js.map +1 -0
  221. package/dist/{themeTokens-7T1AFLhq.js → themeTokens-qP1-55O7.js} +38 -11
  222. package/dist/themeTokens-qP1-55O7.js.map +1 -0
  223. package/dist/{toast-BweSoKn3.js → toast-DIC0iNRf.js} +2 -2
  224. package/dist/{toast-BweSoKn3.js.map → toast-DIC0iNRf.js.map} +1 -1
  225. package/dist/{utils-B-g7d_Jb.js → utils-DSvrhU8E.js} +7 -3
  226. package/dist/{utils-B-g7d_Jb.js.map → utils-DSvrhU8E.js.map} +1 -1
  227. package/dist/vega.d.ts +1 -1
  228. package/dist/vega.js +3 -3
  229. package/dist/vega.js.map +1 -1
  230. package/dist/{vegaTheme-FBXgAdHK.js → vegaTheme-CSvFEMBl.js} +6 -6
  231. package/dist/vegaTheme-CSvFEMBl.js.map +1 -0
  232. package/package.json +12 -10
  233. package/src/styles.css +3 -0
  234. package/dist/AIKit-DaFT7p92.js.map +0 -1
  235. package/dist/Badge-Ch26CXoP.js.map +0 -1
  236. package/dist/BrandLogo-V2pRUAeO.js.map +0 -1
  237. package/dist/Button-CPsEQuTN.js.map +0 -1
  238. package/dist/CodeEditor-CUse8znx.js.map +0 -1
  239. package/dist/Combobox-CyoRA7oY.js.map +0 -1
  240. package/dist/Form-CTygENDc.js.map +0 -1
  241. package/dist/InputGroup-D7zlmqPB.js.map +0 -1
  242. package/dist/Kbd-D6LpR7G2.js.map +0 -1
  243. package/dist/MarkedText-Ckbft5mw.js +0 -44
  244. package/dist/MarkedText-Ckbft5mw.js.map +0 -1
  245. package/dist/MermaidDiagram-JlvuGcY-.js.map +0 -1
  246. package/dist/Modal-o4ATIexE.js.map +0 -1
  247. package/dist/Popover-DsbI2HKu.js.map +0 -1
  248. package/dist/Select-D6IZVvwL.js.map +0 -1
  249. package/dist/Sidebar-r1TW4g_b.js.map +0 -1
  250. package/dist/ThemeProvider-DZfGdf1v.js.map +0 -1
  251. package/dist/Workbench-BzE-Vvd9.js.map +0 -1
  252. package/dist/context-BrNtMc1H.js.map +0 -1
  253. package/dist/defaults-CTfvj2KB.js.map +0 -1
  254. package/dist/escapeStringRegexp-8MxP2knR.js +0 -6
  255. package/dist/escapeStringRegexp-8MxP2knR.js.map +0 -1
  256. package/dist/themeTokens-7T1AFLhq.js.map +0 -1
  257. package/dist/useColorSchemeState-BJEk6esG.js.map +0 -1
  258. package/dist/vegaTheme-FBXgAdHK.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,533 @@
1
1
  # @keboola/design
2
2
 
3
+ ## 11.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - `Markdown` draws a fenced `flint` block as a chart, the way it already draws `mermaid` as a diagram. `flint-chart` is admitted as an alias. `DIAGRAM_LANGUAGES` is exported for callers that stream an answer in and have to leave a fence that is still open as plain code. A spec is refused before anything compiles it if it carries a key that would fetch or evaluate — `url`, `expr`, `signal`, `signals`, `params`, or any `*Expr` property — and the reader is shown the source instead of a chart. Where flint keys a map by column name — `data.values` rows, `semantic_types`, `field_display_names` — the key is the column's, not the spec's, so a chart of landing-page URLs still draws.
8
+
9
+ - `clampPercentage` and `formatPercentage` are exported from `AIKit`, so a usage percentage is clamped and formatted in one place. `clampPercentage` normalizes non-finite input to 0, which the app copies it replaces did not — a `NaN` percentage no longer reaches a `Progress` value, whose indicator is unclamped.
10
+
11
+ - `AuthLayout` floors the form column in the split geometry, and takes `asideCollapse`; `BrandLogo` takes `label`
12
+ - The split's form track is now `minmax(<column + gutters>, 1fr)` instead of `1fr`, so between `lg` and 1536px (`width="form"`) or 1824px (`width="wide"`) it stops handing the column less than `width` asks for — a two-field row there measured ~180px per field.
13
+ - The form column no longer carries `min-h-svh` of its own in the split geometry: the grid root has it and the rows stretch, so `asideCollapse="stack"` no longer reserves a whole empty screen under a short card.
14
+ - `asideCollapse="stack"` keeps the `aside` below `lg`, in its `asideSide` order, for a page whose pitch has to survive a phone. The default stays `hide`.
15
+ - `BrandLogo label` names the logo for a screen reader instead of the brand's registry `name` — a link around the mark otherwise announces the picker's disambiguating suffix (`Keboola (V2)`).
16
+
17
+ - `Combobox` and `MultiCombobox` are now clearable by default — `allowClear` defaults to `true`.
18
+
19
+ Consumer-visible behaviour change. The clear button only mounts once there is something to clear
20
+ (single: a selection; multi: at least one chip), so an empty control renders exactly as before and
21
+ individual chips stay removable regardless. 16 existing call sites across `kbc-ui`,
22
+ `developer-portal` and `theme-generator` gain the affordance; the 10 that already pass `allowClear`
23
+ are unaffected, as are the 4 adder-pattern comboboxes pinned to `value={null}` (nothing is ever
24
+ selected, so the button never mounts).
25
+ - Pass `allowClear={false}` where clearing must not be one click away — a required field, or a
26
+ control whose empty state the surrounding form cannot represent.
27
+ - `RjsfSchemaEditor`'s `SelectWidget` and `DynamicSelectWidget` now both derive it from the schema
28
+ (`allowClear={!required}`), so a schema-required field stays non-clearable. `DynamicSelectWidget`
29
+ passed no `allowClear` before, so its non-required fields gain the button as intended.
30
+ - `MultiComboboxField` deliberately does **not** derive it: a required multi field's chips are
31
+ individually removable, so withholding clear-all prevents nothing.
32
+ - Radix `Select` still has no clear affordance; model "no selection" as an explicit item there.
33
+
34
+ `allowSelectAll` is unchanged (still `false`) — see UT-5422.
35
+
36
+ - `Combobox` and `MultiCombobox` take `inputValueSanitizer` and `onQueryChange`
37
+ - `inputValueSanitizer` rewrites what the user types before the control adopts it, so an identifier field (a bucket, table or column name) can keep the query to its own character set. It runs on typing only, never on the selection's label.
38
+ - `onQueryChange` reports the query the list filters by, sanitizer already applied — for an owner driving a server-side search, an option order, or the name a create form opens with. It goes empty once the input shows the selection's label, and reports from the mount render on, so a search driven by it needs no first fetch of its own.
39
+ - Neither hands the text over: the control keeps owning it, so there is still no controlled `inputValue`.
40
+
41
+ - `Combobox` takes `itemToSelectedLabel` — how the selection renders once the popup closes.
42
+
43
+ Additive. Left unset the closed control shows `itemToStringLabel`'s text, exactly as before.
44
+ - The node renders ahead of the input and the input's own text turns transparent behind it, so the
45
+ selection reads once. Typing dissolves it and hands the row back to the query.
46
+ - The input keeps the string label as its **value**, so the control still reports its selection to a
47
+ screen reader. That is the reason to reach for this over drawing the selection into `prefix` with
48
+ `value` pinned to `null` — same picture, and the input has no accessible value.
49
+ - The three label accessors stay independent: `itemToLabel` styles the open list's rows (and takes
50
+ the query, for `MarkedText`), `itemToStringLabel` is the input's text and the search subject.
51
+
52
+ `Combobox` also now tracks an uncontrolled selection (`defaultValue`, or no `value` at all) the same
53
+ way it tracks a controlled one. Beyond feeding the new accessor, that fixes the list collapsing to
54
+ the selected item after an uncontrolled pick: as with a controlled combobox, reopening now browses
55
+ the whole list instead of filtering by the selection's own label.
56
+
57
+ Radix `Select` needs nothing here — its trigger already mirrors the item's `label` node, and `icon`
58
+ / `hint` sit outside that mirror for a row that carries more than the trigger should.
59
+
60
+ - Add `AuthLayout` — the signed-out page frame (sign-in, sign-up, password reset, MFA, project picker).
61
+
62
+ Compound: `AuthLayout.Header` (the active brand's mark beside the `productName` it is given, or your own lockup as children; takes `actions`), `AuthLayout.Content`, `AuthLayout.Footer`. `width` picks the column (`form` 28rem | `wide` 34rem); passing `aside` switches the page to the split geometry, with `asideSide` choosing the panel's side.
63
+
64
+ - `Select` requires an accessible name — `aria-label`, `aria-labelledby`, or an `id` — and forwards
65
+ `aria-labelledby` to the trigger, which it previously dropped.
66
+
67
+ ```tsx
68
+ // A visible label — point it at the control.
69
+ <Label htmlFor="stage">Stage</Label>
70
+ <Select id="stage" items={STAGES} value={stage} onValueChange={setStage} />
71
+
72
+ // A bare control (toolbar filter, repeated row) — name it directly.
73
+ <Select aria-label="Rows per page" items={ROWS_PER_PAGE} value={rows} onValueChange={setRows} />
74
+ ```
75
+
76
+ The trigger is a `role="combobox"` button, which is not a name-from-content role: neither the
77
+ selected item's label nor the `placeholder` names it, so a `Select` with none of the three reached
78
+ a screen reader as an unnamed control.
79
+ - The requirement sits on `Select.Trigger` as well, so the compound route is covered too. Passing
80
+ more than one route is still allowed — an `id` for the `<Label htmlFor>` plus an `aria-label`
81
+ that overrides it is a legitimate pairing.
82
+ - An `id` alone still names nothing: it counts only when a `<label for>` points at it, and no type
83
+ can check that pair.
84
+ - `field.SelectField` needs none of the three — `FieldItem` owns the label and hands the control
85
+ its `id`. It no longer accepts `aria-label` / `aria-labelledby`, which nothing passed: a name
86
+ from the call site could only disagree with the label the field already renders.
87
+
88
+ - `MultiCombobox` takes `canCreate`, the gate `Combobox` already had. It runs on the typed query once
89
+ that query matches no item and nothing already selected, and returning `false` hides the create row
90
+ so the empty state shows instead of an "Add …" row the caller would only reject afterwards.
91
+
92
+ The case it is for is a free-form value with a format the caller can state — a column name a
93
+ JSON-Schema `pattern` constrains, an email address. Without it a multi creatable control had no way
94
+ to say no before the value was in the selection.
95
+
96
+ The Select All row no longer leaks into the chips. Base UI holds it in `value` like any other item,
97
+ so a complete selection grew a third chip and handed the row's own entry to the caller's
98
+ `itemToChipLabel` / `itemToLabel` as if it were a value — a control that projects its items (an id
99
+ to a label) then rendered that object as a child and React threw. The chip area now filters the
100
+ control's internal entries out before anything reaches a caller callback. The row is also gone
101
+ entirely when there is nothing to select all of, where `0 === 0` had read as "everything is
102
+ selected".
103
+
104
+ `itemToLabel` defaults to `itemToStringLabel` rather than to the built-in label reader, so a control
105
+ whose items are ids with the label beside them states the projection once instead of passing the
106
+ same function twice. Passing both still works, and is what a row richer than the input's own text
107
+ wants.
108
+
109
+ - `MultiCombobox` takes `renderChips` — a slot that renders the chip area from the selected values, for the shape no per-value accessor can express: one node standing for the whole selection, or a chip count that is not the value count. `Combobox.Chip` is exposed as the removable-chip building block a custom render composes from.
110
+
111
+ A chip now also yields Enter and Space to interactive content inside it, so `itemToChipLabel` can return a link or an action and have it activate from the keyboard.
112
+
113
+ - `MultiCombobox` accepts `isItemEqualToValue` and forwards it to Base UI, as `Combobox` already did.
114
+ Without it Base UI matches the selection against `items` by reference, so a control fed object items
115
+ lost its selection the moment the store handed back structurally equal items with fresh references —
116
+ the workaround being a caller-side `string[]` of ids plus an id→label projection.
117
+
118
+ Both controls now build the comparer from one shared factory, with two consequences. An injected
119
+ "Select All" or create row equals only a sentinel of the same marker, so a caller item whose `value`
120
+ is the literal `__select_all__` is a plain item rather than the Select All row. And the comparer
121
+ keeps its identity between renders, where an unmemoized one re-rendered every option row on each
122
+ render of the control.
123
+
124
+ - `MultiCombobox` with `allowCreate` splits a created entry into one value per comma, and takes
125
+ `createSeparator` to change or switch off the split.
126
+
127
+ Consumer-visible behaviour change, on by default. Pasting `id1, id2, id3` into a creatable multi
128
+ control added one chip literally named `id1, id2, id3`; it now adds three. The paste is the reason a
129
+ multi control is creatable at all — tags, column lists, primary keys, allowed component ids, email
130
+ lists — so the split is the default rather than a prop each site has to remember. `Combobox` is
131
+ unchanged: one entry there can only be one value.
132
+ - `createSeparator` takes a string or a `RegExp` for another convention (`';'` for an Outlook
133
+ address list, `/[\s,]+/` where a value can never contain a space), and `null` where a value can
134
+ itself contain the separator and one entry must stay one chip.
135
+ - Whitespace is deliberately not part of the default — tags and column names contain spaces, and
136
+ every value is trimmed anyway, so `id1, id2` needs nothing but the comma.
137
+ - `queryToCreateValue` is called once per value, each already trimmed, so a per-value projection or
138
+ normalisation needs no change. This is also where the legacy wrapper's `trimMultiCreatedValues`
139
+ lands.
140
+ - `canCreate` gates each value rather than the whole entry, so a pasted list keeps the values whose
141
+ format passes and drops the rest instead of being rejected wholesale. A value naming an existing
142
+ item never reaches the gate — committing selects that item rather than minting one.
143
+ - A value repeated inside one entry, or one the selection already holds, is added once. A value
144
+ naming an existing item selects that item rather than minting a twin standing for the same thing.
145
+ - The row is offered whenever the entry adds anything to the selection, and withholds itself only
146
+ when the selection already holds every value, or when a single value names an item (that item's
147
+ own row is the affordance). That is why an entry naming only existing items keeps the row: the
148
+ substring filter matches no item for a multi-value entry, so the row is the only thing that can
149
+ act on such a paste.
150
+ - `queryToCreateLabel` now receives the values committing the row will add, re-joined with the
151
+ entry's own separator, rather than the entry as typed — so the row can neither name a value it
152
+ drops nor stay silent about one it selects. For a single-value entry that string is the entry
153
+ itself, so an existing label reads exactly as before.
154
+
155
+ - Add `AnswerActions` to the AI Kit: the copy-and-vote row for a finished agent answer.
156
+ - Always rendered rather than revealed on hover, so it exists for a reader without a pointer.
157
+ - `aria-pressed` says which vote is recorded, and both buttons stay focusable — a vote can be
158
+ changed but not withdrawn.
159
+ - No copy button when `copyText` is empty: a turn that produced only tool calls has nothing to
160
+ take away.
161
+ - The recorded vote and the write stay with the caller; the two chats vote against different
162
+ endpoints.
163
+
164
+ - `useSidebar(side)` now exposes `setFill(fill: boolean)` alongside `toggleFill()`.
165
+
166
+ `toggleFill` requires the caller to know the current value, so two callers in one commit read the
167
+ same stale flag and undo each other, and a caller whose view of the layout disagrees with the flag
168
+ skips the change entirely. `setFill` states the value it wants, so it is idempotent. `toggleFill`
169
+ is unchanged and still the right call for a user-facing toggle control.
170
+
171
+ - `UsageStats` accepts `open` / `onOpenChange`, so a caller can hold the hover card open past a pointer leave — needed when the card contains a popover of its own.
172
+
173
+ ### Patch Changes
174
+
175
+ - `PlanApproval`, `CollapsibleStepHeader` and `UsageStatsRing` follow the active colour scheme and
176
+ brand. All three pinned fixed grey and white values, so the plan-approval card rendered as a light
177
+ card with near-black text on a dark page, and ignored a customer brand's own surface tokens in
178
+ either scheme. They now consume the semantic tokens throughout: the card's shell is the recessed
179
+ `background` tier, its plan body, action bar and change-request form lift off it on `card`, and each
180
+ tier paints its own foreground so no surface inherits ink from whatever rendered it. Step counts
181
+ read as `muted-foreground`, dividers as `border`, and the usage ring's track as a muted mark.
182
+
183
+ A dismissed step's icon takes its colour from the step's own variant rather than a pinned grey, so
184
+ it now matches the muted label beside it; a failed step keeps the destructive tone. The card's inner
185
+ dividers are a touch more visible in light mode, since one shared border token now draws them and
186
+ the frame alike.
187
+
188
+ - AIKit collapsibles move instead of popping: `PlanApproval`'s plan body and `AnswerSummary`'s answers ease open and closed on `grid-template-rows`, `AnswerSummary` reports a manual toggle through `onExpandToggle`, and `ToolCallStatusHeader` turns one chevron rather than swapping the up and down glyphs.
189
+
190
+ - `AnswerSummary` collapses flush against its header: the spacing before the answers lives inside the collapsible body, so the closed state no longer keeps a 16px gap under the header.
191
+
192
+ - `Combobox` / `MultiCombobox` hold `''` and `' '` as genuine values: `withBlankValues`, `namedLabel`.
193
+
194
+ "This column equals the empty string" and "equals a single space" are real filter predicates against
195
+ Storage data. Base UI keys "is something selected" off `value != null` rather than truthiness, so a
196
+ blank string already round-trips through `onValueChange` verbatim and keeps its clear affordance —
197
+ no sentinel option and no mapping on save. What was missing was the display half: a blank row and a
198
+ blank chip render as nothing, indistinguishable from an empty control.
199
+ - `withBlankValues(values)` offers both blanks ahead of a value list and collapses duplicates. A
200
+ column's distinct values can already hold one, and two `Object.is`-equal rows both report
201
+ `aria-selected` and toggle each other.
202
+ - **Rows and chips** name a blank — `[empty string]`, `[space character]`, and `[whitespace]` for
203
+ anything else whose text is all whitespace. `namedLabel` exposes the rule so an `itemToLabel` /
204
+ `itemToChipLabel` / `renderChips` override can keep it.
205
+ - **`itemToStringLabel` is deliberately not named.** It is the item's string identity, the default
206
+ search text and the input's accessible value, and Base UI counts `''` as a selection — naming it
207
+ would paint text over the placeholder wherever "nothing selected" is spelled `''`. Typing
208
+ therefore never reaches a blank row, and naming a blank selection in a closed `Combobox` is
209
+ `itemToSelectedLabel`'s job.
210
+ - `allowCreate` cannot create a blank: it trims the typed query, so fixed rows are the only way in.
211
+ - Radix `Select` is unaffected and still cannot carry a blank — it reserves `''` for its own empty
212
+ state. The split is documented in `AGENTS.md`.
213
+
214
+ - `Combobox` and `MultiCombobox` popups register in Radix's dismissable-layer stack, so Escape closes
215
+ the popup and leaves the surrounding surface alone. Base UI portals the popup out of the DOM, so a
216
+ `Modal`, `Sheet` or `Popover` around it still counted itself as topmost and took the keystroke — a
217
+ combobox in a modal form lost the whole half-filled form on the key that was meant to dismiss its
218
+ own list. The popup now sits above that surface as a layer of its own; Base UI still owns the
219
+ dismissal itself, and Escape on a closed popup reaches the surface as before.
220
+
221
+ - `Combobox`'s `itemToSelectedLabel` node now paints over the input's own content box instead of
222
+ sitting beside it as a flex sibling. Previously the two boxes shared one row, so the caret and text
223
+ selection lived roughly a badge-width past where the visible label ended.
224
+
225
+ - `Combobox` stores the text the user typed rather than the text it displays, and derives the input's
226
+ value, the search query and the rich selected label from that one piece of state.
227
+
228
+ The two meanings did not fit in one variable, so the difference was reconstructed around it: an
229
+ effect snapping the input back to the controlled value's label, a counter re-triggering that effect
230
+ when `value` stayed put, a string comparison standing in for "this is a selection, not a query", and
231
+ a ref reconstructing why the popup had closed. `typedText: string | null` says it directly, with
232
+ `null` for "nothing typed, showing the selection".
233
+ - Only Base UI's `input-change` reason writes to it. Every other reason it reports — an item press,
234
+ a clear, the label it syncs back on unmount — means "show the selection", which `null` already
235
+ says. Its sync-back is a write to ignore rather than adopt: it stringifies Base UI's own selected
236
+ value, which for a created item is the internal sentinel and not the value the owner holds.
237
+ - The state resets the moment the popup starts closing, so a control the user has walked away from
238
+ shows what its value says. Base UI's own sync-back cannot carry that: it skips the write whenever
239
+ the input's text already matches the label, which a query edited back to the label reaches.
240
+ - Whitespace alone is never adopted as a query. Base UI gates opening the popup on the trimmed
241
+ text, so a control that took it would sit closed showing text its value does not say, with no
242
+ close to discard it.
243
+ - "Showing the selection" is the input's text matching the selection's label, not merely nothing
244
+ having been typed. A query edited back to the label lands on the same screen as never having
245
+ typed: the rich `itemToSelectedLabel` node returns and the list stays browsable rather than
246
+ narrowing to the item already chosen. Base UI's own `shouldBypassFiltering` covers only a fresh
247
+ open — its `queryChangedAfterOpen` flips on the first keystroke and clears on close, never when
248
+ the text returns to the label.
249
+
250
+ One behaviour change: a `value` arriving from elsewhere while the user is mid-query no longer yanks
251
+ the text away on the keystroke. The query stands until it is committed or discarded, and the new
252
+ label shows when the popup closes.
253
+
254
+ - `Combobox` / `MultiCombobox` get a shared word-by-word `filter`: `filterByWords` and `matchByWords`.
255
+
256
+ Both controls match the typed query as one substring of the label, so `frankfurt europe` finds
257
+ nothing in a list holding `Europe (Frankfurt)`. `filter={filterByWords}` requires every word of the
258
+ query to occur somewhere in the label, in any order, ignoring case, matching by containment;
259
+ `matchByWords([a, b], query)` applies the same rule to texts the label does not show — an email
260
+ behind a name, a table id behind a display name.
261
+ - `filterByWords` searches **only** what the control displays, so a filter cannot match an id the
262
+ user cannot see. Extra fields are named explicitly through `matchByWords`.
263
+ - Comparison is a lowercased `includes`, not Base UI's `Intl.Collator` `contains`, so passing
264
+ either helper trades diacritic-insensitive matching (`Muller` finds `Müller`) for word order.
265
+ - Defaults are unchanged — both are opt-in per call site.
266
+
267
+ - `Badge.Button` shows a focus ring. The `focus-visible` block sat on `BadgeRoot`, a `div` that never
268
+ takes focus, so every clickable badge was keyboard-focusable with no visible focus. The ring is
269
+ inset, because a badge root clips its overflow.
270
+
271
+ - `MarkdownEditor`'s plugins, `RjsfSchemaEditor` and `Popconfirm` read their callback props through
272
+ `useStableCallback` instead of a ref assigned in the component body. Each of those assignments wrote
273
+ to shared memory during render, where a render React discards can still leave the ref carrying a
274
+ value that never committed. Two refs in `RjsfSchemaEditor` are gone rather than converted — one
275
+ wrapped a `useState` setter and one a `useMemo` with no dependencies, both already stable, and the
276
+ ref only hid that from the dependency list. `ImagePastePlugin` now decides whether to claim paste
277
+ and drop from a boolean derived from `onImageUpload`, so an editor that gains or loses an uploader
278
+ re-registers its Lexical commands instead of keeping the first answer forever.
279
+
280
+ - `RjsfSchemaEditor`'s enum fields have an accessible name: `SelectWidget` and `DynamicSelectWidget`
281
+ forward rjsf's `id` to their `Combobox`, which is where `FieldTemplate`'s `<Label htmlFor>` points.
282
+ They were the only built-in widgets dropping it, so a screen reader reported every enum field in a
283
+ component configuration as an unnamed combobox and clicking the visible label focused nothing. No
284
+ visual change.
285
+
286
+ Still unnamed, and not addressed here: `RadioWidget` and `CodeEditorWidget` forward the `id` to a
287
+ `div`, which no `<Label htmlFor>` can name — naming those needs `aria-labelledby` from the template
288
+ and a route into the editor's own textarea.
289
+
290
+ - Smoother sidebar motion, and a composer a host can refocus.
291
+ - The sidebar settles instead of sliding linearly: its geometry transitions run
292
+ `cubic-bezier(0.32, 0.72, 0, 1)` over 420ms, the off-canvas slide is composited, and
293
+ all of it drops out under `prefers-reduced-motion`.
294
+ - `Sidebar` takes `mountAfterEnter`: a heavy panel holds its content back until the slide
295
+ has finished, rather than mounting into the same commit and blocking every frame of it.
296
+ Ignored under reduced motion, where there is no transition end to wait for.
297
+ - `Workbench` marks its two boxes `data-workbench-slot="bar"` / `"panel"`, so a floating
298
+ surface can measure the space the dock leaves. The root's own rect is the bar alone
299
+ whatever the state, because the panel is absolutely positioned.
300
+ - `PromptInput` takes `textareaRef`, publishing its textarea so a host that keeps one
301
+ composer across layout changes can put the caret back. Published from the ref callback,
302
+ so it still points at the live element after a host layout restructure recreates it.
303
+
304
+ - `InputGroup.Addon` keeps its own content width instead of giving up space to the input. The input
305
+ was `w-full`, so as a flex item its basis was the whole row — flexbox then shrank a sibling addon in
306
+ proportion to that basis, squeezing even a short badge-plus-label selection to an ellipsis well
307
+ before the field was full. The input is now `flex-1 min-w-0` and `inline-start` / `inline-end`
308
+ addons ship `shrink-0`; behaviour is unchanged where there is no addon, and every existing addon
309
+ holds small non-shrinking content (icons, buttons) so this is a no-op for them. `Combobox`'s rich
310
+ selected-value addon drops the `max-w-[70%] shrink-0` workaround this replaces, keeping only the
311
+ `max-w-[70%]` bound needed for an arbitrarily long selection.
312
+
313
+ `InputGroup`'s padding and sizing rules now target any descendant `input` / `textarea` instead of
314
+ only a direct child, so a consumer that wraps the input in its own layout keeps its padding without
315
+ re-implementing the sizing table by hand. `MultiCombobox` drops the manual `px-4 py-1.5` this
316
+ replaces, and instead cancels the group's rule on its own nested input (`!px-0 !py-0`) since that
317
+ input shares a row with chips rather than sitting at the group's own edge.
318
+
319
+ - `Kbd variant="primary"` and `Kbd.Separator` follow the tooltip chrome they render on. `primary`
320
+ pinned `bg-white/20` + `text-white`, which is only legible on the legacy dark tooltip. Argo's tooltip
321
+ is a light popover, so every shortcut hint on it — Kai's "New chat", the SQL editor's panel toggles,
322
+ flow undo/redo, the sidebar nav — painted white keys on white and vanished, leaving the keys'
323
+ layout width behind as apparent trailing whitespace.
324
+
325
+ Both now consume the muted chrome tokens by default and invert to white only under
326
+ `[data-tooltip-content="true"]`, the attribute the legacy dark tooltip already marks itself with. The
327
+ legacy rendering is unchanged. Call sites no longer need the `tw:[&>span]:text-white/70` override
328
+ that forced the separator white — `Kbd.Separator` handles it.
329
+
330
+ - `MultiCombobox`'s `allowSelectAll` row now hides itself while a query narrows the list. It always
331
+ selected the whole item set, but stayed visible during filtering — type `prod`, see three matches,
332
+ click Select All, end up with all two hundred selected, with nothing on screen saying so. Select All
333
+ keeps meaning "everything"; the reading it can be confused with is no longer on offer. This matches
334
+ the legacy `react/common/Select` wrapper, which hid its own Select-All row while searching.
335
+
336
+ - `PageHeader.Detail`'s documented breadcrumb contract now matches how it's actually used: the
337
+ `breadcrumbs` chain ends at the entity's parent list/hierarchy and never repeats the entity's own
338
+ name as a final, non-link crumb — it's already the `title` right below. The prop doc, `AGENTS.md`
339
+ rules and the `Detail` Storybook examples (Bucket, Table, Workspace, Job, Data App, Shared Code, Add
340
+ Component, Flow) are updated to the same shape.
341
+
342
+ - A chart or diagram inside a `ThemeScope` now follows the scope's scheme, and a chart in dark mode
343
+ keeps readable labels.
344
+
345
+ `ThemeScope` re-points the colour tokens for its subtree, but a canvas that bakes colours into
346
+ markup resolved them from `<html>`: `useThemeAttributes` read the root's `data-theme`, and the
347
+ token resolvers read `getComputedStyle(document.documentElement)`. A chart in a dark-scoped panel
348
+ therefore drew a white plot with black labels on a dark surface.
349
+ - `ThemeScope` publishes its resolved scheme and a container carrying its tokens; `useThemeAttributes`
350
+ prefers both and folds the container into `cacheKey`, so a palette built before the scope answered
351
+ is not served afterwards.
352
+ - `createChannelResolver` / `createTokenResolver` (and the vega, flint and mermaid palettes built on
353
+ them) take the element to read live `--*` values off, defaulting to `<html>` as before.
354
+ - The flint palette now fills `ink.text` and `ink.structure` from the tokens. Flint's own defaults
355
+ assume a light canvas, so axis labels and gridlines were near-black in **any** dark context — not
356
+ only inside a scope.
357
+ - The root barrel grows ~70 B with the scope context; the size budget moves 344→345 KB.
358
+
359
+ - `Timeline` draws its connector line, `Navbar` its accent hairline, and the default `Switch` its keyboard focus ring. All three wrote a pseudo-element variant before another variant — `before:group-data-[variant=solid]/timeline:bg-border`, `before:data-[accent=blue]:bg-sidebar-primary`, `before:focus-visible:ring-4` — which Tailwind compiles to a rule that matches nothing, so none of them had ever rendered for any consumer. Twenty rules were dead. Nothing else changes — no markup, no props. A consumer that already renders these components gains the paint it asked for. `check:css` now fails the build on any class that places a variant after a pseudo-element one, `hover:` and `active:` excepted.
360
+
361
+ ## 11.4.0
362
+
363
+ ### Minor Changes
364
+
365
+ - The AIKit layer for Kai's redesign (DES-20, DES-170).
366
+ - `ThemeScope` (new): a subtree pins its own colour scheme regardless of the app's —
367
+ Kai's panel stays light while its minimized widget is dark. `useThemeScopeContainer`
368
+ hands portalling components the scope element, and `Popover.Content` now takes a
369
+ `container` so a portal stays inside the scope that styles it.
370
+ - `ToolCallStatusHeader` joins AIKit — the tool-call panel header kbc-ui carried,
371
+ promoted with tests and stories.
372
+ - `MessageBubble` drops its avatar; a user turn is a plain bubble. `user` is still accepted
373
+ (deprecated, ignored) so call sites keep compiling.
374
+ - `Command` takes a `variant`, so a command in a small surface (a popover) can opt out
375
+ of the full-screen palette styling the theme would pick.
376
+ - No platform token changes: Kai scopes its own sidebar accent in kbc-ui.
377
+ - The root barrel grows ~1 KB with the new exports; the size budget moves 342→344 KB.
378
+
379
+ - `CodeEditor` search matches literal text instead of a compiled pattern, and `findLiteralMatches` / `hasLiteralMatch` ship as utilities.
380
+
381
+ A search query is data, not a pattern. Compiling one meant escaping it first and living with V8's
382
+ ceiling on how large a pattern may be: a query of a few tens of kilobytes — what pasting a document
383
+ into a find box produces — threw `Regular expression too large` when the pattern was first executed.
384
+ `findMatches` now scans for the query literally, so escaping and that ceiling are both gone, and it
385
+ short-circuits before joining the document's rope when the query cannot fit in it.
386
+
387
+ `findLiteralMatches(text, query)` returns the start offsets of every case-insensitive occurrence and
388
+ `hasLiteralMatch(text, query)` answers whether there is one, stopping at the first. Both fold case
389
+ without changing the length of the string, so the offsets index the caller's own text.
390
+
391
+ Two behaviour notes for anyone matching the editor's counts elsewhere: `İ` (U+0130) now reads as a
392
+ plain `i` rather than matching only itself, and `SearchMatch` no longer carries the matched `text`,
393
+ which nothing read.
394
+
395
+ - `Markdown` accepts `remarkPlugins` and `components`.
396
+ - `remarkPlugins` runs after the built-in GFM one rather than in place of it, so tables, autolinks and task lists keep working whatever a caller adds.
397
+ - `components` is merged over the built-in element renderers by key. An override replaces that element's renderer rather than wrapping it, so a `table` override loses the design `Table` and an `a` override loses the new-tab treatment for absolute links.
398
+ - Together they are the seam an app's own inline syntax arrives through — an annotation an agent writes beside a link, read back by a renderer for that element — which is app product knowledge and does not belong in this package.
399
+ - Both are meant to be held in module constants: `Markdown` re-parses its source on every render, so a caller memoises it, and a prop object built during render defeats that.
400
+
401
+ - `MarkedText` highlights by literal scan instead of a compiled pattern, and `findLiteralMatches` takes an `isCaseSensitive` option.
402
+
403
+ The highlight query was escaped, split on spaces, and joined into one alternation regex — so a
404
+ space-free paste of a few tens of kilobytes (minified SQL, JSON, base64) exceeded V8's pattern
405
+ ceiling and threw `Regular expression too large` from a `useMemo`, taking the page down with it.
406
+ `createMarkedParts` now finds each space-separated term with `findLiteralMatches` and merges the
407
+ offsets, so no pattern is compiled at all.
408
+
409
+ One visible behaviour change: overlapping term matches now merge into one marked run — searching
410
+ `test testing` marks the whole `testing`, where the alternation used to mark only its `test` prefix
411
+ and leave `ing` plain. Adjacent matches merge the same way. Case-insensitive matching also folds
412
+ `İ` (U+0130) to a plain `i`, matching the editor search.
413
+
414
+ `escapeStringRegexp` is gone from the package's internals — nothing compiles user text as a pattern
415
+ anymore.
416
+
417
+ - `MultiCombobox`: a render-only slot for pinned chips, chip callbacks see the real item type, and a created value stops haunting the list.
418
+
419
+ `pinnedChipLabels` renders chips the caller keeps rather than the user — a set of packages the
420
+ runtime already ships, a role every member has. They sit ahead of the values, outside `maxVisible`,
421
+ carry no remove button, and never enter `value`.
422
+
423
+ `itemToChipLabel` and `itemToChipColor` now receive `Value` instead of `unknown`, so a call site
424
+ stops narrowing by hand. Existing call sites keep compiling; their `String(...)` wrappers become
425
+ redundant.
426
+
427
+ Created values were kept in a list that never shrank, so a value the user created and then removed —
428
+ or cleared — stayed in the listbox as an option for the rest of the session, and skewed
429
+ `allowSelectAll`'s "everything is selected" arithmetic. The list now tracks the selection.
430
+
431
+ - Add a `hint` slot to `Select` items, and let the top-level `Select` take `aria-label`.
432
+
433
+ `hint` renders a trailing muted note in the listbox row — "Default" beside the value the
434
+ platform falls back to. It sits outside Radix's `ItemText`, so the trigger keeps showing the
435
+ label alone once the option is picked; because Radix names an option from `ItemText` only
436
+ (`aria-labelledby`), the hint is wired into the item's `aria-describedby` instead, so a screen
437
+ reader still reads it.
438
+
439
+ `aria-label` was the one trigger a11y prop the convenience wrapper did not forward — it already
440
+ curated `aria-describedby`, `aria-invalid` and `aria-required`. Selects that stand without a
441
+ visible `Label` (a filter row, or several selects joined by "OR") need it.
442
+
443
+ - `UsageStats`: the corner alert badge now accepts a severity.
444
+
445
+ `alert` takes `boolean | 'warning' | 'error'` instead of a bare boolean. `'warning'` renders the badge
446
+ amber for an approaching-limit state that isn't at-limit yet; `'error'` (or the existing bare `true`)
447
+ keeps the red at-limit color, so existing call sites keep compiling unchanged.
448
+
449
+ ### Patch Changes
450
+
451
+ - `Button variant="outline"`: the hovered button stops dissolving into the surface behind it.
452
+
453
+ Both hover and pressed fills were tints of `transparent` (Tailwind's `bg-<token>/NN`), so hovering
454
+ replaced the variant's opaque rest fill and the button showed whatever it overlapped — an edge on a
455
+ flow canvas, the status tint inside an `Alert`. They now `color-mix` into the variant's own rest
456
+ token (`--card` under legacy chrome, `--background` under argo), which is the same colour on the
457
+ surface the variant assumes and stays opaque off it.
458
+
459
+ Argo compounded that by keeping `border-input` on hover while tinting the fill toward it, so the
460
+ button's edge contrast _dropped_ as the user reached it — measured 1.42:1 → 1.25:1 in light and
461
+ 2.28:1 → 1.62:1 in dark, against its own fill on a `--muted` surface. Its hover now steps the
462
+ border to `--muted-foreground` (4.00:1 / 5.24:1), the treatment legacy already shipped. A tint
463
+ cannot carry that signal: brand primary stays within ~1.5:1 of a light surface at any opacity that
464
+ still reads as a tint. `--primary` stays reserved for `focus-visible`, so hover and focus remain
465
+ distinguishable.
466
+
467
+ Consumers overriding the hover fill per call site to work around the leak can drop those
468
+ overrides. `packages/design/AGENTS.md` records the underlying rule in the Layer-0 chrome contract.
469
+
470
+ - Security patches for transitive dependencies: qs 6.16.0 in api-client, mermaid 11.17.2 and dompurify
471
+ 3.4.14 in design.
472
+
473
+ No API or behaviour change in either package.
474
+
475
+ - Every theme-aware subpath entry stops carrying `@keboola/brand-registry`.
476
+
477
+ `ThemeProvider/defaults.ts` imported two legacy brands for value, only to build a two-element array
478
+ of their ids — and that array had exactly one consumer, the `renderWithTheme` test helper. Because
479
+ `useThemeAttributes`, `useBrandState` and `useColorSchemeState` each read a constant from
480
+ `defaults.ts`, that one import put the whole registry in the graph of every entry that reads the
481
+ live theme.
482
+
483
+ The ids now live at the helper that uses them, so `defaults.ts` imports `Brand` as a type only. No
484
+ export moved and no behaviour changed — `keboolaLegacySidebar` / `keboolaLegacyTopnav` still resolve
485
+ from the root barrel for the consumers that render them.
486
+
487
+ What a consumer pays for a themed subpath, brotlied:
488
+
489
+ | Entry | Before | After | Δ |
490
+ | --------------------------------- | ---------: | ---------: | ----: |
491
+ | `@keboola/design/vega` | 61.9 kB | 0.9 kB | −98 % |
492
+ | `@keboola/design/editors` | 312.5 kB | 252.0 kB | −19 % |
493
+ | `@keboola/design/flint` | 439.7 kB | 380.2 kB | −14 % |
494
+ | `@keboola/design/rjsf` | 490.2 kB | 430.6 kB | −12 % |
495
+ | `@keboola/design/markdown-editor` | 1 333.9 kB | 1 272.0 kB | −5 % |
496
+ | `@keboola/design/mermaid` | 1 200.4 kB | 1 140.1 kB | −5 % |
497
+ | `{ useAppForm }` from the root | 135.2 kB | 73.9 kB | −45 % |
498
+
499
+ The root barrel is unchanged — it re-exports the brands deliberately, so an app that renders one
500
+ still pays for it once. The per-entry `size-limit` budgets drop to match, so they now measure the
501
+ entry rather than a shared dependency.
502
+
503
+ - `Modal.Footer` now lays its own actions out: right-aligned, 8px apart.
504
+
505
+ The footer owned only horizontal padding, so a bare `<Modal.Footer>` fell back to block flow — its
506
+ buttons sat left-aligned, separated by nothing but collapsed JSX whitespace. Nearly every footer in
507
+ this repo is bare, so that was the default look; a handful re-declared `tw:flex tw:justify-end
508
+ tw:gap-2` at the call site. Consumers can drop those.
509
+
510
+ Overriding the arrangement still works through `className`, but `cn` runs tailwind-merge, which
511
+ displaces only within a conflict group: `tw:justify-between` replaces `justify-end`, while
512
+ `tw:flex-col` leaves `items-center` and the gap in place, so a stacked footer wants
513
+ `tw:items-stretch` alongside it. The footer imposes no width on its children, so one that should
514
+ fill the row carries `tw:w-full` itself (`Button block`, `ButtonGroup variant="block"`).
515
+
516
+ One footer in this repo wanted the stacked arrangement — `wr-google-sheets`' `SheetModal`, which
517
+ puts a saving message above its buttons — and is the only one still passing layout classes.
518
+
519
+ `@keboola/kbc-ui` and `@keboola/control-ui` are private — nothing to release for them; they are
520
+ listed because their call sites changed.
521
+
522
+ - `PromptInput`: `autoFocus` no longer scrolls ancestors to reveal the textarea.
523
+
524
+ The prop was forwarded as the DOM attribute, and the browser's autofocus scrolls every scrollable
525
+ ancestor toward the element. A composer mounting inside a panel that is still sliding in — kbc-ui's
526
+ Kai rail opens this way — sits off-canvas at focus time, so the page scrolled sideways toward it,
527
+ shoving the whole content left until the slide finished and the overflow clamp snapped it back.
528
+ The focus is now applied imperatively on mount with `focus({ preventScroll: true })`: same focus,
529
+ same mount-only semantics, no ancestor scrolling.
530
+
3
531
  ## 11.3.0
4
532
 
5
533
  ### Minor Changes