@aceshooting/lyra-ui 11.2.0 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +388 -0
  2. package/README.md +11 -8
  3. package/custom-elements.json +1 -1
  4. package/dist/all.d.ts +1 -0
  5. package/dist/all.js +1 -1
  6. package/dist/components/charts/chart/chart.class.d.ts +13 -1
  7. package/dist/components/data/funnel/funnel.class.d.ts +92 -0
  8. package/dist/components/data/funnel/funnel.class.js +25 -0
  9. package/dist/components/data/funnel/funnel.d.ts +1 -0
  10. package/dist/components/data/funnel/funnel.js +1 -0
  11. package/dist/components/data/funnel/funnel.styles.d.ts +1 -0
  12. package/dist/components/data/funnel/funnel.styles.js +127 -0
  13. package/dist/components/data/heatmap/heatmap.class.d.ts +195 -13
  14. package/dist/components/data/heatmap/heatmap.class.js +30 -22
  15. package/dist/components/data/heatmap/heatmap.styles.js +36 -0
  16. package/dist/components/data/index.d.ts +1 -0
  17. package/dist/components/data/index.js +1 -1
  18. package/dist/components/forms/date-picker/date-input.class.d.ts +23 -0
  19. package/dist/components/forms/date-picker/date-input.class.js +1 -1
  20. package/dist/components/layout/app-rail/app-rail-item.class.d.ts +16 -0
  21. package/dist/components/layout/app-rail/app-rail-item.class.js +4 -4
  22. package/dist/components/layout/filter-bar/filter-bar.class.d.ts +32 -3
  23. package/dist/components/layout/filter-bar/filter-bar.class.js +2 -1
  24. package/dist/components/layout/widget/widget.class.d.ts +16 -0
  25. package/dist/components/layout/widget/widget.class.js +2 -2
  26. package/dist/components/lr-funnel.d.ts +1 -0
  27. package/dist/components/lr-funnel.js +1 -0
  28. package/dist/components/media/flag/flag-peer-bulk-standard.d.ts +25 -0
  29. package/dist/components/media/flag/flag-peer-bulk-standard.js +1 -0
  30. package/dist/components/media/flag/flag.class.d.ts +8 -0
  31. package/dist/components/media/flag/flag.class.js +1 -1
  32. package/dist/components/media/map/map.class.d.ts +196 -15
  33. package/dist/components/media/map/map.class.js +1 -1
  34. package/dist/components/overlays/chip/chip-group.class.d.ts +16 -0
  35. package/dist/components/overlays/chip/chip-group.class.js +10 -3
  36. package/dist/custom-elements-jsx.d.ts +12 -4
  37. package/dist/internal/autoloader-manifest.js +1 -1
  38. package/dist/internal/autoloader-tags.d.ts +1 -1
  39. package/dist/internal/autoloader-tags.js +1 -1
  40. package/dist/internal/lyra-element.d.ts +7 -1
  41. package/dist/internal/lyra-element.js +1 -1
  42. package/dist/internal/package-metadata.d.ts +1 -1
  43. package/dist/internal/package-metadata.js +1 -1
  44. package/dist/internal/root-registration-allowlist.d.ts +1 -1
  45. package/dist/internal/root-registration-allowlist.js +1 -1
  46. package/dist/lyra.d.ts +4 -2
  47. package/dist/lyra.js +1 -1
  48. package/dist/ssr/all.d.ts +1 -0
  49. package/dist/ssr/all.js +1 -1
  50. package/dist/ssr.d.ts +4 -4
  51. package/dist/styles/tokens-root.css +488 -0
  52. package/dist/svelte.d.ts +13 -4
  53. package/dist/vue.d.ts +12 -4
  54. package/llms/components/lr-app-rail-item.md +8 -1
  55. package/llms/components/lr-app-rail.md +6 -0
  56. package/llms/components/lr-chip-group.md +7 -0
  57. package/llms/components/lr-chip.md +7 -0
  58. package/llms/components/lr-date-input.md +19 -1
  59. package/llms/components/lr-date-picker.md +19 -1
  60. package/llms/components/lr-filter-bar.md +50 -1
  61. package/llms/components/lr-flag.md +14 -0
  62. package/llms/components/lr-funnel.md +122 -0
  63. package/llms/components/lr-heatmap.md +51 -7
  64. package/llms/components/lr-map.md +65 -8
  65. package/llms/components/lr-widget.md +7 -1
  66. package/llms/index.md +3 -2
  67. package/llms/peers.md +1 -1
  68. package/llms/shared.md +110 -1
  69. package/llms-full.txt +468 -18
  70. package/llms.txt +1 -1
  71. package/package.json +25 -6
  72. package/vscode-css-data.json +42 -0
  73. package/vscode-html-data.json +96 -17
  74. package/web-types.json +32714 -317
