mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
[CL-800] delete a11y-grid, a11y-row, a11y-cell directives (#15709)
* delete a11y-grid,-row,-cell directives * remove missed references * remove missed references
This commit is contained in:
@@ -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<HTMLElement>) {}
|
||||
}
|
||||
@@ -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<A11yRowDirective>;
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<A11yCellDirective>;
|
||||
|
||||
@ContentChildren(A11yCellDirective)
|
||||
private contentCells: QueryList<A11yCellDirective>;
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.cells = [...this.viewCells, ...this.contentCells];
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { A11yCellDirective } from "../a11y/a11y-cell.directive";
|
||||
|
||||
@Component({
|
||||
selector: "bit-item-action",
|
||||
imports: [],
|
||||
template: `<ng-content></ng-content>`,
|
||||
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 {}
|
||||
|
||||
@@ -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`
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user