@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
package/llms-full.txt
CHANGED
|
@@ -49,37 +49,62 @@ Not a custom element itself — infrastructure every `lyra-*` component in the l
|
|
|
49
49
|
`{ bubbles: true, composed: true, cancelable: true }` `CustomEvent` and returns it, so callers can
|
|
50
50
|
do `const ev = this.emit('lyra-export', {...}); if (ev.defaultPrevented) return;`.
|
|
51
51
|
- **`FormAssociated(Base)`** mixin (`internal/form-associated.ts`) — makes a `LitElement`
|
|
52
|
-
form-associated: `static formAssociated = true`, `attachInternals()` in the constructor
|
|
53
|
-
|
|
54
|
-
`
|
|
55
|
-
`disabled: boolean` (reflected), `required: boolean` (reflected)
|
|
56
|
-
`
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
form-associated: `static formAssociated = true`, `attachInternals()` in the constructor (which also
|
|
53
|
+
eagerly calls `internals.setFormValue('')`, so an untouched control is present in `FormData` as
|
|
54
|
+
`""` from construction — matching native `<input>` — instead of being absent). Adds properties
|
|
55
|
+
`name: string`, `value: string`, `disabled: boolean` (reflected), `required: boolean` (reflected) —
|
|
56
|
+
**all four** use a hand-written accessor via Lit's `noAccessor` (not a plain reactive property), so
|
|
57
|
+
the attribute write/`internals` call fires synchronously on assignment instead of on Lit's async
|
|
58
|
+
update cycle (e.g. `internals.setFormValue()` runs synchronously off `value`; `disabled`'s
|
|
59
|
+
attribute reflection lands before same-tick form APIs run). Provides
|
|
60
|
+
`checkValidity()`/`reportValidity()` (delegate to `internals`), readonly `form`, `labels`,
|
|
61
|
+
`validity`, `validationMessage`, `willValidate`, and `effectiveDisabled` getters —
|
|
62
|
+
`effectiveDisabled` is own `disabled` OR an ancestor `<fieldset disabled>`'s cascaded state;
|
|
63
|
+
`formDisabledCallback(fieldsetDisabled)` stores that ancestor state in a private field rather than
|
|
64
|
+
writing into `disabled` itself, so `disabled` always reflects only the consumer's own
|
|
65
|
+
attribute/property (never mutated by fieldset cascading, matching native `<input>`) — **read
|
|
66
|
+
`effectiveDisabled`, not `disabled`, to get the merged state**. A real `updateValidity()` calls
|
|
67
|
+
`internals.setValidity({ valueMissing: true }, ...)` whenever `required` is set and `value === ''`
|
|
68
|
+
(re-run on every `value`/`required` change and once from `connectedCallback()`) — so
|
|
69
|
+
`checkValidity()`/`reportValidity()`/`:invalid`/`:user-invalid` now reflect real constraint state
|
|
70
|
+
instead of always reporting valid. Validity is routed through an internal
|
|
71
|
+
`AnchoredValidityController` (`internal/anchored-validity.ts`): it calls
|
|
72
|
+
`internals.setValidity(flags, message, anchor)` with `anchor` = the first focusable descendant in
|
|
73
|
+
the shadow root (`input:not([type='hidden']), textarea, select, button,
|
|
74
|
+
[tabindex]:not([tabindex='-1'])`), re-resolved after every render — required because the browser
|
|
75
|
+
can't focus the (non-focusable) custom-element host itself when `reportValidity()`/native
|
|
76
|
+
validation UI tries to focus the invalid control. `formResetCallback()` restores the value captured
|
|
77
|
+
from the element's original `value` *content attribute* (native `defaultValue` semantics), not a
|
|
78
|
+
hardcoded blank — only a later `setAttribute('value', ...)` or declarative markup updates that
|
|
79
|
+
captured default; assigning the `.value` IDL property never does. `formStateRestoreCallback()`
|
|
80
|
+
restores string state synchronously without emitting a user event. `lyra-date-input`,
|
|
81
|
+
`lyra-chat-composer`, and `lyra-slider` use this mixin; controls with non-string values and
|
|
82
|
+
controls whose defaults come from child markup hand-roll an equivalent independently instead (same
|
|
83
|
+
`setValidity`/default-capture behavior — `lyra-combobox` needs its own copy because its value can
|
|
84
|
+
be an array in `multiple` mode, `lyra-select` because its default is captured from a
|
|
85
|
+
declared-`selected` `<lyra-option>` rather than a `value` content attribute — see each component's
|
|
86
|
+
own gotchas for where its copy diverges from the mixin). Every form-associated control exposes the
|
|
87
|
+
same six readonly native-like getters (`form`, `labels`, `validity`, `validationMessage`,
|
|
88
|
+
`willValidate`, `effectiveDisabled`), including `lyra-time-range`, whose form association exists
|
|
89
|
+
only for fieldset-cascaded disablement and has no submission-value/state-restoration contract.
|
|
69
90
|
- **`positioner.ts` → `place(anchor, popup, opts?): () => void`** — thin wrapper over
|
|
70
91
|
`@floating-ui/dom`'s `computePosition` + `autoUpdate`. Forces `strategy: 'fixed'` (to match the
|
|
71
92
|
popup's own `position:fixed` CSS — otherwise it lands offset by the page scroll position),
|
|
72
93
|
middleware `offset(opts.offset ?? 4)`, `flip()`, `shift({ padding: 8 })`, default
|
|
73
94
|
`placement: 'bottom-start'`. Returns a cleanup function that stops the `autoUpdate` loop — call it
|
|
74
95
|
in `disconnectedCallback()`. Used by `lyra-combobox`, `lyra-select`, `lyra-date-input`,
|
|
75
|
-
`lyra-export-button
|
|
96
|
+
`lyra-export-button`, `lyra-model-select`, `lyra-mention-popover`, `lyra-tool-call-chip`,
|
|
97
|
+
`lyra-citation-badge`, and `lyra-menu` (which resolves its `placement` through
|
|
98
|
+
`rtlAwarePlacement()` first — see `rtl.ts` below — before handing it to `place()`).
|
|
76
99
|
- **`tokens.styles.ts` → `tokens`** (a `css` template prepended to every component's stylesheet) —
|
|
77
100
|
the whole design-token layer. Every value chains through a `--wa-*` Web Awesome token first, with
|
|
78
101
|
a hard-coded fallback, e.g. `--lyra-color-brand: var(--wa-color-brand-fill-loud, #0969da)`. Full
|
|
79
102
|
set: `--lyra-color-surface / -text / -text-quiet / -border / -brand / -brand-quiet / -success /
|
|
80
|
-
-
|
|
81
|
-
brand
|
|
82
|
-
|
|
103
|
+
-success-quiet / -warning / -warning-quiet / -danger / -danger-quiet`,
|
|
104
|
+
`--lyra-color-on-brand / -on-success / -on-warning / -on-danger` (paired
|
|
105
|
+
text/icon colors for content painted on the corresponding solid semantic fill; the standalone
|
|
106
|
+
fallbacks switch from light content in light mode to dark content on the brighter dark-mode
|
|
107
|
+
fills), `--lyra-space-xs / -s / -m / -l`, `--lyra-radius`, `--lyra-shadow`,
|
|
83
108
|
`--lyra-font`, `--lyra-transition-fast` / `-base` (motion timing every animating component should
|
|
84
109
|
share), `--lyra-opacity-disabled` (one shared disabled-state opacity), `--lyra-focus-ring-width` /
|
|
85
110
|
`-color` / `-offset` (every `:focus-visible` rule should reference these), `--lyra-icon-button-size`
|
|
@@ -100,6 +125,16 @@ Not a custom element itself — infrastructure every `lyra-*` component in the l
|
|
|
100
125
|
- **`scroll-lock.ts` → `lockScroll(): () => void`** — ref-counted `document.documentElement`
|
|
101
126
|
scroll lock (used by `lyra-widget`'s fullscreen mode); safe to acquire/release more than once
|
|
102
127
|
concurrently, restores the original `overflow` only once the last outstanding lock releases.
|
|
128
|
+
- **`overlay-manager.ts` → `activateOverlay(options): OverlayHandle`** — per-`Document`
|
|
129
|
+
coordination for `lyra-dialog`, overlay-mode `lyra-responsive-panel`, the three tool dialogs,
|
|
130
|
+
mobile `lyra-app-rail`, and fullscreen `lyra-widget`. Overlays from different families share one
|
|
131
|
+
visual/topmost stack: only the top entry handles Escape, Tab trapping, and backdrop dismissal.
|
|
132
|
+
Content outside the active modal's composed path is inert, including lower overlays and page
|
|
133
|
+
content added while it is open. Focus traversal crosses slots and open shadow roots; activation
|
|
134
|
+
preserves focus already inside but moves outside focus into the panel, and an open-state close
|
|
135
|
+
restores the still-connected opener even after a direct property/attribute transition. Nested
|
|
136
|
+
closes restore into the surviving overlay before eventually returning to the original page
|
|
137
|
+
trigger.
|
|
103
138
|
- **`rtl.ts` → `isRtl(el): boolean`** — reads `getComputedStyle(el).direction === 'rtl'`, for the
|
|
104
139
|
pointer/keyboard math (drag ratios, arrow-key direction) in `lyra-split`, `lyra-time-range`, and
|
|
105
140
|
`lyra-dock-panel` that reasons about physical left/right and can't rely on logical CSS properties
|
|
@@ -148,16 +183,20 @@ shared `FormAssociated` mixin — see gotchas).
|
|
|
148
183
|
- `maxOptionsVisible: number = 3` (attribute `max-options-visible` — caps how many selected tags
|
|
149
184
|
show before collapsing to `+N`)
|
|
150
185
|
- `emptyText: string = 'No results'` (attribute `empty-text`)
|
|
186
|
+
- `loadingText: string = 'Loading…'` (attribute `loading-text` — listbox row shown while a `source`
|
|
187
|
+
fetch is in flight)
|
|
188
|
+
- `overflowText: string = '+{n} more — refine your search'` (attribute `overflow-text` — listbox row
|
|
189
|
+
shown when `maxRender` caps the row list; `{n}` is replaced with the hidden count)
|
|
151
190
|
- `filter: OptionFilter | null = null` (attribute: false — `(option, query) => boolean`; default
|
|
152
191
|
matches `label`/`searchText` case-insensitively; ignored while `source` is set)
|
|
153
192
|
- `source: ComboboxSource | null = null` (attribute: false — `(query: string) =>
|
|
154
193
|
Promise<ComboboxSourceRow[]>`; when set, replaces the light-DOM `<lyra-option>` list with an async
|
|
155
|
-
lookup, debounced ~200ms after each keystroke and re-run on clear/pick.
|
|
194
|
+
lookup, debounced ~200ms after each keystroke and re-run on clear/pick. `loadingText` is shown
|
|
156
195
|
while a call is in flight; a stale in-flight call that resolves after a newer one (or after
|
|
157
196
|
disconnect) is dropped via a monotonic token)
|
|
158
197
|
- `maxRender: number = 200` (attribute `max-render` — caps how many rows render at once, always
|
|
159
|
-
keeping the current selection visible even if it's outside the cap; the excess
|
|
160
|
-
|
|
198
|
+
keeping the current selection visible even if it's outside the cap; the excess renders as one
|
|
199
|
+
`overflowText` row instead of being dropped silently)
|
|
161
200
|
- `value: string | string[]` — a getter/setter: plain `string` in single mode, `string[]` in
|
|
162
201
|
`multiple` mode
|
|
163
202
|
|
|
@@ -165,8 +204,13 @@ shared `FormAssociated` mixin — see gotchas).
|
|
|
165
204
|
string; disabled?: boolean }` — the row shape both the async `source` and the light-DOM
|
|
166
205
|
`<lyra-option>` path normalize to internally.
|
|
167
206
|
|
|
168
|
-
**Events:**
|
|
169
|
-
|
|
207
|
+
**Events:** typing in the filter exposes the original bubbling/composed, non-cancelable `InputEvent`
|
|
208
|
+
as exactly one host `input` event and does not fire `change`. An actual user selection mutation —
|
|
209
|
+
pointer or keyboard selection, multiple-value toggle, tag/Backspace removal, or clear — emits exactly
|
|
210
|
+
one bubbling/composed, non-cancelable plain `input` `Event`, immediately followed by the same shape
|
|
211
|
+
of `change` `Event`. Re-picking the current single value and programmatic/default/reset/restore
|
|
212
|
+
writes are silent. The clear button emits one `lyra-clear` after its `input`/`change` pair.
|
|
213
|
+
`lyra-show` and `lyra-hide` report listbox visibility transitions.
|
|
170
214
|
|
|
171
215
|
**Slots:** default (`<lyra-option>` children), `label`, `hint`, `error` (overrides the `errorText`
|
|
172
216
|
attribute when provided)
|
|
@@ -222,6 +266,11 @@ Multi-select submits as **repeated `FormData` entries** (not a joined string)
|
|
|
222
266
|
(`multiple` with no `name`) contributes nothing to the form at all, matching a nameless native
|
|
223
267
|
`<select multiple>`, rather than falling back to a shared literal key that could collide with
|
|
224
268
|
another unnamed combobox in the same form.
|
|
269
|
+
Session-history/autofill state is stored as a name-independent JSON string array. A valid string
|
|
270
|
+
array restores the selection (single mode keeps its first entry); malformed or wrong-shape state
|
|
271
|
+
restores an empty selection. Restored state wins even when it arrives before the first option
|
|
272
|
+
collection, while `form.reset()` still returns to the declarative selected default. Restoration is
|
|
273
|
+
synchronous and fires no `input`/`change` event.
|
|
225
274
|
|
|
226
275
|
**Known gotchas:**
|
|
227
276
|
- naming drift from Web Awesome parity: the shipped attribute is **`with-clear`**, not
|
|
@@ -257,19 +306,22 @@ as `lyra-combobox`, see the shared-foundation notes above). The trigger is a `<b
|
|
|
257
306
|
input: click/Enter/Space/ArrowDown opens it, and there's no typing-to-filter. Options are
|
|
258
307
|
`<lyra-option value>` children — the same element `<lyra-combobox>` uses — reconciled the same way
|
|
259
308
|
combobox does, and the popup reuses `internal/positioner.ts` for placement.
|
|
309
|
+
Session-history/autofill restoration assigns the stored string through the same synchronous
|
|
310
|
+
value/form/validity path as a programmatic value write and does not emit `input` or `change`.
|
|
260
311
|
|
|
261
312
|
Single-select only, with no `filter`/`source`/`with-clear`/`max-options-visible`/`empty-text`/
|
|
262
313
|
`max-render`/`multiple` surface — reach for `<lyra-combobox>` instead whenever any of those apply.
|
|
263
314
|
|
|
264
|
-
**Single-option auto-commit.**
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
315
|
+
**Single-option auto-commit.** Opt-in via `autoCommitSingleOption` (default `false` — a select always
|
|
316
|
+
renders the normal combobox/listbox/chevron trigger unless enabled, matching pre-1.3.0 behavior).
|
|
317
|
+
When set and exactly one option is enabled (regardless of how many disabled ones exist alongside
|
|
318
|
+
it), the popup never opens at all: a click, Enter, Space, ArrowDown, or ArrowUp on the trigger
|
|
319
|
+
commits that sole option directly, and the trigger renders as a plain `role="button"` with no
|
|
320
|
+
chevron/`aria-haspopup`/`aria-expanded`/`aria-controls`/`aria-activedescendant` rather than a
|
|
321
|
+
combobox with a permanently inert popup state — opening a one-row list to pick the only available
|
|
322
|
+
choice is pure friction with no real decision behind it. It never changes `value`/validity defaults
|
|
323
|
+
on its own: an unselected single-option select stays unselected (and a `required` one stays invalid)
|
|
324
|
+
exactly like the multi-option case, until the trigger is actually activated.
|
|
273
325
|
|
|
274
326
|
**Properties:**
|
|
275
327
|
- `placeholder: string = ''`
|
|
@@ -283,6 +335,8 @@ multi-option case, until the trigger is actually activated.
|
|
|
283
335
|
- `open: boolean = false` (reflected)
|
|
284
336
|
- `size: 'xs'|'s'|'m'|'l'|'xl' = 'm'` (reflected — same scale as `lyra-toast-item`'s `size`, for
|
|
285
337
|
compact toolbar placements that don't fit the default trigger height)
|
|
338
|
+
- `autoCommitSingleOption: boolean = false` (attribute `auto-commit-single-option`) — opts in to the
|
|
339
|
+
single-option auto-commit behavior described above
|
|
286
340
|
- `value: string` — a getter/setter; always a single string (no `multiple` mode)
|
|
287
341
|
|
|
288
342
|
**Events:** `change` (native-style — selection changed), `input` (fired alongside `change` on every
|
|
@@ -292,11 +346,17 @@ selection change — a native `<select>` doesn't meaningfully distinguish the tw
|
|
|
292
346
|
**Slots:** default (`<lyra-option>` children), `label`, `hint`, `error` (overrides the `errorText`
|
|
293
347
|
attribute when provided)
|
|
294
348
|
|
|
349
|
+
When hint/error content is present, the trigger's `aria-describedby` references stable shadow-local
|
|
350
|
+
IDs for both messages (error first, then hint), so the visible supporting text is part of the
|
|
351
|
+
control's accessible description.
|
|
352
|
+
|
|
295
353
|
**CSS parts:** `form-control`, `form-control-label`, `trigger`, `listbox`, `option`, `option-dot`
|
|
296
354
|
(the leading status dot, when a row's `dotColor` is set), `option-label`, `option-sub` (a row's
|
|
297
355
|
secondary line, when `sub` is set), `expand-icon`, `error`, `hint`
|
|
298
356
|
|
|
299
|
-
**Themeable custom properties:**
|
|
357
|
+
**Themeable custom properties:** `--lyra-select-trigger-padding`, `--lyra-select-trigger-min-height`,
|
|
358
|
+
`--lyra-select-font-size` — all three auto-swapped per `size` (`xs`…`xl`), the same pattern
|
|
359
|
+
`lyra-toast-item`'s `--padding`/`--font-size` use.
|
|
300
360
|
|
|
301
361
|
**Optional peer deps:** none.
|
|
302
362
|
|
|
@@ -327,10 +387,11 @@ secondary line, when `sub` is set), `expand-icon`, `error`, `hint`
|
|
|
327
387
|
- The trigger's accessible name now checks a host-level `aria-label` attribute first, before falling
|
|
328
388
|
back to `label`/`placeholder`/`"Select"` — a plain `aria-label` on `<lyra-select>` is no longer
|
|
329
389
|
silently ignored.
|
|
330
|
-
-
|
|
331
|
-
all — see "Single-option auto-commit" above.
|
|
332
|
-
`role="combobox"` trigger, or that opens the
|
|
333
|
-
|
|
390
|
+
- With `autoCommitSingleOption` set, a select with exactly one enabled option never exposes
|
|
391
|
+
`role="combobox"`/opens a listbox at all — see "Single-option auto-commit" above.
|
|
392
|
+
Testing/automation code that always expects a `role="combobox"` trigger, or that opens the
|
|
393
|
+
listbox before asserting on a row, either needs at least two enabled options or should leave
|
|
394
|
+
`autoCommitSingleOption` unset to observe the normal dropdown chrome.
|
|
334
395
|
|
|
335
396
|
---
|
|
336
397
|
|
|
@@ -346,24 +407,30 @@ Inline month-grid calendar, not form-associated (used standalone or embedded ins
|
|
|
346
407
|
|
|
347
408
|
**Properties:**
|
|
348
409
|
- `value: string = ''`
|
|
349
|
-
- `mode: 'single'|'range' = 'single'`
|
|
410
|
+
- `mode: 'single'|'range' = 'single'` — unknown runtime values fall back to `single`
|
|
350
411
|
- `min: string = ''`
|
|
351
412
|
- `max: string = ''`
|
|
352
413
|
- `disabled: boolean = false` (reflected)
|
|
353
414
|
- `readonly: boolean = false` (reflected)
|
|
354
|
-
- `months: 1|2 = 1`
|
|
355
|
-
|
|
415
|
+
- `months: 1|2 = 1` — finite runtime values are truncated and clamped to `1..2`; non-finite
|
|
416
|
+
values fall back to `1`
|
|
417
|
+
- `locale: string = ''` — malformed locale tags fall back to the platform locale
|
|
356
418
|
- `firstDayOfWeek: string = 'auto'` (attribute `first-day-of-week` — see gotchas)
|
|
357
|
-
- `weekdayFormat: 'narrow'|'short'|'long' = 'short'` (attribute `weekday-format
|
|
419
|
+
- `weekdayFormat: 'narrow'|'short'|'long' = 'short'` (attribute `weekday-format`; unknown runtime
|
|
420
|
+
values fall back to `short`)
|
|
358
421
|
- `disablePast: boolean = false` (attribute `disable-past`)
|
|
359
422
|
- `disableFuture: boolean = false` (attribute `disable-future`)
|
|
360
423
|
- `withOutsideDays: boolean = false` (attribute `with-outside-days`)
|
|
424
|
+
- `previousLabel: string = 'Previous month'` (attribute `previous-label` — accessible label for the
|
|
425
|
+
previous-month nav button; override for a non-English `locale`)
|
|
426
|
+
- `nextLabel: string = 'Next month'` (attribute `next-label` — accessible label for the next-month
|
|
427
|
+
nav button)
|
|
361
428
|
|
|
362
429
|
**Getters:** `selection: { from: Date|null; to: Date|null }`, `valueAsDate: Date | null` (single
|
|
363
430
|
mode only)
|
|
364
431
|
|
|
365
|
-
**Methods:** `clear()`, `goToToday()`, `goToDate(date: string | Date)` (clamped to
|
|
366
|
-
navigating/focusing)
|
|
432
|
+
**Methods:** `clear()`, `goToToday()`, `goToDate(date: string | Date)` (valid dates are clamped to
|
|
433
|
+
`min`/`max` before navigating/focusing; invalid `Date` objects and strings are ignored)
|
|
367
434
|
|
|
368
435
|
**Events:** `input` (during interaction — for range mode, fires after the first click of a pair),
|
|
369
436
|
`change` (committed value)
|
|
@@ -388,16 +455,18 @@ Text field + calendar popover, **form-associated** via the shared `FormAssociate
|
|
|
388
455
|
- `mode: 'single'|'range' = 'single'`
|
|
389
456
|
- `min: string = ''`
|
|
390
457
|
- `max: string = ''`
|
|
391
|
-
- `readonly: boolean = false` (reflected
|
|
458
|
+
- `readonly: boolean = false` (reflected; preserves the submitted value but bars required and
|
|
459
|
+
date-bound constraint validation until removed)
|
|
392
460
|
- `open: boolean = false` (reflected)
|
|
393
461
|
- `withClear: boolean = false` (attribute `with-clear`)
|
|
394
462
|
- `label: string = ''`
|
|
395
463
|
- `hint: string = ''`
|
|
396
464
|
- `placeholder: string = ''`
|
|
397
|
-
- `locale: string = ''`
|
|
398
|
-
- `months: 1|2 = 1`
|
|
465
|
+
- `locale: string = ''` — malformed locale tags fall back to the platform locale
|
|
466
|
+
- `months: 1|2 = 1` — finite runtime values are truncated and clamped to `1..2`; non-finite
|
|
467
|
+
values fall back to `1`
|
|
399
468
|
- `firstDayOfWeek: string = 'auto'`
|
|
400
|
-
- `weekdayFormat: 'narrow'|'short'|'long' = 'short'`
|
|
469
|
+
- `weekdayFormat: 'narrow'|'short'|'long' = 'short'` — unknown runtime values fall back to `short`
|
|
401
470
|
- `disablePast: boolean = false` (attribute `disable-past`)
|
|
402
471
|
- `disableFuture: boolean = false` (attribute `disable-future`)
|
|
403
472
|
- `withOutsideDays: boolean = false` (attribute `with-outside-days`)
|
|
@@ -405,6 +474,11 @@ Text field + calendar popover, **form-associated** via the shared `FormAssociate
|
|
|
405
474
|
**Properties (own, continued):**
|
|
406
475
|
- `errorText: string = ''` (attribute `error-text` — static error copy; overridden by slotted
|
|
407
476
|
`error` content when provided)
|
|
477
|
+
- `clearLabel: string = 'Clear'` (attribute `clear-label` — accessible label for the clear button)
|
|
478
|
+
- `openLabel: string = 'Open calendar'` (attribute `open-label` — accessible label for the
|
|
479
|
+
calendar-toggle button)
|
|
480
|
+
- `dialogLabel: string = 'Choose date'` (attribute `dialog-label` — accessible name for the
|
|
481
|
+
`role="dialog"` calendar popover)
|
|
408
482
|
|
|
409
483
|
**Methods:** `show()`, `hide()`, `clear()`
|
|
410
484
|
|
|
@@ -430,9 +504,13 @@ Text field + calendar popover, **form-associated** via the shared `FormAssociate
|
|
|
430
504
|
|
|
431
505
|
`calendar-core.ts` exports the pure date math both components share (**internal — not re-exported
|
|
432
506
|
from the package root**, but worth knowing when reasoning about behavior): `parseISO`, `formatISO`,
|
|
433
|
-
`isSameDay`, `addMonths`, `
|
|
434
|
-
|
|
435
|
-
`
|
|
507
|
+
`isSameDay`, `addMonths`, `addMonthsClampingDay(date, n)` (like `addMonths` but clamps the result to
|
|
508
|
+
the target month's last day instead of overflowing into the month after — e.g. Jan 31 + 1 month
|
|
509
|
+
lands on Feb 28/29, not Mar 3; backs `lyra-date-picker`'s PageUp/PageDown), `clampDate`,
|
|
510
|
+
`monthMatrix(year, month, firstDayOfWeek)`, `weekdayLabels(firstDayOfWeek, format, locale)`,
|
|
511
|
+
`monthTitle(year, month, locale)`, `resolveFirstDayOfWeek(value, locale?)`,
|
|
512
|
+
`normalizeCalendarMode(value)`, `normalizeCalendarMonths(value)`, `normalizeWeekdayFormat(value)`,
|
|
513
|
+
and `dateTimeFormat(locale, options)`.
|
|
436
514
|
|
|
437
515
|
**Known gotchas:**
|
|
438
516
|
- `first-day-of-week="auto"` now derives from `locale` when the runtime's `Intl.Locale` exposes
|
|
@@ -442,16 +520,30 @@ from the package root**, but worth knowing when reasoning about behavior): `pars
|
|
|
442
520
|
show; on an older runtime it still falls back to Sunday-first silently. **If you need a guaranteed
|
|
443
521
|
locale-correct week start regardless of runtime**, pass an explicit
|
|
444
522
|
`first-day-of-week="mon"` (accepted values: `sun`/`mon`/`tue`/`wed`/`thu`/`fri`/`sat`).
|
|
445
|
-
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
523
|
+
- Runtime attribute/property inputs are normalized before calendar math or `Intl` formatting:
|
|
524
|
+
unsupported modes and weekday formats use their documented defaults, month counts cannot exceed
|
|
525
|
+
the two-grid API, malformed locale tags use the platform locale, and invalid `Date` objects do
|
|
526
|
+
not replace the current view.
|
|
527
|
+
- (date-input only) — opening the calendar does not move focus into it: `Alt+ArrowDown` leaves focus
|
|
528
|
+
on the text input, and the expand button keeps focus when clicked. The focused opener is
|
|
529
|
+
remembered. Escape and a finalized calendar selection close the popup and return focus to that
|
|
530
|
+
same connected element; a direct `.open = false` also restores it when focus would otherwise
|
|
531
|
+
remain inside the now-hidden popup. Outside-pointer dismissal deliberately leaves focus at the
|
|
532
|
+
clicked target.
|
|
449
533
|
- (date-input only) — typing an unparseable string and blurring/committing sets `badInput` via
|
|
450
|
-
`internals.setValidity()` and reverts the displayed text to the last valid commit
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
534
|
+
`internals.setValidity()` and reverts the displayed text to the last valid commit. The visible
|
|
535
|
+
`<input part="input">` mirrors `required` through native `required`/`aria-required` and exposes
|
|
536
|
+
touched outer validity through `aria-invalid`; this includes required, bad-input, and range
|
|
537
|
+
failures owned by the form-associated host. A host `aria-label` is reactively forwarded to the
|
|
538
|
+
input, including later changes/removal, and takes precedence over the component's
|
|
539
|
+
label/placeholder fallback. Native validation attempts reveal `aria-invalid`; `form.reset()`
|
|
540
|
+
clears that touched presentation. A parseable typed date outside an active bound is committed
|
|
541
|
+
instead and reports the precise `rangeUnderflow`/`rangeOverflow` state.
|
|
542
|
+
- (date-input only) — declarative, IDL, reset, and restored values are sanitized to exact ISO dates:
|
|
543
|
+
calendar-invalid or malformed strings become `''` (and therefore `valueMissing` when required),
|
|
544
|
+
while valid dates outside `min`/`max` or `disable-past`/`disable-future` remain submitted and expose
|
|
545
|
+
`rangeUnderflow`/`rangeOverflow`. Changes to these constraints, `mode`, and `readonly` recompute
|
|
546
|
+
validity synchronously; range mode validates both endpoints.
|
|
455
547
|
- The grid keyboard pattern (Arrow/PageUp/PageDown/Home/End navigation with correct focus
|
|
456
548
|
sequencing) is implemented correctly and safe to rely on, as is the selected/range-day text color
|
|
457
549
|
(`--lyra-color-on-brand`, not a hardcoded literal — safe to override `--lyra-color-brand` without
|
|
@@ -604,11 +696,14 @@ the reviewed family (pure CSS cascade, no JS/`getComputedStyle` bridging needed)
|
|
|
604
696
|
a glance-value sparkline, worth reconsidering if it's ever the *sole* representation of a metric.
|
|
605
697
|
- flat data (every value equal, so the auto-computed range spans zero) now renders a centered
|
|
606
698
|
midline/mid-height bars instead of collapsing every point to the bottom edge, and a single-value
|
|
607
|
-
series renders a visible flat line (a zero-length path was previously invisible).
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
point count
|
|
699
|
+
series renders a visible flat line (a zero-length path was previously invisible). **Every** `type`
|
|
700
|
+
(`line`/`area`/`bar`) decimates a `values` array past 500 points down to at most 500 plotted
|
|
701
|
+
samples — evenly sampled by index, always keeping the first and last value exactly, not
|
|
702
|
+
aggregated/averaged. `type="bar"` caps at 500 rendered `<rect>`s directly; `line`/`area` cap the
|
|
703
|
+
point count baked into the single `<path>`'s `d` string instead (an uncapped path string also
|
|
704
|
+
grows unbounded, even though the element count stays at one `<path>`). Auto `min`/`max` is still
|
|
705
|
+
scanned from the *full* pre-decimation `values` array, so a real extreme value that decimation
|
|
706
|
+
happens to drop can't silently narrow the rendered scale.
|
|
612
707
|
|
|
613
708
|
---
|
|
614
709
|
|
|
@@ -629,9 +724,16 @@ Country/language flag image. Flag artwork ships in a **separate, optional peer p
|
|
|
629
724
|
- `label?: string` (accessible name / `alt` text — **defaults to a localized, human-readable region
|
|
630
725
|
name derived from the *resolved country* code via `Intl.DisplayNames` if omitted**, see gotchas)
|
|
631
726
|
- `round: boolean = false` (reflected — circular crop)
|
|
632
|
-
- `
|
|
633
|
-
|
|
634
|
-
|
|
727
|
+
- `variant?: 'compact' | 'standard' | 'detailed'` (attribute `variant`, not reflected — picks a
|
|
728
|
+
fidelity tier for the ~65 codes whose source art embeds a coat of arms/seal/emblem; every other
|
|
729
|
+
code resolves to the same file regardless of `variant`. `'compact'` = a tiny WebP raster for
|
|
730
|
+
icon-scale use (menus, language pickers, ~12–28px); `'standard'` (the effective default, when both
|
|
731
|
+
`variant` and `detailed` are unset) = the icon-optimized vector for card/row sizes (~28–96px);
|
|
732
|
+
`'detailed'` = the pristine full-fidelity vector for hero-scale display. Takes precedence over the
|
|
733
|
+
deprecated `detailed` below. No effect when `src` is set.)
|
|
734
|
+
- `detailed: boolean = false` (reflected — **deprecated: use `variant="detailed"` instead**. Kept as
|
|
735
|
+
an alias for one minor cycle — when `variant` is left unset, `detailed` still maps to the detailed
|
|
736
|
+
tier — scheduled for removal in the next major. No effect when `src` is set.)
|
|
635
737
|
|
|
636
738
|
**Events:** none.
|
|
637
739
|
|
|
@@ -639,8 +741,10 @@ Country/language flag image. Flag artwork ships in a **separate, optional peer p
|
|
|
639
741
|
|
|
640
742
|
**CSS parts:** `image`
|
|
641
743
|
|
|
642
|
-
**Themeable custom properties:** `--lyra-flag-radius` (default `
|
|
643
|
-
|
|
744
|
+
**Themeable custom properties:** `--lyra-flag-radius` (default `calc(var(--lyra-radius) * 0.33)` —
|
|
745
|
+
≈2px at the library's default `--lyra-radius` of `0.375rem`; scales with `--lyra-radius` unless
|
|
746
|
+
overridden directly — non-`round` corner radius); also consumes `--lyra-color-border` (the inset
|
|
747
|
+
ring around the flag image).
|
|
644
748
|
|
|
645
749
|
**Optional peer deps:** `@aceshooting/lyra-flags` — required for the component to actually render an
|
|
646
750
|
image; without it, renders an empty template (see gotchas).
|
|
@@ -653,6 +757,8 @@ need different defaults).
|
|
|
653
757
|
```html
|
|
654
758
|
<lyra-flag country="fr" label="France"></lyra-flag>
|
|
655
759
|
<lyra-flag language="en-US" round></lyra-flag>
|
|
760
|
+
<lyra-flag country="es" variant="compact"></lyra-flag> <!-- tiny WebP raster, icon-scale -->
|
|
761
|
+
<lyra-flag country="es" variant="detailed"></lyra-flag> <!-- pristine full-fidelity vector -->
|
|
656
762
|
```
|
|
657
763
|
|
|
658
764
|
```bash
|
|
@@ -690,12 +796,16 @@ pnpm add @aceshooting/lyra-flags # required peer — without it, <lyra-flag> r
|
|
|
690
796
|
shipped ~28 KB total, not all 249). If you already have a flag's URL at build time, `src` skips
|
|
691
797
|
the peer-package round trip (and its loading-skeleton flash) entirely.
|
|
692
798
|
- 65 of `@aceshooting/lyra-flags`' 249 flags (any whose design includes a detailed coat of
|
|
693
|
-
arms/seal/emblem, e.g. `es`, `pt`, `sv`) ship
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
799
|
+
arms/seal/emblem, e.g. `es`, `pt`, `sv`) ship **three** fidelity tiers, selected via the `variant`
|
|
800
|
+
property (`flagUrl(code, { variant })` under the hood): `"compact"` — a tiny WebP raster for
|
|
801
|
+
icon-scale use (menus, language pickers, dense lists); `"standard"` — the default, the
|
|
802
|
+
icon-optimized vector for card/row sizes, ~65% smaller on average than the pristine source for the
|
|
803
|
+
65 affected codes with no visible fidelity loss at that scale; `"detailed"` — the pristine
|
|
804
|
+
full-fidelity vector, for hero-scale display where the extra illustrative detail is actually
|
|
805
|
+
visible. The other 184 codes resolve to the same file regardless of `variant` — a safe no-op. The
|
|
806
|
+
older boolean `detailed` attribute predates `variant` and is now **deprecated**
|
|
807
|
+
(`variant="detailed"` is the replacement); left unset, `variant` falls back to honoring `detailed`
|
|
808
|
+
for one more minor version before removal.
|
|
699
809
|
|
|
700
810
|
---
|
|
701
811
|
|
|
@@ -749,6 +859,9 @@ Loading placeholder (`text`/`circle`/`rect` shapes, `pulse`/`sheen` effects).
|
|
|
749
859
|
- `effect: 'pulse'|'sheen' = 'pulse'` (reflected)
|
|
750
860
|
- `width?: string`
|
|
751
861
|
- `height?: string`
|
|
862
|
+
- `label: string = 'Loading…'` — accessible name for this instance's own `role="status"` (rendered as
|
|
863
|
+
visually-hidden text inside `[part="base"]`); override with a description of what's actually
|
|
864
|
+
loading, e.g. `label="Loading chart"`
|
|
752
865
|
|
|
753
866
|
**Events:** none.
|
|
754
867
|
|
|
@@ -763,16 +876,18 @@ Loading placeholder (`text`/`circle`/`rect` shapes, `pulse`/`sheen` effects).
|
|
|
763
876
|
|
|
764
877
|
```html
|
|
765
878
|
<lyra-skeleton variant="circle" width="3rem" height="3rem"></lyra-skeleton>
|
|
766
|
-
<lyra-skeleton variant="text"></lyra-skeleton>
|
|
879
|
+
<lyra-skeleton variant="text" label="Loading name"></lyra-skeleton>
|
|
767
880
|
```
|
|
768
881
|
|
|
769
882
|
**Known gotchas:**
|
|
770
|
-
- every instance is an independent, simultaneously-firing `role="status"` live region
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
883
|
+
- every instance is an independent, simultaneously-firing `role="status"` live region (set via
|
|
884
|
+
`willUpdate`) with its own visually-hidden `label` text (default `"Loading…"`). A realistic loading
|
|
885
|
+
state (e.g. a 10×5 skeleton table = 50 instances) mounts 50 separate live regions at nearly the same
|
|
886
|
+
tick — expect screen readers to announce the label dozens of times, not once, even if every
|
|
887
|
+
instance shares the same default `label`. A more specific per-instance `label` doesn't fix the
|
|
888
|
+
fan-out by itself; there's still no grouping primitive to dedupe it — if it matters, wrap your own
|
|
889
|
+
single `role="status"` region around the set of skeletons and mark the individual ones
|
|
890
|
+
`aria-hidden`.
|
|
776
891
|
- no `lines`/`count` shorthand for "N lines of skeleton text" — stamp out N elements
|
|
777
892
|
yourself.
|
|
778
893
|
- Respects `prefers-reduced-motion` (both effects) — safe to leave as-is for that concern.
|
|
@@ -824,7 +939,10 @@ render one itself), `sub` (rich sub-line content — overrides the `sub` attribu
|
|
|
824
939
|
is provided)
|
|
825
940
|
|
|
826
941
|
**CSS parts:** `base`, `icon`, `label`, `value-row`, `value`, `unit`, `trend`, `sub`, `spark`,
|
|
827
|
-
`caption`, `rows`, `row`, `row-label`, `row-value`
|
|
942
|
+
`caption`, `rows`, `row`, `row-label`, `row-value` — `[part="value"]` gets `aria-labelledby` pairing
|
|
943
|
+
it with `[part="label"]`'s generated id whenever `label` is non-empty (so tabbing straight to the
|
|
944
|
+
`exactValue`-focusable value still announces e.g. "Revenue $1.2K", not just the bare value); each
|
|
945
|
+
`[part="row-value"]` is paired the same way with its own row's `[part="row-label"]`.
|
|
828
946
|
|
|
829
947
|
**Themeable custom properties:** shared tokens only (`--lyra-color-success/-warning/-danger` drive
|
|
830
948
|
the `variant`-colored value text and up/down trend pill; `--lyra-color-brand` drives `emphasis`'s
|
|
@@ -860,7 +978,9 @@ of `rows` — this component only renders and emits intents.
|
|
|
860
978
|
(`'low'` hides first, under a ~900px container width; `'medium'` next, under ~640px; both
|
|
861
979
|
breakpoints are fixed in `table.styles.ts`, not themeable tokens), reversible via
|
|
862
980
|
`[part='reveal-columns-button']`; `sticky` pins that column's header cell/cells to the inline-start
|
|
863
|
-
edge (`position: sticky`) while the table scrolls horizontally
|
|
981
|
+
edge (`position: sticky`) while the table scrolls horizontally — multiple `sticky` columns stack
|
|
982
|
+
left-to-right (each measures every earlier sticky column's rendered width via
|
|
983
|
+
`--lyra-table-sticky-offset`) instead of overlapping at the same edge
|
|
864
984
|
- `columnsHidden: boolean = false` (attribute `columns-hidden`, reflected) — computed/read-only: true
|
|
865
985
|
when a `priority` column is *actually* hidden right now by the `@container` breakpoints above, or
|
|
866
986
|
`showAllColumns` force-visible mode is currently active. Measured via a `ResizeObserver` on
|
|
@@ -873,7 +993,10 @@ of `rows` — this component only renders and emits intents.
|
|
|
873
993
|
- `rows: T[] = []` (attribute: false)
|
|
874
994
|
- `sortKey: string = ''` (attribute `sort-key`)
|
|
875
995
|
- `sortDir: 'asc'|'desc' = 'asc'` (attribute `sort-dir`)
|
|
876
|
-
- `rowKey?: (row: T) => string | number` (attribute: false)
|
|
996
|
+
- `rowKey?: (row: T) => string | number` (attribute: false) — derives each row's stable identity for
|
|
997
|
+
DOM-reconciliation and the delegated row click/keydown lookup; falls back to the row's array index
|
|
998
|
+
when omitted, which is only safe while `rows` never reorders — set it whenever `rows` can be
|
|
999
|
+
sorted/filtered/re-ordered across renders, or selection/click can silently attach to the wrong row
|
|
877
1000
|
- `selectedKey: string | number | null = null` (attribute: false) — **single**-selection only
|
|
878
1001
|
- `hasMore: boolean = false` (attribute `has-more`, reflected)
|
|
879
1002
|
- `moreLabel: string = 'Load more'` (attribute `more-label`)
|
|
@@ -920,18 +1043,23 @@ body's `max-block-size`).
|
|
|
920
1043
|
- only single-row selection is modeled (`selectedKey: string | number | null`);
|
|
921
1044
|
there's no bulk-select/checkbox-column API — you must hand-roll a checkbox column entirely inside
|
|
922
1045
|
a `cell()` callback if you need multi-select.
|
|
923
|
-
- no `caption`/`aria-label` property,
|
|
924
|
-
|
|
1046
|
+
- no `caption`/`aria-label` *property* exists, but a plain `aria-label` HTML attribute set on the
|
|
1047
|
+
host **is** forwarded into the shadow-DOM `<table role="grid">` (read via
|
|
1048
|
+
`this.getAttribute('aria-label')` at render time — a plain global-attribute read, not a reactive
|
|
1049
|
+
Lit `@property`): `<lyra-table aria-label="Scores">` gives the grid an accessible name; omit it and
|
|
1050
|
+
the shadow table simply has none.
|
|
925
1051
|
- Full roving-tabindex grid keyboard pattern (one `tabindex="0"` stop among header cells, one among
|
|
926
1052
|
body rows) — Left/Right/Home/End move within the header row, Up/Down/Home/End move within the
|
|
927
1053
|
body, Down from the header enters the body's roving stop and Up from the body's first row returns
|
|
928
1054
|
to the header, Enter/Space still only sort/activate — a genuine strength versus most siblings in
|
|
929
1055
|
this family. A `priority`-hidden header/cell is skipped when computing the visible header stops,
|
|
930
1056
|
so arrow-key navigation never strands the roving stop on a hidden column.
|
|
931
|
-
- a `cell()` template can render its own interactive content
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1057
|
+
- a `cell()` template can render its own interactive content without it being swallowed by
|
|
1058
|
+
row/column activation: clicks and Enter/Space landing on (or bubbling through) anything matching
|
|
1059
|
+
`button, a[href], input, select, textarea, [role="button"], [role="combobox"], [role="listbox"],
|
|
1060
|
+
[role="slider"]`, **or any custom element** (any tag name containing a hyphen — e.g. a
|
|
1061
|
+
`<lyra-select>`/`<lyra-combobox>` rendered inside a `cell()`), are left alone by the table's own
|
|
1062
|
+
delegated `click`/`keydown` handlers instead of triggering `lyra-sort`/`lyra-row-click`.
|
|
935
1063
|
|
|
936
1064
|
---
|
|
937
1065
|
|
|
@@ -979,11 +1107,9 @@ gauge-specific custom property exists).
|
|
|
979
1107
|
element instead.
|
|
980
1108
|
- Divide-by-zero guarded (`max - min || 1`), and radial/linear share one component via the `type`
|
|
981
1109
|
attribute.
|
|
982
|
-
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
to `aria-valuenow` (the SVG arc/bar fill was already clamped via `ratio`; the ARIA number now
|
|
986
|
-
matches what's drawn).
|
|
1110
|
+
- non-finite `value` text remains blank, while non-finite `min`/`max` use finite default domain
|
|
1111
|
+
bounds; no `NaN`/`Infinity` value leaks into the SVG geometry or ARIA attributes, and a finite
|
|
1112
|
+
value is clamped into the resolved domain before being announced.
|
|
987
1113
|
|
|
988
1114
|
---
|
|
989
1115
|
|
|
@@ -994,15 +1120,18 @@ opens a small menu).
|
|
|
994
1120
|
|
|
995
1121
|
**Properties:**
|
|
996
1122
|
- `rows: Record<string, unknown>[] = []` (attribute: false)
|
|
997
|
-
- `columns: CsvColumn[] = []` (attribute: false) — `{ key, label }`;
|
|
998
|
-
for **both** export formats when non-empty
|
|
999
|
-
|
|
1000
|
-
CSV
|
|
1123
|
+
- `columns: CsvColumn[] = []` (attribute: false) — `{ key, label }`; acts as a field allow-list **and**
|
|
1124
|
+
CSV header-label source for **both** export formats when non-empty. Left empty, **both** CSV and
|
|
1125
|
+
JSON fall back to the union of the rows' own keys (`key`/`label` both set to the key name) instead
|
|
1126
|
+
of CSV degrading to a header-less/blank file while only JSON had a fallback — so an unconfigured
|
|
1127
|
+
export still produces a proper header + data file in either format
|
|
1001
1128
|
- `filename: string = 'export'`
|
|
1002
1129
|
- `formats: ExportFormat[] = ['csv']` (attribute: false; `ExportFormat = 'csv' | 'json'` **only** —
|
|
1003
1130
|
no XLSX despite the family sometimes being described as covering it)
|
|
1004
|
-
- `disabled: boolean = false` (reflected)
|
|
1005
|
-
|
|
1131
|
+
- `disabled: boolean = false` (reflected) — also disables every `[part="menu-item"]` button, not just
|
|
1132
|
+
the trigger
|
|
1133
|
+
- `label: string = 'Export'` — trigger button text; also feeds the format-choice menu's `aria-label`
|
|
1134
|
+
as `` `${label} format` `` so assistive tech gets an accessible name for the menu
|
|
1006
1135
|
- `open: boolean = false` (reflected)
|
|
1007
1136
|
|
|
1008
1137
|
**Events:** `lyra-export` (`detail: { format }`, **cancelable** — call `preventDefault()` to
|
|
@@ -1083,6 +1212,20 @@ auto-inserted between each adjacent pair.
|
|
|
1083
1212
|
- `floatBreakpoint: number = 400` (attribute `float-breakpoint`, px) — below this narrower container
|
|
1084
1213
|
width, the collapsing panel instead becomes an absolutely-positioned overlay ("floating card") on
|
|
1085
1214
|
top of the other pane(s), removed from the normal flex flow; the sibling(s) take the full width.
|
|
1215
|
+
- `collapseState: 'wide'|'rail'|'floating'` (reflected, attribute `collapse-state`) — a public
|
|
1216
|
+
accessor with force/auto semantics mirroring `<lyra-app-rail>`'s `mode`: normally derived
|
|
1217
|
+
automatically from the measured container width, but assigning it a concrete value pins it there
|
|
1218
|
+
(stopping automatic breakpoint tracking) — useful for a consumer-driven toggle (e.g. a button that
|
|
1219
|
+
forces `'floating'` regardless of width). Assigning the write-only `'auto'` sentinel releases the
|
|
1220
|
+
pin and immediately re-derives the state from the current measured width; the getter never returns
|
|
1221
|
+
`'auto'`.
|
|
1222
|
+
- `open: boolean = false` (reflected) — whether the `'floating'` collapse state's drawer is shown.
|
|
1223
|
+
While `collapseState` is `'floating'` and `open` is `false` (the default), the collapsing panel
|
|
1224
|
+
renders nothing (`hidden`, out of the accessibility tree) instead of the always-visible overlay
|
|
1225
|
+
card this state rendered before `open` existed. Setting `open = true` reveals it as a
|
|
1226
|
+
focus-trapped floating panel with a `[part="backdrop"]` scrim; Escape or a backdrop click set
|
|
1227
|
+
`open` back to `false`. Leaving `'floating'` while `open` is still `true` also closes it, the same
|
|
1228
|
+
way `<lyra-app-rail>` closes its mobile overlay when leaving `'mobile'` while open.
|
|
1086
1229
|
|
|
1087
1230
|
`collapse`'s three resulting states — `'wide'` (default, today's plain layout) / `'rail'` / `'floating'`
|
|
1088
1231
|
— are exposed as: a `data-collapse-state` attribute on both the host and the collapsing panel element
|
|
@@ -1092,12 +1235,17 @@ collapsed. `collapse="none"` (the default) is byte-for-byte identical to pre-col
|
|
|
1092
1235
|
|
|
1093
1236
|
**Events:** `lyra-resize` (`detail: { sizes }`, fired on every drag step/release **and** every
|
|
1094
1237
|
keyboard step), `lyra-split-collapse-change` (`detail: { state: 'wide'|'rail'|'floating' }`, fired only
|
|
1095
|
-
on a real `collapse`-state transition, never on every resize/render)
|
|
1238
|
+
on a real `collapse`-state transition, never on every resize/render),
|
|
1239
|
+
`lyra-split-constraints-invalid` (`detail: SplitConstraintIssueDetail`, fired once when the configured
|
|
1240
|
+
panel minimums/maximums cannot fit the track; the infeasible set is rejected for interaction and a
|
|
1241
|
+
normalized percent minimum is used instead)
|
|
1096
1242
|
|
|
1097
1243
|
**Slots:** default (each direct child element is one panel).
|
|
1098
1244
|
|
|
1099
1245
|
**CSS parts:** `base` (`position: relative`, so the `'floating'` state can anchor to it), `divider`
|
|
1100
|
-
(carries `aria-disabled="true"` and is drag/keyboard-inert while its adjacent panel is collapsed)
|
|
1246
|
+
(carries `aria-disabled="true"` and is drag/keyboard-inert while its adjacent panel is collapsed),
|
|
1247
|
+
`backdrop` (the `'floating'` drawer's scrim — only rendered while `collapseState === 'floating'` and
|
|
1248
|
+
`open`)
|
|
1101
1249
|
|
|
1102
1250
|
**Themeable custom properties:** shared tokens only.
|
|
1103
1251
|
|
|
@@ -1128,8 +1276,9 @@ the visually-adjacent panel).
|
|
|
1128
1276
|
`aria-valuemin`/`aria-valuemax` report. Each divider also now has its own `aria-label` ("Resize
|
|
1129
1277
|
divider between panel N and panel N+1") distinguishing it from any other divider in a
|
|
1130
1278
|
multi-divider layout.
|
|
1131
|
-
-
|
|
1132
|
-
|
|
1279
|
+
- infeasible aggregate constraints (for example, three panels with `min=40`) are reported through
|
|
1280
|
+
`lyra-split-constraints-invalid`; interaction rejects that set and uses a normalized percent minimum
|
|
1281
|
+
with aggregate slack, so the divider remains operable instead of silently freezing.
|
|
1133
1282
|
- concurrent drags are tracked per `pointerId` (not a single scalar), so a multi-touch drag on two
|
|
1134
1283
|
different dividers moves both independently instead of the second pointer clobbering the first's
|
|
1135
1284
|
drag state; `pointercancel`/`lostpointercapture` (not just `pointerup`) both end a drag.
|
|
@@ -1201,10 +1350,10 @@ fullscreen panel/backdrop — it overrides the default `var(--lyra-space-l)` ins
|
|
|
1201
1350
|
both `[part="base"]` and `[part="backdrop"]`. Set `compact` for tighter header/body padding.
|
|
1202
1351
|
|
|
1203
1352
|
**Known gotchas:**
|
|
1204
|
-
- a reconnect that preserves the same element instance (e.g. a drag-and-drop reparent)
|
|
1205
|
-
|
|
1206
|
-
move — `disconnectedCallback`/`connectedCallback` fire back-to-back with no update in
|
|
1207
|
-
`willUpdate()` alone wouldn't otherwise notice.
|
|
1353
|
+
- a reconnect that preserves the same element instance (e.g. a drag-and-drop reparent) resumes its
|
|
1354
|
+
shared overlay registration and re-acquires the scroll lock if `fullscreen` was still `true`
|
|
1355
|
+
across the move — `disconnectedCallback`/`connectedCallback` fire back-to-back with no update in
|
|
1356
|
+
between, so `willUpdate()` alone wouldn't otherwise notice.
|
|
1208
1357
|
- `collapsed` hides the body via `hidden` rather than an animated height transition — collapsing is
|
|
1209
1358
|
instant, not a slide.
|
|
1210
1359
|
|
|
@@ -1215,15 +1364,27 @@ both `[part="base"]` and `[part="backdrop"]`. Set `compact` for tighter header/b
|
|
|
1215
1364
|
Dependency-free SVG word/tag cloud. First-party invention (no Web Awesome equivalent). Lays words
|
|
1216
1365
|
out via an outward Archimedean-spiral search — heaviest word placed first, each word spiraling from
|
|
1217
1366
|
the center until it clears every word already placed. Unlike sibling `lyra-sparkline`/`lyra-heatmap`
|
|
1218
|
-
(one `role="img"` glyph standing in for an aggregate value),
|
|
1219
|
-
|
|
1367
|
+
(one `role="img"` glyph standing in for an aggregate value), the individual words here *are* the
|
|
1368
|
+
meaningful interactive content — but with up to `MAX_WORDS` (150) of them, making every single one
|
|
1369
|
+
its own tab stop would be a poor keyboard experience. Instead, like `lyra-heatmap`'s cells, the whole
|
|
1370
|
+
`[part="svg"]` is **one tab stop with roving arrow-key focus**: `ArrowRight`/`ArrowDown` move the
|
|
1371
|
+
focus cursor to the next word in **declaration order** (not weight/placement order),
|
|
1372
|
+
`ArrowLeft`/`ArrowUp` to the previous, `Home`/`End` to the first/last, and `Enter`/`Space` fires
|
|
1373
|
+
`lyra-word-click` for the currently-focused word. A `[part="focus-ring"]` `<rect>` is drawn around
|
|
1374
|
+
the focused word (absent until a word has actually been focused via keyboard or click), and a
|
|
1375
|
+
visually-hidden `[part="live-region"]` (`role="status" aria-live="polite"`) announces
|
|
1376
|
+
`"${text}, ${weight}"` on every focus move.
|
|
1220
1377
|
|
|
1221
1378
|
**Properties:**
|
|
1222
1379
|
- `words: WordCloudWord[] = []` (attribute: false) — `{ text: string, weight: number, color?:
|
|
1223
|
-
string, group?: string }`; `weight` drives font size
|
|
1224
|
-
`
|
|
1225
|
-
|
|
1226
|
-
|
|
1380
|
+
string, group?: string }`; `weight` drives font size (a negative/non-finite `weight` is clamped to
|
|
1381
|
+
`0` for sizing purposes only — the original value is still echoed verbatim in `lyra-word-click`'s
|
|
1382
|
+
`detail`), `color` overrides the palette for that word, `group` shares one palette color across
|
|
1383
|
+
every word with the same `group` value
|
|
1384
|
+
- `minFontSize: number = 12` (attribute `min-font-size`) — px, applied to the lowest-weight word;
|
|
1385
|
+
layout clamps positive finite values to at most 512px and uses 1px for invalid/non-positive values
|
|
1386
|
+
- `maxFontSize: number = 48` (attribute `max-font-size`) — px, applied to the highest-weight word;
|
|
1387
|
+
normalized by the same 1–512px layout bound (reversed min/max bounds are swapped)
|
|
1227
1388
|
- `scale: 'linear'|'sqrt' = 'linear'` — `sqrt` compresses the weight→font-size mapping so one heavy
|
|
1228
1389
|
word doesn't dwarf the rest, matching `lyra-heatmap`'s `scale` property
|
|
1229
1390
|
- `orientations: 'horizontal'|'mixed' = 'horizontal'` — `mixed` lets ~25% of words render rotated
|
|
@@ -1231,12 +1392,20 @@ the center until it clears every word already placed. Unlike sibling `lyra-spark
|
|
|
1231
1392
|
- `palette?: string[]` (attribute: false) — custom categorical colors, cycled by word index (or by
|
|
1232
1393
|
`group`); defaults to the `--lyra-word-cloud-color-1..8` tokens
|
|
1233
1394
|
|
|
1395
|
+
**Methods:** `refreshTheme(): void` — forces a relayout so the `--lyra-font` custom property is
|
|
1396
|
+
re-read from computed style (font-family affects the canvas text measurement layout depends on);
|
|
1397
|
+
call this from your own theme-toggle handler, since there's no global theme-change event to
|
|
1398
|
+
subscribe to automatically (mirrors `lyra-chart`'s `refreshTheme()`).
|
|
1399
|
+
|
|
1234
1400
|
**Events:** `lyra-word-click` (`detail: { text, weight, group }`, fires on click, or Enter/Space on
|
|
1235
|
-
a focused
|
|
1401
|
+
the currently-focused word — a no-op if nothing is focused yet)
|
|
1236
1402
|
|
|
1237
1403
|
**Slots:** none.
|
|
1238
1404
|
|
|
1239
|
-
**CSS parts:** `base`, `svg`, `word` (each `<text>`), `
|
|
1405
|
+
**CSS parts:** `base`, `svg`, `word` (each `<text>`), `focus-ring` (the rect around the roving-focus
|
|
1406
|
+
cursor's word), `live-region` (visually-hidden `role="status" aria-live="polite"` announcement text),
|
|
1407
|
+
`empty` (the no-data placeholder — hardcoded `"No data"` text, no property to customize it, unlike
|
|
1408
|
+
`lyra-empty`/`lyra-table`)
|
|
1240
1409
|
|
|
1241
1410
|
**Themeable custom properties:** `--lyra-word-cloud-color-1` through `-8` (the default categorical
|
|
1242
1411
|
palette, a data-driven literal exception like `lyra-heatmap`'s scale-ramp endpoints — exposed as
|
|
@@ -1257,20 +1426,34 @@ retheme-able custom properties instead of hardcoded), plus shared tokens (`--lyr
|
|
|
1257
1426
|
</script>
|
|
1258
1427
|
```
|
|
1259
1428
|
|
|
1429
|
+
The host itself gets `role="group"` and an auto-computed `aria-label` (e.g. `"Word cloud of 12
|
|
1430
|
+
words"` / `"Word cloud of 1 word"`, counting only words actually rendered — post `MAX_WORDS`-cap and
|
|
1431
|
+
post-drop, not the raw `words.length`) **unless** the host already carried its own `role`/`aria-label`
|
|
1432
|
+
attribute at first render. That opt-out check runs exactly once (the very first update) and is never
|
|
1433
|
+
re-checked afterwards, so setting `role`/`aria-label` yourself *after* the component has already
|
|
1434
|
+
rendered at least once only sticks until the next `words`-driven relayout, which overwrites it back
|
|
1435
|
+
to the auto default — set it in the initial markup (or before first paint) to opt out permanently.
|
|
1436
|
+
|
|
1260
1437
|
**Known gotchas:**
|
|
1261
1438
|
- capped at 150 words (`MAX_WORDS` in `word-cloud-layout.ts`, mirroring `lyra-sparkline`'s
|
|
1262
|
-
`MAX_BARS` DOM-node-count guard) —
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
-
|
|
1270
|
-
|
|
1439
|
+
`MAX_BARS` DOM-node-count guard) — over the cap, the **heaviest** 150 survive and the rest are
|
|
1440
|
+
dropped, regardless of where they fell in the input array (it is not simply "first 150 in, rest
|
|
1441
|
+
dropped"). A pathological input (e.g. one huge word repeated many times) can also exhaust the
|
|
1442
|
+
spiral search's radius bound and get dropped the same way; blank/whitespace-only `text` is dropped
|
|
1443
|
+
too. Every drop reason logs one deduplicated `console.warn` per distinct skipped-count (not one
|
|
1444
|
+
warning per word, and not repeated twice for the same count) — nothing throws.
|
|
1445
|
+
- each word's spiral search tests at most 4,096 candidate positions. Together with the 150-word and
|
|
1446
|
+
512px font-size caps, this bounds placement work even for dense or adversarial layouts; a word
|
|
1447
|
+
that exhausts the search budget is reported through the same skipped-word path.
|
|
1448
|
+
- text width is measured via a detached `<canvas>` 2D context (`ctx.measureText`) at a hardcoded
|
|
1449
|
+
`font-weight: 600` matching `[part='word']`'s own default CSS — close enough for
|
|
1450
|
+
collision-avoidance spacing, but not pixel-exact, and overriding `[part='word']`'s `font-weight` via
|
|
1451
|
+
`::part()` desyncs measurement from what's actually painted (looser/denser packing, not a crash).
|
|
1271
1452
|
- rotation (`orientations="mixed"`) is genuinely random per layout (`Math.random()`, not seeded), so
|
|
1272
1453
|
which words render rotated changes on every re-layout (any `words`/`minFontSize`/`maxFontSize`/
|
|
1273
1454
|
`scale`/`orientations` change) — don't rely on rotation being stable across renders.
|
|
1455
|
+
- only one word is ever in the page's tab sequence at a time (the roving cursor on `[part="svg"]`) —
|
|
1456
|
+
there's no way to Tab directly to the Nth word; arrow-key/Home/End your way there, or click it.
|
|
1274
1457
|
|
|
1275
1458
|
---
|
|
1276
1459
|
|
|
@@ -1279,7 +1462,10 @@ retheme-able custom properties instead of hardcoded), plus shared tokens (`--lyr
|
|
|
1279
1462
|
## `lyra-time-range`
|
|
1280
1463
|
|
|
1281
1464
|
A two-handle brush/scrubber over a numeric domain (no date logic — callers map their own time axis
|
|
1282
|
-
onto `[min, max]`).
|
|
1465
|
+
onto `[min, max]`). Form-associated (`static formAssociated = true`, via `ElementInternals`): an
|
|
1466
|
+
ancestor `<fieldset disabled>` disables both handles and every preset button through an internal
|
|
1467
|
+
`effectiveDisabled` getter, the same way it would a native `<input>`, without ever mutating the
|
|
1468
|
+
consumer-facing `disabled` property/attribute itself.
|
|
1283
1469
|
|
|
1284
1470
|
**Properties:**
|
|
1285
1471
|
- `min: number = 0`
|
|
@@ -1288,6 +1474,8 @@ onto `[min, max]`).
|
|
|
1288
1474
|
- `end: number = 100`
|
|
1289
1475
|
- `step: number = 1`
|
|
1290
1476
|
- `disabled: boolean = false` (reflected)
|
|
1477
|
+
- `startLabel: string = 'Range start'` (attribute `start-label`) — `aria-label` for the start handle
|
|
1478
|
+
- `endLabel: string = 'Range end'` (attribute `end-label`) — `aria-label` for the end handle
|
|
1291
1479
|
- `presets: TimeRangePreset[] = []` (attribute: false) — `TimeRangePreset { label: string; start:
|
|
1292
1480
|
number; end: number }`; optional discrete presets (e.g. "Last 7 days") rendered as a
|
|
1293
1481
|
`[part="presets"]` button row above the track — purely additive, the continuous brush is
|
|
@@ -1303,9 +1491,10 @@ key-up-commit, or when a preset button is clicked, `detail: { start, end }`)
|
|
|
1303
1491
|
**CSS parts:** `base`, `track`, `range`, `handle-start`, `handle-end`, `presets`, `preset-button`
|
|
1304
1492
|
|
|
1305
1493
|
**Themeable custom properties:** shared tokens only — `--lyra-color-border`, `--lyra-color-brand`,
|
|
1306
|
-
`--lyra-color-surface`, `--lyra-shadow` (track/handles),
|
|
1307
|
-
`--lyra-color-on-brand` (the active preset
|
|
1308
|
-
`--lyra-
|
|
1494
|
+
`--lyra-color-surface`, `--lyra-shadow` (track/handles), `--lyra-opacity-disabled` (`:host([disabled])`
|
|
1495
|
+
dimming), plus (for `presets`) `--lyra-color-text`, `--lyra-color-on-brand` (the active preset
|
|
1496
|
+
button's text), `--lyra-radius`, `--lyra-space-xs/-s`, `--lyra-transition-fast`,
|
|
1497
|
+
`--lyra-focus-ring-*`. No component-local tokens.
|
|
1309
1498
|
|
|
1310
1499
|
**Optional peer deps:** none.
|
|
1311
1500
|
|
|
@@ -1339,6 +1528,14 @@ key-up-commit, or when a preset button is clicked, `detail: { start, end }`)
|
|
|
1339
1528
|
moves both independently instead of the second pointer hijacking which handle the first pointer's
|
|
1340
1529
|
moves apply to; `pointercancel`/`lostpointercapture` (not just `pointerup`) both end a drag, same
|
|
1341
1530
|
fix as `lyra-split`.
|
|
1531
|
+
- Non-finite domain/handle values use finite fallback geometry, and non-finite or negative steps are
|
|
1532
|
+
treated as unstepped; invalid values never become `NaN`/`Infinity` CSS or ARIA strings.
|
|
1533
|
+
- `startLabel`/`endLabel` only override each handle's `aria-label`; they don't affect
|
|
1534
|
+
`aria-valuenow`/`aria-valuemin`/`aria-valuemax` (still raw numbers) or any visible text.
|
|
1535
|
+
- An ancestor `<fieldset disabled>` toggling is reflected via `formDisabledCallback` into
|
|
1536
|
+
`effectiveDisabled` (tracked separately from the consumer's own `disabled`), so re-enabling the
|
|
1537
|
+
fieldset correctly restores a handle that had `disabled` set explicitly by the consumer, and vice
|
|
1538
|
+
versa — mirrors `lyra-combobox`'s identical pattern.
|
|
1342
1539
|
|
|
1343
1540
|
---
|
|
1344
1541
|
|
|
@@ -1367,7 +1564,8 @@ tick and manual step)
|
|
|
1367
1564
|
|
|
1368
1565
|
**Themeable custom properties:** shared tokens only — `--lyra-space-s`, `--lyra-color-border`,
|
|
1369
1566
|
`--lyra-color-surface`, `--lyra-color-text`, `--lyra-color-brand`, `--lyra-icon-button-size` (the
|
|
1370
|
-
play button's box)
|
|
1567
|
+
play button's box), `--lyra-opacity-disabled` (play button/slider dimming at `length <= 1`),
|
|
1568
|
+
`--lyra-focus-ring-*`.
|
|
1371
1569
|
|
|
1372
1570
|
**Optional peer deps:** none.
|
|
1373
1571
|
|
|
@@ -1389,6 +1587,12 @@ play button's box).
|
|
|
1389
1587
|
long-lived `setInterval`), so `intervalMs` is re-read fresh before every tick — changing
|
|
1390
1588
|
`interval-ms` while `playing` takes effect on the very next step instead of only after a
|
|
1391
1589
|
pause/play cycle.
|
|
1590
|
+
- `length` and `index` are normalized to finite non-negative integer counts, with `index` clamped
|
|
1591
|
+
into `[0, length)`; fractional, negative, `NaN`, infinite, and oversized values cannot poison
|
|
1592
|
+
end conditions or the slider.
|
|
1593
|
+
- `interval-ms` is clamped to the 16ms floor and the browser's finite timer ceiling: a non-finite or
|
|
1594
|
+
lower value ticks at 16ms, while an oversized value uses the timer ceiling. Each distinct invalid
|
|
1595
|
+
value is warned once (deduplicated per value, not a single once-ever flag).
|
|
1392
1596
|
- No `aria-valuetext`/visible "N of M" position label on the range input.
|
|
1393
1597
|
- Calling `play()`/`pause()` programmatically (not via the button) gives no `aria-live`
|
|
1394
1598
|
announcement of the Play/Pause state change.
|
|
@@ -1411,19 +1615,27 @@ click, or Enter/Space on the focused cell, fires `lyra-cell-click`.
|
|
|
1411
1615
|
- `colLabels: string[] = []` (attribute: false — matrix mode only)
|
|
1412
1616
|
- `values: number[][] = []` (attribute: false — matrix mode only) — `-1` or any non-finite value is
|
|
1413
1617
|
the "no data" sentinel; ragged/sparse rows are safe (`?? -1`)
|
|
1414
|
-
- `cellSize: number = 22` (attribute `cell-size` — matrix mode
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1618
|
+
- `cellSize: number = 22` (attribute `cell-size` — default `22` in matrix mode, `11` in calendar
|
|
1619
|
+
mode when left unset; explicitly setting it now governs both modes' per-cell size alike, and it's
|
|
1620
|
+
ignored in either mode when `fitToWidth` is set)
|
|
1621
|
+
- `fitToWidth: boolean = false` (attribute `fit-to-width` — derives `cellSize` from the host's
|
|
1622
|
+
measured `clientWidth` on every draw/resize instead of the fixed `cell-size`, so the grid actually
|
|
1623
|
+
fills the available width; now applies to calendar mode as well as matrix mode — see gotchas for
|
|
1624
|
+
the default, non-`fit-to-width` behavior)
|
|
1419
1625
|
- `valueLabel: string = 'value'` (attribute `value-label`)
|
|
1420
|
-
- `scale: 'linear' | 'sqrt' = 'linear'`
|
|
1626
|
+
- `scale: 'linear' | 'sqrt' = 'linear'` — governs both modes: in matrix mode, `'sqrt'` compresses the
|
|
1627
|
+
color ramp via `sqrtStep()` instead of mapping linearly; in calendar mode, the default `'linear'`
|
|
1628
|
+
still buckets by quartile (`quartileBucket()`, unchanged), while `'sqrt'` instead compresses via the
|
|
1629
|
+
same `sqrtStep()` magnitude compression as matrix mode, so one heavy day doesn't wash out the rest
|
|
1421
1630
|
- `mode: 'matrix' | 'calendar' = 'matrix'`
|
|
1422
1631
|
- `days: CalendarDay[] = []` (attribute: false — calendar mode only) — `CalendarDay { date:
|
|
1423
1632
|
string /* ISO yyyy-mm-dd */; value: number }`; need not be sorted or contiguous, and an entry whose
|
|
1424
1633
|
`date` doesn't parse is dropped rather than poisoning the whole grid
|
|
1634
|
+
- `firstDayOfWeek: number = 0` (attribute `first-day-of-week` — calendar mode only, no-op in matrix
|
|
1635
|
+
mode) — anchors the calendar grid at a different weekday instead of always Sunday; `0`-`6`, same
|
|
1636
|
+
numbering as `CalendarCellPos.weekday` (`0` Sunday .. `6` Saturday)
|
|
1425
1637
|
- `bucketCount: number = 5` (attribute `bucket-count` — calendar mode only; non-finite values fall
|
|
1426
|
-
back to 5
|
|
1638
|
+
back to 5, while finite values are floored and clamped to 2–256 before the color-ramp allocation)
|
|
1427
1639
|
- `annotations: HeatmapAnnotation[] = []` (attribute: false) — `HeatmapAnnotation { row?: number;
|
|
1428
1640
|
col?: number; date?: string; label?: string }`: matrix mode matches by `row`/`col`, calendar mode
|
|
1429
1641
|
by `date` (whichever pair matches the active `mode`; the other fields are ignored). Draws a
|
|
@@ -1432,14 +1644,21 @@ click, or Enter/Space on the focused cell, fires `lyra-cell-click`.
|
|
|
1432
1644
|
- `cellText?: (pos: MatrixCellPos | CalendarCellPos, value: number) => string` (attribute: false) —
|
|
1433
1645
|
formats the per-cell hover tooltip and keyboard live-region announcement text; receives the cell
|
|
1434
1646
|
position (`{ row, col }` in matrix mode, `{ week, weekday }` in calendar mode) and its value.
|
|
1435
|
-
Unset (the default) falls back to the built-in English "Row X, Col Y: value" / "
|
|
1436
|
-
|
|
1647
|
+
Unset (the default) falls back to the built-in English "Row X, Col Y: value" (matrix) / "Jan 15:
|
|
1648
|
+
value" — short month + day, **not** a weekday abbreviation (calendar) — template. Additive, not
|
|
1649
|
+
breaking.
|
|
1437
1650
|
- `columnX?: (index: number) => number` (attribute: false, calendar mode only) — overrides the
|
|
1438
1651
|
internal week-column x-coordinate formula (`CAL_PAD_LEFT + week * (CAL_CELL + CAL_GAP)`) used
|
|
1439
1652
|
consistently across drawing, hit-testing, the focus ring, and month-label positioning, so a
|
|
1440
1653
|
consumer can pixel-align this calendar's week columns with a sibling `<lyra-lite-chart>`'s bars
|
|
1441
1654
|
(see that component's own `barX`) by supplying the same coordinate function to both. Unset (the
|
|
1442
1655
|
default) is the original formula, unchanged.
|
|
1656
|
+
- `rowY?: (weekday: number) => number` (attribute: false, calendar mode only) — the vertical
|
|
1657
|
+
analogue of `columnX`: overrides the internal weekday-row y-coordinate formula (`CAL_LABEL_H +
|
|
1658
|
+
weekday * (cellSize + CAL_GAP)`), consulted consistently by drawing, hit-testing, and the focus
|
|
1659
|
+
ring (also consulted at `weekday = 7` to size the canvas height, mirroring `columnX` at
|
|
1660
|
+
`week = weekCount`). Unset (the default) is the original formula, unchanged. Ignored in matrix
|
|
1661
|
+
mode.
|
|
1443
1662
|
|
|
1444
1663
|
**Events:** `lyra-cell-click` (fired on click, or Enter/Space on the keyboard-focused cell —
|
|
1445
1664
|
`detail: { row, col, value }` in matrix mode, `detail: { date, value }` in calendar mode)
|
|
@@ -1466,7 +1685,9 @@ visible regardless of what it's drawn over). `--lyra-heatmap-tooltip-bg` (defaul
|
|
|
1466
1685
|
`var(--lyra-color-surface)`) and `--lyra-heatmap-tooltip-text` (default `var(--lyra-color-text)`) —
|
|
1467
1686
|
unlike the canvas-drawn tokens above, `[part="tooltip"]` is a real DOM element and consumes these
|
|
1468
1687
|
directly, no `getComputedStyle` bridging needed. Also consumes `--lyra-color-text-quiet` (axis label
|
|
1469
|
-
color) and `--lyra-
|
|
1688
|
+
color), `--lyra-space-xs`, `--lyra-radius`/`--lyra-shadow` (tooltip box), and `--lyra-focus-ring-width`/
|
|
1689
|
+
`--lyra-focus-ring-offset` (the real `[part="canvas"]:focus-visible` DOM outline, stroked in the
|
|
1690
|
+
same color as `--lyra-heatmap-focus-ring-color`).
|
|
1470
1691
|
|
|
1471
1692
|
**Optional peer deps:** none.
|
|
1472
1693
|
|
|
@@ -1493,13 +1714,12 @@ color) and `--lyra-space-xs`.
|
|
|
1493
1714
|
```
|
|
1494
1715
|
|
|
1495
1716
|
**Known gotchas:**
|
|
1496
|
-
- the `ResizeObserver` only actually resizes the drawn grid
|
|
1497
|
-
|
|
1498
|
-
`
|
|
1499
|
-
a geometric no-op; the stylesheet's
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
`weekCount`.
|
|
1717
|
+
- the `ResizeObserver` only actually resizes the drawn grid **when `fit-to-width` is set**, in either
|
|
1718
|
+
mode. Without it (the default), `draw()` sizes the canvas as `PAD_LEFT + cols * cellSize` (matrix
|
|
1719
|
+
mode) or `CAL_PAD_LEFT + weekCount * cellSize` (calendar mode), never from the host's measured
|
|
1720
|
+
width, so a container-resize redraw is a geometric no-op; the stylesheet's
|
|
1721
|
+
`canvas { inline-size: 100% }` is also dead code in that case, since `draw()` unconditionally sets
|
|
1722
|
+
an inline `canvas.style.width/height` that wins over it.
|
|
1503
1723
|
- the host is `role="group"` (not `role="img"`) with a dimensions+range summary `aria-label`
|
|
1504
1724
|
(calendar mode: a day-count + range summary instead) — `[part="canvas"]` inside it is a real
|
|
1505
1725
|
focusable, keyboard-operable, per-cell-interactive control (roving arrow-key focus,
|
|
@@ -1525,7 +1745,8 @@ A force-directed node-link diagram with pan/zoom/drag, built on `d3-force`.
|
|
|
1525
1745
|
|
|
1526
1746
|
**Properties:**
|
|
1527
1747
|
- `nodes: GraphNode[] = []` (attribute: false) — `GraphNode { id: string; label?: string; radius?:
|
|
1528
|
-
number; color?: string }`
|
|
1748
|
+
number; color?: string }`; `radius` is clamped to `[6, 24]` (an unset/non-finite value falls back
|
|
1749
|
+
to the midpoint, `15`) so a node can never render invisibly small or absurdly large
|
|
1529
1750
|
- `links: GraphLink[] = []` (attribute: false) — `GraphLink { source: string; target: string;
|
|
1530
1751
|
width?: number }` (source/target are node ids; links whose endpoints don't resolve are silently
|
|
1531
1752
|
dropped)
|
|
@@ -1547,7 +1768,8 @@ A force-directed node-link diagram with pan/zoom/drag, built on `d3-force`.
|
|
|
1547
1768
|
**CSS parts:** `base`, `svg`, `node`, `link`, `label`
|
|
1548
1769
|
|
|
1549
1770
|
**Themeable custom properties:** `--lyra-node-fill` (set inline per-node from `GraphNode.color`,
|
|
1550
|
-
falls back to `--lyra-color-brand`); also uses `--lyra-color-border
|
|
1771
|
+
falls back to `--lyra-color-brand`); also uses `--lyra-color-border` (link stroke), `--lyra-color-text`
|
|
1772
|
+
+ `--lyra-font` (label text), `--lyra-focus-ring-*` (node/link `:focus-visible` outline).
|
|
1551
1773
|
|
|
1552
1774
|
**Optional peer deps:** `d3-force`, `d3-drag`, `d3-zoom`, `d3-selection` (all four required
|
|
1553
1775
|
together; lazy-`import()`ed once per page, `console.warn` once and renders empty if missing —
|
|
@@ -1574,6 +1796,10 @@ install with `pnpm add d3-force d3-drag d3-zoom d3-selection`).
|
|
|
1574
1796
|
- `chargeStrength`/`linkDistance` **are** live-reactive post-mount now (retuned on the existing
|
|
1575
1797
|
force objects and the simulation nudged via `alpha(0.3).restart()`) — no need to also touch
|
|
1576
1798
|
`nodes`/`links` to see the effect.
|
|
1799
|
+
- `width`/`height` are also live-reactive post-mount: changing either re-centers the `forceCenter`
|
|
1800
|
+
force on the new midpoint and nudges the simulation via `alpha(0.1).restart()`, in addition to
|
|
1801
|
+
resizing the rendered `viewBox` — both branches apply independently, so setting `width` and
|
|
1802
|
+
`chargeStrength` in the same synchronous batch retunes both, not just one.
|
|
1577
1803
|
- zoom is bounded via `minZoom`/`maxZoom` (`d3-zoom`'s `.scaleExtent(...)`, live-reactive); pan/
|
|
1578
1804
|
zoom/drag are still pointer-only with no keyboard equivalent. Links (`<line part="link">`) are now
|
|
1579
1805
|
keyboard-operable too (`tabindex="0"`, `role="button"`, `aria-label`, Enter/Space), matching nodes.
|
|
@@ -1636,7 +1862,9 @@ observed internally to keep the roving `activeId` in sync with clicks).
|
|
|
1636
1862
|
**CSS parts:** `base`
|
|
1637
1863
|
|
|
1638
1864
|
**Themeable custom properties:** shared tokens `--lyra-space-xs`/`-s`, `--lyra-color-brand-quiet`,
|
|
1639
|
-
`--lyra-color-text-quiet`, `--lyra-color-border`, `--lyra-radius
|
|
1865
|
+
`--lyra-color-text-quiet`, `--lyra-color-border`, `--lyra-color-text`, `--lyra-radius`,
|
|
1866
|
+
`--lyra-focus-ring-*` (row `:focus-visible` ring, driven by `:host(:focus-visible)` since the host
|
|
1867
|
+
itself is the focusable `role="treeitem"`).
|
|
1640
1868
|
|
|
1641
1869
|
**Optional peer deps:** none.
|
|
1642
1870
|
|
|
@@ -1710,7 +1938,8 @@ Chart.js wrapper every other `lyra-*-chart` tag subclasses; supports both a simp
|
|
|
1710
1938
|
**Properties:**
|
|
1711
1939
|
- `type: LyraChartType = 'line'` — `LyraChartType = 'line' | 'bar' | 'scatter' | 'pie' | 'doughnut' |
|
|
1712
1940
|
'radar' | 'polarArea' | 'bubble'` — every type string the typed subclasses below lock `type` to is
|
|
1713
|
-
already a first-class member, so `<lyra-chart type="pie">` needs no subclass or cast to work
|
|
1941
|
+
already a first-class member, so `<lyra-chart type="pie">` needs no subclass or cast to work;
|
|
1942
|
+
unknown runtime attribute/property values fall back to `line` before reaching Chart.js
|
|
1714
1943
|
- `labels: string[] = []` (attribute: false)
|
|
1715
1944
|
- `datasets: Series[] = []` (attribute: false) — `Series { label: string; data?: (number|null)[];
|
|
1716
1945
|
points?: {x,y,label?}[]; color?: string|string[]; fill?: boolean; width?: number; dash?: boolean;
|
|
@@ -1730,7 +1959,8 @@ Chart.js wrapper every other `lyra-*-chart` tag subclasses; supports both a simp
|
|
|
1730
1959
|
meaningful for `bar`/`line` types (scatter/bubble's linear `x` scale and the radial `r` scale used
|
|
1731
1960
|
by radar/polar-area are out of scope)
|
|
1732
1961
|
- `config?: Partial<ChartConfiguration>` (attribute: false) — deep-merged over the generated
|
|
1733
|
-
config; any nested key wins without clobbering sibling generated keys
|
|
1962
|
+
config; any nested key wins without clobbering sibling generated keys. This is the raw Chart.js
|
|
1963
|
+
escape hatch, so a caller-supplied `config.type` is passed through rather than normalized.
|
|
1734
1964
|
|
|
1735
1965
|
**Methods:** `resetZoom()` (reset any active zoom/pan to the original view), `refreshTheme()`
|
|
1736
1966
|
(forces a redraw so the `--lyra-chart-*` tokens below are re-read from the current computed style —
|
|
@@ -1753,11 +1983,16 @@ properties only cascade downward); `--lyra-chart-grid-color` (default `var(--lyr
|
|
|
1753
1983
|
(default `var(--lyra-color-text)`), `--lyra-chart-tooltip-bg` (default `var(--lyra-color-surface)`),
|
|
1754
1984
|
`--lyra-chart-tooltip-text` (default `var(--lyra-color-text)`) — each resolved fresh via
|
|
1755
1985
|
`getComputedStyle` on every draw (Chart.js renders to canvas, not the DOM, so it can't consume CSS
|
|
1756
|
-
`var()` directly), driving the grid lines, tick labels
|
|
1757
|
-
|
|
1986
|
+
`var()` directly), driving the grid lines, tick labels **and axis titles** (`xLabel`/`yLabel`/
|
|
1987
|
+
`y2Label` title text reuses `--lyra-chart-tick-color` too — there's no separate title-color token),
|
|
1988
|
+
legend text, and tooltip background/text respectively; plus shared `--lyra-space-xs`.
|
|
1758
1989
|
|
|
1759
|
-
**Optional peer deps:** `chart.js
|
|
1760
|
-
`
|
|
1990
|
+
**Optional peer deps:** `chart.js` (mandatory peer, lazy-imported on every `connectedCallback()`
|
|
1991
|
+
regardless of `zoom`), `chartjs-plugin-zoom` (lazy-imported *additionally* only when `zoom` is — or
|
|
1992
|
+
later becomes — `true`; never fetched for a chart that keeps `zoom` unset/false, since the plugin
|
|
1993
|
+
has a hard dependency on `hammerjs`). Both loads are memoized once per page via `chart-loader.ts`
|
|
1994
|
+
(`loadChartJs()` / `loadChartJsWithZoom()`), registering only the tree-shaken controller/element/scale
|
|
1995
|
+
subset actually used.
|
|
1761
1996
|
|
|
1762
1997
|
```html
|
|
1763
1998
|
<lyra-chart type="line" x-label="Day" y-label="kWh" legend></lyra-chart>
|
|
@@ -1777,20 +2012,26 @@ respectively; plus shared `--lyra-space-xs`.
|
|
|
1777
2012
|
a consumer flipping something upstream (e.g. a `data-theme` attribute) that doesn't otherwise
|
|
1778
2013
|
change any `lyra-chart` property must call `refreshTheme()` itself to make Chart.js re-read the
|
|
1779
2014
|
`--lyra-chart-*` tokens above.
|
|
1780
|
-
- generated `scales` are
|
|
1781
|
-
`
|
|
1782
|
-
|
|
1783
|
-
|
|
2015
|
+
- generated `scales` are keyed off the *effective* type (`config.type` ?? `type`, see
|
|
2016
|
+
`effectiveType()`) and are type-appropriate: no scale at all for `type="pie"`/`"doughnut"` (true of
|
|
2017
|
+
`<lyra-chart type="pie">` directly, not just the `lyra-pie-chart`/`lyra-doughnut-chart` subclasses),
|
|
2018
|
+
and a single radial `r` scale (respecting `beginAtZero`) for `type="radar"`/`"polarArea"`
|
|
2019
|
+
(`lyra-radar-chart`/`lyra-polar-area-chart`), instead of always generating the cartesian `x`/`y`/
|
|
2020
|
+
`y2` block. `xLabel`/`yLabel`/`y2Label` are still silently inert for all four of those types (a
|
|
1784
2021
|
radial scale and "no scale" both have nowhere to put an axis title) — reach a titled radial scale
|
|
1785
2022
|
only via raw `config`.
|
|
1786
2023
|
- No `chartjs-plugin-annotation` is registered by default — reachable only by importing it
|
|
1787
2024
|
separately and using the raw `config` passthrough (Chart.js's registry is a global singleton).
|
|
1788
|
-
- while the `chart.js` peer is resolving,
|
|
1789
|
-
`aria-busy="true"`
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
`
|
|
2025
|
+
- while the `chart.js` peer is resolving, `render()` swaps in a `<lyra-skeleton variant="rect">` for
|
|
2026
|
+
the canvas, and the **host element itself** (not the skeleton) carries `aria-busy="true"` — set/
|
|
2027
|
+
cleared in `updated()` off the private `loading` state (same lazy-load pattern as
|
|
2028
|
+
`lyra-graph`/`lyra-map`/`lyra-flag`). Chart.js's own ~1000ms draw-in animation only ever fires on
|
|
2029
|
+
initial construction or a type change that rebuilds the `Chart` instance (every in-place data
|
|
2030
|
+
update already passes `'none'` to `Chart#update()` and never animates regardless); that
|
|
2031
|
+
construction-time animation is additionally skipped outright under `prefers-reduced-motion: reduce`.
|
|
2032
|
+
The raw `config` passthrough is deep-merged with `__proto__`/`constructor`/`prototype` keys skipped
|
|
2033
|
+
unconditionally, so a JSON-sourced `config` (e.g. parsed from an API response) can't reach up and
|
|
2034
|
+
pollute `Object.prototype` through the merge.
|
|
1794
2035
|
- lazy-redraw + memoization: an `IntersectionObserver` gates `draw()` — while the host is scrolled
|
|
1795
2036
|
off-screen, property changes that would otherwise trigger a Chart.js redraw are skipped (and a
|
|
1796
2037
|
single redraw fires once it re-enters the viewport). Independently, a content-signature check
|
|
@@ -1843,6 +2084,25 @@ passthrough). Not a subclass of `LyraChart`.
|
|
|
1843
2084
|
consumer can pixel-align this chart's bars with a sibling `<lyra-heatmap>` calendar's week columns
|
|
1844
2085
|
(see that component's own `columnX`) by supplying the same coordinate function to both. Unset (the
|
|
1845
2086
|
default) is the original formula, unchanged.
|
|
2087
|
+
- `pointText?: (label: string, value: number, datasetIndex: number) => string` (attribute: false) —
|
|
2088
|
+
overrides the per-bar/per-point `<title>`/`aria-label` tooltip text (mirrors `lyra-heatmap`'s
|
|
2089
|
+
`cellText`). Falls back to the built-in raw-value template when unset.
|
|
2090
|
+
- `roundedBars: boolean = false` (attribute `rounded-bars`, bar type only) — draws each bar as a
|
|
2091
|
+
rounded-top-corner shape instead of a square-cornered `<rect>`.
|
|
2092
|
+
- `skipZero: boolean = false` (attribute `skip-zero`, bar type only) — omits a bar entirely (no
|
|
2093
|
+
mark/tabindex/tooltip) for a value that is exactly `0`; `null`/non-finite values are always
|
|
2094
|
+
skipped regardless.
|
|
2095
|
+
- `padLeft?: number` (attribute `pad-left`) — overrides the internal 36px `PAD_LEFT` plot-left-
|
|
2096
|
+
padding constant. Unset keeps the fixed 36px.
|
|
2097
|
+
- `barGapRatio?: number` (attribute `bar-gap-ratio`) — overrides the internal 0.2 `BAR_GROUP_GAP`
|
|
2098
|
+
fraction of a category slot left as a gap between categories. Unset keeps the fixed 0.2.
|
|
2099
|
+
- `scale: 'linear' | 'sqrt' = 'linear'` (bar type only) — `'sqrt'` maps a bar's value to height via
|
|
2100
|
+
`Math.sqrt(value / domainMax)` instead of the standard linear `niceDomain` fraction (mirroring
|
|
2101
|
+
`lyra-heatmap`'s matrix-mode `sqrt` scale), so a skewed dataset's smaller bars aren't washed out
|
|
2102
|
+
by one dominant value; gridlines/tick labels stay on the linear domain either way, and `type="line"`
|
|
2103
|
+
ignores it entirely.
|
|
2104
|
+
- `hideAxis: boolean = false` (attribute `hide-axis`) — suppresses gridlines/y-axis tick labels
|
|
2105
|
+
altogether; x-axis category labels (rendered separately) are unaffected.
|
|
1846
2106
|
|
|
1847
2107
|
**Events:** `lyra-point-click` — fired when a bar/point is activated (click, or Enter/Space while
|
|
1848
2108
|
focused). `detail: { datasetIndex: number, index: number, label: string | undefined, value: number
|
|
@@ -1934,7 +2194,8 @@ Bins `values` into `bins` equal-width buckets and renders as a bar chart (extend
|
|
|
1934
2194
|
`type` fixed to `'bar'`).
|
|
1935
2195
|
|
|
1936
2196
|
**Properties:**
|
|
1937
|
-
- `bins: number = 10`
|
|
2197
|
+
- `bins: number = 10` — finite values are floored and clamped to 0–1,000 before allocation;
|
|
2198
|
+
non-finite values produce no buckets
|
|
1938
2199
|
- `values: number[] = []` (attribute: false)
|
|
1939
2200
|
- `label: string = 'Frequency'`
|
|
1940
2201
|
- `labels`/`datasets`/`type` are **derived, read-only** (installed as getter/setter pairs on the
|
|
@@ -1957,6 +2218,8 @@ Bins `values` into `bins` equal-width buckets and renders as a bar chart (extend
|
|
|
1957
2218
|
**Known gotchas:**
|
|
1958
2219
|
- `bins <= 0` no longer crashes: `binValues()` now returns an empty bucket array for `binCount <= 0`
|
|
1959
2220
|
(or empty `values`), so the histogram just renders with no bars instead of throwing.
|
|
2221
|
+
- excessively large finite bin counts are capped at 1,000, preventing an attribute or direct
|
|
2222
|
+
property write from requesting an unbounded bucket array.
|
|
1960
2223
|
|
|
1961
2224
|
---
|
|
1962
2225
|
|
|
@@ -2024,12 +2287,16 @@ raw `map` escape hatch for anything unexposed.
|
|
|
2024
2287
|
`[value, color]` pair — an empty array is ignored, leaving whatever fill layer already exists, if
|
|
2025
2288
|
any, untouched, rather than being applied)
|
|
2026
2289
|
- `markers: MapMarker[] = []` (attribute: false) — `MapMarker { id?: string; lngLat: [number,
|
|
2027
|
-
number]; color?: string; label?: string;
|
|
2028
|
-
`lng,lat` key
|
|
2029
|
-
`
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2290
|
+
number]; color?: string; label?: string; unsafeHtml?: string }`; reconciled by `id` (falling back
|
|
2291
|
+
to a `lng,lat` key, disambiguated by occurrence order for duplicate-coordinate id-less markers,
|
|
2292
|
+
when `id` is omitted) so an unchanged marker isn't torn down and recreated on every `markers`
|
|
2293
|
+
reassignment — its `lngLat` **and** its popup content (`unsafeHtml`/`label`, in that precedence)
|
|
2294
|
+
are both updated in place, and the popup is removed if a later update sets neither. `unsafeHtml` is
|
|
2295
|
+
rendered via `Popup.setHTML()` — **raw markup, inline event handlers included** — only pass trusted
|
|
2296
|
+
content, sanitize anything derived from user input first; prefer `label` (`Popup.setText()`,
|
|
2297
|
+
escaped) when the content is plain text. A marker whose `color` changes for a persisting `id`
|
|
2298
|
+
can't be recolored in place (no `Marker.setColor()`) and is torn down/reconstructed instead — see
|
|
2299
|
+
gotchas.
|
|
2033
2300
|
|
|
2034
2301
|
**Getters:** `map` → the raw `maplibregl.Map` instance.
|
|
2035
2302
|
|
|
@@ -2041,7 +2308,7 @@ exists and was hit)
|
|
|
2041
2308
|
|
|
2042
2309
|
**CSS parts:** `base`, `container`, `legend`, `legend-swatch`
|
|
2043
2310
|
|
|
2044
|
-
**Themeable custom properties:** shared tokens only — `--lyra-space-s`, `--lyra-color-surface`,
|
|
2311
|
+
**Themeable custom properties:** shared tokens only — `--lyra-space-xs/-s`, `--lyra-color-surface`,
|
|
2045
2312
|
`--lyra-color-border`, `--lyra-shadow`, `--lyra-radius`.
|
|
2046
2313
|
|
|
2047
2314
|
**Optional peer deps:** `maplibre-gl` (lazy-loaded; consumer must **separately** `import
|
|
@@ -2075,16 +2342,27 @@ exists and was hit)
|
|
|
2075
2342
|
about.
|
|
2076
2343
|
- Point markers now have a declarative API (`markers`, above) with popup support — the `.map` escape
|
|
2077
2344
|
hatch and manual `new maplibregl.Marker()` are no longer the only way to place pins.
|
|
2345
|
+
- a marker whose `color` changes for a persisting `id` is torn down and reconstructed (maplibre-gl's
|
|
2346
|
+
`Marker` has no `setColor()`) rather than mutated in place — this also closes any popup the user
|
|
2347
|
+
currently has open on that marker (a fresh, closed `Popup` is built for the new instance); an
|
|
2348
|
+
accepted side effect of the reconstruction fallback, not a bug.
|
|
2078
2349
|
- No click-select visual highlight on choropleth features (no `setFeatureState` call) — clicking
|
|
2079
2350
|
only fires the event, no built-in visual feedback. Popups are still only reachable declaratively
|
|
2080
|
-
through `markers`' `
|
|
2081
|
-
raw `lyra-map-click` event.
|
|
2351
|
+
through `markers`' `unsafeHtml`/`label` — a choropleth-feature click still has no built-in popup,
|
|
2352
|
+
only the raw `lyra-map-click` event.
|
|
2082
2353
|
- `LegendEntry.color` is validated against a strict CSS-color-syntax allowlist before being applied
|
|
2083
2354
|
to the legend swatch's `background`, rejecting anything that isn't recognizable color syntax
|
|
2084
2355
|
(notably `url(...)`, which `background` also accepts and would otherwise fetch as soon as the
|
|
2085
2356
|
swatch renders).
|
|
2086
2357
|
- while the `maplibre-gl` peer is resolving, the host shows a `<lyra-skeleton variant="rect">` with
|
|
2087
2358
|
`aria-busy="true"` in place of the map container.
|
|
2359
|
+
- construction of the real `maplibregl.Map` (and its WebGL context) is additionally gated on this
|
|
2360
|
+
element being observed intersecting the viewport (`IntersectionObserver`), independent of whether
|
|
2361
|
+
the `maplibre-gl` peer has already loaded — an off-screen `<lyra-map>` swaps its skeleton for the
|
|
2362
|
+
empty `[part="container"]` div as soon as the peer resolves, but `map` stays `undefined` and
|
|
2363
|
+
`lyra-map-load` never fires until the element is actually scrolled into view. Deliberate: caps
|
|
2364
|
+
concurrent WebGL contexts when many `<lyra-map>`s sit in one dashboard/grid. Skipped entirely
|
|
2365
|
+
(constructs immediately once the peer loads) when `IntersectionObserver` itself is unavailable.
|
|
2088
2366
|
|
|
2089
2367
|
---
|
|
2090
2368
|
|
|
@@ -2122,7 +2400,9 @@ element carrying the drag accept/reject announcement)
|
|
|
2122
2400
|
|
|
2123
2401
|
**Themeable custom properties:** shared tokens only — `--lyra-space-xs`, `--lyra-space-l`,
|
|
2124
2402
|
`--lyra-color-border`, `--lyra-radius`, `--lyra-color-surface`, `--lyra-color-text-quiet`,
|
|
2125
|
-
`--lyra-color-success` (drag-accept state), `--lyra-color-danger` (drag-reject state)
|
|
2403
|
+
`--lyra-color-success` (drag-accept state), `--lyra-color-danger` (drag-reject state),
|
|
2404
|
+
`--lyra-focus-ring-width/-color/-offset` (`[part="base"]:focus-visible` outline),
|
|
2405
|
+
`--lyra-opacity-disabled` (`:host([disabled])` dimming).
|
|
2126
2406
|
|
|
2127
2407
|
**Optional peer deps:** none.
|
|
2128
2408
|
|
|
@@ -2167,35 +2447,58 @@ General-purpose modal/overlay plus a promise-based confirmation helper built on
|
|
|
2167
2447
|
### `lyra-dialog`
|
|
2168
2448
|
|
|
2169
2449
|
A modal/overlay: `role="dialog"`, focus-trapped while open, dismissible via Escape or a backdrop
|
|
2170
|
-
click, and scroll-locks the document for as long as it's open. Chrome stays minimal — no
|
|
2171
|
-
title bar or close button; a consumer supplies a heading and any close affordance itself via
|
|
2172
|
-
default/`footer` slots.
|
|
2450
|
+
click, and scroll-locks the document for as long as it's open. Chrome stays minimal by default — no
|
|
2451
|
+
built-in title bar or close button; a consumer supplies a heading and any close affordance itself via
|
|
2452
|
+
the default/`footer` slots. `heading`/`closable` are an opt-in convenience for the common case where
|
|
2453
|
+
hand-building that chrome isn't worth it.
|
|
2173
2454
|
|
|
2174
2455
|
**Properties:**
|
|
2175
2456
|
- `open: boolean = false` (reflected) — there is no separate `show()`/`hide()` pair; set this (or
|
|
2176
2457
|
call `close()`)
|
|
2177
|
-
- `label: string = ''` — accessible name used only when no heading is slotted
|
|
2458
|
+
- `label: string = ''` — accessible name used only when no heading is slotted and `heading` is unset
|
|
2459
|
+
(see below)
|
|
2460
|
+
- `heading?: string` — visible header text (rendered in `[part="header"]`/`[part="heading"]`), used
|
|
2461
|
+
only when no light-DOM heading is slotted into the default slot; has no effect (renders nothing)
|
|
2462
|
+
when a heading element *is* slotted, which keeps working completely unchanged either way
|
|
2463
|
+
- `closable: boolean = false` (attribute `closable`) — renders a built-in close (X) button in the
|
|
2464
|
+
header row (creating one, with no heading text, if `heading` is unset), wired to the same
|
|
2465
|
+
`close()` path Escape/backdrop-dismiss already use, with reason `'close-button'`
|
|
2178
2466
|
|
|
2179
2467
|
**Methods:** `close(reason: DialogCloseReason = 'api'): void` — closes the dialog, emits
|
|
2180
2468
|
`lyra-dialog-close` with `reason`, and returns focus to whatever had it right before the dialog
|
|
2181
|
-
opened. `DialogCloseReason = 'escape' | 'backdrop' | '
|
|
2182
|
-
emitted by the dialog's own built-in dismiss triggers;
|
|
2183
|
-
|
|
2469
|
+
opened. `DialogCloseReason = 'escape' | 'backdrop' | 'close-button' | 'api' | 'unmount' | string` —
|
|
2470
|
+
`'escape'`/`'backdrop'` are emitted by the dialog's own built-in dismiss triggers; `'close-button'`
|
|
2471
|
+
by the built-in header close button (rendered when `closable` is set); `'unmount'` is emitted
|
|
2472
|
+
automatically if the dialog is removed from the DOM while still `open` by anything other than its
|
|
2473
|
+
own `close()` (a consumer's own cleanup code, a parent re-render that drops it); any other string is
|
|
2474
|
+
whatever a caller passes (e.g. a footer Cancel button calling `dlg.close('cancel')`, or `confirm()`'s
|
|
2475
|
+
own `'confirm'`/`'cancel'`).
|
|
2184
2476
|
|
|
2185
2477
|
**Events:** `lyra-dialog-close` (`detail: DialogCloseReason`) — fired on every dismissal path
|
|
2186
|
-
(Escape, backdrop click,
|
|
2478
|
+
(Escape, backdrop click, any `close()` call, or an `'unmount'` removal as above).
|
|
2479
|
+
|
|
2480
|
+
**Stacking:** participates in the shared per-document overlay stack described above. A dialog can be
|
|
2481
|
+
stacked with another dialog or any other modal family; only the visually topmost overlay receives
|
|
2482
|
+
Escape, Tab trapping, or backdrop dismissal, while overlays beneath remain open until the top one
|
|
2483
|
+
closes.
|
|
2187
2484
|
|
|
2188
2485
|
**Slots:** default (the dialog body), `footer` (action buttons, rendered in a bottom row, hidden
|
|
2189
2486
|
entirely when empty)
|
|
2190
2487
|
|
|
2191
2488
|
**CSS parts:** `backdrop` (the full-viewport scrim), `panel` (the dialog panel, `role="dialog"`
|
|
2192
|
-
while open
|
|
2193
|
-
|
|
2489
|
+
while open; its max-inline-size is controlled by `--lyra-dialog-max-width`), `header` (the header
|
|
2490
|
+
row, rendered when `heading` is set — and no heading is slotted — and/or `closable` is `true`),
|
|
2491
|
+
`heading` (the visible `heading`-text element inside `header`, rendered only when `heading` is set
|
|
2492
|
+
and no heading is slotted), `close-button` (the built-in close button inside `header`, rendered only
|
|
2493
|
+
when `closable` is `true`), `label` (the invisible label-text element used for `aria-labelledby`
|
|
2494
|
+
when no heading is slotted and `heading` is unset), `body` (wrapper around the default slot),
|
|
2495
|
+
`footer` (wrapper around the `footer` slot)
|
|
2194
2496
|
|
|
2195
2497
|
**Themeable custom properties:** `--lyra-dialog-overlay-color` (default `rgb(0 0 0 / 0.5)` — the
|
|
2196
2498
|
backdrop scrim color; component-specific since no shared `--wa-*`/`--lyra-*` overlay token exists),
|
|
2197
|
-
|
|
2198
|
-
`--lyra-
|
|
2499
|
+
`--lyra-dialog-max-width` (default `32rem` — the panel's max-inline-size, via
|
|
2500
|
+
`min(var(--lyra-dialog-max-width, 32rem), 100%)`), plus shared tokens `--lyra-space-l/-m/-s`,
|
|
2501
|
+
`--lyra-color-surface/-border`, `--lyra-radius`, `--lyra-shadow`.
|
|
2199
2502
|
|
|
2200
2503
|
**Optional peer deps:** none.
|
|
2201
2504
|
|
|
@@ -2216,14 +2519,21 @@ plus shared tokens `--lyra-space-l/-m/-s`, `--lyra-color-surface/-border`, `--ly
|
|
|
2216
2519
|
</script>
|
|
2217
2520
|
```
|
|
2218
2521
|
|
|
2219
|
-
Accessible name
|
|
2220
|
-
child* (not inside `slot="footer"`), its text content becomes
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2522
|
+
Accessible name / visible header, in priority order: (1) if a heading element (`h1`–`h6` or
|
|
2523
|
+
`[role="heading"]`) is a *direct child* (not inside `slot="footer"`), its text content becomes
|
|
2524
|
+
`aria-label` on the panel — takes priority over `heading` below so an existing consumer that already
|
|
2525
|
+
slots its own heading keeps rendering it exactly as before; (2) otherwise, when `heading` is set, a
|
|
2526
|
+
visible header row (`[part="header"]`) renders containing that text (`[part="heading"]`), which
|
|
2527
|
+
becomes the `aria-labelledby` target; (3) otherwise, when `label` is set, an invisible (`.sr-only`,
|
|
2528
|
+
exposed as the `label` part) element carrying that text is rendered inside the panel and
|
|
2529
|
+
`aria-labelledby` points at it instead. Only one of cases 2/3 ever renders at a time. `label` itself
|
|
2530
|
+
never renders visible chrome on its own — `::part(label)` can be restyled to make the sr-only text
|
|
2531
|
+
visible, or `heading` can be set instead, for visible chrome without slotting a real heading element.
|
|
2532
|
+
The slotted-heading case (1) deliberately uses `aria-label` (a copied string) rather than
|
|
2533
|
+
`aria-labelledby` pointing at the heading's `id`, because the heading is light-DOM content while
|
|
2534
|
+
`[part="panel"]` lives in shadow DOM and an ID-reference attribute can't resolve across that
|
|
2535
|
+
boundary; cases 2/3 use `aria-labelledby` safely since their target renders inside the same shadow
|
|
2536
|
+
root it labels.
|
|
2227
2537
|
|
|
2228
2538
|
**Known gotchas:**
|
|
2229
2539
|
- `role="dialog"`/`aria-modal="true"` are only present on `[part="panel"]` while `open` is `true` —
|
|
@@ -2233,8 +2543,8 @@ and an ID-reference attribute can't resolve across that boundary.
|
|
|
2233
2543
|
`aria-label`; set `label` instead for a title that needs to change live.
|
|
2234
2544
|
- Only *direct* children are scanned for a heading — one nested several layers deep, or inside a
|
|
2235
2545
|
slotted custom element's own shadow root, is left to the consumer to label explicitly via `label`.
|
|
2236
|
-
- A reconnect that preserves the same element instance (e.g. a drag-and-drop reparent)
|
|
2237
|
-
|
|
2546
|
+
- A reconnect that preserves the same element instance (e.g. a drag-and-drop reparent) resumes its
|
|
2547
|
+
shared overlay registration and re-acquires the scroll lock if `open` was still `true` across the
|
|
2238
2548
|
move — `disconnectedCallback`/`connectedCallback` fire back-to-back with no update in between, so
|
|
2239
2549
|
`willUpdate()` alone wouldn't otherwise notice.
|
|
2240
2550
|
- Tab-trap focus order follows the default (body) slot, then the `footer` slot — the same order the
|
|
@@ -2247,7 +2557,7 @@ and an ID-reference attribute can't resolve across that boundary.
|
|
|
2247
2557
|
A drop-in async replacement for `window.confirm()`, built on `<lyra-dialog>`.
|
|
2248
2558
|
|
|
2249
2559
|
```ts
|
|
2250
|
-
import { confirm } from '@aceshooting/lyra-ui';
|
|
2560
|
+
import { confirm } from '@aceshooting/lyra-ui/components/dialog/confirm.js';
|
|
2251
2561
|
|
|
2252
2562
|
const ok = await confirm({
|
|
2253
2563
|
title: 'Delete conversation?',
|
|
@@ -2278,9 +2588,10 @@ used is still a `--lyra-*` token reference, never a raw literal.
|
|
|
2278
2588
|
`<lyra-dialog>`'s own `close()`/`lyra-dialog-close` event, so there is exactly one place that
|
|
2279
2589
|
resolves the promise and tears the dialog down — a consumer never needs to (and shouldn't) call
|
|
2280
2590
|
`.remove()` itself.
|
|
2281
|
-
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2591
|
+
- The neutral confirm button pairs `--lyra-color-on-brand` with `--lyra-color-brand`; the danger
|
|
2592
|
+
tone pairs `--lyra-color-on-danger` with `--lyra-color-danger`. Each token chains through Web
|
|
2593
|
+
Awesome's matching `*-on-loud` semantic role and has contrast-tested standalone light/dark
|
|
2594
|
+
fallbacks.
|
|
2284
2595
|
- Importing `confirm` alone is enough to register `<lyra-dialog>` — `confirm.ts` imports
|
|
2285
2596
|
`./dialog.js` for its side effect, so a consumer doesn't need a separate import for the dialog
|
|
2286
2597
|
element.
|
|
@@ -2296,6 +2607,14 @@ child, simply never produces a tab. Implements the WAI-ARIA APG tabs pattern wit
|
|
|
2296
2607
|
activation: Left/Right (swapped under RTL) move focus *and* selection together, Home/End jump to the
|
|
2297
2608
|
first/last enabled tab, and a roving `tabindex` follows whichever tab is currently selected.
|
|
2298
2609
|
|
|
2610
|
+
A tab button's *visible* content can carry a leading icon without ever changing its *accessible
|
|
2611
|
+
name* (always exactly `label`'s text): give a tab an extra direct-child sibling of `<lyra-tabs>`
|
|
2612
|
+
carrying `slot="<id>-icon"` (that sibling's own content — inline SVG, emoji span, a custom icon
|
|
2613
|
+
element, anything — is entirely up to the consumer). It renders ahead of the label inside that tab's
|
|
2614
|
+
button, wrapped in an `aria-hidden="true"` `[part="tab-icon"]` so it's excluded from accessible-name
|
|
2615
|
+
computation regardless of content. A tab with no matching `<id>-icon` sibling renders no icon
|
|
2616
|
+
wrapper at all, so existing text-only tabs are unaffected.
|
|
2617
|
+
|
|
2299
2618
|
**Properties:**
|
|
2300
2619
|
- `active: string = ''` (reflected) — the active tab's `slot`/id; falls back to the first enabled
|
|
2301
2620
|
tab whenever the current value doesn't resolve to one (including on every children/attribute
|
|
@@ -2306,13 +2625,15 @@ click or keyboard. Not fired when `active` self-corrects to a valid tab (initial
|
|
|
2306
2625
|
disappearing/becoming disabled underneath the current selection).
|
|
2307
2626
|
|
|
2308
2627
|
**Slots:** default — direct children with `slot="<id>" label="<text>"` (and optionally `disabled`);
|
|
2309
|
-
one becomes each tab's panel.
|
|
2628
|
+
one becomes each tab's panel. `<id>-icon` — optional sibling direct child supplying a tab's leading
|
|
2629
|
+
icon content; excluded from the tab button's accessible name.
|
|
2310
2630
|
|
|
2311
2631
|
**CSS parts:** `base` (root wrapper around the tablist and panels), `tablist` (the `role="tablist"`
|
|
2312
|
-
row of tab buttons), `tab` (a single tab button), `
|
|
2313
|
-
|
|
2632
|
+
row of tab buttons), `tab` (a single tab button), `tab-icon` (the optional leading-icon wrapper
|
|
2633
|
+
inside a tab button; only rendered when that tab has a matching `<id>-icon` sibling), `panel` (a
|
|
2634
|
+
single `role="tabpanel"` wrapper, one per tab, hidden unless active)
|
|
2314
2635
|
|
|
2315
|
-
**Themeable custom properties:** shared tokens only — `--lyra-space-s/-m`,
|
|
2636
|
+
**Themeable custom properties:** shared tokens only — `--lyra-space-xs/-s/-m`,
|
|
2316
2637
|
`--lyra-color-border/-text-quiet/-text/-brand`, `--lyra-transition-fast`, `--lyra-radius`,
|
|
2317
2638
|
`--lyra-focus-ring-width/-color/-offset`, `--lyra-opacity-disabled`.
|
|
2318
2639
|
|
|
@@ -2371,8 +2692,8 @@ showing the checkmark/indeterminate dash), `checkmark` (the checkmark or indeter
|
|
|
2371
2692
|
`label` (wrapper around the default slot)
|
|
2372
2693
|
|
|
2373
2694
|
**Themeable custom properties:** shared tokens only — `--lyra-space-s`, `--lyra-icon-button-size`,
|
|
2374
|
-
`--lyra-color-border/-surface/-on-brand/-brand/-text`, `--lyra-radius`,
|
|
2375
|
-
`--lyra-focus-ring-width/-color/-offset`, `--lyra-opacity-disabled`.
|
|
2695
|
+
`--lyra-color-border/-surface/-on-brand/-brand/-text/-danger`, `--lyra-radius`,
|
|
2696
|
+
`--lyra-transition-fast`, `--lyra-focus-ring-width/-color/-offset`, `--lyra-opacity-disabled`.
|
|
2376
2697
|
|
|
2377
2698
|
**Optional peer deps:** none.
|
|
2378
2699
|
|
|
@@ -2389,6 +2710,11 @@ Form-associated via a directly-attached `ElementInternals` (not the shared `Form
|
|
|
2389
2710
|
whose `value` accessor assumes a plain string default flow) with its own hand-rolled
|
|
2390
2711
|
`updateValidity()` — same shape as `<lyra-combobox>`'s and `<lyra-switch>`'s direct-`ElementInternals`
|
|
2391
2712
|
handling.
|
|
2713
|
+
Session-history/autofill restoration uses four explicit state tokens: `checked`, `unchecked`,
|
|
2714
|
+
`checked/indeterminate`, and `unchecked/indeterminate`. This preserves both public booleans while
|
|
2715
|
+
keeping an unchecked control distinguishable from a checked control whose submitted value is an
|
|
2716
|
+
empty string. Restoration updates state, form data, and validity synchronously without firing
|
|
2717
|
+
`lyra-change`.
|
|
2392
2718
|
|
|
2393
2719
|
**Known gotchas:**
|
|
2394
2720
|
- `formResetCallback()` restores `checked` to whatever the declarative `checked` attribute parsed to
|
|
@@ -2447,6 +2773,8 @@ Form-associated the same way as `<lyra-checkbox>`: a directly-attached `ElementI
|
|
|
2447
2773
|
hand-rolled `updateValidity()`, not the shared `FormAssociated` mixin. The thumb animates the
|
|
2448
2774
|
logical `inset-inline-start` property (not a physical `transform: translateX()`), so the slide
|
|
2449
2775
|
direction mirrors correctly under `dir="rtl"`.
|
|
2776
|
+
Session-history/autofill restoration uses the same explicit `checked`/`unchecked` state tokens as
|
|
2777
|
+
checkbox and does not emit `lyra-change`.
|
|
2450
2778
|
|
|
2451
2779
|
**Known gotchas:**
|
|
2452
2780
|
- `formResetCallback()` restores `checked` to the value captured from the declarative `checked`
|
|
@@ -2463,7 +2791,9 @@ A collapsible, copyable tree view for an arbitrary JSON-serializable value (obje
|
|
|
2463
2791
|
number, boolean, null, or `undefined`). Serves as a fallback renderer wherever a raw payload needs
|
|
2464
2792
|
inspecting without a bespoke view. Expand/collapse state is keyed by structural path (not object
|
|
2465
2793
|
identity), so it survives a `data` reassignment that keeps the same shape — e.g. a streaming result
|
|
2466
|
-
being patched in place.
|
|
2794
|
+
being patched in place. A container value that self-references (directly or through a longer cycle)
|
|
2795
|
+
renders as a leaf `Circular reference` marker (`data-type="circular"`) instead of recursing — no
|
|
2796
|
+
stack overflow on cyclic `data`.
|
|
2467
2797
|
|
|
2468
2798
|
**Properties:**
|
|
2469
2799
|
- `data: unknown` (attribute `false` — property-only, not settable via an HTML attribute)
|
|
@@ -2485,12 +2815,14 @@ context, older browser, a stubbed-out test environment), so a consumer can still
|
|
|
2485
2815
|
|
|
2486
2816
|
**CSS parts:** `base` (root scroll container, respects `max-height`), `toolbar` (wrapper around the
|
|
2487
2817
|
top-level copy button, only rendered when `copyable`), `tree` (wrapper around the rendered node
|
|
2488
|
-
tree), `key` (an object property key or array index label
|
|
2489
|
-
carries `data-type` of
|
|
2490
|
-
`
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2818
|
+
tree), `key` (an object property key or array index label, `data-match` while it matches `search`),
|
|
2819
|
+
`value` (a primitive value's text — carries `data-type` of
|
|
2820
|
+
`string`/`number`/`boolean`/`null`/`undefined`/`circular` for per-type coloring, and `data-match`
|
|
2821
|
+
while it matches `search`), `bracket` (a `{`, `}`, `[`, or `]` delimiter), `toggle` (a container
|
|
2822
|
+
node's expand/collapse button; hidden but present for row alignment on leaf/empty nodes),
|
|
2823
|
+
`copy-button` (a copy-to-clipboard button — the top-level one in `toolbar` (aria-label "Copy JSON to
|
|
2824
|
+
clipboard") or a per-node one (aria-label `Copy ${key/type}`, e.g. "Copy age"); only rendered when
|
|
2825
|
+
`copyable`)
|
|
2494
2826
|
|
|
2495
2827
|
**Themeable custom properties:** `--lyra-json-viewer-max-height` (default `none` — grows with content
|
|
2496
2828
|
until `max-height` is set), `--lyra-json-viewer-font` (default `ui-monospace, SFMono-Regular, Menlo,
|
|
@@ -2582,7 +2914,7 @@ window and flushes immediately.
|
|
|
2582
2914
|
|
|
2583
2915
|
**Slots:** none.
|
|
2584
2916
|
|
|
2585
|
-
**CSS parts:** `region` — the visually-hidden element carrying `role`/`aria-live`.
|
|
2917
|
+
**CSS parts:** `region` — the visually-hidden element carrying `role`/`aria-live`/`aria-atomic="true"`.
|
|
2586
2918
|
|
|
2587
2919
|
**Themeable custom properties:** none component-specific — the region is hidden via the shared
|
|
2588
2920
|
`.sr-only` helper class (`internal/a11y.ts`), not tokenized CSS.
|
|
@@ -2607,14 +2939,16 @@ A parent Lit component would instead hold the reference via `@query('lyra-live-r
|
|
|
2607
2939
|
- Re-announcing text identical to what was last written is special-cased: screen readers announce a
|
|
2608
2940
|
live region only on text-content *change*, so the component clears `textContent` first and
|
|
2609
2941
|
re-sets it on the next animation frame (not the same tick, which can coalesce into nothing ever
|
|
2610
|
-
appearing to change) to give assistive tech a real empty-to-populated transition to observe.
|
|
2942
|
+
appearing to change) to give assistive tech a real empty-to-populated transition to observe. The
|
|
2943
|
+
frame is scheduled and canceled through the region's own document window, including after the
|
|
2944
|
+
element is adopted into an iframe.
|
|
2611
2945
|
- The region's DOM is tracked outside Lit's own template bindings (`write()` mutates
|
|
2612
2946
|
`regionEl.textContent` directly) — an `announce()`/flush landing before `firstUpdated()` has run
|
|
2613
2947
|
(e.g. a consumer creates, appends, and calls `announce()` synchronously) is buffered and applied
|
|
2614
2948
|
on the next `firstUpdated()` rather than dropped.
|
|
2615
|
-
- `disconnectedCallback()` cancels any pending (unflushed) announcement
|
|
2616
|
-
animation-frame callback — an element removed
|
|
2617
|
-
|
|
2949
|
+
- `disconnectedCallback()` cancels any pending (unflushed) announcement, any before-first-render
|
|
2950
|
+
buffered write, and any in-flight re-announce animation-frame callback — an element removed before
|
|
2951
|
+
a deferred write lands silently drops it, including across a later reconnect.
|
|
2618
2952
|
- Changing `throttle-ms` updates the live `Announcer`'s window immediately, but a flush already
|
|
2619
2953
|
scheduled under the old window keeps the deadline it was scheduled with.
|
|
2620
2954
|
|
|
@@ -2626,20 +2960,34 @@ Sanitized Markdown-to-HTML rendering (GFM tables, fenced code blocks, links, blo
|
|
|
2626
2960
|
two optional peer dependencies — `marked` (parsing) and `dompurify` (sanitizing) — both lazy-loaded
|
|
2627
2961
|
independently via `markdown-loader.ts`'s `loadMarkdownDeps()` on first connect, cached per page the
|
|
2628
2962
|
same way `chart-loader.ts`/`map-loader.ts` cache their load promise so every `<lyra-markdown>`
|
|
2629
|
-
instance on a page shares one load. `heading`/`code`/`blockquote`/`table`/`link` tokens are
|
|
2630
|
-
through a `marked` renderer override that injects `part="..."` attributes directly into the
|
|
2631
|
-
HTML in a single pass (no second DOM walk after insertion).
|
|
2963
|
+
instance on a page shares one load. `heading`/`code`/`blockquote`/`table`/`link`/`image` tokens are
|
|
2964
|
+
rendered through a `marked` renderer override that injects `part="..."` attributes directly into the
|
|
2965
|
+
produced HTML in a single pass (no second DOM walk after insertion).
|
|
2632
2966
|
|
|
2633
2967
|
**Properties:**
|
|
2634
2968
|
- `content: string = ''` — the Markdown source to render
|
|
2635
2969
|
- `sanitize: boolean = true` — sanitize `marked`'s HTML output with DOMPurify before rendering
|
|
2636
2970
|
- `gfm: boolean = true` — GitHub-flavored Markdown (tables, strikethrough, autolinks, task lists)
|
|
2637
|
-
- `linkTarget: string = '_blank'` (attribute `link-target`) — `target` applied to every
|
|
2638
|
-
`<a
|
|
2971
|
+
- `linkTarget: string | null = '_blank'` (attribute `link-target`) — `target` applied to every
|
|
2972
|
+
rendered `<a>`, with `rel="noopener noreferrer"` always added alongside it whenever a `target` is
|
|
2973
|
+
emitted. `'_blank'` (the default) preserves the original output; a falsy value (`null`, or the
|
|
2974
|
+
empty string via `link-target=""`) omits `target`/`rel` entirely instead of always defaulting to
|
|
2975
|
+
`_blank`, so rendered links open in the same tab
|
|
2639
2976
|
- `internalLinkPrefix: string = ''` (attribute `internal-link-prefix`) — when set, a rendered link
|
|
2640
2977
|
whose `href` *attribute* (not the browser-resolved `.href` property) starts with this prefix is
|
|
2641
2978
|
intercepted on click and reported via `lyra-link-click` instead of navigating; empty (the default)
|
|
2642
2979
|
means every link is treated as external
|
|
2980
|
+
- `headingOffset: number = 0` (attribute `heading-offset`) — added to every rendered heading's
|
|
2981
|
+
source `token.depth` before emitting `<h${depth}>` (e.g. `heading-offset="2"` renders a source `#`
|
|
2982
|
+
as `<h3>`); clamped to `[1, 6]` so a source `######` with a positive offset stays at `<h6>` rather
|
|
2983
|
+
than overflowing past the HTML heading levels. `0` (the default) preserves the original
|
|
2984
|
+
`<h${token.depth}>` output
|
|
2985
|
+
- `eagerLoad: boolean = false` (attribute `eager-load`) — when `true`, `connectedCallback()` skips
|
|
2986
|
+
awaiting the async `loadMarkdownDeps()` import and renders synchronously if the shared
|
|
2987
|
+
`marked`/`dompurify` module cache is *already* warm (e.g. an earlier `<lyra-markdown>` instance on
|
|
2988
|
+
the page already finished loading); falls back to the normal async path (with its brief
|
|
2989
|
+
plain-text-fallback first paint) when the cache isn't warm yet. `false` (the default) is
|
|
2990
|
+
byte-identical to always taking the async path
|
|
2643
2991
|
- `streaming: boolean = false` (reflected) — forward-compatible hint for a future streaming renderer
|
|
2644
2992
|
expected to build on this component (coalescing partial tokens as they arrive); setting it has no
|
|
2645
2993
|
rendering effect yet, it only lets a consumer already target `lyra-markdown[streaming]` in CSS
|
|
@@ -2651,9 +2999,12 @@ fired whenever rendering falls back to plain text — see the fallback matrix be
|
|
|
2651
2999
|
|
|
2652
3000
|
**Slots:** none — content comes from the `content` property, not light-DOM children.
|
|
2653
3001
|
|
|
2654
|
-
**CSS parts:** `content` (the wrapper around the rendered or plain-text-fallback output
|
|
2655
|
-
|
|
2656
|
-
|
|
3002
|
+
**CSS parts:** `content` (the wrapper around the rendered or plain-text-fallback output; carries
|
|
3003
|
+
`data-fallback` while showing the plain-text fallback — still-loading peers or a failed render —
|
|
3004
|
+
so a consumer can target `lyra-markdown [part='content'][data-fallback]` to style it distinctly),
|
|
3005
|
+
`heading` (every rendered `<h1>`–`<h6>`, shifted by `heading-offset`), `code-block` (every rendered
|
|
3006
|
+
fenced/indented `<pre>`), `link` (every rendered `<a>`), `table` (every rendered `<table>`),
|
|
3007
|
+
`blockquote` (every rendered `<blockquote>`), `img` (every rendered `<img>`)
|
|
2657
3008
|
|
|
2658
3009
|
**Themeable custom properties:** `--lyra-markdown-font-mono` (default `ui-monospace, SFMono-Regular,
|
|
2659
3010
|
Menlo, Consolas, monospace` — the code/code-block font; component-specific since no shared
|
|
@@ -2718,9 +3069,10 @@ a `<lyra-markdown>`, a custom template, anything) and this component only suppli
|
|
|
2718
3069
|
chrome: alignment/coloring by `role`, an avatar/badges header row, an optional collapse toggle, an
|
|
2719
3070
|
attachments strip, and a status-aware footer (a live-updating status dot + text, the formatted
|
|
2720
3071
|
`timestamp`, a built-in retry affordance for `status="failed"`, and an `actions` slot for everything
|
|
2721
|
-
else). No built-in copy button is rendered — slot a copy control into `actions` and fire
|
|
2722
|
-
from it if you want one (matching `<lyra-json-viewer>`'s
|
|
2723
|
-
listening at the
|
|
3072
|
+
else). No built-in copy button is rendered — slot a copy control into `actions` and fire
|
|
3073
|
+
`lyra-copy` (`detail: { text: string }`) from it if you want one (matching `<lyra-json-viewer>`'s
|
|
3074
|
+
and `<lyra-code-block>`'s copy-affordance event name/shape, for anything listening at the
|
|
3075
|
+
conversation-surface level).
|
|
2724
3076
|
|
|
2725
3077
|
**Properties:**
|
|
2726
3078
|
- `role: ChatMessageRole = 'assistant'` (`'user' | 'assistant' | 'system'`) — reflects to
|
|
@@ -2885,7 +3237,8 @@ a compact call summary is useful, with or without a detail surface behind it.
|
|
|
2885
3237
|
- `category: string = ''` — optional grouping label, e.g. `research`
|
|
2886
3238
|
- `status: 'pending'|'running'|'success'|'error'|'denied' = 'pending'` (reflected) — drives the
|
|
2887
3239
|
glyph, accent color, and `status-text`; same status vocabulary as `<lyra-tool-result-dialog>` so a
|
|
2888
|
-
call's chip and its detail dialog always agree
|
|
3240
|
+
call's chip and its detail dialog always agree; unknown runtime values render the pending icon,
|
|
3241
|
+
text, and accessible label instead of failing the update
|
|
2889
3242
|
- `summary: string = ''` — short human-readable status text, e.g. `Searching web…`
|
|
2890
3243
|
- `durationMs?: number` (attribute `duration-ms`) — how long the call took, in milliseconds; the
|
|
2891
3244
|
`duration` part is omitted entirely when unset
|
|
@@ -2956,8 +3309,9 @@ sub-1000ms `"820ms"`, else trimmed to at most one decimal of seconds (`"1.5s"`,
|
|
|
2956
3309
|
## `lyra-tool-result-view`
|
|
2957
3310
|
|
|
2958
3311
|
Renders a tool call's result via whichever custom renderer a host app has registered for it,
|
|
2959
|
-
falling back to `<lyra-json-viewer>` whenever no renderer matches, a renderer's
|
|
2960
|
-
|
|
3312
|
+
falling back to `<lyra-json-viewer>` whenever no renderer matches, a candidate renderer's
|
|
3313
|
+
`matches()` predicate throws during dispatch, a renderer's optional `load()` rejects, or its
|
|
3314
|
+
`render()` throws. First-party invention (no Web Awesome equivalent). This component
|
|
2961
3315
|
owns none of the actual visual weight of a populated tool result — that's entirely whatever the
|
|
2962
3316
|
registered renderer returns; `<lyra-tool-result-view>` is just the dispatch + fallback + loading-state
|
|
2963
3317
|
shell around it.
|
|
@@ -2978,8 +3332,9 @@ shell around it.
|
|
|
2978
3332
|
once more fallback kinds land
|
|
2979
3333
|
|
|
2980
3334
|
**Events:** `lyra-render-error` (`detail: { toolName: string; error: unknown }`) — fired immediately
|
|
2981
|
-
before falling back to `<lyra-json-viewer>`, whether because no renderer matched, a
|
|
2982
|
-
`
|
|
3335
|
+
before falling back to `<lyra-json-viewer>`, whether because no renderer matched, a candidate
|
|
3336
|
+
renderer's `matches()` predicate threw during dispatch, a renderer's `load()` rejected, or its
|
|
3337
|
+
`render()` threw.
|
|
2983
3338
|
|
|
2984
3339
|
**Slots:** none.
|
|
2985
3340
|
|
|
@@ -3097,10 +3452,10 @@ registerToolRenderer('web_search', {
|
|
|
3097
3452
|
A full tool-call detail overlay: a status/duration header plus a `body` slot where a consumer
|
|
3098
3453
|
typically places a `<lyra-tabs>` with Input/Preview/JSON/Raw panels. First-party invention (no Web
|
|
3099
3454
|
Awesome equivalent). This component knows nothing about what's inside that slot — it only supplies
|
|
3100
|
-
the modal chrome around it. It
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3455
|
+
the modal chrome around it. It keeps its own shadow template rather than nesting a `<lyra-dialog>`,
|
|
3456
|
+
so slot-forwarding does not put a forwarding `<slot>` where a slotted `<lyra-tabs>`'s own light-DOM
|
|
3457
|
+
child scan expects real projected content, while its modal behavior participates in the shared
|
|
3458
|
+
overlay stack.
|
|
3104
3459
|
|
|
3105
3460
|
**Properties:**
|
|
3106
3461
|
- `open: boolean = false` (reflected) — whether the dialog is open; set this (or call `close()`) —
|
|
@@ -3151,8 +3506,8 @@ tokens `--lyra-color-surface/-border/-text-quiet/-brand/-brand-quiet/-success/-s
|
|
|
3151
3506
|
@lyra-maximize-change=${(e) => console.log('maximized:', e.detail)}
|
|
3152
3507
|
>
|
|
3153
3508
|
<lyra-tabs slot="body">
|
|
3154
|
-
<
|
|
3155
|
-
<
|
|
3509
|
+
<div slot="preview" label="Preview">…</div>
|
|
3510
|
+
<div slot="json" label="JSON"><lyra-json-viewer .data=${result}></lyra-json-viewer></div>
|
|
3156
3511
|
</lyra-tabs>
|
|
3157
3512
|
<button slot="footer">Rerun</button>
|
|
3158
3513
|
</lyra-tool-result-dialog>
|
|
@@ -3170,14 +3525,13 @@ open/close lifecycle — unlike `<lyra-widget>`'s fullscreen mode there's no sep
|
|
|
3170
3525
|
state, so no additional scroll-lock/focus-trap bookkeeping is needed for that transition alone.
|
|
3171
3526
|
|
|
3172
3527
|
**Known gotchas:**
|
|
3173
|
-
- a reconnect that preserves the same element instance (e.g. a drag-and-drop reparent)
|
|
3174
|
-
|
|
3175
|
-
`disconnectedCallback`/`connectedCallback` fire back-to-back with no intervening update, so
|
|
3176
|
-
`willUpdate` never reruns to notice `open`
|
|
3177
|
-
- this component deliberately does **not** compose `<lyra-dialog>` internally
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
real projected content rather than a forwarding `<slot>` from a nested dialog
|
|
3528
|
+
- a reconnect that preserves the same element instance (e.g. a drag-and-drop reparent) resumes its
|
|
3529
|
+
shared overlay registration and re-acquires the scroll lock if `open` was still `true` across the
|
|
3530
|
+
move — `disconnectedCallback`/`connectedCallback` fire back-to-back with no intervening update, so
|
|
3531
|
+
`willUpdate` never reruns to notice `open` did not change
|
|
3532
|
+
- this component deliberately does **not** compose `<lyra-dialog>` internally; it keeps its own
|
|
3533
|
+
panel template so a slotted `<lyra-tabs>` (or any other light-DOM-scanning child) sees real
|
|
3534
|
+
projected content rather than a forwarding `<slot>`, while still sharing the overlay stack
|
|
3181
3535
|
- `close()` is a no-op when `open` is already `false` — calling it twice in a row only fires
|
|
3182
3536
|
`lyra-dialog-close` once
|
|
3183
3537
|
- the `maximize`/`close` buttons are always the first elements in the Tab order while open,
|
|
@@ -3194,6 +3548,11 @@ built-in send/stop button. **Form-associated** via the shared `FormAssociated` m
|
|
|
3194
3548
|
`checkValidity()`/`reportValidity()`, so it participates in native `<form>` submission/validation/
|
|
3195
3549
|
reset like any other text control.
|
|
3196
3550
|
|
|
3551
|
+
The inner textarea mirrors `required` through native `required`/`aria-required`. Its
|
|
3552
|
+
`aria-invalid` remains false until the textarea has been blurred, then follows the host's
|
|
3553
|
+
`ElementInternals` validity as `value` or `required` changes. A native validation attempt also
|
|
3554
|
+
reveals the invalid state, and `form.reset()` clears the touched presentation.
|
|
3555
|
+
|
|
3197
3556
|
**Properties (own):**
|
|
3198
3557
|
- `placeholder: string = ''`
|
|
3199
3558
|
- `minRows: number = 1` (attribute `min-rows`) — floored to `1` at render time
|
|
@@ -3250,7 +3609,11 @@ entirely when it has assigned content)
|
|
|
3250
3609
|
Auto-resize (`resizeTextarea()`) reads the textarea's own *computed* line-height/padding/border at
|
|
3251
3610
|
call time rather than assuming a fixed px-per-row constant, so it stays correct under a consumer's
|
|
3252
3611
|
own font-size/line-height overrides; it grows between `min-rows` and `max-rows`, then switches to
|
|
3253
|
-
internal scrolling (`overflow-y: auto`) past `max-rows`.
|
|
3612
|
+
internal scrolling (`overflow-y: auto`) past `max-rows`. A `ResizeObserver` on the textarea itself
|
|
3613
|
+
also re-runs this fit (one animation frame later, to avoid a `ResizeObserver`-loop console error)
|
|
3614
|
+
whenever the textarea's own *width* changes — a sidebar collapsing, a responsive breakpoint, a
|
|
3615
|
+
window resize — even though `value`/`min-rows`/`max-rows` never did, since the same text now wraps
|
|
3616
|
+
across a different number of lines. Enter-to-send only fires while
|
|
3254
3617
|
`submit-on-enter` is `true` (the default): plain Enter submits and prevents the default newline;
|
|
3255
3618
|
Shift+Enter always inserts a newline regardless of `submit-on-enter`; an IME composition step
|
|
3256
3619
|
(checked via `isComposing`, with `keyCode === 229` as a defense-in-depth fallback for browsers that
|
|
@@ -3302,6 +3665,15 @@ wins when both are present.
|
|
|
3302
3665
|
- `progress: number = 0` — upload completion, 0-100; only meaningful while `status="uploading"`, a
|
|
3303
3666
|
value of `0` or `NaN` falls back to the indeterminate spinner
|
|
3304
3667
|
- `removable: boolean = true` (reflected) — shows the remove (×) button
|
|
3668
|
+
- `removeLabel: string = 'Remove'` (attribute `remove-label`) — verb used in the remove button's
|
|
3669
|
+
`aria-label`, interpolated as `` `${removeLabel} ${displayName}` ``; override for i18n/locale
|
|
3670
|
+
- `retryLabel: string = 'Retry'` (attribute `retry-label`) — verb used in the retry button's
|
|
3671
|
+
`aria-label`, interpolated as `` `${retryLabel} ${displayName}` ``; override for i18n/locale
|
|
3672
|
+
- `uploadingLabel: string = 'Uploading'` (attribute `uploading-label`) — verb used in the visible
|
|
3673
|
+
uploading status text, rendered as `` `${uploadingLabel} ${percent}%` `` once progress is a
|
|
3674
|
+
meaningful number, else `` `${uploadingLabel}…` ``; override for i18n/locale
|
|
3675
|
+
- `uploadFailedLabel: string = 'Upload failed'` (attribute `upload-failed-label`) — visible status
|
|
3676
|
+
text shown for `status="error"`; override for i18n/locale
|
|
3305
3677
|
|
|
3306
3678
|
The component identifies *which* attachment a `lyra-remove`/`lyra-retry` event is about via the
|
|
3307
3679
|
platform's own `id` attribute/property rather than a second, differently-named prop. Set `id="..."`
|
|
@@ -3384,7 +3756,9 @@ reads as stalled.
|
|
|
3384
3756
|
- `stallThresholdMs: number = 10000` (attribute `stall-threshold-ms`) — how long `phase` may stay
|
|
3385
3757
|
`'streaming'` with no `recordActivity()` call before the component auto-transitions to
|
|
3386
3758
|
`'stalled'`. A non-finite or `<= 0` value disables the stall timer entirely (arming becomes a
|
|
3387
|
-
no-op, so the phase will never auto-stall).
|
|
3759
|
+
no-op, so the phase will never auto-stall). Changing this value while already `'streaming'`
|
|
3760
|
+
re-arms the timer immediately against the new value, rather than waiting for the next
|
|
3761
|
+
`recordActivity()` call or phase change.
|
|
3388
3762
|
|
|
3389
3763
|
**Methods:**
|
|
3390
3764
|
- `recordActivity(): void` — call on every semantic (non-heartbeat) frame received while
|
|
@@ -3445,8 +3819,13 @@ the instant `phase` becomes anything else, including a host-driven reassignment
|
|
|
3445
3819
|
on. Phase transitions into/out of `'stalled'` are announced through an internal
|
|
3446
3820
|
`<lyra-live-region>` rather than a hand-rolled `aria-live` region: entering `'stalled'` announces
|
|
3447
3821
|
"Connection stalled." with `mode="assertive"` (a stall can need the user's attention before they
|
|
3448
|
-
give up and navigate away)
|
|
3449
|
-
|
|
3822
|
+
give up and navigate away). Leaving `'stalled'` always announces with `mode="polite"` (good news
|
|
3823
|
+
doesn't need to interrupt), but the *wording* depends on the destination phase: "Connection
|
|
3824
|
+
restored." only when leaving `'stalled'` for `'streaming'` (a genuine recovery, typically via
|
|
3825
|
+
`recordActivity()`); a neutral "No longer stalled." when the destination is `'idle'`/`'connecting'`
|
|
3826
|
+
instead (the host gave up on the stream, which is not the same thing as it recovering — a
|
|
3827
|
+
screen-reader user must not be told the opposite of what a sighted user sees). Calling
|
|
3828
|
+
`recordActivity()` itself never announces anything, no
|
|
3450
3829
|
matter how often the host calls it — only the phase *transition* announces, exactly once. The
|
|
3451
3830
|
decorative indicator dot is `aria-hidden` (a color/motion cue only) and only pulses while
|
|
3452
3831
|
`phase="streaming"`; `'stalled'` is styled as a warning tone, not danger, since a stall is usually
|
|
@@ -3466,6 +3845,10 @@ instead. The pulse animation is suppressed under `prefers-reduced-motion: reduce
|
|
|
3466
3845
|
`phase`.
|
|
3467
3846
|
- `phase` remains directly settable at all times; assigning `'stalled'` yourself fires `lyra-stall`
|
|
3468
3847
|
and the assertive announcement exactly as if the timer had fired.
|
|
3848
|
+
- reconnecting the element while still `phase === 'streaming'` (e.g. a drag-and-drop reparent that
|
|
3849
|
+
keeps the same instance) automatically re-arms the stall timer in `connectedCallback` —
|
|
3850
|
+
`disconnectedCallback` always disarms it, and disconnect/reconnect fire back-to-back with no
|
|
3851
|
+
`updated()` cycle in between, so nothing else would otherwise notice.
|
|
3469
3852
|
- The `actions` slot's wrapper is always present in the DOM and toggled with the `hidden` attribute
|
|
3470
3853
|
based on slotted content, not gated by `phase` — content placed there is visible regardless of
|
|
3471
3854
|
the current phase.
|
|
@@ -3500,7 +3883,9 @@ invention (no Web Awesome equivalent).
|
|
|
3500
3883
|
- `rowHeight: string = 'auto'` (attribute `row-height`) — `'auto'` measures each row's real height via
|
|
3501
3884
|
`ResizeObserver`; a numeric string (e.g. `"56"`) fixes every row to that many pixels. Anything else
|
|
3502
3885
|
(non-numeric, zero, negative, non-finite) silently falls back to `'auto'` rather than throwing.
|
|
3503
|
-
- `overscan: number = 6` — extra rows rendered beyond the visible viewport on each side
|
|
3886
|
+
- `overscan: number = 6` — extra rows rendered beyond the visible viewport on each side; finite
|
|
3887
|
+
values are floored and clamped to 0–100, while non-finite values use the default 6, so an invalid
|
|
3888
|
+
runtime value cannot disable windowing and render the entire collection.
|
|
3504
3889
|
- `activeId: string = ''` (attribute `active-id`) — when set and it matches a row's `keyFunction`
|
|
3505
3890
|
result (compared as a string), that row is smoothly scrolled into view whenever this changes, and
|
|
3506
3891
|
rendered with `aria-current="true"`.
|
|
@@ -3555,13 +3940,17 @@ so it isn't clipped by the container's own `overflow: auto`).
|
|
|
3555
3940
|
```
|
|
3556
3941
|
|
|
3557
3942
|
Every row — in both `row-height` modes — is positioned by a `transform: translateY(offset)` computed
|
|
3558
|
-
from a single cumulative offsets array
|
|
3559
|
-
|
|
3560
|
-
`row-height
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3943
|
+
from a single cumulative offsets array, rather than by page flow; this is what lets a small DOM window
|
|
3944
|
+
exist while the scrollbar still reflects the true total content height. That array is rebuilt only
|
|
3945
|
+
when `items`, `row-height`, or `keyFunction` change, or a row's measured height changes — **not** on
|
|
3946
|
+
every update, so a pure scroll-position tick (potentially every `rAF` while scrolling) only re-runs the
|
|
3947
|
+
cheap range/visibility math, never the `O(n)` offsets rebuild (which, in `row-height="auto"` mode, also
|
|
3948
|
+
means a `keyFunction` call per item). In `row-height="auto"` mode, unmeasured rows contribute a fixed
|
|
3949
|
+
estimate to that array until their real `ResizeObserver`-reported height lands, so only rows after a
|
|
3950
|
+
newly-measured one shift on each measurement rather than the whole list reflowing. The offsets rebuild,
|
|
3951
|
+
when it does run, is an `O(n)` loop appropriate for hundreds to a few thousand rows (a scrollable
|
|
3952
|
+
history sidebar); it is not the right approach for a hundred-thousand-row list without further work
|
|
3953
|
+
(e.g. a Fenwick/segment tree for `O(log n)` offset queries+updates).
|
|
3565
3954
|
|
|
3566
3955
|
**Known gotchas:**
|
|
3567
3956
|
- `items`, `renderItem`, `keyFunction`, and `groups` are all `attribute: false` — they must be set as
|
|
@@ -3600,7 +3989,9 @@ First-party invention (no Web Awesome equivalent).
|
|
|
3600
3989
|
- `active: boolean = false` (reflected) — whether this is the currently-selected/open session; drives
|
|
3601
3990
|
the brand-quiet background treatment.
|
|
3602
3991
|
- `editable: boolean = true` (reflected) — whether inline-rename is available at all. When `false`, the
|
|
3603
|
-
rename button never renders and the row can never enter its editing state
|
|
3992
|
+
rename button never renders and the row can never enter its editing state; flipping it to `false`
|
|
3993
|
+
while a rename is already in progress cancels that edit (discards the draft, like Escape) rather
|
|
3994
|
+
than leaving it stranded and still committable.
|
|
3604
3995
|
|
|
3605
3996
|
**Events:** `lyra-select` (no detail payload — identify the row via the platform `id` attribute on the
|
|
3606
3997
|
event's `target`/`currentTarget`, the same convention `<lyra-attachment-chip>` uses; fires on a click on
|
|
@@ -3656,6 +4047,9 @@ just visually swapping content.
|
|
|
3656
4047
|
single-click `lyra-select`.
|
|
3657
4048
|
- While renaming, `[part="option"]` has no `role`/`tabindex`/`aria-selected`/`aria-label` at all — a
|
|
3658
4049
|
screen reader briefly stops announcing it as a listbox option for the duration of the edit.
|
|
4050
|
+
- Setting `editable = false` mid-rename silently discards the in-progress draft (no `lyra-rename`
|
|
4051
|
+
fires) — a consumer toggling `editable` off (e.g. in response to some other row entering rename
|
|
4052
|
+
mode) should not expect the previous edit to be committed first.
|
|
3659
4053
|
|
|
3660
4054
|
---
|
|
3661
4055
|
|
|
@@ -3668,6 +4062,8 @@ shared `FormAssociated` mixin — same reasoning as `lyra-combobox`/`lyra-select
|
|
|
3668
4062
|
notes). Built on the same trigger-button/`aria-activedescendant` listbox technique `<lyra-select>` uses
|
|
3669
4063
|
and the filter-as-you-type suggestion-popup technique `<lyra-combobox>` uses, without composing either
|
|
3670
4064
|
element. First-party invention (no Web Awesome equivalent).
|
|
4065
|
+
Session-history/autofill restoration synchronously restores the model id and form entry without
|
|
4066
|
+
emitting `lyra-change`.
|
|
3671
4067
|
|
|
3672
4068
|
**Exported types:**
|
|
3673
4069
|
- `LyraModelCatalogEntry { id: string; label: string }` — one catalog row.
|
|
@@ -3681,13 +4077,19 @@ element. First-party invention (no Web Awesome equivalent).
|
|
|
3681
4077
|
back to plain free-text entry.
|
|
3682
4078
|
- `allowCustom: boolean = false` (attribute `allow-custom`, reflected) — let the user type/commit a
|
|
3683
4079
|
value that isn't in `catalog`, even when `catalog` is non-empty.
|
|
4080
|
+
- `label: string = ''` — optional visible title above the control, mirroring `<lyra-select>`'s own
|
|
4081
|
+
`label` exactly: rendered via a `[part="form-control-label"]` `<label>` paired with the control's
|
|
4082
|
+
id, and once non-empty it takes over as the accessible-name source (an `aria-label` override is
|
|
4083
|
+
then only consulted as the fallback). Empty (the default) keeps the original
|
|
4084
|
+
`aria-label || placeholder || 'Model'` accessible-name chain untouched.
|
|
3684
4085
|
- `placeholder: string = ''`
|
|
3685
|
-
- `name: string = ''`
|
|
4086
|
+
- `name: string = ''` (reflected)
|
|
3686
4087
|
- `disabled: boolean = false` (reflected)
|
|
3687
4088
|
- `required: boolean = false` (reflected — enforced via `internals.setValidity()`)
|
|
3688
4089
|
- `open: boolean = false` (reflected)
|
|
3689
4090
|
- `value: string` — getter/setter (hand-rolled, not the `FormAssociated` mixin); the current model id,
|
|
3690
|
-
`''` when nothing is selected. Writing it calls `internals.setFormValue()` synchronously.
|
|
4091
|
+
`''` when nothing is selected. Writing it calls `internals.setFormValue()` synchronously. A named,
|
|
4092
|
+
untouched model-select contributes `''` to `FormData` instead of omitting its key.
|
|
3691
4093
|
|
|
3692
4094
|
**Mode switching:** `closedMode` (private) is `true` whenever `normalizedCatalog.length > 0 &&
|
|
3693
4095
|
!allowCustom` — a non-empty `catalog` with `allowCustom` left `false` renders the closed dropdown
|
|
@@ -3710,8 +4112,13 @@ from a real catalog pick)
|
|
|
3710
4112
|
|
|
3711
4113
|
**Slots:** none.
|
|
3712
4114
|
|
|
3713
|
-
**CSS parts:** `
|
|
3714
|
-
`
|
|
4115
|
+
**CSS parts:** `form-control-label` (the `<label>` element — only rendered, and only contributes to
|
|
4116
|
+
the accessible name, once `label` is non-empty), `trigger` (closed-dropdown mode's
|
|
4117
|
+
`<button role="combobox">`, also its positioning anchor), `combobox` (free-text mode's input
|
|
4118
|
+
container, also its positioning anchor), `combobox-input` (the free-text `<input>`),
|
|
4119
|
+
`provider-badge` (the optional leading `provider` label), `listbox` (the options popover, shared by
|
|
4120
|
+
both modes), `option`, `option-label`, `option-badge` (the "not in catalog" badge on a synthetic
|
|
4121
|
+
stale-value row), `expand-icon` (the dropdown chevron, present in both modes)
|
|
3715
4122
|
|
|
3716
4123
|
**Themeable custom properties:** shared tokens only — `--lyra-space-xs/-s`,
|
|
3717
4124
|
`--lyra-color-border/-surface/-brand/-brand-quiet/-text-quiet`, `--lyra-radius`, `--lyra-shadow`,
|
|
@@ -3757,7 +4164,10 @@ A numeric range control (e.g. an LLM "temperature" setting). **Form-associated**
|
|
|
3757
4164
|
`FormAssociated` mixin (`name`, `value`, `disabled`, `required` all inherited). Mirrors native `<input
|
|
3758
4165
|
type="range">` semantics: `value` is the string form-submitted via the mixin, `valueAsNumber` is the
|
|
3759
4166
|
ergonomic numeric accessor (matching native `<input type=range>`'s IDL attribute of the same name) kept
|
|
3760
|
-
in sync with it in both directions.
|
|
4167
|
+
in sync with it in both directions. Clicking anywhere on `[part="base"]` (not just the thumb) jumps the
|
|
4168
|
+
thumb to that point and continues the same gesture as a drag, matching native `<input type=range>`
|
|
4169
|
+
click-to-seek — the thumb is also `.focus()`ed on that click, so keyboard interaction can continue
|
|
4170
|
+
seamlessly right after. First-party invention (no Web Awesome equivalent).
|
|
3761
4171
|
|
|
3762
4172
|
**Properties:**
|
|
3763
4173
|
- `min: number = 0`
|
|
@@ -3832,10 +4242,9 @@ native range input either.
|
|
|
3832
4242
|
## `lyra-tool-select-dialog`
|
|
3833
4243
|
|
|
3834
4244
|
A category-grouped, filterable, searchable tool-enablement dialog for picking which agent tools are
|
|
3835
|
-
available in a conversation.
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
the general-purpose dialog. First-party invention (no Web Awesome equivalent).
|
|
4245
|
+
available in a conversation. It keeps its own panel template rather than nesting `<lyra-dialog>`, so
|
|
4246
|
+
it has no dependency on the general-purpose dialog, while its modal behavior participates in the
|
|
4247
|
+
shared overlay stack. First-party invention (no Web Awesome equivalent).
|
|
3839
4248
|
|
|
3840
4249
|
**Exported types:**
|
|
3841
4250
|
- `ToolSelectDialogTool { id: string; name: string; description?: string; category?: string; icon?:
|
|
@@ -3889,7 +4298,7 @@ assigned elements.
|
|
|
3889
4298
|
the backdrop scrim color; component-specific since no shared overlay token exists, mirrors
|
|
3890
4299
|
`<lyra-dialog>`'s/`<lyra-tool-result-dialog>`'s identical pattern), plus shared `--lyra-space-*`,
|
|
3891
4300
|
`--lyra-color-surface/-border/-text/-text-quiet/-warning`, `--lyra-radius`, `--lyra-shadow`,
|
|
3892
|
-
`--lyra-focus-ring-width/-color/-offset`, `--lyra-opacity-disabled
|
|
4301
|
+
`--lyra-focus-ring-width/-color/-offset`, `--lyra-opacity-disabled`.
|
|
3893
4302
|
|
|
3894
4303
|
**Optional peer deps:** none — internally renders `<lyra-checkbox>` and `<lyra-switch>`, both bundled
|
|
3895
4304
|
dependencies of this package imported directly, not optional peers.
|
|
@@ -3931,7 +4340,7 @@ checkboxes for editing.
|
|
|
3931
4340
|
into a trailing "Other" bucket that's always rendered last. A category left with zero matches after
|
|
3932
4341
|
filtering is dropped entirely, not rendered as an empty heading.
|
|
3933
4342
|
- Reconnecting the element while still `open` (e.g. a drag-and-drop reparent that keeps the same
|
|
3934
|
-
instance)
|
|
4343
|
+
instance) resumes its shared overlay registration and re-acquires the scroll lock dropped in
|
|
3935
4344
|
`disconnectedCallback`.
|
|
3936
4345
|
- The search input is the first focusable element in the panel and receives focus automatically on open.
|
|
3937
4346
|
|
|
@@ -3975,10 +4384,10 @@ popover on hover/focus. This is *not* the badge's visible content (the badge alw
|
|
|
3975
4384
|
**Themeable custom properties:** `--lyra-citation-badge-accent` / `--lyra-citation-badge-bg` /
|
|
3976
4385
|
`--lyra-citation-badge-border` (internal per-status accent variables, not typically overridden
|
|
3977
4386
|
directly — set instead by the `:host([status=...])` rules), plus shared tokens
|
|
3978
|
-
`--lyra-color-text-quiet`, `--lyra-color-success` / `-success-quiet`,
|
|
3979
|
-
`-warning-quiet`, `--lyra-color-danger` / `-danger-quiet`, `--lyra-radius`,
|
|
3980
|
-
`--lyra-color-border`, `--lyra-shadow`, `--lyra-space-s`/`-m`,
|
|
3981
|
-
`--lyra-focus-ring-*`.
|
|
4387
|
+
`--lyra-color-text-quiet`, `--lyra-color-text`, `--lyra-color-success` / `-success-quiet`,
|
|
4388
|
+
`--lyra-color-warning` / `-warning-quiet`, `--lyra-color-danger` / `-danger-quiet`, `--lyra-radius`,
|
|
4389
|
+
`--lyra-color-surface`, `--lyra-color-border`, `--lyra-shadow`, `--lyra-space-s`/`-m`,
|
|
4390
|
+
`--lyra-transition-fast`, `--lyra-focus-ring-*`.
|
|
3982
4391
|
|
|
3983
4392
|
**Optional peer deps:** none.
|
|
3984
4393
|
|
|
@@ -4081,13 +4490,15 @@ while collapsed).
|
|
|
4081
4490
|
component never navigates on its own (a controlled component, the same convention
|
|
4082
4491
|
`<lyra-tool-call-chip>`'s `lyra-tool-chip-select` follows); a listener decides what "open" means.
|
|
4083
4492
|
|
|
4084
|
-
**Slots:** `excerpt` (a short preview
|
|
4085
|
-
|
|
4493
|
+
**Slots:** `excerpt` (a short preview; when left empty, the `excerpt` part collapses away entirely —
|
|
4494
|
+
its wrapper is `hidden`, not just visually empty), `full` (the complete source text/chunk, hidden
|
|
4495
|
+
behind the "Show more"/"Show less" toggle — when left empty, no toggle renders at all; removing all
|
|
4496
|
+
`full`-slotted content while expanded auto-collapses it back).
|
|
4086
4497
|
|
|
4087
4498
|
**CSS parts:** `base` (outer container), `title` (the clickable title/page heading, a `<button>`),
|
|
4088
|
-
`excerpt` (wrapper around the `excerpt` slot
|
|
4089
|
-
collapsed), `toggle` (the "Show more"/"Show less"
|
|
4090
|
-
content).
|
|
4499
|
+
`excerpt` (wrapper around the `excerpt` slot, `hidden` when the slot has no assigned content), `full`
|
|
4500
|
+
(wrapper around the `full` slot, `hidden` while collapsed), `toggle` (the "Show more"/"Show less"
|
|
4501
|
+
button — only rendered when the `full` slot has content).
|
|
4091
4502
|
|
|
4092
4503
|
**Themeable custom properties:** shared tokens only — `--lyra-color-border`, `--lyra-color-surface`,
|
|
4093
4504
|
`--lyra-color-text` / `-text-quiet`, `--lyra-color-brand`, `--lyra-radius`, `--lyra-space-xs`/`-s`,
|
|
@@ -4134,6 +4545,10 @@ time.
|
|
|
4134
4545
|
independent of the parent list's `expanded`/`lyra-toggle` — collapsing the list doesn't reset an
|
|
4135
4546
|
individual card's `fullExpanded` state, and there is no cross-talk between the two components at
|
|
4136
4547
|
all beyond DOM nesting.
|
|
4548
|
+
- `lyra-source-card` actively strips a bare host-level `title` attribute right after Lit syncs it into
|
|
4549
|
+
the `title` property — otherwise the whole card would grow an unsolicited native tooltip repeating
|
|
4550
|
+
the title text on hover. Set `title` only as a property/attribute meant to become the rendered
|
|
4551
|
+
heading; don't rely on it surviving as a DOM attribute afterward.
|
|
4137
4552
|
|
|
4138
4553
|
---
|
|
4139
4554
|
|
|
@@ -4218,10 +4633,10 @@ exposed standalone so a consumer can compute or unit-test the same three-state r
|
|
|
4218
4633
|
real browser window. `mobileMatches` wins over `iconOnlyMatches` when both are true (the viewport is
|
|
4219
4634
|
narrower than both breakpoints at once).
|
|
4220
4635
|
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4636
|
+
The mobile state keeps its own panel template rather than nesting `<lyra-dialog>`, while its modal
|
|
4637
|
+
behavior participates in the shared overlay stack. It is a plain `<div>` with an explicit
|
|
4638
|
+
`role="navigation"` (swapping to `role="dialog"` while the overlay is open) rather than a literal
|
|
4639
|
+
`<nav>` tag, since a
|
|
4225
4640
|
`<nav>`'s implicit role can't be overridden to `role="dialog"` without an `aria-allowed-role`
|
|
4226
4641
|
violation. In `'icon-only'` mode, slotted nav items lose their visible text label — give each one a
|
|
4227
4642
|
real accessible name (`aria-label`, visually hidden text, or `title`) regardless, since this
|
|
@@ -4247,10 +4662,10 @@ component only lays out whatever is slotted and can't inspect or fix up a consum
|
|
|
4247
4662
|
selector (`translateX(100%)`), not through the shared `internal/rtl.ts` JS helper used for pointer/
|
|
4248
4663
|
keyboard math elsewhere in this library — a physical `transform` isn't expressible with logical
|
|
4249
4664
|
properties, so this one case is handled purely in CSS.
|
|
4250
|
-
- a reconnect that preserves the same element instance (e.g. a drag-and-drop reparent)
|
|
4251
|
-
|
|
4252
|
-
`disconnectedCallback`/`connectedCallback` fire back-to-back with no update in
|
|
4253
|
-
`willUpdate()` alone wouldn't otherwise notice.
|
|
4665
|
+
- a reconnect that preserves the same element instance (e.g. a drag-and-drop reparent) resumes its
|
|
4666
|
+
shared overlay registration and re-acquires the scroll lock if the overlay was still active
|
|
4667
|
+
across the move — `disconnectedCallback`/`connectedCallback` fire back-to-back with no update in
|
|
4668
|
+
between, so `willUpdate()` alone wouldn't otherwise notice.
|
|
4254
4669
|
|
|
4255
4670
|
---
|
|
4256
4671
|
|
|
@@ -4270,8 +4685,14 @@ sidebar that's a permanent docked pane on a wide screen but a modal on a phone.
|
|
|
4270
4685
|
presentation's visual treatment: `'fullscreen'` covers the whole viewport; `'bottom-sheet'` slides
|
|
4271
4686
|
up from the bottom and doesn't cover the full height. Has no visual effect while the effective
|
|
4272
4687
|
presentation resolves to `'inline'`.
|
|
4273
|
-
- `label: string = ''` — accessible name for the overlay presentation's `role="dialog"
|
|
4274
|
-
|
|
4688
|
+
- `label: string = ''` — accessible name for the overlay presentation's `role="dialog"`, used
|
|
4689
|
+
verbatim when set. When empty, falls back to the `header` slot's content: a heading element
|
|
4690
|
+
(`h1`–`h6` or `[role="heading"]`) among the slotted header content wins if present, otherwise the
|
|
4691
|
+
header slot's combined text content is used (mirrors `lyra-dialog`'s `detectHeading()` fallback,
|
|
4692
|
+
via `aria-label` rather than `aria-labelledby` since the header content is light DOM while
|
|
4693
|
+
`[part="panel"]` is in shadow DOM). A panel opened with neither `label` nor header content still
|
|
4694
|
+
renders `role="dialog"` with no accessible name. Unused in the inline presentation, which has no
|
|
4695
|
+
dialog semantics to name.
|
|
4275
4696
|
- `mobileBreakpoint: string = '768px'` (attribute `mobile-breakpoint`) — CSS length passed to
|
|
4276
4697
|
`matchMedia` as `(max-width: <this>)` to decide, in `mode="auto"`, whether the effective
|
|
4277
4698
|
presentation is `'overlay'` (below/at this width) or `'inline'` (above it).
|
|
@@ -4313,16 +4734,13 @@ shared token exists), plus shared tokens (`--lyra-color-border`, `--lyra-color-s
|
|
|
4313
4734
|
|
|
4314
4735
|
Breakpoint detection uses `matchMedia('(max-width: ' + mobileBreakpoint + ')')`, re-evaluated live
|
|
4315
4736
|
while connected — resizing/rotating a device that crosses the breakpoint while `mode="auto"` (the
|
|
4316
|
-
default) updates the effective presentation without unmounting or re-creating the slotted content
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
actively typing into inside the now-newly-modal panel. The overlay presentation duplicates
|
|
4324
|
-
`<lyra-dialog>`'s `role="dialog"` + focus-trap + Escape/backdrop-dismiss + scroll-lock mechanics
|
|
4325
|
-
locally rather than nesting a `<lyra-dialog>` inside this component's shadow DOM.
|
|
4737
|
+
default) updates the effective presentation without unmounting or re-creating the slotted content.
|
|
4738
|
+
Inline and overlay presentations share the same shadow DOM, so slotted content and scroll position
|
|
4739
|
+
survive the transition. Focus already inside the panel is preserved. If focus is outside when an
|
|
4740
|
+
open inline panel becomes an overlay, focus moves to the first composed focus target (falling back
|
|
4741
|
+
to the panel), so it cannot remain behind `aria-modal="true"`. Closing restores the element captured
|
|
4742
|
+
when the panel originally opened, even when that original open happened inline. The overlay
|
|
4743
|
+
presentation participates in the shared modal stack rather than nesting a `<lyra-dialog>`.
|
|
4326
4744
|
|
|
4327
4745
|
The package root also exports the pure `resolveEffectiveMode(mode: ResponsivePanelMode,
|
|
4328
4746
|
belowBreakpoint: boolean): ResponsivePanelEffectiveMode` resolver — renamed on export to
|
|
@@ -4335,17 +4753,18 @@ exposed standalone so a consumer can compute or unit-test the same resolution wi
|
|
|
4335
4753
|
window.
|
|
4336
4754
|
|
|
4337
4755
|
**Known gotchas:**
|
|
4338
|
-
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4756
|
+
- assigning `open` directly still does not emit `lyra-close`; use `close()` when the dismissal
|
|
4757
|
+
event/reason is required. While overlay chrome is active, however, the `true` → `false` state
|
|
4758
|
+
transition restores opener focus regardless of whether it came from `close()`, a property write,
|
|
4759
|
+
or attribute removal.
|
|
4760
|
+
- crossing inline → overlay while already open preserves focus that is already inside and moves
|
|
4761
|
+
outside focus into the panel; do not expect focus to remain on page content behind the modal.
|
|
4343
4762
|
- `variant="bottom-sheet"` has no visible effect at all while the effective presentation is
|
|
4344
4763
|
`'inline'` — it only changes the overlay presentation's anchoring/height.
|
|
4345
|
-
- a reconnect that preserves the same element instance (e.g. a drag-and-drop reparent)
|
|
4346
|
-
|
|
4347
|
-
— `disconnectedCallback`/`connectedCallback` fire back-to-back with no update in
|
|
4348
|
-
`willUpdate()` alone wouldn't otherwise notice.
|
|
4764
|
+
- a reconnect that preserves the same element instance (e.g. a drag-and-drop reparent) resumes its
|
|
4765
|
+
shared overlay registration and re-acquires the scroll lock if overlay chrome was still active
|
|
4766
|
+
across the move — `disconnectedCallback`/`connectedCallback` fire back-to-back with no update in
|
|
4767
|
+
between, so `willUpdate()` alone wouldn't otherwise notice.
|
|
4349
4768
|
- the exported pure function is named `resolveEffectiveMode` in the component module but re-exported
|
|
4350
4769
|
from the package root as `resolveResponsivePanelEffectiveMode` — importing the un-prefixed name
|
|
4351
4770
|
from `@aceshooting/lyra-ui` will fail.
|
|
@@ -4381,10 +4800,13 @@ own listbox.
|
|
|
4381
4800
|
also wants to wire `aria-controls`.
|
|
4382
4801
|
|
|
4383
4802
|
**Methods:** `handleKeyDown(e: KeyboardEvent): boolean` — the host's own text-control `keydown`
|
|
4384
|
-
handler calls this while the popover is open. Handles `ArrowDown`/`ArrowUp` (moves the highlight)
|
|
4385
|
-
`Enter`/`Tab` (commits the highlighted row
|
|
4386
|
-
|
|
4387
|
-
(
|
|
4803
|
+
handler calls this while the popover is open. Handles `ArrowDown`/`ArrowUp` (moves the highlight) and
|
|
4804
|
+
`Enter`/`Tab` (commits the highlighted row) — both pairs return `false` with no `preventDefault()`
|
|
4805
|
+
when `filteredItems` is empty, letting the keystroke fall through to the host's own control unchanged
|
|
4806
|
+
(e.g. the textarea's caret still moves to the next line on ArrowDown when there's nothing to
|
|
4807
|
+
navigate); `Escape` always closes with no selection and returns `true`. Returns `true` whenever the
|
|
4808
|
+
key was intercepted (`preventDefault()` already called) and the host should not also act on it,
|
|
4809
|
+
`false` otherwise (including for any key this method doesn't recognize).
|
|
4388
4810
|
|
|
4389
4811
|
**Exported types:** `MentionItem { id: string; label: string; description?: string; icon?: string
|
|
4390
4812
|
}`; `MentionFilter = (item: MentionItem, query: string) => boolean`; `MentionSelectDetail { id:
|
|
@@ -4464,6 +4886,10 @@ fresh `query` is the proxy for "the caret may have moved").
|
|
|
4464
4886
|
`open` or reassigning `anchor`.
|
|
4465
4887
|
- `activeIndex` resets to `0` whenever `query` or `items` changes, but not when only `anchor`
|
|
4466
4888
|
changes — reassigning `anchor` alone preserves whatever row was last highlighted.
|
|
4889
|
+
- There's no persisted "selection" the way `<lyra-combobox>`'s own listbox has one — a mention is
|
|
4890
|
+
either committed (closing the popover) or dismissed with nothing chosen. `aria-selected="true"`
|
|
4891
|
+
here marks whichever row is currently *active* (what Enter/Tab would commit right now, per the
|
|
4892
|
+
WAI-ARIA combobox-with-list-autocomplete pattern), not a separate persisted value.
|
|
4467
4893
|
|
|
4468
4894
|
---
|
|
4469
4895
|
|
|
@@ -4501,10 +4927,11 @@ of the stream arrives.
|
|
|
4501
4927
|
**CSS parts:** `base`, `cursor` (only rendered while `streaming` is `true`)
|
|
4502
4928
|
|
|
4503
4929
|
**Themeable custom properties:** `--lyra-streaming-text-cursor-width` (default `0.125rem` — the
|
|
4504
|
-
cursor bar's inline size
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4930
|
+
cursor bar's inline size), `--lyra-streaming-text-cursor-height` (default `1em`) — both
|
|
4931
|
+
component-specific, since no shared "inline cursor bar" token exists, the same pattern
|
|
4932
|
+
`<lyra-typing-indicator>`'s own `--lyra-typing-cursor-width`/`-height` use, plus shared
|
|
4933
|
+
`--lyra-space-xs` (cursor's `margin-inline-start`) and `--lyra-transition-base` (blink animation
|
|
4934
|
+
cycle length).
|
|
4508
4935
|
|
|
4509
4936
|
**Optional peer deps:** none — internally imports and auto-registers `<lyra-markdown>` for
|
|
4510
4937
|
Markdown-mode rendering (a side-effect import; the host never needs to import or register it
|
|
@@ -4529,8 +4956,9 @@ purely as a generic "coalesce rapid calls, flush the latest" timing primitive
|
|
|
4529
4956
|
class's usual DOM/ARIA plumbing. Within any `coalesce-ms` window, only the *last* `content` value
|
|
4530
4957
|
assigned actually reaches the rendered DOM. Two cases always bypass the throttle and flush
|
|
4531
4958
|
immediately: the very first `content` assignment after mount, and any transition of `streaming`
|
|
4532
|
-
|
|
4533
|
-
window
|
|
4959
|
+
between `true` and `false` in *either* direction — so the final chunk of a finished stream can
|
|
4960
|
+
never be left stranded mid-window, and a stream restarting on a reused element can never keep
|
|
4961
|
+
showing the previous stream's stale final content for the length of the window.
|
|
4534
4962
|
|
|
4535
4963
|
Rendering itself is never reimplemented here: Markdown mode composes `<lyra-markdown>` directly,
|
|
4536
4964
|
forwarding this component's own `streaming` through as that component's `streaming` hint prop;
|
|
@@ -4543,8 +4971,10 @@ happens to end with.
|
|
|
4543
4971
|
**Known gotchas:**
|
|
4544
4972
|
- `content` must always be the complete string so far, never a delta — this component does no
|
|
4545
4973
|
accumulation of its own.
|
|
4546
|
-
- Only the very *first* `content` assignment after mount bypasses `coalesceMs` —
|
|
4547
|
-
assignment is throttled normally
|
|
4974
|
+
- Only the very *first* `content` assignment after mount bypasses `coalesceMs` unconditionally —
|
|
4975
|
+
every later assignment is throttled normally except when it lands in the same update as a
|
|
4976
|
+
`streaming` transition (either `true → false` or `false → true`), which also forces an immediate
|
|
4977
|
+
flush.
|
|
4548
4978
|
- `markdown="false"` (any string value other than exactly `"false"` is treated as `true` by the
|
|
4549
4979
|
converter) forces plain-text mode even if the text obviously contains Markdown syntax.
|
|
4550
4980
|
- Purely presentational: no events, and it does not announce anything to assistive tech itself — a
|
|
@@ -4612,8 +5042,8 @@ Live-mode auto-scroll ("stick to bottom") is the classic chat-transcript convent
|
|
|
4612
5042
|
user has manually scrolled up to re-read earlier content (tracked via a `scroll` listener on
|
|
4613
5043
|
`[part="body"]`: every user-driven scroll records whether the body was left within 48px of its own
|
|
4614
5044
|
max scroll position, and only a mutation that arrives while that's still true triggers a follow-up
|
|
4615
|
-
scroll). Opening an already-`'live'` panel
|
|
4616
|
-
content. New content is detected via a `MutationObserver` on this element's own light DOM
|
|
5045
|
+
scroll). Opening an already-`'live'` panel — or a still-`expanded` panel later becoming `'live'` —
|
|
5046
|
+
always resets this to "anchored" and jumps to the latest content. New content is detected via a `MutationObserver` on this element's own light DOM
|
|
4617
5047
|
(`childList`+`subtree`+`characterData`), not `slotchange`, since streamed reasoning typically
|
|
4618
5048
|
appends chunks to an existing node's `textContent` rather than re-slotting a whole new element per
|
|
4619
5049
|
token; scroll-to-bottom calls are coalesced to at most one per animation frame under a fast token
|
|
@@ -4625,9 +5055,11 @@ stream.
|
|
|
4625
5055
|
`<lyra-markdown>` re-rendering its shadow tree after a `content` change). A slotted element whose
|
|
4626
5056
|
own internal updates should drive auto-scroll needs to append/mutate visible light-DOM text
|
|
4627
5057
|
itself (as `<lyra-streaming-text>` does), or the host can call `scrollToBottom()` directly.
|
|
4628
|
-
-
|
|
4629
|
-
|
|
4630
|
-
`mode
|
|
5058
|
+
- Either half of the pair can trigger the jump-to-bottom/reset-stickiness behavior, as long as the
|
|
5059
|
+
*other* half already holds: an `expanded` transition to `true` while `mode` is already `'live'`,
|
|
5060
|
+
**or** a `mode` transition to `'live'` while the panel is already `expanded`, both jump to the
|
|
5061
|
+
bottom and reset stickiness. Only a change that leaves the panel in some other combination
|
|
5062
|
+
(collapsed, or `mode !== 'live'`) skips it.
|
|
4631
5063
|
- The sticky-bottom flag starts `true` internally, so a panel that mounts already `expanded` and
|
|
4632
5064
|
`mode="live"` follows its very first content mutation even before any `scroll` event has fired.
|
|
4633
5065
|
|
|
@@ -4643,17 +5075,24 @@ equivalent). Renders as e.g. `12.3s · 340 tokens · 27 tok/s [Stop]`.
|
|
|
4643
5075
|
- `active: boolean = false` (reflected) — whether generation is currently in progress. The
|
|
4644
5076
|
elapsed-time ticker (a ~1s `setInterval`) runs only while this is `true`.
|
|
4645
5077
|
- `startedAt?: number` (attribute `started-at`) — epoch-ms timestamp of when generation began.
|
|
4646
|
-
Optional — when unset
|
|
4647
|
-
|
|
5078
|
+
Optional — when unset, or set to a value that fails to parse as a finite number (e.g. an ISO-8601
|
|
5079
|
+
date string, which `type: Number` conversion turns into `NaN`), while `active` is `true`, this
|
|
5080
|
+
component captures `Date.now()` itself the moment `active` becomes `true` and counts from there
|
|
5081
|
+
instead — an invalid value is treated identically to "unset", never rendered as literal `"NaNs"`.
|
|
4648
5082
|
- `tokenCount?: number` (attribute `token-count`) — running token count so far. Omitted from the
|
|
4649
5083
|
readout entirely (no `tokens` segment) while unset.
|
|
4650
5084
|
- `tokensPerSecond?: number` (attribute `tokens-per-second`) — host-computed tokens/sec figure,
|
|
4651
5085
|
used as-is when set. When unset, derived from `token-count`/elapsed time instead (see prose).
|
|
4652
|
-
- `showStop: boolean = true` (attribute `show-stop
|
|
4653
|
-
all.
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
5086
|
+
- `showStop: boolean = true` (attribute `show-stop`, **not reflected**) — whether the built-in Stop
|
|
5087
|
+
button renders at all. Uses a string-value-aware `ComplexAttributeConverter` (not Lit's default
|
|
5088
|
+
presence-based `type: Boolean`), so a plain-HTML `show-stop="false"` content attribute correctly
|
|
5089
|
+
turns it off — the literal string `"false"` maps to `false`; the attribute's mere presence with any
|
|
5090
|
+
other value (or no value) maps to `true`. A Lit template can instead use a `.showStop=${false}`
|
|
5091
|
+
property binding. **Caveat:** a `?show-stop=${false}` boolean-attribute *binding* still can't turn
|
|
5092
|
+
it off when the attribute was never present in markup to begin with — that binding only ever
|
|
5093
|
+
removes the attribute when falsy, and removing an attribute that's already absent fires no
|
|
5094
|
+
`attributeChangedCallback` (see AGENTS.md); use `.showStop=${false}` or the plain
|
|
5095
|
+
`show-stop="false"` string form instead.
|
|
4657
5096
|
|
|
4658
5097
|
**Events:** `lyra-stop` (no detail payload — `this.emit('lyra-stop')` is called with no second
|
|
4659
5098
|
argument, so `event.detail` is `null`, not `undefined`) — fired when the built-in Stop button is
|
|
@@ -4704,8 +5143,10 @@ something that announces state *transitions* instead. The Stop button gets a nor
|
|
|
4704
5143
|
`aria-label="Stop generating"`, no different from any other icon-only button in this library.
|
|
4705
5144
|
|
|
4706
5145
|
**Known gotchas:**
|
|
4707
|
-
- `showStop` defaults to `true` and is not a reflected property
|
|
4708
|
-
the
|
|
5146
|
+
- `showStop` defaults to `true` and is not a reflected property. Its `ComplexAttributeConverter`
|
|
5147
|
+
makes the plain content attribute `show-stop="false"` work correctly, but a `?show-stop=${false}`
|
|
5148
|
+
Lit boolean-attribute *binding* still can't turn it off starting from absent markup — see the
|
|
5149
|
+
property list above for the exact footgun.
|
|
4709
5150
|
- The derived `tokens-per-second` figure only appears once `elapsedMs >= 1000`; before that, the
|
|
4710
5151
|
`throughput` part simply doesn't render — supply `tokens-per-second` yourself for a stable figure
|
|
4711
5152
|
from the very first tick.
|
|
@@ -4741,9 +5182,19 @@ and it's what every instance renders at zero extra bytes until shiki resolves.
|
|
|
4741
5182
|
cannot override a true default)
|
|
4742
5183
|
- `maxHeight: string = ''` (attribute `max-height`) — a CSS length (e.g. `"20rem"`); once set, the
|
|
4743
5184
|
code scrolls internally past this height instead of growing the page
|
|
5185
|
+
- `languages?: Record<string, ShikiLanguageInput>` (attribute: false) — a map of language id to an
|
|
5186
|
+
already-imported shiki grammar module (e.g. `{ bash: bashGrammar }` from a module-scope
|
|
5187
|
+
`import bash from 'shiki/langs/bash.mjs'`). When `language` matches a key here, highlighting is
|
|
5188
|
+
seeded from exactly that pre-supplied grammar via a fine-grained `createHighlighterCore()`
|
|
5189
|
+
highlighter, bypassing the default ~200-language dynamic-import path (`loadShikiHighlighter()`)
|
|
5190
|
+
for that language — an additive, opt-in escape hatch for a build scoped to just the languages a
|
|
5191
|
+
consumer actually needs. A `language` absent from this map (or `languages` left unset) falls back
|
|
5192
|
+
to the default dynamic-import path unchanged.
|
|
4744
5193
|
|
|
4745
5194
|
**Events:** `lyra-copy` (`detail: { text: string }` — always the raw `code` value, never the
|
|
4746
|
-
highlighted HTML, and always fires regardless of whether the actual OS clipboard write succeeded)
|
|
5195
|
+
highlighted HTML, and always fires regardless of whether the actual OS clipboard write succeeded),
|
|
5196
|
+
`lyra-toggle` (`detail: { collapsed: boolean }` — fired when the built-in collapse/expand header
|
|
5197
|
+
button is activated, same event name/shape convention as `<lyra-thinking-panel>`'s own `lyra-toggle`)
|
|
4747
5198
|
|
|
4748
5199
|
**Slots:** none.
|
|
4749
5200
|
|
|
@@ -4810,10 +5261,9 @@ theme data.
|
|
|
4810
5261
|
|
|
4811
5262
|
A human-in-the-loop gate: presents one proposed tool/function call (`toolName` + `args`) and blocks an
|
|
4812
5263
|
agent from executing it until a person explicitly approves or denies it, with an optional inline
|
|
4813
|
-
"edit the arguments before approving" step. First-party invention (no Web Awesome equivalent). It
|
|
4814
|
-
its own
|
|
4815
|
-
|
|
4816
|
-
the general-purpose dialog component.
|
|
5264
|
+
"edit the arguments before approving" step. First-party invention (no Web Awesome equivalent). It
|
|
5265
|
+
keeps its own panel template rather than nesting `<lyra-dialog>`, so it has no dependency on the
|
|
5266
|
+
general-purpose dialog component, while its modal behavior participates in the shared overlay stack.
|
|
4817
5267
|
|
|
4818
5268
|
Approve/Deny/Edit are built-in chrome, not a `footer` slot a consumer must assemble — there is exactly
|
|
4819
5269
|
one correct action set for "approve this call". The `footer` slot is offered only for *supplementary*
|
|
@@ -4893,10 +5343,9 @@ Initial focus deliberately does *not* land on Approve: approving a tool call is
|
|
|
4893
5343
|
potentially irreversible action, so a user who opens the dialog and reflexively presses Enter/Space
|
|
4894
5344
|
before reading anything should deny, not approve. Deny gets the initial focus instead — the same
|
|
4895
5345
|
"focus the safe action" convention a native destructive-confirmation dialog typically follows for its
|
|
4896
|
-
own Cancel button. Tab/Shift+Tab are bounded to the panel's own focusable content
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
`<lyra-tool-select-dialog>`'s own overlays use.
|
|
5346
|
+
own Cancel button. Tab/Shift+Tab are bounded to the panel's own focusable content, and
|
|
5347
|
+
`<lyra-json-viewer>`'s internal controls plus slotted custom-element controls are found through the
|
|
5348
|
+
shared composed-tree focus traversal used by the other modal families.
|
|
4900
5349
|
|
|
4901
5350
|
**Known gotchas:**
|
|
4902
5351
|
- `editable` defaults to `true` and reflects — see the property note above about overriding it with a
|
|
@@ -4905,12 +5354,11 @@ target are found too) via a locally-duplicated focus-trap helper, the same shape
|
|
|
4905
5354
|
- a consumer flipping `editable` off while an edit is already in progress automatically exits edit mode
|
|
4906
5355
|
and discards the draft, so an unreachable "Cancel" affordance is never left stranded on screen.
|
|
4907
5356
|
- reconnecting the element while still `open` (e.g. a drag-and-drop reparent that keeps the same
|
|
4908
|
-
instance)
|
|
4909
|
-
`disconnectedCallback` — `willUpdate()` alone wouldn't otherwise notice, since
|
|
4910
|
-
fire back-to-back with no update in between.
|
|
5357
|
+
instance) resumes its shared overlay registration and re-acquires the ref-counted scroll lock
|
|
5358
|
+
dropped in `disconnectedCallback` — `willUpdate()` alone wouldn't otherwise notice, since
|
|
5359
|
+
disconnect/reconnect fire back-to-back with no update in between.
|
|
4911
5360
|
- the Approve button's native `disabled` attribute (while the draft is invalid JSON) automatically
|
|
4912
|
-
excludes it from the Tab trap,
|
|
4913
|
-
focusable set already skips disabled controls.
|
|
5361
|
+
excludes it from the shared Tab trap, whose focusable-set computation skips disabled controls.
|
|
4914
5362
|
|
|
4915
5363
|
---
|
|
4916
5364
|
|
|
@@ -4920,20 +5368,22 @@ Renders one form control per top-level property of a JSON Schema object, for ad
|
|
|
4920
5368
|
approval-editing UIs (e.g. "the agent wants to call `create_event(title, attendees, allDay)` — let the
|
|
4921
5369
|
user tweak the arguments before running it"). First-party invention (no Web Awesome equivalent).
|
|
4922
5370
|
|
|
4923
|
-
**
|
|
4924
|
-
|
|
4925
|
-
`
|
|
4926
|
-
|
|
4927
|
-
|
|
5371
|
+
**Supported schema subset:** a *flat* object whose properties use one primitive `type`
|
|
5372
|
+
(`'string'`, `'number'`, `'integer'`, or `'boolean'`), `required` property presence, string `enum`,
|
|
5373
|
+
primitive `const`, and the `title`/`description`/`default` annotations. Nested objects, arrays, type
|
|
5374
|
+
unions, `oneOf`/`anyOf`/`allOf`, `$ref`, string/numeric constraints, and schema-valued
|
|
5375
|
+
`additionalProperties` are not interpreted. An unsupported property type renders a visible fallback
|
|
5376
|
+
and makes the form invalid instead of being silently accepted.
|
|
4928
5377
|
|
|
4929
5378
|
**Exported types:**
|
|
4930
5379
|
- `ToolParamFormPropertyType = 'string' | 'number' | 'integer' | 'boolean'` — the four leaf property
|
|
4931
5380
|
types this renderer understands
|
|
5381
|
+
- `ToolParamFormPrimitive = string | number | boolean` — values accepted by the supported `const`
|
|
4932
5382
|
- `ToolParamFormProperty { type: ToolParamFormPropertyType; enum?: string[]; description?: string;
|
|
4933
|
-
title?: string; default?: unknown }` — one `schema.properties`
|
|
4934
|
-
`type` is `'string'` (rendered as a `<lyra-select>`); `
|
|
4935
|
-
|
|
4936
|
-
a field whenever `value` doesn't already have that key.
|
|
5383
|
+
title?: string; default?: unknown; const?: ToolParamFormPrimitive }` — one `schema.properties`
|
|
5384
|
+
entry. `enum` is only meaningful when `type` is `'string'` (rendered as a `<lyra-select>`); `const`
|
|
5385
|
+
enforces one exact primitive value; `title` is the display label; `description` is helper text;
|
|
5386
|
+
`default` pre-fills a field whenever `value` doesn't already have that key.
|
|
4937
5387
|
- `ToolParamFormSchema { type: 'object'; properties: Record<string, ToolParamFormProperty>; required?:
|
|
4938
5388
|
string[] }` — the (intentionally flat) schema shape this component can render.
|
|
4939
5389
|
|
|
@@ -4942,7 +5392,9 @@ type" note instead of silently dropping it or throwing.
|
|
|
4942
5392
|
- `value: Record<string, unknown> = {}` (attribute: false) — exactly what the consumer last set it to.
|
|
4943
5393
|
A field with no entry in `value` but a schema `default` *displays* (and is *emitted*, via
|
|
4944
5394
|
`lyra-input`) as that default, but the `value` property itself is left alone until the user actually
|
|
4945
|
-
edits that field
|
|
5395
|
+
edits that field. JSON Schema ordinarily treats `default` as an annotation; this renderer
|
|
5396
|
+
deliberately materializes it before validation/submission, so a valid default can satisfy
|
|
5397
|
+
`required`.
|
|
4946
5398
|
- `name: string = ''` — submission key for optional native `<form>` participation
|
|
4947
5399
|
- `disabled: boolean = false` (reflected)
|
|
4948
5400
|
|
|
@@ -4952,21 +5404,23 @@ type" note instead of silently dropping it or throwing.
|
|
|
4952
5404
|
A key the user has explicitly cleared (a real own property set to `undefined`) stays cleared rather
|
|
4953
5405
|
than snapping back to its default — only a key genuinely absent from `value` falls back.
|
|
4954
5406
|
- `errors: Record<string, string>` — the current per-field validation errors (`{ [propertyKey]:
|
|
4955
|
-
message }`)
|
|
4956
|
-
|
|
5407
|
+
message }`) for required presence, primitive type, finite number/integer, enum, const, and
|
|
5408
|
+
unsupported type; independent of which fields have been visited.
|
|
5409
|
+
- `formError: string` — a schema-wide/JSON-serialization error that has no honest field key; empty
|
|
5410
|
+
when the current effective value is safe to submit.
|
|
4957
5411
|
|
|
4958
5412
|
**Methods:**
|
|
4959
|
-
- `checkValidity(): boolean` —
|
|
4960
|
-
|
|
4961
|
-
- `reportValidity(): boolean` —
|
|
4962
|
-
|
|
4963
|
-
|
|
5413
|
+
- `checkValidity(): boolean` — synchronously re-snapshots even an in-place-mutated value/schema,
|
|
5414
|
+
updates `ElementInternals`, and returns validity without revealing inline errors.
|
|
5415
|
+
- `reportValidity(): boolean` — performs the same resynchronization, reveals all current field/root
|
|
5416
|
+
errors, focuses the first invalid generated field when one exists, and delegates to native
|
|
5417
|
+
`ElementInternals.reportValidity()`.
|
|
4964
5418
|
|
|
4965
5419
|
**Events:** `lyra-input` (`detail: { value: Record<string, unknown> }` — the full current value
|
|
4966
5420
|
object, every property with defaults resolved, not just the field that changed), `lyra-validity-change`
|
|
4967
|
-
(`detail: { valid: boolean; errors: Record<string, string> }` — fired whenever overall validity
|
|
4968
|
-
changes, including once up front at connect time
|
|
4969
|
-
|
|
5421
|
+
(`detail: { valid: boolean; errors: Record<string, string> }` — fired whenever overall validity or
|
|
5422
|
+
the field-error set changes, including once up front at connect time; serialization-only failures
|
|
5423
|
+
set `valid: false` while `formError`, rather than a fabricated field key, carries the root message)
|
|
4970
5424
|
|
|
4971
5425
|
**Slots:** none.
|
|
4972
5426
|
|
|
@@ -5007,29 +5461,39 @@ This component owns no Submit/Cancel/Approve chrome — a consumer composes it i
|
|
|
5007
5461
|
that's `<lyra-checkbox>`'s documented way to give itself an accessible name, unlike `aria-describedby`
|
|
5008
5462
|
(neither `<lyra-select>` nor `<lyra-checkbox>` forward a host-level `aria-describedby` to their
|
|
5009
5463
|
internal focusable element, so for the enum-select case the error message is folded into `aria-label`
|
|
5010
|
-
instead, alongside the always-visible adjacent `[part="description"]` text).
|
|
5011
|
-
|
|
5012
|
-
|
|
5464
|
+
instead, alongside the always-visible adjacent `[part="description"]` text). The outer component owns
|
|
5465
|
+
schema validity rather than forwarding HTML `required` constraints: JSON Schema `required` means an
|
|
5466
|
+
own property is present, so `''`, `0`, and `false` are valid present values. Use
|
|
5467
|
+
`{ type: 'boolean', const: true }` together with `required` for a must-confirm checkbox.
|
|
5013
5468
|
|
|
5014
5469
|
Optional native `<form>` participation is implemented via `ElementInternals` attached directly in the
|
|
5015
5470
|
constructor (`static formAssociated = true`) rather than a string-value mixin, since this component's
|
|
5016
5471
|
value is a whole object: `formResetCallback()` clears `value`/touched state back to `{}`, and
|
|
5017
|
-
`formDisabledCallback(disabled)`
|
|
5018
|
-
property.
|
|
5019
|
-
|
|
5020
|
-
|
|
5472
|
+
`formDisabledCallback(disabled)` tracks inherited fieldset state separately from the author-owned
|
|
5473
|
+
`disabled` property. JSON serialization is guarded: circular values, `BigInt`, throwing getters/`toJSON`, and
|
|
5474
|
+
non-finite numbers cannot escape from an assignment or leave stale form data; the form entry is
|
|
5475
|
+
temporarily removed and `formError`/custom validity are set until a serializable value replaces it.
|
|
5476
|
+
This is layered on top of the primary `value` + `lyra-input`/`lyra-validity-change` contract.
|
|
5477
|
+
The same safe serialized object is used as session-history/autofill state. Restoration accepts only
|
|
5478
|
+
a JSON object, falls back to `{}` for malformed/non-object state, and does not emit `lyra-input`.
|
|
5021
5479
|
|
|
5022
5480
|
**Known gotchas:**
|
|
5023
5481
|
- a schema property whose `type` isn't `'string'`/`'number'`/`'integer'`/`'boolean'` renders an inline
|
|
5024
5482
|
"Unsupported field type" message (a plain `.unsupported`-classed element, not one of the documented
|
|
5025
|
-
CSS parts) instead of throwing or being silently dropped.
|
|
5483
|
+
CSS parts) and fails closed with custom validity instead of throwing or being silently dropped.
|
|
5026
5484
|
- inline per-field errors only render once a field has been visited (`focusout`) at least once, or
|
|
5027
5485
|
after an explicit `reportValidity()` call — `checkValidity()` alone never reveals them, matching
|
|
5028
5486
|
every other form control in this library (`<lyra-select>`/`<lyra-combobox>`/`<lyra-model-select>`
|
|
5029
5487
|
all avoid flashing red before the user has touched anything).
|
|
5030
5488
|
- `effectiveValue` distinguishes "key absent from `value`" (falls back to `default`) from "key present
|
|
5031
|
-
but `undefined`" (stays cleared) via `hasOwnProperty`, not an
|
|
5032
|
-
|
|
5489
|
+
but `undefined`" (stays cleared and counts as absent for `required`) via `hasOwnProperty`, not an
|
|
5490
|
+
`=== undefined` fallback check.
|
|
5491
|
+
- additional value keys are retained and submitted (matching JSON Schema's default open-object
|
|
5492
|
+
behavior), but schema-valued/false `additionalProperties` is outside this renderer's subset.
|
|
5493
|
+
- reassign `value`/`schema` after changing them. Direct in-place mutation is resnapshotted by the
|
|
5494
|
+
component's own `checkValidity()`/`reportValidity()`, but native `form.checkValidity()` and
|
|
5495
|
+
submission use the last synchronized snapshot because the browser cannot observe arbitrary object
|
|
5496
|
+
mutation.
|
|
5033
5497
|
- `lyra-validity-change` fires once immediately at connect time even before any user interaction, so a
|
|
5034
5498
|
form with an unmet required field announces `valid: false` on mount, not only after the first edit.
|
|
5035
5499
|
|
|
@@ -5090,17 +5554,32 @@ internal shadow-DOM button; `<lyra-menu>` is the sole owner of this element's `t
|
|
|
5090
5554
|
`<lyra-menu>`'s roving-tabindex navigation entirely)
|
|
5091
5555
|
- `destructive: boolean = false` (reflected — tints the row with `--lyra-color-danger`, for a
|
|
5092
5556
|
dangerous action like "Delete")
|
|
5557
|
+
- `type: 'normal' | 'checkbox' = 'normal'` — `'checkbox'` (mirroring `wa-dropdown-item`'s identical
|
|
5558
|
+
`type` option) renders `role="menuitemcheckbox"` in place of `role="menuitem"`, with `aria-checked`
|
|
5559
|
+
reflecting `checked` and a checkmark glyph shown once `checked` is `true`. `'normal'` (the default)
|
|
5560
|
+
renders and behaves exactly as before this option existed.
|
|
5561
|
+
- `checked: boolean = false` (reflected) — whether a `type="checkbox"` item is checked; meaningless
|
|
5562
|
+
(ignored) for `type="normal"`
|
|
5563
|
+
|
|
5564
|
+
**Methods:** `select(): void` — fires `lyra-menu-item-select` (no-op while `disabled`). Called
|
|
5565
|
+
internally by this element's own click handler and by `<lyra-menu>`'s Enter/Space keydown handling
|
|
5566
|
+
of the roving-focused item; also the cleanest way for a consumer/test to trigger selection
|
|
5567
|
+
programmatically instead of clicking the shadow-DOM `[part="base"]` element (see the gotcha below).
|
|
5568
|
+
For `type="checkbox"`, also toggles `checked` and fires `lyra-menu-item-change` first.
|
|
5093
5569
|
|
|
5094
5570
|
**Events:** `lyra-menu-item-select` (no detail payload — `this.emit('lyra-menu-item-select')` is
|
|
5095
5571
|
called with no second argument, so `event.detail` is `null`, not `undefined`; fires on click, or
|
|
5096
5572
|
when the parent `<lyra-menu>`'s own Enter/Space keydown handling calls `select()` on the currently
|
|
5097
|
-
roving-focused item)
|
|
5573
|
+
roving-focused item), `lyra-menu-item-change` (`detail: { value, checked }` — fired when a
|
|
5574
|
+
`type="checkbox"` item is activated and its `checked` state toggled, in addition to — never instead
|
|
5575
|
+
of — `lyra-menu-item-select`; never fired for `type="normal"`)
|
|
5098
5576
|
|
|
5099
5577
|
**Slots:** default (the item's label content), `icon` (optional leading icon)
|
|
5100
5578
|
|
|
5101
5579
|
**CSS parts:** `base` (the row — `role` lives on the host, not this part), `icon` (wrapper around
|
|
5102
5580
|
the `icon` slot; not rendered/hidden entirely while the slot is empty), `label` (wrapper around the
|
|
5103
|
-
default slot)
|
|
5581
|
+
default slot), `checkmark` (the checkmark glyph shown when a `type="checkbox"` item is `checked`;
|
|
5582
|
+
not rendered at all for `type="normal"`)
|
|
5104
5583
|
|
|
5105
5584
|
**Themeable custom properties:** shared tokens only (`--lyra-radius`, `--lyra-focus-ring-width`,
|
|
5106
5585
|
`--lyra-focus-ring-color`, `--lyra-space-xs`, `--lyra-space-s`, `--lyra-color-brand-quiet`,
|
|
@@ -5125,17 +5604,24 @@ The popup is always rendered in the DOM (never `display:none`) so `.focus()` cal
|
|
|
5125
5604
|
work synchronously the instant it opens — closed state is conveyed via `visibility`/`opacity`
|
|
5126
5605
|
instead. `visibility` is an inherited CSS property that pierces the `<slot>` projection boundary, so
|
|
5127
5606
|
every closed-state `<lyra-menu-item>` is automatically excluded from sequential (Tab-key)
|
|
5128
|
-
navigation with no separate JS bookkeeping.
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5607
|
+
navigation with no separate JS bookkeeping. ArrowDown/ArrowUp *on the trigger while closed* also
|
|
5608
|
+
opens the menu, focusing the first/last non-disabled item respectively (mirrors native `<select>`).
|
|
5609
|
+
Once open, ArrowDown/ArrowUp move the roving focus among non-disabled items and wrap past either end
|
|
5610
|
+
(unlike `<lyra-select>`'s clamped listbox nav); Home/End jump to the first/last non-disabled item;
|
|
5611
|
+
Enter/Space activate the focused item; Escape closes and refocuses the trigger; Tab closes without
|
|
5612
|
+
`preventDefault()`, letting the browser's own Tab navigation proceed untouched. A printable keypress
|
|
5613
|
+
runs type-ahead: roving focus jumps to the next non-disabled item whose text starts with the
|
|
5614
|
+
accumulated buffer (cycling from just after the active item, buffer resets ~500ms after the last
|
|
5615
|
+
keystroke) — mirrors `<lyra-select>`'s identical listbox type-ahead. A click outside both the trigger
|
|
5616
|
+
and the open popup also closes it, but deliberately does *not* refocus the trigger — the outside
|
|
5617
|
+
click itself already moved focus somewhere the user chose.
|
|
5133
5618
|
|
|
5134
5619
|
**Known gotchas:**
|
|
5135
5620
|
- `<lyra-menu-item>`'s click handler lives on an inner shadow-DOM element (`[part="base"]`), not the
|
|
5136
5621
|
host — calling `.click()` directly on the `<lyra-menu-item>` host element in a test does **not**
|
|
5137
|
-
trigger selection;
|
|
5138
|
-
`menuItemEl.shadowRoot.querySelector('[part="base"]')
|
|
5622
|
+
trigger selection; either click (or dispatch on) the element returned by
|
|
5623
|
+
`menuItemEl.shadowRoot.querySelector('[part="base"]')`, or just call the item's own `select()`
|
|
5624
|
+
method directly.
|
|
5139
5625
|
- `lyra-show`/`lyra-hide` are suppressed on the very first render even if `open` is already `true`
|
|
5140
5626
|
in markup — only later `open` transitions fire them.
|
|
5141
5627
|
- `lyra-menu-item-select` carries no detail payload (`event.detail === null`); read
|
|
@@ -5279,6 +5765,10 @@ of `catalog`/`allowCustom` and `temperatureMin`/`temperatureMax`/`temperatureSte
|
|
|
5279
5765
|
- `layout: 'vertical' | 'compact' = 'vertical'` (reflected) — `vertical` stacks full-width rows with
|
|
5280
5766
|
visible labels; `compact` runs the same two rows side by side with a smaller, uppercase temperature
|
|
5281
5767
|
caption, for toolbars/sidebars where the vertical layout's height doesn't fit.
|
|
5768
|
+
- `disabled: boolean = false` (reflected) — disables the panel as a unit by forwarding to *both*
|
|
5769
|
+
internal `lyra-model-select` and `lyra-slider`; a wrapping `<fieldset disabled>` alone would not
|
|
5770
|
+
reach either, since a form-associated control's own `disabled` IDL property/attribute is never
|
|
5771
|
+
mutated by fieldset cascading.
|
|
5282
5772
|
|
|
5283
5773
|
**Events:** `lyra-change` — `detail: { modelValue: string; inCatalog: boolean; temperature: number }`.
|
|
5284
5774
|
Fires whenever *either* child control's own `lyra-change` fires, and always carries the full current
|
|
@@ -5313,7 +5803,13 @@ internally (both imported unconditionally as side effects, not optional).
|
|
|
5313
5803
|
The internal `lyra-slider` renders with its own value readout suppressed (`.showValue=${false}`);
|
|
5314
5804
|
the current temperature is instead shown via this component's own `[part="temperature-value"]` span,
|
|
5315
5805
|
which interpolates `temperature` verbatim with no `toFixed`/formatting applied — a value like `0.1`
|
|
5316
|
-
shows as `0.1`, and any floating-point noise a slider drag produces would render digit-for-digit.
|
|
5806
|
+
shows as `0.1`, and any floating-point noise a slider drag produces would render digit-for-digit. The
|
|
5807
|
+
panel's own `temperature` property mirrors the nested slider's *live* value on every one of its
|
|
5808
|
+
`lyra-input` events (drag/key-repeat), not just its committed `lyra-change` — so `temperature` (and
|
|
5809
|
+
the visible readout) tick continuously during a drag, but the panel's own `lyra-change` event only
|
|
5810
|
+
fires once the slider's own `lyra-change` commits (pointerup/keyup) or the model changes; reading
|
|
5811
|
+
`.temperature` mid-drag will already reflect the live position even though no `lyra-change` has fired
|
|
5812
|
+
yet for it.
|
|
5317
5813
|
|
|
5318
5814
|
**Known gotchas:**
|
|
5319
5815
|
- `catalog` is JS-only (`attribute: false`) — set it via a property binding (`.catalog=`), never as
|
|
@@ -5355,8 +5851,8 @@ capacity), `segment` (one occupied segment — carries `data-tone` for styling),
|
|
|
5355
5851
|
|
|
5356
5852
|
**Themeable custom properties:** no component-specific custom properties; consumes shared tokens
|
|
5357
5853
|
`--lyra-space-xs`, `--lyra-color-text-quiet`, `--lyra-font`, `--lyra-radius`, `--lyra-color-border`,
|
|
5358
|
-
`--lyra-color-
|
|
5359
|
-
`--lyra-transition-base`.
|
|
5854
|
+
`--lyra-color-surface` (the bar variant's inter-segment seam), `--lyra-color-brand`,
|
|
5855
|
+
`--lyra-color-success`, `--lyra-color-warning`, `--lyra-color-danger`, `--lyra-transition-base`.
|
|
5360
5856
|
|
|
5361
5857
|
**Optional peer deps:** none.
|
|
5362
5858
|
|
|
@@ -5377,15 +5873,17 @@ capacity), `segment` (one occupied segment — carries `data-tone` for styling),
|
|
|
5377
5873
|
`role="img"` and a computed `aria-label` are set imperatively on the *host* element itself in
|
|
5378
5874
|
`willUpdate` (mirroring `lyra-gauge`'s "meter" role convention) — every internal node (`track`,
|
|
5379
5875
|
`segment`, the ring's `<svg>`, `label`) is `aria-hidden`, so a screen reader gets one meaningful
|
|
5380
|
-
summary string instead of the raw markup. That summary's "used" figure is the
|
|
5381
|
-
`segments[].value`
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5876
|
+
summary string instead of the raw markup. That summary's "used" figure is the sum of
|
|
5877
|
+
`segments[].value`, clamped to `total` whenever `total > 0` so the announced text can never claim
|
|
5878
|
+
more than 100% used (e.g. `segments` summing to `150000` against `total="128000"` still announces
|
|
5879
|
+
`"128,000 of 128,000 used"`) — matching what the *visual* meter shows, since each segment's ratio is
|
|
5880
|
+
independently clamped so the running cumulative fill across all segments can never exceed 100% of the
|
|
5881
|
+
bar/ring either: an over-`total` `segments` array renders as a fully (not over-) filled meter, with
|
|
5882
|
+
later segments truncated or squeezed to zero width/arc-length as the budget runs out. `total <= 0`
|
|
5883
|
+
(or non-finite) renders zero segments — an empty track/ring — and the announced summary falls back to
|
|
5884
|
+
just `"{used} used"` with no `"of {total}"` clause, regardless of what's in `segments`. Ring geometry
|
|
5885
|
+
(a 40-radius circle, 12px stroke, centered at 50,50) intentionally matches `lyra-gauge`'s own radial
|
|
5886
|
+
numbers, so the two circular-meter components in the library share one visual scale.
|
|
5389
5887
|
|
|
5390
5888
|
**Known gotchas:**
|
|
5391
5889
|
- The ring variant's per-segment `<title>` and the bar variant's per-segment `title=` attribute are
|
|
@@ -5523,10 +6021,21 @@ way to get a `<pre>`'s text content from a URL — gated behind a generation-cou
|
|
|
5523
6021
|
(`lyra-tool-result-view`'s `resolve()` uses the identical pattern) so a `src` reassigned mid-fetch
|
|
5524
6022
|
can't have a stale response clobber a newer one.
|
|
5525
6023
|
|
|
6024
|
+
Every `src` is validated (via `internal/safe-url.ts`) against a scheme allowlist specific to the DOM/
|
|
6025
|
+
API sink it's about to reach: `fetch(src)` (text preview) and an `<img src>` (image preview) both
|
|
6026
|
+
allow relative URLs plus `http:`, `https:`, `blob:`, and `data:`; the download `<a href>` deliberately
|
|
6027
|
+
excludes `data:` (following a `data:text/html` URL can create an active document, unlike using it as
|
|
6028
|
+
inert media/fetch data). A `src` that fails its sink's check never reaches `fetch()`/`<img>`/the
|
|
6029
|
+
anchor: the text preview renders `[part="error"]` with `"Document URL is not allowed."`, the image
|
|
6030
|
+
preview silently falls back to the generic download-or-`unsupported`-slot fallback, and the generic
|
|
6031
|
+
fallback simply omits `[part="download-link"]` entirely.
|
|
6032
|
+
|
|
5526
6033
|
**Properties:**
|
|
5527
6034
|
- `src: string = ''` — URL to fetch (for `text`/`application/json`) or display (`image`, or as the
|
|
5528
6035
|
generic fallback's download `href`). Optional — gracefully absent while, e.g., a conversion is
|
|
5529
|
-
still in progress.
|
|
6036
|
+
still in progress. Validated per-sink before use — see the URL-safety note above; an unsafe/
|
|
6037
|
+
malformed value is treated as if `src` were unusable for that sink, never passed to `fetch()`/
|
|
6038
|
+
`<img>`/the anchor.
|
|
5530
6039
|
- `mimeType: string = ''` (attribute `mime-type`) — drives format dispatch (see above).
|
|
5531
6040
|
- `filename: string = ''` — shown in the header and used as the download link's suggested filename.
|
|
5532
6041
|
- `status: 'idle' | 'converting' | 'ready' | 'error' = 'idle'` (reflected) — host-owned lifecycle
|
|
@@ -5557,7 +6066,8 @@ download fallback for any `mime-type` this component doesn't natively support. I
|
|
|
5557
6066
|
`spinner` (indeterminate `role="status"`, or `role="progressbar"` once numeric progress is known —
|
|
5558
6067
|
used both for `status="converting"` and this component's own in-flight text fetch), `error`
|
|
5559
6068
|
(`role="alert"` — used both for `status="error"` and a failed text fetch), `download-link` (only
|
|
5560
|
-
rendered when `src` is set
|
|
6069
|
+
rendered when `src` is set *and* passes the link-safe scheme allowlist — see the URL-safety note
|
|
6070
|
+
above; excludes `data:` even though the other two sinks allow it)
|
|
5561
6071
|
|
|
5562
6072
|
**Themeable custom properties:** `--lyra-document-preview-max-height` (default `none`) — the
|
|
5563
6073
|
consumer-tunable scroll cap on `[part="body"]`, set from `max-height`; `none` means the preview grows
|
|
@@ -5612,6 +6122,13 @@ available, the region becomes a standard `role="progressbar"` instead, self-desc
|
|
|
5612
6122
|
just via two different mechanisms depending on timing.
|
|
5613
6123
|
- `download-link` (and thus `lyra-download`) only renders/fires when `src` is set — a generic-fallback
|
|
5614
6124
|
state with no `src` shows only the file glyph and message, with no download affordance at all.
|
|
6125
|
+
- `download-link` also never renders for a `data:` URL, even though `data:` is accepted for text/image
|
|
6126
|
+
sinks — a `src="data:..."` document renders/fetches fine but falls back to no download affordance
|
|
6127
|
+
in the generic state.
|
|
6128
|
+
- A `src` that fails its sink's URL-safety check does **not** raise `lyra-render-error` — that event is
|
|
6129
|
+
reserved for a `fetch()` that was actually attempted and failed at the network layer; an unsafe/
|
|
6130
|
+
malformed `src` is silently treated as unusable instead (a rendered `[part="error"]` message for
|
|
6131
|
+
text, a silent fallback for image/download).
|
|
5615
6132
|
|
|
5616
6133
|
---
|
|
5617
6134
|
|
|
@@ -5633,13 +6150,20 @@ final.
|
|
|
5633
6150
|
filename, and folded into the accessible name.
|
|
5634
6151
|
- `alt: string = ''` — alt text for the image case (and reused as a video label fallback). Falls
|
|
5635
6152
|
back to `filename`, then a generic per-kind description.
|
|
6153
|
+
- `maxHeight: string = ''` (attribute `max-height`) — a CSS length (e.g. `"16rem"`); once set,
|
|
6154
|
+
overrides the `--lyra-media-card-max-height` custom property for this instance only (applied
|
|
6155
|
+
inline on `[part="base"]`, so it reliably wins over a `:host{}`-declared default from outside the
|
|
6156
|
+
shadow root) — same contract as `<lyra-document-preview>`'s identically-named prop.
|
|
5636
6157
|
|
|
5637
6158
|
**Events:** `lyra-open` (`detail: { src: string; filename: string }`, cancelable) — fired when the
|
|
5638
|
-
card (or, for `kind="video"`, its separate `open-button`) is activated.
|
|
5639
|
-
|
|
5640
|
-
the
|
|
5641
|
-
|
|
5642
|
-
|
|
6159
|
+
card (or, for `kind="video"`, its separate `open-button`) is activated. `detail.src` is whichever
|
|
6160
|
+
safe-URL sink actually rendered (`safeMediaSrc(src) ?? safeLinkHref(src) ?? src.trim()`), not
|
|
6161
|
+
necessarily the raw `src` property verbatim — a whitespace-padded value is trimmed, so `detail.src`
|
|
6162
|
+
always matches what the DOM would show if it were safe. This component never navigates on its own
|
|
6163
|
+
for `image`/`video` — a host decides what "open" means. The `file`-chip case is the exception: when
|
|
6164
|
+
`src` passes the stricter href safety check, the chip is a real `<a href download>` so a bare
|
|
6165
|
+
drop-in still does something useful, but `lyra-open` fires first — a host calling
|
|
6166
|
+
`preventDefault()` on it suppresses that default download/open so it can substitute its own
|
|
5643
6167
|
handling.
|
|
5644
6168
|
|
|
5645
6169
|
**Slots:** none.
|
|
@@ -5649,8 +6173,13 @@ an `<a>` or `<span>` for the file-chip fallback depending on href safety), `medi
|
|
|
5649
6173
|
`<video>`), `file-icon`, `filename` (file-chip fallback only), `open-button` (video only — see
|
|
5650
6174
|
below).
|
|
5651
6175
|
|
|
5652
|
-
**Themeable custom properties:**
|
|
5653
|
-
|
|
6176
|
+
**Themeable custom properties:** `--lyra-media-card-max-height` (default `20rem` — caps `[part="media"]`'s
|
|
6177
|
+
block-size so one oversized image/video can't blow out a chat bubble; same naming/contract as
|
|
6178
|
+
`<lyra-document-preview>`'s identical `--lyra-document-preview-max-height`; override per-instance via
|
|
6179
|
+
the `max-height` attribute instead of this property directly). Plus shared tokens
|
|
6180
|
+
`--lyra-space-xs`/`-s`, `--lyra-color-border`, `--lyra-color-surface`, `--lyra-color-text`/`-text-quiet`,
|
|
6181
|
+
`--lyra-color-brand` (hover border), `--lyra-radius`, `--lyra-icon-button-size` (video's `open-button`
|
|
6182
|
+
sizing), `--lyra-focus-ring-*`, `--lyra-transition-fast`.
|
|
5654
6183
|
|
|
5655
6184
|
**Optional peer deps:** none.
|
|
5656
6185
|
|
|
@@ -5715,6 +6244,11 @@ capability as a row.
|
|
|
5715
6244
|
- `multiple: boolean = true` (reflected) — forwarded to the hidden file input's own `multiple`
|
|
5716
6245
|
attribute.
|
|
5717
6246
|
- `disabled: boolean = false` (reflected)
|
|
6247
|
+
- `triggerLabel?: string` (attribute `trigger-label`) — overrides the single-capability trigger
|
|
6248
|
+
button's `aria-label`, which otherwise comes from `CAPABILITY_META[capability].triggerLabel` (e.g.
|
|
6249
|
+
`'Attach files'`); only affects the single-capability button (`[part='trigger']`) — the
|
|
6250
|
+
multi-capability menu's own trigger keeps its fixed `'Add attachment'` label regardless. Unset
|
|
6251
|
+
(the default) keeps the built-in English default.
|
|
5718
6252
|
|
|
5719
6253
|
**Events:** `lyra-pick` (`detail: { capability: 'files' | 'image'; files: FileList }`) — fired once a
|
|
5720
6254
|
file-backed capability's hidden input produces a real selection. The `FileList` is an independent
|
|
@@ -5729,10 +6263,13 @@ native wrapper's own camera API).
|
|
|
5729
6263
|
|
|
5730
6264
|
**CSS parts:** `trigger` (the single-capability button, only rendered when
|
|
5731
6265
|
`capabilities.length === 1`), `menu` (the `lyra-menu` wrapper, only rendered when
|
|
5732
|
-
`capabilities.length > 1`)
|
|
6266
|
+
`capabilities.length > 1`), `menu-trigger` (the multi-capability button slotted into `lyra-menu`'s own
|
|
6267
|
+
`trigger` slot, only rendered when `capabilities.length > 1`), `expand-icon` (the disclosure chevron
|
|
6268
|
+
inside the multi-capability trigger button, only rendered when `capabilities.length > 1`).
|
|
5733
6269
|
|
|
5734
|
-
**Themeable custom properties:** shared tokens only — `--lyra-space-xs`, `--lyra-color-text
|
|
5735
|
-
`--lyra-icon-button-size`, `--lyra-focus-ring-*`, `--lyra-opacity-disabled`,
|
|
6270
|
+
**Themeable custom properties:** shared tokens only — `--lyra-space-xs`, `--lyra-color-text`/
|
|
6271
|
+
`-text-quiet`, `--lyra-icon-button-size`, `--lyra-focus-ring-*`, `--lyra-opacity-disabled`,
|
|
6272
|
+
`--lyra-radius`, `--lyra-transition-fast`.
|
|
5736
6273
|
|
|
5737
6274
|
**Optional peer deps:** none.
|
|
5738
6275
|
|
|
@@ -5786,13 +6323,14 @@ string.
|
|
|
5786
6323
|
|
|
5787
6324
|
**Slots:** default — an escape hatch for fully custom key-cap content (e.g. an icon instead of a
|
|
5788
6325
|
text glyph). When it has any real (non-whitespace) content, it replaces the `keys`-driven rendering
|
|
5789
|
-
entirely and this component stops
|
|
5790
|
-
carry its own accessible name
|
|
6326
|
+
entirely and this component stops *computing* its own `aria-label` from `keys`, leaving the slotted
|
|
6327
|
+
content to carry its own accessible name — a host-supplied `aria-label` attribute is still forwarded
|
|
6328
|
+
onto the rendered chip in either mode, custom content included.
|
|
5791
6329
|
|
|
5792
6330
|
**CSS parts:** `base` (the chip root), `key` (one per rendered token).
|
|
5793
6331
|
|
|
5794
6332
|
**Themeable custom properties:** shared tokens only — `--lyra-space-xs`, `--lyra-color-surface`/
|
|
5795
|
-
`-border`/`-text-quiet`, `--lyra-radius`, `--lyra-font`.
|
|
6333
|
+
`-border`/`-text`/`-text-quiet`, `--lyra-radius`, `--lyra-font`.
|
|
5796
6334
|
|
|
5797
6335
|
**Optional peer deps:** none.
|
|
5798
6336
|
|
|
@@ -5811,7 +6349,10 @@ The rendered chip carries `role="img"` with a single spelled-out `aria-label` (e
|
|
|
5811
6349
|
rather than exposing each glyph/`+`-separator as separate accessible-tree text, since the individual
|
|
5812
6350
|
pieces aren't real words and would read worse piecemeal than as one label — glyphs like ⌘/⇧/⌥ are
|
|
5813
6351
|
not reliably announced by every screen reader/platform combination, which is exactly why the
|
|
5814
|
-
spelled-out word form exists at all.
|
|
6352
|
+
spelled-out word form exists at all. An empty `keys` with no explicit `aria-label` override and no
|
|
6353
|
+
slotted content renders nothing visible and is marked `aria-hidden="true"` (no `role="img"`) instead
|
|
6354
|
+
of exposing a nameless image element — `role`/`aria-hidden` are both derived from the same
|
|
6355
|
+
computed-label value so the two can never disagree.
|
|
5815
6356
|
|
|
5816
6357
|
---
|
|
5817
6358
|
|
|
@@ -5839,7 +6380,10 @@ title).
|
|
|
5839
6380
|
|
|
5840
6381
|
**CSS parts:** `base` (outer bordered container), `header` (present in the DOM at all times so a
|
|
5841
6382
|
later `slotchange` on `actions` is still observed, but `hidden` whenever there's no `title` and no
|
|
5842
|
-
`actions` content), `title
|
|
6383
|
+
`actions` content), `title` (truncates with an ellipsis when it overflows; carries its own native
|
|
6384
|
+
`title` attribute — the full string — so hovering the truncated text reveals it via the browser's
|
|
6385
|
+
default tooltip, scoped to just this element rather than the whole card), `actions` (`hidden`
|
|
6386
|
+
whenever the slot has no assigned content), `body`.
|
|
5843
6387
|
|
|
5844
6388
|
**Themeable custom properties:** shared tokens only — `--lyra-space-xs`/`-s`, `--lyra-color-border`/
|
|
5845
6389
|
`-surface`/`-text`, `--lyra-radius`.
|