@aceshooting/lyra-ui 12.0.0 → 12.1.1

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 (174) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/custom-elements.json +1 -1
  3. package/dist/autoloader.js +1 -1
  4. package/dist/components/agent-tools/tool-param-form/tool-param-form.class.d.ts +0 -1
  5. package/dist/components/agent-tools/tool-param-form/tool-param-form.class.js +1 -1
  6. package/dist/components/charts/chart/histogram-bin.d.ts +0 -1
  7. package/dist/components/charts/chart/histogram-bin.js +1 -1
  8. package/dist/components/charts/chart/lite-chart.class.d.ts +22 -9
  9. package/dist/components/charts/chart/lite-chart.class.js +6 -6
  10. package/dist/components/conversation/code-block/code-block-shared.d.ts +4 -54
  11. package/dist/components/conversation/code-block/code-block-shared.js +1 -1
  12. package/dist/components/conversation/code-block/shiki-types.d.ts +2 -1
  13. package/dist/components/conversation/generation-metrics/generation-metrics.class.js +1 -1
  14. package/dist/components/conversation/model-select/model-select.class.d.ts +0 -3
  15. package/dist/components/conversation/model-select/model-select.class.js +1 -1
  16. package/dist/components/conversation/selection-toolbar/selection-toolbar.class.js +1 -1
  17. package/dist/components/conversation/voice-picker/voice-picker.class.d.ts +0 -1
  18. package/dist/components/conversation/voice-picker/voice-picker.class.js +1 -1
  19. package/dist/components/data/data-grid/data-grid.class.js +1 -1
  20. package/dist/components/data/graph-query-builder/graph-query-builder.class.d.ts +0 -1
  21. package/dist/components/data/graph-query-builder/graph-query-builder.class.js +1 -1
  22. package/dist/components/data/heatmap/heatmap.class.d.ts +4 -1
  23. package/dist/components/data/heatmap/heatmap.class.js +2 -2
  24. package/dist/components/data/table/table.class.d.ts +18 -4
  25. package/dist/components/data/table/table.class.js +2 -2
  26. package/dist/components/data/table/table.styles.js +2 -1
  27. package/dist/components/data/timeline/timeline-cluster.d.ts +1 -0
  28. package/dist/components/data/timeline/timeline-cluster.js +1 -0
  29. package/dist/components/data/timeline/timeline-item.class.d.ts +17 -2
  30. package/dist/components/data/timeline/timeline-item.class.js +13 -2
  31. package/dist/components/data/timeline/timeline-item.styles.js +65 -0
  32. package/dist/components/data/timeline/timeline.class.d.ts +83 -27
  33. package/dist/components/data/timeline/timeline.class.js +3 -3
  34. package/dist/components/data/timeline/timeline.d.ts +1 -0
  35. package/dist/components/data/timeline/timeline.js +1 -1
  36. package/dist/components/data/timeline/timeline.styles.js +14 -0
  37. package/dist/components/forms/button/button.class.js +1 -1
  38. package/dist/components/forms/checkbox/checkbox.class.d.ts +0 -1
  39. package/dist/components/forms/checkbox/checkbox.class.js +1 -1
  40. package/dist/components/forms/checkbox-group/checkbox-group.class.d.ts +0 -1
  41. package/dist/components/forms/checkbox-group/checkbox-group.class.js +1 -1
  42. package/dist/components/forms/code-editor/code-editor.class.js +1 -1
  43. package/dist/components/forms/color-picker/color-picker.class.js +1 -1
  44. package/dist/components/forms/combobox/combobox.class.d.ts +7 -1
  45. package/dist/components/forms/combobox/combobox.class.js +3 -2
  46. package/dist/components/forms/combobox/combobox.styles.js +1 -1
  47. package/dist/components/forms/date-picker/date-input.class.js +1 -1
  48. package/dist/components/forms/emoji-picker/emoji-data-loader.d.ts +0 -1
  49. package/dist/components/forms/input/native-time-input.class.js +1 -1
  50. package/dist/components/forms/input/time-input-shared.d.ts +2 -1
  51. package/dist/components/forms/input/time-input.class.js +1 -1
  52. package/dist/components/forms/locale-picker/locale-picker.class.d.ts +0 -1
  53. package/dist/components/forms/locale-picker/locale-picker.class.js +1 -1
  54. package/dist/components/forms/radio/radio-button.styles.d.ts +0 -1
  55. package/dist/components/forms/radio/radio-button.styles.js +1 -1
  56. package/dist/components/forms/radio/radio-group.class.d.ts +0 -1
  57. package/dist/components/forms/radio/radio-group.class.js +1 -1
  58. package/dist/components/forms/radio/radio.class.d.ts +0 -1
  59. package/dist/components/forms/radio/radio.class.js +1 -1
  60. package/dist/components/forms/rubric-form/rubric-form.class.d.ts +0 -1
  61. package/dist/components/forms/rubric-form/rubric-form.class.js +1 -1
  62. package/dist/components/forms/select/select.class.d.ts +0 -1
  63. package/dist/components/forms/select/select.class.js +1 -1
  64. package/dist/components/forms/slider/slider.class.d.ts +0 -1
  65. package/dist/components/forms/slider/slider.class.js +1 -1
  66. package/dist/components/forms/switch/switch.class.d.ts +0 -1
  67. package/dist/components/forms/switch/switch.class.js +1 -1
  68. package/dist/components/forms/time-range/time-range.class.d.ts +0 -1
  69. package/dist/components/forms/time-range/time-range.class.js +1 -1
  70. package/dist/components/forms/token-input/token-input.class.d.ts +0 -1
  71. package/dist/components/forms/token-input/token-input.class.js +1 -1
  72. package/dist/components/layout/dashboard-grid/layout-internal.d.ts +0 -1
  73. package/dist/components/layout/dashboard-grid/layout-internal.js +1 -1
  74. package/dist/components/layout/details/accordion-owner.d.ts +1 -1
  75. package/dist/components/layout/filter-bar/filter-bar.class.js +1 -1
  76. package/dist/components/layout/menu/menu-item.class.js +1 -1
  77. package/dist/components/layout/virtual-list/virtual-list.class.js +1 -1
  78. package/dist/components/media/av-player/av-metadata.d.ts +0 -2
  79. package/dist/components/media/av-player/av-metadata.js +1 -1
  80. package/dist/components/media/file-input/file-input.class.d.ts +0 -1
  81. package/dist/components/media/file-input/file-input.class.js +1 -1
  82. package/dist/components/media/map/map-loader.d.ts +4 -1
  83. package/dist/components/media/map/map.class.d.ts +50 -12
  84. package/dist/components/media/map/map.class.js +2 -2
  85. package/dist/components/overlays/overlay/popover.class.d.ts +7 -1
  86. package/dist/components/overlays/overlay/popover.class.js +1 -1
  87. package/dist/components/overlays/overlay/tooltip.class.d.ts +1 -0
  88. package/dist/components/overlays/overlay/tooltip.class.js +1 -1
  89. package/dist/components/overlays/progress/progress-shared.d.ts +0 -2
  90. package/dist/components/overlays/progress/progress-shared.js +1 -1
  91. package/dist/components/overlays/rating/rating.class.js +1 -1
  92. package/dist/components/retrieval/graph/graph-canvas.d.ts +10 -9
  93. package/dist/components/retrieval/mind-map/mind-map-layout.d.ts +0 -4
  94. package/dist/components/retrieval/mind-map/mind-map-layout.js +1 -1
  95. package/dist/components/utility/format/format-options.d.ts +0 -2
  96. package/dist/components/utility/format/format-options.js +1 -1
  97. package/dist/components/utility/icon/icon-resource.d.ts +0 -2
  98. package/dist/components/utility/icon/icon-resource.js +1 -1
  99. package/dist/components/viewers/archive-viewer/zip-resource-guard.d.ts +2 -1
  100. package/dist/components/viewers/docx-viewer/docx-resource-guard.d.ts +0 -3
  101. package/dist/components/viewers/docx-viewer/docx-resource-guard.js +1 -1
  102. package/dist/components/viewers/ebook-viewer/ebook-loader.d.ts +2 -1
  103. package/dist/components/viewers/ebook-viewer/epub-resource-guard.d.ts +0 -3
  104. package/dist/components/viewers/ebook-viewer/epub-resource-guard.js +1 -1
  105. package/dist/components/viewers/pdf-viewer/pdf-loader.d.ts +2 -2
  106. package/dist/components/viewers/pdf-viewer/pdf-viewer.class.js +2 -2
  107. package/dist/components/viewers/pptx-viewer/pptx-resource-guard.d.ts +0 -2
  108. package/dist/components/viewers/pptx-viewer/pptx-resource-guard.js +1 -1
  109. package/dist/components/viewers/spreadsheet-viewer/xlsx-resource-guard.d.ts +0 -5
  110. package/dist/components/viewers/spreadsheet-viewer/xlsx-resource-guard.js +1 -1
  111. package/dist/custom-elements-jsx.d.ts +4 -4
  112. package/dist/events.d.ts +15 -0
  113. package/dist/internal/accessibility-visibility.d.ts +1 -1
  114. package/dist/internal/anchor-target.d.ts +1 -3
  115. package/dist/internal/anchor-target.js +2 -2
  116. package/dist/internal/anchored-overlay-runtime.d.ts +7 -0
  117. package/dist/internal/anchored-overlay-runtime.js +1 -0
  118. package/dist/internal/announcement-text.d.ts +1 -1
  119. package/dist/internal/ansi.d.ts +0 -4
  120. package/dist/internal/ansi.js +1 -1
  121. package/dist/internal/aria-ownership.d.ts +2 -1
  122. package/dist/internal/default-strings.generated.d.ts +1 -0
  123. package/dist/internal/default-strings.generated.js +1 -1
  124. package/dist/internal/focus-navigation.d.ts +3 -2
  125. package/dist/internal/form-associated.d.ts +0 -1
  126. package/dist/internal/form-associated.js +1 -1
  127. package/dist/internal/form-control-labels.d.ts +0 -3
  128. package/dist/internal/form-control-labels.js +1 -1
  129. package/dist/internal/localization-runtime.js +1 -1
  130. package/dist/internal/localization-types.d.ts +1 -1
  131. package/dist/internal/localization.js +1 -1
  132. package/dist/internal/lyra-element.d.ts +0 -13
  133. package/dist/internal/lyra-element.js +1 -1
  134. package/dist/internal/nonmodal-overlay-manager.d.ts +9 -0
  135. package/dist/internal/nonmodal-overlay-manager.js +1 -0
  136. package/dist/internal/overlay-manager.d.ts +4 -20
  137. package/dist/internal/overlay-manager.js +1 -1
  138. package/dist/internal/overlay-stack.d.ts +66 -0
  139. package/dist/internal/overlay-stack.js +1 -0
  140. package/dist/internal/package-metadata.d.ts +1 -1
  141. package/dist/internal/package-metadata.js +1 -1
  142. package/dist/internal/positioner.js +1 -1
  143. package/dist/internal/text-quote.d.ts +1 -1
  144. package/dist/lyra.d.ts +2 -3
  145. package/dist/svelte.d.ts +4 -4
  146. package/dist/translations/ar.js +1 -1
  147. package/dist/translations/de.js +1 -1
  148. package/dist/translations/es.js +1 -1
  149. package/dist/translations/fa.js +1 -1
  150. package/dist/translations/fr.js +1 -1
  151. package/dist/translations/he.js +1 -1
  152. package/dist/translations/ja.js +1 -1
  153. package/dist/translations/pt-BR.js +1 -1
  154. package/dist/translations/ru.js +1 -1
  155. package/dist/translations/zh-CN.js +1 -1
  156. package/dist/utilities/localization.js +1 -1
  157. package/dist/vue.d.ts +4 -4
  158. package/llms/components/lr-combobox.md +42 -0
  159. package/llms/components/lr-date-input.md +1 -1
  160. package/llms/components/lr-date-picker.md +1 -1
  161. package/llms/components/lr-filter-bar.md +2 -2
  162. package/llms/components/lr-heatmap.md +4 -1
  163. package/llms/components/lr-lite-chart.md +20 -8
  164. package/llms/components/lr-map.md +52 -22
  165. package/llms/components/lr-option.md +42 -0
  166. package/llms/components/lr-popover.md +39 -0
  167. package/llms/components/lr-table.md +7 -3
  168. package/llms/components/lr-timeline-item.md +49 -24
  169. package/llms/components/lr-timeline.md +49 -24
  170. package/llms-full.txt +224 -60
  171. package/package.json +3 -4
  172. package/vscode-css-data.json +21 -0
  173. package/vscode-html-data.json +12 -6
  174. package/web-types.json +47 -16
