@aceshooting/lyra-ui 21.0.0 → 21.1.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 (272) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +6 -3
  3. package/custom-elements.json +1 -1
  4. package/dist/ai/runtime.js +1 -1
  5. package/dist/ai/types.d.ts +13 -5
  6. package/dist/cli/lyra-rename-ledger.mjs +1 -0
  7. package/dist/cli/migrate-wa.mjs +33 -13
  8. package/dist/cli/migration-contract.json +1 -1
  9. package/dist/components/agent-tools/agent-trace/agent-trace.class.d.ts +5 -3
  10. package/dist/components/agent-tools/agent-trace/agent-trace.class.js +3 -2
  11. package/dist/components/agent-tools/tool-call-block/tool-call-block.class.d.ts +15 -3
  12. package/dist/components/agent-tools/tool-call-block/tool-call-block.class.js +3 -3
  13. package/dist/components/agent-tools/tool-call-chip/tool-call-chip.class.d.ts +24 -8
  14. package/dist/components/agent-tools/tool-call-chip/tool-call-chip.class.js +3 -3
  15. package/dist/components/agent-tools/tool-param-form/tool-param-form.class.js +4 -0
  16. package/dist/components/agent-tools/tool-result-dialog/tool-result-dialog.class.d.ts +9 -5
  17. package/dist/components/agent-tools/tool-result-dialog/tool-result-dialog.class.js +2 -2
  18. package/dist/components/agent-tools/tool-result-dialog/tool-result-dialog.styles.js +1 -1
  19. package/dist/components/agent-tools/tool-status.js +2 -2
  20. package/dist/components/agent-tools/tool-timeline/tool-timeline.class.d.ts +8 -0
  21. package/dist/components/agent-tools/tool-timeline/tool-timeline.class.js +3 -2
  22. package/dist/components/agent-tools/tool-timeline/tool-timeline.styles.js +1 -1
  23. package/dist/components/agent-tools/trace-tree/span.d.ts +2 -2
  24. package/dist/components/charts/chart/box-plot.class.d.ts +3 -1
  25. package/dist/components/charts/chart/box-plot.class.js +9 -9
  26. package/dist/components/charts/chart/chart-bidi.d.ts +109 -0
  27. package/dist/components/charts/chart/chart-bidi.js +1 -0
  28. package/dist/components/charts/chart/chart.class.d.ts +17 -2
  29. package/dist/components/charts/chart/chart.class.js +10 -8
  30. package/dist/components/charts/chart/histogram.class.js +1 -1
  31. package/dist/components/charts/chart/lite-chart.class.d.ts +24 -5
  32. package/dist/components/charts/chart/lite-chart.class.js +9 -9
  33. package/dist/components/charts/chart/lite-chart.styles.js +1 -1
  34. package/dist/components/conversation/code-block/shiki-dark-theme.d.ts +2 -2
  35. package/dist/components/conversation/code-block/shiki-dark-theme.js +1 -1
  36. package/dist/components/conversation/conversation-item/conversation-item.class.d.ts +18 -2
  37. package/dist/components/conversation/conversation-item/conversation-item.class.js +3 -3
  38. package/dist/components/conversation/conversation-item/conversation-item.styles.js +1 -1
  39. package/dist/components/conversation/markdown/markdown-base.class.js +1 -1
  40. package/dist/components/conversation/markdown/markdown-core.class.d.ts +3 -1
  41. package/dist/components/conversation/markdown/markdown.class.d.ts +3 -1
  42. package/dist/components/conversation/message-actions/message-actions.class.d.ts +7 -2
  43. package/dist/components/conversation/message-actions/message-actions.class.js +2 -2
  44. package/dist/components/conversation/message-parts/message-parts.class.d.ts +6 -2
  45. package/dist/components/conversation/message-parts/message-parts.class.js +5 -3
  46. package/dist/components/conversation/selection-toolbar/selection-toolbar.class.d.ts +3 -2
  47. package/dist/components/conversation/selection-toolbar/selection-toolbar.class.js +1 -1
  48. package/dist/components/conversation/streaming-text/streaming-text-base.class.d.ts +1 -5
  49. package/dist/components/conversation/streaming-text/streaming-text-base.class.js +2 -2
  50. package/dist/components/conversation/streaming-text/streaming-text-core.class.d.ts +7 -1
  51. package/dist/components/conversation/streaming-text/streaming-text-core.class.js +3 -4
  52. package/dist/components/conversation/streaming-text/streaming-text.class.d.ts +7 -1
  53. package/dist/components/conversation/streaming-text/streaming-text.class.js +3 -4
  54. package/dist/components/conversation/thread-list/thread-list.class.d.ts +13 -2
  55. package/dist/components/conversation/thread-list/thread-list.class.js +3 -3
  56. package/dist/components/data/data-grid/data-grid.class.js +1 -1
  57. package/dist/components/data/document-library/document-library.class.js +2 -0
  58. package/dist/components/data/flow-canvas/flow-canvas.class.js +1 -1
  59. package/dist/components/data/flow-minimap/flow-minimap.class.js +1 -1
  60. package/dist/components/data/flow-node/flow-node.class.d.ts +6 -2
  61. package/dist/components/data/flow-node/flow-node.class.js +6 -2
  62. package/dist/components/data/flow-node/flow-node.styles.js +1 -1
  63. package/dist/components/data/gauge/gauge.styles.js +1 -1
  64. package/dist/components/data/heatmap/heatmap.class.d.ts +3 -0
  65. package/dist/components/data/heatmap/heatmap.class.js +2 -2
  66. package/dist/components/data/heatmap/heatmap.styles.js +1 -1
  67. package/dist/components/data/sparkline/sparkline.class.d.ts +5 -3
  68. package/dist/components/data/stat/stat.class.d.ts +4 -3
  69. package/dist/components/data/table/table.class.d.ts +32 -5
  70. package/dist/components/data/table/table.class.js +3 -2
  71. package/dist/components/forms/button/button.class.d.ts +7 -1
  72. package/dist/components/forms/button/button.styles.js +1 -1
  73. package/dist/components/forms/date-picker/date-input.styles.js +1 -1
  74. package/dist/components/forms/input/input.styles.js +1 -1
  75. package/dist/components/forms/rubric-form/rubric-form.class.js +1 -0
  76. package/dist/components/layout/app-rail/app-rail.class.d.ts +50 -11
  77. package/dist/components/layout/app-rail/app-rail.class.js +2 -2
  78. package/dist/components/layout/command-palette/command-palette.class.js +1 -1
  79. package/dist/components/layout/menu/menu.class.d.ts +6 -5
  80. package/dist/components/layout/split-panel/split-panel.class.d.ts +2 -1
  81. package/dist/components/media/flag/language-map.d.ts +8 -0
  82. package/dist/components/media/flag/language-map.js +1 -1
  83. package/dist/components/media/map/map-loader.d.ts +7 -1
  84. package/dist/components/media/map/map.class.d.ts +111 -5
  85. package/dist/components/media/map/map.class.js +3 -3
  86. package/dist/components/media/media-card/media-card.class.d.ts +18 -5
  87. package/dist/components/media/media-card/media-card.class.js +1 -1
  88. package/dist/components/overlays/overlay/popover.class.d.ts +20 -0
  89. package/dist/components/overlays/overlay/popover.class.js +2 -2
  90. package/dist/components/retrieval/graph/graph.class.d.ts +3 -3
  91. package/dist/components/retrieval/graph/graph.class.js +1 -1
  92. package/dist/components/retrieval/knowledge-graph-explorer/knowledge-graph-explorer.class.d.ts +5 -3
  93. package/dist/components/retrieval/knowledge-graph-explorer/knowledge-graph-explorer.class.js +3 -2
  94. package/dist/components/retrieval/mind-map/mind-map.class.d.ts +3 -3
  95. package/dist/components/retrieval/mind-map/mind-map.class.js +1 -1
  96. package/dist/components/retrieval/retrieval-results/retrieval-results.class.d.ts +4 -1
  97. package/dist/components/retrieval/retrieval-results/retrieval-results.class.js +5 -1
  98. package/dist/components/retrieval/source-picker/source-picker.class.d.ts +4 -1
  99. package/dist/components/retrieval/source-picker/source-picker.class.js +5 -1
  100. package/dist/components/utility/icon/icon.class.d.ts +9 -3
  101. package/dist/components/utility/icon/icon.class.js +1 -1
  102. package/dist/components/utility/mutation-observer/mutation-observer.class.d.ts +14 -8
  103. package/dist/components/utility/mutation-observer/mutation-observer.class.js +1 -1
  104. package/dist/components/viewers/document-viewer/document-viewer.class.d.ts +6 -1
  105. package/dist/components/viewers/document-viewer/document-viewer.class.js +7 -1
  106. package/dist/components/viewers/document-viewer/registry.d.ts +12 -3
  107. package/dist/components/viewers/geojson-view/geojson-view.class.d.ts +13 -5
  108. package/dist/components/viewers/geojson-view/geojson-view.class.js +1 -1
  109. package/dist/components/viewers/geojson-view/geojson-viewer.class.d.ts +3 -2
  110. package/dist/custom-elements-jsx.d.ts +1 -1
  111. package/dist/events.d.ts +644 -1242
  112. package/dist/internal/anchored-overlay-runtime.d.ts +2 -2
  113. package/dist/internal/cell-range.d.ts +0 -6
  114. package/dist/internal/cell-range.js +1 -1
  115. package/dist/internal/css-token-length.d.ts +7 -0
  116. package/dist/internal/css-token-length.js +1 -0
  117. package/dist/internal/default-strings.generated.d.ts +1 -1
  118. package/dist/internal/default-strings.generated.js +1 -1
  119. package/dist/internal/deferred-focus-return.d.ts +7 -4
  120. package/dist/internal/deferred-focus-return.js +1 -1
  121. package/dist/internal/dev-mode-attribute-warning.d.ts +35 -0
  122. package/dist/internal/dev-mode-attribute-warning.js +1 -1
  123. package/dist/internal/localization-runtime.js +1 -1
  124. package/dist/internal/localization-types.d.ts +1 -1
  125. package/dist/internal/localization.js +2 -2
  126. package/dist/internal/package-metadata.d.ts +1 -1
  127. package/dist/internal/package-metadata.js +1 -1
  128. package/dist/internal/papaparse-loader.d.ts +1 -1
  129. package/dist/internal/papaparse-loader.js +1 -1
  130. package/dist/internal/positioner.d.ts +10 -0
  131. package/dist/internal/positioner.js +1 -1
  132. package/dist/internal/shared-unions.d.ts +2 -1
  133. package/dist/internal/slotted-overlay-controller.d.ts +20 -0
  134. package/dist/internal/slotted-overlay-controller.js +1 -0
  135. package/dist/internal/text-quote.d.ts +1 -1
  136. package/dist/internal/text-quote.js +1 -1
  137. package/dist/internal/theme-observation.d.ts +4 -0
  138. package/dist/internal/theme-observation.js +1 -0
  139. package/dist/internal/theme-watcher.js +1 -1
  140. package/dist/localization.d.ts +6 -1
  141. package/dist/localization.js +1 -1
  142. package/dist/lyra.d.ts +1 -1
  143. package/dist/lyra.js +1 -1
  144. package/dist/svelte.d.ts +1 -1
  145. package/dist/testing/lyra-tag-event-map.js +1 -1
  146. package/dist/translations/ar/agent-tools.js +1 -1
  147. package/dist/translations/cs/agent-tools.js +1 -1
  148. package/dist/translations/da/agent-tools.js +1 -1
  149. package/dist/translations/de/agent-tools.js +1 -1
  150. package/dist/translations/de-CH/agent-tools.js +1 -1
  151. package/dist/translations/es/agent-tools.js +1 -1
  152. package/dist/translations/fa/agent-tools.js +1 -1
  153. package/dist/translations/fi/agent-tools.js +1 -1
  154. package/dist/translations/fr/agent-tools.js +1 -1
  155. package/dist/translations/he/agent-tools.js +1 -1
  156. package/dist/translations/hi/agent-tools.js +1 -1
  157. package/dist/translations/hr/agent-tools.js +1 -1
  158. package/dist/translations/hu/agent-tools.js +1 -1
  159. package/dist/translations/id/agent-tools.js +1 -1
  160. package/dist/translations/it/agent-tools.js +1 -1
  161. package/dist/translations/ja/agent-tools.js +1 -1
  162. package/dist/translations/kk/agent-tools.js +1 -1
  163. package/dist/translations/ko/agent-tools.js +1 -1
  164. package/dist/translations/nb/agent-tools.js +1 -1
  165. package/dist/translations/nl/agent-tools.js +1 -1
  166. package/dist/translations/nn/agent-tools.js +1 -1
  167. package/dist/translations/pl/agent-tools.js +1 -1
  168. package/dist/translations/pt-BR/agent-tools.js +1 -1
  169. package/dist/translations/pt-PT/agent-tools.js +1 -1
  170. package/dist/translations/pt-PT/charts.js +1 -1
  171. package/dist/translations/pt-PT/conversation.js +1 -1
  172. package/dist/translations/pt-PT/data.js +1 -1
  173. package/dist/translations/pt-PT/forms.js +1 -1
  174. package/dist/translations/pt-PT/layout.js +1 -1
  175. package/dist/translations/pt-PT/media.js +1 -1
  176. package/dist/translations/pt-PT/retrieval.js +1 -1
  177. package/dist/translations/pt-PT/shared.js +1 -1
  178. package/dist/translations/pt-PT/utility.js +1 -1
  179. package/dist/translations/pt-PT/viewers.js +2 -2
  180. package/dist/translations/ro/agent-tools.js +1 -1
  181. package/dist/translations/ru/agent-tools.js +1 -1
  182. package/dist/translations/sl/agent-tools.js +1 -1
  183. package/dist/translations/sv/agent-tools.js +1 -1
  184. package/dist/translations/tr/agent-tools.js +1 -1
  185. package/dist/translations/uk/agent-tools.js +1 -1
  186. package/dist/translations/zh-CN/agent-tools.js +1 -1
  187. package/dist/translations/zh-TW/agent-tools.js +1 -1
  188. package/dist/utilities/index.d.ts +1 -1
  189. package/dist/utilities/index.js +1 -1
  190. package/dist/utilities/localization.d.ts +28 -10
  191. package/dist/vue.d.ts +1 -1
  192. package/llms/components/lr-accordion-item.md +2 -344
  193. package/llms/components/lr-accordion.md +2 -344
  194. package/llms/components/lr-app-rail-group.md +2 -721
  195. package/llms/components/lr-app-rail-item.md +2 -721
  196. package/llms/components/lr-app-rail.md +29 -4
  197. package/llms/components/lr-bar-chart.md +2 -87
  198. package/llms/components/lr-box-plot.md +1 -1
  199. package/llms/components/lr-breadcrumb-item.md +2 -67
  200. package/llms/components/lr-bubble-chart.md +2 -87
  201. package/llms/components/lr-button.md +7 -0
  202. package/llms/components/lr-chart.md +4 -5
  203. package/llms/components/lr-chip-group.md +2 -272
  204. package/llms/components/lr-conversation-item.md +20 -2
  205. package/llms/components/lr-date-input.md +2 -457
  206. package/llms/components/lr-date-picker.md +6 -2
  207. package/llms/components/lr-document-library.md +3 -3
  208. package/llms/components/lr-document-viewer.md +14 -5
  209. package/llms/components/lr-doughnut-chart.md +2 -87
  210. package/llms/components/lr-dropdown-item.md +2 -306
  211. package/llms/components/lr-dropdown.md +6 -0
  212. package/llms/components/lr-flag.md +9 -1
  213. package/llms/components/lr-flow-node.md +4 -1
  214. package/llms/components/lr-geojson-view.md +3 -90
  215. package/llms/components/lr-geojson-viewer.md +13 -5
  216. package/llms/components/lr-icon-button.md +4 -1
  217. package/llms/components/lr-icon.md +14 -6
  218. package/llms/components/lr-input.md +9 -2
  219. package/llms/components/lr-lite-chart.md +17 -9
  220. package/llms/components/lr-map.md +87 -2
  221. package/llms/components/lr-markdown-core.md +5 -4
  222. package/llms/components/lr-markdown.md +4 -2
  223. package/llms/components/lr-media-card.md +10 -5
  224. package/llms/components/lr-menu-item.md +2 -306
  225. package/llms/components/lr-menu.md +9 -6
  226. package/llms/components/lr-menubar-item.md +2 -125
  227. package/llms/components/lr-message-actions.md +4 -1
  228. package/llms/components/lr-message-parts.md +14 -5
  229. package/llms/components/lr-mutation-observer.md +12 -6
  230. package/llms/components/lr-navigation-menu-item.md +2 -237
  231. package/llms/components/lr-number-input.md +4 -0
  232. package/llms/components/lr-option.md +2 -712
  233. package/llms/components/lr-pie-chart.md +2 -87
  234. package/llms/components/lr-polar-area-chart.md +2 -87
  235. package/llms/components/lr-popover.md +10 -0
  236. package/llms/components/lr-prompt-input.md +5 -1
  237. package/llms/components/lr-radar-chart.md +2 -87
  238. package/llms/components/lr-reorder-item.md +2 -157
  239. package/llms/components/lr-result-field.md +2 -117
  240. package/llms/components/lr-retrieval-results.md +3 -1
  241. package/llms/components/lr-rubric-form.md +3 -2
  242. package/llms/components/lr-scatter-chart.md +2 -87
  243. package/llms/components/lr-selection-toolbar.md +4 -2
  244. package/llms/components/lr-source-card.md +2 -186
  245. package/llms/components/lr-source-picker.md +3 -1
  246. package/llms/components/lr-sparkline.md +9 -4
  247. package/llms/components/lr-split-panel.md +5 -3
  248. package/llms/components/lr-stat.md +4 -3
  249. package/llms/components/lr-streaming-text-core.md +7 -5
  250. package/llms/components/lr-streaming-text.md +9 -6
  251. package/llms/components/lr-swatch-picker.md +130 -0
  252. package/llms/components/lr-tag.md +2 -177
  253. package/llms/components/lr-thread-list.md +27 -4
  254. package/llms/components/lr-timeline-item.md +2 -120
  255. package/llms/components/lr-toast-item.md +2 -223
  256. package/llms/components/lr-tool-call-block.md +12 -3
  257. package/llms/components/lr-tool-call-chip.md +12 -6
  258. package/llms/components/lr-tool-param-form.md +3 -2
  259. package/llms/components/lr-tool-result-dialog.md +7 -3
  260. package/llms/components/lr-tool-result-view.md +4 -3
  261. package/llms/components/lr-tool-timeline.md +12 -5
  262. package/llms/components/lr-tree-item.md +2 -354
  263. package/llms/index.md +22 -4
  264. package/llms/migration.md +75 -1
  265. package/llms/peers.md +1 -1
  266. package/llms/shared.md +86 -39
  267. package/llms-full.txt +739 -163
  268. package/package.json +6 -6
  269. package/registrations.json +1 -1
  270. package/vscode-css-data.json +1 -1
  271. package/vscode-html-data.json +1 -1
  272. package/web-types.json +1 -1
