@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,40 @@
|
|
|
1
|
+
import './dialog.js';
|
|
2
|
+
export interface ConfirmOptions {
|
|
3
|
+
/** Dialog title -- rendered as a slotted `<h2>`, which also drives the dialog's accessible name (see dialog.ts). */
|
|
4
|
+
title: string;
|
|
5
|
+
/** Optional supporting text rendered below the title. */
|
|
6
|
+
description?: string;
|
|
7
|
+
/** Confirm button label. Defaults to `'Confirm'`. */
|
|
8
|
+
confirmLabel?: string;
|
|
9
|
+
/** Cancel button label. Defaults to `'Cancel'`. */
|
|
10
|
+
cancelLabel?: string;
|
|
11
|
+
/** `'danger'` fills the confirm button with `--lyra-color-danger` instead of `--lyra-color-brand` -- for destructive actions. */
|
|
12
|
+
tone?: 'neutral' | 'danger';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Show a confirmation modal, resolving `true` only when the confirm button
|
|
16
|
+
* is pressed -- Escape, a backdrop click, and the cancel button all resolve
|
|
17
|
+
* `false`. A drop-in replacement for `window.confirm()`.
|
|
18
|
+
*
|
|
19
|
+
* Mounts a transient `<lyra-dialog>` for the duration of the call and
|
|
20
|
+
* removes it once settled, rather than reusing a persistent page-level
|
|
21
|
+
* region (contrast `lyra-toast`'s `toaster.ts`): a confirmation modal has no
|
|
22
|
+
* stacking/queueing concerns -- only one is ever meant to be open at a time
|
|
23
|
+
* -- so a mount-and-remove per call keeps its lifetime trivially tied to the
|
|
24
|
+
* returned promise, with nothing left mounted between calls.
|
|
25
|
+
*
|
|
26
|
+
* Every dismissal path (confirm button, cancel button, Escape, backdrop
|
|
27
|
+
* click) funnels through `<lyra-dialog>`'s own `close()`/`lyra-dialog-close`
|
|
28
|
+
* event, so there is exactly one place that resolves the promise and tears
|
|
29
|
+
* the dialog down.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const ok = await confirm({
|
|
33
|
+
* title: 'Delete conversation?',
|
|
34
|
+
* description: 'This cannot be undone.',
|
|
35
|
+
* confirmLabel: 'Delete',
|
|
36
|
+
* tone: 'danger',
|
|
37
|
+
* });
|
|
38
|
+
* if (ok) deleteConversation();
|
|
39
|
+
*/
|
|
40
|
+
export declare function confirm(options: ConfirmOptions): Promise<boolean>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import './dialog.js';
|
|
2
|
+
// Plain inline-styled <button>s, not a shared lyra-button component -- none
|
|
3
|
+
// exists in this library yet. Every value below is still a --lyra-* token
|
|
4
|
+
// reference, never a raw literal, same requirement as a component's own
|
|
5
|
+
// styles.ts. --lyra-color-on-brand is documented (tokens.styles.ts) as the
|
|
6
|
+
// text color for *brand*-fill content specifically, but it's white in both
|
|
7
|
+
// light and dark themes and reads fine over the danger fill too, so the
|
|
8
|
+
// confirm button reuses it rather than introducing a redundant --lyra-color-
|
|
9
|
+
// on-danger token for one call site.
|
|
10
|
+
const BUTTON_BASE_STYLE = 'font: inherit; font-size: 0.875rem; padding: var(--lyra-space-xs) var(--lyra-space-m); ' +
|
|
11
|
+
'border-radius: var(--lyra-radius); cursor: pointer; border: 1px solid var(--lyra-color-border);';
|
|
12
|
+
const CANCEL_STYLE = 'background: var(--lyra-color-surface); color: var(--lyra-color-text);';
|
|
13
|
+
const CONFIRM_TONE_STYLE = {
|
|
14
|
+
neutral: 'background: var(--lyra-color-brand); color: var(--lyra-color-on-brand); border-color: var(--lyra-color-brand);',
|
|
15
|
+
danger: 'background: var(--lyra-color-danger); color: var(--lyra-color-on-brand); border-color: var(--lyra-color-danger);',
|
|
16
|
+
};
|
|
17
|
+
function createButton(label, style, onClick) {
|
|
18
|
+
const button = document.createElement('button');
|
|
19
|
+
button.type = 'button';
|
|
20
|
+
button.textContent = label;
|
|
21
|
+
button.style.cssText = `${BUTTON_BASE_STYLE} ${style}`;
|
|
22
|
+
button.addEventListener('click', onClick);
|
|
23
|
+
return button;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Show a confirmation modal, resolving `true` only when the confirm button
|
|
27
|
+
* is pressed -- Escape, a backdrop click, and the cancel button all resolve
|
|
28
|
+
* `false`. A drop-in replacement for `window.confirm()`.
|
|
29
|
+
*
|
|
30
|
+
* Mounts a transient `<lyra-dialog>` for the duration of the call and
|
|
31
|
+
* removes it once settled, rather than reusing a persistent page-level
|
|
32
|
+
* region (contrast `lyra-toast`'s `toaster.ts`): a confirmation modal has no
|
|
33
|
+
* stacking/queueing concerns -- only one is ever meant to be open at a time
|
|
34
|
+
* -- so a mount-and-remove per call keeps its lifetime trivially tied to the
|
|
35
|
+
* returned promise, with nothing left mounted between calls.
|
|
36
|
+
*
|
|
37
|
+
* Every dismissal path (confirm button, cancel button, Escape, backdrop
|
|
38
|
+
* click) funnels through `<lyra-dialog>`'s own `close()`/`lyra-dialog-close`
|
|
39
|
+
* event, so there is exactly one place that resolves the promise and tears
|
|
40
|
+
* the dialog down.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const ok = await confirm({
|
|
44
|
+
* title: 'Delete conversation?',
|
|
45
|
+
* description: 'This cannot be undone.',
|
|
46
|
+
* confirmLabel: 'Delete',
|
|
47
|
+
* tone: 'danger',
|
|
48
|
+
* });
|
|
49
|
+
* if (ok) deleteConversation();
|
|
50
|
+
*/
|
|
51
|
+
export function confirm(options) {
|
|
52
|
+
const { title, description, confirmLabel = 'Confirm', cancelLabel = 'Cancel', tone = 'neutral' } = options;
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const dialog = document.createElement('lyra-dialog');
|
|
55
|
+
const heading = document.createElement('h2');
|
|
56
|
+
heading.textContent = title;
|
|
57
|
+
heading.style.cssText = 'margin: 0 0 var(--lyra-space-s) 0; font-size: 1.0625rem; font-weight: 600;';
|
|
58
|
+
dialog.appendChild(heading);
|
|
59
|
+
if (description) {
|
|
60
|
+
const desc = document.createElement('p');
|
|
61
|
+
desc.textContent = description;
|
|
62
|
+
desc.style.cssText = 'margin: var(--lyra-space-xs) 0 0 0; color: var(--lyra-color-text-quiet);';
|
|
63
|
+
dialog.appendChild(desc);
|
|
64
|
+
}
|
|
65
|
+
dialog.addEventListener('lyra-dialog-close', (e) => {
|
|
66
|
+
const reason = e.detail;
|
|
67
|
+
resolve(reason === 'confirm');
|
|
68
|
+
dialog.remove();
|
|
69
|
+
});
|
|
70
|
+
const cancelButton = createButton(cancelLabel, CANCEL_STYLE, () => dialog.close('cancel'));
|
|
71
|
+
cancelButton.slot = 'footer';
|
|
72
|
+
const confirmButton = createButton(confirmLabel, CONFIRM_TONE_STYLE[tone], () => dialog.close('confirm'));
|
|
73
|
+
confirmButton.slot = 'footer';
|
|
74
|
+
dialog.appendChild(cancelButton);
|
|
75
|
+
dialog.appendChild(confirmButton);
|
|
76
|
+
dialog.open = true;
|
|
77
|
+
document.body.appendChild(dialog);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reason a dialog was dismissed, forwarded as the `lyra-dialog-close` event
|
|
5
|
+
* detail. `'escape'` and `'backdrop'` are emitted by the dialog's own built-in
|
|
6
|
+
* dismiss triggers; any other string is whatever a caller passes to
|
|
7
|
+
* `close()` (e.g. a consumer's own footer close button, or confirm.ts's
|
|
8
|
+
* `'confirm'`/`'cancel'`).
|
|
9
|
+
*/
|
|
10
|
+
export type DialogCloseReason = 'escape' | 'backdrop' | 'api' | string;
|
|
11
|
+
/**
|
|
12
|
+
* `<lyra-dialog>` — a general-purpose modal/overlay. `role="dialog"`,
|
|
13
|
+
* focus-trapped while open, dismissible via Escape or a backdrop click, and
|
|
14
|
+
* scroll-locks the document for as long as it's open. Chrome stays minimal —
|
|
15
|
+
* no built-in title bar or close button; a consumer supplies a heading and
|
|
16
|
+
* any close affordance itself via the default/`footer` slots.
|
|
17
|
+
*
|
|
18
|
+
* Accessible name: if a heading element (`h1`–`h6` or `[role="heading"]`) is
|
|
19
|
+
* a *direct child* (not inside `slot="footer"`), its text content becomes
|
|
20
|
+
* `aria-label` on the panel. Otherwise, when `label` is set, an invisible
|
|
21
|
+
* (`.sr-only`, exposed as the `label` part) element carrying that text is
|
|
22
|
+
* rendered inside the panel and `aria-labelledby` points at it instead.
|
|
23
|
+
* Either way `label` itself never renders visible chrome — a slotted heading
|
|
24
|
+
* is what a sighted user sees; `::part(label)` can be restyled to make the
|
|
25
|
+
* sr-only text visible too, if a consumer wants that instead of slotting a
|
|
26
|
+
* heading.
|
|
27
|
+
*
|
|
28
|
+
* The heading case deliberately uses `aria-label` (a copied string) rather
|
|
29
|
+
* than `aria-labelledby` pointing at the heading's `id`: the heading is
|
|
30
|
+
* *light-DOM* content while `[part="panel"]` lives in this element's
|
|
31
|
+
* *shadow* tree, and an ID-reference attribute can't resolve across that
|
|
32
|
+
* boundary (verified against axe's `aria-dialog-name` rule) — unlike the
|
|
33
|
+
* `label`-prop case above, where the sr-only element is rendered inside the
|
|
34
|
+
* same shadow root it labels, so `aria-labelledby` there is safe.
|
|
35
|
+
*
|
|
36
|
+
* @customElement lyra-dialog
|
|
37
|
+
* @slot - The dialog body.
|
|
38
|
+
* @slot footer - Action buttons, rendered in a bottom row.
|
|
39
|
+
* @event lyra-dialog-close - `detail: DialogCloseReason`. Fired whenever the
|
|
40
|
+
* dialog is dismissed via Escape, a backdrop click, or a `close()` call.
|
|
41
|
+
* @csspart backdrop - The full-viewport scrim behind the panel.
|
|
42
|
+
* @csspart panel - The dialog panel itself (`role="dialog"` while open).
|
|
43
|
+
* @csspart label - The invisible `label`-text element used for
|
|
44
|
+
* `aria-labelledby` when no heading is slotted.
|
|
45
|
+
* @csspart body - The wrapper around the default slot.
|
|
46
|
+
* @csspart footer - The wrapper around the `footer` slot.
|
|
47
|
+
*/
|
|
48
|
+
export declare class LyraDialog extends LyraElement {
|
|
49
|
+
static styles: import("lit").CSSResultGroup[];
|
|
50
|
+
/** Whether the dialog is open. Set this (or call `close()`) — there is no separate `show()`/`hide()` pair. */
|
|
51
|
+
open: boolean;
|
|
52
|
+
/** Accessible name used when no heading is slotted — see the class doc for the full fallback order. */
|
|
53
|
+
label: string;
|
|
54
|
+
private hasFooterSlot;
|
|
55
|
+
private headingText?;
|
|
56
|
+
private releaseScrollLock?;
|
|
57
|
+
private lastTrigger?;
|
|
58
|
+
private readonly srLabelId;
|
|
59
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
60
|
+
protected updated(changed: PropertyValues): void;
|
|
61
|
+
connectedCallback(): void;
|
|
62
|
+
disconnectedCallback(): void;
|
|
63
|
+
private onDefaultSlotChange;
|
|
64
|
+
private onFooterSlotChange;
|
|
65
|
+
private detectHeading;
|
|
66
|
+
/**
|
|
67
|
+
* Close the dialog and return focus to whatever had it before the dialog
|
|
68
|
+
* opened. `reason` is forwarded as the `lyra-dialog-close` detail --
|
|
69
|
+
* built-in triggers pass `'escape'`/`'backdrop'`; a consumer's own close
|
|
70
|
+
* affordance (e.g. a footer Cancel button) should call this directly with
|
|
71
|
+
* its own reason string, so every dismissal path funnels through the same
|
|
72
|
+
* event instead of the consumer having to also toggle `open` itself.
|
|
73
|
+
*/
|
|
74
|
+
close(reason?: DialogCloseReason): void;
|
|
75
|
+
private onBackdropClick;
|
|
76
|
+
private onDocKeyDown;
|
|
77
|
+
private getFocusableElements;
|
|
78
|
+
private getActiveElement;
|
|
79
|
+
render(): TemplateResult;
|
|
80
|
+
}
|
|
81
|
+
declare global {
|
|
82
|
+
interface HTMLElementTagNameMap {
|
|
83
|
+
'lyra-dialog': LyraDialog;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
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, 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, srOnly } from '../../internal/a11y.js';
|
|
13
|
+
import { styles } from './dialog.styles.js';
|
|
14
|
+
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
15
|
+
const HEADING_SELECTOR = 'h1, h2, h3, h4, h5, h6, [role="heading"]';
|
|
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-widget's identical helper rather than imported/shared -- widget's
|
|
20
|
+
// fullscreen mode is a separate feature and this component must not depend
|
|
21
|
+
// on it (see the module doc in dialog.ts's authoring notes).
|
|
22
|
+
function collectFocusable(el) {
|
|
23
|
+
const result = [];
|
|
24
|
+
if (el.matches(FOCUSABLE_SELECTOR)) {
|
|
25
|
+
result.push(el);
|
|
26
|
+
}
|
|
27
|
+
if (el instanceof HTMLSlotElement) {
|
|
28
|
+
for (const assigned of el.assignedElements({ flatten: true })) {
|
|
29
|
+
result.push(...collectFocusable(assigned));
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
const container = el.shadowRoot ?? el;
|
|
34
|
+
for (const child of Array.from(container.children)) {
|
|
35
|
+
result.push(...collectFocusable(child));
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
/** Whether `el` is actually laid out/paintable -- `checkVisibility()` (falling
|
|
40
|
+
* back to `getClientRects().length` where unsupported) correctly follows
|
|
41
|
+
* flattened-tree/slot assignment, unlike `offsetParent`. Mirrors
|
|
42
|
+
* lyra-widget's identical helper. */
|
|
43
|
+
function isRendered(el) {
|
|
44
|
+
return el.checkVisibility ? el.checkVisibility() : el.getClientRects().length > 0;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `<lyra-dialog>` — a general-purpose modal/overlay. `role="dialog"`,
|
|
48
|
+
* focus-trapped while open, dismissible via Escape or a backdrop click, and
|
|
49
|
+
* scroll-locks the document for as long as it's open. Chrome stays minimal —
|
|
50
|
+
* no built-in title bar or close button; a consumer supplies a heading and
|
|
51
|
+
* any close affordance itself via the default/`footer` slots.
|
|
52
|
+
*
|
|
53
|
+
* Accessible name: if a heading element (`h1`–`h6` or `[role="heading"]`) is
|
|
54
|
+
* a *direct child* (not inside `slot="footer"`), its text content becomes
|
|
55
|
+
* `aria-label` on the panel. Otherwise, when `label` is set, an invisible
|
|
56
|
+
* (`.sr-only`, exposed as the `label` part) element carrying that text is
|
|
57
|
+
* rendered inside the panel and `aria-labelledby` points at it instead.
|
|
58
|
+
* Either way `label` itself never renders visible chrome — a slotted heading
|
|
59
|
+
* is what a sighted user sees; `::part(label)` can be restyled to make the
|
|
60
|
+
* sr-only text visible too, if a consumer wants that instead of slotting a
|
|
61
|
+
* heading.
|
|
62
|
+
*
|
|
63
|
+
* The heading case deliberately uses `aria-label` (a copied string) rather
|
|
64
|
+
* than `aria-labelledby` pointing at the heading's `id`: the heading is
|
|
65
|
+
* *light-DOM* content while `[part="panel"]` lives in this element's
|
|
66
|
+
* *shadow* tree, and an ID-reference attribute can't resolve across that
|
|
67
|
+
* boundary (verified against axe's `aria-dialog-name` rule) — unlike the
|
|
68
|
+
* `label`-prop case above, where the sr-only element is rendered inside the
|
|
69
|
+
* same shadow root it labels, so `aria-labelledby` there is safe.
|
|
70
|
+
*
|
|
71
|
+
* @customElement lyra-dialog
|
|
72
|
+
* @slot - The dialog body.
|
|
73
|
+
* @slot footer - Action buttons, rendered in a bottom row.
|
|
74
|
+
* @event lyra-dialog-close - `detail: DialogCloseReason`. Fired whenever the
|
|
75
|
+
* dialog is dismissed via Escape, a backdrop click, or a `close()` call.
|
|
76
|
+
* @csspart backdrop - The full-viewport scrim behind the panel.
|
|
77
|
+
* @csspart panel - The dialog panel itself (`role="dialog"` while open).
|
|
78
|
+
* @csspart label - The invisible `label`-text element used for
|
|
79
|
+
* `aria-labelledby` when no heading is slotted.
|
|
80
|
+
* @csspart body - The wrapper around the default slot.
|
|
81
|
+
* @csspart footer - The wrapper around the `footer` slot.
|
|
82
|
+
*/
|
|
83
|
+
export class LyraDialog extends LyraElement {
|
|
84
|
+
constructor() {
|
|
85
|
+
super(...arguments);
|
|
86
|
+
/** Whether the dialog is open. Set this (or call `close()`) — there is no separate `show()`/`hide()` pair. */
|
|
87
|
+
this.open = false;
|
|
88
|
+
/** Accessible name used when no heading is slotted — see the class doc for the full fallback order. */
|
|
89
|
+
this.label = '';
|
|
90
|
+
this.hasFooterSlot = false;
|
|
91
|
+
this.srLabelId = nextId('dialog-label');
|
|
92
|
+
this.onDefaultSlotChange = () => {
|
|
93
|
+
this.detectHeading();
|
|
94
|
+
};
|
|
95
|
+
this.onFooterSlotChange = (e) => {
|
|
96
|
+
this.hasFooterSlot = e.target.assignedElements({ flatten: true }).length > 0;
|
|
97
|
+
};
|
|
98
|
+
this.onBackdropClick = () => {
|
|
99
|
+
this.close('backdrop');
|
|
100
|
+
};
|
|
101
|
+
this.onDocKeyDown = (e) => {
|
|
102
|
+
if (!this.open)
|
|
103
|
+
return;
|
|
104
|
+
if (e.key === 'Escape') {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
this.close('escape');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (e.key !== 'Tab')
|
|
110
|
+
return;
|
|
111
|
+
const focusable = this.getFocusableElements();
|
|
112
|
+
if (focusable.length === 0) {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const first = focusable[0];
|
|
117
|
+
const last = focusable[focusable.length - 1];
|
|
118
|
+
const active = this.getActiveElement();
|
|
119
|
+
if (e.shiftKey && active === first) {
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
last.focus();
|
|
122
|
+
}
|
|
123
|
+
else if (!e.shiftKey && active === last) {
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
first.focus();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
static { this.styles = [LyraElement.styles, srOnly, styles]; }
|
|
130
|
+
willUpdate(changed) {
|
|
131
|
+
if (!this.hasUpdated) {
|
|
132
|
+
this.hasFooterSlot = Array.from(this.children).some((el) => el.getAttribute('slot') === 'footer');
|
|
133
|
+
this.detectHeading();
|
|
134
|
+
}
|
|
135
|
+
if (changed.has('open')) {
|
|
136
|
+
if (this.open) {
|
|
137
|
+
// Captured here (before render) rather than from a click event on
|
|
138
|
+
// some specific internal control -- unlike lyra-widget's fullscreen
|
|
139
|
+
// toggle, a dialog's trigger typically lives *outside* the
|
|
140
|
+
// component entirely, so "whatever had focus right before open"
|
|
141
|
+
// is the only generally correct definition of "the trigger".
|
|
142
|
+
const active = this.getActiveElement();
|
|
143
|
+
this.lastTrigger = active instanceof HTMLElement ? active : undefined;
|
|
144
|
+
this.releaseScrollLock = lockScroll();
|
|
145
|
+
document.addEventListener('keydown', this.onDocKeyDown);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
this.releaseScrollLock?.();
|
|
149
|
+
this.releaseScrollLock = undefined;
|
|
150
|
+
document.removeEventListener('keydown', this.onDocKeyDown);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Runs after render (not willUpdate) so [part="panel"] has already landed
|
|
155
|
+
// in the DOM before the fallback .focus() call below can rely on it --
|
|
156
|
+
// mirrors lyra-widget's identical ordering rationale for its fullscreen mode.
|
|
157
|
+
updated(changed) {
|
|
158
|
+
if (changed.has('open') && this.open) {
|
|
159
|
+
const first = this.getFocusableElements()[0];
|
|
160
|
+
if (first) {
|
|
161
|
+
first.focus();
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
this.shadowRoot?.querySelector('[part="panel"]')?.focus();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
connectedCallback() {
|
|
169
|
+
super.connectedCallback();
|
|
170
|
+
// A reconnect (e.g. a drag-and-drop reparent keeping this same element
|
|
171
|
+
// instance) fires disconnectedCallback then connectedCallback
|
|
172
|
+
// synchronously with no update in between, so willUpdate never reruns to
|
|
173
|
+
// notice `open` is still true -- restore the scroll lock/trap it dropped.
|
|
174
|
+
if (this.hasUpdated && this.open && !this.releaseScrollLock) {
|
|
175
|
+
this.releaseScrollLock = lockScroll();
|
|
176
|
+
document.addEventListener('keydown', this.onDocKeyDown);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
disconnectedCallback() {
|
|
180
|
+
super.disconnectedCallback();
|
|
181
|
+
this.releaseScrollLock?.();
|
|
182
|
+
this.releaseScrollLock = undefined;
|
|
183
|
+
document.removeEventListener('keydown', this.onDocKeyDown);
|
|
184
|
+
}
|
|
185
|
+
// Only direct children are scanned -- a heading nested several layers deep
|
|
186
|
+
// (or inside a slotted custom element's own shadow root) is left to the
|
|
187
|
+
// consumer to label explicitly via `label` instead. Same depth limit
|
|
188
|
+
// lyra-widget applies to its own actions-slot presence check. Recomputed
|
|
189
|
+
// only on slot assignment changes, not on every render -- a consumer that
|
|
190
|
+
// mutates an already-slotted heading's textContent in place (rather than
|
|
191
|
+
// replacing the node) won't retroactively update aria-label; set `label`
|
|
192
|
+
// instead for a title that needs to change live.
|
|
193
|
+
detectHeading() {
|
|
194
|
+
const heading = Array.from(this.children).find((el) => el.getAttribute('slot') !== 'footer' && el.matches(HEADING_SELECTOR));
|
|
195
|
+
this.headingText = heading?.textContent?.trim() || undefined;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Close the dialog and return focus to whatever had it before the dialog
|
|
199
|
+
* opened. `reason` is forwarded as the `lyra-dialog-close` detail --
|
|
200
|
+
* built-in triggers pass `'escape'`/`'backdrop'`; a consumer's own close
|
|
201
|
+
* affordance (e.g. a footer Cancel button) should call this directly with
|
|
202
|
+
* its own reason string, so every dismissal path funnels through the same
|
|
203
|
+
* event instead of the consumer having to also toggle `open` itself.
|
|
204
|
+
*/
|
|
205
|
+
close(reason = 'api') {
|
|
206
|
+
if (!this.open)
|
|
207
|
+
return;
|
|
208
|
+
this.open = false;
|
|
209
|
+
this.emit('lyra-dialog-close', reason);
|
|
210
|
+
this.lastTrigger?.focus();
|
|
211
|
+
}
|
|
212
|
+
// Bounds Tab/Shift+Tab to the panel while open. Order follows the default
|
|
213
|
+
// (body) slot, then the footer slot -- the same order the flattened tree
|
|
214
|
+
// already tabs through.
|
|
215
|
+
getFocusableElements() {
|
|
216
|
+
const root = this.shadowRoot;
|
|
217
|
+
if (!root)
|
|
218
|
+
return [];
|
|
219
|
+
const fromSlot = (selector) => {
|
|
220
|
+
const slot = root.querySelector(selector);
|
|
221
|
+
return slot ? slot.assignedElements({ flatten: true }).flatMap(collectFocusable) : [];
|
|
222
|
+
};
|
|
223
|
+
return [...fromSlot('slot:not([name])'), ...fromSlot('slot[name="footer"]')].filter(isRendered);
|
|
224
|
+
}
|
|
225
|
+
getActiveElement() {
|
|
226
|
+
let active = document.activeElement;
|
|
227
|
+
while (active) {
|
|
228
|
+
const inner = active.shadowRoot?.activeElement ?? null;
|
|
229
|
+
if (!inner)
|
|
230
|
+
break;
|
|
231
|
+
active = inner;
|
|
232
|
+
}
|
|
233
|
+
return active;
|
|
234
|
+
}
|
|
235
|
+
render() {
|
|
236
|
+
const useSrLabel = !this.headingText && this.label.length > 0;
|
|
237
|
+
return html `
|
|
238
|
+
<div part="backdrop" @click=${this.onBackdropClick}></div>
|
|
239
|
+
<div
|
|
240
|
+
part="panel"
|
|
241
|
+
role=${this.open ? 'dialog' : nothing}
|
|
242
|
+
aria-modal=${this.open ? 'true' : nothing}
|
|
243
|
+
aria-label=${this.headingText ?? nothing}
|
|
244
|
+
aria-labelledby=${useSrLabel ? this.srLabelId : nothing}
|
|
245
|
+
tabindex="-1"
|
|
246
|
+
>
|
|
247
|
+
${useSrLabel
|
|
248
|
+
? html `<span id=${this.srLabelId} part="label" class="sr-only">${this.label}</span>`
|
|
249
|
+
: nothing}
|
|
250
|
+
<div part="body">
|
|
251
|
+
<slot @slotchange=${this.onDefaultSlotChange}></slot>
|
|
252
|
+
</div>
|
|
253
|
+
<div part="footer" ?hidden=${!this.hasFooterSlot}>
|
|
254
|
+
<slot name="footer" @slotchange=${this.onFooterSlotChange}></slot>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
`;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
__decorate([
|
|
261
|
+
property({ type: Boolean, reflect: true })
|
|
262
|
+
], LyraDialog.prototype, "open", void 0);
|
|
263
|
+
__decorate([
|
|
264
|
+
property()
|
|
265
|
+
], LyraDialog.prototype, "label", void 0);
|
|
266
|
+
__decorate([
|
|
267
|
+
state()
|
|
268
|
+
], LyraDialog.prototype, "hasFooterSlot", void 0);
|
|
269
|
+
__decorate([
|
|
270
|
+
state()
|
|
271
|
+
], LyraDialog.prototype, "headingText", void 0);
|
|
272
|
+
defineElement('dialog', LyraDialog);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import("lit").CSSResult;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const styles = css `
|
|
3
|
+
:host {
|
|
4
|
+
/* Backdrop scrim color -- component-specific so a host can retheme it
|
|
5
|
+
without a raw literal leaking into the public API (no shared
|
|
6
|
+
--wa-*-overlay token exists in the design system to resolve through,
|
|
7
|
+
same rationale as lyra-widget's --lyra-widget-overlay-color). */
|
|
8
|
+
--lyra-dialog-overlay-color: rgb(0 0 0 / 0.5);
|
|
9
|
+
display: none;
|
|
10
|
+
position: fixed;
|
|
11
|
+
inset: 0;
|
|
12
|
+
z-index: 1000;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
padding: var(--lyra-space-l);
|
|
16
|
+
}
|
|
17
|
+
:host([open]) {
|
|
18
|
+
display: flex;
|
|
19
|
+
}
|
|
20
|
+
[part='backdrop'] {
|
|
21
|
+
position: absolute;
|
|
22
|
+
inset: 0;
|
|
23
|
+
background: var(--lyra-dialog-overlay-color);
|
|
24
|
+
}
|
|
25
|
+
[part='panel'] {
|
|
26
|
+
position: relative;
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
max-inline-size: min(32rem, 100%);
|
|
30
|
+
max-block-size: 100%;
|
|
31
|
+
background: var(--lyra-color-surface);
|
|
32
|
+
border: 1px solid var(--lyra-color-border);
|
|
33
|
+
border-radius: var(--lyra-radius);
|
|
34
|
+
box-shadow: var(--lyra-shadow);
|
|
35
|
+
overflow: auto;
|
|
36
|
+
}
|
|
37
|
+
[part='body'] {
|
|
38
|
+
padding: var(--lyra-space-l);
|
|
39
|
+
overflow: auto;
|
|
40
|
+
}
|
|
41
|
+
[part='footer'] {
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
justify-content: flex-end;
|
|
45
|
+
gap: var(--lyra-space-s);
|
|
46
|
+
padding: var(--lyra-space-m) var(--lyra-space-l);
|
|
47
|
+
border-block-start: 1px solid var(--lyra-color-border);
|
|
48
|
+
}
|
|
49
|
+
[part='footer'][hidden] {
|
|
50
|
+
display: none;
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
/** Which edge of the panel's own container it's docked to. `'start'`/`'end'`
|
|
4
|
+
* are logical-inline (mirror left/right depending on writing direction);
|
|
5
|
+
* `'top'`/`'bottom'` are block-direction and unaffected by RTL. */
|
|
6
|
+
export type DockPanelEdge = 'start' | 'end' | 'top' | 'bottom';
|
|
7
|
+
export interface DockPanelResizeDetail {
|
|
8
|
+
size: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DockPanelCollapseChangeDetail {
|
|
11
|
+
collapsed: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolves an arbitrary CSS length (`px`, `rem`, `em`, `vw`, `vh`, `%`, or a
|
|
15
|
+
* bare/unitless number treated as `px`) to a live pixel value, without a DOM
|
|
16
|
+
* probe measurement -- `min-size`/`max-size` are pure constraints that are
|
|
17
|
+
* never themselves rendered anywhere, so there's nothing to measure; the
|
|
18
|
+
* *current* `size` is instead always read back from the host's own
|
|
19
|
+
* `getBoundingClientRect()` (see `currentSizePx()`), which handles any unit
|
|
20
|
+
* for free since the browser already resolved it during layout. Returns
|
|
21
|
+
* `undefined` for an empty/unparseable string.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseLengthPx(length: string, containerPx: number, fontSizeEl?: Element): number | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* `<lyra-dock-panel>` — a single panel docked to one edge of whatever
|
|
26
|
+
* contains it, resizable by dragging its inner edge. Unlike `<lyra-split>`
|
|
27
|
+
* (which owns and lays out N sibling panels, and requires restructuring a
|
|
28
|
+
* layout so every panel becomes its direct child), this is one self-
|
|
29
|
+
* contained element you drop next to your existing content -- typically as
|
|
30
|
+
* an absolutely-positioned child of a `position: relative` parent, or as a
|
|
31
|
+
* flex item alongside a main-content sibling. It deliberately imposes no
|
|
32
|
+
* `position`/`inset` of its own (see the styles module): it only manages its
|
|
33
|
+
* own size along the resize axis (`inline-size` for `start`/`end`,
|
|
34
|
+
* `block-size` for `top`/`bottom`) and fills 100% of the cross axis, leaving
|
|
35
|
+
* where it sits in the page entirely up to the consumer's own layout.
|
|
36
|
+
*
|
|
37
|
+
* `lyra-split` stays the right primitive for the multi-sibling-panel case;
|
|
38
|
+
* this is the primitive for the single-edge-docked case, kept as a separate
|
|
39
|
+
* component rather than a second mode bolted onto `lyra-split`'s API.
|
|
40
|
+
*
|
|
41
|
+
* Pointer-drag-resize mirrors `lyra-split`'s pointer-capture technique
|
|
42
|
+
* (pointerdown captures the pointer on the handle, pointermove computes a
|
|
43
|
+
* new size, pointerup/pointercancel/lostpointercapture all release it) but
|
|
44
|
+
* for a single draggable edge instead of N-1 dividers between N panels, and
|
|
45
|
+
* reasons in raw pixels throughout rather than percent -- `size` is a CSS
|
|
46
|
+
* length, and pointer movement is naturally pixels, so there's no percent
|
|
47
|
+
* domain to convert through here. Every resize (drag step, drag release, or
|
|
48
|
+
* keyboard step) always commits `size` as a `px` string regardless of what
|
|
49
|
+
* unit `size`/`min-size`/`max-size` were originally expressed in -- a drag
|
|
50
|
+
* inherently produces a pixel-precise result, so re-expressing it in the
|
|
51
|
+
* caller's original unit (e.g. back into `rem`) would just be lossy
|
|
52
|
+
* re-derivation for no benefit.
|
|
53
|
+
*
|
|
54
|
+
* Collapsing hides the slotted content but keeps the panel itself at a
|
|
55
|
+
* small persistent "rail" width/height (`--lyra-dock-panel-collapsed-size`,
|
|
56
|
+
* default `var(--lyra-icon-button-size)`) rather than collapsing to zero --
|
|
57
|
+
* a zero-size collapsed panel would have nowhere left to host the toggle
|
|
58
|
+
* button that re-expands it. `size` itself is left untouched while
|
|
59
|
+
* collapsed, so re-expanding restores exactly what it was.
|
|
60
|
+
*
|
|
61
|
+
* @customElement lyra-dock-panel
|
|
62
|
+
* @slot - The panel's own content.
|
|
63
|
+
* @event lyra-resize - `detail: { size }` (a `px` CSS length string), fired on every drag step,
|
|
64
|
+
* drag release, and keyboard step.
|
|
65
|
+
* @event lyra-collapse-change - `detail: { collapsed }`, fired whenever the collapse toggle changes
|
|
66
|
+
* `collapsed`.
|
|
67
|
+
* @csspart base - The panel root.
|
|
68
|
+
* @csspart content - The wrapper around the default slot; hidden while `collapsed`.
|
|
69
|
+
* @csspart handle - The draggable resize handle on the panel's inner edge. Only rendered when
|
|
70
|
+
* `resizable` and not `collapsed`.
|
|
71
|
+
* @csspart collapse-toggle - The collapse/expand toggle button. Only rendered when `collapsible`.
|
|
72
|
+
*/
|
|
73
|
+
export declare class LyraDockPanel extends LyraElement {
|
|
74
|
+
static styles: import("lit").CSSResultGroup[];
|
|
75
|
+
edge: DockPanelEdge;
|
|
76
|
+
/** The current docked size along the resize axis, as a CSS length (e.g. `"320px"`). */
|
|
77
|
+
size: string;
|
|
78
|
+
/** Minimum resize bound, as a CSS length. */
|
|
79
|
+
minSize: string;
|
|
80
|
+
/** Maximum resize bound, as a CSS length. Empty means "no explicit cap" -- the live extent of
|
|
81
|
+
* the containing element is used instead, so the panel still can't be dragged wider/taller than
|
|
82
|
+
* its container. */
|
|
83
|
+
maxSize: string;
|
|
84
|
+
collapsible: boolean;
|
|
85
|
+
collapsed: boolean;
|
|
86
|
+
/** When `false`, no drag handle renders at all and the panel is a fixed size. */
|
|
87
|
+
resizable: boolean;
|
|
88
|
+
private drag;
|
|
89
|
+
private readonly contentId;
|
|
90
|
+
connectedCallback(): void;
|
|
91
|
+
disconnectedCallback(): void;
|
|
92
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
93
|
+
/** `'inline'` for the `start`/`end` edges (resizing changes `inline-size`), `'block'` for
|
|
94
|
+
* `top`/`bottom` (resizing changes `block-size`). */
|
|
95
|
+
private get axis();
|
|
96
|
+
/** +1 or -1: which physical pointer-movement/keyboard direction *grows* the panel, folding in
|
|
97
|
+
* both which edge is pinned (the opposite edge is what's dragged) and, for the inline axis
|
|
98
|
+
* only, the current RTL-ness -- mirrors lyra-split's own horizontal+RTL delta inversion, just
|
|
99
|
+
* generalized to four possible pinned edges instead of split's always-LTR-authored pair order. */
|
|
100
|
+
private get growSign();
|
|
101
|
+
private applyHostSize;
|
|
102
|
+
/** Live pixel size of the containing block along the resize axis, used both to resolve a `%`
|
|
103
|
+
* `min-size`/`max-size` and as the `max-size` fallback when unset. Falls back to the viewport
|
|
104
|
+
* when there's no parent element (e.g. not yet connected). */
|
|
105
|
+
private containerPx;
|
|
106
|
+
private resolveBoundsPx;
|
|
107
|
+
/** The panel's own current rendered size (px) along the resize axis, read straight off the
|
|
108
|
+
* live box -- this is what lets `size` be expressed in any CSS unit and still drag/step
|
|
109
|
+
* correctly from wherever it actually rendered, with no separate unit-conversion path for the
|
|
110
|
+
* "current" value (only `min-size`/`max-size`, which are never themselves rendered, need
|
|
111
|
+
* `parseLengthPx`). */
|
|
112
|
+
private currentSizePx;
|
|
113
|
+
private applySize;
|
|
114
|
+
private onPointerDown;
|
|
115
|
+
private onPointerMove;
|
|
116
|
+
private onPointerUp;
|
|
117
|
+
private onHandleKeyDown;
|
|
118
|
+
private toggleCollapsed;
|
|
119
|
+
/** Rotation (deg) for the collapse-toggle's chevron: it points toward the
|
|
120
|
+
* panel's pinned edge when expanded (that's the direction clicking it
|
|
121
|
+
* will shrink toward) and away from it when collapsed (the direction
|
|
122
|
+
* clicking it will grow toward) -- mirrors lyra-widget's collapse-button
|
|
123
|
+
* rotate-the-wrapping-part technique, generalized across four possible
|
|
124
|
+
* pinned edges and, for `start`/`end`, RTL. */
|
|
125
|
+
private get toggleChevronDeg();
|
|
126
|
+
private handleTemplate;
|
|
127
|
+
private collapseToggleTemplate;
|
|
128
|
+
render(): TemplateResult;
|
|
129
|
+
}
|
|
130
|
+
declare global {
|
|
131
|
+
interface HTMLElementTagNameMap {
|
|
132
|
+
'lyra-dock-panel': LyraDockPanel;
|
|
133
|
+
}
|
|
134
|
+
}
|