@aceshooting/lyra-ui 2.0.0 → 2.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 (224) hide show
  1. package/README.md +87 -14
  2. package/custom-elements.json +11555 -5539
  3. package/dist/components/app-rail/app-rail-item.d.ts +30 -0
  4. package/dist/components/app-rail/app-rail-item.js +68 -0
  5. package/dist/components/app-rail/app-rail-item.styles.d.ts +1 -0
  6. package/dist/components/app-rail/app-rail-item.styles.js +65 -0
  7. package/dist/components/app-rail/app-rail.d.ts +21 -24
  8. package/dist/components/app-rail/app-rail.js +68 -132
  9. package/dist/components/app-rail/app-rail.styles.js +5 -2
  10. package/dist/components/attachment-chip/attachment-chip.d.ts +38 -11
  11. package/dist/components/attachment-chip/attachment-chip.js +73 -29
  12. package/dist/components/attachment-trigger/attachment-trigger.d.ts +13 -0
  13. package/dist/components/attachment-trigger/attachment-trigger.js +13 -1
  14. package/dist/components/attachment-trigger/attachment-trigger.styles.js +28 -6
  15. package/dist/components/chart/bar-chart.js +2 -18
  16. package/dist/components/chart/box-plot.d.ts +25 -1
  17. package/dist/components/chart/box-plot.js +136 -6
  18. package/dist/components/chart/box-plot.styles.js +13 -0
  19. package/dist/components/chart/bubble-chart.js +2 -18
  20. package/dist/components/chart/chart.d.ts +54 -10
  21. package/dist/components/chart/chart.js +182 -39
  22. package/dist/components/chart/doughnut-chart.js +2 -18
  23. package/dist/components/chart/histogram-bin.d.ts +5 -2
  24. package/dist/components/chart/histogram-bin.js +12 -4
  25. package/dist/components/chart/histogram.js +8 -16
  26. package/dist/components/chart/line-chart.js +2 -18
  27. package/dist/components/chart/lite-chart.d.ts +80 -3
  28. package/dist/components/chart/lite-chart.js +250 -53
  29. package/dist/components/chart/pie-chart.js +2 -18
  30. package/dist/components/chart/polar-area-chart.js +2 -18
  31. package/dist/components/chart/radar-chart.js +2 -18
  32. package/dist/components/chart/scatter-chart.js +2 -18
  33. package/dist/components/chat-composer/chat-composer.d.ts +42 -1
  34. package/dist/components/chat-composer/chat-composer.js +84 -3
  35. package/dist/components/chat-composer/chat-composer.styles.js +11 -1
  36. package/dist/components/chat-message/chat-message.d.ts +32 -4
  37. package/dist/components/chat-message/chat-message.js +57 -17
  38. package/dist/components/checkbox/checkbox.d.ts +66 -8
  39. package/dist/components/checkbox/checkbox.js +173 -50
  40. package/dist/components/checkbox/checkbox.styles.js +9 -2
  41. package/dist/components/chip/chip-group.js +8 -2
  42. package/dist/components/chip/chip.js +9 -2
  43. package/dist/components/citation-badge/citation-badge.js +21 -3
  44. package/dist/components/code-block/code-block.d.ts +38 -0
  45. package/dist/components/code-block/code-block.js +72 -7
  46. package/dist/components/code-block/code-loader.d.ts +44 -1
  47. package/dist/components/code-block/code-loader.js +63 -0
  48. package/dist/components/combobox/combobox.d.ts +46 -5
  49. package/dist/components/combobox/combobox.js +175 -35
  50. package/dist/components/combobox/combobox.styles.js +3 -3
  51. package/dist/components/context-meter/context-meter.js +17 -8
  52. package/dist/components/context-meter/context-meter.styles.js +0 -6
  53. package/dist/components/conversation-item/conversation-item.d.ts +13 -16
  54. package/dist/components/conversation-item/conversation-item.js +29 -20
  55. package/dist/components/date-picker/calendar-core.d.ts +24 -0
  56. package/dist/components/date-picker/calendar-core.js +51 -4
  57. package/dist/components/date-picker/date-input.d.ts +77 -10
  58. package/dist/components/date-picker/date-input.js +278 -73
  59. package/dist/components/date-picker/date-picker.d.ts +28 -4
  60. package/dist/components/date-picker/date-picker.js +95 -37
  61. package/dist/components/dialog/confirm.js +3 -6
  62. package/dist/components/dialog/dialog.d.ts +75 -31
  63. package/dist/components/dialog/dialog.js +147 -131
  64. package/dist/components/dialog/dialog.styles.js +46 -2
  65. package/dist/components/dock-panel/dock-panel.d.ts +8 -0
  66. package/dist/components/dock-panel/dock-panel.js +15 -1
  67. package/dist/components/dock-panel/dock-panel.styles.js +15 -1
  68. package/dist/components/document-preview/document-preview.d.ts +15 -7
  69. package/dist/components/document-preview/document-preview.js +29 -13
  70. package/dist/components/empty/empty.d.ts +1 -2
  71. package/dist/components/empty/empty.js +18 -12
  72. package/dist/components/empty/empty.styles.js +0 -3
  73. package/dist/components/export-button/export-button.d.ts +12 -0
  74. package/dist/components/export-button/export-button.js +33 -5
  75. package/dist/components/export-button/export-button.styles.js +5 -1
  76. package/dist/components/file-input/accept.js +2 -0
  77. package/dist/components/file-input/file-input.d.ts +10 -1
  78. package/dist/components/file-input/file-input.js +36 -5
  79. package/dist/components/flag/flag.d.ts +1 -1
  80. package/dist/components/flag/flag.js +15 -1
  81. package/dist/components/flag/flag.styles.js +1 -1
  82. package/dist/components/flag/language-map.d.ts +4 -1
  83. package/dist/components/flag/language-map.js +6 -3
  84. package/dist/components/gauge/gauge.d.ts +10 -1
  85. package/dist/components/gauge/gauge.js +28 -9
  86. package/dist/components/generation-status/generation-status.d.ts +13 -5
  87. package/dist/components/generation-status/generation-status.js +59 -5
  88. package/dist/components/graph/graph.d.ts +37 -3
  89. package/dist/components/graph/graph.js +132 -20
  90. package/dist/components/heatmap/calendar-grid.d.ts +15 -6
  91. package/dist/components/heatmap/calendar-grid.js +17 -6
  92. package/dist/components/heatmap/heatmap-scale.d.ts +5 -1
  93. package/dist/components/heatmap/heatmap-scale.js +8 -2
  94. package/dist/components/heatmap/heatmap.d.ts +166 -25
  95. package/dist/components/heatmap/heatmap.js +372 -89
  96. package/dist/components/json-viewer/json-viewer.d.ts +22 -5
  97. package/dist/components/json-viewer/json-viewer.js +115 -37
  98. package/dist/components/json-viewer/json-viewer.styles.js +2 -1
  99. package/dist/components/kbd/kbd.js +24 -7
  100. package/dist/components/live-region/live-region.d.ts +10 -1
  101. package/dist/components/live-region/live-region.js +42 -6
  102. package/dist/components/map/map.d.ts +24 -1
  103. package/dist/components/map/map.js +89 -24
  104. package/dist/components/markdown/markdown-loader.d.ts +24 -0
  105. package/dist/components/markdown/markdown-loader.js +35 -1
  106. package/dist/components/markdown/markdown.d.ts +46 -8
  107. package/dist/components/markdown/markdown.js +136 -31
  108. package/dist/components/markdown/markdown.styles.js +7 -1
  109. package/dist/components/media-card/media-card.d.ts +25 -17
  110. package/dist/components/media-card/media-card.js +43 -65
  111. package/dist/components/mention-popover/mention-popover.d.ts +4 -1
  112. package/dist/components/mention-popover/mention-popover.js +23 -5
  113. package/dist/components/menu/menu-item.d.ts +32 -3
  114. package/dist/components/menu/menu-item.js +94 -7
  115. package/dist/components/menu/menu-item.styles.js +8 -0
  116. package/dist/components/menu/menu.d.ts +25 -1
  117. package/dist/components/menu/menu.js +99 -3
  118. package/dist/components/menu/menu.styles.js +3 -2
  119. package/dist/components/model-select/model-select.d.ts +48 -3
  120. package/dist/components/model-select/model-select.js +120 -22
  121. package/dist/components/model-select/model-select.styles.js +17 -3
  122. package/dist/components/model-settings-panel/model-settings-panel.d.ts +25 -1
  123. package/dist/components/model-settings-panel/model-settings-panel.js +65 -1
  124. package/dist/components/playback/playback.d.ts +6 -2
  125. package/dist/components/playback/playback.js +72 -29
  126. package/dist/components/playback/playback.styles.js +6 -1
  127. package/dist/components/responsive-panel/responsive-panel.d.ts +29 -16
  128. package/dist/components/responsive-panel/responsive-panel.js +130 -132
  129. package/dist/components/responsive-panel/responsive-panel.styles.js +1 -1
  130. package/dist/components/result-card/result-card.d.ts +8 -2
  131. package/dist/components/result-card/result-card.js +9 -3
  132. package/dist/components/result-card/result-card.styles.js +0 -1
  133. package/dist/components/select/select.d.ts +59 -20
  134. package/dist/components/select/select.js +125 -43
  135. package/dist/components/skeleton/skeleton.d.ts +3 -0
  136. package/dist/components/skeleton/skeleton.js +7 -1
  137. package/dist/components/skeleton/skeleton.styles.js +5 -4
  138. package/dist/components/slider/slider.d.ts +57 -5
  139. package/dist/components/slider/slider.js +195 -80
  140. package/dist/components/source-card/source-card.d.ts +17 -5
  141. package/dist/components/source-card/source-card.js +55 -8
  142. package/dist/components/source-card/source-card.styles.js +1 -1
  143. package/dist/components/source-list/source-list.js +9 -1
  144. package/dist/components/sparkline/sparkline.js +34 -10
  145. package/dist/components/split/split.d.ts +141 -14
  146. package/dist/components/split/split.js +357 -52
  147. package/dist/components/split/split.styles.js +18 -1
  148. package/dist/components/stat/stat.d.ts +10 -4
  149. package/dist/components/stat/stat.js +46 -11
  150. package/dist/components/stream-status/stream-status.d.ts +27 -11
  151. package/dist/components/stream-status/stream-status.js +95 -13
  152. package/dist/components/streaming-text/streaming-text.d.ts +9 -3
  153. package/dist/components/streaming-text/streaming-text.js +25 -10
  154. package/dist/components/streaming-text/streaming-text.styles.js +3 -2
  155. package/dist/components/switch/switch.d.ts +52 -7
  156. package/dist/components/switch/switch.js +111 -40
  157. package/dist/components/switch/switch.styles.js +1 -1
  158. package/dist/components/table/table.d.ts +18 -3
  159. package/dist/components/table/table.js +88 -20
  160. package/dist/components/tabs/tabs.d.ts +23 -0
  161. package/dist/components/tabs/tabs.js +76 -12
  162. package/dist/components/tabs/tabs.styles.js +11 -0
  163. package/dist/components/thinking-panel/thinking-panel.d.ts +3 -1
  164. package/dist/components/thinking-panel/thinking-panel.js +34 -9
  165. package/dist/components/thinking-panel/thinking-panel.styles.js +6 -0
  166. package/dist/components/time-range/time-range.d.ts +59 -2
  167. package/dist/components/time-range/time-range.js +163 -67
  168. package/dist/components/toast/toast-item.d.ts +12 -3
  169. package/dist/components/toast/toast-item.js +90 -53
  170. package/dist/components/toast/toast-item.styles.js +59 -28
  171. package/dist/components/toast/toast.js +17 -5
  172. package/dist/components/toast/toaster.js +10 -1
  173. package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +11 -10
  174. package/dist/components/tool-approval-dialog/tool-approval-dialog.js +50 -124
  175. package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +1 -1
  176. package/dist/components/tool-call-chip/tool-call-chip.d.ts +18 -2
  177. package/dist/components/tool-call-chip/tool-call-chip.js +86 -25
  178. package/dist/components/tool-param-form/tool-param-form.d.ts +89 -28
  179. package/dist/components/tool-param-form/tool-param-form.js +350 -106
  180. package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +27 -7
  181. package/dist/components/tool-result-dialog/tool-result-dialog.js +80 -115
  182. package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +1 -1
  183. package/dist/components/tool-result-view/registry.d.ts +0 -8
  184. package/dist/components/tool-result-view/registry.js +7 -2
  185. package/dist/components/tool-result-view/tool-result-view.d.ts +6 -3
  186. package/dist/components/tool-result-view/tool-result-view.js +18 -4
  187. package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +11 -11
  188. package/dist/components/tool-select-dialog/tool-select-dialog.js +44 -133
  189. package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +13 -4
  190. package/dist/components/tree/tree-node.d.ts +4 -1
  191. package/dist/components/tree/tree-node.js +4 -1
  192. package/dist/components/tree/tree.d.ts +17 -8
  193. package/dist/components/tree/tree.js +21 -20
  194. package/dist/components/typing-indicator/typing-indicator.d.ts +12 -2
  195. package/dist/components/typing-indicator/typing-indicator.js +16 -4
  196. package/dist/components/typing-indicator/typing-indicator.styles.js +2 -2
  197. package/dist/components/virtual-list/virtual-list.d.ts +56 -19
  198. package/dist/components/virtual-list/virtual-list.js +184 -38
  199. package/dist/components/virtual-list/virtual-list.styles.js +12 -0
  200. package/dist/components/widget/widget.d.ts +5 -4
  201. package/dist/components/widget/widget.js +54 -120
  202. package/dist/components/widget/widget.styles.js +8 -2
  203. package/dist/components/word-cloud/word-cloud-layout.d.ts +7 -0
  204. package/dist/components/word-cloud/word-cloud-layout.js +20 -6
  205. package/dist/components/word-cloud/word-cloud.d.ts +22 -2
  206. package/dist/components/word-cloud/word-cloud.js +45 -6
  207. package/dist/components/word-cloud/word-cloud.styles.js +13 -1
  208. package/dist/internal/anchored-validity.d.ts +1 -0
  209. package/dist/internal/anchored-validity.js +86 -0
  210. package/dist/internal/form-associated.d.ts +7 -0
  211. package/dist/internal/form-associated.js +43 -4
  212. package/dist/internal/numbers.d.ts +12 -0
  213. package/dist/internal/numbers.js +26 -0
  214. package/dist/internal/overlay-manager.d.ts +51 -0
  215. package/dist/internal/overlay-manager.js +523 -0
  216. package/dist/internal/positioner.js +27 -3
  217. package/dist/internal/safe-url.d.ts +12 -0
  218. package/dist/internal/safe-url.js +41 -0
  219. package/dist/internal/tokens.styles.js +17 -11
  220. package/dist/lyra.d.ts +5 -2
  221. package/dist/lyra.js +3 -1
  222. package/llms-full.txt +970 -426
  223. package/llms.txt +5 -4
  224. package/package.json +19 -2