@@ -9,716 +9,6 @@
9
9
  - **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
10
10
  - **Deprecations** none
11
11
  - **Optional peers** none
12
- - **Themeable via** 7 parts, 7 custom properties — see this component's own `@csspart`/`@cssprop` list below
13
- - **Documented with** `lr-combobox` (same section below)
12
+ - **Themeable via** 7 parts, 7 custom properties — see `lr-combobox.md`
13
+ - **Documented with** `lr-combobox`: see [lr-combobox.md](./lr-combobox.md).
14
14
  - **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`
15
-
16
- ---
17
-
18
- ## `lr-combobox` / `lr-option`
19
-
20
- Filterable single/multi-select combining a text input with a listbox. Mirrors the core
21
- `<wa-combobox>` API under the `lr-` prefix. **Form-associated** (hand-rolled internals, not the
22
- shared `FormAssociated` mixin — see gotchas).
23
-
24
- Consumer writes on mounted options immediately update the owning picker and its submission,
25
- without changing the reset default or emitting picker input/change events. Owner synchronization
26
- does not echo as a consumer selected write.
27
-
28
- A mounted `option.selected` assignment updates the live picker value and form submission
29
- immediately, including deselection and equal-value writes; it emits no user input/change event.
30
-
31
- When `multiple` becomes false, the public value, live option flags, and popup `aria-selected` expose
32
- one selected occurrence. The retained multiple-selection history returns if multiple is enabled
33
- again without another selection write.
34
-
35
- Composing keyboard events (`isComposing` or legacy key code 229) remain with filter editing; they do
36
- not navigate, select/create a value, or dismiss the popup.
37
-
38
- Source options with `inert`, including inherited inertness, are unavailable through the popup. Live
39
- inert changes refresh row availability. Text, insertion, replacement, removal, slot
40
- reassignment/removal, and relevant attributes of `start`/`end`/`prefix`/`suffix` adornments refresh
41
- the corresponding cloned presentation; unchanged presentation retains its clone identity while
42
- filtering.
43
-
44
- Host `aria-describedby` resolves external descriptions onto the native combobox filter before local
45
- error/hint guidance. References track missing targets, replacement, removal/reinsertion, reconnect,
46
- and adoption. Removing `label`, `hint`, or `error-text` safely omits the content while leaving
47
- removed string properties at their native `null` readback; explicit empty strings remain supplied
48
- empty strings.
49
-
50
- **First-interaction registration.** Where initial-route weight is stricter than a static combobox
51
- registration allows, keep a labelled native `<input list>` as the working pre-JavaScript control
52
- and import only the granular combobox registration on its first focus. Copy the native value after
53
- the import resolves so typing that happens while the chunk is in flight is not lost, then transfer
54
- focus explicitly — the browser does not replay the focus event after custom-element upgrade:
55
-
56
- ```html
57
- <div id="country-fallback">
58
- <label for="country-native">Country</label>
59
- <input id="country-native" name="country" list="country-options">
60
- <datalist id="country-options"><option value="France"></option></datalist>
61
- </div>
62
- <lr-combobox id="country-enhanced" name="country" label="Country" hidden>
63
- <lr-option value="France">France</lr-option>
64
- </lr-combobox>
65
- <script type="module">
66
- const fallback = document.querySelector("#country-fallback");
67
- const input = document.querySelector("#country-native");
68
- const combobox = document.querySelector("#country-enhanced");
69
- let registration;
70
- input.addEventListener("focus", async () => {
71
- if (!combobox.hidden) return;
72
- registration ??= import(
73
- "@aceshooting/lyra-ui/components/forms/combobox/combobox.js"
74
- ).catch((error) => {
75
- registration = undefined; // let a later interaction retry
76
- throw error;
77
- });
78
- await registration;
79
- await customElements.whenDefined("lr-combobox");
80
- combobox.value = input.value;
81
- fallback.hidden = true;
82
- combobox.hidden = false;
83
- combobox.focus();
84
- });
85
- </script>
86
- ```
87
-
88
- Leave the native control in place if registration fails. This pattern preserves the initial shell;
89
- the full form-label, option, overlay, and first-open positioning contracts arrive in deferred
90
- chunks instead of being weakened in a separate partial combobox implementation.
91
-
92
- An `lr-option` row remains bounded by its owning listbox: the default label ellipsizes and each
93
- `start`/`end` (or `prefix`/`suffix`) adornment is capped at 40% of the row, where a long slotted text adornment truncates with an
94
- ellipsis. Unbroken metadata
95
- therefore cannot widen a 320px LTR or RTL picker.
96
-
97
- **Adornments in the popup (fixed in 11.0.0).** Before 11.0.0 this paragraph described behavior the
98
- code did not have: `lr-combobox` builds its popup from normalized row *data* rather than from the
99
- light-DOM nodes, so a slotted `start`/`end`/`prefix`/`suffix` adornment had nowhere to land and
100
- simply never rendered — the documented slots and their documented parts were both dead inside the
101
- one component `lr-option` exists to feed. They now render. The nodes are **cloned** into the row
102
- (`option-start` / `option-end` parts, inert and `aria-hidden`, so they never join the option's
103
- accessible name), which means the author's own `<lr-option>` subtree is left exactly where they put
104
- it rather than being moved into a shadow root as a side effect of opening a dropdown:
105
-
106
- ```html
107
- <lr-combobox label="Country">
108
- <lr-option value="fr"><lr-flag slot="start" country="fr"></lr-flag>France</lr-option>
109
- <lr-option value="mt"><lr-flag slot="start" country="mt"></lr-flag>Malta</lr-option>
110
- </lr-combobox>
111
- ```
112
-
113
- An async `source` row can carry the same two fields (`start`, `end`) alongside its existing `icon`.
114
-
115
- ### `lr-combobox`
116
-
117
- **Properties:**
118
-
119
- - `multiple: boolean = false` (reflected)
120
- - `size: LyraSize = 'm'` (reflected — the shared control ladder, so both `2xs`/`xs`/`s`/`m`/`l`/`xl`
121
- and the `small`/`medium`/`large` spellings are accepted; also scales the "+N" overflow tag and
122
- decorative expand icon; `size="s"` shares its outer control height with `lr-input`, `lr-select`,
123
- and `lr-segmented` without part overrides)
124
- - `pill: boolean = false` (reflected) — rounds the trigger row's corners to a full pill, mirroring
125
- `lr-input`'s own `pill`. It changes the private radius default to `--lr-radius-pill`, so an
126
- inherited or direct `--lr-combobox-radius` remains authoritative
127
- - `placeholder: string = ''`
128
- - `disabled: boolean = false` (reflected)
129
- - `required: boolean = false` (reflected — enforced via `internals.setValidity()`; also reflected as
130
- `aria-required` on `<input part="combobox-input">` immediately. That semantic input exposes
131
- `aria-invalid="true"` whenever visible error chrome is present, or after interaction while
132
- intrinsic/custom validity fails; it explicitly returns to `"false"` when neither applies)
133
- - `name: string = ''`
134
- - `label: string = ''`
135
- - `hint: string = ''`
136
- - `errorText: string = ''` (attribute `error-text` — static error copy shown below the hint;
137
- overridden by slotted `error` content when provided)
138
- - `open: boolean = false` (reflected)
139
- - `allowCreate: boolean = false` (attribute `allow-create`) — a nonmatching query renders a
140
- localized create row. Activating it emits cancelable `lr-create`; unless vetoed, the component
141
- appends a real `<lr-option>` and selects it (also supported in `multiple` mode)
142
- - `allowCustomValue: boolean = false` (attribute `allow-custom-value`) — single-select only;
143
- commits arbitrary text on Enter without creating an option
144
- - `showUnknownOption: boolean = false` (attribute `show-unknown-option`, reflected) — appends every
145
- committed value that no option or async row claims to the end of the listbox as a synthetic,
146
- badged, keyboard-reachable, re-selectable row. Off by default. The synthetic row is filtered by
147
- the active query exactly like the `allow-create` row is, so a query it does not match neither
148
- shows it nor suppresses the "no matches" copy; re-picking it re-commits the same value and
149
- deliberately does **not** reclassify it as known — the badge and the row both survive, and the row
150
- never appears in `selectedRows`
151
- - `getUnknownLabel?: (value: string) => string` (attribute: false) — renders the label for a
152
- committed value that matches no option or async row, everywhere it appears (trigger, `multiple`
153
- tag, synthetic row). `getTag` cannot serve this case: it is handed a matched option and there is
154
- none. A blank return falls back to the raw value. Not consulted while an async `source` fetch has
155
- never yet resolved for this element — see "Unknown committed values" below; `loadingText` covers
156
- that window instead, since the value is not yet known to be unmatched at all
157
- - `loading: boolean = false` (reflected) — mirrors `lr-select`'s own `loading`: whether a
158
- committed value's real label may still be pending because its `<lr-option>` catalog hasn't
159
- arrived yet (e.g. mounted asynchronously with no `source` involved). While `true`, a committed
160
- value that currently matches no option/row renders the localized `loadingText` placeholder in the
161
- trigger label or the relevant `multiple` tag instead of the raw value, with no
162
- `notInCatalog`/`[part='unknown-value']` badge, and the open listbox shows the same loading row a
163
- `source` fetch in flight shows. Independent of `source`'s own async lifecycle — either condition
164
- alone suppresses the unknown-value presentation. Never mutates `value`/`selectedOptions` itself,
165
- and does not itself disable the trigger
166
- - `appearance: 'accent' | 'filled' | 'outlined' | 'filled-outlined' | 'plain' = 'outlined'`
167
- (reflected) — the library's shared field-surface vocabulary, matching `lr-select`'s trigger.
168
- `outlined` (the default) is a bordered surface; `filled` swaps the border for a raised fill;
169
- `filled-outlined` keeps both; `plain` drops both; `accent` paints the loud brand fill with
170
- on-brand text (the placeholder, expand icon, adornments and `multiple` tags all ride that
171
- on-brand color rather than the quiet-text tokens). An unsupported value, including a raw
172
- attribute/property write outside this type, clamps to the `'outlined'` default
173
- - `placement: 'top' | 'bottom' = 'bottom'` (reflected; flip/shift can still keep the listbox in view)
174
- - `positioningStrategy: PlaceStrategy = 'fixed'` (attribute `positioning-strategy`, reflected) —
175
- the CSS positioning scheme the listbox is laid out with, spelled the same as on `lr-select`,
176
- `lr-popover`, `lr-dropdown`, `lr-tooltip` and `lr-color-picker`. `fixed` is this control's
177
- default and what it has always rendered: it positions against the viewport and escapes most
178
- clipping ancestors, which suits a typeahead list that usually sits inside a scrollable region;
179
- under a transformed, filtered or contained ancestor the listbox is promoted into the browser top
180
- layer where the native Popover API exists (otherwise, as before, that ancestor clips it).
181
- `absolute` positions against the nearest containing block and scrolls with it. An unsupported
182
- value resolves to the default. Like `placement`, a change takes effect the next time the listbox
183
- opens. When the instance sets nothing, the cascading `--lr-positioning-strategy` custom property
184
- (`absolute`/`fixed`, set on `:root`, a theme, or one clipping ancestor) is honoured ahead of the
185
- default; an explicit instance value always wins. There is deliberately no `hoist` alias here:
186
- unlike on `lr-select`, where it is Shoelace's established spelling, it would be a boolean
187
- defaulting to `true`, so its attribute could only ever express the value the control already has
188
- - `sync?: PlaceSync` (reflected) — copies the trigger's width, height, or both onto the listbox,
189
- spelled the same as on `lr-dropdown`/`lr-popup`/`lr-popover`. Unset (the default), the listbox
190
- sizes to its own content, clamped between `--lr-size-12rem` and `--lr-size-28rem`, exactly as
191
- before. `sync="width"` drops that content-based clamp so a full-width trigger with short option
192
- labels gets a listbox that aligns to its own edges instead of floating narrower in the middle.
193
- A synced listbox is capped on `--lr-positioner-available-inline-size` **alone** — the space the
194
- positioner actually measured beside the anchor, which still keeps an over-wide trigger from
195
- pushing it off-screen. `--lr-popover-viewport-clamp` does **not** apply to a synced listbox — its
196
- 92vw default used to shorten the listbox against its own trigger in exactly the full-width case
197
- `sync` exists for, and `lr-popup` never applied it either. It still applies with `sync` unset.
198
- Like `placement`, a change takes effect the next time the listbox opens
199
- - `clearable: boolean = false` (reflected) — displays the clear button while there is something to
200
- clear on **either** axis this control owns: a committed selection, or _visible_ filter text. See
201
- "the clear button covers two axes" below
202
- - `withClear: boolean = false` (attribute `with-clear`) — Web Awesome's spelling of `clearable`;
203
- either one enables the same clear button. Not deprecated: Web Awesome names this attribute
204
- `with-clear` and Shoelace names it `clearable`, so honouring both is what keeps a mechanical tag
205
- rename from silently dropping the control
206
- - `withLabel: boolean = false` and `withHint: boolean = false` (attributes `with-label` and
207
- `with-hint`) — SSR slot-presence hints
208
- - `getTag: ((option: LyraOption, index: number) => unknown) | undefined` (attribute: false) —
209
- replaces a built-in multiple-selection tag with consumer Lit/DOM/text output; strings render as
210
- text, never as HTML
211
- - `validators: LyraComboboxValidator[] = []` (attribute: false) — extra JavaScript validators run
212
- after the intrinsic `required` constraint, the same contract `lr-date-input` implements. Each
213
- entry may be a `(value, input) => void | boolean | string | ValidityStateFlags` function (`value`
214
- is the live `string | string[]`), an object with `validate(value, input)` returning that same
215
- vocabulary, or a Web Awesome-compatible object with `checkValidity(input)` returning
216
- `{ isValid, message, invalidKeys }`, where `invalidKeys` names `ValidityState` flags; that object
217
- may also expose `observedAttributes` and a string or callback `message`. Changing any listed host
218
- attribute revalidates automatically. `isValid: true` (or `true`/`undefined` from a function)
219
- passes; otherwise the listed flags are set (`customError` when the list maps to nothing) and the
220
- returned message wins over the validator-level fallback. A throwing validator fails closed with
221
- the localized generic message. `disabled` bars them exactly as it bars `required`
222
- - `validationTarget: HTMLElement | undefined` — writable native-validity focus anchor. After the
223
- first render it defaults to the internal filter input; assign another element to override it, or
224
- assign `undefined` to restore that input. It is `undefined` before the input exists
225
- - `autocomplete: string = 'off'`, `inputMode: string = ''` (attribute `inputmode`),
226
- `enterKeyHint: string = ''` (attribute `enterkeyhint`), `spellcheck: boolean = false`,
227
- `autocapitalize: string = ''`, and `autocorrect: boolean = true` (attribute values `on`/`off`) —
228
- native editing-assistance properties forwarded to the internal filter input. Removing a
229
- `spellcheck` attribute after an override restores this component's declared `false` default.
230
- The lowercase
231
- mapped IDLs `inputmode` and `enterkeyhint` delegate to the corresponding camel-case native
232
- properties
233
- - `inputValue: string` — the live filter input text; programmatic writes are event-silent
234
- - `maxOptionsVisible: number = 3` (attribute `max-options-visible` — caps how many selected **tags**
235
- show before collapsing to `+N`; nothing to do with the suggestion list, see the three-caps note
236
- below)
237
- - `visibleOptions?: number` (attribute `visible-options`, new in 11.0.0) — bounds the popup to about
238
- this many suggestion rows, leaving the rest reachable by scrolling. Purely presentational: every
239
- row is still rendered. Measured from where row N actually starts rather than computed from a
240
- token, because a row's height varies with `sub` lines, adornments and group labels. Unset imposes
241
- no bound of its own and the listbox keeps exactly its previous max-height behavior; zero,
242
- negative, and non-finite values normalize to unset rather than collapsing the popup
243
-
244
- **The three caps, which are easy to confuse.** `visibleOptions` caps how many suggestion rows are
245
- *visible* (presentation; the rest scroll). `maxRender` caps how many suggestion rows are *rendered
246
- at all* (performance; the rest do not exist and are summarized by `option-overflow`).
247
- `maxOptionsVisible` caps how many *selected tags* show in multi-select and never touches the
248
- suggestion list.
249
- - `emptyText?: string` (attribute `empty-text`) — omission displays localized `noMatches` (`"No
250
- matches"` in the built-in English locale); any supplied string, including `''`, renders verbatim
251
- - `loadingText?: string` (attribute `loading-text`) — shown while a `source` fetch is in flight,
252
- and in place of the raw value on the trigger/tag for a committed value the fetch has never yet
253
- resolved (see "Unknown committed values" below); omission displays localized `loading`
254
- (`"Loading…"` in English), while any supplied string, including `''`, renders verbatim
255
- - `overflowText?: string` (attribute `overflow-text`) — shown when `maxRender` caps the rows;
256
- omission displays localized `comboboxOverflow` (`"+{n} more — refine your search"` in English).
257
- A supplied template wins verbatim over `.strings`, including when it equals that English
258
- template or is empty; `{n}` is still replaced with the locale-formatted hidden count
259
- - `filter: OptionFilter | null = null` (attribute: false — `(option, query) => boolean`; default
260
- matches `label`/`searchText` case-insensitively; ignored while `source` is set)
261
- - `source: ComboboxSource | null = null` (attribute: false — `(query: string, options: { signal:
262
- AbortSignal; limit: number }) => Promise<readonly ComboboxSourceRow[] | { rows, total? }>`; when set, replaces the light-DOM `<lr-option>`
263
- list with an async lookup, debounced by `sourceDelay` ms after each keystroke and re-run on
264
- clear/pick. Forward `options.signal` to `fetch(url, { signal })` to cancel the request when a
265
- newer query supersedes it or the element disconnects. `loadingText` is shown while a call is in
266
- flight; a stale in-flight call that resolves after a newer one (or after disconnect) is dropped
267
- via a monotonic token. The exported type requires the `options` parameter; an existing
268
- one-parameter `(query) => …` function remains assignable under TypeScript's ordinary function
269
- parameter compatibility, but consumers that need cancellation should accept and forward
270
- `options.signal`; honor `options.limit` when practical and return `{ rows, total }` to report the
271
- provider-side match count. The component independently clone-normalizes the result, retains at
272
- most 2,000 rows/250,000 aggregate text units, skips malformed or hostile rows, and never trusts a
273
- provider to enforce the ceiling. A current rejection clears stale rows and renders a localized disabled
274
- listbox row; that visible row is not a shadow live region. The same localized message is appended
275
- to `[data-lr-live-region="assertive"]` in the document for each fresh post-mount rejection,
276
- including an identical retry, while raw caught error text stays out of the UI.)
277
- - `sourceDelay: number = 200` (attribute `source-delay` — debounce in ms between the last keystroke
278
- and the `source` call; `0` fires on every keystroke. Sanitized to a finite non-negative duration,
279
- falling back to `200` for a non-finite value)
280
- - `maxRender: number = 200` (attribute `max-render` — caps how many rows render at once, always
281
- keeping the current selection visible even if it's outside the cap; the excess renders as one
282
- `overflowText` row instead of being dropped silently. See "Large option lists" below for how to
283
- size it, and when `source` is the better answer. Runtime writes are capped at 1,000)
284
- - `sourceTotal: number` (read-only) — provider-side total for the latest accepted response
285
- - `sourceTruncated: boolean` (read-only) — whether the response reported or contained more rows
286
- than the bounded retained snapshot; the overflow row includes that hidden count
287
- - `value: string | string[]` — a getter/setter: plain `string` in single mode, `string[]` in
288
- `multiple` mode. Assigning `undefined`/`null` clears the selection; every string, including `''`,
289
- is instead a candidate value resolved against the current local options/async rows — an
290
- `<lr-option value="">` (or a matching async row) is legitimate and round-trips like any other
291
- value. A value matching no current option/row still commits rather than being dropped or treated
292
- as a clear — see "Unknown committed values" below
293
- - `customError: string | null` (attribute `custom-error`) — reflected consumer validation message
294
- - `selectedRows` (read: `ComboboxSourceRow[]`; write: `readonly ComboboxSourceRow[]`) — structured
295
- rows for the current selection, including any opaque `data` payload supplied by an async source
296
- row or a light-DOM `<lr-option data>`, reached by reference and never deep-cloned.
297
- Reads return detached row snapshots. Writes select stable row values resolved against the
298
- current or deferred source, dropping duplicate and detached values without emitting selection
299
- events. Selected async rows remain available after the query changes or a later source result
300
- no longer contains them
301
- - `selectionStart`, `selectionEnd`, and `selectionDirection` — selection getters/setters forwarded
302
- to the internal input
303
-
304
- **Unknown committed values.** A committed value matching no current option/row (a stale value from
305
- a removed option, or a programmatic assignment with a typo) still commits — the raw string stays
306
- fully reachable through `value`/`selectedRows` — but renders a dashed/italic
307
- `[part='unknown-value']` badge next to the closed single-select input, or on the relevant
308
- `multiple`-mode tag, instead of an unexplained bare label, mirroring `<lr-model-select>`'s synthetic
309
- "not in catalog" stale-value row — see `--lr-combobox-unknown-value-border-style`/`-color` below.
310
- The badge is suppressed while an async `source` fetch has never yet resolved for this element —
311
- from mount through the debounce delay and the in-flight call itself — and never shown for an
312
- `allowCustomValue` commit, which is a sanctioned unmatched value, not a stale one. Over that same
313
- unresolved window the raw value itself is also withheld: the trigger (and any `multiple`-mode tag
314
- for the same value) shows the `loadingText` placeholder instead, so a value seeded before its async
315
- catalogue has ever answered never flashes an unexplained, unbadged machine key. Once the fetch
316
- settles — success or failure — the raw value returns, badged if it still matches nothing.
317
-
318
- **Methods:** `focus(options?)`, `blur()`, `select()`, `setSelectionRange()`, and `setRangeText()`
319
- forward to the internal input. `setRangeText()` synchronizes the filter query and visible options.
320
- `show(): Promise<void>` and `hide(): Promise<void>` settle after `lr-after-show` and
321
- `lr-after-hide`, respectively. `refresh(): void` re-runs the current `source` query without changing
322
- the source's identity, its debounce controller, or its delay. Reassigning `source` is a provider
323
- change and clears the fetched rows and pending-selection cache; `refresh()` does not. It queues for
324
- the next open while the listbox is closed, and is a no-op without a `source`. `resetValidity()`
325
- clears consumer custom validity and restores the current intrinsic constraints. `getForm()` returns the owning form, including an external owner
326
- selected by the `form` attribute.
327
- `setCustomValidity(message)` carries a rejection no client-side constraint can express ("that option
328
- is no longer available"): a non-empty message raises `customError`, becomes `validationMessage`, and
329
- blocks submission; `''` clears it and restores the control's own computed validity, so a `required`
330
- combobox with nothing chosen goes back to `valueMissing` rather than to valid. The message survives
331
- every selection change and a `form.reset()` — like a native control, only another
332
- `setCustomValidity('')` or `resetValidity()` clears it — and is used verbatim, never localized.
333
-
334
- **8.0 migration:** the former camel-case string property `autoCorrect` is not retained as a public
335
- alias. Set the boolean `autocorrect` IDL, or use `autocorrect="on"` / `autocorrect="off"` in markup.
336
-
337
- `ComboboxSourceRow = { readonly value: string; readonly label: string; readonly sub?: string; readonly icon?: unknown; readonly start?: unknown;
338
- readonly end?: unknown; readonly badge?: string |
339
- number; accessibleLabel?: string; data?: unknown; dotColor?: string; group?: string; disabled?:
340
- boolean }` — the row shape used by the async `source` path. `start` and `end` (new in 11.0.0) are
341
- the async counterparts of `<lr-option>`'s `start`/`end` adornment slots and render as the
342
- `option-start` / `option-end` parts, inert and aria-hidden exactly like `icon`. A string or number `start`/`end` truncates with an ellipsis when it outgrows the part; template content should render an element at its top level to do the same. `icon` renders as a decorative leading
343
- visual whose rendered subtree stays visible but is inert and hidden from assistive technology;
344
- put independent actions outside it. `badge` renders as trailing metadata, `accessibleLabel` can
345
- provide richer spoken text than the visible label, and `data` is retained without being rendered
346
- for retrieval through `selectedRows`.
347
- `dotColor` accepts a valid CSS `color`; invalid values, declaration-breaking input, and `url()`
348
- render a transparent dot.
349
- The light-DOM `<lr-option>` path normalizes its supported label/sub/dot/group/data fields to the
350
- same internal row model — `<lr-option data>` is the light-DOM counterpart of an async row's own
351
- `data` field, reached by reference through `selectedRows` exactly the same way.
352
-
353
- When a local option is removed or becomes disabled, or an async response shrinks, an existing
354
- keyboard-active row clamps to the nearest enabled survivor. If every row is disabled or removed,
355
- `aria-activedescendant` clears; an untouched list with no active row remains untouched.
356
-
357
- **Events:** typing in the filter exposes the original bubbling/composed, non-cancelable `InputEvent`
358
- as exactly one host `input` event (no `value` detail) and does not fire `change`. An actual user
359
- selection mutation — pointer or keyboard selection, multiple-value toggle, tag/Backspace removal, or
360
- clear — emits exactly one bubbling/composed, non-cancelable `input` `CustomEvent`, immediately
361
- followed by the same shape of `change`, then a prefixed `lr-change` alias. All three carry
362
- `detail: { value; data: readonly unknown[] }` — `value` is the new committed selection (a string in
363
- single mode, a `string[]` in `multiple` mode); `data` is index-aligned with `value`: `data[i]`
364
- describes `value[i]` — the opaque `data` payload of a light-DOM `<lr-option data>` or an async
365
- source row's own `data`, reached by reference and never deep-cloned — or `undefined` in that
366
- value's own slot when it currently matches no live row/option (see "Unknown committed values"
367
- above; unlike `selectedRows`, which drops that entry instead). `lr-change` mirrors `<lr-checkbox>`'s namespaced alias; subscribe to it when you
368
- want a `lr-`-prefixed event, or to the native-style `input`/`change` for parity with a native
369
- control. Re-picking the current single value and programmatic/default/reset/restore writes are
370
- silent (including on `lr-change`). The clear button emits one `lr-clear` after its
371
- `input`/`change`/`lr-change` triple.
372
- `lr-activate` (`detail: { value: string }`, bubbling/composed, non-cancelable) fires on **every**
373
- activation of an available listbox row — a click, or Enter on the active row — whether or not the
374
- selection actually moved. Its `value` is the activated option's own value, **always a single
375
- string**, even in `multiple` mode, where the `input`/`change`/`lr-change` triple carries the whole
376
- `string[]` instead. It reports that the user picked a row and gates nothing. Use it for the
377
- single-select repeat pick that `change`/`lr-change` deliberately stay silent for — "re-run that
378
- filter" is a real intent — which is otherwise unobservable, because the rows live in this shadow
379
- root, so a retargeted `click` names no option and a keyboard commit produces no click at all. When
380
- an activation _does_ move the selection, `input`/`change`/`lr-change` are emitted first, so either
381
- listener reads the settled selection. Not fired for typing, for a committed custom value matching no
382
- row, for the clear button, or for a programmatic `value` assignment.
383
- `lr-filter` (`detail: { value: string }`) reports the in-progress filter text on every user-driven
384
- keystroke — the live as-you-typed search string, deliberately _not_ `value`, which is the committed
385
- selection. It is the supported way to read that text; reaching into the shadow root for
386
- `[part="combobox-input"]`'s value is not. Named `lr-filter` rather than `lr-input` precisely because
387
- `lr-input`'s detail on `<lr-input>` is the committed value, and the two must not share a name while
388
- carrying different strings. It fires for user edits only. Picking a row, `form.reset()`, dismissing
389
- the listbox, and a programmatic `value` write blank the filter silently. `setRangeText()` silently
390
- replaces the requested or selected native text range, synchronizes the resulting query and visible
391
- options, and refreshes an async source when present; it preserves the committed selection.
392
- Activating the clear button is a user edit: when a query existed it emits `lr-filter` with
393
- `value: ''` before the clear transaction finishes.
394
- `lr-show` and `lr-hide` report the start of listbox visibility transitions. `lr-show` is a
395
- cancelable veto point; `lr-hide` is cancelable while connected, but the disconnect-driven close is
396
- non-cancelable because an already-removed control cannot honour a veto. Vetoing a connected close
397
- is atomic: the filter query, active option, async result rows, reflected `open` state, and overlay
398
- ownership remain unchanged, so the host can defer dismissal without reconstructing the search.
399
- `lr-after-show` and
400
- `lr-after-hide` fire when the corresponding transition settles. `lr-create` carries
401
- `detail: { inputValue }` and is also cancelable: preventing it suppresses the default append/select
402
- action so the host can normalize and commit its own option.
403
- The internal input's `focus` and `blur` are relayed exactly once from the host as owner-realm
404
- native `FocusEvent`s. Both bubble, cross the shadow boundary, and preserve `relatedTarget`.
405
- `lr-invalid` (no detail) is emitted once as a bubbling/composed, **cancelable** alias when native
406
- validity fails — see "The validity alias is cancelable in 8.0.0" above.
407
- `lr-source-error` is non-cancelable, `detail: { error, query }` carrying the raw rejection from an
408
- async `source` call plus the exact query string that call was made with (the rejected call's own
409
- query, not necessarily the live `query`/`inputValue`, which may have moved on — or been cleared by
410
- closing the listbox — by the time the rejection settles). The rendered copy stays localized and
411
- never shows the raw error.
412
- `lr-retry` is cancelable; the built-in failed-load action calls `refresh()`, and `preventDefault()`
413
- leaves the failure on screen. While the failure state is the only popup content, the popup swaps
414
- `role="listbox"` for `role="dialog"` (the input gains the matching `aria-haspopup="dialog"` and
415
- drops `aria-activedescendant`, and the popup carries the localized failure heading as its accessible
416
- name). `dialog` is one of the four popup roles WAI-ARIA lets a `role="combobox"` own, so the still
417
- expanded `aria-controls` target keeps a valid owner while holding a retry `button` that is not a
418
- legal listbox child. A successful retry restores `role="listbox"`.
419
-
420
- **The clear button covers two axes, and announces only the one that moved.** A combobox owns both a
421
- committed selection and an in-progress filter query, so the button renders whenever either has
422
- something to clear, and one press clears both:
423
-
424
- - Clearing a selection emits `input`, then `change`, then `lr-change`, then `lr-clear` — and, if the
425
- query was also non-empty, `lr-filter` with an empty `value`.
426
- - A **query-only** clear (nothing selected, just typed text) emits `lr-filter` with an empty
427
- `value` and deliberately **no** `change` and **no** `lr-clear`. There was no selection
428
- transition to report, so announcing one would be a lie. Don't listen for `lr-clear` to detect
429
- "the user emptied the field" — listen for `lr-filter` when you care about the query.
430
- - The query half of the render gate is scoped to states where the query is actually _visible_: an
431
- open listbox in single-select, or any time in `multiple` mode. A closed single-select shows the
432
- selected label rather than the query, so a stale query alone never surfaces a button offering to
433
- clear text the user cannot see.
434
-
435
- **Large option lists: sizing `maxRender`, and why the listbox is not windowed.** Every row that
436
- survives the filter and fits under `maxRender` is a real DOM element — the listbox renders its rows
437
- in full rather than recycling a small window of them across a scroll. That is a deliberate
438
- accessibility constraint, not an omission: the filter input carries `aria-activedescendant`, which
439
- is an **IDREF**, and an IDREF only resolves within its own tree scope. Moving the rows into the
440
- library's windowing primitive (`<lr-virtual-list>`) would place them one shadow root deeper than the
441
- input that has to point at them, where neither the attribute nor its `ariaActiveDescendantElement`
442
- element-reflection replacement can reach — element reflection resolves same-root or upward only. So
443
- `<lr-virtual-list>` stays `role="list"`/`role="listitem"` and is the right tool for feeds and
444
- viewers, not for a listbox whose active row must stay addressable.
445
-
446
- What that means in practice:
447
-
448
- - **Up to a few hundred rows, raise `maxRender` and move on.** A few hundred flex rows is an
449
- unremarkable amount of DOM; a country, currency, or time-zone list (typically 200–450 entries)
450
- renders comfortably with `max-render` set to cover it. The cap exists to bound pathological
451
- cases, not to make lists of that size expensive.
452
- - **Past roughly a thousand rows, reach for `source` instead of a larger cap.** An async source
453
- narrows the candidate set before it ever becomes DOM, which is a categorically better trade than
454
- rendering everything and asking the browser to lay it out. Pair it with `sourceDelay` to debounce.
455
- - **Leave the overflow row doing its job.** When the cap does bite, the excess collapses into one
456
- `overflowText` row (default `"+{n} more — refine your search"`) rather than disappearing
457
- silently, and the current selection is always kept visible even when it falls outside the cap.
458
- Suppressing that row by setting `maxRender` far above the real list size trades a useful "keep
459
- typing" affordance for layout work no user asked for.
460
-
461
- **Slots:** default (`<lr-option>` children), `label`, `hint`, `error` (overrides the `errorText`
462
- attribute when provided), plus two adornment slots:
463
-
464
- - `start` — content at the inline-start of the trigger row, before the selected-value tags and the
465
- filter input. It is decorative chrome, **not** an option: only `<lr-option>` elements in the
466
- default slot are ever collected into the option list.
467
- - `end` — content after the filter input and the built-in clear action, and before the expand icon,
468
- so consumer content never sits outboard of the dropdown chevron.
469
- - `clear-icon` and `expand-icon` replace the corresponding built-in glyphs.
470
- - `source-error` — replaces the built-in failed-`source` state, retry control included. Deliberately
471
- named apart from the form-control `error` slot: they are different failures and a field has to be
472
- able to show both.
473
-
474
- **CSS parts:** `form-control`, `form-control-label`, `label`, `form-control-input`, `combobox`,
475
- `start` and `end` (the two
476
- adornment-slot wrappers, each `hidden` while nothing is slotted into it), `tags`, `tag`,
477
- `tag-overflow` (the "+N" indicator, carried alongside `tag` past `max-options-visible`,
478
- mirroring `lr-select`'s identical two-part overflow chip), `tag-label`, `tag__content`,
479
- `tag__remove-button`, `tag__remove-button__base`, `combobox-input`,
480
- `clear-button`, `unknown-value` (the dashed/italic badge shown next to the closed single-select
481
- input, or on a `multiple`-mode tag, when the committed value matches no current option/row),
482
- `expand-icon`, `listbox`,
483
- `group-label` (the heading of an option group — rows sharing a `group` — named as on `lr-select` and
484
- `lr-emoji-picker` so one rule styles every grouped list; it labels the `role="group"` wrapper here),
485
- `option`,
486
- `option-dot` (the leading status dot, when a row's `dotColor` is set), `option-icon` (the inert,
487
- aria-hidden decorative leading visual for an async row), `option-start` and `option-end` (the inert,
488
- aria-hidden adornments cloned from the source option's `start`/`prefix` and `end`/`suffix` slots, or
489
- from an async row's `start`/`end`), `option-label`, `option-sub` (a row's
490
- secondary line, when `sub` is set), `option-badge` (an async row's trailing metadata),
491
- `option-overflow` (the "+N more" indicator from `maxRender`),
492
- `source-error-row` (the listbox row holding the failed-`source` state), `source-error` (the shared
493
- failed-load state itself, with `source-error-base`, `source-error-icon`, `source-error-heading`,
494
- `source-error-description` and `source-error-actions` forwarded from the composed `<lr-empty>`),
495
- `retry-button`, `error`, `hint`
496
-
497
- **TypeScript:** `LyraCombobox<Multiple extends boolean = boolean>` — `value`/`defaultValue` and the
498
- `lr-change`/native `input` event detail `value` narrow to `string` when `Multiple` is `false` and
499
- `string[]` (`readonly string[]` in a detail) when `true`. Types only; the runtime and the mirrored
500
- surface are unchanged, and an untyped `<lr-combobox>` keeps `string | string[]`. Combobox has no
501
- dedicated `lr-input` custom event (unlike `lr-select`); the exported `LyraComboboxChangeEvent`/
502
- `LyraComboboxInputEvent` aliases type `lr-change` and the native `input` listener respectively.
503
-
504
- **The required marker.** `required` with a non-empty `label` paints the library's shared marker on
505
- `[part="form-control-label"]` — the one `::after` rule described above, not a copy of it, so
506
- `--lr-form-control-required-content`, `--lr-form-control-required-color` and
507
- `--lr-form-control-required-offset` retune or suppress it here exactly as they do on `lr-input`.
508
- With no label text the part is hidden and no glyph is painted.
509
-
510
- **Themeable custom properties:** `--lr-combobox-trigger-padding`,
511
- `--lr-combobox-trigger-min-height`, `--lr-combobox-font-size`, `--lr-combobox-tag-padding`,
512
- `--lr-combobox-tag-font-size`, `--tag-max-size` (default `var(--lr-size-5rem)`), `--show-duration`,
513
- `--hide-duration`, and `--lr-combobox-expand-size` (the decorative icon box; each
514
- standard size supplies an aligned default), plus shared tokens. `--lr-combobox-gap` (default
515
- `--lr-space-xs`, the gap inside `[part='combobox']`) and `--lr-combobox-radius` (default
516
- `--lr-radius`, its corner radius) are both retunable without a `::part(combobox)` rule but, unlike
517
- the properties above, do not vary by `size` — the same `--lr-button-gap`/`-radius` pattern.
518
- `--lr-combobox-tag-bg` (default `var(--lr-color-brand-quiet)`), `--lr-combobox-tag-color` (default
519
- `var(--lr-color-text)`) and `--lr-combobox-tag-radius` (default `var(--lr-radius)`) retint a
520
- selected tag's background, text color and corner radius without a `::part(tag)` rule; `-tag-bg` is
521
- scoped independently of `--lr-combobox-option-active-bg`/`-option-badge-bg` below, which already
522
- scope the same shared `--lr-color-brand-quiet` token for their own purposes.
523
-
524
- The trigger row's own surface became public in 16.0.0, having been a private pair until then:
525
- `--lr-combobox-fill` (default `var(--lr-color-surface)`) and `--lr-combobox-border-color` (default
526
- `var(--lr-color-border)`). The `filled`/`filled-outlined` treatments default the fill to
527
- `var(--lr-color-surface-raised)` and `filled` defaults the border to `transparent`; a value set
528
- here wins over every treatment. `--lr-combobox-open-border-color` (default
529
- `var(--lr-color-brand)`) recolors the row's edge while it holds focus, which was a hardcoded brand
530
- border before. Read the name as the state the listbox opens in rather than as a synonym for `open`:
531
- it is bound to `:focus-within`, so it also paints on a focused row whose listbox is closed — after
532
- an Escape dismissal, say. It is named for symmetry with `lr-select`'s
533
- `--lr-select-open-border-color`, which really is gated on `open`. `--lr-combobox-text-color`
534
- (default `inherit`) recolors the trigger's own text the same way; `appearance="accent"` defaults it
535
- instead to `var(--lr-color-on-brand)`, keeping placeholder, adornments and tag text readable on the
536
- loud brand fill.
537
-
538
- The shared field halo `--lr-form-control-focus-shadow` (default `none`) paints a `box-shadow`
539
- while this control is focused — one name for every field-shaped control in the library, so a
540
- halo is configured once instead of per component. It is additive: the focus outline and
541
- border cue are the accessibility answer to focus and are never replaced by it.
542
-
543
- `--lr-combobox-option-active-bg` (default `var(--lr-color-brand-quiet)`) recolors the background of
544
- a hovered or keyboard-active `[part='option']` row — the same per-component indirection
545
- `lr-select`'s identical `--lr-select-option-active-bg` uses, so a consumer can retheme just this
546
- row state without hijacking the shared `--lr-color-brand-quiet` token library-wide.
547
-
548
- The currently-**selected** row (`[part='option'][aria-selected='true']`) has its own matching set:
549
- `--lr-combobox-option-selected-bg` (default `transparent`), `--lr-combobox-option-selected-border`
550
- and `--lr-combobox-option-selected-color` (both default `var(--lr-color-brand)`), and
551
- `--lr-combobox-option-selected-font-weight` (default `var(--lr-font-weight-semibold)`) — the same
552
- four-token indirection `lr-select`/`lr-model-select` already provide for their own selected row.
553
- Like the active-bg knob these are inline `var()` fallbacks, not declared on `:host`, so a consumer
554
- can retheme the selected row without hijacking `--lr-color-brand` library-wide.
555
-
556
- The listbox popup itself is a floating surface and paints from the **shared overlay-surface family**
557
- (16.0.0), not from the page surface every card and input reads: `--lr-overlay-surface` (default
558
- `var(--lr-color-surface-overlay)`), `--lr-overlay-border` (default `var(--lr-color-border)`),
559
- `--lr-overlay-radius` (default `var(--lr-radius)`) and `--lr-overlay-shadow-anchored` (default
560
- `var(--lr-shadow-m)`). None is declared on `:host`, so one declaration on `:root` — or on any
561
- ancestor, to scope it — retints this popup together with every other floating surface, and none of
562
- it touches the trigger row the popup drops from. The edge deliberately keeps the control tier, `var(--lr-color-border)`, rather than the
563
- decorative `var(--lr-color-border-subtle)` floating panels default to: this popup belongs to the
564
- form control it opens from and keeps that control's boundary contrast (see `<lr-popover>`).
565
-
566
- `--lr-combobox-unknown-value-border-style` (default `dashed`) and
567
- `--lr-combobox-unknown-value-border-color` (default `var(--lr-color-border)`) retheme the
568
- `[part='unknown-value']` badge described above under "Unknown committed values".
569
- `--lr-combobox-option-badge-bg` (default `var(--lr-color-brand-quiet)`) retints the
570
- `[part='option-badge']` trailing metadata badge on an async row, and the "not in catalog" badge
571
- `show-unknown-option` renders on the synthetic unmatched-value listbox row — the same
572
- per-component indirection `lr-select`'s `--lr-select-option-badge-bg` uses.
573
-
574
- `--lr-combobox-trigger-height` pins an **exact** input-container height (both floors and caps it),
575
- for pixel-matching an `<lr-input>` or `<lr-select>` in the same toolbar row. It is **undeclared by
576
- default**, leaving `--lr-combobox-trigger-min-height` as a floor only and the row free to grow —
577
- see "exact-height hatches" under `lr-input` for why `auto` is not a way to opt back out. Intended
578
- for a single-row combobox: in `multiple` mode, a tag row long enough to wrap overflows the pinned
579
- box visibly (nothing is clipped or made unreachable), so leave it unset there.
580
-
581
- **CSS custom states:** `blank`, `disabled`, `required`, `optional`, `valid`, `invalid`,
582
- `user-valid`, and `user-invalid`.
583
-
584
- **Optional peer deps:** none.
585
-
586
- ### `lr-option`
587
-
588
- **Properties:**
589
-
590
- - `value: string = ''`
591
- - `disabled: boolean = false`
592
- - `defaultSelected: boolean = false` (attribute `selected`; property writes do not reflect) — the
593
- declarative and `form.reset()` default. Changing it after mount updates the parent's reset
594
- baseline without overwriting a dirty live selection
595
- - `selected: boolean = false` (property only) — live selectedness. The parent combobox/select
596
- updates it as the current value changes; those live writes never rewrite the `selected`
597
- attribute or `defaultSelected`
598
- - `group: string = ''` (section header)
599
- - `searchText: string = ''` (attribute `search-text` — extra text the filter matches beyond the
600
- visible label)
601
- - `sub: string = ''` (optional secondary line rendered under the label, e.g. a status/date summary)
602
- - `dotColor: string = ''` (attribute `dot-color` — optional CSS color for a small leading status
603
- dot; invalid values, declaration-breaking input, and `url()` render the dot transparently)
604
- - `data?: unknown` (attribute: false) — opaque application payload, e.g. the backend record this
605
- option represents. Never read or rendered by this component; retained by reference, never
606
- deep-cloned, through the owning `lr-combobox`'s `selectedRows` and the owning `lr-select`'s
607
- `selectedData`, and in both controls' `lr-input`/`lr-change`/`input`/`change` event details.
608
- Assigning it notifies the owning picker with `lr-option-change`, like `sub`/`dotColor`/`group`
609
- - `label: string` — settable WA-compatible plain-text label. A non-empty property/attribute wins;
610
- otherwise it resolves to `defaultLabel`. Property writes stay property-only (no reflection)
611
- - `defaultLabel: string` (read-only) — normalized accessibility-visible text generated from the
612
- flattened default slot. Hidden subtrees are excluded, visible nested `aria-label` values replace
613
- their descendants, and `start`/`end`/`prefix`/`suffix` adornments are excluded. Direct and
614
- forwarding-slot mutations update the value and notify the owning picker
615
-
616
- **Method:** `getTextLabel(): string` returns `defaultLabel`, preserving Shoelace's content-derived
617
- plain-text contract even when a separate WA `label` override is present.
618
-
619
- **Events:** `lr-option-change` — bubbles when the option's label or selectable data changes so
620
- its parent `lr-combobox` or `lr-select` can refresh its normalized option rows. It is a private
621
- child-to-parent refresh signal, not a picker event: the owning `lr-combobox`/`lr-select` consumes
622
- it and stops it, so it never reaches a listener on the picker host (whose own contract is
623
- `lr-change`/`lr-input`/`change`/`input`). Listen on the `<lr-option>` itself to observe it.
624
-
625
- **Slots:** default (visible label), `start`/`end` (WA adornments), and `prefix`/`suffix` (Shoelace
626
- aliases). `start` and `prefix` project into one leading wrapper; `end` and `suffix` project into one
627
- trailing wrapper.
628
-
629
- **CSS parts:** `base`, `checked-icon`, `label`, `start`/`prefix` (same node), and `end`/`suffix`
630
- (same node). **CSS custom property:** `--current-text-color` (default `var(--lr-color-text)`) colors
631
- the keyboard-current row. **CSS custom states:** `current` (the host is the roving-focus target),
632
- `selected`, `disabled`, and `hover` (pointer presence, including drag sessions).
633
-
634
- Own-anatomy state hooks are `--lr-option-hover-bg`, `--lr-option-active-bg`,
635
- `--lr-option-current-bg`, `--lr-option-current-color`, `--lr-option-selected-font-weight`, and
636
- `--lr-option-checked-icon-color`. Their defaults preserve brand-quiet hover/current paint, the
637
- shared active mix, semibold selected text, and the brand checkmark. The current-color hook falls
638
- back through upstream `--current-text-color`, so existing themes keep working.
639
-
640
- The stock `lr-combobox` and `lr-select` intentionally treat each option as light-DOM data and
641
- render normalized rows in their own shadow roots. Style those rows through the parent's `option`,
642
- `option-label`, and related parts; the parts above style an option's own anatomy when it is rendered
643
- by a custom owner.
644
-
645
- ```html
646
- <lr-combobox id="cb" label="Country" placeholder="Search…" with-clear>
647
- <lr-option value="fr">France</lr-option>
648
- <lr-option value="de" search-text="deutschland">Germany</lr-option>
649
- </lr-combobox>
650
- <script type="module">
651
- document
652
- .getElementById("cb")
653
- .addEventListener("change", (e) => console.log(e.target.value));
654
- </script>
655
- ```
656
-
657
- ```html
658
- <!-- Async data source instead of light-DOM <lr-option> children: -->
659
- <lr-combobox id="cb2" label="Fruit (async)" with-clear></lr-combobox>
660
- <script type="module">
661
- document.getElementById("cb2").source = async (query) => {
662
- const rows = await fetchFruit(query); // your own lookup
663
- return rows.map((r) => ({
664
- value: r.id,
665
- label: r.name,
666
- icon: renderFruitIcon(r), // decorative; hidden from assistive technology
667
- badge: r.category,
668
- accessibleLabel: `${r.name}, ${r.category}`,
669
- data: r, // retained in cb2.selectedRows after selection
670
- }));
671
- };
672
- </script>
673
- ```
674
-
675
- Multi-select submits as **repeated `FormData` entries** (not a joined string) —
676
- `new FormData(form).getAll(name)` behaves like a native multi-value control. An unnamed multi-select
677
- (`multiple` with no `name`) contributes nothing to the form at all, matching a nameless native
678
- `<select multiple>`, rather than falling back to a shared literal key that could collide with
679
- another unnamed combobox in the same form.
680
- Session-history/autofill state is stored as a name-independent JSON string array. A valid string
681
- array restores the selection (single mode keeps its first entry); malformed or wrong-shape state
682
- restores an empty selection. Restored state wins even when it arrives before the first option
683
- collection, while `form.reset()` still returns to the declarative selected default. Restoration is
684
- synchronous and fires no `input`/`change`/`lr-change` event.
685
-
686
- **Known gotchas:**
687
-
688
- - `with-clear` and `clearable` are equivalent and both are supported indefinitely; prefer
689
- `clearable` in new code. `lr-input` and `lr-select` accept both spellings too.
690
- - a host-level `aria-label` attribute on `<lr-combobox>` now takes priority over `label`/
691
- `placeholder`/`"Combobox"` when resolving the accessible name on `[part="combobox-input"]` —
692
- previously it was silently ignored. Matches the same fallback on `<lr-select>`.
693
- - `aria-required` reflects `required` immediately; `aria-invalid`, by contrast, only reflects
694
- **after the field has been touched** (first `blur`) — a `required` field with a validity error
695
- doesn't look invalid to assistive tech before that, by design (avoids flashing invalid styling on
696
- first render). Blurring the input (Tab away) now also closes an open listbox, the same as a
697
- native `<select>`'s popup, not just a click outside or Escape.
698
- - `dotColor`/`sub`/`group` are read from light-DOM `<lr-option>` children as before, but are also
699
- first-class fields on `ComboboxSourceRow` for the async `source` path — an async lookup can drive
700
- the same grouped/dot/sub-text rendering a static option list can.
701
- - `icon`, `badge`, and `accessibleLabel` are async-source row features rather than `<lr-option>`
702
- properties. Icons are decorative (`aria-hidden`); use `accessibleLabel` when the visible
703
- label/sub/badge combination needs a fuller spoken name. `data`, by contrast, is a first-class
704
- `<lr-option>` property too (the light-DOM counterpart of the async row's own `data`): it is
705
- deliberately opaque, never rendered, and reachable through the read-only `selectedRows` getter
706
- (combobox), the read-only `selectedData` getter (select), and both controls' `lr-input`/
707
- `lr-change`/`input`/`change` event details.
708
- - Full ARIA 1.2 combobox pattern (`role=combobox`, roving `aria-activedescendant`, real DOM focus
709
- kept on the input) is implemented correctly — a genuine strength, safe to build on.
710
- `<lr-option value="b" selected>` sets that option's `defaultSelected`, seeds the live selection,
711
- and supplies the `form.reset()` baseline, mirroring native `<select><option selected>`. A
712
- later-slotted option or post-mount `defaultSelected` change updates that baseline and updates the
713
- live selection only while it is pristine. A user pick or direct `option.selected` write makes the
714
- live selection dirty and never rewrites the declarative `selected` attribute/reset default.
715
- - The floating listbox participates in Lyra's shared nonmodal overlay stack. Its computed
716
- `--lr-overlay-stack-index`, Escape owner, outside-pointer dismissal, and focus handoff follow the
717
- newest open Lyra overlay. Opening it above a color picker (or vice versa) therefore closes only
718
- the visual top layer per Escape/pointer action rather than both popups.
719
-
720
- **Additional API surface:**
721
-
722
- - `part="tag-label"` — The selected-tag label. Kept on one line and truncated with an ellipsis at `--tag-max-size` (default `var(--lr-size-5rem)`), rather than wrapped. Note that `lr-select`'s tag of the same name defaults to `var(--lr-size-12rem)`, so the same `--tag-max-size` value yields a wider tag there.
723
-
724
- ---