diff --git a/libs/components/src/a11y/a11y-cell.directive.ts b/libs/components/src/a11y/a11y-cell.directive.ts deleted file mode 100644 index 3a2d5c4f6b2..00000000000 --- a/libs/components/src/a11y/a11y-cell.directive.ts +++ /dev/null @@ -1,34 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ContentChild, Directive, ElementRef, HostBinding } from "@angular/core"; - -import { FocusableElement } from "../shared/focusable-element"; - -@Directive({ - selector: "bitA11yCell", - providers: [{ provide: FocusableElement, useExisting: A11yCellDirective }], -}) -export class A11yCellDirective implements FocusableElement { - @HostBinding("attr.role") - role: "gridcell" | null; - - @ContentChild(FocusableElement) - private focusableChild: FocusableElement; - - getFocusTarget() { - let focusTarget: HTMLElement; - if (this.focusableChild) { - focusTarget = this.focusableChild.getFocusTarget(); - } else { - focusTarget = this.elementRef.nativeElement.querySelector("button, a"); - } - - if (!focusTarget) { - return this.elementRef.nativeElement; - } - - return focusTarget; - } - - constructor(private elementRef: ElementRef) {} -} diff --git a/libs/components/src/a11y/a11y-grid.directive.ts b/libs/components/src/a11y/a11y-grid.directive.ts deleted file mode 100644 index c061464239e..00000000000 --- a/libs/components/src/a11y/a11y-grid.directive.ts +++ /dev/null @@ -1,148 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { - AfterViewInit, - ContentChildren, - Directive, - HostBinding, - HostListener, - Input, - QueryList, -} from "@angular/core"; - -import type { A11yCellDirective } from "./a11y-cell.directive"; -import { A11yRowDirective } from "./a11y-row.directive"; - -@Directive({ - selector: "bitA11yGrid", -}) -export class A11yGridDirective implements AfterViewInit { - @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[][]; - - /** 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(-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.activeRow, -this.activeCol); - break; - case "End": - this.updateCellFocusByDelta(this.grid.length, this.grid[this.grid.length - 1].length); - break; - case "PageUp": - this.updateCellFocusByDelta(-this.pageSize, 0); - break; - case "PageDown": - this.updateCellFocusByDelta(this.pageSize, 0); - break; - default: - return; - } - - /** Prevent default scrolling behavior */ - event.preventDefault(); - } - - ngAfterViewInit(): void { - this.initializeGrid(); - } - - private initializeGrid(): void { - try { - this.grid = this.rows.map((listItem) => { - listItem.role = "row"; - return [...listItem.cells]; - }); - this.grid.flat().forEach((cell) => { - cell.role = "gridcell"; - cell.getFocusTarget().tabIndex = -1; - }); - - this.getActiveCellContent().tabIndex = 0; - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error) { - // eslint-disable-next-line no-console - console.error("Unable to initialize grid"); - } - } - - /** Get the focusable content of the active cell */ - private getActiveCellContent(): HTMLElement { - return this.grid[this.activeRow][this.activeCol].getFocusTarget(); - } - - /** 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 { - this.activeCol = this.grid[this.activeRow].length - 1; - } - } - - // 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 { - this.activeCol = 0; - } - } - - const nextActive = this.getActiveCellContent(); - nextActive.tabIndex = 0; - nextActive.focus(); - - if (nextActive !== prevActive) { - prevActive.tabIndex = -1; - } - } -} diff --git a/libs/components/src/a11y/a11y-row.directive.ts b/libs/components/src/a11y/a11y-row.directive.ts deleted file mode 100644 index f7588dc0053..00000000000 --- a/libs/components/src/a11y/a11y-row.directive.ts +++ /dev/null @@ -1,32 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { - AfterViewInit, - ContentChildren, - Directive, - HostBinding, - QueryList, - ViewChildren, -} from "@angular/core"; - -import { A11yCellDirective } from "./a11y-cell.directive"; - -@Directive({ - selector: "bitA11yRow", -}) -export class A11yRowDirective implements AfterViewInit { - @HostBinding("attr.role") - role: "row" | null; - - cells: A11yCellDirective[]; - - @ViewChildren(A11yCellDirective) - private viewCells: QueryList; - - @ContentChildren(A11yCellDirective) - private contentCells: QueryList; - - ngAfterViewInit(): void { - this.cells = [...this.viewCells, ...this.contentCells]; - } -} diff --git a/libs/components/src/item/item-action.component.ts b/libs/components/src/item/item-action.component.ts index d169ee7c00b..c47ee8eea69 100644 --- a/libs/components/src/item/item-action.component.ts +++ b/libs/components/src/item/item-action.component.ts @@ -1,12 +1,9 @@ import { Component } from "@angular/core"; -import { A11yCellDirective } from "../a11y/a11y-cell.directive"; - @Component({ selector: "bit-item-action", imports: [], template: ``, - providers: [{ provide: A11yCellDirective, useExisting: ItemActionComponent }], host: { class: /** @@ -16,4 +13,4 @@ import { A11yCellDirective } from "../a11y/a11y-cell.directive"; "[&>button]:tw-relative [&>button:not([bit-item-content])]:after:tw-content-[''] [&>button]:after:tw-absolute [&>button]:after:tw-block bit-compact:[&>button]:after:tw-top-[-0.7rem] bit-compact:[&>button]:after:tw-bottom-[-0.7rem] [&>button]:after:tw-top-[-0.8rem] [&>button]:after:tw-bottom-[-0.80rem] [&>button]:after:tw-right-[-0.25rem] [&>button]:after:tw-left-[-0.25rem]", }, }) -export class ItemActionComponent extends A11yCellDirective {} +export class ItemActionComponent {} diff --git a/libs/components/src/item/item.component.ts b/libs/components/src/item/item.component.ts index e1dfd599aac..6058ed07a83 100644 --- a/libs/components/src/item/item.component.ts +++ b/libs/components/src/item/item.component.ts @@ -6,8 +6,6 @@ import { signal, } from "@angular/core"; -import { A11yRowDirective } from "../a11y/a11y-row.directive"; - import { ItemActionComponent } from "./item-action.component"; @Component({ @@ -15,13 +13,12 @@ import { ItemActionComponent } from "./item-action.component"; imports: [ItemActionComponent], changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: "item.component.html", - providers: [{ provide: A11yRowDirective, useExisting: ItemComponent }], host: { class: "tw-block tw-box-border tw-overflow-hidden tw-flex tw-bg-background [&:has([data-item-main-content]_button:hover,[data-item-main-content]_a:hover)]:tw-cursor-pointer [&:has([data-item-main-content]_button:hover,[data-item-main-content]_a:hover)]:tw-bg-hover-default tw-text-main tw-border-solid tw-border-b tw-border-0 [&:not(bit-layout_*)]:tw-rounded-lg bit-compact:[&:not(bit-layout_*)]:tw-rounded-none bit-compact:[&:not(bit-layout_*)]:last-of-type:tw-rounded-b-lg bit-compact:[&:not(bit-layout_*)]:first-of-type:tw-rounded-t-lg tw-min-h-9 tw-mb-1.5 bit-compact:tw-mb-0", }, }) -export class ItemComponent extends A11yRowDirective { +export class ItemComponent { /** * We have `:focus-within` and `:focus-visible` but no `:focus-visible-within` */ diff --git a/libs/components/src/item/item.stories.ts b/libs/components/src/item/item.stories.ts index fd2d59c7ac2..983ae168f8f 100644 --- a/libs/components/src/item/item.stories.ts +++ b/libs/components/src/item/item.stories.ts @@ -5,7 +5,6 @@ import { Meta, StoryObj, componentWrapperDecorator, moduleMetadata } from "@stor import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { A11yGridDirective } from "../a11y/a11y-grid.directive"; import { AvatarModule } from "../avatar"; import { BadgeModule } from "../badge"; import { IconButtonModule } from "../icon-button"; @@ -32,7 +31,6 @@ export default { TypographyModule, ItemActionComponent, ItemContentComponent, - A11yGridDirective, ScrollingModule, LayoutComponent, RouterTestingModule, diff --git a/libs/components/src/shared/focusable-element.ts b/libs/components/src/shared/focusable-element.ts index 99340d5a7bf..b67dd099dd4 100644 --- a/libs/components/src/shared/focusable-element.ts +++ b/libs/components/src/shared/focusable-element.ts @@ -3,7 +3,7 @@ /** * Interface for implementing focusable components. * - * Used by the `AutofocusDirective` and `A11yGridDirective`. + * Used by the `AutofocusDirective`. */ export abstract class FocusableElement { getFocusTarget: () => HTMLElement | undefined;