diff --git a/src/aria/combobox/combobox.spec.ts b/src/aria/combobox/combobox.spec.ts index 247cb8d1331d..53f8d1e7edbb 100644 --- a/src/aria/combobox/combobox.spec.ts +++ b/src/aria/combobox/combobox.spec.ts @@ -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'; @@ -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; @@ -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; @@ -1126,7 +1128,7 @@ class ComboboxListboxExample { readonly = signal(false); searchString = signal(''); values = signal([]); - filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual'); + filterMode = signal('manual'); options = computed(() => states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), @@ -1197,7 +1199,7 @@ class ComboboxTreeExample { searchString = signal(''); values = signal([]); nodes = computed(() => this.filterTreeNodes(TREE_NODES)); - filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual'); + filterMode = signal('manual'); firstMatch = computed(() => { const flatNodes = this.flattenTreeNodes(this.nodes()); diff --git a/src/aria/combobox/combobox.ts b/src/aria/combobox/combobox.ts index 66e566bcf48f..12c59bfe2d82 100644 --- a/src/aria/combobox/combobox.ts +++ b/src/aria/combobox/combobox.ts @@ -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 */ @@ -71,7 +74,7 @@ export class Combobox { readonly popup = contentChild>(ComboboxPopup); /** The filter mode for the combobox. */ - filterMode = input<'manual' | 'auto-select' | 'highlight'>('manual'); + filterMode = input('manual'); /** Whether the combobox is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); diff --git a/src/aria/combobox/index.ts b/src/aria/combobox/index.ts index 70513ea9f2ec..13d913c3c084 100644 --- a/src/aria/combobox/index.ts +++ b/src/aria/combobox/index.ts @@ -12,4 +12,5 @@ export { ComboboxInput, ComboboxPopup, ComboboxPopupContainer, + ComboboxFilterMode, } from './combobox'; diff --git a/src/aria/grid/grid.ts b/src/aria/grid/grid.ts index e3604e5d8344..2f14a1e3f7a6 100644 --- a/src/aria/grid/grid.ts +++ b/src/aria/grid/grid.ts @@ -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. @@ -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('roving'); /** The wrapping behavior for keyboard navigation along the row axis. */ - readonly rowWrap = input<'continuous' | 'loop' | 'nowrap'>('loop'); + readonly rowWrap = input('loop'); /** The wrapping behavior for keyboard navigation along the column axis. */ - readonly colWrap = input<'continuous' | 'loop' | 'nowrap'>('loop'); + readonly colWrap = input('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('follow'); /** Whether enable range selections (with modifier keys or dragging). */ readonly enableRangeSelection = input(false, {transform: booleanAttribute}); @@ -123,6 +133,8 @@ export class Grid { } } +export type GridRowRole = 'row' | 'rowheader'; + /** * A directive that represents a row in a grid. * @@ -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('row'); /** The index of this row within the grid. */ readonly rowIndex = input(); @@ -170,6 +182,8 @@ export class GridRow { }); } +export type GridCellRole = 'gridcell' | 'columnheader'; + /** * A directive that represents a cell in a grid. * @@ -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('gridcell'); /** The number of rows the cell should span. */ readonly rowSpan = input(1); diff --git a/src/aria/listbox/listbox.spec.ts b/src/aria/listbox/listbox.spec.ts index df138a3eca61..1782ae12870f 100644 --- a/src/aria/listbox/listbox.spec.ts +++ b/src/aria/listbox/listbox.spec.ts @@ -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'; @@ -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[]; @@ -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]}); @@ -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; } diff --git a/src/aria/listbox/listbox.ts b/src/aria/listbox/listbox.ts index 446ebedd7182..a0cc7116343d 100644 --- a/src/aria/listbox/listbox.ts +++ b/src/aria/listbox/listbox.ts @@ -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. * @@ -91,7 +100,7 @@ export class Listbox { protected items = computed(() => this._options().map(option => option._pattern)); /** Whether the list is vertically or horizontally oriented. */ - orientation = input<'vertical' | 'horizontal'>('vertical'); + orientation = input('vertical'); /** Whether multiple items in the list can be selected at once. */ multi = input(false, {transform: booleanAttribute}); @@ -103,10 +112,10 @@ export class Listbox { softDisabled = input(true, {transform: booleanAttribute}); /** The focus strategy used by the list. */ - focusMode = input<'roving' | 'activedescendant'>('roving'); + focusMode = input('roving'); /** The selection strategy used by the list. */ - selectionMode = input<'follow' | 'explicit'>('follow'); + selectionMode = input('follow'); /** The amount of time before the typeahead search is reset. */ typeaheadDelay = input(0.5); // Picked arbitrarily. diff --git a/src/aria/private/BUILD.bazel b/src/aria/private/BUILD.bazel index f688ab1b20e1..af2c32a5a7e5 100644 --- a/src/aria/private/BUILD.bazel +++ b/src/aria/private/BUILD.bazel @@ -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", diff --git a/src/aria/private/behaviors/grid/BUILD.bazel b/src/aria/private/behaviors/grid/BUILD.bazel index a5c7c160071c..173620dafecb 100644 --- a/src/aria/private/behaviors/grid/BUILD.bazel +++ b/src/aria/private/behaviors/grid/BUILD.bazel @@ -10,6 +10,7 @@ ts_project( ), deps = [ "//:node_modules/@angular/core", + "//src/aria/private/behaviors/grid-focus", "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/private/behaviors/grid/grid-focus.ts b/src/aria/private/behaviors/grid/grid-focus.ts index 75ce16990322..63bd980c3863 100644 --- a/src/aria/private/behaviors/grid/grid-focus.ts +++ b/src/aria/private/behaviors/grid/grid-focus.ts @@ -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. */ @@ -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; /** Whether the grid is disabled. */ disabled: SignalLike; diff --git a/src/aria/private/behaviors/grid/grid-navigation.spec.ts b/src/aria/private/behaviors/grid/grid-navigation.spec.ts index 669378f2b394..be30536b54f6 100644 --- a/src/aria/private/behaviors/grid/grid-navigation.spec.ts +++ b/src/aria/private/behaviors/grid/grid-navigation.spec.ts @@ -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; @@ -62,8 +62,8 @@ function setupGridNavigation( const gridNav = new GridNavigation({ grid: gridData, gridFocus: gridFocus, - rowWrap: signal('loop'), - colWrap: signal('loop'), + rowWrap: signal('loop'), + colWrap: signal('loop'), ...gridFocusInputs, ...inputs, }); diff --git a/src/aria/private/behaviors/grid/grid-navigation.ts b/src/aria/private/behaviors/grid/grid-navigation.ts index 48fa87910ca1..dc3c2901c41b 100644 --- a/src/aria/private/behaviors/grid/grid-navigation.ts +++ b/src/aria/private/behaviors/grid/grid-navigation.ts @@ -28,7 +28,7 @@ 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 {} @@ -36,10 +36,10 @@ 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; + rowWrap: SignalLike; /** The wrapping behavior for keyboard navigation along the column axis. */ - colWrap: SignalLike; + colWrap: SignalLike; } /** Dependencies for the `GridNavigation` class. */ @@ -76,7 +76,7 @@ export class GridNavigation { peek( direction: Delta, fromCoords: RowCol, - wrap?: WrapStrategy, + wrap?: GridWrapStrategy, allowDisabled?: boolean, ): RowCol | undefined { wrap = wrap ?? (direction.row !== undefined ? this.inputs.rowWrap() : this.inputs.colWrap()); @@ -143,7 +143,7 @@ export class GridNavigation { private _peekDirectional( delta: Delta, fromCoords: RowCol, - wrap: 'continuous' | 'loop' | 'nowrap', + wrap: GridWrapStrategy, allowDisabled: boolean = false, ): RowCol | undefined { if (this.inputs.gridFocus.gridDisabled()) return undefined; diff --git a/src/aria/private/behaviors/grid/grid.spec.ts b/src/aria/private/behaviors/grid/grid.spec.ts index c3728cb6edf5..3ae4a333d9bc 100644 --- a/src/aria/private/behaviors/grid/grid.spec.ts +++ b/src/aria/private/behaviors/grid/grid.spec.ts @@ -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; @@ -44,8 +44,8 @@ function setupGrid( focusMode: signal('roving'), disabled: signal(false), softDisabled: signal(true), - rowWrap: signal('loop'), - colWrap: signal('loop'), + rowWrap: signal('loop'), + colWrap: signal('loop'), ...inputs, }; diff --git a/src/aria/private/behaviors/list-focus/list-focus.ts b/src/aria/private/behaviors/list-focus/list-focus.ts index ac0f43ce952b..ef7770ce3427 100644 --- a/src/aria/private/behaviors/list-focus/list-focus.ts +++ b/src/aria/private/behaviors/list-focus/list-focus.ts @@ -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. */ @@ -27,7 +29,7 @@ export interface ListFocusItem { /** Represents the required inputs for a collection that contains focusable items. */ export interface ListFocusInputs { /** The focus strategy used by the list. */ - focusMode: SignalLike<'roving' | 'activedescendant'>; + focusMode: SignalLike; /** Whether the list is disabled. */ disabled: SignalLike; diff --git a/src/aria/private/behaviors/list-navigation/list-navigation.ts b/src/aria/private/behaviors/list-navigation/list-navigation.ts index fb63c70386ac..bde952ec4ba2 100644 --- a/src/aria/private/behaviors/list-navigation/list-navigation.ts +++ b/src/aria/private/behaviors/list-navigation/list-navigation.ts @@ -9,6 +9,8 @@ import {SignalLike} from '../signal-like/signal-like'; import {ListFocus, ListFocusInputs, ListFocusItem} from '../list-focus/list-focus'; +export type ListOrientation = 'horizontal' | 'vertical'; + /** Represents an item in a collection, such as a listbox option, than can be navigated to. */ export interface ListNavigationItem extends ListFocusItem {} @@ -18,9 +20,9 @@ export interface ListNavigationInputs extends List wrap: SignalLike; /** Whether the list is vertically or horizontally oriented. */ - orientation: SignalLike<'vertical' | 'horizontal'>; + orientation: SignalLike; - /** The direction that text is read based on the users locale. */ + /** The direction that text is read based on the user's locale. */ textDirection: SignalLike<'rtl' | 'ltr'>; } diff --git a/src/aria/private/behaviors/list-selection/list-selection.ts b/src/aria/private/behaviors/list-selection/list-selection.ts index 6f7c191defec..67c7a0faf439 100644 --- a/src/aria/private/behaviors/list-selection/list-selection.ts +++ b/src/aria/private/behaviors/list-selection/list-selection.ts @@ -10,6 +10,8 @@ import {computed, signal} from '@angular/core'; import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; import {ListFocus, ListFocusInputs, ListFocusItem} from '../list-focus/list-focus'; +export type ListSelectionMode = 'follow' | 'explicit'; + /** Represents an item in a collection, such as a listbox option, that can be selected. */ export interface ListSelectionItem extends ListFocusItem { /** The value of the item. */ @@ -28,7 +30,7 @@ export interface ListSelectionInputs, V> extends values: WritableSignalLike; /** The selection strategy used by the list. */ - selectionMode: SignalLike<'follow' | 'explicit'>; + selectionMode: SignalLike; } /** Controls selection for a list of items. */ diff --git a/src/aria/private/combobox/combobox.ts b/src/aria/private/combobox/combobox.ts index 954beaba13d5..18250350ab58 100644 --- a/src/aria/private/combobox/combobox.ts +++ b/src/aria/private/combobox/combobox.ts @@ -11,6 +11,8 @@ import {computed, signal} from '@angular/core'; import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; import {ListItem} from '../behaviors/list/list'; +export type ComboboxFilterMode = 'manual' | 'auto-select' | 'highlight'; + /** Represents the required inputs for a combobox. */ export interface ComboboxInputs, V> { /** The controls for the popup associated with the combobox. */ @@ -25,7 +27,7 @@ export interface ComboboxInputs, V> { containerEl: SignalLike; /** The filtering mode for the combobox. */ - filterMode: SignalLike<'manual' | 'auto-select' | 'highlight'>; + filterMode: SignalLike; /** The current value of the combobox. */ inputValue?: WritableSignalLike; diff --git a/src/aria/private/grid/grid.ts b/src/aria/private/grid/grid.ts index 0e0fb3594e90..87f85d5326a6 100644 --- a/src/aria/private/grid/grid.ts +++ b/src/aria/private/grid/grid.ts @@ -13,6 +13,8 @@ import {NavOptions, Grid, GridInputs as GridBehaviorInputs} from '../behaviors/g import type {GridRowPattern} from './row'; import type {GridCellPattern} from './cell'; +export type GridSelectionMode = 'follow' | 'explicit'; + /** Represents the required inputs for the grid pattern. */ export interface GridInputs extends Omit, 'cells'> { /** The html element of the grid. */ @@ -31,7 +33,7 @@ export interface GridInputs extends Omit, 'c multi: SignalLike; /** The selection strategy used by the grid. */ - selectionMode: SignalLike<'follow' | 'explicit'>; + selectionMode: SignalLike; /** Whether enable range selection. */ enableRangeSelection: SignalLike; diff --git a/src/aria/private/listbox/BUILD.bazel b/src/aria/private/listbox/BUILD.bazel index f38e71709afa..5f4886be2f1e 100644 --- a/src/aria/private/listbox/BUILD.bazel +++ b/src/aria/private/listbox/BUILD.bazel @@ -12,6 +12,7 @@ ts_project( "//:node_modules/@angular/core", "//src/aria/private/behaviors/event-manager", "//src/aria/private/behaviors/list", + "//src/aria/private/behaviors/list-navigation", "//src/aria/private/behaviors/signal-like", "//src/aria/private/combobox", ], diff --git a/src/aria/private/listbox/listbox.ts b/src/aria/private/listbox/listbox.ts index e65fffb1e05f..07c93c08560f 100644 --- a/src/aria/private/listbox/listbox.ts +++ b/src/aria/private/listbox/listbox.ts @@ -11,6 +11,7 @@ import {KeyboardEventManager, PointerEventManager, Modifier} from '../behaviors/ import {computed, signal} from '@angular/core'; import {SignalLike} from '../behaviors/signal-like/signal-like'; import {List, ListInputs} from '../behaviors/list/list'; +import {ListOrientation} from '../behaviors/list-navigation/list-navigation'; /** Represents the required inputs for a listbox. */ export type ListboxInputs = ListInputs, V> & { @@ -26,7 +27,7 @@ export class ListboxPattern { listBehavior: List, V>; /** Whether the list is vertically or horizontally oriented. */ - orientation: SignalLike<'vertical' | 'horizontal'>; + orientation: SignalLike; /** Whether the listbox is disabled. */ disabled = computed(() => this.listBehavior.disabled()); diff --git a/src/aria/private/public-api.ts b/src/aria/private/public-api.ts index ed8716c7b67b..050c56305a73 100644 --- a/src/aria/private/public-api.ts +++ b/src/aria/private/public-api.ts @@ -12,6 +12,11 @@ export * from './listbox/option'; export * from './listbox/combobox-listbox'; export * from './menu/menu'; export * from './behaviors/signal-like/signal-like'; +export {GridFocusMode} from './behaviors/grid/grid-focus'; +export {GridWrapStrategy} from './behaviors/grid/grid-navigation'; +export {ListOrientation} from './behaviors/list-navigation/list-navigation'; +export {ListFocusMode} from './behaviors/list-focus/list-focus'; +export {ListSelectionMode} from './behaviors/list-selection/list-selection'; export * from './tabs/tabs'; export * from './toolbar/toolbar'; export * from './toolbar/toolbar-widget'; diff --git a/src/aria/private/tabs/BUILD.bazel b/src/aria/private/tabs/BUILD.bazel index d22f3f920731..087fa2288a93 100644 --- a/src/aria/private/tabs/BUILD.bazel +++ b/src/aria/private/tabs/BUILD.bazel @@ -13,6 +13,7 @@ ts_project( "//src/aria/private/behaviors/expansion", "//src/aria/private/behaviors/label", "//src/aria/private/behaviors/list", + "//src/aria/private/behaviors/list-navigation", "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/private/tabs/tabs.ts b/src/aria/private/tabs/tabs.ts index 50c4dc782175..81fbc19fc38f 100644 --- a/src/aria/private/tabs/tabs.ts +++ b/src/aria/private/tabs/tabs.ts @@ -17,6 +17,7 @@ import { import {SignalLike} from '../behaviors/signal-like/signal-like'; import {LabelControl, LabelControlOptionalInputs} from '../behaviors/label/label'; import {List, ListInputs, ListItem} from '../behaviors/list/list'; +import {ListOrientation} from '../behaviors/list-navigation/list-navigation'; /** The required inputs to tabs. */ export interface TabInputs @@ -146,7 +147,7 @@ export class TabListPattern { readonly expansionManager: ListExpansion; /** Whether the tablist is vertically or horizontally oriented. */ - readonly orientation: SignalLike<'vertical' | 'horizontal'>; + readonly orientation: SignalLike; /** Whether the tablist is disabled. */ readonly disabled: SignalLike; diff --git a/src/aria/private/toolbar/BUILD.bazel b/src/aria/private/toolbar/BUILD.bazel index 6c2dd3d6221b..cb1782015a31 100644 --- a/src/aria/private/toolbar/BUILD.bazel +++ b/src/aria/private/toolbar/BUILD.bazel @@ -13,6 +13,7 @@ ts_project( "//:node_modules/@angular/core", "//src/aria/private/behaviors/event-manager", "//src/aria/private/behaviors/list", + "//src/aria/private/behaviors/list-navigation", "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/private/toolbar/toolbar.ts b/src/aria/private/toolbar/toolbar.ts index 68d63af0a53a..d2813cf444bf 100644 --- a/src/aria/private/toolbar/toolbar.ts +++ b/src/aria/private/toolbar/toolbar.ts @@ -11,6 +11,7 @@ import {SignalLike} from '../behaviors/signal-like/signal-like'; import {KeyboardEventManager} from '../behaviors/event-manager'; import {List, ListInputs} from '../behaviors/list/list'; import {ToolbarWidgetPattern} from './toolbar-widget'; +import {ListOrientation} from '../behaviors/list-navigation/list-navigation'; /** Represents the required inputs for a toolbar. */ export type ToolbarInputs = Omit< @@ -26,8 +27,8 @@ export class ToolbarPattern { /** The list behavior for the toolbar. */ readonly listBehavior: List, V>; - /** Whether the tablist is vertically or horizontally oriented. */ - readonly orientation: SignalLike<'vertical' | 'horizontal'>; + /** Whether the toolbar is vertically or horizontally oriented. */ + readonly orientation: SignalLike; /** Whether disabled items in the group should be focusable. */ readonly softDisabled: SignalLike; diff --git a/src/aria/private/tree/BUILD.bazel b/src/aria/private/tree/BUILD.bazel index 6f1779bf56d2..956576ac7209 100644 --- a/src/aria/private/tree/BUILD.bazel +++ b/src/aria/private/tree/BUILD.bazel @@ -13,6 +13,8 @@ ts_project( "//src/aria/private/behaviors/event-manager", "//src/aria/private/behaviors/expansion", "//src/aria/private/behaviors/list", + "//src/aria/private/behaviors/list-navigation", + "//src/aria/private/behaviors/list-selection", "//src/aria/private/behaviors/signal-like", "//src/aria/private/combobox", ], diff --git a/src/aria/private/tree/tree.ts b/src/aria/private/tree/tree.ts index b51d784994b1..fda8b6d3fe74 100644 --- a/src/aria/private/tree/tree.ts +++ b/src/aria/private/tree/tree.ts @@ -9,6 +9,8 @@ import {computed, signal} from '@angular/core'; import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; import {List, ListInputs, ListItem} from '../behaviors/list/list'; +import {ListOrientation} from '../behaviors/list-navigation/list-navigation'; +import {ListSelectionMode} from '../behaviors/list-selection/list-selection'; import {ExpansionItem, ExpansionControl, ListExpansion} from '../behaviors/expansion/expansion'; import {KeyboardEventManager, PointerEventManager, Modifier} from '../behaviors/event-manager'; @@ -155,6 +157,8 @@ interface SelectOptions { anchor?: boolean; } +export type TreeCurrentType = 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'; + /** Represents the required inputs for a tree. */ export interface TreeInputs extends Omit, V>, 'items'> { /** A unique identifier for the tree. */ @@ -167,7 +171,7 @@ export interface TreeInputs extends Omit, V>, ' nav: SignalLike; /** The aria-current type. */ - currentType: SignalLike<'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'>; + currentType: SignalLike; } export interface TreePattern extends TreeInputs {} @@ -343,7 +347,7 @@ export class TreePattern { nav: SignalLike; /** The aria-current type. */ - currentType: SignalLike<'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'>; + currentType: SignalLike; /** All items in the tree, in document order (DFS-like, a flattened list). */ allItems: SignalLike[]>; @@ -361,7 +365,7 @@ export class TreePattern { wrap: SignalLike; /** The orientation of the tree. */ - orientation: SignalLike<'vertical' | 'horizontal'>; + orientation: SignalLike; /** The text direction of the tree. */ textDirection: SignalLike<'ltr' | 'rtl'>; @@ -370,7 +374,7 @@ export class TreePattern { multi: SignalLike; /** The selection mode of the tree. */ - selectionMode: SignalLike<'follow' | 'explicit'>; + selectionMode: SignalLike; /** The delay in milliseconds to wait before clearing the typeahead buffer. */ typeaheadDelay: SignalLike; diff --git a/src/aria/tabs/tabs.spec.ts b/src/aria/tabs/tabs.spec.ts index 8bf4ecfbeb6b..0c7145a03584 100644 --- a/src/aria/tabs/tabs.spec.ts +++ b/src/aria/tabs/tabs.spec.ts @@ -3,7 +3,16 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Direction} from '@angular/cdk/bidi'; import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; -import {Tabs, TabList, Tab, TabPanel, TabContent} from './tabs'; +import { + Tabs, + TabList, + Tab, + TabPanel, + TabContent, + ListOrientation, + ListFocusMode, + ListSelectionMode, +} from './tabs'; interface ModifierKeys { ctrlKey?: boolean; @@ -76,12 +85,12 @@ describe('Tabs', () => { options: { initialTabs?: TestTabDefinition[]; selectedTab?: string | undefined; - orientation?: 'horizontal' | 'vertical'; + orientation?: ListOrientation; disabled?: boolean; wrap?: boolean; softDisabled?: boolean; - focusMode?: 'roving' | 'activedescendant'; - selectionMode?: 'follow' | 'explicit'; + focusMode?: ListFocusMode; + selectionMode?: ListSelectionMode; } = {}, ) { if (options.initialTabs !== undefined) testComponent.tabsData.set(options.initialTabs); @@ -731,10 +740,10 @@ class TestTabsComponent { ]); selectedTab = signal(undefined); - orientation = signal<'horizontal' | 'vertical'>('horizontal'); + orientation = signal('horizontal'); disabled = signal(false); wrap = signal(true); softDisabled = signal(true); - focusMode = signal<'roving' | 'activedescendant'>('roving'); - selectionMode = signal<'follow' | 'explicit'>('follow'); + focusMode = signal('roving'); + selectionMode = signal('follow'); } diff --git a/src/aria/tabs/tabs.ts b/src/aria/tabs/tabs.ts index ae65f5f6241c..c6b23361126f 100644 --- a/src/aria/tabs/tabs.ts +++ b/src/aria/tabs/tabs.ts @@ -29,8 +29,13 @@ import { TabPattern, DeferredContent, DeferredContentAware, + ListOrientation, + ListFocusMode, + ListSelectionMode, } from '@angular/aria/private'; +export {ListOrientation, ListFocusMode, ListSelectionMode}; + interface HasElement { element: Signal; } @@ -169,7 +174,7 @@ export class TabList implements OnInit, OnDestroy { ); /** Whether the tablist is vertically or horizontally oriented. */ - readonly orientation = input<'vertical' | 'horizontal'>('horizontal'); + readonly orientation = input('horizontal'); /** Whether focus should wrap when navigating. */ readonly wrap = input(true, {transform: booleanAttribute}); @@ -178,10 +183,10 @@ export class TabList implements OnInit, OnDestroy { readonly softDisabled = input(true, {transform: booleanAttribute}); /** The focus strategy used by the tablist. */ - readonly focusMode = input<'roving' | 'activedescendant'>('roving'); + readonly focusMode = input('roving'); /** The selection strategy used by the tablist. */ - readonly selectionMode = input<'follow' | 'explicit'>('follow'); + readonly selectionMode = input('follow'); /** Whether the tablist is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); diff --git a/src/aria/toolbar/toolbar.spec.ts b/src/aria/toolbar/toolbar.spec.ts index 18cf316c8180..30acbdb05780 100644 --- a/src/aria/toolbar/toolbar.spec.ts +++ b/src/aria/toolbar/toolbar.spec.ts @@ -1,7 +1,7 @@ import {Component, DebugElement, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; -import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from './toolbar'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup, ListOrientation} from './toolbar'; import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; describe('Toolbar', () => { @@ -37,7 +37,7 @@ describe('Toolbar', () => { function setupToolbar( opts: { - orientation?: 'vertical' | 'horizontal'; + orientation?: ListOrientation; softDisabled?: boolean; disabled?: boolean; wrap?: boolean; @@ -622,7 +622,7 @@ describe('Toolbar', () => { imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], }) class ToolbarExample { - orientation = signal<'vertical' | 'horizontal'>('horizontal'); + orientation = signal('horizontal'); softDisabled = signal(true); disabled = signal(false); wrap = signal(true); diff --git a/src/aria/toolbar/toolbar.ts b/src/aria/toolbar/toolbar.ts index e54edb888a94..412651294914 100644 --- a/src/aria/toolbar/toolbar.ts +++ b/src/aria/toolbar/toolbar.ts @@ -25,10 +25,13 @@ import { ToolbarWidgetPattern, ToolbarWidgetGroupPattern, SignalLike, + ListOrientation, } from '@angular/aria/private'; import {Directionality} from '@angular/cdk/bidi'; import {_IdGenerator} from '@angular/cdk/a11y'; +export {ListOrientation}; + interface HasElement { element: Signal; } @@ -93,7 +96,7 @@ export class Toolbar { ); /** Whether the toolbar is vertically or horizontally oriented. */ - readonly orientation = input<'vertical' | 'horizontal'>('horizontal'); + readonly orientation = input('horizontal'); /** Whether to allow disabled items to receive focus. */ softDisabled = input(true, {transform: booleanAttribute}); diff --git a/src/aria/tree/tree.spec.ts b/src/aria/tree/tree.spec.ts index 9ab2c1d6daf0..a90e204b187c 100644 --- a/src/aria/tree/tree.spec.ts +++ b/src/aria/tree/tree.spec.ts @@ -4,7 +4,15 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Direction} from '@angular/cdk/bidi'; import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; -import {Tree, TreeItem, TreeItemGroup} from './tree'; +import { + Tree, + TreeItem, + TreeItemGroup, + ListOrientation, + ListFocusMode, + ListSelectionMode, + TreeCurrentType, +} from './tree'; interface ModifierKeys { ctrlKey?: boolean; @@ -77,14 +85,14 @@ describe('Tree', () => { nodes?: TestTreeNode[]; values?: string[]; disabled?: boolean; - orientation?: 'horizontal' | 'vertical'; + orientation?: ListOrientation; multi?: boolean; wrap?: boolean; softDisabled?: boolean; - focusMode?: 'roving' | 'activedescendant'; - selectionMode?: 'follow' | 'explicit'; + focusMode?: ListFocusMode; + selectionMode?: ListSelectionMode; nav?: boolean; - currentType?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'; + currentType?: TreeCurrentType; } = {}, ) { if (config.nodes !== undefined) testComponent.nodes.set(config.nodes); @@ -1535,12 +1543,12 @@ class TestTreeComponent { ]); values = signal([]); disabled = signal(false); - orientation = signal<'vertical' | 'horizontal'>('vertical'); + orientation = signal('vertical'); multi = signal(false); wrap = signal(true); softDisabled = signal(true); - focusMode = signal<'roving' | 'activedescendant'>('roving'); - selectionMode = signal<'explicit' | 'follow'>('explicit'); + focusMode = signal('roving'); + selectionMode = signal('explicit'); nav = signal(false); - currentType = signal('page' as 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'); + currentType = signal('page' as TreeCurrentType); } diff --git a/src/aria/tree/tree.ts b/src/aria/tree/tree.ts index 635810f95733..6c6bd7ababe6 100644 --- a/src/aria/tree/tree.ts +++ b/src/aria/tree/tree.ts @@ -30,9 +30,15 @@ import { TreePattern, DeferredContent, DeferredContentAware, + ListOrientation, + ListFocusMode, + ListSelectionMode, + TreeCurrentType, } from '@angular/aria/private'; import {ComboboxPopup} from '../combobox'; +export {ListOrientation, ListFocusMode, ListSelectionMode, TreeCurrentType}; + interface HasElement { element: Signal; } @@ -107,7 +113,7 @@ export class Tree { private readonly _unorderedItems = signal(new Set>()); /** Orientation of the tree. */ - readonly orientation = input<'vertical' | 'horizontal'>('vertical'); + readonly orientation = input('vertical'); /** Whether multi-selection is allowed. */ readonly multi = input(false, {transform: booleanAttribute}); @@ -116,10 +122,10 @@ export class Tree { readonly disabled = input(false, {transform: booleanAttribute}); /** The selection strategy used by the tree. */ - readonly selectionMode = input<'explicit' | 'follow'>('explicit'); + readonly selectionMode = input('explicit'); /** The focus strategy used by the tree. */ - readonly focusMode = input<'roving' | 'activedescendant'>('roving'); + readonly focusMode = input('roving'); /** Whether navigation wraps. */ readonly wrap = input(true, {transform: booleanAttribute}); @@ -140,9 +146,7 @@ export class Tree { readonly nav = input(false); /** The aria-current type. */ - readonly currentType = input<'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'>( - 'page', - ); + readonly currentType = input('page'); /** The UI pattern for the tree. */ readonly _pattern: TreePattern;