Skip to content

Commit

Permalink
feat(material/tree): revamp Tree data model, a11y and documentation
Browse files Browse the repository at this point in the history
Iterate on Tree component to improve its data model, accessibility and documentation.

Introduce levelAcessor and childrenAccessor as an alternative to TreeController. Given a data node, childrenAccessor determines the children of that node. Given a data node, levelAccessor determines level of the node. One of levelAccessor or childrenAccessor must be specified, not both.

Implement keyboard navigation in Tree component to align with [WAI ARIA Tree View Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/).

Make changes directly to Cdk tree API, which also apply to Mat tree APIs. See “Breaking Change” and “Deprecated” sections below for specifics on adopting changes.

Accessibility improvements
 * Implement keyboard navigation for CdkTree and MatTree
 * Implement keyboard usability for CdkTreeNodeToggle.
 * Improve ARIA semantics for CdkTree, CdkTreeNode, Tree and TreeNode components
 * Make accessibility fixes to tree and cdk-tree examples
 * Add accessibility instructions to documentation

Documentation updates
 * Add API and usage examples for TreeKeyManager
 * Update @angular/cdk/tree and @angular/material/tree to be more consistent
 * Update examples to use levelAccessor and childrenAccessor
 * Add example for (activation) on MatTreeNode and CdkTreeNode

BREAKING CHANGE: Tree component uses a levelAcessor or childrenAccessor as alternative to a controller.
 - CdkTree requires either a levelAccessor or childrenAccessor as Input. Cannot specify both.
 - CdkTree optionally takes a expansionKey as Input. expansionKey is a function that returns key of a given node. CdkTree uses key to determine expanded state.

BREAKING CHANGE: By default, tree nodes cannot be expanded and collapsed. Use isExpandable function to specify nodes that can be expanded and collapsed.
 - Add isExpandable function to NestedTreeControlOptions to determine if argument tree node can be expanded or collapsed.
 - Add isExpanded Input to CdkTreeNode to specify the expanded state. Has no effect if node is not expandable.

BREAKING CHANGE: CdkTree exposes methods and Output to manage expanded state.
 - Add isExpanded method to CdkTree
 - Add toggle, expand and collapse methods to CdkTree
 - Add  toggleDescendants, expandDescendants, and collapseDescendants methods to CdkTree
 - Add expandAll and collapseAll methods to CdkTree
 - Add expandedChange Output to CdkTreeNode

BREAKING CHANGE: CdkTree exposes method and output to manage active state.
 - Add activate method to CdkTreeNode
 - Add activation Output to CdkTreeNode

BREAKING CHANGE: Use isDisabled to set the disabled state of tree nodes.
 - Add isDisabled Input to CdkTreeNode. By default, nodes are not disabled.

BREAKING CHANGE: MatTree and CdkTree components responds to keyboard navigation.
 - CdkTree and MatTree respond to arrow keys, page up, page down, etc.; Keyboard commands align with [WAI ARIA Tree View Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/).
 - Add TreeKeyManager class
 - CdkTree consumes TreeKeyManager
 - Can no longer set the tabindex on MatTreeNode. MatTreeNode ignores the passed value of tabindex. MatTreeNode always has a tabindex attribute of “-1”.

BREAKING CHANGE: CdkTreeNodeToggle directive and component respond to keyboard navigation.
 - CdkTreeNodeToggle toggles expanded state on Enter or Space keypress

DEPRECATED: Tree controllers deprecated. Use one of levelAccessor or childrenAccessor instead. To be removed in a future version.
 * BaseTreeControl deprecated
 * TreeControl deprecated
 * FlatTreeControl deprecated
 * NestedTreeControl deprecated
 * treeControl Input on CdkTree deprecated in favor of using one of levelAccessor childrenAccessor Inputs.

DEPRECATED: Setting tabindex of tree nodes deprecated.
 * `@Input tabIndex` on `MatTreeNode` deprecated. MatTreeNode ignores Input tabIndex and manages its own focus behavior.
 * `@Input defaultTabIndex` on `MatTreeNode` deprecated. MatTreeNode ignores Input defaultTabIndex and manages its own focus behavior.
 * `@Input tabIndex` on `MatNestedTreeNode` deprecated. MatTreeNode ignores Input defaultTabIndex and manages its own focus behavior.

DEPRECATED: disabled Input renamed to isDiabled. disabled to be deleted in a future version.
 * disabled Input deprecated and alias to isDisabled
 * disabled Input deprecated and alias to isDiabled
  • Loading branch information
BobobUnicorn authored and zarend committed Oct 3, 2023
1 parent 47ac55f commit f567a4f
Show file tree
Hide file tree
Showing 50 changed files with 6,273 additions and 526 deletions.
60 changes: 59 additions & 1 deletion src/cdk/a11y/a11y.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Navigation through options can be made to wrap via the `withWrap` method
this.keyManager = new FocusKeyManager(...).withWrap();
```

#### Types of key managers
#### Types of list key managers

There are two varieties of `ListKeyManager`, `FocusKeyManager` and `ActiveDescendantKeyManager`.

Expand Down Expand Up @@ -55,6 +55,64 @@ interface Highlightable extends ListKeyManagerOption {

Each item must also have an ID bound to the listbox's or menu's `aria-activedescendant`.

### TreeKeyManager

`TreeKeyManager` manages the active option in a tree view. This is intended to be used with
components that correspond to a `role="tree"` pattern.

#### Basic usage

Any component that uses a `TreeKeyManager` will generally do three things:
* Create a `@ViewChildren` query for the tree items being managed.
* Initialize the `TreeKeyManager`, passing in the options.
* Forward keyboard events from the managed component to the `TreeKeyManager` via `onKeydown`.

Each tree item should implement the `TreeKeyManagerItem` interface:
```ts
interface TreeKeyManagerItem {
/** Whether the item is disabled. */
isDisabled?: (() => boolean) | boolean;

/** The user-facing label for this item. */
getLabel?(): string;

/** Perform the main action (i.e. selection) for this item. */
activate(): void;

/** Retrieves the parent for this item. This is `null` if there is no parent. */
getParent(): TreeKeyManagerItem | null;

/** Retrieves the children for this item. */
getChildren(): TreeKeyManagerItem[] | Observable<TreeKeyManagerItem[]>;

/** Determines if the item is currently expanded. */
isExpanded: (() => boolean) | boolean;

/** Collapses the item, hiding its children. */
collapse(): void;

/** Expands the item, showing its children. */
expand(): void;

/**
* Focuses the item. This should provide some indication to the user that this item is focused.
*/
focus(): void;
}
```

#### Focus management

The `TreeKeyManager` will handle focusing the appropriate item on keyboard interactions. However,
the component should call `onInitialFocus` when the component is focused for the first time (i.e.
when there is no active item).

`tabindex` should also be set by the component when the active item changes. This can be listened to
via the `change` property on the `TreeKeyManager`. In particular, the tree should only have a
`tabindex` set if there is no active item, and should not have a `tabindex` set if there is an
active item. Only the HTML node corresponding to the active item should have a `tabindex` set to
`0`, with all other items set to `-1`.


### FocusTrap

Expand Down
Loading

0 comments on commit f567a4f

Please sign in to comment.