@lmvz-ds/components 0.36.0 → 0.37.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 (157) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/cjs/aria.constants-DPK5mQEL.js +65 -0
  3. package/cjs/{stencil.util-TjS8_DoU.js → async.util-Dnl6c82X.js} +1 -13
  4. package/cjs/{directional-focus-controller-3uDoin45.js → directional-focus-controller-8Xd_7MHT.js} +1 -1
  5. package/cjs/{element-activation-controller-BWtUKzu2.js → element-activation-controller-DzTUocEO.js} +1 -1
  6. package/cjs/{element.util-CctmBvUY.js → element.util-BlhcwmfR.js} +2 -62
  7. package/cjs/lmvz-action-list.cjs.entry.js +4 -3
  8. package/cjs/lmvz-button-group.cjs.entry.js +2 -1
  9. package/cjs/lmvz-button_2.cjs.entry.js +4 -3
  10. package/cjs/lmvz-card.cjs.entry.js +1 -1
  11. package/cjs/lmvz-checkbox.cjs.entry.js +38 -4
  12. package/cjs/lmvz-components.cjs.js +1 -1
  13. package/cjs/lmvz-datepicker.cjs.entry.js +10 -3
  14. package/cjs/lmvz-header_2.cjs.entry.js +4 -3
  15. package/cjs/lmvz-input.cjs.entry.js +242 -0
  16. package/cjs/lmvz-menuitem.cjs.entry.js +3 -2
  17. package/cjs/lmvz-modal.cjs.entry.js +3 -2
  18. package/cjs/{lmvz-input_2.cjs.entry.js → lmvz-popover.cjs.entry.js} +24 -228
  19. package/cjs/lmvz-radio.cjs.entry.js +61 -3
  20. package/cjs/lmvz-select.cjs.entry.js +423 -16
  21. package/cjs/lmvz-snackbar.cjs.entry.js +1 -1
  22. package/cjs/lmvz-spinner.cjs.entry.js +1 -1
  23. package/cjs/lmvz-tab.cjs.entry.js +2 -2
  24. package/cjs/lmvz-tabs.cjs.entry.js +5 -4
  25. package/cjs/lmvz-toggle.cjs.entry.js +7 -3
  26. package/cjs/loader.cjs.js +1 -1
  27. package/cjs/stencil.util-CBrPLd-F.js +17 -0
  28. package/collection/components/lmvz-action-list/lmvz-action-list.css +11 -0
  29. package/collection/components/lmvz-button/lmvz-button.css +11 -0
  30. package/collection/components/lmvz-card/lmvz-card.css +11 -0
  31. package/collection/components/lmvz-checkbox/lmvz-checkbox.css +4 -2
  32. package/collection/components/lmvz-checkbox/lmvz-checkbox.js +68 -8
  33. package/collection/components/lmvz-datepicker/lmvz-datepicker.css +0 -5
  34. package/collection/components/lmvz-datepicker/lmvz-datepicker.js +17 -3
  35. package/collection/components/lmvz-input/lmvz-input.css +1 -1
  36. package/collection/components/lmvz-input/lmvz-input.js +47 -12
  37. package/collection/components/lmvz-modal/lmvz-modal.css +11 -0
  38. package/collection/components/lmvz-popover/lmvz-popover.css +1 -1
  39. package/collection/components/lmvz-popover/lmvz-popover.js +45 -5
  40. package/collection/components/lmvz-radio/lmvz-radio.js +77 -9
  41. package/collection/components/lmvz-select/lmvz-select.css +96 -34
  42. package/collection/components/lmvz-select/lmvz-select.js +412 -21
  43. package/collection/components/lmvz-snackbar/lmvz-snackbar.js +1 -1
  44. package/collection/components/lmvz-spinner/lmvz-spinner.js +1 -1
  45. package/collection/components/lmvz-tab/lmvz-tab.css +11 -0
  46. package/collection/components/lmvz-tab/lmvz-tab.js +1 -1
  47. package/collection/components/lmvz-tabs/lmvz-tabs.css +11 -0
  48. package/collection/components/lmvz-tabs/lmvz-tabs.js +1 -1
  49. package/collection/components/lmvz-toggle/lmvz-toggle.js +21 -1
  50. package/collection/integration/header-integration/header-integration.js +1 -1
  51. package/collection/styles/fragments/_focus-within.css +2 -2
  52. package/collection/utils/aria/listbox-controller.js +154 -0
  53. package/collection/utils/radio/radio-group-controller.js +17 -0
  54. package/components/lmvz-action-list.js +1 -1
  55. package/components/lmvz-button.js +1 -1
  56. package/components/lmvz-card.js +1 -1
  57. package/components/lmvz-checkbox.js +1 -1
  58. package/components/lmvz-datepicker.js +1 -1
  59. package/components/lmvz-input.js +1 -1
  60. package/components/lmvz-message.js +1 -1
  61. package/components/lmvz-modal.js +1 -1
  62. package/components/lmvz-popover.js +1 -1
  63. package/components/lmvz-radio.js +1 -1
  64. package/components/lmvz-select.js +1 -1
  65. package/components/lmvz-snackbar.js +1 -1
  66. package/components/lmvz-spinner.js +1 -1
  67. package/components/lmvz-tab.js +1 -1
  68. package/components/lmvz-tabs.js +1 -1
  69. package/components/lmvz-toggle.js +1 -1
  70. package/components/p-BApwCp0R.js +1 -0
  71. package/components/p-C5gRiLRR.js +1 -0
  72. package/components/p-DiRcp9V6.js +1 -0
  73. package/esm/aria.constants-DIyiticz.js +62 -0
  74. package/esm/{stencil.util-C3QOYC8E.js → async.util-DZj20TV1.js} +1 -13
  75. package/esm/{directional-focus-controller-Bhu7RqYE.js → directional-focus-controller-QukTTWBE.js} +1 -1
  76. package/esm/{element-activation-controller-BuwyVq4A.js → element-activation-controller-dxJZKMag.js} +1 -1
  77. package/esm/{element.util-Bd9JyKJZ.js → element.util-3ydVsxUW.js} +2 -61
  78. package/esm/lmvz-action-list.entry.js +4 -3
  79. package/esm/lmvz-button-group.entry.js +2 -1
  80. package/esm/lmvz-button_2.entry.js +4 -3
  81. package/esm/lmvz-card.entry.js +1 -1
  82. package/esm/lmvz-checkbox.entry.js +38 -4
  83. package/esm/lmvz-components.js +1 -1
  84. package/esm/lmvz-datepicker.entry.js +10 -3
  85. package/esm/lmvz-header_2.entry.js +4 -3
  86. package/esm/lmvz-input.entry.js +240 -0
  87. package/esm/lmvz-menuitem.entry.js +3 -2
  88. package/esm/lmvz-modal.entry.js +3 -2
  89. package/esm/{lmvz-input_2.entry.js → lmvz-popover.entry.js} +23 -226
  90. package/esm/lmvz-radio.entry.js +61 -3
  91. package/esm/lmvz-select.entry.js +423 -16
  92. package/esm/lmvz-snackbar.entry.js +1 -1
  93. package/esm/lmvz-spinner.entry.js +1 -1
  94. package/esm/lmvz-tab.entry.js +2 -2
  95. package/esm/lmvz-tabs.entry.js +5 -4
  96. package/esm/lmvz-toggle.entry.js +7 -3
  97. package/esm/loader.js +1 -1
  98. package/esm/stencil.util-D2-5rsN3.js +15 -0
  99. package/hydrate/index.js +631 -74
  100. package/hydrate/index.mjs +631 -74
  101. package/lmvz-components/lmvz-components.esm.js +1 -1
  102. package/lmvz-components/p-00489151.entry.js +1 -0
  103. package/lmvz-components/{p-544e3ccb.entry.js → p-0f65d001.entry.js} +1 -1
  104. package/lmvz-components/p-137b1fe1.entry.js +1 -0
  105. package/lmvz-components/p-26dd9684.entry.js +1 -0
  106. package/lmvz-components/p-398b5cf4.entry.js +1 -0
  107. package/lmvz-components/p-40716828.entry.js +1 -0
  108. package/lmvz-components/p-4deaa010.entry.js +1 -0
  109. package/lmvz-components/p-57a366e6.entry.js +1 -0
  110. package/lmvz-components/p-70f1e31a.entry.js +1 -0
  111. package/lmvz-components/p-7872a6b4.entry.js +1 -0
  112. package/lmvz-components/{p-d280d21a.entry.js → p-88661c50.entry.js} +1 -1
  113. package/lmvz-components/p-91eb71bb.entry.js +1 -0
  114. package/lmvz-components/p-BA3wizqg.js +1 -0
  115. package/lmvz-components/{p-GGkwaiT6.js → p-CHxiS3ja.js} +1 -1
  116. package/lmvz-components/{p-UhNgD4L3.js → p-CPSyM_e-.js} +1 -1
  117. package/lmvz-components/p-DIyiticz.js +1 -0
  118. package/lmvz-components/p-DZj20TV1.js +1 -0
  119. package/lmvz-components/p-a680a7b2.entry.js +1 -0
  120. package/lmvz-components/{p-d48203f9.entry.js → p-a6fe9b61.entry.js} +1 -1
  121. package/lmvz-components/p-bh9cItsP.js +1 -0
  122. package/lmvz-components/p-e14058e1.entry.js +1 -0
  123. package/lmvz-components/p-eabbf901.entry.js +1 -0
  124. package/lmvz-components/p-f3f0ed49.entry.js +1 -0
  125. package/lmvz-components/p-f516a949.entry.js +1 -0
  126. package/manifest.json +366 -30
  127. package/package.json +1 -1
  128. package/types/api/ds.types.d.ts +1 -0
  129. package/types/components/lmvz-checkbox/lmvz-checkbox.d.ts +9 -1
  130. package/types/components/lmvz-datepicker/lmvz-datepicker.d.ts +2 -0
  131. package/types/components/lmvz-input/lmvz-input.d.ts +3 -0
  132. package/types/components/lmvz-popover/lmvz-popover.d.ts +2 -0
  133. package/types/components/lmvz-radio/lmvz-radio.d.ts +12 -2
  134. package/types/components/lmvz-select/lmvz-select.d.ts +53 -4
  135. package/types/components/lmvz-toggle/lmvz-toggle.d.ts +1 -0
  136. package/types/components.d.ts +96 -12
  137. package/types/utils/aria/listbox-controller.d.ts +42 -0
  138. package/types/utils/radio/radio-group-controller.d.ts +3 -0
  139. package/components/p-Bnri3fEI.js +0 -1
  140. package/components/p-CPuI0_r_.js +0 -1
  141. package/components/p-CWpl0WaZ.js +0 -1
  142. package/lmvz-components/p-63f6a596.entry.js +0 -1
  143. package/lmvz-components/p-7a7aa922.entry.js +0 -1
  144. package/lmvz-components/p-8022c6dc.entry.js +0 -1
  145. package/lmvz-components/p-Bd9JyKJZ.js +0 -1
  146. package/lmvz-components/p-C3QOYC8E.js +0 -1
  147. package/lmvz-components/p-a611c110.entry.js +0 -1
  148. package/lmvz-components/p-b1bb4393.entry.js +0 -1
  149. package/lmvz-components/p-b50b1cb5.entry.js +0 -1
  150. package/lmvz-components/p-b932cf91.entry.js +0 -1
  151. package/lmvz-components/p-bff115d4.entry.js +0 -1
  152. package/lmvz-components/p-d9aede81.entry.js +0 -1
  153. package/lmvz-components/p-dbfbcb74.entry.js +0 -1
  154. package/lmvz-components/p-f0e28f3a.entry.js +0 -1
  155. package/lmvz-components/p-f853faae.entry.js +0 -1
  156. package/lmvz-components/p-fa2efc42.entry.js +0 -1
  157. package/lmvz-components/p-fe6f5bdc.entry.js +0 -1
