Skip to content

Commit 66a8ad3

Browse files
committed
implement code changes
1 parent 372f9df commit 66a8ad3

File tree

6 files changed

+65
-78
lines changed

6 files changed

+65
-78
lines changed

src/cdk/a11y/key-manager/noop-tree-key-manager.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ export class NoopTreeKeyManager<T extends TreeKeyManagerItem> implements TreeKey
5050
return null;
5151
}
5252

53-
onInitialFocus() {
54-
// noop
55-
}
56-
5753
focusItem() {
5854
// noop
5955
}

src/cdk/a11y/key-manager/tree-key-manager-strategy.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ export interface TreeKeyManagerItem {
3939
* Focuses the item. This should provide some indication to the user that this item is focused.
4040
*/
4141
focus(): void;
42+
43+
/**
44+
* Unfocus the item. This should remove the focus state.
45+
*/
46+
unfocus(): void;
4247
}
4348

4449
/**
@@ -100,14 +105,6 @@ export interface TreeKeyManagerStrategy<T extends TreeKeyManagerItem> {
100105
/** The currently active item. */
101106
getActiveItem(): T | null;
102107

103-
/**
104-
* Called the first time the Tree component is focused. This method will only be called once over
105-
* the lifetime of the Tree component.
106-
*
107-
* Intended to be used to focus the first item in the tree.
108-
*/
109-
onInitialFocus(): void;
110-
111108
/**
112109
* Focus the provided item by index.
113110
*

src/cdk/a11y/key-manager/tree-key-manager.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,23 +67,46 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
6767

6868
private _items: T[] = [];
6969

70+
private _hasInitialFocused = false;
71+
72+
private _initialFocus() {
73+
if (this._hasInitialFocused) {
74+
return;
75+
}
76+
77+
if (!this._items.length) {
78+
return;
79+
}
80+
81+
// TODO(zarend): align with WAI ARIA keyboard interface guide
82+
console.log('initial focus', this._items.length);
83+
this._focusFirstItem();
84+
85+
this._hasInitialFocused = true;
86+
}
87+
7088
constructor(items: Observable<T[]> | QueryList<T> | T[], config: TreeKeyManagerOptions<T>) {
7189
// We allow for the items to be an array or Observable because, in some cases, the consumer may
7290
// not have access to a QueryList of the items they want to manage (e.g. when the
7391
// items aren't being collected via `ViewChildren` or `ContentChildren`).
7492
if (items instanceof QueryList) {
7593
this._items = items.toArray();
7694
items.changes.subscribe((newItems: QueryList<T>) => {
95+
console.log('items changes', newItems.length);
7796
this._items = newItems.toArray();
7897
this._updateActiveItemIndex(this._items);
98+
this._initialFocus();
7999
});
80100
} else if (isObservable(items)) {
81101
items.subscribe(newItems => {
102+
console.log('items changes', newItems.length);
82103
this._items = newItems;
83104
this._updateActiveItemIndex(newItems);
105+
this._initialFocus();
84106
});
85107
} else {
86108
this._items = items;
109+
this._initialFocus();
87110
}
88111

89112
if (typeof config.activationFollowsFocus === 'boolean') {
@@ -188,14 +211,6 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
188211
return this._activeItem;
189212
}
190213

191-
/**
192-
* Focus the initial element; this is intended to be called when the tree is focused for
193-
* the first time.
194-
*/
195-
onInitialFocus(): void {
196-
this._focusFirstItem();
197-
}
198-
199214
/** Focus the first available item. */
200215
private _focusFirstItem(): void {
201216
this.focusItem(this._findNextAvailableItemIndex(-1));
@@ -245,14 +260,18 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
245260
return;
246261
}
247262

263+
const previousActiveItem = this._activeItem;
248264
this._activeItem = activeItem ?? null;
249265
this._activeItemIndex = index;
250266

267+
this._activeItem?.focus();
268+
previousActiveItem?.unfocus();
269+
251270
if (options.emitChangeEvent) {
252271
// Emit to `change` stream as required by TreeKeyManagerStrategy interface.
253272
this.change.next(this._activeItem);
254273
}
255-
this._activeItem?.focus();
274+
256275
if (this._activationFollowsFocus) {
257276
this._activateCurrentItem();
258277
}

src/cdk/tree/tree.ts

Lines changed: 9 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ type RenderingData<T> =
110110
'class': 'cdk-tree',
111111
'role': 'tree',
112112
'(keydown)': '_sendKeydownToKeyManager($event)',
113-
'(focus)': '_focusInitialTreeItem()',
114113
},
115114
encapsulation: ViewEncapsulation.None,
116115

@@ -319,23 +318,6 @@ export class CdkTree<T, K = T>
319318
}
320319
}
321320

