From 10c9b621c9202a2d0dac4dc17e7c5f236bef57b0 Mon Sep 17 00:00:00 2001 From: William Martin Date: Tue, 23 Apr 2024 21:59:41 -0400 Subject: [PATCH] clean up grid code --- .../src/a11y/a11y-cell.directive.ts | 1 - .../src/a11y/a11y-grid.directive.ts | 63 ++++++++++++------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/libs/components/src/a11y/a11y-cell.directive.ts b/libs/components/src/a11y/a11y-cell.directive.ts index 13abb0c7e0..1aaf76a214 100644 --- a/libs/components/src/a11y/a11y-cell.directive.ts +++ b/libs/components/src/a11y/a11y-cell.directive.ts @@ -19,6 +19,5 @@ export class A11yCellDirective implements AfterContentChecked { console.error("A11yCellDirective must contain content that provides FocusableElement"); return; } - // this.focusableChild.getFocusTarget().tabIndex = -1; } } diff --git a/libs/components/src/a11y/a11y-grid.directive.ts b/libs/components/src/a11y/a11y-grid.directive.ts index 6f9be52f44..18d2d73418 100644 --- a/libs/components/src/a11y/a11y-grid.directive.ts +++ b/libs/components/src/a11y/a11y-grid.directive.ts @@ -4,6 +4,7 @@ import { Directive, HostBinding, HostListener, + Input, QueryList, } from "@angular/core"; @@ -15,77 +16,95 @@ import { A11yRowDirective } from "./a11y-row.directive"; standalone: true, }) export class A11yGridDirective implements AfterViewInit { - @ContentChildren(A11yRowDirective) - rows: QueryList; - @HostBinding("attr.role") role = "grid"; + @ContentChildren(A11yRowDirective) + rows: QueryList; + + /** The number of pages to navigate on `PageUp` and `PageDown` */ + @Input() pageSize = 5; + private grid: A11yCellDirective[][]; - private activeRow: number = 0; - private activeCol: number = 0; + + /** The row that currently has focus */ + private activeRow = 0; + + /** The cell that currently has focus */ + private activeCol = 0; @HostListener("keydown", ["$event"]) onKeyDown(event: KeyboardEvent) { switch (event.code) { case "ArrowUp": - this.updateCellFocusByDelta(0, -1); - break; - case "ArrowRight": - this.updateCellFocusByDelta(1, 0); - break; - case "ArrowDown": - this.updateCellFocusByDelta(0, 1); - break; - case "ArrowLeft": this.updateCellFocusByDelta(-1, 0); break; + case "ArrowRight": + this.updateCellFocusByDelta(0, 1); + break; + case "ArrowDown": + this.updateCellFocusByDelta(1, 0); + break; + case "ArrowLeft": + this.updateCellFocusByDelta(0, -1); + break; case "Home": - this.updateCellFocusByDelta(-this.activeCol, -this.activeRow); + this.updateCellFocusByDelta(-this.activeRow, -this.activeCol); break; case "End": - this.updateCellFocusByDelta(this.grid[this.grid.length - 1].length, this.grid.length); + this.updateCellFocusByDelta(this.grid.length, this.grid[this.grid.length - 1].length); break; case "PageUp": - this.updateCellFocusByDelta(0, -5); + this.updateCellFocusByDelta(-this.pageSize, 0); break; case "PageDown": - this.updateCellFocusByDelta(0, 5); + this.updateCellFocusByDelta(this.pageSize, 0); break; default: return; } + + /** Prevent default scrolling behavior */ event.preventDefault(); } ngAfterViewInit(): void { + this.initializeGrid(); + } + + private initializeGrid(): void { this.grid = this.rows.map((listItem) => [...listItem.cells]); this.grid.flat().map((cell) => (cell.focusableChild.getFocusTarget().tabIndex = -1)); - const firstCell = this.getActiveCellContent(); - firstCell.tabIndex = 0; + this.getActiveCellContent().tabIndex = 0; } + /** Get the focusable content of the active cell */ private getActiveCellContent(): HTMLElement { return this.grid[this.activeRow][this.activeCol].focusableChild.getFocusTarget(); } - private updateCellFocusByDelta(colDelta: number, rowDelta: number) { + /** Move focus via a delta against the currently active gridcell */ + private updateCellFocusByDelta(rowDelta: number, colDelta: number) { const prevActive = this.getActiveCellContent(); this.activeCol += colDelta; this.activeRow += rowDelta; + // Row upper bound if (this.activeRow >= this.grid.length) { this.activeRow = this.grid.length - 1; } + // Row lower bound if (this.activeRow < 0) { this.activeRow = 0; } + // Column upper bound if (this.activeCol >= this.grid[this.activeRow].length) { if (this.activeRow < this.grid.length - 1) { + // Wrap to next row on right arrow this.activeCol = 0; this.activeRow += 1; } else { @@ -93,8 +112,10 @@ export class A11yGridDirective implements AfterViewInit { } } + // Column lower bound if (this.activeCol < 0) { if (this.activeRow > 0) { + // Wrap to prev row on left arrow this.activeRow -= 1; this.activeCol = this.grid[this.activeRow].length - 1; } else {