@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
@@ -39,7 +39,6 @@ export class LmvzPopover {
39
39
  }
40
40
  componentDidLoad() {
41
41
  autoAssignId(this.el);
42
- this.syncContainerId();
43
42
  }
44
43
  handleOpenChange(isOpen) {
45
44
  if (isOpen) {
@@ -66,23 +65,16 @@ export class LmvzPopover {
66
65
  this.removeCloseHandlers();
67
66
  this.extEventController.abort();
68
67
  }
69
- syncContainerId() {
70
- const hostId = this.el.getAttribute('id');
71
- if (!hostId || !this.containerEl)
72
- return;
73
- this.containerEl.id = hostId;
74
- }
75
68
  setupAnchorEl() {
76
69
  const { anchorEl } = this;
77
70
  if (!anchorEl) {
78
71
  console.warn('no anchor element set for', this);
79
72
  return;
80
73
  }
81
- this.syncContainerId();
82
74
  if (!anchorEl.hasAttribute('aria-haspopup')) {
83
75
  anchorEl.setAttribute('aria-haspopup', this.containerRole === 'dialog' ? 'dialog' : this.containerRole);
84
76
  }
85
- anchorEl.setAttribute('aria-controls', this.containerEl?.id || this.el.getAttribute('id'));
77
+ anchorEl.setAttribute('aria-controls', this.el.getAttribute('id'));
86
78
  if (!anchorEl.hasAttribute('aria-expanded')) {
87
79
  anchorEl.setAttribute('aria-expanded', this.open ? 'true' : 'false');
88
80
  }
@@ -110,7 +102,6 @@ export class LmvzPopover {
110
102
  await waitFrame();
111
103
  await this.updatePosition();
112
104
  autoAssignId(this.el);
113
- this.syncContainerId();
114
105
  this.applyAnchorAriaOpen();
115
106
  this.lmvzOpen.emit();
116
107
  await waitFrame();
@@ -189,7 +180,7 @@ export class LmvzPopover {
189
180
  this.closureController?.abort();
190
181
  }
191
182
  render() {
192
- return (h(Host, { key: '8dadcc766deebc03ea744fd1002078b382bfd48d' }, h("div", { key: 'd332714964212db6f5446afb1b8f38ff031a87b9', role: this.containerRole, part: "container", class: `container lmvz-wrapper-card-${this.size}`, "aria-label": this.label || undefined, ref: (el) => (this.containerEl = el) }, h("slot", { key: '0fa1d214c8c48942751cbd4759ebc992eacc8f72', ref: (el) => (this.slotEl = el) }))));
183
+ return (h(Host, { key: '83529aeac05cdc706193fa8bbbcc8f8672756111', role: this.containerRole, "aria-label": this.label || undefined }, h("div", { key: '97c255e788806c0dec2f4ed04c6fc815150912a2', part: "container", class: `container lmvz-wrapper-card-${this.size}`, ref: (el) => (this.containerEl = el) }, h("slot", { key: 'e4ea69d179557e7d34f1475c07c9284ea8d10a67', ref: (el) => (this.slotEl = el) }))));
193
184
  }
194
185
  static get is() { return "lmvz-popover"; }
195
186
  static get encapsulation() { return "shadow"; }
@@ -60,6 +60,8 @@
60
60
  --lmvz-select-helper-color: var(--lmvz-semantic-color-on-surface-input-secondary, #545454);
61
61
  --lmvz-select-focus-color: var(--lmvz-semantic-color-status-on-active, #0e7ab4);
62
62
  --lmvz-select-error-color: var(--lmvz-semantic-color-status-on-danger, #a31419);
63
+ /* Sizes the trailing chevron icon, shared by single- and multiselect modes. */
64
+ --lmvz-select-chevron-size: var(--lmvz-component-icon-size-2xs, clamp(0.63rem, 0.59rem + 0.13vw, 0.75rem));
63
65
 
64
66
  padding-top: var(--lmvz-internal-select-floating-label-top-offset);
65
67
  }
@@ -124,24 +126,12 @@ label > span[aria-hidden='true'],
124
126
  z-index: 1;
125
127
  transition: border-color 0.15s ease;
126
128
  }
127
- :host(:not([disabled], [highlighted], [error])) .select-wrapper:hover .select-trigger {
129
+ :host(:not([disabled], [error])) .select-wrapper:hover .select-trigger {
128
130
  border-color: var(--lmvz-select-border-color-hover);
129
131
  }
130
132
  :host([error]) .select-trigger {
131
133
  border-color: var(--lmvz-select-error-color);
132
134
  }
133
- :host([highlighted]) .select-trigger {
134
- background-color: var(--lmvz-semantic-color-status-active-alternate, #0f8acc);
135
- border-color: var(--lmvz-semantic-color-border-active, #0f8acc);
136
- color: var(--lmvz-semantic-color-status-on-active-alternate, #ffffff);
137
- }
138
- :host([highlighted]) .select-trigger > span:first-child {
139
- color: var(--lmvz-semantic-color-status-on-active-alternate, #ffffff);
140
- }
141
- :host([highlighted]) label.floating-label {
142
- background-color: var(--lmvz-semantic-color-status-active-alternate, #0f8acc);
143
- color: var(--lmvz-semantic-color-status-on-active-alternate, #ffffff);
144
- }
145
135
  :host:has(:focus-visible) .select-trigger {
146
136
  outline: var(--lmvz-ds-outline, 1px solid #0e7ab4);
147
137
  outline-offset: var(--lmvz-ds-outline-offset, clamp(0.25rem, 0.19rem + 0.26vw, 0.5rem));
@@ -156,21 +146,31 @@ label > span[aria-hidden='true'],
156
146
  white-space: nowrap;
157
147
  text-align: left;
158
148
  }
159
- /* Active-selected text color: when a value is selected (not applicable in highlighted state) */
160
- :host(:not([highlighted])) .select-wrapper.has-value .select-trigger > span:first-child {
149
+ .select-wrapper.has-value .select-trigger > span:first-child {
161
150
  color: var(--lmvz-semantic-color-int-on-secondary-active, #000000);
162
151
  }
163
- .select-trigger > span:last-child {
152
+ /* Trailing chevron child span, shared by single- and multiselect modes. Scoped with the
153
+ adjacent-sibling combinator (rather than :last-child) so it never matches the leading text/chip
154
+ span. */
155
+ .select-trigger > span:first-child + span {
164
156
  flex-shrink: 0;
165
157
  display: flex;
166
158
  align-items: center;
167
159
  justify-content: center;
168
160
  }
169
161
  .select-trigger svg {
170
- width: var(--lmvz-component-icon-size-2xs, clamp(0.63rem, 0.59rem + 0.13vw, 0.75rem));
171
- height: var(--lmvz-component-icon-size-2xs, clamp(0.63rem, 0.59rem + 0.13vw, 0.75rem));
162
+ width: var(--lmvz-select-chevron-size);
163
+ height: var(--lmvz-select-chevron-size);
172
164
  flex-shrink: 0;
173
165
  }
166
+ /* Chip row — rendered inline inside the trigger's leading text span (.select-trigger >
167
+ span:first-child). Overflowing chips are truncated with an ellipsis to enforce single line. */
168
+ .select-trigger > span:first-child lmvz-chip {
169
+ vertical-align: middle;
170
+ }
171
+ .select-trigger > span:first-child lmvz-chip + lmvz-chip {
172
+ margin-inline-start: var(--lmvz-select-gap);
173
+ }
174
174
  /* Native <option>/<optgroup> markup is only a data source for the internal listbox; never shown. */
175
175
  .lmvz-select-native-options {
176
176
  display: none;
@@ -199,61 +199,84 @@ label > span[aria-hidden='true'],
199
199
  --lmvz-select-padding-x: var(--lmvz-component-input-padding-sm, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem));
200
200
  --lmvz-select-gap: var(--lmvz-component-input-gap-sm, clamp(0.38rem, 0.31rem + 0.26vw, 0.63rem));
201
201
  --lmvz-select-label-font-size: var(--lmvz-component-body-2xs-font-size, clamp(0.5rem, 0.47rem + 0.13vw, 0.63rem));
202
+ --lmvz-select-chevron-size: var(--lmvz-component-icon-size-4xs, clamp(0.5rem, 0.47rem + 0.13vw, 0.63rem));
202
203
  }
203
204
  :host([size='small']) .select-trigger > span:first-child {
204
205
  font-size: var(--lmvz-component-body-sm-font-size, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem));
205
206
  }
206
- :host([size='small']) .select-trigger svg {
207
- width: var(--lmvz-component-icon-size-4xs, clamp(0.5rem, 0.47rem + 0.13vw, 0.63rem));
208
- height: var(--lmvz-component-icon-size-4xs, clamp(0.5rem, 0.47rem + 0.13vw, 0.63rem));
209
- }
210
207
  :host([size='large']) {
211
208
  --lmvz-select-height: var(--lmvz-component-input-size-lg, clamp(2.75rem, 2.69rem + 0.26vw, 3rem));
212
209
  --lmvz-select-padding-x: var(--lmvz-component-input-padding-lg, clamp(1rem, 0.94rem + 0.26vw, 1.25rem));
213
210
  --lmvz-select-gap: var(--lmvz-component-input-gap-lg, clamp(0.75rem, 0.69rem + 0.26vw, 1rem));
214
211
  --lmvz-select-label-font-size: var(--lmvz-component-body-sm-font-size, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem));
212
+ --lmvz-select-chevron-size: var(--lmvz-component-icon-size-xs, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem));
215
213
  }
216
214
  :host([size='large']) .select-trigger > span:first-child {
217
215
  font-size: var(--lmvz-component-body-lg-font-size, clamp(1rem, 0.97rem + 0.13vw, 1.13rem));
218
216
  }
219
- :host([size='large']) .select-trigger svg {
220
- width: var(--lmvz-component-icon-size-xs, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem));
221
- height: var(--lmvz-component-icon-size-xs, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem));
222
- }
223
- /*
224
- * Option-list item styling for the `role="option"` elements rendered inside the
225
- * `lmvz-popover` (containerRole="listbox") popup. The token set mirrors the states
226
- * `lmvz-action-list` applies to its `<lmvz-button>`/`<button>` items (tertiary variant),
227
- * so the listbox looks visually identical to an action-list item:
228
- * - default: --lmvz-semantic-color-int-tertiary / --lmvz-semantic-color-int-on-tertiary
229
- * - hover: --lmvz-semantic-color-int-tertiary-hover / --lmvz-semantic-color-int-on-tertiary-hover
230
- * - active: --lmvz-semantic-color-int-tertiary-active / --lmvz-semantic-color-int-on-tertiary-active
231
- * - disabled: --lmvz-component-input-disabled-opacity (opacity, same token as lib-styles buttons fragment)
232
- * - focus: --lmvz-ds-outline / --lmvz-ds-outline-offset (same as lib-styles buttons fragment focus-visible)
233
- * "selected" has no action-list equivalent (action-list is a menu, not a listbox), so it
234
- * reuses this component's own highlighted-state tokens for consistency with the trigger.
235
- * "active" (`data-active="true"`) is this component's own addition: since the combobox uses
236
- * the activedescendant model, the highlighted option never receives real DOM focus, so
237
- * `:hover`/`:focus-visible` cannot indicate it to sighted keyboard users — `data-active` is
238
- * set by the host from `ListboxController`'s active id and styled the same as hover/focus.
239
- */
240
217
  .lmvz-select-option {
241
218
  --lmvz-select-option-background: var(--lmvz-semantic-color-int-tertiary, #ffffff);
242
219
  --lmvz-select-option-color: var(--lmvz-semantic-color-int-on-tertiary, #000000);
243
220
 
244
- display: flex;
221
+ /* The configuration here will match lmvz-action-list's items, though semantic states might differ intentionally */
222
+ --lmvz-button-background-override: var(--lmvz-select-option-background);
223
+ --lmvz-button-color-override: var(--lmvz-select-option-color);
224
+ --lmvz-button-radius-override: var(--lmvz-semantic-border-radius-md, 6px);
225
+ --lmvz-button-padding-inline-override: var(--lmvz-component-input-padding-sm, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem));
226
+ --lmvz-button-padding-block-override: var(--lmvz-component-input-padding-sm, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem));
227
+ --lmvz-button-content-justification: normal;
228
+ --lmvz-button-font-override: var(--lmvz-typography-body-md-strong, 500 clamp(0.88rem, 0.84rem + 0.13vw, 1rem) / 1.5
229
+ Router);
230
+
231
+ /* the (invalid nested) import will be handled by postcss */
232
+ /* stylelint-disable-next-line no-invalid-position-at-import-rule */
233
+ /* stylelint-disable nesting-selector-no-missing-scoping-root */
234
+ & {
235
+ /* variables are declared on :host
236
+ so that outer ::slotted(lmvz-button) overrides are inherited correctly via shadow inheritance.
237
+ They are also declared here so that non-shadow consumers have the default values in scope. */
238
+
239
+ /* secondary styling is default, primary is explicit */
240
+
241
+ --lmvz-button-background: var(--lmvz-button-background-override, var(--lmvz-semantic-color-int-secondary, #f0f0f0));
242
+ --lmvz-button-color: var(--lmvz-button-color-override, var(--lmvz-semantic-color-int-on-secondary, #000000));
243
+ --lmvz-button-radius: var(--lmvz-button-radius-override, var(--lmvz-component-input-radius-default, 999px));
244
+ --lmvz-button-padding-inline: var(--lmvz-button-padding-inline-override, var(--lmvz-button-padding, var(--lmvz-component-input-padding-md, clamp(0.88rem, 0.84rem + 0.13vw, 1rem))));
245
+ --lmvz-button-padding-block: var(--lmvz-button-padding-block-override, var(--lmvz-button-padding, var(--lmvz-component-input-padding-md, clamp(0.88rem, 0.84rem + 0.13vw, 1rem))));
246
+ --lmvz-button-gap: var(--lmvz-button-gap-override, var(--lmvz-component-input-gap-md, clamp(0.5rem, 0.44rem + 0.26vw, 0.75rem)));
247
+ --lmvz-button-min-height: var(--lmvz-button-min-height-override, var(--lmvz-component-input-size-md, clamp(2.5rem, 2.44rem + 0.26vw, 2.75rem)));
248
+ --lmvz-button-font: var(--lmvz-button-font-override, var(--lmvz-typography-label-md-strong, normal 700 clamp(0.88rem, 0.84rem + 0.13vw, 1rem) / 1.5
249
+ Router));
250
+ --lmvz-button-border-width: var(--lmvz-button-border-width-override, 0);
251
+ --lmvz-button-border-color: var(--lmvz-button-border-color-override, transparent);
252
+
253
+ display: inline-flex;
245
254
  align-items: center;
246
- gap: var(--lmvz-select-gap);
247
- padding: var(--lmvz-component-input-padding-sm, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem));
248
- border-radius: var(--lmvz-semantic-border-radius-md, 6px);
249
- background-color: var(--lmvz-select-option-background);
250
- color: var(--lmvz-select-option-color);
255
+ justify-content: var(--lmvz-button-content-justification, center);
256
+ gap: var(--lmvz-button-gap, var(--lmvz-component-input-gap-md, clamp(0.5rem, 0.44rem + 0.26vw, 0.75rem)));
257
+
258
+ box-sizing: border-box; /* to inlclude paddings & border in min-height */
259
+ padding-block: var(--lmvz-button-padding-block, var(--lmvz-component-input-padding-md, clamp(0.88rem, 0.84rem + 0.13vw, 1rem)));
260
+ padding-inline: var(--lmvz-button-padding-inline, var(--lmvz-component-input-padding-md, clamp(0.88rem, 0.84rem + 0.13vw, 1rem)));
261
+ min-height: var(--lmvz-button-min-height, var(--lmvz-component-input-size-md, clamp(2.5rem, 2.44rem + 0.26vw, 2.75rem)));
262
+ border: var(--lmvz-button-border-width, 0) solid var(--lmvz-button-border-color, transparent);
263
+ border-radius: var(--lmvz-button-radius, var(--lmvz-component-input-radius-default, 999px));
264
+
265
+ background-color: var(--lmvz-button-background);
266
+ color: var(--lmvz-button-color);
251
267
  cursor: pointer;
252
- font: var(--lmvz-typography-body-md-strong, 500 clamp(0.88rem, 0.84rem + 0.13vw, 1rem) / 1.5
253
- Router);
268
+ font: var(--lmvz-button-font);
269
+ text-decoration: none;
270
+ white-space: nowrap;
254
271
  transition:
255
272
  background-color 0.15s ease,
256
- color 0.15s ease;
273
+ color 0.15s ease,
274
+ border-color 0.15s ease,
275
+ box-shadow 0.15s ease;
276
+ }
277
+
278
+
279
+ display: flex;
257
280
  }
258
281
  .lmvz-select-option:not([aria-disabled='true']):hover {
259
282
  --lmvz-select-option-background: var(--lmvz-semantic-color-int-tertiary-hover, #f0f0f0);
@@ -273,7 +296,7 @@ label > span[aria-hidden='true'],
273
296
  --lmvz-select-option-background: var(--lmvz-semantic-color-int-tertiary-hover, #f0f0f0);
274
297
  --lmvz-select-option-color: var(--lmvz-semantic-color-int-on-tertiary-hover, #000000);
275
298
  }
276
- .lmvz-select-option[aria-selected='true'] {
299
+ :host(:not([multiple])) .lmvz-select-option[aria-selected='true'] {
277
300
  --lmvz-select-option-background: var(--lmvz-semantic-color-status-active-alternate, #0f8acc);
278
301
  --lmvz-select-option-color: var(--lmvz-semantic-color-status-on-active-alternate, #ffffff);
279
302
  }
@@ -282,3 +305,29 @@ label > span[aria-hidden='true'],
282
305
  pointer-events: none;
283
306
  opacity: var(--lmvz-component-input-disabled-opacity, 40%);
284
307
  }
308
+ /*
309
+ * Decorative, aria-hidden checkbox-style indicator shown per option in multiselect mode.
310
+ * Mirrors lmvz-checkbox's box/indicator sizing tokens for visual consistency across the DS.
311
+ * `aria-selected` on the option (above) remains the real, sole selection semantic; this is
312
+ * purely a visual affordance.
313
+ */
314
+ .lmvz-select-option-checkbox {
315
+ display: flex;
316
+ align-items: center;
317
+ justify-content: center;
318
+ width: var(--lmvz-global-s18, 18px);
319
+ height: var(--lmvz-global-s18, 18px);
320
+ border: var(--lmvz-semantic-border-width-default, 1px) solid currentcolor;
321
+ border-radius: var(--lmvz-global-s4, 4px);
322
+ flex-shrink: 0;
323
+ }
324
+ .lmvz-select-option-checkbox-indicator {
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ line-height: 0;
329
+ }
330
+ .lmvz-select-option-checkbox-indicator svg {
331
+ width: 100%;
332
+ height: 100%;
333
+ }
@@ -1,8 +1,11 @@
1
- import { h, Host, } from "@stencil/core";
1
+ import checkmark from "@lmvz-ds/icons/assets/checkmark.svg";
2
+ import { toValidSvgStringWithFallback } from "@lmvz-ds/lib-ts/validation/svg.js";
3
+ import { Fragment, h, Host } from "@stencil/core";
2
4
  import { AriaValidationController } from "../../utils/aria/aria-validation-controller";
3
5
  import { ListboxController } from "../../utils/aria/listbox-controller";
4
6
  import { ReactiveControllerHost } from "../../utils/stencil/reactive-controller-host";
5
7
  let selectIdCounter = 0;
8
+ const CHECKMARK_SVG = toValidSvgStringWithFallback(checkmark);
6
9
  export class LmvzSelect extends ReactiveControllerHost {
7
10
  el;
8
11
  get validationEl() {
@@ -17,6 +20,8 @@ export class LmvzSelect extends ReactiveControllerHost {
17
20
  optionsMutationObserver;
18
21
  initialValue;
19
22
  initialValueCaptured = false;
23
+ initialValues;
24
+ initialValuesCaptured = false;
20
25
  flatOptions = [];
21
26
  optionsById = new Map();
22
27
  listboxController = new ListboxController({
@@ -50,6 +55,8 @@ export class LmvzSelect extends ReactiveControllerHost {
50
55
  }
51
56
  lmvzChange;
52
57
  value;
58
+ multiple = false;
59
+ values;
53
60
  label;
54
61
  helperText;
55
62
  nativeError = false;
@@ -65,14 +72,23 @@ export class LmvzSelect extends ReactiveControllerHost {
65
72
  required = false;
66
73
  name;
67
74
  size = 'default';
68
- highlighted = false;
69
75
  selectedLabel = '';
70
76
  selectedId;
71
77
  activeId;
72
78
  isOpen = false;
73
79
  optionGroups = [];
74
80
  get hasValue() {
75
- return Boolean(this.value);
81
+ return this.multiple ? Boolean(this.values && this.values.length > 0) : Boolean(this.value);
82
+ }
83
+ get triggerAriaLabel() {
84
+ if (this.multiple) {
85
+ const count = this.values?.length ?? 0;
86
+ return count > 0 ? `${this.label}, ${count} selected` : this.label;
87
+ }
88
+ return this.hasValue ? `${this.label}, ${this.selectedLabel}` : this.label;
89
+ }
90
+ get chipSize() {
91
+ return this.size === 'small' ? 'small' : 'default';
76
92
  }
77
93
  componentWillLoad() {
78
94
  this.updateValidity();
@@ -119,17 +135,28 @@ export class LmvzSelect extends ReactiveControllerHost {
119
135
  formAssociatedCallback(form) {
120
136
  if (!form)
121
137
  return;
122
- this.internals.setFormValue?.(this.value ?? '');
138
+ this.syncFormValue();
123
139
  }
124
140
  formResetCallback() {
125
141
  this.internals.setValidity?.({});
126
- this.value = this.initialValue;
142
+ if (this.multiple) {
143
+ this.values = this.initialValues ? [...this.initialValues] : [];
144
+ }
145
+ else {
146
+ this.value = this.initialValue;
147
+ }
127
148
  this.updateValidity();
128
149
  }
129
150
  formStateRestoreCallback(state) {
151
+ if (this.multiple) {
152
+ this.values = state instanceof FormData && this.name ? state.getAll(this.name).map(String) : [];
153
+ return;
154
+ }
130
155
  this.value = state;
131
156
  }
132
157
  handleValueChange(newValue) {
158
+ if (this.multiple)
159
+ return;
133
160
  this.applySelectionFromValue();
134
161
  if (this.selectedId && !this.isOpen) {
135
162
  this.listboxController.setActiveId(this.selectedId);
@@ -137,6 +164,12 @@ export class LmvzSelect extends ReactiveControllerHost {
137
164
  this.internals.setFormValue?.(newValue ?? '');
138
165
  this.updateValidity();
139
166
  }
167
+ handleValuesChange() {
168
+ if (!this.multiple)
169
+ return;
170
+ this.syncFormValue();
171
+ this.updateValidity();
172
+ }
140
173
  handleRequiredChange() {
141
174
  this.updateValidity();
142
175
  }
@@ -154,15 +187,31 @@ export class LmvzSelect extends ReactiveControllerHost {
154
187
  this.flatOptions = flat;
155
188
  this.optionsById = new Map(flat.map((option) => [option.id, option]));
156
189
  this.optionGroups = groups;
157
- if (!this.value) {
158
- const preselected = flat.find((option) => option.selected && !option.disabled);
159
- if (preselected) {
160
- this.value = preselected.value;
190
+ if (this.multiple) {
191
+ if (!this.values) {
192
+ const preselected = flat.filter((option) => option.selected && !option.disabled).map((option) => option.value);
193
+ if (preselected.length > 0) {
194
+ this.values = preselected;
195
+ }
161
196
  }
162
197
  }
163
- this.applySelectionFromValue();
198
+ else {
199
+ if (!this.value) {
200
+ const preselected = flat.find((option) => option.selected && !option.disabled);
201
+ if (preselected) {
202
+ this.value = preselected.value;
203
+ }
204
+ }
205
+ this.applySelectionFromValue();
206
+ }
164
207
  this.listboxController.setOptions(flat.map(({ id, label, disabled }) => ({ id, label, disabled })));
165
- if (!this.initialValueCaptured) {
208
+ if (this.multiple) {
209
+ if (!this.initialValuesCaptured) {
210
+ this.initialValues = this.values ? [...this.values] : [];
211
+ this.initialValuesCaptured = true;
212
+ }
213
+ }
214
+ else if (!this.initialValueCaptured) {
166
215
  this.initialValue = this.value;
167
216
  this.initialValueCaptured = true;
168
217
  }
@@ -173,8 +222,28 @@ export class LmvzSelect extends ReactiveControllerHost {
173
222
  this.selectedLabel = match?.label ?? '';
174
223
  this.listboxController.setSelectedId(match?.id);
175
224
  }
225
+ isOptionSelected(option) {
226
+ if (this.multiple) {
227
+ return (this.values ?? []).includes(option.value);
228
+ }
229
+ return option.id === this.selectedId;
230
+ }
231
+ syncFormValue() {
232
+ if (this.multiple) {
233
+ const formData = new FormData();
234
+ if (this.name) {
235
+ for (const value of this.values ?? []) {
236
+ formData.append(this.name, value);
237
+ }
238
+ }
239
+ this.internals.setFormValue?.(formData);
240
+ return;
241
+ }
242
+ this.internals.setFormValue?.(this.value ?? '');
243
+ }
176
244
  updateValidity() {
177
- if (this.required && !this.value) {
245
+ const missing = this.multiple ? !(this.values && this.values.length > 0) : !this.value;
246
+ if (this.required && missing) {
178
247
  this.internals.setValidity?.({ valueMissing: true }, 'Please select a value.', this.triggerEl);
179
248
  return;
180
249
  }
@@ -188,6 +257,13 @@ export class LmvzSelect extends ReactiveControllerHost {
188
257
  const option = this.optionsById.get(id);
189
258
  if (!option)
190
259
  return;
260
+ if (this.multiple) {
261
+ const current = this.values ?? [];
262
+ const updated = current.includes(option.value) ? current.filter((value) => value !== option.value) : [...current, option.value];
263
+ this.values = updated;
264
+ this.lmvzChange.emit(updated);
265
+ return;
266
+ }
191
267
  const isNewValue = option.value !== this.value;
192
268
  this.value = option.value;
193
269
  if (isNewValue) {
@@ -250,12 +326,26 @@ export class LmvzSelect extends ReactiveControllerHost {
250
326
  return this.optionGroups.map((group, groupIndex) => group.label ? (h("div", { role: "group", "aria-label": group.label, key: `lmvz-select-group-${groupIndex}` }, group.options.map((option) => this.renderOption(option)))) : (group.options.map((option) => this.renderOption(option))));
251
327
  }
252
328
  renderOption(option) {
253
- return (h("div", { id: option.id, key: option.id, role: "option", class: "lmvz-select-option", "aria-selected": option.id === this.selectedId ? 'true' : 'false', "aria-disabled": option.disabled ? 'true' : undefined, "data-active": option.id === this.activeId ? 'true' : undefined, onMouseDown: (event) => event.preventDefault(), onClick: () => this.handleOptionClick(option) }, option.label));
329
+ const selected = this.isOptionSelected(option);
330
+ return (h("div", { id: option.id, key: option.id, role: "option", class: "lmvz-select-option", "aria-selected": selected ? 'true' : 'false', "aria-disabled": option.disabled ? 'true' : undefined, "data-active": option.id === this.activeId ? 'true' : undefined, onMouseDown: (event) => event.preventDefault(), onClick: () => this.handleOptionClick(option) }, this.multiple && (h("span", { class: "lmvz-select-option-checkbox", "aria-hidden": "true" }, selected && h("span", { class: "lmvz-select-option-checkbox-indicator", innerHTML: CHECKMARK_SVG }))), option.label));
331
+ }
332
+ renderChips() {
333
+ const values = this.values ?? [];
334
+ const occurrenceCounts = new Map();
335
+ return (h(Fragment, null, values.map((value) => {
336
+ const occurrence = occurrenceCounts.get(value) ?? 0;
337
+ occurrenceCounts.set(value, occurrence + 1);
338
+ const chipKey = occurrence === 0 ? value : `${value}-dup-${occurrence}`;
339
+ const option = this.flatOptions.find((candidate) => candidate.value === value);
340
+ const chipLabel = option?.label ?? value;
341
+ return (h("lmvz-chip", { key: chipKey, size: this.chipSize }, chipLabel));
342
+ })));
254
343
  }
255
344
  render() {
256
345
  const hasValue = this.hasValue;
257
346
  const shouldShowLabel = hasValue;
258
- return (h(Host, { key: '0bcc303a802db20a6be6558b8add53d0fa398493', "data-highlighted": String(this.highlighted) }, h("div", { key: 'e248f8277b4786b34dfd064cb9c7b70302cf018a', class: hasValue ? 'select-wrapper has-value' : 'select-wrapper' }, h("label", { key: '1c97c830d5782cadad98fb895d70c438031d3457', htmlFor: this.triggerId, class: shouldShowLabel ? 'floating-label' : 'assistive-label', onMouseDown: (event) => event.preventDefault(), onClick: this.handleLabelClick }, this.label, this.required && shouldShowLabel && h("span", { key: '16157962846bffc111f5c672852f7b23e9ed2b73', "aria-hidden": "true" }, " *")), h("div", { key: 'bc5e3e6b335723cfd9872ff955a1e8d664d4eeb8', id: this.triggerId, ref: (el) => (this.triggerEl = el), class: "select-trigger", role: "combobox", tabIndex: this.disabled ? -1 : 0, "aria-haspopup": "listbox", "aria-expanded": String(this.isOpen), "aria-controls": this.popoverId, "aria-activedescendant": this.isOpen ? this.activeId : undefined, "aria-label": hasValue ? `${this.label}, ${this.selectedLabel}` : this.label, "aria-disabled": this.disabled ? 'true' : undefined, "aria-required": this.required ? 'true' : 'false', "aria-invalid": this.error ? 'true' : 'false', "aria-errormessage": this.showErrorMessage ? this.errorId : undefined, "aria-describedby": this.describedBy, onClick: this.handleTriggerClick, onKeyDown: this.handleTriggerKeydown, onBlur: this.handleTriggerBlur }, h("span", { key: '620c6108f679c8e5563c0e21d7b89d71434c01a2' }, hasValue ? this.selectedLabel : this.label, this.required && !hasValue && h("span", { key: 'a2183941c7843bae3e9989f8e8b26be28fbbf6d7', "aria-hidden": "true" }, " *")), h("span", { key: 'b57e14bda44dc0de5a35623a5cb203b7f570c802', "aria-hidden": "true" }, h("svg", { key: '71ac319e398f07f7e65fe1f73f6d2a7c08b631a8', width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, h("path", { key: '80b4a978471251ee8d2cce2d74a48fad750db125', d: "M20.3334 8.66663L12.0001 17L3.66675 8.66663", stroke: "currentColor", "stroke-width": "1.75", "stroke-linecap": "round", "stroke-linejoin": "round" })))), h("lmvz-popover", { key: '15528c9a3f1404c385f4eb865cf78ef5891dcb32', id: this.popoverId, anchor: this.triggerId, open: this.isOpen, containerRole: "listbox", autoFocus: false, label: this.label, onLmvzClose: this.handlePopoverClose, size: "sm" }, this.renderOptionGroups()), h("div", { key: 'f1b833973497e6dfd2013522e067382c73baa7a1', class: "lmvz-select-native-options", hidden: true, "aria-hidden": "true", ref: (el) => (this.optionsSourceEl = el) }, h("slot", { key: '670bf62c50cbdd965342279b99e303614771ffa1', onSlotchange: this.handleSlotChange }))), this.helperText && (h("div", { key: '849e35da09bff25896b08f275c265baa0ee12960', id: this.helperId, role: "status" }, this.helperText)), this.showErrorMessage && (h("div", { key: 'd01c2dca8767aa815d08d895f308c42150cc3263', id: this.errorId, role: "alert" }, this.errorMessage))));
347
+ const showChips = this.multiple && hasValue;
348
+ return (h(Host, { key: '77cb72cdce635eae0f9a29cb9121dd7d16955219' }, h("div", { key: 'cc6c04090457df17c6a9e74354fc21875d378642', class: hasValue ? 'select-wrapper has-value' : 'select-wrapper' }, h("label", { key: '8dc52469a7b3333578494b2f7bdb4e870044ac6c', htmlFor: this.triggerId, class: shouldShowLabel ? 'floating-label' : 'assistive-label', onMouseDown: (event) => event.preventDefault(), onClick: this.handleLabelClick }, this.label, this.required && shouldShowLabel && h("span", { key: 'c198fb4944e46c5219a42e16ae24dc3c414b1b00', "aria-hidden": "true" }, " *")), h("div", { key: '034cc67405aac73a6557036dea90e4644c1578ed', id: this.triggerId, ref: (el) => (this.triggerEl = el), class: "select-trigger", role: "combobox", tabIndex: this.disabled ? -1 : 0, "aria-haspopup": "listbox", "aria-expanded": String(this.isOpen), "aria-controls": this.popoverId, "aria-activedescendant": this.isOpen ? this.activeId : undefined, "aria-label": this.triggerAriaLabel, "aria-disabled": this.disabled ? 'true' : undefined, "aria-required": this.required ? 'true' : 'false', "aria-invalid": this.error ? 'true' : 'false', "aria-errormessage": this.showErrorMessage ? this.errorId : undefined, "aria-describedby": this.describedBy, onClick: this.handleTriggerClick, onKeyDown: this.handleTriggerKeydown, onBlur: this.handleTriggerBlur }, h("span", { key: 'c61c136bfaab211572a988d3b16c2746f80b1ac6' }, showChips ? this.renderChips() : hasValue ? this.selectedLabel : this.label, this.required && !hasValue && h("span", { key: '24201baae63a4f6fdaef2441d304cd8f69430fb5', "aria-hidden": "true" }, " *")), h("span", { key: '547c61dfdc83f89cf7cd0582c785b9552190e533', "aria-hidden": "true" }, h("svg", { key: 'f6a0cb7c77682aec03cd146e84712c17df5ff910', width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, h("path", { key: '35a8a50e27f18e54003974ae5b5ed578602664d3', d: "M20.3334 8.66663L12.0001 17L3.66675 8.66663", stroke: "currentColor", "stroke-width": "1.75", "stroke-linecap": "round", "stroke-linejoin": "round" })))), h("lmvz-popover", { key: '0d5b94659c23763d59638046a397963d79a5e588', id: this.popoverId, anchor: this.triggerId, open: this.isOpen, containerRole: "listbox", autoFocus: false, label: this.label, "aria-multiselectable": this.multiple ? 'true' : undefined, onLmvzClose: this.handlePopoverClose, size: "sm" }, this.renderOptionGroups()), h("div", { key: 'c4a313fe4b6c53678a4f3dafced210f00e3c93d5', class: "lmvz-select-native-options", hidden: true, "aria-hidden": "true", ref: (el) => (this.optionsSourceEl = el) }, h("slot", { key: '1fb311e6938812e68ae5155184af2e2ba7c19861', onSlotchange: this.handleSlotChange }))), this.helperText && (h("div", { key: 'accade9aae4cb2e23fa1d8f62dd9b6efdf3ddfb5', id: this.helperId, role: "status" }, this.helperText)), this.showErrorMessage && (h("div", { key: 'faa5eba225adda67b44b8a63ae98a678fddf49ad', id: this.errorId, role: "alert" }, this.errorMessage))));
259
349
  }
260
350
  static get is() { return "lmvz-select"; }
261
351
  static get encapsulation() { return "scoped"; }
@@ -284,13 +374,53 @@ export class LmvzSelect extends ReactiveControllerHost {
284
374
  "optional": true,
285
375
  "docs": {
286
376
  "tags": [],
287
- "text": "Currently selected value."
377
+ "text": "Currently selected value. Ignored (not read or written) when `multiple` is `true` \u2014 use\n`values` instead."
288
378
  },
289
379
  "getter": false,
290
380
  "setter": false,
291
381
  "reflect": false,
292
382
  "attribute": "value"
293
383
  },
384
+ "multiple": {
385
+ "type": "boolean",
386
+ "mutable": false,
387
+ "complexType": {
388
+ "original": "boolean",
389
+ "resolved": "boolean",
390
+ "references": {}
391
+ },
392
+ "required": false,
393
+ "optional": false,
394
+ "docs": {
395
+ "tags": [{
396
+ "name": "default",
397
+ "text": "false"
398
+ }],
399
+ "text": "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."
400
+ },
401
+ "getter": false,
402
+ "setter": false,
403
+ "reflect": true,
404
+ "attribute": "multiple",
405
+ "defaultValue": "false"
406
+ },
407
+ "values": {
408
+ "type": "unknown",
409
+ "mutable": true,
410
+ "complexType": {
411
+ "original": "string[]",
412
+ "resolved": "string[] | undefined",
413
+ "references": {}
414
+ },
415
+ "required": false,
416
+ "optional": true,
417
+ "docs": {
418
+ "tags": [],
419
+ "text": "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."
420
+ },
421
+ "getter": false,
422
+ "setter": false
423
+ },
294
424
  "label": {
295
425
  "type": "string",
296
426
  "mutable": false,
@@ -461,29 +591,6 @@ export class LmvzSelect extends ReactiveControllerHost {
461
591
  "reflect": true,
462
592
  "attribute": "size",
463
593
  "defaultValue": "'default'"
464
- },
465
- "highlighted": {
466
- "type": "boolean",
467
- "mutable": false,
468
- "complexType": {
469
- "original": "boolean",
470
- "resolved": "boolean",
471
- "references": {}
472
- },
473
- "required": false,
474
- "optional": false,
475
- "docs": {
476
- "tags": [{
477
- "name": "default",
478
- "text": "false"
479
- }],
480
- "text": "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."
481
- },
482
- "getter": false,
483
- "setter": false,
484
- "reflect": true,
485
- "attribute": "highlighted",
486
- "defaultValue": "false"
487
594
  }
488
595
  };
489
596
  }
@@ -506,11 +613,11 @@ export class LmvzSelect extends ReactiveControllerHost {
506
613
  "composed": true,
507
614
  "docs": {
508
615
  "tags": [],
509
- "text": "Emitted when the user selects a new option. Detail contains the new value."
616
+ "text": "Emitted when the user changes the selection. Detail contains the new value:\na `string` in single-select mode (`multiple` false/unset \u2014 same shape as before), or the\nfull updated `string[]` of selected values in multiselect mode (`multiple` true)."
510
617
  },
511
618
  "complexType": {
512
- "original": "string",
513
- "resolved": "string",
619
+ "original": "string | string[]",
620
+ "resolved": "string | string[]",
514
621
  "references": {}
515
622
  }
516
623
  }];
@@ -609,6 +716,9 @@ export class LmvzSelect extends ReactiveControllerHost {
609
716
  return [{
610
717
  "propName": "value",
611
718
  "methodName": "handleValueChange"
719
+ }, {
720
+ "propName": "values",
721
+ "methodName": "handleValuesChange"
612
722
  }, {
613
723
  "propName": "required",
614
724
  "methodName": "handleRequiredChange"
@@ -1 +1,4 @@
1
+ import { SnackbarController } from "./snackbar-controller";
1
2
  export { SnackbarController } from './snackbar-controller';
3
+ window.LmvzApi ??= {};
4
+ window.LmvzApi.SnackbarController = SnackbarController;
@@ -7,10 +7,12 @@ export class LmvzTabs extends ReactiveControllerHost {
7
7
  instanceId = ++tabsIdCounter;
8
8
  tabSlotEl;
9
9
  panelSlotEl;
10
+ tablistEl;
10
11
  focusController = new DirectionalFocusController(this, {
11
12
  orientation: 'horizontal',
12
13
  manageTabIndex: true,
13
14
  enableHomeAndEnd: true,
15
+ listenerTarget: this.tablistEl,
14
16
  });
15
17
  value;
16
18
  orientation = 'horizontal';
@@ -29,6 +31,9 @@ export class LmvzTabs extends ReactiveControllerHost {
29
31
  this.panelSlotEl?.addEventListener('slotchange', this.handlePanelSlotChange);
30
32
  this.el.addEventListener('lmvzTabReady', this.handleTabReady);
31
33
  this.handleTabSlotChange();
34
+ if (this.tablistEl) {
35
+ this.focusController.updateListenerTarget(this.tablistEl);
36
+ }
32
37
  super.componentDidLoad();
33
38
  }
34
39
  disconnectedCallback() {
@@ -113,6 +118,7 @@ export class LmvzTabs extends ReactiveControllerHost {
113
118
  orientation: this.orientation,
114
119
  manageTabIndex: true,
115
120
  enableHomeAndEnd: true,
121
+ listenerTarget: this.tablistEl,
116
122
  });
117
123
  this.addController(this.focusController);
118
124
  if (this.el.isConnected) {
@@ -235,7 +241,7 @@ export class LmvzTabs extends ReactiveControllerHost {
235
241
  this.selectTab(value);
236
242
  };
237
243
  render() {
238
- return (h(Host, { key: '33205038c047420618800be3ef755e1d13a0127c', onClick: this.handleTabClick, onKeydown: this.handleKeydown }, h("div", { key: 'e803f772c5b187326fc80cfa2d3b9d74f6ee1df8', role: "tablist", "aria-orientation": this.orientation, class: "tablist" }, h("slot", { key: '6871a7b6b26c6251ceb544c03b245450aa53373d', ref: (el) => (this.tabSlotEl = el) })), h("div", { key: '17721962da2ecf107a5b878dea996812c61be998', part: "panels-container", class: "panels-container" }, h("slot", { key: '485de66efc7812ecb2368a6b01b0d8ab63971497', name: "panels", ref: (el) => (this.panelSlotEl = el) }))));
244
+ return (h(Host, { key: 'be4fe04b1374a2793d3b1e9a658a449474efdd03', onClick: this.handleTabClick, onKeydown: this.handleKeydown }, h("div", { key: 'd91015eddf16dfad5d49e942eb86e9f568978f34', role: "tablist", "aria-orientation": this.orientation, class: "tablist", ref: (el) => (this.tablistEl = el) }, h("slot", { key: 'be0990a7c668bc96a67c01561e3142ec028dabd0', ref: (el) => (this.tabSlotEl = el) })), h("div", { key: '338323738dfdbb436d86cdb4f6041a67c1af433d', part: "panels-container", class: "panels-container" }, h("slot", { key: 'd225be099c8cb902776ce0a7dbf7dcb2aef2d03b', name: "panels", ref: (el) => (this.panelSlotEl = el) }))));
239
245
  }
240
246
  static get is() { return "lmvz-tabs"; }
241
247
  static get encapsulation() { return "shadow"; }