@aceshooting/lyra-ui 7.0.0 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +12 -4
  3. package/custom-elements.json +146 -8
  4. package/dist/components/forms/button/button.class.d.ts +10 -4
  5. package/dist/components/forms/button/button.class.d.ts.map +1 -1
  6. package/dist/components/forms/button/button.class.js +21 -5
  7. package/dist/components/forms/button/button.class.js.map +1 -1
  8. package/dist/components/forms/checkbox/checkbox.class.d.ts +5 -3
  9. package/dist/components/forms/checkbox/checkbox.class.d.ts.map +1 -1
  10. package/dist/components/forms/checkbox/checkbox.class.js +13 -3
  11. package/dist/components/forms/checkbox/checkbox.class.js.map +1 -1
  12. package/dist/components/forms/icon-button/icon-button.class.d.ts +11 -4
  13. package/dist/components/forms/icon-button/icon-button.class.d.ts.map +1 -1
  14. package/dist/components/forms/icon-button/icon-button.class.js +21 -5
  15. package/dist/components/forms/icon-button/icon-button.class.js.map +1 -1
  16. package/dist/components/forms/icon-button/icon-button.styles.js +1 -1
  17. package/dist/components/layout/menu/menu.class.d.ts +4 -1
  18. package/dist/components/layout/menu/menu.class.d.ts.map +1 -1
  19. package/dist/components/layout/menu/menu.class.js +4 -1
  20. package/dist/components/layout/menu/menu.class.js.map +1 -1
  21. package/dist/components/media/map/map-loader.d.ts +2 -2
  22. package/dist/components/media/map/map-loader.js +2 -2
  23. package/dist/components/media/map/map.class.d.ts +2 -2
  24. package/dist/components/media/map/map.class.js +2 -2
  25. package/dist/components/overlays/overlay/popover.class.d.ts +7 -0
  26. package/dist/components/overlays/overlay/popover.class.d.ts.map +1 -1
  27. package/dist/components/overlays/overlay/popover.class.js +13 -2
  28. package/dist/components/overlays/overlay/popover.class.js.map +1 -1
  29. package/dist/components/overlays/overlay/tooltip.class.d.ts +9 -0
  30. package/dist/components/overlays/overlay/tooltip.class.d.ts.map +1 -1
  31. package/dist/components/overlays/overlay/tooltip.class.js +43 -3
  32. package/dist/components/overlays/overlay/tooltip.class.js.map +1 -1
  33. package/dist/internal/aria-controls.d.ts +11 -2
  34. package/dist/internal/aria-controls.d.ts.map +1 -1
  35. package/dist/internal/aria-controls.js +35 -11
  36. package/dist/internal/aria-controls.js.map +1 -1
  37. package/dist/localization.d.ts +9 -0
  38. package/dist/localization.d.ts.map +1 -0
  39. package/dist/localization.js +8 -0
  40. package/dist/localization.js.map +1 -0
  41. package/dist/lyra.d.ts +2 -2
  42. package/dist/lyra.d.ts.map +1 -1
  43. package/dist/lyra.js +1 -1
  44. package/dist/lyra.js.map +1 -1
  45. package/llms/components/lr-button.md +7 -4
  46. package/llms/components/lr-checkbox.md +6 -3
  47. package/llms/components/lr-dropdown-item.md +10 -1
  48. package/llms/components/lr-dropdown.md +2 -0
  49. package/llms/components/lr-geojson-view.md +4 -3
  50. package/llms/components/lr-icon-button.md +11 -6
  51. package/llms/components/lr-map.md +5 -4
  52. package/llms/components/lr-menu-item.md +10 -1
  53. package/llms/components/lr-menu.md +10 -1
  54. package/llms/components/lr-popover.md +7 -0
  55. package/llms/components/lr-tooltip.md +8 -0
  56. package/llms/peers.md +1 -1
  57. package/llms/shared.md +15 -8
  58. package/llms-full.txt +74 -28
  59. package/package.json +4 -3
  60. package/vscode-css-data.json +7 -0
  61. package/vscode-html-data.json +21 -7
  62. package/web-types.json +20 -8
