mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(autocomplete): scroll options below fold into view (#2728)
This commit is contained in:
@@ -9,13 +9,25 @@ import {ConnectedPositionStrategy} from '../core/overlay/position/connected-posi
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {MdOptionSelectEvent, MdOption} from '../core/option/option';
|
||||
import {ActiveDescendantKeyManager} from '../core/a11y/activedescendant-key-manager';
|
||||
import {ENTER} from '../core/keyboard/keycodes';
|
||||
import {ENTER, UP_ARROW, DOWN_ARROW} from '../core/keyboard/keycodes';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import 'rxjs/add/observable/merge';
|
||||
import {Dir} from '../core/rtl/dir';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
|
||||
/**
|
||||
* The following style constants are necessary to save here in order
|
||||
* to properly calculate the scrollTop of the panel. Because we are not
|
||||
* actually focusing the active item, scroll must be handled manually.
|
||||
*/
|
||||
|
||||
/** The height of each autocomplete option. */
|
||||
export const AUTOCOMPLETE_OPTION_HEIGHT = 48;
|
||||
|
||||
/** The total height of the autocomplete panel. */
|
||||
export const AUTOCOMPLETE_PANEL_HEIGHT = 256;
|
||||
|
||||
@Directive({
|
||||
selector: 'input[mdAutocomplete], input[matAutocomplete]',
|
||||
host: {
|
||||
@@ -117,9 +129,25 @@ export class MdAutocompleteTrigger implements AfterContentInit, OnDestroy {
|
||||
} else {
|
||||
this.openPanel();
|
||||
this._keyManager.onKeydown(event);
|
||||
if (event.keyCode === UP_ARROW || event.keyCode === DOWN_ARROW) {
|
||||
this._scrollToOption();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given that we are not actually focusing active options, we must manually adjust scroll
|
||||
* to reveal options below the fold. First, we find the offset of the option from the top
|
||||
* of the panel. The new scrollTop will be that offset - the panel height + the option
|
||||
* height, so the active option will be just visible at the bottom of the panel.
|
||||
*/
|
||||
private _scrollToOption(): void {
|
||||
const optionOffset = this._keyManager.activeItemIndex * AUTOCOMPLETE_OPTION_HEIGHT;
|
||||
const newScrollTop =
|
||||
Math.max(0, optionOffset - AUTOCOMPLETE_PANEL_HEIGHT + AUTOCOMPLETE_OPTION_HEIGHT);
|
||||
this.autocomplete._setScrollTop(newScrollTop);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method listens to a stream of panel closing actions and resets the
|
||||
* stream every time the option list changes.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="md-autocomplete-panel" role="listbox" [id]="id" [ngClass]="_getPositionClass()">
|
||||
<div class="md-autocomplete-panel" role="listbox" [id]="id" [ngClass]="_getPositionClass()" #panel>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</template>
|
||||
@@ -391,6 +391,26 @@ describe('MdAutocomplete', () => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should scroll to active options below the fold', () => {
|
||||
fixture.componentInstance.trigger.openPanel();
|
||||
fixture.detectChanges();
|
||||
|
||||
const scrollContainer = document.querySelector('.cdk-overlay-pane .md-autocomplete-panel');
|
||||
|
||||
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
|
||||
fixture.detectChanges();
|
||||
expect(scrollContainer.scrollTop).toEqual(0, `Expected panel not to scroll.`);
|
||||
|
||||
// These down arrows will set the 6th option active, below the fold.
|
||||
[1, 2, 3, 4, 5].forEach(() => {
|
||||
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
// Expect option bottom minus the panel height (288 - 256 = 32)
|
||||
expect(scrollContainer.scrollTop).toEqual(32, `Expected panel to reveal the sixth option.`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('aria', () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Component,
|
||||
ContentChildren,
|
||||
ElementRef,
|
||||
QueryList,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
@@ -29,11 +30,20 @@ export class MdAutocomplete {
|
||||
positionY: MenuPositionY = 'below';
|
||||
|
||||
@ViewChild(TemplateRef) template: TemplateRef<any>;
|
||||
@ViewChild('panel') panel: ElementRef;
|
||||
@ContentChildren(MdOption) options: QueryList<MdOption>;
|
||||
|
||||
/** Unique ID to be used by autocomplete trigger's "aria-owns" property. */
|
||||
id: string = `md-autocomplete-${_uniqueAutocompleteIdCounter++}`;
|
||||
|
||||
/**
|
||||
* Sets the panel scrollTop. This allows us to manually scroll to display
|
||||
* options below the fold, as they are not actually being focused when active.
|
||||
*/
|
||||
_setScrollTop(scrollTop: number): void {
|
||||
this.panel.nativeElement.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
/** Sets a class on the panel based on its position (used to set y-offset). */
|
||||
_getPositionClass() {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user