@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
@@ -10,7 +10,7 @@ export type ToolResultStatus = 'pending' | 'running' | 'success' | 'error' | 'de
10
10
  * other string is whatever a caller passes to `close()` directly (e.g. a
11
11
  * consumer's own footer action).
12
12
  */
13
- export type ToolResultDialogCloseReason = 'escape' | 'backdrop' | 'close-button' | 'api' | string;
13
+ export type ToolResultDialogCloseReason = 'escape' | 'backdrop' | 'close-button' | 'api' | (string & Record<never, never>);
14
14
  /**
15
15
  * `<lyra-tool-result-dialog>` — a full tool-call detail overlay: a status/
16
16
  * duration header plus a `body` slot where a consumer typically places a
@@ -34,6 +34,10 @@ export type ToolResultDialogCloseReason = 'escape' | 'backdrop' | 'close-button'
34
34
  * return to, so no separate scroll-lock/focus-trap bookkeeping is needed for
35
35
  * the transition itself.
36
36
  *
37
+ * Stacking: opening more than one of these dialogs at once is supported --
38
+ * Escape and the Tab focus trap only ever act on the topmost open instance,
39
+ * so instances beneath it stay open and untouched until the one on top closes.
40
+ *
37
41
  * @customElement lyra-tool-result-dialog
38
42
  * @slot body - The dialog's main content — typically a `<lyra-tabs>` with
39
43
  * Input/Preview/JSON/Raw panels, entirely consumer-assembled.
@@ -58,11 +62,22 @@ export type ToolResultDialogCloseReason = 'escape' | 'backdrop' | 'close-button'
58
62
  */
59
63
  export declare class LyraToolResultDialog extends LyraElement {
60
64
  static styles: import("lit").CSSResultGroup[];
61
- /** Whether the dialog is open. Set this (or call `close()`) — there is no separate `show()`/`hide()` pair. */
65
+ /**
66
+ * Whether the dialog is open. Set this (or call `close()`) — there is no
67
+ * separate `show()`/`hide()` pair. Both paths restore focus to the trigger
68
+ * element identically; only `close()` additionally fires
69
+ * `lyra-dialog-close`, since a direct assignment carries no reason string
70
+ * to attach to that event.
71
+ */
62
72
  open: boolean;
63
73
  /** The tool's name, rendered prominently in the header. */
64
74
  toolName: string;
65
- /** The tool call's current lifecycle state — drives the header's status badge. */
75
+ /**
76
+ * The tool call's current lifecycle state — drives the header's status
77
+ * badge. An out-of-union value (e.g. a stray `status` attribute, or a
78
+ * direct property assignment from an untyped caller) is treated as
79
+ * `'pending'` rather than crashing render.
80
+ */
66
81
  status: ToolResultStatus;
67
82
  /** How long the call took, in milliseconds. Omitted from the header entirely when unset. */
68
83
  durationMs?: number;
@@ -70,7 +85,7 @@ export declare class LyraToolResultDialog extends LyraElement {
70
85
  maximized: boolean;
71
86
  private hasFooterSlot;
72
87
  private releaseScrollLock?;
73
- private lastTrigger?;
88
+ private overlay?;
74
89
  private readonly titleId;
75
90
  protected willUpdate(changed: PropertyValues): void;
76
91
  protected updated(changed: PropertyValues): void;
@@ -85,14 +100,19 @@ export declare class LyraToolResultDialog extends LyraElement {
85
100
  * call this directly with its own reason string, so every dismissal path
86
101
  * funnels through the same event instead of the consumer having to also
87
102
  * toggle `open` itself.
103
+ *
104
+ * Focus restoration follows the `open` lifecycle, so a direct `.open =
105
+ * false` assignment restores focus identically to calling `close()` -- the
106
+ * one thing a direct assignment still can't do is fire
107
+ * `lyra-dialog-close`, since there's no reason string to attach without
108
+ * going through this method.
88
109
  */
89
110
  close(reason?: ToolResultDialogCloseReason): void;
90
111
  private onBackdropClick;
91
112
  private onCloseButtonClick;
92
113
  private toggleMaximized;
93
- private onDocKeyDown;
94
- private getFocusableElements;
95
- private getActiveElement;
114
+ private activateOverlay;
115
+ private deactivateOverlay;
96
116
  render(): TemplateResult;
97
117
  }
98
118
  declare global {
@@ -4,45 +4,15 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { html, svg, nothing } from 'lit';
7
+ import { html, svg, nothing, } from 'lit';
8
8
  import { property, state } from 'lit/decorators.js';
9
9
  import { LyraElement } from '../../internal/lyra-element.js';
10
10
  import { defineElement } from '../../internal/prefix.js';
11
11
  import { lockScroll } from '../../internal/scroll-lock.js';
12
+ import { activateOverlay } from '../../internal/overlay-manager.js';
12
13
  import { nextId } from '../../internal/a11y.js';
13
14
  import { closeIcon, expandIcon } from '../../internal/icons.js';
14
15
  import { styles } from './tool-result-dialog.styles.js';
15
- const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
16
- // Shadow-piercing so a slotted custom element's real focusable target (e.g.
17
- // an <input> inside its own shadow root) is found even though the host tag
18
- // itself doesn't match FOCUSABLE_SELECTOR. Deliberately duplicated from
19
- // lyra-dialog's identical helper rather than imported/shared -- this
20
- // component is its own standalone overlay with its own header chrome and
21
- // must not depend on lyra-dialog (see this file's authoring notes).
22
- function collectFocusable(el) {
23
- const result = [];
24
- if (el.matches(FOCUSABLE_SELECTOR)) {
25
- result.push(el);
26
- }
27
- if (el instanceof HTMLSlotElement) {
28
- for (const assigned of el.assignedElements({ flatten: true })) {
29
- result.push(...collectFocusable(assigned));
30
- }
31
- return result;
32
- }
33
- const container = el.shadowRoot ?? el;
34
- for (const child of Array.from(container.children)) {
35
- result.push(...collectFocusable(child));
36
- }
37
- return result;
38
- }
39
- /** Whether `el` is actually laid out/paintable -- `checkVisibility()` (falling
40
- * back to `getClientRects().length` where unsupported) correctly follows
41
- * flattened-tree/slot assignment, unlike `offsetParent`. Mirrors
42
- * lyra-dialog's/lyra-widget's identical helper. */
43
- function isRendered(el) {
44
- return el.checkVisibility ? el.checkVisibility() : el.getClientRects().length > 0;
45
- }
46
16
  // Mirrors the shared icon set's viewBox/stroke conventions
47
17
  // (internal/icons.ts's chevronIcon()/closeIcon()/etc.) without adding
48
18
  // tool-result-specific glyphs to that module -- it's off limits here -- so
@@ -121,6 +91,24 @@ const STATUS_LABEL = {
121
91
  error: 'Error',
122
92
  denied: 'Denied',
123
93
  };
94
+ const STATUS_VALUES = new Set(Object.keys(STATUS_LABEL));
95
+ /**
96
+ * Normalizes `status` at the attribute boundary -- an out-of-union value
97
+ * (markup a caller doesn't fully control, or a raw string from an untyped
98
+ * consumer) falls back to `'pending'` here rather than reaching
99
+ * STATUS_ICON/STATUS_LABEL as a bad lookup key and crashing `render()`. This
100
+ * only covers attribute parsing; a `.status = ...` assignment made directly
101
+ * as a property bypasses converters entirely, which is why `render()` below
102
+ * also falls back at the STATUS_ICON/STATUS_LABEL lookup itself.
103
+ */
104
+ const statusConverter = {
105
+ fromAttribute(value) {
106
+ return value !== null && STATUS_VALUES.has(value) ? value : 'pending';
107
+ },
108
+ toAttribute(value) {
109
+ return value;
110
+ },
111
+ };
124
112
  /** `820` -> `"820ms"`; `1500` -> `"1.5s"`; `2000` -> `"2s"`. Sub-second
125
113
  * durations are the common case for a single tool call, so they get the
126
114
  * more precise unit; once a call runs a full second or longer, trimming to
@@ -157,6 +145,10 @@ function formatDuration(ms) {
157
145
  * return to, so no separate scroll-lock/focus-trap bookkeeping is needed for
158
146
  * the transition itself.
159
147
  *
148
+ * Stacking: opening more than one of these dialogs at once is supported --
149
+ * Escape and the Tab focus trap only ever act on the topmost open instance,
150
+ * so instances beneath it stay open and untouched until the one on top closes.
151
+ *
160
152
  * @customElement lyra-tool-result-dialog
161
153
  * @slot body - The dialog's main content — typically a `<lyra-tabs>` with
162
154
  * Input/Preview/JSON/Raw panels, entirely consumer-assembled.
@@ -182,11 +174,22 @@ function formatDuration(ms) {
182
174
  export class LyraToolResultDialog extends LyraElement {
183
175
  constructor() {
184
176
  super(...arguments);
185
- /** Whether the dialog is open. Set this (or call `close()`) — there is no separate `show()`/`hide()` pair. */
177
+ /**
178
+ * Whether the dialog is open. Set this (or call `close()`) — there is no
179
+ * separate `show()`/`hide()` pair. Both paths restore focus to the trigger
180
+ * element identically; only `close()` additionally fires
181
+ * `lyra-dialog-close`, since a direct assignment carries no reason string
182
+ * to attach to that event.
183
+ */
186
184
  this.open = false;
187
185
  /** The tool's name, rendered prominently in the header. */
188
186
  this.toolName = '';
189
- /** The tool call's current lifecycle state — drives the header's status badge. */
187
+ /**
188
+ * The tool call's current lifecycle state — drives the header's status
189
+ * badge. An out-of-union value (e.g. a stray `status` attribute, or a
190
+ * direct property assignment from an untyped caller) is treated as
191
+ * `'pending'` rather than crashing render.
192
+ */
190
193
  this.status = 'pending';
191
194
  /** Near-fullscreen presentation of the same open dialog. */
192
195
  this.maximized = false;
@@ -196,7 +199,7 @@ export class LyraToolResultDialog extends LyraElement {
196
199
  this.hasFooterSlot = e.target.assignedElements({ flatten: true }).length > 0;
197
200
  };
198
201
  this.onBackdropClick = () => {
199
- this.close('backdrop');
202
+ this.overlay?.dismissBackdrop();
200
203
  };
201
204
  this.onCloseButtonClick = () => {
202
205
  this.close('close-button');
@@ -205,33 +208,6 @@ export class LyraToolResultDialog extends LyraElement {
205
208
  this.maximized = !this.maximized;
206
209
  this.emit('lyra-maximize-change', this.maximized);
207
210
  };
208
- this.onDocKeyDown = (e) => {
209
- if (!this.open)
210
- return;
211
- if (e.key === 'Escape') {
212
- e.preventDefault();
213
- this.close('escape');
214
- return;
215
- }
216
- if (e.key !== 'Tab')
217
- return;
218
- const focusable = this.getFocusableElements();
219
- if (focusable.length === 0) {
220
- e.preventDefault();
221
- return;
222
- }
223
- const first = focusable[0];
224
- const last = focusable[focusable.length - 1];
225
- const active = this.getActiveElement();
226
- if (e.shiftKey && active === first) {
227
- e.preventDefault();
228
- last.focus();
229
- }
230
- else if (!e.shiftKey && active === last) {
231
- e.preventDefault();
232
- first.focus();
233
- }
234
- };
235
211
  }
236
212
  static { this.styles = [LyraElement.styles, styles]; }
237
213
  willUpdate(changed) {
@@ -240,35 +216,18 @@ export class LyraToolResultDialog extends LyraElement {
240
216
  }
241
217
  if (changed.has('open')) {
242
218
  if (this.open) {
243
- // Captured here (before render) rather than from a click event on
244
- // some specific internal control -- like lyra-dialog, a trigger
245
- // typically lives *outside* this component entirely, so "whatever
246
- // had focus right before open" is the only generally correct
247
- // definition of "the trigger".
248
- const active = this.getActiveElement();
249
- this.lastTrigger = active instanceof HTMLElement ? active : undefined;
250
- this.releaseScrollLock = lockScroll();
251
- document.addEventListener('keydown', this.onDocKeyDown);
219
+ this.activateOverlay();
252
220
  }
253
221
  else {
254
- this.releaseScrollLock?.();
255
- this.releaseScrollLock = undefined;
256
- document.removeEventListener('keydown', this.onDocKeyDown);
222
+ this.deactivateOverlay();
257
223
  }
258
224
  }
259
225
  }
260
- // Runs after render (not willUpdate) so [part="panel"] has already landed
261
- // in the DOM before the fallback .focus() call below can rely on it --
262
- // mirrors lyra-dialog's identical ordering rationale.
226
+ // Runs after render so the manager can resolve the panel and its composed
227
+ // focus targets, including controls projected through either slot.
263
228
  updated(changed) {
264
229
  if (changed.has('open') && this.open) {
265
- const first = this.getFocusableElements()[0];
266
- if (first) {
267
- first.focus();
268
- }
269
- else {
270
- this.shadowRoot?.querySelector('[part="panel"]')?.focus();
271
- }
230
+ this.overlay?.focusInitial();
272
231
  }
273
232
  }
274
233
  connectedCallback() {
@@ -277,16 +236,22 @@ export class LyraToolResultDialog extends LyraElement {
277
236
  // instance) fires disconnectedCallback then connectedCallback
278
237
  // synchronously with no update in between, so willUpdate never reruns to
279
238
  // notice `open` is still true -- restore the scroll lock/trap it dropped.
280
- if (this.hasUpdated && this.open && !this.releaseScrollLock) {
281
- this.releaseScrollLock = lockScroll();
282
- document.addEventListener('keydown', this.onDocKeyDown);
239
+ if (this.hasUpdated && this.open) {
240
+ if (this.overlay?.isActive()) {
241
+ this.overlay.resume();
242
+ this.releaseScrollLock ??= lockScroll(this.ownerDocument);
243
+ }
244
+ else {
245
+ this.activateOverlay();
246
+ }
247
+ queueMicrotask(() => this.overlay?.focusInitial());
283
248
  }
284
249
  }
285
250
  disconnectedCallback() {
286
251
  super.disconnectedCallback();
287
252
  this.releaseScrollLock?.();
288
253
  this.releaseScrollLock = undefined;
289
- document.removeEventListener('keydown', this.onDocKeyDown);
254
+ this.overlay?.suspend();
290
255
  }
291
256
  /**
292
257
  * Close the dialog and return focus to whatever had it before the dialog
@@ -296,39 +261,35 @@ export class LyraToolResultDialog extends LyraElement {
296
261
  * call this directly with its own reason string, so every dismissal path
297
262
  * funnels through the same event instead of the consumer having to also
298
263
  * toggle `open` itself.
264
+ *
265
+ * Focus restoration follows the `open` lifecycle, so a direct `.open =
266
+ * false` assignment restores focus identically to calling `close()` -- the
267
+ * one thing a direct assignment still can't do is fire
268
+ * `lyra-dialog-close`, since there's no reason string to attach without
269
+ * going through this method.
299
270
  */
300
271
  close(reason = 'api') {
301
272
  if (!this.open)
302
273
  return;
303
274
  this.open = false;
304
275
  this.emit('lyra-dialog-close', reason);
305
- this.lastTrigger?.focus();
306
276
  }
307
- // Bounds Tab/Shift+Tab to the panel while open. Order follows the header's
308
- // own maximize/close buttons, then the body slot, then the footer slot --
309
- // the same order the flattened tree already tabs through (mirrors
310
- // lyra-widget's identical actions-before-body ordering for its fullscreen
311
- // mode).
312
- getFocusableElements() {
313
- const root = this.shadowRoot;
314
- if (!root)
315
- return [];
316
- const fromSlot = (selector) => {
317
- const slot = root.querySelector(selector);
318
- return slot ? slot.assignedElements({ flatten: true }).flatMap(collectFocusable) : [];
319
- };
320
- const headerButtons = Array.from(root.querySelectorAll('[part="maximize-button"], [part="close-button"]'));
321
- return [...headerButtons, ...fromSlot('slot[name="body"]'), ...fromSlot('slot[name="footer"]')].filter(isRendered);
277
+ activateOverlay() {
278
+ if (this.overlay?.isActive())
279
+ return;
280
+ this.releaseScrollLock ??= lockScroll(this.ownerDocument);
281
+ this.overlay = activateOverlay({
282
+ host: this,
283
+ panel: () => this.shadowRoot?.querySelector('[part="panel"]') ?? null,
284
+ onEscape: () => this.close('escape'),
285
+ onBackdrop: () => this.close('backdrop'),
286
+ });
322
287
  }
323
- getActiveElement() {
324
- let active = document.activeElement;
325
- while (active) {
326
- const inner = active.shadowRoot?.activeElement ?? null;
327
- if (!inner)
328
- break;
329
- active = inner;
330
- }
331
- return active;
288
+ deactivateOverlay() {
289
+ this.releaseScrollLock?.();
290
+ this.releaseScrollLock = undefined;
291
+ this.overlay?.deactivate();
292
+ this.overlay = undefined;
332
293
  }
333
294
  render() {
334
295
  const hasDuration = this.durationMs != null;
@@ -344,7 +305,11 @@ export class LyraToolResultDialog extends LyraElement {
344
305
  <div part="header">
345
306
  <div part="title">
346
307
  <span part="tool-name" id=${this.titleId}>${this.toolName || 'Tool call'}</span>
347
- <span part="status">${STATUS_ICON[this.status]()}<span>${STATUS_LABEL[this.status]}</span></span>
308
+ <span part="status"
309
+ >${(STATUS_ICON[this.status] ?? STATUS_ICON.pending)()}<span
310
+ >${STATUS_LABEL[this.status] ?? STATUS_LABEL.pending}</span
311
+ ></span
312
+ >
348
313
  ${hasDuration ? html `<span part="duration">${formatDuration(this.durationMs)}</span>` : nothing}
349
314
  </div>
350
315
  <div part="header-actions">
@@ -379,7 +344,7 @@ __decorate([
379
344
  property({ attribute: 'tool-name' })
380
345
  ], LyraToolResultDialog.prototype, "toolName", void 0);
381
346
  __decorate([
382
- property({ reflect: true })
347
+ property({ reflect: true, converter: statusConverter })
383
348
  ], LyraToolResultDialog.prototype, "status", void 0);
384
349
  __decorate([
385
350
  property({ type: Number, attribute: 'duration-ms' })
@@ -14,7 +14,7 @@ export const styles = css `
14
14
  display: none;
15
15
  position: fixed;
16
16
  inset: 0;
17
- z-index: 1000;
17
+ z-index: var(--lyra-overlay-stack-index, 1000);
18
18
  align-items: center;
19
19
  justify-content: center;
20
20
  padding: var(--lyra-space-l);
@@ -71,11 +71,3 @@ export declare function findToolRenderer(toolName: string, payload: unknown, reg
71
71
  * failed promise forever.
72
72
  */
73
73
  export declare function loadToolRenderer(def: ToolRendererDefinition): Promise<ToolRendererDefinition>;
74
- /**
75
- * Test-only utility: empties the default registry and its `load()` cache so
76
- * one test's `registerToolRenderer()` calls can't leak into the next. Mirrors
77
- * the fact that this module (like `toaster.ts`'s per-placement region map) is
78
- * deliberately singleton, module-level state -- there is no per-instance
79
- * registry to construct fresh in a test's own `fixture()` call instead.
80
- */
81
- export declare function clearToolRenderers(): void;
@@ -72,11 +72,16 @@ export function loadToolRenderer(def) {
72
72
  return promise;
73
73
  }
74
74
  /**
75
- * Test-only utility: empties the default registry and its `load()` cache so
75
+ * @internal Test-only utility: empties the default registry and its `load()` cache so
76
76
  * one test's `registerToolRenderer()` calls can't leak into the next. Mirrors
77
77
  * the fact that this module (like `toaster.ts`'s per-placement region map) is
78
78
  * deliberately singleton, module-level state -- there is no per-instance
79
- * registry to construct fresh in a test's own `fixture()` call instead.
79
+ * registry to construct fresh in a test's own `fixture()` call instead. Not
80
+ * part of the public API; not re-exported from the root barrel -- a
81
+ * consuming app has no legitimate reason to wipe this page's entire
82
+ * registered-renderer registry at runtime, only a test suite resetting
83
+ * state between cases (import directly from this module, as the test suite
84
+ * already does).
80
85
  */
81
86
  export function clearToolRenderers() {
82
87
  defaultRegistry.clear();
@@ -7,8 +7,9 @@ import '../json-viewer/json-viewer.js';
7
7
  * `<lyra-tool-result-view>` — renders a tool call's result via whichever
8
8
  * custom renderer a host app has registered for it (see `registerToolRenderer()`
9
9
  * in `registry.ts`), falling back to `<lyra-json-viewer>` whenever no
10
- * renderer matches, a renderer's optional `load()` rejects, or its `render()`
11
- * throws. This component owns none of the actual visual weight of a
10
+ * renderer matches, a candidate renderer's `matches()` predicate throws during
11
+ * dispatch, a renderer's optional `load()` rejects, or its `render()` throws.
12
+ * This component owns none of the actual visual weight of a
12
13
  * populated tool result — that's entirely whatever the registered renderer
13
14
  * returns; this is just the dispatch + fallback + loading-state shell.
14
15
  *
@@ -25,7 +26,8 @@ import '../json-viewer/json-viewer.js';
25
26
  * @customElement lyra-tool-result-view
26
27
  * @event lyra-render-error - `detail: { toolName, error }` — fired immediately
27
28
  * before falling back to `<lyra-json-viewer>`, whether because no renderer
28
- * matched, a renderer's `load()` rejected, or its `render()` threw.
29
+ * matched, a candidate renderer's `matches()` predicate threw during dispatch,
30
+ * a renderer's `load()` rejected, or its `render()` threw.
29
31
  * @csspart base - The root wrapper around the resolved renderer's output (or the loading/fallback view).
30
32
  */
31
33
  export declare class LyraToolResultView extends LyraElement {
@@ -42,6 +44,7 @@ export declare class LyraToolResultView extends LyraElement {
42
44
  fallback: string;
43
45
  private renderState;
44
46
  private generation;
47
+ private resolvedLazy?;
45
48
  protected willUpdate(changed: PropertyValues): void;
46
49
  private resolve;
47
50
  private renderWith;
@@ -17,8 +17,9 @@ const FALLBACK_STATE = { kind: 'fallback' };
17
17
  * `<lyra-tool-result-view>` — renders a tool call's result via whichever
18
18
  * custom renderer a host app has registered for it (see `registerToolRenderer()`
19
19
  * in `registry.ts`), falling back to `<lyra-json-viewer>` whenever no
20
- * renderer matches, a renderer's optional `load()` rejects, or its `render()`
21
- * throws. This component owns none of the actual visual weight of a
20
+ * renderer matches, a candidate renderer's `matches()` predicate throws during
21
+ * dispatch, a renderer's optional `load()` rejects, or its `render()` throws.
22
+ * This component owns none of the actual visual weight of a
22
23
  * populated tool result — that's entirely whatever the registered renderer
23
24
  * returns; this is just the dispatch + fallback + loading-state shell.
24
25
  *
@@ -35,7 +36,8 @@ const FALLBACK_STATE = { kind: 'fallback' };
35
36
  * @customElement lyra-tool-result-view
36
37
  * @event lyra-render-error - `detail: { toolName, error }` — fired immediately
37
38
  * before falling back to `<lyra-json-viewer>`, whether because no renderer
38
- * matched, a renderer's `load()` rejected, or its `render()` threw.
39
+ * matched, a candidate renderer's `matches()` predicate threw during dispatch,
40
+ * a renderer's `load()` rejected, or its `render()` threw.
39
41
  * @csspart base - The root wrapper around the resolved renderer's output (or the loading/fallback view).
40
42
  */
41
43
  export class LyraToolResultView extends LyraElement {
@@ -64,12 +66,23 @@ export class LyraToolResultView extends LyraElement {
64
66
  async resolve() {
65
67
  const generation = ++this.generation;
66
68
  const registry = this.registry ?? getDefaultToolRendererRegistry();
67
- const def = findToolRenderer(this.toolName, this.result, registry);
69
+ let def;
70
+ try {
71
+ def = findToolRenderer(this.toolName, this.result, registry);
72
+ }
73
+ catch (error) {
74
+ this.fail(error);
75
+ return;
76
+ }
68
77
  if (!def) {
69
78
  this.fail(new Error(`<lyra-tool-result-view>: no renderer registered for tool "${this.toolName}"`));
70
79
  return;
71
80
  }
72
81
  if (def.load) {
82
+ if (this.resolvedLazy?.def === def) {
83
+ this.renderWith(this.resolvedLazy.resolved);
84
+ return;
85
+ }
73
86
  this.renderState = { kind: 'loading' };
74
87
  let resolved;
75
88
  try {
@@ -83,6 +96,7 @@ export class LyraToolResultView extends LyraElement {
83
96
  }
84
97
  if (generation !== this.generation)
85
98
  return;
99
+ this.resolvedLazy = { def, resolved };
86
100
  this.renderWith(resolved);
87
101
  return;
88
102
  }
@@ -39,11 +39,10 @@ export type ToolSelectDialogCloseReason = 'escape' | 'backdrop' | 'api' | string
39
39
  * tool-enablement dialog for picking which agent tools are available in a
40
40
  * conversation.
41
41
  *
42
- * This is its own standalone overlay implementation (`role="dialog"`,
43
- * focus-trapped, Escape/backdrop-dismissible, scroll-locking) rather than
44
- * nesting a `<lyra-dialog>` in its shadow template see `<lyra-dialog>`'s
45
- * own header comment for why a new overlay component in this library
46
- * duplicates that pattern locally instead of composing the previous one.
42
+ * This renders its own dialog panel rather than nesting a `<lyra-dialog>` in
43
+ * its shadow template. Shared overlay infrastructure coordinates stacking,
44
+ * focus trapping, Escape/backdrop dismissal, and focus return with every
45
+ * other overlay in the same document.
47
46
  *
48
47
  * `useDefaults` is a single top-level switch: while `true`, every per-tool
49
48
  * checkbox below renders disabled (still reflecting whatever `selected`
@@ -81,14 +80,17 @@ export type ToolSelectDialogCloseReason = 'escape' | 'backdrop' | 'api' | string
81
80
  * @csspart empty - The "no tools" / "no matches" message.
82
81
  * @csspart category - A single category's wrapper (`role="group"`).
83
82
  * @csspart category-heading - A category's heading.
84
- * @csspart category-count - The visible tool count next to a category heading.
83
+ * @csspart category-count - The terse, `aria-hidden` tool count next to a category heading
84
+ * (the heading's accessible name gets the full sentence from an sr-only sibling instead).
85
85
  * @csspart category-list - The `<ul>` of tool rows within a category.
86
86
  * @csspart tool-row - A single tool's `<li>` row.
87
87
  * @csspart tool-checkbox - A row's `<lyra-checkbox>`.
88
88
  * @csspart tool-name - A row's name text (plus its `icon`, if set).
89
89
  * @csspart tool-icon - A row's leading icon glyph, when `icon` is set.
90
90
  * @csspart tool-description - A row's optional description text.
91
- * @csspart tool-disabled-reason - A disabled row's `disabledReason` text.
91
+ * @csspart tool-disabled-reason - A disabled row's `disabledReason` text, slotted inside
92
+ * `tool-checkbox` (alongside `tool-name`/`tool-description`) so it contributes to the
93
+ * checkbox's accessible name/description instead of going unannounced.
92
94
  * @csspart footer - The wrapper around the `footer` slot.
93
95
  */
94
96
  export declare class LyraToolSelectDialog extends LyraElement {
@@ -109,13 +111,14 @@ export declare class LyraToolSelectDialog extends LyraElement {
109
111
  private query;
110
112
  private hasFooterSlot;
111
113
  private releaseScrollLock?;
112
- private lastTrigger?;
114
+ private overlay?;
113
115
  private readonly titleId;
114
116
  private readonly categoryIds;
115
117
  protected willUpdate(changed: PropertyValues): void;
116
118
  protected updated(changed: PropertyValues): void;
117
119
  connectedCallback(): void;
118
120
  disconnectedCallback(): void;
121
+ private activateOverlay;
119
122
  private onFooterSlotChange;
120
123
  /**
121
124
  * Close the dialog and return focus to whatever had it before the dialog
@@ -127,9 +130,6 @@ export declare class LyraToolSelectDialog extends LyraElement {
127
130
  */
128
131
  close(reason?: ToolSelectDialogCloseReason): void;
129
132
  private onBackdropClick;
130
- private onDocKeyDown;
131
- private getFocusableElements;
132
- private getActiveElement;
133
133
  private emitChange;
134
134
  private onSearchInput;
135
135
  private onDefaultsToggle;