@lmvz-ds/components 0.37.0 → 0.39.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 (182) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +12 -20
  3. package/cjs/{aria-validation-controller-Do0uEK4t.js → aria-validation-controller-DKNXjnQr.js} +1 -1
  4. package/cjs/checkmark-CuKDPAcx.js +5 -0
  5. package/cjs/{directional-focus-controller-8Xd_7MHT.js → directional-focus-controller-DDnhfX5w.js} +15 -5
  6. package/cjs/{index-BgvZ4y-3.js → index-DWy4nuyZ.js} +41 -0
  7. package/cjs/index.cjs.js +3 -195
  8. package/cjs/lmvz-action-list.cjs.entry.js +4 -4
  9. package/cjs/lmvz-action.cjs.entry.js +1 -1
  10. package/cjs/lmvz-button-group.cjs.entry.js +5 -1
  11. package/cjs/lmvz-button_2.cjs.entry.js +3 -3
  12. package/cjs/lmvz-card.cjs.entry.js +2 -2
  13. package/cjs/lmvz-checkbox.cjs.entry.js +4 -5
  14. package/cjs/lmvz-chip.cjs.entry.js +5 -5
  15. package/cjs/lmvz-components.cjs.js +2 -2
  16. package/cjs/lmvz-datepicker.cjs.entry.js +1 -1
  17. package/cjs/lmvz-header_2.cjs.entry.js +5 -5
  18. package/cjs/lmvz-input.cjs.entry.js +4 -7
  19. package/cjs/lmvz-link.cjs.entry.js +1 -1
  20. package/cjs/lmvz-menuitem.cjs.entry.js +2 -2
  21. package/cjs/lmvz-message.cjs.entry.js +4 -2
  22. package/cjs/lmvz-modal.cjs.entry.js +2 -2
  23. package/cjs/lmvz-popover.cjs.entry.js +5 -14
  24. package/cjs/lmvz-radio.cjs.entry.js +2 -2
  25. package/cjs/lmvz-select.cjs.entry.js +110 -16
  26. package/cjs/lmvz-snackbar.cjs.entry.js +1 -1
  27. package/cjs/lmvz-spinner.cjs.entry.js +1 -1
  28. package/cjs/lmvz-tab.cjs.entry.js +1 -1
  29. package/cjs/lmvz-tabs.cjs.entry.js +9 -3
  30. package/cjs/lmvz-toggle.cjs.entry.js +2 -2
  31. package/cjs/loader.cjs.js +2 -2
  32. package/cjs/public-DTgpEs1_.js +206 -0
  33. package/collection/components/lmvz-action-list/lmvz-action-list.css +119 -116
  34. package/collection/components/lmvz-button/lmvz-button.css +109 -106
  35. package/collection/components/lmvz-card/lmvz-card.css +106 -103
  36. package/collection/components/lmvz-chip/lmvz-chip.css +1 -1
  37. package/collection/components/lmvz-chip/lmvz-chip.js +2 -2
  38. package/collection/components/lmvz-header/lmvz-header.js +2 -2
  39. package/collection/components/lmvz-input/lmvz-input.js +2 -5
  40. package/collection/components/lmvz-message/lmvz-message.css +1 -1
  41. package/collection/components/lmvz-popover/lmvz-popover.css +1 -1
  42. package/collection/components/lmvz-popover/lmvz-popover.js +2 -11
  43. package/collection/components/lmvz-select/lmvz-select.css +102 -53
  44. package/collection/components/lmvz-select/lmvz-select.js +151 -41
  45. package/collection/components/lmvz-snackbar/public.js +3 -0
  46. package/collection/components/lmvz-tabs/lmvz-tabs.js +7 -1
  47. package/collection/utils/aria/directional-focus-controller.js +16 -7
  48. package/collection/utils/icons/public.js +3 -0
  49. package/components/index.js +1 -1
  50. package/components/lmvz-action-list.js +1 -1
  51. package/components/lmvz-action.js +1 -1
  52. package/components/lmvz-button-group.js +1 -1
  53. package/components/lmvz-button.js +1 -1
  54. package/components/lmvz-card.js +1 -1
  55. package/components/lmvz-checkbox.js +1 -1
  56. package/components/lmvz-chip.js +1 -1
  57. package/components/lmvz-datepicker.js +1 -1
  58. package/components/lmvz-header.js +1 -1
  59. package/components/lmvz-icon.js +1 -1
  60. package/components/lmvz-input.js +1 -1
  61. package/components/lmvz-link.js +1 -1
  62. package/components/lmvz-menuitem.js +1 -1
  63. package/components/lmvz-message.js +1 -1
  64. package/components/lmvz-modal.js +1 -1
  65. package/components/lmvz-popover.js +1 -1
  66. package/components/lmvz-radio.js +1 -1
  67. package/components/lmvz-select.js +1 -1
  68. package/components/lmvz-snackbar.js +1 -1
  69. package/components/lmvz-spinner.js +1 -1
  70. package/components/lmvz-tab.js +1 -1
  71. package/components/lmvz-tabs.js +1 -1
  72. package/components/lmvz-toggle.js +1 -1
  73. package/components/{p-BLMXiDE0.js → p--mU29mrX.js} +1 -1
  74. package/components/p-BPa0FHVO.js +1 -0
  75. package/components/p-Bc5MM0zh.js +1 -0
  76. package/components/p-CyDgnpOB.js +1 -0
  77. package/components/p-DAikapVO.js +1 -0
  78. package/components/{p-C-pRMuMh.js → p-DIyiticz.js} +1 -1
  79. package/components/{p-BhALzK62.js → p-DJLdOjL4.js} +1 -1
  80. package/components/p-DSDBThqb.js +1 -0
  81. package/components/{p-diOze8wI.js → p-DXFoXTse.js} +1 -1
  82. package/components/p-DXhP6UeF.js +1 -0
  83. package/components/p-D_mff3n7.js +1 -0
  84. package/components/p-DbBJxuBm.js +1 -0
  85. package/components/p-DsTgD0_a.js +1 -0
  86. package/components/p-DyQ1GbaN.js +1 -0
  87. package/components/p-niXjuPxe.js +1 -0
  88. package/components/{p-BAj4FkUt.js → p-sACbyedu.js} +1 -1
  89. package/esm/{aria-validation-controller-Cp6pofER.js → aria-validation-controller-Dwv7n0rk.js} +1 -1
  90. package/esm/checkmark-DAikapVO.js +3 -0
  91. package/esm/{directional-focus-controller-QukTTWBE.js → directional-focus-controller-CJ0yNC00.js} +15 -5
  92. package/esm/{index-BOXP1Vx0.js → index-DVab4vzV.js} +41 -1
  93. package/esm/index.js +1 -197
  94. package/esm/lmvz-action-list.entry.js +4 -4
  95. package/esm/lmvz-action.entry.js +1 -1
  96. package/esm/lmvz-button-group.entry.js +5 -1
  97. package/esm/lmvz-button_2.entry.js +3 -3
  98. package/esm/lmvz-card.entry.js +2 -2
  99. package/esm/lmvz-checkbox.entry.js +3 -4
  100. package/esm/lmvz-chip.entry.js +5 -5
  101. package/esm/lmvz-components.js +3 -3
  102. package/esm/lmvz-datepicker.entry.js +1 -1
  103. package/esm/lmvz-header_2.entry.js +5 -5
  104. package/esm/lmvz-input.entry.js +4 -7
  105. package/esm/lmvz-link.entry.js +1 -1
  106. package/esm/lmvz-menuitem.entry.js +2 -2
  107. package/esm/lmvz-message.entry.js +4 -2
  108. package/esm/lmvz-modal.entry.js +2 -2
  109. package/esm/lmvz-popover.entry.js +5 -14
  110. package/esm/lmvz-radio.entry.js +2 -2
  111. package/esm/lmvz-select.entry.js +110 -16
  112. package/esm/lmvz-snackbar.entry.js +1 -1
  113. package/esm/lmvz-spinner.entry.js +1 -1
  114. package/esm/lmvz-tab.entry.js +1 -1
  115. package/esm/lmvz-tabs.entry.js +9 -3
  116. package/esm/lmvz-toggle.entry.js +2 -2
  117. package/esm/loader.js +3 -3
  118. package/esm/public-m6pRVUeP.js +204 -0
  119. package/hydrate/index.js +394 -50
  120. package/hydrate/index.mjs +394 -50
  121. package/lmvz-components/index.esm.js +1 -1
  122. package/lmvz-components/lmvz-components.esm.js +1 -1
  123. package/lmvz-components/p-1ba1af8d.entry.js +1 -0
  124. package/lmvz-components/{p-0f65d001.entry.js → p-27e8b4fa.entry.js} +1 -1
  125. package/lmvz-components/p-304c7554.entry.js +1 -0
  126. package/lmvz-components/p-316dd61a.entry.js +1 -0
  127. package/lmvz-components/{p-3d5867df.entry.js → p-335b3867.entry.js} +1 -1
  128. package/lmvz-components/{p-7872a6b4.entry.js → p-3b7203a3.entry.js} +1 -1
  129. package/lmvz-components/p-41286514.entry.js +1 -0
  130. package/lmvz-components/p-6aae1df9.entry.js +1 -0
  131. package/lmvz-components/p-743616c0.entry.js +1 -0
  132. package/lmvz-components/{p-26dd9684.entry.js → p-773e9d95.entry.js} +1 -1
  133. package/lmvz-components/{p-a6fe9b61.entry.js → p-7cbfbc88.entry.js} +1 -1
  134. package/lmvz-components/p-87f21ac9.entry.js +1 -0
  135. package/lmvz-components/{p-a680a7b2.entry.js → p-8ba6ec19.entry.js} +1 -1
  136. package/lmvz-components/p-93b781ca.entry.js +1 -0
  137. package/lmvz-components/p-BPa0FHVO.js +1 -0
  138. package/lmvz-components/p-BlkpdeQI.js +1 -0
  139. package/lmvz-components/p-DAikapVO.js +1 -0
  140. package/lmvz-components/p-DVab4vzV.js +2 -0
  141. package/lmvz-components/{p-BeY96W7m.js → p-Wi9d2-w9.js} +1 -1
  142. package/lmvz-components/p-c884c76e.entry.js +1 -0
  143. package/lmvz-components/{p-eabbf901.entry.js → p-d73bb7fd.entry.js} +1 -1
  144. package/lmvz-components/{p-137b1fe1.entry.js → p-e35f49a1.entry.js} +1 -1
  145. package/lmvz-components/p-ebad823c.entry.js +1 -0
  146. package/lmvz-components/p-edec4d09.entry.js +1 -0
  147. package/lmvz-components/{p-88661c50.entry.js → p-efd9d50e.entry.js} +1 -1
  148. package/lmvz-components/p-f24f5207.entry.js +1 -0
  149. package/lmvz-components/p-f911691c.entry.js +1 -0
  150. package/manifest.json +72 -39
  151. package/package.json +2 -1
  152. package/types/components/lmvz-input/lmvz-input.d.ts +0 -2
  153. package/types/components/lmvz-popover/lmvz-popover.d.ts +0 -1
  154. package/types/components/lmvz-select/lmvz-select.d.ts +12 -3
  155. package/types/components/lmvz-snackbar/public.d.ts +6 -0
  156. package/types/components/lmvz-tabs/lmvz-tabs.d.ts +1 -0
  157. package/types/components.d.ts +39 -19
  158. package/types/index.d.ts +5 -0
  159. package/types/utils/aria/directional-focus-controller.d.ts +9 -7
  160. package/types/utils/icons/public.d.ts +6 -0
  161. package/components/p--FDdvoIg.js +0 -1
  162. package/components/p-BApwCp0R.js +0 -1
  163. package/components/p-BPYBmylI.js +0 -1
  164. package/components/p-C5gRiLRR.js +0 -1
  165. package/components/p-CrQCHVKL.js +0 -1
  166. package/components/p-DiRcp9V6.js +0 -1
  167. package/components/p-DqdrUt_G.js +0 -1
  168. package/lmvz-components/p-00489151.entry.js +0 -1
  169. package/lmvz-components/p-0af0211a.entry.js +0 -1
  170. package/lmvz-components/p-398b5cf4.entry.js +0 -1
  171. package/lmvz-components/p-40716828.entry.js +0 -1
  172. package/lmvz-components/p-492adf21.entry.js +0 -1
  173. package/lmvz-components/p-4deaa010.entry.js +0 -1
  174. package/lmvz-components/p-4fa6e72f.entry.js +0 -1
  175. package/lmvz-components/p-57a366e6.entry.js +0 -1
  176. package/lmvz-components/p-70f1e31a.entry.js +0 -1
  177. package/lmvz-components/p-91eb71bb.entry.js +0 -1
  178. package/lmvz-components/p-BOXP1Vx0.js +0 -2
  179. package/lmvz-components/p-CHxiS3ja.js +0 -1
  180. package/lmvz-components/p-e14058e1.entry.js +0 -1
  181. package/lmvz-components/p-f3f0ed49.entry.js +0 -1
  182. package/lmvz-components/p-f516a949.entry.js +0 -1
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-07-14T13:31:39",
2
+ "timestamp": "2026-08-11T13:11:51",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.43.5",
@@ -1426,9 +1426,15 @@
1426
1426
  ],
