Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion goldens/aria/grid/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class Grid {
readonly tabIndex: _angular_core.InputSignalWithTransform<number | undefined, string | number | undefined>;
readonly textDirection: _angular_core.WritableSignal<_angular_cdk_bidi.Direction>;
// (undocumented)
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>;
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>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Grid, never>;
}
Expand Down
2 changes: 1 addition & 1 deletion goldens/aria/listbox/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class Listbox<V> implements OnDestroy {
readonly value: _angular_core.ModelSignal<V[]>;
readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
// (undocumented)
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: {}; }]>;
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: {}; }]>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Listbox<any>, never>;
}
Expand Down
3 changes: 2 additions & 1 deletion goldens/aria/simple-combobox/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ export class Combobox extends DeferredContentAware implements OnInit {
readonly _popup: _angular_core.WritableSignal<ComboboxPopup | undefined>;
_registerPopup(popup: ComboboxPopup): void;
readonly softDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly tabIndex: _angular_core.InputSignal<string | number | null | undefined>;
_unregisterPopup(): void;
readonly value: _angular_core.ModelSignal<string>;
// (undocumented)
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>;
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>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Combobox, never>;
}
Expand Down
2 changes: 1 addition & 1 deletion goldens/aria/tree/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class Tree<V> {
readonly value: _angular_core.ModelSignal<V[]>;
readonly wrap: _angular_core.InputSignalWithTransform<boolean, unknown>;
// (undocumented)
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: {}; }]>;
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: {}; }]>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<Tree<any>, never>;
}
Expand Down
2 changes: 1 addition & 1 deletion src/aria/grid/grid.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ describe('Grid directives', () => {
[softDisabled]="softDisabled()"
[enableSelection]="enableSelection()"
[selectionMode]="selectionMode()"
[tabIndex]="tabIndex()">
[tabindex]="tabIndex()">
@for (row of gridData(); track $index; let rIndex = $index) {
<tr ngGridRow [rowIndex]="row.rowIndex">
@for (cell of row.cells; track $index; let cIndex = $index) {
Expand Down
1 change: 1 addition & 0 deletions src/aria/grid/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export class Grid {

/** The tabindex of the grid. */
readonly tabIndex = input(undefined, {
alias: 'tabindex',
transform: (v: string | number | undefined) =>
v === undefined ? undefined : numberAttribute(v),
});
Expand Down
1 change: 1 addition & 0 deletions src/aria/listbox/listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export class Listbox<V> implements OnDestroy {

/** The tabindex of the listbox. */
readonly tabIndex = input(undefined, {
alias: 'tabindex',
transform: (v: string | number | undefined) =>
v === undefined ? undefined : numberAttribute(v),
});
Expand Down
3 changes: 2 additions & 1 deletion src/aria/private/simple-combobox/simple-combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ export class SimpleComboboxPattern {
manager.on('ArrowDown', () => this.inputs.expanded.set(true));

if (!this.isEditable()) {
manager.on(/^(Enter| )$/, () => this.inputs.expanded.set(true));
manager.on('Enter', () => this.inputs.expanded.set(true));
manager.on(' ', () => this.inputs.expanded.set(true));
}

return manager;
Expand Down
33 changes: 33 additions & 0 deletions src/aria/simple-combobox/simple-combobox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ describe('Combobox', () => {
fixture.detectChanges();

expect(inputElement.disabled).toBe(false);
expect(inputElement.getAttribute('disabled')).toBeNull();
expect(inputElement.getAttribute('aria-disabled')).toBe('true');
});

Expand All @@ -562,8 +563,38 @@ describe('Combobox', () => {
fixture.detectChanges();

expect(inputElement.disabled).toBe(true);
expect(inputElement.getAttribute('disabled')).toBe('');
expect(inputElement.getAttribute('aria-disabled')).toBe('true');
});

it('should respect user-defined tabindex when softDisabled is true', () => {
fixture.componentInstance.disabled.set(true);
fixture.componentInstance.tabIndex.set(0);
fixture.detectChanges();

expect(inputElement.getAttribute('tabindex')).toBe('0');
});

it('should respect user-defined tabindex when not disabled', () => {
fixture.componentInstance.tabIndex.set(0);
fixture.detectChanges();

expect(inputElement.getAttribute('tabindex')).toBe('0');
});

it('should default to tabindex 0 when not disabled', () => {
fixture.detectChanges();
expect(inputElement.getAttribute('tabindex')).toBe('0');
});

it('should force tabindex to -1 when hard-disabled, ignoring user-defined tabindex', () => {
fixture.componentInstance.disabled.set(true);
fixture.componentInstance.softDisabled.set(false);
fixture.componentInstance.tabIndex.set(0);
fixture.detectChanges();

expect(inputElement.getAttribute('tabindex')).toBe('-1');
});
});
});

Expand Down Expand Up @@ -1178,6 +1209,7 @@ describe('Combobox', () => {
[disabled]="disabled()"
[softDisabled]="softDisabled()"
[alwaysExpanded]="alwaysExpanded()"
[tabindex]="tabIndex()"
(focusout)="onBlur()"
/>

Expand All @@ -1203,6 +1235,7 @@ class ComboboxListboxExample {
disabled = signal(false);
softDisabled = signal(true);
alwaysExpanded = signal(false);
tabIndex = signal<number | undefined>(undefined);
popupExpanded = signal(false);
searchString = signal('');
value = signal<string[]>([]);
Expand Down
11 changes: 10 additions & 1 deletion src/aria/simple-combobox/simple-combobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
inject,
input,
model,
numberAttribute,
OnInit,
signal,
Renderer2,
Expand Down Expand Up @@ -51,7 +52,8 @@ import type {ComboboxPopup} from './simple-combobox-popup';
'[attr.aria-activedescendant]': '_pattern.activeDescendant()',
'[attr.aria-controls]': '_pattern.popupId()',
'[attr.aria-haspopup]': '_pattern.popupType()',
'[attr.tabindex]': 'disabled() && !softDisabled() ? -1 : null',
'[attr.tabindex]':
'disabled() && !softDisabled() ? -1 : (tabIndex() !== undefined ? tabIndex() : 0)',
'[attr.disabled]': 'disabled() && !softDisabled() ? "" : null',
'(keydown)': '_pattern.onKeydown($event)',
'(focusin)': '_pattern.onFocusin()',
Expand Down Expand Up @@ -81,6 +83,13 @@ export class Combobox extends DeferredContentAware implements OnInit {
/** Whether the combobox should always remain expanded. */
readonly alwaysExpanded = input(false, {transform: booleanAttribute});

/** The tabindex of the combobox. */
readonly tabIndex = input(undefined, {
alias: 'tabindex',
transform: (v: string | number | undefined) =>
Comment thread
tjshiu marked this conversation as resolved.
Outdated
v === undefined ? undefined : numberAttribute(v),
});

/** Whether the combobox is expanded. */
readonly expanded = model<boolean>(false);

Expand Down
1 change: 1 addition & 0 deletions src/aria/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export class Tree<V> {

/** The tabindex of the tree. */
readonly tabIndex = input(undefined, {
alias: 'tabindex',
transform: (v: string | number | undefined) =>
v === undefined ? undefined : numberAttribute(v),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[cdkConnectedOverlayDisableClose]="true">
<ng-template ngComboboxPopup [combobox]="combobox">
<div #listbox="ngListbox" ngListbox ngComboboxWidget class="example-listbox example-popup" focusMode="activedescendant"
[tabIndex]="-1" [(value)]="selectedOption" (click)="onCommit()" (keydown.enter)="onCommit()"
[tabindex]="-1" [(value)]="selectedOption" (click)="onCommit()" (keydown.enter)="onCommit()"
[activeDescendant]="listbox.activeDescendant()">
@for (option of options(); track option) {
<div class="example-option example-selectable example-stateful" ngOption [value]="option" [label]="option">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
<div class="example-no-results">No results found</div>
}

<div ngListbox ngComboboxWidget focusMode="activedescendant" [tabIndex]="-1" [(value)]="selectedOption" (click)="onCommit()" (keydown.enter)="onCommit()">
<div ngListbox ngComboboxWidget focusMode="activedescendant" [tabindex]="-1" [(value)]="selectedOption"
(click)="onCommit()" (keydown.enter)="onCommit()">
@for (country of countries(); track country) {
<div ngOption [value]="country" [label]="country" [disabled]="country === 'Brazil'">
<span class="example-option-label">{{country}}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<div class="example-no-results">No results found</div>
}

<div ngListbox ngComboboxWidget focusMode="activedescendant" [tabIndex]="-1" [(value)]="selectedOption">
<div ngListbox ngComboboxWidget focusMode="activedescendant" [tabindex]="-1" [(value)]="selectedOption">
@for (country of countries(); track country) {
<div ngOption [value]="country" [label]="country" [disabled]="country === 'Brazil'">
<span class="example-option-label">{{country}}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
<div class="example-no-results">No results found</div>
}

<div ngListbox ngComboboxWidget focusMode="activedescendant" [tabIndex]="-1" [(value)]="selectedOption" (click)="onCommit()" (keydown.enter)="onCommit()">
<div ngListbox ngComboboxWidget focusMode="activedescendant" [tabindex]="-1" [(value)]="selectedOption"
(click)="onCommit()" (keydown.enter)="onCommit()">
@for (country of countries(); track country) {
<div ngOption [value]="country" [label]="country" [disabled]="country === 'Brazil'">
<span class="example-option-label">{{country}}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<div class="example-no-results">No results found</div>
}

<div ngListbox ngComboboxWidget focusMode="activedescendant" [tabIndex]="-1" selectionMode="explicit"
<div ngListbox ngComboboxWidget focusMode="activedescendant" [tabindex]="-1" selectionMode="explicit"
[(value)]="selectedOption" (click)="onCommit()" (keydown.enter)="onCommit()">
@for (country of countries(); track country) {
<div ngOption [value]="country" [label]="country" [disabled]="country === 'Brazil'">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div class="example-combobox-container" ngCombobox #combobox="ngCombobox" [(expanded)]="popupExpanded" tabindex="0">
<div class="example-combobox-container" ngCombobox #combobox="ngCombobox" [(expanded)]="popupExpanded">
<div #origin class="example-combobox-input-container">
<input class="example-combobox-input example-dialog-input" placeholder="Select a state..." [value]="value()"
[readonly]="true" tabindex="-1" />
[readonly]="true" [tabindex]="-1" />
<span class="material-symbols-outlined example-icon example-arrow-icon">arrow_drop_down</span>
</div>

Expand All @@ -19,9 +19,9 @@
(keydown.escape)="onSearchEscape($event)" />
</div>
<ng-template ngComboboxPopup [combobox]="innerCombobox">
<div #listbox="ngListbox" ngListbox ngComboboxWidget class="example-listbox" focusMode="activedescendant" [tabIndex]="-1"
selectionMode="explicit" [(value)]="selectedStates" (click)="onCommit()" (keydown.enter)="onCommit()"
[activeDescendant]="listbox.activeDescendant()">
<div #listbox="ngListbox" ngListbox ngComboboxWidget class="example-listbox" focusMode="activedescendant"
tabindex="-1" selectionMode="explicit" [(value)]="selectedStates" (click)="onCommit()"
(keydown.enter)="onCommit()" [activeDescendant]="listbox.activeDescendant()">
@for (option of options(); track option) {
<div class="example-option example-selectable example-stateful" ngOption [value]="option"
[label]="option">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
[cdkConnectedOverlayOpen]="popupExpanded()" [cdkConnectedOverlayDisableClose]="true">
<ng-template ngComboboxPopup [combobox]="combobox">
<div class="example-popover">
<div #listbox="ngListbox" ngListbox ngComboboxWidget class="example-listbox" focusMode="activedescendant" [tabIndex]="-1"
<div #listbox="ngListbox" ngListbox ngComboboxWidget class="example-listbox" focusMode="activedescendant"
[tabindex]="-1"
[(value)]="selectedOption" (click)="onCommit()" (keydown.enter)="onCommit()"
[activeDescendant]="listbox.activeDescendant()">
@for (option of options(); track option) {
Expand Down
Loading
Loading