@aceshooting/lyra-ui 17.0.0 → 18.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/CHANGELOG.md +483 -0
- package/README.md +2 -2
- package/custom-elements.json +1 -1
- package/design-tokens.json +1 -1
- package/dist/cli/migration-contract.json +1 -1
- package/dist/components/conversation/message-actions/message-actions.class.d.ts +2 -1
- package/dist/components/conversation/streaming-text/streaming-text-core.class.d.ts +22 -0
- package/dist/components/conversation/streaming-text/streaming-text-core.class.js +1 -0
- package/dist/components/conversation/streaming-text/streaming-text.class.d.ts +22 -0
- package/dist/components/conversation/streaming-text/streaming-text.class.js +1 -0
- package/dist/components/conversation/thread-list/thread-list.class.js +1 -1
- package/dist/components/data/table/table.styles.js +1 -1
- package/dist/components/forms/combobox/combobox.class.d.ts +12 -2
- package/dist/components/forms/combobox/combobox.class.js +2 -2
- package/dist/components/forms/combobox/combobox.styles.js +1 -1
- package/dist/components/forms/icon-button/icon-button.class.d.ts +18 -9
- package/dist/components/forms/select/select.class.d.ts +63 -8
- package/dist/components/forms/select/select.class.js +4 -4
- package/dist/components/forms/select/select.styles.js +1 -1
- package/dist/components/layout/filter-bar/filter-bar.class.d.ts +148 -14
- package/dist/components/layout/filter-bar/filter-bar.class.js +12 -4
- package/dist/components/layout/filter-bar/filter-bar.styles.js +1 -1
- package/dist/components/layout/reorder-list/reorder-list.class.js +1 -1
- package/dist/components/layout/virtual-list/virtual-list.class.d.ts +154 -18
- package/dist/components/layout/virtual-list/virtual-list.class.js +12 -7
- package/dist/components/media/map/map.class.d.ts +269 -14
- package/dist/components/media/map/map.class.js +62 -30
- package/dist/components/media/map/map.styles.js +1 -1
- package/dist/components/utility/copy-button/copy-button.class.d.ts +4 -3
- package/dist/custom-elements-jsx.d.ts +1 -1
- package/dist/events.d.ts +13 -1
- package/dist/internal/package-metadata.d.ts +1 -1
- package/dist/internal/package-metadata.js +1 -1
- package/dist/internal/tokens.styles.js +1 -1
- package/dist/lyra.d.ts +1 -1
- package/dist/lyra.js +1 -1
- package/dist/styles/tokens-root.css +1 -1
- package/dist/svelte.d.ts +1 -1
- package/dist/testing/lyra-tag-event-map.js +1 -1
- package/dist/translations/ar/retrieval.js +1 -1
- package/dist/translations/ar/shared.js +1 -1
- package/dist/translations/de/retrieval.js +1 -1
- package/dist/translations/de/shared.js +1 -1
- package/dist/translations/es/retrieval.js +1 -1
- package/dist/translations/es/shared.js +1 -1
- package/dist/translations/fa/retrieval.js +1 -1
- package/dist/translations/fa/shared.js +1 -1
- package/dist/translations/fr/retrieval.js +1 -1
- package/dist/translations/fr/shared.js +1 -1
- package/dist/translations/he/retrieval.js +1 -1
- package/dist/translations/he/shared.js +1 -1
- package/dist/translations/it/retrieval.js +1 -1
- package/dist/translations/it/shared.js +1 -1
- package/dist/translations/ja/retrieval.js +1 -1
- package/dist/translations/ja/shared.js +1 -1
- package/dist/translations/pt-BR/retrieval.js +1 -1
- package/dist/translations/pt-BR/shared.js +1 -1
- package/dist/translations/ro/agent-tools.d.ts +1 -0
- package/dist/translations/ro/agent-tools.js +1 -0
- package/dist/translations/ro/charts.d.ts +1 -0
- package/dist/translations/ro/charts.js +1 -0
- package/dist/translations/ro/conversation.d.ts +1 -0
- package/dist/translations/ro/conversation.js +1 -0
- package/dist/translations/ro/data.d.ts +1 -0
- package/dist/translations/ro/data.js +1 -0
- package/dist/translations/ro/forms.d.ts +1 -0
- package/dist/translations/ro/forms.js +1 -0
- package/dist/translations/ro/layout.d.ts +1 -0
- package/dist/translations/ro/layout.js +1 -0
- package/dist/translations/ro/media.d.ts +1 -0
- package/dist/translations/ro/media.js +1 -0
- package/dist/translations/ro/overlays.d.ts +1 -0
- package/dist/translations/ro/overlays.js +1 -0
- package/dist/translations/ro/retrieval.d.ts +1 -0
- package/dist/translations/ro/retrieval.js +1 -0
- package/dist/translations/ro/shared.d.ts +1 -0
- package/dist/translations/ro/shared.js +1 -0
- package/dist/translations/ro/utility.d.ts +1 -0
- package/dist/translations/ro/utility.js +1 -0
- package/dist/translations/ro/viewers.d.ts +1 -0
- package/dist/translations/ro/viewers.js +5 -0
- package/dist/translations/ro.d.ts +1 -0
- package/dist/translations/ro.js +1 -0
- package/dist/translations/ru/retrieval.js +1 -1
- package/dist/translations/ru/shared.js +1 -1
- package/dist/translations/zh-CN/retrieval.js +1 -1
- package/dist/translations/zh-CN/shared.js +1 -1
- package/dist/vue.d.ts +1 -1
- package/llms/components/lr-button.md +5 -4
- package/llms/components/lr-card.md +2 -1
- package/llms/components/lr-combobox.md +11 -0
- package/llms/components/lr-filter-bar.md +123 -7
- package/llms/components/lr-icon-button.md +17 -8
- package/llms/components/lr-map.md +127 -8
- package/llms/components/lr-option.md +11 -0
- package/llms/components/lr-select.md +45 -3
- package/llms/components/lr-streaming-text-core.md +7 -3
- package/llms/components/lr-streaming-text.md +7 -2
- package/llms/components/lr-virtual-list.md +53 -0
- package/llms/shared.md +47 -3
- package/llms/tokens.md +7 -4
- package/llms-full.txt +465 -36
- package/package.json +28 -2
- package/registrations.json +1 -1
- package/vscode-css-data.json +1 -1
- package/vscode-html-data.json +1 -1
- package/web-types.json +1 -1
|
@@ -373,10 +373,11 @@ box no matter what tier or override is in play.
|
|
|
373
373
|
For toggle buttons, host `aria-pressed="true|false|mixed"` reaches the focused native button —
|
|
374
374
|
BUTTONS only. A link button (`href` set) never receives it, because `role="link"` does not support
|
|
375
375
|
`aria-pressed`; remove `href` and the same host attribute starts reaching the `<button>` that
|
|
376
|
-
replaces the anchor.
|
|
377
|
-
`
|
|
378
|
-
|
|
379
|
-
|
|
376
|
+
replaces the anchor. That link carve-out arrived in **16.0.0** — before it, `aria-pressed` was
|
|
377
|
+
forwarded onto the anchor as well, asserting a state `role="link"` does not have. Navigation can
|
|
378
|
+
use the global `aria-current="page|step|location|date|time|true|false"`, which does reach the
|
|
379
|
+
anchor. Both states update reactively, including native `ariaPressed`/`ariaCurrent` property
|
|
380
|
+
assignments, attribute removal and `href` changes. Empty or unsupported tokens are omitted. The native button/link role
|
|
380
381
|
remains unchanged for an enabled control: use pressed state for button toggles and current state
|
|
381
382
|
for navigation links. A DISABLED link button drops `href` (so it genuinely cannot navigate) and
|
|
382
383
|
therefore also drops the anchor's implicit role, so it renders an explicit `role="link"` — without
|
|
@@ -73,7 +73,8 @@ to `<wa-card>`'s contract, staying slot-compatible with `lr-result-card` where t
|
|
|
73
73
|
- `aria-pressed` and `aria-current` (attributes only) — forwarded reactively onto the native
|
|
74
74
|
control the card actually renders, the same mechanism `<lr-button>` and `<lr-icon-button>` use.
|
|
75
75
|
`aria-pressed` accepts `'true' | 'false' | 'mixed'` and reaches the `activation-button` only —
|
|
76
|
-
`link` has no pressed state, so a linked card never receives it
|
|
76
|
+
`link` has no pressed state, so a linked card never receives it — the same **16.0.0** carve-out
|
|
77
|
+
`<lr-button>` and `<lr-icon-button>` took. The global `aria-current`
|
|
77
78
|
accepts `'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'` and reaches both the
|
|
78
79
|
activation button and the stretched link. Anything outside those sets is dropped rather than
|
|
79
80
|
passed through, so a typo never reaches the accessibility tree. This is what lets a single-select
|
|
@@ -167,6 +167,17 @@ An async `source` row can carry the same two fields (`start`, `end`) alongside i
|
|
|
167
167
|
default; an explicit instance value always wins. There is deliberately no `hoist` alias here:
|
|
168
168
|
unlike on `lr-select`, where it is Shoelace's established spelling, it would be a boolean
|
|
169
169
|
defaulting to `true`, so its attribute could only ever express the value the control already has
|
|
170
|
+
- `sync?: PlaceSync` (reflected) — copies the trigger's width, height, or both onto the listbox,
|
|
171
|
+
spelled the same as on `lr-dropdown`/`lr-popup`/`lr-popover`. Unset (the default), the listbox
|
|
172
|
+
sizes to its own content, clamped between `--lr-size-12rem` and `--lr-size-28rem`, exactly as
|
|
173
|
+
before. `sync="width"` drops that content-based clamp so a full-width trigger with short option
|
|
174
|
+
labels gets a listbox that aligns to its own edges instead of floating narrower in the middle.
|
|
175
|
+
A synced listbox is capped on `--lr-positioner-available-inline-size` **alone** — the space the
|
|
176
|
+
positioner actually measured beside the anchor, which still keeps an over-wide trigger from
|
|
177
|
+
pushing it off-screen. `--lr-popover-viewport-clamp` does **not** apply to a synced listbox — its
|
|
178
|
+
92vw default used to shorten the listbox against its own trigger in exactly the full-width case
|
|
179
|
+
`sync` exists for, and `lr-popup` never applied it either. It still applies with `sync` unset.
|
|
180
|
+
Like `placement`, a change takes effect the next time the listbox opens
|
|
170
181
|
- `clearable: boolean = false` (reflected) — displays the clear button while there is something to
|
|
171
182
|
clear on **either** axis this control owns: a committed selection, or _visible_ filter text. See
|
|
172
183
|
"the clear button covers two axes" below
|
|
@@ -21,7 +21,10 @@ Dashboard filter row that composes Lyra inputs and removable chips, with reset a
|
|
|
21
21
|
Choice option entries must expose string `value` and `label` data fields; malformed entries are
|
|
22
22
|
omitted independently, while supplied empty strings remain valid. A custom definition requires its
|
|
23
23
|
adapter and a callable `render`; a rejected definition does not reserve its filter ID. Valid
|
|
24
|
-
siblings remain available. Exceptions thrown by an admitted trusted renderer still propagate.
|
|
24
|
+
siblings remain available. Exceptions thrown by an admitted trusted renderer still propagate. A
|
|
25
|
+
chip-only definition requires neither options nor an adapter — a stable filter ID and a label are
|
|
26
|
+
the whole schema, since a malformed `formatValue`/`isEmpty` still has a correct fallback and so is
|
|
27
|
+
guarded where it is used rather than rejected outright.
|
|
25
28
|
|
|
26
29
|
**Lean registration entry.** `components/layout/filter-bar/filter-bar.js` (the default entry)
|
|
27
30
|
eagerly imports every composed control this bar could possibly render — `<lr-select>`,
|
|
@@ -42,6 +45,7 @@ the filter `type`s actually declared:
|
|
|
42
45
|
| `'checkbox-menu'` | `components/overlays/overlay/dropdown.js` **and** `components/layout/menu/dropdown-item.js` |
|
|
43
46
|
| `'date'` / `'date-range'` | `components/forms/date-picker/date-input.js` |
|
|
44
47
|
| `'text'` | `components/forms/input/input.js` |
|
|
48
|
+
| `'chip'` | none — renders no control |
|
|
45
49
|
|
|
46
50
|
Two more are unconditional regardless of which filter `type`s are declared: `<lr-button>` renders
|
|
47
51
|
the reset action on every bar, and `<lr-chip>`/`<lr-chip-group>` render the active-filter row
|
|
@@ -78,8 +82,21 @@ until something else registers it, the same trade `icon-button-register.js` docu
|
|
|
78
82
|
- `disabled: boolean = false` (reflected) — disables every filter control and reset action.
|
|
79
83
|
- `loading: boolean = false` (reflected) — shows the status spinner and disables reset while leaving
|
|
80
84
|
filters editable.
|
|
81
|
-
- `hasActiveFilters: boolean` (read-only) — whether any configured filter currently has a value
|
|
82
|
-
Drives the reset button's own disabled
|
|
85
|
+
- `hasActiveFilters: boolean` (read-only) — whether any configured filter currently has a value,
|
|
86
|
+
including one sitting at its own declared `defaultValue`. Drives the reset button's own disabled
|
|
87
|
+
state in every `activeFiltersDisplay` mode except `'changed'`; the getter itself is unaffected by
|
|
88
|
+
`activeFiltersDisplay`.
|
|
89
|
+
- `hasChangedFilters: boolean` (read-only) — whether any filter's value differs from its own
|
|
90
|
+
declared `defaultValue`, using the same equality `activeFiltersDisplay: 'changed'` filters its
|
|
91
|
+
chip row on: a `readonly string[]` default compares positionally, everything else compares with
|
|
92
|
+
`Object.is`. This is the counterpart to `hasActiveFilters`, not a synonym — a bar whose filters
|
|
93
|
+
were all declared with non-empty defaults and never touched reads `hasActiveFilters === true` and
|
|
94
|
+
`hasChangedFilters === false`, because a bar whose defaults narrow the view on load has not been
|
|
95
|
+
narrowed by the user. A filter with no declared `defaultValue` counts as changed the moment it
|
|
96
|
+
holds any value at all (there is nothing for it to still equal), and clearing a filter that *does*
|
|
97
|
+
declare one counts as changed too, since `reset()` would restore it — which is the one case where
|
|
98
|
+
this getter and the `'changed'` chip row differ, the row's entries being non-empty by
|
|
99
|
+
construction. Always live, never cached.
|
|
83
100
|
- `invalidFilterIds: readonly string[]` (read-only) — immutable ids of required filters whose
|
|
84
101
|
values are unset.
|
|
85
102
|
- `activeFiltersDisplay: 'all' | 'changed' | 'hidden' = 'all'` (reflected, attribute
|
|
@@ -93,6 +110,10 @@ until something else registers it, the same trade `icon-button-register.js` docu
|
|
|
93
110
|
at each index), matching this component's only other array-equality precedent (a custom adapter's
|
|
94
111
|
own `clearValue` comparison); a `'date-range'` value is a single composed string, so it compares
|
|
95
112
|
like any other string. Removing a chip always clears that filter, unaffected by this property.
|
|
113
|
+
`'changed'` additionally gates the reset button on `hasChangedFilters` instead of
|
|
114
|
+
`hasActiveFilters`, so an untouched defaults-only bar — which renders no chip in this mode — no
|
|
115
|
+
longer offers an enabled reset that would change nothing. Enablement under `'all'` and `'hidden'`
|
|
116
|
+
is unchanged, `disabled`/`loading` still win in every mode, and `reset()` itself is untouched.
|
|
96
117
|
|
|
97
118
|
The composed reset action uses `lr-button`'s default `m` size tier, matching the default rendered
|
|
98
119
|
height of adjacent select, combobox, input, and date fields instead of introducing a shorter action
|
|
@@ -222,11 +243,13 @@ same-named counterparts (with `combobox`'s `multiple` opting into a multi-value
|
|
|
222
243
|
`'date'`/`'date-range'` both map to `<lr-date-input>` (single vs. `mode="range"`), and `'text'` maps
|
|
223
244
|
to `<lr-input>` for an open-ended free-text query rather than a closed choice set. A `'text'`
|
|
224
245
|
filter's value is the raw query string, verbatim, and its chip shows exactly that string — the same
|
|
225
|
-
text the user typed, not a truncated or normalized form.
|
|
246
|
+
text the user typed, not a truncated or normalized form. `'chip'` is the one type that renders no
|
|
247
|
+
control at all (see **Chip-only filters** below): its value belongs to a widget elsewhere on the
|
|
248
|
+
page, so the bar renders only its active-filter chip and gives it no toolbar cell.
|
|
226
249
|
|
|
227
250
|
Every built-in (non-`'custom'`) filter definition additionally accepts optional `size: LyraSize`,
|
|
228
|
-
`icon: unknown` and `labelVisibility: 'visible' | 'hidden'` fields, and every one whose
|
|
229
|
-
control ships a clear action also accepts `clearable: boolean`. They are forwarded verbatim to that
|
|
251
|
+
`icon: unknown` and `labelVisibility: 'visible' | 'hidden' | 'auto'` fields, and every one whose
|
|
252
|
+
composed control ships a clear action also accepts `clearable: boolean`. They are forwarded verbatim to that
|
|
230
253
|
control's own same-named property — `icon` into its `start` slot exactly like a choice option's own
|
|
231
254
|
`icon`, rendered inert and `aria-hidden`; `clearable` reaching `<lr-date-input>` under its own
|
|
232
255
|
`with-clear` spelling, since that control has no `clearable`. `'text'` also accepts
|
|
@@ -245,6 +268,20 @@ so a compact toolbar row still names every field for assistive technology; visua
|
|
|
245
268
|
`::part(filter-control-label)` in CSS, the only previous option, removed the accessible name along
|
|
246
269
|
with the text.
|
|
247
270
|
|
|
271
|
+
`labelVisibility: 'auto'` is the width-dependent middle between the two. It renders exactly what
|
|
272
|
+
`'visible'` renders — the same stacked label element, the same accessible name computed from it, no
|
|
273
|
+
`aria-label` and no placeholder fallback — and the bar's own stylesheet visually clips that label
|
|
274
|
+
once the bar's allocation drops below `30rem`. The threshold is a container query on the host, so it
|
|
275
|
+
reads the bar's own allocated width, not the viewport's: the same definitions render labelled across
|
|
276
|
+
a dashboard and unlabelled in a 320px side panel, dialog or split pane, with no host-side breakpoint
|
|
277
|
+
logic. The label element is never removed at any width, so the field's accessible name is identical
|
|
278
|
+
in both states, and `'auto'` deliberately does not route the name onto the control the way
|
|
279
|
+
`'hidden'` does — doing so would name a wide-allocation field twice. A `'checkbox-menu'` filter
|
|
280
|
+
participates through its own trigger label run, the same one `'hidden'` already clips there. The
|
|
281
|
+
threshold is fixed rather than themeable: a CSS container query's prelude cannot read a custom
|
|
282
|
+
property (`var()` is not substituted in an at-rule prelude), so a `--lr-*` hook for it would parse
|
|
283
|
+
and silently never apply.
|
|
284
|
+
|
|
248
285
|
`'combobox'` also accepts the same `debounce?: number` (ms) `'text'` already had: it coalesces a
|
|
249
286
|
burst of rapid selection changes (picks, a multi-select toggle, an
|
|
250
287
|
`allowCustomValue`/`allowCreate` commit, or the clear action) into one delayed commit. Unlike
|
|
@@ -274,6 +311,8 @@ makes that text visually hidden — never removed — so the button keeps its ac
|
|
|
274
311
|
one case where the hidden label would be the *only* thing the button says (hidden routing, no
|
|
275
312
|
declared `placeholder`, nothing selected) the label routes to the visible summary instead of being
|
|
276
313
|
emitted twice, so the trigger's accessible name stays "Teams", never "Teams Teams".
|
|
314
|
+
`labelVisibility: 'auto'` clips that same trigger label run, and only below the `30rem` threshold —
|
|
315
|
+
the label run is always emitted under `'auto'`, since nothing is routed to the summary there.
|
|
277
316
|
|
|
278
317
|
Because its trigger is a button rather than a field, a `required` `'checkbox-menu'` deliberately
|
|
279
318
|
renders **no** required asterisk and sets **no** `aria-invalid`: the shared required marker has no
|
|
@@ -438,12 +477,89 @@ const filters: LyraFilterBarFilterDefinition[] = [
|
|
|
438
477
|
The custom renderer returns a Lit `TemplateResult`; the filter bar places it in its
|
|
439
478
|
`filter-control` part and re-renders it whenever the controlled value or validation state changes.
|
|
440
479
|
|
|
480
|
+
### Chip-only filters
|
|
481
|
+
|
|
482
|
+
Use `type: 'chip'` when the value is already owned by a widget elsewhere on the page — a calendar
|
|
483
|
+
heatmap cell, a map selection, a chart brush — and the bar's job is only to *show* that the filter
|
|
484
|
+
is applied and to let the user take it off. Unlike `type: 'custom'`, which still renders a control
|
|
485
|
+
inside the toolbar, a chip-only filter renders **no control and no toolbar cell at all**: no `field`
|
|
486
|
+
wrapper is emitted for it, so `lr-filter-bar::part(field)` and `::part(field-<filterId>)` never
|
|
487
|
+
match one and a bar whose filters are *all* chip-only shows no empty column — its `controls` row
|
|
488
|
+
still holds the reset button (the "clear all" action such a bar needs), the `end` slot, and the
|
|
489
|
+
loading spinner, exactly like a bar with no filters at all.
|
|
490
|
+
|
|
491
|
+
Everything else is unchanged from any other filter type. The value lives in `value` under its own
|
|
492
|
+
filter ID, rides every `lr-input`/`lr-reset` detail, counts toward `hasActiveFilters` (so it enables
|
|
493
|
+
the reset button) and toward `invalidFilterIds` when the definition is `required`, renders a
|
|
494
|
+
removable active-filter chip subject to `activeFiltersDisplay`, and is cleared both by removing that
|
|
495
|
+
chip and by `reset()`. A `required` chip-only filter is honoured in **bookkeeping only**: it joins
|
|
496
|
+
`invalidFilterIds`, fails `checkValidity()` and moves `lr-validity-change`, but renders no inline
|
|
497
|
+
error, because the bar renders no element of its own for it — the owning widget is responsible for
|
|
498
|
+
its own error affordance. The inherited `placeholder` is inert here for the same reason it is for
|
|
499
|
+
`type: 'custom'`: there is no field to place it in.
|
|
500
|
+
|
|
501
|
+
A chip-only definition adds three optional fields of its own:
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
type: 'chip';
|
|
505
|
+
formatValue?: (value: LyraFilterBarFieldValue, locale: string) => string;
|
|
506
|
+
clearValue?: LyraFilterBarFieldValue;
|
|
507
|
+
isEmpty?: (value: LyraFilterBarFieldValue) => boolean;
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
`formatValue` produces the chip's text, and its `locale` argument is the bar's `effectiveLocale` —
|
|
511
|
+
the same locale every built-in type's own chip formatting and a custom adapter's `formatValue`
|
|
512
|
+
already receive, and the reason a chip-only value (normally an already-formatted string such as a
|
|
513
|
+
localized date) can be localized by the caller. That output is caller data, so — like a filter's
|
|
514
|
+
own label — the bar never routes it through its own localization. Omitted, the fallback ladder is
|
|
515
|
+
the one a custom adapter's omitted `formatValue` uses: a string array renders as a localized
|
|
516
|
+
conjunction list, anything else renders verbatim through `String(value)`, and an unset value renders
|
|
517
|
+
as the empty string. Verbatim is exact: a chip-only value is never run through the date branch that
|
|
518
|
+
localizes a `'date'`/`'date-range'` chip, so an ISO day is not silently reformatted and a value
|
|
519
|
+
containing a slash is not mangled.
|
|
520
|
+
|
|
521
|
+
`clearValue` is what a chip removal (and `clearFilter()`) writes, defaulting to the empty string —
|
|
522
|
+
what every non-multi built-in type writes. Declare an empty array for an array-valued chip-only
|
|
523
|
+
filter. `isEmpty` overrides the built-in emptiness rule (absent, `false`, the empty string and the
|
|
524
|
+
empty array are empty; everything else is set). **A domain sentinel must pair the two**: declaring
|
|
525
|
+
a sentinel clear value without a matching `isEmpty` leaves the bar reading the "cleared" value as
|
|
526
|
+
still set, so it keeps rendering a chip for it — the identical pairing a custom adapter's own
|
|
527
|
+
`clearValue`/`isEmpty` documents. With the pair declared, the sentinel is never stored in `value`
|
|
528
|
+
(cleared keys are omitted) and an absent key reads back as the sentinel for the owning widget.
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
const filters: LyraFilterBarFilterDefinition[] = [
|
|
532
|
+
{ filterId: "query", label: "Query", type: "text" },
|
|
533
|
+
{
|
|
534
|
+
filterId: "day",
|
|
535
|
+
label: "Day",
|
|
536
|
+
type: "chip",
|
|
537
|
+
formatValue: (value, locale) =>
|
|
538
|
+
new Intl.DateTimeFormat(locale, {
|
|
539
|
+
dateStyle: "medium",
|
|
540
|
+
timeZone: "UTC",
|
|
541
|
+
}).format(new Date(`${String(value)}T00:00:00Z`)),
|
|
542
|
+
},
|
|
543
|
+
];
|
|
544
|
+
|
|
545
|
+
// The calendar heatmap beside the bar owns the value; the bar only shows and removes it.
|
|
546
|
+
heatmap.addEventListener("app-select-day", (event) => {
|
|
547
|
+
const { isoDate } = (event as CustomEvent<{ isoDate: string }>).detail;
|
|
548
|
+
bar.value = { ...bar.value, day: isoDate };
|
|
549
|
+
});
|
|
550
|
+
bar.addEventListener("lr-input", (event) => {
|
|
551
|
+
const { value } = (event as LyraFilterBarInputEvent).detail;
|
|
552
|
+
heatmap.selectedDay = (value["day"] as string | undefined) ?? "";
|
|
553
|
+
});
|
|
554
|
+
```
|
|
555
|
+
|
|
441
556
|
**TypeScript:** `LyraFilterBar<Defs extends readonly LyraFilterBarFilterDefinition[] =
|
|
442
557
|
readonly LyraFilterBarFilterDefinition[]>` — `value` and the `lr-input`/`lr-reset` detail `value`
|
|
443
558
|
narrow to a record keyed per `filterId`, whose value type follows that filter's own definition (a
|
|
444
559
|
`'select'`, a non-`multiple` `'combobox'`, `'text'`, `'date'`, and `'date-range'` narrow to
|
|
445
560
|
`string`; a `'checkbox-menu'` and a `multiple: true` `'combobox'` narrow to `readonly string[]`; a
|
|
446
|
-
`'custom'` filter keeps the full unconstrained field value
|
|
561
|
+
`'custom'` filter keeps the full unconstrained field value, and so does a `'chip'` filter, whose
|
|
562
|
+
value is owned by a widget this component never renders). Declare the schema with `as const
|
|
447
563
|
satisfies readonly LyraFilterBarFilterDefinition[]` and type the element as
|
|
448
564
|
`LyraFilterBar<typeof FILTERS>` to pick it up. Types only; the runtime is unchanged, and an untyped
|
|
449
565
|
`<lr-filter-bar>` keeps today's `LyraFilterBarValue` (`Readonly<Record<string, string | readonly
|
|
@@ -69,7 +69,8 @@ button whose purpose isn't generic.
|
|
|
69
69
|
Host `aria-haspopup` and `aria-expanded` values are forwarded reactively to the shadow-internal
|
|
70
70
|
native button. `aria-pressed` (`true`, `false`, `mixed`) supports icon-only toggle actions such as
|
|
71
71
|
mute, favorite, or pin, and reaches the `<button>` rendering only — a link icon-button (`href` set)
|
|
72
|
-
never receives it, since `role="link"` has no pressed state
|
|
72
|
+
never receives it, since `role="link"` has no pressed state (that carve-out arrived in **16.0.0**;
|
|
73
|
+
earlier versions forwarded it onto the anchor too); `aria-current` (`page`, `step`,
|
|
73
74
|
`location`, `date`, `time`, `true`, `false`) is global and supports current-item icon buttons such
|
|
74
75
|
as an active nav/pagination target in both renderings. Both follow attribute
|
|
75
76
|
changes, removal, and button/link replacement without changing the native role of an enabled
|
|
@@ -130,15 +131,23 @@ above 24px — see `llms/shared.md`. **`--lr-icon-button-size` is element-scoped
|
|
|
130
131
|
token in this section:** the shared token layer re-declares it on every `lr-*` host's own `:host`,
|
|
131
132
|
so a rule that sets `--lr-icon-button-size` on an ancestor wrapper is reset the moment it crosses
|
|
132
133
|
into any intervening `lr-*` component and never reaches a `<lr-icon-button>` composed inside it
|
|
133
|
-
(e.g. one slotted through `<lr-popover>`). The
|
|
134
|
+
(e.g. one slotted through `<lr-popover>`). The three levers that actually reach it are: setting
|
|
134
135
|
`--lr-icon-button-size` directly on the icon button element itself, where no intervening component
|
|
135
|
-
sits between the rule and the property;
|
|
136
|
-
|
|
136
|
+
sits between the rule and the property; setting `--lr-icon-button-size-scope` on any ancestor to
|
|
137
|
+
resize one subtree; or setting `--lr-theme-icon-button-size` on an ancestor to resize the whole
|
|
138
|
+
application. The shared layer reads the latter two through `var()` at every level, so both reach a
|
|
137
139
|
`<lr-icon-button>` composed inside another component, e.g. `<lr-copy-button>`/
|
|
138
|
-
`<lr-message-actions
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
`<lr-message-actions>` -- see `llms/shared.md` for the full scope table.
|
|
141
|
+
|
|
142
|
+
Lowering the floor for a dense action row below 24px is safe **only through the two ancestor
|
|
143
|
+
levers**. The coarse-pointer/no-hover media rule reads those, and floors the RENDERED hit area back
|
|
144
|
+
at 2.75rem/44px however far the override lowered it, so the control stays comfortably tappable the
|
|
145
|
+
moment the pointer reaching it is a finger rather than a mouse. It does **not** rescue
|
|
146
|
+
`--lr-icon-button-size` set directly on the element: that declaration comes from the outer tree and
|
|
147
|
+
outranks the shadow tree's own `:host` rule, so the media rule never wins and the rendered hit area
|
|
148
|
+
is exactly the value set -- `--lr-icon-button-size: 1rem` really does render a 16px target under a
|
|
149
|
+
coarse pointer, which fails WCAG 2.2 SC 2.5.8. Prefer `--lr-icon-button-size-scope` when the intent
|
|
150
|
+
is a denser row; reach for the element-scoped name only as a deliberate, localized trade-off. `--lr-icon-button-radius` (default
|
|
142
151
|
`--lr-radius`) is not re-declared anywhere in the shared layer, so — like every other token below —
|
|
143
152
|
it inherits normally from an ancestor even through an intervening component; it is the
|
|
144
153
|
`[part='button']` corner radius, retunable without a `::part(button)` rule — the same
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
|
|
10
10
|
- **Deprecations** none
|
|
11
11
|
- **Optional peers** `maplibre-gl` — see `llms/peers.md`
|
|
12
|
-
- **Themeable via**
|
|
12
|
+
- **Themeable via** 26 parts, 8 custom properties — see this component's own `@csspart`/`@cssprop` list below
|
|
13
13
|
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`
|
|
14
14
|
|
|
15
15
|
---
|
|
@@ -72,18 +72,72 @@ LyraMapLegendPattern }`, where `LyraMapLegendPattern` is `'solid' | 'diagonal' |
|
|
|
72
72
|
'crosshatch'`. Pattern is required so color is never the sole category cue. At most 100 valid
|
|
73
73
|
rows, 256 characters per label, and 8,192 aggregate label characters are retained; colors are
|
|
74
74
|
bounded before validation. The overlay scrolls within the map allocation.
|
|
75
|
+
A row may also carry its own `value`: the category key that row stands for — the same string a
|
|
76
|
+
`point.colors`/`point.icons` entry matches against `point.field`/`point.iconField`. It is
|
|
77
|
+
trimmed, bounded to 256 characters (sliced, never ellipsized, so a bounded key still matches) and
|
|
78
|
+
**retained** in the canonical readback; a non-string, empty or whitespace-only key leaves no
|
|
79
|
+
`value` property on the frozen row at all. It is what makes a row operable under
|
|
80
|
+
`legendInteractive`, and it does not count toward the label budget.
|
|
75
81
|
A row may also carry `icon`, deliberately the same record a `point.icons` entry uses — hand the
|
|
76
82
|
legend the very icon object its point layer renders and the key shows the symbol drawn on the
|
|
77
|
-
map instead of describing it in colour alone. The
|
|
78
|
-
accepted so a pass-through needs no reshaping, and
|
|
83
|
+
map instead of describing it in colour alone. The **icon record's own** `value` is a separate
|
|
84
|
+
field from the row-level one above: it is accepted so a pass-through needs no reshaping, and it
|
|
85
|
+
is still left out of the canonical readback. A row's key is never derived from its glyph's, which
|
|
86
|
+
would silently make a row interactive that the author never marked;
|
|
79
87
|
`path`, `viewBox`, `mode`, `strokeWidth`, `lineCap` and `lineJoin` keep their point-icon meaning
|
|
80
88
|
and their point-icon defaults, and the same validation applies (path data only, at most 8,192
|
|
81
89
|
characters, positive `viewBox` dimensions). An unusable record is dropped and that row keeps
|
|
82
90
|
rendering its colour swatch, exactly as a row that supplies no `icon` does.
|
|
91
|
+
A row may finally carry a `group`: the section it belongs to, for a key that describes two
|
|
92
|
+
layers at once and otherwise could not say which rows belong to which. The rule is pinned
|
|
93
|
+
rather than inferred: **consecutive** entries sharing an identical `group` render as one section
|
|
94
|
+
— a visible heading plus a `role="group"` that heading names — an entry with **no** `group`
|
|
95
|
+
keeps its **declared** position rather than being hoisted above or sunk below a section, and a
|
|
96
|
+
`group` that reappears after an interruption opens a *second* section rather than reordering
|
|
97
|
+
rows to merge them. Declaration order is the one thing the legend never rewrites, because the
|
|
98
|
+
order is itself information about the map. `group` is caller-supplied **data**: it renders
|
|
99
|
+
verbatim and is never resolved through the locale catalogue. It is trimmed and bounded to 256
|
|
100
|
+
characters (ellipsized, since it is rendered prose rather than a key matched against
|
|
101
|
+
`point.field`); a non-string, empty or whitespace-only value leaves no `group` property on the
|
|
102
|
+
frozen row at all, so an empty string means "ungrouped" instead of an empty heading. Like the
|
|
103
|
+
row-level `value`, it does not count toward the aggregate label budget — the rendered total is
|
|
104
|
+
already finite and stated: at most one heading per rendered row, so at most 100 of them. A
|
|
105
|
+
section is not a row: the 100-row cap and the `legend-limit` summary count rows, never sections.
|
|
83
106
|
- readonly `legendProjection: LyraMapLegendProjection` — frozen `{ inputCount, renderedCount,
|
|
84
107
|
omittedCount, truncatedLabelCount, truncated }` result for the latest assignment. A truncated
|
|
85
108
|
projection renders a localized visible `1–N of M items` summary rather than silently claiming
|
|
86
109
|
the bounded rows are complete.
|
|
110
|
+
- `legendInteractive: boolean = false` (attribute `legend-interactive`, reflected) — opt-in: turns
|
|
111
|
+
every legend row that carries a `value` into a keyboard-operable visibility toggle, and leaves a
|
|
112
|
+
row without one inert. Unset, the legend renders exactly the read-only key it rendered before —
|
|
113
|
+
no `button`, no extra attribute, and no extra MapLibre paint key. Each toggle is an independently
|
|
114
|
+
tabbable native `button`, so Enter and Space are the platform's own activation and there is no
|
|
115
|
+
roving tabindex: a 100-row interactive legend contributes 100 tab stops, exactly as a 100-series
|
|
116
|
+
`lr-chart` legend does. Each interactive row also grows to the shared `--lr-icon-button-size`
|
|
117
|
+
hit-area floor (WCAG 2.5.8), which the panel's existing `max-block-size` and scrolling contain.
|
|
118
|
+
- `legendCollapsible: boolean = false` (attribute `legend-collapsible`, reflected) — opt-in: renders
|
|
119
|
+
a `legend-disclosure` `button` inside the panel that collapses the key down to its header, so a
|
|
120
|
+
large legend stops permanently covering part of the map. Unset, the panel renders exactly what it
|
|
121
|
+
rendered before — no button, no `id` minted on the row list, and no `hidden` attribute anywhere.
|
|
122
|
+
Collapsing hides the gradient bar, the rows, the `legend-limit` summary and the trailing `legend`
|
|
123
|
+
slot; the `legend-start` slot and the disclosure itself stay visible, so a slotted header survives
|
|
124
|
+
the collapse and the control that restores the key is never what the collapse hides.
|
|
125
|
+
- `legendOpen: boolean = true` (attribute `legend-open`, reflected) — whether a `legendCollapsible`
|
|
126
|
+
panel is expanded. It defaults **open**, so adding only `legendCollapsible` never hides an existing
|
|
127
|
+
key, and it does nothing at all while `legendCollapsible` is unset. Because it is a
|
|
128
|
+
`true`-defaulting boolean it uses a custom attribute converter, so `legend-open="false"` parses —
|
|
129
|
+
the bare presence-based boolean form cannot express `false` at all — and the reflection follows the
|
|
130
|
+
same converter: open (the default) reflects as an **absent** attribute and collapsed reflects as
|
|
131
|
+
`legend-open="false"`. It is controlled public state and survives a disconnect/reconnect. Assigning
|
|
132
|
+
it programmatically reconciles the rendered panel and emits nothing.
|
|
133
|
+
- `hiddenCategories: readonly string[] = []` (attribute: false) — the complete controlled set of
|
|
134
|
+
muted category keys, mirroring `lr-chart`'s `hiddenDatasets`. Clone-owned and frozen; non-string,
|
|
135
|
+
empty, whitespace-only and duplicate entries are dropped (first occurrence wins, matching
|
|
136
|
+
`point.colors`), and at most 100 keys are retained. Honoured on the **first** render and the
|
|
137
|
+
first MapLibre paint, not only after a user toggle, so a host can restore a saved selection. An
|
|
138
|
+
empty array deliberately means every category is visible. It is controlled public state and
|
|
139
|
+
survives a disconnect/reconnect. A hidden key that matches no category is harmless. Assigning it
|
|
140
|
+
programmatically emits nothing.
|
|
87
141
|
- `choropleth?: LyraMapChoroplethLayer` (attribute: false) — `LyraMapChoroplethLayer { sourceId:
|
|
88
142
|
string; geojson: GeoJSON.FeatureCollection; field: string; stops: [number, string][]; interpolation?:
|
|
89
143
|
'linear' | 'logarithmic' | 'step'; stepBaseColor?: string }` (interpolated
|
|
@@ -364,7 +418,8 @@ payload beside the map.
|
|
|
364
418
|
`LyraMapHeatmapZoomValue`, `LyraMapLineOptions`, `LyraMapPointOptions`, `LyraMapPointRadiusOptions`,
|
|
365
419
|
`LyraMapPointRadiusInterpolation`, `LyraMapPointIcon`, `LyraMapPointIconMode`,
|
|
366
420
|
`LyraMapPointIconLineCap`, `LyraMapPointIconLineJoin`, `LyraMapMarker`, `LyraMapMarkerActivationDetail`,
|
|
367
|
-
`LyraMapMarkerActivationSource`, `
|
|
421
|
+
`LyraMapMarkerActivationSource`, `LyraMapLegendToggleDetail`, `LyraMapLegendPanelToggleDetail`,
|
|
422
|
+
`LyraMapStyleSpecification`, and `LyraMapInstance`.
|
|
368
423
|
The former `LegendEntry`, `ChoroplethLayer`, `GeoJsonDataLayer`, and `MapMarker` names are removed
|
|
369
424
|
in v9 rather than retained as aliases.
|
|
370
425
|
|
|
@@ -380,7 +435,24 @@ shared `maplibre-gl` import without constructing a map or allocating a WebGL con
|
|
|
380
435
|
an element.
|
|
381
436
|
|
|
382
437
|
**Events:** `lr-map-load` (fired once, after the underlying map's own `'load'`),
|
|
383
|
-
`lr-map-
|
|
438
|
+
`lr-map-legend-toggle` (**cancelable**; frozen `LyraMapLegendToggleDetail { value, visible,
|
|
439
|
+
hiddenCategories }` — the activated row's category key, its proposed visibility, and the complete
|
|
440
|
+
proposed hidden set in the order it would be committed, with the array detached and frozen so a
|
|
441
|
+
listener cannot mutate the component's state through it). `preventDefault()` is a genuine veto, not
|
|
442
|
+
a notification: `hiddenCategories` is not written, the row's `aria-pressed` does not change, the
|
|
443
|
+
MapLibre paint is untouched, and nothing is announced — which is exactly what a host needs to own
|
|
444
|
+
the set and assign its own value instead. There is deliberately no second, confirmation event: the
|
|
445
|
+
committed state is `hiddenCategories`, which the host already observes, so a paired before/after
|
|
446
|
+
vocabulary would be permanent public surface nobody asked for. The event is a DOM-interaction
|
|
447
|
+
proposal only, so a programmatic `hiddenCategories` assignment reconciles without emitting it.
|
|
448
|
+
Also `lr-map-legend-panel-toggle` (**cancelable**; frozen `LyraMapLegendPanelToggleDetail { open }` —
|
|
449
|
+
the proposed `legendOpen` value), fired once when the `legendCollapsible` disclosure is activated by
|
|
450
|
+
pointer or by Enter/Space. It is the *panel's* disclosure, not a *category's* visibility, so it
|
|
451
|
+
deliberately does not reuse `lr-map-legend-toggle`. `preventDefault()` is the same genuine veto:
|
|
452
|
+
`legendOpen` is not written, the rendered rows and the disclosure's `aria-expanded` do not change,
|
|
453
|
+
so a host can own the open state and assign its own value from `event.detail.open`. A programmatic
|
|
454
|
+
`legendOpen` assignment reconciles without emitting it, so a controlled host cannot loop.
|
|
455
|
+
Also `lr-map-marker-activate` (non-cancelable; frozen `LyraMapMarkerActivationDetail { id, lngLat,
|
|
384
456
|
marker, source }`; `id` is the trimmed explicit identity or `undefined`, `marker` is the accepted
|
|
385
457
|
declarative snapshot, and `source` is `'pointer' | 'keyboard'`), and `lr-map-click`
|
|
386
458
|
(frozen `detail: { readonly lngLat: readonly [lng, lat], readonly feature?, readonly origin?,
|
|
@@ -402,9 +474,15 @@ markup as described above.
|
|
|
402
474
|
|
|
403
475
|
**Slots:** `legend` — custom legend content, rendered inside the legend panel's own layout so it
|
|
404
476
|
stays positioned with the map instead of floating beside it. Supplying it opens the panel even
|
|
405
|
-
when `legend` and `legendGradient` are both empty.
|
|
406
|
-
|
|
407
|
-
|
|
477
|
+
when `legend` and `legendGradient` are both empty. `legend-start` — the same extension point at the
|
|
478
|
+
**top** of the panel: it renders ahead of the gradient bar and every projected row, where `legend`
|
|
479
|
+
renders after them, so a host-authored panel header is no longer forced to be a footer. Content in
|
|
480
|
+
it alone opens the panel too, and neither slot is ever made interactive by `legendInteractive`,
|
|
481
|
+
which only reaches rows projected from `legend`.
|
|
482
|
+
|
|
483
|
+
**CSS parts:** `base`, `container`, `legend`, `legend-disclosure`, `legend-disclosure-icon`,
|
|
484
|
+
`legend-group`, `legend-group-heading`, `legend-swatch`, `legend-toggle`,
|
|
485
|
+
`legend-toggle-hidden`, `legend-gradient`, `legend-lo`,
|
|
408
486
|
`legend-hi`, `legend-limit`, `marker`, `popup`,
|
|
409
487
|
`popup-content`, `popup-close-button`, `attribution`, `attribution-toggle`, `navigation`,
|
|
410
488
|
`zoom-in`, `zoom-out`, `compass`, `scale`, `error`.
|
|
@@ -429,6 +507,38 @@ visible label carries its meaning. That association is advertised only while it
|
|
|
429
507
|
the optional `maplibre-gl` peer is still loading, and after any failure, there is no map
|
|
430
508
|
container in the tree, so `legend` withholds `aria-controls` rather than leaving a dangling
|
|
431
509
|
idref.
|
|
510
|
+
Under `legendInteractive`, a row carrying a `value` wraps its swatch and label in a
|
|
511
|
+
`legend-toggle` `button` with `aria-pressed` rendered as the literal `"true"`/`"false"` — never
|
|
512
|
+
omitted, because a missing attribute reports "not a toggle button" rather than "unpressed". The
|
|
513
|
+
button's accessible name is its own visible label (caller-supplied data, so deliberately not
|
|
514
|
+
localized), scoped by the legend's own localized group name; no `aria-label` restates the state,
|
|
515
|
+
which would make assistive tech announce it twice. A hidden row's button additionally carries the
|
|
516
|
+
`legend-toggle-hidden` token — state lives in the part name, so `::part(legend-toggle-hidden)` is a
|
|
517
|
+
reachable hook — and dims only its `aria-hidden` swatch while re-colouring the label through the
|
|
518
|
+
quiet text token, so the label keeps AA contrast rather than fading with the whole button. In
|
|
519
|
+
forced colors the hidden row falls back to `line-through`, which survives a system-color collapse.
|
|
520
|
+
Each activation is announced through the shared light-DOM polite live region, using the same
|
|
521
|
+
`legendTypeShown`/`legendTypeHidden` strings `lr-graph-legend` announces with.
|
|
522
|
+
Under `legendCollapsible`, `legend-disclosure` is a native `button` whose **visible localized text
|
|
523
|
+
is its accessible name** and whose `aria-expanded` renders the literal `"true"`/`"false"` — never
|
|
524
|
+
omitted — with `aria-controls` naming the row list in the same shadow root (idrefs do not cross
|
|
525
|
+
shadow boundaries, so this is deliberately not an idref into the light DOM). It carries the shared
|
|
526
|
+
`--lr-icon-button-size` hit-area floor, and `legend-disclosure-icon` is the decorative chevron it
|
|
527
|
+
rotates: the shared icon set ships one right-pointing glyph and asks callers to rotate the wrapping
|
|
528
|
+
part, so collapsed points along the reading direction and expanded points down in both directions.
|
|
529
|
+
The rotation runs on `--lr-transition-fast`, which the token layer already flattens under
|
|
530
|
+
`prefers-reduced-motion`. A collapsed panel hides its gradient, rows, `legend-limit` summary and
|
|
531
|
+
trailing `legend` slot with the plain `hidden` attribute, so they leave layout, the accessibility
|
|
532
|
+
tree and the tab order together.
|
|
533
|
+
When at least one entry carries a `group`, each consecutive run renders inside `legend-group`
|
|
534
|
+
(`role="group"`, named by its own `legend-group-heading` through `aria-labelledby`) and **each run
|
|
535
|
+
gets its own `role="list"`**, because a `list` may only own `listitem`s — a `group` sitting directly
|
|
536
|
+
inside the outer list is an `aria-required-children`/`aria-required-parent` violation. The outer
|
|
537
|
+
container therefore drops its own `role` in that case; a legend with no groups keeps the single
|
|
538
|
+
`role="list"` it has always had. Row `aria-posinset`/`aria-setsize` stay whole-key values inside a
|
|
539
|
+
section: `aria-setsize` already reports the *input* count so a bounded key stays honest, and a
|
|
540
|
+
dropped row carries no attributable group, so a section adds a labelled sub-region without
|
|
541
|
+
renumbering the key.
|
|
432
542
|
`legend-limit` is the localized bounded-projection summary. The five peer-chrome parts project
|
|
433
543
|
stable Lyra names onto MapLibre-generated DOM without erasing peer-supplied part tokens;
|
|
434
544
|
`marker` retains a 24px minimum target in both axes even when a peer/custom marker has no intrinsic
|
|
@@ -460,6 +570,15 @@ integration; there is no declarative controls property.
|
|
|
460
570
|
- `--lr-map-choropleth-fill-opacity` (default `0.75`) — fill opacity for the declarative
|
|
461
571
|
`choropleth` layer and polygon fills in every `dataLayers` entry. It intentionally inherits from
|
|
462
572
|
an ancestor, so one scoped declaration rethemes every nested map without setting each host.
|
|
573
|
+
- `--lr-map-hidden-category-opacity` (default `0.15`) — opacity a category listed in
|
|
574
|
+
`hiddenCategories` is muted to in the rendered MapLibre paint: `circle-opacity` and
|
|
575
|
+
`circle-stroke-opacity` on the points layer and `icon-opacity` on the point-icon symbol layer.
|
|
576
|
+
Like the fill-opacity token it is read from the resolved cascade on every paint, because MapLibre
|
|
577
|
+
draws to a WebGL canvas the CSS cascade never reaches, and it inherits from an ancestor. A
|
|
578
|
+
`kind: 'heatmap'` entry is out of scope: a density surface has no per-category field to mute.
|
|
579
|
+
- `--lr-map-legend-hidden-swatch-opacity` (default `0.5`) — opacity of a hidden interactive legend
|
|
580
|
+
row's decorative swatch. Only the `aria-hidden` swatch dims; the label re-colours through
|
|
581
|
+
`--lr-color-text-quiet` instead, so it never drops below 4.5:1.
|
|
463
582
|
- `--lr-map-popup-close-button-hover-bg` (default `var(--lr-color-brand-quiet)`) and
|
|
464
583
|
`--lr-map-popup-close-button-hover-color` (default `var(--lr-color-brand)`) — hover background
|
|
465
584
|
and foreground of `popup-close-button`.
|
|
@@ -167,6 +167,17 @@ An async `source` row can carry the same two fields (`start`, `end`) alongside i
|
|
|
167
167
|
default; an explicit instance value always wins. There is deliberately no `hoist` alias here:
|
|
168
168
|
unlike on `lr-select`, where it is Shoelace's established spelling, it would be a boolean
|
|
169
169
|
defaulting to `true`, so its attribute could only ever express the value the control already has
|
|
170
|
+
- `sync?: PlaceSync` (reflected) — copies the trigger's width, height, or both onto the listbox,
|
|
171
|
+
spelled the same as on `lr-dropdown`/`lr-popup`/`lr-popover`. Unset (the default), the listbox
|
|
172
|
+
sizes to its own content, clamped between `--lr-size-12rem` and `--lr-size-28rem`, exactly as
|
|
173
|
+
before. `sync="width"` drops that content-based clamp so a full-width trigger with short option
|
|
174
|
+
labels gets a listbox that aligns to its own edges instead of floating narrower in the middle.
|
|
175
|
+
A synced listbox is capped on `--lr-positioner-available-inline-size` **alone** — the space the
|
|
176
|
+
positioner actually measured beside the anchor, which still keeps an over-wide trigger from
|
|
177
|
+
pushing it off-screen. `--lr-popover-viewport-clamp` does **not** apply to a synced listbox — its
|
|
178
|
+
92vw default used to shorten the listbox against its own trigger in exactly the full-width case
|
|
179
|
+
`sync` exists for, and `lr-popup` never applied it either. It still applies with `sync` unset.
|
|
180
|
+
Like `placement`, a change takes effect the next time the listbox opens
|
|
170
181
|
- `clearable: boolean = false` (reflected) — displays the clear button while there is something to
|
|
171
182
|
clear on **either** axis this control owns: a committed selection, or _visible_ filter text. See
|
|
172
183
|
"the clear button covers two axes" below
|
|
@@ -85,7 +85,11 @@ exactly like the multi-option case, until the trigger is actually activated.
|
|
|
85
85
|
|
|
86
86
|
**Properties:**
|
|
87
87
|
|
|
88
|
-
- `placeholder: string = ''`
|
|
88
|
+
- `placeholder: string = ''` — text shown on the trigger while nothing is selected, and the
|
|
89
|
+
trigger's accessible name when neither a host `aria-label` nor a `label` supplies one. One
|
|
90
|
+
exception to "an empty selection always shows this": while `loading` is `true` the trigger shows
|
|
91
|
+
the localized `loading` text instead — see `loading` below. The accessible name is unaffected
|
|
92
|
+
either way
|
|
89
93
|
- `disabled: boolean = false` (reflected)
|
|
90
94
|
- `required: boolean = false` (reflected — enforced via `internals.setValidity()`)
|
|
91
95
|
- `name: string = ''`
|
|
@@ -124,13 +128,41 @@ exactly like the multi-option case, until the trigger is actually activated.
|
|
|
124
128
|
authored, the listbox is actually placed with the cascading `--lr-positioning-strategy` custom
|
|
125
129
|
property honored ahead of that default — see the listbox's own **Themeable custom properties**
|
|
126
130
|
below
|
|
131
|
+
- `sync?: PlaceSync` (reflected) — copies the trigger's width, height, or both onto the listbox,
|
|
132
|
+
spelled and typed the same as on `lr-popup`/`lr-popover`/`lr-dropdown`/`lr-combobox`
|
|
133
|
+
(`'width' | 'height' | 'both'`). Unset (the default), the listbox sizes to its own content,
|
|
134
|
+
clamped between `--lr-size-12rem` and `min(--lr-popover-viewport-clamp, --lr-size-28rem)`,
|
|
135
|
+
exactly as before. `sync="width"` drops that content-based clamp so a full-width trigger with
|
|
136
|
+
short option labels gets a listbox that aligns to its own edges instead of floating narrower in
|
|
137
|
+
the middle. A synced listbox is capped on `--lr-positioner-available-inline-size` **alone** — the
|
|
138
|
+
space the positioner actually measured beside the trigger, which still keeps an over-wide trigger
|
|
139
|
+
from pushing it off-screen; `--lr-popover-viewport-clamp` does **not** apply to a synced listbox,
|
|
140
|
+
and still does with `sync` unset. Assignment while open repositions in place without closing;
|
|
141
|
+
unsetting it releases the inline width the positioner wrote
|
|
127
142
|
- `showUnknownOption: boolean = false` (attribute `show-unknown-option`, reflected) — appends every
|
|
128
143
|
committed value that no `<lr-option>` claims to the end of the listbox as a synthetic, badged,
|
|
129
144
|
keyboard-reachable, re-selectable row. Off by default
|
|
130
145
|
- `getUnknownLabel?: (value: string) => string` (attribute: false) — renders the label for a
|
|
131
146
|
committed value that matches no option, everywhere it appears (trigger, `multiple` tag, synthetic
|
|
132
147
|
row). `getTag` cannot serve this case: it is handed a matched option and there is none. A blank
|
|
133
|
-
return falls back to the raw value
|
|
148
|
+
return falls back to the raw value. Not consulted while `loading` suppresses that same value —
|
|
149
|
+
see below
|
|
150
|
+
- `loading: boolean = false` (reflected) — whether a committed value's real label may still be
|
|
151
|
+
pending because its `<lr-option>` catalog hasn't arrived yet (e.g. an async fetch still in
|
|
152
|
+
flight). While `true`, a committed value that currently matches no option renders the localized
|
|
153
|
+
`loading` placeholder in the trigger label or the relevant `multiple` tag instead of the raw
|
|
154
|
+
value, with no `notInCatalog`/`[part='unknown-value']` badge and no synthetic
|
|
155
|
+
`showUnknownOption` listbox row — "not yet resolved" is a different state from "known to be
|
|
156
|
+
missing". It covers an **empty** selection too: with nothing selected — a create form whose
|
|
157
|
+
catalogue is still being fetched, or an edit form whose saved selection is legitimately empty —
|
|
158
|
+
the trigger renders that same localized text in place of `placeholder`, from the same `loading`
|
|
159
|
+
message key, so both halves of a pending state read the same words and one
|
|
160
|
+
`registerLyraLocale()` translation reaches both. Nothing to re-localize in the consuming app.
|
|
161
|
+
With `loading` false an empty selection renders `placeholder` exactly as before. The trigger's
|
|
162
|
+
accessible name never changes for this: a host `aria-label` wins, then `label`, then
|
|
163
|
+
`placeholder`, then the localized `select` fallback. A value already matching a live option is
|
|
164
|
+
unaffected. Never mutates `value`/`selectedOptions` itself, and does not itself disable the
|
|
165
|
+
trigger — pair it with `disabled` when the control should also be non-interactive while pending
|
|
134
166
|
- `filled: boolean = false` (reflected) — Shoelace alias for the filled trigger treatment
|
|
135
167
|
- `autofocus: boolean = false` / `title: string = ''` — forwarded to the internal trigger
|
|
136
168
|
- `multiple: boolean = false` (reflected) — several options selectable at once; see "Multi-select"
|
|
@@ -182,7 +214,17 @@ from a removed option, or a programmatic assignment with a typo) still commits
|
|
|
182
214
|
stays fully reachable through `value`/`selectedOptions` — but renders a dashed/italic
|
|
183
215
|
`[part='unknown-value']` badge next to the trigger label, or on the relevant `multiple`-mode tag,
|
|
184
216
|
instead of an unexplained bare label, mirroring `<lr-model-select>`'s synthetic "not in catalog"
|
|
185
|
-
stale-value row — see `--lr-select-unknown-value-border-style`/`-color` below.
|
|
217
|
+
stale-value row — see `--lr-select-unknown-value-border-style`/`-color` below. Set `loading` while
|
|
218
|
+
that same value's own `<lr-option>` catalog simply hasn't arrived yet (unlike `<lr-combobox>`,
|
|
219
|
+
`<lr-select>` has no async `source` of its own, so this is consumer-driven rather than automatic):
|
|
220
|
+
a still-unmatched value then renders the localized `loading` placeholder instead of the raw value,
|
|
221
|
+
with no `unknown-value` badge and no synthetic `showUnknownOption` row, since it is not yet known
|
|
222
|
+
to be missing. Once the matching option mounts, the real label renders on the next render with no
|
|
223
|
+
`value`/`selectedOptions` re-assignment needed, whether or not `loading` is also flipped back to
|
|
224
|
+
`false`. The same flag covers the other half of that state: with **nothing** selected the trigger
|
|
225
|
+
renders the same localized `loading` text in place of `placeholder`, so a consumer never has to
|
|
226
|
+
hand-write a conditional placeholder bound to the same flag and re-localize, in its own catalogue,
|
|
227
|
+
the string this control already owns.
|
|
186
228
|
|
|
187
229
|
**Methods:** `focus(options?)`, `blur()`, and `click()` forward to the internal trigger button.
|
|
188
230
|
`show()` and `hide()` return `Promise<void>` and resolve after `lr-after-show`/`lr-after-hide` once
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
|
|
10
10
|
- **Deprecations** none
|
|
11
11
|
- **Optional peers** `dompurify`, `katex`, `marked`, `shiki` — see `llms/peers.md`
|
|
12
|
-
- **Themeable via**
|
|
12
|
+
- **Themeable via** 13 parts, 2 custom properties — see this component's own `@csspart`/`@cssprop` list below
|
|
13
13
|
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`
|
|
14
14
|
|
|
15
15
|
---
|
|
@@ -59,8 +59,12 @@ share one implementation.
|
|
|
59
59
|
|
|
60
60
|
**Slots:** none — content renders from `content`, not a slot.
|
|
61
61
|
|
|
62
|
-
**CSS parts:** `base`, `cursor` (only rendered while `streaming` is `true`)
|
|
63
|
-
`<lr-
|
|
62
|
+
**CSS parts:** `base`, `cursor` (only rendered while `streaming` is `true`), plus every part
|
|
63
|
+
`<lr-markdown-core>` documents forwarded verbatim from the composed `<lr-markdown-core>` in
|
|
64
|
+
Markdown mode via `exportparts`: `content`, `heading`, `paragraph`, `list`, `code-block`,
|
|
65
|
+
`inline-code`, `link`, `table`, `blockquote`, `img`, `math` — the identical forwarded list
|
|
66
|
+
`<lr-streaming-text>` documents, since `<lr-markdown>` and `<lr-markdown-core>` share the same
|
|
67
|
+
documented part vocabulary.
|
|
64
68
|
|
|
65
69
|
**Themeable custom properties:** `--lr-inline-cursor-width` (default `var(--lr-size-0-125rem)`) and
|
|
66
70
|
`--lr-inline-cursor-height` (default `var(--lr-size-1em)`) — the same shared inline-cursor tokens
|