@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
@@ -0,0 +1,30 @@
1
+ import { type TemplateResult } from 'lit';
2
+ import { LyraElement } from '../../internal/lyra-element.js';
3
+ /**
4
+ * `<lyra-app-rail-item>` — an explicit icon/label navigation item for
5
+ * `<lyra-app-rail>`. The rail sets its `icon-only` attribute as the viewport
6
+ * changes, keeping the label available to assistive technology while removing
7
+ * it from the visual layout.
8
+ *
9
+ * @customElement lyra-app-rail-item
10
+ * @slot - The visible navigation label.
11
+ * @slot icon - The leading icon.
12
+ * @csspart base - The link or button receiving focus and activation.
13
+ * @csspart icon - The icon wrapper.
14
+ * @csspart label - The label wrapper; visually clipped in icon-only mode.
15
+ */
16
+ export declare class LyraAppRailItem extends LyraElement {
17
+ static styles: import("lit").CSSResultGroup[];
18
+ /** Optional destination. Without `href`, the item renders as a button. */
19
+ href: string;
20
+ /** Optional link target. */
21
+ target: string;
22
+ /** Prevents activation while retaining the item in the rail. */
23
+ disabled: boolean;
24
+ render(): TemplateResult;
25
+ }
26
+ declare global {
27
+ interface HTMLElementTagNameMap {
28
+ 'lyra-app-rail-item': LyraAppRailItem;
29
+ }
30
+ }
@@ -0,0 +1,68 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html, nothing } from 'lit';
8
+ import { property } from 'lit/decorators.js';
9
+ import { LyraElement } from '../../internal/lyra-element.js';
10
+ import { defineElement } from '../../internal/prefix.js';
11
+ import { styles } from './app-rail-item.styles.js';
12
+ /**
13
+ * `<lyra-app-rail-item>` — an explicit icon/label navigation item for
14
+ * `<lyra-app-rail>`. The rail sets its `icon-only` attribute as the viewport
15
+ * changes, keeping the label available to assistive technology while removing
16
+ * it from the visual layout.
17
+ *
18
+ * @customElement lyra-app-rail-item
19
+ * @slot - The visible navigation label.
20
+ * @slot icon - The leading icon.
21
+ * @csspart base - The link or button receiving focus and activation.
22
+ * @csspart icon - The icon wrapper.
23
+ * @csspart label - The label wrapper; visually clipped in icon-only mode.
24
+ */
25
+ export class LyraAppRailItem extends LyraElement {
26
+ constructor() {
27
+ super(...arguments);
28
+ /** Optional destination. Without `href`, the item renders as a button. */
29
+ this.href = '';
30
+ /** Optional link target. */
31
+ this.target = '';
32
+ /** Prevents activation while retaining the item in the rail. */
33
+ this.disabled = false;
34
+ }
35
+ static { this.styles = [LyraElement.styles, styles]; }
36
+ render() {
37
+ const label = this.getAttribute('aria-label');
38
+ const content = html `
39
+ <span part="icon" aria-hidden=${label ? 'true' : nothing}><slot name="icon"></slot></span>
40
+ <span part="label"><slot></slot></span>
41
+ `;
42
+ if (this.href && !this.disabled) {
43
+ return html `<a
44
+ part="base"
45
+ href=${this.href}
46
+ target=${this.target || nothing}
47
+ aria-label=${label || nothing}
48
+ >${content}</a>`;
49
+ }
50
+ return html `<button
51
+ part="base"
52
+ type="button"
53
+ ?disabled=${this.disabled}
54
+ aria-disabled=${this.disabled ? 'true' : nothing}
55
+ aria-label=${label || nothing}
56
+ >${content}</button>`;
57
+ }
58
+ }
59
+ __decorate([
60
+ property()
61
+ ], LyraAppRailItem.prototype, "href", void 0);
62
+ __decorate([
63
+ property()
64
+ ], LyraAppRailItem.prototype, "target", void 0);
65
+ __decorate([
66
+ property({ type: Boolean, reflect: true })
67
+ ], LyraAppRailItem.prototype, "disabled", void 0);
68
+ defineElement('app-rail-item', LyraAppRailItem);
@@ -0,0 +1 @@
1
+ export declare const styles: import("lit").CSSResult;
@@ -0,0 +1,65 @@
1
+ import { css } from 'lit';
2
+ export const styles = css `
3
+ :host {
4
+ display: block;
5
+ inline-size: 100%;
6
+ }
7
+ [part='base'] {
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: flex-start;
11
+ gap: var(--lyra-space-s);
12
+ inline-size: 100%;
13
+ min-block-size: var(--lyra-icon-button-size);
14
+ padding: var(--lyra-space-s);
15
+ border: 0;
16
+ border-radius: var(--lyra-radius);
17
+ background: transparent;
18
+ color: var(--lyra-color-text);
19
+ font: inherit;
20
+ text-align: start;
21
+ text-decoration: none;
22
+ cursor: pointer;
23
+ }
24
+ [part='base']:hover {
25
+ background: var(--lyra-color-brand-quiet);
26
+ color: var(--lyra-color-brand);
27
+ }
28
+ [part='base']:focus-visible {
29
+ outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
30
+ outline-offset: var(--lyra-focus-ring-offset);
31
+ }
32
+ [part='base'][aria-disabled='true'] {
33
+ opacity: var(--lyra-opacity-disabled);
34
+ cursor: not-allowed;
35
+ }
36
+ [part='icon'] {
37
+ display: inline-flex;
38
+ flex: 0 0 auto;
39
+ align-items: center;
40
+ justify-content: center;
41
+ inline-size: var(--lyra-icon-button-size);
42
+ min-inline-size: var(--lyra-icon-button-size);
43
+ }
44
+ [part='label'] {
45
+ min-inline-size: 0;
46
+ overflow: hidden;
47
+ text-overflow: ellipsis;
48
+ white-space: nowrap;
49
+ }
50
+ :host([icon-only]) [part='label'] {
51
+ position: absolute;
52
+ inline-size: 1px;
53
+ block-size: 1px;
54
+ padding: 0;
55
+ margin: -1px;
56
+ overflow: hidden;
57
+ clip: rect(0, 0, 0, 0);
58
+ white-space: nowrap;
59
+ border: 0;
60
+ }
61
+ :host([icon-only]) [part='base'] {
62
+ justify-content: center;
63
+ padding-inline: 0;
64
+ }
65
+ `;
@@ -1,5 +1,6 @@
1
1
  import { type TemplateResult, type PropertyValues } from 'lit';
