diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index c0c83903593..c811973f9f5 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -2704,6 +2704,41 @@ describe('MatSelect', () => { }); + describe('with option centering disabled', () => { + beforeEach(async(() => configureMatSelectTestingModule([ + SelectWithoutOptionCentering, + ]))); + + let fixture: ComponentFixture; + let trigger: HTMLElement; + let select: HTMLElement; + let formField: HTMLElement; + + beforeEach(fakeAsync(() => { + fixture = TestBed.createComponent(SelectWithoutOptionCentering); + fixture.detectChanges(); + trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement; + select = fixture.debugElement.query(By.css('mat-select')).nativeElement; + formField = fixture.debugElement.query(By.css('mat-form-field')).nativeElement; + })); + + it('should not align the active option with the trigger if centering is disabled', + fakeAsync(() => { + trigger.click(); + fixture.detectChanges(); + flush(); + + const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel')!; + + // The panel should be scrolled to 0 because centering the option disabled. + expect(scrollContainer.scrollTop).toEqual(0, `Expected panel not to be scrolled.`); + // The trigger should contain 'Pizza' because it was preselected + expect(trigger.textContent).toContain('Pizza'); + // The selected index should be 1 because it was preselected + expect(fixture.componentInstance.options.toArray()[1].selected).toBe(true); + })); + }); + describe('positioning', () => { beforeEach(async(() => configureMatSelectTestingModule([ BasicSelect, @@ -4445,3 +4480,32 @@ class SingleSelectWithPreselectedArrayValues { @ViewChild(MatSelect) select: MatSelect; @ViewChildren(MatOption) options: QueryList; } + +@Component({ + selector: 'select-without-option-centering', + template: ` + + + + {{ food.viewValue }} + + + + ` +}) +class SelectWithoutOptionCentering { + foods: any[] = [ + { value: 'steak-0', viewValue: 'Steak' }, + { value: 'pizza-1', viewValue: 'Pizza' }, + { value: 'tacos-2', viewValue: 'Tacos'}, + { value: 'sandwich-3', viewValue: 'Sandwich' }, + { value: 'chips-4', viewValue: 'Chips' }, + { value: 'eggs-5', viewValue: 'Eggs' }, + { value: 'pasta-6', viewValue: 'Pasta' }, + { value: 'sushi-7', viewValue: 'Sushi' }, + ]; + control = new FormControl('pizza-1'); + + @ViewChild(MatSelect) select: MatSelect; + @ViewChildren(MatOption) options: QueryList; +} diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 977e033ec19..87ad71a0c7d 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -308,6 +308,9 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, }, ]; + /** Whether the component is disabling centering of the active option over the trigger. */ + private _disableOptionCentering: boolean = false; + /** Whether the select is focused. */ focused: boolean = false; @@ -362,6 +365,13 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, this._multiple = coerceBooleanProperty(value); } + /** Whether to center the active option over the trigger. */ + @Input() + get disableOptionCentering(): boolean { return this._disableOptionCentering; } + set disableOptionCentering(value: boolean) { + this._disableOptionCentering = coerceBooleanProperty(value); + } + /** * A function to compare the option values with the selected values. The first argument * is a value from an option. The second is a value from the selection. A boolean @@ -1118,6 +1128,11 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit, const maxOptionsDisplayed = Math.floor(SELECT_PANEL_MAX_HEIGHT / itemHeight); let optionOffsetFromPanelTop: number; + // Disable offset if requested by user by returning 0 as value to offset + if (this._disableOptionCentering) { + return 0; + } + if (this._scrollTop === 0) { optionOffsetFromPanelTop = selectedIndex * itemHeight; } else if (this._scrollTop === maxScroll) {