Skip to content

Commit f23a56a

Browse files
crisbetojelbourn
authored andcommitted
feat(select): allow for typeahead debounce interval to be customized (#16579)
Allows the consumer to customize the typeahead debounce interval. This can be useful for some cases where the values are harder to type quickly (e.g. ZIP codes). Fixes #16472.
1 parent bd24369 commit f23a56a

File tree

3 files changed

+39
-7
lines changed

3 files changed

+39
-7
lines changed

src/material/select/select.spec.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ import {
7171
} from './select-errors';
7272

7373

74-
/** The debounce interval when typing letters to select an option. */
75-
const LETTER_KEY_DEBOUNCE_INTERVAL = 200;
74+
/** Default debounce interval when typing letters to select an option. */
75+
const DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL = 200;
7676

7777
describe('MatSelect', () => {
7878
let overlayContainer: OverlayContainer;
@@ -469,20 +469,42 @@ describe('MatSelect', () => {
469469
expect(formControl.value).toBeFalsy('Expected no initial value.');
470470

471471
dispatchEvent(select, createKeyboardEvent('keydown', 80, undefined, 'p'));
472-
tick(200);
472+
tick(DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL);
473473

474474
expect(options[1].selected).toBe(true, 'Expected second option to be selected.');
475475
expect(formControl.value).toBe(options[1].value,
476476
'Expected value from second option to have been set on the model.');
477477

478478
dispatchEvent(select, createKeyboardEvent('keydown', 69, undefined, 'e'));
479-
tick(200);
479+
tick(DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL);
480480

481481
expect(options[5].selected).toBe(true, 'Expected sixth option to be selected.');
482482
expect(formControl.value).toBe(options[5].value,
483483
'Expected value from sixth option to have been set on the model.');
484484
}));
485485

486+
it('should be able to customize the typeahead debounce interval', fakeAsync(() => {
487+
const formControl = fixture.componentInstance.control;
488+
const options = fixture.componentInstance.options.toArray();
489+
490+
fixture.componentInstance.typeaheadDebounceInterval = 1337;
491+
fixture.detectChanges();
492+
493+
expect(formControl.value).toBeFalsy('Expected no initial value.');
494+
495+
dispatchEvent(select, createKeyboardEvent('keydown', 80, undefined, 'p'));
496+
tick(DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL);
497+
498+
expect(formControl.value).toBeFalsy('Expected no value after a bit of time has passed.');
499+
500+
tick(1337);
501+
502+
expect(options[1].selected)
503+
.toBe(true, 'Expected second option to be selected after all the time has passed.');
504+
expect(formControl.value).toBe(options[1].value,
505+
'Expected value from second option to have been set on the model.');
506+
}));
507+
486508
it('should open the panel when pressing a vertical arrow key on a closed multiple select',
487509
fakeAsync(() => {
488510
fixture.destroy();
@@ -1969,7 +1991,7 @@ describe('MatSelect', () => {
19691991
// Press the letter 'o' 15 times since all the options are named 'Option <index>'
19701992
dispatchEvent(host, createKeyboardEvent('keydown', 79, undefined, 'o'));
19711993
fixture.detectChanges();
1972-
tick(LETTER_KEY_DEBOUNCE_INTERVAL);
1994+
tick(DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL);
19731995
}
19741996
flush();
19751997

@@ -4275,7 +4297,8 @@ describe('MatSelect', () => {
42754297
<mat-form-field>
42764298
<mat-select placeholder="Food" [formControl]="control" [required]="isRequired"
42774299
[tabIndex]="tabIndexOverride" [aria-label]="ariaLabel" [aria-labelledby]="ariaLabelledby"
4278-
[panelClass]="panelClass" [disableRipple]="disableRipple">
4300+
[panelClass]="panelClass" [disableRipple]="disableRipple"
4301+
[typeaheadDebounceInterval]="typeaheadDebounceInterval">
42794302
<mat-option *ngFor="let food of foods" [value]="food.value" [disabled]="food.disabled">
42804303
{{ food.viewValue }}
42814304
</mat-option>
@@ -4304,6 +4327,7 @@ class BasicSelect {
43044327
ariaLabelledby: string;
43054328
panelClass = ['custom-one', 'custom-two'];
43064329
disableRipple: boolean;
4330+
typeaheadDebounceInterval: number;
43074331

43084332
@ViewChild(MatSelect, {static: true}) select: MatSelect;
43094333
@ViewChildren(MatOption) options: QueryList<MatOption>;

src/material/select/select.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,9 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
426426
/** Object used to control when error messages are shown. */
427427
@Input() errorStateMatcher: ErrorStateMatcher;
428428

429+
/** Time to wait in milliseconds after the last keystroke before moving focus to an item. */
430+
@Input() typeaheadDebounceInterval: number;
431+
429432
/**
430433
* Function used to sort the values in a select in multiple mode.
431434
* Follows the same logic as `Array.prototype.sort`.
@@ -570,6 +573,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
570573
if (changes['disabled']) {
571574
this.stateChanges.next();
572575
}
576+
577+
if (changes['typeaheadDebounceInterval'] && this._keyManager) {
578+
this._keyManager.withTypeAhead(this.typeaheadDebounceInterval);
579+
}
573580
}
574581

575582
ngOnDestroy() {
@@ -897,7 +904,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
897904
/** Sets up a key manager to listen to keyboard events on the overlay panel. */
898905
private _initKeyManager() {
899906
this._keyManager = new ActiveDescendantKeyManager<MatOption>(this.options)
900-
.withTypeAhead()
907+
.withTypeAhead(this.typeaheadDebounceInterval)
901908
.withVerticalOrientation()
902909
.withHorizontalOrientation(this._isRtl() ? 'rtl' : 'ltr')
903910
.withAllowedModifierKeys(['shiftKey']);

tools/public_api_guard/material/select.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export declare class MatSelect extends _MatSelectMixinBase implements AfterConte
6161
sortComparator: (a: MatOption, b: MatOption, options: MatOption[]) => number;
6262
trigger: ElementRef;
6363
readonly triggerValue: string;
64+
typeaheadDebounceInterval: number;
6465
value: any;
6566
readonly valueChange: EventEmitter<any>;
6667
constructor(_viewportRuler: ViewportRuler, _changeDetectorRef: ChangeDetectorRef, _ngZone: NgZone, _defaultErrorStateMatcher: ErrorStateMatcher, elementRef: ElementRef, _dir: Directionality, _parentForm: NgForm, _parentFormGroup: FormGroupDirective, _parentFormField: MatFormField, ngControl: NgControl, tabIndex: string, scrollStrategyFactory: any,

0 commit comments

Comments
 (0)