322-
/**
323-
* Sets the tabIndex on the host element.
324-
*
325-
* NB: we don't set this as a host binding since children being activated
326-
* (e.g. on user click) doesn't trigger this component's change detection.
327-
*/
328-
_setTabIndex() {
329-
// If the `TreeKeyManager` has no active item, then we know that we need to focus the initial
330-
// item when the tree is focused. We set the tabindex to be `0` so that we can capture
331-
// the focus event and redirect it. Otherwise, we unset it.
332-
if (!this._keyManager.getActiveItem()) {
333-
this._elementRef.nativeElement.setAttribute('tabindex', '0');
334-
} else {
335-
this._elementRef.nativeElement.removeAttribute('tabindex');
336-
}
337-
}
338-
339321
/**
340322
* Switch to the provided data source by resetting the data and unsubscribing from the current
341323
* render change subscription if one exists. If the data source is null, interpret this by
@@ -474,18 +456,6 @@ export class CdkTree<T, K = T>
474456
};
475457

476458
this._keyManager = this._keyManagerFactory(items, keyManagerOptions);
477-
478-
this._keyManager.change
479-
.pipe(startWith(null), pairwise(), takeUntil(this._onDestroy))
480-
.subscribe(([prev, next]) => {
481-
prev?._setTabUnfocusable();
482-
next?._setTabFocusable();
483-
});
484-
485-
this._keyManager.change.pipe(startWith(null), takeUntil(this._onDestroy)).subscribe(() => {
486-
// refresh the tabindex when the active item changes.
487-
this._setTabIndex();
488-
});
489459
}
490460

491461
private _initializeDataDiffer() {
@@ -864,14 +834,6 @@ export class CdkTree<T, K = T>
864834
this._keyManager.onKeydown(event);
865835
}
866836

867-
/** `focus` event handler; this focuses the initial item if there isn't already one available. */
868-
_focusInitialTreeItem() {
869-
if (this._keyManager.getActiveItem()) {
870-
return;
871-
}
872-
this._keyManager.onInitialFocus();
873-
}
874-
875837
/** Gets all nested descendants of a given node. */
876838
private _getDescendants(dataNode: T): Observable<T[]> {
877839
if (this.treeControl) {
@@ -1140,13 +1102,15 @@ export class CdkTree<T, K = T>
11401102
'[attr.aria-level]': 'level + 1',
11411103
'[attr.aria-posinset]': '_getPositionInSet()',
11421104
'[attr.aria-setsize]': '_getSetSize()',
1143-
'tabindex': '-1',
1105+
'[tabindex]': '_tabindex',
11441106
'role': 'treeitem',
11451107
'(click)': '_focusItem()',
11461108
'(focus)': '_focusItem()',
11471109
},
11481110
})
11491111
export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerItem {
1112+
protected _tabindex: number | null = -1;
1113+
11501114
_changeDetectorRef = inject(ChangeDetectorRef);
11511115

11521116
/**
@@ -1317,9 +1281,15 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
13171281

13181282
/** Focuses this data node. Implemented for TreeKeyManagerItem. */
13191283
focus(): void {
1284+
this._tabindex = 0;
13201285
this._elementRef.nativeElement.focus();
13211286
}
13221287

1288+
/** Defocus this data node. */
1289+
unfocus(): void {
1290+
this._tabindex = -1;
1291+
}
1292+
13231293
/** Emits an activation event. Implemented for TreeKeyManagerItem. */
13241294
activate(): void {
13251295
if (this.isDisabled) {
@@ -1342,14 +1312,6 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
13421312
}
13431313
}
13441314

1345-
_setTabFocusable() {
1346-
this._elementRef.nativeElement.setAttribute('tabindex', '0');
1347-
}
1348-
1349-
_setTabUnfocusable() {
1350-
this._elementRef.nativeElement.setAttribute('tabindex', '-1');
1351-
}
1352-
13531315
_focusItem() {
13541316
if (this.isDisabled) {
13551317
return;

src/components-examples/cdk/tree/cdk-tree-custom-key-manager/cdk-tree-custom-key-manager-example.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,24 @@ export class VimTreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyM
111111

112112
private _items: T[] = [];
113113

114+
private _hasInitialFocused = false;
115+
116+
private _initialFocus() {
117+
if (this._hasInitialFocused) {
118+
return;
119+
}
120+
121+
if (!this._items.length) {
122+
return;
123+
}
124+
125+
// TODO(zarend): align with WAI ARIA keyboard interface guide
126+
console.log('initial focus', this._items.length);
127+
this._focusFirstItem();
128+
129+
this._hasInitialFocused = true;
130+
}
131+
114132
// TreeKeyManagerOptions not implemented.
115133
constructor(items: Observable<T[]> | QueryList<T> | T[]) {
116134
// We allow for the items to be an array or Observable because, in some cases, the consumer may
@@ -121,14 +139,17 @@ export class VimTreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyM
121139
items.changes.subscribe((newItems: QueryList<T>) => {
122140
this._items = newItems.toArray();
123141
this._updateActiveItemIndex(this._items);
142+
this._initialFocus();
124143
});
125144
} else if (isObservable(items)) {
126145
items.subscribe(newItems => {
127146
this._items = newItems;
128147
this._updateActiveItemIndex(newItems);
148+
this._initialFocus();
129149
});
130150
} else {
131151
this._items = items;
152+
this._initialFocus();
132153
}
133154
}
134155

@@ -192,14 +213,6 @@ export class VimTreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyM
192213
return this._activeItem;
193214
}
194215

195-
/**
196-
* Focus the initial element; this is intended to be called when the tree is focused for
197-
* the first time.
198-
*/
199-
onInitialFocus(): void {
200-
this._focusFirstItem();
201-
}
202-
203216
/**
204217
* Focus the provided item by index.
205218
* @param index The index of the item to focus.

src/material/tree/node.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function isNoopTreeKeyManager<T extends TreeKeyManagerItem>(
5151
'[attr.aria-posinset]': '_getPositionInSet()',
5252
'[attr.aria-setsize]': '_getSetSize()',
5353
'(click)': '_focusItem()',
54-
'tabindex': '_getTabindexAttribute()',
54+
'[tabindex]': '_getTabindexAttribute()',
5555
},
5656
})
5757
export class MatTreeNode<T, K = T> extends CdkTreeNode<T, K> implements OnInit, OnDestroy {
@@ -90,7 +90,7 @@ export class MatTreeNode<T, K = T> extends CdkTreeNode<T, K> implements OnInit,
9090
if (isNoopTreeKeyManager(this._tree._keyManager)) {
9191
return this.tabIndex;
9292
}
93-
return -1;
93+
return this._tabindex;
9494
}
9595

9696
/**

0 commit comments

Comments
 (0)