Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions src/aria/combobox/combobox.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import {Component, computed, DebugElement, signal} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {Combobox, ComboboxInput, ComboboxPopup, ComboboxPopupContainer} from '../combobox';
import {
Combobox,
ComboboxInput,
ComboboxPopup,
ComboboxFilterMode,
ComboboxPopupContainer,
} from '../combobox';
import {Listbox, Option} from '../listbox';
import {runAccessibilityChecks} from '@angular/cdk/testing/private';
import {Tree, TreeItem, TreeItemGroup} from '../tree';
Expand Down Expand Up @@ -52,9 +58,7 @@ describe('Combobox', () => {
const enter = (modifierKeys?: {}) => keydown('Enter', modifierKeys);
const escape = (modifierKeys?: {}) => keydown('Escape', modifierKeys);

function setupCombobox(
opts: {readonly?: boolean; filterMode?: 'manual' | 'auto-select' | 'highlight'} = {},
) {
function setupCombobox(opts: {readonly?: boolean; filterMode?: ComboboxFilterMode} = {}) {
TestBed.configureTestingModule({});
fixture = TestBed.createComponent(ComboboxListboxExample);
const testComponent = fixture.componentInstance;
Expand Down Expand Up @@ -605,9 +609,7 @@ describe('Combobox', () => {
const enter = (modifierKeys?: {}) => keydown('Enter', modifierKeys);
const escape = (modifierKeys?: {}) => keydown('Escape', modifierKeys);

function setupCombobox(
opts: {readonly?: boolean; filterMode?: 'manual' | 'auto-select' | 'highlight'} = {},
) {
function setupCombobox(opts: {readonly?: boolean; filterMode?: ComboboxFilterMode} = {}) {
TestBed.configureTestingModule({});
fixture = TestBed.createComponent(ComboboxTreeExample);
const testComponent = fixture.componentInstance;
Expand Down Expand Up @@ -1126,7 +1128,7 @@ class ComboboxListboxExample {
readonly = signal(false);
searchString = signal('');
values = signal<string[]>([]);
filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual');
filterMode = signal<ComboboxFilterMode>('manual');

options = computed(() =>
states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())),
Expand Down Expand Up @@ -1197,7 +1199,7 @@ class ComboboxTreeExample {
searchString = signal('');
values = signal<string[]>([]);
nodes = computed(() => this.filterTreeNodes(TREE_NODES));
filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual');
filterMode = signal<ComboboxFilterMode>('manual');

firstMatch = computed<string | undefined>(() => {
const flatNodes = this.flattenTreeNodes(this.nodes());
Expand Down
5 changes: 4 additions & 1 deletion src/aria/combobox/combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ import {
ComboboxListboxControls,
ComboboxTreeControls,
ComboboxDialogPattern,
ComboboxFilterMode,
} from '@angular/aria/private';
import {Directionality} from '@angular/cdk/bidi';
import {toSignal} from '@angular/core/rxjs-interop';

export {ComboboxFilterMode};

/**
* @developerPreview 21.0
*/
Expand Down Expand Up @@ -71,7 +74,7 @@ export class Combobox<V> {
readonly popup = contentChild<ComboboxPopup<V>>(ComboboxPopup);

/** The filter mode for the combobox. */
filterMode = input<'manual' | 'auto-select' | 'highlight'>('manual');
filterMode = input<ComboboxFilterMode>('manual');

/** Whether the combobox is disabled. */
readonly disabled = input(false, {transform: booleanAttribute});
Expand Down
1 change: 1 addition & 0 deletions src/aria/combobox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export {
ComboboxInput,
ComboboxPopup,
ComboboxPopupContainer,
ComboboxFilterMode,
} from './combobox';
28 changes: 21 additions & 7 deletions src/aria/grid/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@ import {
Signal,
} from '@angular/core';
import {Directionality} from '@angular/cdk/bidi';
import {GridPattern, GridRowPattern, GridCellPattern, GridCellWidgetPattern} from '../private';
import {
GridPattern,
GridRowPattern,
GridCellPattern,
GridCellWidgetPattern,
GridFocusMode,
GridWrapStrategy,
GridSelectionMode,
} from '../private';

export {GridFocusMode, GridWrapStrategy, GridSelectionMode};

/**
* A directive that provides grid-based navigation and selection behavior.
Expand Down Expand Up @@ -73,19 +83,19 @@ export class Grid {
readonly softDisabled = input(true, {transform: booleanAttribute});

/** The focus strategy used by the grid. */
readonly focusMode = input<'roving' | 'activedescendant'>('roving');
readonly focusMode = input<GridFocusMode>('roving');

/** The wrapping behavior for keyboard navigation along the row axis. */
readonly rowWrap = input<'continuous' | 'loop' | 'nowrap'>('loop');
readonly rowWrap = input<GridWrapStrategy>('loop');

/** The wrapping behavior for keyboard navigation along the column axis. */
readonly colWrap = input<'continuous' | 'loop' | 'nowrap'>('loop');
readonly colWrap = input<GridWrapStrategy>('loop');

/** Whether multiple cells in the grid can be selected. */
readonly multi = input(false, {transform: booleanAttribute});

/** The selection strategy used by the grid. */
readonly selectionMode = input<'follow' | 'explicit'>('follow');
readonly selectionMode = input<GridSelectionMode>('follow');

/** Whether enable range selections (with modifier keys or dragging). */
readonly enableRangeSelection = input(false, {transform: booleanAttribute});
Expand Down Expand Up @@ -123,6 +133,8 @@ export class Grid {
}
}

export type GridRowRole = 'row' | 'rowheader';

/**
* A directive that represents a row in a grid.
*
Expand Down Expand Up @@ -158,7 +170,7 @@ export class GridRow {
readonly element = computed(() => this._elementRef.nativeElement);

/** The ARIA role for the row. */
readonly role = input<'row' | 'rowheader'>('row');
readonly role = input<GridRowRole>('row');

/** The index of this row within the grid. */
readonly rowIndex = input<number>();
Expand All @@ -170,6 +182,8 @@ export class GridRow {
});
}

export type GridCellRole = 'gridcell' | 'columnheader';

/**
* A directive that represents a cell in a grid.
*
Expand Down Expand Up @@ -217,7 +231,7 @@ export class GridCell {
readonly element = computed(() => this._elementRef.nativeElement);

/** The ARIA role for the cell. */
readonly role = input<'gridcell' | 'columnheader'>('gridcell');
readonly role = input<GridCellRole>('gridcell');

/** The number of rows the cell should span. */
readonly rowSpan = input<number>(1);
Expand Down
19 changes: 8 additions & 11 deletions src/aria/listbox/listbox.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Component, DebugElement, signal} from '@angular/core';
import {Listbox, Option} from './listbox';
import {Listbox, Option, ListOrientation, ListSelectionMode, ListFocusMode} from './listbox';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {Direction} from '@angular/cdk/bidi';
Expand Down Expand Up @@ -49,15 +49,15 @@ describe('Listbox', () => {
const type = (char: string) => keydown(char);

function setupListbox(opts?: {
orientation?: 'horizontal' | 'vertical';
orientation?: ListOrientation;
disabled?: boolean;
readonly?: boolean;
values?: number[];
softDisabled?: boolean;
focusMode?: 'roving' | 'activedescendant';
focusMode?: ListFocusMode;
multi?: boolean;
wrap?: boolean;
selectionMode?: 'follow' | 'explicit';
selectionMode?: ListSelectionMode;
typeaheadDelay?: number;
disabledOptions?: number[];
options?: TestOption[];
Expand Down Expand Up @@ -573,10 +573,7 @@ describe('Listbox', () => {
});
});

function runNavigationTests(
focusMode: 'roving' | 'activedescendant',
isFocused: (index: number) => boolean,
) {
function runNavigationTests(focusMode: ListFocusMode, isFocused: (index: number) => boolean) {
describe(`keyboard navigation (focusMode="${focusMode}")`, () => {
it('should move focus to the last focusable option on End', () => {
setupListbox({focusMode, disabledOptions: [4]});
Expand Down Expand Up @@ -812,11 +809,11 @@ class ListboxExample {
disabled = false;
readonly = false;
softDisabled = true;
focusMode: 'roving' | 'activedescendant' = 'roving';
orientation: 'vertical' | 'horizontal' = 'vertical';
focusMode: ListFocusMode = 'roving';
orientation: ListOrientation = 'vertical';
multi = false;
wrap = true;
selectionMode: 'follow' | 'explicit' = 'explicit';
selectionMode: ListSelectionMode = 'explicit';
typeaheadDelay = 0.5;
}

Expand Down
17 changes: 13 additions & 4 deletions src/aria/listbox/listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,21 @@ import {
signal,
untracked,
} from '@angular/core';
import {ComboboxListboxPattern, ListboxPattern, OptionPattern} from '@angular/aria/private';
import {
ComboboxListboxPattern,
ListOrientation,
ListboxPattern,
OptionPattern,
ListSelectionMode,
ListFocusMode,
} from '@angular/aria/private';
import {Directionality} from '@angular/cdk/bidi';
import {toSignal} from '@angular/core/rxjs-interop';
import {_IdGenerator} from '@angular/cdk/a11y';
import {ComboboxPopup} from '../combobox';

export {ListOrientation, ListFocusMode, ListSelectionMode};

/**
* A listbox container.
*
Expand Down Expand Up @@ -91,7 +100,7 @@ export class Listbox<V> {
protected items = computed(() => this._options().map(option => option._pattern));

/** Whether the list is vertically or horizontally oriented. */
orientation = input<'vertical' | 'horizontal'>('vertical');
orientation = input<ListOrientation>('vertical');

/** Whether multiple items in the list can be selected at once. */
multi = input(false, {transform: booleanAttribute});
Expand All @@ -103,10 +112,10 @@ export class Listbox<V> {
softDisabled = input(true, {transform: booleanAttribute});

/** The focus strategy used by the list. */
focusMode = input<'roving' | 'activedescendant'>('roving');
focusMode = input<ListFocusMode>('roving');

/** The selection strategy used by the list. */
selectionMode = input<'follow' | 'explicit'>('follow');
selectionMode = input<ListSelectionMode>('follow');

/** The amount of time before the typeahead search is reset. */
typeaheadDelay = input<number>(0.5); // Picked arbitrarily.
Expand Down
4 changes: 4 additions & 0 deletions src/aria/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ ts_project(
deps = [
"//:node_modules/@angular/core",
"//src/aria/private/accordion",
"//src/aria/private/behaviors/grid",
"//src/aria/private/behaviors/list-focus",
"//src/aria/private/behaviors/list-navigation",
"//src/aria/private/behaviors/list-selection",
"//src/aria/private/behaviors/signal-like",
"//src/aria/private/combobox",
"//src/aria/private/deferred-content",
Expand Down
1 change: 1 addition & 0 deletions src/aria/private/behaviors/grid/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ts_project(
),
deps = [
"//:node_modules/@angular/core",
"//src/aria/private/behaviors/grid-focus",
"//src/aria/private/behaviors/signal-like",
],
)
Expand Down
4 changes: 3 additions & 1 deletion src/aria/private/behaviors/grid/grid-focus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {computed, signal} from '@angular/core';
import {SignalLike} from '../signal-like/signal-like';
import type {GridData, BaseGridCell, RowCol} from './grid-data';

export type GridFocusMode = 'roving' | 'activedescendant';

/** Represents an cell in a grid, such as a grid cell, that may receive focus. */
export interface GridFocusCell extends BaseGridCell {
/** A unique identifier for the cell. */
Expand All @@ -25,7 +27,7 @@ export interface GridFocusCell extends BaseGridCell {
/** Represents the required inputs for a grid that contains focusable cells. */
export interface GridFocusInputs {
/** The focus strategy used by the grid. */
focusMode: SignalLike<'roving' | 'activedescendant'>;
focusMode: SignalLike<GridFocusMode>;

/** Whether the grid is disabled. */
disabled: SignalLike<boolean>;
Expand Down
6 changes: 3 additions & 3 deletions src/aria/private/behaviors/grid/grid-navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
TestBaseGridCell,
} from './grid-data.spec';
import {GridFocus, GridFocusInputs} from './grid-focus';
import {direction, GridNavigation, GridNavigationInputs, WrapStrategy} from './grid-navigation';
import {direction, GridNavigation, GridNavigationInputs, GridWrapStrategy} from './grid-navigation';

export interface TestGridNavigationCell extends TestBaseGridCell {
element: WritableSignal<HTMLElement>;
Expand Down Expand Up @@ -62,8 +62,8 @@ function setupGridNavigation(
const gridNav = new GridNavigation({
grid: gridData,
gridFocus: gridFocus,
rowWrap: signal<WrapStrategy>('loop'),
colWrap: signal<WrapStrategy>('loop'),
rowWrap: signal<GridWrapStrategy>('loop'),
colWrap: signal<GridWrapStrategy>('loop'),
...gridFocusInputs,
...inputs,
});
Expand Down
10 changes: 5 additions & 5 deletions src/aria/private/behaviors/grid/grid-navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ export const direction: Record<'Up' | 'Down' | 'Left' | 'Right', Delta> = {
} as const;

/** The wrapping behavior for keyboard navigation. */
export type WrapStrategy = 'continuous' | 'loop' | 'nowrap';
export type GridWrapStrategy = 'continuous' | 'loop' | 'nowrap';

/** Represents an item in a collection, such as a listbox option, than can be navigated to. */
export interface GridNavigationCell extends GridFocusCell {}

/** Represents the required inputs for a collection that has navigable items. */
export interface GridNavigationInputs extends GridFocusInputs {
/** The wrapping behavior for keyboard navigation along the row axis. */
rowWrap: SignalLike<WrapStrategy>;
rowWrap: SignalLike<GridWrapStrategy>;

/** The wrapping behavior for keyboard navigation along the column axis. */
colWrap: SignalLike<WrapStrategy>;
colWrap: SignalLike<GridWrapStrategy>;
}

/** Dependencies for the `GridNavigation` class. */
Expand Down Expand Up @@ -76,7 +76,7 @@ export class GridNavigation<T extends GridNavigationCell> {
peek(
direction: Delta,
fromCoords: RowCol,
wrap?: WrapStrategy,
wrap?: GridWrapStrategy,
allowDisabled?: boolean,
): RowCol | undefined {
wrap = wrap ?? (direction.row !== undefined ? this.inputs.rowWrap() : this.inputs.colWrap());
Expand Down Expand Up @@ -143,7 +143,7 @@ export class GridNavigation<T extends GridNavigationCell> {
private _peekDirectional(
delta: Delta,
fromCoords: RowCol,
wrap: 'continuous' | 'loop' | 'nowrap',
wrap: GridWrapStrategy,
allowDisabled: boolean = false,
): RowCol | undefined {
if (this.inputs.gridFocus.gridDisabled()) return undefined;
Expand Down
6 changes: 3 additions & 3 deletions src/aria/private/behaviors/grid/grid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import {signal, Signal, WritableSignal} from '@angular/core';
import {Grid, GridInputs} from './grid';
import {createGridA, createGridD, TestBaseGridCell} from './grid-data.spec';
import {WrapStrategy} from './grid-navigation';
import {GridWrapStrategy} from './grid-navigation';

interface TestGridCell extends TestBaseGridCell {
element: WritableSignal<HTMLElement>;
Expand Down Expand Up @@ -44,8 +44,8 @@ function setupGrid(
focusMode: signal('roving'),
disabled: signal(false),
softDisabled: signal(true),
rowWrap: signal<WrapStrategy>('loop'),
colWrap: signal<WrapStrategy>('loop'),
rowWrap: signal<GridWrapStrategy>('loop'),
colWrap: signal<GridWrapStrategy>('loop'),
...inputs,
};

Expand Down
4 changes: 3 additions & 1 deletion src/aria/private/behaviors/list-focus/list-focus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import {computed, signal} from '@angular/core';
import {SignalLike, WritableSignalLike} from '../signal-like/signal-like';

export type ListFocusMode = 'roving' | 'activedescendant';

/** Represents an item in a collection, such as a listbox option, than may receive focus. */
export interface ListFocusItem {
/** A unique identifier for the item. */
Expand All @@ -27,7 +29,7 @@ export interface ListFocusItem {
/** Represents the required inputs for a collection that contains focusable items. */
export interface ListFocusInputs<T extends ListFocusItem> {
/** The focus strategy used by the list. */
focusMode: SignalLike<'roving' | 'activedescendant'>;
focusMode: SignalLike<ListFocusMode>;

/** Whether the list is disabled. */
disabled: SignalLike<boolean>;
Expand Down
Loading
Loading