Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cdk/tree): CdkTreeNodeToggle focuses node when toggling it #1

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
22 changes: 11 additions & 11 deletions src/cdk/a11y/key-manager/tree-key-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> {
* @param treeItem The item that was clicked by the user.
*/
onClick(treeItem: T) {
this._setActiveItem(treeItem);
this.setActiveItem(treeItem);
}

/** Index of the currently active item. */
Expand All @@ -284,9 +284,9 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> {
this._focusFirstItem();
}

private _setActiveItem(index: number): void;
private _setActiveItem(item: T): void;
private _setActiveItem(itemOrIndex: number | T) {
setActiveItem(index: number): void;
setActiveItem(item: T): void;
setActiveItem(itemOrIndex: number | T) {
let index =
typeof itemOrIndex === 'number'
? itemOrIndex
Expand Down Expand Up @@ -358,7 +358,7 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> {
!this._skipPredicateFn(item) &&
item.getLabel?.().toLocaleUpperCase().trim().indexOf(inputString) === 0
) {
this._setActiveItem(index);
this.setActiveItem(index);
break;
}
}
Expand All @@ -370,19 +370,19 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> {
//// Navigational methods

private _focusFirstItem() {
this._setActiveItem(this._findNextAvailableItemIndex(-1));
this.setActiveItem(this._findNextAvailableItemIndex(-1));
}

private _focusLastItem() {
this._setActiveItem(this._findPreviousAvailableItemIndex(this._items.length));
this.setActiveItem(this._findPreviousAvailableItemIndex(this._items.length));
}

private _focusPreviousItem() {
this._setActiveItem(this._findPreviousAvailableItemIndex(this._activeItemIndex));
this.setActiveItem(this._findPreviousAvailableItemIndex(this._activeItemIndex));
}

private _focusNextItem() {
this._setActiveItem(this._findNextAvailableItemIndex(this._activeItemIndex));
this.setActiveItem(this._findNextAvailableItemIndex(this._activeItemIndex));
}

private _findNextAvailableItemIndex(startingIndex: number) {
Expand Down Expand Up @@ -418,7 +418,7 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> {
if (!parent || this._skipPredicateFn(parent as T)) {
return;
}
this._setActiveItem(parent as T);
this.setActiveItem(parent as T);
}
}

Expand All @@ -440,7 +440,7 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> {
if (!firstChild) {
return;
}
this._setActiveItem(firstChild as T);
this.setActiveItem(firstChild as T);
});
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/cdk/tree/toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
import {Directive, Input} from '@angular/core';
import {ChangeDetectorRef, Directive, Input, inject} from '@angular/core';

import {CdkTree, CdkTreeNode} from './tree';

Expand All @@ -34,11 +34,17 @@ export class CdkTreeNodeToggle<T, K = T> {

constructor(protected _tree: CdkTree<T, K>, protected _treeNode: CdkTreeNode<T, K>) {}

// Toggle the expanded or collapsed state of this node.

// Focus this node with expanding or collapsing it. This ensures that the active node will always
// be visible when expanding and collapsing.
_toggle(event: Event): void {
this.recursive
? this._tree.toggleDescendants(this._treeNode.data)
: this._tree.toggle(this._treeNode.data);

this._tree._keyManager.setActiveItem(this._treeNode);

event.stopPropagation();
}
}
37 changes: 34 additions & 3 deletions src/cdk/tree/tree-redesign.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,26 @@ describe('CdkTree redesign', () => {
.toBe(0);
});

it('should focus a node when collapsing it', () => {
dataSource.clear();
const parent = dataSource.addData();
const child = dataSource.addChild(parent);
component.tree.expandAll();
fixture.detectChanges();

// focus the child node
getNodes(treeElement)[1].click();
fixture.detectChanges();

// collapse the parent node
getNodes(treeElement)[0].click();
fixture.detectChanges();

expect(getNodes(treeElement).map(x => x.getAttribute('tabindex')))
.withContext(`Expecting parent node to be focused since it was collapsed.`)
.toEqual(['0', '-1']);
});

it('should expand/collapse the node recursively', () => {
expect(dataSource.data.length).toBe(3);

Expand Down Expand Up @@ -1310,22 +1330,33 @@ class FakeDataSource extends DataSource<TestData> {
return child;
}

addData(level: number = 1) {
addData(level: number = 1): TestData {
const nextIndex = ++this.dataIndex;

let copiedData = this.data.slice();
copiedData.push(
new TestData(`topping_${nextIndex}`, `cheese_${nextIndex}`, `base_${nextIndex}`, level),
const newData = new TestData(
`topping_${nextIndex}`,
`cheese_${nextIndex}`,
`base_${nextIndex}`,
level,
);
copiedData.push(newData);

this.data = copiedData;

return newData;
}

getRecursiveData(nodes: TestData[] = this._dataChange.getValue()): TestData[] {
return [
...new Set(nodes.flatMap(parent => [parent, ...this.getRecursiveData(parent.children)])),
];
}

clear() {
this.data = [];
this.dataIndex = 0;
}
}

function getNodes(treeElement: Element): HTMLElement[] {
Expand Down
4 changes: 4 additions & 0 deletions tools/public_api_guard/cdk/a11y.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,10 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> {
onClick(treeItem: T): void;
onInitialFocus(): void;
onKeydown(event: KeyboardEvent): void;
// (undocumented)
setActiveItem(index: number): void;
// (undocumented)
setActiveItem(item: T): void;
readonly tabOut: Subject<void>;
}

Expand Down