@aceshooting/lyra-ui 2.0.0 → 2.1.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 +87 -14
- package/custom-elements.json +11555 -5539
- package/dist/components/app-rail/app-rail-item.d.ts +30 -0
- package/dist/components/app-rail/app-rail-item.js +68 -0
- package/dist/components/app-rail/app-rail-item.styles.d.ts +1 -0
- package/dist/components/app-rail/app-rail-item.styles.js +65 -0
- package/dist/components/app-rail/app-rail.d.ts +21 -24
- package/dist/components/app-rail/app-rail.js +68 -132
- package/dist/components/app-rail/app-rail.styles.js +5 -2
- package/dist/components/attachment-chip/attachment-chip.d.ts +38 -11
- package/dist/components/attachment-chip/attachment-chip.js +73 -29
- package/dist/components/attachment-trigger/attachment-trigger.d.ts +13 -0
- package/dist/components/attachment-trigger/attachment-trigger.js +13 -1
- package/dist/components/attachment-trigger/attachment-trigger.styles.js +28 -6
- package/dist/components/chart/bar-chart.js +2 -18
- package/dist/components/chart/box-plot.d.ts +25 -1
- package/dist/components/chart/box-plot.js +136 -6
- package/dist/components/chart/box-plot.styles.js +13 -0
- package/dist/components/chart/bubble-chart.js +2 -18
- package/dist/components/chart/chart.d.ts +54 -10
- package/dist/components/chart/chart.js +182 -39
- package/dist/components/chart/doughnut-chart.js +2 -18
- package/dist/components/chart/histogram-bin.d.ts +5 -2
- package/dist/components/chart/histogram-bin.js +12 -4
- package/dist/components/chart/histogram.js +8 -16
- package/dist/components/chart/line-chart.js +2 -18
- package/dist/components/chart/lite-chart.d.ts +80 -3
- package/dist/components/chart/lite-chart.js +250 -53
- package/dist/components/chart/pie-chart.js +2 -18
- package/dist/components/chart/polar-area-chart.js +2 -18
- package/dist/components/chart/radar-chart.js +2 -18
- package/dist/components/chart/scatter-chart.js +2 -18
- package/dist/components/chat-composer/chat-composer.d.ts +42 -1
- package/dist/components/chat-composer/chat-composer.js +84 -3
- package/dist/components/chat-composer/chat-composer.styles.js +11 -1
- package/dist/components/chat-message/chat-message.d.ts +32 -4
- package/dist/components/chat-message/chat-message.js +57 -17
- package/dist/components/checkbox/checkbox.d.ts +66 -8
- package/dist/components/checkbox/checkbox.js +173 -50
- package/dist/components/checkbox/checkbox.styles.js +9 -2
- package/dist/components/chip/chip-group.js +8 -2
- package/dist/components/chip/chip.js +9 -2
- package/dist/components/citation-badge/citation-badge.js +21 -3
- package/dist/components/code-block/code-block.d.ts +38 -0
- package/dist/components/code-block/code-block.js +72 -7
- package/dist/components/code-block/code-loader.d.ts +44 -1
- package/dist/components/code-block/code-loader.js +63 -0
- package/dist/components/combobox/combobox.d.ts +46 -5
- package/dist/components/combobox/combobox.js +175 -35
- package/dist/components/combobox/combobox.styles.js +3 -3
- package/dist/components/context-meter/context-meter.js +17 -8
- package/dist/components/context-meter/context-meter.styles.js +0 -6
- package/dist/components/conversation-item/conversation-item.d.ts +13 -16
- package/dist/components/conversation-item/conversation-item.js +29 -20
- package/dist/components/date-picker/calendar-core.d.ts +24 -0
- package/dist/components/date-picker/calendar-core.js +51 -4
- package/dist/components/date-picker/date-input.d.ts +77 -10
- package/dist/components/date-picker/date-input.js +278 -73
- package/dist/components/date-picker/date-picker.d.ts +28 -4
- package/dist/components/date-picker/date-picker.js +95 -37
- package/dist/components/dialog/confirm.js +3 -6
- package/dist/components/dialog/dialog.d.ts +75 -31
- package/dist/components/dialog/dialog.js +147 -131
- package/dist/components/dialog/dialog.styles.js +46 -2
- package/dist/components/dock-panel/dock-panel.d.ts +8 -0
- package/dist/components/dock-panel/dock-panel.js +15 -1
- package/dist/components/dock-panel/dock-panel.styles.js +15 -1
- package/dist/components/document-preview/document-preview.d.ts +15 -7
- package/dist/components/document-preview/document-preview.js +29 -13
- package/dist/components/empty/empty.d.ts +1 -2
- package/dist/components/empty/empty.js +18 -12
- package/dist/components/empty/empty.styles.js +0 -3
- package/dist/components/export-button/export-button.d.ts +12 -0
- package/dist/components/export-button/export-button.js +33 -5
- package/dist/components/export-button/export-button.styles.js +5 -1
- package/dist/components/file-input/accept.js +2 -0
- package/dist/components/file-input/file-input.d.ts +10 -1
- package/dist/components/file-input/file-input.js +36 -5
- package/dist/components/flag/flag.d.ts +1 -1
- package/dist/components/flag/flag.js +15 -1
- package/dist/components/flag/flag.styles.js +1 -1
- package/dist/components/flag/language-map.d.ts +4 -1
- package/dist/components/flag/language-map.js +6 -3
- package/dist/components/gauge/gauge.d.ts +10 -1
- package/dist/components/gauge/gauge.js +28 -9
- package/dist/components/generation-status/generation-status.d.ts +13 -5
- package/dist/components/generation-status/generation-status.js +59 -5
- package/dist/components/graph/graph.d.ts +37 -3
- package/dist/components/graph/graph.js +132 -20
- package/dist/components/heatmap/calendar-grid.d.ts +15 -6
- package/dist/components/heatmap/calendar-grid.js +17 -6
- package/dist/components/heatmap/heatmap-scale.d.ts +5 -1
- package/dist/components/heatmap/heatmap-scale.js +8 -2
- package/dist/components/heatmap/heatmap.d.ts +166 -25
- package/dist/components/heatmap/heatmap.js +372 -89
- package/dist/components/json-viewer/json-viewer.d.ts +22 -5
- package/dist/components/json-viewer/json-viewer.js +115 -37
- package/dist/components/json-viewer/json-viewer.styles.js +2 -1
- package/dist/components/kbd/kbd.js +24 -7
- package/dist/components/live-region/live-region.d.ts +10 -1
- package/dist/components/live-region/live-region.js +42 -6
- package/dist/components/map/map.d.ts +24 -1
- package/dist/components/map/map.js +89 -24
- package/dist/components/markdown/markdown-loader.d.ts +24 -0
- package/dist/components/markdown/markdown-loader.js +35 -1
- package/dist/components/markdown/markdown.d.ts +46 -8
- package/dist/components/markdown/markdown.js +136 -31
- package/dist/components/markdown/markdown.styles.js +7 -1
- package/dist/components/media-card/media-card.d.ts +25 -17
- package/dist/components/media-card/media-card.js +43 -65
- package/dist/components/mention-popover/mention-popover.d.ts +4 -1
- package/dist/components/mention-popover/mention-popover.js +23 -5
- package/dist/components/menu/menu-item.d.ts +32 -3
- package/dist/components/menu/menu-item.js +94 -7
- package/dist/components/menu/menu-item.styles.js +8 -0
- package/dist/components/menu/menu.d.ts +25 -1
- package/dist/components/menu/menu.js +99 -3
- package/dist/components/menu/menu.styles.js +3 -2
- package/dist/components/model-select/model-select.d.ts +48 -3
- package/dist/components/model-select/model-select.js +120 -22
- package/dist/components/model-select/model-select.styles.js +17 -3
- package/dist/components/model-settings-panel/model-settings-panel.d.ts +25 -1
- package/dist/components/model-settings-panel/model-settings-panel.js +65 -1
- package/dist/components/playback/playback.d.ts +6 -2
- package/dist/components/playback/playback.js +72 -29
- package/dist/components/playback/playback.styles.js +6 -1
- package/dist/components/responsive-panel/responsive-panel.d.ts +29 -16
- package/dist/components/responsive-panel/responsive-panel.js +130 -132
- package/dist/components/responsive-panel/responsive-panel.styles.js +1 -1
- package/dist/components/result-card/result-card.d.ts +8 -2
- package/dist/components/result-card/result-card.js +9 -3
- package/dist/components/result-card/result-card.styles.js +0 -1
- package/dist/components/select/select.d.ts +59 -20
- package/dist/components/select/select.js +125 -43
- package/dist/components/skeleton/skeleton.d.ts +3 -0
- package/dist/components/skeleton/skeleton.js +7 -1
- package/dist/components/skeleton/skeleton.styles.js +5 -4
- package/dist/components/slider/slider.d.ts +57 -5
- package/dist/components/slider/slider.js +195 -80
- package/dist/components/source-card/source-card.d.ts +17 -5
- package/dist/components/source-card/source-card.js +55 -8
- package/dist/components/source-card/source-card.styles.js +1 -1
- package/dist/components/source-list/source-list.js +9 -1
- package/dist/components/sparkline/sparkline.js +34 -10
- package/dist/components/split/split.d.ts +141 -14
- package/dist/components/split/split.js +357 -52
- package/dist/components/split/split.styles.js +18 -1
- package/dist/components/stat/stat.d.ts +10 -4
- package/dist/components/stat/stat.js +46 -11
- package/dist/components/stream-status/stream-status.d.ts +27 -11
- package/dist/components/stream-status/stream-status.js +95 -13
- package/dist/components/streaming-text/streaming-text.d.ts +9 -3
- package/dist/components/streaming-text/streaming-text.js +25 -10
- package/dist/components/streaming-text/streaming-text.styles.js +3 -2
- package/dist/components/switch/switch.d.ts +52 -7
- package/dist/components/switch/switch.js +111 -40
- package/dist/components/switch/switch.styles.js +1 -1
- package/dist/components/table/table.d.ts +18 -3
- package/dist/components/table/table.js +88 -20
- package/dist/components/tabs/tabs.d.ts +23 -0
- package/dist/components/tabs/tabs.js +76 -12
- package/dist/components/tabs/tabs.styles.js +11 -0
- package/dist/components/thinking-panel/thinking-panel.d.ts +3 -1
- package/dist/components/thinking-panel/thinking-panel.js +34 -9
- package/dist/components/thinking-panel/thinking-panel.styles.js +6 -0
- package/dist/components/time-range/time-range.d.ts +59 -2
- package/dist/components/time-range/time-range.js +163 -67
- package/dist/components/toast/toast-item.d.ts +12 -3
- package/dist/components/toast/toast-item.js +90 -53
- package/dist/components/toast/toast-item.styles.js +59 -28
- package/dist/components/toast/toast.js +17 -5
- package/dist/components/toast/toaster.js +10 -1
- package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +11 -10
- package/dist/components/tool-approval-dialog/tool-approval-dialog.js +50 -124
- package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +1 -1
- package/dist/components/tool-call-chip/tool-call-chip.d.ts +18 -2
- package/dist/components/tool-call-chip/tool-call-chip.js +86 -25
- package/dist/components/tool-param-form/tool-param-form.d.ts +89 -28
- package/dist/components/tool-param-form/tool-param-form.js +350 -106
- package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +27 -7
- package/dist/components/tool-result-dialog/tool-result-dialog.js +80 -115
- package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +1 -1
- package/dist/components/tool-result-view/registry.d.ts +0 -8
- package/dist/components/tool-result-view/registry.js +7 -2
- package/dist/components/tool-result-view/tool-result-view.d.ts +6 -3
- package/dist/components/tool-result-view/tool-result-view.js +18 -4
- package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +11 -11
- package/dist/components/tool-select-dialog/tool-select-dialog.js +44 -133
- package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +13 -4
- package/dist/components/tree/tree-node.d.ts +4 -1
- package/dist/components/tree/tree-node.js +4 -1
- package/dist/components/tree/tree.d.ts +17 -8
- package/dist/components/tree/tree.js +21 -20
- package/dist/components/typing-indicator/typing-indicator.d.ts +12 -2
- package/dist/components/typing-indicator/typing-indicator.js +16 -4
- package/dist/components/typing-indicator/typing-indicator.styles.js +2 -2
- package/dist/components/virtual-list/virtual-list.d.ts +56 -19
- package/dist/components/virtual-list/virtual-list.js +184 -38
- package/dist/components/virtual-list/virtual-list.styles.js +12 -0
- package/dist/components/widget/widget.d.ts +5 -4
- package/dist/components/widget/widget.js +54 -120
- package/dist/components/widget/widget.styles.js +8 -2
- package/dist/components/word-cloud/word-cloud-layout.d.ts +7 -0
- package/dist/components/word-cloud/word-cloud-layout.js +20 -6
- package/dist/components/word-cloud/word-cloud.d.ts +22 -2
- package/dist/components/word-cloud/word-cloud.js +45 -6
- package/dist/components/word-cloud/word-cloud.styles.js +13 -1
- package/dist/internal/anchored-validity.d.ts +1 -0
- package/dist/internal/anchored-validity.js +86 -0
- package/dist/internal/form-associated.d.ts +7 -0
- package/dist/internal/form-associated.js +43 -4
- package/dist/internal/numbers.d.ts +12 -0
- package/dist/internal/numbers.js +26 -0
- package/dist/internal/overlay-manager.d.ts +51 -0
- package/dist/internal/overlay-manager.js +523 -0
- package/dist/internal/positioner.js +27 -3
- package/dist/internal/safe-url.d.ts +12 -0
- package/dist/internal/safe-url.js +41 -0
- package/dist/internal/tokens.styles.js +17 -11
- package/dist/lyra.d.ts +5 -2
- package/dist/lyra.js +3 -1
- package/llms-full.txt +970 -426
- package/llms.txt +5 -4
- package/package.json +19 -2
|
@@ -5,14 +5,11 @@ export interface MediaCardOpenDetail {
|
|
|
5
5
|
src: string;
|
|
6
6
|
filename: string;
|
|
7
7
|
}
|
|
8
|
-
/** Validates `url`
|
|
9
|
-
*
|
|
10
|
-
* checking" comment above for the `data:` judgement call and why `URL` is
|
|
11
|
-
* used instead of a regex. */
|
|
8
|
+
/** Validates `url` for an `<img>`/`<video>` source. Kept as a public wrapper
|
|
9
|
+
* so existing imports and generated API metadata remain stable. */
|
|
12
10
|
export declare function safeMediaSrc(url: string): string | null;
|
|
13
|
-
/** Validates `url`
|
|
14
|
-
*
|
|
15
|
-
* trimmed url when safe, `null` otherwise. */
|
|
11
|
+
/** Validates `url` for the library's download/open link sinks. Kept as a
|
|
12
|
+
* public wrapper so existing imports and generated API metadata remain stable. */
|
|
16
13
|
export declare function safeLinkHref(url: string): string | null;
|
|
17
14
|
/**
|
|
18
15
|
* `<lyra-media-card>` — a lightweight inline preview for one already-sent,
|
|
@@ -35,16 +32,19 @@ export declare function safeLinkHref(url: string): string | null;
|
|
|
35
32
|
* `<img>`/`<video>` `src` or an `<a href>` — only `http:`/`https:`/`blob:`
|
|
36
33
|
* (plus `data:` for a *media* `src` only) or a scheme-relative/relative URL
|
|
37
34
|
* with no scheme at all pass; anything else (`javascript:`, `vbscript:`,
|
|
38
|
-
* and similarly suspicious schemes) is rejected.
|
|
39
|
-
* `safeLinkHref()`
|
|
40
|
-
* why `data:` gets two different answers depending on
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
35
|
+
* and similarly suspicious schemes) is rejected. `safeMediaSrc()` and
|
|
36
|
+
* `safeLinkHref()` share a platform-URL-based validator; their sink-specific
|
|
37
|
+
* allowlists explain why `data:` gets two different answers depending on
|
|
38
|
+
* where it is used. An `image`/`video` `kind` whose `src` fails the
|
|
39
|
+
* media-src check falls back to the generic file-chip rendering — this is
|
|
40
|
+
* the "plain preview unavailable state" a dangerous URL degrades to, rather
|
|
41
|
+
* than ever reaching a real media/anchor sink. That fallback also re-checks
|
|
42
|
+
* `src` against the stricter href allowlist for its own download affordance,
|
|
43
|
+
* but since the href allowlist is a strict subset of the media-src allowlist,
|
|
44
|
+
* that re-check can only ever change the outcome for a direct/auto-detected
|
|
45
|
+
* `kind="file"` `src` — an `image`/`video` `src` that already failed the
|
|
46
|
+
* wider media-src check necessarily fails the narrower href check too, so
|
|
47
|
+
* the fallback for those two kinds is always the plain, unclickable `span`.
|
|
48
48
|
*
|
|
49
49
|
* **The `video` case renders its open affordance separately from `base`.**
|
|
50
50
|
* `image`/`file` wrap their *entire* card in one native `<button>`/`<a>` —
|
|
@@ -98,6 +98,10 @@ export declare class LyraMediaCard extends LyraElement {
|
|
|
98
98
|
/** Alt text for the image case (and reused as a video label fallback).
|
|
99
99
|
* Falls back to `filename`, then a generic per-kind description. */
|
|
100
100
|
alt: string;
|
|
101
|
+
/** A CSS length (e.g. `"16rem"`); once set, overrides the
|
|
102
|
+
* `--lyra-media-card-max-height` custom property for this instance only —
|
|
103
|
+
* same contract as `<lyra-document-preview>`'s identically-named prop. */
|
|
104
|
+
maxHeight: string;
|
|
101
105
|
/** Effective kind used for rendering — `kind` if explicitly set,
|
|
102
106
|
* otherwise detected from `mime-type`. */
|
|
103
107
|
private get resolvedKind();
|
|
@@ -107,6 +111,10 @@ export declare class LyraMediaCard extends LyraElement {
|
|
|
107
111
|
private get accessibleLabel();
|
|
108
112
|
private get imgAlt();
|
|
109
113
|
private get videoLabel();
|
|
114
|
+
/** Per-instance override for `--lyra-media-card-max-height`, applied
|
|
115
|
+
* inline on `[part="base"]` -- the only mechanism that reliably wins
|
|
116
|
+
* over the `:host{}`-declared default from outside the shadow root. */
|
|
117
|
+
private get baseStyle();
|
|
110
118
|
private emitOpen;
|
|
111
119
|
private onActivate;
|
|
112
120
|
private onLinkClick;
|
|
@@ -4,65 +4,22 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { html, svg } from 'lit';
|
|
7
|
+
import { html, nothing, svg } from 'lit';
|
|
8
8
|
import { property } from 'lit/decorators.js';
|
|
9
9
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
10
|
import { defineElement } from '../../internal/prefix.js';
|
|
11
11
|
import { expandIcon } from '../../internal/icons.js';
|
|
12
|
+
import { safeLinkHref as validateLinkHref, safeMediaSrc as validateMediaSrc, } from '../../internal/safe-url.js';
|
|
12
13
|
import { styles } from './media-card.styles.js';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// `new URL()` is used (rather than a hand-rolled "does this look like a
|
|
16
|
-
// scheme" regex) specifically because the platform's own URL parser already
|
|
17
|
-
// implements the WHATWG URL Standard's input normalization -- stripping
|
|
18
|
-
// ASCII tab/newline and leading/trailing C0-control-or-space from the input
|
|
19
|
-
// before it ever looks for a scheme. A naive regex checked against the raw
|
|
20
|
-
// string is exactly what a crafted value like `"java\tscript:alert(1)"` is
|
|
21
|
-
// designed to defeat: the tab breaks a `/^[a-z]+:/`-style match (so the
|
|
22
|
-
// string would wrongly fall through to the "no scheme, must be relative"
|
|
23
|
-
// branch) while a browser attribute sink still normalizes it down to a
|
|
24
|
-
// working `javascript:` URL. Delegating to `URL` means this check can never
|
|
25
|
-
// drift out of sync with what the browser itself will actually do with the
|
|
26
|
-
// string. `new URL(url)` throws when `url` has no scheme and no base to
|
|
27
|
-
// resolve against, which is exactly the "relative or scheme-relative" case
|
|
28
|
-
// this component allows unconditionally -- see `schemeOf()` below.
|
|
29
|
-
const SAFE_MEDIA_SRC_SCHEMES = new Set(['http:', 'https:', 'blob:', 'data:']);
|
|
30
|
-
// Deliberately narrower than the media-src allowlist: `data:` is excluded.
|
|
31
|
-
// A `data:` URI is inert as an `<img>`/`<video>` *source* (a browser never
|
|
32
|
-
// executes script from a media element's `src`), but a `data:text/html;...`
|
|
33
|
-
// URI navigated to directly via a clicked `<a href>` runs as a full document
|
|
34
|
-
// and can execute script -- so the same scheme gets a different verdict
|
|
35
|
-
// depending on which DOM sink it's headed for.
|
|
36
|
-
const SAFE_LINK_HREF_SCHEMES = new Set(['http:', 'https:', 'blob:']);
|
|
37
|
-
function schemeOf(url) {
|
|
38
|
-
try {
|
|
39
|
-
return new URL(url).protocol;
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function safeUrlOrNull(url, allowedSchemes) {
|
|
46
|
-
const trimmed = url.trim();
|
|
47
|
-
if (trimmed === '')
|
|
48
|
-
return null;
|
|
49
|
-
const scheme = schemeOf(trimmed);
|
|
50
|
-
if (scheme === null)
|
|
51
|
-
return trimmed; // no scheme -- relative or scheme-relative, always allowed
|
|
52
|
-
return allowedSchemes.has(scheme) ? trimmed : null;
|
|
53
|
-
}
|
|
54
|
-
/** Validates `url` is safe to assign as an `<img>`/`<video>` `src`. Returns
|
|
55
|
-
* the trimmed url when safe, `null` otherwise -- see the "safe-URL
|
|
56
|
-
* checking" comment above for the `data:` judgement call and why `URL` is
|
|
57
|
-
* used instead of a regex. */
|
|
14
|
+
/** Validates `url` for an `<img>`/`<video>` source. Kept as a public wrapper
|
|
15
|
+
* so existing imports and generated API metadata remain stable. */
|
|
58
16
|
export function safeMediaSrc(url) {
|
|
59
|
-
return
|
|
17
|
+
return validateMediaSrc(url);
|
|
60
18
|
}
|
|
61
|
-
/** Validates `url`
|
|
62
|
-
*
|
|
63
|
-
* trimmed url when safe, `null` otherwise. */
|
|
19
|
+
/** Validates `url` for the library's download/open link sinks. Kept as a
|
|
20
|
+
* public wrapper so existing imports and generated API metadata remain stable. */
|
|
64
21
|
export function safeLinkHref(url) {
|
|
65
|
-
return
|
|
22
|
+
return validateLinkHref(url);
|
|
66
23
|
}
|
|
67
24
|
function detectKind(mimeType) {
|
|
68
25
|
const mt = mimeType.trim().toLowerCase();
|
|
@@ -118,16 +75,19 @@ function fileGlyph() {
|
|
|
118
75
|
* `<img>`/`<video>` `src` or an `<a href>` — only `http:`/`https:`/`blob:`
|
|
119
76
|
* (plus `data:` for a *media* `src` only) or a scheme-relative/relative URL
|
|
120
77
|
* with no scheme at all pass; anything else (`javascript:`, `vbscript:`,
|
|
121
|
-
* and similarly suspicious schemes) is rejected.
|
|
122
|
-
* `safeLinkHref()`
|
|
123
|
-
* why `data:` gets two different answers depending on
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
78
|
+
* and similarly suspicious schemes) is rejected. `safeMediaSrc()` and
|
|
79
|
+
* `safeLinkHref()` share a platform-URL-based validator; their sink-specific
|
|
80
|
+
* allowlists explain why `data:` gets two different answers depending on
|
|
81
|
+
* where it is used. An `image`/`video` `kind` whose `src` fails the
|
|
82
|
+
* media-src check falls back to the generic file-chip rendering — this is
|
|
83
|
+
* the "plain preview unavailable state" a dangerous URL degrades to, rather
|
|
84
|
+
* than ever reaching a real media/anchor sink. That fallback also re-checks
|
|
85
|
+
* `src` against the stricter href allowlist for its own download affordance,
|
|
86
|
+
* but since the href allowlist is a strict subset of the media-src allowlist,
|
|
87
|
+
* that re-check can only ever change the outcome for a direct/auto-detected
|
|
88
|
+
* `kind="file"` `src` — an `image`/`video` `src` that already failed the
|
|
89
|
+
* wider media-src check necessarily fails the narrower href check too, so
|
|
90
|
+
* the fallback for those two kinds is always the plain, unclickable `span`.
|
|
131
91
|
*
|
|
132
92
|
* **The `video` case renders its open affordance separately from `base`.**
|
|
133
93
|
* `image`/`file` wrap their *entire* card in one native `<button>`/`<a>` —
|
|
@@ -180,6 +140,10 @@ export class LyraMediaCard extends LyraElement {
|
|
|
180
140
|
/** Alt text for the image case (and reused as a video label fallback).
|
|
181
141
|
* Falls back to `filename`, then a generic per-kind description. */
|
|
182
142
|
this.alt = '';
|
|
143
|
+
/** A CSS length (e.g. `"16rem"`); once set, overrides the
|
|
144
|
+
* `--lyra-media-card-max-height` custom property for this instance only —
|
|
145
|
+
* same contract as `<lyra-document-preview>`'s identically-named prop. */
|
|
146
|
+
this.maxHeight = '';
|
|
183
147
|
this.onActivate = () => {
|
|
184
148
|
this.emitOpen();
|
|
185
149
|
};
|
|
@@ -214,19 +178,29 @@ export class LyraMediaCard extends LyraElement {
|
|
|
214
178
|
get videoLabel() {
|
|
215
179
|
return this.alt || this.filename || 'Video attachment';
|
|
216
180
|
}
|
|
181
|
+
/** Per-instance override for `--lyra-media-card-max-height`, applied
|
|
182
|
+
* inline on `[part="base"]` -- the only mechanism that reliably wins
|
|
183
|
+
* over the `:host{}`-declared default from outside the shadow root. */
|
|
184
|
+
get baseStyle() {
|
|
185
|
+
return this.maxHeight ? `--lyra-media-card-max-height:${this.maxHeight}` : nothing;
|
|
186
|
+
}
|
|
217
187
|
emitOpen() {
|
|
218
|
-
|
|
188
|
+
// Matches whichever safe-URL sink actually rendered (see the class doc):
|
|
189
|
+
// falls back to a trimmed raw src so a whitespace-padded, otherwise-unsafe
|
|
190
|
+
// src still reports the same value the DOM would show if it were safe.
|
|
191
|
+
const src = safeMediaSrc(this.src) ?? safeLinkHref(this.src) ?? this.src.trim();
|
|
192
|
+
return this.emit('lyra-open', { src, filename: this.filename });
|
|
219
193
|
}
|
|
220
194
|
renderImage(src) {
|
|
221
195
|
return html `
|
|
222
|
-
<button part="base" type="button" aria-label=${this.accessibleLabel} @click=${this.onActivate}>
|
|
196
|
+
<button part="base" type="button" style=${this.baseStyle} aria-label=${this.accessibleLabel} @click=${this.onActivate}>
|
|
223
197
|
<img part="media" src=${src} alt=${this.imgAlt} />
|
|
224
198
|
</button>
|
|
225
199
|
`;
|
|
226
200
|
}
|
|
227
201
|
renderVideo(src) {
|
|
228
202
|
return html `
|
|
229
|
-
<div part="base">
|
|
203
|
+
<div part="base" style=${this.baseStyle}>
|
|
230
204
|
<video part="media" controls src=${src} aria-label=${this.videoLabel}></video>
|
|
231
205
|
<button part="open-button" type="button" aria-label=${this.accessibleLabel} @click=${this.onActivate}>
|
|
232
206
|
${expandIcon()}
|
|
@@ -246,6 +220,7 @@ export class LyraMediaCard extends LyraElement {
|
|
|
246
220
|
<a
|
|
247
221
|
part="base"
|
|
248
222
|
href=${href}
|
|
223
|
+
style=${this.baseStyle}
|
|
249
224
|
download=${this.filename || ''}
|
|
250
225
|
aria-label=${this.accessibleLabel}
|
|
251
226
|
@click=${this.onLinkClick}
|
|
@@ -258,7 +233,7 @@ export class LyraMediaCard extends LyraElement {
|
|
|
258
233
|
// doc describes: the filename/icon still display, but nothing is
|
|
259
234
|
// clickable (there's nothing safe to point a download/open affordance
|
|
260
235
|
// at, and no built-in lightbox to fall back to for this kind).
|
|
261
|
-
return html `<span part="base">${content}</span>`;
|
|
236
|
+
return html `<span part="base" style=${this.baseStyle}>${content}</span>`;
|
|
262
237
|
}
|
|
263
238
|
render() {
|
|
264
239
|
const kind = this.resolvedKind;
|
|
@@ -292,4 +267,7 @@ __decorate([
|
|
|
292
267
|
__decorate([
|
|
293
268
|
property()
|
|
294
269
|
], LyraMediaCard.prototype, "alt", void 0);
|
|
270
|
+
__decorate([
|
|
271
|
+
property({ attribute: 'max-height' })
|
|
272
|
+
], LyraMediaCard.prototype, "maxHeight", void 0);
|
|
295
273
|
defineElement('media-card', LyraMediaCard);
|
|
@@ -177,7 +177,10 @@ export declare class LyraMentionPopover extends LyraElement {
|
|
|
177
177
|
* The host's own text-control keydown handler calls this while the
|
|
178
178
|
* popover is open. Returns `true` when the key was intercepted
|
|
179
179
|
* (`preventDefault()` already called) and the host should not also act on
|
|
180
|
-
* it; `false` when the host's normal handling should proceed untouched
|
|
180
|
+
* it; `false` when the host's normal handling should proceed untouched --
|
|
181
|
+
* including ArrowDown/ArrowUp/Enter/Tab when there are zero filtered rows
|
|
182
|
+
* to act on, so e.g. the host's own textarea still moves its caret a line
|
|
183
|
+
* normally rather than having the keystroke silently eaten.
|
|
181
184
|
*/
|
|
182
185
|
handleKeyDown(e: KeyboardEvent): boolean;
|
|
183
186
|
private clampedIndex;
|
|
@@ -297,6 +297,16 @@ export class LyraMentionPopover extends LyraElement {
|
|
|
297
297
|
else if (this.open && (changed.has('anchor') || changed.has('query'))) {
|
|
298
298
|
this.reposition();
|
|
299
299
|
}
|
|
300
|
+
// Keyed separately from the open/anchor/query branch above: a plain
|
|
301
|
+
// ArrowDown/ArrowUp only ever changes activeIndex (see handleKeyDown), so
|
|
302
|
+
// this must fire on its own rather than piggyback on a reposition. The
|
|
303
|
+
// popup's own [part='listbox'] is height-capped and scrollable (see
|
|
304
|
+
// mention-popover.styles.ts) -- without this, arrowing past its visible
|
|
305
|
+
// rows would silently move the highlight off-screen. `block: 'nearest'`
|
|
306
|
+
// makes this a no-op whenever the active row is already fully visible.
|
|
307
|
+
if (changed.has('activeIndex')) {
|
|
308
|
+
this.renderRoot.querySelector('[part="option"][data-active]')?.scrollIntoView({ block: 'nearest' });
|
|
309
|
+
}
|
|
300
310
|
}
|
|
301
311
|
disconnectedCallback() {
|
|
302
312
|
super.disconnectedCallback();
|
|
@@ -334,7 +344,10 @@ export class LyraMentionPopover extends LyraElement {
|
|
|
334
344
|
* The host's own text-control keydown handler calls this while the
|
|
335
345
|
* popover is open. Returns `true` when the key was intercepted
|
|
336
346
|
* (`preventDefault()` already called) and the host should not also act on
|
|
337
|
-
* it; `false` when the host's normal handling should proceed untouched
|
|
347
|
+
* it; `false` when the host's normal handling should proceed untouched --
|
|
348
|
+
* including ArrowDown/ArrowUp/Enter/Tab when there are zero filtered rows
|
|
349
|
+
* to act on, so e.g. the host's own textarea still moves its caret a line
|
|
350
|
+
* normally rather than having the keystroke silently eaten.
|
|
338
351
|
*/
|
|
339
352
|
handleKeyDown(e) {
|
|
340
353
|
if (!this.open)
|
|
@@ -342,14 +355,19 @@ export class LyraMentionPopover extends LyraElement {
|
|
|
342
355
|
const rows = this.filteredItems;
|
|
343
356
|
switch (e.key) {
|
|
344
357
|
case 'ArrowDown':
|
|
358
|
+
// Nothing to navigate -- let the key fall through to the host's own
|
|
359
|
+
// control (e.g. move the caret to the next line), matching Enter/Tab's
|
|
360
|
+
// identical "no active row" fallthrough right below.
|
|
361
|
+
if (!rows.length)
|
|
362
|
+
return false;
|
|
345
363
|
e.preventDefault();
|
|
346
|
-
|
|
347
|
-
this.activeIndex = Math.min(rows.length - 1, this.clampedIndex(rows) + 1);
|
|
364
|
+
this.activeIndex = Math.min(rows.length - 1, this.clampedIndex(rows) + 1);
|
|
348
365
|
return true;
|
|
349
366
|
case 'ArrowUp':
|
|
367
|
+
if (!rows.length)
|
|
368
|
+
return false;
|
|
350
369
|
e.preventDefault();
|
|
351
|
-
|
|
352
|
-
this.activeIndex = Math.max(0, this.clampedIndex(rows) - 1);
|
|
370
|
+
this.activeIndex = Math.max(0, this.clampedIndex(rows) - 1);
|
|
353
371
|
return true;
|
|
354
372
|
case 'Enter':
|
|
355
373
|
case 'Tab': {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { type TemplateResult } from 'lit';
|
|
1
|
+
import { type PropertyValues, type TemplateResult } from 'lit';
|
|
2
2
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
3
|
+
export type MenuItemType = 'normal' | 'checkbox';
|
|
4
|
+
export interface MenuItemChangeDetail {
|
|
5
|
+
value: string;
|
|
6
|
+
checked: boolean;
|
|
7
|
+
}
|
|
3
8
|
/**
|
|
4
9
|
* `<lyra-menu-item>` — a single action row inside `<lyra-menu>`'s default
|
|
5
10
|
* slot. Not meaningful on its own (there is no standalone "click a menu item"
|
|
@@ -23,6 +28,18 @@ import { LyraElement } from '../../internal/lyra-element.js';
|
|
|
23
28
|
* itself, so `select()` fires identically whether the item was reached by
|
|
24
29
|
* mouse or keyboard.
|
|
25
30
|
*
|
|
31
|
+
* `type="checkbox"` (mirroring `wa-dropdown-item`'s identical `type` option)
|
|
32
|
+
* renders `role="menuitemcheckbox"` in place of `role="menuitem"`, with
|
|
33
|
+
* `aria-checked` reflecting `checked` and a checkmark glyph shown once
|
|
34
|
+
* `checked` is `true`. Activating a `checkbox`-type item (click, or the
|
|
35
|
+
* parent's Enter/Space handling via `select()`) toggles `checked` and fires
|
|
36
|
+
* `lyra-menu-item-change` *in addition to* the usual `lyra-menu-item-select`
|
|
37
|
+
* — the latter is never suppressed, so a parent `<lyra-menu>` still closes
|
|
38
|
+
* and re-fires its consolidated `lyra-menu-select` exactly as it does for a
|
|
39
|
+
* `type="normal"` item. `type="normal"` (the default) renders and behaves
|
|
40
|
+
* exactly as before this option existed — no role, rendering, or event
|
|
41
|
+
* differences.
|
|
42
|
+
*
|
|
26
43
|
* @customElement lyra-menu-item
|
|
27
44
|
* @slot - The item's label content.
|
|
28
45
|
* @slot icon - Optional leading icon.
|
|
@@ -34,9 +51,14 @@ import { LyraElement } from '../../internal/lyra-element.js';
|
|
|
34
51
|
* (`detail: { value }`) rather than requiring a consumer to listen on every
|
|
35
52
|
* individual item — listen there instead unless you specifically need a
|
|
36
53
|
* per-item handler.
|
|
54
|
+
* @event lyra-menu-item-change - A `type="checkbox"` item was activated and
|
|
55
|
+
* its `checked` state toggled. `detail: { value, checked }` — the item's own
|
|
56
|
+
* `value` and its new `checked` value. Fired in addition to (never instead
|
|
57
|
+
* of) `lyra-menu-item-select` above. Never fired for `type="normal"`.
|
|
37
58
|
* @csspart base - The row (`role` lives on the host — see the class doc).
|
|
38
59
|
* @csspart icon - Wrapper around the `icon` slot. Not rendered at all when the slot is empty.
|
|
39
60
|
* @csspart label - Wrapper around the default slot.
|
|
61
|
+
* @csspart checkmark - The checkmark glyph shown when a `type="checkbox"` item is `checked`. Not rendered at all for `type="normal"`.
|
|
40
62
|
*/
|
|
41
63
|
export declare class LyraMenuItem extends LyraElement {
|
|
42
64
|
static styles: import("lit").CSSResultGroup[];
|
|
@@ -46,11 +68,18 @@ export declare class LyraMenuItem extends LyraElement {
|
|
|
46
68
|
disabled: boolean;
|
|
47
69
|
/** Visual treatment for a dangerous action (e.g. "Delete") — tints the row with `--lyra-color-danger`. */
|
|
48
70
|
destructive: boolean;
|
|
71
|
+
/** `'checkbox'` renders `role="menuitemcheckbox"` with a toggleable `checked` state and a
|
|
72
|
+
* checkmark glyph, mirroring `wa-dropdown-item`'s identical `type` option — see the class doc. */
|
|
73
|
+
type: MenuItemType;
|
|
74
|
+
/** Whether a `type="checkbox"` item is checked. Meaningless (ignored) for `type="normal"`. */
|
|
75
|
+
checked: boolean;
|
|
49
76
|
private hasIconSlot;
|
|
50
77
|
connectedCallback(): void;
|
|
51
|
-
protected willUpdate(): void;
|
|
78
|
+
protected willUpdate(changed: PropertyValues): void;
|
|
52
79
|
/** 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.
|
|
80
|
+
* click handler, and by `<lyra-menu>`'s Enter/Space keydown handling of the active item.
|
|
81
|
+
* For `type="checkbox"`, also toggles `checked` and fires `lyra-menu-item-change` first --
|
|
82
|
+
* see the class doc. */
|
|
54
83
|
select(): void;
|
|
55
84
|
private onIconSlotChange;
|
|
56
85
|
render(): TemplateResult;
|
|
@@ -4,11 +4,37 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { html } from 'lit';
|
|
7
|
+
import { html, nothing, svg } from 'lit';
|
|
8
8
|
import { property, state } from 'lit/decorators.js';
|
|
9
9
|
import { LyraElement } from '../../internal/lyra-element.js';
|
|
10
10
|
import { defineElement } from '../../internal/prefix.js';
|
|
11
11
|
import { styles } from './menu-item.styles.js';
|
|
12
|
+
// Mirrors the shared icon set's viewBox/stroke conventions
|
|
13
|
+
// (internal/icons.ts's chevronIcon()/closeIcon()/etc.) without adding a
|
|
14
|
+
// checkmark glyph to that module -- it's off limits here -- so this one-off
|
|
15
|
+
// icon still reads as part of the same visual language as the rest of the
|
|
16
|
+
// library's inline icons. Same approach lyra-checkbox's own local
|
|
17
|
+
// checkmark/indeterminate glyphs (and lyra-chat-message's local retryIcon())
|
|
18
|
+
// take for the identical reason.
|
|
19
|
+
const GLYPH_VIEW_BOX = '0 0 24 24';
|
|
20
|
+
const GLYPH_STROKE_WIDTH = '1.75';
|
|
21
|
+
function checkmarkGlyph() {
|
|
22
|
+
return svg `
|
|
23
|
+
<svg
|
|
24
|
+
part="checkmark"
|
|
25
|
+
width="1em"
|
|
26
|
+
height="1em"
|
|
27
|
+
viewBox=${GLYPH_VIEW_BOX}
|
|
28
|
+
fill="none"
|
|
29
|
+
stroke="currentColor"
|
|
30
|
+
stroke-width=${GLYPH_STROKE_WIDTH}
|
|
31
|
+
stroke-linecap="round"
|
|
32
|
+
stroke-linejoin="round"
|
|
33
|
+
aria-hidden="true"
|
|
34
|
+
focusable="false"
|
|
35
|
+
><polyline points="5 12.5 10 17.5 19 6.5"></polyline></svg>
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
12
38
|
/**
|
|
13
39
|
* `<lyra-menu-item>` — a single action row inside `<lyra-menu>`'s default
|
|
14
40
|
* slot. Not meaningful on its own (there is no standalone "click a menu item"
|
|
@@ -32,6 +58,18 @@ import { styles } from './menu-item.styles.js';
|
|
|
32
58
|
* itself, so `select()` fires identically whether the item was reached by
|
|
33
59
|
* mouse or keyboard.
|
|
34
60
|
*
|
|
61
|
+
* `type="checkbox"` (mirroring `wa-dropdown-item`'s identical `type` option)
|
|
62
|
+
* renders `role="menuitemcheckbox"` in place of `role="menuitem"`, with
|
|
63
|
+
* `aria-checked` reflecting `checked` and a checkmark glyph shown once
|
|
64
|
+
* `checked` is `true`. Activating a `checkbox`-type item (click, or the
|
|
65
|
+
* parent's Enter/Space handling via `select()`) toggles `checked` and fires
|
|
66
|
+
* `lyra-menu-item-change` *in addition to* the usual `lyra-menu-item-select`
|
|
67
|
+
* — the latter is never suppressed, so a parent `<lyra-menu>` still closes
|
|
68
|
+
* and re-fires its consolidated `lyra-menu-select` exactly as it does for a
|
|
69
|
+
* `type="normal"` item. `type="normal"` (the default) renders and behaves
|
|
70
|
+
* exactly as before this option existed — no role, rendering, or event
|
|
71
|
+
* differences.
|
|
72
|
+
*
|
|
35
73
|
* @customElement lyra-menu-item
|
|
36
74
|
* @slot - The item's label content.
|
|
37
75
|
* @slot icon - Optional leading icon.
|
|
@@ -43,9 +81,14 @@ import { styles } from './menu-item.styles.js';
|
|
|
43
81
|
* (`detail: { value }`) rather than requiring a consumer to listen on every
|
|
44
82
|
* individual item — listen there instead unless you specifically need a
|
|
45
83
|
* per-item handler.
|
|
84
|
+
* @event lyra-menu-item-change - A `type="checkbox"` item was activated and
|
|
85
|
+
* its `checked` state toggled. `detail: { value, checked }` — the item's own
|
|
86
|
+
* `value` and its new `checked` value. Fired in addition to (never instead
|
|
87
|
+
* of) `lyra-menu-item-select` above. Never fired for `type="normal"`.
|
|
46
88
|
* @csspart base - The row (`role` lives on the host — see the class doc).
|
|
47
89
|
* @csspart icon - Wrapper around the `icon` slot. Not rendered at all when the slot is empty.
|
|
48
90
|
* @csspart label - Wrapper around the default slot.
|
|
91
|
+
* @csspart checkmark - The checkmark glyph shown when a `type="checkbox"` item is `checked`. Not rendered at all for `type="normal"`.
|
|
49
92
|
*/
|
|
50
93
|
export class LyraMenuItem extends LyraElement {
|
|
51
94
|
constructor() {
|
|
@@ -56,6 +99,11 @@ export class LyraMenuItem extends LyraElement {
|
|
|
56
99
|
this.disabled = false;
|
|
57
100
|
/** Visual treatment for a dangerous action (e.g. "Delete") — tints the row with `--lyra-color-danger`. */
|
|
58
101
|
this.destructive = false;
|
|
102
|
+
/** `'checkbox'` renders `role="menuitemcheckbox"` with a toggleable `checked` state and a
|
|
103
|
+
* checkmark glyph, mirroring `wa-dropdown-item`'s identical `type` option — see the class doc. */
|
|
104
|
+
this.type = 'normal';
|
|
105
|
+
/** Whether a `type="checkbox"` item is checked. Meaningless (ignored) for `type="normal"`. */
|
|
106
|
+
this.checked = false;
|
|
59
107
|
// [part='icon'] never matches a bare :empty selector -- see menu-item.styles.ts's
|
|
60
108
|
// own comment on that part. Same fix as lyra-tool-call-chip's hasDetailSlot.
|
|
61
109
|
this.hasIconSlot = false;
|
|
@@ -74,23 +122,55 @@ export class LyraMenuItem extends LyraElement {
|
|
|
74
122
|
if (this.tabIndex !== 0)
|
|
75
123
|
this.tabIndex = -1;
|
|
76
124
|
}
|
|
77
|
-
willUpdate() {
|
|
78
|
-
// role/aria-disabled live on the host (see the class doc),
|
|
79
|
-
// plain imperative attribute writes here rather than part of
|
|
80
|
-
// shadow-DOM template -- mirrors lyra-tree-node's identical
|
|
81
|
-
|
|
125
|
+
willUpdate(changed) {
|
|
126
|
+
// role/aria-disabled/aria-checked live on the host (see the class doc),
|
|
127
|
+
// so they're plain imperative attribute writes here rather than part of
|
|
128
|
+
// render()'s shadow-DOM template -- mirrors lyra-tree-node's identical
|
|
129
|
+
// willUpdate.
|
|
130
|
+
const isCheckbox = this.type === 'checkbox';
|
|
131
|
+
this.setAttribute('role', isCheckbox ? 'menuitemcheckbox' : 'menuitem');
|
|
132
|
+
if (isCheckbox) {
|
|
133
|
+
this.setAttribute('aria-checked', this.checked ? 'true' : 'false');
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// Kept absent entirely for type="normal" -- see the class doc's "no
|
|
137
|
+
// role, rendering, or event differences" guarantee for that default.
|
|
138
|
+
this.removeAttribute('aria-checked');
|
|
139
|
+
}
|
|
82
140
|
if (this.disabled) {
|
|
83
141
|
this.setAttribute('aria-disabled', 'true');
|
|
142
|
+
// Defense-in-depth mirroring connectedCallback's baseline above:
|
|
143
|
+
// <lyra-menu>'s roving-tabindex bookkeeping (activeIndex) only gets a
|
|
144
|
+
// chance to resync once real focus actually moves (via its own
|
|
145
|
+
// focusin listener), so a `disabled` flip must proactively strip this
|
|
146
|
+
// item out of the roving target and drop any focus it's currently
|
|
147
|
+
// holding right here -- regardless of what the parent's activeIndex
|
|
148
|
+
// still thinks -- so a disabled item can never remain the roving
|
|
149
|
+
// target or retain focus.
|
|
150
|
+
if (changed.has('disabled')) {
|
|
151
|
+
this.tabIndex = -1;
|
|
152
|
+
if (document.activeElement === this)
|
|
153
|
+
this.blur();
|
|
154
|
+
}
|
|
84
155
|
}
|
|
85
156
|
else {
|
|
86
157
|
this.removeAttribute('aria-disabled');
|
|
87
158
|
}
|
|
159
|
+
if (changed.has('disabled')) {
|
|
160
|
+
this.emit('lyra-menu-item-state-change', { disabled: this.disabled, hidden: this.hidden });
|
|
161
|
+
}
|
|
88
162
|
}
|
|
89
163
|
/** Fires `lyra-menu-item-select` (no-op while `disabled`). Called by this element's own
|
|
90
|
-
* click handler, and by `<lyra-menu>`'s Enter/Space keydown handling of the active item.
|
|
164
|
+
* click handler, and by `<lyra-menu>`'s Enter/Space keydown handling of the active item.
|
|
165
|
+
* For `type="checkbox"`, also toggles `checked` and fires `lyra-menu-item-change` first --
|
|
166
|
+
* see the class doc. */
|
|
91
167
|
select() {
|
|
92
168
|
if (this.disabled)
|
|
93
169
|
return;
|
|
170
|
+
if (this.type === 'checkbox') {
|
|
171
|
+
this.checked = !this.checked;
|
|
172
|
+
this.emit('lyra-menu-item-change', { value: this.value, checked: this.checked });
|
|
173
|
+
}
|
|
94
174
|
this.emit('lyra-menu-item-select');
|
|
95
175
|
}
|
|
96
176
|
render() {
|
|
@@ -100,6 +180,7 @@ export class LyraMenuItem extends LyraElement {
|
|
|
100
180
|
<slot name="icon" @slotchange=${this.onIconSlotChange}></slot>
|
|
101
181
|
</span>
|
|
102
182
|
<span part="label"><slot></slot></span>
|
|
183
|
+
${this.type === 'checkbox' && this.checked ? checkmarkGlyph() : nothing}
|
|
103
184
|
</span>
|
|
104
185
|
`;
|
|
105
186
|
}
|
|
@@ -113,6 +194,12 @@ __decorate([
|
|
|
113
194
|
__decorate([
|
|
114
195
|
property({ type: Boolean, reflect: true })
|
|
115
196
|
], LyraMenuItem.prototype, "destructive", void 0);
|
|
197
|
+
__decorate([
|
|
198
|
+
property()
|
|
199
|
+
], LyraMenuItem.prototype, "type", void 0);
|
|
200
|
+
__decorate([
|
|
201
|
+
property({ type: Boolean, reflect: true })
|
|
202
|
+
], LyraMenuItem.prototype, "checked", void 0);
|
|
116
203
|
__decorate([
|
|
117
204
|
state()
|
|
118
205
|
], LyraMenuItem.prototype, "hasIconSlot", void 0);
|
|
@@ -64,4 +64,12 @@ export const styles = css `
|
|
|
64
64
|
text-overflow: ellipsis;
|
|
65
65
|
white-space: nowrap;
|
|
66
66
|
}
|
|
67
|
+
/* Only ever present in the DOM at all for a checked type="checkbox" item
|
|
68
|
+
(see menu-item.ts's render()) -- no [hidden]-toggling needed, unlike
|
|
69
|
+
[part='icon'] above, since there's no always-present <slot> child here
|
|
70
|
+
to keep visually empty in between. */
|
|
71
|
+
[part='checkmark'] {
|
|
72
|
+
flex: 0 0 auto;
|
|
73
|
+
color: var(--lyra-color-brand);
|
|
74
|
+
}
|
|
67
75
|
`;
|
|
@@ -42,7 +42,10 @@ export interface MenuSelectDetail {
|
|
|
42
42
|
* Home/End jump to the first/last non-disabled item. Enter/Space activate
|
|
43
43
|
* the focused item. Escape closes and returns focus to the trigger. Tab
|
|
44
44
|
* closes the menu without trapping focus (the browser's own default Tab
|
|
45
|
-
* behavior proceeds untouched).
|
|
45
|
+
* behavior proceeds untouched). A printable keypress runs type-ahead:
|
|
46
|
+
* roving focus jumps to the next non-disabled item whose text starts with
|
|
47
|
+
* the accumulated buffer, cycling from just after the active item (mirrors
|
|
48
|
+
* `<lyra-select>`'s identical listbox type-ahead).
|
|
46
49
|
* - A click outside both the trigger and the open popup closes it (mirrors
|
|
47
50
|
* `<lyra-select>`'s `onDocPointer` exactly) — this does *not* refocus the
|
|
48
51
|
* trigger, since the outside click itself already moved focus somewhere
|
|
@@ -102,9 +105,12 @@ export declare class LyraMenu extends LyraElement {
|
|
|
102
105
|
private activeIndex;
|
|
103
106
|
private triggerEl?;
|
|
104
107
|
private cleanup?;
|
|
108
|
+
private itemStateObserver?;
|
|
105
109
|
private _isFirstUpdate;
|
|
106
110
|
private pendingFocus;
|
|
107
111
|
private readonly listId;
|
|
112
|
+
private typeAheadBuffer;
|
|
113
|
+
private typeAheadTimer?;
|
|
108
114
|
protected willUpdate(): void;
|
|
109
115
|
protected updated(changed: PropertyValues): void;
|
|
110
116
|
private reposition;
|
|
@@ -123,6 +129,9 @@ export declare class LyraMenu extends LyraElement {
|
|
|
123
129
|
* this component's own shadow root -- see the class doc. */
|
|
124
130
|
private syncTriggerA11y;
|
|
125
131
|
private onItemsSlotChange;
|
|
132
|
+
private isNavigable;
|
|
133
|
+
/** Rehomes roving focus immediately when an active item becomes disabled or hidden. */
|
|
134
|
+
private onItemStateChange;
|
|
126
135
|
private onItemSelect;
|
|
127
136
|
/** Flips exactly one non-disabled item's `tabIndex` to `0` (the roving
|
|
128
137
|
* target) and every other item's to `-1` -- see `<lyra-menu-item>`'s doc
|
|
@@ -133,7 +142,22 @@ export declare class LyraMenu extends LyraElement {
|
|
|
133
142
|
* stays on the trigger (see onTriggerKeyDown's Escape safety net). */
|
|
134
143
|
private focusRoving;
|
|
135
144
|
private setActiveItem;
|
|
145
|
+
/** Resyncs `activeIndex` (and the roving `tabindex`) to wherever real DOM
|
|
146
|
+
* focus actually lands, for any path that doesn't go through
|
|
147
|
+
* `setActiveItem()` -- e.g. a real mousedown on an item, which focuses it
|
|
148
|
+
* even while `disabled` (`tabIndex="-1"` remains mouse-focusable per
|
|
149
|
+
* spec). Without this, `activeIndex` goes stale the moment focus moves any
|
|
150
|
+
* other way, and subsequent Arrow-key navigation computes its next item
|
|
151
|
+
* from that stale position instead of from where focus actually is. A
|
|
152
|
+
* no-op for `setActiveItem()`'s own `.focus()` call, since `activeIndex`
|
|
153
|
+
* there is already set to match before focus moves. */
|
|
154
|
+
private onListFocusIn;
|
|
136
155
|
private onListKeyDown;
|
|
156
|
+
/** Standard WAI-ARIA APG menu-button type-ahead: moves the roving focus to
|
|
157
|
+
* the next non-disabled item whose text content starts with the
|
|
158
|
+
* accumulated buffer, cycling from just after the currently active item
|
|
159
|
+
* -- mirrors `<lyra-select>`'s identical listbox type-ahead. */
|
|
160
|
+
private typeAhead;
|
|
137
161
|
render(): TemplateResult;
|
|
138
162
|
}
|
|
139
163
|
declare global {
|