@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,475 @@
|
|
|
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 { place } from '../../internal/positioner.js';
|
|
12
|
+
import { nextId } from '../../internal/a11y.js';
|
|
13
|
+
import { styles } from './mention-popover.styles.js';
|
|
14
|
+
function defaultFilter(item, query) {
|
|
15
|
+
return item.label.toLowerCase().includes(query) || (item.description ?? '').toLowerCase().includes(query);
|
|
16
|
+
}
|
|
17
|
+
function isTextControl(el) {
|
|
18
|
+
if (el instanceof HTMLTextAreaElement)
|
|
19
|
+
return true;
|
|
20
|
+
return el instanceof HTMLInputElement && (el.type === 'text' || el.type === 'search');
|
|
21
|
+
}
|
|
22
|
+
// Computed-style properties that affect text layout/measurement, copied
|
|
23
|
+
// verbatim from the real control onto the hidden mirror in caretClientRect()
|
|
24
|
+
// below so wrapped-line offsets inside the mirror land exactly where they do
|
|
25
|
+
// in the real control. Deliberately excludes paint-only properties (color,
|
|
26
|
+
// background, etc.) -- those never affect where text/lines break.
|
|
27
|
+
const MIRROR_CSS_PROPS = [
|
|
28
|
+
'box-sizing',
|
|
29
|
+
'width',
|
|
30
|
+
'padding-top',
|
|
31
|
+
'padding-right',
|
|
32
|
+
'padding-bottom',
|
|
33
|
+
'padding-left',
|
|
34
|
+
'border-top-width',
|
|
35
|
+
'border-right-width',
|
|
36
|
+
'border-bottom-width',
|
|
37
|
+
'border-left-width',
|
|
38
|
+
'border-style',
|
|
39
|
+
'font-family',
|
|
40
|
+
'font-size',
|
|
41
|
+
'font-style',
|
|
42
|
+
'font-variant',
|
|
43
|
+
'font-weight',
|
|
44
|
+
'font-stretch',
|
|
45
|
+
'letter-spacing',
|
|
46
|
+
'word-spacing',
|
|
47
|
+
'line-height',
|
|
48
|
+
'text-align',
|
|
49
|
+
'text-indent',
|
|
50
|
+
'text-transform',
|
|
51
|
+
'tab-size',
|
|
52
|
+
'direction',
|
|
53
|
+
];
|
|
54
|
+
/**
|
|
55
|
+
* Measures exactly where `el`'s caret (`selectionStart`) currently paints,
|
|
56
|
+
* in viewport coordinates, via the classic "hidden mirror div + marker span"
|
|
57
|
+
* technique: there is no native DOM API for this -- `getBoundingClientRect()`
|
|
58
|
+
* on a text control only reports the control's own box, never a cursor
|
|
59
|
+
* position inside it. A throwaway off-screen clone of `el` is built (font/
|
|
60
|
+
* box metrics copied via `MIRROR_CSS_PROPS` above), a marker `<span>` is
|
|
61
|
+
* inserted at the caret's text offset, its rect is measured relative to the
|
|
62
|
+
* mirror's own rect (so the absolute off-screen position of the mirror
|
|
63
|
+
* itself never matters), and that local offset is applied to `el`'s real
|
|
64
|
+
* `getBoundingClientRect()` minus its current scroll offset. Returns `null`
|
|
65
|
+
* for a zero-size (e.g. `display: none`) control.
|
|
66
|
+
*/
|
|
67
|
+
function caretClientRect(el) {
|
|
68
|
+
const elRect = el.getBoundingClientRect();
|
|
69
|
+
if (elRect.width === 0 || elRect.height === 0)
|
|
70
|
+
return null;
|
|
71
|
+
const computed = window.getComputedStyle(el);
|
|
72
|
+
const mirror = document.createElement('div');
|
|
73
|
+
mirror.style.position = 'absolute';
|
|
74
|
+
mirror.style.visibility = 'hidden';
|
|
75
|
+
mirror.style.top = '0';
|
|
76
|
+
mirror.style.left = '-9999px';
|
|
77
|
+
// <textarea> soft-wraps; a single-line <input> never does -- mismatching
|
|
78
|
+
// this makes the mirror wrap where the real control wouldn't (or vice
|
|
79
|
+
// versa), throwing off every offset past the first line/character run.
|
|
80
|
+
mirror.style.whiteSpace = el instanceof HTMLTextAreaElement ? 'pre-wrap' : 'pre';
|
|
81
|
+
mirror.style.overflowWrap = 'break-word';
|
|
82
|
+
for (const prop of MIRROR_CSS_PROPS) {
|
|
83
|
+
mirror.style.setProperty(prop, computed.getPropertyValue(prop));
|
|
84
|
+
}
|
|
85
|
+
const index = el.selectionStart ?? el.value.length;
|
|
86
|
+
mirror.append(document.createTextNode(el.value.slice(0, index)));
|
|
87
|
+
const marker = document.createElement('span');
|
|
88
|
+
// A marker with no content at all collapses to zero width -- a hair of
|
|
89
|
+
// content keeps it reliably measurable even for a caret sitting at the
|
|
90
|
+
// very end of the value.
|
|
91
|
+
marker.textContent = el.value.slice(index) || '';
|
|
92
|
+
mirror.appendChild(marker);
|
|
93
|
+
document.body.appendChild(mirror);
|
|
94
|
+
const mirrorRect = mirror.getBoundingClientRect();
|
|
95
|
+
const markerRect = marker.getBoundingClientRect();
|
|
96
|
+
mirror.remove();
|
|
97
|
+
const lineHeight = parseFloat(computed.lineHeight) || markerRect.height || 16;
|
|
98
|
+
return new DOMRect(elRect.left + (markerRect.left - mirrorRect.left) - el.scrollLeft, elRect.top + (markerRect.top - mirrorRect.top) - el.scrollTop, 1, lineHeight);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* `<lyra-mention-popover>` — a caret-anchored, keyboard-navigable popover for
|
|
102
|
+
* `@`-mention and `/`-slash-command autocomplete inside a plain-text
|
|
103
|
+
* `<textarea>`/`<input>` the host owns (e.g. `<lyra-chat-composer>`'s own
|
|
104
|
+
* textarea, though this component has no dependency on that or any other
|
|
105
|
+
* specific input). It never takes DOM focus itself — the same "focus stays
|
|
106
|
+
* put, `aria-activedescendant` conveys the active row" pattern
|
|
107
|
+
* `<lyra-select>`/`<lyra-combobox>` use for their own listbox — so a host
|
|
108
|
+
* must apply `aria-activedescendant` to its *own* input element, pointing at
|
|
109
|
+
* whatever `activeDescendantId` currently returns.
|
|
110
|
+
*
|
|
111
|
+
* Integration contract (entirely the host's responsibility — this component
|
|
112
|
+
* never inspects the text control's value or listens to it directly):
|
|
113
|
+
* 1. Detect a mention/command trigger (e.g. `@`/`/` at the start of a word)
|
|
114
|
+
* in the host's own `input` handling.
|
|
115
|
+
* 2. Set `anchor` (the `<textarea>`/`<input>` itself, or any element for
|
|
116
|
+
* plain whole-element anchoring — see "Positioning" below), `items`, and
|
|
117
|
+
* `query` (the text typed since the trigger character), then flip
|
|
118
|
+
* `open = true`.
|
|
119
|
+
* 3. Forward every `keydown` the input receives, while `open`, through
|
|
120
|
+
* `handleKeyDown()` — it returns `true` when it consumed the key (so the
|
|
121
|
+
* host's own handler should stop, e.g. skip submitting the message on an
|
|
122
|
+
* Enter that actually picked a mention) and `false` otherwise.
|
|
123
|
+
* 4. Set `open = false` whenever the query stops looking like an active
|
|
124
|
+
* mention context (a space typed, the trigger character deleted, the
|
|
125
|
+
* input blurred, …) — `lyra-mention-close` fires automatically from that
|
|
126
|
+
* (see below), there is no separate "tell it to close" call needed.
|
|
127
|
+
* 5. Keep the host's own input's `aria-activedescendant` (and, optionally,
|
|
128
|
+
* `aria-controls`, via `listboxId`) in sync with `activeDescendantId` —
|
|
129
|
+
* this component has no DOM of the host's to attach that to itself.
|
|
130
|
+
*
|
|
131
|
+
* Positioning: when `anchor` is a plain `<textarea>` or single-line text
|
|
132
|
+
* `<input>`, this component measures exactly where the caret currently
|
|
133
|
+
* paints (`caretClientRect()`, the standard hidden-mirror-element technique
|
|
134
|
+
* — see that function's own doc) and positions against that single point
|
|
135
|
+
* with `internal/positioner.js`'s `place()`, so the popup tracks the caret
|
|
136
|
+
* rather than sitting under the whole textarea. Any other `anchor` element
|
|
137
|
+
* (or a text control this component fails to measure, e.g. one with
|
|
138
|
+
* `display: none`) falls back to `place(anchor, popup)` against the whole
|
|
139
|
+
* element — the same whole-element anchoring `<lyra-combobox>`/
|
|
140
|
+
* `<lyra-select>` use for their own popups. Re-measures on every `anchor`/
|
|
141
|
+
* `query` change while open (a keystroke moves the caret, so a fresh `query`
|
|
142
|
+
* is the proxy for "the caret may have moved"); a caret that moves for a
|
|
143
|
+
* reason other than typing (e.g. a mouse click elsewhere in the text while
|
|
144
|
+
* the popover happens to still be open) is not separately tracked — the
|
|
145
|
+
* host can force a re-measure by toggling `open` or reassigning `anchor`.
|
|
146
|
+
*
|
|
147
|
+
* Filtering happens internally against `items` (mirroring `<lyra-combobox>`'s
|
|
148
|
+
* filter-predicate convention via `filter`, rather than requiring the host to
|
|
149
|
+
* pre-filter): the default predicate is a case-insensitive substring match
|
|
150
|
+
* against `label`/`description`, overridable via `filter`.
|
|
151
|
+
*
|
|
152
|
+
* There is no persisted "selection" the way a real listbox has one — a
|
|
153
|
+
* mention is either committed (closing the popover) or the popover is
|
|
154
|
+
* dismissed with nothing chosen — so `aria-selected="true"` marks whichever
|
|
155
|
+
* row is currently *active* (what Enter/Tab would commit right now), per the
|
|
156
|
+
* WAI-ARIA combobox-with-list-autocomplete pattern, rather than tracking a
|
|
157
|
+
* separate persisted value the way `<lyra-combobox>`'s own `aria-selected`
|
|
158
|
+
* does.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```html
|
|
162
|
+
* <textarea id="composer"></textarea>
|
|
163
|
+
* <lyra-mention-popover id="mentions"></lyra-mention-popover>
|
|
164
|
+
* <script>
|
|
165
|
+
* const textarea = document.getElementById('composer');
|
|
166
|
+
* const popover = document.getElementById('mentions');
|
|
167
|
+
*
|
|
168
|
+
* textarea.addEventListener('keydown', (e) => {
|
|
169
|
+
* if (popover.open && popover.handleKeyDown(e)) return; // consumed
|
|
170
|
+
* // ...the host's own Enter-to-send handling, etc.
|
|
171
|
+
* });
|
|
172
|
+
* textarea.addEventListener('input', () => {
|
|
173
|
+
* // host's own '@'/'/' + query detection, then:
|
|
174
|
+
* popover.anchor = textarea;
|
|
175
|
+
* popover.items = candidates;
|
|
176
|
+
* popover.query = detectedQuery;
|
|
177
|
+
* popover.open = detectedQuery !== null;
|
|
178
|
+
* });
|
|
179
|
+
* textarea.addEventListener('blur', () => (popover.open = false));
|
|
180
|
+
*
|
|
181
|
+
* popover.addEventListener('lyra-mention-select', (e) => {
|
|
182
|
+
* // splice `${e.detail.label}` into the textarea at the trigger offset
|
|
183
|
+
* });
|
|
184
|
+
* popover.addEventListener('lyra-mention-close', () => {
|
|
185
|
+
* textarea.removeAttribute('aria-activedescendant');
|
|
186
|
+
* });
|
|
187
|
+
*
|
|
188
|
+
* // Whenever the popover re-renders its active row (e.g. after every
|
|
189
|
+
* // ArrowUp/ArrowDown handleKeyDown() call above), sync the host's own
|
|
190
|
+
* // input so assistive tech announces the current candidate:
|
|
191
|
+
* const syncActiveDescendant = () => {
|
|
192
|
+
* if (popover.open && popover.activeDescendantId) {
|
|
193
|
+
* textarea.setAttribute('aria-activedescendant', popover.activeDescendantId);
|
|
194
|
+
* } else {
|
|
195
|
+
* textarea.removeAttribute('aria-activedescendant');
|
|
196
|
+
* }
|
|
197
|
+
* };
|
|
198
|
+
* </script>
|
|
199
|
+
* ```
|
|
200
|
+
*
|
|
201
|
+
* @customElement lyra-mention-popover
|
|
202
|
+
* @event lyra-mention-select - An item was committed (Enter/Tab/click). `detail: { id, label }`.
|
|
203
|
+
* @event lyra-mention-close - The popover was dismissed with no selection — Escape, or `open`
|
|
204
|
+
* transitioning to `false` by any other means (a direct host assignment included). Never fires
|
|
205
|
+
* for a close that followed a `lyra-mention-select` commit.
|
|
206
|
+
* @csspart listbox - The popover's root element (`role="listbox"`).
|
|
207
|
+
* @csspart option - A candidate row (`role="option"`).
|
|
208
|
+
* @csspart option-icon - A row's leading icon glyph, when `icon` is set.
|
|
209
|
+
* @csspart option-label - Wrapper around a row's label/description.
|
|
210
|
+
* @csspart option-description - A row's optional secondary line, when `description` is set.
|
|
211
|
+
* @csspart empty - The "no matches" message, shown when `items`/`query` produce zero rows.
|
|
212
|
+
*/
|
|
213
|
+
export class LyraMentionPopover extends LyraElement {
|
|
214
|
+
constructor() {
|
|
215
|
+
super(...arguments);
|
|
216
|
+
/** The full candidate set, pre-`query`-filtering. */
|
|
217
|
+
this.items = [];
|
|
218
|
+
/** The text typed since the trigger character (`@`/`/`/…) — drives the
|
|
219
|
+
* built-in internal filtering (see `filter` to override it). */
|
|
220
|
+
this.query = '';
|
|
221
|
+
/** Whether the popover is shown. */
|
|
222
|
+
this.open = false;
|
|
223
|
+
/** Overrides the built-in case-insensitive label/description substring match. */
|
|
224
|
+
this.filter = null;
|
|
225
|
+
/** Message shown when `items` (post-`query`-filtering) is empty. */
|
|
226
|
+
this.emptyText = 'No matches';
|
|
227
|
+
/** Accessible name for the `role="listbox"` popup. */
|
|
228
|
+
this.label = 'Suggestions';
|
|
229
|
+
// Highlighted row, opens pre-highlighted on the top match (index 0) so a
|
|
230
|
+
// bare Enter right after opening commits immediately -- the same "first
|
|
231
|
+
// result is pre-selected" UX every mainstream @-mention/slash-command
|
|
232
|
+
// picker (Slack, GitHub, Notion, …) uses, unlike lyra-combobox's own
|
|
233
|
+
// listbox which opens with nothing highlighted (-1) since a combobox's
|
|
234
|
+
// typed text can itself already equal a full, deliberately-typed value.
|
|
235
|
+
this.activeIndex = 0;
|
|
236
|
+
this._listboxId = nextId('mention-popover-listbox');
|
|
237
|
+
// A synthetic zero-size point element, positioned at the measured caret
|
|
238
|
+
// rect and handed to place() in caret-precision mode -- place()/Floating
|
|
239
|
+
// UI only understand a real HTMLElement anchor, so caret positioning goes
|
|
240
|
+
// through this rather than a bespoke non-place()-based positioning path.
|
|
241
|
+
this.virtualAnchor = null;
|
|
242
|
+
this._isFirstUpdate = true;
|
|
243
|
+
// Set by commit() immediately before it flips `open` false, so updated()'s
|
|
244
|
+
// open-transition handling below (which otherwise fires lyra-mention-close
|
|
245
|
+
// on every true->false transition, matching lyra-combobox's/lyra-select's
|
|
246
|
+
// identical lyra-hide handling) can tell a successful-selection close
|
|
247
|
+
// apart from every other close and skip the event for that one case.
|
|
248
|
+
this._suppressCloseEvent = false;
|
|
249
|
+
// Delegated onto [part="listbox"] rather than one closure pair allocated
|
|
250
|
+
// per row per render -- same pattern as lyra-combobox's/lyra-select's
|
|
251
|
+
// identical onListboxMouseDown/onListboxClick.
|
|
252
|
+
this.onListboxMouseDown = (e) => {
|
|
253
|
+
// Focus must never leave the host's own text control -- preventing the
|
|
254
|
+
// default here is what stops a plain (non-focusable) row click from
|
|
255
|
+
// blurring it.
|
|
256
|
+
if (e.target.closest('[part="option"]'))
|
|
257
|
+
e.preventDefault();
|
|
258
|
+
};
|
|
259
|
+
this.onListboxClick = (e) => {
|
|
260
|
+
const optionEl = e.target.closest('[part="option"]');
|
|
261
|
+
const id = optionEl?.dataset.id;
|
|
262
|
+
if (id === undefined)
|
|
263
|
+
return;
|
|
264
|
+
const item = this.filteredItems.find((i) => i.id === id);
|
|
265
|
+
if (item)
|
|
266
|
+
this.commit(item);
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
static { this.styles = [LyraElement.styles, styles]; }
|
|
270
|
+
willUpdate(changed) {
|
|
271
|
+
this._isFirstUpdate = !this.hasUpdated;
|
|
272
|
+
// A fresh query or candidate set re-highlights the top match, mirroring
|
|
273
|
+
// how a filtering text field's own suggestion list re-anchors to the
|
|
274
|
+
// first result on every keystroke rather than preserving a highlight
|
|
275
|
+
// that may no longer even be in the filtered set.
|
|
276
|
+
if (changed.has('query') || changed.has('items'))
|
|
277
|
+
this.activeIndex = 0;
|
|
278
|
+
}
|
|
279
|
+
updated(changed) {
|
|
280
|
+
if (changed.has('open')) {
|
|
281
|
+
if (this.open) {
|
|
282
|
+
this.reposition();
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
this.cleanup?.();
|
|
286
|
+
this.cleanup = undefined;
|
|
287
|
+
this.virtualAnchor?.remove();
|
|
288
|
+
// Don't fire for markup that's simply rendering open="false" for the
|
|
289
|
+
// first time, and don't fire for the commit() path (see
|
|
290
|
+
// _suppressCloseEvent's own doc above) -- every other true->false
|
|
291
|
+
// transition (Escape, or a direct host assignment) does fire.
|
|
292
|
+
if (!this._isFirstUpdate && !this._suppressCloseEvent)
|
|
293
|
+
this.emit('lyra-mention-close');
|
|
294
|
+
this._suppressCloseEvent = false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else if (this.open && (changed.has('anchor') || changed.has('query'))) {
|
|
298
|
+
this.reposition();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
disconnectedCallback() {
|
|
302
|
+
super.disconnectedCallback();
|
|
303
|
+
this.cleanup?.();
|
|
304
|
+
this.cleanup = undefined;
|
|
305
|
+
this.virtualAnchor?.remove();
|
|
306
|
+
}
|
|
307
|
+
/** The current candidate set: `items` filtered by `query` via `filter`
|
|
308
|
+
* (or the built-in default). Empty `query` returns `items` unfiltered. */
|
|
309
|
+
get filteredItems() {
|
|
310
|
+
const q = this.query.trim().toLowerCase();
|
|
311
|
+
if (!q)
|
|
312
|
+
return this.items;
|
|
313
|
+
const fn = this.filter ?? defaultFilter;
|
|
314
|
+
return this.items.filter((item) => fn(item, q));
|
|
315
|
+
}
|
|
316
|
+
/** The `id` of the currently-highlighted row for the host to apply as its
|
|
317
|
+
* own input's `aria-activedescendant` — `null` while closed or when
|
|
318
|
+
* `filteredItems` is empty (nothing to point at). See the class doc's
|
|
319
|
+
* `@example` for the full wiring. */
|
|
320
|
+
get activeDescendantId() {
|
|
321
|
+
if (!this.open)
|
|
322
|
+
return null;
|
|
323
|
+
const idx = this.clampedIndex(this.filteredItems);
|
|
324
|
+
return idx >= 0 ? this.rowId(idx) : null;
|
|
325
|
+
}
|
|
326
|
+
/** The `id` of the `role="listbox"` popup, for a host that also wants to
|
|
327
|
+
* wire `aria-controls` on its own input (screen-reader support for
|
|
328
|
+
* `aria-controls` is inconsistent, so `activeDescendantId` remains the
|
|
329
|
+
* load-bearing piece; this is a supplementary nicety). */
|
|
330
|
+
get listboxId() {
|
|
331
|
+
return this._listboxId;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* The host's own text-control keydown handler calls this while the
|
|
335
|
+
* popover is open. Returns `true` when the key was intercepted
|
|
336
|
+
* (`preventDefault()` already called) and the host should not also act on
|
|
337
|
+
* it; `false` when the host's normal handling should proceed untouched.
|
|
338
|
+
*/
|
|
339
|
+
handleKeyDown(e) {
|
|
340
|
+
if (!this.open)
|
|
341
|
+
return false;
|
|
342
|
+
const rows = this.filteredItems;
|
|
343
|
+
switch (e.key) {
|
|
344
|
+
case 'ArrowDown':
|
|
345
|
+
e.preventDefault();
|
|
346
|
+
if (rows.length)
|
|
347
|
+
this.activeIndex = Math.min(rows.length - 1, this.clampedIndex(rows) + 1);
|
|
348
|
+
return true;
|
|
349
|
+
case 'ArrowUp':
|
|
350
|
+
e.preventDefault();
|
|
351
|
+
if (rows.length)
|
|
352
|
+
this.activeIndex = Math.max(0, this.clampedIndex(rows) - 1);
|
|
353
|
+
return true;
|
|
354
|
+
case 'Enter':
|
|
355
|
+
case 'Tab': {
|
|
356
|
+
const idx = this.clampedIndex(rows);
|
|
357
|
+
// Nothing to commit -- let Enter submit / Tab move focus normally,
|
|
358
|
+
// matching a native combobox with no highlighted suggestion.
|
|
359
|
+
if (idx < 0)
|
|
360
|
+
return false;
|
|
361
|
+
e.preventDefault();
|
|
362
|
+
this.commit(rows[idx]);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
case 'Escape':
|
|
366
|
+
e.preventDefault();
|
|
367
|
+
this.open = false;
|
|
368
|
+
return true;
|
|
369
|
+
default:
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
clampedIndex(rows) {
|
|
374
|
+
if (!rows.length)
|
|
375
|
+
return -1;
|
|
376
|
+
return Math.min(Math.max(this.activeIndex, 0), rows.length - 1);
|
|
377
|
+
}
|
|
378
|
+
rowId(index) {
|
|
379
|
+
return `${this._listboxId}-opt-${index}`;
|
|
380
|
+
}
|
|
381
|
+
commit(item) {
|
|
382
|
+
this.emit('lyra-mention-select', { id: item.id, label: item.label });
|
|
383
|
+
this._suppressCloseEvent = true;
|
|
384
|
+
this.open = false;
|
|
385
|
+
}
|
|
386
|
+
reposition() {
|
|
387
|
+
this.cleanup?.();
|
|
388
|
+
this.cleanup = undefined;
|
|
389
|
+
const anchorEl = this.resolveAnchorElement();
|
|
390
|
+
const popup = this.renderRoot.querySelector('[part="listbox"]');
|
|
391
|
+
if (anchorEl && popup)
|
|
392
|
+
this.cleanup = place(anchorEl, popup, { placement: 'bottom-start' });
|
|
393
|
+
}
|
|
394
|
+
/** Resolves what to actually hand `place()` -- a caret-precise virtual
|
|
395
|
+
* point for a measurable text control, `anchor` itself otherwise. See the
|
|
396
|
+
* class doc's "Positioning" section. */
|
|
397
|
+
resolveAnchorElement() {
|
|
398
|
+
const anchor = this.anchor;
|
|
399
|
+
if (!anchor)
|
|
400
|
+
return null;
|
|
401
|
+
if (isTextControl(anchor)) {
|
|
402
|
+
const rect = caretClientRect(anchor);
|
|
403
|
+
if (rect) {
|
|
404
|
+
const virtual = this.virtualAnchor ?? (this.virtualAnchor = document.createElement('div'));
|
|
405
|
+
virtual.style.position = 'fixed';
|
|
406
|
+
virtual.style.left = `${rect.left}px`;
|
|
407
|
+
virtual.style.top = `${rect.top}px`;
|
|
408
|
+
virtual.style.width = '0';
|
|
409
|
+
virtual.style.height = `${rect.height}px`;
|
|
410
|
+
virtual.style.pointerEvents = 'none';
|
|
411
|
+
if (!virtual.isConnected)
|
|
412
|
+
document.body.appendChild(virtual);
|
|
413
|
+
return virtual;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return anchor;
|
|
417
|
+
}
|
|
418
|
+
renderRow(item, index, activeId) {
|
|
419
|
+
const id = this.rowId(index);
|
|
420
|
+
const active = id === activeId;
|
|
421
|
+
return html `
|
|
422
|
+
<div part="option" id=${id} role="option" data-id=${item.id} aria-selected=${active ? 'true' : 'false'} ?data-active=${active}>
|
|
423
|
+
${item.icon ? html `<span part="option-icon" aria-hidden="true">${item.icon}</span>` : nothing}
|
|
424
|
+
<span part="option-label">
|
|
425
|
+
<span>${item.label}</span>
|
|
426
|
+
${item.description ? html `<span part="option-description">${item.description}</span>` : nothing}
|
|
427
|
+
</span>
|
|
428
|
+
</div>
|
|
429
|
+
`;
|
|
430
|
+
}
|
|
431
|
+
render() {
|
|
432
|
+
const rows = this.filteredItems;
|
|
433
|
+
const idx = this.clampedIndex(rows);
|
|
434
|
+
const activeId = idx >= 0 ? this.rowId(idx) : '';
|
|
435
|
+
return html `
|
|
436
|
+
<div
|
|
437
|
+
part="listbox"
|
|
438
|
+
id=${this._listboxId}
|
|
439
|
+
role="listbox"
|
|
440
|
+
aria-label=${this.label}
|
|
441
|
+
@mousedown=${this.onListboxMouseDown}
|
|
442
|
+
@click=${this.onListboxClick}
|
|
443
|
+
>
|
|
444
|
+
${rows.length === 0
|
|
445
|
+
? html `<div part="empty" role="option" aria-selected="false" aria-disabled="true">${this.emptyText}</div>`
|
|
446
|
+
: rows.map((item, i) => this.renderRow(item, i, activeId))}
|
|
447
|
+
</div>
|
|
448
|
+
`;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
__decorate([
|
|
452
|
+
property({ attribute: false })
|
|
453
|
+
], LyraMentionPopover.prototype, "anchor", void 0);
|
|
454
|
+
__decorate([
|
|
455
|
+
property({ attribute: false })
|
|
456
|
+
], LyraMentionPopover.prototype, "items", void 0);
|
|
457
|
+
__decorate([
|
|
458
|
+
property()
|
|
459
|
+
], LyraMentionPopover.prototype, "query", void 0);
|
|
460
|
+
__decorate([
|
|
461
|
+
property({ type: Boolean, reflect: true })
|
|
462
|
+
], LyraMentionPopover.prototype, "open", void 0);
|
|
463
|
+
__decorate([
|
|
464
|
+
property({ attribute: false })
|
|
465
|
+
], LyraMentionPopover.prototype, "filter", void 0);
|
|
466
|
+
__decorate([
|
|
467
|
+
property({ attribute: 'empty-text' })
|
|
468
|
+
], LyraMentionPopover.prototype, "emptyText", void 0);
|
|
469
|
+
__decorate([
|
|
470
|
+
property()
|
|
471
|
+
], LyraMentionPopover.prototype, "label", void 0);
|
|
472
|
+
__decorate([
|
|
473
|
+
state()
|
|
474
|
+
], LyraMentionPopover.prototype, "activeIndex", void 0);
|
|
475
|
+
defineElement('mention-popover', LyraMentionPopover);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const styles: import("lit").CSSResult;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const styles = css `
|
|
3
|
+
:host {
|
|
4
|
+
display: contents;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* Positioned by internal/positioner.js's place() -- same fixed/z-index
|
|
8
|
+
shape as lyra-combobox's/lyra-select's own [part='listbox']. Closed
|
|
9
|
+
state: invisible + slightly raised, transitioning in on :host([open]),
|
|
10
|
+
same as those two. */
|
|
11
|
+
[part='listbox'] {
|
|
12
|
+
position: fixed;
|
|
13
|
+
z-index: 900;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
max-block-size: 16rem;
|
|
16
|
+
overflow-y: auto;
|
|
17
|
+
inline-size: max-content;
|
|
18
|
+
min-inline-size: 14rem;
|
|
19
|
+
max-inline-size: min(92vw, 24rem);
|
|
20
|
+
padding: var(--lyra-space-xs);
|
|
21
|
+
background: var(--lyra-color-surface);
|
|
22
|
+
border: 1px solid var(--lyra-color-border);
|
|
23
|
+
border-radius: var(--lyra-radius);
|
|
24
|
+
box-shadow: var(--lyra-shadow);
|
|
25
|
+
visibility: hidden;
|
|
26
|
+
opacity: 0;
|
|
27
|
+
transform: translateY(-0.25rem);
|
|
28
|
+
transition:
|
|
29
|
+
opacity var(--lyra-transition-fast),
|
|
30
|
+
transform var(--lyra-transition-fast),
|
|
31
|
+
visibility var(--lyra-transition-fast);
|
|
32
|
+
}
|
|
33
|
+
:host([open]) [part='listbox'] {
|
|
34
|
+
visibility: visible;
|
|
35
|
+
opacity: 1;
|
|
36
|
+
transform: translateY(0);
|
|
37
|
+
}
|
|
38
|
+
@media (prefers-reduced-motion: reduce) {
|
|
39
|
+
[part='listbox'] {
|
|
40
|
+
transition: none !important;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[part='option'] {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: flex-start;
|
|
47
|
+
gap: var(--lyra-space-xs);
|
|
48
|
+
inline-size: 100%;
|
|
49
|
+
padding: var(--lyra-space-xs) var(--lyra-space-s);
|
|
50
|
+
border-radius: var(--lyra-radius);
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
}
|
|
53
|
+
[part='option'][data-active] {
|
|
54
|
+
background: var(--lyra-color-brand-quiet);
|
|
55
|
+
}
|
|
56
|
+
[part='option'][aria-selected='true'] {
|
|
57
|
+
color: var(--lyra-color-brand);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[part='option-icon'] {
|
|
61
|
+
flex: 0 0 auto;
|
|
62
|
+
line-height: 1.4;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[part='option-label'] {
|
|
66
|
+
display: flex;
|
|
67
|
+
flex-direction: column;
|
|
68
|
+
min-inline-size: 0;
|
|
69
|
+
line-height: 1.4;
|
|
70
|
+
}
|
|
71
|
+
[part='option-label'] span:first-child {
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
text-overflow: ellipsis;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
[part='option-description'] {
|
|
78
|
+
font-size: 0.75rem;
|
|
79
|
+
color: var(--lyra-color-text-quiet);
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
text-overflow: ellipsis;
|
|
82
|
+
white-space: nowrap;
|
|
83
|
+
}
|
|
84
|
+
/* --lyra-color-text-quiet-on-brand-quiet fails WCAG AA (~4.24:1, needs
|
|
85
|
+
4.5:1) -- unlike lyra-combobox's/lyra-select's identical-looking
|
|
86
|
+
[part='option'][data-active] background (which never has this problem
|
|
87
|
+
in practice, since those default their active index to -1/none until a
|
|
88
|
+
user actually arrows through the list), this component always opens
|
|
89
|
+
with row 0 pre-highlighted (see the activeIndex field's own doc), so the
|
|
90
|
+
active/quiet-text combination is the default state, not an edge case --
|
|
91
|
+
it must pass contrast on its own. */
|
|
92
|
+
[part='option'][data-active] [part='option-description'] {
|
|
93
|
+
color: var(--lyra-color-text);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
[part='empty'] {
|
|
97
|
+
padding: var(--lyra-space-s) var(--lyra-space-m);
|
|
98
|
+
color: var(--lyra-color-text-quiet);
|
|
99
|
+
font-size: 0.875rem;
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
/**
|
|
4
|
+
* `<lyra-menu-item>` — a single action row inside `<lyra-menu>`'s default
|
|
5
|
+
* slot. Not meaningful on its own (there is no standalone "click a menu item"
|
|
6
|
+
* use case) — it exists purely as `<lyra-menu>`'s light-DOM child, the same
|
|
7
|
+
* relationship `<lyra-option>` has to `<lyra-combobox>`/`<lyra-select>`.
|
|
8
|
+
*
|
|
9
|
+
* `role="menuitem"` and the roving `tabindex` both live on *this host
|
|
10
|
+
* element*, not an internal shadow-DOM button — mirroring `<lyra-tree-node>`'s
|
|
11
|
+
* identical choice (see that class's doc). `<lyra-menu>` is the sole owner of
|
|
12
|
+
* this element's `tabIndex`: it flips exactly one navigable item's `tabIndex`
|
|
13
|
+
* to `0` (the rest sit at `-1`) as its roving-tabindex highlight moves, and
|
|
14
|
+
* calls `.focus()` directly on this host to move real DOM focus there.
|
|
15
|
+
* `[part="base"]` is purely a visual box with no interactive semantics of its
|
|
16
|
+
* own — see the class doc on `<lyra-menu>` for why real DOM focus (rather
|
|
17
|
+
* than `aria-activedescendant`) was chosen for this pair.
|
|
18
|
+
*
|
|
19
|
+
* Enter/Space activation is handled by `<lyra-menu>`'s own delegated
|
|
20
|
+
* `keydown` listener calling `select()` on whichever item is currently
|
|
21
|
+
* roving-focused (mirrors `<lyra-tree>` calling `current.select()` from its
|
|
22
|
+
* own keydown handler) — this element only wires a plain `click` listener
|
|
23
|
+
* itself, so `select()` fires identically whether the item was reached by
|
|
24
|
+
* mouse or keyboard.
|
|
25
|
+
*
|
|
26
|
+
* @customElement lyra-menu-item
|
|
27
|
+
* @slot - The item's label content.
|
|
28
|
+
* @slot icon - Optional leading icon.
|
|
29
|
+
* @event lyra-menu-item-select - This item was activated (click, or the
|
|
30
|
+
* parent `<lyra-menu>`'s own Enter/Space handling of the roving-focused
|
|
31
|
+
* item). No detail payload — a listener already has `event.target` (this
|
|
32
|
+
* element) to read `value` off of, and `<lyra-menu>` itself consumes this
|
|
33
|
+
* event to close and re-fire it as its own consolidated `lyra-menu-select`
|
|
34
|
+
* (`detail: { value }`) rather than requiring a consumer to listen on every
|
|
35
|
+
* individual item — listen there instead unless you specifically need a
|
|
36
|
+
* per-item handler.
|
|
37
|
+
* @csspart base - The row (`role` lives on the host — see the class doc).
|
|
38
|
+
* @csspart icon - Wrapper around the `icon` slot. Not rendered at all when the slot is empty.
|
|
39
|
+
* @csspart label - Wrapper around the default slot.
|
|
40
|
+
*/
|
|
41
|
+
export declare class LyraMenuItem extends LyraElement {
|
|
42
|
+
static styles: import("lit").CSSResultGroup[];
|
|
43
|
+
/** An id/value the parent `<lyra-menu>`'s `lyra-menu-select` detail keys off of. */
|
|
44
|
+
value: string;
|
|
45
|
+
/** Disables selection and excludes this item from `<lyra-menu>`'s roving-tabindex nav entirely. */
|
|
46
|
+
disabled: boolean;
|
|
47
|
+
/** Visual treatment for a dangerous action (e.g. "Delete") — tints the row with `--lyra-color-danger`. */
|
|
48
|
+
destructive: boolean;
|
|
49
|
+
private hasIconSlot;
|
|
50
|
+
connectedCallback(): void;
|
|
51
|
+
protected willUpdate(): void;
|
|
52
|
+
/** Fires `lyra-menu-item-select` (no-op while `disabled`). Called by this element's own
|
|
53
|
+
* click handler, and by `<lyra-menu>`'s Enter/Space keydown handling of the active item. */
|
|
54
|
+
select(): void;
|
|
55
|
+
private onIconSlotChange;
|
|
56
|
+
render(): TemplateResult;
|
|
57
|
+
}
|
|
58
|
+
declare global {
|
|
59
|
+
interface HTMLElementTagNameMap {
|
|
60
|
+
'lyra-menu-item': LyraMenuItem;
|
|
61
|
+
}
|
|
62
|
+
}
|