@aceshooting/lyra-ui 7.0.0 → 7.2.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 (81) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +12 -4
  3. package/custom-elements.json +885 -40
  4. package/dist/components/charts/chart/chart.class.d.ts +9 -3
  5. package/dist/components/charts/chart/chart.class.d.ts.map +1 -1
  6. package/dist/components/charts/chart/chart.class.js +31 -3
  7. package/dist/components/charts/chart/chart.class.js.map +1 -1
  8. package/dist/components/charts/chart/lite-chart.class.d.ts +24 -1
  9. package/dist/components/charts/chart/lite-chart.class.d.ts.map +1 -1
  10. package/dist/components/charts/chart/lite-chart.class.js +53 -3
  11. package/dist/components/charts/chart/lite-chart.class.js.map +1 -1
  12. package/dist/components/forms/button/button.class.d.ts +10 -4
  13. package/dist/components/forms/button/button.class.d.ts.map +1 -1
  14. package/dist/components/forms/button/button.class.js +21 -5
  15. package/dist/components/forms/button/button.class.js.map +1 -1
  16. package/dist/components/forms/checkbox/checkbox.class.d.ts +5 -3
  17. package/dist/components/forms/checkbox/checkbox.class.d.ts.map +1 -1
  18. package/dist/components/forms/checkbox/checkbox.class.js +13 -3
  19. package/dist/components/forms/checkbox/checkbox.class.js.map +1 -1
  20. package/dist/components/forms/icon-button/icon-button.class.d.ts +11 -4
  21. package/dist/components/forms/icon-button/icon-button.class.d.ts.map +1 -1
  22. package/dist/components/forms/icon-button/icon-button.class.js +21 -5
  23. package/dist/components/forms/icon-button/icon-button.class.js.map +1 -1
  24. package/dist/components/forms/icon-button/icon-button.styles.js +1 -1
  25. package/dist/components/layout/menu/menu.class.d.ts +4 -1
  26. package/dist/components/layout/menu/menu.class.d.ts.map +1 -1
  27. package/dist/components/layout/menu/menu.class.js +4 -1
  28. package/dist/components/layout/menu/menu.class.js.map +1 -1
  29. package/dist/components/media/map/map-loader.d.ts +2 -2
  30. package/dist/components/media/map/map-loader.js +2 -2
  31. package/dist/components/media/map/map.class.d.ts +2 -2
  32. package/dist/components/media/map/map.class.js +2 -2
  33. package/dist/components/overlays/overlay/popover.class.d.ts +7 -0
  34. package/dist/components/overlays/overlay/popover.class.d.ts.map +1 -1
  35. package/dist/components/overlays/overlay/popover.class.js +13 -2
  36. package/dist/components/overlays/overlay/popover.class.js.map +1 -1
  37. package/dist/components/overlays/overlay/tooltip.class.d.ts +9 -0
  38. package/dist/components/overlays/overlay/tooltip.class.d.ts.map +1 -1
  39. package/dist/components/overlays/overlay/tooltip.class.js +43 -3
  40. package/dist/components/overlays/overlay/tooltip.class.js.map +1 -1
  41. package/dist/components/retrieval/knowledge-graph-explorer/knowledge-graph-explorer.class.d.ts +10 -2
  42. package/dist/components/retrieval/knowledge-graph-explorer/knowledge-graph-explorer.class.d.ts.map +1 -1
  43. package/dist/components/retrieval/knowledge-graph-explorer/knowledge-graph-explorer.class.js +7 -2
  44. package/dist/components/retrieval/knowledge-graph-explorer/knowledge-graph-explorer.class.js.map +1 -1
  45. package/dist/internal/aria-controls.d.ts +11 -2
  46. package/dist/internal/aria-controls.d.ts.map +1 -1
  47. package/dist/internal/aria-controls.js +35 -11
  48. package/dist/internal/aria-controls.js.map +1 -1
  49. package/dist/internal/localization.d.ts +1 -1
  50. package/dist/internal/localization.d.ts.map +1 -1
  51. package/dist/internal/localization.js +4 -0
  52. package/dist/internal/localization.js.map +1 -1
  53. package/dist/localization.d.ts +9 -0
  54. package/dist/localization.d.ts.map +1 -0
  55. package/dist/localization.js +8 -0
  56. package/dist/localization.js.map +1 -0
  57. package/dist/lyra.d.ts +3 -3
  58. package/dist/lyra.d.ts.map +1 -1
  59. package/dist/lyra.js +1 -1
  60. package/dist/lyra.js.map +1 -1
  61. package/llms/components/lr-button.md +7 -4
  62. package/llms/components/lr-chart.md +8 -5
  63. package/llms/components/lr-checkbox.md +6 -3
  64. package/llms/components/lr-dropdown-item.md +10 -1
  65. package/llms/components/lr-dropdown.md +2 -0
  66. package/llms/components/lr-geojson-view.md +4 -3
  67. package/llms/components/lr-icon-button.md +11 -6
  68. package/llms/components/lr-knowledge-graph-explorer.md +5 -0
  69. package/llms/components/lr-lite-chart.md +12 -0
  70. package/llms/components/lr-map.md +5 -4
  71. package/llms/components/lr-menu-item.md +10 -1
  72. package/llms/components/lr-menu.md +10 -1
  73. package/llms/components/lr-popover.md +7 -0
  74. package/llms/components/lr-tooltip.md +8 -0
  75. package/llms/peers.md +1 -1
  76. package/llms/shared.md +15 -8
  77. package/llms-full.txt +99 -33
  78. package/package.json +4 -3
  79. package/vscode-css-data.json +7 -0
  80. package/vscode-html-data.json +40 -19
  81. package/web-types.json +41 -20
@@ -6,7 +6,7 @@
6
6
  - **Class** `LyraIconButton`, also available unregistered from `@aceshooting/lyra-ui/components/forms/icon-button/icon-button.class.js`
7
7
  - **Family** `components/forms/` — see `llms/index.md` for its siblings
8
8
  - **Optional peers** none
