Skip to content

Commit aab7426

Browse files
committed
refactor(select,autocomplete): consolidate logic for scrolling options into view
Combines some duplicated logic for determining how to scroll an option into view into a single place so it's easier to maintain.
1 parent 3352201 commit aab7426

File tree

3 files changed

+75
-49
lines changed

3 files changed

+75
-49
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ import {
3636
ViewContainerRef,
3737
} from '@angular/core';
3838
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
39-
import {MatOption, MatOptionSelectionChange} from '@angular/material/core';
39+
import {
40+
MatOption,
41+
MatOptionSelectionChange,
42+
_getOptionScrollPosition,
43+
_countGroupLabelsBeforeOption,
44+
} from '@angular/material/core';
4045
import {MatFormField} from '@angular/material/form-field';
4146
import {DOCUMENT} from '@angular/common';
4247
import {Observable} from 'rxjs/Observable';
@@ -374,20 +379,18 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
374379
* not adjusted.
375380
*/
376381
private _scrollToOption(): void {
377-
const activeOptionIndex = this.autocomplete._keyManager.activeItemIndex || 0;
378-
const labelCount = MatOption.countGroupLabelsBeforeOption(activeOptionIndex,
382+
const index = this.autocomplete._keyManager.activeItemIndex || 0;
383+
const labelCount = _countGroupLabelsBeforeOption(index,
379384
this.autocomplete.options, this.autocomplete.optionGroups);
380-
const optionOffset = (activeOptionIndex + labelCount) * AUTOCOMPLETE_OPTION_HEIGHT;
381-
const panelTop = this.autocomplete._getScrollTop();
382-
383-
if (optionOffset < panelTop) {
384-
// Scroll up to reveal selected option scrolled above the panel top
385-
this.autocomplete._setScrollTop(optionOffset);
386-
} else if (optionOffset + AUTOCOMPLETE_OPTION_HEIGHT > panelTop + AUTOCOMPLETE_PANEL_HEIGHT) {
387-
// Scroll down to reveal selected option scrolled below the panel bottom
388-
const newScrollTop = optionOffset - AUTOCOMPLETE_PANEL_HEIGHT + AUTOCOMPLETE_OPTION_HEIGHT;
389-
this.autocomplete._setScrollTop(Math.max(0, newScrollTop));
390-
}
385+
386+
const newScrollPosition = _getOptionScrollPosition(
387+
index + labelCount,
388+
AUTOCOMPLETE_OPTION_HEIGHT,
389+
this.autocomplete._getScrollTop(),
390+
AUTOCOMPLETE_PANEL_HEIGHT
391+
);
392+
393+
this.autocomplete._setScrollTop(newScrollPosition);
391394
}
392395

393396
/**

src/lib/core/option/option.ts

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -246,31 +246,55 @@ export class MatOption implements AfterViewChecked {
246246
private _emitSelectionChangeEvent(isUserInput = false): void {
247247
this.onSelectionChange.emit(new MatOptionSelectionChange(this, isUserInput));
248248
}
249+
}
249250

250-
/**
251-
* Counts the amount of option group labels that precede the specified option.
252-
* @param optionIndex Index of the option at which to start counting.
253-
* @param options Flat list of all of the options.
254-
* @param optionGroups Flat list of all of the option groups.
255-
*/
256-
static countGroupLabelsBeforeOption(optionIndex: number, options: QueryList<MatOption>,
257-
optionGroups: QueryList<MatOptgroup>): number {
258-
259-
if (optionGroups.length) {
260-
let optionsArray = options.toArray();
261-
let groups = optionGroups.toArray();
262-
let groupCounter = 0;
263-
264-
for (let i = 0; i < optionIndex + 1; i++) {
265-
if (optionsArray[i].group && optionsArray[i].group === groups[groupCounter]) {
266-
groupCounter++;
267-
}
268-
}
251+
/**
252+
* Counts the amount of option group labels that precede the specified option.
253+
* @param optionIndex Index of the option at which to start counting.
254+
* @param options Flat list of all of the options.
255+
* @param optionGroups Flat list of all of the option groups.
256+
* @docs-private
257+
*/
258+
export function _countGroupLabelsBeforeOption(optionIndex: number, options: QueryList<MatOption>,
259+
optionGroups: QueryList<MatOptgroup>): number {
260+
261+
if (optionGroups.length) {
262+
let optionsArray = options.toArray();
263+
let groups = optionGroups.toArray();
264+
let groupCounter = 0;
269265

270-
return groupCounter;
266+
for (let i = 0; i < optionIndex + 1; i++) {
267+
if (optionsArray[i].group && optionsArray[i].group === groups[groupCounter]) {
268+
groupCounter++;
269+
}
271270
}
272271

273-
return 0;
272+
return groupCounter;
273+
}
274+
275+
return 0;
276+
}
277+
278+
/**
279+
* Determines the position to which to scroll a panel in order for an option to be into view.
280+
* @param optionIndex Index of the option to be scrolled into the view.
281+
* @param optionHeight Height of the options.
282+
* @param currentScrollPosition Current scroll position of the panel.
283+
* @param panelHeight Height of the panel.
284+
* @docs-private
285+
*/
286+
export function _getOptionScrollPosition(optionIndex: number, optionHeight: number,
287+
currentScrollPosition: number, panelHeight: number): number {
288+
const optionOffset = optionIndex * optionHeight;
289+
290+
if (optionOffset < currentScrollPosition) {
291+
return optionOffset;
274292
}
275293

294+
if (optionOffset + optionHeight > currentScrollPosition + panelHeight) {
295+
return Math.max(0, optionOffset - panelHeight + optionHeight);
296+
}
297+
298+
return currentScrollPosition;
276299
}
300+

src/lib/select/select.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ import {
8080
MAT_OPTION_PARENT_COMPONENT,
8181
mixinDisableRipple,
8282
CanDisableRipple,
83+
_countGroupLabelsBeforeOption,
84+
_getOptionScrollPosition,
8385
} from '@angular/material/core';
8486
import {MatFormField, MatFormFieldControl} from '@angular/material/form-field';
8587
import {Observable} from 'rxjs/Observable';
@@ -971,19 +973,16 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
971973

972974
/** Scrolls the active option into view. */
973975
private _scrollActiveOptionIntoView(): void {
974-
const itemHeight = this._getItemHeight();
975976
const activeOptionIndex = this._keyManager.activeItemIndex || 0;
976-
const labelCount = MatOption.countGroupLabelsBeforeOption(activeOptionIndex,
977-
this.options, this.optionGroups);
978-
const scrollOffset = (activeOptionIndex + labelCount) * itemHeight;
979-
const panelTop = this.panel.nativeElement.scrollTop;
980-
981-
if (scrollOffset < panelTop) {
982-
this.panel.nativeElement.scrollTop = scrollOffset;
983-
} else if (scrollOffset + itemHeight > panelTop + SELECT_PANEL_MAX_HEIGHT) {
984-
this.panel.nativeElement.scrollTop =
985-
Math.max(0, scrollOffset - SELECT_PANEL_MAX_HEIGHT + itemHeight);
986-
}
977+
const labelCount = _countGroupLabelsBeforeOption(activeOptionIndex, this.options,
978+
this.optionGroups);
979+
980+
this.panel.nativeElement.scrollTop = _getOptionScrollPosition(
981+
activeOptionIndex + labelCount,
982+
this._getItemHeight(),
983+
this.panel.nativeElement.scrollTop,
984+
SELECT_PANEL_MAX_HEIGHT
985+
);
987986
}
988987

989988
/** Focuses the select element. */
@@ -1012,8 +1011,8 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
10121011
let selectedOptionOffset =
10131012
this.empty ? 0 : this._getOptionIndex(this._selectionModel.selected[0])!;
10141013

1015-
selectedOptionOffset += MatOption.countGroupLabelsBeforeOption(selectedOptionOffset,
1016-
this.options, this.optionGroups);
1014+
selectedOptionOffset += _countGroupLabelsBeforeOption(selectedOptionOffset, this.options,
1015+
this.optionGroups);
10171016

10181017
// We must maintain a scroll buffer so the selected option will be scrolled to the
10191018
// center of the overlay panel rather than the top.

0 commit comments

Comments
 (0)