@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
@@ -7,46 +7,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { html, nothing } from 'lit';
8
8
  import { property, state } from 'lit/decorators.js';
9
9
  import { LyraElement } from '../../internal/lyra-element.js';
10
+ import { activateOverlay } from '../../internal/overlay-manager.js';
10
11
  import { defineElement } from '../../internal/prefix.js';
11
12
  import { lockScroll } from '../../internal/scroll-lock.js';
12
- import { nextId } from '../../internal/a11y.js';
13
+ import { nextId, srOnly } from '../../internal/a11y.js';
13
14
  import { styles } from './tool-select-dialog.styles.js';
14
15
  import '../checkbox/checkbox.js';
15
16
  import '../switch/switch.js';
16
17
  const OTHER_CATEGORY = 'Other';
17
- const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
18
- // Shadow-piercing so both a slotted custom element's real focusable target
19
- // (footer content) and a directly-rendered one (the search input, the
20
- // use-defaults <lyra-switch>, each row's <lyra-checkbox> -- all live in this
21
- // component's own shadow tree, not projected via <slot>) are found even
22
- // though the tag itself doesn't match FOCUSABLE_SELECTOR. Deliberately
23
- // duplicated from lyra-dialog's/lyra-tool-result-dialog's identical helper
24
- // rather than imported/shared -- this component is its own standalone
25
- // overlay and must not depend on either (see this file's class doc).
26
- function collectFocusable(el) {
27
- const result = [];
28
- if (el.matches(FOCUSABLE_SELECTOR)) {
29
- result.push(el);
30
- }
31
- if (el instanceof HTMLSlotElement) {
32
- for (const assigned of el.assignedElements({ flatten: true })) {
33
- result.push(...collectFocusable(assigned));
34
- }
35
- return result;
36
- }
37
- const container = el.shadowRoot ?? el;
38
- for (const child of Array.from(container.children)) {
39
- result.push(...collectFocusable(child));
40
- }
41
- return result;
42
- }
43
- /** Whether `el` is actually laid out/paintable -- `checkVisibility()` (falling
44
- * back to `getClientRects().length` where unsupported) correctly follows
45
- * flattened-tree/slot assignment, unlike `offsetParent`. Mirrors
46
- * lyra-dialog's/lyra-tool-result-dialog's identical helper. */
47
- function isRendered(el) {
48
- return el.checkVisibility ? el.checkVisibility() : el.getClientRects().length > 0;
49
- }
50
18
  /** Default `filter`: case-insensitive substring match against the tool's name and description. */
