@momentum-design/components 0.112.6 → 0.113.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 (29) hide show
  1. package/dist/browser/index.js +619 -388
  2. package/dist/browser/index.js.map +4 -4
  3. package/dist/components/combobox/combobox.component.d.ts +261 -7
  4. package/dist/components/combobox/combobox.component.js +737 -9
  5. package/dist/components/combobox/combobox.constants.d.ts +8 -1
  6. package/dist/components/combobox/combobox.constants.js +8 -1
  7. package/dist/components/combobox/combobox.events.d.ts +27 -0
  8. package/dist/components/combobox/combobox.events.js +35 -0
  9. package/dist/components/combobox/combobox.styles.js +122 -1
  10. package/dist/components/combobox/combobox.types.d.ts +22 -1
  11. package/dist/components/combobox/combobox.types.js +0 -1
  12. package/dist/components/combobox/index.d.ts +5 -0
  13. package/dist/components/combobox/index.js +5 -0
  14. package/dist/components/divider/divider.styles.js +4 -0
  15. package/dist/components/input/input.styles.js +10 -10
  16. package/dist/components/optgroup/optgroup.styles.js +3 -0
  17. package/dist/components/option/option.styles.d.ts +2 -2
  18. package/dist/components/option/option.styles.js +36 -23
  19. package/dist/components/popover/popover.component.d.ts +2 -2
  20. package/dist/components/popover/popover.component.js +1 -1
  21. package/dist/components/popover/popover.constants.d.ts +5 -1
  22. package/dist/components/popover/popover.constants.js +7 -2
  23. package/dist/components/popover/popover.types.d.ts +3 -2
  24. package/dist/custom-elements.json +11704 -10553
  25. package/dist/react/combobox/index.d.ts +71 -4
  26. package/dist/react/combobox/index.js +60 -4
  27. package/dist/react/index.d.ts +7 -7
  28. package/dist/react/index.js +7 -7
  29. package/package.json +1 -1
@@ -1,20 +1,748 @@
1
- import { html } from 'lit';
2
- import { Component } from '../../models';
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { html, nothing } from 'lit';
11
+ import { property, query, queryAssignedElements, state } from 'lit/decorators.js';
12
+ import { ifDefined } from 'lit/directives/if-defined.js';
13
+ import { live } from 'lit/directives/live.js';
14
+ import { ElementStore } from '../../utils/controllers/ElementStore';
15
+ import { KEYS } from '../../utils/keys';
16
+ import { AutoFocusOnMountMixin } from '../../utils/mixins/AutoFocusOnMountMixin';
17
+ import { DataAriaLabelMixin } from '../../utils/mixins/DataAriaLabelMixin';
18
+ import { FormInternalsMixin } from '../../utils/mixins/FormInternalsMixin';
19
+ import { CaptureDestroyEventForChildElement } from '../../utils/mixins/lifecycle/CaptureDestroyEventForChildElement';
20
+ import { LIFE_CYCLE_EVENTS } from '../../utils/mixins/lifecycle/lifecycle.contants';
21
+ import { ROLE } from '../../utils/roles';
22
+ import { TAG_NAME as DIVIDER_TAG_NAME } from '../divider/divider.constants';
23
+ import FormfieldWrapper from '../formfieldwrapper/formfieldwrapper.component';
24
+ import { DEFAULTS as FORMFIELD_DEFAULTS, VALIDATION } from '../formfieldwrapper/formfieldwrapper.constants';
25
+ import Input from '../input/input.component';
26
+ import { AUTO_COMPLETE } from '../input/input.constants';
27
+ import { TAG_NAME as OPTIONGROUP_TAG_NAME } from '../optgroup/optgroup.constants';
28
+ import { TAG_NAME as OPTION_TAG_NAME } from '../option/option.constants';
29
+ import { DEFAULTS as POPOVER_DEFAULTS, POPOVER_PLACEMENT, TRIGGER } from '../popover/popover.constants';
30
+ import { TAG_NAME as SELECTLISTBOX_TAG_NAME } from '../selectlistbox/selectlistbox.constants';
31
+ import { AUTOCOMPLETE_LIST, ICON_NAME, TRIGGER_ID } from './combobox.constants';
32
+ import { ComboboxEventManager } from './combobox.events';
3
33
  import styles from './combobox.styles';
