@keboola/design 11.4.0 → 11.6.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 (184) hide show
  1. package/AGENTS.md +198 -7
  2. package/CHANGELOG.md +383 -0
  3. package/COMPONENT-API.md +73 -52
  4. package/dist/{AIKit-DDg5DBCG.js → AIKit-BExvt9pt.js} +390 -70
  5. package/dist/AIKit-BExvt9pt.js.map +1 -0
  6. package/dist/{Alert-DHJFrWnS.js → Alert-Ba5b2YPq.js} +2 -2
  7. package/dist/{Alert-DHJFrWnS.js.map → Alert-Ba5b2YPq.js.map} +1 -1
  8. package/dist/{Badge-Ch26CXoP.js → Badge-D0pXdekt.js} +2 -2
  9. package/dist/Badge-D0pXdekt.js.map +1 -0
  10. package/dist/{BrandEditor-Go7w8Zj2.js → BrandEditor-CSANsxUz.js} +6 -6
  11. package/dist/{BrandEditor-Go7w8Zj2.js.map → BrandEditor-CSANsxUz.js.map} +1 -1
  12. package/dist/{BrandLogo-Dmi7jo21.js → BrandLogo-CoKBk4MI.js} +4 -4
  13. package/dist/BrandLogo-CoKBk4MI.js.map +1 -0
  14. package/dist/{Breadcrumb-uEkM6adI.js → Breadcrumb-g_xn_-Nf.js} +2 -2
  15. package/dist/{Breadcrumb-uEkM6adI.js.map → Breadcrumb-g_xn_-Nf.js.map} +1 -1
  16. package/dist/{Checkbox-fC0BuO2B.js → Checkbox-CUIn2jap.js} +2 -2
  17. package/dist/{Checkbox-fC0BuO2B.js.map → Checkbox-CUIn2jap.js.map} +1 -1
  18. package/dist/{Clipboard-ChPdiUy3.js → Clipboard-BTW6C-b_.js} +2 -2
  19. package/dist/{Clipboard-ChPdiUy3.js.map → Clipboard-BTW6C-b_.js.map} +1 -1
  20. package/dist/{ClipboardButton-D9pDjgkB.js → ClipboardButton-CwZJdRaz.js} +2 -2
  21. package/dist/{ClipboardButton-D9pDjgkB.js.map → ClipboardButton-CwZJdRaz.js.map} +1 -1
  22. package/dist/{CodeEditor-Cxy3HnKH.js → CodeEditor-C9zy7PxK.js} +6 -6
  23. package/dist/{CodeEditor-Cxy3HnKH.js.map → CodeEditor-C9zy7PxK.js.map} +1 -1
  24. package/dist/{ColorInput-Bxj-1eLT.js → ColorInput-BZV-u_94.js} +3 -3
  25. package/dist/{ColorInput-Bxj-1eLT.js.map → ColorInput-BZV-u_94.js.map} +1 -1
  26. package/dist/{Combobox-Cs_1pj2q.js → Combobox-BfIfaVMA.js} +273 -104
  27. package/dist/Combobox-BfIfaVMA.js.map +1 -0
  28. package/dist/{Command-B7ML9YMv.js → Command-Bfa1kpbt.js} +2 -2
  29. package/dist/{Command-B7ML9YMv.js.map → Command-Bfa1kpbt.js.map} +1 -1
  30. package/dist/{CopyField-BO_sh-HO.js → CopyField-DpT0Ngz1.js} +2 -2
  31. package/dist/{CopyField-BO_sh-HO.js.map → CopyField-DpT0Ngz1.js.map} +1 -1
  32. package/dist/{DateInput-ZdaMQo5i.js → DateInput-DiEd5uCL.js} +3 -3
  33. package/dist/{DateInput-ZdaMQo5i.js.map → DateInput-DiEd5uCL.js.map} +1 -1
  34. package/dist/{DateRangeInput-DkvNh_sT.js → DateRangeInput-DPfVYchl.js} +3 -3
  35. package/dist/{DateRangeInput-DkvNh_sT.js.map → DateRangeInput-DPfVYchl.js.map} +1 -1
  36. package/dist/DiagramSource-C5_d8Gdj.js +11 -0
  37. package/dist/DiagramSource-C5_d8Gdj.js.map +1 -0
  38. package/dist/{FieldItem-CCd9-xnp.js → FieldItem-B7XMmZSz.js} +3 -3
  39. package/dist/{FieldItem-CCd9-xnp.js.map → FieldItem-B7XMmZSz.js.map} +1 -1
  40. package/dist/{Form-k6PH-em3.js → Form-CsttTHn4.js} +7 -7
  41. package/dist/Form-CsttTHn4.js.map +1 -0
  42. package/dist/{FormGroup-DbcDtrgU.js → FormGroup-DHvTFdno.js} +3 -3
  43. package/dist/{FormGroup-DbcDtrgU.js.map → FormGroup-DHvTFdno.js.map} +1 -1
  44. package/dist/{FullScreenModal-BvPuYibe.js → FullScreenModal-CN9nAs72.js} +50 -2
  45. package/dist/FullScreenModal-CN9nAs72.js.map +1 -0
  46. package/dist/{InputGroup-BnLHY0eq.js → InputGroup-DcZRlVnN.js} +22 -14
  47. package/dist/InputGroup-DcZRlVnN.js.map +1 -0
  48. package/dist/{Kbd-CvSM3D3-.js → Kbd-Cvvt91Q8.js} +14 -4
  49. package/dist/Kbd-Cvvt91Q8.js.map +1 -0
  50. package/dist/{MermaidDiagram-WkonYErO.js → MermaidDiagram-DSI7xMu2.js} +21 -19
  51. package/dist/MermaidDiagram-DSI7xMu2.js.map +1 -0
  52. package/dist/{MultiActionButtonGroup-CxWH7yWK.js → MultiActionButtonGroup-DokHbPgM.js} +2 -2
  53. package/dist/{MultiActionButtonGroup-CxWH7yWK.js.map → MultiActionButtonGroup-DokHbPgM.js.map} +1 -1
  54. package/dist/{Navbar-B2UsolVU.js → Navbar-Liy1TPI7.js} +6 -6
  55. package/dist/{Navbar-B2UsolVU.js.map → Navbar-Liy1TPI7.js.map} +1 -1
  56. package/dist/PageHeader-Q3IpCnQd.js.map +1 -1
  57. package/dist/{PageToolbar-DnbFt14Q.js → PageToolbar-DExqEYGl.js} +2 -2
  58. package/dist/{PageToolbar-DnbFt14Q.js.map → PageToolbar-DExqEYGl.js.map} +1 -1
  59. package/dist/{Popconfirm-CILjmzKI.js → Popconfirm-5pBzT_HQ.js} +10 -11
  60. package/dist/{Popconfirm-CILjmzKI.js.map → Popconfirm-5pBzT_HQ.js.map} +1 -1
  61. package/dist/{RadioGroup-XFzFd1ti.js → RadioGroup-DXPbSj8P.js} +2 -2
  62. package/dist/{RadioGroup-XFzFd1ti.js.map → RadioGroup-DXPbSj8P.js.map} +1 -1
  63. package/dist/{Search-4Bxtt8hS.js → Search-5uQAiNIP.js} +2 -2
  64. package/dist/{Search-4Bxtt8hS.js.map → Search-5uQAiNIP.js.map} +1 -1
  65. package/dist/{SearchInput-f_oY9T4Z.js → SearchInput-D4UgAXJK.js} +3 -3
  66. package/dist/{SearchInput-f_oY9T4Z.js.map → SearchInput-D4UgAXJK.js.map} +1 -1
  67. package/dist/{SectionNav-BpJlisbT.js → SectionNav-OkKCjSA-.js} +3 -3
  68. package/dist/{SectionNav-BpJlisbT.js.map → SectionNav-OkKCjSA-.js.map} +1 -1
  69. package/dist/{Select-DpcgKqKB.js → Select-DV3Hak_l.js} +3 -4
  70. package/dist/Select-DV3Hak_l.js.map +1 -0
  71. package/dist/{Sidebar-Cm6I4dE1.js → Sidebar-D3cY1kha.js} +44 -20
  72. package/dist/Sidebar-D3cY1kha.js.map +1 -0
  73. package/dist/{Slider-pPC_3gz9.js → Slider-CRf-bB4y.js} +2 -2
  74. package/dist/{Slider-pPC_3gz9.js.map → Slider-CRf-bB4y.js.map} +1 -1
  75. package/dist/{Switch-ChfPKcTE.js → Switch-B0WdGSjz.js} +7 -7
  76. package/dist/{Switch-ChfPKcTE.js.map → Switch-B0WdGSjz.js.map} +1 -1
  77. package/dist/{TextInput-DHM6dHmn.js → TextInput-DH5SkbsE.js} +3 -3
  78. package/dist/{TextInput-DHM6dHmn.js.map → TextInput-DH5SkbsE.js.map} +1 -1
  79. package/dist/{ThemeProvider-DrQJtzpd.js → ThemeProvider-2RMdVGxO.js} +17 -12
  80. package/dist/{ThemeProvider-DrQJtzpd.js.map → ThemeProvider-2RMdVGxO.js.map} +1 -1
  81. package/dist/{Timeline-6btyJl4D.js → Timeline-BG5Xogxh.js} +11 -11
  82. package/dist/{Timeline-6btyJl4D.js.map → Timeline-BG5Xogxh.js.map} +1 -1
  83. package/dist/{Workbench-BzE-Vvd9.js → Workbench-UR2FBC7I.js} +3 -1
  84. package/dist/Workbench-UR2FBC7I.js.map +1 -0
  85. package/dist/{ZoomableSvg-BWnDkR61.js → ZoomableSvg-pooPb1Cu.js} +3 -3
  86. package/dist/{ZoomableSvg-BWnDkR61.js.map → ZoomableSvg-pooPb1Cu.js.map} +1 -1
  87. package/dist/editors.js +4 -4
  88. package/dist/flint.js +136 -9
  89. package/dist/flint.js.map +1 -1
  90. package/dist/index.d.ts +801 -324
  91. package/dist/index.js +41 -40
  92. package/dist/internal/blocks/AIKit.js +2 -2
  93. package/dist/internal/blocks/AuthLayout.js +118 -0
  94. package/dist/internal/blocks/AuthLayout.js.map +1 -0
  95. package/dist/internal/blocks/BrandEditor.js +1 -1
  96. package/dist/internal/blocks/ClipboardButton.js +1 -1
  97. package/dist/internal/blocks/ColorInput.js +1 -1
  98. package/dist/internal/blocks/Command.js +1 -1
  99. package/dist/internal/blocks/CopyField.js +1 -1
  100. package/dist/internal/blocks/DateInput.js +1 -1
  101. package/dist/internal/blocks/DateRangeInput.js +1 -1
  102. package/dist/internal/blocks/ExportMenu.js +1 -1
  103. package/dist/internal/blocks/Form/connectors/MultiComboboxField.js +1 -1
  104. package/dist/internal/blocks/Form.js +1 -1
  105. package/dist/internal/blocks/FormGroup.js +1 -1
  106. package/dist/internal/blocks/MultiActionButtonGroup.js +1 -1
  107. package/dist/internal/blocks/MultiCombobox.js +107 -48
  108. package/dist/internal/blocks/MultiCombobox.js.map +1 -1
  109. package/dist/internal/blocks/NumberInput.js +1 -1
  110. package/dist/internal/blocks/PageToolbar.js +1 -1
  111. package/dist/internal/blocks/PasswordInput.js +2 -2
  112. package/dist/internal/blocks/SearchInput.js +1 -1
  113. package/dist/internal/blocks/SearchInputWithNavigation.js +2 -2
  114. package/dist/internal/blocks/SectionNav.js +1 -1
  115. package/dist/internal/blocks/TextInput.js +1 -1
  116. package/dist/internal/blocks/Textarea.js +1 -1
  117. package/dist/internal/blocks/TimeInput.js +2 -2
  118. package/dist/internal/components/Alert.js +1 -1
  119. package/dist/internal/components/Avatar.js +1 -1
  120. package/dist/internal/components/Badge.js +1 -1
  121. package/dist/internal/components/BrandLogo.js +1 -1
  122. package/dist/internal/components/Breadcrumb.js +1 -1
  123. package/dist/internal/components/Checkbox.js +1 -1
  124. package/dist/internal/components/Clipboard.js +1 -1
  125. package/dist/internal/components/CollapsiblePanel.js +1 -1
  126. package/dist/internal/components/Combobox.js +2 -2
  127. package/dist/internal/components/FullScreenModal.js +1 -1
  128. package/dist/internal/components/InputGroup.js +1 -1
  129. package/dist/internal/components/Kbd.js +1 -1
  130. package/dist/internal/components/Navbar.js +1 -1
  131. package/dist/internal/components/Popconfirm.js +1 -1
  132. package/dist/internal/components/Progress.js +3 -1
  133. package/dist/internal/components/Progress.js.map +1 -1
  134. package/dist/internal/components/RadioGroup.js +1 -1
  135. package/dist/internal/components/Search.js +1 -1
  136. package/dist/internal/components/Select.js +1 -1
  137. package/dist/internal/components/Sidebar.js +1 -1
  138. package/dist/internal/components/Slider.js +1 -1
  139. package/dist/internal/components/Switch.js +1 -1
  140. package/dist/internal/components/ThemeProvider.js +1 -1
  141. package/dist/internal/components/Timeline.js +1 -1
  142. package/dist/internal/components/Tooltip.js +1 -1
  143. package/dist/internal/components/Truncated.js +1 -1
  144. package/dist/internal/components/Workbench.js +1 -1
  145. package/dist/internal/components/ZoomableSvg.js +1 -1
  146. package/dist/internal/utils.js +2 -2
  147. package/dist/markdown-editor.js +29 -23
  148. package/dist/markdown-editor.js.map +1 -1
  149. package/dist/mermaid.js +1 -1
  150. package/dist/rjsf.js +22 -23
  151. package/dist/rjsf.js.map +1 -1
  152. package/dist/styles.css +1 -1
  153. package/dist/{svgTransforms-C9njt92h.js → svgTransforms-CJAYPk7o.js} +2 -10
  154. package/dist/svgTransforms-CJAYPk7o.js.map +1 -0
  155. package/dist/testing.js +1 -1
  156. package/dist/{useColorSchemeState-klihG9-i.js → themeScopeContext-D7fSyb06.js} +16 -3
  157. package/dist/themeScopeContext-D7fSyb06.js.map +1 -0
  158. package/dist/{themeTokens-D70HeNVF.js → themeTokens-qP1-55O7.js} +37 -10
  159. package/dist/themeTokens-qP1-55O7.js.map +1 -0
  160. package/dist/{utils-B5Z52hYs.js → utils-DSvrhU8E.js} +6 -2
  161. package/dist/{utils-B5Z52hYs.js.map → utils-DSvrhU8E.js.map} +1 -1
  162. package/dist/vega.d.ts +1 -1
  163. package/dist/vega.js +3 -3
  164. package/dist/vega.js.map +1 -1
  165. package/dist/{vegaTheme-DfoEQhOy.js → vegaTheme-CSvFEMBl.js} +6 -6
  166. package/dist/vegaTheme-CSvFEMBl.js.map +1 -0
  167. package/package.json +11 -9
  168. package/src/styles.css +3 -0
  169. package/dist/AIKit-DDg5DBCG.js.map +0 -1
  170. package/dist/Badge-Ch26CXoP.js.map +0 -1
  171. package/dist/BrandLogo-Dmi7jo21.js.map +0 -1
  172. package/dist/Combobox-Cs_1pj2q.js.map +0 -1
  173. package/dist/Form-k6PH-em3.js.map +0 -1
  174. package/dist/FullScreenModal-BvPuYibe.js.map +0 -1
  175. package/dist/InputGroup-BnLHY0eq.js.map +0 -1
  176. package/dist/Kbd-CvSM3D3-.js.map +0 -1
  177. package/dist/MermaidDiagram-WkonYErO.js.map +0 -1
  178. package/dist/Select-DpcgKqKB.js.map +0 -1
  179. package/dist/Sidebar-Cm6I4dE1.js.map +0 -1
  180. package/dist/Workbench-BzE-Vvd9.js.map +0 -1
  181. package/dist/svgTransforms-C9njt92h.js.map +0 -1
  182. package/dist/themeTokens-D70HeNVF.js.map +0 -1
  183. package/dist/useColorSchemeState-klihG9-i.js.map +0 -1
  184. package/dist/vegaTheme-DfoEQhOy.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,388 @@
