@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
|
@@ -0,0 +1,214 @@
|
|
|
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 { styles } from './kbd.styles.js';
|
|
12
|
+
// A deliberately small map — just the modifier-adjacent/navigation keys
|
|
13
|
+
// common enough in real shortcuts to be worth a friendly glyph. Anything not
|
|
14
|
+
// listed here (e.g. 'f1', 'k', a punctuation key) falls through to the
|
|
15
|
+
// generic "render as typed, upper-case single letters" rule in
|
|
16
|
+
// `shortcutTokenLabel` below, per this component's spec.
|
|
17
|
+
//
|
|
18
|
+
// 'plus'/'minus' exist specifically so a shortcut that includes a literal
|
|
19
|
+
// "+" or "-" key (e.g. the classic zoom-in shortcut) has a way to say so:
|
|
20
|
+
// the '+'-separated `keys` grammar can't itself carry a literal "+" token
|
|
21
|
+
// (it's the delimiter), so callers spell it as the word instead.
|
|
22
|
+
const NAMED_KEY_LABELS = {
|
|
23
|
+
enter: { visual: '↵', word: 'Enter' },
|
|
24
|
+
esc: { visual: 'Esc', word: 'Escape' },
|
|
25
|
+
escape: { visual: 'Esc', word: 'Escape' },
|
|
26
|
+
tab: { visual: 'Tab', word: 'Tab' },
|
|
27
|
+
space: { visual: 'Space', word: 'Space' },
|
|
28
|
+
backspace: { visual: '⌫', word: 'Backspace' },
|
|
29
|
+
delete: { visual: 'Del', word: 'Delete' },
|
|
30
|
+
home: { visual: 'Home', word: 'Home' },
|
|
31
|
+
end: { visual: 'End', word: 'End' },
|
|
32
|
+
pageup: { visual: 'PgUp', word: 'Page Up' },
|
|
33
|
+
pagedown: { visual: 'PgDn', word: 'Page Down' },
|
|
34
|
+
arrowup: { visual: '↑', word: 'Arrow Up' },
|
|
35
|
+
arrowdown: { visual: '↓', word: 'Arrow Down' },
|
|
36
|
+
arrowleft: { visual: '←', word: 'Arrow Left' },
|
|
37
|
+
arrowright: { visual: '→', word: 'Arrow Right' },
|
|
38
|
+
plus: { visual: '+', word: 'Plus' },
|
|
39
|
+
minus: { visual: '−', word: 'Minus' },
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Resolves one `+`-separated token of a `keys` string to its rendered glyph
|
|
43
|
+
* and spelled-out word, given whether the shortcut is being shown on macOS.
|
|
44
|
+
*
|
|
45
|
+
* Exported as a pure function (parameterized on `isMac` rather than reading
|
|
46
|
+
* the module-scope platform constant directly) so both the macOS and
|
|
47
|
+
* non-macOS branches are directly unit-testable without having to spoof
|
|
48
|
+
* `navigator` in a browser test environment that only ever runs as one
|
|
49
|
+
* platform.
|
|
50
|
+
*/
|
|
51
|
+
export function shortcutTokenLabel(rawToken, isMac) {
|
|
52
|
+
const token = rawToken.trim();
|
|
53
|
+
const lower = token.toLowerCase();
|
|
54
|
+
// 'mod' is the platform-neutral primary modifier: Command on macOS,
|
|
55
|
+
// Control everywhere else. 'ctrl' is deliberately distinct — it always
|
|
56
|
+
// means the literal Control key, even on macOS, for shortcuts that are
|
|
57
|
+
// specifically Ctrl-based (e.g. terminal Ctrl+C) rather than
|
|
58
|
+
// platform-adapted.
|
|
59
|
+
if (lower === 'mod')
|
|
60
|
+
return isMac ? { visual: '⌘', word: 'Command' } : { visual: 'Ctrl', word: 'Control' };
|
|
61
|
+
if (lower === 'ctrl' || lower === 'control')
|
|
62
|
+
return { visual: 'Ctrl', word: 'Control' };
|
|
63
|
+
if (lower === 'alt')
|
|
64
|
+
return isMac ? { visual: '⌥', word: 'Option' } : { visual: 'Alt', word: 'Alt' };
|
|
65
|
+
if (lower === 'shift')
|
|
66
|
+
return { visual: '⇧', word: 'Shift' };
|
|
67
|
+
const named = NAMED_KEY_LABELS[lower];
|
|
68
|
+
if (named)
|
|
69
|
+
return named;
|
|
70
|
+
// Anything else renders as typed (preserving the caller's own casing),
|
|
71
|
+
// except a bare single letter/digit, which is upper-cased for a
|
|
72
|
+
// consistent key-cap look ('k' and 'K' both render 'K').
|
|
73
|
+
if (token.length === 1) {
|
|
74
|
+
const upper = token.toUpperCase();
|
|
75
|
+
return { visual: upper, word: upper };
|
|
76
|
+
}
|
|
77
|
+
return { visual: token, word: token };
|
|
78
|
+
}
|
|
79
|
+
/** Splits a `keys` string (e.g. `'mod+shift+p'`) into its resolved token
|
|
80
|
+
* labels, dropping empty segments from stray/leading/trailing `+`s. */
|
|
81
|
+
export function parseShortcut(keys, isMac) {
|
|
82
|
+
return keys
|
|
83
|
+
.split('+')
|
|
84
|
+
.map((t) => t.trim())
|
|
85
|
+
.filter((t) => t.length > 0)
|
|
86
|
+
.map((t) => shortcutTokenLabel(t, isMac));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Detects macOS to decide whether `mod`/`alt` render their Mac glyphs (⌘/⌥)
|
|
90
|
+
* or their generic-platform text (Ctrl/Alt). Prefers the modern
|
|
91
|
+
* `navigator.userAgentData` (Client Hints) API when the browser exposes it,
|
|
92
|
+
* falling back through `navigator.platform` and finally a `navigator.userAgent`
|
|
93
|
+
* substring check — all three are deprecated/non-standard to varying
|
|
94
|
+
* degrees (`userAgentData` is Chromium-only so far; `platform` is
|
|
95
|
+
* long-deprecated; `userAgent` sniffing is a last resort) but remain, in
|
|
96
|
+
* combination, the practical cross-browser way to answer "is this macOS"
|
|
97
|
+
* today with no dependency.
|
|
98
|
+
*/
|
|
99
|
+
function detectIsMac() {
|
|
100
|
+
if (typeof navigator === 'undefined')
|
|
101
|
+
return false;
|
|
102
|
+
const uaData = navigator.userAgentData;
|
|
103
|
+
if (uaData?.platform)
|
|
104
|
+
return uaData.platform.toLowerCase().includes('mac');
|
|
105
|
+
if (navigator.platform)
|
|
106
|
+
return /mac/i.test(navigator.platform);
|
|
107
|
+
return /mac/i.test(navigator.userAgent ?? '');
|
|
108
|
+
}
|
|
109
|
+
// Computed once at module scope, not per-instance/per-render — a page's
|
|
110
|
+
// platform never changes mid-session, so there is nothing to gain (and a
|
|
111
|
+
// little cost, however small) from re-detecting it on every <lyra-kbd>
|
|
112
|
+
// instance or every re-render.
|
|
113
|
+
const IS_MAC = detectIsMac();
|
|
114
|
+
/**
|
|
115
|
+
* `<lyra-kbd>` — a small chip representing a keyboard shortcut, rendering
|
|
116
|
+
* the platform-appropriate glyph for cross-platform modifier keys (⌘ on
|
|
117
|
+
* macOS, "Ctrl" elsewhere) from a single platform-neutral `keys` string.
|
|
118
|
+
*
|
|
119
|
+
* `keys` is a `+`-separated sequence of tokens, e.g. `"mod+k"` or
|
|
120
|
+
* `"mod+shift+p"`. Recognized modifier tokens: `mod` (the platform-neutral
|
|
121
|
+
* primary modifier — ⌘ on macOS, "Ctrl" elsewhere), `alt` (⌥ / "Alt"),
|
|
122
|
+
* `shift` (⇧ on every platform), and `ctrl` (always the literal Control
|
|
123
|
+
* key, distinct from `mod`, for a shortcut that's specifically Ctrl even on
|
|
124
|
+
* macOS). Any other token renders as typed, except a bare single
|
|
125
|
+
* letter/digit which is upper-cased, with a small built-in map of friendly
|
|
126
|
+
* labels for common named keys (`enter` → `↵`, `esc` → "Esc", the four
|
|
127
|
+
* arrow keys → arrow glyphs, plus `tab`/`space`/`backspace`/`delete`/`home`/
|
|
128
|
+
* `end`/`pageup`/`pagedown`/`plus`/`minus`). `enter` renders as its `↵`
|
|
129
|
+
* glyph (not the word "Enter") to match the other single-glyph modifier/
|
|
130
|
+
* arrow keys visually — its spelled-out word form still appears in the
|
|
131
|
+
* computed `aria-label`.
|
|
132
|
+
*
|
|
133
|
+
* Each token renders as its own key cap (`part="key"`); consecutive caps
|
|
134
|
+
* are joined by a small "+" separator between them, matching how most
|
|
135
|
+
* cross-platform shortcut documentation (including on macOS, despite the
|
|
136
|
+
* OS's own native shortcut hints usually running the glyphs together with
|
|
137
|
+
* no separator) reads unambiguously regardless of how many/which glyphs are
|
|
138
|
+
* involved.
|
|
139
|
+
*
|
|
140
|
+
* The default slot is not used for the normal glyph rendering above — it's
|
|
141
|
+
* an escape hatch for fully custom content (e.g. an icon instead of a text
|
|
142
|
+
* glyph) that, when non-empty, replaces the `keys`-driven rendering
|
|
143
|
+
* entirely and stops this component from asserting its own `aria-label`,
|
|
144
|
+
* leaving the slotted content to carry its own accessible name.
|
|
145
|
+
*
|
|
146
|
+
* @customElement lyra-kbd
|
|
147
|
+
* @slot - Optional override for fully custom key-cap content, replacing the
|
|
148
|
+
* `keys`-driven rendering. Leave empty to use `keys`.
|
|
149
|
+
* @csspart base - The chip's root element.
|
|
150
|
+
* @csspart key - Each rendered key cap (one per token in `keys`).
|
|
151
|
+
*/
|
|
152
|
+
export class LyraKbd extends LyraElement {
|
|
153
|
+
constructor() {
|
|
154
|
+
super(...arguments);
|
|
155
|
+
/** A `+`-separated shortcut, e.g. `'mod+k'`. See the class doc for the
|
|
156
|
+
* full token grammar. */
|
|
157
|
+
this.keys = '';
|
|
158
|
+
// Real (non-whitespace) light-DOM content overrides the keys-driven
|
|
159
|
+
// rendering below — same "seed synchronously, refine on slotchange"
|
|
160
|
+
// pattern as lyra-checkbox's hasLabelSlot/lyra-citation-badge's
|
|
161
|
+
// hasPreviewSlot, so a declaratively-slotted override doesn't flash the
|
|
162
|
+
// keys rendering for one frame before the first slotchange event.
|
|
163
|
+
this.hasCustomContent = false;
|
|
164
|
+
this.onSlotChange = (e) => {
|
|
165
|
+
this.hasCustomContent = e.target.assignedNodes({ flatten: true }).length > 0;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
static { this.styles = [LyraElement.styles, styles]; }
|
|
169
|
+
willUpdate() {
|
|
170
|
+
if (!this.hasUpdated) {
|
|
171
|
+
this.hasCustomContent = Array.from(this.childNodes).some((n) => (n.textContent ?? '').trim().length > 0);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
render() {
|
|
175
|
+
const explicitLabel = this.getAttribute('aria-label');
|
|
176
|
+
if (this.hasCustomContent) {
|
|
177
|
+
return html `
|
|
178
|
+
<span part="base" aria-label=${explicitLabel || nothing}>
|
|
179
|
+
<slot @slotchange=${this.onSlotChange}></slot>
|
|
180
|
+
</span>
|
|
181
|
+
`;
|
|
182
|
+
}
|
|
183
|
+
const tokens = parseShortcut(this.keys, IS_MAC);
|
|
184
|
+
// role="img" treats the chip as one opaque unit (matching
|
|
185
|
+
// lyra-context-meter's/lyra-chart's canvas usage of the same pattern):
|
|
186
|
+
// the individual glyphs and "+" separators aren't real words, so
|
|
187
|
+
// exposing them as separate accessible-tree text would read worse than
|
|
188
|
+
// the single spelled-out aria-label below. An empty `keys` renders
|
|
189
|
+
// nothing visible, so it's marked aria-hidden instead of exposed as a
|
|
190
|
+
// nameless image.
|
|
191
|
+
const ariaLabel = explicitLabel || tokens.map((t) => t.word).join('+');
|
|
192
|
+
return html `
|
|
193
|
+
<span
|
|
194
|
+
part="base"
|
|
195
|
+
role=${tokens.length > 0 ? 'img' : nothing}
|
|
196
|
+
aria-hidden=${tokens.length === 0 && !explicitLabel ? 'true' : nothing}
|
|
197
|
+
aria-label=${ariaLabel || nothing}
|
|
198
|
+
>
|
|
199
|
+
${tokens.map((t, i) => html `
|
|
200
|
+
${i > 0 ? html `<span class="sep" aria-hidden="true">+</span>` : nothing}
|
|
201
|
+
<span part="key">${t.visual}</span>
|
|
202
|
+
`)}
|
|
203
|
+
<slot @slotchange=${this.onSlotChange} hidden></slot>
|
|
204
|
+
</span>
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
__decorate([
|
|
209
|
+
property()
|
|
210
|
+
], LyraKbd.prototype, "keys", void 0);
|
|
211
|
+
__decorate([
|
|
212
|
+
state()
|
|
213
|
+
], LyraKbd.prototype, "hasCustomContent", void 0);
|
|
214
|
+
defineElement('kbd', LyraKbd);
|
|
@@ -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
|
+
display: inline-flex;
|
|
5
|
+
max-inline-size: 100%;
|
|
6
|
+
vertical-align: middle;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
[part='base'] {
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
flex-wrap: wrap;
|
|
13
|
+
/* The body font, not a monospace stack — unlike lyra-code-block's own
|
|
14
|
+
local --lyra-code-block-font token, a key cap's content is mostly
|
|
15
|
+
single characters and modifier glyphs (⌘⇧⌥), which read more legibly
|
|
16
|
+
— and have more complete glyph coverage — in the UI font than in a
|
|
17
|
+
monospace stack that may not even ship those symbols. */
|
|
18
|
+
font-family: var(--lyra-font);
|
|
19
|
+
font-size: 0.8125rem;
|
|
20
|
+
line-height: 1.3;
|
|
21
|
+
color: var(--lyra-color-text-quiet);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
[part='key'] {
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
min-inline-size: 1.5em;
|
|
30
|
+
padding: 0.0625rem var(--lyra-space-xs);
|
|
31
|
+
border: 1px solid var(--lyra-color-border);
|
|
32
|
+
border-radius: calc(var(--lyra-radius) * 0.6);
|
|
33
|
+
background: var(--lyra-color-surface);
|
|
34
|
+
color: var(--lyra-color-text);
|
|
35
|
+
font-weight: 500;
|
|
36
|
+
/* A subtle bottom-weighted inset shadow reads as a slightly-raised
|
|
37
|
+
physical key cap rather than a flat label chip, echoing the
|
|
38
|
+
lyra-chip/lyra-checkbox box's own bordered-box treatment. */
|
|
39
|
+
box-shadow: inset 0 -1px 0 var(--lyra-color-border);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.sep {
|
|
43
|
+
margin-inline: 0.1875rem;
|
|
44
|
+
color: var(--lyra-color-text-quiet);
|
|
45
|
+
font-size: 0.75em;
|
|
46
|
+
user-select: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
::slotted(*) {
|
|
50
|
+
vertical-align: middle;
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
import { type AnnounceOptions } from '../../internal/announcer.js';
|
|
4
|
+
export type LiveRegionMode = 'polite' | 'assertive';
|
|
5
|
+
/**
|
|
6
|
+
* `<lyra-live-region>` — a visually-hidden ARIA live region that throttles
|
|
7
|
+
* and coalesces announcements instead of relaying every call verbatim.
|
|
8
|
+
*
|
|
9
|
+
* Naive live regions plus token-by-token streaming text (a chat response, a
|
|
10
|
+
* progress readout, ...) equals screen-reader spam: every incremental chunk
|
|
11
|
+
* gets announced. This component wraps `Announcer`
|
|
12
|
+
* (`../../internal/announcer.js`) so callers can fire `announce()` as often
|
|
13
|
+
* as they like — only the latest text within each `throttle-ms` window
|
|
14
|
+
* actually reaches assistive tech, and a `{ force: true }` call (e.g. once a
|
|
15
|
+
* stream ends) always lands immediately regardless of any window in
|
|
16
|
+
* progress.
|
|
17
|
+
*
|
|
18
|
+
* A consumer typically mounts one `<lyra-live-region>` per page/surface
|
|
19
|
+
* (much like `<lyra-toast>` is one region per placement — see
|
|
20
|
+
* `../toast/toaster.ts`) and keeps a reference to call `announce()` from
|
|
21
|
+
* application code or a parent component:
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```html
|
|
25
|
+
* <!-- once, near the root of a chat surface's shell -->
|
|
26
|
+
* <lyra-live-region id="chat-live" mode="polite"></lyra-live-region>
|
|
27
|
+
* ```
|
|
28
|
+
* ```ts
|
|
29
|
+
* const live = document.getElementById('chat-live') as LyraLiveRegion;
|
|
30
|
+
*
|
|
31
|
+
* // streaming tokens: fine to call on every chunk, only the trailing state lands
|
|
32
|
+
* live.announce(`${partialText} …`);
|
|
33
|
+
*
|
|
34
|
+
* // stream finished: always announced, even mid-throttle-window
|
|
35
|
+
* live.announce('Response complete', { force: true });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* A parent Lit component would instead hold the reference via `@query`:
|
|
39
|
+
* ```ts
|
|
40
|
+
* @query('lyra-live-region') private liveRegion!: LyraLiveRegion;
|
|
41
|
+
* ```
|
|
42
|
+
* and is exactly what later components in this family (a stream-status
|
|
43
|
+
* indicator, a tool-call chip's status transitions, a chat message's
|
|
44
|
+
* streaming state) are expected to do rather than hand-rolling their own
|
|
45
|
+
* `aria-live` element.
|
|
46
|
+
*
|
|
47
|
+
* @customElement lyra-live-region
|
|
48
|
+
* @csspart region - The visually-hidden element carrying `role`/`aria-live`.
|
|
49
|
+
*/
|
|
50
|
+
export declare class LyraLiveRegion extends LyraElement {
|
|
51
|
+
static styles: import("lit").CSSResultGroup[];
|
|
52
|
+
/** `polite` (role="status") waits for the user to be idle; `assertive`
|
|
53
|
+
* (role="alert") interrupts. Mirrors native `aria-live` semantics. */
|
|
54
|
+
mode: LiveRegionMode;
|
|
55
|
+
/** Throttle window in ms — see `Announcer` in `internal/announcer.ts`. */
|
|
56
|
+
throttleMs: number;
|
|
57
|
+
private readonly announcer;
|
|
58
|
+
private lastWritten;
|
|
59
|
+
private regionEl?;
|
|
60
|
+
private reannounceHandle?;
|
|
61
|
+
private pendingWrite?;
|
|
62
|
+
constructor();
|
|
63
|
+
protected updated(changed: PropertyValues): void;
|
|
64
|
+
firstUpdated(): void;
|
|
65
|
+
disconnectedCallback(): void;
|
|
66
|
+
/**
|
|
67
|
+
* Queue `text` for announcement. Calls arriving within `throttle-ms` of
|
|
68
|
+
* the first call in a burst collapse to a single announcement of the
|
|
69
|
+
* latest text; pass `{ force: true }` to bypass the window and flush
|
|
70
|
+
* immediately (e.g. for a final/terminal message that must not be
|
|
71
|
+
* dropped).
|
|
72
|
+
*/
|
|
73
|
+
announce(text: string, options?: AnnounceOptions): void;
|
|
74
|
+
private write;
|
|
75
|
+
render(): TemplateResult;
|
|
76
|
+
}
|
|
77
|
+
declare global {
|
|
78
|
+
interface HTMLElementTagNameMap {
|
|
79
|
+
'lyra-live-region': LyraLiveRegion;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
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 } from 'lit';
|
|
8
|
+
import { property } from 'lit/decorators.js';
|
|
9
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
|
+
import { defineElement } from '../../internal/prefix.js';
|
|
11
|
+
import { srOnly } from '../../internal/a11y.js';
|
|
12
|
+
import { Announcer } from '../../internal/announcer.js';
|
|
13
|
+
import { styles } from './live-region.styles.js';
|
|
14
|
+
/**
|
|
15
|
+
* `<lyra-live-region>` — a visually-hidden ARIA live region that throttles
|
|
16
|
+
* and coalesces announcements instead of relaying every call verbatim.
|
|
17
|
+
*
|
|
18
|
+
* Naive live regions plus token-by-token streaming text (a chat response, a
|
|
19
|
+
* progress readout, ...) equals screen-reader spam: every incremental chunk
|
|
20
|
+
* gets announced. This component wraps `Announcer`
|
|
21
|
+
* (`../../internal/announcer.js`) so callers can fire `announce()` as often
|
|
22
|
+
* as they like — only the latest text within each `throttle-ms` window
|
|
23
|
+
* actually reaches assistive tech, and a `{ force: true }` call (e.g. once a
|
|
24
|
+
* stream ends) always lands immediately regardless of any window in
|
|
25
|
+
* progress.
|
|
26
|
+
*
|
|
27
|
+
* A consumer typically mounts one `<lyra-live-region>` per page/surface
|
|
28
|
+
* (much like `<lyra-toast>` is one region per placement — see
|
|
29
|
+
* `../toast/toaster.ts`) and keeps a reference to call `announce()` from
|
|
30
|
+
* application code or a parent component:
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```html
|
|
34
|
+
* <!-- once, near the root of a chat surface's shell -->
|
|
35
|
+
* <lyra-live-region id="chat-live" mode="polite"></lyra-live-region>
|
|
36
|
+
* ```
|
|
37
|
+
* ```ts
|
|
38
|
+
* const live = document.getElementById('chat-live') as LyraLiveRegion;
|
|
39
|
+
*
|
|
40
|
+
* // streaming tokens: fine to call on every chunk, only the trailing state lands
|
|
41
|
+
* live.announce(`${partialText} …`);
|
|
42
|
+
*
|
|
43
|
+
* // stream finished: always announced, even mid-throttle-window
|
|
44
|
+
* live.announce('Response complete', { force: true });
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* A parent Lit component would instead hold the reference via `@query`:
|
|
48
|
+
* ```ts
|
|
49
|
+
* @query('lyra-live-region') private liveRegion!: LyraLiveRegion;
|
|
50
|
+
* ```
|
|
51
|
+
* and is exactly what later components in this family (a stream-status
|
|
52
|
+
* indicator, a tool-call chip's status transitions, a chat message's
|
|
53
|
+
* streaming state) are expected to do rather than hand-rolling their own
|
|
54
|
+
* `aria-live` element.
|
|
55
|
+
*
|
|
56
|
+
* @customElement lyra-live-region
|
|
57
|
+
* @csspart region - The visually-hidden element carrying `role`/`aria-live`.
|
|
58
|
+
*/
|
|
59
|
+
export class LyraLiveRegion extends LyraElement {
|
|
60
|
+
static { this.styles = [LyraElement.styles, styles, srOnly]; }
|
|
61
|
+
constructor() {
|
|
62
|
+
super();
|
|
63
|
+
/** `polite` (role="status") waits for the user to be idle; `assertive`
|
|
64
|
+
* (role="alert") interrupts. Mirrors native `aria-live` semantics. */
|
|
65
|
+
this.mode = 'polite';
|
|
66
|
+
/** Throttle window in ms — see `Announcer` in `internal/announcer.ts`. */
|
|
67
|
+
this.throttleMs = 500;
|
|
68
|
+
// The live region's own last-written text. Tracked separately from
|
|
69
|
+
// whatever Lit thinks the DOM looks like because `write()` mutates
|
|
70
|
+
// `regionEl.textContent` directly (outside Lit's template bindings) --
|
|
71
|
+
// see `write()` for why.
|
|
72
|
+
this.lastWritten = '';
|
|
73
|
+
// Built in the constructor (not a class-field initializer) so it reads
|
|
74
|
+
// `this.throttleMs` only after that property's own field initializer
|
|
75
|
+
// has already run and set the declared default.
|
|
76
|
+
this.announcer = new Announcer({
|
|
77
|
+
throttleMs: this.throttleMs,
|
|
78
|
+
onFlush: (text) => this.write(text),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
updated(changed) {
|
|
82
|
+
if (changed.has('throttleMs')) {
|
|
83
|
+
this.announcer.throttleMs = this.throttleMs;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
firstUpdated() {
|
|
87
|
+
this.regionEl = this.renderRoot.querySelector('[part="region"]') ?? undefined;
|
|
88
|
+
if (this.pendingWrite !== undefined) {
|
|
89
|
+
const text = this.pendingWrite;
|
|
90
|
+
this.pendingWrite = undefined;
|
|
91
|
+
this.write(text);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
disconnectedCallback() {
|
|
95
|
+
super.disconnectedCallback();
|
|
96
|
+
this.announcer.cancel();
|
|
97
|
+
if (this.reannounceHandle !== undefined) {
|
|
98
|
+
cancelAnimationFrame(this.reannounceHandle);
|
|
99
|
+
this.reannounceHandle = undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Queue `text` for announcement. Calls arriving within `throttle-ms` of
|
|
104
|
+
* the first call in a burst collapse to a single announcement of the
|
|
105
|
+
* latest text; pass `{ force: true }` to bypass the window and flush
|
|
106
|
+
* immediately (e.g. for a final/terminal message that must not be
|
|
107
|
+
* dropped).
|
|
108
|
+
*/
|
|
109
|
+
announce(text, options) {
|
|
110
|
+
this.announcer.announce(text, options);
|
|
111
|
+
}
|
|
112
|
+
write(text) {
|
|
113
|
+
const region = this.regionEl;
|
|
114
|
+
if (!region) {
|
|
115
|
+
this.pendingWrite = text;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (text === this.lastWritten) {
|
|
119
|
+
// Screen readers announce a live region on text-content *change* --
|
|
120
|
+
// re-writing the identical string is otherwise a silent no-op to
|
|
121
|
+
// assistive tech. Clearing first and re-setting on the next frame
|
|
122
|
+
// (rather than in the same task) gives the DOM an actual empty ->
|
|
123
|
+
// populated transition to observe instead of a same-tick clear+set
|
|
124
|
+
// that can coalesce into nothing ever appearing to change.
|
|
125
|
+
region.textContent = '';
|
|
126
|
+
this.reannounceHandle = requestAnimationFrame(() => {
|
|
127
|
+
this.reannounceHandle = undefined;
|
|
128
|
+
region.textContent = text;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
region.textContent = text;
|
|
133
|
+
}
|
|
134
|
+
this.lastWritten = text;
|
|
135
|
+
}
|
|
136
|
+
render() {
|
|
137
|
+
const assertive = this.mode === 'assertive';
|
|
138
|
+
return html `<div
|
|
139
|
+
part="region"
|
|
140
|
+
class="sr-only"
|
|
141
|
+
role=${assertive ? 'alert' : 'status'}
|
|
142
|
+
aria-live=${assertive ? 'assertive' : 'polite'}
|
|
143
|
+
aria-atomic="true"
|
|
144
|
+
></div>`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
__decorate([
|
|
148
|
+
property({ reflect: true })
|
|
149
|
+
], LyraLiveRegion.prototype, "mode", void 0);
|
|
150
|
+
__decorate([
|
|
151
|
+
property({ type: Number, attribute: 'throttle-ms' })
|
|
152
|
+
], LyraLiveRegion.prototype, "throttleMs", void 0);
|
|
153
|
+
defineElement('live-region', LyraLiveRegion);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import("lit").CSSResult;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const styles = css `
|
|
3
|
+
/* The host contributes no box of its own -- \`.sr-only\` (composed in via
|
|
4
|
+
internal/a11y.ts's shared \`srOnly\`) already takes the single child out
|
|
5
|
+
of flow via absolute positioning, so a wrapping host box would just be
|
|
6
|
+
an empty, purposeless inline element sitting in the document. */
|
|
7
|
+
:host {
|
|
8
|
+
display: contents;
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type TemplateResult, type PropertyValues } from 'lit';
|
|
2
|
-
import type { Map as MaplibreMap, StyleSpecification } from 'maplibre-gl';
|
|
3
2
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
4
3
|
import '../skeleton/skeleton.js';
|
|
5
4
|
export interface LegendEntry {
|
|
@@ -30,10 +29,12 @@ export interface MapMarker {
|
|
|
30
29
|
/**
|
|
31
30
|
* Rendered as the marker's popup content via maplibre-gl's
|
|
32
31
|
* `Popup.setHTML()` -- parsed as raw markup, inline event handlers
|
|
33
|
-
* included.
|
|
34
|
-
*
|
|
32
|
+
* included. This is the library's one explicit unsafe-HTML escape hatch:
|
|
33
|
+
* only ever pass trusted content, and sanitize anything derived from user
|
|
34
|
+
* input before assigning it here. Prefer `label` (rendered via the safe
|
|
35
|
+
* `Popup.setText()`) whenever the content is plain text.
|
|
35
36
|
*/
|
|
36
|
-
|
|
37
|
+
unsafeHtml?: string;
|
|
37
38
|
}
|
|
38
39
|
/**
|
|
39
40
|
* `<lyra-map>` — a maplibre-gl wrapper with a declarative legend and
|
|
@@ -54,7 +55,7 @@ export declare class LyraMap extends LyraElement {
|
|
|
54
55
|
static styles: import("lit").CSSResultGroup[];
|
|
55
56
|
center: [number, number];
|
|
56
57
|
zoom: number;
|
|
57
|
-
mapStyle: StyleSpecification | string;
|
|
58
|
+
mapStyle: import('maplibre-gl').StyleSpecification | string;
|
|
58
59
|
legend: LegendEntry[];
|
|
59
60
|
choropleth?: ChoroplethLayer;
|
|
60
61
|
markers: MapMarker[];
|
|
@@ -67,9 +68,10 @@ export declare class LyraMap extends LyraElement {
|
|
|
67
68
|
private _appliedFillLayerId?;
|
|
68
69
|
private _maplibreModule?;
|
|
69
70
|
private _markerInstances;
|
|
71
|
+
private _markerColors;
|
|
70
72
|
private _connectGeneration;
|
|
71
73
|
/** The raw `maplibregl.Map` instance — escape hatch for anything this wrapper doesn't expose. */
|
|
72
|
-
get map():
|
|
74
|
+
get map(): import('maplibre-gl').Map | undefined;
|
|
73
75
|
connectedCallback(): void;
|
|
74
76
|
disconnectedCallback(): void;
|
|
75
77
|
protected updated(changed: PropertyValues): void;
|
|
@@ -93,6 +93,14 @@ export class LyraMap extends LyraElement {
|
|
|
93
93
|
// choropleth updates from ever re-running.
|
|
94
94
|
this._styleLoaded = false;
|
|
95
95
|
this._markerInstances = new Map();
|
|
96
|
+
// The installed maplibre-gl's `Marker` class has no `setColor()` (verified
|
|
97
|
+
// against its shipped `.d.ts` -- `color` is only ever consumed by the
|
|
98
|
+
// constructor), so an id-matched marker whose `color` changes can't be
|
|
99
|
+
// mutated in place; it has to be torn down and reconstructed instead. This
|
|
100
|
+
// tracks the color each currently-live marker instance was last
|
|
101
|
+
// constructed with, keyed the same as `_markerInstances`, so `applyMarkers`
|
|
102
|
+
// can detect that mismatch without re-deriving it from the DOM.
|
|
103
|
+
this._markerColors = new Map();
|
|
96
104
|
// Bumped on every connectedCallback and captured by value in its
|
|
97
105
|
// loadMaplibre().then() closure below. A disconnect immediately followed
|
|
98
106
|
// by a reconnect (fast remounts, route/tab switches, etc.) before that
|
|
@@ -170,6 +178,7 @@ export class LyraMap extends LyraElement {
|
|
|
170
178
|
for (const marker of this._markerInstances.values())
|
|
171
179
|
marker.remove();
|
|
172
180
|
this._markerInstances.clear();
|
|
181
|
+
this._markerColors.clear();
|
|
173
182
|
}
|
|
174
183
|
updated(changed) {
|
|
175
184
|
if (this.loading)
|
|
@@ -281,31 +290,57 @@ export class LyraMap extends LyraElement {
|
|
|
281
290
|
if (!map || !mod)
|
|
282
291
|
return;
|
|
283
292
|
const visible = new Set();
|
|
293
|
+
// Markers with no `id` used to key solely by `lngLat`, so two different
|
|
294
|
+
// id-less markers placed at the exact same coordinates collided onto one
|
|
295
|
+
// `_markerInstances` entry. An occurrence index (reset per render, per
|
|
296
|
+
// coordinate) makes same-coordinate id-less markers distinct while
|
|
297
|
+
// staying stable/consistent across re-renders as long as their relative
|
|
298
|
+
// order in `this.markers` doesn't change.
|
|
299
|
+
const coordCounts = new Map();
|
|
284
300
|
for (const m of this.markers) {
|
|
285
|
-
|
|
301
|
+
let key = m.id;
|
|
302
|
+
if (key == null) {
|
|
303
|
+
const coordKey = `${m.lngLat[0]},${m.lngLat[1]}`;
|
|
304
|
+
const occurrence = coordCounts.get(coordKey) ?? 0;
|
|
305
|
+
coordCounts.set(coordKey, occurrence + 1);
|
|
306
|
+
key = `${coordKey}#${occurrence}`;
|
|
307
|
+
}
|
|
286
308
|
visible.add(key);
|
|
287
|
-
|
|
309
|
+
let existing = this._markerInstances.get(key);
|
|
310
|
+
if (existing && this._markerColors.get(key) !== m.color) {
|
|
311
|
+
// `color` is baked into the marker's SVG at construction time with
|
|
312
|
+
// no way to mutate it afterwards -- fall through to the "no existing
|
|
313
|
+
// marker" branch below to reconstruct it instead. Note: this closes
|
|
314
|
+
// any popup the user currently has open on this marker (a fresh,
|
|
315
|
+
// closed Popup is built for the new instance) -- an accepted, narrow
|
|
316
|
+
// side effect of the reconstruction fallback, not a bug.
|
|
317
|
+
existing.remove();
|
|
318
|
+
this._markerInstances.delete(key);
|
|
319
|
+
this._markerColors.delete(key);
|
|
320
|
+
existing = undefined;
|
|
321
|
+
}
|
|
288
322
|
if (!existing) {
|
|
289
323
|
const marker = new mod.Marker(m.color ? { color: m.color } : undefined).setLngLat(m.lngLat);
|
|
290
|
-
if (m.
|
|
324
|
+
if (m.unsafeHtml || m.label) {
|
|
291
325
|
const popup = new mod.Popup({ offset: 12 });
|
|
292
|
-
if (m.
|
|
293
|
-
popup.setHTML(m.
|
|
326
|
+
if (m.unsafeHtml)
|
|
327
|
+
popup.setHTML(m.unsafeHtml);
|
|
294
328
|
else if (m.label)
|
|
295
329
|
popup.setText(m.label);
|
|
296
330
|
marker.setPopup(popup);
|
|
297
331
|
}
|
|
298
332
|
marker.addTo(map);
|
|
299
333
|
this._markerInstances.set(key, marker);
|
|
334
|
+
this._markerColors.set(key, m.color);
|
|
300
335
|
}
|
|
301
336
|
else {
|
|
302
337
|
existing.setLngLat(m.lngLat);
|
|
303
338
|
const popup = existing.getPopup();
|
|
304
|
-
if (m.
|
|
339
|
+
if (m.unsafeHtml) {
|
|
305
340
|
if (popup)
|
|
306
|
-
popup.setHTML(m.
|
|
341
|
+
popup.setHTML(m.unsafeHtml);
|
|
307
342
|
else
|
|
308
|
-
existing.setPopup(new mod.Popup({ offset: 12 }).setHTML(m.
|
|
343
|
+
existing.setPopup(new mod.Popup({ offset: 12 }).setHTML(m.unsafeHtml));
|
|
309
344
|
}
|
|
310
345
|
else if (m.label) {
|
|
311
346
|
if (popup)
|
|
@@ -322,6 +357,7 @@ export class LyraMap extends LyraElement {
|
|
|
322
357
|
if (!visible.has(key)) {
|
|
323
358
|
marker.remove();
|
|
324
359
|
this._markerInstances.delete(key);
|
|
360
|
+
this._markerColors.delete(key);
|
|
325
361
|
}
|
|
326
362
|
}
|
|
327
363
|
}
|