@@ -11,6 +11,7 @@ import { defineElement } from '../../internal/prefix.js';
11
11
  import { place } from '../../internal/positioner.js';
12
12
  import { nextId } from '../../internal/a11y.js';
13
13
  import { chevronIcon, closeIcon } from '../../internal/icons.js';
14
+ import { AnchoredValidityController, VALIDITY_ANCHOR } from '../../internal/anchored-validity.js';
14
15
  import { styles } from './combobox.styles.js';
15
16
  import { LyraOption } from './option.js';
16
17
  import './option.js';
@@ -26,8 +27,11 @@ import './option.js';
26
27
  * @slot label - Custom label content.
27
28
  * @slot hint - Custom hint content.
28
29
  * @slot error - Custom error content.
29
- * @event change - The selection changed.
30
- * @event input - The user typed in the filter or changed the selection.
30
+ * @event {Event} change - The selection changed through user interaction. A bubbling,
31
+ * composed, non-cancelable Event.
32
+ * @event {InputEvent | Event} input - The user typed in the filter or changed the selection. Text
33
+ * edits expose the original InputEvent; selection changes emit a bubbling,
34
+ * composed, non-cancelable Event.
31
35
  * @event lyra-show - The listbox opened.
32
36
  * @event lyra-hide - The listbox closed.
