Skip to content

Commit 60234c2

Browse files
authored
refactor(aria/combobox): allow adding tabIndex and default it to 0 (#33183)
* refactor(aria/combobox): allow adding tabIndex and default it to 0 * refactor(multiple): add tabindex alias for tabIndex input Adds the 'tabindex' alias to the 'tabIndex' input signal in Grid, Listbox, Combobox, and Tree components. This allows the input to be bound using the more idiomatic lowercase 'tabindex' attribute in templates. Related tests and examples have also been updated to use the new alias. * refactor(multiple): create a private util for the transform function
1 parent d1b075b commit 60234c2

31 files changed

Lines changed: 149 additions & 112 deletions

File tree

goldens/aria/grid/index.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class Grid {
2828
readonly tabIndex: _angular_core.InputSignalWithTransform<number | undefined, string | number | undefined>;
2929
readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>;
3030
// (undocumented)
31-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Grid, "[ngGrid]", ["ngGrid"], { "enableSelection": { "alias": "enableSelection"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "rowWrap": { "alias": "rowWrap"; "required": false; "isSignal": true; }; "colWrap": { "alias": "colWrap"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "tabIndex": { "alias": "tabIndex"; "required": false; "isSignal": true; }; }, {}, ["_rows"], never, true, never>;
31+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Grid, "[ngGrid]", ["ngGrid"], { "enableSelection": { "alias": "enableSelection"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "rowWrap": { "alias": "rowWrap"; "required": false; "isSignal": true; }; "colWrap": { "alias": "colWrap"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "tabIndex": { "alias": "tabindex"; "required": false; "isSignal": true; }; }, {}, ["_rows"], never, true, never>;
3232
// (undocumented)
3333
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Grid, never>;
3434
}

goldens/aria/listbox/index.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class Listbox<V> implements OnDestroy {
3636
readonly value: _angular_core.ModelSignal<V[]>;
3737
readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
3838
// (undocumented)
39-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Listbox<any>, "[ngListbox]", ["ngListbox"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "tabIndex": { "alias": "tabIndex"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>;
39+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Listbox<any>, "[ngListbox]", ["ngListbox"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "tabIndex": { "alias": "tabindex"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>;
4040
// (undocumented)
4141
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Listbox<any>, never>;
4242
}

goldens/aria/private/index.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,9 @@ export class SortedCollection<T extends HasElement> {
746746
unregister(item: T): void;
747747
}
748748

749+
// @public
750+
export function tabIndexTransform(v: string | number | undefined): number | undefined;
751+
749752
// @public
750753
export interface TabInputs extends Omit<ListNavigationItem, 'index'>, Omit<ExpansionItem, 'expandable' | 'expanded'> {
751754
tabList: SignalLike<TabListPattern>;

goldens/aria/simple-combobox/index.api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ export class Combobox extends DeferredContentAware implements OnInit {
2626
readonly _popup: _angular_core.WritableSignal<ComboboxPopup | undefined>;
2727
_registerPopup(popup: ComboboxPopup): void;
2828
readonly softDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
29+
readonly tabIndex: _angular_core.InputSignalWithTransform<number | undefined, string | number | undefined>;
2930
_unregisterPopup(): void;
3031
readonly value: _angular_core.ModelSignal<string>;
3132
// (undocumented)
32-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Combobox, "[ngCombobox]", ["ngCombobox"], { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "alwaysExpanded": { "alias": "alwaysExpanded"; "required": false; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "inlineSuggestion": { "alias": "inlineSuggestion"; "required": false; "isSignal": true; }; }, { "expanded": "expandedChange"; "value": "valueChange"; }, never, never, true, never>;
33+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Combobox, "[ngCombobox]", ["ngCombobox"], { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "alwaysExpanded": { "alias": "alwaysExpanded"; "required": false; "isSignal": true; }; "tabIndex": { "alias": "tabindex"; "required": false; "isSignal": true; }; "expanded": { "alias": "expanded"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "inlineSuggestion": { "alias": "inlineSuggestion"; "required": false; "isSignal": true; }; }, { "expanded": "expandedChange"; "value": "valueChange"; }, never, never, true, never>;
3334
// (undocumented)
3435
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Combobox, never>;
3536
}

goldens/aria/tree/index.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class Tree<V> {
3737
readonly value: _angular_core.ModelSignal<V[]>;
3838
readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
3939
// (undocumented)
40-
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Tree<any>, "[ngTree]", ["ngTree"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "tabIndex": { "alias": "tabIndex"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "nav": { "alias": "nav"; "required": false; "isSignal": true; }; "currentType": { "alias": "currentType"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>;
40+
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<Tree<any>, "[ngTree]", ["ngTree"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "multi": { "alias": "multi"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "focusMode": { "alias": "focusMode"; "required": false; "isSignal": true; }; "wrap": { "alias": "wrap"; "required": false; "isSignal": true; }; "softDisabled": { "alias": "softDisabled"; "required": false; "isSignal": true; }; "typeaheadDelay": { "alias": "typeaheadDelay"; "required": false; "isSignal": true; }; "tabIndex": { "alias": "tabindex"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "nav": { "alias": "nav"; "required": false; "isSignal": true; }; "currentType": { "alias": "currentType"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, [{ directive: typeof ComboboxPopup; inputs: {}; outputs: {}; }]>;
4141
// (undocumented)
4242
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Tree<any>, never>;
4343
}

src/aria/grid/grid.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,7 @@ describe('Grid directives', () => {
974974
[softDisabled]="softDisabled()"
975975
[enableSelection]="enableSelection()"
976976
[selectionMode]="selectionMode()"
977-
[tabIndex]="tabIndex()">
977+
[tabindex]="tabIndex()">
978978
@for (row of gridData(); track $index; let rIndex = $index) {
979979
<tr ngGridRow [rowIndex]="row.rowIndex">
980980
@for (cell of row.cells; track $index; let cIndex = $index) {

src/aria/grid/grid.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ import {
1515
ElementRef,
1616
inject,
1717
input,
18-
numberAttribute,
1918
Signal,
2019
} from '@angular/core';
2120
import {Directionality} from '@angular/cdk/bidi';
22-
import {GridPattern, GridCellPattern} from '../private';
21+
import {GridPattern, GridCellPattern, tabIndexTransform} from '../private';
2322
import {GRID_ROW} from './grid-tokens';
2423

2524
/**
@@ -123,8 +122,8 @@ export class Grid {
123122

124123
/** The tabindex of the grid. */
125124
readonly tabIndex = input(undefined, {
126-
transform: (v: string | number | undefined) =>
127-
v === undefined ? undefined : numberAttribute(v),
125+
alias: 'tabindex',
126+
transform: tabIndexTransform,
128127
});
129128

130129
/** The UI pattern for the grid. */

src/aria/listbox/listbox.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ import {
1616
inject,
1717
input,
1818
model,
19-
numberAttribute,
2019
OnDestroy,
2120
signal,
2221
Signal,
2322
untracked,
2423
} from '@angular/core';
2524
import {Directionality} from '@angular/cdk/bidi';
2625
import {_IdGenerator} from '@angular/cdk/a11y';
27-
import {ComboboxListboxPattern, ListboxPattern, SortedCollection} from '../private';
26+
import {
27+
ComboboxListboxPattern,
28+
ListboxPattern,
29+
SortedCollection,
30+
tabIndexTransform,
31+
} from '../private';
2832
import {ComboboxPopup} from '../combobox';
2933
import {Option} from './option';
3034
import {LISTBOX} from './tokens';
@@ -133,8 +137,8 @@ export class Listbox<V> implements OnDestroy {
133137

134138
/** The tabindex of the listbox. */
135139
readonly tabIndex = input(undefined, {
136-
transform: (v: string | number | undefined) =>
137-
v === undefined ? undefined : numberAttribute(v),
140+
alias: 'tabindex',
141+
transform: tabIndexTransform,
138142
});
139143

140144
/** The values of the currently selected items. */

src/aria/private/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ export * from './deferred-content';
2828
export * from './utils/collection';
2929
export * from './utils/element';
3030
export * from './utils/element-resolver';
31+
export * from './utils/transforms';
3132
export * from './simple-combobox/simple-combobox';

src/aria/private/simple-combobox/simple-combobox.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ export class SimpleComboboxPattern {
106106
manager.on('ArrowDown', () => this.inputs.expanded.set(true));
107107

108108
if (!this.isEditable()) {
109-
manager.on(/^(Enter| )$/, () => this.inputs.expanded.set(true));
109+
manager.on('Enter', () => this.inputs.expanded.set(true));
110+
manager.on(' ', () => this.inputs.expanded.set(true));
110111
}
111112

112113
return manager;

0 commit comments

Comments
 (0)