2019-04-04 15:03:40 -07:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2019-04-04 15:03:40 -07:00
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2020-04-13 16:40:21 -07:00
|
|
|
import {AfterContentInit, AfterViewInit, ChangeDetectionStrategy, Component, ContentChildren, ElementRef, EventEmitter, forwardRef, HostListener, Input, OnDestroy, Output, QueryList, ViewChild} from '@angular/core';
|
2019-04-04 15:03:40 -07:00
|
|
|
import {Subscription} from 'rxjs';
|
|
|
|
|
|
|
|
import {EXPANDING_ROW_HOST_INJECTION_TOKEN, ExpandingRow, ExpandingRowHostBase} from './expanding_row';
|
|
|
|
|
2019-05-14 09:39:11 +02:00
|
|
|
|
2019-04-04 15:03:40 -07:00
|
|
|
/**
|
|
|
|
* We use this class in <cfc-expanding-row/> template to identify the row.
|
|
|
|
* The [cfcExpandingRowHost] directive also uses this class to check if a given
|
|
|
|
* HTMLElement is within an <cfc-expanding-row/>.
|
|
|
|
*/
|
|
|
|
const EXPANDING_ROW_CLASS_NAME = 'cfc-expanding-row';
|
|
|
|
|
|
|
|
/** Throttle duration in milliseconds for repeated key presses. */
|
|
|
|
export const EXPANDING_ROW_KEYPRESS_THORTTLE_MS = 50;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This type union is created to make arguments of handleUpOrDownPress*
|
|
|
|
* methods in ExpandingRowHost class more readable.
|
|
|
|
*/
|
2020-04-13 16:40:21 -07:00
|
|
|
type UpOrDown = 'up'|'down';
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This is the wrapper directive for the cfc-expanding-row components. Note that
|
|
|
|
* we wanted to make this a directive instead of component because child
|
|
|
|
* cfc-expanding-row components does not have to be a direct child.
|
|
|
|
*/
|
|
|
|
@Component({
|
|
|
|
selector: 'cfc-expanding-row-host',
|
|
|
|
template: `
|
|
|
|
<div #firstFocusable
|
|
|
|
(focus)="focusOnLastFocusedRow()"
|
|
|
|
tabindex="0">
|
|
|
|
</div>
|
|
|
|
<ng-content></ng-content>
|
|
|
|
<div #lastFocusable
|
|
|
|
(focus)="focusOnLastFocusedRow()"
|
|
|
|
tabindex="0">
|
|
|
|
</div>`,
|
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
2019-05-14 09:39:11 +02:00
|
|
|
providers: [{provide: EXPANDING_ROW_HOST_INJECTION_TOKEN, useExisting: ExpandingRowHost}],
|
2019-04-04 15:03:40 -07:00
|
|
|
})
|
2020-04-13 16:40:21 -07:00
|
|
|
export class ExpandingRowHost implements AfterViewInit, OnDestroy, ExpandingRowHostBase {
|
2019-04-04 15:03:40 -07:00
|
|
|
/**
|
|
|
|
* An HTML selector (e.g. "body") for the scroll element. We need this to
|
|
|
|
* make some scroll adjustments.
|
|
|
|
*/
|
|
|
|
@Input() scrollElementSelector = '.cfc-panel-body-scrollable';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An HTML selector (e.g. "body") for the click root. While the row is
|
|
|
|
* expanded, and user clicks outside of the expanded row, we collapse this row
|
|
|
|
* But to do this, we need to know the clickable area.
|
|
|
|
*/
|
|
|
|
@Input() clickRootElementSelector = 'cfc-panel-body';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The @Output will be triggered when the user wants to focus on the
|
|
|
|
* previously expanded row, and we are already at the first row. The logs team
|
|
|
|
* will use this to prepend data on demand.
|
|
|
|
*/
|
|
|
|
@Output() onPrepend = new EventEmitter<void>();
|
|
|
|
|
|
|
|
/** A reference to the last focusable element in list of expanding rows. */
|
2020-04-13 16:40:21 -07:00
|
|
|
@ViewChild('lastFocusable', {static: true}) lastFocusableElement!: ElementRef;
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
/** A reference to the first focusable element in list of expanding rows. */
|
2020-04-13 16:40:21 -07:00
|
|
|
@ViewChild('firstFocusable', {static: true}) firstFocusableElement!: ElementRef;
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A reference to all child cfc-expanding-row elements. We will need for
|
|
|
|
* keyboard accessibility and scroll adjustments. For example, we need to know
|
|
|
|
* which row is previous row when user presses "left arrow" on a focused row.
|
|
|
|
*/
|
|
|
|
@ContentChildren(forwardRef(() => ExpandingRow), {descendants: true})
|
2020-04-13 16:40:21 -07:00
|
|
|
contentRows!: QueryList<ExpandingRow>;
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Keeps track of the last row that had focus before focus left the list
|
|
|
|
* of expanding rows.
|
|
|
|
*/
|
|
|
|
lastFocusedRow?: ExpandingRow = undefined;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Focused rows just show a blue left border. This node is not expanded. We
|
|
|
|
* need to keep a reference to the focused row to unfocus when another row
|
|
|
|
* is focused.
|
|
|
|
*/
|
|
|
|
private focusedRow?: ExpandingRow = undefined;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This is the expanded row. If there is an expanded row there shouldn't be
|
|
|
|
* any focused rows. We need a reference to this. For example we need to
|
|
|
|
* collapse the currently expanded row, if another row is expanded.
|
|
|
|
*/
|
|
|
|
private expandedRow?: ExpandingRow = undefined;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This is just handleRootMouseUp.bind(this). handleRootMouseUp handles
|
|
|
|
* click events on root element (defined by clickRootElementSelector @Input)
|
|
|
|
* Since we attach the click listener dynamically, we need to keep this
|
|
|
|
* function around. This enables us to detach the click listener when
|
|
|
|
* component is destroyed.
|
|
|
|
*/
|
2019-06-14 09:29:04 +02:00
|
|
|
private handleRootMouseUpBound = this.handleRootMouseUp.bind(this);
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 16px is the margin animation we have on cfc-expanding-row component.
|
|
|
|
* We need this value to compute scroll adjustments.
|
|
|
|
*/
|
|
|
|
private static rowMargin = 16;
|
|
|
|
|
|
|
|
/** Subscription to changes in the expanding rows. */
|
|
|
|
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
2020-04-13 16:40:21 -07:00
|
|
|
private rowChangeSubscription!: Subscription;
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* When component initializes we need to attach click listener to the root
|
|
|
|
* element. This click listener will allows us to collapse the
|
|
|
|
* currently expanded row when user clicks outside of it.
|
|
|
|
*/
|
|
|
|
ngAfterViewInit(): void {
|
|
|
|
const clickRootElement: HTMLElement = this.getClickRootElement();
|
|
|
|
|
|
|
|
if (!clickRootElement) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
clickRootElement.addEventListener('mouseup', this.handleRootMouseUpBound);
|
|
|
|
|
2020-04-13 16:40:21 -07:00
|
|
|
this.rowChangeSubscription = this.contentRows.changes.subscribe(() => {
|
|
|
|
this.recalcRowIndexes();
|
|
|
|
});
|
2019-04-04 15:03:40 -07:00
|
|
|
this.recalcRowIndexes();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Detaches the click listener on the root element. Note that we are attaching
|
|
|
|
* this listener on ngAfterViewInit function.
|
|
|
|
*/
|
|
|
|
ngOnDestroy(): void {
|
|
|
|
const clickRootElement: HTMLElement = this.getClickRootElement();
|
|
|
|
|
|
|
|
if (!clickRootElement) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-05-14 09:39:11 +02:00
|
|
|
clickRootElement.removeEventListener('mouseup', this.handleRootMouseUpBound);
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
if (this.rowChangeSubscription) {
|
|
|
|
this.rowChangeSubscription.unsubscribe();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles caption element click on a cfc-expanding-row component. Note
|
|
|
|
* that caption element is visible only when the row is expanded. So this
|
|
|
|
* means we will collapse the expanded row. The scroll adjustment below
|
|
|
|
* makes sure that the mouse stays under the summary of the expanded row
|
|
|
|
* when the row collapses.
|
|
|
|
*/
|
|
|
|
handleRowCaptionClick(row: ExpandingRow): void {
|
|
|
|
const scrollAdjustment: number = -ExpandingRowHost.rowMargin;
|
|
|
|
const scrollElement: HTMLElement = this.getScrollElement() as HTMLElement;
|
|
|
|
if (!scrollElement) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
scrollElement.scrollTop += scrollAdjustment;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles summary element click on a cfc-expanding-row component. Note
|
|
|
|
* that summary element is visible only when the row is collapsed. So this
|
|
|
|
* event will fired prior to expansion of a collapsed row. Scroll adjustment
|
|
|
|
* below makes sure mouse stays on the caption element when the collapsed
|
|
|
|
* row expands.
|
|
|
|
*/
|
|
|
|
handleRowSummaryClick(row: ExpandingRow): void {
|
|
|
|
const hadPreviousSelection: boolean = !!this.expandedRow;
|
2019-05-14 09:39:11 +02:00
|
|
|
const previousSelectedRowIndex: number = this.getRowIndex(this.expandedRow as ExpandingRow);
|
2019-04-04 15:03:40 -07:00
|
|
|
const newSelectedRowIndex: number = this.getRowIndex(row);
|
2019-05-14 09:39:11 +02:00
|
|
|
const previousCollapsedHeight: number = this.getSelectedRowCollapsedHeight();
|
2019-04-04 15:03:40 -07:00
|
|
|
const previousExpansionHeight = this.getSelectedRowExpandedHeight();
|
|
|
|
|
|
|
|
if (this.expandedRow) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let scrollAdjustment = 0;
|
|
|
|
const scrollElement: HTMLElement = this.getScrollElement() as HTMLElement;
|
|
|
|
if (!scrollElement) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (previousExpansionHeight > 0 && previousCollapsedHeight >= 0) {
|
|
|
|
scrollAdjustment = previousExpansionHeight - previousCollapsedHeight;
|
|
|
|
}
|
|
|
|
|
2019-05-14 09:39:11 +02:00
|
|
|
const newSelectionIsInfrontOfPrevious: boolean = newSelectedRowIndex > previousSelectedRowIndex;
|
2019-04-04 15:03:40 -07:00
|
|
|
const multiplier = newSelectionIsInfrontOfPrevious ? -1 : 0;
|
2019-05-14 09:39:11 +02:00
|
|
|
scrollAdjustment = scrollAdjustment * multiplier + ExpandingRowHost.rowMargin;
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
scrollElement.scrollTop += scrollAdjustment;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles expansion of a row. When a new row expands, we need to remove
|
|
|
|
* previous expansion and collapse. We also need to save the currently
|
|
|
|
* expanded row so that we can collapse this row once another row expands.
|
|
|
|
*/
|
|
|
|
handleRowExpand(row: ExpandingRow): void {
|
|
|
|
this.removePreviousFocus();
|
|
|
|
this.removePreviousExpansion();
|
|
|
|
this.expandedRow = row;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles focus on a row. When a new row gets focus (note that this is
|
|
|
|
* different from expansion), we need to remove previous focus and expansion.
|
|
|
|
* We need to save the reference to this focused row so that we can unfocus
|
|
|
|
* this row when another row is focused.
|
|
|
|
*/
|
|
|
|
handleRowFocus(row: ExpandingRow): void {
|
|
|
|
// Do not blur then refocus the row if it's already selected.
|
|
|
|
if (row === this.focusedRow) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.removePreviousFocus();
|
|
|
|
this.removePreviousExpansion();
|
|
|
|
this.focusedRow = row;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when shift+tabbing from the first focusable element after the list
|
|
|
|
* of expanding rows or tabbing from the last focusable element before.
|
|
|
|
*/
|
|
|
|
focusOnLastFocusedRow(): void {
|
|
|
|
if (!this.lastFocusedRow) {
|
|
|
|
this.lastFocusedRow = this.contentRows.toArray()[0];
|
|
|
|
}
|
|
|
|
this.lastFocusedRow.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function that is called by expanding row summary to focus on the last
|
|
|
|
* focusable element before the list of expanding rows.
|
|
|
|
*/
|
2020-04-13 16:40:21 -07:00
|
|
|
focusOnPreviousFocusableElement(): void {
|
|
|
|
this.lastFocusedRow = this.focusedRow;
|
|
|
|
}
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Function that is called by expanding row summary to focus on the next
|
|
|
|
* focusable element after the list of expanding rows.
|
|
|
|
*/
|
2020-04-13 16:40:21 -07:00
|
|
|
focusOnNextFocusableElement(): void {
|
|
|
|
this.lastFocusedRow = this.focusedRow;
|
|
|
|
}
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles keydown event on the host. We are just concerned with up,
|
|
|
|
* down arrow, ESC, and ENTER presses here. Note that Up/Down presses
|
|
|
|
* can be repeated.
|
|
|
|
*
|
|
|
|
* - Up: Focuses on the row above.
|
|
|
|
* - Down: Focuses on the row below.
|
|
|
|
* - Escape: Collapses the expanded row.
|
|
|
|
* - Enter: Expands the focused row.
|
|
|
|
*/
|
|
|
|
@HostListener('keydown', ['$event'])
|
2020-04-13 16:40:21 -07:00
|
|
|
handleKeyDown(event: KeyboardEvent) {
|
|
|
|
}
|
2019-04-04 15:03:40 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Recursively returns true if target HTMLElement is within a
|
|
|
|
* cfc-expanding-row component. It will return false otherwise.
|
|
|
|
* We need this function in handleRootMouseUp to collapse the expanded row
|
|
|
|
* when user clicks outside of all expanded rows.
|
|
|
|
*/
|
|
|
|
private isTargetInRow(target: HTMLElement): boolean {
|
|
|
|
return target.classList.contains(EXPANDING_ROW_CLASS_NAME);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the click root element that is described by clickRootElementSelector
|
|
|
|
* @Input value.
|
|
|
|
*/
|
|
|
|
private getClickRootElement(): HTMLElement {
|
|
|
|
return document.querySelector(this.clickRootElementSelector) as HTMLElement;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles all of the mouseup events on the click root. When user clicks
|
|
|
|
* outside of an expanded row, we need to collapse that row.
|
|
|
|
* We trigger collapse by calling handleCaptionClick() on the expanded row.
|
|
|
|
*/
|
|
|
|
private handleRootMouseUp(event: MouseEvent): void {
|
|
|
|
if (!this.expandedRow) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.isTargetInRow(event.target as {} as HTMLElement)) {
|
|
|
|
this.expandedRow.handleCaptionClick(event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if element is blacklisted. Blacklisted elements will not collapse an
|
|
|
|
* open row when clicked.
|
|
|
|
*/
|
|
|
|
isBlacklisted(element: HTMLElement|null): boolean {
|
|
|
|
const clickRoot = this.getClickRootElement();
|
|
|
|
while (element && element !== clickRoot) {
|
|
|
|
if (element.hasAttribute('cfcexpandingrowblacklist')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
element = element.parentElement;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes focus state from a previously focused row. We blur this row and
|
|
|
|
* set the focusedRow to undefined in this method. This usually happens when
|
|
|
|
* another row is focused.
|
|
|
|
*/
|
|
|
|
private removePreviousFocus(): void {
|
|
|
|
if (this.focusedRow) {
|
|
|
|
this.focusedRow.blur();
|
|
|
|
this.focusedRow = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes the expanded state from a previously expanded row. We collapse this
|
|
|
|
* row and set the expandedRow to undefined in this method. This usually
|
|
|
|
* happens when another row is expanded.
|
|
|
|
*/
|
|
|
|
private removePreviousExpansion(): void {
|
|
|
|
if (this.expandedRow) {
|
|
|
|
this.expandedRow.collapse();
|
|
|
|
this.expandedRow = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the collapsed height of the currently expanded row. We need this for
|
|
|
|
* scroll adjustments. Note that collapsed height of a cfc-expanding-row
|
|
|
|
* component is equal to height of cfc-expanding-row-summary component within
|
|
|
|
* the row.
|
|
|
|
*/
|
|
|
|
private getSelectedRowCollapsedHeight(): number {
|
|
|
|
if (this.expandedRow) {
|
|
|
|
return this.expandedRow.collapsedHeight;
|
|
|
|
} else {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the current height of the expanded row. We need this value for the
|
|
|
|
* scroll adjustment computation.
|
|
|
|
*/
|
|
|
|
private getSelectedRowExpandedHeight(): number {
|
|
|
|
if (this.expandedRow) {
|
|
|
|
return this.expandedRow.getHeight();
|
|
|
|
} else {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the HTML element described by scrollElementSelector @Input value.
|
|
|
|
* We need this value for scroll adjustments.
|
|
|
|
*/
|
|
|
|
private getScrollElement(): HTMLElement|undefined {
|
|
|
|
if (!this.scrollElementSelector) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return document.querySelector(this.scrollElementSelector) as HTMLElement;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles escape presses on the host element. Escape removes previous focus
|
|
|
|
* if there is one. If there is an expanded row, escape row collapses this
|
|
|
|
* row and focuses on it. A subsequent escape press will blur this row.
|
|
|
|
*/
|
|
|
|
private handleEscapePress(): void {
|
|
|
|
this.removePreviousFocus();
|
|
|
|
|
|
|
|
if (this.expandedRow) {
|
|
|
|
this.expandedRow.collapse();
|
|
|
|
this.expandedRow.focus();
|
|
|
|
this.expandedRow = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles enter keypress. If there is a focused row, an enter key press on
|
|
|
|
* host element will expand this row.
|
|
|
|
*/
|
|
|
|
private handleEnterPress(): void {
|
|
|
|
if (document.activeElement !== this.focusedRowSummary()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.focusedRow) {
|
|
|
|
this.focusedRow.expand();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Returns the HTMLElement that is the currently focused row summary. */
|
|
|
|
private focusedRowSummary(): HTMLElement|undefined {
|
2019-05-14 09:39:11 +02:00
|
|
|
return this.focusedRow ? this.focusedRow.summaryViewChild.mainElementRef.nativeElement :
|
|
|
|
undefined;
|
2019-04-04 15:03:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the index of a given row. This enables us to figure out the row
|
|
|
|
* above/below the focused row.
|
|
|
|
*/
|
|
|
|
private getRowIndex(rowToLookFor: ExpandingRow): number {
|
|
|
|
return rowToLookFor ? rowToLookFor.index : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handles up/down arrow presses on the host element. Up arrow press will
|
|
|
|
* focus/expand on the row above. Down arrow press will focus/expand the row
|
|
|
|
* below. If we have a focus on the current row, this function will focus on
|
|
|
|
* the computed (the one above or below) row. If host has an expanded row,
|
|
|
|
* this function will expand the computed row.
|
|
|
|
*/
|
2019-05-14 09:39:11 +02:00
|
|
|
private handleUpOrDownPressOnce(upOrDown: UpOrDown, event: KeyboardEvent): void {
|
2019-04-04 15:03:40 -07:00
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
// If row is expanded but focus is inside the expanded element, arrow
|
|
|
|
// key presses should not do anything.
|
|
|
|
if (this.expandedRow &&
|
2019-05-14 09:39:11 +02:00
|
|
|
document.activeElement !== this.expandedRow.expandingRowMainElement.nativeElement) {
|
2019-04-04 15:03:40 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If focus is inside a collapsed row header, arrow key presses should not
|
|
|
|
// do anything.
|
2019-05-14 09:39:11 +02:00
|
|
|
if (this.focusedRow && document.activeElement !== this.focusedRowSummary()) {
|
2019-04-04 15:03:40 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// We only want screen reader to read the message the first time we enter
|
|
|
|
// the list of expanding rows, so we must reset the variable here
|
|
|
|
this.lastFocusedRow = undefined;
|
|
|
|
|
2019-05-14 09:39:11 +02:00
|
|
|
const rowToLookFor: ExpandingRow|undefined = this.expandedRow || this.focusedRow;
|
2019-04-04 15:03:40 -07:00
|
|
|
if (!rowToLookFor) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const isFocus: boolean = (rowToLookFor === this.focusedRow);
|
|
|
|
|
|
|
|
const rowIndex: number = this.getRowIndex(rowToLookFor);
|
|
|
|
const contentRowsArray: ExpandingRow[] = this.contentRows.toArray();
|
|
|
|
|
|
|
|
if (rowIndex < 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const potentialIndex: number = (upOrDown === 'up' ? -1 : +1) + rowIndex;
|
|
|
|
if (potentialIndex < 0) {
|
|
|
|
this.onPrepend.emit();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (potentialIndex >= contentRowsArray.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const potentialRow: ExpandingRow = contentRowsArray[potentialIndex];
|
|
|
|
if (isFocus) {
|
|
|
|
potentialRow.focus();
|
|
|
|
} else {
|
|
|
|
potentialRow.expand();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Updates all of the rows with their new index.
|
|
|
|
private recalcRowIndexes() {
|
|
|
|
let index = 0;
|
2020-04-13 16:40:21 -07:00
|
|
|
setTimeout(() => {
|
|
|
|
this.contentRows.forEach((row: ExpandingRow) => {
|
|
|
|
row.index = index++;
|
|
|
|
});
|
|
|
|
});
|
2019-04-04 15:03:40 -07:00
|
|
|
}
|
|
|
|
}
|