@@ -1,5 +1,6 @@
1
- import { h, Host } from "@stencil/core";
1
+ import { h, Host, } from "@stencil/core";
2
2
  import { AriaValidationController } from "../../utils/aria/aria-validation-controller";
3
+ import { ListboxController } from "../../utils/aria/listbox-controller";
3
4
  import { ReactiveControllerHost } from "../../utils/stencil/reactive-controller-host";
4
5
  let selectIdCounter = 0;
5
6
  export class LmvzSelect extends ReactiveControllerHost {
@@ -7,54 +8,258 @@ export class LmvzSelect extends ReactiveControllerHost {
7
8
  get validationEl() {
8
9
  return this.el;
9
10
  }
11
+ internals;
10
12
  selectId = `lmvz-select-${selectIdCounter++}`;
11
- nativeSelectEl;
13
+ triggerId = `${this.selectId}-trigger`;
14
+ popoverId = `${this.selectId}-listbox`;
15
+ triggerEl;
16
+ optionsSourceEl;
17
+ optionsMutationObserver;
18
+ initialValue;
19
+ initialValueCaptured = false;
20
+ flatOptions = [];
21
+ optionsById = new Map();
22
+ listboxController = new ListboxController({
23
+ onActiveChange: (id) => {
24
+ this.activeId = id;
25
+ },
26
+ onSelect: (id) => this.handleOptionSelect(id),
27
+ onClose: () => this.closePopover(),
28
+ });
29
+ get helperId() {
30
+ return this.helperText ? `${this.selectId}-helper` : undefined;
31
+ }
32
+ get showErrorMessage() {
33
+ return this.error && Boolean(this.errorMessage);
34
+ }
35
+ get errorId() {
36
+ return `${this.selectId}-error`;
37
+ }
38
+ get describedBy() {
39
+ const ids = [];
40
+ if (this.helperId)
41
+ ids.push(this.helperId);
42
+ if (this.showErrorMessage)
43
+ ids.push(this.errorId);
44
+ return ids.length > 0 ? ids.join(' ') : undefined;
45
+ }
12
46
  constructor() {
13
47
  super();
14
48
  this.addController(new AriaValidationController(this));
49
+ this.addController(this.listboxController);
15
50
  }
16
51
  lmvzChange;
17
52
  value;
18
53
  label;
19
54
  helperText;
55
+ nativeError = false;
56
+ errorFromProp = false;
57
+ get error() {
58
+ return this.errorFromProp || this.nativeError;
59
+ }
60
+ set error(value) {
61
+ this.errorFromProp = value;
62
+ }
63
+ errorMessage;
20
64
  disabled = false;
21
65
  required = false;
22
66
  name;
23
67
  size = 'default';
24
68
  highlighted = false;
25
69
  selectedLabel = '';
70
+ selectedId;
71
+ activeId;
72
+ isOpen = false;
73
+ optionGroups = [];
26
74
  get hasValue() {
27
75
  return Boolean(this.value);
28
76
  }
77
+ componentWillLoad() {
78
+ this.updateValidity();
79
+ super.componentWillLoad();
80
+ }
29
81
  componentDidLoad() {
30
- this.syncSelectedLabel();
82
+ this.handleSlotChange();
83
+ this.observeSlottedOptionMutations();
84
+ this.updateValidity();
31
85
  super.componentDidLoad();
32
86
  }
87
+ disconnectedCallback() {
88
+ this.optionsMutationObserver?.disconnect();
89
+ super.disconnectedCallback();
90
+ }
91
+ observeSlottedOptionMutations() {
92
+ if (typeof MutationObserver === 'undefined')
93
+ return;
94
+ this.optionsMutationObserver?.disconnect();
95
+ this.optionsMutationObserver = new MutationObserver(this.handleSlotChange);
96
+ this.optionsMutationObserver.observe(this.el, {
97
+ attributes: true,
98
+ attributeFilter: ['disabled', 'selected', 'value', 'label'],
99
+ characterData: true,
100
+ childList: true,
101
+ subtree: true,
102
+ });
103
+ }
104
+ async focusTrigger() {
105
+ this.triggerEl?.focus();
106
+ }
107
+ async blurTrigger() {
108
+ this.triggerEl?.blur();
109
+ }
110
+ async checkValidity() {
111
+ return this.internals.checkValidity?.() ?? true;
112
+ }
113
+ async reportValidity() {
114
+ return this.internals.reportValidity?.() ?? true;
115
+ }
116
+ async getValidationMessage() {
117
+ return this.internals.validationMessage ?? '';
118
+ }
119
+ formAssociatedCallback(form) {
120
+ if (!form)
121
+ return;
122
+ this.internals.setFormValue?.(this.value ?? '');
123
+ }
124
+ formResetCallback() {
125
+ this.internals.setValidity?.({});
126
+ this.value = this.initialValue;
127
+ this.updateValidity();
128
+ }
129
+ formStateRestoreCallback(state) {
130
+ this.value = state;
131
+ }
33
132
  handleValueChange(newValue) {
34
- if (this.nativeSelectEl && this.nativeSelectEl.value !== (newValue ?? '')) {
35
- this.nativeSelectEl.value = newValue ?? '';
133
+ this.applySelectionFromValue();
134
+ if (this.selectedId && !this.isOpen) {
135
+ this.listboxController.setActiveId(this.selectedId);
136
+ }
137
+ this.internals.setFormValue?.(newValue ?? '');
138
+ this.updateValidity();
139
+ }
140
+ handleRequiredChange() {
141
+ this.updateValidity();
142
+ }
143
+ handleDisabledChange(disabled) {
144
+ if (!disabled)
145
+ return;
146
+ this.closePopover();
147
+ this.triggerEl?.blur();
148
+ }
149
+ handleSlotChange = () => {
150
+ const container = this.optionsSourceEl;
151
+ if (!container)
152
+ return;
153
+ const { flat, groups } = parseOptionElements(container, this.selectId);
154
+ this.flatOptions = flat;
155
+ this.optionsById = new Map(flat.map((option) => [option.id, option]));
156
+ 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;
161
+ }
162
+ }
163
+ this.applySelectionFromValue();
164
+ this.listboxController.setOptions(flat.map(({ id, label, disabled }) => ({ id, label, disabled })));
165
+ if (!this.initialValueCaptured) {
166
+ this.initialValue = this.value;
167
+ this.initialValueCaptured = true;
168
+ }
169
+ };
170
+ applySelectionFromValue() {
171
+ const match = this.value ? this.flatOptions.find((option) => option.value === this.value) : undefined;
172
+ this.selectedId = match?.id;
173
+ this.selectedLabel = match?.label ?? '';
174
+ this.listboxController.setSelectedId(match?.id);
175
+ }
176
+ updateValidity() {
177
+ if (this.required && !this.value) {
178
+ this.internals.setValidity?.({ valueMissing: true }, 'Please select a value.', this.triggerEl);
179
+ return;
180
+ }
181
+ this.internals.setValidity?.({});
182
+ this.nativeError = false;
183
+ }
184
+ handleInvalid() {
185
+ this.nativeError = true;
186
+ }
187
+ handleOptionSelect(id) {
188
+ const option = this.optionsById.get(id);
189
+ if (!option)
190
+ return;
191
+ const isNewValue = option.value !== this.value;
192
+ this.value = option.value;
193
+ if (isNewValue) {
194
+ this.lmvzChange.emit(option.value);
36
195
  }
37
- this.syncSelectedLabel();
196
+ this.closePopover();
38
197
  }
39
- syncSelectedLabel() {
40
- if (!this.nativeSelectEl)
198
+ handleOptionClick = (option) => {
199
+ if (option.disabled || this.disabled)
41
200
  return;
42
- const idx = this.nativeSelectEl.selectedIndex;
43
- this.selectedLabel = idx >= 0 ? (this.nativeSelectEl.options[idx]?.text ?? '') : '';
44
- }
45
- handleChange = (event) => {
46
- const select = event.target;
47
- this.value = select.value;
48
- this.syncSelectedLabel();
49
- this.lmvzChange.emit(select.value);
201
+ this.listboxController.selectOption(option.id);
50
202
  };
203
+ handleLabelClick = () => {
204
+ if (this.disabled)
205
+ return;
206
+ this.triggerEl?.focus();
207
+ this.openPopover();
208
+ };
209
+ handleTriggerClick = () => {
210
+ if (this.disabled)
211
+ return;
212
+ if (this.isOpen) {
213
+ this.closePopover();
214
+ }
215
+ else {
216
+ this.openPopover();
217
+ }
218
+ };
219
+ handleTriggerKeydown = (event) => {
220
+ if (this.disabled)
221
+ return;
222
+ if (!this.isOpen) {
223
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter' || event.key === ' ') {
224
+ event.preventDefault();
225
+ this.openPopover();
226
+ }
227
+ return;
228
+ }
229
+ this.listboxController.handleKeydown(event);
230
+ };
231
+ handleTriggerBlur = () => {
232
+ this.closePopover();
233
+ };
234
+ handlePopoverClose = () => {
235
+ this.isOpen = false;
236
+ this.listboxController.clearTypeahead();
237
+ };
238
+ openPopover() {
239
+ if (this.isOpen || this.disabled)
240
+ return;
241
+ this.isOpen = true;
242
+ }
243
+ closePopover() {
244
+ if (!this.isOpen)
245
+ return;
246
+ this.isOpen = false;
247
+ this.listboxController.clearTypeahead();
248
+ }
249
+ renderOptionGroups() {
250
+ 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
+ }
252
+ 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));
254
+ }
51
255
  render() {
52
256
  const hasValue = this.hasValue;
53
257
  const shouldShowLabel = hasValue;
54
- return (h(Host, { key: '764be3255f76406e66600e800da85d84de8a5b62', "data-highlighted": String(this.highlighted) }, h("div", { key: '81c6c1eb04d9062b142f30c201b3ec24feec8814', class: hasValue ? 'select-wrapper has-value' : 'select-wrapper' }, h("label", { key: '49199adcb31515ce4b4789f27cc2520805c29a3c', htmlFor: this.selectId, class: shouldShowLabel ? 'floating-label' : 'assistive-label' }, this.label, this.required && shouldShowLabel && h("span", { key: 'e7e02e878d8a130d61e426774d9d362a49e481a7', "aria-hidden": "true" }, " *")), h("div", { key: 'bc5ff1214d856a084918c8e68d1b1567a97b41bb', "aria-hidden": "true" }, h("span", { key: 'cc3513a16e4e621bc47f35c252e981bc25bc0d77' }, hasValue ? this.selectedLabel : this.label, this.required && !hasValue && h("span", { key: 'a34658592f038b9438808418cc106842d98e63d5', "aria-hidden": "true" }, " *")), h("span", { key: 'cd71dd492d758e7e53e6212cc9549ec4057abe3f' }, h("svg", { key: 'fa1ea83d316fa9280516316b695163634264151c', width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" }, h("path", { key: '7750c64531e98cc5cdebafa1ecacf624945ab198', d: "M20.3334 8.66663L12.0001 17L3.66675 8.66663", stroke: "currentColor", "stroke-width": "1.75", "stroke-linecap": "round", "stroke-linejoin": "round" })))), h("select", { key: '53a4e34ad1405447177675e67afcc2e9c18319cb', id: this.selectId, ref: (el) => (this.nativeSelectEl = el), name: this.name, disabled: this.disabled, required: this.required, "aria-label": this.label, onChange: this.handleChange }, !hasValue && h("option", { key: '78ba2e2225d3d3b558c8a20f276f6fbbc0a670b8', value: "", disabled: true, selected: true, hidden: true }), h("slot", { key: 'bbc561f3f55dbc42b705f251fdc146b0496c72a1' }))), this.helperText && h("div", { key: '5bc860035c0ee46f62fefdb74d752b419bb19f7c', role: "status" }, this.helperText)));
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))));
55
259
  }
56
260
  static get is() { return "lmvz-select"; }
57
261
  static get encapsulation() { return "scoped"; }
262
+ static get formAssociated() { return true; }
58
263
  static get originalStyleUrls() {
59
264
  return {
60
265
  "$": ["./lmvz-select.css"]
@@ -124,6 +329,47 @@ export class LmvzSelect extends ReactiveControllerHost {
124
329
  "reflect": false,
125
330
  "attribute": "helper-text"
126
331
  },
332
+ "error": {
333
+ "type": "boolean",
334
+ "mutable": false,
335
+ "complexType": {
336
+ "original": "boolean",
337
+ "resolved": "boolean",
338
+ "references": {}
339
+ },
340
+ "required": false,
341
+ "optional": false,
342
+ "docs": {
343
+ "tags": [{
344
+ "name": "default",
345
+ "text": "false"
346
+ }],
347
+ "text": "Whether the select is in an error state.\nWhen not set manually, this will automatically reflect native HTML5 validation state"
348
+ },
349
+ "getter": true,
350
+ "setter": true,
351
+ "reflect": true,
352
+ "attribute": "error"
353
+ },
354
+ "errorMessage": {
355
+ "type": "string",
356
+ "mutable": false,
357
+ "complexType": {
358
+ "original": "string",
359
+ "resolved": "string | undefined",
360
+ "references": {}
361
+ },
362
+ "required": false,
363
+ "optional": true,
364
+ "docs": {
365
+ "tags": [],
366
+ "text": "Error message displayed below the select field when error is true."
367
+ },
368
+ "getter": false,
369
+ "setter": false,
370
+ "reflect": false,
371
+ "attribute": "error-message"
372
+ },
127
373
  "disabled": {
128
374
  "type": "boolean",
129
375
  "mutable": false,
@@ -182,11 +428,11 @@ export class LmvzSelect extends ReactiveControllerHost {
182
428
  "optional": true,
183
429
  "docs": {
184
430
  "tags": [],
185
- "text": "Name attribute passed to the native select for form submission."
431
+ "text": "Name attribute for form submission via ElementInternals."
186
432
  },
187
433
  "getter": false,
188
434
  "setter": false,
189
- "reflect": false,
435
+ "reflect": true,
190
436
  "attribute": "name"
191
437
  },
192
438
  "size": {
@@ -231,7 +477,7 @@ export class LmvzSelect extends ReactiveControllerHost {
231
477
  "name": "default",
232
478
  "text": "false"
233
479
  }],
234
- "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` and `aria-selected` are intentionally not used here.\n`aria-pressed` is reserved for toggle-button roles; `aria-selected` is\nreserved for option/listbox roles. The `data-highlighted` attribute provides\nthe accessible contract without conflicting with native `<select>` semantics."
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."
235
481
  },
236
482
  "getter": false,
237
483
  "setter": false,
@@ -243,7 +489,12 @@ export class LmvzSelect extends ReactiveControllerHost {
243
489
  }
244
490
  static get states() {
245
491
  return {
246
- "selectedLabel": {}
492
+ "nativeError": {},
493
+ "selectedLabel": {},
494
+ "selectedId": {},
495
+ "activeId": {},
496
+ "isOpen": {},
497
+ "optionGroups": {}
247
498
  };
248
499
  }
249
500
  static get events() {
@@ -264,11 +515,151 @@ export class LmvzSelect extends ReactiveControllerHost {
264
515
  }
265
516
  }];
266
517
  }
518
+ static get methods() {
519
+ return {
520
+ "focusTrigger": {
521
+ "complexType": {
522
+ "signature": "() => Promise<void>",
523
+ "parameters": [],
524
+ "references": {
525
+ "Promise": {
526
+ "location": "global",
527
+ "id": "global::Promise"
528
+ }
529
+ },
530
+ "return": "Promise<void>"
531
+ },
532
+ "docs": {
533
+ "text": "Sets focus on the combobox trigger.",
534
+ "tags": []
535
+ }
536
+ },
537
+ "blurTrigger": {
538
+ "complexType": {
539
+ "signature": "() => Promise<void>",
540
+ "parameters": [],
541
+ "references": {
542
+ "Promise": {
543
+ "location": "global",
544
+ "id": "global::Promise"
545
+ }
546
+ },
547
+ "return": "Promise<void>"
548
+ },
549
+ "docs": {
550
+ "text": "Removes focus from the combobox trigger.",
551
+ "tags": []
552
+ }
553
+ },
554
+ "checkValidity": {
555
+ "complexType": {
556
+ "signature": "() => Promise<boolean>",
557
+ "parameters": [],
558
+ "references": {
559
+ "Promise": {
560
+ "location": "global",
561
+ "id": "global::Promise"
562
+ }
563
+ },
564
+ "return": "Promise<boolean>"
565
+ },
566
+ "docs": {
567
+ "text": "Returns whether the select satisfies its validation constraints.",
568
+ "tags": []
569
+ }
570
+ },
571
+ "reportValidity": {
572
+ "complexType": {
573
+ "signature": "() => Promise<boolean>",
574
+ "parameters": [],
575
+ "references": {
576
+ "Promise": {
577
+ "location": "global",
578
+ "id": "global::Promise"
579
+ }
580
+ },
581
+ "return": "Promise<boolean>"
582
+ },
583
+ "docs": {
584
+ "text": "Reports validation errors to the user.",
585
+ "tags": []
586
+ }
587
+ },
588
+ "getValidationMessage": {
589
+ "complexType": {
590
+ "signature": "() => Promise<string>",
591
+ "parameters": [],
592
+ "references": {
593
+ "Promise": {
594
+ "location": "global",
595
+ "id": "global::Promise"
596
+ }
597
+ },
598
+ "return": "Promise<string>"
599
+ },
600
+ "docs": {
601
+ "text": "Returns the current validation message, if any.",
602
+ "tags": []
603
+ }
604
+ }
605
+ };
606
+ }
267
607
  static get elementRef() { return "el"; }
268
608
  static get watchers() {
269
609
  return [{
270
610
  "propName": "value",
271
611
  "methodName": "handleValueChange"
612
+ }, {
613
+ "propName": "required",
614
+ "methodName": "handleRequiredChange"
615
+ }, {
616
+ "propName": "disabled",
617
+ "methodName": "handleDisabledChange"
272
618
  }];
273
619
  }
620
+ static get listeners() {
621
+ return [{
622
+ "name": "invalid",
623
+ "method": "handleInvalid",
624
+ "target": undefined,
625
+ "capture": false,
626
+ "passive": false
627
+ }];
628
+ }
629
+ static get attachInternalsMemberName() { return "internals"; }
630
+ }
631
+ function parseOptionElements(container, idPrefix) {
632
+ const flat = [];
633
+ const groups = [];
634
+ let looseGroup;
635
+ let optionIndex = 0;
636
+ const toOptionData = (optionEl, disabledOverride = false) => {
637
+ const option = optionEl;
638
+ const data = {
639
+ id: `${idPrefix}-option-${optionIndex++}`,
640
+ value: option.value,
641
+ label: (option.textContent ?? '').trim(),
642
+ disabled: disabledOverride || option.disabled,
643
+ selected: option.selected,
644
+ };
645
+ flat.push(data);
646
+ return data;
647
+ };
648
+ Array.from(container.children).forEach((child) => {
649
+ if (child.tagName === 'OPTGROUP') {
650
+ looseGroup = undefined;
651
+ const optgroup = child;
652
+ const options = Array.from(optgroup.querySelectorAll('option')).map((option) => toOptionData(option, optgroup.disabled));
653
+ groups.push({ label: optgroup.label || undefined, options });
654
+ return;
655
+ }
656
+ if (child.tagName === 'OPTION') {
657
+ if (!looseGroup) {
658
+ looseGroup = { label: undefined, options: [] };
659
+ groups.push(looseGroup);
660
+ }
661
+ looseGroup.options.push(toOptionData(child));
662
+ }
663
+ });
664
+ return { flat, groups };
274
665
  }
@@ -58,7 +58,7 @@ export class LmvzSnackbar {
58
58
  this.el.dispatchEvent(new CustomEvent('lmvzAction', { bubbles: false, composed: false }));
59
59
  };
60
60
  render() {
61
- return (h(Host, { key: '3526d31aa5842f2d4bf5b5b878a7ca03bb4bf731', class: this.animationClass }, h("lmvz-icon", { key: '52080f476e527c9af337eb1997db1b29a90fb5c0', icon: STATUS_SVG[this.status], size: "md", weight: "bold" }), h("span", { key: 'a18eaf04ca959432fc76037efd24ecf1001bd798', class: "message" }, this.message), this.actionLabel && (h("lmvz-button", { key: '3126781af40ea74854db8e7c2aa7e1fb1b632d38', variant: "tertiary", scale: "small", onLmvzActivation: this.handleActionClick }, this.actionLabel))));
61
+ return (h(Host, { key: 'f58c7657bf5511c72930dfc54ace4af1ff7f3554', class: this.animationClass }, h("lmvz-icon", { key: '8632b42b9b246bf58480fb17ca336ce5f0883845', icon: STATUS_SVG[this.status], size: "md", weight: "bold" }), h("span", { key: '4ca56d2cdf29415c6d21cc50399dc28aebb8de99', class: "message" }, this.message), this.actionLabel && (h("lmvz-button", { key: 'd09043056b968421143aa180f464effcd7b781fe', variant: "tertiary", scale: "small", onLmvzActivation: this.handleActionClick }, this.actionLabel))));
62
62
  }
63
63
  static get is() { return "lmvz-snackbar"; }
64
64
  static get encapsulation() { return "shadow"; }
@@ -4,7 +4,7 @@ export class LmvzSpinner {
4
4
  ariaLabel;
5
5
  render() {
6
6
  const hasLabel = !!this.ariaLabel?.trim();
7
- return (h(Host, { key: '96a151ca5586df967f9973e73cfb7be458254dc4', role: hasLabel ? 'status' : undefined, "aria-label": hasLabel ? this.ariaLabel : undefined, "aria-hidden": hasLabel ? undefined : 'true' }, h("svg", { key: '27d404b929294aba50170e0ad4951c0c03d7a286', part: "indicator", viewBox: "0 0 16 16", "aria-hidden": "true" }, h("circle", { key: 'bcf47ee3a704f46ab4ec26de41dd859db17266e9', pathLength: "100", cx: "8", cy: "8", r: "6" }))));
7
+ return (h(Host, { key: 'cf60fcf652f377a66596cc51138bb73f7465a4e7', role: hasLabel ? 'status' : undefined, "aria-label": hasLabel ? this.ariaLabel : undefined, "aria-hidden": hasLabel ? undefined : 'true' }, h("svg", { key: '121ab46a591242fbc0f1c3540e8f2a7b72c9582a', part: "indicator", viewBox: "0 0 16 16", "aria-hidden": "true" }, h("circle", { key: 'fe649394c9bd6e7a06113e9a50b595bb890a6fac', pathLength: "100", cx: "8", cy: "8", r: "6" }))));
8
8
  }
9
9
  static get is() { return "lmvz-spinner"; }
10
10
  static get encapsulation() { return "shadow"; }
@@ -29,6 +29,17 @@ hr {
29
29
  border-top: var(--lmvz-semantic-border-width-default, 1px) solid var(--lmvz-semantic-color-border-subtle, #f0f0f0);
30
30
  }
31
31
 
32
+ fieldset {
33
+ border: none;
34
+ margin: 0;
35
+ padding: 0;
36
+ }
37
+
38
+ legend {
39
+ padding: 0;
40
+ display: block;
41
+ }
42
+
32
43
  }
33
44
  :host {
34
45
  display: inline-block;
@@ -14,7 +14,7 @@ export class LmvzTab {
14
14
  this.el.dispatchEvent(new CustomEvent('lmvzTabReady', { bubbles: true, composed: false }));
15
15
  }
16
16
  render() {
17
- return (h(Host, { key: '992500dcc371efbeab4eb83fbcfb9745384fa423' }, h("button", { key: 'e346c88625b701636704e89b749971e6a016a3ff', part: "button", role: "tab", disabled: this.disabled, "aria-selected": String(this.selected), "aria-disabled": this.disabled ? 'true' : undefined, tabIndex: 0 }, h("slot", { key: 'ee0790e6f04c1c05fbbb7a624e0c791e1db24506', name: "media", ref: (el) => (this.mediaSlotEl = el), onSlotchange: this.handleMediaSlotChange }), h("span", { key: '45b0c2c38dcec72ab02cd308e0301d1c912e96f7', class: "label" }, h("slot", { key: 'e31e6c8ade0c7b44684836c29fbb65464db7ae59' })), h("span", { key: '03016f465e673bf27261e8ceb5c3a6a1666551a7', "aria-hidden": "true", part: "indicator", class: "indicator" }))));
17
+ return (h(Host, { key: '4380fe35cacb11bc7114664c126504e8f4fb09a0' }, h("button", { key: 'ac77443cbba5c94f864820088f60c0bac23d0775', part: "button", role: "tab", disabled: this.disabled, "aria-selected": String(this.selected), "aria-disabled": this.disabled ? 'true' : undefined, tabIndex: 0 }, h("slot", { key: '9988497505ca3effa6c41011ac408a5d25629ca1', name: "media", ref: (el) => (this.mediaSlotEl = el), onSlotchange: this.handleMediaSlotChange }), h("span", { key: '70566c46fba36b8717aa82917e45abd39cd7a2ea', class: "label" }, h("slot", { key: 'a595738c387ee2f4f77f6ccdfdfb0589751ce999' })), h("span", { key: '933758dc965d1151ca6a96367ea40290189384ad', "aria-hidden": "true", part: "indicator", class: "indicator" }))));
18
18
  }
19
19
  static get is() { return "lmvz-tab"; }
20
20
  static get encapsulation() { return "shadow"; }
@@ -29,6 +29,17 @@ hr {
29
29
  border-top: var(--lmvz-semantic-border-width-default, 1px) solid var(--lmvz-semantic-color-border-subtle, #f0f0f0);
30
30
  }
31
31
 
32
+ fieldset {
33
+ border: none;
34
+ margin: 0;
35
+ padding: 0;
36
+ }
37
+
38
+ legend {
39
+ padding: 0;
40
+ display: block;
41
+ }
42
+
32
43
  }
33
44
  :host {
34
45
  display: inline-block;
@@ -235,7 +235,7 @@ export class LmvzTabs extends ReactiveControllerHost {
235
235
  this.selectTab(value);
236
236
  };
237
237
  render() {
238
- return (h(Host, { key: '27d096ac6ed21fb72a5c6336c6f07ee2fe36bd92', onClick: this.handleTabClick, onKeydown: this.handleKeydown }, h("div", { key: 'c3b530f13cf6829e8519a678e577e8aa6bf1c3d6', role: "tablist", "aria-orientation": this.orientation, class: "tablist" }, h("slot", { key: '018641f52dfcab6dea884c95a5e550dbe60f74f6', ref: (el) => (this.tabSlotEl = el) })), h("div", { key: '6f7cce2b8e551683ba04c53ebae8233c3ef61969', part: "panels-container", class: "panels-container" }, h("slot", { key: '8e9f0c1e1866180a763dd41aa1974f5cf354fb44', name: "panels", ref: (el) => (this.panelSlotEl = el) }))));
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) }))));
239
239
  }
