@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
@@ -2,44 +2,44 @@ import { css } from 'lit';
2
2
  export const styles = css `
3
3
  :host {
4
4
  display: block;
5
- --accent-width: 4px;
6
- --show-duration: var(--lyra-transition-base, 180ms ease-out);
7
- --hide-duration: var(--lyra-transition-base, 180ms ease-out);
8
- --padding: var(--lyra-space-m);
9
- --font-size: 1rem;
10
- --accent-color: var(--lyra-color-border);
5
+ --lyra-toast-accent-width: 4px;
6
+ --lyra-toast-show-duration: var(--lyra-transition-base, 180ms ease-out);
7
+ --lyra-toast-hide-duration: var(--lyra-transition-base, 180ms ease-out);
8
+ --lyra-toast-padding: var(--lyra-space-m);
9
+ --lyra-toast-font-size: 1rem;
10
+ --lyra-toast-accent-color: var(--lyra-color-border);
11
11
  }
12
12
  :host([variant='brand']) {
13
- --accent-color: var(--lyra-color-brand);
13
+ --lyra-toast-accent-color: var(--lyra-color-brand);
14
14
  }
15
15
  :host([variant='success']) {
16
- --accent-color: var(--lyra-color-success);
16
+ --lyra-toast-accent-color: var(--lyra-color-success);
17
17
  }
18
18
  :host([variant='warning']) {
19
- --accent-color: var(--lyra-color-warning);
19
+ --lyra-toast-accent-color: var(--lyra-color-warning);
20
20
  }
21
21
  :host([variant='danger']) {
22
- --accent-color: var(--lyra-color-danger);
22
+ --lyra-toast-accent-color: var(--lyra-color-danger);
23
23
  }
24
24
  :host([size='xs']) {
25
- --padding: var(--lyra-space-xs);
26
- --font-size: 0.75rem;
25
+ --lyra-toast-padding: var(--lyra-space-xs);
26
+ --lyra-toast-font-size: 0.75rem;
27
27
  }
28
28
  :host([size='s']) {
29
- --padding: var(--lyra-space-s);
30
- --font-size: 0.875rem;
29
+ --lyra-toast-padding: var(--lyra-space-s);
30
+ --lyra-toast-font-size: 0.875rem;
31
31
  }
32
32
  :host([size='m']) {
33
- --padding: var(--lyra-space-m);
34
- --font-size: 1rem;
33
+ --lyra-toast-padding: var(--lyra-space-m);
34
+ --lyra-toast-font-size: 1rem;
35
35
  }
36
36
  :host([size='l']) {
37
- --padding: var(--lyra-space-l);
38
- --font-size: 1.125rem;
37
+ --lyra-toast-padding: var(--lyra-space-l);
38
+ --lyra-toast-font-size: 1.125rem;
39
39
  }
40
40
  :host([size='xl']) {
41
- --padding: calc(var(--lyra-space-l) * 1.5);
42
- --font-size: 1.25rem;
41
+ --lyra-toast-padding: calc(var(--lyra-space-l) * 1.5);
42
+ --lyra-toast-font-size: 1.25rem;
43
43
  }
44
44
 
45
45
  [part='toast-item'] {
@@ -48,9 +48,9 @@ export const styles = css `
48
48
  align-items: start;
49
49
  gap: var(--lyra-space-s);
50
50
  inline-size: 100%;
51
- padding: var(--padding);
52
- padding-inline-start: calc(var(--padding) + var(--accent-width));
53
- font-size: var(--font-size);
51
+ padding: var(--lyra-toast-padding);
52
+ padding-inline-start: calc(var(--lyra-toast-padding) + var(--lyra-toast-accent-width));
53
+ font-size: var(--lyra-toast-font-size);
54
54
  background: var(--lyra-color-surface);
55
55
  color: var(--lyra-color-text);
56
56
  border: 1px solid var(--lyra-color-border);