4
34
  /**
5
- * combobox component, which ...
35
+ * The Combobox component is a text-based dropdown control that allows users to select an option from a predefined list.
36
+ * Users can type text to filter the options and select their desired choice.
37
+ *
38
+ * When the user starts typing, the filter uses a "starts with" search and displays options based on the text entered by the user.
39
+ * If the user entered text that doesn't match with any of the options, then the text in the `no-result-text` attribute will be displayed.
40
+ *
41
+ * If there is no text in the `no-result-text` attribute then nothing will be shown.
42
+ *
43
+ * Combobox is designed to work with `mdc-option` for individual options and `mdc-optgroup` for grouping related options.
44
+ * The component ensures accessibility and usability while handling various use cases, including long text truncation with tooltip support.
45
+ *
46
+ * Every mdc-option should have a `value` attribute set to ensure proper form submission.
47
+ *
48
+ * To set a default option, use the `selected` attribute on the `mdc-option` element.
49
+ *
50
+ * **Note:** Make sure to add `mdc-selectlistbox` as a child of `mdc-combobox` and wrap options/optgroup in it to ensure proper accessibility functionality. Read more about it in SelectListBox documentation.
51
+ *
52
+ * If you need to use `mdc-tooltip` with any options, make sure to place the tooltip component outside the `mdc-selectlistbox` element. Read more about it in Options documentation.
53
+ *
54
+ * To understand more about combobox and its patterns, refer to this [WCAG example](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/).
55
+ *
56
+ * @dependency mdc-buttonsimple
57
+ * @dependency mdc-icon
58
+ * @dependency mdc-input
59
+ * @dependency mdc-listitem
60
+ * @dependency mdc-popover
6
61
  *
7
62
  * @tagname mdc-combobox
8
63
  *
9
- * @slot default - This is a default/unnamed slot
64
+ * @slot default - This is a default/unnamed slot for Selectlistbox including options and/or option group.
10
65
  *
11
- * @cssproperty --custom-property-name - Description of the CSS custom property
66
+ * @event click - (React: onClick) This event is dispatched when the combobox is clicked.
67
+ * @event change - (React: onChange) This event is dispatched when the combobox is changed.
68
+ * @event input - (React: onInput) This event is dispatched when the combobox is changed.
69
+ * @event keydown - (React: onKeyDown) This event is dispatched when a key is pressed down on the combobox.
70
+ * @event focus - (React: onFocus) This event is dispatched when the combobox receives focus.
71
+ *
72
+ * @cssproperty --mdc-combobox-border-color - The border color of the combobox
73
+ * @cssproperty --mdc-combobox-icon-color - The icon color of the combobox
74
+ * @cssproperty --mdc-combobox-listbox-height - The height of the listbox inside the combobox
75
+ * @cssproperty --mdc-combobox-listbox-width - The width of the listbox inside the combobox
76
+ * @cssproperty --mdc-combobox-width - The width of the combobox
77
+ * @cssproperty --mdc-combobox-hover-background-color - The background color of the combobox when hovered
78
+ * @cssproperty --mdc-combobox-focused-background-color - The background color of the combobox when focused
79
+ * @cssproperty --mdc-combobox-error-border-color - The border color of the combobox when in error state
80
+ * @cssproperty --mdc-combobox-warning-border-color - The border color of the combobox when in warning state
81
+ * @cssproperty --mdc-combobox-success-border-color - The border color of the combobox when in success state
82
+ * @cssproperty --mdc-combobox-primary-border-color - The border color of the combobox when in primary state
83
+ * @cssproperty --mdc-combobox-text-color-disabled - The text color of the combobox when disabled
84
+ * @cssproperty --mdc-combobox-focused-border-color - The border color of the combobox when focused
85
+ *
86
+ * @csspart internal-native-input - The internal native input element of the combobox.
87
+ * @csspart mdc-input - The input element of the combobox.
88
+ * @csspart no-result-text - The no result text element of the combobox.
89
+ * @csspart combobox__base - The base container element of the combobox.
90
+ * @csspart combobox__button - The button element of the combobox.
91
+ * @csspart combobox__button-icon - The icon element of the button of the combobox.
12
92
  */