240
240
  static get is() { return "lmvz-tabs"; }
241
241
  static get encapsulation() { return "shadow"; }
@@ -53,6 +53,9 @@ export class LmvzToggle extends ReactiveControllerHost {
53
53
  async reportValidity() {
54
54
  return this.nativeInputElement?.reportValidity?.() ?? false;
55
55
  }
56
+ async getValidationMessage() {
57
+ return this.nativeInputElement?.validationMessage ?? '';
58
+ }
56
59
  async getInputElement() {
57
60
  if (!this.nativeInputElement) {
58
61
  await new Promise((resolve) => componentOnReady(this.el, resolve));
@@ -65,7 +68,7 @@ export class LmvzToggle extends ReactiveControllerHost {
65
68
  this.lmvzChange.emit(newChecked);
66
69
  };
67
70
  render() {
68
- return (h(Host, { key: 'bde08dcbd39c0c02602590bc0c536b5d671fa76e' }, h("span", { key: '2567f70f81014d8ab7b9073a0d59f33bfeeca6a5', class: "track" }, h("input", { key: '88c9fc7523489dd0decf5b5f3ddda604aadcf8a9', type: "checkbox", role: "switch", id: this.toggleId, checked: this.checked, disabled: this.disabled, required: this.required, name: this.name, value: this.value, form: this.form, ref: (el) => (this.nativeInputElement = el), onChange: this.handleChange }), h("span", { key: '816fc007d7c87acc8ff0286eb861bd29059b44eb', class: "thumb", "aria-hidden": "true" })), h("label", { key: '9525cd785d2c6c3b6242c6f5eac215996b881873', htmlFor: this.toggleId }, this.label)));
71
+ return (h(Host, { key: 'a0e0272a8457ee6b07a102aa10e5a7c0292a55d9' }, h("span", { key: '5d14029bee4b66f5ed2ee1eb189a1d80a68abe3e', class: "track" }, h("input", { key: '926b1ca6065750717577ece30e8e78b9004ec0e8', type: "checkbox", role: "switch", id: this.toggleId, checked: this.checked, disabled: this.disabled, required: this.required, name: this.name, value: this.value, form: this.form, ref: (el) => (this.nativeInputElement = el), onChange: this.handleChange }), h("span", { key: 'bdee06bab6acba72d019134828f7415d7b347c49', class: "thumb", "aria-hidden": "true" })), h("label", { key: 'b8eaa9305352f7146bf95bb517766310517d60e6', htmlFor: this.toggleId }, this.label)));
69
72
  }
70
73
  static get is() { return "lmvz-toggle"; }
71
74
  static get encapsulation() { return "scoped"; }
@@ -321,6 +324,23 @@ export class LmvzToggle extends ReactiveControllerHost {
321
324
  "tags": []
322
325
  }
323
326
  },
327
+ "getValidationMessage": {
328
+ "complexType": {
329
+ "signature": "() => Promise<string>",
330
+ "parameters": [],
331
+ "references": {
332
+ "Promise": {
333
+ "location": "global",
334
+ "id": "global::Promise"
335
+ }
336
+ },
337
+ "return": "Promise<string>"
338
+ },
339
+ "docs": {
340
+ "text": "Returns the current validation message, if any.",
341
+ "tags": []
342
+ }
343
+ },
324
344
  "getInputElement": {
325
345
  "complexType": {
326
346
  "signature": "() => Promise<HTMLInputElement | undefined>",