@momentum-design/components 0.95.0 → 0.95.2
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.
- package/dist/browser/index.js +314 -310
- package/dist/browser/index.js.map +4 -4
- package/dist/components/card/card.component.d.ts +1 -1
- package/dist/components/card/card.component.js +2 -2
- package/dist/components/card/card.styles.js +4 -0
- package/dist/components/dialog/dialog.component.d.ts +1 -1
- package/dist/components/dialog/dialog.component.js +2 -2
- package/dist/components/option/option.component.d.ts +11 -11
- package/dist/components/option/option.component.js +12 -19
- package/dist/components/popover/popover.component.js +2 -1
- package/dist/components/select/select.component.d.ts +74 -68
- package/dist/components/select/select.component.js +287 -307
- package/dist/components/select/select.styles.js +6 -3
- package/dist/components/select/select.types.d.ts +8 -3
- package/dist/custom-elements.json +2065 -2110
- package/dist/react/index.d.ts +4 -4
- package/dist/react/index.js +4 -4
- package/dist/react/option/index.d.ts +11 -5
- package/dist/react/option/index.js +11 -5
- package/dist/react/select/index.d.ts +9 -3
- package/dist/react/select/index.js +3 -1
- package/dist/utils/mixins/CardComponentMixin.js +2 -2
- package/dist/utils/mixins/{CardAndDialogFooterMixin.d.ts → FooterMixin.d.ts} +2 -2
- package/dist/utils/mixins/{CardAndDialogFooterMixin.js → FooterMixin.js} +1 -1
- package/package.json +1 -1
@@ -10,14 +10,12 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
10
10
|
import { html, nothing } from 'lit';
|
11
11
|
import { property, query, queryAssignedElements, state } from 'lit/decorators.js';
|
12
12
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
13
|
-
import { live } from 'lit/directives/live.js';
|
14
13
|
import { KEYS } from '../../utils/keys';
|
15
14
|
import { DataAriaLabelMixin } from '../../utils/mixins/DataAriaLabelMixin';
|
16
15
|
import { FormInternalsMixin } from '../../utils/mixins/FormInternalsMixin';
|
17
16
|
import { ROLE } from '../../utils/roles';
|
18
17
|
import FormfieldWrapper from '../formfieldwrapper/formfieldwrapper.component';
|
19
18
|
import { DEFAULTS as FORMFIELD_DEFAULTS, VALIDATION } from '../formfieldwrapper/formfieldwrapper.constants';
|
20
|
-
import { TAG_NAME as OPTION_GROUP_TAG_NAME } from '../optgroup/optgroup.constants';
|
21
19
|
import { TAG_NAME as OPTION_TAG_NAME } from '../option/option.constants';
|
22
20
|
import { POPOVER_PLACEMENT } from '../popover/popover.constants';
|
23
21
|
import { TYPE, VALID_TEXT_TAGS } from '../text/text.constants';
|
@@ -29,6 +27,8 @@ import styles from './select.styles';
|
|
29
27
|
* The component ensures accessibility and usability while handling various use cases,
|
30
28
|
* including long text truncation with tooltip support.
|
31
29
|
*
|
30
|
+
* Every mdc-option should have a `value` attribute set to ensure proper form submission.
|
31
|
+
*
|
32
32
|
* To set a default option, use the `selected` attribute on the `mdc-option` element.
|
33
33
|
*
|
34
34
|
* **Note:** Make sure to add `mdc-selectlistbox` as a child of `mdc-select` and wrap options/optgroup in it to ensure proper accessibility functionality. Read more about it in SelectListBox documentation.
|
@@ -41,7 +41,7 @@ import styles from './select.styles';
|
|
41
41
|
*
|
42
42
|
* @tagname mdc-select
|
43
43
|
*
|
44
|
-
* @slot default - This is a default/unnamed slot for options and/or option group.
|
44
|
+
* @slot default - This is a default/unnamed slot for Selectlistbox including options and/or option group.
|
45
45
|
*
|
46
46
|
* @event click - (React: onClick) This event is dispatched when the select is clicked.
|
47
47
|
* @event change - (React: onChange) This event is dispatched when the select is changed.
|
@@ -68,11 +68,68 @@ class Select extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)) {
|
|
68
68
|
*/
|
69
69
|
this.placement = POPOVER_PLACEMENT.BOTTOM_START;
|
70
70
|
/** @internal */
|
71
|
-
this.baseIconName = ARROW_ICON.ARROW_DOWN;
|
72
|
-
/** @internal */
|
73
|
-
this.selectedValue = '';
|
74
|
-
/** @internal */
|
75
71
|
this.displayPopover = false;
|
72
|
+
/** @internal */
|
73
|
+
this.initialSelectedOption = null;
|
74
|
+
}
|
75
|
+
getAllValidOptions() {
|
76
|
+
var _a;
|
77
|
+
return Array.from(((_a = this.slottedListboxes[0]) === null || _a === void 0 ? void 0 : _a.querySelectorAll(OPTION_TAG_NAME)) || []);
|
78
|
+
}
|
79
|
+
getFirstValidOption() {
|
80
|
+
var _a;
|
81
|
+
return (_a = this.slottedListboxes[0]) === null || _a === void 0 ? void 0 : _a.querySelector(OPTION_TAG_NAME);
|
82
|
+
}
|
83
|
+
getLastValidOption() {
|
84
|
+
const options = this.getAllValidOptions();
|
85
|
+
return options.length > 0 ? options[options.length - 1] : null;
|
86
|
+
}
|
87
|
+
getFirstSelectedOption() {
|
88
|
+
var _a;
|
89
|
+
return (_a = this.slottedListboxes[0]) === null || _a === void 0 ? void 0 : _a.querySelector(`${OPTION_TAG_NAME}[selected]`);
|
90
|
+
}
|
91
|
+
/**
|
92
|
+
* Handles the first updated lifecycle event.
|
93
|
+
* If an option is selected, use that as the value.
|
94
|
+
* If not, use the placeholder if it exists, otherwise use the first option.
|
95
|
+
*/
|
96
|
+
async firstUpdated() {
|
97
|
+
await this.updateComplete;
|
98
|
+
this.modifyListBoxWrapper();
|
99
|
+
const firstSelectedOption = this.getFirstSelectedOption();
|
100
|
+
if (firstSelectedOption) {
|
101
|
+
this.initialSelectedOption = firstSelectedOption;
|
102
|
+
// do not fire events when setting the selected value
|
103
|
+
// which is already selected in the DOM on first update
|
104
|
+
this.setSelectedOption(firstSelectedOption);
|
105
|
+
}
|
106
|
+
else if (!this.placeholder) {
|
107
|
+
const firstValidOption = this.getFirstValidOption();
|
108
|
+
// We will show the first option as selected & fire
|
109
|
+
// and event since the selected option changed
|
110
|
+
this.setSelectedOption(firstValidOption);
|
111
|
+
this.fireEvents();
|
112
|
+
}
|
113
|
+
else if (this.placeholder) {
|
114
|
+
// If there is no default selected option
|
115
|
+
// then we call the native validity
|
116
|
+
this.setInputValidity();
|
117
|
+
}
|
118
|
+
}
|
119
|
+
updated(changedProperties) {
|
120
|
+
super.updated(changedProperties);
|
121
|
+
if (changedProperties.has('disabled') ||
|
122
|
+
changedProperties.has('softDisabled') ||
|
123
|
+
changedProperties.has('readonly')) {
|
124
|
+
if (this.disabled || this.softDisabled || this.readonly) {
|
125
|
+
// If the select is disabled, soft-disabled or readonly,
|
126
|
+
// we close the popover if it is open.
|
127
|
+
this.displayPopover = false;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
if (changedProperties.has('dataAriaLabel')) {
|
131
|
+
this.modifyListBoxWrapper();
|
132
|
+
}
|
76
133
|
}
|
77
134
|
/**
|
78
135
|
* Modifies the listbox wrapper to ensure it has the correct attributes
|
@@ -92,189 +149,143 @@ class Select extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)) {
|
|
92
149
|
slottedListBox.setAttribute('aria-labelledby', TRIGGER_ID);
|
93
150
|
}
|
94
151
|
/**
|
95
|
-
* A
|
96
|
-
* It
|
97
|
-
*
|
152
|
+
* A private method which is called when an option is clicked.
|
153
|
+
* It sets the selected option, removes selected from other options, updates the tabindex for all options,
|
154
|
+
* closes the popover, and fires the change and input events.
|
155
|
+
* @param event - The event which triggered this function.
|
98
156
|
*/
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
if (option.tagName.toLowerCase() === OPTION_TAG_NAME) {
|
104
|
-
return option;
|
105
|
-
}
|
106
|
-
if (option.tagName.toLowerCase() === OPTION_GROUP_TAG_NAME) {
|
107
|
-
return Array.from(option.children).filter(optgroup => optgroup.tagName.toLowerCase() === OPTION_TAG_NAME);
|
108
|
-
}
|
109
|
-
return [];
|
110
|
-
}).flat()) || []);
|
157
|
+
handleOptionsClick(event) {
|
158
|
+
this.setSelectedOption(event.target);
|
159
|
+
this.displayPopover = false;
|
160
|
+
this.fireEvents();
|
111
161
|
}
|
112
162
|
/**
|
113
|
-
*
|
114
|
-
*
|
115
|
-
*
|
116
|
-
*
|
163
|
+
* Sets the selected option in the component state and updates the input element's value.
|
164
|
+
* This method ensures that only the selected option is marked as selected in the DOM,
|
165
|
+
* and updates the tabindex for all options accordingly.
|
166
|
+
* It also updates the validity of the input element based on the selected option.
|
167
|
+
* This method is called when an option is selected.
|
168
|
+
*
|
169
|
+
* @param option - The option element in DOM which gets selected.
|
117
170
|
*/
|
118
|
-
|
119
|
-
var _a;
|
120
|
-
|
121
|
-
this.
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
171
|
+
setSelectedOption(option) {
|
172
|
+
var _a, _b;
|
173
|
+
// set the attribute 'selected' on the option in HTML and remove it from others
|
174
|
+
this.updateSelectedInChildOptions(option);
|
175
|
+
// update the tabindex for all options
|
176
|
+
this.updateTabIndexForAllOptions(option);
|
177
|
+
// set the selected option in the component state
|
178
|
+
this.selectedOption = option;
|
179
|
+
// update all form related values
|
180
|
+
this.value = (_b = (_a = this.selectedOption) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
|
181
|
+
this.internals.setFormValue(this.value);
|
182
|
+
this.inputElement.setAttribute('value', this.value);
|
183
|
+
this.setInputValidity();
|
184
|
+
}
|
185
|
+
/**
|
186
|
+
* Updates the tabindex of all options.
|
187
|
+
* Sets the tabindex of the selected option to '0' and others to '-1'.
|
188
|
+
*
|
189
|
+
* @param option - The option which tabIndex should be set to 0.
|
190
|
+
*/
|
191
|
+
updateTabIndexForAllOptions(option) {
|
192
|
+
const options = this.getAllValidOptions();
|
193
|
+
const optionToGetTabIndex0 = option || options[0];
|
194
|
+
options.forEach(option => {
|
195
|
+
option.setAttribute('tabindex', option === optionToGetTabIndex0 ? '0' : '-1');
|
132
196
|
});
|
133
|
-
if (!isTabIndexSet) {
|
134
|
-
// if no option is selected, set the first option as focused
|
135
|
-
(_a = this.getAllValidOptions()[0]) === null || _a === void 0 ? void 0 : _a.setAttribute('tabindex', '0');
|
136
|
-
}
|
137
197
|
}
|
138
198
|
/**
|
139
|
-
*
|
140
|
-
*
|
141
|
-
* @param event - The event which triggered this function.
|
199
|
+
* Sets selected attribute on the selected option and removes it from all options
|
200
|
+
* @param selectedOption - The option which gets selected
|
142
201
|
*/
|
143
|
-
|
144
|
-
|
202
|
+
updateSelectedInChildOptions(selectedOption) {
|
203
|
+
selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.setAttribute('selected', 'true');
|
204
|
+
const options = this.getAllValidOptions();
|
205
|
+
options.forEach(option => {
|
206
|
+
if (option !== selectedOption) {
|
207
|
+
option.removeAttribute('selected');
|
208
|
+
}
|
209
|
+
});
|
145
210
|
}
|
146
211
|
/**
|
147
|
-
*
|
148
|
-
* It
|
149
|
-
* otherwise it falls back to the option's text content.
|
150
|
-
* @param option - The option element from which to set the selected value.
|
212
|
+
* A private method which is called to fire the change and input events.
|
213
|
+
* It dispatches the input and change events with the selected option's value and label.
|
151
214
|
*/
|
152
|
-
|
153
|
-
|
154
|
-
this.
|
155
|
-
this.selectedIcon = option === null || option === void 0 ? void 0 : option.getAttribute('prefix-icon');
|
156
|
-
this.selectedValue = (_d = (_c = option === null || option === void 0 ? void 0 : option.getAttribute('value')) !== null && _c !== void 0 ? _c : option === null || option === void 0 ? void 0 : option.textContent) !== null && _d !== void 0 ? _d : '';
|
157
|
-
this.value = this.selectedValue;
|
158
|
-
// Set form value
|
159
|
-
this.internals.setFormValue(this.selectedValue);
|
160
|
-
this.manageRequired();
|
161
|
-
// dispatch a change event when a value is selected
|
162
|
-
this.dispatchInput(this.selectedValue);
|
163
|
-
this.dispatchChange(this.selectedValue);
|
215
|
+
fireEvents() {
|
216
|
+
this.dispatchInput(this.selectedOption);
|
217
|
+
this.dispatchChange(this.selectedOption);
|
164
218
|
}
|
165
219
|
/**
|
166
|
-
*
|
167
|
-
* If the
|
168
|
-
* it sets a custom
|
169
|
-
* If the
|
170
|
-
*
|
220
|
+
* Sets the validity of the input element based on the selected option.
|
221
|
+
* If the selected option is not set and the select is required,
|
222
|
+
* it sets a custom validation message.
|
223
|
+
* If the selected option is set or the select is not required,
|
224
|
+
* it clears the custom validation message.
|
225
|
+
* This method is called to ensure that the select component behaves correctly
|
226
|
+
* in form validation scenarios, especially when the select is required.
|
171
227
|
* @internal
|
172
228
|
*/
|
173
|
-
|
174
|
-
if (!this.
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
this.inputElement.setCustomValidity('');
|
180
|
-
}
|
229
|
+
setInputValidity() {
|
230
|
+
if (!this.selectedOption && this.required && this.validationMessage) {
|
231
|
+
this.inputElement.setCustomValidity(this.validationMessage);
|
232
|
+
}
|
233
|
+
else {
|
234
|
+
this.inputElement.setCustomValidity('');
|
181
235
|
}
|
182
236
|
this.setValidity();
|
183
237
|
}
|
184
238
|
/**
|
239
|
+
* Resets the select to its initially selected option.
|
185
240
|
* @internal
|
186
|
-
* Resets the select to its initial state.
|
187
241
|
*/
|
188
242
|
formResetCallback() {
|
189
|
-
|
190
|
-
this.
|
191
|
-
this.
|
192
|
-
|
193
|
-
|
194
|
-
|
243
|
+
var _a;
|
244
|
+
const optionToResetTo = this.initialSelectedOption || null;
|
245
|
+
if (((_a = this.selectedOption) === null || _a === void 0 ? void 0 : _a.value) !== (optionToResetTo === null || optionToResetTo === void 0 ? void 0 : optionToResetTo.value)) {
|
246
|
+
this.setSelectedOption(optionToResetTo);
|
247
|
+
// fire events to notify the change in case of reset
|
248
|
+
this.fireEvents();
|
249
|
+
}
|
195
250
|
}
|
196
251
|
/** @internal */
|
197
252
|
formStateRestoreCallback(state) {
|
198
|
-
|
199
|
-
this.
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
253
|
+
var _a;
|
254
|
+
const optionToRestoreTo = this.getAllValidOptions().find(option => option.value === state || option.label === state);
|
255
|
+
if (((_a = this.selectedOption) === null || _a === void 0 ? void 0 : _a.value) !== (optionToRestoreTo === null || optionToRestoreTo === void 0 ? void 0 : optionToRestoreTo.value)) {
|
256
|
+
this.setSelectedOption(optionToRestoreTo || null);
|
257
|
+
// fire events to notify the change in case of restore
|
258
|
+
this.fireEvents();
|
204
259
|
}
|
260
|
+
}
|
261
|
+
dispatchChange(option) {
|
205
262
|
this.dispatchEvent(new CustomEvent('change', {
|
206
|
-
detail: { value },
|
263
|
+
detail: { value: option === null || option === void 0 ? void 0 : option.value, label: option === null || option === void 0 ? void 0 : option.label },
|
207
264
|
composed: true,
|
208
265
|
bubbles: true,
|
209
266
|
}));
|
210
267
|
}
|
211
|
-
dispatchInput(
|
212
|
-
if (!value) {
|
213
|
-
return;
|
214
|
-
}
|
268
|
+
dispatchInput(option) {
|
215
269
|
this.dispatchEvent(new CustomEvent('input', {
|
216
|
-
detail: { value },
|
270
|
+
detail: { value: option === null || option === void 0 ? void 0 : option.value, label: option === null || option === void 0 ? void 0 : option.label },
|
217
271
|
composed: true,
|
218
272
|
bubbles: true,
|
219
273
|
}));
|
220
274
|
}
|
221
275
|
/**
|
222
|
-
* Handles the
|
223
|
-
*
|
224
|
-
*
|
225
|
-
*
|
226
|
-
*
|
227
|
-
* - ARROW_DOWN, ARROW_UP, PAGE_DOWN, PAGE_UP: Handles navigation between options.
|
228
|
-
* @param event - The keyboard event.
|
276
|
+
* Handles the click event on the visual combobox.
|
277
|
+
* If the select is disabled, soft-disabled or readonly, it does nothing.
|
278
|
+
* If the popover is already open, it closes it.
|
279
|
+
* If it is closed, it opens it.
|
280
|
+
* @param event - The mouse event which triggered this function.
|
229
281
|
*/
|
230
|
-
handlePopoverOnOpen(event) {
|
231
|
-
var _a;
|
232
|
-
switch (event.key) {
|
233
|
-
case KEYS.TAB: {
|
234
|
-
const focusedOptionIndex = this.getAllValidOptions().findIndex(option => option === event.target);
|
235
|
-
this.setFocusAndTabIndex(focusedOptionIndex);
|
236
|
-
event.preventDefault();
|
237
|
-
break;
|
238
|
-
}
|
239
|
-
case KEYS.SPACE:
|
240
|
-
this.closePopover();
|
241
|
-
event.preventDefault();
|
242
|
-
break;
|
243
|
-
case KEYS.ENTER:
|
244
|
-
this.closePopover();
|
245
|
-
event.preventDefault();
|
246
|
-
// if the popover is closed, then we submit the form.
|
247
|
-
(_a = this.form) === null || _a === void 0 ? void 0 : _a.requestSubmit();
|
248
|
-
break;
|
249
|
-
case KEYS.HOME:
|
250
|
-
this.setFocusAndTabIndex(0);
|
251
|
-
event.preventDefault();
|
252
|
-
break;
|
253
|
-
case KEYS.END:
|
254
|
-
this.setFocusAndTabIndex(this.getAllValidOptions().length - 1);
|
255
|
-
event.preventDefault();
|
256
|
-
break;
|
257
|
-
case KEYS.ARROW_DOWN:
|
258
|
-
case KEYS.ARROW_UP:
|
259
|
-
case KEYS.PAGE_DOWN:
|
260
|
-
case KEYS.PAGE_UP:
|
261
|
-
this.handleOptionsNavigation(event);
|
262
|
-
event.preventDefault();
|
263
|
-
break;
|
264
|
-
default:
|
265
|
-
break;
|
266
|
-
}
|
267
|
-
}
|
268
282
|
handleClickCombobox(event) {
|
269
283
|
if (this.disabled || this.softDisabled || this.readonly) {
|
270
284
|
return;
|
271
285
|
}
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
else {
|
276
|
-
this.openPopover();
|
277
|
-
}
|
286
|
+
// If the popover is already open, we close it.
|
287
|
+
// If it is closed, we open it.
|
288
|
+
this.displayPopover = !this.displayPopover;
|
278
289
|
event.stopPropagation();
|
279
290
|
}
|
280
291
|
/**
|
@@ -293,173 +304,126 @@ class Select extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)) {
|
|
293
304
|
switch (event.key) {
|
294
305
|
case KEYS.ARROW_DOWN:
|
295
306
|
case KEYS.ARROW_UP:
|
296
|
-
this.
|
307
|
+
this.displayPopover = true;
|
297
308
|
// Prevent the default browser behavior of scrolling down
|
298
309
|
event.preventDefault();
|
299
310
|
break;
|
300
311
|
case KEYS.ENTER:
|
301
312
|
case KEYS.SPACE:
|
302
|
-
this.
|
313
|
+
this.displayPopover = true;
|
303
314
|
// Prevent the default browser behavior of scrolling down
|
304
315
|
event.preventDefault();
|
305
316
|
event.stopPropagation();
|
306
317
|
break;
|
307
|
-
case KEYS.HOME:
|
308
|
-
this.
|
309
|
-
this.
|
318
|
+
case KEYS.HOME: {
|
319
|
+
this.displayPopover = true;
|
320
|
+
const firstOption = this.getFirstValidOption();
|
321
|
+
if (firstOption) {
|
322
|
+
firstOption === null || firstOption === void 0 ? void 0 : firstOption.focus();
|
323
|
+
this.updateTabIndexForAllOptions(firstOption);
|
324
|
+
}
|
310
325
|
event.preventDefault();
|
311
326
|
break;
|
312
|
-
|
313
|
-
|
314
|
-
this.
|
327
|
+
}
|
328
|
+
case KEYS.END: {
|
329
|
+
this.displayPopover = true;
|
330
|
+
const lastOption = this.getLastValidOption();
|
331
|
+
if (lastOption) {
|
332
|
+
lastOption.focus();
|
333
|
+
this.updateTabIndexForAllOptions(lastOption);
|
334
|
+
}
|
315
335
|
event.preventDefault();
|
316
336
|
break;
|
337
|
+
}
|
317
338
|
default:
|
318
339
|
break;
|
319
340
|
}
|
320
341
|
}
|
321
342
|
/**
|
322
|
-
* Handles the
|
323
|
-
*
|
324
|
-
*
|
343
|
+
* Handles the keydown event on the select element when the popover is open.
|
344
|
+
* The options are as follows:
|
345
|
+
* - HOME: Sets focus and tabindex on the first option.
|
346
|
+
* - END: Sets focus and tabindex on the last option.
|
347
|
+
* - ARROW_DOWN, ARROW_UP, PAGE_DOWN, PAGE_UP: Handles navigation between options.
|
348
|
+
* @param event - The keyboard event.
|
325
349
|
*/
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
350
|
+
handlePopoverKeydown(event) {
|
351
|
+
switch (event.key) {
|
352
|
+
case KEYS.HOME: {
|
353
|
+
const firstOption = this.getFirstValidOption();
|
354
|
+
this.focusAndUpdateTabIndexes(firstOption);
|
355
|
+
event.preventDefault();
|
356
|
+
break;
|
357
|
+
}
|
358
|
+
case KEYS.END: {
|
359
|
+
const lastOption = this.getLastValidOption();
|
360
|
+
this.focusAndUpdateTabIndexes(lastOption);
|
361
|
+
event.preventDefault();
|
362
|
+
break;
|
363
|
+
}
|
364
|
+
case KEYS.ARROW_DOWN: {
|
365
|
+
const options = this.getAllValidOptions();
|
366
|
+
const currentIndex = options.findIndex(option => option === event.target);
|
367
|
+
const newIndex = Math.min(currentIndex + 1, options.length - 1);
|
368
|
+
this.focusAndUpdateTabIndexes(options[newIndex]);
|
369
|
+
event.preventDefault();
|
370
|
+
break;
|
371
|
+
}
|
372
|
+
case KEYS.ARROW_UP: {
|
373
|
+
const options = this.getAllValidOptions();
|
374
|
+
const currentIndex = options.findIndex(option => option === event.target);
|
375
|
+
const newIndex = Math.max(currentIndex - 1, 0);
|
376
|
+
this.focusAndUpdateTabIndexes(options[newIndex]);
|
377
|
+
event.preventDefault();
|
378
|
+
break;
|
379
|
+
}
|
380
|
+
case KEYS.PAGE_DOWN: {
|
381
|
+
const options = this.getAllValidOptions();
|
382
|
+
const currentIndex = options.findIndex(option => option === event.target);
|
383
|
+
const newIndex = Math.min(currentIndex + 10, options.length - 1);
|
384
|
+
this.focusAndUpdateTabIndexes(options[newIndex]);
|
385
|
+
event.preventDefault();
|
386
|
+
break;
|
387
|
+
}
|
388
|
+
case KEYS.PAGE_UP: {
|
389
|
+
const options = this.getAllValidOptions();
|
390
|
+
const currentIndex = options.findIndex(option => option === event.target);
|
391
|
+
const newIndex = Math.max(currentIndex - 10, 0);
|
392
|
+
this.focusAndUpdateTabIndexes(options[newIndex]);
|
393
|
+
event.preventDefault();
|
394
|
+
break;
|
395
|
+
}
|
396
|
+
default:
|
397
|
+
break;
|
335
398
|
}
|
336
399
|
}
|
337
400
|
/**
|
338
|
-
*
|
339
|
-
*
|
340
|
-
*
|
341
|
-
* - ArrowUp: Moves focus to the previous option, if available.
|
342
|
-
* - PageDown: Moves focus 10 options down or to the last option.
|
343
|
-
* - PageUp: Moves focus 10 options up or to the first option.
|
344
|
-
*
|
345
|
-
* @param key - The navigation key that was pressed.
|
346
|
-
* @param currentIndex - The current index of the focused option.
|
347
|
-
* @param optionsLength - The total number of options.
|
348
|
-
* @returns The new index to focus on, or -1 if no movement is possible.
|
401
|
+
* Focuses the given option and updates the tabindex for all options.
|
402
|
+
* @param option - The option to focus.
|
403
|
+
* @internal
|
349
404
|
*/
|
350
|
-
|
351
|
-
if (
|
352
|
-
|
353
|
-
|
354
|
-
if (key === KEYS.ARROW_UP && currentIndex > 0) {
|
355
|
-
return currentIndex - 1;
|
356
|
-
}
|
357
|
-
if (key === KEYS.PAGE_DOWN) {
|
358
|
-
// Jumps visual focus down 10 options (or to last option).
|
359
|
-
return currentIndex + 10 > optionsLength ? optionsLength - 1 : currentIndex + 10;
|
360
|
-
}
|
361
|
-
if (key === KEYS.PAGE_UP) {
|
362
|
-
// Jumps visual focus up 10 options (or to first option).
|
363
|
-
return currentIndex - 10 < 0 ? 0 : currentIndex - 10;
|
405
|
+
focusAndUpdateTabIndexes(option) {
|
406
|
+
if (option) {
|
407
|
+
option.focus();
|
408
|
+
this.updateTabIndexForAllOptions(option);
|
364
409
|
}
|
365
|
-
return -1;
|
366
|
-
}
|
367
|
-
setFocusAndTabIndex(newIndex) {
|
368
|
-
const options = this.getAllValidOptions();
|
369
|
-
const targetOption = options[newIndex];
|
370
|
-
if (targetOption) {
|
371
|
-
targetOption.focus();
|
372
|
-
options.forEach((node, index) => {
|
373
|
-
const newTabindex = newIndex === index ? '0' : '-1';
|
374
|
-
node === null || node === void 0 ? void 0 : node.setAttribute('tabindex', newTabindex);
|
375
|
-
});
|
376
|
-
}
|
377
|
-
}
|
378
|
-
openPopover() {
|
379
|
-
this.displayPopover = true;
|
380
|
-
this.baseIconName = ARROW_ICON.ARROW_UP;
|
381
|
-
}
|
382
|
-
closePopover() {
|
383
|
-
this.displayPopover = false;
|
384
|
-
this.baseIconName = ARROW_ICON.ARROW_DOWN;
|
385
410
|
}
|
386
411
|
/**
|
387
|
-
*
|
388
|
-
*
|
389
|
-
*
|
412
|
+
* If the native input is focused, it will focus the visual combobox.
|
413
|
+
* This is to ensure that the visual combobox is focused when the native input is focused.
|
414
|
+
* For example when a form is submitted and the native input is focused,
|
415
|
+
* we want to focus the visual combobox so that the user can see the selected option
|
416
|
+
* and can interact with it.
|
417
|
+
* @internal
|
390
418
|
*/
|
391
|
-
|
392
|
-
|
393
|
-
this.modifyListBoxWrapper();
|
394
|
-
const options = this.getAllValidOptions();
|
395
|
-
const selectedOptionIndex = options.findIndex(option => option === null || option === void 0 ? void 0 : option.hasAttribute('selected'));
|
396
|
-
if (selectedOptionIndex !== -1) {
|
397
|
-
this.setSelectedValue(options[selectedOptionIndex]);
|
398
|
-
this.updateTabIndexForAllOptions(options[selectedOptionIndex]);
|
399
|
-
}
|
400
|
-
else if (!this.placeholder) {
|
401
|
-
// We will show the first option as selected.
|
402
|
-
this.setSelectedValue(options[0]);
|
403
|
-
this.updateTabIndexForAllOptions();
|
404
|
-
}
|
405
|
-
else if (this.placeholder) {
|
406
|
-
// If there is no default selected option
|
407
|
-
// then we set the placeholder and call the native validity
|
408
|
-
this.manageRequired();
|
409
|
-
}
|
410
|
-
}
|
411
|
-
updated(changedProperties) {
|
412
|
-
super.updated(changedProperties);
|
413
|
-
if (changedProperties.has('disabled') ||
|
414
|
-
changedProperties.has('softDisabled') ||
|
415
|
-
changedProperties.has('readonly')) {
|
416
|
-
if (this.disabled || this.softDisabled || this.readonly) {
|
417
|
-
this.closePopover();
|
418
|
-
}
|
419
|
-
}
|
420
|
-
if (changedProperties.has('dataAriaLabel')) {
|
421
|
-
this.modifyListBoxWrapper();
|
422
|
-
}
|
423
|
-
}
|
424
|
-
handleOnChange() {
|
425
|
-
this.selectedValue = this.inputElement.value;
|
426
|
-
this.internals.setFormValue(this.selectedValue);
|
419
|
+
handleNativeInputFocus() {
|
420
|
+
this.visualCombobox.focus();
|
427
421
|
}
|
428
422
|
render() {
|
429
|
-
var _a, _b;
|
423
|
+
var _a, _b, _c, _d, _e, _f;
|
430
424
|
return html `
|
431
425
|
${this.renderLabel()}
|
432
426
|
<div part="container">
|
433
|
-
<select
|
434
|
-
part="native-select"
|
435
|
-
id="${this.id}"
|
436
|
-
tabindex="-1"
|
437
|
-
aria-hidden="true"
|
438
|
-
name="${this.name}"
|
439
|
-
size="1"
|
440
|
-
.value="${live(this.selectedValue)}"
|
441
|
-
?autofocus="${this.autofocus}"
|
442
|
-
?disabled="${this.disabled}"
|
443
|
-
?required="${this.required}"
|
444
|
-
aria-disabled="${ifDefined(this.disabled || this.softDisabled)}"
|
445
|
-
@change="${this.handleOnChange}"
|
446
|
-
@mousedown="${(event) => event.preventDefault()}"
|
447
|
-
>
|
448
|
-
${this.getAllValidOptions().map(option => {
|
449
|
-
var _a, _b;
|
450
|
-
return html `
|
451
|
-
<option
|
452
|
-
part="native-select"
|
453
|
-
value="${(_a = option.getAttribute('value')) !== null && _a !== void 0 ? _a : ''}"
|
454
|
-
label="${(_b = option.getAttribute('label')) !== null && _b !== void 0 ? _b : ''}"
|
455
|
-
?disabled="${!!option.hasAttribute('disabled')}"
|
456
|
-
?selected="${!!option.hasAttribute('selected')}"
|
457
|
-
>
|
458
|
-
${option.textContent}
|
459
|
-
</option>
|
460
|
-
`;
|
461
|
-
})}
|
462
|
-
</select>
|
463
427
|
<div
|
464
428
|
id="${TRIGGER_ID}"
|
465
429
|
part="base-container"
|
@@ -478,24 +442,48 @@ class Select extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)) {
|
|
478
442
|
aria-disabled="${ifDefined(this.disabled || this.softDisabled)}"
|
479
443
|
aria-readonly="${ifDefined(this.readonly)}"
|
480
444
|
>
|
481
|
-
${this.
|
482
|
-
? html `<mdc-icon
|
445
|
+
${((_b = this.selectedOption) === null || _b === void 0 ? void 0 : _b.prefixIcon)
|
446
|
+
? html `<mdc-icon
|
447
|
+
length-unit="rem"
|
448
|
+
size="1"
|
449
|
+
name="${(_c = this.selectedOption) === null || _c === void 0 ? void 0 : _c.prefixIcon}"
|
450
|
+
part="selected-icon"
|
451
|
+
></mdc-icon>`
|
483
452
|
: nothing}
|
484
453
|
<mdc-text
|
485
|
-
part="base-text ${this.
|
454
|
+
part="base-text ${((_d = this.selectedOption) === null || _d === void 0 ? void 0 : _d.label) ? 'selected' : ''}"
|
486
455
|
type="${TYPE.BODY_MIDSIZE_REGULAR}"
|
487
456
|
tagname="${VALID_TEXT_TAGS.SPAN}"
|
488
457
|
>
|
489
|
-
${(
|
458
|
+
${(_f = (_e = this.selectedOption) === null || _e === void 0 ? void 0 : _e.label) !== null && _f !== void 0 ? _f : this.placeholder}
|
490
459
|
</mdc-text>
|
491
460
|
<div part="icon-container">
|
492
|
-
<mdc-icon
|
461
|
+
<mdc-icon
|
462
|
+
size="1"
|
463
|
+
length-unit="rem"
|
464
|
+
name="${this.displayPopover ? ARROW_ICON.ARROW_UP : ARROW_ICON.ARROW_DOWN}"
|
465
|
+
></mdc-icon>
|
493
466
|
</div>
|
494
467
|
</div>
|
468
|
+
<input
|
469
|
+
id="${this.id}"
|
470
|
+
part="native-input"
|
471
|
+
name="${this.name}"
|
472
|
+
type="text"
|
473
|
+
?autofocus="${this.autofocus}"
|
474
|
+
?disabled=${this.disabled}
|
475
|
+
?required=${this.required}
|
476
|
+
?readonly=${this.readonly}
|
477
|
+
tabindex="-1"
|
478
|
+
aria-hidden="true"
|
479
|
+
@focus=${this.handleNativeInputFocus}
|
480
|
+
@invalid=${this.setInputValidity}
|
481
|
+
aria-disabled="${ifDefined(this.disabled || this.softDisabled)}"
|
482
|
+
/>
|
495
483
|
<mdc-popover
|
496
484
|
trigger="manual"
|
497
485
|
triggerid="${TRIGGER_ID}"
|
498
|
-
@keydown="${this.
|
486
|
+
@keydown="${this.handlePopoverKeydown}"
|
499
487
|
interactive
|
500
488
|
?visible="${this.displayPopover}"
|
501
489
|
role=""
|
@@ -505,8 +493,12 @@ class Select extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)) {
|
|
505
493
|
focus-trap
|
506
494
|
size
|
507
495
|
placement="${this.placement}"
|
508
|
-
@closebyescape="${
|
509
|
-
|
496
|
+
@closebyescape="${() => {
|
497
|
+
this.displayPopover = false;
|
498
|
+
}}"
|
499
|
+
@closebyoutsideclick="${() => {
|
500
|
+
this.displayPopover = false;
|
501
|
+
}}"
|
510
502
|
style="--mdc-popover-max-width: 100%; --mdc-popover-max-height: ${this.height};"
|
511
503
|
>
|
512
504
|
<slot @click="${this.handleOptionsClick}"></slot>
|
@@ -542,27 +534,15 @@ __decorate([
|
|
542
534
|
__metadata("design:type", Array)
|
543
535
|
], Select.prototype, "slottedListboxes", void 0);
|
544
536
|
__decorate([
|
545
|
-
|
546
|
-
__metadata("design:type",
|
547
|
-
], Select.prototype, "
|
548
|
-
__decorate([
|
549
|
-
state(),
|
550
|
-
__metadata("design:type", String)
|
551
|
-
], Select.prototype, "selectedValueText", void 0);
|
552
|
-
__decorate([
|
553
|
-
state(),
|
554
|
-
__metadata("design:type", Object)
|
555
|
-
], Select.prototype, "selectedIcon", void 0);
|
537
|
+
query(`[id="${TRIGGER_ID}"]`),
|
538
|
+
__metadata("design:type", HTMLDivElement)
|
539
|
+
], Select.prototype, "visualCombobox", void 0);
|
556
540
|
__decorate([
|
557
541
|
state(),
|
558
542
|
__metadata("design:type", Object)
|
559
|
-
], Select.prototype, "
|
543
|
+
], Select.prototype, "selectedOption", void 0);
|
560
544
|
__decorate([
|
561
545
|
state(),
|
562
546
|
__metadata("design:type", Object)
|
563
547
|
], Select.prototype, "displayPopover", void 0);
|
564
|
-
__decorate([
|
565
|
-
query('select'),
|
566
|
-
__metadata("design:type", HTMLInputElement)
|
567
|
-
], Select.prototype, "inputElement", void 0);
|
568
548
|
export default Select;
|