9
- - **Themeable via** 2 parts, 7 custom properties — see this component's own `@csspart`/`@cssprop` list below
9
+ - **Themeable via** 2 parts, 8 custom properties — see this component's own `@csspart`/`@cssprop` list below
10
10
  - **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`
11
11
 
12
12
  ---
@@ -30,10 +30,13 @@ app-wide with `registerLyraLocale()` (see `llms/shared.md`); don't rely on the f
30
30
  button whose purpose isn't generic.
31
31
 
32
32
  Host `aria-haspopup` and `aria-expanded` values are forwarded reactively to the shadow-internal
33
- native button. Host `aria-controls` targets in the host's own root are resolved through the
34
- reflected element-reference API, so using `<lr-icon-button slot="trigger">` inside `<lr-menu>`
35
- exposes the menu relationship and expanded state on the element that actually receives focus;
36
- browsers without that API retain the forwarded string attribute as a best-effort fallback.
33
+ native button. Host `aria-describedby` targets in the host's own root are resolved through
34
+ `ariaDescribedByElements`. Host `aria-controls` targets use the corresponding
35
+ `ariaControlsElements` API, so using `<lr-icon-button slot="trigger">` inside `<lr-menu>` exposes
36
+ the menu relationship and expanded state on the element that actually receives focus. Supporting
37
+ browsers intentionally clear each serialized internal IDREF attribute after its explicit element
38
+ list is assigned. Browsers without those APIs retain the forwarded string attributes as
39
+ best-effort fallbacks.
37
40
 
38
41
  **Methods:** `focus(options?)`, `blur()` — forward to the native button. `click()` also forwards to
39
42
  the native button, activating it — including this component's own `type="submit"`/`type="reset"`
@@ -76,7 +79,9 @@ counterpart to it. `--lr-icon-button-background` (default `transparent`) tints t
76
79
  its hover-state background, `--lr-icon-button-color` (default `inherit`) its icon/text color,
77
80
  `--lr-icon-button-color-hover` (default `var(--lr-icon-button-color, inherit)`) its hover-state
78
81
  foreground, and `--lr-icon-button-border` (default `0`) the complete native-button border
79
- shorthand. These are the same per-component indirection `lr-button`'s
82
+ shorthand. `--lr-icon-button-border-hover` (default
83
+ `var(--lr-icon-button-border, 0)`) replaces that complete shorthand on hover. These are the same
84
+ per-component indirection `lr-button`'s
80
85
  `--lr-button-fill`/`--lr-button-on-fill` provide, letting a single button be bordered and tinted
81
86
  without a `::part(button)` rule. Left unset, each falls back to the original value, so rendering is
82
87
  unchanged.
@@ -53,6 +53,11 @@ same self-toggle-then-emit contract `lr-graph-legend` uses, so every feature wor
53
53
  `graphExplorerLabel`
54
54
 
55
55
  **Events:**
56
+ - `lr-selection-change` (`detail: { selectedNodeId: string | null }`) — emitted after the explorer
57
+ changes its own selection through search, graph, keyboard/neighborhood/path activation, or
58
+ closing/invalidating the details selection. Clearing reports `null`. Direct host assignments to
59
+ `selectedNodeId` remain silent, and one interaction emits at most once even when a composed
60
+ child also reports its primitive click event.
56
61
  - `lr-path-request` (`detail: { sourceId: string; targetId: string }`) — the "Find path" action was
57
62
  activated with exactly two nodes pinned. This component has no traversal algorithm; the host
58
63
  computes/fetches the path and assigns it back through `path`.
@@ -35,6 +35,14 @@ passthrough). Not a subclass of `LyraChart`.
35
35
  - `tickFormat?: (value: number) => string` (attribute: false) — formats a y-axis tick value for
36
36
  display (e.g. `(v) => \`$${v.toFixed(2)}\`` for currency, or a duration formatter for `"42s"`).
37
37
  Falls back to the built-in "nice numbers" formatter when unset.
38
+ - `tableCellFormatter?: LyraLiteChartTableCellFormatter` (attribute: false) — formats each finite
39
+ numeric cell in the built-in multi-series accessible table. The callback receives `(value,
40
+ context)`, where `context` is `{ kind: 'value' | 'total'; datasetIndex: number | null; index:
41
+ number; label: string; seriesLabel: string | null }`; total cells have `datasetIndex` and
42
+ `seriesLabel` set to `null`. Unset cells retain locale-aware `Intl.NumberFormat` output.
43
+ - `tableTotals: boolean = false` (attribute `table-totals`) — adds a localized total column to the
44
+ multi-series accessible table when `type="bar"` and `stacked` are both active. Ignored for
45
+ grouped bars, line charts, and the single-series `data-list`.
38
46
  - `layout: 'fit' | 'scroll' = 'fit'` (reflected) — `'fit'` (default) is the original squeeze-the-
39
47
  whole-plot-to-host-width behavior, unchanged. `'scroll'` gives bars a fixed `barWidth` instead: plot
40
48
  content width becomes `categoryCount * barWidth` (can exceed the host's measured width), and
@@ -121,6 +129,10 @@ plotted point, matching the roving-tabindex mark order). More than one dataset i
121
129
  `<th scope="col">` per series (plus a leading `chartCategory` corner header), and one
122
130
  `<th scope="row">` per category label with its per-series values in the body — so a screen-reader
123
131
  user hears the values grouped by series rather than one flattened N×M sequence.
132
+ Finite table cells use `tableCellFormatter` when supplied and otherwise use the component's
133
+ effective locale. A stacked multi-series bar chart with `tableTotals` adds a localized total
134
+ column; null/non-finite inputs are skipped, while an all-missing category leaves the total cell
135
+ blank instead of reporting a misleading zero.
124
136
 
125
137
  **Themeable custom properties:** `--lr-chart-height` (same host-level property as `lr-chart`);
126
138
  `--lr-chart-grid-color`, `--lr-chart-tick-color`, `--lr-chart-legend-color` — same token
@@ -74,10 +74,11 @@ not installed)
74
74
  **Themeable custom properties:** shared tokens only — `--lr-space-xs/-s`, `--lr-color-surface`,
75
75
  `--lr-color-border`, `--lr-shadow`, `--lr-radius`.
76
76
 