@@ -18,12 +18,17 @@
18
18
  A `maplibre-gl` wrapper with a declarative legend, a single choropleth GeoJSON fill layer, markers,
19
19
  and additive `dataLayers` — plain GeoJSON, natively clustered points, or a heatmap density surface —
20
20
  plus a peer-neutral `map` getter for common imperative operations. Its runtime value is the
21
- underlying MapLibre map.
21
+ underlying MapLibre map. The component observes its own map-container allocation and calls the
22
+ peer's `resize()` when that allocation changes.
22
23
 
23
24
  **Properties:**
24
25
 
25
26
  - `center: [number, number] = [0, 0]`
26
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.
27
32
  - `mapStyle?: LyraMapStyleSpecification | string` (attribute: false) — required before a map is
28
33
  constructed. Object assignments are detached and recursively frozen; create and reassign a new
29
34
  style to update it. `LyraMapStyleSpecification` is the peer-neutral structural subset accepted from
@@ -98,6 +103,10 @@ string; geojson: GeoJSON.FeatureCollection; field: string; stops: [number, strin
98
103
  gotchas. Entries with non-finite coordinates or latitude outside `[-90, 90]` are skipped without
99
104
  aborting valid siblings. `color` is used only when the browser accepts it as CSS `color`;
100
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`.
101
110
  - `dataLayers: LyraMapGeoJsonDataLayer[] = []` (attribute: false) —
102
111
  `LyraMapGeoJsonDataLayer { sourceId: string; geojson: GeoJSON.Feature |
103
112
  GeoJSON.FeatureCollection; tone?: 'accent' | 'success' | 'warning' |
@@ -122,11 +131,12 @@ heatmap?: LyraMapHeatmapOptions; cluster?: LyraMapClusterOptions }`. `sourceId`
122
131
  from overwriting or removing a same-named source supplied by `mapStyle`. Independent of
123
132
  `choropleth` — no `field`/`stops` color-interpolation, just the geometry rendered in a flat tone;
124
133
  use `choropleth` instead when you need a data-driven color ramp. An entry whose `sourceId`
125
- persists across a `dataLayers` reassignment gets its GeoJSON updated in place (`setData()`), one
126
- that's dropped has its private source/layers removed, and a genuinely new `sourceId` gets new
127
- resources nothing leaks on removal, style change, or disconnect.
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.
128
138
 
129
- `cluster` and `kind` (both new in 11.3.0) opt one entry out of that three-layer geometry split.
139
+ `cluster` and `kind` (both new in 12.1.0) opt one entry out of that three-layer geometry split.
130
140
  **Both are strictly additive: an entry that sets neither renders exactly what it rendered before,
131
141
  down to the layer ids and the point layer's filter.**
132
142
 
@@ -156,7 +166,9 @@ number][]; colorSteps?: [number, string][]; countFont?: string[] }` turns the en
156
166
  renders the source as MapLibre's own first-class `heatmap` layer instead, which the geometry split
157
167
  cannot express at all: thousands of overlapping circles read as one opaque blob rather than as
158
168
  where the data is concentrated. `heatmap?: LyraMapHeatmapOptions { weightField?: string;
159
- weightRange?: [number, number]; stops?: [number, string][]; radius?: number; intensity?: number }`
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,
160
172
  configures it. `weightField` weights each point by a feature property, and `weightRange` maps that
161
173
  property's own units onto the 0–1 weight MapLibre expects — without it the raw value is passed
162
174
  through, which saturates the surface for any quantity above ~1; with neither, every point weighs 1.
@@ -170,10 +182,14 @@ weightRange?: [number, number]; stops?: [number, string][]; radius?: number; int
170
182
  describes a flat colour rather than a gradient; that one — like an unset or wholly unusable
171
183
  `stops` — falls back to the built-in ramp, which runs transparent → `--lr-color-brand` →
172
184
  `--lr-color-success` → `--lr-color-warning` → `--lr-color-danger`, so a retheme moves the density
173
- surface with everything else. `radius`
174
- (default 30) and `intensity` (default 1) are MapLibre's own. `cluster` is ignored on a heatmap
175
- entry: a heatmap already aggregates density, and clustering its input would feed it one point per
176
- cluster instead of the real distribution.
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.
177
193
  - `maxBounds: LyraMapBounds | null = null` (attribute: false) — box the map may not pan outside,
178
194
  `[[west, south], [east, north]]`. Prefer it over calling `map.setMaxBounds()` through the `.map`
179
195
  escape hatch: constraining the camera can wedge maplibre-gl at a sub-1 fractional zoom in a wide
@@ -185,14 +201,15 @@ weightRange?: [number, number]; stops?: [number, string][]; radius?: number; int
185
201
 
186
202
  **Choropleth and `dataLayers` updates are diffed before they reach the peer.** `setData()`
187
203
  unconditionally re-tiles and repaints an entire source, which is invisible on a static map and
188
- expensive on an animated one. When an update changes only feature *properties* the values driving
189
- the colour ramp the component emits maplibre-gl's incremental `updateData()` instead. The fast
190
- path is deliberately strict, and needs all of: the same feature count, a `string`/`number` `id` on
191
- every feature in the same order, and geometry that is the **same object** as last time (not merely
192
- deep-equal). Structurally comparing polygon rings would cost about what the re-tile costs, and a
193
- false positive would paint stale geometry. Reuse your geometry objects across frames — the
194
- efficient way to build an animation anyway and the fast path applies; otherwise it falls back to
195
- `setData()` with no change in behaviour. Peers predating `updateData()` always take the old path.
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.
196
213
 
197
214
  **Feature properties are tiled, and therefore bounded in numeric magnitude.** MapLibre GL tiles
198
215
  every GeoJSON source through a worker into a protobuf vector tile, so a property carrying a huge
@@ -212,7 +229,8 @@ payload beside the map.
212
229
 
213
230
  **Authoring types:** `LyraMapLegendEntry`, `LyraMapLegendPattern`, `LyraMapLegendProjection`, `LyraMapChoroplethLayer`,
214
231
  `LyraMapGeoJsonDataLayer`, `LyraMapDataLayerKind`, `LyraMapClusterOptions`, `LyraMapHeatmapOptions`,
215
- `LyraMapMarker`, `LyraMapStyleSpecification`, and `LyraMapInstance`.
232
+ `LyraMapHeatmapZoomValue`, `LyraMapMarker`, `LyraMapMarkerActivationDetail`,
233
+ `LyraMapMarkerActivationSource`, `LyraMapStyleSpecification`, and `LyraMapInstance`.
216
234
  The former `LegendEntry`, `ChoroplethLayer`, `GeoJsonDataLayer`, and `MapMarker` names are removed
217
235
  in v9 rather than retained as aliases.
218
236
 
@@ -227,7 +245,10 @@ shared `maplibre-gl` import without constructing a map or allocating a WebGL con
227
245
  `false` when the peer is unavailable, allowing an application to choose a fallback before connecting
228
246
  an element.
229
247
 
230
- **Events:** `lr-map-load` (fired once, after the underlying map's own `'load'`), `lr-map-click`
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`
231
252
  (frozen `detail: { readonly lngLat: readonly [lng, lat], readonly feature?, readonly origin?,
232
253
  readonly sourceId? }`; the tuple and any hit GeoJSON feature are detached and recursively frozen).
233
254
  `feature` resolves against the choropleth fill layer **and** every applied `dataLayers`
@@ -305,6 +326,10 @@ Vite with v6:
305
326
  { color: "#0969da", label: "High", pattern: "diagonal" },
306
327
  ];
307
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
+ );
308
333
  m.addEventListener("lr-map-click", (e) =>
309
334
  console.log(e.detail.feature?.properties)
310
335
  );
@@ -321,8 +346,9 @@ https://maplibre.org/maplibre-gl-js/docs/#esm.
321
346
  created peer instance, renders only the localized initialization failure, and can retry after a
322
347
  new style or reconnect without an unhandled promise rejection. Capability probes and error
323
348
  constructors come from the current owner document, including after same-origin adoption.
324
- - A marker with a popup handles Space at its focus boundary: one activation toggles the peer popup
325
- and suppresses the page-scroll default. Markers without a popup do not consume Space.
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.
326
352
  - clearing or swapping the choropleth no longer leaks the old layer: setting `choropleth =
327
353
  undefined`, or changing `choropleth.sourceId` to a different value, now calls `removeLayer`/
328
354
  `removeSource` on whatever was previously applied before adding the new one (or nothing, if
@@ -367,5 +393,9 @@ undefined`, or changing `choropleth.sourceId` to a different value, now calls `r
367
393
  `lr-map-load` never fires until the element is actually scrolled into view. Deliberate: caps
368
394
  concurrent WebGL contexts when many `<lr-map>`s sit in one dashboard/grid. Skipped entirely
369
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.
370
400
 
371
401
  ---
@@ -20,6 +20,48 @@ Filterable single/multi-select combining a text input with a listbox. Mirrors th
20
20
  `<wa-combobox>` API under the `lr-` prefix. **Form-associated** (hand-rolled internals, not the
21
21
  shared `FormAssociated` mixin — see gotchas).
22
22
 
23
+ **First-interaction registration.** Where initial-route weight is stricter than a static combobox
24
+ registration allows, keep a labelled native `<input list>` as the working pre-JavaScript control
25
+ and import only the granular combobox registration on its first focus. Copy the native value after
26
+ the import resolves so typing that happens while the chunk is in flight is not lost, then transfer
27
+ focus explicitly — the browser does not replay the focus event after custom-element upgrade:
28
+
29
+ ```html
30
+ <div id="country-fallback">
31
+ <label for="country-native">Country</label>
32
+ <input id="country-native" name="country" list="country-options">
33
+ <datalist id="country-options"><option value="France"></option></datalist>
34
+ </div>
35
+ <lr-combobox id="country-enhanced" name="country" label="Country" hidden>
36
+ <lr-option value="France">France</lr-option>
37
+ </lr-combobox>
38
+ <script type="module">
39
+ const fallback = document.querySelector("#country-fallback");
40
+ const input = document.querySelector("#country-native");
41
+ const combobox = document.querySelector("#country-enhanced");
42
+ let registration;
43
+ input.addEventListener("focus", async () => {
44
+ if (!combobox.hidden) return;
45
+ registration ??= import(
46
+ "@aceshooting/lyra-ui/components/forms/combobox/combobox.js"
47
+ ).catch((error) => {
48
+ registration = undefined; // let a later interaction retry
49
+ throw error;
50
+ });
51
+ await registration;
52
+ await customElements.whenDefined("lr-combobox");
53
+ combobox.value = input.value;
54
+ fallback.hidden = true;
55
+ combobox.hidden = false;
56
+ combobox.focus();
57
+ });
58
+ </script>
59
+ ```
60
+
61
+ Leave the native control in place if registration fails. This pattern preserves the initial shell;
62
+ the full form-label, option, overlay, and first-open positioning contracts arrive in deferred
63
+ chunks instead of being weakened in a separate partial combobox implementation.
64
+
23
65
  An `lr-option` row remains bounded by its owning listbox: the default label ellipsizes and each
24
66
  `start`/`end` (or `prefix`/`suffix`) adornment is capped at 40% of the row. Unbroken metadata
25
67
  therefore cannot widen a 320px LTR or RTL picker.
@@ -17,6 +17,45 @@
17
17
 
18
18
  A click-triggered, light-dismiss floating surface positioned with the shared Floating UI positioner.
19
19
 
20
+ **First-interaction registration.** A performance-sensitive navigation shell can keep a native
21
+ `<details>` disclosure working before JavaScript, then load only the granular popover registration
22
+ when that fallback first opens. The interaction that starts an async import is not replayed after
23
+ the custom element upgrades, so finish the handoff explicitly with `show()`:
24
+
25
+ ```html
26
+ <details id="account-fallback">
27
+ <summary>Account</summary>
28
+ <nav aria-label="Account"><a href="/profile">Profile</a></nav>
29
+ </details>
30
+ <lr-popover id="account-popover" popup-role="none" hidden>
31
+ <button slot="trigger">Account</button>
32
+ <nav aria-label="Account"><a href="/profile">Profile</a></nav>
33
+ </lr-popover>
34
+ <script type="module">
35
+ const fallback = document.querySelector("#account-fallback");
36
+ const popover = document.querySelector("#account-popover");
37
+ let registration;
38
+ fallback.addEventListener("toggle", async () => {
39
+ if (!fallback.open || !popover.hidden) return;
40
+ registration ??= import(
41
+ "@aceshooting/lyra-ui/components/overlays/overlay/popover.js"
42
+ ).catch((error) => {
43
+ registration = undefined; // let a later interaction retry
44
+ throw error;
45
+ });
46
+ await registration;
47
+ await customElements.whenDefined("lr-popover");
48
+ fallback.hidden = true;
49
+ popover.hidden = false;
50
+ await popover.show();
51
+ });
52
+ </script>
53
+ ```
54
+
55
+ This keeps the fallback's markup and semantics in the initial document. The registration, Lyra
56
+ base, nonmodal stack, and first-open positioning runtime remain outside that initial module graph.
57
+ If the import fails, leave the native disclosure visible and usable.
58
+
20
59
  **Properties:**
21
60
 
22
61
  - `open: boolean = false` (reflected) — assigning it runs the same `lr-show`/`lr-hide` lifecycle as
@@ -331,7 +331,7 @@ no-rows states return the empty element as the shadow root's own root, with no `
331
331
  wrapper around it — `::part(base)` does not apply in those two states, only in the filtered-to-zero
332
332
  one — and that `empty` disappears entirely once the `empty` slot is filled.
333
333
 
334
- - `scrollMode: 'self' | 'page' = 'self'` (attribute `scroll-mode`, reflected) — which element
334
+ - `scrollMode: 'self' | 'page' | 'auto' = 'self'` (attribute `scroll-mode`, reflected) — which element
335
335
  scrolls when the table overflows. `'self'` makes `[part="base"]` the scroll container, which is
336
336
  what pairs with `--lr-table-max-height` and makes the sticky header pin inside the table's own
337
337
  viewport. `'page'` hands scrolling back to the document. Needed because a scroll container clips
@@ -339,8 +339,12 @@ one — and that `empty` disappears entirely once the `empty` slot is filled.
339
339
  still `overflow: auto` becomes a sticky containing block that never scrolls, and its header
340
340
  scrolls away with the page. With `'page'` the header's nearest scrollport is the page, so it pins
341
341
  there; the cost is that a table wider than its host overflows the page instead of scrolling
342
- inside itself. Named `scrollMode` rather than `scroll` because a `scroll` property would shadow
343
- `Element.prototype.scroll()`
342
+ inside itself. The opt-in `'auto'` mode resolves between those two behaviors from the rendered
343
+ allocation: while content fits it uses page flow, and only while content actually overflows
344
+ horizontally does `[part="base"]` become the contained scrollport. It re-evaluates when either
345
+ the allocated width or the rendered table's intrinsic width changes, so the same table can flow
346
+ with a desktop page and contain itself in a 320px panel. The default remains `'self'`. Named
347
+ `scrollMode` rather than `scroll` because a `scroll` property would shadow `Element.prototype.scroll()`
344
348
 
345
349
  **Themeable custom properties:** `--lr-table-cell-color` (default `inherit`),
346
350
  `--lr-table-cell-link-color` (default `var(--lr-color-brand)`) and
@@ -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** 9 parts, 5 custom properties — see this component's own `@csspart`/`@cssprop` list below
11
+ - **Themeable via** 11 parts, 8 custom properties — see this component's own `@csspart`/`@cssprop` list below
12
12
  - **Documented with** `lr-timeline` (same section below)
13
13
  - **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`
14
14
 
@@ -16,20 +16,21 @@
16
16
 
17
17
  ## `lr-timeline` and `lr-timeline-item`
18
18
 
19
- Read-only chronological sequence. `lr-timeline` is a `role="list"` flex container; each
19
+ Read-only chronological sequence by default. `lr-timeline` is a `role="list"` flex container; each
20
20
  `lr-timeline-item` is a light-DOM child that sets `role="listitem"` on itself and renders its own
21
21
  marker plus the trailing rail segment reaching toward the next item's marker. The last item's rail is
22
22
  suppressed purely in CSS (`::slotted([role='listitem']:last-child)`) — no JS coordination anywhere.
23
- Neither element has events, keyboard navigation, or a selection model: a passive record display, by
24
- design (an item's `title`/`description` routinely hold focusable content, so wrapping the row in
25
- `role="button"` would trip `nested-interactive`).
23
+ Items have no keyboard navigation or selection model: a passive record display, by design (an
24
+ item's `title`/`description` routinely hold focusable content, so wrapping the row in `role="button"`
25
+ would trip `nested-interactive`). The opt-in clustered time scale adds only native count-marker
26
+ buttons; it does not make the individual rows interactive.
26
27
 
27
28
  **`lr-timeline` properties:** `orientation: 'vertical' | 'horizontal' = 'vertical'` — note the
28
29
  opposite default from `lr-stepper`; `horizontal` makes `[part='base']` a horizontally scrollable row.
29
30
  `accessibleLabel: string = ''` (attribute `aria-label`) overrides the localized `"Timeline"` name
30
31
  (the `role="list"` element is in the shadow root and never inherits a host attribute). Read-only
31
- `itemCount: number` is the live count of direct default-slot `<lr-timeline-item>` children;
32
- unrelated slotted elements and text nodes are ignored.
32
+ `itemCount: number` is the live count of default-slot `<lr-timeline-item>` assignments (including
33
+ flattened forwarding slots); unrelated slotted elements and text nodes are ignored.
33
34
  `scale: 'flow' | 'time' = 'flow'` (attribute `scale`, type `LyraTimelineScale`) chooses how items
34
35
  are distributed along the main axis. `'flow'` is the default even sequence, where `timestamp` is
35
36
  rendered as text but carries no positional meaning. `'time'` positions each item at its true
@@ -37,23 +38,34 @@ proportion of the range, so a gap of weeks and a gap of decades stop looking ide
37
38
  needs a definite extent to distribute along — `--lr-timeline-time-extent` (default
38
39
  `var(--lr-size-20rem)`), applied as `block-size` when vertical and `inline-size` when horizontal —
39
40
  because items are absolutely positioned and a percentage against an auto-sized track resolves to
40
- zero. `collision: 'overlap' | 'stack' = 'overlap'` (attribute `collision`, type `LyraTimelineCollision`)
41
- chooses what `scale="time"` does with items landing on nearly the same position: `'overlap'` leaves
42
- them stacked on one another, `'stack'` steps each colliding item one lane along the **cross** axis
43
- (indent per lane: `--lr-timeline-collision-offset`, default `var(--lr-space-l)`), which is what a
44
- dense chronology needs items within 1.5% of the axis of each other count as colliding, and an
45
- isolated item returns to lane 0 rather than inheriting a preceding run's depth. There is
46
- deliberately no `'cluster'` mode: collapsing coincident items into one expandable marker needs a
47
- selection model and click events this passive component does not have.
41
+ zero. `collision: 'overlap' | 'stack' | 'cluster' = 'overlap'` (attribute `collision`, type
42
+ `LyraTimelineCollision`) chooses what `scale="time"` does with items landing on nearly the same
43
+ position: `'overlap'` leaves them stacked on one another, `'stack'` steps each colliding item one
44
+ lane along the **cross** axis (indent per lane: `--lr-timeline-collision-offset`, default
45
+ `var(--lr-space-l)`), and `'cluster'` replaces every group of at least two colliding items with one
46
+ count marker. The group's first member in author order becomes its representative and renders the
47
+ button in that item's existing list/Tab position; the remaining members are hidden, so visual,
48
+ semantic, and keyboard order do not diverge merely because clustering is enabled. Stack mode and
49
+ the cluster floor treat items within 1.5% of the axis as colliding.
50
+ Cluster mode widens that floor to the rendered count action's footprint on the currently allocated
51
+ axis, then reclusters when the allocation or rendered action size changes, so interactive marker
52
+ hit areas do not overlap.
53
+ Cluster windows are bounded from their first sorted position rather than transitively chaining every
54
+ dense neighbour, so a large history becomes a useful sequence of markers rather than one axis-wide
55
+ cluster. An isolated item remains an ordinary timeline item. Activating a count marker is a
56
+ notification only; it never expands or selects items internally.
48
57
  `rangeStart` / `rangeEnd` (`Date | string | number`, attribute: false) pin the axis instead of
49
58
  deriving it from the earliest/latest item; a reversed or non-finite pair falls back to the derived
50
59
  range. An item with no parseable `timestamp` (including one supplied only through the `timestamp`
51
60
  slot, which carries no machine-readable instant) keeps document order and is spread evenly, so a
52
- partially-timestamped list degrades rather than stacking at the origin. Items sharing an instant
53
- overlap rather than being fanned into lanes: lane assignment, brushing, zooming and per-event
54
- selection belong to a denser component than this deliberately passive one. Positions are written to
61
+ partially-timestamped list degrades rather than stacking at the origin. Reassigning an item's
62
+ `timestamp` reclusters without requiring a slot mutation. Positions are written to
55
63
  each child as a private `--_lr-timeline-item-offset` custom property and removed again on a switch
56
- back to `'flow'`, so the component still never alters its children's content or structure.
64
+ back to `'flow'`. Cluster mode adds and removes a private visibility marker on non-representative
65
+ members and temporarily changes only the representative item's shadow presentation; switching
66
+ mode, shrinking the data, or disconnecting restores every ordinary row. If regrouping would remove
67
+ the focused row content or count action, focus moves to the replacement cluster action, the first
68
+ surviving item action, or the timeline list as a programmatic fallback.
57
69
 
58
70
  **`lr-timeline-item` properties:** `timestamp?: Date | string | number` (attribute: false — `Date`
59
71
  isn't attribute-serializable; invalid input normalizes to unset and renders no timestamp UI),
@@ -63,8 +75,16 @@ isn't attribute-serializable; invalid input normalizes to unset and renders no t
63
75
  optional pulse disabled under `prefers-reduced-motion: reduce`, with explicit `aria-current="true"`
64
76
  or `"false"` on the host).
65
77
 
66
- **Events:** none on either element. Read the reactive `itemCount` property after changing direct
67
- children; the internal slot's non-composed `slotchange` event is not a host-level public signal.
78
+ **Events:** `lr-timeline` emits `lr-cluster-activate` when a `collision="cluster"` marker is
79
+ activated by pointer, Enter, or Space. Its non-cancelable, bubbling, composed
80
+ `detail: LyraTimelineClusterActivateDetail` is `{ items: readonly LyraTimelineItem[] }`: a fresh
81
+ frozen snapshot of the cluster members in document order, preserving each element's identity.
82
+ `LyraTimelineClusterActivateDetail {
83
+ items: unknown;
84
+ }`
85
+ Use it to open a consumer-owned popover, dialog, or detail view. `lr-timeline-item` emits no events.
86
+ Read the reactive `itemCount` property after changing assigned items; the internal slot's
87
+ non-composed `slotchange` event is not a host-level public signal.
68
88
 
69
89
  **Slots:** `lr-timeline`'s default slot holds the items, in display order. On an item the **default
70
90
  slot is the title** (there is no `title` slot), plus `marker-icon` (marker glyph override; an empty
@@ -73,7 +93,9 @@ slot falls back to a color-coded dot), `timestamp` (wins outright over the
73
93
  entirely when empty).
74
94
 
75
95
  **CSS parts:** timeline `base` — the `role="list"` flex container (no separate `list` part). Item:
76
- `base`, `track` (marker + rail spine, always the opposite axis from `base`), `marker`
96
+ `cluster` the native count button rendered while that item represents a cluster, with a 40px
97
+ minimum action surface; `cluster-count` — its painted count pill; `base`, `track`
98
+ (marker + rail spine, always the opposite axis from `base`), `marker`
77
99
  (`aria-hidden="true"`, decorative), `rail` (the connecting segment; `visibility: hidden` rather than
78
100
  removed on the last item, so marker alignment stays consistent), `content`, `header` (flex row
79
101
  wrapping `title` and `timestamp`; wraps at narrow widths rather than truncating), `title`,
@@ -81,8 +103,11 @@ wrapping `title` and `timestamp`; wraps at narrow widths rather than truncating)
81
103
 
82
104
  **Themeable custom properties:** `--lr-timeline-gap` (default `var(--lr-space-l)`) — declared on
83
105
  `lr-timeline` but consumed inside each item via inheritance across the slot boundary; it is both the
84
- inter-item spacing and the length each rail bridges. `--lr-scroll-fade-size` (default `2rem`) controls each
85
- horizontal-overflow edge fade; forced-colors mode removes the masks while retaining native
106
+ inter-item spacing and the length each rail bridges. `--lr-timeline-cluster-size` (default
107
+ `var(--lr-size-2rem)`) sizes the painted count pill, `--lr-timeline-cluster-bg` (default
108
+ `var(--lr-color-brand)`) sets its background, and `--lr-timeline-cluster-color` (default
109
+ `var(--lr-color-on-brand)`) sets its foreground. `--lr-scroll-fade-size` (default `2rem`) controls
110
+ each horizontal-overflow edge fade; forced-colors mode removes the masks while retaining native
86
111
  scrolling. On the item: `--lr-timeline-marker-size`
87
112
  (default `var(--lr-size-1-25rem)`, both dimensions so the dot stays circular),
88
113
  `--lr-timeline-rail-width` (default `var(--lr-border-width-medium)`), `--lr-timeline-rail-color`
@@ -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** 1 part, 4 custom properties — see this component's own `@csspart`/`@cssprop` list below
11
+ - **Themeable via** 1 part, 7 custom properties — see this component's own `@csspart`/`@cssprop` list below
12
12
  - **Documented with** `lr-timeline-item` (same section below)
13
13
  - **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`
14
14
 
@@ -16,20 +16,21 @@
16
16
 
17
17
  ## `lr-timeline` and `lr-timeline-item`
18
18
 
19
- Read-only chronological sequence. `lr-timeline` is a `role="list"` flex container; each
19
+ Read-only chronological sequence by default. `lr-timeline` is a `role="list"` flex container; each
20
20
  `lr-timeline-item` is a light-DOM child that sets `role="listitem"` on itself and renders its own
21
21
  marker plus the trailing rail segment reaching toward the next item's marker. The last item's rail is
22
22
  suppressed purely in CSS (`::slotted([role='listitem']:last-child)`) — no JS coordination anywhere.
23
- Neither element has events, keyboard navigation, or a selection model: a passive record display, by
24
- design (an item's `title`/`description` routinely hold focusable content, so wrapping the row in
25
- `role="button"` would trip `nested-interactive`).
23
+ Items have no keyboard navigation or selection model: a passive record display, by design (an
24
+ item's `title`/`description` routinely hold focusable content, so wrapping the row in `role="button"`
25
+ would trip `nested-interactive`). The opt-in clustered time scale adds only native count-marker
26
+ buttons; it does not make the individual rows interactive.
26
27
 
27
28
  **`lr-timeline` properties:** `orientation: 'vertical' | 'horizontal' = 'vertical'` — note the
28
29
  opposite default from `lr-stepper`; `horizontal` makes `[part='base']` a horizontally scrollable row.
29
30
  `accessibleLabel: string = ''` (attribute `aria-label`) overrides the localized `"Timeline"` name
30
31
  (the `role="list"` element is in the shadow root and never inherits a host attribute). Read-only
31
- `itemCount: number` is the live count of direct default-slot `<lr-timeline-item>` children;
32
- unrelated slotted elements and text nodes are ignored.
32
+ `itemCount: number` is the live count of default-slot `<lr-timeline-item>` assignments (including
33
+ flattened forwarding slots); unrelated slotted elements and text nodes are ignored.
33
34
  `scale: 'flow' | 'time' = 'flow'` (attribute `scale`, type `LyraTimelineScale`) chooses how items
34
35
  are distributed along the main axis. `'flow'` is the default even sequence, where `timestamp` is
35
36
  rendered as text but carries no positional meaning. `'time'` positions each item at its true
@@ -37,23 +38,34 @@ proportion of the range, so a gap of weeks and a gap of decades stop looking ide
37
38
  needs a definite extent to distribute along — `--lr-timeline-time-extent` (default
38
39
  `var(--lr-size-20rem)`), applied as `block-size` when vertical and `inline-size` when horizontal —
39
40
  because items are absolutely positioned and a percentage against an auto-sized track resolves to
40
- zero. `collision: 'overlap' | 'stack' = 'overlap'` (attribute `collision`, type `LyraTimelineCollision`)
41
- chooses what `scale="time"` does with items landing on nearly the same position: `'overlap'` leaves
42
- them stacked on one another, `'stack'` steps each colliding item one lane along the **cross** axis
43
- (indent per lane: `--lr-timeline-collision-offset`, default `var(--lr-space-l)`), which is what a
44
- dense chronology needs items within 1.5% of the axis of each other count as colliding, and an
45
- isolated item returns to lane 0 rather than inheriting a preceding run's depth. There is
46
- deliberately no `'cluster'` mode: collapsing coincident items into one expandable marker needs a
47
- selection model and click events this passive component does not have.
41
+ zero. `collision: 'overlap' | 'stack' | 'cluster' = 'overlap'` (attribute `collision`, type
42
+ `LyraTimelineCollision`) chooses what `scale="time"` does with items landing on nearly the same
43
+ position: `'overlap'` leaves them stacked on one another, `'stack'` steps each colliding item one
44
+ lane along the **cross** axis (indent per lane: `--lr-timeline-collision-offset`, default
45
+ `var(--lr-space-l)`), and `'cluster'` replaces every group of at least two colliding items with one
46
+ count marker. The group's first member in author order becomes its representative and renders the
47
+ button in that item's existing list/Tab position; the remaining members are hidden, so visual,
48
+ semantic, and keyboard order do not diverge merely because clustering is enabled. Stack mode and
49
+ the cluster floor treat items within 1.5% of the axis as colliding.
50
+ Cluster mode widens that floor to the rendered count action's footprint on the currently allocated
51
+ axis, then reclusters when the allocation or rendered action size changes, so interactive marker
52
+ hit areas do not overlap.
53
+ Cluster windows are bounded from their first sorted position rather than transitively chaining every
54
+ dense neighbour, so a large history becomes a useful sequence of markers rather than one axis-wide
55
+ cluster. An isolated item remains an ordinary timeline item. Activating a count marker is a
56
+ notification only; it never expands or selects items internally.
48
57
  `rangeStart` / `rangeEnd` (`Date | string | number`, attribute: false) pin the axis instead of
49
58
  deriving it from the earliest/latest item; a reversed or non-finite pair falls back to the derived
50
59
  range. An item with no parseable `timestamp` (including one supplied only through the `timestamp`
51
60
  slot, which carries no machine-readable instant) keeps document order and is spread evenly, so a
52
- partially-timestamped list degrades rather than stacking at the origin. Items sharing an instant
53
- overlap rather than being fanned into lanes: lane assignment, brushing, zooming and per-event
54
- selection belong to a denser component than this deliberately passive one. Positions are written to
61
+ partially-timestamped list degrades rather than stacking at the origin. Reassigning an item's
62
+ `timestamp` reclusters without requiring a slot mutation. Positions are written to
55
63
  each child as a private `--_lr-timeline-item-offset` custom property and removed again on a switch
56
- back to `'flow'`, so the component still never alters its children's content or structure.
64
+ back to `'flow'`. Cluster mode adds and removes a private visibility marker on non-representative
65
+ members and temporarily changes only the representative item's shadow presentation; switching
66
+ mode, shrinking the data, or disconnecting restores every ordinary row. If regrouping would remove
67
+ the focused row content or count action, focus moves to the replacement cluster action, the first
68
+ surviving item action, or the timeline list as a programmatic fallback.
57
69
 
58
70
  **`lr-timeline-item` properties:** `timestamp?: Date | string | number` (attribute: false — `Date`
59
71
  isn't attribute-serializable; invalid input normalizes to unset and renders no timestamp UI),
@@ -63,8 +75,16 @@ isn't attribute-serializable; invalid input normalizes to unset and renders no t
63
75
  optional pulse disabled under `prefers-reduced-motion: reduce`, with explicit `aria-current="true"`
64
76
  or `"false"` on the host).
65
77
 
66
- **Events:** none on either element. Read the reactive `itemCount` property after changing direct
67
- children; the internal slot's non-composed `slotchange` event is not a host-level public signal.
78
+ **Events:** `lr-timeline` emits `lr-cluster-activate` when a `collision="cluster"` marker is
79
+ activated by pointer, Enter, or Space. Its non-cancelable, bubbling, composed
80
+ `detail: LyraTimelineClusterActivateDetail` is `{ items: readonly LyraTimelineItem[] }`: a fresh
81
+ frozen snapshot of the cluster members in document order, preserving each element's identity.
82
+ `LyraTimelineClusterActivateDetail {
83
+ items: unknown;
84
+ }`
85
+ Use it to open a consumer-owned popover, dialog, or detail view. `lr-timeline-item` emits no events.
86
+ Read the reactive `itemCount` property after changing assigned items; the internal slot's
87
+ non-composed `slotchange` event is not a host-level public signal.
68
88
 
69
89
  **Slots:** `lr-timeline`'s default slot holds the items, in display order. On an item the **default
70
90
  slot is the title** (there is no `title` slot), plus `marker-icon` (marker glyph override; an empty
@@ -73,7 +93,9 @@ slot falls back to a color-coded dot), `timestamp` (wins outright over the
73
93
  entirely when empty).
74
94
 
75
95
  **CSS parts:** timeline `base` — the `role="list"` flex container (no separate `list` part). Item:
76
- `base`, `track` (marker + rail spine, always the opposite axis from `base`), `marker`
96
+ `cluster` the native count button rendered while that item represents a cluster, with a 40px
97
+ minimum action surface; `cluster-count` — its painted count pill; `base`, `track`
98
+ (marker + rail spine, always the opposite axis from `base`), `marker`
77
99
  (`aria-hidden="true"`, decorative), `rail` (the connecting segment; `visibility: hidden` rather than
78
100
  removed on the last item, so marker alignment stays consistent), `content`, `header` (flex row
79
101
  wrapping `title` and `timestamp`; wraps at narrow widths rather than truncating), `title`,
@@ -81,8 +103,11 @@ wrapping `title` and `timestamp`; wraps at narrow widths rather than truncating)
81
103
 
82
104
  **Themeable custom properties:** `--lr-timeline-gap` (default `var(--lr-space-l)`) — declared on
83
105
  `lr-timeline` but consumed inside each item via inheritance across the slot boundary; it is both the
84
- inter-item spacing and the length each rail bridges. `--lr-scroll-fade-size` (default `2rem`) controls each
85
- horizontal-overflow edge fade; forced-colors mode removes the masks while retaining native
106
+ inter-item spacing and the length each rail bridges. `--lr-timeline-cluster-size` (default
107
+ `var(--lr-size-2rem)`) sizes the painted count pill, `--lr-timeline-cluster-bg` (default
108
+ `var(--lr-color-brand)`) sets its background, and `--lr-timeline-cluster-color` (default
109
+ `var(--lr-color-on-brand)`) sets its foreground. `--lr-scroll-fade-size` (default `2rem`) controls
110
+ each horizontal-overflow edge fade; forced-colors mode removes the masks while retaining native
86
111
  scrolling. On the item: `--lr-timeline-marker-size`
87
112
  (default `var(--lr-size-1-25rem)`, both dimensions so the dot stays circular),
88
113
  `--lr-timeline-rail-width` (default `var(--lr-border-width-medium)`), `--lr-timeline-rail-color`