@@ -59,8 +59,13 @@ export const styles = css `
59
59
  opacity: 0;
60
60
  transform: translateY(-8px);
61
61
  transition:
62
- opacity var(--show-duration),
63
- transform var(--show-duration);
62
+ opacity var(--lyra-toast-show-duration),
63
+ transform var(--lyra-toast-show-duration);
64
+ }
65
+ :host([data-hiding]) [part='toast-item'] {
66
+ transition:
67
+ opacity var(--lyra-toast-hide-duration),
68
+ transform var(--lyra-toast-hide-duration);
64
69
  }
65
70
  :host([data-visible]) [part='toast-item'] {
66
71
  opacity: 1;
@@ -76,20 +81,46 @@ export const styles = css `
76
81
  position: absolute;
77
82
  inset-block: 0;
78
83
  inset-inline-start: 0;
79
- inline-size: var(--accent-width);
80
- background: var(--accent-color);
84
+ inline-size: var(--lyra-toast-accent-width);
85
+ background: var(--lyra-toast-accent-color);
81
86
  border-start-start-radius: var(--lyra-radius);
82
87
  border-end-start-radius: var(--lyra-radius);
83
88
  }
84
89
  [part='icon'] {
85
90
  display: inline-flex;
86
91
  flex: 0 0 auto;
87
- color: var(--accent-color);
92
+ color: var(--lyra-toast-accent-color);
88
93
  }
89
94
  [part='content'] {
90
95
  flex: 1 1 auto;
91
96
  min-inline-size: 0;
92
97
  }