13
- class Combobox extends Component {
93
+ class Combobox extends CaptureDestroyEventForChildElement(AutoFocusOnMountMixin(FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)))) {
94
+ /** @internal */
95
+ get navItems() {
96
+ return this.itemsStore.items;
97
+ }
98
+ constructor() {
99
+ super();
100
+ /**
101
+ * readonly attribute of the combobox field. If true, the combobox is read-only.
102
+ * @default false
103
+ */
104
+ this.readonly = false;
105
+ /**
106
+ * The placement of the popover within Combobox.
107
+ * This defines the position of the popover relative to the combobox input field.
108
+ *
109
+ * Possible values:
110
+ * - 'top-start'
111
+ * - 'bottom-start'
112
+ * @default 'bottom-start'
113
+ */
114
+ this.placement = POPOVER_PLACEMENT.BOTTOM_START;
115
+ /**
116
+ * This describes the clipping element(s) or area relative to which the overflow of the popover will be checked.
117
+ * The default is 'clippingAncestors', which are the overflow ancestors that can cause the element to be clipped.
118
+ * Possible values:
119
+ * - 'clippingAncestors'
120
+ * - any css selector
121
+ *
122
+ * @default 'clippingAncestors'
123
+ *
124
+ * @see [Floating UI - boundary](https://floating-ui.com/docs/detectOverflow#boundary)
125
+ */
126
+ this.boundary = POPOVER_DEFAULTS.BOUNDARY;
127
+ /**
128
+ * The strategy of the popover within Combobox.
129
+ * This determines how the popover is positioned in the DOM.
130
+ *
131
+ * In case `boundary` is set to something other than 'clippingAncestors',
132
+ * it might be necessary to set the `strategy` to 'fixed' to ensure that the popover
133
+ * is not getting clipped by scrollable containers enclosing the combobox.
134
+ *
135
+ * @default absolute
136
+ * @see [Floating UI - strategy](https://floating-ui.com/docs/computePosition#strategy)
137
+ */
138
+ this.strategy = POPOVER_DEFAULTS.STRATEGY;
139
+ /**
140
+ * The z-index of the popover within Combobox.
141
+ *
142
+ * Override this to make sure this stays on top of other components.
143
+ * @default 1000
144
+ */
145
+ this.popoverZIndex = POPOVER_DEFAULTS.Z_INDEX;
146
+ /** @internal */
147
+ this.isOpen = false;
148
+ /** @internal */
149
+ this.filteredValue = '';
150
+ /** @internal */
151
+ this.initialSelectedOption = null;
152
+ this.handleUpdateError = (error) => {
153
+ if (this.onerror) {
154
+ this.onerror(error);
155
+ }
156
+ };
157
+ /**
158
+ * Update the focus when an item is removed.
159
+ * If there is a next item, focus it. If not, focus the previous item.
160
+ *
161
+ * @internal
162
+ */
163
+ this.handleDestroyEvent = (event) => {
164
+ const destroyedElement = event.target;
165
+ if (!this.isValidItem(destroyedElement) || destroyedElement.tabIndex !== 0) {
166
+ return;
167
+ }
168
+ const destroyedItemIndex = this.navItems.findIndex(node => node === destroyedElement);
169
+ if (destroyedItemIndex === -1) {
170
+ return;
171
+ }
172
+ let newIndex = destroyedItemIndex + 1;
173
+ if (newIndex >= this.navItems.length) {
174
+ newIndex = destroyedItemIndex - 1;
175
+ }
176
+ };
177
+ this.addEventListener(LIFE_CYCLE_EVENTS.DESTROYED, this.handleDestroyEvent);
178
+ // This must be initialized after the destroyed event listener
179
+ // to keep the element in the itemStore in order to move the focus correctly
180
+ this.itemsStore = new ElementStore(this, {
181
+ isValidItem: this.isValidItem,
182
+ });
183
+ }
184
+ connectedCallback() {
185
+ super.connectedCallback();
186
+ this.updateComplete
187
+ .then(() => {
188
+ if (this.inputElement) {
189
+ this.setInputValidity();
190
+ this.internals.setFormValue(this.inputElement.value);
191
+ }
192
+ })
193
+ .catch(this.handleUpdateError);
194
+ }
195
+ isValidItem(item) {
196
+ return item.matches(`${OPTION_TAG_NAME}:not([disabled])`);
197
+ }
198
+ openPopover() {
199
+ this.isOpen = true;
200
+ }
201
+ closePopover() {
202
+ this.isOpen = false;
203
+ }
204
+ toggleDropdown() {
205
+ this.isOpen = !this.isOpen;
206
+ }
207
+ compareOptionWithValue(option, value) {
208
+ const optionValue = option.getAttribute('label') || '';
209
+ return optionValue.toLowerCase().startsWith(value === null || value === void 0 ? void 0 : value.toLowerCase());
210
+ }
211
+ getFirstSelectedOption() {
212
+ return this.navItems.find(el => el.hasAttribute('selected'));
213
+ }
214
+ getVisibleOptions(internalValue) {
215
+ return this.navItems.filter(option => this.compareOptionWithValue(option, internalValue));
216
+ }
217
+ setSelectedValue(option) {
218
+ // this.value is the actual value of the component
219
+ this.value = (option === null || option === void 0 ? void 0 : option.getAttribute('value')) || '';
220
+ // this.filteredValue is the visible label of the component
221
+ this.filteredValue = (option === null || option === void 0 ? void 0 : option.getAttribute('label')) || '';
222
+ this.internals.setFormValue(this.value);
223
+ this.updateHiddenOptions();
224
+ this.updateSelectedOption(option);
225
+ this.setInputValidity();
226
+ this.resetHelpText();
227
+ ComboboxEventManager.onInputCombobox(this, option);
228
+ ComboboxEventManager.onChangeCombobox(this, option);
229
+ }
230
+ /**
231
+ * Resets the selected value to an empty string and clears the form value.
232
+ * This method is called when there is a change on the input.
233
+ */
234
+ resetSelectedValue() {
235
+ this.value = '';
236
+ this.internals.setFormValue(this.value);
237
+ this.resetHelpText();
238
+ }
239
+ resetHelpText() {
240
+ if (this.invalidCustomValueText && this.helpText === this.invalidCustomValueText) {
241
+ this.helpText = '';
242
+ this.helpTextType = VALIDATION.DEFAULT;
243
+ }
244
+ }
245
+ /**
246
+ * This function is called when the attribute changes.
247
+ * It updates the validity of the input field based on the input field's validity.
248
+ *
249
+ * @param name - attribute name
250
+ * @param old - old value
251
+ * @param value - new value
252
+ */
253
+ attributeChangedCallback(name, old, value) {
254
+ super.attributeChangedCallback(name, old, value);
255
+ if (name === 'validation-message') {
256
+ this.updateComplete
257
+ .then(() => {
258
+ this.setInputValidity();
259
+ })
260
+ .catch(this.handleUpdateError);
261
+ }
262
+ }
263
+ async firstUpdated(_changedProperties) {
264
+ await this.updateComplete;
265
+ // set the element to auto focus if autoFocusOnMount is set to true
266
+ // before running the super method, so that the AutoFocusOnMountMixin can use it
267
+ // to focus the correct element
268
+ if (this.inputElement && this.autoFocusOnMount) {
269
+ this.elementToAutoFocus = this.inputElement;
270
+ }
271
+ super.firstUpdated(_changedProperties);
272
+ const firstSelectedOption = this.getFirstSelectedOption();
273
+ if (firstSelectedOption) {
274
+ this.initialSelectedOption = firstSelectedOption;
275
+ this.setSelectedValue(firstSelectedOption);
276
+ }
277
+ else if (this.value) {
278
+ const validOption = this.navItems.find(option => option.value === this.value);
279
+ this.setSelectedValue(validOption);
280
+ }
281
+ else if (this.placeholder) {
282
+ this.setInputValidity();
283
+ }
284
+ this.navItems.forEach(option => {
285
+ option.setAttribute('tabindex', '-1');
286
+ });
287
+ }
288
+ updated(changedProperties) {
289
+ super.updated(changedProperties);
290
+ if (changedProperties.has('slottedListboxes') || changedProperties.has('isOpen')) {
291
+ // Ensure the element exists and has the ariaControlsElements property
292
+ if (this.visualCombobox && 'ariaControlsElements' in this.visualCombobox) {
293
+ this.visualCombobox.ariaControlsElements = this.slottedListboxes;
294
+ }
295
+ if (this.dropDownButton && 'ariaControlsElements' in this.dropDownButton) {
296
+ this.dropDownButton.ariaControlsElements = this.slottedListboxes;
297
+ }
298
+ }
299
+ if (changedProperties.has('disabled') || changedProperties.has('readonly')) {
300
+ if (this.disabled || this.readonly) {
301
+ // If the combobox is disabled or readonly,
302
+ // we close the popover if it is open.
303
+ this.closePopover();
304
+ }
305
+ }
306
+ }
307
+ /**
308
+ * Sets the validity of the input element based on the selected option.
309
+ * If the selected option is not set and the combobox is required,
310
+ * it sets a custom validation message.
311
+ * If the selected option is set or the combobox is not required,
312
+ * it clears the custom validation message.
313
+ * This method is called to ensure that the combobox component behaves correctly
314
+ * in form validation scenarios, especially when the combobox is required.
315
+ * @internal
316
+ */
317
+ setInputValidity() {
318
+ var _a, _b;
319
+ if (!this.value && this.required) {
320
+ if (this.validationMessage) {
321
+ (_a = this.inputElement) === null || _a === void 0 ? void 0 : _a.setCustomValidity(this.validationMessage);
322
+ }
323
+ else {
324
+ (_b = this.inputElement) === null || _b === void 0 ? void 0 : _b.setCustomValidity('');
325
+ }
326
+ this.setValidity();
327
+ }
328
+ else {
329
+ this.internals.setValidity({});
330
+ }
331
+ }
332
+ /**
333
+ * Resets the combobox to its initially selected option.
334
+ * @internal
335
+ */
336
+ formResetCallback() {
337
+ var _a;
338
+ const optionToResetTo = this.initialSelectedOption || null;
339
+ // Restore the selected option
340
+ this.setSelectedValue(optionToResetTo);
341
+ // Reset the filtered text (typed value shown in input)
342
+ this.filteredValue = (_a = optionToResetTo === null || optionToResetTo === void 0 ? void 0 : optionToResetTo.label) !== null && _a !== void 0 ? _a : '';
343
+ // Force revalidation after reset
344
+ this.setInputValidity();
345
+ }
346
+ /** @internal */
347
+ formStateRestoreCallback(state) {
348
+ const optionToRestoreTo = this.navItems.find(option => option.value === state || option.label === state);
349
+ this.setSelectedValue(optionToRestoreTo || null);
350
+ }
351
+ /**
352
+ * When the native input is focused, visually highlight the dropdown options (the "visual combobox"),
353
+ * so users can see which option is active, even though the actual DOM focus remains on the input box.
354
+ * This ensures that after actions like form submission, users can still interact with the dropdown options
355
+ * through visual cues, while keyboard focus stays on the input field.
356
+ * @internal
357
+ */
358
+ handleNativeInputFocus() {
359
+ this.visualCombobox.focus();
360
+ }
361
+ /**
362
+ * Resets the focused option in the dropdown list.
363
+ * This method removes the 'data-focused' attribute from all options that currently have it,
364
+ * effectively clearing any visual indication of focus within the dropdown.
365
+ * It is typically called when the user navigates away from the dropdown or when the dropdown is closed,
366
+ * ensuring that no option remains visually highlighted as focused.
367
+ * @internal
368
+ */
369
+ resetFocusedOption() {
370
+ this.navItems
371
+ .filter(option => option.hasAttribute('data-focused'))
372
+ .forEach(option => this.updateOptionAttributes(option, false));
373
+ }
374
+ updateSelectedOption(newOption) {
375
+ this.navItems.forEach(option => {
376
+ option.removeAttribute('selected');
377
+ });
378
+ newOption === null || newOption === void 0 ? void 0 : newOption.setAttribute('selected', '');
379
+ }
380
+ /**
381
+ * Updates the visual focus state of a specific option in the dropdown list based on 'data-focused' attribute.
382
+ * It also updates the 'aria-selected' attribute for a11y purposes.
383
+ *
384
+ * @param option - The option element to update focus state for.
385
+ * @param value - The new focus state to set (true for focused, false for unfocused).
386
+ */
387
+ updateOptionAttributes(option, value) {
388
+ if (option === undefined)
389
+ return;
390
+ if (value) {
391
+ option.setAttribute('data-focused', '');
392
+ }
393
+ else {
394
+ option.removeAttribute('data-focused');
395
+ }
396
+ option.setAttribute('aria-selected', value.toString());
397
+ }
398
+ /**
399
+ * Handles the blur event of the combobox.
400
+ * This method is called when the combobox loses focus.
401
+ * It checks if the combobox has a focused option and sets the selected value to that option and closes the popover.
402
+ * If the combobox does not have a focused option and the filtered value is not empty,
403
+ * it sets the help text to the invalid custom value text and closes the popover.
404
+ * It also updates the input validity.
405
+ */
406
+ handleBlurChange() {
407
+ const options = this.getVisibleOptions(this.filteredValue);
408
+ const activeIndex = options.findIndex(option => option.hasAttribute('data-focused'));
409
+ if (activeIndex !== -1) {
410
+ this.setSelectedValue(options[activeIndex]);
411
+ this.closePopover();
412
+ return;
413
+ }
414
+ if (activeIndex === -1 &&
415
+ this.filteredValue !== '' &&
416
+ this.invalidCustomValueText &&
417
+ !this.getFirstSelectedOption()) {
418
+ this.helpText = this.invalidCustomValueText;
419
+ this.helpTextType = VALIDATION.ERROR;
420
+ }
421
+ // In common cases (when no selection made and focus moved away), close the popover.
422
+ this.setInputValidity();
423
+ }
424
+ updateFocusAndScrollIntoView(options, oldIndex, newIndex) {
425
+ var _a;
426
+ this.updateOptionAttributes(options[oldIndex], false);
427
+ this.updateOptionAttributes(options[newIndex], true);
428
+ (_a = options[newIndex]) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ block: 'nearest' });
429
+ }
430
+ handleInputKeydown(event) {
431
+ const options = this.getVisibleOptions(this.filteredValue);
432
+ const activeIndex = options.findIndex(option => option.hasAttribute('data-focused'));
433
+ switch (event.key) {
434
+ case KEYS.ARROW_DOWN: {
435
+ this.openPopover();
436
+ const newIndex = options.length - 1 === activeIndex ? 0 : activeIndex + 1;
437
+ this.updateFocusAndScrollIntoView(options, activeIndex, newIndex);
438
+ event.preventDefault();
439
+ break;
440
+ }
441
+ case KEYS.ARROW_UP: {
442
+ this.openPopover();
443
+ const newIndex = activeIndex === -1 || activeIndex === 0 ? options.length - 1 : activeIndex - 1;
444
+ this.updateFocusAndScrollIntoView(options, activeIndex, newIndex);
445
+ event.preventDefault();
446
+ break;
447
+ }
448
+ case KEYS.ENTER: {
449
+ if (activeIndex === -1)
450
+ return;
451
+ this.setSelectedValue(options[activeIndex]);
452
+ if (this.isOpen === true) {
453
+ this.closePopover();
454
+ }
455
+ break;
456
+ }
457
+ case KEYS.ESCAPE: {
458
+ if (activeIndex !== -1) {
459
+ this.updateOptionAttributes(options[activeIndex], false);
460
+ }
461
+ if (options.length && this.shouldDisplayPopover(options.length)) {
462
+ this.closePopover();
463
+ }
464
+ else {
465
+ this.resetSelectedValue();
466
+ // clear the visible value
467
+ this.filteredValue = '';
468
+ }
469
+ break;
470
+ }
471
+ case KEYS.TAB: {
472
+ this.closePopover();
473
+ break;
474
+ }
475
+ case KEYS.HOME:
476
+ case KEYS.END: {
477
+ this.resetFocusedOption();
478
+ break;
479
+ }
480
+ default:
481
+ break;
482
+ }
483
+ }
484
+ /**
485
+ * Updates the hidden state of options based on the current filtered value.
486
+ * If an option does not match the current filtered value, it is hidden.
487
+ * Otherwise, it is made visible.
488
+ * Additionally, it updates the hidden state of option groups and dividers based on the current filtered value.
489
+ */
490
+ updateHiddenOptions() {
491
+ this.navItems.forEach(option => {
492
+ if (!this.compareOptionWithValue(option, this.filteredValue)) {
493
+ option.setAttribute('data-hidden', '');
494
+ this.hideOptionGroupAndDivider(option);
495
+ }
496
+ else {
497
+ option.removeAttribute('data-hidden');
498
+ this.showOptionGroupAndDivider(option);
499
+ }
500
+ });
501
+ }
502
+ hideOptionGroupAndDivider(option) {
503
+ var _a, _b, _c;
504
+ if ((_a = option.parentElement) === null || _a === void 0 ? void 0 : _a.matches(OPTIONGROUP_TAG_NAME)) {
505
+ const optionGroupChildren = (_b = Array.from(option.parentElement.children)) === null || _b === void 0 ? void 0 : _b.filter(option => !option.hasAttribute('data-hidden'));
506
+ if (optionGroupChildren.length === 0) {
507
+ option.parentElement.setAttribute('data-hidden', '');
508
+ if ((_c = option.parentElement.nextElementSibling) === null || _c === void 0 ? void 0 : _c.matches(DIVIDER_TAG_NAME)) {
509
+ option.parentElement.nextElementSibling.setAttribute('data-hidden', '');
510
+ }
511
+ }
512
+ }
513
+ }
514
+ showOptionGroupAndDivider(option) {
515
+ var _a, _b, _c;
516
+ if ((_a = option.parentElement) === null || _a === void 0 ? void 0 : _a.matches(OPTIONGROUP_TAG_NAME)) {
517
+ const optionGroupChildren = (_b = Array.from(option.parentElement.children)) === null || _b === void 0 ? void 0 : _b.filter(option => !option.hasAttribute('data-hidden'));
518
+ if (optionGroupChildren.length > 0) {
519
+ option.parentElement.removeAttribute('data-hidden');
520
+ if ((_c = option.parentElement.nextElementSibling) === null || _c === void 0 ? void 0 : _c.matches(DIVIDER_TAG_NAME)) {
521
+ option.parentElement.nextElementSibling.removeAttribute('data-hidden');
522
+ }
523
+ }
524
+ }
525
+ }
526
+ handleInputChange(event) {
527
+ var _a;
528
+ this.filteredValue = event.target.value;
529
+ this.resetSelectedValue();
530
+ this.resetFocusedOption();
531
+ this.updateHiddenOptions();
532
+ // remove the selected attribute on input change
533
+ (_a = this.getFirstSelectedOption()) === null || _a === void 0 ? void 0 : _a.removeAttribute('selected');
534
+ if (this.isOpen === false) {
535
+ this.openPopover();
536
+ }
537
+ }
538
+ handleOptionsClick(event) {
539
+ var _a;
540
+ // ensure we get the actual option element even if the click target is a child node
541
+ const option = (_a = event.target.closest(OPTION_TAG_NAME)) !== null && _a !== void 0 ? _a : null;
542
+ if (option && !option.hasAttribute('disabled')) {
543
+ this.setSelectedValue(option);
544
+ this.closePopover();
545
+ // Focus on the combobox after click
546
+ this.updateComplete.then(() => this.handleNativeInputFocus()).catch(this.handleUpdateError);
547
+ }
548
+ }
549
+ shouldDisplayPopover(optionsLength) {
550
+ if (this.disabled || this.readonly) {
551
+ return false;
552
+ }
553
+ if (optionsLength) {
554
+ return this.isOpen;
555
+ }
556
+ if (this.noResultText) {
557
+ return true;
558
+ }
559
+ return false;
560
+ }
561
+ /**
562
+ * Renders the native input element.
563
+ * This input is hidden and is used for internal purposes only.
564
+ * The value of the selected option is set as the value of this input.
565
+ * @internal
566
+ */
567
+ renderNativeInput() {
568
+ return html `
569
+ <input
570
+ id="${this.inputId}"
571
+ name="${this.name}"
572
+ type="text"
573
+ .value="${live(this.value)}"
574
+ aria-hidden="true"
575
+ part="internal-native-input"
576
+ tabindex="-1"
577
+ ?required=${this.required}
578
+ ?disabled=${this.disabled}
579
+ ?readonly=${this.readonly}
580
+ autocomplete="${AUTO_COMPLETE.OFF}"
581
+ @focus=${this.handleNativeInputFocus}
582
+ @invalid=${this.setInputValidity}
583
+ />
584
+ `;
585
+ }
586
+ /**
587
+ * Renders the base input element with accessibility.
588
+ * This input is displayed on the screen and is used for user interaction only.
589
+ * The label of the selected option is set as the value of this input.
590
+ * @internal
591
+ */
592
+ renderBaseInput() {
593
+ var _a;
594
+ return html `
595
+ <input
596
+ id="${this.id}"
597
+ slot="input"
598
+ ?disabled="${this.disabled}"
599
+ .value="${live(this.filteredValue)}"
600
+ autocomplete="${AUTO_COMPLETE.OFF}"
601
+ class="input"
602
+ part="mdc-input"
603
+ placeholder="${ifDefined(this.placeholder)}"
604
+ role="${ROLE.COMBOBOX}"
605
+ ?readonly="${this.readonly}"
606
+ ?required="${this.required}"
607
+ @input=${this.handleInputChange}
608
+ @keydown=${this.handleInputKeydown}
609
+ @blur="${this.handleBlurChange}"
610
+ aria-autocomplete="${AUTOCOMPLETE_LIST}"
611
+ aria-describedby="${ifDefined(this.helpText ? FORMFIELD_DEFAULTS.HELPER_TEXT_ID : '')}"
612
+ aria-disabled="${this.disabled ? 'true' : 'false'}"
613
+ aria-expanded="${this.isOpen ? 'true' : 'false'}"
614
+ aria-haspopup="${ROLE.LISTBOX}"
615
+ aria-invalid="${this.helpTextType === VALIDATION.ERROR ? 'true' : 'false'}"
616
+ aria-label="${(_a = this.dataAriaLabel) !== null && _a !== void 0 ? _a : ''}"
617
+ aria-labelledby="${this.label ? FORMFIELD_DEFAULTS.HEADING_ID : ''}"
618
+ aria-readonly="${this.readonly ? 'true' : 'false'}"
619
+ aria-required="${this.required ? 'true' : 'false'}"
620
+ />
621
+ `;
622
+ }
623
+ renderNoResultsText(optionsLength) {
624
+ return optionsLength === 0 && this.noResultText
625
+ ? html `<mdc-listitem part="no-result-text" tabindex="-1" role="" label="${this.noResultText}"></mdc-listitem>`
626
+ : nothing;
627
+ }
14
628
  render() {
15
- return html `<p>This is a dummy combobox component!</p>
16
- <slot></slot>`;
629
+ var _a;
630
+ const options = this.getVisibleOptions(this.filteredValue);
631
+ return html `
632
+ ${this.renderLabel()}
633
+ <div part="combobox__base" id="${TRIGGER_ID}">
634
+ ${this.renderNativeInput()}
635
+ <mdc-input
636
+ @click="${() => this.toggleDropdown()}"
637
+ ?disabled="${this.disabled}"
638
+ ?readonly="${this.readonly}"
639
+ help-text-type="${this.helpTextType}"
640
+ >
641
+ ${this.renderBaseInput()}
642
+ </mdc-input>
643
+ <mdc-buttonsimple
644
+ @click="${() => this.toggleDropdown()}"
645
+ part="combobox__button"
646
+ ?disabled="${this.disabled}"
647
+ tabindex="-1"
648
+ aria-expanded="${this.isOpen ? 'true' : 'false'}"
649
+ aria-label="${(_a = this.dataAriaLabel) !== null && _a !== void 0 ? _a : ''}"
650
+ >
651
+ <mdc-icon
652
+ part="combobox__button-icon"
653
+ name="${this.shouldDisplayPopover(options.length) ? ICON_NAME.ARROW_UP : ICON_NAME.ARROW_DOWN}"
654
+ size="1"
655
+ length-unit="rem"
656
+ ></mdc-icon>
657
+ </mdc-buttonsimple>
658
+ <mdc-popover
659
+ ?visible="${this.shouldDisplayPopover(options.length)}"
660
+ @closebyescape="${() => {
661
+ this.closePopover();
662
+ }}"
663
+ @closebyoutsideclick="${() => {
664
+ this.closePopover();
665
+ this.handleNativeInputFocus();
666
+ }}"
667
+ backdrop
668
+ backdrop-append-to="${ifDefined(this.backdropAppendTo)}"
669
+ boundary="${ifDefined(this.boundary)}"
670
+ disable-aria-expanded
671
+ exportparts="popover-content"
672
+ hide-on-escape
673
+ hide-on-outside-click
674
+ is-backdrop-invisible
675
+ placement="${this.placement}"
676
+ role=""
677
+ size
678
+ strategy="${ifDefined(this.strategy)}"
679
+ trigger="${TRIGGER.MANUAL}"
680
+ triggerid="${TRIGGER_ID}"
681
+ z-index="${ifDefined(this.popoverZIndex)}"
682
+ >
683
+ ${this.renderNoResultsText(options.length)}
684
+ <slot @click="${this.handleOptionsClick}"></slot>
685
+ </mdc-popover>
686
+ </div>
687
+ ${this.renderHelperText()}
688
+ `;
17
689
  }
