From 4c47a25b77ff9f96942e32c92d7ac471b571b3a7 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sat, 8 Jul 2017 15:04:03 +0200 Subject: [PATCH] feat(autocomplete): support for md-optgroup Adds support for `md-optgroup` in `md-autocomplete` by: * Changing the `@ViewChild` query to pick up descendant options. * Tweaking the keyboard scrolling to handle having group labels before options. Fixes #5581. --- src/lib/autocomplete/autocomplete-trigger.ts | 26 ++++- src/lib/autocomplete/autocomplete.spec.ts | 115 ++++++++++++++++++- src/lib/autocomplete/autocomplete.ts | 7 +- 3 files changed, 143 insertions(+), 5 deletions(-) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index ba175e64dd56..f76fe49ea6fb 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -328,8 +328,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { * not adjusted. */ private _scrollToOption(): void { - const optionOffset = this.autocomplete._keyManager.activeItemIndex ? - this.autocomplete._keyManager.activeItemIndex * AUTOCOMPLETE_OPTION_HEIGHT : 0; + const optionOffset = this._getActiveOptionIndex() * AUTOCOMPLETE_OPTION_HEIGHT; const panelTop = this.autocomplete._getScrollTop(); if (optionOffset < panelTop) { @@ -343,6 +342,29 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { } } + /** Determines the index of the active option. */ + private _getActiveOptionIndex(): number { + let optionIndex = this.autocomplete._keyManager.activeItemIndex || 0; + + // If there are any option groups, we need to offset + // the index by the amount of groups that come before the option. + if (this.autocomplete.optionGroups.length) { + const options = this.autocomplete.options.toArray(); + const groups = this.autocomplete.optionGroups.toArray(); + let groupCounter = 0; + + for (let i = 0; i < optionIndex + 1; i++) { + if (options[i].group && options[i].group === groups[groupCounter]) { + groupCounter++; + } + } + + optionIndex += groupCounter; + } + + return optionIndex; + } + /** * This method listens to a stream of panel closing actions and resets the * stream every time the option list changes. diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index 6ad3319b624c..18444f252d0d 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -54,7 +54,8 @@ describe('MdAutocomplete', () => { AutocompleteWithNumbers, AutocompleteWithOnPushDelay, AutocompleteWithNativeInput, - AutocompleteWithoutPanel + AutocompleteWithoutPanel, + AutocompleteWithGroups ], providers: [ {provide: OverlayContainer, useFactory: () => { @@ -853,6 +854,84 @@ describe('MdAutocomplete', () => { }); + describe('option groups', () => { + let fixture: ComponentFixture; + let DOWN_ARROW_EVENT: KeyboardEvent; + let UP_ARROW_EVENT: KeyboardEvent; + + beforeEach(() => { + fixture = TestBed.createComponent(AutocompleteWithGroups); + fixture.detectChanges(); + + DOWN_ARROW_EVENT = createKeyboardEvent('keydown', DOWN_ARROW); + UP_ARROW_EVENT = createKeyboardEvent('keydown', UP_ARROW); + + fixture.componentInstance.trigger.openPanel(); + fixture.detectChanges(); + }); + + it('should scroll to active options below the fold', fakeAsync(() => { + tick(); + const container = document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel')!; + + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + fixture.detectChanges(); + expect(container.scrollTop).toBe(0, 'Expected the panel not to scroll.'); + + // Press the down arrow five times. + [1, 2, 3, 4, 5].forEach(() => { + fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); + tick(); + }); + + //