1
1
  # @keboola/design
2
2
 
3
+ ## 11.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - `MarkdownTable` ships from `AIKit`: a table in an answer with the fact that it is cut off on the right said out loud. It draws a rail above the grid for the host's own actions, a fade at the cut edge that clears at the end of the scroll, and — once the table is past 1.5× the width it has — an `Open wider` action. Where wider leads is the host's: the action is a prop (`onExpand`), so a docked panel can hand it its own fullscreen switch and a standalone app a modal, and a host with nowhere wider to go omits it and keeps the fade. `surface` names the token the host paints behind the table (`card`, the default, or `background`), which is the colour the fade ends in — naming the wrong one draws a contrasting strip over the cells instead of hiding them. The table passed in has to keep its intrinsic width (`tw:min-w-max`): `Table` is `w-full` with auto layout, so a grid left to wrap into its column never overflows and neither affordance can appear.
8
+
9
+ `FullScreenModal` now moves focus into itself when it opens and returns it on close. Its `onEscapeKeyDown` is a `keydown` on the modal element, so until focus was inside, Escape reached nothing and keyboard readers went on walking the page behind. Focus is left alone where the modal's own content has already claimed it, and Escape is now answered only by the modal it was pressed in — a nested modal is portalled beside its parent but React routes its events up the logical tree, so one keypress used to dismiss both. Which modal that is comes off the DOM, where the nesting is not a lie, rather than from `stopPropagation`: a synthetic `stopPropagation` also stops the native event, and React mounts a portal's listeners on `document.body`, so it would cut off every `document`-level `keydown` handler on the page. This is not yet a focus trap: Tab still leaves for the obscured page.
10
+
11
+ - `Progress` gains `warning` and `error` variants, on the same ramps `Badge` maps `orange` and `red` to, so a meter can carry the state its label already names
12
+
13
+ ### Patch Changes
14
+
15
+ - `ThinkingIndicator` now announces itself as a live region, so a screen reader hears that an agent's turn is in flight rather than only seeing the spinner.
16
+
17
+ - Retry a failed diagram chunk instead of rendering source for the rest of the session
18
+
19
+ `Markdown` loaded `MermaidDiagram` and `FlintChart` through `lazy()`, which memoizes the settled
20
+ import for the life of the page. One failed fetch — a dev server restarting, a stale tab after a
21
+ deploy, a flaky network — left every later `mermaid` and `flint` fence in that tab rendering as
22
+ raw source until a full reload. The import is now attempted per mount and the failed promise is
23
+ cleared, and a block that fell back to source carries a Try again button — so one bad fetch costs
24
+ that one block rather than the session.
25
+
26
+ - `formatPercentage` prints whole numbers from 1% up, keeping one decimal only below 1%
27
+
28
+ ## 11.5.0
29
+
30
+ ### Minor Changes
31
+
32
+ - `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.
33
+
34
+ - `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.
35
+
36
+ - `AuthLayout` floors the form column in the split geometry, and takes `asideCollapse`; `BrandLogo` takes `label`
37
+ - 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.
38
+ - 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.
39
+ - `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`.
40
+ - `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)`).
41
+
42
+ - `Combobox` and `MultiCombobox` are now clearable by default — `allowClear` defaults to `true`.
43
+
44
+ Consumer-visible behaviour change. The clear button only mounts once there is something to clear
45
+ (single: a selection; multi: at least one chip), so an empty control renders exactly as before and
46
+ individual chips stay removable regardless. 16 existing call sites across `kbc-ui`,
47
+ `developer-portal` and `theme-generator` gain the affordance; the 10 that already pass `allowClear`
48
+ are unaffected, as are the 4 adder-pattern comboboxes pinned to `value={null}` (nothing is ever
49
+ selected, so the button never mounts).
50
+ - Pass `allowClear={false}` where clearing must not be one click away — a required field, or a
51
+ control whose empty state the surrounding form cannot represent.
52
+ - `RjsfSchemaEditor`'s `SelectWidget` and `DynamicSelectWidget` now both derive it from the schema
53
+ (`allowClear={!required}`), so a schema-required field stays non-clearable. `DynamicSelectWidget`
54
+ passed no `allowClear` before, so its non-required fields gain the button as intended.
55
+ - `MultiComboboxField` deliberately does **not** derive it: a required multi field's chips are
56
+ individually removable, so withholding clear-all prevents nothing.
57
+ - Radix `Select` still has no clear affordance; model "no selection" as an explicit item there.
58
+
59
+ `allowSelectAll` is unchanged (still `false`) — see UT-5422.
60
+
61
+ - `Combobox` and `MultiCombobox` take `inputValueSanitizer` and `onQueryChange`
62
+ - `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.
63
+ - `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.
64
+ - Neither hands the text over: the control keeps owning it, so there is still no controlled `inputValue`.
65
+
66
+ - `Combobox` takes `itemToSelectedLabel` — how the selection renders once the popup closes.
67
+
68
+ Additive. Left unset the closed control shows `itemToStringLabel`'s text, exactly as before.
69
+ - The node renders ahead of the input and the input's own text turns transparent behind it, so the
70
+ selection reads once. Typing dissolves it and hands the row back to the query.
71
+ - The input keeps the string label as its **value**, so the control still reports its selection to a
72
+ screen reader. That is the reason to reach for this over drawing the selection into `prefix` with
73
+ `value` pinned to `null` — same picture, and the input has no accessible value.
74
+ - The three label accessors stay independent: `itemToLabel` styles the open list's rows (and takes
75
+ the query, for `MarkedText`), `itemToStringLabel` is the input's text and the search subject.
76
+
77
+ `Combobox` also now tracks an uncontrolled selection (`defaultValue`, or no `value` at all) the same
78
+ way it tracks a controlled one. Beyond feeding the new accessor, that fixes the list collapsing to
79
+ the selected item after an uncontrolled pick: as with a controlled combobox, reopening now browses
80
+ the whole list instead of filtering by the selection's own label.
81
+
82
+ Radix `Select` needs nothing here — its trigger already mirrors the item's `label` node, and `icon`
83
+ / `hint` sit outside that mirror for a row that carries more than the trigger should.
84
+
85
+ - Add `AuthLayout` — the signed-out page frame (sign-in, sign-up, password reset, MFA, project picker).
86
+
87
+ 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.
88
+
89
+ - `Select` requires an accessible name — `aria-label`, `aria-labelledby`, or an `id` — and forwards
90
+ `aria-labelledby` to the trigger, which it previously dropped.
91
+
92
+ ```tsx
93
+ // A visible label — point it at the control.
94
+ <Label htmlFor="stage">Stage</Label>
95
+ <Select id="stage" items={STAGES} value={stage} onValueChange={setStage} />
96
+
97
+ // A bare control (toolbar filter, repeated row) — name it directly.
98
+ <Select aria-label="Rows per page" items={ROWS_PER_PAGE} value={rows} onValueChange={setRows} />
99
+ ```
100
+
101
+ The trigger is a `role="combobox"` button, which is not a name-from-content role: neither the
102
+ selected item's label nor the `placeholder` names it, so a `Select` with none of the three reached
103
+ a screen reader as an unnamed control.
104
+ - The requirement sits on `Select.Trigger` as well, so the compound route is covered too. Passing
105
+ more than one route is still allowed — an `id` for the `<Label htmlFor>` plus an `aria-label`
106
+ that overrides it is a legitimate pairing.
107
+ - An `id` alone still names nothing: it counts only when a `<label for>` points at it, and no type
108
+ can check that pair.
109
+ - `field.SelectField` needs none of the three — `FieldItem` owns the label and hands the control
110
+ its `id`. It no longer accepts `aria-label` / `aria-labelledby`, which nothing passed: a name
111
+ from the call site could only disagree with the label the field already renders.
112
+
113
+ - `MultiCombobox` takes `canCreate`, the gate `Combobox` already had. It runs on the typed query once
114
+ that query matches no item and nothing already selected, and returning `false` hides the create row
115
+ so the empty state shows instead of an "Add …" row the caller would only reject afterwards.
116
+
117
+ The case it is for is a free-form value with a format the caller can state — a column name a
118
+ JSON-Schema `pattern` constrains, an email address. Without it a multi creatable control had no way
119
+ to say no before the value was in the selection.
120
+
121
+ The Select All row no longer leaks into the chips. Base UI holds it in `value` like any other item,
122
+ so a complete selection grew a third chip and handed the row's own entry to the caller's
123
+ `itemToChipLabel` / `itemToLabel` as if it were a value — a control that projects its items (an id
124
+ to a label) then rendered that object as a child and React threw. The chip area now filters the
125
+ control's internal entries out before anything reaches a caller callback. The row is also gone
126
+ entirely when there is nothing to select all of, where `0 === 0` had read as "everything is
127
+ selected".
128
+
129
+ `itemToLabel` defaults to `itemToStringLabel` rather than to the built-in label reader, so a control
130
+ whose items are ids with the label beside them states the projection once instead of passing the
131
+ same function twice. Passing both still works, and is what a row richer than the input's own text
132
+ wants.
133
+
134
+ - `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.
135
+
136
+ 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.
137
+
138
+ - `MultiCombobox` accepts `isItemEqualToValue` and forwards it to Base UI, as `Combobox` already did.
139
+ Without it Base UI matches the selection against `items` by reference, so a control fed object items
140
+ lost its selection the moment the store handed back structurally equal items with fresh references —
141
+ the workaround being a caller-side `string[]` of ids plus an id→label projection.
142
+
143
+ Both controls now build the comparer from one shared factory, with two consequences. An injected
144
+ "Select All" or create row equals only a sentinel of the same marker, so a caller item whose `value`
145
+ is the literal `__select_all__` is a plain item rather than the Select All row. And the comparer
146
+ keeps its identity between renders, where an unmemoized one re-rendered every option row on each
147
+ render of the control.
148
+
149
+ - `MultiCombobox` with `allowCreate` splits a created entry into one value per comma, and takes
150
+ `createSeparator` to change or switch off the split.
151
+
152
+ Consumer-visible behaviour change, on by default. Pasting `id1, id2, id3` into a creatable multi
153
+ control added one chip literally named `id1, id2, id3`; it now adds three. The paste is the reason a
154
+ multi control is creatable at all — tags, column lists, primary keys, allowed component ids, email
155
+ lists — so the split is the default rather than a prop each site has to remember. `Combobox` is
156
+ unchanged: one entry there can only be one value.
157
+ - `createSeparator` takes a string or a `RegExp` for another convention (`';'` for an Outlook
158
+ address list, `/[\s,]+/` where a value can never contain a space), and `null` where a value can
159
+ itself contain the separator and one entry must stay one chip.
160
+ - Whitespace is deliberately not part of the default — tags and column names contain spaces, and
161
+ every value is trimmed anyway, so `id1, id2` needs nothing but the comma.
162
+ - `queryToCreateValue` is called once per value, each already trimmed, so a per-value projection or
163
+ normalisation needs no change. This is also where the legacy wrapper's `trimMultiCreatedValues`
164
+ lands.
165
+ - `canCreate` gates each value rather than the whole entry, so a pasted list keeps the values whose
166
+ format passes and drops the rest instead of being rejected wholesale. A value naming an existing
167
+ item never reaches the gate — committing selects that item rather than minting one.
168
+ - A value repeated inside one entry, or one the selection already holds, is added once. A value
169
+ naming an existing item selects that item rather than minting a twin standing for the same thing.
170
+ - The row is offered whenever the entry adds anything to the selection, and withholds itself only
171
+ when the selection already holds every value, or when a single value names an item (that item's
172
+ own row is the affordance). That is why an entry naming only existing items keeps the row: the
173
+ substring filter matches no item for a multi-value entry, so the row is the only thing that can
174
+ act on such a paste.
175
+ - `queryToCreateLabel` now receives the values committing the row will add, re-joined with the
176
+ entry's own separator, rather than the entry as typed — so the row can neither name a value it
177
+ drops nor stay silent about one it selects. For a single-value entry that string is the entry
178
+ itself, so an existing label reads exactly as before.
179
+
180
+ - Add `AnswerActions` to the AI Kit: the copy-and-vote row for a finished agent answer.
181
+ - Always rendered rather than revealed on hover, so it exists for a reader without a pointer.
182
+ - `aria-pressed` says which vote is recorded, and both buttons stay focusable — a vote can be
183
+ changed but not withdrawn.
184
+ - No copy button when `copyText` is empty: a turn that produced only tool calls has nothing to
185
+ take away.
186
+ - The recorded vote and the write stay with the caller; the two chats vote against different
187
+ endpoints.
188
+
189
+ - `useSidebar(side)` now exposes `setFill(fill: boolean)` alongside `toggleFill()`.
190
+
191
+ `toggleFill` requires the caller to know the current value, so two callers in one commit read the
192
+ same stale flag and undo each other, and a caller whose view of the layout disagrees with the flag
193
+ skips the change entirely. `setFill` states the value it wants, so it is idempotent. `toggleFill`
194
+ is unchanged and still the right call for a user-facing toggle control.
195
+
196
+ - `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.
197
+
198
+ ### Patch Changes
199
+
200
+ - `PlanApproval`, `CollapsibleStepHeader` and `UsageStatsRing` follow the active colour scheme and
201
+ brand. All three pinned fixed grey and white values, so the plan-approval card rendered as a light
202
+ card with near-black text on a dark page, and ignored a customer brand's own surface tokens in
203
+ either scheme. They now consume the semantic tokens throughout: the card's shell is the recessed
204
+ `background` tier, its plan body, action bar and change-request form lift off it on `card`, and each
205
+ tier paints its own foreground so no surface inherits ink from whatever rendered it. Step counts
206
+ read as `muted-foreground`, dividers as `border`, and the usage ring's track as a muted mark.
207
+
208
+ A dismissed step's icon takes its colour from the step's own variant rather than a pinned grey, so
209
+ it now matches the muted label beside it; a failed step keeps the destructive tone. The card's inner
210
+ dividers are a touch more visible in light mode, since one shared border token now draws them and
211
+ the frame alike.
212
+
213
+ - 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.
214
+
215
+ - `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.
216
+
217
+ - `Combobox` / `MultiCombobox` hold `''` and `' '` as genuine values: `withBlankValues`, `namedLabel`.
218
+
219
+ "This column equals the empty string" and "equals a single space" are real filter predicates against
220
+ Storage data. Base UI keys "is something selected" off `value != null` rather than truthiness, so a
221
+ blank string already round-trips through `onValueChange` verbatim and keeps its clear affordance —
222
+ no sentinel option and no mapping on save. What was missing was the display half: a blank row and a
223
+ blank chip render as nothing, indistinguishable from an empty control.
224
+ - `withBlankValues(values)` offers both blanks ahead of a value list and collapses duplicates. A
225
+ column's distinct values can already hold one, and two `Object.is`-equal rows both report
226
+ `aria-selected` and toggle each other.
227
+ - **Rows and chips** name a blank — `[empty string]`, `[space character]`, and `[whitespace]` for
228
+ anything else whose text is all whitespace. `namedLabel` exposes the rule so an `itemToLabel` /
229
+ `itemToChipLabel` / `renderChips` override can keep it.
230
+ - **`itemToStringLabel` is deliberately not named.** It is the item's string identity, the default
231
+ search text and the input's accessible value, and Base UI counts `''` as a selection — naming it
232
+ would paint text over the placeholder wherever "nothing selected" is spelled `''`. Typing
233
+ therefore never reaches a blank row, and naming a blank selection in a closed `Combobox` is
234
+ `itemToSelectedLabel`'s job.
235
+ - `allowCreate` cannot create a blank: it trims the typed query, so fixed rows are the only way in.
236
+ - Radix `Select` is unaffected and still cannot carry a blank — it reserves `''` for its own empty
237
+ state. The split is documented in `AGENTS.md`.
238
+
239
+ - `Combobox` and `MultiCombobox` popups register in Radix's dismissable-layer stack, so Escape closes
240
+ the popup and leaves the surrounding surface alone. Base UI portals the popup out of the DOM, so a
241
+ `Modal`, `Sheet` or `Popover` around it still counted itself as topmost and took the keystroke — a
242
+ combobox in a modal form lost the whole half-filled form on the key that was meant to dismiss its
243
+ own list. The popup now sits above that surface as a layer of its own; Base UI still owns the
244
+ dismissal itself, and Escape on a closed popup reaches the surface as before.
245
+
246
+ - `Combobox`'s `itemToSelectedLabel` node now paints over the input's own content box instead of
247
+ sitting beside it as a flex sibling. Previously the two boxes shared one row, so the caret and text
248
+ selection lived roughly a badge-width past where the visible label ended.
249
+
250
+ - `Combobox` stores the text the user typed rather than the text it displays, and derives the input's
251
+ value, the search query and the rich selected label from that one piece of state.
252
+
253
+ The two meanings did not fit in one variable, so the difference was reconstructed around it: an
254
+ effect snapping the input back to the controlled value's label, a counter re-triggering that effect
255
+ when `value` stayed put, a string comparison standing in for "this is a selection, not a query", and
256
+ a ref reconstructing why the popup had closed. `typedText: string | null` says it directly, with
257
+ `null` for "nothing typed, showing the selection".
258
+ - Only Base UI's `input-change` reason writes to it. Every other reason it reports — an item press,
259
+ a clear, the label it syncs back on unmount — means "show the selection", which `null` already
260
+ says. Its sync-back is a write to ignore rather than adopt: it stringifies Base UI's own selected
261
+ value, which for a created item is the internal sentinel and not the value the owner holds.
262
+ - The state resets the moment the popup starts closing, so a control the user has walked away from
263
+ shows what its value says. Base UI's own sync-back cannot carry that: it skips the write whenever
264
+ the input's text already matches the label, which a query edited back to the label reaches.
265
+ - Whitespace alone is never adopted as a query. Base UI gates opening the popup on the trimmed
266
+ text, so a control that took it would sit closed showing text its value does not say, with no
267
+ close to discard it.
268
+ - "Showing the selection" is the input's text matching the selection's label, not merely nothing
269
+ having been typed. A query edited back to the label lands on the same screen as never having
270
+ typed: the rich `itemToSelectedLabel` node returns and the list stays browsable rather than
271
+ narrowing to the item already chosen. Base UI's own `shouldBypassFiltering` covers only a fresh
272
+ open — its `queryChangedAfterOpen` flips on the first keystroke and clears on close, never when
273
+ the text returns to the label.
274
+
275
+ One behaviour change: a `value` arriving from elsewhere while the user is mid-query no longer yanks
276
+ the text away on the keystroke. The query stands until it is committed or discarded, and the new
277
+ label shows when the popup closes.
278
+
279
+ - `Combobox` / `MultiCombobox` get a shared word-by-word `filter`: `filterByWords` and `matchByWords`.
280
+
281
+ Both controls match the typed query as one substring of the label, so `frankfurt europe` finds
282
+ nothing in a list holding `Europe (Frankfurt)`. `filter={filterByWords}` requires every word of the
283
+ query to occur somewhere in the label, in any order, ignoring case, matching by containment;
284
+ `matchByWords([a, b], query)` applies the same rule to texts the label does not show — an email
285
+ behind a name, a table id behind a display name.
286
+ - `filterByWords` searches **only** what the control displays, so a filter cannot match an id the
287
+ user cannot see. Extra fields are named explicitly through `matchByWords`.
288
+ - Comparison is a lowercased `includes`, not Base UI's `Intl.Collator` `contains`, so passing
289
+ either helper trades diacritic-insensitive matching (`Muller` finds `Müller`) for word order.
290
+ - Defaults are unchanged — both are opt-in per call site.
291
+
292
+ - `Badge.Button` shows a focus ring. The `focus-visible` block sat on `BadgeRoot`, a `div` that never
293
+ takes focus, so every clickable badge was keyboard-focusable with no visible focus. The ring is
294
+ inset, because a badge root clips its overflow.
295
+
296
+ - `MarkdownEditor`'s plugins, `RjsfSchemaEditor` and `Popconfirm` read their callback props through
297
+ `useStableCallback` instead of a ref assigned in the component body. Each of those assignments wrote
298
+ to shared memory during render, where a render React discards can still leave the ref carrying a
299
+ value that never committed. Two refs in `RjsfSchemaEditor` are gone rather than converted — one
300
+ wrapped a `useState` setter and one a `useMemo` with no dependencies, both already stable, and the
301
+ ref only hid that from the dependency list. `ImagePastePlugin` now decides whether to claim paste
302
+ and drop from a boolean derived from `onImageUpload`, so an editor that gains or loses an uploader
303
+ re-registers its Lexical commands instead of keeping the first answer forever.
304
+
305
+ - `RjsfSchemaEditor`'s enum fields have an accessible name: `SelectWidget` and `DynamicSelectWidget`
306
+ forward rjsf's `id` to their `Combobox`, which is where `FieldTemplate`'s `<Label htmlFor>` points.
307
+ They were the only built-in widgets dropping it, so a screen reader reported every enum field in a
308
+ component configuration as an unnamed combobox and clicking the visible label focused nothing. No
309
+ visual change.
310
+
311
+ Still unnamed, and not addressed here: `RadioWidget` and `CodeEditorWidget` forward the `id` to a
312
+ `div`, which no `<Label htmlFor>` can name — naming those needs `aria-labelledby` from the template
313
+ and a route into the editor's own textarea.
314
+
315
+ - Smoother sidebar motion, and a composer a host can refocus.
316
+ - The sidebar settles instead of sliding linearly: its geometry transitions run
317
+ `cubic-bezier(0.32, 0.72, 0, 1)` over 420ms, the off-canvas slide is composited, and
318
+ all of it drops out under `prefers-reduced-motion`.
319
+ - `Sidebar` takes `mountAfterEnter`: a heavy panel holds its content back until the slide
320
+ has finished, rather than mounting into the same commit and blocking every frame of it.
321
+ Ignored under reduced motion, where there is no transition end to wait for.
322
+ - `Workbench` marks its two boxes `data-workbench-slot="bar"` / `"panel"`, so a floating
323
+ surface can measure the space the dock leaves. The root's own rect is the bar alone
324
+ whatever the state, because the panel is absolutely positioned.
325
+ - `PromptInput` takes `textareaRef`, publishing its textarea so a host that keeps one
326
+ composer across layout changes can put the caret back. Published from the ref callback,
327
+ so it still points at the live element after a host layout restructure recreates it.
328
+
329
+ - `InputGroup.Addon` keeps its own content width instead of giving up space to the input. The input
330
+ was `w-full`, so as a flex item its basis was the whole row — flexbox then shrank a sibling addon in
331
+ proportion to that basis, squeezing even a short badge-plus-label selection to an ellipsis well
332
+ before the field was full. The input is now `flex-1 min-w-0` and `inline-start` / `inline-end`
333
+ addons ship `shrink-0`; behaviour is unchanged where there is no addon, and every existing addon
334
+ holds small non-shrinking content (icons, buttons) so this is a no-op for them. `Combobox`'s rich
335
+ selected-value addon drops the `max-w-[70%] shrink-0` workaround this replaces, keeping only the
336
+ `max-w-[70%]` bound needed for an arbitrarily long selection.
337
+
338
+ `InputGroup`'s padding and sizing rules now target any descendant `input` / `textarea` instead of
339
+ only a direct child, so a consumer that wraps the input in its own layout keeps its padding without
340
+ re-implementing the sizing table by hand. `MultiCombobox` drops the manual `px-4 py-1.5` this
341
+ replaces, and instead cancels the group's rule on its own nested input (`!px-0 !py-0`) since that
342
+ input shares a row with chips rather than sitting at the group's own edge.
343
+
344
+ - `Kbd variant="primary"` and `Kbd.Separator` follow the tooltip chrome they render on. `primary`
345
+ pinned `bg-white/20` + `text-white`, which is only legible on the legacy dark tooltip. Argo's tooltip
346
+ is a light popover, so every shortcut hint on it — Kai's "New chat", the SQL editor's panel toggles,
347
+ flow undo/redo, the sidebar nav — painted white keys on white and vanished, leaving the keys'
348
+ layout width behind as apparent trailing whitespace.
349
+
350
+ Both now consume the muted chrome tokens by default and invert to white only under
351
+ `[data-tooltip-content="true"]`, the attribute the legacy dark tooltip already marks itself with. The
352
+ legacy rendering is unchanged. Call sites no longer need the `tw:[&>span]:text-white/70` override
353
+ that forced the separator white — `Kbd.Separator` handles it.
354
+
355
+ - `MultiCombobox`'s `allowSelectAll` row now hides itself while a query narrows the list. It always
356
+ selected the whole item set, but stayed visible during filtering — type `prod`, see three matches,
357
+ click Select All, end up with all two hundred selected, with nothing on screen saying so. Select All
358
+ keeps meaning "everything"; the reading it can be confused with is no longer on offer. This matches
359
+ the legacy `react/common/Select` wrapper, which hid its own Select-All row while searching.
360
+
361
+ - `PageHeader.Detail`'s documented breadcrumb contract now matches how it's actually used: the
362
+ `breadcrumbs` chain ends at the entity's parent list/hierarchy and never repeats the entity's own
363
+ name as a final, non-link crumb — it's already the `title` right below. The prop doc, `AGENTS.md`
364
+ rules and the `Detail` Storybook examples (Bucket, Table, Workspace, Job, Data App, Shared Code, Add
365
+ Component, Flow) are updated to the same shape.
366
+
367
+ - A chart or diagram inside a `ThemeScope` now follows the scope's scheme, and a chart in dark mode
368
+ keeps readable labels.
369
+
370
+ `ThemeScope` re-points the colour tokens for its subtree, but a canvas that bakes colours into
371
+ markup resolved them from `<html>`: `useThemeAttributes` read the root's `data-theme`, and the
372
+ token resolvers read `getComputedStyle(document.documentElement)`. A chart in a dark-scoped panel
373
+ therefore drew a white plot with black labels on a dark surface.
374
+ - `ThemeScope` publishes its resolved scheme and a container carrying its tokens; `useThemeAttributes`
375
+ prefers both and folds the container into `cacheKey`, so a palette built before the scope answered
376
+ is not served afterwards.
377
+ - `createChannelResolver` / `createTokenResolver` (and the vega, flint and mermaid palettes built on
378
+ them) take the element to read live `--*` values off, defaulting to `<html>` as before.
379
+ - The flint palette now fills `ink.text` and `ink.structure` from the tokens. Flint's own defaults
380
+ assume a light canvas, so axis labels and gridlines were near-black in **any** dark context — not
381
+ only inside a scope.
382
+ - The root barrel grows ~70 B with the scope context; the size budget moves 344→345 KB.
383
+
384
+ - `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.
385
+
3
386
  ## 11.4.0
4
387
 
5
388
  ### Minor Changes