Skip to content

Commit e65f49a

Browse files
committed
fix(cdk/tree): CdkTreeNodeToggle responds to Enter and Space keys
Implement keydown event for CdkTreeNodeToggle to perform that same action as click when receiving Enter or Space. Update examples to expand/collapse nodes when pressing enter. Fix a11y issue in demos where pressing Enter when focused on a tree node seems to not perform any action. Use CdkTreeNodeToggle to perform the action of expanding or collaping the node. Align with instructions in [APG Tree View Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/): "Enter: activates a node, i.e., performs its default action. For parent nodes, one possible default action is to open or close the node. In single-select trees where selection does not follow focus (see note below), the default action is typically to select the focused node."
1 parent e7a5b31 commit e65f49a

File tree

14 files changed

+33
-9
lines changed

14 files changed

+33
-9
lines changed

src/cdk/tree/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ ng_test_library(
3535
":tree",
3636
"//src/cdk/bidi",
3737
"//src/cdk/collections",
38+
"//src/cdk/keycodes",
39+
"//src/cdk/testing/testbed",
3840
"@npm//rxjs",
3941
],
4042
)

src/cdk/tree/toggle.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@
88

99
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
1010
import {Directive, Input} from '@angular/core';
11+
import {ENTER, SPACE} from '@angular/cdk/keycodes';
1112

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

1415
/**
1516
* Node toggle to expand/collapse the node.
17+
*
18+
* CdkTreeNodeToggle is intended only to be used on native button elements, elements with button role,
19+
* or elements with treeitem role.
1620
*/
1721
@Directive({
1822
selector: '[cdkTreeNodeToggle]',
1923
host: {
2024
'(click)': '_toggle($event)',
25+
'(keydown)': '_toggleOnEnterOrSpace($event)',
2126
'tabindex': '-1',
2227
},
2328
})
@@ -41,4 +46,11 @@ export class CdkTreeNodeToggle<T, K = T> {
4146

4247
event.stopPropagation();
4348
}
49+
50+
_toggleOnEnterOrSpace(event: KeyboardEvent) {
51+
if (event.keyCode === ENTER || event.keyCode === SPACE) {
52+
this._toggle(event);
53+
event.preventDefault();
54+
}
55+
}
4456
}

src/cdk/tree/tree-redesign.spec.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ import {
1616
ViewChildren,
1717
QueryList,
1818
} from '@angular/core';
19-
2019
import {CollectionViewer, DataSource} from '@angular/cdk/collections';
2120
import {Directionality, Direction} from '@angular/cdk/bidi';
2221
import {combineLatest, BehaviorSubject, Observable} from 'rxjs';
2322
import {map} from 'rxjs/operators';
24-
2523
import {CdkTreeModule, CdkTreeNodePadding} from './index';
2624
import {CdkTree, CdkTreeNode} from './tree';
25+
import {createKeyboardEvent} from '@angular/cdk/testing/testbed/fake-events';
26+
import {ENTER} from '@angular/cdk/keycodes';
2727