1427
1427
  "parts": [],
1428
1428
  "states": [],
1429
- "dependents": [],
1429
+ "dependents": [
1430
+ "lmvz-select"
1431
+ ],
1430
1432
  "dependencies": [],
1431
- "dependencyGraph": {}
1433
+ "dependencyGraph": {
1434
+ "lmvz-select": [
1435
+ "lmvz-chip"
1436
+ ]
1437
+ }
1432
1438
  },
1433
1439
  {
1434
1440
  "filePath": "src/components/lmvz-datepicker/lmvz-datepicker.tsx",
@@ -7376,7 +7382,7 @@
7376
7382
  "filePath": "src/components/lmvz-popover/lmvz-popover.tsx",
7377
7383
  "encapsulation": "shadow",
7378
7384
  "tag": "lmvz-popover",
7379
- "readme": "# lmvz-popover\n\nA non-modal floating dialog anchored to a trigger element. Positions automatically via `@floating-ui/dom` with collision avoidance (flip and shift middleware). Content-agnostic: accepts any slotted elements.\n\n## Usage\n\nAnchor the popover to a trigger element by setting the `anchor` prop to the trigger's `id` or element reference.\n\nIf no `id` is set on the popover host, one is auto-assigned at load time.\n\n```html\n<button\n id=\"trigger\"\n onclick=\"document.getElementById('my-popover').toggle()\"\n>\n Open menu\n</button>\n<lmvz-popover id=\"my-popover\" anchor=\"trigger\" label=\"Menu options\">\n <p>Popover content goes here</p>\n</lmvz-popover>\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `open` | `boolean` | `false` | Opens or closes the popover. |\n| `anchor` | `string \\| HTMLElement` | – | Anchor element id or reference for positioning. |\n| `placement` | `'top' \\| 'bottom' \\| 'start' \\| 'end' \\| 'top-start' \\| 'top-end' \\| 'bottom-start' \\| 'bottom-end'` | `'bottom-start'` | Preferred placement relative to the anchor. |\n| `size` | `'sm' \\| 'md' \\| 'lg'` | `'sm'` | Size variant of the card surface. |\n| `autoFocus` | `boolean` | `true` | Moves focus to the first focusable element when opened. |\n| `label` | `string` | – | Accessible label for the popover container (reflected on dialog role). |\n\n## Slots\n\n| Slot | Description |\n|---|---|\n| `default` | Popover content. Any elements may be slotted. |\n\n## Events\n\n| Event | Cancelable | Description |\n|---|---|---|\n| `lmvzOpen` | No | Fired after the popover opens. |\n| `lmvzClose` | No | Fired after the popover closes. |\n| `lmvzCancel` | **Yes** | Fired before light-dismiss (click outside) or Escape close. Call `event.preventDefault()` to veto the close and keep the popover open. |\n\n## Methods\n\n| Method | Returns | Description |\n|---|---|---|\n| `show()` | `Promise<void>` | Opens the popover. |\n| `hide()` | `Promise<void>` | Closes the popover after firing `lmvzCancel`. If `lmvzCancel` is prevented, the popover remains open. |\n| `toggle()` | `Promise<void>` | Toggles the open state. |\n\n## CSS Customization\n\n### Shadow Parts\n\n| Part | Description |\n|---|---|\n| `container` | The popover's dialog container. Target to customize positioning, overflow, or appearance. |\n\n### Custom Properties\n\n| Property | Description | Default |\n|---|---|---|\n| `--lmvz-popover-max-width` | Maximum width of the popover surface. | `none` |\n| `--lmvz-popover-max-height` | Maximum height of the popover surface. | `none` |\n| `--lmvz-popover-z-index` | Z-index of the popover surface. | `1` |\n\n## Accessibility\n\n### Trigger Requirements\n\nWhen you set the `anchor` prop, the popover automatically writes three ARIA attributes to the resolved anchor element for screen reader and assistive technology support:\n\n- **`aria-haspopup=\"dialog\"`** — Announces that the trigger opens a dialog.\n- **`aria-expanded`** — Reflects the popover's open state (`\"true\"` when open, `\"false\"` when closed).\n- **`aria-controls`** — Points to the popover's `id` to establish the relationship between trigger and content.\n\nDo not set these attributes manually. Exception: if you pre-set `aria-haspopup` to a different value (e.g. `\"menu\"` for an action-list popover), the component preserves your value.\n\n### Content Semantics\n\nThe component renders a `role=\"dialog\"` container. Provide a `label` prop or set `aria-labelledby` on the host element to satisfy dialog labeling requirements.\n\n### AriaValidation\n\nWhen `@lmvz-ds/aria-validation` is enabled (development only), the component validates:\n\n- **Host element:** Must have an accessible label via the `label` prop or `aria-labelledby` attribute.\n- **Anchor element:** Must be interactive and focusable (e.g., `<button>`, `<a href>`, elements with `tabindex`, or elements with an interactive ARIA role). Non-interactive anchors produce a dev-time warning.\n\nValidation is disabled in production builds and test runtimes.\n\n## Notes\n\n- The popover is non-modal: focus is not trapped, and interaction outside does not require acknowledgment.\n- Positioning is automatic with collision avoidance. Use `placement` to express preference, but the component will flip or shift as needed to keep content visible.\n- Light-dismiss (clicking outside the popover or pressing Escape) fires `lmvzCancel` first, allowing consumers to intercept and prevent close (e.g., to warn unsaved changes).\n\n## Integration Patterns\n\n### Combobox and Listbox (Active Descendant Model)\n\nThe popover supports integration with combobox and listbox components via the `containerRole` prop. When `containerRole` is set to a non-dialog value (e.g. `'listbox'`, `'menu'`, `'tree'`, or `'grid'`):\n\n- The popover container receives the specified ARIA role instead of `role=\"dialog\"`\n- Focus remains on the trigger/anchor element (active descendant pattern), so `autoFocus` has no effect\n- `aria-haspopup` on the anchor is automatically set to match the `containerRole` value\n\nThis enables tight integration with components like `lmvz-select`, which uses `containerRole=\"listbox\"` to implement the WAI-ARIA combobox pattern with a listbox popup. In this mode, the popover acts as a styled container while the trigger manages focus and active descendant state.\n\n## Out of Scope\n\nBroader component integration patterns (e.g., date-picker, action menu with complex internal logic) remain the responsibility of consuming applications. The popover provides the container and positioning mechanism; application-specific behavior flows stay at the consuming component level.\n",
7385
+ "readme": "# lmvz-popover\n\nA non-modal floating dialog anchored to a trigger element. Positions automatically via `@floating-ui/dom` with collision avoidance (flip and shift middleware). Content-agnostic: accepts any slotted elements.\n\n## Usage\n\nAnchor the popover to a trigger element by setting the `anchor` prop to the trigger's `id` or element reference.\n\nIf no `id` is set on the popover host, one is auto-assigned at load time.\n\n```html\n<button\n id=\"trigger\"\n onclick=\"document.getElementById('my-popover').toggle()\"\n>\n Open menu\n</button>\n<lmvz-popover id=\"my-popover\" anchor=\"trigger\" label=\"Menu options\">\n <p>Popover content goes here</p>\n</lmvz-popover>\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n|---|---|---|---|\n| `open` | `boolean` | `false` | Opens or closes the popover. |\n| `anchor` | `string \\| HTMLElement` | – | Anchor element id or reference for positioning. |\n| `placement` | `'top' \\| 'bottom' \\| 'start' \\| 'end' \\| 'top-start' \\| 'top-end' \\| 'bottom-start' \\| 'bottom-end'` | `'bottom-start'` | Preferred placement relative to the anchor. |\n| `size` | `'sm' \\| 'md' \\| 'lg'` | `'sm'` | Size variant of the card surface. |\n| `autoFocus` | `boolean` | `true` | Moves focus to the first focusable element when opened. |\n| `label` | `string` | – | Accessible label for the popover container (reflected on host element). |\n\n## Slots\n\n| Slot | Description |\n|---|---|\n| `default` | Popover content. Any elements may be slotted. |\n\n## Events\n\n| Event | Cancelable | Description |\n|---|---|---|\n| `lmvzOpen` | No | Fired after the popover opens. |\n| `lmvzClose` | No | Fired after the popover closes. |\n| `lmvzCancel` | **Yes** | Fired before light-dismiss (click outside) or Escape close. Call `event.preventDefault()` to veto the close and keep the popover open. |\n\n## Methods\n\n| Method | Returns | Description |\n|---|---|---|\n| `show()` | `Promise<void>` | Opens the popover. |\n| `hide()` | `Promise<void>` | Closes the popover after firing `lmvzCancel`. If `lmvzCancel` is prevented, the popover remains open. |\n| `toggle()` | `Promise<void>` | Toggles the open state. |\n\n## CSS Customization\n\n### Shadow Parts\n\n| Part | Description |\n|---|---|\n| `container` | The popover's content container (a styled surface). The ARIA role is on the host element, not this container. Target to customize sizing, overflow, or visual appearance. |\n\n### Custom Properties\n\n| Property | Description | Default |\n|---|---|---|\n| `--lmvz-popover-max-width` | Maximum width of the popover surface. | `none` |\n| `--lmvz-popover-max-height` | Maximum height of the popover surface. | `none` |\n| `--lmvz-popover-z-index` | Z-index of the popover surface. | `1` |\n\n## Accessibility\n\n### Trigger Requirements\n\nWhen you set the `anchor` prop, the popover automatically writes three ARIA attributes to the resolved anchor element for screen reader and assistive technology support:\n\n- **`aria-haspopup=\"dialog\"`** — Announces that the trigger opens a dialog.\n- **`aria-expanded`** — Reflects the popover's open state (`\"true\"` when open, `\"false\"` when closed).\n- **`aria-controls`** — Points to the popover's `id` to establish the relationship between trigger and content.\n\nDo not set these attributes manually. Exception: if you pre-set `aria-haspopup` to a different value (e.g. `\"menu\"` for an action-list popover), the component preserves your value.\n\n### Content Semantics\n\nThe popover's ARIA role and accessible label are applied to the **host element** (not an internal container). The role defaults to `'dialog'`, configurable via the `containerRole` prop. Provide a `label` prop or set `aria-labelledby` on the host element to satisfy dialog labeling requirements.\n\nConsumers may set additional ARIA attributes directly on the `<lmvz-popover>` host element as plain attributes. For example, when using the popover as a listbox in a combobox pattern, set `aria-multiselectable=\"true\"` on the host if the listbox supports multiselection:\n\n```html\n<lmvz-popover container-role=\"listbox\" aria-multiselectable=\"true\">\n <!-- listbox content -->\n</lmvz-popover>\n```\n\n**Accessibility note (WebKit/Safari):** The host element uses `display: contents`, combined with an explicit host-level `role`. This combination has a known cross-engine accessibility-tree inconsistency in WebKit/Safari that is not fully resolvable in the current Stencil architecture. This design has been accepted as a documented trade-off to maintain semantic clarity in the DOM.\n\n### AriaValidation\n\nWhen `@lmvz-ds/aria-validation` is enabled (development only), the component validates:\n\n- **Host element:** Must have an accessible label via the `label` prop or `aria-labelledby` attribute.\n- **Anchor element:** Must be interactive and focusable (e.g., `<button>`, `<a href>`, elements with `tabindex`, or elements with an interactive ARIA role). Non-interactive anchors produce a dev-time warning.\n\nValidation is disabled in production builds and test runtimes.\n\n## Notes\n\n- The popover is non-modal: focus is not trapped, and interaction outside does not require acknowledgment.\n- Positioning is automatic with collision avoidance. Use `placement` to express preference, but the component will flip or shift as needed to keep content visible.\n- Light-dismiss (clicking outside the popover or pressing Escape) fires `lmvzCancel` first, allowing consumers to intercept and prevent close (e.g., to warn unsaved changes).\n\n## Integration Patterns\n\n### Combobox and Listbox (Active Descendant Model)\n\nThe popover supports integration with combobox and listbox components via the `containerRole` prop. When `containerRole` is set to a non-dialog value (e.g. `'listbox'`, `'menu'`, `'tree'`, or `'grid'`):\n\n- The popover container receives the specified ARIA role instead of `role=\"dialog\"`\n- Focus remains on the trigger/anchor element (active descendant pattern), so `autoFocus` has no effect\n- `aria-haspopup` on the anchor is automatically set to match the `containerRole` value\n\nThis enables tight integration with components like `lmvz-select`, which uses `containerRole=\"listbox\"` to implement the WAI-ARIA combobox pattern with a listbox popup. In this mode, the popover acts as a styled container while the trigger manages focus and active descendant state.\n\n## Out of Scope\n\nBroader component integration patterns (e.g., date-picker, action menu with complex internal logic) remain the responsibility of consuming applications. The popover provides the container and positioning mechanism; application-specific behavior flows stay at the consuming component level.\n",
7380
7386
  "docs": "A non-modal floating dialog anchored to a trigger element. Positions automatically via `@floating-ui/dom` with collision avoidance (flip and shift middleware). Content-agnostic: accepts any slotted elements.",
7381
7387
  "docsTags": [
7382
7388
  {
@@ -8247,8 +8253,8 @@
8247
8253
  "filePath": "src/components/lmvz-select/lmvz-select.tsx",
8248
8254
  "encapsulation": "scoped",
8249
8255
  "tag": "lmvz-select",
8250
- "readme": "# lmvz-select\n\nSelect component with floating label and pill-shaped trigger that implements the WAI-ARIA \"combobox with listbox popup\" pattern.\n\n## What changed: Breaking rewrite from native `<select>` to custom WAI-ARIA combobox\n\nIn previous versions, `lmvz-select` wrapped a native `<select>` element and inherited its form behavior and platform-specific UI (including native mobile pickers on iOS/Android). As of this version, it is a fully custom WAI-ARIA combobox implemented using:\n\n- A `role=\"combobox\"` trigger button (your interaction target)\n- A `role=\"listbox\"` popup menu hosted in `lmvz-popover`\n- Focus management via `aria-activedescendant` (trigger keeps focus; the selected item is tracked via an aria attribute rather than DOM focus)\n- Form submission via `ElementInternals` (like `lmvz-input`, `lmvz-checkbox`, etc. in this design system)\n\n**What consumers must know:**\n\n- **No native mobile picker:** On iOS, Android, and other platforms, users see the custom popup instead of the OS-native picker. This is by design, to ensure consistent styling and behavior across platforms.\n- **Slot contract:** You pass native `<option>` and `<optgroup>` elements as children. The component translates them internally to `role=\"listbox\"` and `role=\"option\"` elements.\n- **`<option selected>` is meaningful:** If you do not set the `value` prop, the first `<option selected=\"selected\">` becomes the initial value.\n- **Validation is minimal:** The component uses native HTML form constraint validation (`ElementInternals.setValidity()`). When `required` is true and no value is set, the browser's default validation UI appears on form submit; the component does not provide custom validation message styling beyond `errorMessage` (which you control via props).\n- **Form submission:** Use the `name` attribute (set directly on the `<lmvz-select>` host) and the current `value` to participate in HTML form submission. Form reset correctly restores the initial value.\n\n## Slot contract\n\nThe default slot accepts only native `<option>` and `<optgroup>` elements. The component reads this markup once on mount and translates it into an internal listbox model, re-parsing whenever the slotted content changes structurally (options/optgroups added or removed) or an already-slotted option/optgroup is mutated in place (e.g. `.disabled`, `.selected`, `.value`, `.label`, or its text content), mirroring native `<select>` reactivity. The slotted markup is never rendered; it serves as a template.\n\n```html\n<lmvz-select label=\"Fruit\" name=\"fruit\">\n <option value=\"apple\">Apple</option>\n <optgroup label=\"Citrus\">\n <option value=\"orange\">Orange</option>\n <option value=\"lemon\">Lemon</option>\n </optgroup>\n <option value=\"banana\" selected>Banana</option>\n</lmvz-select>\n```\n\n## Form integration\n\n`lmvz-select` is a form-associated custom element (`formAssociated: true`). Set the `name` attribute on the host element, and the component participates in:\n\n- **Form submission:** The current `value` is submitted under the `name` key.\n- **Form reset:** Restores `value` to its initial state (the prop value at mount time).\n- **Validation:** Set `required` to enforce that a value is present. Use `checkValidity()`, `reportValidity()`, and `getValidationMessage()` methods for programmatic validation.\n\n## Props explained\n\n### `size`\n\n- Type: `'small' | 'default' | 'large'`\n- Default: `'default'`\n\nControls the visual height variant of the select trigger. Use `small` for compact layouts, `default` for standard use, and `large` for emphasis.\n\n### `highlighted`\n\n- Type: `boolean`\n- Default: `false`\n\nIndicates that an active filter or selection applies (e.g., a filter chip is active). When `true`:\n- The host attribute `[highlighted]` is present for CSS styling (`:host([highlighted])`).\n- The host attribute `data-highlighted=\"true\"` is set for assistive-technology consumers.\n\nWhen `false`, `data-highlighted=\"false\"` is set. The `data-highlighted` attribute (rather than `aria-pressed`) provides this accessible contract; `aria-selected` is reserved for the `role=\"option\"` elements in the listbox popup.\n\n### `error`\n\n- Type: `boolean`\n- Default: `false`\n- Reflected as host attribute: yes\n\nWhen `true`:\n- Applies danger-themed border color to the combobox trigger.\n- Sets `aria-invalid=\"true\"` on the trigger element.\n\n`aria-invalid` is also automatically set to `true` when the select's real constraint validity becomes invalid following a validation attempt (e.g., after a failed `submit`, `checkValidity()`, or `reportValidity()` call), and is cleared once the constraint is resolved. This allows automatic reflect of native HTML5 validation state independent of the `error` prop.\n\nTypically set alongside `errorMessage` to provide inline error text.\n\n### `errorMessage`\n\n- Type: `string | undefined`\n- Default: `undefined`\n\nError message text rendered below the select field when `error={true}`. Rendered as an assertive live region (`role=\"alert\"`), linked to the trigger via `aria-describedby` and `aria-errormessage`.\n\nCoexists with `helperText` — both can be displayed simultaneously, with `helperText` rendered first.\n",
8251
- "docs": "Select component with floating label and pill-shaped trigger.\nImplements the WAI-ARIA APG \"combobox with listbox popup\" pattern: a `role=\"combobox\"`\ntrigger drives an `lmvz-popover`-hosted `role=\"listbox\"`/`role=\"option\"` list via the\nactivedescendant model (the trigger keeps real DOM focus; `aria-activedescendant` tracks\nthe highlighted option).\n\nParticipates in native HTML forms via `ElementInternals` (form-associated custom element):\nthe `name` attribute (set directly on the host element) and the current `value` are submitted,\nand the value resets to its initial value on form reset.",
8256
+ "readme": "# lmvz-select\n\nSelect component with floating label and pill-shaped trigger that implements the WAI-ARIA \"combobox with listbox popup\" pattern.\n\n## What changed: Breaking rewrite from native `<select>` to custom WAI-ARIA combobox\n\nIn previous versions, `lmvz-select` wrapped a native `<select>` element and inherited its form behavior and platform-specific UI (including native mobile pickers on iOS/Android). As of this version, it is a fully custom WAI-ARIA combobox implemented using:\n\n- A `role=\"combobox\"` trigger button (your interaction target)\n- A `role=\"listbox\"` popup menu hosted in `lmvz-popover`\n- Focus management via `aria-activedescendant` (trigger keeps focus; the selected item is tracked via an aria attribute rather than DOM focus)\n- Form submission via `ElementInternals` (like `lmvz-input`, `lmvz-checkbox`, etc. in this design system)\n\n**What consumers must know:**\n\n- **No native mobile picker:** On iOS, Android, and other platforms, users see the custom popup instead of the OS-native picker. This is by design, to ensure consistent styling and behavior across platforms.\n- **Slot contract:** You pass native `<option>` and `<optgroup>` elements as children. The component translates them internally to `role=\"listbox\"` and `role=\"option\"` elements.\n- **`<option selected>` is meaningful:** If you do not set the `value` prop, the first `<option selected=\"selected\">` becomes the initial value (or, in multiselect mode, all enabled `<option selected>` elements are collected into the initial `values`).\n- **Validation is minimal:** The component uses native HTML form constraint validation (`ElementInternals.setValidity()`). When `required` is true and no value is set, the browser's default validation UI appears on form submit; the component does not provide custom validation message styling beyond `errorMessage` (which you control via props).\n- **Form submission:** Use the `name` attribute (set directly on the `<lmvz-select>` host) and the current `value` to participate in HTML form submission. Form reset correctly restores the initial value.\n\n### Multiselect mode\n\nSet the `multiple` prop to enable multiselect mode. When `multiple` is `true`:\n\n- **Selection is tracked in `values`** (a `string[]` array), not in the single-select `value` prop. The `value` prop is ignored; do not set it alongside `multiple`.\n- **Options toggle membership** on selection instead of replacing a single value. Selecting an already-selected option deselects it.\n- **The listbox stays open** after selecting or deselecting an option. Close it via Escape, outside click, or re-clicking the trigger.\n- **The closed trigger renders one chip per selected value** for display only, using the `lmvz-chip` component. Removing a value is done by reselecting it in the open listbox.\n- **Keyboard selection works the same:** Space or Enter while an option is active (highlighted) toggles its selection without closing the popover.\n- **Preselection:** Pass `<option selected>` elements — *all* enabled ones are collected into the initial `values` array, not just the first (mirroring how `<select multiple>` works).\n- **Known limitation:** A \"select all\" control is not implemented in this iteration; it is planned as a future follow-up.\n\n## Slot contract\n\nThe default slot accepts only native `<option>` and `<optgroup>` elements. The component reads this markup once on mount and translates it into an internal listbox model, re-parsing whenever the slotted content changes structurally (options/optgroups added or removed) or an already-slotted option/optgroup is mutated in place (e.g. `.disabled`, `.selected`, `.value`, `.label`, or its text content), mirroring native `<select>` reactivity. The slotted markup is never rendered; it serves as a template.\n\n```html\n<lmvz-select label=\"Fruit\" name=\"fruit\">\n <option value=\"apple\">Apple</option>\n <optgroup label=\"Citrus\">\n <option value=\"orange\">Orange</option>\n <option value=\"lemon\">Lemon</option>\n </optgroup>\n <option value=\"banana\" selected>Banana</option>\n</lmvz-select>\n```\n\n## Form integration\n\n`lmvz-select` is a form-associated custom element (`formAssociated: true`). Set the `name` attribute on the host element, and the component participates in:\n\n- **Form submission:** The current `value` is submitted under the `name` key (single-select mode), or multiple form entries are submitted (multiselect mode).\n- **Form reset:** Restores `value` or `values` to their initial state (the prop values at mount time).\n- **Validation:** Set `required` to enforce that a value is present. Use `checkValidity()`, `reportValidity()`, and `getValidationMessage()` methods for programmatic validation.\n\n### Multiselect form submission\n\nIn multiselect mode (`multiple` true), form submission uses `FormData` to submit one entry per selected value:\n\n```javascript\nconst form = document.querySelector('form');\nconst formData = new FormData(form);\n// If the select has name=\"country\" and values=[\"ch\", \"de\"], then:\n// formData.getAll('country') returns ['ch', 'de']\n```\n\nForm reset restores `values` to its initial state (all enabled `<option selected>` elements, or an empty array if none). Programmatically, you can also detect multiselect form state restoration via the `formStateRestoreCallback()` lifecycle (called during form reset or history navigation).\n\n## Props explained\n\n### `size`\n\n- Type: `'small' | 'default' | 'large'`\n- Default: `'default'`\n\nControls the visual height variant of the select trigger. Use `small` for compact layouts, `default` for standard use, and `large` for emphasis.\n\n### `error`\n\n- Type: `boolean`\n- Default: `false`\n- Reflected as host attribute: yes\n\nWhen `true`:\n- Applies danger-themed border color to the combobox trigger.\n- Sets `aria-invalid=\"true\"` on the trigger element.\n\n`aria-invalid` is also automatically set to `true` when the select's real constraint validity becomes invalid following a validation attempt (e.g., after a failed `submit`, `checkValidity()`, or `reportValidity()` call), and is cleared once the constraint is resolved. This allows automatic reflect of native HTML5 validation state independent of the `error` prop.\n\nTypically set alongside `errorMessage` to provide inline error text.\n\n### `errorMessage`\n\n- Type: `string | undefined`\n- Default: `undefined`\n\nError message text rendered below the select field when `error={true}`. Rendered as an assertive live region (`role=\"alert\"`), linked to the trigger via `aria-describedby` and `aria-errormessage`.\n\nCoexists with `helperText` — both can be displayed simultaneously, with `helperText` rendered first.\n",
8257
+ "docs": "Select component with floating label and pill-shaped trigger.\nImplements the WAI-ARIA APG \"combobox with listbox popup\" pattern: a `role=\"combobox\"`\ntrigger drives an `lmvz-popover`-hosted `role=\"listbox\"`/`role=\"option\"` list via the\nactivedescendant model (the trigger keeps real DOM focus; `aria-activedescendant` tracks\nthe highlighted option).\n\nSupports an opt-in multiselect mode (`multiple`): selection is tracked in `values` (an array)\ninstead of the single-select `value`, selecting an option toggles its membership without\nclosing the popup, and the closed trigger renders one `lmvz-chip` per selected value for\ndisplay only — removing a value is done by reselecting it in the open listbox, not from a\nchip control.\n\nParticipates in native HTML forms via `ElementInternals` (form-associated custom element):\nthe `name` attribute (set directly on the host element) and the current `value` (or, in\nmultiselect mode, one form entry per selected `values` entry) are submitted, and selection\nresets to its initial state on form reset.",
8252
8258
  "docsTags": [
8253
8259
  {
8254
8260
  "name": "example",
@@ -8260,7 +8266,7 @@
8260
8266
  },
8261
8267
  {
8262
8268
  "name": "slot",
8263
- "text": "default - Native `<option>` or `<optgroup>` elements. Never rendered directly; read once\nassigned to build the internal `role=\"option\"` listbox, and re-read on structural slot changes\nor in-place mutation (attribute/text/subtree) of already-slotted elements. `<option selected>`\ndetermines the initial value when the `value` prop is not set."
8269
+ "text": "default - Native `<option>` or `<optgroup>` elements. Never rendered directly; read once\nassigned to build the internal `role=\"option\"` listbox, and re-read on structural slot changes\nor in-place mutation (attribute/text/subtree) of already-slotted elements. `<option selected>`\ndetermines the initial value (or, in multiselect mode, the initial `values`) when the `value`/\n`values` prop is not set."
8264
8270
  }
8265
8271
  ],
8266
8272
  "usage": {},
@@ -8375,54 +8381,54 @@
8375
8381
  "setter": false
8376
8382
  },
8377
8383
  {
8378
- "name": "highlighted",
8379
- "type": "boolean",
8384
+ "name": "label",
8385
+ "type": "string",
8380
8386
  "complexType": {
8381
- "original": "boolean",
8382
- "resolved": "boolean",
8387
+ "original": "string",
8388
+ "resolved": "string",
8383
8389
  "references": {}
8384
8390
  },
8385
8391
  "mutable": false,
8386
- "attr": "highlighted",
8387
- "reflectToAttr": true,
8388
- "docs": "Whether the select has an active filter or selection that should be visually\ndistinguished (e.g. a filter chip is applied).\n\nWhen `true`:\n- The host attribute `highlighted` is present (CSS: `:host([highlighted])`).\n- The host attribute `data-highlighted=\"true\"` is set for assistive-technology\n consumers that observe data attributes. When `false`, `data-highlighted` is\n set to `\"false\"`.\n\nNote: `aria-pressed` is intentionally not used here; it is reserved for toggle-button\nroles. `aria-selected` is reserved for this component's own `role=\"option\"` elements.\nThe `data-highlighted` attribute provides the accessible contract for this state.",
8389
- "docsTags": [
8390
- {
8391
- "name": "default",
8392
- "text": "false"
8393
- }
8394
- ],
8395
- "default": "false",
8392
+ "attr": "label",
8393
+ "reflectToAttr": false,
8394
+ "docs": "Label text displayed as a placeholder when no value is selected,\nand floated above the trigger when a value is present.",
8395
+ "docsTags": [],
8396
8396
  "values": [
8397
8397
  {
8398
- "type": "boolean"
8398
+ "type": "string"
8399
8399
  }
8400
8400
  ],
8401
8401
  "optional": false,
8402
- "required": false,
8402
+ "required": true,
8403
8403
  "getter": false,
8404
8404
  "setter": false
8405
8405
  },
8406
8406
  {
8407
- "name": "label",
8408
- "type": "string",
8407
+ "name": "multiple",
8408
+ "type": "boolean",
8409
8409
  "complexType": {
8410
- "original": "string",
8411
- "resolved": "string",
8410
+ "original": "boolean",
8411
+ "resolved": "boolean",
8412
8412
  "references": {}
8413
8413
  },
8414
8414
  "mutable": false,
8415
- "attr": "label",
8416
- "reflectToAttr": false,
8417
- "docs": "Label text displayed as a placeholder when no value is selected,\nand floated above the trigger when a value is present.",
8418
- "docsTags": [],
8415
+ "attr": "multiple",
8416
+ "reflectToAttr": true,
8417
+ "docs": "Enables multiselect mode. When `true`, `values` becomes the sole selection source (`value`\nis not read or written by the component), options toggle membership on selection instead of\nreplacing a single value, the popup stays open after selecting/deselecting an option, and the\nclosed trigger renders one `lmvz-chip` per selected value for display only. Removing a value\nis done by reselecting it in the open listbox (via `handleOptionSelect`'s toggle-off path),\nnot from a control on the chip itself.",
8418
+ "docsTags": [
8419
+ {
8420
+ "name": "default",
8421
+ "text": "false"
8422
+ }
8423
+ ],
8424
+ "default": "false",
8419
8425
  "values": [
8420
8426
  {
8421
- "type": "string"
8427
+ "type": "boolean"
8422
8428
  }
8423
8429
  ],
8424
8430
  "optional": false,
8425
- "required": true,
8431
+ "required": false,
8426
8432
  "getter": false,
8427
8433
  "setter": false
8428
8434
  },
@@ -8537,7 +8543,7 @@
8537
8543
  "mutable": true,
8538
8544
  "attr": "value",
8539
8545
  "reflectToAttr": false,
8540
- "docs": "Currently selected value.",
8546
+ "docs": "Currently selected value. Ignored (not read or written) when `multiple` is `true` — use\n`values` instead.",
8541
8547
  "docsTags": [],
8542
8548
  "values": [
8543
8549
  {
@@ -8551,6 +8557,31 @@
8551
8557
  "required": false,
8552
8558
  "getter": false,
8553
8559
  "setter": false
8560
+ },
8561
+ {
8562
+ "name": "values",
8563
+ "type": "string[] | undefined",
8564
+ "complexType": {
8565
+ "original": "string[]",
8566
+ "resolved": "string[] | undefined",
8567
+ "references": {}
8568
+ },
8569
+ "mutable": true,
8570
+ "reflectToAttr": false,
8571
+ "docs": "Currently selected values in multiselect mode (`multiple` true). Not attribute-reflected;\nset/read as a JS property. Preselection collects every enabled `<option selected>` (not just\nthe first) into the initial value when unset.",
8572
+ "docsTags": [],
8573
+ "values": [
8574
+ {
8575
+ "type": "string[]"
8576
+ },
8577
+ {
8578
+ "type": "undefined"
8579
+ }
8580
+ ],
8581
+ "optional": true,
8582
+ "required": false,
8583
+ "getter": false,
8584
+ "setter": false
8554
8585
  }
8555
8586
  ],
8556
8587
  "methods": [
@@ -8668,16 +8699,16 @@
8668
8699
  "events": [
8669
8700
  {
8670
8701
  "event": "lmvzChange",
8671
- "detail": "string",
8702
+ "detail": "string | string[]",
8672
8703
  "bubbles": true,
8673
8704
  "complexType": {
8674
- "original": "string",
8675
- "resolved": "string",
8705
+ "original": "string | string[]",
8706
+ "resolved": "string | string[]",
8676
8707
  "references": {}
8677
8708
  },
8678
8709
  "cancelable": true,
8679
8710
  "composed": true,
8680
- "docs": "Emitted when the user selects a new option. Detail contains the new value.",
8711
+ "docs": "Emitted when the user changes the selection. Detail contains the new value:\na `string` in single-select mode (`multiple` false/unset — same shape as before), or the\nfull updated `string[]` of selected values in multiselect mode (`multiple` true).",
8681
8712
  "docsTags": []
8682
8713
  }
8683
8714
  ],
@@ -8692,17 +8723,19 @@
8692
8723
  "slots": [
8693
8724
  {
8694
8725
  "name": "default",
8695
- "docs": "Native `<option>` or `<optgroup>` elements. Never rendered directly; read once\nassigned to build the internal `role=\"option\"` listbox, and re-read on structural slot changes\nor in-place mutation (attribute/text/subtree) of already-slotted elements. `<option selected>`\ndetermines the initial value when the `value` prop is not set."
8726
+ "docs": "Native `<option>` or `<optgroup>` elements. Never rendered directly; read once\nassigned to build the internal `role=\"option\"` listbox, and re-read on structural slot changes\nor in-place mutation (attribute/text/subtree) of already-slotted elements. `<option selected>`\ndetermines the initial value (or, in multiselect mode, the initial `values`) when the `value`/\n`values` prop is not set."
8696
8727
  }
8697
8728
  ],
8698
8729
  "parts": [],
8699
8730
  "states": [],
8700
8731
  "dependents": [],
8701
8732
  "dependencies": [
8733
+ "lmvz-chip",
8702
8734
  "lmvz-popover"
8703
8735
  ],
8704
8736
  "dependencyGraph": {
8705
8737
  "lmvz-select": [
8738
+ "lmvz-chip",
8706
8739
  "lmvz-popover"
8707
8740
  ]
8708
8741
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmvz-ds/components",
3
3
  "private": false,
4
- "version": "0.37.0",
4
+ "version": "0.39.0",
5
5
  "type": "module",
6
6
  "description": "The components of the design system",
7
7
  "author": "Patrick Nemenz <patrick.nemenz@adesso.at>",
@@ -10,6 +10,7 @@
10
10
  "linkDirectory": false
11
11
  },
12
12
  "exports": {
13
+ "./index.js": "./index.js",
13
14
  ".": {
14
15
  "types": "./components/index.d.ts",
15
16
  "import": "./components/index.js"
@@ -64,8 +64,6 @@ export declare class LmvzInput extends ReactiveControllerHost implements AriaVal
64
64
  protected handleValueChange(newValue: string | undefined): void;
65
65
  protected handleDisabledChange(disabled: boolean): void;
66
66
  private handleInput;
67
- private handleChange;
68
- private handleFocus;
69
67
  private handleInvalid;
70
68
  private handleBlur;
71
69
  private updateNativeError;
@@ -25,7 +25,6 @@ export declare class LmvzPopover {
25
25
  protected handleOpenChange(isOpen: boolean): void;
26
26
  protected handleAnchorChange(anchor: string | HTMLElement | undefined): Promise<void>;
27
27
  disconnectedCallback(): void;
28
- private syncContainerId;
29
28
  private setupAnchorEl;
30
29
  private updatePosition;
31
30
  private onOpen;
@@ -14,6 +14,8 @@ export declare class LmvzSelect extends ReactiveControllerHost implements AriaVa
14
14
  private optionsMutationObserver?;
15
15
  private initialValue?;
16
16
  private initialValueCaptured;
17
+ private initialValues?;
18
+ private initialValuesCaptured;
17
19
  private flatOptions;
18
20
  private optionsById;
19
21
  private readonly listboxController;
@@ -22,8 +24,10 @@ export declare class LmvzSelect extends ReactiveControllerHost implements AriaVa
22
24
  private get errorId();
23
25
  private get describedBy();
24
26
  constructor();
25
- lmvzChange: EventEmitter<string>;
27
+ lmvzChange: EventEmitter<string | string[]>;
26
28
  value?: string;
29
+ multiple: boolean;
30
+ values?: string[];
27
31
  label: string;
28
32
  helperText?: string;
29
33
  private nativeError;
@@ -35,13 +39,14 @@ export declare class LmvzSelect extends ReactiveControllerHost implements AriaVa
35
39
  required: boolean;
36
40
  name?: string;
37
41
  size: Select.Size;
38
- highlighted: boolean;
39
42
  private selectedLabel;
40
43
  private selectedId?;
41
44
  private activeId?;
42
45
  private isOpen;
43
46
  private optionGroups;
44
47
  private get hasValue();
48
+ private get triggerAriaLabel();
49
+ private get chipSize();
45
50
  componentWillLoad(): void;
46
51
  componentDidLoad(): void;
47
52
  disconnectedCallback(): void;
@@ -53,12 +58,15 @@ export declare class LmvzSelect extends ReactiveControllerHost implements AriaVa
53
58
  getValidationMessage(): Promise<string>;
54
59
  formAssociatedCallback(form: HTMLFormElement | undefined): void;
55
60
  formResetCallback(): void;
56
- formStateRestoreCallback(state: string): void;
61
+ formStateRestoreCallback(state: string | FormData | File): void;
57
62
  protected handleValueChange(newValue: string | undefined): void;
63
+ protected handleValuesChange(): void;
58
64
  protected handleRequiredChange(): void;
59
65
  protected handleDisabledChange(disabled: boolean): void;
60
66
  private handleSlotChange;
61
67
  private applySelectionFromValue;
68
+ private isOptionSelected;
69
+ private syncFormValue;
62
70
  private updateValidity;
63
71
  protected handleInvalid(): void;
64
72
  private handleOptionSelect;
@@ -72,5 +80,6 @@ export declare class LmvzSelect extends ReactiveControllerHost implements AriaVa
72
80
  private closePopover;
73
81
  private renderOptionGroups;
74
82
  private renderOption;
83
+ private renderChips;
75
84
  render(): any;
76
85
  }
@@ -1,2 +1,8 @@
1
+ import { SnackbarController } from './snackbar-controller';
1
2
  export { SnackbarController } from './snackbar-controller';
2
3
  export type { SnackbarDismissReason, SnackbarHandle, SnackbarOptions, SnackbarPriority, SnackbarStatus } from './snackbar-controller';
4
+ declare global {
5
+ interface LmvzApi {
6
+ SnackbarController?: typeof SnackbarController;
7
+ }
8
+ }
@@ -6,6 +6,7 @@ export declare class LmvzTabs extends ReactiveControllerHost implements Directio
6
6
  private readonly instanceId;
7
7
  private tabSlotEl?;
8
8
  private panelSlotEl?;
9
+ private tablistEl?;
9
10
  private focusController;
10
11
  value: string;
11
12
  orientation: 'horizontal' | 'vertical';
@@ -729,9 +729,15 @@ export declare namespace Components {
729
729
  * trigger drives an `lmvz-popover`-hosted `role="listbox"`/`role="option"` list via the
730
730
  * activedescendant model (the trigger keeps real DOM focus; `aria-activedescendant` tracks
731
731
  * the highlighted option).
732
+ * Supports an opt-in multiselect mode (`multiple`): selection is tracked in `values` (an array)
733
+ * instead of the single-select `value`, selecting an option toggles its membership without
734
+ * closing the popup, and the closed trigger renders one `lmvz-chip` per selected value for
735
+ * display only — removing a value is done by reselecting it in the open listbox, not from a
736
+ * chip control.
732
737
  * Participates in native HTML forms via `ElementInternals` (form-associated custom element):
733
- * the `name` attribute (set directly on the host element) and the current `value` are submitted,
734
- * and the value resets to its initial value on form reset.
738
+ * the `name` attribute (set directly on the host element) and the current `value` (or, in
739
+ * multiselect mode, one form entry per selected `values` entry) are submitted, and selection
740
+ * resets to its initial state on form reset.
735
741
  * @example ```html
736
742
  * <lmvz-select label="Country" name="country">
737
743
  * <option value="ch">Switzerland</option>
@@ -779,15 +785,15 @@ export declare namespace Components {
779
785
  * Helper text displayed below the select field.
780
786
  */
781
787
  "helperText"?: string;
782
- /**
783
- * Whether the select has an active filter or selection that should be visually distinguished (e.g. a filter chip is applied). When `true`: - The host attribute `highlighted` is present (CSS: `:host([highlighted])`). - The host attribute `data-highlighted="true"` is set for assistive-technology consumers that observe data attributes. When `false`, `data-highlighted` is set to `"false"`. Note: `aria-pressed` is intentionally not used here; it is reserved for toggle-button roles. `aria-selected` is reserved for this component's own `role="option"` elements. The `data-highlighted` attribute provides the accessible contract for this state.
784
- * @default false
785
- */
786
- "highlighted": boolean;
787
788
  /**
788
789
  * Label text displayed as a placeholder when no value is selected, and floated above the trigger when a value is present.
789
790
  */
790
791
  "label": string;
792
+ /**
793
+ * Enables multiselect mode. When `true`, `values` becomes the sole selection source (`value` is not read or written by the component), options toggle membership on selection instead of replacing a single value, the popup stays open after selecting/deselecting an option, and the closed trigger renders one `lmvz-chip` per selected value for display only. Removing a value is done by reselecting it in the open listbox (via `handleOptionSelect`'s toggle-off path), not from a control on the chip itself.
794
+ * @default false
795
+ */
796
+ "multiple": boolean;
791
797
  /**
792
798
  * Name attribute for form submission via ElementInternals.
793
799
  */
@@ -807,9 +813,13 @@ export declare namespace Components {
807
813
  */
808
814
  "size": Select.Size;
809
815
  /**
810
- * Currently selected value.
816
+ * Currently selected value. Ignored (not read or written) when `multiple` is `true` — use `values` instead.
811
817
  */
812
818
  "value"?: string;
819
+ /**
820
+ * Currently selected values in multiselect mode (`multiple` true). Not attribute-reflected; set/read as a JS property. Preselection collects every enabled `<option selected>` (not just the first) into the initial value when unset.
821
+ */
822
+ "values"?: string[];
813
823
  }
814
824
  export interface LmvzSnackbar {
815
825
  "actionLabel"?: string;
@@ -1732,9 +1742,15 @@ export declare namespace JSX {
1732
1742
  * trigger drives an `lmvz-popover`-hosted `role="listbox"`/`role="option"` list via the
1733
1743
  * activedescendant model (the trigger keeps real DOM focus; `aria-activedescendant` tracks
1734
1744
  * the highlighted option).
1745
+ * Supports an opt-in multiselect mode (`multiple`): selection is tracked in `values` (an array)
1746
+ * instead of the single-select `value`, selecting an option toggles its membership without
1747
+ * closing the popup, and the closed trigger renders one `lmvz-chip` per selected value for
1748
+ * display only — removing a value is done by reselecting it in the open listbox, not from a
1749
+ * chip control.
1735
1750
  * Participates in native HTML forms via `ElementInternals` (form-associated custom element):
1736
- * the `name` attribute (set directly on the host element) and the current `value` are submitted,
1737
- * and the value resets to its initial value on form reset.
1751
+ * the `name` attribute (set directly on the host element) and the current `value` (or, in
1752
+ * multiselect mode, one form entry per selected `values` entry) are submitted, and selection
1753
+ * resets to its initial state on form reset.
1738
1754
  * @example ```html
1739
1755
  * <lmvz-select label="Country" name="country">
1740
1756
  * <option value="ch">Switzerland</option>
@@ -1770,23 +1786,23 @@ export declare namespace JSX {
1770
1786
  * Helper text displayed below the select field.
1771
1787
  */
1772
1788
  "helperText"?: string;
1773
- /**
1774
- * Whether the select has an active filter or selection that should be visually distinguished (e.g. a filter chip is applied). When `true`: - The host attribute `highlighted` is present (CSS: `:host([highlighted])`). - The host attribute `data-highlighted="true"` is set for assistive-technology consumers that observe data attributes. When `false`, `data-highlighted` is set to `"false"`. Note: `aria-pressed` is intentionally not used here; it is reserved for toggle-button roles. `aria-selected` is reserved for this component's own `role="option"` elements. The `data-highlighted` attribute provides the accessible contract for this state.
1775
- * @default false
1776
- */
1777
- "highlighted"?: boolean;
1778
1789
  /**
1779
1790
  * Label text displayed as a placeholder when no value is selected, and floated above the trigger when a value is present.
1780
1791
  */
1781
1792
  "label": string;
1793
+ /**
1794
+ * Enables multiselect mode. When `true`, `values` becomes the sole selection source (`value` is not read or written by the component), options toggle membership on selection instead of replacing a single value, the popup stays open after selecting/deselecting an option, and the closed trigger renders one `lmvz-chip` per selected value for display only. Removing a value is done by reselecting it in the open listbox (via `handleOptionSelect`'s toggle-off path), not from a control on the chip itself.
1795
+ * @default false
1796
+ */
1797
+ "multiple"?: boolean;
1782
1798
  /**
1783
1799
  * Name attribute for form submission via ElementInternals.
1784
1800
  */
1785
1801
  "name"?: string;
1786
1802
  /**
1787
- * Emitted when the user selects a new option. Detail contains the new value.
1803
+ * Emitted when the user changes the selection. Detail contains the new value: a `string` in single-select mode (`multiple` false/unset — same shape as before), or the full updated `string[]` of selected values in multiselect mode (`multiple` true).
1788
1804
  */
1789
- "onLmvzChange"?: (event: LmvzSelectCustomEvent<string>) => void;
1805
+ "onLmvzChange"?: (event: LmvzSelectCustomEvent<string | string[]>) => void;
1790
1806
  /**
1791
1807
  * Whether a value is required.
1792
1808
  * @default false
@@ -1798,9 +1814,13 @@ export declare namespace JSX {
1798
1814
  */
1799
1815
  "size"?: Select.Size;
1800
1816
  /**
1801
- * Currently selected value.
1817
+ * Currently selected value. Ignored (not read or written) when `multiple` is `true` — use `values` instead.
1802
1818
  */
1803
1819
  "value"?: string;
1820
+ /**
1821
+ * Currently selected values in multiselect mode (`multiple` true). Not attribute-reflected; set/read as a JS property. Preselection collects every enabled `<option selected>` (not just the first) into the initial value when unset.
1822
+ */
1823
+ "values"?: string[];
1804
1824
  }
1805
1825
  export interface LmvzSnackbar {
1806
1826
  "actionLabel"?: string;
@@ -2087,6 +2107,7 @@ export declare namespace JSX {
2087
2107
  }
2088
2108
  export interface LmvzSelectAttributes {
2089
2109
  "value": string;
2110
+ "multiple": boolean;
2090
2111
  "label": string;
2091
2112
  "helperText": string;
2092
2113
  "error": boolean;
@@ -2095,7 +2116,6 @@ export declare namespace JSX {
2095
2116
  "required": boolean;
2096
2117
  "name": string;
2097
2118
  "size": Select.Size;
2098
- "highlighted": boolean;
2099
2119
  }
2100
2120
  export interface LmvzSnackbarAttributes {
2101
2121
  "status": Snackbar.Status;
package/types/index.d.ts CHANGED
@@ -5,3 +5,8 @@ export type { LmvzDS, Typography } from './api';
5
5
  export * from './components/lmvz-message/public';
6
6
  export * from './components/lmvz-snackbar/public';
7
7
  export * from './utils/public';
8
+ declare global {
9
+ interface Window {
10
+ LmvzApi: LmvzApi;
11
+ }
12
+ }
@@ -10,17 +10,17 @@ export type DirectionalFocusHost = FocusHostExtensions & {
10
10
  type FocusHostExtensions = {
11
11
  initFocus: DirectionalFocusController['initFocus'];
12
12
  };
13
- export declare function isFocusControllerHost(el: HTMLElement): el is HTMLElement & FocusHostExtensions;
14
13
  export type DirectionalFocusReceiver = {
15
14
  resolveFocusableElement(): HTMLElement | undefined;
16
15
  };
17
16
  export type DirectionalFocusOrientation = 'horizontal' | 'vertical' | 'both';
18
17
  export type DirectionalFocusControllerConfig = {
19
- orientation?: DirectionalFocusOrientation;
20
- enableHomeAndEnd?: boolean;
21
- manageTabIndex?: boolean;
18
+ orientation: DirectionalFocusOrientation;
19
+ enableHomeAndEnd: boolean;
20
+ manageTabIndex: boolean;
22
21
  onFocusTargetChange?: (element: HTMLElement) => void;
23
22
  isFocusableElement?: (element: HTMLElement) => boolean;
23
+ listenerTarget?: HTMLElement;
24
24
  };
25
25
  export declare class DirectionalFocusController implements ReactiveController {
26
26
  private readonly host;
@@ -30,6 +30,8 @@ export declare class DirectionalFocusController implements ReactiveController {
30
30
  constructor(host: DirectionalFocusHost, config?: DirectionalFocusControllerConfig);
31
31
  hostConnected(): void;
32
32
  hostDisconnected(): void;
33
+ updateListenerTarget(listenerTarget: HTMLElement): void;
34
+ private getListenerTarget;
33
35
  updateElements(elements: Element[]): void;
34
36
  setCurrentElement(element: Element | undefined | null): void;
35
37
  initFocus(): Promise<void>;
@@ -44,6 +46,6 @@ export declare class DirectionalFocusController implements ReactiveController {
44
46
  private applyFocusTarget;
45
47
  private canMoveFocusTo;
46
48
  }
47
- export type ListKeyboardNavigationHost = DirectionalFocusHost;
48
- export type ListKeyboardNavigationControllerConfig = DirectionalFocusControllerConfig;
49
- export { DirectionalFocusController as ListKeyboardNavigationController };
49
+ export declare function isFocusControllerHost(el: HTMLElement): el is HTMLElement & FocusHostExtensions;
50
+ export declare function isDirectionalFocusReceiver(el: HTMLElement): el is HTMLElement & DirectionalFocusReceiver;
51
+ export {};
@@ -1,3 +1,9 @@
1
+ import { typedIconFromSet } from './icons.js';
1
2
  export type { IconSetNameMap } from '@lmvz-ds/lib-icon-api';
2
3
  export { getRegisteredIconProvider, registerIconProvider } from './icons-registry.js';
3
4
  export { typedIconFromSet } from './icons.js';
5
+ declare global {
6
+ interface LmvzApi {
7
+ typedIconFromSet?: typeof typedIconFromSet;
8
+ }
9
+ }
@@ -1 +0,0 @@
1
- import{p as i,h as e,d as o,t as n}from"./p-BPYBmylI.js";import{t,e as s,p as c}from"./p-DjvtQE7c.js";import{A as r}from"./p-BAj4FkUt.js";import{R as a}from"./p-diOze8wI.js";const l=Symbol.for("LMVZ__iconProviderRegistry"),m={};function v(){return"undefined"==typeof window?m:window[l]??(window[l]={})}function d(i,e){const o=v(),n=i;Object.prototype.hasOwnProperty.call(o,n)&&console.warn(`Icon provider with name "${n}" is already registered. Overwriting existing provider.`),o[n]=e}function h(i){const e=v();if(i)return e[i];const o=Object.keys(e);if(0===o.length)return;o.length>1&&console.warn(`Multiple icon providers registered (${o.join(", ")}). Using the first one: "${o[0]}".`);const n=o[0];return n?e[n]:void 0}function z(i,e){return{iconset:i,icon:e}}const p=i(class extends a{intersectionObserver;ariaValidationController=new r(this);get el(){return this}validationEl;icon;weight="medium";size="md";iconset;iconData;visible=!1;ariaLabel;get ariaHidden(){return!this.ariaLabel}constructor(i){super(!1),!1!==i&&this.__registerHost(),this.addController(this.ariaValidationController)}connectedCallback(){this.waitUntilVisible((()=>{this.visible=!0,this.loadIconPathData()})),super.connectedCallback()}disconnectedCallback(){this.intersectionObserver&&"function"==typeof this.intersectionObserver.disconnect&&this.intersectionObserver.disconnect(),this.intersectionObserver=void 0,super.disconnectedCallback()}async loadIconPathData(){const{icon:i,iconset:e,visible:o}=this;if(!i||!o)return;const n=c(i);this.iconData=n||await async function(i){const{icon:e,iconset:o}=i;return await(async()=>{const i=h(o);if(i)try{const n=await Promise.resolve(i.resolve(e));if(!n?.svg)return void console.warn(`Icon "${e}"${o?` from set "${o}"`:""} not found in provider. Using default icon.`);const s=t(n.svg);if(s)return s}catch(i){return void console.error(`Error resolving icon "${e}" with provider:`,i)}else console.warn(`No icon provider registered. Unable to resolve icon "${e}"${o?` from set "${o}"`:""}. Using default icon.`)})()||(console.error(`Icon "${e}" not found${o?` in set "${o}"`:""}. Using default icon.`),s())}({icon:i,iconset:e})}componentDidRender(){this.validationEl=this.el.querySelector("svg"),this.ariaValidationController.revalidateAria(),super.componentDidRender()}render(){return e(o,{key:"be6032d11399289c3baddd63ebc7d9e1fb98b773",role:"img","aria-hidden":`${this.ariaHidden}`,innerHTML:this.iconData})}waitUntilVisible(i,e=50){"undefined"!=typeof window&&window.IntersectionObserver?(this.intersectionObserver=new IntersectionObserver((e=>{e.some((e=>!!e.isIntersecting&&(this.intersectionObserver&&"function"==typeof this.intersectionObserver.disconnect&&this.intersectionObserver.disconnect(),this.intersectionObserver=void 0,i(),!0)))}),{rootMargin:`${e}px`}),this.intersectionObserver.observe(this.el)):i()}static get watchers(){return{icon:[{loadIconPathData:0}],iconset:[{loadIconPathData:0}]}}static get style(){return".sc-lmvz-icon-h{--lmvz-icon-color:var(--lmvz-component-color, var(--lmvz-semantic-color-on-surface-input-primary, #000000));--lmvz-icon-size:var(--lmvz-component-size, var(--lmvz-component-icon-size-md, clamp(1rem, 0.94rem + 0.26vw, 1.25rem)));display:inline-block;line-height:0;svg{display:block;height:var(--lmvz-icon-size);width:auto}svg path{stroke:var(--lmvz-icon-color);fill:none}}[size='xs'].sc-lmvz-icon-h{svg{--lmvz-icon-size:var(--lmvz-component-icon-size-xs, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem))}}[size='sm'].sc-lmvz-icon-h{svg{--lmvz-icon-size:var(--lmvz-component-icon-size-sm, clamp(0.88rem, 0.84rem + 0.13vw, 1rem))}}[size='md'].sc-lmvz-icon-h{svg{--lmvz-icon-size:var(--lmvz-component-icon-size-md, clamp(1rem, 0.94rem + 0.26vw, 1.25rem))}}[size='lg'].sc-lmvz-icon-h{svg{--lmvz-icon-size:var(--lmvz-component-icon-size-lg, clamp(1.25rem, 1.19rem + 0.26vw, 1.5rem))}}[size='inherit'].sc-lmvz-icon-h{svg{height:var(--lmvz-component-size, inherit)}}[weight='light'].sc-lmvz-icon-h{svg path{stroke-width:1}}[weight='medium'].sc-lmvz-icon-h{svg path{stroke-width:1.5}}[weight='bold'].sc-lmvz-icon-h{svg path{stroke-width:2}}[weight='filled'].sc-lmvz-icon-h{svg path{stroke-width:2;fill:var(--lmvz-icon-color)}}"}},[514,"lmvz-icon",{icon:[513],weight:[513],size:[513],iconset:[513],ariaLabel:[513,"aria-label"],iconData:[32],visible:[32]},void 0,{icon:[{loadIconPathData:0}],iconset:[{loadIconPathData:0}]}]);function u(){"undefined"!=typeof customElements&&["lmvz-icon"].forEach((i=>{"lmvz-icon"===i&&(customElements.get(n(i))||customElements.define(n(i),p))}))}export{p as L,u as d,h as g,d as r,z as t}