18
690
  }
19
- Combobox.styles = [...Component.styles, ...styles];
691
+ Combobox.styles = [...FormfieldWrapper.styles, ...Input.styles, ...styles];
692
+ __decorate([
693
+ property({ type: String }),
694
+ __metadata("design:type", String)
695
+ ], Combobox.prototype, "placeholder", void 0);
696
+ __decorate([
697
+ property({ type: Boolean }),
698
+ __metadata("design:type", Object)
699
+ ], Combobox.prototype, "readonly", void 0);
700
+ __decorate([
701
+ property({ type: String, reflect: true }),
702
+ __metadata("design:type", String)
703
+ ], Combobox.prototype, "placement", void 0);
704
+ __decorate([
705
+ property({ type: String, attribute: 'no-result-text', reflect: true }),
706
+ __metadata("design:type", String)
707
+ ], Combobox.prototype, "noResultText", void 0);
708
+ __decorate([
709
+ property({ type: String, attribute: 'invalid-custom-value-text', reflect: true }),
710
+ __metadata("design:type", String)
711
+ ], Combobox.prototype, "invalidCustomValueText", void 0);
712
+ __decorate([
713
+ property({ type: String, reflect: true }),
714
+ __metadata("design:type", String)
715
+ ], Combobox.prototype, "boundary", void 0);
716
+ __decorate([
717
+ property({ type: String, reflect: true }),
718
+ __metadata("design:type", String)
719
+ ], Combobox.prototype, "strategy", void 0);
720
+ __decorate([
721
+ property({ type: Number, reflect: true, attribute: 'popover-z-index' }),
722
+ __metadata("design:type", Number)
723
+ ], Combobox.prototype, "popoverZIndex", void 0);
724
+ __decorate([
725
+ property({ type: String, reflect: true, attribute: 'backdrop-append-to' }),
726
+ __metadata("design:type", String)
727
+ ], Combobox.prototype, "backdropAppendTo", void 0);
728
+ __decorate([
729
+ query(`[role="${ROLE.COMBOBOX}"]`),
730
+ __metadata("design:type", HTMLInputElement)
731
+ ], Combobox.prototype, "visualCombobox", void 0);
732
+ __decorate([
733
+ query(`[part="combobox__button"]`),
734
+ __metadata("design:type", HTMLElement)
735
+ ], Combobox.prototype, "dropDownButton", void 0);
736
+ __decorate([
737
+ queryAssignedElements({ selector: SELECTLISTBOX_TAG_NAME }),
738
+ __metadata("design:type", Array)
739
+ ], Combobox.prototype, "slottedListboxes", void 0);
740
+ __decorate([
741
+ state(),
742
+ __metadata("design:type", Object)
743
+ ], Combobox.prototype, "isOpen", void 0);
744
+ __decorate([
745
+ state(),
746
+ __metadata("design:type", Object)
747
+ ], Combobox.prototype, "filteredValue", void 0);
20
748
  export default Combobox;