Skip to content

Commit

Permalink
fix(cdk/listbox): make tabindex zoneless compatible (#29970)
Browse files Browse the repository at this point in the history
(cherry picked from commit 17ff5be)
  • Loading branch information
mmalerba committed Nov 7, 2024
1 parent ccbdb6d commit 1ea3ba3
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 34 deletions.
29 changes: 15 additions & 14 deletions src/cdk/a11y/key-manager/list-key-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@
* found in the LICENSE file at https://angular.io/license
*/

import {EffectRef, Injector, QueryList, Signal, effect, isSignal} from '@angular/core';
import {Subject, Subscription} from 'rxjs';
import {
UP_ARROW,
DOWN_ARROW,
END,
HOME,
LEFT_ARROW,
PAGE_DOWN,
PAGE_UP,
RIGHT_ARROW,
TAB,
UP_ARROW,
hasModifierKey,
HOME,
END,
PAGE_UP,
PAGE_DOWN,
} from '@angular/cdk/keycodes';
import {EffectRef, Injector, QueryList, Signal, effect, isSignal, signal} from '@angular/core';
import {Subject, Subscription} from 'rxjs';
import {Typeahead} from './typeahead';

/** This interface is for items that can be passed to a ListKeyManager. */
Expand All @@ -40,7 +40,7 @@ export type ListKeyManagerModifierKey = 'altKey' | 'ctrlKey' | 'metaKey' | 'shif
*/
export class ListKeyManager<T extends ListKeyManagerOption> {
private _activeItemIndex = -1;
private _activeItem: T | null = null;
private _activeItem = signal<T | null>(null);
private _wrap = false;
private _typeaheadSubscription = Subscription.EMPTY;
private _itemChangesSubscription?: Subscription;
Expand Down Expand Up @@ -204,11 +204,11 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
setActiveItem(item: T): void;

setActiveItem(item: any): void {
const previousActiveItem = this._activeItem;
const previousActiveItem = this._activeItem();

this.updateActiveItem(item);

if (this._activeItem !== previousActiveItem) {
if (this._activeItem() !== previousActiveItem) {
this.change.next(this._activeItemIndex);
}
}
Expand Down Expand Up @@ -317,7 +317,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {

/** The active item. */
get activeItem(): T | null {
return this._activeItem;
return this._activeItem();
}

/** Gets whether the user is currently typing into the manager using the typeahead feature. */
Expand Down Expand Up @@ -365,7 +365,7 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
const activeItem = itemArray[index];

// Explicitly check for `null` and `undefined` because other falsy values are valid.
this._activeItem = activeItem == null ? null : activeItem;
this._activeItem.set(activeItem == null ? null : activeItem);
this._activeItemIndex = index;
this._typeahead?.setCurrentSelectedItemIndex(index);
}
Expand Down Expand Up @@ -452,8 +452,9 @@ export class ListKeyManager<T extends ListKeyManagerOption> {
/** Callback for when the items have changed. */
private _itemsChanged(newItems: T[] | readonly T[]) {
this._typeahead?.setItems(newItems);
if (this._activeItem) {
const newIndex = newItems.indexOf(this._activeItem);
const activeItem = this._activeItem();
if (activeItem) {
const newIndex = newItems.indexOf(activeItem);

if (newIndex > -1 && newIndex !== this._activeItemIndex) {
this._activeItemIndex = newIndex;
Expand Down
37 changes: 25 additions & 12 deletions src/cdk/listbox/listbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
OnDestroy,
Output,
QueryList,
signal,
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {defer, fromEvent, merge, Observable, Subject} from 'rxjs';
Expand Down Expand Up @@ -119,24 +120,24 @@ export class CdkOption<T = unknown> implements ListKeyManagerOption, Highlightab
/** Whether this option is disabled. */
@Input({alias: 'cdkOptionDisabled', transform: booleanAttribute})
get disabled(): boolean {
return this.listbox.disabled || this._disabled;
return this.listbox.disabled || this._disabled();
}
set disabled(value: boolean) {
this._disabled = value;
this._disabled.set(value);
}
private _disabled: boolean = false;
private _disabled = signal(false);

/** The tabindex of the option when it is enabled. */
@Input('tabindex')
get enabledTabIndex() {
return this._enabledTabIndex === undefined
return this._enabledTabIndex() === undefined
? this.listbox.enabledTabIndex
: this._enabledTabIndex;
: this._enabledTabIndex();
}
set enabledTabIndex(value) {
this._enabledTabIndex = value;
this._enabledTabIndex.set(value);
}
private _enabledTabIndex?: number | null;
private _enabledTabIndex = signal<number | null | undefined>(undefined);

/** The option's host element */
readonly element: HTMLElement = inject(ElementRef).nativeElement;
Expand Down Expand Up @@ -269,12 +270,12 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
/** The tabindex to use when the listbox is enabled. */
@Input('tabindex')
get enabledTabIndex() {
return this._enabledTabIndex === undefined ? 0 : this._enabledTabIndex;
return this._enabledTabIndex() === undefined ? 0 : this._enabledTabIndex();
}
set enabledTabIndex(value) {
this._enabledTabIndex = value;
this._enabledTabIndex.set(value);
}
private _enabledTabIndex?: number | null;
private _enabledTabIndex = signal<number | null | undefined>(undefined);

/** The value selected in the listbox, represented as an array of option values. */
@Input('cdkListboxValue')
Expand Down Expand Up @@ -303,11 +304,23 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con

/** Whether the listbox is disabled. */
@Input({alias: 'cdkListboxDisabled', transform: booleanAttribute})
disabled: boolean = false;
get disabled() {
return this._disabled();
}
set disabled(value: boolean) {
this._disabled.set(value);
}
private _disabled = signal(false);

/** Whether the listbox will use active descendant or will move focus onto the options. */
@Input({alias: 'cdkListboxUseActiveDescendant', transform: booleanAttribute})
useActiveDescendant: boolean = false;
get useActiveDescendant() {
return this._useActiveDescendant();
}
set useActiveDescendant(value: boolean) {
this._useActiveDescendant.set(value);
}
private _useActiveDescendant = signal(false);

/** The orientation of the listbox. Only affects keyboard interaction, not visual layout. */
@Input('cdkListboxOrientation')
Expand Down
18 changes: 10 additions & 8 deletions tools/public_api_guard/cdk/listbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
deselect(option: CdkOption<T>): void;
deselectValue(value: T): void;
protected readonly destroyed: Subject<void>;
disabled: boolean;
get disabled(): boolean;
set disabled(value: boolean);
protected readonly element: HTMLElement;
get enabledTabIndex(): number | null;
set enabledTabIndex(value: number | null);
get enabledTabIndex(): number | null | undefined;
set enabledTabIndex(value: number | null | undefined);
focus(): void;
protected _getAriaActiveDescendant(): string | null | undefined;
protected _getTabIndex(): number | null;
protected _getTabIndex(): number | null | undefined;
protected _handleFocus(): void;
protected _handleFocusIn(): void;
protected _handleFocusOut(event: FocusEvent): void;
Expand Down Expand Up @@ -79,7 +80,8 @@ export class CdkListbox<T = unknown> implements AfterContentInit, OnDestroy, Con
toggleValue(value: T): void;
protected triggerOption(option: CdkOption<T> | null): void;
protected triggerRange(trigger: CdkOption<T> | null, from: number, to: number, on: boolean): void;
useActiveDescendant: boolean;
get useActiveDescendant(): boolean;
set useActiveDescendant(value: boolean);
get value(): readonly T[];
set value(value: readonly T[]);
readonly valueChange: Subject<ListboxValueChangeEvent<T>>;
Expand Down Expand Up @@ -108,11 +110,11 @@ export class CdkOption<T = unknown> implements ListKeyManagerOption, Highlightab
get disabled(): boolean;
set disabled(value: boolean);
readonly element: HTMLElement;
get enabledTabIndex(): number | null;
set enabledTabIndex(value: number | null);
get enabledTabIndex(): number | null | undefined;
set enabledTabIndex(value: number | null | undefined);
focus(): void;
getLabel(): string;
protected _getTabIndex(): number | null;
protected _getTabIndex(): number | null | undefined;
protected _handleFocus(): void;
get id(): string;
set id(value: string);
Expand Down

0 comments on commit 1ea3ba3

Please sign in to comment.