Skip to content

Commit

Permalink
fix(material/tree): add levelAccessor, childrenAccessor, TreeKeyManag…
Browse files Browse the repository at this point in the history
…er; a11y and docs improvements

Update multiple facets of Tree component. Add APIs to manage data models, improve existing behaviors, add keyboard functionality and update documentation.

Add APIs options to the Tree data model by introducing levelAccessor and childrenAccessor. See “Api Addition” for usage. Currently, Tree component use TreeControl to manage data model. When applied, add levelAccessor and childrenAccessor functions as alternatives to TreeControl.

Add TreeKeyManager, which provides keyboard functionality. Currently Tree component allows developers to manage focus by setting tabindex on each tree node. When applied, Tree manages its own focus using key manager pattern. Keyboard commands match [WAI ARIA Tree View Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/). See “Deprecated” for adopting changes to existing applications.

Correct the ARIA semantics of Tree and Tree node components.

Document updated APIs and behaviors. Refine documentation of existing APIs and behaviors.

Changes to Cdk Tree API also apply to Mat Tree API. See “Deprecated” for adopting changes to existing applications.

Accessibility:
 * add CdkTreeKeyManager to provide keyboard navigation for CdkTree and MatTree
 * Improve keyboard usability of CdkTreeNodeToggle.
 * Improve ARIA semantics of CdkTree, CdkTreeNode, Tree and TreeNode components
 * Fix miscellaneous accessibility issues in tree and cdk-tree examples
 * Add accessibility instructions to documentation

Documentation:
 * 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

API ADDITION: add CdkTree#childrenAccessor and CdkTree#levelAccessor
 * Add CdkTree#childrenAccessor. Given a data node, childrenAccessor determines the children of that node.
 * Add CdkTree#levelAccessor. Given a data node, levelAccessor determines the level of the node in the parent hierarchy.
 * CdkTreeNode#levelAcessor and CdkTreeNode#childrenAccessor replace CdkTreeNode#treeControl.

See “Deprecated” for updating apps using treeControl.

API ADDITION: control expanded state of tree nodes using isExpandable and isExpanded
 * Add CdkTreeNode#isExpandable, determines if argument tree node can be expanded or collapsed.
 * CdkTreeNode#isExpanded to specify the expanded state. Has no effect if node is not expandable.
 * Add NestedTreeControlOptions#isExpandable function, determines if argument tree node can be expanded or collapsed.

For trees using treeControl, recommend providing isExpandable if not already provided. See “Deprecated” for more information on updating applications.

API ADDITION: use CdkTree to manage expansion state
 * Add CdkTree#isExpanded method.
 * Add CdkTree#toggle, CdkTree#expand and CdkTree#collapse methods.
 * Add  CdkTree#toggleDescendants, CdkTree#expandDescendants, and CdkTree#collapseDescendants methods to CdkTree
 * Add CdkTree#expandAll and CdkTree#collapseAll methods
 * Add expandedChange Output to CdkTreeNode

API ADDITION: add injection token for tree-key-manager
 * Add TREE_KEY_MANAGER injection token. When provided, tree uses given key manager
 * TreeKeyManagerStrategy interface, which defines API contract of TREE_KEY_MANAGER

BEHAVIOR CHANGE: MatTree and CdkTree components respond to keyboard navigation.
 * CdkTree and MatTree respond to arrow keys, page up, page down, etc.; Keyboard commands match [WAI ARIA Tree View Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/).
 * Can no longer set the tabindex on MatTreeNode. See “Deprecated” for adopting existing applications.
 * Add TreeKeyManager to cdk/a11y

DEPRECATED: Tree controller deprecated. Use one of levelAccessor or childrenAccessor instead. To be removed in a future version.
 * BaseTreeControl, TreeControl, FlatTreeControl, and NestedTreeControl deprecated
 * CdkTree#treeControl deprecated. Provide one of CdkTree#levelAccessor or CdkTree#childrenAccessor instead.
 * MatTreeFlattener deprecated. Use MatTree#childrenAccessor and MatTreeNode#isExpandable instead.
 * MatTreeFlatDataSource deprecated. Use one of levelAccessor or childrenAccessor instead of TreeControl.

Note when upgrading: isExpandable works differently on Trees using treeControl than trees using childrenAccessor or levelAccessor. Nodes on trees that have a treeControl are expandable by default. Nodes on trees using childrenAccessor or levelAccessor are *not* expandable by default. Provide isExpandable to override default behavior.

