diff --git a/terminus-ui/selection-list/src/selection-list-panel/selection-list-panel.component.html b/terminus-ui/selection-list/src/selection-list-panel/selection-list-panel.component.html index 5c2ce6362..d6d6f2a00 100644 --- a/terminus-ui/selection-list/src/selection-list-panel/selection-list-panel.component.html +++ b/terminus-ui/selection-list/src/selection-list-panel/selection-list-panel.component.html @@ -1,7 +1,7 @@
implements Cont return null; } + /** + * Calculates the height of the options + * + * Only called if at least one option exists + */ + private get itemHeight(): number { + // Try to use the 2nd option in case the first option is blank or a filter etc. Fall back to the first item if needed. + const options = this.selectionListPanel.options.toArray(); + const option = options[1] || options[0]; + return option.elementRef.nativeElement.offsetHeight; + } + /** * A stream of actions that should close the panel, including when an option is selected, on blur, and when TAB is pressed. */ @@ -439,6 +453,10 @@ export class TsSelectionListTriggerDirective implements Cont } else if (isArrowKey && this.canOpen()) { this.openPanel(); } + + if (isArrowKey || this.selectionListPanel.keyManager.activeItem !== prevActiveItem) { + this.scrollToOption(); + } } } @@ -782,4 +800,35 @@ export class TsSelectionListTriggerDirective implements Cont this.canOpenOnNextFocus = this.document.activeElement !== this.elementRef.nativeElement || this.panelOpen; } + + /** + * Scroll to an option + * + * 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. If that offset is below the fold, the new scrollTop will be the offset - the panel + * height + the option height, so the active option will be just visible at the bottom of the panel. If that offset is above the top of + * the visible panel, the new scrollTop will become the offset. If that offset is visible within the panel already, the scrollTop is not + * adjusted. + */ + private scrollToOption(): void { + const index: number = this.selectionListPanel.keyManager.activeItemIndex || 0; + const labelCount: number = countGroupLabelsBeforeOption(index, this.selectionListPanel.options, this.selectionListPanel.optionGroups); + + if (index === 0 && labelCount === 1) { + // If we've got one group label before the option and we're at the top option, + // scroll the list to the top. This is better UX than scrolling the list to the + // top of the option, because it allows the user to read the top group's label. + this.selectionListPanel.scrollTop = 0; + } else { + const newScrollPosition = getOptionScrollPosition( + index + labelCount, + this.itemHeight, + this.selectionListPanel.scrollTop, + SELECTION_LIST_PANEL_MAX_HEIGHT, + ); + + this.selectionListPanel.scrollTop = newScrollPosition; + } + } + } diff --git a/terminus-ui/selection-list/src/selection-list.component.spec.ts b/terminus-ui/selection-list/src/selection-list.component.spec.ts index f5bc19480..38dee5fd2 100644 --- a/terminus-ui/selection-list/src/selection-list.component.spec.ts +++ b/terminus-ui/selection-list/src/selection-list.component.spec.ts @@ -965,4 +965,11 @@ describe(`TsSelectionListComponent`, function() { expect(fixture.componentInstance.clicked).toHaveBeenCalled(); })); + + describe(`scroll into view`, () => { + + test.todo(`should update panel scroll position when focusing option out of view`); + + }); + });