98
+ /* toaster.ts's action option (and the WithIcon/Triggers stories) append a
99
+ plain light-DOM button as a sibling of the message text -- without
100
+ this, it renders with the browser's unstyled default button chrome,
101
+ clashing with the rest of the token-driven design. Styled as an inline
102
+ text action (matching the toast's own accent color) rather than a
103
+ boxed button, since it sits directly after the message inside the
104
+ content part rather than in its own layout slot. */
105
+ ::slotted(button) {
106
+ display: inline-block;
107
+ margin-inline-start: var(--lyra-space-s);
108
+ padding: 0;
109
+ border: none;
110
+ background: none;
111
+ font: inherit;
112
+ font-weight: bold;
113
+ color: var(--lyra-toast-accent-color);
114
+ text-decoration: underline;
115
+ cursor: pointer;
116
+ }
117
+ ::slotted(button:hover) {
118
+ color: var(--lyra-color-text);
119
+ }
120
+ ::slotted(button:focus-visible) {
121
+ outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
122
+ outline-offset: var(--lyra-focus-ring-offset);
123
+ }
93
124
  [part='close-button'] {
94
125
  flex: 0 0 auto;
95
126
  margin-inline-start: auto;
@@ -29,17 +29,29 @@ export class LyraToast extends LyraElement {
29
29
  /** Create and append a toast item programmatically; resolves to the item. */
30
30
  async create(message, options = {}) {
31
31
  const item = document.createElement('lyra-toast-item');
32
- item.variant = options.variant ?? 'neutral';
33
- item.duration = options.duration ?? 5000;
34
- item.size = options.size ?? 'm';
35
- item.withIcon = options.withIcon ?? false;
32
+ // Only assign what the caller actually specified -- a freshly-created
33
+ // <lyra-toast-item> already carries its own property defaults, so
34
+ // falling back to a literal here (e.g. `?? 5000`) would duplicate those
35
+ // defaults in a second place that has no way of staying in sync if the
36
+ // property declaration in toast-item.ts ever changes.
37
+ if (options.variant !== undefined)
38
+ item.variant = options.variant;
39
+ if (options.duration !== undefined)
40
+ item.duration = options.duration;
41
+ if (options.size !== undefined)
42
+ item.size = options.size;
43
+ if (options.withIcon !== undefined)
44
+ item.withIcon = options.withIcon;
36
45
  item.textContent = message;
37
46
  this.appendChild(item);
38
47
  await item.updateComplete;
39
48
  return item;
40
49
  }
41
50
  render() {
42
- return html `<div part="stack" role="status" aria-live="polite"><slot></slot></div>`;
51
+ // Each toast item owns its own status/alert role. Keeping a live region on
52
+ // the stack as well would nest assertive alerts inside a polite region and
53
+ // can cause duplicate or out-of-order announcements.
54
+ return html `<div part="stack"><slot></slot></div>`;
43
55
  }
44
56
  }
45
57
  __decorate([
@@ -27,8 +27,17 @@ function getRegion(placement = DEFAULT_PLACEMENT) {
27
27
  */
28
28
  export function toast(input) {
29
29
  const opts = typeof input === 'string' ? { message: input } : input;
30
+ // An action must remain available until the user can reach it. Callers can
31
+ // still opt into a finite duration explicitly; the convenience API makes
32
+ // only the omitted-duration/action combination persistent.
33
+ const createOptions = {
34
+ variant: opts.variant,
35
+ duration: opts.duration ?? (opts.action ? 0 : undefined),
36
+ size: opts.size,
37
+ withIcon: opts.withIcon,
38
+ };
30
39
  const item = getRegion(opts.placement)
31
- .create(opts.message, opts)
40
+ .create(opts.message, createOptions)
32
41
  .then((el) => {
33
42
  if (opts.action) {
34
43
  const btn = document.createElement('button');
@@ -17,11 +17,10 @@ export type ToolApprovalDialogCloseReason = 'escape' | 'backdrop' | 'approve' |
17
17
  * executing it until a person explicitly approves or denies it, with an
18
18
  * optional inline "edit the arguments before approving" step.
19
19
  *
20
- * This is its own standalone overlay implementation (`role="dialog"`,
21
- * focus-trapped, Escape/backdrop-dismissible, scroll-locking) rather than
22
- * nesting a `<lyra-dialog>` in its shadow template see `<lyra-dialog>`'s
23
- * own header comment for why a new overlay component in this library
24
- * duplicates that pattern locally instead of composing the previous one.
20
+ * This renders its own dialog panel rather than nesting a `<lyra-dialog>` in
21
+ * its shadow template. Shared overlay infrastructure coordinates stacking,
22
+ * focus trapping, Escape/backdrop dismissal, and focus return with every
23
+ * other overlay in the same document.
25
24
  *
26
25
  * Approve/Deny/Edit are built-in chrome (not a `footer` slot a consumer must
27
26
  * assemble) — this component's interaction shape is fixed enough (there is
@@ -43,8 +42,12 @@ export type ToolApprovalDialogCloseReason = 'escape' | 'backdrop' | 'approve' |
43
42
  * Both `editing` and any in-progress draft reset back to the read-only view
44
43
  * every time the dialog transitions from closed to open, so a reused
45
44
  * instance never leaks one proposal's half-finished edit into the next.
45
+ * `editable` flipping to `false` mid-edit does the same (see `willUpdate()`)
46
+ * and, if the textarea it unmounts still held focus, `updated()` refocuses
47
+ * Deny so the focus trap keeps engaging instead of silently letting focus
48
+ * fall through to the document.
46
49
  *
47
- * Initial focus deliberately does *not* land on Approve (see `updated()`):
50
+ * Initial focus deliberately does *not* land on Approve:
48
51
  * approving a tool call is a consequential, potentially irreversible action,
49
52
  * so a user who opens this dialog and reflexively presses Enter/Space before
50
53
  * reading anything should deny, not approve. Deny gets the initial focus
@@ -95,13 +98,14 @@ export declare class LyraToolApprovalDialog extends LyraElement {
95
98
  /** `JSON.parse` failure message for `draftText`, or `''` while it parses cleanly. Empty string (not `undefined`) so it can drive `?hidden` directly. */
96
99
  private draftError;
97
100
  private releaseScrollLock?;
98
- private lastTrigger?;
101
+ private overlay?;
99
102
  private readonly titleId;
100
103
  private readonly errorId;
101
104
  protected willUpdate(changed: PropertyValues): void;
102
105
  protected updated(changed: PropertyValues): void;
103
106
  connectedCallback(): void;
104
107
  disconnectedCallback(): void;
108
+ private activateOverlay;
105
109
  /**
106
110
  * Close the dialog and return focus to whatever had it before the dialog
107
111
  * opened. `reason` is forwarded as the `lyra-close` detail — built-in
@@ -113,9 +117,6 @@ export declare class LyraToolApprovalDialog extends LyraElement {
113
117
  */
114
118
  close(reason?: ToolApprovalDialogCloseReason): void;
115
119
  private onBackdropClick;
116
- private onDocKeyDown;
117
- private getFocusableElements;
118
- private getActiveElement;
119
120
  private stringifyArgs;
120
121
  private toggleEdit;
121
122
  private onDraftInput;
@@ -7,54 +7,22 @@ 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
13
  import { nextId } from '../../internal/a11y.js';
13
14
  import { styles } from './tool-approval-dialog.styles.js';
14
15
  import '../json-viewer/json-viewer.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, and
17
- // this component's own directly-rendered <lyra-json-viewer>'s internal
18
- // toggle/copy buttons, are found even though neither the slotted host tag
19
- // nor <lyra-json-viewer> itself matches FOCUSABLE_SELECTOR. Deliberately
20
- // duplicated from lyra-dialog's/lyra-tool-select-dialog's identical helper
21
- // rather than imported/shared -- this component is its own standalone
22
- // overlay and must not depend on either (see this file's class doc).
23
- function collectFocusable(el) {
24
- const result = [];
25
- if (el.matches(FOCUSABLE_SELECTOR)) {
26
- result.push(el);
27
- }
28
- if (el instanceof HTMLSlotElement) {
29
- for (const assigned of el.assignedElements({ flatten: true })) {
30
- result.push(...collectFocusable(assigned));
31
- }
32
- return result;
33
- }
34
- const container = el.shadowRoot ?? el;
35
- for (const child of Array.from(container.children)) {
36
- result.push(...collectFocusable(child));
37
- }
38
- return result;
39
- }
40
- /** Whether `el` is actually laid out/paintable -- `checkVisibility()` (falling
41
- * back to `getClientRects().length` where unsupported) correctly follows
42
- * flattened-tree/slot assignment, unlike `offsetParent`. Mirrors
43
- * lyra-dialog's/lyra-tool-select-dialog's identical helper. */
44
- function isRendered(el) {
45
- return el.checkVisibility ? el.checkVisibility() : el.getClientRects().length > 0;
46
- }
47
16
  /**
48
17
  * `<lyra-tool-approval-dialog>` — a human-in-the-loop gate: presents one
49
18
  * proposed tool/function call (`toolName` + `args`) and blocks an agent from
50
19
  * executing it until a person explicitly approves or denies it, with an
51
20
  * optional inline "edit the arguments before approving" step.
52
21
  *
53
- * This is its own standalone overlay implementation (`role="dialog"`,
54
- * focus-trapped, Escape/backdrop-dismissible, scroll-locking) rather than
55
- * nesting a `<lyra-dialog>` in its shadow template see `<lyra-dialog>`'s
56
- * own header comment for why a new overlay component in this library
57
- * duplicates that pattern locally instead of composing the previous one.
22
+ * This renders its own dialog panel rather than nesting a `<lyra-dialog>` in
23
+ * its shadow template. Shared overlay infrastructure coordinates stacking,
24
+ * focus trapping, Escape/backdrop dismissal, and focus return with every
25
+ * other overlay in the same document.
58
26
  *
59
27
  * Approve/Deny/Edit are built-in chrome (not a `footer` slot a consumer must
60
28
  * assemble) — this component's interaction shape is fixed enough (there is
@@ -76,8 +44,12 @@ function isRendered(el) {
76
44
  * Both `editing` and any in-progress draft reset back to the read-only view
77
45
  * every time the dialog transitions from closed to open, so a reused
78
46
  * instance never leaks one proposal's half-finished edit into the next.
47
+ * `editable` flipping to `false` mid-edit does the same (see `willUpdate()`)
48
+ * and, if the textarea it unmounts still held focus, `updated()` refocuses
49
+ * Deny so the focus trap keeps engaging instead of silently letting focus
50
+ * fall through to the document.
79
51
  *
80
- * Initial focus deliberately does *not* land on Approve (see `updated()`):
52
+ * Initial focus deliberately does *not* land on Approve:
81
53
  * approving a tool call is a consequential, potentially irreversible action,
82
54
  * so a user who opens this dialog and reflexively presses Enter/Space before
83
55
  * reading anything should deny, not approve. Deny gets the initial focus
@@ -131,34 +103,7 @@ export class LyraToolApprovalDialog extends LyraElement {
131
103
  this.titleId = nextId('tool-approval-dialog-title');
132
104
  this.errorId = nextId('tool-approval-dialog-error');
133
105
  this.onBackdropClick = () => {
134
- this.close('backdrop');
135
- };
136
- this.onDocKeyDown = (e) => {
137
- if (!this.open)
138
- return;
139
- if (e.key === 'Escape') {
140
- e.preventDefault();
141
- this.close('escape');
142
- return;
143
- }
144
- if (e.key !== 'Tab')
145
- return;
146
- const focusable = this.getFocusableElements();
147
- if (focusable.length === 0) {
148
- e.preventDefault();
149
- return;
150
- }
151
- const first = focusable[0];
152
- const last = focusable[focusable.length - 1];
153
- const active = this.getActiveElement();
154
- if (e.shiftKey && active === first) {
155
- e.preventDefault();
156
- last.focus();
157
- }
158
- else if (!e.shiftKey && active === last) {
159
- e.preventDefault();
160
- first.focus();
161
- }
106
+ this.overlay?.dismissBackdrop();
162
107
  };
163
108
  this.toggleEdit = () => {
164
109
  if (this.editing) {
@@ -208,15 +153,8 @@ export class LyraToolApprovalDialog extends LyraElement {
208
153
  willUpdate(changed) {
209
154
  if (changed.has('open')) {
210
155
  if (this.open) {
211
- // Captured here (before render) rather than from a click event on
212
- // some specific internal control -- like lyra-dialog, a trigger
213
- // typically lives *outside* this component entirely, so "whatever
214
- // had focus right before open" is the only generally correct
215
- // definition of "the trigger".
216
- const active = this.getActiveElement();
217
- this.lastTrigger = active instanceof HTMLElement ? active : undefined;
218
- this.releaseScrollLock = lockScroll();
219
- document.addEventListener('keydown', this.onDocKeyDown);
156
+ this.releaseScrollLock ??= lockScroll(this.ownerDocument);
157
+ this.activateOverlay();
220
158
  // Every open starts fresh in the read-only view -- a reused instance
221
159
  // must never carry a half-finished edit (or its error state) over
222
160
  // from whatever the previous proposal was.
@@ -227,7 +165,8 @@ export class LyraToolApprovalDialog extends LyraElement {
227
165
  else {
228
166
  this.releaseScrollLock?.();
229
167
  this.releaseScrollLock = undefined;
230
- document.removeEventListener('keydown', this.onDocKeyDown);
168
+ this.overlay?.deactivate();
169
+ this.overlay = undefined;
231
170
  }
232
171
  }
233
172
  // A consumer flipping editable off mid-edit (e.g. a policy change
@@ -245,40 +184,53 @@ export class LyraToolApprovalDialog extends LyraElement {
245
184
  // them -- mirrors lyra-dialog's identical ordering rationale.
246
185
  updated(changed) {
247
186
  if (changed.has('open') && this.open) {
248
- // Deliberately the Deny button, not "the first focusable element" --
249
- // see the class doc's focus-management paragraph.
250
- const deny = this.shadowRoot?.querySelector('[part="deny-button"]');
251
- if (deny) {
252
- deny.focus();
253
- }
254
- else {
255
- this.shadowRoot?.querySelector('[part="panel"]')?.focus();
256
- }
187
+ this.overlay?.focusInitial();
257
188
  return;
258
189
  }
259
- if (changed.has('editing') && this.editing) {
260
- // Entering edit mode is a deliberate request to start typing --
261
- // steering focus into the textarea beats leaving it on the Edit
262
- // button the click already left it on.
263
- this.shadowRoot?.querySelector('[part="args-editor"]')?.focus();
190
+ if (changed.has('editing')) {
191
+ if (this.editing) {
192
+ // Entering edit mode is a deliberate request to start typing --
193
+ // steering focus into the textarea beats leaving it on the Edit
194
+ // button the click already left it on.
195
+ this.shadowRoot?.querySelector('[part="args-editor"]')?.focus();
196
+ }
197
+ else if (this.open && !this.shadowRoot?.activeElement) {
198
+ // Reached when editing was turned off some way other than clicking
199
+ // Cancel (e.g. `editable` flipping to false while the textarea held
200
+ // focus, in willUpdate above) -- the textarea that held focus was
201
+ // just unmounted without anything else claiming it, which would
202
+ // otherwise drop focus to <body> and silently stop the Tab trap
203
+ // from engaging. Refocus a stable target inside the panel instead.
204
+ this.shadowRoot?.querySelector('[part="deny-button"]')?.focus();
205
+ }
264
206
  }
265
207
  }
266
208
  connectedCallback() {
267
209
  super.connectedCallback();
268
- // A reconnect (e.g. a drag-and-drop reparent keeping this same element
269
- // instance) fires disconnectedCallback then connectedCallback
270
- // synchronously with no update in between, so willUpdate never reruns to
271
- // notice `open` is still true -- restore the scroll lock/trap it dropped.
272
- if (this.hasUpdated && this.open && !this.releaseScrollLock) {
273
- this.releaseScrollLock = lockScroll();
274
- document.addEventListener('keydown', this.onDocKeyDown);
210
+ if (this.hasUpdated && this.open) {
211
+ this.releaseScrollLock ??= lockScroll(this.ownerDocument);
212
+ this.activateOverlay();
213
+ queueMicrotask(() => this.overlay?.focusInitial());
275
214
  }
276
215
  }
277
216
  disconnectedCallback() {
278
217
  super.disconnectedCallback();
279
218
  this.releaseScrollLock?.();
280
219
  this.releaseScrollLock = undefined;
281
- document.removeEventListener('keydown', this.onDocKeyDown);
220
+ this.overlay?.suspend();
221
+ }
222
+ activateOverlay() {
223
+ if (this.overlay?.isActive()) {
224
+ this.overlay.resume();
225
+ return;
226
+ }
227
+ this.overlay = activateOverlay({
228
+ host: this,
229
+ panel: () => this.shadowRoot?.querySelector('[part="panel"]') ?? null,
230
+ onEscape: () => this.close('escape'),
231
+ onBackdrop: () => this.close('backdrop'),
232
+ preferredInitialFocus: () => this.shadowRoot?.querySelector('[part="deny-button"]') ?? null,
233
+ });
282
234
  }
283
235
  /**
284
236
  * Close the dialog and return focus to whatever had it before the dialog
@@ -294,32 +246,6 @@ export class LyraToolApprovalDialog extends LyraElement {
294
246
  return;
295
247
  this.open = false;
296
248
  this.emit('lyra-close', reason);
297
- this.lastTrigger?.focus();
298
- }
299
- // Bounds Tab/Shift+Tab to the panel while open. Order follows the body
300
- // (the json-viewer's own toggle/copy buttons, or the textarea, depending
301
- // on `editing`), then the footer row (the footer slot, then Deny/Edit/
302
- // Approve) -- the same order the flattened tree already tabs through.
303
- // collectFocusable()'s own FOCUSABLE_SELECTOR check excludes the Approve
304
- // button for free while it's `disabled`, so an invalid in-progress edit
305
- // can't be Tab-trapped into.
306
- getFocusableElements() {
307
- const root = this.shadowRoot;
308
- if (!root)
309
- return [];
310
- const body = root.querySelector('[part="body"]');
311
- const footer = root.querySelector('[part="footer"]');
312
- return [...(body ? collectFocusable(body) : []), ...(footer ? collectFocusable(footer) : [])].filter(isRendered);
313
- }
314
- getActiveElement() {
315
- let active = document.activeElement;
316
- while (active) {
317
- const inner = active.shadowRoot?.activeElement ?? null;
318
- if (!inner)
319
- break;
320
- active = inner;
321
- }
322
- return active;
323
249
  }
324
250
  stringifyArgs() {
325
251
  try {
@@ -16,7 +16,7 @@ export const styles = css `
16
16
  display: none;
17
17
  position: fixed;
18
18
  inset: 0;
19
- z-index: 1000;
19
+ z-index: var(--lyra-overlay-stack-index, 1000);
20
20
  align-items: center;
21
21
  justify-content: center;
22
22
  padding: var(--lyra-space-l);
@@ -23,7 +23,12 @@ export interface ToolChipSelectDetail {
23
23
  * a short preview) shown in a floating tooltip on hover/focus, positioned
24
24
  * with `internal/positioner.js`'s `place()` the same way `<lyra-combobox>`
25
25
  * positions its listbox. No tooltip is shown at all when the slot carries no
26
- * content — hovering an empty chip does nothing.
26
+ * content — hovering an empty chip does nothing. Hover and keyboard focus are
27
+ * tracked as independent reasons to keep the tooltip open (mirrors
28
+ * `<lyra-citation-badge>`'s popover), so releasing one modality while the
29
+ * other is still active doesn't close it, and the trigger button's
30
+ * `aria-describedby` points at the tooltip's id whenever it's open and has
31
+ * content, so the association reaches assistive tech too.
27
32
  *
28
33
  * The `icon` slot overrides the built-in per-status glyph entirely via the
29
34
  * platform's own slot-fallback-content mechanism (`<slot
@@ -56,7 +61,10 @@ export declare class LyraToolCallChip extends LyraElement {
56
61
  name: string;
57
62
  /** Optional grouping label, e.g. `research`. */
58
63
  category: string;
59
- /** The call's current lifecycle state — drives the glyph, color, and `status-text`. */
64
+ /**
65
+ * The call's current lifecycle state — drives the glyph, color, and
66
+ * `status-text`. Invalid runtime values use the pending presentation.
67
+ */
60
68
  status: ToolCallStatus;
61
69
  /** Short human-readable status text, e.g. `Searching web…`. */
62
70
  summary: string;
@@ -70,15 +78,23 @@ export declare class LyraToolCallChip extends LyraElement {
70
78
  callId: string;
71
79
  private hasDetailSlot;
72
80
  private tooltipOpen;
81
+ private readonly tooltipId;
73
82
  private cleanupPositioner?;
83
+ private hovering;
84
+ private focused;
74
85
  protected willUpdate(): void;
75
86
  protected updated(changed: PropertyValues): void;
76
87
  disconnectedCallback(): void;
77
88
  private onDetailSlotChange;
78
89
  private showTooltip;
79
90
  private hideTooltip;
91
+ private onMouseEnter;
92
+ private onMouseLeave;
93
+ private onFocus;
94
+ private onBlur;
80
95
  private onKeyDown;
81
96
  private onClick;
97
+ private get effectiveStatus();
82
98
  private get accessibleLabel();
83
99
  render(): TemplateResult;
84
100
  }