package/dist/vue.d.ts CHANGED
@@ -92,6 +92,7 @@ import type { LyraFlowControls } from './components/data/flow-controls/flow-cont
92
92
  import type { LyraFlowMinimap } from './components/data/flow-minimap/flow-minimap.class.js';
93
93
  import type { LyraFlowNode } from './components/data/flow-node/flow-node.class.js';
94
94
  import type { LyraFlowRunStatus } from './components/data/flow-run-status/flow-run-status.class.js';
95
+ import type { LyraFunnel } from './components/data/funnel/funnel.class.js';
95
96
  import type { LyraGauge } from './components/data/gauge/gauge.class.js';
96
97
  import type { LyraGraphQueryBuilder, LyraGraphQueryBuilderEventMap } from './components/data/graph-query-builder/graph-query-builder.class.js';
97
98
  import type { LyraHeatmap, LyraHeatmapEventMap } from './components/data/heatmap/heatmap.class.js';
@@ -375,7 +376,7 @@ export type LyraAppRailVueProps = LyraVueCustomElement<LyraAppRail, 'forceMode'
375
376
  'rail-width-px'?: LyraAppRail['railWidthPx'];
376
377
  'storage-key'?: LyraAppRail['storageKey'];
377
378
  }>;
378
- export type LyraAppRailItemVueProps = LyraVueCustomElement<LyraAppRailItem, 'current' | 'disabled' | 'href' | 'locale' | 'strings' | 'target' | 'tooltip', {}, {}, never, '--lr-app-rail-item-active-bg' | '--lr-app-rail-item-active-color' | '--lr-app-rail-item-current-bg' | '--lr-app-rail-item-current-color' | '--lr-app-rail-item-hover-bg' | '--lr-app-rail-item-hover-color', {
379
+ export type LyraAppRailItemVueProps = LyraVueCustomElement<LyraAppRailItem, 'active' | 'current' | 'disabled' | 'href' | 'locale' | 'strings' | 'target' | 'tooltip', {}, {}, never, '--lr-app-rail-item-active-bg' | '--lr-app-rail-item-active-color' | '--lr-app-rail-item-current-bg' | '--lr-app-rail-item-current-color' | '--lr-app-rail-item-hover-bg' | '--lr-app-rail-item-hover-color', {
379
380
  'icon-only'?: LyraAttributeValue<boolean>;
380
381
  }>;
381
382
  export type LyraApprovalQueueVueProps = LyraVueCustomElement<LyraApprovalQueue, 'editable' | 'label' | 'locale' | 'open' | 'requests' | 'selectedInvocationId' | 'strings', {}, LyraApprovalQueueEventMap, 'lr-approval-close' | 'lr-approval-decision' | 'lr-approval-select', '--lr-approval-queue-selected-border', {
@@ -582,7 +583,8 @@ export type LyraCheckpointVueProps = LyraVueCustomElement<LyraCheckpoint, 'check
582
583
  'confirm-restore'?: LyraCheckpoint['confirmRestore'];
583
584
  }>;
584
585
  export type LyraChipVueProps = LyraVueCustomElement<LyraChip, 'disabled' | 'locale' | 'pill' | 'removable' | 'selected' | 'size' | 'strings' | 'toggleable' | 'value' | 'variant', {}, LyraChipEventMap, 'lr-chip-select' | 'lr-remove', '--lr-chip-accent' | '--lr-chip-bg' | '--lr-chip-border' | '--lr-chip-font-size' | '--lr-chip-gap' | '--lr-chip-height' | '--lr-chip-icon-size' | '--lr-chip-min-height' | '--lr-chip-padding-block' | '--lr-chip-padding-inline' | '--lr-chip-pressed-bg' | '--lr-chip-pressed-border' | '--lr-chip-radius', {}>;
585
- export type LyraChipGroupVueProps = LyraVueCustomElement<LyraChipGroup, 'locale' | 'maxVisible' | 'strings', {}, LyraChipGroupEventMap, 'lr-overflow-toggle', '--lr-chip-group-overflow-expanded-border-style' | '--lr-chip-group-overflow-expanded-color', {
586
+ export type LyraChipGroupVueProps = LyraVueCustomElement<LyraChipGroup, 'accessibleLabel' | 'locale' | 'maxVisible' | 'strings', {}, LyraChipGroupEventMap, 'lr-overflow-toggle', '--lr-chip-group-overflow-expanded-border-style' | '--lr-chip-group-overflow-expanded-color', {
587
+ 'aria-label'?: LyraChipGroup['accessibleLabel'];
586
588
  'max-visible'?: LyraChipGroup['maxVisible'];
587
589
  }>;
588
590
  export type LyraChunkInspectorVueProps = LyraVueCustomElement<LyraChunkInspector, 'activeChunkId' | 'chunks' | 'compact' | 'label' | 'locale' | 'sort' | 'strings' | 'thresholds' | 'virtualizeAt', {}, LyraChunkInspectorEventMap, 'lr-chunk-open' | 'lr-expand', '--lr-chunk-inspector-current-bg' | '--lr-chunk-inspector-current-color', {
@@ -975,6 +977,10 @@ export type LyraFormatNumberVueProps = LyraVueCustomElement<LyraFormatNumber, 'c
975
977
  'no-grouping'?: LyraFormatNumber['noGrouping'];
976
978
  'without-grouping'?: LyraFormatNumber['withoutGrouping'];
977
979
  }>;
980
+ export type LyraFunnelVueProps = LyraVueCustomElement<LyraFunnel, 'comparison' | 'comparisonLabel' | 'dropoff' | 'label' | 'locale' | 'sharePrecision' | 'stages' | 'strings', {}, {}, never, '--lr-funnel-bar-color' | '--lr-funnel-bar-size' | '--lr-funnel-comparison-color' | '--lr-funnel-track-color', {
981
+ 'comparison-label'?: LyraFunnel['comparisonLabel'];
982
+ 'share-precision'?: LyraFunnel['sharePrecision'];
983
+ }>;
978
984
  export type LyraGaugeVueProps = LyraVueCustomElement<LyraGauge, 'label' | 'locale' | 'max' | 'min' | 'shape' | 'strings' | 'value' | 'valueText', {}, {}, never, '--lr-gauge-fill', {
979
985
  'value-text'?: LyraGauge['valueText'];
980
986
  }>;
@@ -1017,7 +1023,7 @@ export type LyraHandoffDividerVueProps = LyraVueCustomElement<LyraHandoffDivider
1017
1023
  'from-agent'?: LyraHandoffDivider['fromAgent'];
1018
1024
  'to-agent'?: LyraHandoffDivider['toAgent'];
1019
1025
  }>;
1020
- export type LyraHeatmapVueProps = LyraVueCustomElement<LyraHeatmap, 'accessibleCells' | 'annotations' | 'bucketCount' | 'cellColor' | 'cellInteractive' | 'cellSize' | 'cellText' | 'colLabelHeight' | 'colLabelRotation' | 'colorSteps' | 'data' | 'domain' | 'fitToWidth' | 'legendStops' | 'locale' | 'maxCellSize' | 'midpoint' | 'minCellSize' | 'rowLabelWidth' | 'scale' | 'selectedCell' | 'strings' | 'valueLabel', {}, LyraHeatmapEventMap, 'lr-cell-click' | 'lr-matrix-geometry-change', '--lr-heatmap-annotation-color' | '--lr-heatmap-color-steps-gradient' | '--lr-heatmap-focus-ring-color' | '--lr-heatmap-label-font' | '--lr-heatmap-no-data-fill' | '--lr-heatmap-scale-hi' | '--lr-heatmap-scale-lo' | '--lr-heatmap-selected-color' | '--lr-heatmap-tooltip-bg' | '--lr-heatmap-tooltip-text', {
1026
+ export type LyraHeatmapVueProps = LyraVueCustomElement<LyraHeatmap, 'accessibleCells' | 'annotations' | 'bucketCount' | 'cellColor' | 'cellInteractive' | 'cellSize' | 'cellText' | 'colLabelHeight' | 'colLabelRotation' | 'colorSteps' | 'data' | 'domain' | 'fitToWidth' | 'legendStops' | 'locale' | 'maxCellSize' | 'midpoint' | 'minCellSize' | 'rowLabelWidth' | 'scale' | 'selectedCell' | 'stickyLabels' | 'strings' | 'valueLabel', {}, LyraHeatmapEventMap, 'lr-cell-click' | 'lr-matrix-geometry-change', '--lr-heatmap-annotation-color' | '--lr-heatmap-color-steps-gradient' | '--lr-heatmap-focus-ring-color' | '--lr-heatmap-grid-max-block-size' | '--lr-heatmap-label-font' | '--lr-heatmap-no-data-fill' | '--lr-heatmap-scale-hi' | '--lr-heatmap-scale-lo' | '--lr-heatmap-selected-color' | '--lr-heatmap-sticky-label-bg' | '--lr-heatmap-tooltip-bg' | '--lr-heatmap-tooltip-text', {
1021
1027
  'accessible-cells'?: LyraHeatmap['accessibleCells'];
1022
1028
  'bucket-count'?: LyraHeatmap['bucketCount'];
1023
1029
  'cell-size'?: LyraHeatmap['cellSize'];
@@ -1027,6 +1033,7 @@ export type LyraHeatmapVueProps = LyraVueCustomElement<LyraHeatmap, 'accessibleC
1027
1033
  'max-cell-size'?: LyraHeatmap['maxCellSize'];
1028
1034
  'min-cell-size'?: LyraHeatmap['minCellSize'];
1029
1035
  'row-label-width'?: LyraHeatmap['rowLabelWidth'];
1036
+ 'sticky-labels'?: LyraHeatmap['stickyLabels'];
1030
1037
  'value-label'?: LyraHeatmap['valueLabel'];
1031
1038
  }>;
1032
1039
  export type LyraHighlightLayerVueProps = LyraVueCustomElement<LyraHighlightLayer, 'activeHighlightId' | 'interactive' | 'items' | 'locale' | 'strings', {}, LyraHighlightLayerEventMap, 'lr-highlight-activate', '--lr-highlight-layer-accent-background' | '--lr-highlight-layer-accent-outline' | '--lr-highlight-layer-danger-background' | '--lr-highlight-layer-danger-outline' | '--lr-highlight-layer-flash-background' | '--lr-highlight-layer-neutral-background' | '--lr-highlight-layer-neutral-outline' | '--lr-highlight-layer-success-background' | '--lr-highlight-layer-success-outline' | '--lr-highlight-layer-warning-background' | '--lr-highlight-layer-warning-outline', {
@@ -1978,7 +1985,7 @@ export type LyraVoicePickerVueProps = LyraVueCustomElement<LyraVoicePicker, 'all
1978
1985
  'inputmode'?: LyraVoicePicker['inputMode'];
1979
1986
  'value'?: LyraVoicePicker['defaultValue'];
1980
1987
  }>;
1981
- export type LyraWidgetVueProps = LyraVueCustomElement<LyraWidget, 'accessibleLabel' | 'activeViewId' | 'backdropInset' | 'collapsed' | 'collapsible' | 'compact' | 'expandable' | 'fullscreen' | 'fullscreenInset' | 'label' | 'locale' | 'storageKey' | 'strings' | 'sublabel' | 'views', {}, LyraWidgetEventMap, 'lr-collapse-change' | 'lr-collapse-request' | 'lr-fullscreen-change' | 'lr-fullscreen-request' | 'lr-view-change' | 'lr-view-request', '--lr-scroll-fade-size' | '--lr-widget-backdrop-inset' | '--lr-widget-fullscreen-inset' | '--lr-widget-overlay-color' | '--lr-widget-view-toggle-active-bg' | '--lr-widget-view-toggle-active-border-color' | '--lr-widget-view-toggle-active-color' | '--lr-widget-view-toggle-hover-bg' | '--lr-widget-view-toggle-hover-color', {
1988
+ export type LyraWidgetVueProps = LyraVueCustomElement<LyraWidget, 'accessibleLabel' | 'activeView' | 'activeViewId' | 'backdropInset' | 'collapsed' | 'collapsible' | 'compact' | 'expandable' | 'fullscreen' | 'fullscreenInset' | 'label' | 'locale' | 'storageKey' | 'strings' | 'sublabel' | 'views', {}, LyraWidgetEventMap, 'lr-collapse-change' | 'lr-collapse-request' | 'lr-fullscreen-change' | 'lr-fullscreen-request' | 'lr-view-change' | 'lr-view-request', '--lr-scroll-fade-size' | '--lr-widget-backdrop-inset' | '--lr-widget-fullscreen-inset' | '--lr-widget-overlay-color' | '--lr-widget-view-toggle-active-bg' | '--lr-widget-view-toggle-active-border-color' | '--lr-widget-view-toggle-active-color' | '--lr-widget-view-toggle-hover-bg' | '--lr-widget-view-toggle-hover-color', {
1982
1989
  'aria-label'?: LyraWidget['accessibleLabel'];
1983
1990
  'backdrop-inset'?: LyraWidget['backdropInset'];
1984
1991
  'fullscreen-inset'?: LyraWidget['fullscreenInset'];
@@ -2117,6 +2124,7 @@ export interface LyraVueGlobalComponents {
2117
2124
  'lr-format-bytes': LyraFormatBytesVueProps;
2118
2125
  'lr-format-date': LyraFormatDateVueProps;
2119
2126
  'lr-format-number': LyraFormatNumberVueProps;
2127
+ 'lr-funnel': LyraFunnelVueProps;
2120
2128
  'lr-gauge': LyraGaugeVueProps;
2121
2129
  'lr-generation-metrics': LyraGenerationMetricsVueProps;
2122
2130
  'lr-geojson-view': LyraGeojsonViewVueProps;
@@ -6,7 +6,8 @@
6
6
  - **Class** `LyraAppRailItem`, also available unregistered from `@aceshooting/lyra-ui/components/layout/app-rail/app-rail-item.class.js`
7
7
  - **Family** `components/layout/` — see `llms/index.md` for its siblings
8
8
  - **Status** `stable` since `4.0.0` — see the maturity and deprecation policy in `llms/shared.md`
9
- - **Deprecations** none
9
+ - **Deprecated attribute** `active` since `11.2.0`; use attribute `current`; removal not before `13.0.0` — The attribute half of the restored `active` property alias. `active` was this member's original public name in both forms; the rename to `current` was never announced, so markup that already wrote `<lr-app-rail-item active>` stopped marking the item current with no error. Shares the property record's compatibility window because it is the same correction.
10
+ - **Deprecated property** `active` since `11.2.0`; use property `current`; removal not before `13.0.0` — `active` was this property's original public name and was documented as such when it shipped. It was renamed to `current` with no changelog entry, no alias and no deprecation record, so every shipped consumer's `.active=` binding silently became a dead expando -- a Lit property binding on a custom element is untyped, so nothing in a consumer's type check, test suite or build could see it. The measured downstream effect was an app rail with no current-item indicator and a permanent aria-current="false". Restoring the name is a correction, not a new API, so the compatibility window runs long: this alias is what shipped consumers already wrote.
10
11
  - **Optional peers** none
11
12
  - **Themeable via** 4 parts, 6 custom properties — see this component's own `@csspart`/`@cssprop` list below
12
13
  - **Documented with** `lr-app-rail` (same section below)
@@ -253,6 +254,12 @@ removing the label from the accessibility tree.
253
254
  reflects `aria-current="page"` on `[part='base']` and drives the current visual treatment. The rail
254
255
  has no built-in routing, so the consumer sets this per item (e.g. by comparing `href` against the
255
256
  current location).
257
+ - `active: boolean = false` — **deprecated alias for `current`**, read alongside it: the item is
258
+ current when either is true. `active` was this member's original public name, in both property and
259
+ attribute form; it was renamed to `current` without an alias, so shipped consumers writing
260
+ `.active=${…}` or `<lr-app-rail-item active>` silently lost their current-item indicator and kept a
261
+ permanent `aria-current="false"`. A Lit property binding on a custom element is untyped, so nothing
262
+ in a consumer's type check or test suite could catch it. Prefer `current` in new code.
256
263
  - `tooltip: boolean = false` (reflected) — opt-in hover/focus flyout (`[part='tooltip']`) showing
257
264
  this item's label text while the rail's `icon-only` mode (set externally by the parent
258
265
  `<lr-app-rail>` as the viewport narrows) hides it from view. No effect outside icon-only mode,
@@ -253,6 +253,12 @@ removing the label from the accessibility tree.
253
253
  reflects `aria-current="page"` on `[part='base']` and drives the current visual treatment. The rail
254
254
  has no built-in routing, so the consumer sets this per item (e.g. by comparing `href` against the
255
255
  current location).
256
+ - `active: boolean = false` — **deprecated alias for `current`**, read alongside it: the item is
257
+ current when either is true. `active` was this member's original public name, in both property and
258
+ attribute form; it was renamed to `current` without an alias, so shipped consumers writing
259
+ `.active=${…}` or `<lr-app-rail-item active>` silently lost their current-item indicator and kept a
260
+ permanent `aria-current="false"`. A Lit property binding on a custom element is untyped, so nothing
261
+ in a consumer's type check or test suite could catch it. Prefer `current` in new code.
256
262
  - `tooltip: boolean = false` (reflected) — opt-in hover/focus flyout (`[part='tooltip']`) showing
257
263
  this item's label text while the rail's `icon-only` mode (set externally by the parent
258
264
  `<lr-app-rail>` as the viewport narrows) hides it from view. No effect outside icon-only mode,
@@ -180,6 +180,13 @@ no `.items` array prop).
180
180
 
181
181
  **Properties:**
182
182
 
183
+ - `accessibleLabel: string = ''` (attribute `aria-label`) — accessible name for the group,
184
+ forwarded to `[part='base']`, which carries `role='group'` whenever a name is supplied. Supply
185
+ one for any chip row that means something as a set (a filter row, a tag list); without it the
186
+ container stays roleless, because an unnamed group role adds verbosity without information.
187
+ A host `aria-label` does **not** reach a shadow root on its own — this property is what makes it
188
+ reach `[part='base']`, the element that owns the role. Mirrors how `lr-radio-group` and
189
+ `lr-segmented` name their own `radiogroup`.
183
190
  - `maxVisible?: number` (attribute `max-visible`) — maximum number of assigned children shown before
184
191
  the rest collapse behind a "+N" indicator; flattened slot-forwarded children count the same as
185
192
  direct children. Author-hidden or inert children do not consume capacity or inflate the hidden
@@ -180,6 +180,13 @@ no `.items` array prop).
180
180
 
181
181
  **Properties:**
182
182
 
183
+ - `accessibleLabel: string = ''` (attribute `aria-label`) — accessible name for the group,
184
+ forwarded to `[part='base']`, which carries `role='group'` whenever a name is supplied. Supply
185
+ one for any chip row that means something as a set (a filter row, a tag list); without it the
186
+ container stays roleless, because an unnamed group role adds verbosity without information.
187
+ A host `aria-label` does **not** reach a shadow root on its own — this property is what makes it
188
+ reach `[part='base']`, the element that owns the role. Mirrors how `lr-radio-group` and
189
+ `lr-segmented` name their own `radiogroup`.
183
190
  - `maxVisible?: number` (attribute `max-visible`) — maximum number of assigned children shown before
184
191
  the rest collapse behind a "+N" indicator; flattened slot-forwarded children count the same as
185
192
  direct children. Author-hidden or inert children do not consume capacity or inflate the hidden
@@ -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 (42):**
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)
@@ -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 (42):**
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. A built-in or
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** 15 parts, 10 custom properties — see this component's own `@csspart`/`@cssprop` list below
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
@@ -237,9 +269,12 @@ color?: string; label?: string; partOfRamp?: boolean }`: a discrete legend key r
237
269
  color-scheme change; called automatically on theme changes, exposed for a consumer that needs to
238
270
  force a redraw manually. `matrixGeometry: Readonly<{ padLeft: number; padTop: number; cellSize:
239
271
  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 (e.g. a sticky header mirror
241
- for a tall matrix) line up with the canvas without hardcoding the same numbers `row-label-width`/
242
- `col-label-height`'s `"auto"` resolution would otherwise keep private.
272
+ in CSS pixels; `undefined` in calendar mode. Lets a light-DOM consumer line up with the canvas
273
+ without hardcoding the same numbers `row-label-width`/`col-label-height`'s `"auto"` resolution would
274
+ otherwise keep private. For the case that motivated it — a frozen header or gutter on a tall or wide
275
+ matrix — prefer `stickyLabels`, which freezes the band inside the component and needs no mirror at
276
+ all; the getter remains the way to align a *separate* element (a sibling chart, a custom overlay)
277
+ with the grid.
243
278
 
244
279
  **Events:** `lr-cell-click` (fired on click, or Enter/Space on the keyboard-focused cell —
245
280
  `detail: { row, col, value }` in matrix mode, `detail: { date, value }` in calendar mode),
@@ -250,8 +285,12 @@ in calendar mode)
250
285
 
251
286
  **Slots:** none.
252
287
 
253
- **CSS parts:** `base`, `canvas`, `cells` (opt-in per-cell overlay), `cell` (one opt-in native cell
254
- button), `tooltip` (hover tooltip, positioned over the hovered cell),
288
+ **CSS parts:** `base`, `canvas`, `grid` (the scrollport wrapping the canvas while `stickyLabels`
289
+ freezes an axis — absent entirely otherwise), `row-labels`/`col-labels` (the frozen label bands,
290
+ rendered only for the axis `stickyLabels` names), `cells` (opt-in per-cell overlay), `cell` (one opt-in native cell
291
+ button), `tooltip` (hover tooltip, positioned over the hovered cell — inside `[part="grid"]` while
292
+ `stickyLabels` freezes an axis, so it scrolls with the cells, and a `[part="base"]` child
293
+ otherwise),
255
294
  `live-region` (visually-hidden, `aria-hidden` mirror of the keyboard-focused cell; the actual
256
295
  announcement uses the shared light-DOM polite sink), `projection-limit` (localized assistive
257
296
  disclosure when bounded canonicalization truncates input), `legend`, `legend-lo`, `legend-hi` (both omitted, along with the gradient
@@ -287,7 +326,12 @@ dedicated token distinct from both the focus ring and the annotation ring so a h
287
326
  independently). `--lr-heatmap-tooltip-bg` (default
288
327
  `var(--lr-color-surface)`) and `--lr-heatmap-tooltip-text` (default `var(--lr-color-text)`) —
289
328
  unlike the canvas-drawn tokens above, `[part="tooltip"]` is a real DOM element and consumes these
290
- directly, no `getComputedStyle` bridging needed. Also consumes `--lr-color-text-quiet` (axis label
329
+ directly, no `getComputedStyle` bridging needed. `--lr-heatmap-sticky-label-bg` (default
330
+ `var(--lr-color-surface)` — the backdrop painted under a frozen `stickyLabels` band, resolved via
331
+ `getComputedStyle` like the other canvas-drawn tokens; it must stay **opaque**, since it covers the
332
+ same labels the scrolling canvas painted underneath it) and `--lr-heatmap-grid-max-block-size`
333
+ (default `none` — the block-size ceiling of the `[part="grid"]` scrollport, consumed directly by
334
+ 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
335
  color), `--lr-space-xs`, `--lr-radius`/`--lr-shadow` (tooltip box), and `--lr-focus-ring-width`/
292
336
  `--lr-focus-ring-offset` (the real `[part="canvas"]:focus-visible` DOM outline, stroked in the
293
337
  same color as `--lr-heatmap-focus-ring-color`).