DEPRECATED: Setting tabindex of tree nodes deprecated. By default, Tree ignores tabindex passed to tree nodes.
 * MatTreeNode#tabIndex deprecated. MatTreeNode ignores Input tabIndex and manages its own focus behavior.
 * MatTreeNode#defaultTabIndex deprecated. MatTreeNode ignores defaultTabIndex and manages its own focus behavior.
 * MatNestedTreeNode#tabIndex deprecated. MatTreeNode ignores Input defaultTabIndex and manages its own focus behavior.
 * LegacyTreeKeyManager and LEGACY_TREE_KEY_MANAGER_FACTORY_PROVIDER deprecated. Inject a TreeKeyManagerFactory to customize keyboard behavior.

Note when upgrading: an opt-out is available for keyboard functionality changes. Provide LEGACY_TREE_KEY_MANAGER_FACTORY_PROVIDER to opt-out of Tree managing its own focus. When provided, Tree does not manage it’s own focus and respects tabindex passed to TreeNode. When provided, have the same focus behavior as before this commit is applied.

Add Legacy Keyboard Interface demo, which shows usage of LEGACY_TREE_KEY_MANAGER_FACTORY_PROVIDER. Add Custom Key Manager, which shows usage of injecting a TreeKeyManagerStrategy

DEPRECATED: disabled renamed to isDisabled.
 * CdkTreeNode#disabled deprecated and alias to CdkTreeNode#isDisabled
  • Loading branch information
zarend committed Oct 27, 2023
1 parent 6719168 commit 93474c7
Show file tree
Hide file tree
Showing 66 changed files with 8,440 additions and 749 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
89 changes: 89 additions & 0 deletions src/cdk/a11y/key-manager/legacy-tree-key-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Subject} from 'rxjs';
import {
TREE_KEY_MANAGER,
TreeKeyManagerFactory,
TreeKeyManagerItem,
TreeKeyManagerStrategy,
} from './tree-key-manager';

/**
* @docs-private
*
* @deprecated LegacyTreeKeyManager deprecated. Use TreeKeyManager or inject a
* TreeKeyManagerStrategy instead. To be removed in a future version.
*
* @breaking-change 19.0.0
*/
// LegacyTreeKeyManager is a "noop" implementation of TreeKeyMangerStrategy. Methods are noops. Does
// not emit to streams.
//
// Used for applications built before TreeKeyManager to opt-out of TreeKeyManager and revert to
// legacy behavior.
export class LegacyTreeKeyManager<T extends TreeKeyManagerItem>
implements TreeKeyManagerStrategy<T>
{
readonly _isLegacyTreeKeyManager = true;

// Provide change as required by TreeKeyManagerStrategy. LegacyTreeKeyManager is a "noop"
// implementation that does not emit to streams.
readonly change = new Subject<T | null>();

onKeydown() {
// noop
}

getActiveItemIndex() {
// Always return null. LegacyTreeKeyManager is a "noop" implementation that does not maintain
// the active item.
return null;
}

getActiveItem() {
// Always return null. LegacyTreeKeyManager is a "noop" implementation that does not maintain
// the active item.
return null;
}

onInitialFocus() {
// noop
}

focusItem() {
// noop
}
}

/**
* @docs-private
*
* @deprecated LegacyTreeKeyManager deprecated. Use TreeKeyManager or inject a
* TreeKeyManagerStrategy instead. To be removed in a future version.
*
* @breaking-change 19.0.0
*/
export function LEGACY_TREE_KEY_MANAGER_FACTORY<
T extends TreeKeyManagerItem,
>(): TreeKeyManagerFactory<T> {
return () => new LegacyTreeKeyManager<T>();
}

/**
* @docs-private
*
* @deprecated LegacyTreeKeyManager deprecated. Use TreeKeyManager or inject a
* TreeKeyManagerStrategy instead. To be removed in a future version.
*
* @breaking-change 19.0.0
*/
export const LEGACY_TREE_KEY_MANAGER_FACTORY_PROVIDER = {
provide: TREE_KEY_MANAGER,
useFactory: LEGACY_TREE_KEY_MANAGER_FACTORY,
};
Loading

0 comments on commit 93474c7

Please sign in to comment.