@aceshooting/lyra-ui 1.2.0 → 2.0.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.
- package/README.md +11 -0
- package/custom-elements.json +23341 -11202
- package/dist/components/app-rail/app-rail.d.ts +171 -0
- package/dist/components/app-rail/app-rail.js +483 -0
- package/dist/components/app-rail/app-rail.styles.d.ts +1 -0
- package/dist/components/app-rail/app-rail.styles.js +125 -0
- package/dist/components/attachment-chip/attachment-chip.d.ts +114 -0
- package/dist/components/attachment-chip/attachment-chip.js +312 -0
- package/dist/components/attachment-chip/attachment-chip.styles.d.ts +1 -0
- package/dist/components/attachment-chip/attachment-chip.styles.js +184 -0
- package/dist/components/attachment-trigger/attachment-trigger.d.ts +83 -0
- package/dist/components/attachment-trigger/attachment-trigger.js +258 -0
- package/dist/components/attachment-trigger/attachment-trigger.styles.d.ts +1 -0
- package/dist/components/attachment-trigger/attachment-trigger.styles.js +68 -0
- package/dist/components/chart/box-plot.js +11 -1
- package/dist/components/chart/chart-loader.d.ts +44 -13
- package/dist/components/chart/chart-loader.js +65 -18
- package/dist/components/chart/chart.d.ts +1 -2
- package/dist/components/chart/chart.js +67 -12
- package/dist/components/chart/histogram-bin.d.ts +4 -1
- package/dist/components/chart/histogram-bin.js +24 -14
- package/dist/components/chart/lite-chart.d.ts +54 -0
- package/dist/components/chart/lite-chart.js +140 -22
- package/dist/components/chart/lite-chart.styles.js +9 -0
- package/dist/components/chat-composer/chat-composer.d.ts +92 -0
- package/dist/components/chat-composer/chat-composer.js +285 -0
- package/dist/components/chat-composer/chat-composer.styles.d.ts +1 -0
- package/dist/components/chat-composer/chat-composer.styles.js +135 -0
- package/dist/components/chat-message/chat-message.d.ts +113 -0
- package/dist/components/chat-message/chat-message.js +291 -0
- package/dist/components/chat-message/chat-message.styles.d.ts +1 -0
- package/dist/components/chat-message/chat-message.styles.js +203 -0
- package/dist/components/checkbox/checkbox.d.ts +59 -0
- package/dist/components/checkbox/checkbox.js +230 -0
- package/dist/components/checkbox/checkbox.styles.d.ts +1 -0
- package/dist/components/checkbox/checkbox.styles.js +70 -0
- package/dist/components/chip/chip-group.d.ts +56 -0
- package/dist/components/chip/chip-group.js +124 -0
- package/dist/components/chip/chip-group.styles.d.ts +1 -0
- package/dist/components/chip/chip-group.styles.js +57 -0
- package/dist/components/chip/chip.d.ts +64 -0
- package/dist/components/chip/chip.js +115 -0
- package/dist/components/chip/chip.styles.d.ts +1 -0
- package/dist/components/chip/chip.styles.js +115 -0
- package/dist/components/citation-badge/citation-badge.d.ts +113 -0
- package/dist/components/citation-badge/citation-badge.js +300 -0
- package/dist/components/citation-badge/citation-badge.styles.d.ts +1 -0
- package/dist/components/citation-badge/citation-badge.styles.js +96 -0
- package/dist/components/code-block/code-block.d.ts +89 -0
- package/dist/components/code-block/code-block.js +317 -0
- package/dist/components/code-block/code-block.styles.d.ts +1 -0
- package/dist/components/code-block/code-block.styles.js +187 -0
- package/dist/components/code-block/code-loader.d.ts +44 -0
- package/dist/components/code-block/code-loader.js +79 -0
- package/dist/components/combobox/combobox.d.ts +19 -1
- package/dist/components/combobox/combobox.js +84 -14
- package/dist/components/combobox/option.d.ts +5 -1
- package/dist/components/combobox/option.js +35 -0
- package/dist/components/context-meter/context-meter.d.ts +53 -0
- package/dist/components/context-meter/context-meter.js +150 -0
- package/dist/components/context-meter/context-meter.styles.d.ts +1 -0
- package/dist/components/context-meter/context-meter.styles.js +120 -0
- package/dist/components/conversation-item/conversation-item.d.ts +128 -0
- package/dist/components/conversation-item/conversation-item.js +322 -0
- package/dist/components/conversation-item/conversation-item.styles.d.ts +1 -0
- package/dist/components/conversation-item/conversation-item.styles.js +155 -0
- package/dist/components/date-picker/date-input.d.ts +24 -0
- package/dist/components/date-picker/date-input.js +134 -22
- package/dist/components/date-picker/date-picker.d.ts +7 -0
- package/dist/components/date-picker/date-picker.js +85 -16
- package/dist/components/dialog/confirm.d.ts +40 -0
- package/dist/components/dialog/confirm.js +79 -0
- package/dist/components/dialog/dialog.d.ts +85 -0
- package/dist/components/dialog/dialog.js +272 -0
- package/dist/components/dialog/dialog.styles.d.ts +1 -0
- package/dist/components/dialog/dialog.styles.js +52 -0
- package/dist/components/dock-panel/dock-panel.d.ts +134 -0
- package/dist/components/dock-panel/dock-panel.js +345 -0
- package/dist/components/dock-panel/dock-panel.styles.d.ts +1 -0
- package/dist/components/dock-panel/dock-panel.styles.js +145 -0
- package/dist/components/document-preview/document-preview.d.ts +135 -0
- package/dist/components/document-preview/document-preview.js +372 -0
- package/dist/components/document-preview/document-preview.styles.d.ts +1 -0
- package/dist/components/document-preview/document-preview.styles.js +191 -0
- package/dist/components/export-button/csv.js +1 -1
- package/dist/components/export-button/export-button.js +2 -0
- package/dist/components/flag/flag.d.ts +29 -13
- package/dist/components/flag/flag.js +39 -13
- package/dist/components/gauge/gauge.js +19 -10
- package/dist/components/generation-status/generation-status.d.ts +109 -0
- package/dist/components/generation-status/generation-status.js +273 -0
- package/dist/components/generation-status/generation-status.styles.d.ts +1 -0
- package/dist/components/generation-status/generation-status.styles.js +84 -0
- package/dist/components/heatmap/calendar-grid.d.ts +4 -1
- package/dist/components/heatmap/calendar-grid.js +25 -12
- package/dist/components/heatmap/heatmap-scale.d.ts +6 -0
- package/dist/components/heatmap/heatmap-scale.js +7 -1
- package/dist/components/heatmap/heatmap.d.ts +74 -0
- package/dist/components/heatmap/heatmap.js +135 -23
- package/dist/components/json-viewer/json-viewer.d.ts +53 -0
- package/dist/components/json-viewer/json-viewer.js +283 -0
- package/dist/components/json-viewer/json-viewer.styles.d.ts +1 -0
- package/dist/components/json-viewer/json-viewer.styles.js +176 -0
- package/dist/components/kbd/kbd.d.ts +77 -0
- package/dist/components/kbd/kbd.js +214 -0
- package/dist/components/kbd/kbd.styles.d.ts +1 -0
- package/dist/components/kbd/kbd.styles.js +52 -0
- package/dist/components/live-region/live-region.d.ts +81 -0
- package/dist/components/live-region/live-region.js +153 -0
- package/dist/components/live-region/live-region.styles.d.ts +1 -0
- package/dist/components/live-region/live-region.styles.js +10 -0
- package/dist/components/map/map.d.ts +8 -6
- package/dist/components/map/map.js +44 -8
- package/dist/components/markdown/markdown-loader.d.ts +34 -0
- package/dist/components/markdown/markdown-loader.js +43 -0
- package/dist/components/markdown/markdown.d.ts +81 -0
- package/dist/components/markdown/markdown.js +284 -0
- package/dist/components/markdown/markdown.styles.d.ts +1 -0
- package/dist/components/markdown/markdown.styles.js +82 -0
- package/dist/components/media-card/media-card.d.ts +122 -0
- package/dist/components/media-card/media-card.js +295 -0
- package/dist/components/media-card/media-card.styles.d.ts +1 -0
- package/dist/components/media-card/media-card.styles.js +132 -0
- package/dist/components/mention-popover/mention-popover.d.ts +200 -0
- package/dist/components/mention-popover/mention-popover.js +475 -0
- package/dist/components/mention-popover/mention-popover.styles.d.ts +1 -0
- package/dist/components/mention-popover/mention-popover.styles.js +101 -0
- package/dist/components/menu/menu-item.d.ts +62 -0
- package/dist/components/menu/menu-item.js +119 -0
- package/dist/components/menu/menu-item.styles.d.ts +1 -0
- package/dist/components/menu/menu-item.styles.js +67 -0
- package/dist/components/menu/menu.d.ts +143 -0
- package/dist/components/menu/menu.js +365 -0
- package/dist/components/menu/menu.styles.d.ts +1 -0
- package/dist/components/menu/menu.styles.js +66 -0
- package/dist/components/model-select/model-select.d.ts +125 -0
- package/dist/components/model-select/model-select.js +490 -0
- package/dist/components/model-select/model-select.styles.d.ts +1 -0
- package/dist/components/model-select/model-select.styles.js +189 -0
- package/dist/components/model-settings-panel/model-settings-panel.d.ts +69 -0
- package/dist/components/model-settings-panel/model-settings-panel.js +145 -0
- package/dist/components/model-settings-panel/model-settings-panel.styles.d.ts +1 -0
- package/dist/components/model-settings-panel/model-settings-panel.styles.js +82 -0
- package/dist/components/playback/playback.d.ts +13 -1
- package/dist/components/playback/playback.js +47 -14
- package/dist/components/responsive-panel/responsive-panel.d.ts +150 -0
- package/dist/components/responsive-panel/responsive-panel.js +365 -0
- package/dist/components/responsive-panel/responsive-panel.styles.d.ts +1 -0
- package/dist/components/responsive-panel/responsive-panel.styles.js +103 -0
- package/dist/components/result-card/result-card.d.ts +44 -0
- package/dist/components/result-card/result-card.js +83 -0
- package/dist/components/result-card/result-card.styles.d.ts +1 -0
- package/dist/components/result-card/result-card.styles.js +50 -0
- package/dist/components/result-card/result-field.d.ts +36 -0
- package/dist/components/result-card/result-field.js +87 -0
- package/dist/components/result-card/result-field.styles.d.ts +1 -0
- package/dist/components/result-card/result-field.styles.js +32 -0
- package/dist/components/select/select.d.ts +43 -2
- package/dist/components/select/select.js +150 -17
- package/dist/components/slider/slider.d.ts +89 -0
- package/dist/components/slider/slider.js +352 -0
- package/dist/components/slider/slider.styles.d.ts +1 -0
- package/dist/components/slider/slider.styles.js +87 -0
- package/dist/components/source-card/source-card.d.ts +92 -0
- package/dist/components/source-card/source-card.js +148 -0
- package/dist/components/source-card/source-card.styles.d.ts +1 -0
- package/dist/components/source-card/source-card.styles.js +69 -0
- package/dist/components/source-list/source-list.d.ts +63 -0
- package/dist/components/source-list/source-list.js +122 -0
- package/dist/components/source-list/source-list.styles.d.ts +1 -0
- package/dist/components/source-list/source-list.styles.js +59 -0
- package/dist/components/sparkline/sparkline.js +27 -13
- package/dist/components/split/split.d.ts +93 -1
- package/dist/components/split/split.js +284 -15
- package/dist/components/split/split.styles.js +30 -0
- package/dist/components/stat/stat.d.ts +6 -1
- package/dist/components/stat/stat.js +8 -2
- package/dist/components/stream-status/stream-status.d.ts +110 -0
- package/dist/components/stream-status/stream-status.js +210 -0
- package/dist/components/stream-status/stream-status.styles.d.ts +1 -0
- package/dist/components/stream-status/stream-status.styles.js +103 -0
- package/dist/components/streaming-text/streaming-text.d.ts +98 -0
- package/dist/components/streaming-text/streaming-text.js +209 -0
- package/dist/components/streaming-text/streaming-text.styles.d.ts +1 -0
- package/dist/components/streaming-text/streaming-text.styles.js +62 -0
- package/dist/components/switch/switch.d.ts +58 -0
- package/dist/components/switch/switch.js +179 -0
- package/dist/components/switch/switch.styles.d.ts +1 -0
- package/dist/components/switch/switch.styles.js +75 -0
- package/dist/components/table/table.d.ts +73 -4
- package/dist/components/table/table.js +169 -20
- package/dist/components/table/table.styles.js +6 -1
- package/dist/components/tabs/tabs.d.ts +51 -0
- package/dist/components/tabs/tabs.js +190 -0
- package/dist/components/tabs/tabs.styles.d.ts +1 -0
- package/dist/components/tabs/tabs.styles.js +63 -0
- package/dist/components/thinking-panel/thinking-panel.d.ts +121 -0
- package/dist/components/thinking-panel/thinking-panel.js +235 -0
- package/dist/components/thinking-panel/thinking-panel.styles.d.ts +1 -0
- package/dist/components/thinking-panel/thinking-panel.styles.js +117 -0
- package/dist/components/toast/toast-item.d.ts +1 -0
- package/dist/components/toast/toast-item.js +31 -6
- package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +130 -0
- package/dist/components/tool-approval-dialog/tool-approval-dialog.js +403 -0
- package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.d.ts +1 -0
- package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +160 -0
- package/dist/components/tool-call-chip/tool-call-chip.d.ts +89 -0
- package/dist/components/tool-call-chip/tool-call-chip.js +288 -0
- package/dist/components/tool-call-chip/tool-call-chip.styles.d.ts +1 -0
- package/dist/components/tool-call-chip/tool-call-chip.styles.js +203 -0
- package/dist/components/tool-param-form/tool-param-form.d.ts +145 -0
- package/dist/components/tool-param-form/tool-param-form.js +366 -0
- package/dist/components/tool-param-form/tool-param-form.styles.d.ts +1 -0
- package/dist/components/tool-param-form/tool-param-form.styles.js +75 -0
- package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +102 -0
- package/dist/components/tool-result-dialog/tool-result-dialog.js +393 -0
- package/dist/components/tool-result-dialog/tool-result-dialog.styles.d.ts +1 -0
- package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +176 -0
- package/dist/components/tool-result-view/registry.d.ts +81 -0
- package/dist/components/tool-result-view/registry.js +84 -0
- package/dist/components/tool-result-view/tool-result-view.d.ts +55 -0
- package/dist/components/tool-result-view/tool-result-view.js +138 -0
- package/dist/components/tool-result-view/tool-result-view.styles.d.ts +1 -0
- package/dist/components/tool-result-view/tool-result-view.styles.js +13 -0
- package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +151 -0
- package/dist/components/tool-select-dialog/tool-select-dialog.js +439 -0
- package/dist/components/tool-select-dialog/tool-select-dialog.styles.d.ts +1 -0
- package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +182 -0
- package/dist/components/tree/tree-node.d.ts +11 -2
- package/dist/components/tree/tree-node.js +16 -6
- package/dist/components/tree/tree.d.ts +8 -2
- package/dist/components/tree/tree.js +18 -11
- package/dist/components/typing-indicator/typing-indicator.d.ts +60 -0
- package/dist/components/typing-indicator/typing-indicator.js +96 -0
- package/dist/components/typing-indicator/typing-indicator.styles.d.ts +1 -0
- package/dist/components/typing-indicator/typing-indicator.styles.js +132 -0
- package/dist/components/virtual-list/virtual-list.d.ts +173 -0
- package/dist/components/virtual-list/virtual-list.js +461 -0
- package/dist/components/virtual-list/virtual-list.styles.d.ts +1 -0
- package/dist/components/virtual-list/virtual-list.styles.js +44 -0
- package/dist/components/word-cloud/word-cloud-layout.js +14 -1
- package/dist/components/word-cloud/word-cloud.d.ts +6 -0
- package/dist/components/word-cloud/word-cloud.js +25 -14
- package/dist/components/word-cloud/word-cloud.styles.js +3 -2
- package/dist/internal/announcer.d.ts +57 -0
- package/dist/internal/announcer.js +71 -0
- package/dist/internal/form-associated.d.ts +1 -0
- package/dist/internal/form-associated.js +55 -11
- package/dist/internal/group-by-recency.d.ts +50 -0
- package/dist/internal/group-by-recency.js +80 -0
- package/dist/internal/rtl.d.ts +23 -0
- package/dist/internal/rtl.js +28 -0
- package/dist/internal/scroll-lock.d.ts +10 -5
- package/dist/internal/scroll-lock.js +30 -14
- package/dist/internal/tokens.styles.js +6 -0
- package/dist/lyra.d.ts +130 -31
- package/dist/lyra.js +94 -26
- package/llms-full.txt +3836 -17
- package/llms.txt +16 -1
- package/package.json +23 -5
- package/dist/components/chart/bar-chart.stories.d.ts +0 -5
- package/dist/components/chart/bar-chart.stories.js +0 -20
- package/dist/components/chart/box-plot.stories.d.ts +0 -5
- package/dist/components/chart/box-plot.stories.js +0 -29
- package/dist/components/chart/bubble-chart.stories.d.ts +0 -5
- package/dist/components/chart/bubble-chart.stories.js +0 -26
- package/dist/components/chart/chart.stories.d.ts +0 -32
- package/dist/components/chart/chart.stories.js +0 -141
- package/dist/components/chart/doughnut-chart.stories.d.ts +0 -5
- package/dist/components/chart/doughnut-chart.stories.js +0 -26
- package/dist/components/chart/histogram.stories.d.ts +0 -5
- package/dist/components/chart/histogram.stories.js +0 -17
- package/dist/components/chart/line-chart.stories.d.ts +0 -22
- package/dist/components/chart/line-chart.stories.js +0 -99
- package/dist/components/chart/lite-chart.stories.d.ts +0 -14
- package/dist/components/chart/lite-chart.stories.js +0 -114
- package/dist/components/chart/pie-chart.stories.d.ts +0 -5
- package/dist/components/chart/pie-chart.stories.js +0 -26
- package/dist/components/chart/polar-area-chart.stories.d.ts +0 -5
- package/dist/components/chart/polar-area-chart.stories.js +0 -20
- package/dist/components/chart/radar-chart.stories.d.ts +0 -5
- package/dist/components/chart/radar-chart.stories.js +0 -20
- package/dist/components/chart/scatter-chart.stories.d.ts +0 -5
- package/dist/components/chart/scatter-chart.stories.js +0 -31
- package/dist/components/combobox/combobox.stories.d.ts +0 -24
- package/dist/components/combobox/combobox.stories.js +0 -102
- package/dist/components/date-picker/date-input.stories.d.ts +0 -10
- package/dist/components/date-picker/date-input.stories.js +0 -48
- package/dist/components/date-picker/date-picker.stories.d.ts +0 -10
- package/dist/components/date-picker/date-picker.stories.js +0 -25
- package/dist/components/empty/empty.stories.d.ts +0 -7
- package/dist/components/empty/empty.stories.js +0 -34
- package/dist/components/export-button/export-button.stories.d.ts +0 -7
- package/dist/components/export-button/export-button.stories.js +0 -37
- package/dist/components/file-input/file-input.stories.d.ts +0 -7
- package/dist/components/file-input/file-input.stories.js +0 -19
- package/dist/components/flag/flag.stories.d.ts +0 -6
- package/dist/components/flag/flag.stories.js +0 -31
- package/dist/components/gauge/gauge.stories.d.ts +0 -8
- package/dist/components/gauge/gauge.stories.js +0 -19
- package/dist/components/graph/graph.stories.d.ts +0 -8
- package/dist/components/graph/graph.stories.js +0 -68
- package/dist/components/heatmap/heatmap.stories.d.ts +0 -27
- package/dist/components/heatmap/heatmap.stories.js +0 -179
- package/dist/components/map/map.stories.d.ts +0 -15
- package/dist/components/map/map.stories.js +0 -135
- package/dist/components/playback/playback.stories.d.ts +0 -7
- package/dist/components/playback/playback.stories.js +0 -16
- package/dist/components/select/select.stories.d.ts +0 -21
- package/dist/components/select/select.stories.js +0 -91
- package/dist/components/skeleton/skeleton.stories.d.ts +0 -5
- package/dist/components/skeleton/skeleton.stories.js +0 -16
- package/dist/components/sparkline/sparkline.stories.d.ts +0 -11
- package/dist/components/sparkline/sparkline.stories.js +0 -39
- package/dist/components/split/split.stories.d.ts +0 -7
- package/dist/components/split/split.stories.js +0 -36
- package/dist/components/stat/stat.stories.d.ts +0 -9
- package/dist/components/stat/stat.stories.js +0 -81
- package/dist/components/table/table.stories.d.ts +0 -12
- package/dist/components/table/table.stories.js +0 -67
- package/dist/components/time-range/time-range.stories.d.ts +0 -8
- package/dist/components/time-range/time-range.stories.js +0 -26
- package/dist/components/toast/toast.stories.d.ts +0 -8
- package/dist/components/toast/toast.stories.js +0 -72
- package/dist/components/tree/tree.stories.d.ts +0 -9
- package/dist/components/tree/tree.stories.js +0 -52
- package/dist/components/widget/widget.stories.d.ts +0 -9
- package/dist/components/widget/widget.stories.js +0 -74
- package/dist/components/word-cloud/word-cloud.stories.d.ts +0 -10
- package/dist/components/word-cloud/word-cloud.stories.js +0 -55
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
/** The rail's effective presentation -- see the class doc for what each renders. */
|
|
4
|
+
export type AppRailMode = 'full' | 'icon-only' | 'mobile';
|
|
5
|
+
/** What can be *assigned* to `mode` -- `'auto'` is a write-only sentinel; see the `mode` accessor doc. */
|
|
6
|
+
export type AppRailModeInput = AppRailMode | 'auto';
|
|
7
|
+
export interface AppRailModeChangeDetail {
|
|
8
|
+
mode: AppRailMode;
|
|
9
|
+
}
|
|
10
|
+
export interface AppRailToggleDetail {
|
|
11
|
+
open: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Pure breakpoint-to-mode resolver, kept separate from the `matchMedia`
|
|
15
|
+
* wiring below so it's directly unit-testable without resizing a real
|
|
16
|
+
* browser window. `mobileMatches` wins over `iconOnlyMatches` when both are
|
|
17
|
+
* true (the viewport is narrower than both breakpoints at once).
|
|
18
|
+
*/
|
|
19
|
+
export declare function computeAppRailMode(iconOnlyMatches: boolean, mobileMatches: boolean): AppRailMode;
|
|
20
|
+
/**
|
|
21
|
+
* `<lyra-app-rail>` — a responsive navigation rail that adapts across three
|
|
22
|
+
* presentations as the *viewport* narrows (not this element's own inline
|
|
23
|
+
* size — see the `mode` accessor doc for why): `'full'` (nav items show
|
|
24
|
+
* icon + label, inline), `'icon-only'` (a narrower inline rail, icons only),
|
|
25
|
+
* and `'mobile'` (hidden behind a toggle button; opening it shows a
|
|
26
|
+
* focus-trapped floating overlay over the page).
|
|
27
|
+
*
|
|
28
|
+
* Breakpoints are viewport-width `matchMedia()` queries against
|
|
29
|
+
* `icon-only-breakpoint`/`mobile-breakpoint`, not a `ResizeObserver` on this
|
|
30
|
+
* element — a nav rail's presentation should track the actual device/window
|
|
31
|
+
* width the way a native OS shell's navigation does, not however much
|
|
32
|
+
* horizontal space a particular layout happens to give it.
|
|
33
|
+
*
|
|
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
|
|
40
|
+
* `'full'`/`'icon-only'` presentation) and `[part="panel"]` (the mobile
|
|
41
|
+
* overlay) are the *same* element promoted in place across modes (mirrors
|
|
42
|
+
* `<lyra-widget>`'s fullscreen mode) — never both at once, and never two
|
|
43
|
+
* separate copies of the slotted content, which slot projection can't
|
|
44
|
+
* produce anyway (a light-DOM node is only ever assigned to one `<slot>`).
|
|
45
|
+
* It's a plain `<div>` with an explicit `role="navigation"` rather than a
|
|
46
|
+
* literal `<nav>` tag: a `<nav>`'s implicit role can't be overridden to
|
|
47
|
+
* `role="dialog"` while the overlay is modal without an `aria-allowed-role`
|
|
48
|
+
* violation (verified against axe), whereas an explicit `role="navigation"`
|
|
49
|
+
* on a generic element can be swapped for `role="dialog"` freely.
|
|
50
|
+
*
|
|
51
|
+
* @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.
|
|
61
|
+
* @slot header - Logo/brand content, shown above the nav items in every mode.
|
|
62
|
+
* @slot footer - A trailing user/settings trigger, shown below the nav items.
|
|
63
|
+
* @event lyra-mode-change - The effective mode changed, whether from a
|
|
64
|
+
* breakpoint crossing or an explicit `mode` assignment. Not fired for a
|
|
65
|
+
* redundant reassignment to the mode already in effect.
|
|
66
|
+
* `detail: AppRailModeChangeDetail`.
|
|
67
|
+
* @event lyra-toggle - The mobile overlay opened or closed — via the
|
|
68
|
+
* built-in toggle button, Escape, a backdrop click, a nav-item click while
|
|
69
|
+
* open, or a breakpoint/forced mode change leaving `'mobile'` while open.
|
|
70
|
+
* Not fired when a consumer sets `open` directly (mirrors `<lyra-dialog>`'s
|
|
71
|
+
* `open`/`close()` split). `detail: AppRailToggleDetail`.
|
|
72
|
+
* @csspart base - The rail root while inline (`'full'`/`'icon-only'` modes).
|
|
73
|
+
* @csspart header - The wrapper around the `header` slot.
|
|
74
|
+
* @csspart nav - The wrapper around the default (nav items) slot.
|
|
75
|
+
* @csspart footer - The wrapper around the `footer` slot.
|
|
76
|
+
* @csspart toggle - The mobile hamburger/close toggle button. Hidden via
|
|
77
|
+
* CSS outside `'mobile'` mode.
|
|
78
|
+
* @csspart backdrop - The mobile overlay's scrim. Only rendered while open.
|
|
79
|
+
* @csspart panel - The mobile overlay's floating panel — see the class doc
|
|
80
|
+
* for why it's the same element as `base`, never both at once.
|
|
81
|
+
*
|
|
82
|
+
* @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:
|
|
87
|
+
* ```html
|
|
88
|
+
* <lyra-app-rail>
|
|
89
|
+
* <a href="/inbox" aria-label="Inbox"><svg aria-hidden="true">...</svg><span>Inbox</span></a>
|
|
90
|
+
* </lyra-app-rail>
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare class LyraAppRail extends LyraElement {
|
|
94
|
+
static styles: import("lit").CSSResultGroup[];
|
|
95
|
+
static properties: {
|
|
96
|
+
mode: {
|
|
97
|
+
reflect: boolean;
|
|
98
|
+
noAccessor: boolean;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/** Below this viewport width, the rail switches from `'full'` to
|
|
102
|
+
* `'icon-only'`. Any valid CSS length, used directly in a `max-width`
|
|
103
|
+
* media query. */
|
|
104
|
+
iconOnlyBreakpoint: string;
|
|
105
|
+
/** Below this viewport width, the rail switches from `'icon-only'` to
|
|
106
|
+
* `'mobile'`. Should be smaller than `iconOnlyBreakpoint` to produce all
|
|
107
|
+
* three states as the viewport narrows. */
|
|
108
|
+
mobileBreakpoint: string;
|
|
109
|
+
/** Whether the mobile floating overlay is shown. Only meaningful while
|
|
110
|
+
* `mode` is `'mobile'` — the value is preserved (not reset) while another
|
|
111
|
+
* mode is active, but no overlay chrome renders until `mode` is
|
|
112
|
+
* `'mobile'` again. Set this directly, or use the built-in toggle button
|
|
113
|
+
* — there is no separate `show()`/`hide()` pair. */
|
|
114
|
+
open: boolean;
|
|
115
|
+
/** Accessible name for the rail's navigation landmark, and for its dialog
|
|
116
|
+
* role while the mobile overlay is open. */
|
|
117
|
+
label: string;
|
|
118
|
+
private hasHeaderSlot;
|
|
119
|
+
private hasFooterSlot;
|
|
120
|
+
private _mode;
|
|
121
|
+
private forced;
|
|
122
|
+
private iconOnlyMatches;
|
|
123
|
+
private mobileMatches;
|
|
124
|
+
private mqIconOnly?;
|
|
125
|
+
private mqMobile?;
|
|
126
|
+
private overlayActive;
|
|
127
|
+
private justOpened;
|
|
128
|
+
private releaseScrollLock?;
|
|
129
|
+
private lastTrigger?;
|
|
130
|
+
private readonly navId;
|
|
131
|
+
/**
|
|
132
|
+
* The rail's current effective presentation. Always one of the three real
|
|
133
|
+
* modes — never `'auto'` — reflecting either the live breakpoint match or,
|
|
134
|
+
* once forced, whatever was last assigned.
|
|
135
|
+
*
|
|
136
|
+
* Assigning `'full'`/`'icon-only'`/`'mobile'` forces that mode and stops
|
|
137
|
+
* this element from responding to `icon-only-breakpoint`/
|
|
138
|
+
* `mobile-breakpoint` matches. Assigning the sentinel `'auto'` releases
|
|
139
|
+
* the force and immediately re-syncs to the current viewport width,
|
|
140
|
+
* resuming automatic tracking. `'auto'` is a write-only instruction, not a
|
|
141
|
+
* value this getter ever returns — there is no way to observe "currently
|
|
142
|
+
* forced" from the property itself.
|
|
143
|
+
*/
|
|
144
|
+
get mode(): AppRailMode;
|
|
145
|
+
set mode(next: AppRailModeInput);
|
|
146
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
147
|
+
protected updated(): void;
|
|
148
|
+
connectedCallback(): void;
|
|
149
|
+
disconnectedCallback(): void;
|
|
150
|
+
private setupMediaQueries;
|
|
151
|
+
private teardownMediaQueries;
|
|
152
|
+
private onIconOnlyChange;
|
|
153
|
+
private onMobileChange;
|
|
154
|
+
private applyComputedMode;
|
|
155
|
+
private setEffectiveMode;
|
|
156
|
+
private setOpen;
|
|
157
|
+
private onToggleClick;
|
|
158
|
+
private onBackdropClick;
|
|
159
|
+
private onNavItemClick;
|
|
160
|
+
private onHeaderSlotChange;
|
|
161
|
+
private onFooterSlotChange;
|
|
162
|
+
private onDocKeyDown;
|
|
163
|
+
private getFocusableElements;
|
|
164
|
+
private getActiveElement;
|
|
165
|
+
render(): TemplateResult;
|
|
166
|
+
}
|
|
167
|
+
declare global {
|
|
168
|
+
interface HTMLElementTagNameMap {
|
|
169
|
+
'lyra-app-rail': LyraAppRail;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,483 @@
|
|
|
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, svg } from 'lit';
|
|
8
|
+
import { property, state } from 'lit/decorators.js';
|
|
9
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
|
+
import { defineElement } from '../../internal/prefix.js';
|
|
11
|
+
import { lockScroll } from '../../internal/scroll-lock.js';
|
|
12
|
+
import { nextId } from '../../internal/a11y.js';
|
|
13
|
+
import { closeIcon } from '../../internal/icons.js';
|
|
14
|
+
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
|
+
}
|
|
46
|
+
// icons.ts has no hamburger/menu glyph and this component must not modify
|
|
47
|
+
// that shared module -- inlined here instead, matching its 24x24
|
|
48
|
+
// viewBox/stroke-width/currentColor convention so it reads as part of the
|
|
49
|
+
// same icon set.
|
|
50
|
+
function menuIcon() {
|
|
51
|
+
return svg `
|
|
52
|
+
<svg
|
|
53
|
+
width="1em"
|
|
54
|
+
height="1em"
|
|
55
|
+
viewBox="0 0 24 24"
|
|
56
|
+
fill="none"
|
|
57
|
+
stroke="currentColor"
|
|
58
|
+
stroke-width="1.75"
|
|
59
|
+
stroke-linecap="round"
|
|
60
|
+
stroke-linejoin="round"
|
|
61
|
+
aria-hidden="true"
|
|
62
|
+
focusable="false"
|
|
63
|
+
>
|
|
64
|
+
<line x1="4" y1="7" x2="20" y2="7"></line>
|
|
65
|
+
<line x1="4" y1="12" x2="20" y2="12"></line>
|
|
66
|
+
<line x1="4" y1="17" x2="20" y2="17"></line>
|
|
67
|
+
</svg>
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Pure breakpoint-to-mode resolver, kept separate from the `matchMedia`
|
|
72
|
+
* wiring below so it's directly unit-testable without resizing a real
|
|
73
|
+
* browser window. `mobileMatches` wins over `iconOnlyMatches` when both are
|
|
74
|
+
* true (the viewport is narrower than both breakpoints at once).
|
|
75
|
+
*/
|
|
76
|
+
export function computeAppRailMode(iconOnlyMatches, mobileMatches) {
|
|
77
|
+
if (mobileMatches)
|
|
78
|
+
return 'mobile';
|
|
79
|
+
if (iconOnlyMatches)
|
|
80
|
+
return 'icon-only';
|
|
81
|
+
return 'full';
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* `<lyra-app-rail>` — a responsive navigation rail that adapts across three
|
|
85
|
+
* presentations as the *viewport* narrows (not this element's own inline
|
|
86
|
+
* size — see the `mode` accessor doc for why): `'full'` (nav items show
|
|
87
|
+
* icon + label, inline), `'icon-only'` (a narrower inline rail, icons only),
|
|
88
|
+
* and `'mobile'` (hidden behind a toggle button; opening it shows a
|
|
89
|
+
* focus-trapped floating overlay over the page).
|
|
90
|
+
*
|
|
91
|
+
* Breakpoints are viewport-width `matchMedia()` queries against
|
|
92
|
+
* `icon-only-breakpoint`/`mobile-breakpoint`, not a `ResizeObserver` on this
|
|
93
|
+
* element — a nav rail's presentation should track the actual device/window
|
|
94
|
+
* width the way a native OS shell's navigation does, not however much
|
|
95
|
+
* horizontal space a particular layout happens to give it.
|
|
96
|
+
*
|
|
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
|
|
103
|
+
* `'full'`/`'icon-only'` presentation) and `[part="panel"]` (the mobile
|
|
104
|
+
* overlay) are the *same* element promoted in place across modes (mirrors
|
|
105
|
+
* `<lyra-widget>`'s fullscreen mode) — never both at once, and never two
|
|
106
|
+
* separate copies of the slotted content, which slot projection can't
|
|
107
|
+
* produce anyway (a light-DOM node is only ever assigned to one `<slot>`).
|
|
108
|
+
* It's a plain `<div>` with an explicit `role="navigation"` rather than a
|
|
109
|
+
* literal `<nav>` tag: a `<nav>`'s implicit role can't be overridden to
|
|
110
|
+
* `role="dialog"` while the overlay is modal without an `aria-allowed-role`
|
|
111
|
+
* violation (verified against axe), whereas an explicit `role="navigation"`
|
|
112
|
+
* on a generic element can be swapped for `role="dialog"` freely.
|
|
113
|
+
*
|
|
114
|
+
* @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.
|
|
124
|
+
* @slot header - Logo/brand content, shown above the nav items in every mode.
|
|
125
|
+
* @slot footer - A trailing user/settings trigger, shown below the nav items.
|
|
126
|
+
* @event lyra-mode-change - The effective mode changed, whether from a
|
|
127
|
+
* breakpoint crossing or an explicit `mode` assignment. Not fired for a
|
|
128
|
+
* redundant reassignment to the mode already in effect.
|
|
129
|
+
* `detail: AppRailModeChangeDetail`.
|
|
130
|
+
* @event lyra-toggle - The mobile overlay opened or closed — via the
|
|
131
|
+
* built-in toggle button, Escape, a backdrop click, a nav-item click while
|
|
132
|
+
* open, or a breakpoint/forced mode change leaving `'mobile'` while open.
|
|
133
|
+
* Not fired when a consumer sets `open` directly (mirrors `<lyra-dialog>`'s
|
|
134
|
+
* `open`/`close()` split). `detail: AppRailToggleDetail`.
|
|
135
|
+
* @csspart base - The rail root while inline (`'full'`/`'icon-only'` modes).
|
|
136
|
+
* @csspart header - The wrapper around the `header` slot.
|
|
137
|
+
* @csspart nav - The wrapper around the default (nav items) slot.
|
|
138
|
+
* @csspart footer - The wrapper around the `footer` slot.
|
|
139
|
+
* @csspart toggle - The mobile hamburger/close toggle button. Hidden via
|
|
140
|
+
* CSS outside `'mobile'` mode.
|
|
141
|
+
* @csspart backdrop - The mobile overlay's scrim. Only rendered while open.
|
|
142
|
+
* @csspart panel - The mobile overlay's floating panel — see the class doc
|
|
143
|
+
* for why it's the same element as `base`, never both at once.
|
|
144
|
+
*
|
|
145
|
+
* @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:
|
|
150
|
+
* ```html
|
|
151
|
+
* <lyra-app-rail>
|
|
152
|
+
* <a href="/inbox" aria-label="Inbox"><svg aria-hidden="true">...</svg><span>Inbox</span></a>
|
|
153
|
+
* </lyra-app-rail>
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export class LyraAppRail extends LyraElement {
|
|
157
|
+
constructor() {
|
|
158
|
+
super(...arguments);
|
|
159
|
+
/** Below this viewport width, the rail switches from `'full'` to
|
|
160
|
+
* `'icon-only'`. Any valid CSS length, used directly in a `max-width`
|
|
161
|
+
* media query. */
|
|
162
|
+
this.iconOnlyBreakpoint = '960px';
|
|
163
|
+
/** Below this viewport width, the rail switches from `'icon-only'` to
|
|
164
|
+
* `'mobile'`. Should be smaller than `iconOnlyBreakpoint` to produce all
|
|
165
|
+
* three states as the viewport narrows. */
|
|
166
|
+
this.mobileBreakpoint = '600px';
|
|
167
|
+
/** Whether the mobile floating overlay is shown. Only meaningful while
|
|
168
|
+
* `mode` is `'mobile'` — the value is preserved (not reset) while another
|
|
169
|
+
* mode is active, but no overlay chrome renders until `mode` is
|
|
170
|
+
* `'mobile'` again. Set this directly, or use the built-in toggle button
|
|
171
|
+
* — there is no separate `show()`/`hide()` pair. */
|
|
172
|
+
this.open = false;
|
|
173
|
+
/** Accessible name for the rail's navigation landmark, and for its dialog
|
|
174
|
+
* role while the mobile overlay is open. */
|
|
175
|
+
this.label = 'Navigation';
|
|
176
|
+
this.hasHeaderSlot = false;
|
|
177
|
+
this.hasFooterSlot = false;
|
|
178
|
+
this._mode = 'full';
|
|
179
|
+
// Whether matchMedia changes are currently ignored because a consumer
|
|
180
|
+
// forced a specific mode -- see the `mode` accessor doc.
|
|
181
|
+
this.forced = false;
|
|
182
|
+
this.iconOnlyMatches = false;
|
|
183
|
+
this.mobileMatches = false;
|
|
184
|
+
// Derived from mode === 'mobile' && open -- tracked as its own field
|
|
185
|
+
// (rather than recomputed inline everywhere) so willUpdate can detect the
|
|
186
|
+
// specific false->true/true->false transition regardless of which of the
|
|
187
|
+
// two source properties changed.
|
|
188
|
+
this.overlayActive = false;
|
|
189
|
+
this.justOpened = false;
|
|
190
|
+
this.navId = nextId('app-rail-nav');
|
|
191
|
+
// Split into two single-query listeners (rather than one shared handler
|
|
192
|
+
// reading both live MediaQueryLists) specifically so a test can invoke
|
|
193
|
+
// either directly with a fabricated { matches } event -- exercising the
|
|
194
|
+
// full breakpoint-response wiring without needing to actually resize the
|
|
195
|
+
// browser window a test runs in.
|
|
196
|
+
this.onIconOnlyChange = (e) => {
|
|
197
|
+
this.iconOnlyMatches = e.matches;
|
|
198
|
+
if (!this.forced)
|
|
199
|
+
this.applyComputedMode();
|
|
200
|
+
};
|
|
201
|
+
this.onMobileChange = (e) => {
|
|
202
|
+
this.mobileMatches = e.matches;
|
|
203
|
+
if (!this.forced)
|
|
204
|
+
this.applyComputedMode();
|
|
205
|
+
};
|
|
206
|
+
this.onToggleClick = (e) => {
|
|
207
|
+
this.lastTrigger = e.currentTarget;
|
|
208
|
+
this.setOpen(!this.open);
|
|
209
|
+
};
|
|
210
|
+
this.onBackdropClick = () => {
|
|
211
|
+
this.setOpen(false);
|
|
212
|
+
};
|
|
213
|
+
// See the default-slot @slot doc -- any click inside the nav items while
|
|
214
|
+
// the overlay is open closes it, without trying to distinguish a real
|
|
215
|
+
// navigation trigger from incidental slotted content.
|
|
216
|
+
this.onNavItemClick = () => {
|
|
217
|
+
if (this.overlayActive)
|
|
218
|
+
this.setOpen(false);
|
|
219
|
+
};
|
|
220
|
+
this.onHeaderSlotChange = (e) => {
|
|
221
|
+
this.hasHeaderSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
222
|
+
};
|
|
223
|
+
this.onFooterSlotChange = (e) => {
|
|
224
|
+
this.hasFooterSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
225
|
+
};
|
|
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
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
static { this.styles = [LyraElement.styles, styles]; }
|
|
255
|
+
// `mode` needs a custom accessor (force/auto semantics below) rather than
|
|
256
|
+
// the usual @property()-generated one -- registered here, alongside the
|
|
257
|
+
// decorator-declared properties below, the same way lyra-model-select's
|
|
258
|
+
// form-associated `value` accessor is. `reflect: true` still applies:
|
|
259
|
+
// LitElement's update loop reflects any `reflect`-flagged property to its
|
|
260
|
+
// attribute generically by reading `this[name]` after render, regardless
|
|
261
|
+
// of whether that property has a custom or auto-generated accessor.
|
|
262
|
+
static { this.properties = {
|
|
263
|
+
mode: { reflect: true, noAccessor: true },
|
|
264
|
+
}; }
|
|
265
|
+
/**
|
|
266
|
+
* The rail's current effective presentation. Always one of the three real
|
|
267
|
+
* modes — never `'auto'` — reflecting either the live breakpoint match or,
|
|
268
|
+
* once forced, whatever was last assigned.
|
|
269
|
+
*
|
|
270
|
+
* Assigning `'full'`/`'icon-only'`/`'mobile'` forces that mode and stops
|
|
271
|
+
* this element from responding to `icon-only-breakpoint`/
|
|
272
|
+
* `mobile-breakpoint` matches. Assigning the sentinel `'auto'` releases
|
|
273
|
+
* the force and immediately re-syncs to the current viewport width,
|
|
274
|
+
* resuming automatic tracking. `'auto'` is a write-only instruction, not a
|
|
275
|
+
* value this getter ever returns — there is no way to observe "currently
|
|
276
|
+
* forced" from the property itself.
|
|
277
|
+
*/
|
|
278
|
+
get mode() {
|
|
279
|
+
return this._mode;
|
|
280
|
+
}
|
|
281
|
+
set mode(next) {
|
|
282
|
+
if (next === 'auto') {
|
|
283
|
+
if (!this.forced)
|
|
284
|
+
return;
|
|
285
|
+
this.forced = false;
|
|
286
|
+
this.applyComputedMode();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (next !== 'full' && next !== 'icon-only' && next !== 'mobile')
|
|
290
|
+
return;
|
|
291
|
+
this.forced = true;
|
|
292
|
+
this.setEffectiveMode(next);
|
|
293
|
+
}
|
|
294
|
+
willUpdate(changed) {
|
|
295
|
+
if (!this.hasUpdated) {
|
|
296
|
+
this.hasHeaderSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'header');
|
|
297
|
+
this.hasFooterSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'footer');
|
|
298
|
+
}
|
|
299
|
+
if (this.hasUpdated && (changed.has('iconOnlyBreakpoint') || changed.has('mobileBreakpoint'))) {
|
|
300
|
+
this.teardownMediaQueries();
|
|
301
|
+
this.setupMediaQueries();
|
|
302
|
+
}
|
|
303
|
+
if (changed.has('open') || changed.has('mode')) {
|
|
304
|
+
const next = this._mode === 'mobile' && this.open;
|
|
305
|
+
if (next !== this.overlayActive) {
|
|
306
|
+
this.overlayActive = next;
|
|
307
|
+
if (next) {
|
|
308
|
+
this.justOpened = true;
|
|
309
|
+
this.releaseScrollLock = lockScroll();
|
|
310
|
+
document.addEventListener('keydown', this.onDocKeyDown);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
this.releaseScrollLock?.();
|
|
314
|
+
this.releaseScrollLock = undefined;
|
|
315
|
+
document.removeEventListener('keydown', this.onDocKeyDown);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Runs after render (not willUpdate) so [part="panel"] and its slotted
|
|
321
|
+
// content have already landed in the DOM before the focus call below can
|
|
322
|
+
// rely on them -- mirrors lyra-dialog's/lyra-widget's identical ordering
|
|
323
|
+
// rationale.
|
|
324
|
+
updated() {
|
|
325
|
+
if (this.justOpened) {
|
|
326
|
+
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
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
connectedCallback() {
|
|
337
|
+
super.connectedCallback();
|
|
338
|
+
this.setupMediaQueries();
|
|
339
|
+
// A reconnect (e.g. a drag-and-drop reparent keeping this same element
|
|
340
|
+
// instance) fires disconnectedCallback then connectedCallback
|
|
341
|
+
// 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);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
disconnectedCallback() {
|
|
350
|
+
super.disconnectedCallback();
|
|
351
|
+
this.teardownMediaQueries();
|
|
352
|
+
this.releaseScrollLock?.();
|
|
353
|
+
this.releaseScrollLock = undefined;
|
|
354
|
+
document.removeEventListener('keydown', this.onDocKeyDown);
|
|
355
|
+
}
|
|
356
|
+
setupMediaQueries() {
|
|
357
|
+
if (typeof window === 'undefined' || !window.matchMedia)
|
|
358
|
+
return;
|
|
359
|
+
this.mqIconOnly = window.matchMedia(`(max-width: ${this.iconOnlyBreakpoint})`);
|
|
360
|
+
this.mqMobile = window.matchMedia(`(max-width: ${this.mobileBreakpoint})`);
|
|
361
|
+
this.mqIconOnly.addEventListener('change', this.onIconOnlyChange);
|
|
362
|
+
this.mqMobile.addEventListener('change', this.onMobileChange);
|
|
363
|
+
this.iconOnlyMatches = this.mqIconOnly.matches;
|
|
364
|
+
this.mobileMatches = this.mqMobile.matches;
|
|
365
|
+
if (!this.forced)
|
|
366
|
+
this.applyComputedMode();
|
|
367
|
+
}
|
|
368
|
+
teardownMediaQueries() {
|
|
369
|
+
this.mqIconOnly?.removeEventListener('change', this.onIconOnlyChange);
|
|
370
|
+
this.mqMobile?.removeEventListener('change', this.onMobileChange);
|
|
371
|
+
this.mqIconOnly = undefined;
|
|
372
|
+
this.mqMobile = undefined;
|
|
373
|
+
}
|
|
374
|
+
applyComputedMode() {
|
|
375
|
+
this.setEffectiveMode(computeAppRailMode(this.iconOnlyMatches, this.mobileMatches));
|
|
376
|
+
}
|
|
377
|
+
setEffectiveMode(next) {
|
|
378
|
+
if (this._mode === next)
|
|
379
|
+
return;
|
|
380
|
+
const old = this._mode;
|
|
381
|
+
this._mode = next;
|
|
382
|
+
this.requestUpdate('mode', old);
|
|
383
|
+
this.emit('lyra-mode-change', { mode: next });
|
|
384
|
+
// 'open' is only meaningful in 'mobile' mode -- leaving it while open
|
|
385
|
+
// closes the overlay as a side effect (through setOpen, so it still
|
|
386
|
+
// emits lyra-toggle and releases the scroll lock/focus trap normally)
|
|
387
|
+
// rather than leaving a now-invisible overlay primed to reappear the
|
|
388
|
+
// next time mode returns to 'mobile'.
|
|
389
|
+
if (next !== 'mobile' && this.open)
|
|
390
|
+
this.setOpen(false);
|
|
391
|
+
}
|
|
392
|
+
setOpen(next) {
|
|
393
|
+
if (this.open === next)
|
|
394
|
+
return;
|
|
395
|
+
this.open = next;
|
|
396
|
+
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
|
+
}
|
|
429
|
+
render() {
|
|
430
|
+
const mobile = this._mode === 'mobile';
|
|
431
|
+
return html `
|
|
432
|
+
<button
|
|
433
|
+
part="toggle"
|
|
434
|
+
type="button"
|
|
435
|
+
aria-expanded=${this.open ? 'true' : 'false'}
|
|
436
|
+
aria-controls=${this.navId}
|
|
437
|
+
aria-label=${this.open ? 'Close navigation' : 'Open navigation'}
|
|
438
|
+
@click=${this.onToggleClick}
|
|
439
|
+
>
|
|
440
|
+
${this.open ? closeIcon() : menuIcon()}
|
|
441
|
+
</button>
|
|
442
|
+
${mobile && this.open ? html `<div part="backdrop" @click=${this.onBackdropClick}></div>` : nothing}
|
|
443
|
+
<div
|
|
444
|
+
id=${this.navId}
|
|
445
|
+
part=${mobile ? 'panel' : 'base'}
|
|
446
|
+
aria-label=${this.label}
|
|
447
|
+
role=${this.overlayActive ? 'dialog' : 'navigation'}
|
|
448
|
+
aria-modal=${this.overlayActive ? 'true' : nothing}
|
|
449
|
+
tabindex=${this.overlayActive ? '-1' : nothing}
|
|
450
|
+
?inert=${mobile && !this.open}
|
|
451
|
+
>
|
|
452
|
+
<div part="header" ?hidden=${!this.hasHeaderSlot}>
|
|
453
|
+
<slot name="header" @slotchange=${this.onHeaderSlotChange}></slot>
|
|
454
|
+
</div>
|
|
455
|
+
<div part="nav">
|
|
456
|
+
<slot @click=${this.onNavItemClick}></slot>
|
|
457
|
+
</div>
|
|
458
|
+
<div part="footer" ?hidden=${!this.hasFooterSlot}>
|
|
459
|
+
<slot name="footer" @slotchange=${this.onFooterSlotChange}></slot>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
`;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
__decorate([
|
|
466
|
+
property({ attribute: 'icon-only-breakpoint' })
|
|
467
|
+
], LyraAppRail.prototype, "iconOnlyBreakpoint", void 0);
|
|
468
|
+
__decorate([
|
|
469
|
+
property({ attribute: 'mobile-breakpoint' })
|
|
470
|
+
], LyraAppRail.prototype, "mobileBreakpoint", void 0);
|
|
471
|
+
__decorate([
|
|
472
|
+
property({ type: Boolean, reflect: true })
|
|
473
|
+
], LyraAppRail.prototype, "open", void 0);
|
|
474
|
+
__decorate([
|
|
475
|
+
property()
|
|
476
|
+
], LyraAppRail.prototype, "label", void 0);
|
|
477
|
+
__decorate([
|
|
478
|
+
state()
|
|
479
|
+
], LyraAppRail.prototype, "hasHeaderSlot", void 0);
|
|
480
|
+
__decorate([
|
|
481
|
+
state()
|
|
482
|
+
], LyraAppRail.prototype, "hasFooterSlot", void 0);
|
|
483
|
+
defineElement('app-rail', LyraAppRail);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import("lit").CSSResult;
|