77
- **Optional peer deps:** `maplibre-gl` (lazy-loaded). MapLibre v6 is ESM-only and requires WebGL2.
78
- The consumer must **separately** import `maplibre-gl/dist/maplibre-gl.css` and configure the v6
79
- module-worker URL for its bundler once the component cannot choose a bundler-specific worker URL.
80
- For Vite:
77
+ **Optional peer deps:** `maplibre-gl` `>=5 <7` (lazy-loaded). The consumer must **separately**
78
+ import `maplibre-gl/dist/maplibre-gl.css`. MapLibre v5's standard build includes its worker.
79
+ MapLibre v6 is ESM-only, requires WebGL2, and additionally needs its module-worker URL configured
80
+ for the bundler once — the component cannot choose a bundler-specific worker URL. For Vite with
81
+ v6:
81
82
 
82
83
  ```html
83
84
  <lr-map center="[2.35, 48.85]" zoom="10"></lr-map>
@@ -69,7 +69,10 @@ since those attributes belong on the actual interactive trigger, which lives out
69
69
  component's shadow root. `aria-controls` targets the `lr-menu` host, which receives a stable
70
70
  generated id only when the consumer did not provide one, rather than the shadow-private list id.
71
71
  `lr-button`/`lr-icon-button` forward the popup/expanded values to their focused shadow-internal
72
- native control and resolve the controls element-reference across their shadow boundary), default
72
+ native control and resolve the controls element-reference across their shadow boundary. In a
73
+ supporting browser, the reflected `ariaControlsElements` list is the source of truth and its setter
74
+ intentionally clears the internal control's serialized `aria-controls` value; browsers without the
75
+ API retain the string as a best-effort fallback), default
73
76
  (`<lr-menu-item>` elements, plus optionally plain `<hr>` dividers — native `<hr>` already carries
74
77
  an implicit `separator` role),
75
78
  `header` and `footer` (composed, deliberately non-menu-item content — a filter/search field, a
@@ -199,6 +202,12 @@ and the open popup also closes it, but deliberately does *not* refocus the trigg
199
202
  click itself already moved focus somewhere the user chose.
200
203
 
201
204
  **Known gotchas:**
205
+ - A supporting browser reports `trigger.shadowRoot`'s focused control
206
+ `getAttribute('aria-controls') === ''` after the element-reference relationship is assigned.
207
+ Inspect `ariaControlsElements` instead. This is the platform's reflected-element-reference
208
+ contract, not a missing menu id; setting the string again would discard the cross-shadow
209
+ relationship. Browsers without that API keep the string fallback, and `aria-controls` itself is
210
+ optional for the menu-button pattern.
202
211
  - `<lr-menu-item>`'s click handler lives on an inner shadow-DOM element (`[part="base"]`), not the
203
212
  host — calling `.click()` directly on the `<lr-menu-item>` host element in a test does **not**
204
213
  trigger selection; either click (or dispatch on) the element returned by
@@ -69,7 +69,10 @@ since those attributes belong on the actual interactive trigger, which lives out
69
69
  component's shadow root. `aria-controls` targets the `lr-menu` host, which receives a stable
70
70
  generated id only when the consumer did not provide one, rather than the shadow-private list id.
71
71
  `lr-button`/`lr-icon-button` forward the popup/expanded values to their focused shadow-internal
72
- native control and resolve the controls element-reference across their shadow boundary), default
72
+ native control and resolve the controls element-reference across their shadow boundary. In a
73
+ supporting browser, the reflected `ariaControlsElements` list is the source of truth and its setter
74
+ intentionally clears the internal control's serialized `aria-controls` value; browsers without the
75
+ API retain the string as a best-effort fallback), default
73
76
  (`<lr-menu-item>` elements, plus optionally plain `<hr>` dividers — native `<hr>` already carries
74
77
  an implicit `separator` role),
75
78
  `header` and `footer` (composed, deliberately non-menu-item content — a filter/search field, a
@@ -199,6 +202,12 @@ and the open popup also closes it, but deliberately does *not* refocus the trigg
199
202
  click itself already moved focus somewhere the user chose.
200
203
 
201
204
  **Known gotchas:**
205
+ - A supporting browser reports `trigger.shadowRoot`'s focused control
206
+ `getAttribute('aria-controls') === ''` after the element-reference relationship is assigned.
207
+ Inspect `ariaControlsElements` instead. This is the platform's reflected-element-reference
208
+ contract, not a missing menu id; setting the string again would discard the cross-shadow
209
+ relationship. Browsers without that API keep the string fallback, and `aria-controls` itself is
210
+ optional for the menu-button pattern.
202
211
  - `<lr-menu-item>`'s click handler lives on an inner shadow-DOM element (`[part="base"]`), not the
203
212
  host — calling `.click()` directly on the `<lr-menu-item>` host element in a test does **not**
204
213
  trigger selection; either click (or dispatch on) the element returned by
@@ -16,6 +16,13 @@
16
16
  A click-triggered, light-dismiss floating surface positioned with the shared Floating UI positioner.
17
17
 
18
18
  **Properties:** `open`, `placement`, `distance`, `accessibleLabel` (`aria-label`), and `popupRole` (`popup-role`).
19
+ The slotted trigger receives `aria-haspopup`, `aria-expanded`, and `aria-controls`.
20
+ `aria-controls` targets the public `lr-popover` host (which receives a stable generated `id` when
21
+ the consumer did not supply one), rather than the shadow-private popup, so the relationship
22
+ resolves from a native light-DOM trigger. `lr-button` and `lr-icon-button` additionally reflect
23
+ that host onto their focused shadow-internal controls through `ariaControlsElements`; supporting
24
+ browsers intentionally serialize the internal control's `aria-controls` content attribute as an
25
+ empty string after that assignment.
19
26
  **Methods:** `showAt(rect: { x, y, width?, height?, contextElement? }, options?: { returnFocusTo?:
20
27
  HTMLElement })` opens the popover anchored to an arbitrary rectangle instead of the slotted
21
28
  `trigger` — for a graph node, a canvas pixel, a chart datum, or any other non-DOM location
@@ -28,6 +28,14 @@ same way any tooltip closes, by setting `open = false`. **Slots:** `trigger`, de
28
28
  (default `--lr-size-20rem`), `--lr-tooltip-background` (default `--lr-color-neutral`), and
29
29
  `--lr-tooltip-color` (default `--lr-color-on-neutral`).
30
30
 
31
+ While open, trigger `aria-describedby` points to a hidden text proxy in the tooltip's light DOM,
32
+ not the shadow-private popup. Native triggers resolve that ID directly. `lr-button` and
33
+ `lr-icon-button` resolve the same proxy onto their focused shadow-internal controls through
34
+ `ariaDescribedByElements`; in supporting browsers that explicit element-reference assignment
35
+ intentionally leaves the internal control's serialized `aria-describedby` value empty. Existing
36
+ author-provided descriptions are merged while open and restored when the trigger is replaced or
37
+ the tooltip disconnects.
38
+
31
39
  **`showAt()` composed with `lr-graph`** — anchoring a popover to a clicked graph node. Note:
32
40
  `lr-graph.getNodePosition()` and the `lr-node-click` event's `{ x, y }` are in the graph's own
33
41
  *local drawing space* (pre pan/zoom), not viewport pixels, so they can't be passed to `showAt()`
package/llms/peers.md CHANGED
@@ -39,7 +39,7 @@ changes the component library version without installing a supported SheetJS rel
39
39
  | `katex` | `^0.18.1` | `lr-markdown`, `lr-markdown-core`, `lr-message-parts` |
40
40
  | `libphonenumber-js` | `>=1.11.0 <2` | `lr-phone-input` |
41
41
  | `mammoth` | `^1.12.0` | `lr-docx-viewer` |
42
- | `maplibre-gl` | `^6.0.0` | `lr-geojson-view`, `lr-map` |
42
+ | `maplibre-gl` | `>=5 <7` | `lr-geojson-view`, `lr-map` |
43
43
  | `marked` | `^18.0.7` | `lr-markdown`, `lr-markdown-core`, `lr-message-parts` |
44
44
  | `papaparse` | `^5.5.4` | `lr-csv-viewer`, `lr-dataset-viewer` |
45
45
  | `pdfjs-dist` | `^6.1.200` | `lr-pdf-viewer` |
package/llms/shared.md CHANGED
@@ -41,6 +41,7 @@ module-resolution failure, not a silent no-op — `exports` maps `./components/*
41
41
  a pre-resolved `src`) additionally needs
42
42
  `import '@aceshooting/lyra-ui/components/media/flag/flag-peer.js';` once.
43
43
  - **Other subpaths.** `@aceshooting/lyra-ui/theme.css` (ready-made light/dark theme),
44
+ `@aceshooting/lyra-ui/localization.js` (side-effect-free locale runtime),
44
45
  `@aceshooting/lyra-ui/ai` (provider-neutral data types), `@aceshooting/lyra-ui/testing`
45
46
  (happy-dom shims), `@aceshooting/lyra-ui/internal/*` (shared internals, all documented below).
46
47
 
@@ -311,7 +312,10 @@ in the per-component sections:
311
312
  overrides, merged over the registered catalog.
312
313
 
313
314
  ```ts
314
- import { registerLyraLocale, setLyraLocale } from '@aceshooting/lyra-ui';
315
+ import {
316
+ registerLyraLocale,
317
+ setLyraLocale,
318
+ } from '@aceshooting/lyra-ui/localization.js';
315
319
 
316
320
  registerLyraLocale('fr', { close: 'Fermer', retry: 'Réessayer' }); // app-wide, partial catalogs fine
317
321
  setLyraLocale('fr'); // …or just set <html lang="fr"> and let components inherit it
@@ -321,13 +325,16 @@ setLyraLocale('fr'); // …or just set <html lang="fr"> and let components inher
321
325
  <lr-toast .strings=${{ close: 'Fermer' }}></lr-toast>
322
326
  ```
323
327
 
324
- Exported from the package root: `registerLyraLocale`, `setLyraLocale`, `getLyraLocale`,
325
- `getRegisteredLyraLocales`, `subscribeLyraLocaleRegistry`, `resolveLyraLocale`,
326
- `resolveLyraDirection`, `resolveLyraString`, `LYRA_DEFAULT_STRINGS`, and the types
327
- `LyraLocaleStrings` / `LyraMessageKey`. **`LYRA_DEFAULT_STRINGS` is the authoritative key list**
328
- (1005 keys, matching the `LyraMessageKey` union) read it to find the key to override rather than
329
- guessing one. Lookup falls back exact locale base language English. Date, number, byte,
330
- relative-time and calendar output goes through `Intl`.
328
+ The side-effect-free `@aceshooting/lyra-ui/localization.js` entry exports
329
+ `registerLyraLocale`, `setLyraLocale`, `getLyraLocale`, `getRegisteredLyraLocales`,
330
+ `subscribeLyraLocaleRegistry`, `resolveLyraLocale`, `resolveLyraDirection`, `resolveLyraString`,
331
+ `LYRA_DEFAULT_STRINGS`, and the types `LyraLocaleStrings` / `LyraMessageKey`. The package root
332
+ continues to re-export the same surface for compatibility, but it also registers the non-peer-gated
333
+ component graph; use the dedicated entry when the application only needs locale setup.
334
+ **`LYRA_DEFAULT_STRINGS` is the authoritative key list** (1005 keys, matching the
335
+ `LyraMessageKey` union) — read it to find the key to override rather than guessing one. Lookup
336
+ falls back exact locale → base language → English. Date, number, byte, relative-time and calendar
337
+ output goes through `Intl`.
331
338
 
332
339
  `getRegisteredLyraLocales(): string[]` lists every locale with strings registered via
333
340
  `registerLyraLocale()`, plus `'en'` (always available through the built-in English fallback),
package/llms-full.txt CHANGED
@@ -91,6 +91,7 @@ module-resolution failure, not a silent no-op — `exports` maps `./components/*
91
91
  a pre-resolved `src`) additionally needs
92
92
  `import '@aceshooting/lyra-ui/components/media/flag/flag-peer.js';` once.
93
93
  - **Other subpaths.** `@aceshooting/lyra-ui/theme.css` (ready-made light/dark theme),
94
+ `@aceshooting/lyra-ui/localization.js` (side-effect-free locale runtime),
94
95
  `@aceshooting/lyra-ui/ai` (provider-neutral data types), `@aceshooting/lyra-ui/testing`
95
96
  (happy-dom shims), `@aceshooting/lyra-ui/internal/*` (shared internals, all documented below).
96
97
 
@@ -361,7 +362,10 @@ in the per-component sections:
361
362
  overrides, merged over the registered catalog.
362
363
 
363
364
  ```ts
364
- import { registerLyraLocale, setLyraLocale } from '@aceshooting/lyra-ui';
365
+ import {
366
+ registerLyraLocale,
367
+ setLyraLocale,
368
+ } from '@aceshooting/lyra-ui/localization.js';
365
369
 
366
370
  registerLyraLocale('fr', { close: 'Fermer', retry: 'Réessayer' }); // app-wide, partial catalogs fine
367
371
  setLyraLocale('fr'); // …or just set <html lang="fr"> and let components inherit it
@@ -371,13 +375,16 @@ setLyraLocale('fr'); // …or just set <html lang="fr"> and let components inher
371
375
  <lr-toast .strings=${{ close: 'Fermer' }}></lr-toast>
372
376
  ```
373
377
 
374
- Exported from the package root: `registerLyraLocale`, `setLyraLocale`, `getLyraLocale`,
375
- `getRegisteredLyraLocales`, `subscribeLyraLocaleRegistry`, `resolveLyraLocale`,
376
- `resolveLyraDirection`, `resolveLyraString`, `LYRA_DEFAULT_STRINGS`, and the types
377
- `LyraLocaleStrings` / `LyraMessageKey`. **`LYRA_DEFAULT_STRINGS` is the authoritative key list**
378
- (1005 keys, matching the `LyraMessageKey` union) read it to find the key to override rather than
379
- guessing one. Lookup falls back exact locale base language English. Date, number, byte,
380
- relative-time and calendar output goes through `Intl`.
378
+ The side-effect-free `@aceshooting/lyra-ui/localization.js` entry exports
379
+ `registerLyraLocale`, `setLyraLocale`, `getLyraLocale`, `getRegisteredLyraLocales`,
380
+ `subscribeLyraLocaleRegistry`, `resolveLyraLocale`, `resolveLyraDirection`, `resolveLyraString`,
381
+ `LYRA_DEFAULT_STRINGS`, and the types `LyraLocaleStrings` / `LyraMessageKey`. The package root
382
+ continues to re-export the same surface for compatibility, but it also registers the non-peer-gated
383
+ component graph; use the dedicated entry when the application only needs locale setup.
384
+ **`LYRA_DEFAULT_STRINGS` is the authoritative key list** (1005 keys, matching the
385
+ `LyraMessageKey` union) — read it to find the key to override rather than guessing one. Lookup
386
+ falls back exact locale → base language → English. Date, number, byte, relative-time and calendar
387
+ output goes through `Intl`.
381
388
 
382
389
  `getRegisteredLyraLocales(): string[]` lists every locale with strings registered via
383
390
  `registerLyraLocale()`, plus `'en'` (always available through the built-in English fallback),
@@ -1545,13 +1552,16 @@ box no matter what tier or override is in play.
1545
1552
 
1546
1553
  **Known gotchas:**
1547
1554
  - `accessibleLabel`/a host `aria-label` is forwarded reactively to the internal button or anchor as
1548
- a literal string (for an icon-only button); an external `aria-labelledby`/`aria-describedby`
1549
- idref is not copied across the shadow boundary.
1555
+ a literal string (for an icon-only button). Host `aria-describedby` targets in the host's root
1556
+ are resolved onto the focused internal control through `ariaDescribedByElements`; external
1557
+ `aria-labelledby` is not copied across the shadow boundary.
1550
1558
  - Host `aria-haspopup` and `aria-expanded` values are forwarded to the internal semantic control.
1551
1559
  For host `aria-controls`, targets in the host's own root are resolved through the reflected
1552
1560
  element-reference API so a popup relationship survives the component's shadow boundary; browsers
1553
- without that API retain the forwarded string attribute as a best-effort fallback. This is what
1554
- lets either button serve as an `lr-menu` trigger.
1561
+ with that API expose the relationship through `ariaControlsElements` and intentionally serialize
1562
+ the `aria-controls` content attribute as an empty string. Browsers without the API retain the
1563
+ forwarded string attribute as a best-effort fallback. A reflected element list and a non-empty
1564
+ serialized string cannot coexist; this is what lets either button serve as an `lr-menu` trigger.
1555
1565
  - Is form-associated (`static formAssociated = true` + `attachInternals()`), so it participates in
1556
1566
  an ancestor `<form>.elements` the same way `wa-button` does — a sibling text field's own
1557
1567
  Enter-to-submit lookup (which scans `form.elements` for a `type === 'submit'` control) finds it.
@@ -1577,10 +1587,13 @@ app-wide with `registerLyraLocale()` (see `llms/shared.md`); don't rely on the f
1577
1587
  button whose purpose isn't generic.
1578
1588
 
1579
1589
  Host `aria-haspopup` and `aria-expanded` values are forwarded reactively to the shadow-internal
1580
- native button. Host `aria-controls` targets in the host's own root are resolved through the
1581
- reflected element-reference API, so using `<lr-icon-button slot="trigger">` inside `<lr-menu>`
1582
- exposes the menu relationship and expanded state on the element that actually receives focus;
1583
- browsers without that API retain the forwarded string attribute as a best-effort fallback.
1590
+ native button. Host `aria-describedby` targets in the host's own root are resolved through
1591
+ `ariaDescribedByElements`. Host `aria-controls` targets use the corresponding
1592
+ `ariaControlsElements` API, so using `<lr-icon-button slot="trigger">` inside `<lr-menu>` exposes
1593
+ the menu relationship and expanded state on the element that actually receives focus. Supporting
1594
+ browsers intentionally clear each serialized internal IDREF attribute after its explicit element
1595
+ list is assigned. Browsers without those APIs retain the forwarded string attributes as
1596
+ best-effort fallbacks.
1584
1597
 
1585
1598
  **Methods:** `focus(options?)`, `blur()` — forward to the native button. `click()` also forwards to
1586
1599
  the native button, activating it — including this component's own `type="submit"`/`type="reset"`
@@ -1623,7 +1636,9 @@ counterpart to it. `--lr-icon-button-background` (default `transparent`) tints t
1623
1636
  its hover-state background, `--lr-icon-button-color` (default `inherit`) its icon/text color,
1624
1637
  `--lr-icon-button-color-hover` (default `var(--lr-icon-button-color, inherit)`) its hover-state
1625
1638
  foreground, and `--lr-icon-button-border` (default `0`) the complete native-button border
1626
- shorthand. These are the same per-component indirection `lr-button`'s
1639
+ shorthand. `--lr-icon-button-border-hover` (default
1640
+ `var(--lr-icon-button-border, 0)`) replaces that complete shorthand on hover. These are the same
1641
+ per-component indirection `lr-button`'s
1627
1642
  `--lr-button-fill`/`--lr-button-on-fill` provide, letting a single button be bordered and tinted
1628
1643
  without a `::part(button)` rule. Left unset, each falls back to the original value, so rendering is
1629
1644
  unchanged.
@@ -2313,9 +2328,12 @@ silent. Internal `focus`/`blur` are re-dispatched as bubbling, composed host eve
2313
2328
  same as clicking a native checkbox's associated `<label>`. If left empty, set `aria-label` on the
2314
2329
  host so the control still has an accessible name.
2315
2330
 
2316
- Host `aria-describedby` is forwarded to the internal `role="checkbox"`, so an externally-owned
2317
- description can be associated with one checkbox inside a group. The attribute is omitted from the
2318
- internal role when unset and tracks host attribute changes.
2331
+ Host `aria-describedby` targets in the host's own root are resolved onto the internal
2332
+ `role="checkbox"` through `ariaDescribedByElements`, so an externally-owned description remains
2333
+ valid across the shadow boundary. In supporting browsers the explicit element list intentionally
2334
+ leaves the internal role's serialized attribute empty; browsers without the reflected-reference
2335
+ API keep the string fallback. The relationship tracks host attribute changes and clears when
2336
+ unset.
2319
2337
 
2320
2338
  **CSS parts:** `base` (the whole interactive control, `role="checkbox"`), `box` (the small square
2321
2339
  showing the checkmark/indeterminate dash), `checkmark` (the checkmark or indeterminate-dash glyph),
@@ -6389,7 +6407,10 @@ since those attributes belong on the actual interactive trigger, which lives out
6389
6407
  component's shadow root. `aria-controls` targets the `lr-menu` host, which receives a stable
6390
6408
  generated id only when the consumer did not provide one, rather than the shadow-private list id.
6391
6409
  `lr-button`/`lr-icon-button` forward the popup/expanded values to their focused shadow-internal
6392
- native control and resolve the controls element-reference across their shadow boundary), default
6410
+ native control and resolve the controls element-reference across their shadow boundary. In a
6411
+ supporting browser, the reflected `ariaControlsElements` list is the source of truth and its setter
6412
+ intentionally clears the internal control's serialized `aria-controls` value; browsers without the
6413
+ API retain the string as a best-effort fallback), default
6393
6414
  (`<lr-menu-item>` elements, plus optionally plain `<hr>` dividers — native `<hr>` already carries
6394
6415
  an implicit `separator` role),
6395
6416
  `header` and `footer` (composed, deliberately non-menu-item content — a filter/search field, a
@@ -6519,6 +6540,12 @@ and the open popup also closes it, but deliberately does *not* refocus the trigg
6519
6540
  click itself already moved focus somewhere the user chose.
6520
6541
 
6521
6542
  **Known gotchas:**
6543
+ - A supporting browser reports `trigger.shadowRoot`'s focused control
6544
+ `getAttribute('aria-controls') === ''` after the element-reference relationship is assigned.
6545
+ Inspect `ariaControlsElements` instead. This is the platform's reflected-element-reference
6546
+ contract, not a missing menu id; setting the string again would discard the cross-shadow
6547
+ relationship. Browsers without that API keep the string fallback, and `aria-controls` itself is
6548
+ optional for the menu-button pattern.
6522
6549
  - `<lr-menu-item>`'s click handler lives on an inner shadow-DOM element (`[part="base"]`), not the
6523
6550
  host — calling `.click()` directly on the `<lr-menu-item>` host element in a test does **not**
6524
6551
  trigger selection; either click (or dispatch on) the element returned by
@@ -7572,6 +7599,13 @@ computed-label value so the two can never disagree.
7572
7599
  A click-triggered, light-dismiss floating surface positioned with the shared Floating UI positioner.
7573
7600
 
7574
7601
  **Properties:** `open`, `placement`, `distance`, `accessibleLabel` (`aria-label`), and `popupRole` (`popup-role`).
7602
+ The slotted trigger receives `aria-haspopup`, `aria-expanded`, and `aria-controls`.
7603
+ `aria-controls` targets the public `lr-popover` host (which receives a stable generated `id` when
7604
+ the consumer did not supply one), rather than the shadow-private popup, so the relationship
7605
+ resolves from a native light-DOM trigger. `lr-button` and `lr-icon-button` additionally reflect
7606
+ that host onto their focused shadow-internal controls through `ariaControlsElements`; supporting
7607
+ browsers intentionally serialize the internal control's `aria-controls` content attribute as an
7608
+ empty string after that assignment.
7575
7609
  **Methods:** `showAt(rect: { x, y, width?, height?, contextElement? }, options?: { returnFocusTo?:
7576
7610
  HTMLElement })` opens the popover anchored to an arbitrary rectangle instead of the slotted
7577
7611
  `trigger` — for a graph node, a canvas pixel, a chart datum, or any other non-DOM location
@@ -7609,6 +7643,14 @@ same way any tooltip closes, by setting `open = false`. **Slots:** `trigger`, de
7609
7643
  (default `--lr-size-20rem`), `--lr-tooltip-background` (default `--lr-color-neutral`), and
7610
7644
  `--lr-tooltip-color` (default `--lr-color-on-neutral`).
7611
7645
 
7646
+ While open, trigger `aria-describedby` points to a hidden text proxy in the tooltip's light DOM,
7647
+ not the shadow-private popup. Native triggers resolve that ID directly. `lr-button` and
7648
+ `lr-icon-button` resolve the same proxy onto their focused shadow-internal controls through
7649
+ `ariaDescribedByElements`; in supporting browsers that explicit element-reference assignment
7650
+ intentionally leaves the internal control's serialized `aria-describedby` value empty. Existing
7651
+ author-provided descriptions are merged while open and restored when the trigger is replaced or
7652
+ the tooltip disconnects.
7653
+
7612
7654
  **`showAt()` composed with `lr-graph`** — anchoring a popover to a clicked graph node. Note:
7613
7655
  `lr-graph.getNodePosition()` and the `lr-node-click` event's `{ x, y }` are in the graph's own
7614
7656
  *local drawing space* (pre pan/zoom), not viewport pixels, so they can't be passed to `showAt()`
@@ -7636,6 +7678,8 @@ menu-item behavior is needed.
7636
7678
  **Properties:** `open`, `placement`, `distance`, `accessibleLabel` (`aria-label`), and `popupRole` (`popup-role`).
7637
7679
  `popupRole` is seeded to `'menu'` in the constructor — that is the only difference from `lr-popover`,
7638
7680
  whose whole surface (including `showAt()`) is inherited unchanged.
7681
+ Its trigger therefore uses the same public-host `aria-controls` target and Lyra-button
7682
+ element-reference forwarding described above.
7639
7683
  **Events:** `lr-show`, `lr-hide` — inherited from `lr-popover`; neither fires for the initial render.
7640
7684
  **Slots:** `trigger`, default menu content. **CSS parts:** `trigger`, `popup`, `content`.
7641
7685
  **Themeable custom properties:** `--lr-overlay-max-inline-size` (default `--lr-size-20rem` —
@@ -9121,10 +9165,11 @@ not installed)
9121
9165
  **Themeable custom properties:** shared tokens only — `--lr-space-xs/-s`, `--lr-color-surface`,
9122
9166
  `--lr-color-border`, `--lr-shadow`, `--lr-radius`.
9123
9167
 
9124
- **Optional peer deps:** `maplibre-gl` (lazy-loaded). MapLibre v6 is ESM-only and requires WebGL2.
9125
- The consumer must **separately** import `maplibre-gl/dist/maplibre-gl.css` and configure the v6
9126
- module-worker URL for its bundler once the component cannot choose a bundler-specific worker URL.
9127
- For Vite:
9168
+ **Optional peer deps:** `maplibre-gl` `>=5 <7` (lazy-loaded). The consumer must **separately**
9169
+ import `maplibre-gl/dist/maplibre-gl.css`. MapLibre v5's standard build includes its worker.
9170
+ MapLibre v6 is ESM-only, requires WebGL2, and additionally needs its module-worker URL configured
9171
+ for the bundler once — the component cannot choose a bundler-specific worker URL. For Vite with
9172
+ v6:
9128
9173
 
9129
9174
  ```html
9130
9175
  <lr-map center="[2.35, 48.85]" zoom="10"></lr-map>
@@ -10192,10 +10237,10 @@ Chart.js wrapper every other `lr-*-chart` tag subclasses; supports both a simpli
10192
10237
  legend at `top`, `right`, `bottom`, or `left`; `auto` chooses right above 480px and bottom below
10193
10238
  that allocation width
10194
10239
  - `valueFormatter?: LyraChartValueFormatter` (attribute: false) — formats numeric (value-axis)
10195
- tick, tooltip, and legend values; the callback receives the value and `'tick'`, `'tooltip'`, or
10196
- `'legend'` context. Never runs against the categorical x-axis's own labels (line/bar's `labels`
10197
- strings) — Chart.js's category scale passes the tick index to `ticks.callback`, not the label
10198
- text
10240
+ tick, tooltip, legend, and generated accessible-table values; the callback receives the value
10241
+ and `'tick'`, `'tooltip'`, `'legend'`, or `'table'` context. Never runs against the categorical
10242
+ x-axis's own labels (line/bar's `labels` strings) — Chart.js's category scale passes the tick
10243
+ index to `ticks.callback`, not the label text
10199
10244
  - `area: boolean = false`
10200
10245
  - `zoom: boolean = false` — wheel/drag/pinch zoom on the `x` axis only (pan disabled, and the zoom
10201
10246
  range is limited to the original data extent); shows the `reset-zoom-button` while zoomed
@@ -10218,7 +10263,10 @@ Chart.js wrapper every other `lr-*-chart` tag subclasses; supports both a simpli
10218
10263
  purely visual, canvas-only addition and add no new a11y surface.
10219
10264
  - `stackTotals: boolean = false` (attribute `stack-totals`) — with `stacked` (bar/line only), draws
10220
10265
  the per-category stack total above each stack, via the same `chartjs-plugin-datalabels` peer.
10221
- Null/undefined points are skipped; a category whose every value is null shows no total (not `0`)
10266
+ Null/undefined points are skipped; a category whose every value is null shows no total (not
10267
+ `0`). The generated accessible table receives the same formatted total column; a dual-axis stack
10268
+ receives separately labelled primary- and secondary-axis total columns. The table totals do not
10269
+ depend on the optional visual-label peer being installed
10222
10270
  - `config?: Partial<ChartConfiguration>` (attribute: false) — deep-merged over the generated
10223
10271
  config; any nested key wins without clobbering sibling generated keys. This is the raw Chart.js
10224
10272
  escape hatch, so a caller-supplied `config.type` is passed through rather than normalized.
@@ -10372,6 +10420,14 @@ passthrough). Not a subclass of `LyraChart`.
10372
10420
  - `tickFormat?: (value: number) => string` (attribute: false) — formats a y-axis tick value for
10373
10421
  display (e.g. `(v) => \`$${v.toFixed(2)}\`` for currency, or a duration formatter for `"42s"`).
10374
10422
  Falls back to the built-in "nice numbers" formatter when unset.
10423
+ - `tableCellFormatter?: LyraLiteChartTableCellFormatter` (attribute: false) — formats each finite
10424
+ numeric cell in the built-in multi-series accessible table. The callback receives `(value,
10425
+ context)`, where `context` is `{ kind: 'value' | 'total'; datasetIndex: number | null; index:
10426
+ number; label: string; seriesLabel: string | null }`; total cells have `datasetIndex` and
10427
+ `seriesLabel` set to `null`. Unset cells retain locale-aware `Intl.NumberFormat` output.
10428
+ - `tableTotals: boolean = false` (attribute `table-totals`) — adds a localized total column to the
10429
+ multi-series accessible table when `type="bar"` and `stacked` are both active. Ignored for
10430
+ grouped bars, line charts, and the single-series `data-list`.
10375
10431
  - `layout: 'fit' | 'scroll' = 'fit'` (reflected) — `'fit'` (default) is the original squeeze-the-
10376
10432
  whole-plot-to-host-width behavior, unchanged. `'scroll'` gives bars a fixed `barWidth` instead: plot
10377
10433
  content width becomes `categoryCount * barWidth` (can exceed the host's measured width), and
@@ -10458,6 +10514,10 @@ plotted point, matching the roving-tabindex mark order). More than one dataset i
10458
10514
  `<th scope="col">` per series (plus a leading `chartCategory` corner header), and one
10459
10515
  `<th scope="row">` per category label with its per-series values in the body — so a screen-reader
10460
10516
  user hears the values grouped by series rather than one flattened N×M sequence.
10517
+ Finite table cells use `tableCellFormatter` when supplied and otherwise use the component's
10518
+ effective locale. A stacked multi-series bar chart with `tableTotals` adds a localized total
10519
+ column; null/non-finite inputs are skipped, while an all-missing category leaves the total cell
10520
+ blank instead of reporting a misleading zero.
10461
10521
 
10462
10522
  **Themeable custom properties:** `--lr-chart-height` (same host-level property as `lr-chart`);
10463
10523
  `--lr-chart-grid-color`, `--lr-chart-tick-color`, `--lr-chart-legend-color` — same token
@@ -11876,9 +11936,10 @@ alongside the `lr-json-viewer` fallback), `error` (the error region, `role="aler
11876
11936
  Registered by importing `geojson-view/geojson-view.js` directly — not part of the root barrel, the
11877
11937
  same as `lr-map`/`lr-graph`, since it depends on the same optional `maplibre-gl` peer. Remote
11878
11938
  resources are capped at 25 MB; exceeding it surfaces the localized `documentPreviewResourceTooLarge`
11879
- message instead of the map. MapLibre v6 is ESM-only and requires WebGL2; consumers must import its
11880
- CSS and configure its module-worker URL for their bundler before this viewer constructs the nested
11881
- map. See `llms/components/lr-map.md` for the Vite example and the other bundler variants.
11939
+ message instead of the map. Lyra supports MapLibre v5 and v6; consumers must import its CSS.
11940
+ MapLibre v5's standard build includes its worker, while v6 is ESM-only, requires WebGL2, and needs
11941
+ its module-worker URL configured for the bundler before this viewer constructs the nested map. See
11942
+ `llms/components/lr-map.md` for the Vite v6 example and the other bundler variants.
11882
11943
 
11883
11944
  # Conversation & chat UI (`components/conversation/`)
11884
11945
 
@@ -17921,6 +17982,11 @@ same self-toggle-then-emit contract `lr-graph-legend` uses, so every feature wor
17921
17982
  `graphExplorerLabel`
17922
17983
 
17923
17984
  **Events:**
17985
+ - `lr-selection-change` (`detail: { selectedNodeId: string | null }`) — emitted after the explorer
17986
+ changes its own selection through search, graph, keyboard/neighborhood/path activation, or
17987
+ closing/invalidating the details selection. Clearing reports `null`. Direct host assignments to
17988
+ `selectedNodeId` remain silent, and one interaction emits at most once even when a composed
17989
+ child also reports its primitive click event.
17924
17990
  - `lr-path-request` (`detail: { sourceId: string; targetId: string }`) — the "Find path" action was
17925
17991
  activated with exactly two nodes pinned. This component has no traversal algorithm; the host
17926
17992
  computes/fetches the path and assigns it back through `path`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aceshooting/lyra-ui",
3
- "version": "7.0.0",
3
+ "version": "7.2.0",
4
4
  "description": "Free, independent Lit web components: an open-source alternative to Shoelace and Web Awesome with accessible forms, dashboards, charts, and agent UI.",
5
5
  "keywords": [
6
6
  "web-components",
@@ -67,6 +67,7 @@
67
67
  "./components/agent-tools": "./dist/components/agent-tools/index.js",
68
68
  "./components/conversation": "./dist/components/conversation/index.js",
69
69
  "./components/retrieval": "./dist/components/retrieval/index.js",
70
+ "./localization.js": "./dist/localization.js",
70
71
  "./internal/*": "./dist/internal/*",
71
72
  "./ai": "./dist/ai/index.js",
72
73
  "./ai/*": "./dist/ai/*",
@@ -650,7 +651,7 @@
650
651
  "katex": "^0.18.1",
651
652
  "libphonenumber-js": ">=1.11.0 <2",
652
653
  "mammoth": "^1.12.0",
653
- "maplibre-gl": "^6.0.0",
654
+ "maplibre-gl": ">=5 <7",
654
655
  "marked": "^18.0.7",
655
656
  "papaparse": "^5.5.4",
656
657
  "pdfjs-dist": "^6.1.200",
@@ -804,7 +805,7 @@
804
805
  "check:cycles": "node scripts/check-import-cycles.mjs",
805
806
  "check:hit-area": "node scripts/check-hit-area.mjs",
806
807
  "check:numeric-guards": "node scripts/check-numeric-guards.mjs",
807
- "test:tooling": "node scripts/component-families.test.mjs && node scripts/migrate-wa.test.mjs && node scripts/visual-regression-compare.test.mjs",
808
+ "test:tooling": "node scripts/component-families.test.mjs && node scripts/maplibre-peer-range.test.mjs && node scripts/migrate-wa.test.mjs && node scripts/visual-regression-compare.test.mjs",
808
809
  "test:types": "tsc --noEmit -p tsconfig.type-tests.json",
809
810
  "style-policy": "node scripts/check-style-policy.mjs",
810
811
  "check:source-policy": "node scripts/check-source-policy.mjs",
@@ -2444,6 +2444,13 @@
2444
2444
  "value": "**`<lr-icon-button>`** (default: `0`) — Complete border shorthand of the native button."
2445
2445
  }
2446
2446
  },
2447
+ {
2448
+ "name": "--lr-icon-button-border-hover",
2449
+ "description": {
2450
+ "kind": "markdown",
2451
+ "value": "**`<lr-icon-button>`** (default: `var(--lr-icon-button-border, 0)`) — Complete border shorthand on hover."
2452
+ }
2453
+ },
2447
2454
  {
2448
2455
  "name": "--lr-icon-button-color",
2449
2456
  "description": {