2
2
  import { LyraElement } from '../../internal/lyra-element.js';
3
+ import './app-rail-item.js';
3
4
  /** The rail's effective presentation -- see the class doc for what each renders. */
4
5
  export type AppRailMode = 'full' | 'icon-only' | 'mobile';
5
6
  /** What can be *assigned* to `mode` -- `'auto'` is a write-only sentinel; see the `mode` accessor doc. */
@@ -31,12 +32,10 @@ export declare function computeAppRailMode(iconOnlyMatches: boolean, mobileMatch
31
32
  * width the way a native OS shell's navigation does, not however much
32
33
  * horizontal space a particular layout happens to give it.
33
34
  *
34
- * This is its own standalone focus-trapped overlay implementation for the
35
- * `'mobile'` state (`role="dialog"`, Escape/backdrop-dismissible,
36
- * scroll-locking while open) rather than nesting a `<lyra-dialog>` in its
37
- * shadow template see `<lyra-dialog>`'s own header comment for why a new
38
- * overlay-shaped component in this library duplicates that pattern locally
39
- * instead of composing the previous one. `[part="base"]` (the inline
35
+ * The `'mobile'` state participates in the library's shared overlay stack,
36
+ * which supplies focus trapping, Escape/backdrop dismissal, inerting, and
37
+ * focus restoration without nesting a `<lyra-dialog>` in this component's
38
+ * shadow template. `[part="base"]` (the inline
40
39
  * `'full'`/`'icon-only'` presentation) and `[part="panel"]` (the mobile
41
40
  * overlay) are the *same* element promoted in place across modes (mirrors
42
41
  * `<lyra-widget>`'s fullscreen mode) — never both at once, and never two
@@ -49,15 +48,11 @@ export declare function computeAppRailMode(iconOnlyMatches: boolean, mobileMatch
49
48
  * on a generic element can be swapped for `role="dialog"` freely.
50
49
  *
51
50
  * @customElement lyra-app-rail
52
- * @slot - Nav items generic slotted content (e.g. `<a>`/`<button>`
53
- * elements) the consumer builds with its own icon+label structure; this
54
- * component only lays them out and manages the responsive chrome around
55
- * them. See the `@example` below for the accessible-name expectation this
56
- * places on the slotted content in `'icon-only'` mode. While the mobile
57
- * overlay is open, clicking anywhere inside this slot closes it (see
58
- * `onNavItemClick`) — every slotted item is assumed to be a navigation
59
- * trigger, not necessarily a real link, so this fires on any click rather
60
- * than trying to detect one.
51
+ * @slot - Nav items. Use `<lyra-app-rail-item>` for the explicit icon/label
52
+ * contract that automatically hides labels in `'icon-only'` mode. Generic
53
+ * links and buttons remain supported, but their compact presentation is the
54
+ * consumer's responsibility. While the mobile overlay is open, clicking
55
+ * anywhere inside this slot closes it.
61
56
  * @slot header - Logo/brand content, shown above the nav items in every mode.
62
57
  * @slot footer - A trailing user/settings trigger, shown below the nav items.
63
58
  * @event lyra-mode-change - The effective mode changed, whether from a
@@ -80,13 +75,13 @@ export declare function computeAppRailMode(iconOnlyMatches: boolean, mobileMatch
80
75
  * for why it's the same element as `base`, never both at once.
81
76
  *
82
77
  * @example
83
- * In `'icon-only'` mode, slotted nav items lose their visible text label.
84
- * Give each one a real accessible name regardless — `aria-label`, visually
85
- * hidden text, or a `title` — since this component only lays out whatever is
86
- * slotted and has no way to inspect or fix up a consumer's own markup:
78
+ * Use the item contract so the visible label collapses while its accessible
79
+ * name remains available:
87
80
  * ```html
88
81
  * <lyra-app-rail>
89
- * <a href="/inbox" aria-label="Inbox"><svg aria-hidden="true">...</svg><span>Inbox</span></a>
82
+ * <lyra-app-rail-item href="/inbox" aria-label="Inbox">
83
+ * <svg slot="icon" aria-hidden="true">...</svg>Inbox
84
+ * </lyra-app-rail-item>
90
85
  * </lyra-app-rail>
91
86
  * ```
92
87
  */
@@ -126,8 +121,10 @@ export declare class LyraAppRail extends LyraElement {
126
121
  private overlayActive;
127
122
  private justOpened;
128
123
  private releaseScrollLock?;
129
- private lastTrigger?;
124
+ private overlayHandle?;
125
+ private explicitTrigger?;
130
126
  private readonly navId;
127
+ private syncSlottedItems;
131
128
  /**
132
129
  * The rail's current effective presentation. Always one of the three real
133
130
  * modes — never `'auto'` — reflecting either the live breakpoint match or,
@@ -147,6 +144,8 @@ export declare class LyraAppRail extends LyraElement {
147
144
  protected updated(): void;
148
145
  connectedCallback(): void;
149
146
  disconnectedCallback(): void;
147
+ private activateMobileOverlay;
148
+ private deactivateMobileOverlay;
150
149
  private setupMediaQueries;
151
150
  private teardownMediaQueries;
152
151
  private onIconOnlyChange;
@@ -159,9 +158,7 @@ export declare class LyraAppRail extends LyraElement {
159
158
  private onNavItemClick;
160
159
  private onHeaderSlotChange;
161
160
  private onFooterSlotChange;
162
- private onDocKeyDown;
163
- private getFocusableElements;
164
- private getActiveElement;
161
+ private onNavSlotChange;
165
162
  render(): TemplateResult;
166
163
  }
167
164
  declare global {
@@ -7,42 +7,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { html, nothing, svg } 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 { closeIcon } from '../../internal/icons.js';
14
15
  import { styles } from './app-rail.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 for the mobile state and must not
21
- // depend on lyra-dialog (see this file's class doc).
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
- }
16
+ import './app-rail-item.js';
46
17
  // icons.ts has no hamburger/menu glyph and this component must not modify
47
18
  // that shared module -- inlined here instead, matching its 24x24
48
19
  // viewBox/stroke-width/currentColor convention so it reads as part of the
@@ -94,12 +65,10 @@ export function computeAppRailMode(iconOnlyMatches, mobileMatches) {
94
65
  * width the way a native OS shell's navigation does, not however much
95
66
  * horizontal space a particular layout happens to give it.
96
67
  *
97
- * This is its own standalone focus-trapped overlay implementation for the
98
- * `'mobile'` state (`role="dialog"`, Escape/backdrop-dismissible,
99
- * scroll-locking while open) rather than nesting a `<lyra-dialog>` in its
100
- * shadow template see `<lyra-dialog>`'s own header comment for why a new
101
- * overlay-shaped component in this library duplicates that pattern locally
102
- * instead of composing the previous one. `[part="base"]` (the inline
68
+ * The `'mobile'` state participates in the library's shared overlay stack,
69
+ * which supplies focus trapping, Escape/backdrop dismissal, inerting, and
70
+ * focus restoration without nesting a `<lyra-dialog>` in this component's
71
+ * shadow template. `[part="base"]` (the inline
103
72
  * `'full'`/`'icon-only'` presentation) and `[part="panel"]` (the mobile
104
73
  * overlay) are the *same* element promoted in place across modes (mirrors
105
74
  * `<lyra-widget>`'s fullscreen mode) — never both at once, and never two
@@ -112,15 +81,11 @@ export function computeAppRailMode(iconOnlyMatches, mobileMatches) {
112
81
  * on a generic element can be swapped for `role="dialog"` freely.
113
82
  *
114
83
  * @customElement lyra-app-rail
115
- * @slot - Nav items generic slotted content (e.g. `<a>`/`<button>`
116
- * elements) the consumer builds with its own icon+label structure; this
117
- * component only lays them out and manages the responsive chrome around
118
- * them. See the `@example` below for the accessible-name expectation this
119
- * places on the slotted content in `'icon-only'` mode. While the mobile
120
- * overlay is open, clicking anywhere inside this slot closes it (see
121
- * `onNavItemClick`) — every slotted item is assumed to be a navigation
122
- * trigger, not necessarily a real link, so this fires on any click rather
123
- * than trying to detect one.
84
+ * @slot - Nav items. Use `<lyra-app-rail-item>` for the explicit icon/label
85
+ * contract that automatically hides labels in `'icon-only'` mode. Generic
86
+ * links and buttons remain supported, but their compact presentation is the
87
+ * consumer's responsibility. While the mobile overlay is open, clicking
88
+ * anywhere inside this slot closes it.
124
89
  * @slot header - Logo/brand content, shown above the nav items in every mode.
125
90
  * @slot footer - A trailing user/settings trigger, shown below the nav items.
126
91
  * @event lyra-mode-change - The effective mode changed, whether from a
@@ -143,13 +108,13 @@ export function computeAppRailMode(iconOnlyMatches, mobileMatches) {
143
108
  * for why it's the same element as `base`, never both at once.
144
109
  *
145
110
  * @example
146
- * In `'icon-only'` mode, slotted nav items lose their visible text label.
147
- * Give each one a real accessible name regardless — `aria-label`, visually
148
- * hidden text, or a `title` — since this component only lays out whatever is
149
- * slotted and has no way to inspect or fix up a consumer's own markup:
111
+ * Use the item contract so the visible label collapses while its accessible
112
+ * name remains available:
150
113
  * ```html
151
114
  * <lyra-app-rail>
152
- * <a href="/inbox" aria-label="Inbox"><svg aria-hidden="true">...</svg><span>Inbox</span></a>
115
+ * <lyra-app-rail-item href="/inbox" aria-label="Inbox">
116
+ * <svg slot="icon" aria-hidden="true">...</svg>Inbox
117
+ * </lyra-app-rail-item>
153
118
  * </lyra-app-rail>
154
119
  * ```
155
120
  */
@@ -204,11 +169,12 @@ export class LyraAppRail extends LyraElement {
204
169
  this.applyComputedMode();
205
170
  };
206
171
  this.onToggleClick = (e) => {
207
- this.lastTrigger = e.currentTarget;
172
+ if (!this.open)
173
+ this.explicitTrigger = e.currentTarget;
208
174
  this.setOpen(!this.open);
209
175
  };
210
176
  this.onBackdropClick = () => {
211
- this.setOpen(false);
177
+ this.overlayHandle?.dismissBackdrop();
212
178
  };
213
179
  // See the default-slot @slot doc -- any click inside the nav items while
214
180
  // the overlay is open closes it, without trying to distinguish a real
@@ -223,32 +189,8 @@ export class LyraAppRail extends LyraElement {
223
189
  this.onFooterSlotChange = (e) => {
224
190
  this.hasFooterSlot = e.target.assignedElements({ flatten: true }).length > 0;
225
191
  };
226
- this.onDocKeyDown = (e) => {
227
- if (!this.overlayActive)
228
- return;
229
- if (e.key === 'Escape') {
230
- e.preventDefault();
231
- this.setOpen(false);
232
- return;
233
- }
234
- if (e.key !== 'Tab')
235
- return;
236
- const focusable = this.getFocusableElements();
237
- if (focusable.length === 0) {
238
- e.preventDefault();
239
- return;
240
- }
241
- const first = focusable[0];
242
- const last = focusable[focusable.length - 1];
243
- const active = this.getActiveElement();
244
- if (e.shiftKey && active === first) {
245
- e.preventDefault();
246
- last.focus();
247
- }
248
- else if (!e.shiftKey && active === last) {
249
- e.preventDefault();
250
- first.focus();
251
- }
192
+ this.onNavSlotChange = () => {
193
+ this.syncSlottedItems();
252
194
  };
253
195
  }
254
196
  static { this.styles = [LyraElement.styles, styles]; }
@@ -262,6 +204,14 @@ export class LyraAppRail extends LyraElement {
262
204
  static { this.properties = {
263
205
  mode: { reflect: true, noAccessor: true },
264
206
  }; }
207
+ syncSlottedItems() {
208
+ const slot = this.shadowRoot?.querySelector('[part="nav"] > slot');
209
+ for (const item of slot?.assignedElements({ flatten: true }) ?? []) {
210
+ if (item.localName === 'lyra-app-rail-item') {
211
+ item.toggleAttribute('icon-only', this._mode === 'icon-only');
212
+ }
213
+ }
214
+ }
265
215
  /**
266
216
  * The rail's current effective presentation. Always one of the three real
267
217
  * modes — never `'auto'` — reflecting either the live breakpoint match or,
@@ -306,13 +256,10 @@ export class LyraAppRail extends LyraElement {
306
256
  this.overlayActive = next;
307
257
  if (next) {
308
258
  this.justOpened = true;
309
- this.releaseScrollLock = lockScroll();
310
- document.addEventListener('keydown', this.onDocKeyDown);
259
+ this.activateMobileOverlay();
311
260
  }
312
261
  else {
313
- this.releaseScrollLock?.();
314
- this.releaseScrollLock = undefined;
315
- document.removeEventListener('keydown', this.onDocKeyDown);
262
+ this.deactivateMobileOverlay();
316
263
  }
317
264
  }
318
265
  }
@@ -322,15 +269,10 @@ export class LyraAppRail extends LyraElement {
322
269
  // rely on them -- mirrors lyra-dialog's/lyra-widget's identical ordering
323
270
  // rationale.
324
271
  updated() {
272
+ this.syncSlottedItems();
325
273
  if (this.justOpened) {
326
274
  this.justOpened = false;
327
- const first = this.getFocusableElements()[0];
328
- if (first) {
329
- first.focus();
330
- }
331
- else {
332
- this.shadowRoot?.querySelector('[part="panel"]')?.focus();
333
- }
275
+ this.overlayHandle?.focusInitial();
334
276
  }
335
277
  }
336
278
  connectedCallback() {
@@ -339,11 +281,18 @@ export class LyraAppRail extends LyraElement {
339
281
  // A reconnect (e.g. a drag-and-drop reparent keeping this same element
340
282
  // instance) fires disconnectedCallback then connectedCallback
341
283
  // synchronously with no update in between, so willUpdate never reruns to
342
- // notice the overlay is still active -- restore the scroll lock/trap it
343
- // dropped. Mirrors lyra-dialog's/lyra-widget's identical reconnect handling.
344
- if (this.hasUpdated && this.overlayActive && !this.releaseScrollLock) {
345
- this.releaseScrollLock = lockScroll();
346
- document.addEventListener('keydown', this.onDocKeyDown);
284
+ // notice the overlay is still active -- restore its shared registration
285
+ // and scroll lock.
286
+ if (this.hasUpdated && this._mode === 'mobile' && this.open) {
287
+ if (this.overlayHandle?.isActive()) {
288
+ this.overlayHandle.resume();
289
+ }
290
+ else {
291
+ this.activateMobileOverlay();
292
+ }
293
+ if (!this.releaseScrollLock)
294
+ this.releaseScrollLock = lockScroll(this.ownerDocument);
295
+ queueMicrotask(() => this.overlayHandle?.focusInitial());
347
296
  }
348
297
  }
349
298
  disconnectedCallback() {
@@ -351,13 +300,31 @@ export class LyraAppRail extends LyraElement {
351
300
  this.teardownMediaQueries();
352
301
  this.releaseScrollLock?.();
353
302
  this.releaseScrollLock = undefined;
354
- document.removeEventListener('keydown', this.onDocKeyDown);
303
+ this.overlayHandle?.suspend();
304
+ }
305
+ activateMobileOverlay() {
306
+ this.releaseScrollLock ??= lockScroll(this.ownerDocument);
307
+ this.overlayHandle = activateOverlay({
308
+ host: this,
309
+ panel: () => this.shadowRoot?.querySelector('[part="panel"]') ?? null,
310
+ onEscape: () => this.setOpen(false),
311
+ onBackdrop: () => this.setOpen(false),
312
+ restoreFocusTo: this.explicitTrigger,
313
+ });
314
+ this.explicitTrigger = undefined;
315
+ }
316
+ deactivateMobileOverlay() {
317
+ this.releaseScrollLock?.();
318
+ this.releaseScrollLock = undefined;
319
+ this.overlayHandle?.deactivate();
320
+ this.overlayHandle = undefined;
355
321
  }
356
322
  setupMediaQueries() {
357
- if (typeof window === 'undefined' || !window.matchMedia)
323
+ const view = this.ownerDocument.defaultView;
324
+ if (!view?.matchMedia)
358
325
  return;
359
- this.mqIconOnly = window.matchMedia(`(max-width: ${this.iconOnlyBreakpoint})`);
360
- this.mqMobile = window.matchMedia(`(max-width: ${this.mobileBreakpoint})`);
326
+ this.mqIconOnly = view.matchMedia(`(max-width: ${this.iconOnlyBreakpoint})`);
327
+ this.mqMobile = view.matchMedia(`(max-width: ${this.mobileBreakpoint})`);
361
328
  this.mqIconOnly.addEventListener('change', this.onIconOnlyChange);
362
329
  this.mqMobile.addEventListener('change', this.onMobileChange);
363
330
  this.iconOnlyMatches = this.mqIconOnly.matches;
@@ -394,37 +361,6 @@ export class LyraAppRail extends LyraElement {
394
361
  return;
395
362
  this.open = next;
396
363
  this.emit('lyra-toggle', { open: next });
397
- if (!next)
398
- this.lastTrigger?.focus();
399
- }
400
- // Bounds Tab/Shift+Tab to the panel while the overlay is open. Order
401
- // follows the header slot, then the nav (default) slot, then the footer
402
- // slot -- the same order the flattened tree already tabs through. The
403
- // toggle button itself is deliberately excluded, same as a dialog trigger
404
- // living outside its own trap.
405
- getFocusableElements() {
406
- const root = this.shadowRoot;
407
- if (!root)
408
- return [];
409
- const fromSlot = (selector) => {
410
- const slot = root.querySelector(selector);
411
- return slot ? slot.assignedElements({ flatten: true }).flatMap(collectFocusable) : [];
412
- };
413
- return [
414
- ...fromSlot('slot[name="header"]'),
415
- ...fromSlot('slot:not([name])'),
416
- ...fromSlot('slot[name="footer"]'),
417
- ].filter(isRendered);
418
- }
419
- getActiveElement() {
420
- let active = document.activeElement;
421
- while (active) {
422
- const inner = active.shadowRoot?.activeElement ?? null;
423
- if (!inner)
424
- break;
425
- active = inner;
426
- }
427
- return active;
428
364
  }
429
365
  render() {
430
366
  const mobile = this._mode === 'mobile';
@@ -453,7 +389,7 @@ export class LyraAppRail extends LyraElement {
453
389
  <slot name="header" @slotchange=${this.onHeaderSlotChange}></slot>
454
390
  </div>
455
391
  <div part="nav">
456
- <slot @click=${this.onNavItemClick}></slot>
392
+ <slot @slotchange=${this.onNavSlotChange} @click=${this.onNavItemClick}></slot>
457
393
  </div>
458
394
  <div part="footer" ?hidden=${!this.hasFooterSlot}>
459
395
  <slot name="footer" @slotchange=${this.onFooterSlotChange}></slot>
@@ -39,7 +39,7 @@ export const styles = css `
39
39
  [part='backdrop'] {
40
40
  position: fixed;
41
41
  inset: 0;
42
- z-index: 999;
42
+ z-index: var(--lyra-overlay-stack-index, 1000);
43
43
  background: var(--lyra-app-rail-overlay-color);
44
44
  }
45
45
 
@@ -68,7 +68,7 @@ export const styles = css `
68
68
  position: fixed;
69
69
  inset-block: 0;
70
70
  inset-inline-start: 0;
71
- z-index: 1000;
71
+ z-index: calc(var(--lyra-overlay-stack-index, 1000) + 1);
72
72
  display: flex;
73
73
  flex-direction: column;
74
74
  inline-size: min(var(--lyra-app-rail-mobile-width), 85vw);
@@ -115,6 +115,9 @@ export const styles = css `
115
115
  :host([mode='icon-only']) [part='nav'] {
116
116
  align-items: center;
117
117
  }
118
+ :host([mode='icon-only']) ::slotted(lyra-app-rail-item) {
119
+ max-inline-size: var(--lyra-app-rail-icon-width);
120
+ }
118
121
 
119
122
  @media (prefers-reduced-motion: reduce) {
120
123
  [part='base'],