@aceshooting/lyra-ui 1.1.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 +12 -1
- package/custom-elements.json +21460 -8489
- 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 +12 -2
- package/dist/components/chart/chart.js +121 -14
- 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 +63 -0
- package/dist/components/chart/lite-chart.js +181 -23
- 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 +42 -5
- package/dist/components/flag/flag.js +70 -3
- 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 +89 -3
- package/dist/components/heatmap/heatmap.js +158 -29
- 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 +45 -1
- package/dist/components/select/select.js +114 -17
- package/dist/components/select/select.styles.js +26 -2
- 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 +22 -1
- package/dist/components/stat/stat.js +49 -3
- package/dist/components/stat/stat.styles.js +20 -0
- 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/widget/widget.d.ts +10 -0
- package/dist/components/widget/widget.js +24 -1
- package/dist/components/widget/widget.styles.js +9 -2
- 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 +4 -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 +3921 -32
- 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 -12
- package/dist/components/chart/lite-chart.stories.js +0 -98
- 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 -5
- package/dist/components/flag/flag.stories.js +0 -17
- 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 -22
- package/dist/components/heatmap/heatmap.stories.js +0 -154
- 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 -19
- package/dist/components/select/select.stories.js +0 -75
- 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 -8
- package/dist/components/stat/stat.stories.js +0 -64
- 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 -7
- package/dist/components/widget/widget.stories.js +0 -41
- package/dist/components/word-cloud/word-cloud.stories.d.ts +0 -10
- package/dist/components/word-cloud/word-cloud.stories.js +0 -55
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { html } from 'lit';
|
|
7
|
+
import { html, nothing } from 'lit';
|
|
8
8
|
import { property, state } from 'lit/decorators.js';
|
|
9
9
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
10
|
import { FormAssociated } from '../../internal/form-associated.js';
|
|
@@ -16,6 +16,26 @@ import { parseISO, formatISO } from './calendar-core.js';
|
|
|
16
16
|
import { styles } from './date-input.styles.js';
|
|
17
17
|
import { LyraDatePicker } from './date-picker.js';
|
|
18
18
|
import './date-picker.js';
|
|
19
|
+
/** Determines the locale's day/month/year field order from a real formatted
|
|
20
|
+
* sample (Jan 2, 2026 -- a date where day/month/year are all numerically
|
|
21
|
+
* distinguishable), instead of relying on Date.parse()'s implementation-defined
|
|
22
|
+
* (commonly mm/dd/yyyy-biased) heuristics for an ambiguous separated date. */
|
|
23
|
+
function localeDateOrder(locale) {
|
|
24
|
+
try {
|
|
25
|
+
const parts = new Intl.DateTimeFormat(locale || undefined, {
|
|
26
|
+
year: 'numeric',
|
|
27
|
+
month: '2-digit',
|
|
28
|
+
day: '2-digit',
|
|
29
|
+
}).formatToParts(new Date(2026, 0, 2));
|
|
30
|
+
const order = parts
|
|
31
|
+
.filter((p) => p.type === 'day' || p.type === 'month' || p.type === 'year')
|
|
32
|
+
.map((p) => p.type);
|
|
33
|
+
return order.length === 3 ? order : ['month', 'day', 'year'];
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return ['month', 'day', 'year']; // Date.parse()'s own bias, as a last-resort fallback
|
|
37
|
+
}
|
|
38
|
+
}
|
|
19
39
|
/**
|
|
20
40
|
* `<lyra-date-input>` — a date field with an attached calendar popover.
|
|
21
41
|
* Mirrors the core `<wa-date-input>` API under `lyra-`. Value is ISO 8601
|
|
@@ -84,24 +104,18 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
84
104
|
this.emit('change');
|
|
85
105
|
return;
|
|
86
106
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// ISO-shaped string would silently resurrect the same rollover parseISO
|
|
91
|
-
// guards against (Date.parse('2026-02-30') also normalizes to March 2).
|
|
92
|
-
const looksLikeISO = /^\d{4}-\d{2}-\d{2}$/.test(raw);
|
|
93
|
-
const parsed = looksLikeISO ? parseISO(raw) : isNaN(Date.parse(raw)) ? null : new Date(Date.parse(raw));
|
|
94
|
-
if (parsed) {
|
|
95
|
-
this.value = formatISO(parsed);
|
|
107
|
+
const parsed = this.mode === 'range' ? this.parseRangeText(raw) : this.parseSingleText(raw);
|
|
108
|
+
if (parsed && this.isWithinBounds(parsed)) {
|
|
109
|
+
this.value = parsed;
|
|
96
110
|
this.emit('input');
|
|
97
111
|
this.emit('change');
|
|
98
112
|
}
|
|
99
113
|
else {
|
|
100
|
-
// Unparseable text: don't silently keep the
|
|
101
|
-
// field still shows garbage -- revert the
|
|
102
|
-
// last valid commit and flag the
|
|
103
|
-
// how `required` is already
|
|
104
|
-
// form-associated.ts).
|
|
114
|
+
// Unparseable (or out-of-bounds) text: don't silently keep the
|
|
115
|
+
// committed value while the field still shows garbage -- revert the
|
|
116
|
+
// displayed text back to the last valid commit and flag the
|
|
117
|
+
// constraint-validation state (mirrors how `required` is already
|
|
118
|
+
// wired via internals.setValidity(), see form-associated.ts).
|
|
105
119
|
target.value = this.displayText;
|
|
106
120
|
this.internals.setValidity({ badInput: true }, 'Enter a valid date');
|
|
107
121
|
}
|
|
@@ -175,7 +189,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
175
189
|
}
|
|
176
190
|
/** Open the calendar popover. */
|
|
177
191
|
show() {
|
|
178
|
-
if (this.open || this.
|
|
192
|
+
if (this.open || this.effectiveDisabled || this.readonly)
|
|
179
193
|
return;
|
|
180
194
|
this.open = true;
|
|
181
195
|
this.emit('lyra-show');
|
|
@@ -199,7 +213,14 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
199
213
|
disconnectedCallback() {
|
|
200
214
|
super.disconnectedCallback();
|
|
201
215
|
this.cleanupFn?.();
|
|
216
|
+
this.cleanupFn = undefined;
|
|
202
217
|
document.removeEventListener('pointerdown', this.onDocPointer);
|
|
218
|
+
// Reset so a reconnect (e.g. a drag-drop reparent) re-triggers
|
|
219
|
+
// `updated()`'s `open`-driven branch -- without this, `open` stays
|
|
220
|
+
// stuck `true` across the disconnect, `updated()` never sees it
|
|
221
|
+
// *change*, and `place()` never gets called again to re-bind
|
|
222
|
+
// positioning to the (possibly relocated) anchor.
|
|
223
|
+
this.open = false;
|
|
203
224
|
}
|
|
204
225
|
updated(changed) {
|
|
205
226
|
super.updated(changed);
|
|
@@ -217,11 +238,100 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
217
238
|
this.toggleAttribute('data-invalid', this.touched && !this.internals.validity.valid);
|
|
218
239
|
}
|
|
219
240
|
}
|
|
241
|
+
/** Parses one date, ISO-first (so a calendar-invalid ISO string like
|
|
242
|
+
* "2026-02-30" is rejected rather than silently rolled over by Date.parse()).
|
|
243
|
+
* A 3-part, ambiguous slash/dot/dash-separated date (e.g. "15/07/2026") is
|
|
244
|
+
* parsed according to the locale's own day/month/year order (via a real
|
|
245
|
+
* Intl.DateTimeFormat sample, see localeDateOrder()) rather than
|
|
246
|
+
* Date.parse()'s implementation-defined heuristics -- this is what prevents
|
|
247
|
+
* e.g. an en-GB "15/07/2026" from being silently misread as some other day.
|
|
248
|
+
* A 4-digit first group (e.g. "2026-7-15") is never locale-guessed, though:
|
|
249
|
+
* it's unambiguously a year regardless of locale/separator (ISO's own
|
|
250
|
+
* year-first convention, just without zero-padding), so it's routed
|
|
251
|
+
* straight through parseISO() instead -- this is what lets a non-padded
|
|
252
|
+
* ISO-ish date keep parsing correctly (it did via Date.parse() before the
|
|
253
|
+
* ambiguous-date regex below existed at all).
|
|
254
|
+
* Anything else (e.g. "July 15, 2026") still falls through to Date.parse(). */
|
|
255
|
+
parseOneDate(raw) {
|
|
256
|
+
const looksLikeISO = /^\d{4}-\d{2}-\d{2}$/.test(raw);
|
|
257
|
+
if (looksLikeISO)
|
|
258
|
+
return parseISO(raw);
|
|
259
|
+
const ambiguous = /^(\d{1,4})[/.-](\d{1,4})[/.-](\d{1,4})$/.exec(raw);
|
|
260
|
+
if (ambiguous) {
|
|
261
|
+
const [, g1, g2, g3] = ambiguous;
|
|
262
|
+
if (g1.length === 4) {
|
|
263
|
+
return parseISO(`${g1}-${g2.padStart(2, '0')}-${g3.padStart(2, '0')}`);
|
|
264
|
+
}
|
|
265
|
+
const order = localeDateOrder(this.locale);
|
|
266
|
+
const values = [Number(g1), Number(g2), Number(g3)];
|
|
267
|
+
const fields = {};
|
|
268
|
+
order.forEach((type, i) => {
|
|
269
|
+
fields[type] = values[i];
|
|
270
|
+
});
|
|
271
|
+
if (fields.day != null && fields.month != null && fields.year != null) {
|
|
272
|
+
const year = fields.year < 100 ? 2000 + fields.year : fields.year;
|
|
273
|
+
return parseISO(`${String(year).padStart(4, '0')}-${String(fields.month).padStart(2, '0')}-${String(fields.day).padStart(2, '0')}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return isNaN(Date.parse(raw)) ? null : new Date(Date.parse(raw));
|
|
277
|
+
}
|
|
278
|
+
parseSingleText(raw) {
|
|
279
|
+
const parsed = this.parseOneDate(raw);
|
|
280
|
+
return parsed ? formatISO(parsed) : null;
|
|
281
|
+
}
|
|
282
|
+
/** Only round-trips the exact `displayText` shape this component itself
|
|
283
|
+
* renders (`"<locale from> – <locale to>"`) — a raw ISO range typed
|
|
284
|
+
* directly (`"2026-05-01/2026-05-15"`) is also accepted as a convenience.
|
|
285
|
+
* A reversed typed range (`to` before `from`) is normalized into
|
|
286
|
+
* from-before-to order, matching what the date-picker's own UI-driven
|
|
287
|
+
* `commit()` already does for a UI-picked range. */
|
|
288
|
+
parseRangeText(raw) {
|
|
289
|
+
if (/^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/.test(raw)) {
|
|
290
|
+
const [a, b] = raw.split('/');
|
|
291
|
+
const from = parseISO(a);
|
|
292
|
+
const to = parseISO(b);
|
|
293
|
+
if (!from || !to)
|
|
294
|
+
return null;
|
|
295
|
+
return from <= to ? raw : `${b}/${a}`;
|
|
296
|
+
}
|
|
297
|
+
const separator = ' – '; // matches displayText's ` – ` join exactly
|
|
298
|
+
const idx = raw.indexOf(separator);
|
|
299
|
+
if (idx === -1)
|
|
300
|
+
return null;
|
|
301
|
+
const from = this.parseOneDate(raw.slice(0, idx).trim());
|
|
302
|
+
const to = this.parseOneDate(raw.slice(idx + separator.length).trim());
|
|
303
|
+
if (!from || !to)
|
|
304
|
+
return null;
|
|
305
|
+
return from <= to ? `${formatISO(from)}/${formatISO(to)}` : `${formatISO(to)}/${formatISO(from)}`;
|
|
306
|
+
}
|
|
307
|
+
isWithinBounds(isoValue) {
|
|
308
|
+
const min = parseISO(this.min);
|
|
309
|
+
const max = parseISO(this.max);
|
|
310
|
+
const today = new Date();
|
|
311
|
+
today.setHours(0, 0, 0, 0);
|
|
312
|
+
return isoValue.split('/').every((part) => {
|
|
313
|
+
const d = parseISO(part);
|
|
314
|
+
if (!d)
|
|
315
|
+
return false;
|
|
316
|
+
if (min && d < min)
|
|
317
|
+
return false;
|
|
318
|
+
if (max && d > max)
|
|
319
|
+
return false;
|
|
320
|
+
if (this.disablePast && d < today)
|
|
321
|
+
return false;
|
|
322
|
+
if (this.disableFuture && d > today)
|
|
323
|
+
return false;
|
|
324
|
+
return true;
|
|
325
|
+
});
|
|
326
|
+
}
|
|
220
327
|
render() {
|
|
221
328
|
const hasValue = this.value.length > 0;
|
|
222
329
|
const hasHint = this.hasHintSlot || this.hint.length > 0;
|
|
223
330
|
const hasError = this.hasErrorSlot || this.errorText.length > 0;
|
|
224
331
|
const hasLabel = this.hasLabelSlot || this.label.length > 0;
|
|
332
|
+
const describedBy = [hasError ? 'date-input-error' : '', hasHint ? 'date-input-hint' : '']
|
|
333
|
+
.filter(Boolean)
|
|
334
|
+
.join(' ');
|
|
225
335
|
return html `
|
|
226
336
|
<div part="form-control" @keydown=${this.onFormControlKey}>
|
|
227
337
|
<label part="form-control-label" for=${this.inputId} ?hidden=${!hasLabel}>
|
|
@@ -232,10 +342,11 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
232
342
|
id=${this.inputId}
|
|
233
343
|
part="input"
|
|
234
344
|
type="text"
|
|
235
|
-
aria-label=${
|
|
345
|
+
aria-label=${hasLabel ? nothing : this.placeholder || 'Date'}
|
|
346
|
+
aria-describedby=${describedBy || nothing}
|
|
236
347
|
.value=${this.displayText}
|
|
237
348
|
placeholder=${this.placeholder}
|
|
238
|
-
?disabled=${this.
|
|
349
|
+
?disabled=${this.effectiveDisabled}
|
|
239
350
|
?readonly=${this.readonly}
|
|
240
351
|
@change=${this.onInputChange}
|
|
241
352
|
@keydown=${this.onInputKey}
|
|
@@ -245,6 +356,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
245
356
|
? html `<button
|
|
246
357
|
part="clear-button"
|
|
247
358
|
type="button"
|
|
359
|
+
?disabled=${this.effectiveDisabled || this.readonly}
|
|
248
360
|
aria-label="Clear"
|
|
249
361
|
@click=${() => this.clear()}
|
|
250
362
|
>
|
|
@@ -258,7 +370,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
258
370
|
aria-haspopup="dialog"
|
|
259
371
|
aria-expanded=${this.open ? 'true' : 'false'}
|
|
260
372
|
aria-controls=${this.popupId}
|
|
261
|
-
?disabled=${this.
|
|
373
|
+
?disabled=${this.effectiveDisabled || this.readonly}
|
|
262
374
|
@click=${() => (this.open ? this.hide() : this.show())}
|
|
263
375
|
>
|
|
264
376
|
<span part="expand-icon" aria-hidden="true">${calendarIcon()}</span>
|
|
@@ -273,7 +385,7 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
273
385
|
.max=${this.max}
|
|
274
386
|
.months=${this.months}
|
|
275
387
|
.locale=${this.locale}
|
|
276
|
-
.disabled=${this.
|
|
388
|
+
.disabled=${this.effectiveDisabled}
|
|
277
389
|
.readonly=${this.readonly}
|
|
278
390
|
.disablePast=${this.disablePast}
|
|
279
391
|
.disableFuture=${this.disableFuture}
|
|
@@ -284,10 +396,10 @@ export class LyraDateInput extends FormAssociated(LyraElement) {
|
|
|
284
396
|
@change=${this.onPickerChange}
|
|
285
397
|
></lyra-date-picker>
|
|
286
398
|
</div>
|
|
287
|
-
<div part="error" ?hidden=${!hasError}>
|
|
399
|
+
<div id="date-input-error" part="error" ?hidden=${!hasError}>
|
|
288
400
|
${this.errorText}<slot name="error" @slotchange=${this.onErrorSlotChange}></slot>
|
|
289
401
|
</div>
|
|
290
|
-
<div part="hint" ?hidden=${!hasHint}>
|
|
402
|
+
<div id="date-input-hint" part="hint" ?hidden=${!hasHint}>
|
|
291
403
|
${this.hint}<slot name="hint" @slotchange=${this.onHintSlotChange}></slot>
|
|
292
404
|
</div>
|
|
293
405
|
</div>
|
|
@@ -43,6 +43,13 @@ export declare class LyraDatePicker extends LyraElement {
|
|
|
43
43
|
protected willUpdate(changed: PropertyValues): void;
|
|
44
44
|
private get fdow();
|
|
45
45
|
private isDisabled;
|
|
46
|
+
/** The first enabled day at/after the first visible month's start, scanning
|
|
47
|
+
* forward across all visible months -- used as the sole focusable day when
|
|
48
|
+
* there's no selection and no prior keyboard focus, so the empty-grid case
|
|
49
|
+
* never defaults to a possibly-disabled day 1. Returns null if every
|
|
50
|
+
* visible day is disabled (pathological but possible with a very narrow
|
|
51
|
+
* min/max window). */
|
|
52
|
+
private firstEnabledDate;
|
|
46
53
|
private commit;
|
|
47
54
|
private selectDate;
|
|
48
55
|
/** Clear the selection and emit input + change. */
|
|
@@ -50,32 +50,54 @@ export class LyraDatePicker extends LyraElement {
|
|
|
50
50
|
// `selection.from`'s month -- only an externally-set `value` should do that.
|
|
51
51
|
this.suppressViewSync = false;
|
|
52
52
|
this.onGridKey = (e) => {
|
|
53
|
-
const
|
|
53
|
+
const min = parseISO(this.min);
|
|
54
|
+
const max = parseISO(this.max);
|
|
55
|
+
const today = new Date();
|
|
56
|
+
today.setHours(0, 0, 0, 0);
|
|
57
|
+
const current = this.focusedDate ??
|
|
58
|
+
this.selection.from ??
|
|
59
|
+
this.firstEnabledDate(min, max, today) ??
|
|
60
|
+
new Date(this.viewDate.getFullYear(), this.viewDate.getMonth(), 1);
|
|
61
|
+
const step = (base, days) => new Date(base.getFullYear(), base.getMonth(), base.getDate() + days);
|
|
62
|
+
// Walks in the requested direction until an enabled day is found, bounded
|
|
63
|
+
// by a step cap (366 days -- more than a year) so an all-disabled range
|
|
64
|
+
// (e.g. min/max entirely in the past with disable-past set) can't loop
|
|
65
|
+
// forever; returns null rather than landing focus on a disabled cell,
|
|
66
|
+
// whose `.focus()` would be a silent no-op anyway.
|
|
67
|
+
const firstEnabledFrom = (base, dayStep) => {
|
|
68
|
+
let d = base;
|
|
69
|
+
for (let i = 0; i < 366; i++) {
|
|
70
|
+
if (!this.isDisabled(d, min, max, today))
|
|
71
|
+
return d;
|
|
72
|
+
d = step(d, dayStep);
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
54
76
|
let next = null;
|
|
55
77
|
switch (e.key) {
|
|
56
78
|
case 'ArrowLeft':
|
|
57
|
-
next =
|
|
79
|
+
next = firstEnabledFrom(step(current, -1), -1);
|
|
58
80
|
break;
|
|
59
81
|
case 'ArrowRight':
|
|
60
|
-
next =
|
|
82
|
+
next = firstEnabledFrom(step(current, 1), 1);
|
|
61
83
|
break;
|
|
62
84
|
case 'ArrowUp':
|
|
63
|
-
next =
|
|
85
|
+
next = firstEnabledFrom(step(current, -7), -1);
|
|
64
86
|
break;
|
|
65
87
|
case 'ArrowDown':
|
|
66
|
-
next =
|
|
88
|
+
next = firstEnabledFrom(step(current, 7), 1);
|
|
67
89
|
break;
|
|
68
90
|
case 'PageUp':
|
|
69
|
-
next = new Date(current.getFullYear(), current.getMonth() - 1, current.getDate());
|
|
91
|
+
next = firstEnabledFrom(new Date(current.getFullYear(), current.getMonth() - 1, current.getDate()), 1);
|
|
70
92
|
break;
|
|
71
93
|
case 'PageDown':
|
|
72
|
-
next = new Date(current.getFullYear(), current.getMonth() + 1, current.getDate());
|
|
94
|
+
next = firstEnabledFrom(new Date(current.getFullYear(), current.getMonth() + 1, current.getDate()), 1);
|
|
73
95
|
break;
|
|
74
96
|
case 'Home':
|
|
75
|
-
next = new Date(current.getFullYear(), current.getMonth(), 1);
|
|
97
|
+
next = firstEnabledFrom(new Date(current.getFullYear(), current.getMonth(), 1), 1);
|
|
76
98
|
break;
|
|
77
99
|
case 'End':
|
|
78
|
-
next = new Date(current.getFullYear(), current.getMonth() + 1, 0);
|
|
100
|
+
next = firstEnabledFrom(new Date(current.getFullYear(), current.getMonth() + 1, 0), -1);
|
|
79
101
|
break;
|
|
80
102
|
case 'Enter':
|
|
81
103
|
case ' ':
|
|
@@ -86,6 +108,8 @@ export class LyraDatePicker extends LyraElement {
|
|
|
86
108
|
return;
|
|
87
109
|
}
|
|
88
110
|
e.preventDefault();
|
|
111
|
+
if (!next)
|
|
112
|
+
return;
|
|
89
113
|
this.focusedDate = next;
|
|
90
114
|
this.viewDate = this.viewDateForFocus(next);
|
|
91
115
|
this.focusPending = true;
|
|
@@ -127,6 +151,23 @@ export class LyraDatePicker extends LyraElement {
|
|
|
127
151
|
return true;
|
|
128
152
|
return false;
|
|
129
153
|
}
|
|
154
|
+
/** The first enabled day at/after the first visible month's start, scanning
|
|
155
|
+
* forward across all visible months -- used as the sole focusable day when
|
|
156
|
+
* there's no selection and no prior keyboard focus, so the empty-grid case
|
|
157
|
+
* never defaults to a possibly-disabled day 1. Returns null if every
|
|
158
|
+
* visible day is disabled (pathological but possible with a very narrow
|
|
159
|
+
* min/max window). */
|
|
160
|
+
firstEnabledDate(min, max, today) {
|
|
161
|
+
let d = new Date(this.viewDate.getFullYear(), this.viewDate.getMonth(), 1);
|
|
162
|
+
const lastVisibleMonth = addMonths(d, this.months - 1);
|
|
163
|
+
const boundEnd = new Date(lastVisibleMonth.getFullYear(), lastVisibleMonth.getMonth() + 1, 0);
|
|
164
|
+
for (let i = 0; i < 732 && d <= boundEnd; i++) {
|
|
165
|
+
if (!this.isDisabled(d, min, max, today))
|
|
166
|
+
return d;
|
|
167
|
+
d = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1);
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
130
171
|
commit(from, to, fire) {
|
|
131
172
|
const next = this.mode === 'range'
|
|
132
173
|
? from && to
|
|
@@ -217,7 +258,7 @@ export class LyraDatePicker extends LyraElement {
|
|
|
217
258
|
cell?.focus();
|
|
218
259
|
}
|
|
219
260
|
}
|
|
220
|
-
renderDay(date, shownMonth, selection, min, max, today, rowHasVisibleDay) {
|
|
261
|
+
renderDay(date, shownMonth, selection, min, max, today, rowHasVisibleDay, fallbackFocusDate) {
|
|
221
262
|
const outside = date.getMonth() !== shownMonth;
|
|
222
263
|
// Outside-month days are empty placeholders by default (matches WA), keeping the
|
|
223
264
|
// 6-row grid aligned without low-contrast faded numbers.
|
|
@@ -240,7 +281,19 @@ export class LyraDatePicker extends LyraElement {
|
|
|
240
281
|
const isEnd = to && isSameDay(date, to);
|
|
241
282
|
const selected = this.mode === 'single' ? isStart : isStart || isEnd;
|
|
242
283
|
const inRange = this.mode === 'range' && from && to && date > from && date < to;
|
|
243
|
-
|
|
284
|
+
// Falls back to the first *enabled* day across the visible month(s) (see
|
|
285
|
+
// firstEnabledDate()) when there's neither a focusedDate nor a selection,
|
|
286
|
+
// so the grid always has exactly one tabbable cell -- otherwise every
|
|
287
|
+
// cell computes `false` and keyboard users tabbing into an empty picker
|
|
288
|
+
// land nowhere. Must not just be "day 1 of the shown month" unconditionally:
|
|
289
|
+
// day 1 can itself be disabled (e.g. disable-past opened on any day other
|
|
290
|
+
// than the 1st), which would land the sole tabindex="0" cell on a button
|
|
291
|
+
// that can never actually receive focus.
|
|
292
|
+
const focused = this.focusedDate != null
|
|
293
|
+
? isSameDay(date, this.focusedDate)
|
|
294
|
+
: isStart
|
|
295
|
+
? true
|
|
296
|
+
: !from && fallbackFocusDate != null && isSameDay(date, fallbackFocusDate);
|
|
244
297
|
const parts = ['day'];
|
|
245
298
|
if (outside)
|
|
246
299
|
parts.push('day-outside');
|
|
@@ -272,7 +325,7 @@ export class LyraDatePicker extends LyraElement {
|
|
|
272
325
|
${date.getDate()}
|
|
273
326
|
</button>`;
|
|
274
327
|
}
|
|
275
|
-
renderMonth(offset, selection, min, max, today, fdow, labels) {
|
|
328
|
+
renderMonth(offset, selection, min, max, today, fdow, labels, fallbackFocusDate) {
|
|
276
329
|
const base = addMonths(this.viewDate, offset);
|
|
277
330
|
const year = base.getFullYear();
|
|
278
331
|
const month = base.getMonth();
|
|
@@ -282,13 +335,25 @@ export class LyraDatePicker extends LyraElement {
|
|
|
282
335
|
return html `<div part="month">
|
|
283
336
|
<div part="header">
|
|
284
337
|
${isFirst
|
|
285
|
-
? html `<button
|
|
338
|
+
? html `<button
|
|
339
|
+
part="previous"
|
|
340
|
+
type="button"
|
|
341
|
+
aria-label="Previous month"
|
|
342
|
+
?disabled=${this.disabled || this.readonly}
|
|
343
|
+
@click=${() => this.nav(-1)}
|
|
344
|
+
>
|
|
286
345
|
${chevronIcon()}
|
|
287
346
|
</button>`
|
|
288
347
|
: html `<span></span>`}
|
|
289
348
|
<div part="title">${monthTitle(year, month, this.locale)}</div>
|
|
290
349
|
${isLast
|
|
291
|
-
? html `<button
|
|
350
|
+
? html `<button
|
|
351
|
+
part="next"
|
|
352
|
+
type="button"
|
|
353
|
+
aria-label="Next month"
|
|
354
|
+
?disabled=${this.disabled || this.readonly}
|
|
355
|
+
@click=${() => this.nav(1)}
|
|
356
|
+
>
|
|
292
357
|
${chevronIcon()}
|
|
293
358
|
</button>`
|
|
294
359
|
: html `<span></span>`}
|
|
@@ -297,7 +362,7 @@ export class LyraDatePicker extends LyraElement {
|
|
|
297
362
|
<div part="grid" role="grid" @keydown=${this.onGridKey}>
|
|
298
363
|
${matrix.map((week) => {
|
|
299
364
|
const rowHasVisibleDay = week.some((d) => d.getMonth() === month);
|
|
300
|
-
return html `<div part="week" role="row">${week.map((d) => this.renderDay(d, month, selection, min, max, today, rowHasVisibleDay))}</div>`;
|
|
365
|
+
return html `<div part="week" role="row">${week.map((d) => this.renderDay(d, month, selection, min, max, today, rowHasVisibleDay, fallbackFocusDate))}</div>`;
|
|
301
366
|
})}
|
|
302
367
|
</div>
|
|
303
368
|
</div>`;
|
|
@@ -310,9 +375,13 @@ export class LyraDatePicker extends LyraElement {
|
|
|
310
375
|
today.setHours(0, 0, 0, 0);
|
|
311
376
|
const fdow = this.fdow;
|
|
312
377
|
const labels = weekdayLabels(fdow, this.weekdayFormat, this.locale);
|
|
378
|
+
// Only bother scanning for a fallback focus day when it's actually
|
|
379
|
+
// needed: there's no point walking the visible days if a focusedDate or
|
|
380
|
+
// an existing selection already determines the sole tabbable cell.
|
|
381
|
+
const fallbackFocusDate = this.focusedDate || selection.from ? null : this.firstEnabledDate(min, max, today);
|
|
313
382
|
const monthEls = [];
|
|
314
383
|
for (let i = 0; i < this.months; i++) {
|
|
315
|
-
monthEls.push(this.renderMonth(i, selection, min, max, today, fdow, labels));
|
|
384
|
+
monthEls.push(this.renderMonth(i, selection, min, max, today, fdow, labels, fallbackFocusDate));
|
|
316
385
|
}
|
|
317
386
|
return html `<div part="base">${monthEls}</div>`;
|
|
318
387
|
}
|
|
@@ -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
|
+
}
|