33
37
  * @event lyra-clear - The value was cleared.
@@ -53,15 +57,15 @@ export class LyraCombobox extends LyraElement {
53
57
  static { this.formAssociated = true; }
54
58
  static { this.styles = [LyraElement.styles, styles]; }
55
59
  static { this.properties = {
60
+ multiple: { type: Boolean, reflect: true, noAccessor: true },
61
+ disabled: { type: Boolean, reflect: true, noAccessor: true },
62
+ required: { type: Boolean, reflect: true, noAccessor: true },
56
63
  value: { noAccessor: true },
57
64
  name: { reflect: true, noAccessor: true },
58
65
  }; }
59
66
  constructor() {
60
67
  super();
61
- this.multiple = false;
62
68
  this.placeholder = '';
63
- this.disabled = false;
64
- this.required = false;
65
69
  this.label = '';
66
70
  this.hint = '';
67
71
  this.errorText = '';
@@ -69,6 +73,10 @@ export class LyraCombobox extends LyraElement {
69
73
  this.withClear = false;
70
74
  this.maxOptionsVisible = 3;
71
75
  this.emptyText = 'No results';
76
+ /** Status copy shown in the listbox while a `source` fetch is in flight. */
77
+ this.loadingText = 'Loading…';
78
+ /** Status copy shown when `maxRender` caps the row list; `{n}` is replaced with the hidden count. */
79
+ this.overflowText = '+{n} more — refine your search';
72
80
  this.filter = null;
73
81
  this.source = null;
74
82
  this.maxRender = 200;
@@ -91,6 +99,11 @@ export class LyraCombobox extends LyraElement {
91
99
  this.loading = false;
92
100
  this.asyncRows = [];
93
101
  this.sourceToken = 0;
102
+ // Guards the proactive `asyncRows` warm-up in `willUpdate()` below so it
103
+ // fires at most once per mount instead of re-running (and endlessly
104
+ // resetting the debounce timer) on every subsequent render while the fetch
105
+ // is still in flight.
106
+ this._sourceWarmed = false;
94
107
  this._selectedLabelCache = new Map();
95
108
  // Rebuilt once per render (in render(), before renderRows()) from the
96
109
  // currently-visible row set -- backs the delegated listbox click/mousedown
@@ -106,6 +119,9 @@ export class LyraCombobox extends LyraElement {
106
119
  this.inputId = nextId('combobox-input');
107
120
  this._isFirstUpdate = true;
108
121
  this._selected = [];
122
+ this._multiple = false;
123
+ this._disabled = false;
124
+ this._required = false;
109
125
  // What `form.reset()` restores to. Captured exactly once, from whatever
110
126
  // `<lyra-option selected>` markup was present the first time slotted
111
127
  // options are collected (mirrors native `<select><option selected>`) —
@@ -116,6 +132,9 @@ export class LyraCombobox extends LyraElement {
116
132
  // property assignment never does).
117
133
  this._defaultSelected = [];
118
134
  this._defaultCaptured = false;
135
+ // A restored value must win over declarative selected markup collected by
136
+ // the first asynchronous slotchange. Cleared by the next ordinary value write.
137
+ this._restoredStateActive = false;
119
138
  // Hand-written accessor (mirrors the `value` accessor below, and Task 2's
120
139
  // `FormAssociated.name` in `../../internal/form-associated.ts`): a
121
140
  // form-associated custom element's submitted entry name is resolved by the
@@ -142,7 +161,7 @@ export class LyraCombobox extends LyraElement {
142
161
  // after the user has picked something.
143
162
  const declared = this.options.filter((o) => o.selected).map((o) => o.value);
144
163
  this._defaultSelected = [...declared];
145
- if (declared.length) {
164
+ if (declared.length && !this._restoredStateActive) {
146
165
  this.value = this.multiple ? declared : declared[0];
147
166
  return; // `value=`'s setter already called reflectSelected()
148
167
  }
@@ -154,7 +173,7 @@ export class LyraCombobox extends LyraElement {
154
173
  // into the live selection instead of letting reflectSelected() below
155
174
  // strip their `selected` attribute back off.
156
175
  const newlySelected = this.options.filter((o) => !previous.has(o) && o.selected).map((o) => o.value);
157
- if (newlySelected.length) {
176
+ if (newlySelected.length && !this._restoredStateActive) {
158
177
  this.value = this.multiple
159
178
  ? [...new Set([...this._selected, ...newlySelected])]
160
179
  : newlySelected[newlySelected.length - 1];
@@ -181,7 +200,6 @@ export class LyraCombobox extends LyraElement {
181
200
  this.show();
182
201
  if (this.source)
183
202
  this.runSource(this.query);
184
- this.emit('input');
185
203
  };
186
204
  this.onInputBlur = () => {
187
205
  this.touched = true;
@@ -273,6 +291,26 @@ export class LyraCombobox extends LyraElement {
273
291
  this.pickRow(row);
274
292
  };
275
293
  this.internals = this.attachInternals();
294
+ this.validityController = new AnchoredValidityController(this, this.internals, () => this[VALIDITY_ANCHOR]());
295
+ }
296
+ get form() {
297
+ return this.internals.form;
298
+ }
299
+ get labels() {
300
+ return this.internals.labels;
301
+ }
302
+ get validity() {
303
+ return this.internals.validity;
304
+ }
305
+ get validationMessage() {
306
+ return this.internals.validationMessage;
307
+ }
308
+ get willValidate() {
309
+ return this.internals.willValidate;
310
+ }
311
+ /** @internal */
312
+ [VALIDITY_ANCHOR]() {
313
+ return this.renderRoot?.querySelector('[part="combobox-input"]') ?? null;
276
314
  }
277
315
  connectedCallback() {
278
316
  super.connectedCallback();
@@ -289,6 +327,16 @@ export class LyraCombobox extends LyraElement {
289
327
  this.hasErrorSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'error');
290
328
  this.hasLabelSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'label');
291
329
  }
330
+ // In source (async) mode, `labelFor()` can only resolve a programmatically
331
+ // -set value's label from `asyncRows` -- and nothing normally populates
332
+ // `asyncRows` until the listbox is actually opened. Fire the fetch once,
333
+ // independent of `open`, so the closed input / multi-select tag chips can
334
+ // already show the real label the very first time they render (the
335
+ // common "edit an existing record" case) instead of the raw value string.
336
+ if (!this._sourceWarmed && this.source && this._selected.length && this.asyncRows.length === 0) {
337
+ this._sourceWarmed = true;
338
+ this.runSource('');
339
+ }
292
340
  }
293
341
  get name() {
294
342
  return this._name;
@@ -302,14 +350,47 @@ export class LyraCombobox extends LyraElement {
302
350
  else {
303
351
  this.removeAttribute('name');
304
352
  }
353
+ this.syncFormValue();
305
354
  this.requestUpdate('name', old);
306
355
  }
356
+ get multiple() {
357
+ return this._multiple;
358
+ }
359
+ set multiple(next) {
360
+ const old = this._multiple;
361
+ this._multiple = Boolean(next);
362
+ this.toggleAttribute('multiple', this._multiple);
363
+ this.syncFormValue();
364
+ this.requestUpdate('multiple', old);
365
+ }
366
+ get disabled() {
367
+ return this._disabled;
368
+ }
369
+ set disabled(next) {
370
+ const old = this._disabled;
371
+ this._disabled = Boolean(next);
372
+ this.toggleAttribute('disabled', this._disabled);
373
+ if (this._disabled)
374
+ this.hide();
375
+ this.requestUpdate('disabled', old);
376
+ }
377
+ get required() {
378
+ return this._required;
379
+ }
380
+ set required(next) {
381
+ const old = this._required;
382
+ this._required = Boolean(next);
383
+ this.toggleAttribute('required', this._required);
384
+ this.updateValidity();
385
+ this.requestUpdate('required', old);
386
+ }
307
387
  /** The selected value(s): a string in single mode, a string[] in `multiple` mode. */
308
388
  get value() {
309
389
  return this.multiple ? [...this._selected] : (this._selected[0] ?? '');
310
390
  }
311
391
  set value(next) {
312
392
  const old = this._selected;
393
+ this._restoredStateActive = false;
313
394
  this._selected = Array.isArray(next) ? [...next] : next ? [next] : [];
314
395
  this.syncFormValue();
315
396
  this.reflectSelected();
@@ -318,13 +399,14 @@ export class LyraCombobox extends LyraElement {
318
399
  }
319
400
  updateValidity() {
320
401
  if (this.required && this._selected.length === 0) {
321
- this.internals.setValidity({ valueMissing: true }, 'Please select an option.');
402
+ this.validityController.setValidity({ valueMissing: true }, 'Please select an option.');
322
403
  }
323
404
  else {
324
- this.internals.setValidity({});
405
+ this.validityController.setValidity({});
325
406
  }
326
407
  }
327
408
  syncFormValue() {
409
+ const state = JSON.stringify(this._selected);
328
410
  if (this.multiple) {
329
411
  // A FormData form value submits under the keys baked into the FormData
330
412
  // itself, bypassing the element's own `name` the way a plain string
@@ -333,16 +415,16 @@ export class LyraCombobox extends LyraElement {
333
415
  // inventing a shared key that would merge with any other unnamed
334
416
  // combobox in the same form.
335
417
  if (!this.name) {
336
- this.internals.setFormValue(null);
418
+ this.internals.setFormValue(null, state);
337
419
  return;
338
420
  }
339
421
  const fd = new FormData();
340
422
  for (const v of this._selected)
341
423
  fd.append(this.name, v);
342
- this.internals.setFormValue(fd);
424
+ this.internals.setFormValue(fd, state);
343
425
  }
344
426
  else {
345
- this.internals.setFormValue(this._selected[0] ?? '');
427
+ this.internals.setFormValue(this._selected[0] ?? '', state);
346
428
  }
347
429
  }
348
430
  /** Effective disabled state: this element's own `disabled` OR an ancestor
@@ -355,6 +437,21 @@ export class LyraCombobox extends LyraElement {
355
437
  this.value = [...this._defaultSelected];
356
438
  this.query = '';
357
439
  }
440
+ formStateRestoreCallback(state, _mode) {
441
+ let selected = [];
442
+ if (typeof state === 'string') {
443
+ try {
444
+ const parsed = JSON.parse(state);
445
+ if (Array.isArray(parsed) && parsed.every((value) => typeof value === 'string'))
446
+ selected = parsed;
447
+ }
448
+ catch {
449
+ // Malformed persisted state restores an empty selection.
450
+ }
451
+ }
452
+ this.value = this.multiple ? selected : (selected[0] ?? '');
453
+ this._restoredStateActive = true;
454
+ }
358
455
  /**
359
456
  * Called by the browser when an ancestor `<fieldset disabled>` toggles.
360
457
  * Tracked separately from the consumer's own `disabled` (see
@@ -363,6 +460,8 @@ export class LyraCombobox extends LyraElement {
363
460
  */
364
461
  formDisabledCallback(disabled) {
365
462
  this._fieldsetDisabled = disabled;
463
+ if (disabled)
464
+ this.hide();
366
465
  this.requestUpdate();
367
466
  }
368
467
  checkValidity() {
@@ -390,7 +489,17 @@ export class LyraCombobox extends LyraElement {
390
489
  o.selected = sel.has(o.value);
391
490
  }
392
491
  labelFor(value) {
393
- return this._selectedLabelCache.get(value) ?? this.options.find((o) => o.value === value)?.label ?? value;
492
+ // Checked in order: an explicit pick's own label (works even after the
493
+ // source rows backing it have since changed), a slotted `<lyra-option>`
494
+ // (local mode), then the last-fetched async row set (source mode) -- a
495
+ // value set programmatically (e.g. `el.value = 'b'` before the listbox
496
+ // has ever been opened) has no chance to have populated the first two,
497
+ // so without this last fallback it would render as the raw value string
498
+ // instead of its label.
499
+ return (this._selectedLabelCache.get(value) ??
500
+ this.options.find((o) => o.value === value)?.label ??
501
+ this.asyncRows.find((r) => r.value === value)?.label ??
502
+ value);
394
503
  }
395
504
  /** The current row set in a source-agnostic shape, before capping. */
396
505
  get effectiveRows() {
@@ -410,15 +519,27 @@ export class LyraCombobox extends LyraElement {
410
519
  const all = this.effectiveRows;
411
520
  if (all.length <= this.maxRender)
412
521
  return { rows: all, overflow: 0 };
522
+ const originalIndex = new Map(all.map((r, i) => [r, i]));
413
523
  const capped = all.slice(0, this.maxRender);
414
524
  const cappedValues = new Set(capped.map((r) => r.value));
525
+ let appendedOutOfCap = false;
415
526
  for (const v of this._selected) {
416
527
  if (!cappedValues.has(v)) {
417
528
  const selectedRow = all.find((r) => r.value === v);
418
- if (selectedRow)
529
+ if (selectedRow) {
419
530
  capped.push(selectedRow);
531
+ appendedOutOfCap = true;
532
+ }
420
533
  }
421
534
  }
535
+ if (appendedOutOfCap) {
536
+ // A preserved out-of-cap selection was just tacked onto the very end,
537
+ // regardless of its own `group` -- stable-sort back by each row's
538
+ // original position so it lands among its own group's other rows
539
+ // instead of forcing renderRows() to emit that group's label a second
540
+ // time after whatever group happens to trail the cap.
541
+ capped.sort((a, b) => originalIndex.get(a) - originalIndex.get(b));
542
+ }
422
543
  return { rows: capped, overflow: all.length - capped.length };
423
544
  }
424
545
  get filtered() {
@@ -446,6 +567,15 @@ export class LyraCombobox extends LyraElement {
446
567
  return;
447
568
  this.open = false;
448
569
  this.activeIndex = -1;
570
+ // Single-select mode only shows `query` while `open` (see `displayValue`
571
+ // above) -- but dismissing without picking a row (blur, Escape, or an
572
+ // outside click) previously left an abandoned filter string sitting in
573
+ // `query` forever, so the *next* reopen would reappear with stale text
574
+ // and re-filter the list from it. Multiple mode is unaffected: its input
575
+ // always shows `query` regardless of `open`, by design (a persistent
576
+ // search box next to the tags).
577
+ if (!this.multiple)
578
+ this.query = '';
449
579
  }
450
580
  updated(changed) {
451
581
  if (changed.has('open')) {
@@ -476,17 +606,25 @@ export class LyraCombobox extends LyraElement {
476
606
  this.emit('lyra-hide');
477
607
  }
478
608
  }
479
- if (changed.has('required'))
480
- this.updateValidity();
481
- if (changed.has('name') || changed.has('multiple'))
609
+ if (changed.has('name'))
482
610
  this.syncFormValue();
483
611
  if (changed.has('touched') || changed.has('required') || changed.has('value')) {
484
612
  this.toggleAttribute('data-invalid', this.touched && !this.internals.validity.valid);
485
613
  }
486
614
  }
615
+ /** Dispatches the platform-style value-event pair used by non-text user
616
+ * interactions. Text editing keeps and exposes the original InputEvent
617
+ * from the shadow input so its data/inputType metadata is not lost. */
618
+ emitValueEvents() {
619
+ const EventConstructor = this.ownerDocument.defaultView?.Event ?? Event;
620
+ const init = { bubbles: true, composed: true };
621
+ this.dispatchEvent(new EventConstructor('input', init));
622
+ this.dispatchEvent(new EventConstructor('change', init));
623
+ }
487
624
  pickRow(row) {
488
- if (row.disabled)
625
+ if (this.effectiveDisabled || row.disabled)
489
626
  return;
627
+ const selectionChanged = this.multiple || this._selected[0] !== row.value;
490
628
  this._selectedLabelCache.set(row.value, row.label);
491
629
  if (this.multiple) {
492
630
  const set = new Set(this._selected);
@@ -505,20 +643,25 @@ export class LyraCombobox extends LyraElement {
505
643
  // mode) matches the now-empty input instead of the stale prior query.
506
644
  if (this.source)
507
645
  this.runSource(this.query);
508
- this.emit('input');
509
- this.emit('change');
646
+ if (selectionChanged)
647
+ this.emitValueEvents();
510
648
  }
511
649
  removeValue(value) {
512
- this.value = this._selected.filter((v) => v !== value);
513
- this.emit('change');
650
+ const next = this._selected.filter((v) => v !== value);
651
+ if (next.length === this._selected.length)
652
+ return;
653
+ this.value = next;
654
+ this.emitValueEvents();
514
655
  }
515
656
  clear() {
657
+ if (this._selected.length === 0)
658
+ return;
516
659
  this.value = [];
517
660
  this.query = '';
518
661
  if (this.source)
519
662
  this.runSource(this.query);
663
+ this.emitValueEvents();
520
664
  this.emit('lyra-clear');
521
- this.emit('change');
522
665
  }
523
666
  runSource(query) {
524
667
  if (!this.source)
@@ -662,12 +805,12 @@ export class LyraCombobox extends LyraElement {
662
805
  @click=${this.onListboxClick}
663
806
  >
664
807
  ${this.loading
665
- ? html `<div class="loading" role="option" aria-selected="false" aria-disabled="true">Loading…</div>`
808
+ ? html `<div class="loading" role="option" aria-selected="false" aria-disabled="true">${this.loadingText}</div>`
666
809
  : rows.length === 0
667
810
  ? html `<div class="empty" role="option" aria-selected="false" aria-disabled="true">${this.emptyText}</div>`
668
811
  : html `${this.renderRows(rows, activeId)}
669
812
  ${overflow > 0
670
- ? html `<div part="option-overflow">+${overflow} more — refine your search</div>`
813
+ ? html `<div part="option-overflow">${this.overflowText.replace('{n}', String(overflow))}</div>`
671
814
  : ''}`}
672
815
  </div>
673
816
  <div id="combobox-error" part="error" ?hidden=${!hasError}>
@@ -681,18 +824,9 @@ export class LyraCombobox extends LyraElement {
681
824
  `;
682
825
  }
683
826
  }
684
- __decorate([
685
- property({ type: Boolean, reflect: true })
686
- ], LyraCombobox.prototype, "multiple", void 0);
687
827
  __decorate([
688
828
  property()
689
829
  ], LyraCombobox.prototype, "placeholder", void 0);
690
- __decorate([
691
- property({ type: Boolean, reflect: true })
692
- ], LyraCombobox.prototype, "disabled", void 0);
693
- __decorate([
694
- property({ type: Boolean, reflect: true })
695
- ], LyraCombobox.prototype, "required", void 0);
696
830
  __decorate([
697
831
  property()
698
832
  ], LyraCombobox.prototype, "label", void 0);
@@ -714,6 +848,12 @@ __decorate([
714
848
  __decorate([
715
849
  property({ attribute: 'empty-text' })
716
850
  ], LyraCombobox.prototype, "emptyText", void 0);
851
+ __decorate([
852
+ property({ attribute: 'loading-text' })
853
+ ], LyraCombobox.prototype, "loadingText", void 0);
854
+ __decorate([
855
+ property({ attribute: 'overflow-text' })
856
+ ], LyraCombobox.prototype, "overflowText", void 0);
717
857
  __decorate([
718
858
  property({ attribute: false })
719
859
  ], LyraCombobox.prototype, "filter", void 0);
@@ -111,11 +111,11 @@ export const styles = css `
111
111
  position: fixed;
112
112
  z-index: 900;
113
113
  box-sizing: border-box;
114
- max-block-size: 18rem;
114
+ max-block-size: min(18rem, var(--lyra-positioner-available-block-size, 18rem));
115
115
  overflow-y: auto;
116
116
  inline-size: max-content;
117
- min-inline-size: 12rem;
118
- max-inline-size: min(92vw, 28rem);
117
+ min-inline-size: min(12rem, var(--lyra-positioner-available-inline-size, 12rem));
118
+ max-inline-size: min(92vw, 28rem, var(--lyra-positioner-available-inline-size, 100vw));
119
119
  padding: var(--lyra-space-xs);
120
120
  background: var(--lyra-color-surface);
121
121
  border: 1px solid var(--lyra-color-border);
@@ -9,15 +9,19 @@ import { property } from 'lit/decorators.js';
9
9
  import { styleMap } from 'lit/directives/style-map.js';
10
10
  import { LyraElement } from '../../internal/lyra-element.js';
11
11
  import { defineElement } from '../../internal/prefix.js';
12
+ import { finiteNumber } from '../../internal/numbers.js';
12
13
  import { styles } from './context-meter.styles.js';
13
- // Ring geometry, matching lyra-gauge's radial numbers so the two "circular
14
- // meter" components in the library share one visual scale.
14
+ // Ring geometry: RADIUS/CENTER match lyra-gauge's radial numbers, so both
15
+ // components' rings sit on the same circle within their viewBox. STROKE is
16
+ // intentionally heavier than the gauge's (12 vs. 10) -- tightly-packed
17
+ // multi-tone arcs need more width to stay visually distinct than a gauge's
18
+ // single fill arc, so it isn't shared.
15
19
  const RADIUS = 40;
16
20
  const CENTER = 50;
17
21
  const STROKE = 12;
18
22
  const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
19
23
  function formatCount(n) {
20
- return Number.isFinite(n) ? Math.round(n).toLocaleString() : '0';
24
+ return Math.round(finiteNumber(n, 0)).toLocaleString();
21
25
  }
22
26
  /**
23
27
  * `<lyra-context-meter>` — a segmented occupancy meter (bar or ring) for
@@ -50,19 +54,19 @@ export class LyraContextMeter extends LyraElement {
50
54
  static { this.styles = [LyraElement.styles, styles]; }
51
55
  /** Sum of segment values, clamped so a negative/NaN entry can't produce a negative total. */
52
56
  get usedTotal() {
53
- return this.segments.reduce((sum, s) => sum + (Number.isFinite(s.value) ? Math.max(0, s.value) : 0), 0);
57
+ return this.segments.reduce((sum, s) => sum + Math.max(0, finiteNumber(s.value, 0)), 0);
54
58
  }
55
59
  /** Per-segment ratio of `value` to `total`, clamped to [0,1] and capped so the
56
60
  * running sum across all segments never overflows past 1 — a `segments` array
57
61
  * that sums to more than `total` still renders a fully (not over-) filled meter. */
58
62
  ratios() {
59
- const total = Number.isFinite(this.total) ? this.total : 0;
63
+ const total = Math.max(0, finiteNumber(this.total, 0));
60
64
  if (total <= 0)
61
65
  return [];
62
66
  const out = [];
63
67
  let cumulative = 0;
64
68
  for (const segment of this.segments) {
65
- const raw = Number.isFinite(segment.value) ? Math.max(0, segment.value) : 0;
69
+ const raw = Math.max(0, finiteNumber(segment.value, 0));
66
70
  const available = Math.max(0, 1 - cumulative);
67
71
  const ratio = Math.min(raw / total, available);
68
72
  cumulative += ratio;
@@ -71,8 +75,13 @@ export class LyraContextMeter extends LyraElement {
71
75
  return out;
72
76
  }
73
77
  get summary() {
74
- const used = formatCount(this.usedTotal);
75
- const total = Number.isFinite(this.total) && this.total > 0 ? formatCount(this.total) : null;
78
+ // Clamp the announced figure the same way ratios() clamps the visual fill --
79
+ // a segments array that sums past total must not report an impossible
80
+ // "160 of 100 used" to assistive tech while the bar/ring visibly caps at 100%.
81
+ const totalValue = Math.max(0, finiteNumber(this.total, 0));
82
+ const usedForSummary = totalValue > 0 ? Math.min(this.usedTotal, totalValue) : this.usedTotal;
83
+ const used = formatCount(usedForSummary);
84
+ const total = totalValue > 0 ? formatCount(totalValue) : null;
76
85
  const phrase = total ? `${used} of ${total} used` : `${used} used`;
77
86
  return this.label ? `${this.label} — ${phrase}` : phrase;
78
87
  }
@@ -67,9 +67,6 @@ export const styles = css `
67
67
  [part='segment'][data-tone='danger'] {
68
68
  background: var(--lyra-color-danger);
69
69
  }
70
- [part='segment'][data-tone='neutral'] {
71
- background: var(--lyra-color-border);
72
- }
73
70
 
74
71
  /* --- ring variant -------------------------------------------------- */
75
72
  :host([variant='ring']) svg[part='base'] {
@@ -102,9 +99,6 @@ export const styles = css `
102
99
  :host([variant='ring']) [part='segment'][data-tone='danger'] {
103
100
  stroke: var(--lyra-color-danger);
104
101
  }
105
- :host([variant='ring']) [part='segment'][data-tone='neutral'] {
106
- stroke: var(--lyra-color-border);
107
- }
108
102
  :host([variant='ring']) [part='label'] {
109
103
  text-anchor: middle;
110
104
  fill: var(--lyra-color-text-quiet);
@@ -24,16 +24,12 @@ export interface ConversationItemRenameDetail {
24
24
  * identically-shaped choice. `lyra-select` therefore carries no detail
25
25
  * payload at all.
26
26
  *
27
- * `role="option"` (not `"button"`) on `[part="option"]`, since this is meant
28
- * to live inside a `role="listbox"` history list (that future list
29
- * component supplies the listbox/group ancestor `role="option"` requires)
30
- * rather than stand in for a single push-button action. Used truly
31
- * standalone -- with no listbox/group ancestor -- it's still fully
32
- * operable, just technically missing that ARIA parent context; wrap it in a
33
- * `role="listbox"` (or `role="group"`) container to satisfy that when
34
- * accessibility-auditing a standalone usage.
27
+ * `role="button"` on `[part="option"]` so the item has valid semantics both
28
+ * standalone and when placed in a larger history-list layout. A conversation
29
+ * row activates one current session; it is not itself a listbox option and
30
+ * therefore does not require a particular owner role.
35
31
  *
36
- * `role="option"` (like native `<option>`/`role="button"`) forbids focusable
32
+ * `role="button"` forbids focusable
37
33
  * descendants -- verified against axe-core's `nested-interactive` rule,
38
34
  * which flags it. That's why the rename button and the `actions` slot are
39
35
  * rendered as DOM *siblings* of `[part="option"]` (both inside
@@ -41,10 +37,9 @@ export interface ConversationItemRenameDetail {
41
37
  * contains plain text/`<time>` content. The in-place rename `<input>` is the
42
38
  * same problem one level deeper -- it replaces the title *inside*
43
39
  * `[part="option"]` while renaming -- so `[part="option"]` sheds its
44
- * `role`/`tabindex`/`aria-selected`/`aria-label` entirely for the duration
45
- * of an edit. This isn't just an ARIA-compliance workaround: a row mid-edit
46
- * genuinely isn't a selectable listbox choice at that moment, it's a text
47
- * field, so suspending the option semantics is also the more accurate
40
+ * `role`/`tabindex`/`aria-current`/`aria-label` entirely for the duration
41
+ * of an edit. A row mid-edit is a text field, so suspending the button
42
+ * semantics is also the more accurate
48
43
  * description of what's on screen.
49
44
  *
50
45
  * Inline rename is a dedicated pencil/edit icon button (not a double-click
@@ -70,7 +65,7 @@ export interface ConversationItemRenameDetail {
70
65
  * when the trimmed draft is empty or unchanged from the original `title`
71
66
  * (that's treated as an implicit cancel).
72
67
  * @csspart base - The outer row wrapper (plain, no ARIA role) laying out `[part="option"]`, the rename button, and `actions`.
73
- * @csspart option - The selectable region (`role="option"`, removed while renaming -- see the class doc). Wraps `content` and `timestamp`.
68
+ * @csspart option - The selectable region (`role="button"`, removed while renaming -- see the class doc). Wraps `content` and `timestamp`.
74
69
  * @csspart content - Wrapper around the title and excerpt.
75
70
  * @csspart title - The title text, shown while not renaming.
76
71
  * @csspart title-input - The in-place rename `<input>`, shown only while renaming.
@@ -99,13 +94,15 @@ export declare class LyraConversationItem extends LyraElement {
99
94
  * brand-quiet background treatment. */
100
95
  active: boolean;
101
96
  /** Whether inline-rename is available at all. When `false`, the rename
102
- * button never renders and the row can never enter its editing state. */
97
+ * button never renders and the row can never enter its editing state. If
98
+ * flipped to `false` while a rename is already open, the in-progress edit
99
+ * is cancelled (discarded, like Escape) rather than left committable. */
103
100
  editable: boolean;
104
101
  private renaming;
105
102
  private draftTitle;
106
103
  private hasActionsSlot;
107
104
  private titleInput?;
108
- protected willUpdate(): void;
105
+ protected willUpdate(changed: PropertyValues): void;
109
106
  protected updated(changed: PropertyValues): void;
110
107
  private get normalizedTimestamp();
111
108
  private select;