51
19
  function defaultFilter(tool, query) {
52
20
  return tool.name.toLowerCase().includes(query) || (tool.description ?? '').toLowerCase().includes(query);
@@ -56,11 +24,10 @@ function defaultFilter(tool, query) {
56
24
  * tool-enablement dialog for picking which agent tools are available in a
57
25
  * conversation.
58
26
  *
59
- * This is its own standalone overlay implementation (`role="dialog"`,
60
- * focus-trapped, Escape/backdrop-dismissible, scroll-locking) rather than
61
- * nesting a `<lyra-dialog>` in its shadow template see `<lyra-dialog>`'s
62
- * own header comment for why a new overlay component in this library
63
- * duplicates that pattern locally instead of composing the previous one.
27
+ * This renders its own dialog panel rather than nesting a `<lyra-dialog>` in
28
+ * its shadow template. Shared overlay infrastructure coordinates stacking,
29
+ * focus trapping, Escape/backdrop dismissal, and focus return with every
30
+ * other overlay in the same document.
64
31
  *
65
32
  * `useDefaults` is a single top-level switch: while `true`, every per-tool
66
33
  * checkbox below renders disabled (still reflecting whatever `selected`
@@ -98,14 +65,17 @@ function defaultFilter(tool, query) {
98
65
  * @csspart empty - The "no tools" / "no matches" message.
99
66
  * @csspart category - A single category's wrapper (`role="group"`).
100
67
  * @csspart category-heading - A category's heading.
101
- * @csspart category-count - The visible tool count next to a category heading.
68
+ * @csspart category-count - The terse, `aria-hidden` tool count next to a category heading
69
+ * (the heading's accessible name gets the full sentence from an sr-only sibling instead).
102
70
  * @csspart category-list - The `<ul>` of tool rows within a category.
103
71
  * @csspart tool-row - A single tool's `<li>` row.
104
72
  * @csspart tool-checkbox - A row's `<lyra-checkbox>`.
105
73
  * @csspart tool-name - A row's name text (plus its `icon`, if set).
106
74
  * @csspart tool-icon - A row's leading icon glyph, when `icon` is set.
107
75
  * @csspart tool-description - A row's optional description text.
108
- * @csspart tool-disabled-reason - A disabled row's `disabledReason` text.
76
+ * @csspart tool-disabled-reason - A disabled row's `disabledReason` text, slotted inside
77
+ * `tool-checkbox` (alongside `tool-name`/`tool-description`) so it contributes to the
78
+ * checkbox's accessible name/description instead of going unannounced.
109
79
  * @csspart footer - The wrapper around the `footer` slot.
110
80
  */
111
81
  export class LyraToolSelectDialog extends LyraElement {
@@ -135,34 +105,7 @@ export class LyraToolSelectDialog extends LyraElement {
135
105
  this.hasFooterSlot = e.target.assignedElements({ flatten: true }).length > 0;
136
106
  };
137
107
  this.onBackdropClick = () => {
138
- this.close('backdrop');
139
- };
140
- this.onDocKeyDown = (e) => {
141
- if (!this.open)
142
- return;
143
- if (e.key === 'Escape') {
144
- e.preventDefault();
145
- this.close('escape');
146
- return;
147
- }
148
- if (e.key !== 'Tab')
149
- return;
150
- const focusable = this.getFocusableElements();
151
- if (focusable.length === 0) {
152
- e.preventDefault();
153
- return;
154
- }
155
- const first = focusable[0];
156
- const last = focusable[focusable.length - 1];
157
- const active = this.getActiveElement();
158
- if (e.shiftKey && active === first) {
159
- e.preventDefault();
160
- last.focus();
161
- }
162
- else if (!e.shiftKey && active === last) {
163
- e.preventDefault();
164
- first.focus();
165
- }
108
+ this.overlay?.dismissBackdrop();
166
109
  };
167
110
  this.onSearchInput = (e) => {
168
111
  this.query = e.target.value;
@@ -172,27 +115,25 @@ export class LyraToolSelectDialog extends LyraElement {
172
115
  this.emitChange();
173
116
  };
174
117
  }
175
- static { this.styles = [LyraElement.styles, styles]; }
118
+ static { this.styles = [LyraElement.styles, styles, srOnly]; }
176
119
  willUpdate(changed) {
177
120
  if (!this.hasUpdated) {
178
121
  this.hasFooterSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'footer');
179
122
  }
180
123
  if (changed.has('open')) {
181
124
  if (this.open) {
182
- // Captured here (before render) rather than from a click event on
183
- // some specific internal control -- like lyra-dialog, a trigger
184
- // typically lives *outside* this component entirely, so "whatever
185
- // had focus right before open" is the only generally correct
186
- // definition of "the trigger".
187
- const active = this.getActiveElement();
188
- this.lastTrigger = active instanceof HTMLElement ? active : undefined;
189
- this.releaseScrollLock = lockScroll();
190
- document.addEventListener('keydown', this.onDocKeyDown);
125
+ this.releaseScrollLock ??= lockScroll(this.ownerDocument);
126
+ this.activateOverlay();
191
127
  }
192
128
  else {
193
129
  this.releaseScrollLock?.();
194
130
  this.releaseScrollLock = undefined;
195
- document.removeEventListener('keydown', this.onDocKeyDown);
131
+ this.overlay?.deactivate();
132
+ this.overlay = undefined;
133
+ // Otherwise a long-lived instance reopens still showing whatever
134
+ // search filter/collapsed-category state the previous session left
135
+ // behind, rather than the fresh, unfiltered list a reopen implies.
136
+ this.query = '';
196
137
  }
197
138
  }
198
139
  }
@@ -202,31 +143,34 @@ export class LyraToolSelectDialog extends LyraElement {
202
143
  // ordering rationale.
203
144
  updated(changed) {
204
145
  if (changed.has('open') && this.open) {
205
- const first = this.getFocusableElements()[0];
206
- if (first) {
207
- first.focus();
208
- }
209
- else {
210
- this.shadowRoot?.querySelector('[part="panel"]')?.focus();
211
- }
146
+ this.overlay?.focusInitial();
212
147
  }
213
148
  }
214
149
  connectedCallback() {
215
150
  super.connectedCallback();
216
- // A reconnect (e.g. a drag-and-drop reparent keeping this same element
217
- // instance) fires disconnectedCallback then connectedCallback
218
- // synchronously with no update in between, so willUpdate never reruns to
219
- // notice `open` is still true -- restore the scroll lock/trap it dropped.
220
- if (this.hasUpdated && this.open && !this.releaseScrollLock) {
221
- this.releaseScrollLock = lockScroll();
222
- document.addEventListener('keydown', this.onDocKeyDown);
151
+ if (this.hasUpdated && this.open) {
152
+ this.releaseScrollLock ??= lockScroll(this.ownerDocument);
153
+ this.activateOverlay();
154
+ queueMicrotask(() => this.overlay?.focusInitial());
223
155
  }
224
156
  }
225
157
  disconnectedCallback() {
226
158
  super.disconnectedCallback();
227
159
  this.releaseScrollLock?.();
228
160
  this.releaseScrollLock = undefined;
229
- document.removeEventListener('keydown', this.onDocKeyDown);
161
+ this.overlay?.suspend();
162
+ }
163
+ activateOverlay() {
164
+ if (this.overlay?.isActive()) {
165
+ this.overlay.resume();
166
+ return;
167
+ }
168
+ this.overlay = activateOverlay({
169
+ host: this,
170
+ panel: () => this.shadowRoot?.querySelector('[part="panel"]') ?? null,
171
+ onEscape: () => this.close('escape'),
172
+ onBackdrop: () => this.close('backdrop'),
173
+ });
230
174
  }
231
175
  /**
232
176
  * Close the dialog and return focus to whatever had it before the dialog
@@ -241,40 +185,6 @@ export class LyraToolSelectDialog extends LyraElement {
241
185
  return;
242
186
  this.open = false;
243
187
  this.emit('lyra-close', reason);
244
- this.lastTrigger?.focus();
245
- }
246
- // Bounds Tab/Shift+Tab to the panel while open. Order follows the search
247
- // input, then the use-defaults switch, then each visible tool row's
248
- // checkbox, then the footer slot -- the same order the flattened tree
249
- // already tabs through (mirrors lyra-tool-result-dialog's identical
250
- // header-buttons-then-body-then-footer ordering rationale).
251
- getFocusableElements() {
252
- const root = this.shadowRoot;
253
- if (!root)
254
- return [];
255
- const searchInput = root.querySelector('[part="search-input"]');
256
- const defaultsToggle = root.querySelector('[part="defaults-toggle"]');
257
- const checkboxes = Array.from(root.querySelectorAll('[part="tool-checkbox"]'));
258
- const fromSlot = (selector) => {
259
- const slot = root.querySelector(selector);
260
- return slot ? slot.assignedElements({ flatten: true }).flatMap(collectFocusable) : [];
261
- };
262
- return [
263
- ...(searchInput ? collectFocusable(searchInput) : []),
264
- ...(defaultsToggle ? collectFocusable(defaultsToggle) : []),
265
- ...checkboxes.flatMap(collectFocusable),
266
- ...fromSlot('slot[name="footer"]'),
267
- ].filter(isRendered);
268
- }
269
- getActiveElement() {
270
- let active = document.activeElement;
271
- while (active) {
272
- const inner = active.shadowRoot?.activeElement ?? null;
273
- if (!inner)
274
- break;
275
- active = inner;
276
- }
277
- return active;
278
188
  }
279
189
  emitChange() {
280
190
  this.emit('lyra-change', {
@@ -337,10 +247,10 @@ export class LyraToolSelectDialog extends LyraElement {
337
247
  ${tool.icon ? html `<span part="tool-icon" aria-hidden="true">${tool.icon}</span>` : nothing}${tool.name}
338
248
  </span>
339
249
  ${tool.description ? html `<span part="tool-description">${tool.description}</span>` : nothing}
340
- </lyra-checkbox>
341
- ${tool.disabled && tool.disabledReason
342
- ? html `<p part="tool-disabled-reason">${tool.disabledReason}</p>`
250
+ ${tool.disabled && tool.disabledReason
251
+ ? html `<span part="tool-disabled-reason">${tool.disabledReason}</span>`
343
252
  : nothing}
253
+ </lyra-checkbox>
344
254
  </li>
345
255
  `;
346
256
  }
@@ -349,7 +259,8 @@ export class LyraToolSelectDialog extends LyraElement {
349
259
  return html `
350
260
  <div part="category" role="group" aria-labelledby=${headingId}>
351
261
  <h3 part="category-heading" id=${headingId}>
352
- ${group.category}<span part="category-count">${group.tools.length}</span>
262
+ ${group.category}<span part="category-count" aria-hidden="true">${group.tools.length}</span
263
+ ><span class="sr-only">${group.tools.length} tool${group.tools.length === 1 ? '' : 's'}</span>
353
264
  </h3>
354
265
  <ul part="category-list">
355
266
  ${group.tools.map((tool) => this.renderTool(tool))}
@@ -10,7 +10,7 @@ export const styles = css `
10
10
  display: none;
11
11
  position: fixed;
12
12
  inset: 0;
13
- z-index: 1000;
13
+ z-index: var(--lyra-overlay-stack-index, 1000);
14
14
  align-items: center;
15
15
  justify-content: center;
16
16
  padding: var(--lyra-space-l);
@@ -79,7 +79,13 @@ export const styles = css `
79
79
  }
80
80
  [part='defaults-hint'] {
81
81
  margin: 0;
82
- padding-inline-start: calc(var(--lyra-icon-button-size) * 0.7);
82
+ /* Lines the hint up under the switch's label text, not its track --
83
+ lyra-switch's own [part="base"] uses a fixed 2.25rem track
84
+ inline-size plus a --lyra-space-s gap before its label (both defined
85
+ in switch.styles.ts, not exposed as tokens), so this indent is
86
+ coupled to that fixed geometry rather than derived from an unrelated
87
+ component's token. */
88
+ padding-inline-start: calc(2.25rem + var(--lyra-space-s));
83
89
  font-size: 0.75rem;
84
90
  color: var(--lyra-color-text-quiet);
85
91
  }
@@ -162,8 +168,11 @@ export const styles = css `
162
168
  color: var(--lyra-color-text-quiet);
163
169
  }
164
170
  [part='tool-disabled-reason'] {
165
- margin: 0;
166
- padding-inline-start: calc(var(--lyra-icon-button-size) * 0.7);
171
+ /* Slotted into lyra-checkbox's default slot alongside tool-name/
172
+ tool-description (see tool-select-dialog.ts's renderTool()), so it
173
+ inherits its indent for free from ::part(label)'s column layout
174
+ above instead of needing its own guessed padding. */
175
+ display: block;
167
176
  font-size: 0.75rem;
168
177
  color: var(--lyra-color-warning);
169
178
  }
@@ -15,7 +15,10 @@ import type { TreeItem } from './tree.js';
15
15
  * @customElement lyra-tree-node
16
16
  * @event lyra-node-toggle - `detail: { id, expanded }`, fired when this node is expanded or collapsed (via `expand()`/`collapse()`, the toggle button, or ArrowRight/ArrowLeft).
17
17
  * @event lyra-node-select - `detail: { id }`, fired when this node's primary action is activated (via `select()`, clicking anywhere in its row, or Enter/Space).
18
- * @csspart row, toggle, label, badge
18
+ * @csspart row - The tree row.
19
+ * @csspart toggle - The expand/collapse button.
20
+ * @csspart label - The node label.
21
+ * @csspart badge - The optional node badge.
19
22
  */
20
23
  export declare class LyraTreeNode extends LyraElement {
21
24
  static styles: import("lit").CSSResultGroup[];
@@ -90,7 +90,10 @@ const styles = css `
90
90
  * @customElement lyra-tree-node
91
91
  * @event lyra-node-toggle - `detail: { id, expanded }`, fired when this node is expanded or collapsed (via `expand()`/`collapse()`, the toggle button, or ArrowRight/ArrowLeft).
92
92
  * @event lyra-node-select - `detail: { id }`, fired when this node's primary action is activated (via `select()`, clicking anywhere in its row, or Enter/Space).
93
- * @csspart row, toggle, label, badge
93
+ * @csspart row - The tree row.
94
+ * @csspart toggle - The expand/collapse button.
95
+ * @csspart label - The node label.
96
+ * @csspart badge - The optional node badge.
94
97
  */
95
98
  export class LyraTreeNode extends LyraElement {
96
99
  constructor() {
@@ -35,15 +35,18 @@ export declare class LyraTree extends LyraElement {
35
35
  private get nodeElements();
36
36
  private childrenOf;
37
37
  /**
38
- * Memoized result of the last `visibleNodeElements()` walk. Every keydown
39
- * (ArrowUp/Down/Home/End especially) reads the full visible-node list, and
40
- * rebuilding it means a `shadowRoot.querySelectorAll` per currently-expanded
41
- * node -- invalidated below on the only two things that can change which
42
- * nodes are visible: a `data` reassignment, or a node expanding/collapsing.
38
+ * Every currently *visible* (ancestor-expanded) node, top-to-bottom.
39
+ *
40
+ * Recomputed on every call rather than memoized: `item`/`expanded` are
41
+ * plain public settable properties on `<lyra-tree-node>` (not just
42
+ * reachable through this class's own `data` setter or the bubbling
43
+ * `lyra-node-toggle` event), so a cache keyed off those two entry points
44
+ * alone would go stale the moment a caller mutated a node directly --
45
+ * e.g. `node.item = { ...node.item, children: [...] }` to append a child
46
+ * in place. This walk only runs from user-paced `keydown` handling (never
47
+ * a hot render-loop path), so the cost of a `shadowRoot.querySelectorAll`
48
+ * per currently-expanded node is not worth trading for that staleness risk.
43
49
  */
44
- private cachedVisible;
45
- private invalidateVisibleCache;
46
- /** Every currently *visible* (ancestor-expanded) node, top-to-bottom. */
47
50
  private visibleNodeElements;
48
51
  private findItem;
49
52
  protected willUpdate(changed: PropertyValues): void;
@@ -100,6 +103,12 @@ export declare class LyraTree extends LyraElement {
100
103
  * -- callers that immediately read `visibleNodeElements()`-derived state
101
104
  * (or call `collapseAll()` right after) should `await` this instead of
102
105
  * firing it and moving on.
106
+ *
107
+ * Guarded on `n.hasChildren`, matching `expand()`'s own invariant -- a leaf
108
+ * node's `expanded` must never be set to `true`, since `collapse()` (and
109
+ * this method's own counterpart, `collapseAll()`) refuse to act on a node
110
+ * that's `!hasChildren`, which would otherwise leave the leaf permanently
111
+ * stuck with a reflected `expanded` attribute nothing can clear.
103
112
  */
104
113
  expandAll(): Promise<void>;
105
114
  /**
@@ -38,14 +38,6 @@ export class LyraTree extends LyraElement {
38
38
  this.activeId = null;
39
39
  /** Set by `willUpdate()` when a `data` reassignment removes the node that currently holds real DOM focus; consumed by `updated()` to refocus the newly-designated `activeId` node once it's actually focusable. */
40
40
  this.pendingFocusId = null;
41
- /**
42
- * Memoized result of the last `visibleNodeElements()` walk. Every keydown
43
- * (ArrowUp/Down/Home/End especially) reads the full visible-node list, and
44
- * rebuilding it means a `shadowRoot.querySelectorAll` per currently-expanded
45
- * node -- invalidated below on the only two things that can change which
46
- * nodes are visible: a `data` reassignment, or a node expanding/collapsing.
47
- */
48
- this.cachedVisible = null;
49
41
  /**
50
42
  * A mouse click always lands directly on the node it interacts with --
51
43
  * `select()`/`expand()`/`collapse()` all emit their own node's id --
@@ -62,8 +54,6 @@ export class LyraTree extends LyraElement {
62
54
  * same-value, no-op assignment for them.
63
55
  */
64
56
  this.onNodeActivate = (e) => {
65
- if (e.type === 'lyra-node-toggle')
66
- this.invalidateVisibleCache();
67
57
  this.activeId = e.detail.id;
68
58
  };
69
59
  this.onTreeKeyDown = (e) => {
@@ -138,13 +128,20 @@ export class LyraTree extends LyraElement {
138
128
  childrenOf(node) {
139
129
  return [...(node.shadowRoot?.querySelectorAll(tag('tree-node')) ?? [])];
140
130
  }
141
- invalidateVisibleCache() {
142
- this.cachedVisible = null;
143
- }
144
- /** Every currently *visible* (ancestor-expanded) node, top-to-bottom. */
131
+ /**
132
+ * Every currently *visible* (ancestor-expanded) node, top-to-bottom.
133
+ *
134
+ * Recomputed on every call rather than memoized: `item`/`expanded` are
135
+ * plain public settable properties on `<lyra-tree-node>` (not just
136
+ * reachable through this class's own `data` setter or the bubbling
137
+ * `lyra-node-toggle` event), so a cache keyed off those two entry points
138
+ * alone would go stale the moment a caller mutated a node directly --
139
+ * e.g. `node.item = { ...node.item, children: [...] }` to append a child
140
+ * in place. This walk only runs from user-paced `keydown` handling (never
141
+ * a hot render-loop path), so the cost of a `shadowRoot.querySelectorAll`
142
+ * per currently-expanded node is not worth trading for that staleness risk.
143
+ */
145
144
  visibleNodeElements() {
146
- if (this.cachedVisible)
147
- return this.cachedVisible;
148
145
  const acc = [];
149
146
  const walk = (nodes) => {
150
147
  for (const n of nodes) {
@@ -154,7 +151,6 @@ export class LyraTree extends LyraElement {
154
151
  }
155
152
  };
156
153
  walk(this.nodeElements);
157
- this.cachedVisible = acc;
158
154
  return acc;
159
155
  }
160
156
  findItem(items, id) {
@@ -169,7 +165,6 @@ export class LyraTree extends LyraElement {
169
165
  }
170
166
  willUpdate(changed) {
171
167
  if (changed.has('data')) {
172
- this.invalidateVisibleCache();
173
168
  // `document.activeElement` collapses to the outermost light-DOM node
174
169
  // even when the real focus target is a nested descendant several
175
170
  // shadow roots down, so this also catches a focused nested node whose
@@ -269,12 +264,18 @@ export class LyraTree extends LyraElement {
269
264
  * -- callers that immediately read `visibleNodeElements()`-derived state
270
265
  * (or call `collapseAll()` right after) should `await` this instead of
271
266
  * firing it and moving on.
267
+ *
268
+ * Guarded on `n.hasChildren`, matching `expand()`'s own invariant -- a leaf
269
+ * node's `expanded` must never be set to `true`, since `collapse()` (and
270
+ * this method's own counterpart, `collapseAll()`) refuse to act on a node
271
+ * that's `!hasChildren`, which would otherwise leave the leaf permanently
272
+ * stuck with a reflected `expanded` attribute nothing can clear.
272
273
  */
273
274
  async expandAll() {
274
275
  const setAll = async (nodes) => {
275
276
  await Promise.all(nodes.map(async (n) => {
276
- n.expanded = true;
277
- this.invalidateVisibleCache();
277
+ if (n.hasChildren)
278
+ n.expanded = true;
278
279
  await n.updateComplete;
279
280
  await setAll(this.childrenOf(n));
280
281
  }));
@@ -29,7 +29,9 @@ export type TypingIndicatorSize = 'sm' | 'md';
29
29
  * accessible name derived from `label` is sufficient, set both as an
30
30
  * `aria-label` on the host *and* as a visually-hidden text node in the
31
31
  * shadow tree, so the name survives even if only one of the two is picked up
32
- * by a given assistive-tech/browser pairing. The animated dots/pulse/cursor
32
+ * by a given assistive-tech/browser pairing. An empty or whitespace-only
33
+ * `label` falls back to the default "Thinking…" copy in both places, rather
34
+ * than leaving the component with no accessible name at all. The animated dots/pulse/cursor
33
35
  * shape is `aria-hidden="true"` — it's decorative; `label` is the entire
34
36
  * accessible content, so nothing narrates individual animation frames.
35
37
  *
@@ -45,10 +47,18 @@ export declare class LyraTypingIndicator extends LyraElement {
45
47
  variant: TypingIndicatorVariant;
46
48
  /** Accessible name, exposed via `role="status"`. Not re-announced on every
47
49
  * animation frame — only mount (and any later change to this property)
48
- * produces a new announcement. */
50
+ * produces a new announcement. An empty or whitespace-only value falls
51
+ * back to the default "Thinking…" copy (see `accessibleLabel`) so the
52
+ * component never loses its accessible name. */
49
53
  label: string;
50
54
  /** Compact sizing for dense layouts (e.g. inline with a message bubble). */
51
55
  size: TypingIndicatorSize;
56
+ /** The accessible name actually used, falling back to the default
57
+ * "Thinking…" copy when `label` is set to an empty or whitespace-only
58
+ * string -- otherwise both the `aria-label` and the sr-only text node
59
+ * would go blank, leaving this purely-decorative component with no
60
+ * accessible name at all. */
61
+ private get accessibleLabel();
52
62
  protected willUpdate(changed: PropertyValues): void;
53
63
  render(): TemplateResult;
54
64
  private renderShape;
@@ -37,7 +37,9 @@ import { styles } from './typing-indicator.styles.js';
37
37
  * accessible name derived from `label` is sufficient, set both as an
38
38
  * `aria-label` on the host *and* as a visually-hidden text node in the
39
39
  * shadow tree, so the name survives even if only one of the two is picked up
40
- * by a given assistive-tech/browser pairing. The animated dots/pulse/cursor
40
+ * by a given assistive-tech/browser pairing. An empty or whitespace-only
41
+ * `label` falls back to the default "Thinking…" copy in both places, rather
42
+ * than leaving the component with no accessible name at all. The animated dots/pulse/cursor
41
43
  * shape is `aria-hidden="true"` — it's decorative; `label` is the entire
42
44
  * accessible content, so nothing narrates individual animation frames.
43
45
  *
@@ -54,22 +56,32 @@ export class LyraTypingIndicator extends LyraElement {
54
56
  this.variant = 'dots';
55
57
  /** Accessible name, exposed via `role="status"`. Not re-announced on every
56
58
  * animation frame — only mount (and any later change to this property)
57
- * produces a new announcement. */
59
+ * produces a new announcement. An empty or whitespace-only value falls
60
+ * back to the default "Thinking…" copy (see `accessibleLabel`) so the
61
+ * component never loses its accessible name. */
58
62
  this.label = 'Thinking…';
59
63
  /** Compact sizing for dense layouts (e.g. inline with a message bubble). */
60
64
  this.size = 'md';
61
65
  }
62
66
  static { this.styles = [LyraElement.styles, styles, srOnly]; }
67
+ /** The accessible name actually used, falling back to the default
68
+ * "Thinking…" copy when `label` is set to an empty or whitespace-only
69
+ * string -- otherwise both the `aria-label` and the sr-only text node
70
+ * would go blank, leaving this purely-decorative component with no
71
+ * accessible name at all. */
72
+ get accessibleLabel() {
73
+ return this.label.trim() || 'Thinking…';
74
+ }
63
75
  willUpdate(changed) {
64
76
  this.setAttribute('role', 'status');
65
77
  if (changed.has('label')) {
66
- this.setAttribute('aria-label', this.label);
78
+ this.setAttribute('aria-label', this.accessibleLabel);
67
79
  }
68
80
  }
69
81
  render() {
70
82
  return html `
71
83
  <span part="base" aria-hidden="true">${this.renderShape()}</span>
72
- <span class="sr-only">${this.label}</span>
84
+ <span class="sr-only">${this.accessibleLabel}</span>
73
85
  `;
74
86
  }
75
87
  renderShape() {
@@ -18,8 +18,8 @@ export const styles = css `
18
18
  align-items: center;
19
19
  vertical-align: middle;
20
20
  line-height: 1;
21
- --lyra-typing-dot-size: 0.5rem;
22
- --lyra-typing-gap: 0.25rem;
21
+ --lyra-typing-dot-size: var(--lyra-space-s);
22
+ --lyra-typing-gap: var(--lyra-space-xs);
23
23
  --lyra-typing-cursor-width: 0.125rem;
24
24
  --lyra-typing-cursor-height: 1em;
25
25
  }