@@ -38,9 +38,12 @@ silent. Internal `focus`/`blur` are re-dispatched as bubbling, composed host eve
38
38
  same as clicking a native checkbox's associated `<label>`. If left empty, set `aria-label` on the
39
39
  host so the control still has an accessible name.
40
40
 
41
- Host `aria-describedby` is forwarded to the internal `role="checkbox"`, so an externally-owned
42
- description can be associated with one checkbox inside a group. The attribute is omitted from the
43
- internal role when unset and tracks host attribute changes.
41
+ Host `aria-describedby` targets in the host's own root are resolved onto the internal
42
+ `role="checkbox"` through `ariaDescribedByElements`, so an externally-owned description remains
43
+ valid across the shadow boundary. In supporting browsers the explicit element list intentionally
44
+ leaves the internal role's serialized attribute empty; browsers without the reflected-reference
45
+ API keep the string fallback. The relationship tracks host attribute changes and clears when
46
+ unset.
44
47
 
45
48
  **CSS parts:** `base` (the whole interactive control, `role="checkbox"`), `box` (the small square
46
49
  showing the checkmark/indeterminate dash), `checkmark` (the checkmark or indeterminate-dash glyph),
@@ -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
@@ -19,6 +19,8 @@ menu-item behavior is needed.
19
19
  **Properties:** `open`, `placement`, `distance`, `accessibleLabel` (`aria-label`), and `popupRole` (`popup-role`).
20
20
  `popupRole` is seeded to `'menu'` in the constructor — that is the only difference from `lr-popover`,
21
21
  whose whole surface (including `showAt()`) is inherited unchanged.
22
+ Its trigger therefore uses the same public-host `aria-controls` target and Lyra-button
23
+ element-reference forwarding described above.
22
24
  **Events:** `lr-show`, `lr-hide` — inherited from `lr-popover`; neither fires for the initial render.
23
25
  **Slots:** `trigger`, default menu content. **CSS parts:** `trigger`, `popup`, `content`.
24
26
  **Themeable custom properties:** `--lr-overlay-max-inline-size` (default `--lr-size-20rem` —
@@ -44,6 +44,7 @@ alongside the `lr-json-viewer` fallback), `error` (the error region, `role="aler
44
44
  Registered by importing `geojson-view/geojson-view.js` directly — not part of the root barrel, the
45
45
  same as `lr-map`/`lr-graph`, since it depends on the same optional `maplibre-gl` peer. Remote
46
46
  resources are capped at 25 MB; exceeding it surfaces the localized `documentPreviewResourceTooLarge`
47
- message instead of the map. MapLibre v6 is ESM-only and requires WebGL2; consumers must import its
48
- CSS and configure its module-worker URL for their bundler before this viewer constructs the nested
49
- map. See `llms/components/lr-map.md` for the Vite example and the other bundler variants.
47
+ message instead of the map. Lyra supports MapLibre v5 and v6; consumers must import its CSS.
48
+ MapLibre v5's standard build includes its worker, while v6 is ESM-only, requires WebGL2, and needs
49
+ its module-worker URL configured for the bundler before this viewer constructs the nested map. See
50
+ `llms/components/lr-map.md` for the Vite v6 example and the other bundler variants.
@@ -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.
@@ -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>
@@ -11876,9 +11921,10 @@ alongside the `lr-json-viewer` fallback), `error` (the error region, `role="aler
11876
11921
  Registered by importing `geojson-view/geojson-view.js` directly — not part of the root barrel, the
11877
11922
  same as `lr-map`/`lr-graph`, since it depends on the same optional `maplibre-gl` peer. Remote
11878
11923
  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.
11924
+ message instead of the map. Lyra supports MapLibre v5 and v6; consumers must import its CSS.
11925
+ MapLibre v5's standard build includes its worker, while v6 is ESM-only, requires WebGL2, and needs
11926
+ its module-worker URL configured for the bundler before this viewer constructs the nested map. See
11927
+ `llms/components/lr-map.md` for the Vite v6 example and the other bundler variants.
11882
11928
 
11883
11929
  # Conversation & chat UI (`components/conversation/`)
11884
11930
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aceshooting/lyra-ui",
3
- "version": "7.0.0",
3
+ "version": "7.1.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": {