@momentum-design/components 0.131.2 → 0.131.4

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.
@@ -21,6 +21,10 @@ declare const Combobox_base: import("../../utils/mixins/index.types").Constructo
21
21
  *
22
22
  * To set a default option, use the `selected` attribute on the `mdc-option` element.
23
23
  *
24
+ * When the combobox `control-type` attribute is "controlled", then the value should be set by the parent only, and the combobox will emit `change` and `input` events
25
+ * with the selected option details when the user makes a selection or types in the input, but it won't update the selected value internally.
26
+ * The parent component is expected to listen to these events and update the `value` property of the combobox accordingly to reflect the changes in the UI.
27
+ *
24
28
  * **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.
25
29
  *
26
30
  * 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.
@@ -81,6 +85,8 @@ declare const Combobox_base: import("../../utils/mixins/index.types").Constructo
81
85
  declare class Combobox extends Combobox_base implements AssociatedFormControl {
82
86
  /** @internal */
83
87
  private itemsStore;
88
+ /** @internal */
89
+ private lastCommittedValue;
84
90
  /**
85
91
  * The placeholder text which will be shown on the text if provided.
86
92
  * @default undefined
@@ -154,8 +160,6 @@ declare class Combobox extends Combobox_base implements AssociatedFormControl {
154
160
  /** @internal */
155
161
  private filteredValue;
156
162
  /** @internal */
157
- private forceValueUpdate;
158
- /** @internal */
159
163
  private initialSelectedOption;
160
164
  /** @internal */
161
165
  get navItems(): Option[];
@@ -168,8 +172,6 @@ declare class Combobox extends Combobox_base implements AssociatedFormControl {
168
172
  /** @internal */
169
173
  private closePopover;
170
174
  /** @internal */
171
- private toggleDropdown;
172
- /** @internal */
173
175
  private compareOptionWithValue;
174
176
  /** @internal */
175
177
  private getFirstSelectedOption;
@@ -179,6 +181,10 @@ declare class Combobox extends Combobox_base implements AssociatedFormControl {
179
181
  private handleUpdateError;
180
182
  /** @internal */
181
183
  private onStoreUpdate;
184
+ /** @internal */
185
+ private focusComboboxBase;
186
+ /** @internal */
187
+ private handleTriggerClick;
182
188
  /**
183
189
  * Update the selected value when an option is modified.
184
190
  *
@@ -189,8 +195,8 @@ declare class Combobox extends Combobox_base implements AssociatedFormControl {
189
195
  * Sets the selected option of the combobox.
190
196
  *
191
197
  * @param option - the new option to be set
192
- * @param updateFromValue - indicates if the update is triggered from the value attribute change
193
198
  * @param emitEvents - indicates if the change and input events should be emitted
199
+ * @param updateFromValue - indicates if the update is triggered from the value attribute change
194
200
  *
195
201
  * @internal
196
202
  */
@@ -266,6 +272,7 @@ declare class Combobox extends Combobox_base implements AssociatedFormControl {
266
272
  /**
267
273
  * Updates the visual focus state of a specific option in the dropdown list based on 'data-focused' attribute.
268
274
  * It also updates the 'aria-selected' attribute for a11y purposes.
275
+ * If an option has a tooltip attached to it, this will open the tooltip when the option is focused and close it when the option is unfocused.
269
276
  *
270
277
  * @param option - The option element to update focus state for.
271
278
  * @param value - The new focus state to set (true for focused, false for unfocused).
@@ -273,6 +280,20 @@ declare class Combobox extends Combobox_base implements AssociatedFormControl {
273
280
  * @internal
274
281
  */
275
282
  private updateOptionAttributes;
283
+ /**
284
+ * Opens the tooltip associated with the given option, if it exists.
285
+ * @param option - option
286
+ *
287
+ * @internal
288
+ */
289
+ private openTooltipIfExists;
290
+ /**
291
+ * Closes the tooltip associated with the given option, if it exists.
292
+ * @param option - option
293
+ *
294
+ * @internal
295
+ */
296
+ private closeTooltipIfExists;
276
297
  /**
277
298
  * Handles the blur event of the combobox.
278
299
  * This method is called when the combobox loses focus.
@@ -298,10 +319,6 @@ declare class Combobox extends Combobox_base implements AssociatedFormControl {
298
319
  */
299
320
  private updateHiddenOptions;
300
321
  /** @internal */
301
- private hideOptionGroupAndDivider;
302
- /** @internal */
303
- private showOptionGroupAndDivider;
304
- /** @internal */
305
322
  private handleInputChange;
306
323
  /** @internal */
307
324
  private handleOptionsClick;
@@ -49,6 +49,10 @@ import styles from './combobox.styles';
49
49
  *
50
50
  * To set a default option, use the `selected` attribute on the `mdc-option` element.
51
51
  *
52
+ * When the combobox `control-type` attribute is "controlled", then the value should be set by the parent only, and the combobox will emit `change` and `input` events
53
+ * with the selected option details when the user makes a selection or types in the input, but it won't update the selected value internally.
54
+ * The parent component is expected to listen to these events and update the `value` property of the combobox accordingly to reflect the changes in the UI.
55
+ *
52
56
  * **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.
53
57
  *
54
58
  * 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.
@@ -113,6 +117,8 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
113
117
  }
114
118
  constructor() {
115
119
  super();
120
+ /** @internal */
121
+ this.lastCommittedValue = '';
116
122
  /**
117
123
  * The placement of the popover within Combobox.
118
124
  * This defines the position of the popover relative to the combobox input field.
@@ -159,8 +165,6 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
159
165
  /** @internal */
160
166
  this.filteredValue = '';
161
167
  /** @internal */
162
- this.forceValueUpdate = false;
163
- /** @internal */
164
168
  this.initialSelectedOption = null;
165
169
  /** @internal */
166
170
  this.handleUpdateError = (error) => {
@@ -205,7 +209,11 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
205
209
  */
206
210
  this.handleModifiedEvent = (event) => {
207
211
  // When the combobox is controlled, we don't update/modify the selected value internally.
212
+ // Instead, we rely on the consumer to update the selected value based on the change event emitted.
208
213
  if (this.controlType === 'controlled') {
214
+ // We still need to update the hidden options when an option is modified, because even in controlled mode,
215
+ // the modification of an option (e.g. changing the label) should be reflected in the UI by updating the hidden options based on the new label.
216
+ this.updateHiddenOptions();
209
217
  return;
210
218
  }
211
219
  const firstSelectedOption = this.getFirstSelectedOption();
@@ -265,10 +273,6 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
265
273
  this.isOpen = false;
266
274
  }
267
275
  /** @internal */
268
- toggleDropdown() {
269
- this.isOpen = !this.isOpen;
270
- }
271
- /** @internal */
272
276
  compareOptionWithValue(option, value) {
273
277
  const optionValue = option.getAttribute('label') || '';
274
278
  return optionValue.toLowerCase().startsWith(value === null || value === void 0 ? void 0 : value.toLowerCase());
@@ -281,31 +285,69 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
281
285
  getVisibleOptions(internalValue) {
282
286
  return this.navItems.filter(option => this.compareOptionWithValue(option, internalValue));
283
287
  }
288
+ /** @internal */
289
+ focusComboboxBase() {
290
+ if (this.disabled) {
291
+ return;
292
+ }
293
+ this.updateComplete
294
+ .then(() => {
295
+ var _a;
296
+ // `visualCombobox` points to the slotted input with role="combobox".
297
+ // Clicking the surrounding `mdc-input` or the dropdown button can toggle the popover
298
+ // without moving DOM focus, so we explicitly focus the base input.
299
+ (_a = this.visualCombobox) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true });
300
+ })
301
+ .catch(this.handleUpdateError);
302
+ }
303
+ /** @internal */
304
+ handleTriggerClick() {
305
+ if (this.disabled) {
306
+ return;
307
+ }
308
+ // Toggle dropdown when clicking the trigger button
309
+ this.isOpen = !this.isOpen;
310
+ // Focus on the combobox after click
311
+ this.focusComboboxBase();
312
+ }
284
313
  /**
285
314
  * Sets the selected option of the combobox.
286
315
  *
287
316
  * @param option - the new option to be set
288
- * @param updateFromValue - indicates if the update is triggered from the value attribute change
289
317
  * @param emitEvents - indicates if the change and input events should be emitted
318
+ * @param updateFromValue - indicates if the update is triggered from the value attribute change
290
319
  *
291
320
  * @internal
292
321
  */
293
- setSelectedValue(option, emitEvents = true) {
294
- if (this.controlType === 'controlled' && !this.forceValueUpdate) {
295
- ComboboxEventManager.onChangeCombobox(this, option);
322
+ setSelectedValue(option, emitEvents = true, updateFromValue = false) {
323
+ const label = (option === null || option === void 0 ? void 0 : option.getAttribute('label')) || '';
324
+ const value = (option === null || option === void 0 ? void 0 : option.getAttribute('value')) || '';
325
+ // If the value and the label are the same as the current selected option,
326
+ // then do nothing to prevent unnecessary updates and event emissions.
327
+ if (this.value === value && this.filteredValue === label) {
296
328
  return;
297
329
  }
298
- this.forceValueUpdate = false;
299
- // this.filteredValue is the visible label of the component
300
- this.filteredValue = (option === null || option === void 0 ? void 0 : option.getAttribute('label')) || '';
301
- // this.value is the actual value of the component
302
- this.value = (option === null || option === void 0 ? void 0 : option.getAttribute('value')) || '';
330
+ // For controlled components, user interactions (not coming from a value prop change)
331
+ // should only emit events and let the parent drive the value.
332
+ if (this.controlType === 'controlled' && !updateFromValue) {
333
+ if (emitEvents) {
334
+ ComboboxEventManager.onInputCombobox(this, option);
335
+ ComboboxEventManager.onChangeCombobox(this, option);
336
+ }
337
+ return;
338
+ }
339
+ this.filteredValue = label;
340
+ this.value = value;
341
+ // Update last committed values when a real selection is made
342
+ this.lastCommittedValue = value;
303
343
  this.internals.setFormValue(this.value);
304
344
  this.updateHiddenOptions();
305
- this.updateSelectedOption(option);
345
+ if (option) {
346
+ this.updateSelectedOption(option);
347
+ }
306
348
  this.setInputValidity();
307
349
  this.resetHelpText();
308
- if (emitEvents) {
350
+ if (emitEvents && !updateFromValue && option) {
309
351
  ComboboxEventManager.onInputCombobox(this, option);
310
352
  ComboboxEventManager.onChangeCombobox(this, option);
311
353
  }
@@ -342,11 +384,10 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
342
384
  */
343
385
  attributeChangedCallback(name, oldValue, newValue) {
344
386
  super.attributeChangedCallback(name, oldValue, newValue);
345
- if (name === 'value' &&
346
- newValue !== '' &&
347
- newValue !== oldValue &&
348
- this.navItems.length &&
349
- this.controlType !== 'controlled') {
387
+ // keep value-attribute based default selection working for both
388
+ // controlled and uncontrolled modes, while avoiding change/input events
389
+ // by delegating to setSelectedValue with updateFromValue=true.
390
+ if (name === 'value' && newValue !== '' && this.navItems.length) {
350
391
  const firstSelectedOption = this.getFirstSelectedOption();
351
392
  const valueBasedOption = this.navItems.find(option => option.value === newValue);
352
393
  let optionToSelect = null;
@@ -364,7 +405,7 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
364
405
  }
365
406
  this.updateComplete
366
407
  .then(() => {
367
- this.setSelectedValue(optionToSelect);
408
+ this.setSelectedValue(optionToSelect, false, true);
368
409
  })
369
410
  .catch(this.handleUpdateError);
370
411
  }
@@ -397,6 +438,8 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
397
438
  else if (this.placeholder) {
398
439
  this.setInputValidity();
399
440
  }
441
+ // Initialize last committed values
442
+ this.lastCommittedValue = this.value;
400
443
  this.navItems.forEach(option => {
401
444
  option.setAttribute('tabindex', '-1');
402
445
  });
@@ -407,10 +450,10 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
407
450
  * @internal
408
451
  */
409
452
  updateValueBasedSelection() {
410
- this.forceValueUpdate = true;
411
453
  const validOption = this.navItems.find(option => option.value === this.value);
412
454
  if (validOption) {
413
- this.setSelectedValue(validOption);
455
+ // Sync UI from value prop without firing change/input events
456
+ this.setSelectedValue(validOption, false, true);
414
457
  }
415
458
  }
416
459
  updated(changedProperties) {
@@ -470,7 +513,7 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
470
513
  var _a;
471
514
  const optionToResetTo = this.initialSelectedOption || null;
472
515
  // Restore the selected option
473
- this.setSelectedValue(optionToResetTo);
516
+ this.setSelectedValue(optionToResetTo, false, true);
474
517
  // Reset the filtered text (typed value shown in input)
475
518
  if (this.controlType !== 'controlled') {
476
519
  this.filteredValue = (_a = optionToResetTo === null || optionToResetTo === void 0 ? void 0 : optionToResetTo.label) !== null && _a !== void 0 ? _a : '';
@@ -481,7 +524,7 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
481
524
  /** @internal */
482
525
  formStateRestoreCallback(state) {
483
526
  const optionToRestoreTo = this.navItems.find(option => option.value === state || option.label === state);
484
- this.setSelectedValue(optionToRestoreTo || null);
527
+ this.setSelectedValue(optionToRestoreTo || null, false, true);
485
528
  }
486
529
  /**
487
530
  * When the native input is focused, visually highlight the dropdown options (the "visual combobox"),
@@ -518,6 +561,7 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
518
561
  /**
519
562
  * Updates the visual focus state of a specific option in the dropdown list based on 'data-focused' attribute.
520
563
  * It also updates the 'aria-selected' attribute for a11y purposes.
564
+ * If an option has a tooltip attached to it, this will open the tooltip when the option is focused and close it when the option is unfocused.
521
565
  *
522
566
  * @param option - The option element to update focus state for.
523
567
  * @param value - The new focus state to set (true for focused, false for unfocused).
@@ -529,12 +573,42 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
529
573
  return;
530
574
  if (value) {
531
575
  option.setAttribute('data-focused', '');
576
+ this.openTooltipIfExists(option);
532
577
  }
533
578
  else {
534
579
  option.removeAttribute('data-focused');
580
+ this.closeTooltipIfExists(option);
535
581
  }
536
582
  option.setAttribute('aria-selected', value.toString());
537
583
  }
584
+ /**
585
+ * Opens the tooltip associated with the given option, if it exists.
586
+ * @param option - option
587
+ *
588
+ * @internal
589
+ */
590
+ openTooltipIfExists(option) {
591
+ const id = option.getAttribute('id');
592
+ if (!id)
593
+ return;
594
+ const tooltip = this.querySelector(`mdc-tooltip[triggerid="${id}"]`);
595
+ tooltip === null || tooltip === void 0 ? void 0 : tooltip.setAttribute('visible', '');
596
+ }
597
+ /**
598
+ * Closes the tooltip associated with the given option, if it exists.
599
+ * @param option - option
600
+ *
601
+ * @internal
602
+ */
603
+ closeTooltipIfExists(option) {
604
+ const id = option.getAttribute('id');
605
+ if (!id)
606
+ return;
607
+ const tooltip = this.querySelector(`mdc-tooltip[triggerid="${id}"][visible]`);
608
+ if (tooltip) {
609
+ tooltip.removeAttribute('visible');
610
+ }
611
+ }
538
612
  /**
539
613
  * Handles the blur event of the combobox.
540
614
  * This method is called when the combobox loses focus.
@@ -553,15 +627,35 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
553
627
  this.closePopover();
554
628
  return;
555
629
  }
556
- if (activeIndex === -1 &&
557
- this.filteredValue !== '' &&
558
- this.invalidCustomValueText &&
559
- !this.getFirstSelectedOption()) {
560
- this.helpText = this.invalidCustomValueText;
561
- this.helpTextType = VALIDATION.ERROR;
630
+ // Check if the typed text exactly matches an option
631
+ const exactMatch = this.navItems.find(option => {
632
+ const optionLabel = option.getAttribute('label') || '';
633
+ return optionLabel.toLowerCase() === this.filteredValue.toLowerCase();
634
+ });
635
+ if (exactMatch) {
636
+ this.setSelectedValue(exactMatch);
637
+ this.closePopover();
638
+ return;
562
639
  }
563
- // In common cases (when no selection made and focus moved away), close the popover.
564
- this.setInputValidity();
640
+ // If user typed something but didn't select anything, check what to do
641
+ if (this.filteredValue !== '') {
642
+ // If the input doesn't match any option and we have a previously committed value, revert to it
643
+ if (this.lastCommittedValue) {
644
+ const newOption = this.navItems.find(option => option.value === this.lastCommittedValue);
645
+ this.setSelectedValue(newOption);
646
+ }
647
+ else if (this.invalidCustomValueText && !this.getFirstSelectedOption()) {
648
+ // Show invalid custom value message if configured
649
+ this.helpText = this.invalidCustomValueText;
650
+ this.helpTextType = VALIDATION.ERROR;
651
+ this.setInputValidity();
652
+ }
653
+ }
654
+ else {
655
+ // When the input is cleared, reset the selected value to null (placeholder will be shown if present)
656
+ this.setSelectedValue(null);
657
+ }
658
+ this.closePopover();
565
659
  }
566
660
  /** @internal */
567
661
  updateFocusAndScrollIntoView(options, oldIndex, newIndex) {
@@ -655,50 +749,54 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
655
749
  * @internal
656
750
  */
657
751
  updateHiddenOptions() {
752
+ const groupVisibleCounts = new Map();
753
+ const groups = new Set();
754
+ // First pass: update options and collect visibility info per optgroup
658
755
  this.navItems.forEach(option => {
659
- if (!this.compareOptionWithValue(option, this.filteredValue)) {
660
- option.setAttribute('data-hidden', '');
661
- this.hideOptionGroupAndDivider(option);
756
+ var _a;
757
+ const matchesFilter = this.compareOptionWithValue(option, this.filteredValue);
758
+ if (matchesFilter) {
759
+ option.removeAttribute('data-hidden');
662
760
  }
663
761
  else {
664
- option.removeAttribute('data-hidden');
665
- this.showOptionGroupAndDivider(option);
762
+ option.setAttribute('data-hidden', '');
763
+ }
764
+ const parent = option.parentElement;
765
+ if (parent && parent.matches(OPTIONGROUP_TAG_NAME)) {
766
+ const group = parent;
767
+ groups.add(group);
768
+ if (matchesFilter) {
769
+ groupVisibleCounts.set(group, ((_a = groupVisibleCounts.get(group)) !== null && _a !== void 0 ? _a : 0) + 1);
770
+ }
666
771
  }
667
772
  });
668
- }
669
- /** @internal */
670
- hideOptionGroupAndDivider(option) {
671
- var _a, _b, _c;
672
- if ((_a = option.parentElement) === null || _a === void 0 ? void 0 : _a.matches(OPTIONGROUP_TAG_NAME)) {
673
- const optionGroupChildren = (_b = Array.from(option.parentElement.children)) === null || _b === void 0 ? void 0 : _b.filter(option => !option.hasAttribute('data-hidden'));
674
- if (optionGroupChildren.length === 0) {
675
- option.parentElement.setAttribute('data-hidden', '');
676
- if ((_c = option.parentElement.nextElementSibling) === null || _c === void 0 ? void 0 : _c.matches(DIVIDER_TAG_NAME)) {
677
- option.parentElement.nextElementSibling.setAttribute('data-hidden', '');
773
+ // Second pass: toggle optgroup + following divider based on aggregated counts
774
+ groups.forEach(group => {
775
+ var _a;
776
+ const hasVisibleChildren = ((_a = groupVisibleCounts.get(group)) !== null && _a !== void 0 ? _a : 0) > 0;
777
+ const divider = group.nextElementSibling;
778
+ if (hasVisibleChildren) {
779
+ group.removeAttribute('data-hidden');
780
+ if (divider && divider.matches(DIVIDER_TAG_NAME)) {
781
+ divider.removeAttribute('data-hidden');
678
782
  }
679
783
  }
680
- }
681
- }
682
- /** @internal */
683
- showOptionGroupAndDivider(option) {
684
- var _a, _b, _c;
685
- if ((_a = option.parentElement) === null || _a === void 0 ? void 0 : _a.matches(OPTIONGROUP_TAG_NAME)) {
686
- const optionGroupChildren = (_b = Array.from(option.parentElement.children)) === null || _b === void 0 ? void 0 : _b.filter(option => !option.hasAttribute('data-hidden'));
687
- if (optionGroupChildren.length > 0) {
688
- option.parentElement.removeAttribute('data-hidden');
689
- if ((_c = option.parentElement.nextElementSibling) === null || _c === void 0 ? void 0 : _c.matches(DIVIDER_TAG_NAME)) {
690
- option.parentElement.nextElementSibling.removeAttribute('data-hidden');
784
+ else {
785
+ group.setAttribute('data-hidden', '');
786
+ if (divider && divider.matches(DIVIDER_TAG_NAME)) {
787
+ divider.setAttribute('data-hidden', '');
691
788
  }
692
789
  }
693
- }
790
+ });
694
791
  }
695
792
  /** @internal */
696
793
  handleInputChange(event) {
697
794
  var _a;
698
- if (this.controlType !== 'controlled') {
699
- this.filteredValue = event.target.value;
700
- }
701
- this.resetSelectedValue();
795
+ event.preventDefault();
796
+ event.stopPropagation();
797
+ this.filteredValue = event.target.value;
798
+ // Don't reset the selected value immediately - preserve it until user makes a selection
799
+ // or loses focus and we determine what to do
702
800
  this.resetFocusedOption();
703
801
  this.updateHiddenOptions();
704
802
  // remove the selected attribute on input change
@@ -706,17 +804,21 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
706
804
  if (this.isOpen === false) {
707
805
  this.openPopover();
708
806
  }
807
+ // Dispatch custom event to notify about the input change with the current filtered value.
808
+ ComboboxEventManager.onInputCombobox(this, { value: this.filteredValue });
709
809
  }
710
810
  /** @internal */
711
811
  handleOptionsClick(event) {
712
812
  var _a;
813
+ event.preventDefault();
814
+ event.stopPropagation();
713
815
  // ensure we get the actual option element even if the click target is a child node
714
816
  const option = (_a = event.target.closest(OPTION_TAG_NAME)) !== null && _a !== void 0 ? _a : null;
715
817
  if (option && !option.hasAttribute('disabled')) {
716
818
  this.setSelectedValue(option);
717
819
  this.closePopover();
718
820
  // Focus on the combobox after click
719
- this.updateComplete.then(() => this.handleNativeInputFocus()).catch(this.handleUpdateError);
821
+ this.focusComboboxBase();
720
822
  }
721
823
  }
722
824
  /** @internal */
@@ -772,6 +874,7 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
772
874
  id="${this.id}"
773
875
  slot="input"
774
876
  ?disabled="${this.disabled}"
877
+ tabindex="${this.disabled ? -1 : 0}"
775
878
  .value="${live(this.filteredValue)}"
776
879
  autocomplete="${AUTO_COMPLETE.OFF}"
777
880
  part="input-text"
@@ -809,7 +912,7 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
809
912
  <div part="combobox-base" id="${TRIGGER_ID}">
810
913
  ${this.renderNativeInput()}
811
914
  <mdc-input
812
- @click="${() => this.toggleDropdown()}"
915
+ @click="${this.handleTriggerClick}"
813
916
  ?disabled="${this.disabled}"
814
917
  ?readonly="${this.readonly}"
815
918
  help-text-type="${this.helpTextType}"
@@ -817,7 +920,7 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
817
920
  ${this.renderBaseInput()}
818
921
  </mdc-input>
819
922
  <mdc-buttonsimple
820
- @click="${() => this.toggleDropdown()}"
923
+ @click="${this.handleTriggerClick}"
821
924
  part="combobox-button"
822
925
  ?disabled="${this.disabled}"
823
926
  tabindex="-1"
@@ -838,7 +941,7 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
838
941
  }}"
839
942
  @closebyoutsideclick="${() => {
840
943
  this.closePopover();
841
- this.handleNativeInputFocus();
944
+ this.focusComboboxBase();
842
945
  }}"
843
946
  backdrop
844
947
  backdrop-append-to="${ifDefined(this.backdropAppendTo)}"
@@ -857,7 +960,7 @@ class Combobox extends KeyDownHandledMixin(KeyToActionMixin(CaptureDestroyEventF
857
960
  z-index="${ifDefined(this.popoverZIndex)}"
858
961
  >
859
962
  ${this.renderNoResultsText(options.length)}
860
- <slot @click="${this.handleOptionsClick}"></slot>
963
+ <slot @mousedown="${this.handleOptionsClick}"></slot>
861
964
  </mdc-popover>
862
965
  </div>
863
966
  ${this.renderHelperText()}
@@ -917,8 +1020,4 @@ __decorate([
917
1020
  state(),
918
1021
  __metadata("design:type", Object)
919
1022
  ], Combobox.prototype, "filteredValue", void 0);
920
- __decorate([
921
- state(),
922
- __metadata("design:type", Object)
923
- ], Combobox.prototype, "forceValueUpdate", void 0);
924
1023
  export default Combobox;
@@ -16,12 +16,12 @@ export declare class ComboboxEventManager {
16
16
  * @param instance - The combobox instance.
17
17
  * @param option - The value and label of the selected option.
18
18
  */
19
- static onInputCombobox(instance: Combobox, option: Option): void;
19
+ static onInputCombobox(instance: Combobox, option: Option | null): void;
20
20
  /**
21
21
  * Dispatches a custom event for the Combobox when the selected option changes.
22
22
  *
23
23
  * @param instance - The combobox instance.
24
24
  * @param option - The value and label of the selected option.
25
25
  */
26
- static onChangeCombobox(instance: Combobox, option: Option): void;
26
+ static onChangeCombobox(instance: Combobox, option: Option | null): void;
27
27
  }
@@ -2,6 +2,7 @@ import '../button';
2
2
  import '../icon';
3
3
  import '../option';
4
4
  import '../popover';
5
+ import '../selectlistbox';
5
6
  import '../text';
6
7
  import '../toggletip';
7
8
  import TimePicker from './timepicker.component';
@@ -2,6 +2,7 @@ import '../button';
2
2
  import '../icon';
3
3
  import '../option';
4
4
  import '../popover';
5
+ import '../selectlistbox';
5
6
  import '../text';
6
7
  import '../toggletip';
7
8
  import TimePicker from './timepicker.component';
@@ -43,6 +43,7 @@ declare const TimePicker_base: import("../../utils/mixins/index.types").Construc
43
43
  * @cssproperty --mdc-timepicker-border-color - Border color of the timepicker input.
44
44
  * @cssproperty --mdc-timepicker-text-color - Text color of the timepicker input.
45
45
  * @cssproperty --mdc-timepicker-width - Width of the timepicker component.
46
+ * @cssproperty --mdc-timepicker-listbox-height - The max-height of the dropdown listbox. Default shows ~6 items.
46
47
  *
47
48
  * @csspart label - The label element.
48
49
  * @csspart label-text - The container for the label and required indicator elements.
@@ -55,6 +55,7 @@ import styles from './timepicker.styles';
55
55
  * @cssproperty --mdc-timepicker-border-color - Border color of the timepicker input.
56
56
  * @cssproperty --mdc-timepicker-text-color - Text color of the timepicker input.
57
57
  * @cssproperty --mdc-timepicker-width - Width of the timepicker component.
58
+ * @cssproperty --mdc-timepicker-listbox-height - The max-height of the dropdown listbox. Default shows ~6 items.
58
59
  *
59
60
  * @csspart label - The label element.
60
61
  * @csspart label-text - The container for the label and required indicator elements.
@@ -881,15 +882,14 @@ class TimePicker extends FormInternalsMixin(DataAriaLabelMixin(FormfieldWrapper)
881
882
  }}"
882
883
  exportparts="popover-content"
883
884
  >
884
- <div
885
+ <mdc-selectlistbox
885
886
  id="${LISTBOX_ID}"
886
887
  part="listbox"
887
- role="listbox"
888
888
  aria-label="${this.localeTimeOptionsLabel}"
889
889
  @keydown="${this.handleListboxKeydown}"
890
890
  >
891
891
  ${this.renderTimeOptions()}
892
- </div>
892
+ </mdc-selectlistbox>
893
893
  </mdc-popover>
894
894
  </div>
895
895
  ${this.helpText ? this.renderHelperText() : nothing}
@@ -7,6 +7,7 @@ const styles = css `
7
7
  --mdc-timepicker-border-color: var(--mds-color-theme-outline-input-normal);
8
8
  --mdc-timepicker-width: fit-content;
9
9
  --mdc-timepicker-listbox-width: 100%;
10
+ --mdc-timepicker-listbox-height: 15rem;
10
11
 
11
12
  display: flex;
12
13
  flex-direction: column;
@@ -130,9 +131,14 @@ const styles = css `
130
131
  }
131
132
 
132
133
  /* Popover height, width & padding overrides */
134
+ :host mdc-popover {
135
+ --mdc-popover-max-width: var(--mdc-timepicker-listbox-width);
136
+ --mdc-popover-max-height: var(--mdc-timepicker-listbox-height);
137
+ min-width: max-content;
138
+ }
133
139
  :host mdc-popover::part(popover-content) {
134
140
  max-height: var(--mdc-popover-max-height);
135
- min-width: var(--mdc-timepicker-listbox-width);
141
+ min-width: auto;
136
142
  padding: 0.75rem 0.5rem;
137
143
  }
138
144