2828
/**
2929
* This is a cloned version of `tree.spec.ts` that contains all the same tests,
@@ -293,7 +293,9 @@ describe('CdkTree redesign', () => {
293293
[_, `${data[3].pizzaTopping} - ${data[3].pizzaCheese} + ${data[3].pizzaBase}`],
294294
);
295295

296-
(getNodes(treeElement)[2] as HTMLElement).click();
296+
(getNodes(treeElement)[2] as HTMLElement)!.dispatchEvent(
297+
createKeyboardEvent('keydown', ENTER),
298+
);
297299
fixture.detectChanges();
298300

299301
const expandedNodes = getExpandedNodes(

src/components-examples/cdk/tree/cdk-tree-flat-children-accessor/cdk-tree-flat-children-accessor-example.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</cdk-tree-node>
1111
<!-- This is the tree node template for expandable nodes -->
1212
<cdk-tree-node *cdkTreeNodeDef="let node; when: hasChild" cdkTreeNodePadding
13+
cdkTreeNodeToggle
1314
[style.display]="shouldRender(node) ? 'flex' : 'none'"
1415
[isDisabled]="!shouldRender(node)"
1516
[isExpandable]="true"

src/components-examples/cdk/tree/cdk-tree-flat-level-accessor/cdk-tree-flat-level-accessor-example.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</cdk-tree-node>
1111
<!-- This is the tree node template for expandable nodes -->
1212
<cdk-tree-node *cdkTreeNodeDef="let node; when: hasChild" cdkTreeNodePadding
13+
cdkTreeNodeToggle
1314
[style.display]="shouldRender(node) ? 'flex' : 'none'"
1415
[isDisabled]="!shouldRender(node)"
1516
[isExpandable]="node.expandable"

src/components-examples/cdk/tree/cdk-tree-flat/cdk-tree-flat-example.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</cdk-tree-node>
1111
<!-- This is the tree node template for expandable nodes -->
1212
<cdk-tree-node *cdkTreeNodeDef="let node; when: hasChild" cdkTreeNodePadding
13+
cdkTreeNodeToggle
1314
[style.display]="shouldRender(node) ? 'flex' : 'none'"
1415
[isDisabled]="!shouldRender(node)"
1516
(expandedChange)="node.isExpanded = $event"

src/components-examples/cdk/tree/cdk-tree-nested-level-accessor/cdk-tree-nested-level-accessor-example.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
</cdk-nested-tree-node>
77
<!-- This is the tree node template for expandable nodes -->
88
<cdk-nested-tree-node *cdkTreeNodeDef="let node; when: hasChild"
9+
cdkTreeNodeToggle
910
[isExpandable]="node.expandable"
1011
[isDisabled]="!shouldRender(node)"
1112
class="example-tree-node example-expandable">

src/components-examples/cdk/tree/cdk-tree-nested/cdk-tree-nested-example.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
</cdk-nested-tree-node>
99
<!-- This is the tree node template for expandable nodes -->
1010
<cdk-nested-tree-node #treeNode="cdkNestedTreeNode"
11+
cdkTreeNodeToggle
1112
*cdkTreeNodeDef="let node; when: hasChild"
1213
isExpandable
1314
class="example-tree-node">

src/components-examples/material/tree/tree-dynamic/tree-dynamic-example.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<button mat-icon-button disabled></button>
44
{{node.item}}
55
</mat-tree-node>
6-
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
6+
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding matTreeNodeToggle>
77
<button mat-icon-button
88
[attr.aria-label]="'Toggle ' + node.item" matTreeNodeToggle>
99
<mat-icon class="mat-icon-rtl-mirror">

src/components-examples/material/tree/tree-flat-overview/tree-flat-overview-example.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{{node.name}}
77
</mat-tree-node>
88
<!-- This is the tree node template for expandable nodes -->
9-
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding>
9+
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding matTreeNodeToggle>
1010
<button mat-icon-button matTreeNodeToggle
1111
[attr.aria-label]="'Toggle ' + node.name">
1212
<mat-icon class="mat-icon-rtl-mirror">

src/components-examples/material/tree/tree-harness/tree-harness-example.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{{node.name}}
77
</mat-tree-node>
88
<!-- This is the tree node template for expandable nodes -->
9-
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding>
9+
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding matTreeNodeToggle>
1010
<button mat-icon-button matTreeNodeToggle
1111
[attr.aria-label]="'Toggle ' + node.name">
1212
<mat-icon class="mat-icon-rtl-mirror">

src/components-examples/material/tree/tree-loadmore/tree-loadmore-example.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
</mat-tree-node>
77

88
<!-- expandable node -->
9-
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
9+
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding matTreeNodeToggle>
1010
<button mat-icon-button
1111
[attr.aria-label]="'Toggle ' + node.item"
1212
(click)="loadChildren(node)"
@@ -18,7 +18,7 @@
1818
{{node.item}}
1919
</mat-tree-node>
2020

21-
<mat-tree-node *matTreeNodeDef="let node; when: isLoadMore">
21+
<mat-tree-node *matTreeNodeDef="let node; when: isLoadMore" matTreeNodeToggle>
2222
<button mat-button (click)="loadMore(node.loadMoreParentItem)">
2323
Load more...
2424
</button>

src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
<!-- This is the tree node template for leaf nodes -->
33
<!-- There is inline padding applied to this node using styles.
44
This padding value depends on the mat-icon-button width. -->
5-
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
5+
<mat-tree-node *matTreeNodeDef="let node">
66
{{node.name}}
77
</mat-tree-node>
88
<!-- This is the tree node template for expandable nodes -->
99
<mat-nested-tree-node
1010
*matTreeNodeDef="let node; when: hasChild"
11+
matTreeNodeToggle
1112
isExpandable>
1213
<div class="mat-tree-node">
1314
<button mat-icon-button matTreeNodeToggle

tools/public_api_guard/cdk/tree.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ export class CdkTreeNodeToggle<T, K = T> {
274274
// (undocumented)
275275
_toggle(event: Event): void;
276276
// (undocumented)
277+
_toggleOnEnterOrSpace(event: KeyboardEvent): void;
278+
// (undocumented)
277279
protected _tree: CdkTree<T, K>;
278280
// (undocumented)
279281
protected _treeNode: CdkTreeNode<T, K>;

0 commit comments

Comments
 (0)