@aceshooting/lyra-ui 11.2.0 → 12.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 +407 -0
- package/README.md +11 -8
- package/custom-elements.json +1 -1
- package/dist/all.d.ts +1 -0
- package/dist/all.js +1 -1
- package/dist/autoloader.js +1 -1
- package/dist/components/agent-tools/tool-param-form/tool-param-form.class.d.ts +0 -1
- package/dist/components/agent-tools/tool-param-form/tool-param-form.class.js +1 -1
- package/dist/components/charts/chart/chart.class.d.ts +13 -1
- package/dist/components/charts/chart/histogram-bin.d.ts +0 -1
- package/dist/components/charts/chart/histogram-bin.js +1 -1
- package/dist/components/charts/chart/lite-chart.class.d.ts +22 -9
- package/dist/components/charts/chart/lite-chart.class.js +6 -6
- package/dist/components/conversation/code-block/code-block-shared.d.ts +4 -54
- package/dist/components/conversation/code-block/code-block-shared.js +1 -1
- package/dist/components/conversation/code-block/shiki-types.d.ts +2 -1
- package/dist/components/conversation/generation-metrics/generation-metrics.class.js +1 -1
- package/dist/components/conversation/model-select/model-select.class.d.ts +0 -3
- package/dist/components/conversation/model-select/model-select.class.js +1 -1
- package/dist/components/conversation/selection-toolbar/selection-toolbar.class.js +1 -1
- package/dist/components/conversation/voice-picker/voice-picker.class.d.ts +0 -1
- package/dist/components/conversation/voice-picker/voice-picker.class.js +1 -1
- package/dist/components/data/data-grid/data-grid.class.js +1 -1
- package/dist/components/data/funnel/funnel.class.d.ts +92 -0
- package/dist/components/data/funnel/funnel.class.js +25 -0
- package/dist/components/data/funnel/funnel.d.ts +1 -0
- package/dist/components/data/funnel/funnel.js +1 -0
- package/dist/components/data/funnel/funnel.styles.d.ts +1 -0
- package/dist/components/data/funnel/funnel.styles.js +127 -0
- package/dist/components/data/graph-query-builder/graph-query-builder.class.d.ts +0 -1
- package/dist/components/data/graph-query-builder/graph-query-builder.class.js +1 -1
- package/dist/components/data/heatmap/heatmap.class.d.ts +199 -14
- package/dist/components/data/heatmap/heatmap.class.js +30 -22
- package/dist/components/data/heatmap/heatmap.styles.js +36 -0
- package/dist/components/data/index.d.ts +1 -0
- package/dist/components/data/index.js +1 -1
- package/dist/components/data/table/table.class.d.ts +18 -4
- package/dist/components/data/table/table.class.js +2 -2
- package/dist/components/data/table/table.styles.js +2 -1
- package/dist/components/data/timeline/timeline-cluster.d.ts +1 -0
- package/dist/components/data/timeline/timeline-cluster.js +1 -0
- package/dist/components/data/timeline/timeline-item.class.d.ts +17 -2
- package/dist/components/data/timeline/timeline-item.class.js +13 -2
- package/dist/components/data/timeline/timeline-item.styles.js +65 -0
- package/dist/components/data/timeline/timeline.class.d.ts +83 -27
- package/dist/components/data/timeline/timeline.class.js +3 -3
- package/dist/components/data/timeline/timeline.d.ts +1 -0
- package/dist/components/data/timeline/timeline.js +1 -1
- package/dist/components/data/timeline/timeline.styles.js +14 -0
- package/dist/components/forms/button/button.class.js +1 -1
- package/dist/components/forms/checkbox/checkbox.class.d.ts +0 -1
- package/dist/components/forms/checkbox/checkbox.class.js +1 -1
- package/dist/components/forms/checkbox-group/checkbox-group.class.d.ts +0 -1
- package/dist/components/forms/checkbox-group/checkbox-group.class.js +1 -1
- package/dist/components/forms/code-editor/code-editor.class.js +1 -1
- package/dist/components/forms/color-picker/color-picker.class.js +1 -1
- package/dist/components/forms/combobox/combobox.class.d.ts +7 -1
- package/dist/components/forms/combobox/combobox.class.js +3 -2
- package/dist/components/forms/combobox/combobox.styles.js +1 -1
- package/dist/components/forms/date-picker/date-input.class.d.ts +23 -0
- package/dist/components/forms/date-picker/date-input.class.js +1 -1
- package/dist/components/forms/emoji-picker/emoji-data-loader.d.ts +0 -1
- package/dist/components/forms/input/native-time-input.class.js +1 -1
- package/dist/components/forms/input/time-input-shared.d.ts +2 -1
- package/dist/components/forms/input/time-input.class.js +1 -1
- package/dist/components/forms/locale-picker/locale-picker.class.d.ts +0 -1
- package/dist/components/forms/locale-picker/locale-picker.class.js +1 -1
- package/dist/components/forms/radio/radio-button.styles.d.ts +0 -1
- package/dist/components/forms/radio/radio-button.styles.js +1 -1
- package/dist/components/forms/radio/radio-group.class.d.ts +0 -1
- package/dist/components/forms/radio/radio-group.class.js +1 -1
- package/dist/components/forms/radio/radio.class.d.ts +0 -1
- package/dist/components/forms/radio/radio.class.js +1 -1
- package/dist/components/forms/rubric-form/rubric-form.class.d.ts +0 -1
- package/dist/components/forms/rubric-form/rubric-form.class.js +1 -1
- package/dist/components/forms/select/select.class.d.ts +0 -1
- package/dist/components/forms/select/select.class.js +1 -1
- package/dist/components/forms/slider/slider.class.d.ts +0 -1
- package/dist/components/forms/slider/slider.class.js +1 -1
- package/dist/components/forms/switch/switch.class.d.ts +0 -1
- package/dist/components/forms/switch/switch.class.js +1 -1
- package/dist/components/forms/time-range/time-range.class.d.ts +0 -1
- package/dist/components/forms/time-range/time-range.class.js +1 -1
- package/dist/components/forms/token-input/token-input.class.d.ts +0 -1
- package/dist/components/forms/token-input/token-input.class.js +1 -1
- package/dist/components/layout/app-rail/app-rail-item.class.d.ts +16 -0
- package/dist/components/layout/app-rail/app-rail-item.class.js +4 -4
- package/dist/components/layout/dashboard-grid/layout-internal.d.ts +0 -1
- package/dist/components/layout/dashboard-grid/layout-internal.js +1 -1
- package/dist/components/layout/details/accordion-owner.d.ts +1 -1
- package/dist/components/layout/filter-bar/filter-bar.class.d.ts +32 -3
- package/dist/components/layout/filter-bar/filter-bar.class.js +2 -1
- package/dist/components/layout/menu/menu-item.class.js +1 -1
- package/dist/components/layout/virtual-list/virtual-list.class.js +1 -1
- package/dist/components/layout/widget/widget.class.d.ts +16 -0
- package/dist/components/layout/widget/widget.class.js +2 -2
- package/dist/components/lr-funnel.d.ts +1 -0
- package/dist/components/lr-funnel.js +1 -0
- package/dist/components/media/av-player/av-metadata.d.ts +0 -2
- package/dist/components/media/av-player/av-metadata.js +1 -1
- package/dist/components/media/file-input/file-input.class.d.ts +0 -1
- package/dist/components/media/file-input/file-input.class.js +1 -1
- package/dist/components/media/flag/flag-peer-bulk-standard.d.ts +25 -0
- package/dist/components/media/flag/flag-peer-bulk-standard.js +1 -0
- package/dist/components/media/flag/flag.class.d.ts +8 -0
- package/dist/components/media/flag/flag.class.js +1 -1
- package/dist/components/media/map/map-loader.d.ts +4 -1
- package/dist/components/media/map/map.class.d.ts +242 -23
- package/dist/components/media/map/map.class.js +2 -2
- package/dist/components/overlays/chip/chip-group.class.d.ts +16 -0
- package/dist/components/overlays/chip/chip-group.class.js +10 -3
- package/dist/components/overlays/overlay/popover.class.d.ts +7 -1
- package/dist/components/overlays/overlay/popover.class.js +1 -1
- package/dist/components/overlays/overlay/tooltip.class.d.ts +1 -0
- package/dist/components/overlays/overlay/tooltip.class.js +1 -1
- package/dist/components/overlays/progress/progress-shared.d.ts +0 -2
- package/dist/components/overlays/progress/progress-shared.js +1 -1
- package/dist/components/overlays/rating/rating.class.js +1 -1
- package/dist/components/retrieval/graph/graph-canvas.d.ts +10 -9
- package/dist/components/retrieval/mind-map/mind-map-layout.d.ts +0 -4
- package/dist/components/retrieval/mind-map/mind-map-layout.js +1 -1
- package/dist/components/utility/format/format-options.d.ts +0 -2
- package/dist/components/utility/format/format-options.js +1 -1
- package/dist/components/utility/icon/icon-resource.d.ts +0 -2
- package/dist/components/utility/icon/icon-resource.js +1 -1
- package/dist/components/viewers/archive-viewer/zip-resource-guard.d.ts +2 -1
- package/dist/components/viewers/docx-viewer/docx-resource-guard.d.ts +0 -3
- package/dist/components/viewers/docx-viewer/docx-resource-guard.js +1 -1
- package/dist/components/viewers/ebook-viewer/ebook-loader.d.ts +2 -1
- package/dist/components/viewers/ebook-viewer/epub-resource-guard.d.ts +0 -3
- package/dist/components/viewers/ebook-viewer/epub-resource-guard.js +1 -1
- package/dist/components/viewers/pdf-viewer/pdf-loader.d.ts +2 -2
- package/dist/components/viewers/pdf-viewer/pdf-viewer.class.js +2 -2
- package/dist/components/viewers/pptx-viewer/pptx-resource-guard.d.ts +0 -2
- package/dist/components/viewers/pptx-viewer/pptx-resource-guard.js +1 -1
- package/dist/components/viewers/spreadsheet-viewer/xlsx-resource-guard.d.ts +0 -5
- package/dist/components/viewers/spreadsheet-viewer/xlsx-resource-guard.js +1 -1
- package/dist/custom-elements-jsx.d.ts +16 -8
- package/dist/events.d.ts +15 -0
- package/dist/internal/accessibility-visibility.d.ts +1 -1
- package/dist/internal/anchor-target.d.ts +1 -3
- package/dist/internal/anchor-target.js +2 -2
- package/dist/internal/anchored-overlay-runtime.d.ts +7 -0
- package/dist/internal/anchored-overlay-runtime.js +1 -0
- package/dist/internal/announcement-text.d.ts +1 -1
- package/dist/internal/ansi.d.ts +0 -4
- package/dist/internal/ansi.js +1 -1
- package/dist/internal/aria-ownership.d.ts +2 -1
- package/dist/internal/autoloader-manifest.js +1 -1
- package/dist/internal/autoloader-tags.d.ts +1 -1
- package/dist/internal/autoloader-tags.js +1 -1
- package/dist/internal/default-strings.generated.d.ts +1 -0
- package/dist/internal/default-strings.generated.js +1 -1
- package/dist/internal/focus-navigation.d.ts +3 -2
- package/dist/internal/form-associated.d.ts +0 -1
- package/dist/internal/form-associated.js +1 -1
- package/dist/internal/form-control-labels.d.ts +0 -3
- package/dist/internal/form-control-labels.js +1 -1
- package/dist/internal/localization-runtime.js +1 -1
- package/dist/internal/localization-types.d.ts +1 -1
- package/dist/internal/localization.js +1 -1
- package/dist/internal/lyra-element.d.ts +7 -14
- package/dist/internal/lyra-element.js +1 -1
- package/dist/internal/nonmodal-overlay-manager.d.ts +9 -0
- package/dist/internal/nonmodal-overlay-manager.js +1 -0
- package/dist/internal/overlay-manager.d.ts +4 -20
- package/dist/internal/overlay-manager.js +1 -1
- package/dist/internal/overlay-stack.d.ts +66 -0
- package/dist/internal/overlay-stack.js +1 -0
- package/dist/internal/package-metadata.d.ts +1 -1
- package/dist/internal/package-metadata.js +1 -1
- package/dist/internal/positioner.js +1 -1
- package/dist/internal/root-registration-allowlist.d.ts +1 -1
- package/dist/internal/root-registration-allowlist.js +1 -1
- package/dist/internal/text-quote.d.ts +1 -1
- package/dist/lyra.d.ts +5 -4
- package/dist/lyra.js +1 -1
- package/dist/ssr/all.d.ts +1 -0
- package/dist/ssr/all.js +1 -1
- package/dist/ssr.d.ts +4 -4
- package/dist/styles/tokens-root.css +488 -0
- package/dist/svelte.d.ts +17 -8
- package/dist/translations/ar.js +1 -1
- package/dist/translations/de.js +1 -1
- package/dist/translations/es.js +1 -1
- package/dist/translations/fa.js +1 -1
- package/dist/translations/fr.js +1 -1
- package/dist/translations/he.js +1 -1
- package/dist/translations/ja.js +1 -1
- package/dist/translations/pt-BR.js +1 -1
- package/dist/translations/ru.js +1 -1
- package/dist/translations/zh-CN.js +1 -1
- package/dist/utilities/localization.js +1 -1
- package/dist/vue.d.ts +16 -8
- package/llms/components/lr-app-rail-item.md +8 -1
- package/llms/components/lr-app-rail.md +6 -0
- package/llms/components/lr-chip-group.md +7 -0
- package/llms/components/lr-chip.md +7 -0
- package/llms/components/lr-combobox.md +42 -0
- package/llms/components/lr-date-input.md +19 -1
- package/llms/components/lr-date-picker.md +19 -1
- package/llms/components/lr-filter-bar.md +50 -1
- package/llms/components/lr-flag.md +14 -0
- package/llms/components/lr-funnel.md +122 -0
- package/llms/components/lr-heatmap.md +55 -8
- package/llms/components/lr-lite-chart.md +20 -8
- package/llms/components/lr-map.md +109 -22
- package/llms/components/lr-option.md +42 -0
- package/llms/components/lr-popover.md +39 -0
- package/llms/components/lr-table.md +7 -3
- package/llms/components/lr-timeline-item.md +49 -24
- package/llms/components/lr-timeline.md +49 -24
- package/llms/components/lr-widget.md +7 -1
- package/llms/index.md +3 -2
- package/llms/peers.md +1 -1
- package/llms/shared.md +110 -1
- package/llms-full.txt +681 -67
- package/llms.txt +1 -1
- package/package.json +26 -8
- package/vscode-css-data.json +63 -0
- package/vscode-html-data.json +108 -23
- package/web-types.json +32738 -310
|
@@ -133,9 +133,21 @@ default). An inherited or direct public value remains authoritative in every tie
|
|
|
133
133
|
Text field + calendar popover, **form-associated** via the shared `FormAssociated` mixin (`name`,
|
|
134
134
|
`value`, `disabled`, `required` all inherited).
|
|
135
135
|
|
|
136
|
-
**Properties (
|
|
136
|
+
**Properties (44):**
|
|
137
137
|
|
|
138
138
|
- `appearance: 'filled'|'outlined'|'filled-outlined' = 'outlined'` (reflected)
|
|
139
|
+
- `appliedPreset: LyraDateRangePreset | undefined` (read-only, new in 11.3.0) — the `presets` entry
|
|
140
|
+
whose button produced the current `value`, or `undefined` when the value was picked on the
|
|
141
|
+
calendar, typed into the field, cleared, or reset. Read it inside your own `change`/`input`
|
|
142
|
+
handler; it is updated before those events are relayed, so a handler observes the preset that
|
|
143
|
+
caused the very commit it is handling, and it is `undefined` while the popover has never been
|
|
144
|
+
opened. Mirrors the nested `lr-date-picker`'s identically-named property across this component's
|
|
145
|
+
shadow boundary, which is where the readback is actually needed: the compact
|
|
146
|
+
text-field-plus-popover shape is the one a dashboard filter uses, the nested picker instance is
|
|
147
|
+
unreachable from outside (a CSS part cannot yield it), and the fact is not recoverable from
|
|
148
|
+
`value` — re-deriving it by string-matching is the mapping table `presets` exists to delete and is
|
|
149
|
+
ambiguous anyway. A property rather than an event detail because `input`/`change` here are
|
|
150
|
+
**native** events that cannot carry one
|
|
139
151
|
- `assumeInteractionOn: string[] = ['input']` (JS only)
|
|
140
152
|
- `autocomplete: string = ''`
|
|
141
153
|
- `dayContent?: LyraDatePickerDayContent` (JS only)
|
|
@@ -160,6 +172,12 @@ Text field + calendar popover, **form-associated** via the shared `FormAssociate
|
|
|
160
172
|
- `pill: boolean = false` (reflected)
|
|
161
173
|
- `placement: LyraDateInputPlacement = 'bottom-start'` (reflected; all 12 side/alignment
|
|
162
174
|
placements are accepted)
|
|
175
|
+
- `presets: LyraDateRangePreset[] = []` (JS only, new in 11.1.0) — forwarded verbatim to the nested
|
|
176
|
+
`lr-date-picker`, whose own `presets` documents the semantics (range mode only, open bounds
|
|
177
|
+
resolving to `min`/`max`, unset renders nothing). Forwarded rather than reimplemented because the
|
|
178
|
+
picker lives in this component's shadow root, so a consumer has no route to it. The row's parts
|
|
179
|
+
are re-exported as `presets`/`preset-button`, and `appliedPreset` above reports which entry
|
|
180
|
+
produced the current value
|
|
163
181
|
- `readonly: boolean = false` and `required: boolean = false` (reflected)
|
|
164
182
|
- `size: LyraSize = 'm'` (reflected; `2xs`–`xl` and aliases)
|
|
165
183
|
- `today: string = ''` (reflected ISO override)
|
|
@@ -52,7 +52,8 @@ value therefore stays inside a 320px LTR or RTL bar, with the chip's own label e
|
|
|
52
52
|
overflow ownership rather than widening the page.
|
|
53
53
|
|
|
54
54
|
Each edit exposes one filter-bar `lr-input` carrying a detached, deeply frozen snapshot of the
|
|
55
|
-
complete value object
|
|
55
|
+
complete value object, plus the `filterId` that changed and (new in 11.3.0) `appliedPreset` — see
|
|
56
|
+
"Date-range quick ranges" below. A built-in or
|
|
56
57
|
custom control's own `lr-input`/`lr-change` aliases stay inside the wrapper so their incompatible
|
|
57
58
|
detail shapes cannot escape as duplicate bar events; native-style `input`/`change` events from the
|
|
58
59
|
composed controls continue bubbling normally.
|
|
@@ -102,6 +103,54 @@ to `<lr-input>` for an open-ended free-text query rather than a closed choice se
|
|
|
102
103
|
filter's value is the raw query string, verbatim, and its chip shows exactly that string — the same
|
|
103
104
|
text the user typed, not a truncated or normalized form.
|
|
104
105
|
|
|
106
|
+
### Date-range quick ranges
|
|
107
|
+
|
|
108
|
+
A `'date-range'` definition also accepts `presets?: readonly LyraDateRangePreset[]` (new in 11.3.0),
|
|
109
|
+
forwarded to its composed `<lr-date-input>` exactly like `min`/`max`, so the quick-range row
|
|
110
|
+
("Today", "Last 7 days", "All time") renders inside that filter's own calendar popover. Entries are
|
|
111
|
+
`LyraDateRangePreset { label, start?, end? }` with ISO `YYYY-MM-DD` bounds; an omitted bound is open
|
|
112
|
+
and resolves to the filter's `min`/`max`, and an open bound with no corresponding limit renders that
|
|
113
|
+
button disabled. `presets` is deliberately **not** accepted on a single `'date'` filter: a preset
|
|
114
|
+
names two dates, so `lr-date-picker` ignores the list outside range mode, and a list passed there is
|
|
115
|
+
dropped rather than rendering a row that cannot do anything.
|
|
116
|
+
|
|
117
|
+
The `lr-input` emitted by such a commit carries `appliedPreset`, the definition entry whose button
|
|
118
|
+
produced it — the bar's own frozen snapshot, so it compares identical to `filters[i].presets[j]`. It
|
|
119
|
+
is `undefined` for every other filter type and for a range picked or typed by hand. A filter bar
|
|
120
|
+
whose values round-trip through a query string needs it because `value` holds only the frozen ISO
|
|
121
|
+
range: persisting "Last 7 days" as a preset id keeps it meaning the last 7 days after the next
|
|
122
|
+
reload, and re-deriving it by string-matching `value` is both the mapping table `presets` exists to
|
|
123
|
+
delete and ambiguous (Today and This month coincide on the 1st). It rides the event rather than
|
|
124
|
+
`value` because it is metadata about one edit, not a filter value — `value` stays the plain,
|
|
125
|
+
JSON-serializable record it has always been.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const filters: LyraFilterBarFilterDefinition[] = [
|
|
129
|
+
{
|
|
130
|
+
filterId: "period",
|
|
131
|
+
label: "Reporting period",
|
|
132
|
+
type: "date-range",
|
|
133
|
+
min: "2020-01-01",
|
|
134
|
+
max: "2030-12-31",
|
|
135
|
+
presets: [
|
|
136
|
+
{ label: "Last 7 days", start: "2026-08-13", end: "2026-08-19" },
|
|
137
|
+
{ label: "This month", start: "2026-08-01", end: "2026-08-31" },
|
|
138
|
+
{ label: "All time" },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
bar.addEventListener("lr-input", (event) => {
|
|
144
|
+
const { value, filterId, appliedPreset } = event.detail;
|
|
145
|
+
persist({ ...value, periodPreset: appliedPreset?.label });
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Before this, the only way to give a filter-bar date range a quick-range row was `type: 'custom'`,
|
|
150
|
+
which means hand-rendering an `lr-date-input` plus a full adapter (`clearValue`, `isEmpty`,
|
|
151
|
+
`formatValue`) to set one property, and forfeits the built-in date-range chip localization described
|
|
152
|
+
next.
|
|
153
|
+
|
|
105
154
|
Date chips localize exactly one round-trip-valid ISO `YYYY-MM-DD` segment; date-range chips require
|
|
106
155
|
exactly two slash-separated segments. Four-digit
|
|
107
156
|
years `0000`–`0099` retain those literal years rather than inheriting JavaScript's 1900 offset;
|
|
@@ -219,6 +219,20 @@ import "@aceshooting/lyra-ui/components/media/flag/flag-peer.js";
|
|
|
219
219
|
unneeded 249-entry fetch. Only the standard tier is bulk-fetched this way;
|
|
220
220
|
`fidelity="compact"/"detailed"` on individual elements still resolves through its own lazy
|
|
221
221
|
per-code loader.
|
|
222
|
+
- Rendering many flags at once **and** leaving every `<lr-flag>` on the default
|
|
223
|
+
`fidelity="standard"`: import
|
|
224
|
+
`@aceshooting/lyra-ui/components/media/flag/flag-peer-bulk-standard.js` instead of
|
|
225
|
+
`flag-peer-bulk.js` (never more than one of the three peer entries — each
|
|
226
|
+
`setFlagUrlResolver()` call replaces the previous resolver). It registers
|
|
227
|
+
`@aceshooting/lyra-flags/standard`'s `createFlagUrlResolver()`, which is backed by the same
|
|
228
|
+
standard-tier-only eager map as the root's, without statically importing the detailed and
|
|
229
|
+
compact loader maps the root entry needs for its per-call `variant`. Those maps are what
|
|
230
|
+
`flag-peer-bulk.js` pays for its batching: on a real production build with a 156-country flag
|
|
231
|
+
column, routing bulk resolution through the package root emitted +65 detailed SVGs and +31
|
|
232
|
+
compact WebPs — +15.8MB of assets no route rendered — swamping the chunk-count win the bulk path
|
|
233
|
+
exists for. The tradeoff is the tier commitment: `fidelity="compact"/"detailed"` on an individual
|
|
234
|
+
element resolves to that code's standard asset instead (a silent no-op, not an error), so use
|
|
235
|
+
`flag-peer-bulk.js` when per-instance fidelity must actually be honoured.
|
|
222
236
|
- 65 of `@aceshooting/lyra-flags`' 249 flags (any whose design includes a detailed coat of
|
|
223
237
|
arms/seal/emblem, e.g. `es`, `pt`, `sv`) ship **three** fidelity tiers, selected via the
|
|
224
238
|
`fidelity` property (`flagUrl(code, { variant: fidelity })` under the hood): `"compact"` — a tiny WebP raster for
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<!-- GENERATED by scripts/build-llms.mjs from llms/data.md — do not edit this file. -->
|
|
2
|
+
|
|
3
|
+
# `lr-funnel`
|
|
4
|
+
|
|
5
|
+
- **Import** `import '@aceshooting/lyra-ui/components/lr-funnel.js';` (stable tag alias; registers the tag)
|
|
6
|
+
- **Class** `LyraFunnel`, also available unregistered from `@aceshooting/lyra-ui/components/data/funnel/funnel.class.js`
|
|
7
|
+
- **Family** `components/data/` — see `llms/index.md` for its siblings
|
|
8
|
+
- **Status** `experimental` since `12.0.0` — see the maturity and deprecation policy in `llms/shared.md`
|
|
9
|
+
- **Deprecations** none
|
|
10
|
+
- **Optional peers** none
|
|
11
|
+
- **Themeable via** 14 parts, 4 custom properties — see this component's own `@csspart`/`@cssprop` list below
|
|
12
|
+
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## `lr-funnel`
|
|
17
|
+
|
|
18
|
+
Dependency-free conversion funnel (no charting library): an ordered set of stages, each drawn as a
|
|
19
|
+
bar whose length is that stage's share of the **first** stage, read top-to-bottom as progressive
|
|
20
|
+
drop-off. It sits beside `lr-gauge` and `lr-heatmap` as an analytics primitive rather than a general
|
|
21
|
+
chart type.
|
|
22
|
+
|
|
23
|
+
It is deliberately not a sorted bar chart: it normalizes to the first stage rather than to the data
|
|
24
|
+
maximum, draws no value axis, and reads as stage-to-stage retention rather than category comparison.
|
|
25
|
+
Reach for `lr-bar-chart`/`lr-lite-chart` when you actually want a value axis and category comparison.
|
|
26
|
+
|
|
27
|
+
The whole chart is plain HTML — stage names, absolute values, shares and drop-off percentages are
|
|
28
|
+
real text inside an `<ol>`, so there is no sighted-only drawing needing a separate transcript. The
|
|
29
|
+
list carries the accessible name; a host `aria-label` overrides `label`.
|
|
30
|
+
|
|
31
|
+
**Properties:**
|
|
32
|
+
|
|
33
|
+
- `stages: readonly LyraFunnelStage[] = []` (property only; every share is measured against
|
|
34
|
+
`stages[0]`)
|
|
35
|
+
- `comparison: readonly LyraFunnelStage[] = []` (property only — an optional baseline/peer cohort
|
|
36
|
+
drawn behind each bar as a dashed outline, normalized to **its own** first stage so a cohort's
|
|
37
|
+
funnel *shape* stays comparable against a baseline whose absolute volumes are not)
|
|
38
|
+
- `comparisonLabel: string = ''` (attribute `comparison-label`; falls back to a localized generic
|
|
39
|
+
label)
|
|
40
|
+
- `label: string = ''` (accessible name for the stage list)
|
|
41
|
+
- `dropoff: boolean = true` (reflected; `dropoff="false"` in markup really does turn it off —
|
|
42
|
+
the property uses the explicit `false`-parsing converter, not attribute presence)
|
|
43
|
+
- `sharePrecision: number = 0` (attribute `share-precision`; fraction digits for every share and
|
|
44
|
+
drop-off percentage, clamped to `0`–`20`)
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
interface LyraFunnelStage {
|
|
48
|
+
readonly label: string;
|
|
49
|
+
readonly value: number;
|
|
50
|
+
readonly color?: string; // CSS color; unparseable values fall back to --lr-funnel-bar-color
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Events:** none.
|
|
55
|
+
|
|
56
|
+
**Slots:** none.
|
|
57
|
+
|
|
58
|
+
**CSS parts:** `base`, `stages` (the `<ol>`), `stage`, `dropoff`, `stage-header`, `stage-label`,
|
|
59
|
+
`stage-value`, `stage-share`, `comparison-value`, `track`, `bar`, `bar-overflow` (a second token on
|
|
60
|
+
`bar` when the stage exceeds the first stage), `comparison-bar`, `empty`
|
|
61
|
+
|
|
62
|
+
**Themeable custom properties:** `--lr-funnel-bar-color` (default `var(--lr-color-brand)`),
|
|
63
|
+
`--lr-funnel-comparison-color` (default `var(--lr-color-border-strong)`), `--lr-funnel-track-color`
|
|
64
|
+
(default `var(--lr-color-surface-raised)`), `--lr-funnel-bar-size` (track thickness, default
|
|
65
|
+
`var(--lr-size-1-5rem)`).
|
|
66
|
+
|
|
67
|
+
**Optional peer deps:** none.
|
|
68
|
+
|
|
69
|
+
```html
|
|
70
|
+
<script type="module">
|
|
71
|
+
import '@aceshooting/lyra-ui/components/data/funnel/funnel.js';
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<lr-funnel id="signup" label="Self-serve signup" share-precision="1"></lr-funnel>
|
|
75
|
+
|
|
76
|
+
<script type="module">
|
|
77
|
+
const funnel = document.querySelector('#signup');
|
|
78
|
+
funnel.stages = [
|
|
79
|
+
{ label: 'Visited pricing', value: 12480 },
|
|
80
|
+
{ label: 'Started trial', value: 4310 },
|
|
81
|
+
{ label: 'Converted to paid', value: 512 },
|
|
82
|
+
];
|
|
83
|
+
// Normalized to ITS OWN first stage, so a 380-visitor account still compares against a
|
|
84
|
+
// 12,480-visitor peer group.
|
|
85
|
+
funnel.comparison = [
|
|
86
|
+
{ label: 'Visited pricing', value: 380 },
|
|
87
|
+
{ label: 'Started trial', value: 141 },
|
|
88
|
+
{ label: 'Converted to paid', value: 12 },
|
|
89
|
+
];
|
|
90
|
+
funnel.comparisonLabel = 'Acme Corp';
|
|
91
|
+
</script>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Known gotchas:**
|
|
95
|
+
|
|
96
|
+
- **Shares are of the first stage, not of the previous one.** `stage-share` answers "how much of the
|
|
97
|
+
top of the funnel is left here?"; the separate `dropoff` row answers "what changed since the
|
|
98
|
+
previous stage?". Both are rendered because the percentage is usually the interesting number and
|
|
99
|
+
the absolute count is the credibility check.
|
|
100
|
+
- **A zero or negative first stage cannot define a share.** Every `stage-share` is omitted, every bar
|
|
101
|
+
is zero-length, and only the absolute values render. Nothing is silently divided by zero and no
|
|
102
|
+
`NaN` reaches the geometry.
|
|
103
|
+
- **A stage larger than its predecessor is legal** (funnel re-entry). Its share is reported
|
|
104
|
+
truthfully above 100% in text, while the bar clamps to the track and gains the `bar-overflow` part
|
|
105
|
+
token so you can style it. Drop-off for that stage reads as an increase.
|
|
106
|
+
- **Drop-off is omitted, not zeroed, when the previous stage is non-positive** — a change relative to
|
|
107
|
+
zero is undefined.
|
|
108
|
+
- **A comparison series of a different length pairs by index.** Extra comparison entries are ignored;
|
|
109
|
+
stages past its end simply get no comparison bar. A comparison series whose own first value is
|
|
110
|
+
zero or negative draws nothing.
|
|
111
|
+
- **Non-finite `value`s are treated as `0`** rather than blanking the stage, so one bad row cannot
|
|
112
|
+
take the chart with it.
|
|
113
|
+
- Values, shares and drop-off percentages all format through the component's effective locale
|
|
114
|
+
(`locale` attribute, or an inherited one) — never a hardcoded `en`.
|
|
115
|
+
- Bars grow from the inline-start edge and every dimension is a logical property, so `dir="rtl"`
|
|
116
|
+
needs no extra work. The component never sets its own `dir`.
|
|
117
|
+
- Layout responds to the **container** (`container-type: inline-size` on `base`), not the viewport:
|
|
118
|
+
below roughly `18rem` of allocated width the stage name moves to its own line so the value and
|
|
119
|
+
share stay together. Bar motion is a token-driven transition that stops under
|
|
120
|
+
`prefers-reduced-motion`.
|
|
121
|
+
- No events, no interaction model, no zoom/pan. If you need a clickable funnel, wrap the element and
|
|
122
|
+
handle clicks yourself.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
- **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
|
|
9
9
|
- **Deprecations** none
|
|
10
10
|
- **Optional peers** none
|
|
11
|
-
- **Themeable via**
|
|
11
|
+
- **Themeable via** 18 parts, 12 custom properties — see this component's own `@csspart`/`@cssprop` list below
|
|
12
12
|
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`
|
|
13
13
|
|
|
14
14
|
---
|
|
@@ -98,6 +98,38 @@ weekdayLabelText?: (jsWeekday:number)=>string|undefined; monthLabelText?:
|
|
|
98
98
|
`colLabelHeight="auto"` to have the band size itself to the rotated extent. **Not mirrored under
|
|
99
99
|
`dir="rtl"`** — both grid modes deliberately retain physical LTR geometry, so leaning one axis'
|
|
100
100
|
labels the other way would be incoherent
|
|
101
|
+
- `stickyLabels: 'none' | 'rows' | 'cols' | 'both' = 'none'` (attribute `sticky-labels`, reflected)
|
|
102
|
+
— freezes a **matrix** label band against the grid's own scrolling. `'rows'` pins the row-label
|
|
103
|
+
gutter so it survives horizontal scrolling, `'cols'` pins the column-label band so it survives
|
|
104
|
+
vertical scrolling, `'both'` pins both. Labels and cells otherwise share one bitmap, so neither
|
|
105
|
+
band can be `position: sticky` on its own and a tall matrix scrolls its column header out of
|
|
106
|
+
view; the only workaround was a light-DOM mirror row that had to hardcode the gutter width and
|
|
107
|
+
cell size, which made it mutually exclusive with `row-label-width="auto"`. A frozen band is
|
|
108
|
+
repainted into its own layer in the same draw pass, from the same resolved `matrixGeometry` the
|
|
109
|
+
cells were painted with, so it tracks a `row-label-width`/`col-label-height` `"auto"`
|
|
110
|
+
re-resolution, a resize, and a DPR change without drifting a pixel. Freezing needs something to
|
|
111
|
+
scroll, so the frozen modes wrap the grid in a `[part="grid"]` scrollport: it is bounded inline by
|
|
112
|
+
the host's own allocation — a matrix wider than a 320px host scrolls inside the component instead
|
|
113
|
+
of overflowing it — and unbounded in block until you set `--lr-heatmap-grid-max-block-size`, which
|
|
114
|
+
a frozen column band needs in order to have vertical scrolling to stay behind. The bands are
|
|
115
|
+
`aria-hidden` duplicates of pixels the canvas already painted, so the accessible representation is
|
|
116
|
+
unchanged, and the `accessibleCells` overlay moves inside the scrollport so the cell buttons
|
|
117
|
+
scroll with the canvas they cover. The grid keeps this component's physical LTR geometry under
|
|
118
|
+
`dir="rtl"`, so the frozen gutter stays on the same physical side as the labels the canvas paints.
|
|
119
|
+
Matrix mode only, like `matrixGeometry`: the property is read in calendar mode but has no effect
|
|
120
|
+
there, since a calendar's axes are a different geometry (fixed weekday gutter, month band, and the
|
|
121
|
+
optional `columnX`/`rowY` overrides). Unset (`'none'`, the default) renders exactly what it always
|
|
122
|
+
did — one canvas, no scrollport, no extra elements — and an unsupported value normalizes to it.
|
|
123
|
+
The union is re-exported from the package root as `LyraHeatmapStickyLabels`, so a typed consumer
|
|
124
|
+
can annotate a variable or a framework prop with it. Everything positioned in canvas coordinates
|
|
125
|
+
moves into the scrollport with the cells: `[part="tooltip"]` renders inside it, so it stays on
|
|
126
|
+
the cell it describes through a scroll instead of drifting by the scroll offset, and because
|
|
127
|
+
`overflow: auto` clips whatever leaves the scrollport, the tooltip is kept inside the visible
|
|
128
|
+
window too — clamped along the inline axis and flipped to below its cell when a frozen band
|
|
129
|
+
leaves no room above it. Arrow-key navigation scrolls the focused cell into that window, clear of
|
|
130
|
+
the frozen bands, which the frozen modes need in their own right: the canvas is the roving tab
|
|
131
|
+
stop, its focus ring is painted into the bitmap rather than carried by a focusable element the
|
|
132
|
+
browser would scroll to, and it calls `preventDefault()` on the arrows
|
|
101
133
|
- `maxCellSize?: number` (attribute `max-cell-size`) — ceiling, in CSS px, on the cell size
|
|
102
134
|
`fitToWidth` derives from the host width, in **both** modes. Exists because `fitToWidth` divides
|
|
103
135
|
the _whole_ host width across the grid, so a 5-week calendar or a 3-column matrix in a wide pane
|
|
@@ -227,7 +259,10 @@ color?: string; label?: string; partOfRamp?: boolean }`: a discrete legend key r
|
|
|
227
259
|
are never consulted by the color ramp, the bucket math, the tooltip, or the generated accessible
|
|
228
260
|
name, so adding them changes nothing a cell renders. Labeled `annotations` still render their
|
|
229
261
|
`[part="legend-annotation"]` entries after the stops. Unset (the default) or an empty array
|
|
230
|
-
reproduces the exact gradient legend, unchanged.
|
|
262
|
+
reproduces the exact gradient legend, unchanged. Reassigning stops whose `value`/`color`/`label`/
|
|
263
|
+
`partOfRamp` fields are unchanged does not schedule a canvas redraw. The assignment still takes
|
|
264
|
+
a fresh frozen ownership snapshot, so mutating and reassigning the same caller array is detected
|
|
265
|
+
while mutation without reassignment remains isolated. A dev-mode-only warning fires when a stop's
|
|
231
266
|
`color` doesn't match the corresponding `colorSteps` entry — set `partOfRamp: false` on a stop
|
|
232
267
|
that is a real, distinctly-colored swatch deliberately outside the sequential ramp (e.g. a fixed
|
|
233
268
|
"no data" color next to an N-step ramp) to exclude just that stop from the comparison; defaults to
|
|
@@ -237,9 +272,12 @@ color?: string; label?: string; partOfRamp?: boolean }`: a discrete legend key r
|
|
|
237
272
|
color-scheme change; called automatically on theme changes, exposed for a consumer that needs to
|
|
238
273
|
force a redraw manually. `matrixGeometry: Readonly<{ padLeft: number; padTop: number; cellSize:
|
|
239
274
|
number }> | undefined` — the gutter/cell geometry the last matrix-mode draw actually painted with,
|
|
240
|
-
in CSS pixels; `undefined` in calendar mode. Lets a light-DOM consumer
|
|
241
|
-
|
|
242
|
-
|
|
275
|
+
in CSS pixels; `undefined` in calendar mode. Lets a light-DOM consumer line up with the canvas
|
|
276
|
+
without hardcoding the same numbers `row-label-width`/`col-label-height`'s `"auto"` resolution would
|
|
277
|
+
otherwise keep private. For the case that motivated it — a frozen header or gutter on a tall or wide
|
|
278
|
+
matrix — prefer `stickyLabels`, which freezes the band inside the component and needs no mirror at
|
|
279
|
+
all; the getter remains the way to align a *separate* element (a sibling chart, a custom overlay)
|
|
280
|
+
with the grid.
|
|
243
281
|
|
|
244
282
|
**Events:** `lr-cell-click` (fired on click, or Enter/Space on the keyboard-focused cell —
|
|
245
283
|
`detail: { row, col, value }` in matrix mode, `detail: { date, value }` in calendar mode),
|
|
@@ -250,8 +288,12 @@ in calendar mode)
|
|
|
250
288
|
|
|
251
289
|
**Slots:** none.
|
|
252
290
|
|
|
253
|
-
**CSS parts:** `base`, `canvas`, `
|
|
254
|
-
|
|
291
|
+
**CSS parts:** `base`, `canvas`, `grid` (the scrollport wrapping the canvas while `stickyLabels`
|
|
292
|
+
freezes an axis — absent entirely otherwise), `row-labels`/`col-labels` (the frozen label bands,
|
|
293
|
+
rendered only for the axis `stickyLabels` names), `cells` (opt-in per-cell overlay), `cell` (one opt-in native cell
|
|
294
|
+
button), `tooltip` (hover tooltip, positioned over the hovered cell — inside `[part="grid"]` while
|
|
295
|
+
`stickyLabels` freezes an axis, so it scrolls with the cells, and a `[part="base"]` child
|
|
296
|
+
otherwise),
|
|
255
297
|
`live-region` (visually-hidden, `aria-hidden` mirror of the keyboard-focused cell; the actual
|
|
256
298
|
announcement uses the shared light-DOM polite sink), `projection-limit` (localized assistive
|
|
257
299
|
disclosure when bounded canonicalization truncates input), `legend`, `legend-lo`, `legend-hi` (both omitted, along with the gradient
|
|
@@ -287,7 +329,12 @@ dedicated token distinct from both the focus ring and the annotation ring so a h
|
|
|
287
329
|
independently). `--lr-heatmap-tooltip-bg` (default
|
|
288
330
|
`var(--lr-color-surface)`) and `--lr-heatmap-tooltip-text` (default `var(--lr-color-text)`) —
|
|
289
331
|
unlike the canvas-drawn tokens above, `[part="tooltip"]` is a real DOM element and consumes these
|
|
290
|
-
directly, no `getComputedStyle` bridging needed.
|
|
332
|
+
directly, no `getComputedStyle` bridging needed. `--lr-heatmap-sticky-label-bg` (default
|
|
333
|
+
`var(--lr-color-surface)` — the backdrop painted under a frozen `stickyLabels` band, resolved via
|
|
334
|
+
`getComputedStyle` like the other canvas-drawn tokens; it must stay **opaque**, since it covers the
|
|
335
|
+
same labels the scrolling canvas painted underneath it) and `--lr-heatmap-grid-max-block-size`
|
|
336
|
+
(default `none` — the block-size ceiling of the `[part="grid"]` scrollport, consumed directly by
|
|
337
|
+
that real DOM element; set it to give a frozen column band vertical scrolling to stay behind). Also consumes `--lr-color-text-quiet` (axis label
|
|
291
338
|
color), `--lr-space-xs`, `--lr-radius`/`--lr-shadow` (tooltip box), and `--lr-focus-ring-width`/
|
|
292
339
|
`--lr-focus-ring-offset` (the real `[part="canvas"]:focus-visible` DOM outline, stroked in the
|
|
293
340
|
same color as `--lr-heatmap-focus-ring-color`).
|
|
@@ -87,13 +87,16 @@ passthrough). Not a subclass of `LyraChart`.
|
|
|
87
87
|
- `barWidth: number = 32` (attribute `bar-width`, px) — each bar's fixed width in `layout="scroll"`
|
|
88
88
|
mode; ignored in the default `'fit'` mode. An excessive value is reduced as needed by the
|
|
89
89
|
1,000,000px scroll-content ceiling.
|
|
90
|
-
- `maxLabels?: number` (attribute `max-labels
|
|
91
|
-
actually render *text
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
- `maxLabels?: number | 'auto'` (attribute `max-labels`) — decimates which category axis labels
|
|
91
|
+
actually render *text*: always shows the first and last label and roughly evenly distributes the
|
|
92
|
+
rest between them. A number is authoritative. `'auto'` derives the cap after each resize from the
|
|
93
|
+
resolved plot width and widest rendered caller label, using the same deterministic 7px-per-
|
|
94
|
+
character estimate as label ellipsis plus 10px of lane breathing room. It therefore responds to
|
|
95
|
+
either `layout` mode without DOM text measurement or browser-specific font metrics. Unset (the
|
|
96
|
+
default) renders every label, unchanged. Each rendered category label is allocation-aware:
|
|
97
|
+
narrow/long text is ellipsized before paint, with the complete caller label retained as its
|
|
98
|
+
accessible name. Independently, the global 1,000-record safety sampler may bound both marks and
|
|
99
|
+
labels for very large category×series input.
|
|
97
100
|
- `barX?: (index: number) => number` (attribute: false, bar type only) — overrides the internal
|
|
98
101
|
per-category x-origin formula (`plotX + i * slot`) used by both bars and their axis labels, so a
|
|
99
102
|
consumer can pixel-align this chart's bars with a sibling `<lr-heatmap>` calendar's week columns
|
|
@@ -115,7 +118,16 @@ passthrough). Not a subclass of `LyraChart`.
|
|
|
115
118
|
- `skipZero: boolean = false` (attribute `skip-zero`, bar type only) — omits a bar entirely (no
|
|
116
119
|
mark/tabindex/tooltip) for a value that is exactly `0`; `null`/non-finite values are always
|
|
117
120
|
skipped regardless.
|
|
118
|
-
- `valueAxisGutter?: number` (attribute `value-axis-gutter`) — value-axis gutter width
|
|
121
|
+
- `valueAxisGutter?: number | 'auto'` (attribute `value-axis-gutter`) — value-axis gutter width in
|
|
122
|
+
CSS px. A finite number is authoritative (clamped to 0…1,000,000px as before). `'auto'` sizes the
|
|
123
|
+
gutter from the exact value-tick strings rendered in that pass — `formatter` output first, then
|
|
124
|
+
`tickFormat`, then the component's `effectiveLocale` number formatting — using the deterministic
|
|
125
|
+
7px-per-character estimate plus 14px for the tick offset and font-width variance. Automatic
|
|
126
|
+
sizing never shrinks below the legacy 36px default. In `layout="fit"` it is bounded to the smaller
|
|
127
|
+
of 240px or 40% of the measured SVG width, so a pathological formatter result cannot consume the
|
|
128
|
+
plot. In `layout="scroll"` the cap is 240px: that SVG has an explicit content width, so deriving a
|
|
129
|
+
percentage cap from its own ResizeObserver result would create a shrinking feedback loop. The
|
|
130
|
+
gutter remains at logical start under RTL. Unset keeps exactly 36px.
|
|
119
131
|
- `barGapRatio?: number` (attribute `bar-gap-ratio`) — overrides the internal 0.2 `BAR_GROUP_GAP`
|
|
120
132
|
fraction of a category slot left as a gap between categories. Unset keeps the fixed 0.2.
|
|
121
133
|
- `scale: 'linear' | 'sqrt' | 'logarithmic' = 'linear'` — `'sqrt'` (**bar type only**) maps a bar's
|
|
@@ -16,13 +16,19 @@
|
|
|
16
16
|
## `lr-map`
|
|
17
17
|
|
|
18
18
|
A `maplibre-gl` wrapper with a declarative legend, a single choropleth GeoJSON fill layer, markers,
|
|
19
|
-
and additive plain
|
|
20
|
-
operations. Its runtime value is the
|
|
19
|
+
and additive `dataLayers` — plain GeoJSON, natively clustered points, or a heatmap density surface —
|
|
20
|
+
plus a peer-neutral `map` getter for common imperative operations. Its runtime value is the
|
|
21
|
+
underlying MapLibre map. The component observes its own map-container allocation and calls the
|
|
22
|
+
peer's `resize()` when that allocation changes.
|
|
21
23
|
|
|
22
24
|
**Properties:**
|
|
23
25
|
|
|
24
26
|
- `center: [number, number] = [0, 0]`
|
|
25
27
|
- `zoom: number = 2`
|
|
28
|
+
- `renderWorldCopies?: boolean` (attribute: false) — forwarded to MapLibre when its map is
|
|
29
|
+
constructed. Leave it unset to preserve MapLibre's own current default; set `false` before
|
|
30
|
+
construction to stop repeating the world horizontally. This is a construction-time option, so a
|
|
31
|
+
later change takes effect after the component is disconnected and reconnected.
|
|
26
32
|
- `mapStyle?: LyraMapStyleSpecification | string` (attribute: false) — required before a map is
|
|
27
33
|
constructed. Object assignments are detached and recursively frozen; create and reassign a new
|
|
28
34
|
style to update it. `LyraMapStyleSpecification` is the peer-neutral structural subset accepted from
|
|
@@ -97,10 +103,15 @@ string; geojson: GeoJSON.FeatureCollection; field: string; stops: [number, strin
|
|
|
97
103
|
gotchas. Entries with non-finite coordinates or latitude outside `[-90, 90]` are skipped without
|
|
98
104
|
aborting valid siblings. `color` is used only when the browser accepts it as CSS `color`;
|
|
99
105
|
declaration breaks and `url()` paint servers fall back to MapLibre's default marker color.
|
|
106
|
+
Every retained marker is a named `role="button"` tab stop, including one without a popup. Click,
|
|
107
|
+
Enter, and Space emit `lr-map-marker-activate`; Space suppresses its page-scroll default while
|
|
108
|
+
preserving MapLibre's popup toggle. A popup-bearing marker additionally exposes
|
|
109
|
+
`aria-haspopup="dialog"`, `aria-controls`, and explicit `aria-expanded`.
|
|
100
110
|
- `dataLayers: LyraMapGeoJsonDataLayer[] = []` (attribute: false) —
|
|
101
111
|
`LyraMapGeoJsonDataLayer { sourceId: string; geojson: GeoJSON.Feature |
|
|
102
112
|
GeoJSON.FeatureCollection; tone?: 'accent' | 'success' | 'warning' |
|
|
103
|
-
'danger' | 'neutral'; color?: string; strokeColor?: string
|
|
113
|
+
'danger' | 'neutral'; color?: string; strokeColor?: string; kind?: LyraMapDataLayerKind;
|
|
114
|
+
heatmap?: LyraMapHeatmapOptions; cluster?: LyraMapClusterOptions }`. `sourceId` is trimmed and must be nonempty; the first layer for a
|
|
104
115
|
`sourceId` wins and blank or later duplicate records are ignored. Each retained entry adds one
|
|
105
116
|
GeoJSON source plus three geometry-filtered layers
|
|
106
117
|
(fill, line, and circle, so a mixed `FeatureCollection` renders correctly), colored from the
|
|
@@ -120,9 +131,65 @@ GeoJSON.FeatureCollection; tone?: 'accent' | 'success' | 'warning' |
|
|
|
120
131
|
from overwriting or removing a same-named source supplied by `mapStyle`. Independent of
|
|
121
132
|
`choropleth` — no `field`/`stops` color-interpolation, just the geometry rendered in a flat tone;
|
|
122
133
|
use `choropleth` instead when you need a data-driven color ramp. An entry whose `sourceId`
|
|
123
|
-
persists across a `dataLayers` reassignment gets its GeoJSON updated in place (`
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
persists across a `dataLayers` reassignment gets its GeoJSON updated in place (`updateData()`
|
|
135
|
+
when stable feature IDs make a safe diff possible, otherwise `setData()`), one that's dropped has
|
|
136
|
+
its private source/layers removed, and a genuinely new `sourceId` gets new resources — nothing
|
|
137
|
+
leaks on removal, style change, or disconnect.
|
|
138
|
+
|
|
139
|
+
`cluster` and `kind` (both new in 11.3.0) opt one entry out of that three-layer geometry split.
|
|
140
|
+
**Both are strictly additive: an entry that sets neither renders exactly what it rendered before,
|
|
141
|
+
down to the layer ids and the point layer's filter.**
|
|
142
|
+
|
|
143
|
+
`cluster?: LyraMapClusterOptions { radius?: number; maxZoom?: number; radiusSteps?: [number,
|
|
144
|
+
number][]; colorSteps?: [number, string][]; countFont?: string[] }` turns the entry's source into a
|
|
145
|
+
natively clustered one — `cluster`/`clusterRadius` (default 50)/`clusterMaxZoom` (default 14) on
|
|
146
|
+
the source, plus a `${sourceId}-cluster` circle filtered on `has('point_count')`, a
|
|
147
|
+
`${sourceId}-cluster-count` label, and a `${sourceId}-circle` layer for the points that stayed
|
|
148
|
+
unclustered. `cluster: {}` opts in at every default. This is what a thousands-of-points map needs
|
|
149
|
+
and what `markers` cannot be: `markers` mints one real, individually focusable DOM element per
|
|
150
|
+
entry, which is right for tens of pins and both unreadable and expensive for thousands.
|
|
151
|
+
`radiusSteps` and `colorSteps` are `['step', …]` breaks keyed on `point_count`, in the same
|
|
152
|
+
ascending `[value, output]` vocabulary as `choropleth.stops` — including the same base rule, where
|
|
153
|
+
the first entry's own output covers everything below the first threshold, and the same colour
|
|
154
|
+
resolution, where a `var(--lr-…)` reference in a `colorSteps` entry is resolved against the host
|
|
155
|
+
before it reaches MapLibre (which paints to a WebGL canvas and never sees the CSS cascade), so a
|
|
156
|
+
retheme moves the cluster breaks with everything else. No fill or line layer is created for a
|
|
157
|
+
clustered entry, because MapLibre's clustering keeps point features only. The count
|
|
158
|
+
label needs glyphs: a style that declares none gets the graduated circles without the numbers
|
|
159
|
+
(adding a text layer against a glyph-less style paints nothing and only emits peer errors), and
|
|
160
|
+
`countFont` names the font stack when your style's glyph source lacks MapLibre's spec default.
|
|
161
|
+
Cluster options are baked into the source at creation time by MapLibre and have no setter, so
|
|
162
|
+
changing them (or `kind`) rebuilds that one entry's source and layers; every other update still
|
|
163
|
+
reconciles in place.
|
|
164
|
+
|
|
165
|
+
`kind?: 'auto' | 'heatmap'` (`LyraMapDataLayerKind`, default `'auto'` — today's geometry split)
|
|
166
|
+
renders the source as MapLibre's own first-class `heatmap` layer instead, which the geometry split
|
|
167
|
+
cannot express at all: thousands of overlapping circles read as one opaque blob rather than as
|
|
168
|
+
where the data is concentrated. `heatmap?: LyraMapHeatmapOptions { weightField?: string;
|
|
169
|
+
weightRange?: [number, number]; stops?: [number, string][]; radius?: LyraMapHeatmapZoomValue;
|
|
170
|
+
intensity?: LyraMapHeatmapZoomValue; opacity?: number }`, where `LyraMapHeatmapZoomValue` is a
|
|
171
|
+
scalar number or bounded `[zoom, value][]` stops,
|
|
172
|
+
configures it. `weightField` weights each point by a feature property, and `weightRange` maps that
|
|
173
|
+
property's own units onto the 0–1 weight MapLibre expects — without it the raw value is passed
|
|
174
|
+
through, which saturates the surface for any quantity above ~1; with neither, every point weighs 1.
|
|
175
|
+
`stops` are `[density, color]` pairs with density in `[0, 1]`, **the same `[value, color]`
|
|
176
|
+
vocabulary `choropleth.stops` and `legendGradient` already share**, so a `legendGradient` bar can
|
|
177
|
+
describe the ramp without a second copy of it, and `var(--lr-…)` stops resolve against the host the
|
|
178
|
+
same way `color`/`strokeColor` do. A ramp that doesn't start at density 0 gets a fully transparent
|
|
179
|
+
stop prepended, because a coloured zero tints the entire map — so **a single stop is already a
|
|
180
|
+
complete ramp**, as long as it sits above density 0: `stops: [[1, '#ff0000']]` is exactly
|
|
181
|
+
transparent → red. The one authored ramp that can't be honoured is a lone stop AT density 0, which
|
|
182
|
+
describes a flat colour rather than a gradient; that one — like an unset or wholly unusable
|
|
183
|
+
`stops` — falls back to the built-in ramp, which runs transparent → `--lr-color-brand` →
|
|
184
|
+
`--lr-color-success` → `--lr-color-warning` → `--lr-color-danger`, so a retheme moves the density
|
|
185
|
+
surface with everything else. Scalar `radius` (default 30) and `intensity` (default 1) preserve
|
|
186
|
+
their established behavior. Two or more usable `[zoom, value]` stops emit linear zoom
|
|
187
|
+
interpolation; stops are sorted, duplicate zooms removed, zoom clamped into `[0, 24]`, radius
|
|
188
|
+
into `[1, 200]`, and intensity into `[0, 100]`. One usable stop becomes a scalar and an unusable
|
|
189
|
+
array falls back to the existing default. `opacity` is clamped into `[0, 1]`; omission leaves the
|
|
190
|
+
peer's default untouched on construction, and dropping a previously-authored value restores 1.
|
|
191
|
+
`cluster` is ignored on a heatmap entry: a heatmap already aggregates density, and clustering its
|
|
192
|
+
input would feed it one point per cluster instead of the real distribution.
|
|
126
193
|
- `maxBounds: LyraMapBounds | null = null` (attribute: false) — box the map may not pan outside,
|
|
127
194
|
`[[west, south], [east, north]]`. Prefer it over calling `map.setMaxBounds()` through the `.map`
|
|
128
195
|
escape hatch: constraining the camera can wedge maplibre-gl at a sub-1 fractional zoom in a wide
|
|
@@ -134,14 +201,15 @@ GeoJSON.FeatureCollection; tone?: 'accent' | 'success' | 'warning' |
|
|
|
134
201
|
|
|
135
202
|
**Choropleth and `dataLayers` updates are diffed before they reach the peer.** `setData()`
|
|
136
203
|
unconditionally re-tiles and repaints an entire source, which is invisible on a static map and
|
|
137
|
-
expensive on an animated one. When
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
`setData()` with no change in behaviour. Peers
|
|
204
|
+
expensive on an animated one. When every feature has a unique `string`/`number` `id` and retained
|
|
205
|
+
features keep semantically unchanged geometry/bbox values, the component emits MapLibre's
|
|
206
|
+
incremental `updateData()` for property changes, additions, removals, and order changes. The exact
|
|
207
|
+
next order is preserved: an unchanged prefix stays in place and only the invalidated suffix is
|
|
208
|
+
removed and re-added. Lyra's immutable ownership boundary detaches GeoJSON assignments, so geometry
|
|
209
|
+
reference identity cannot survive between frames; a bounded, accessor-free comparison verifies the
|
|
210
|
+
JSON geometry graph instead. A missing/duplicate ID, changed geometry, exceeded bound, or uncertain
|
|
211
|
+
comparison falls back to `setData()` with no change in rendered behaviour. Peers without
|
|
212
|
+
`updateData()` always take the old path.
|
|
145
213
|
|
|
146
214
|
**Feature properties are tiled, and therefore bounded in numeric magnitude.** MapLibre GL tiles
|
|
147
215
|
every GeoJSON source through a worker into a protobuf vector tile, so a property carrying a huge
|
|
@@ -160,7 +228,9 @@ payload beside the map.
|
|
|
160
228
|
empty canvas name. The non-semantic `[part="base"]` wrapper is not named instead.
|
|
161
229
|
|
|
162
230
|
**Authoring types:** `LyraMapLegendEntry`, `LyraMapLegendPattern`, `LyraMapLegendProjection`, `LyraMapChoroplethLayer`,
|
|
163
|
-
`LyraMapGeoJsonDataLayer`, `
|
|
231
|
+
`LyraMapGeoJsonDataLayer`, `LyraMapDataLayerKind`, `LyraMapClusterOptions`, `LyraMapHeatmapOptions`,
|
|
232
|
+
`LyraMapHeatmapZoomValue`, `LyraMapMarker`, `LyraMapMarkerActivationDetail`,
|
|
233
|
+
`LyraMapMarkerActivationSource`, `LyraMapStyleSpecification`, and `LyraMapInstance`.
|
|
164
234
|
The former `LegendEntry`, `ChoroplethLayer`, `GeoJsonDataLayer`, and `MapMarker` names are removed
|
|
165
235
|
in v9 rather than retained as aliases.
|
|
166
236
|
|
|
@@ -175,14 +245,22 @@ shared `maplibre-gl` import without constructing a map or allocating a WebGL con
|
|
|
175
245
|
`false` when the peer is unavailable, allowing an application to choose a fallback before connecting
|
|
176
246
|
an element.
|
|
177
247
|
|
|
178
|
-
**Events:** `lr-map-load` (fired once, after the underlying map's own `'load'`),
|
|
248
|
+
**Events:** `lr-map-load` (fired once, after the underlying map's own `'load'`),
|
|
249
|
+
`lr-map-marker-activate` (non-cancelable; frozen `LyraMapMarkerActivationDetail { id, lngLat,
|
|
250
|
+
marker, source }`; `id` is the trimmed explicit identity or `undefined`, `marker` is the accepted
|
|
251
|
+
declarative snapshot, and `source` is `'pointer' | 'keyboard'`), and `lr-map-click`
|
|
179
252
|
(frozen `detail: { readonly lngLat: readonly [lng, lat], readonly feature?, readonly origin?,
|
|
180
253
|
readonly sourceId? }`; the tuple and any hit GeoJSON feature are detached and recursively frozen).
|
|
181
254
|
`feature` resolves against the choropleth fill layer **and** every applied `dataLayers`
|
|
182
|
-
fill/line/circle layer, topmost first — so a shape painted through `dataLayers` is
|
|
183
|
-
instead of being indistinguishable from empty space. `origin` is `'choropleth'
|
|
184
|
-
and `sourceId` carries the authored `dataLayers[].sourceId` for a
|
|
185
|
-
`undefined` whenever `feature` is
|
|
255
|
+
fill/line/circle/cluster layer, topmost first — so a shape painted through `dataLayers` is
|
|
256
|
+
identifiable instead of being indistinguishable from empty space. `origin` is `'choropleth'`,
|
|
257
|
+
`'data-layer'` or `'cluster'`, and `sourceId` carries the authored `dataLayers[].sourceId` for a
|
|
258
|
+
data-layer or cluster hit; both are `undefined` whenever `feature` is. A cluster hit is reported
|
|
259
|
+
separately because it is a synthetic aggregate rather than one of your features: its useful payload
|
|
260
|
+
is MapLibre's `point_count`/`point_count_abbreviated`/`cluster_id` properties, which is what a
|
|
261
|
+
zoom-to-cluster handler reads. The count label is deliberately not hit-tested (it sits exactly on
|
|
262
|
+
the circle already queried and would only make the label the topmost hit), and a `kind: 'heatmap'`
|
|
263
|
+
layer is never queried at all — MapLibre returns no features for a rendered density surface
|
|
186
264
|
|
|
187
265
|
**Slots:** `legend` — custom legend content, rendered inside the legend panel's own layout so it
|
|
188
266
|
stays positioned with the map instead of floating beside it. Supplying it opens the panel even
|
|
@@ -248,6 +326,10 @@ Vite with v6:
|
|
|
248
326
|
{ color: "#0969da", label: "High", pattern: "diagonal" },
|
|
249
327
|
];
|
|
250
328
|
m.markers = [{ lngLat: [2.29, 48.86], label: "Eiffel Tower" }];
|
|
329
|
+
m.renderWorldCopies = false;
|
|
330
|
+
m.addEventListener("lr-map-marker-activate", (e) =>
|
|
331
|
+
console.log(e.detail.id, e.detail.lngLat)
|
|
332
|
+
);
|
|
251
333
|
m.addEventListener("lr-map-click", (e) =>
|
|
252
334
|
console.log(e.detail.feature?.properties)
|
|
253
335
|
);
|
|
@@ -264,8 +346,9 @@ https://maplibre.org/maplibre-gl-js/docs/#esm.
|
|
|
264
346
|
created peer instance, renders only the localized initialization failure, and can retry after a
|
|
265
347
|
new style or reconnect without an unhandled promise rejection. Capability probes and error
|
|
266
348
|
constructors come from the current owner document, including after same-origin adoption.
|
|
267
|
-
-
|
|
268
|
-
|
|
349
|
+
- Every marker handles Enter and Space as button activation. Space suppresses the page-scroll
|
|
350
|
+
default whether or not a popup exists; when one does, MapLibre still receives its own popup
|
|
351
|
+
toggle and the component emits exactly one `lr-map-marker-activate` notification.
|
|
269
352
|
- clearing or swapping the choropleth no longer leaks the old layer: setting `choropleth =
|
|
270
353
|
undefined`, or changing `choropleth.sourceId` to a different value, now calls `removeLayer`/
|
|
271
354
|
`removeSource` on whatever was previously applied before adding the new one (or nothing, if
|
|
@@ -310,5 +393,9 @@ undefined`, or changing `choropleth.sourceId` to a different value, now calls `r
|
|
|
310
393
|
`lr-map-load` never fires until the element is actually scrolled into view. Deliberate: caps
|
|
311
394
|
concurrent WebGL contexts when many `<lr-map>`s sit in one dashboard/grid. Skipped entirely
|
|
312
395
|
(constructs immediately once the peer loads) when `IntersectionObserver` itself is unavailable.
|
|
396
|
+
- Once constructed, the owner realm's `ResizeObserver` watches the rendered map container and calls
|
|
397
|
+
the current peer's `resize()` on allocation changes. It is replaced on reconnect/adoption and
|
|
398
|
+
disconnected before peer teardown, so a stale delivery cannot resize a removed map. Browsers
|
|
399
|
+
without `ResizeObserver` retain MapLibre's own window-resize behavior.
|
|
313
400
|
|
|
314
401
|
---
|