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(material/tree): add levelAccessor, childrenAccessor, TreeKeyManag… …er; a11y and docs improvements #27626

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

zarend
Copy link
Contributor

@zarend zarend commented Aug 11, 2023

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. 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.
  • 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

@zarend zarend added the dev-app preview When applied, previews of the dev-app are deployed to Firebase label Aug 11, 2023
@angular-robot angular-robot bot added the detected: feature PR contains a feature commit label Aug 11, 2023
@zarend zarend added Accessibility This issue is related to accessibility (a11y) target: major This PR is targeted for the next major release area: cdk/a11y area: cdk/tree area: material/tree and removed detected: feature PR contains a feature commit labels Aug 11, 2023
@github-actions
Copy link

github-actions bot commented Aug 11, 2023

Deployed dev-app for e7a5b31 to: https://ng-dev-previews-comp--pr-angular-components-27626-gieu7oe0.web.app

Note: As new commits are pushed to this pull request, this link is updated after the preview is rebuilt.

@angular-robot angular-robot bot added detected: feature PR contains a feature commit area: docs Related to the documentation labels Aug 15, 2023
@angular-robot angular-robot bot added the area: build & ci Related the build and CI infrastructure of the project label Aug 29, 2023
@zarend zarend force-pushed the cdk-tree-revamp branch 4 times, most recently from d451c18 to aedb0b4 Compare September 22, 2023 23:07
@angular-robot angular-robot bot removed area: docs Related to the documentation area: build & ci Related the build and CI infrastructure of the project labels Sep 22, 2023
@zarend zarend force-pushed the cdk-tree-revamp branch 2 times, most recently from 38e48c7 to b608e1f Compare October 2, 2023 22:54
@angular-robot angular-robot bot added detected: breaking change PR contains a commit with a breaking change detected: deprecation PR contains a commit with a deprecation labels Oct 2, 2023
@zarend zarend changed the title feat(cdk/tree): Add levelAccessor and childrenAccessor to CDK tree API feat(material/tree): revamp tree data model, accessibility and documentation Oct 2, 2023
@zarend zarend changed the title feat(material/tree): revamp tree data model, accessibility and documentation feat(material/tree): revamp Tree data model, a11y and documentation Oct 2, 2023
@zarend zarend force-pushed the cdk-tree-revamp branch 2 times, most recently from f567a4f to b9a847f Compare October 4, 2023 20:54
@angular-robot angular-robot bot added the area: build & ci Related the build and CI infrastructure of the project label Oct 6, 2023
@zarend zarend changed the base branch from 1.2.x to main November 8, 2023 22:03
@zarend
Copy link
Contributor Author

zarend commented Nov 8, 2023

Since this was last reviewed: resolved merged conflicts.

Resolved conflicts by first rebasing with target branch then merging target branch into this branch.

This is planned to merge as a single commit. Additional commits will be squashed away.

@devversion devversion removed the request for review from a team November 9, 2023 10:36
* Node toggle to expand/collapse the node.
* Node toggle to expand and collapse the node.
*
* CdkTreeNodeToggle is intended only to be used on native button elements, elements with button role,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want, you could detect this and log a warning in devmode if its on an invalid element. In a future major release we could upgrade the warning to an error

* correct aria attribute values.
*
* The structure of this is that:
* - the outer index is the level
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make sense to me. What does outer index mean, the map key? and inner means the array index? That's what I would assume this means, but the types don't seem to line up with that meaning

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this structure was changed without updating the comment (will fix)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// item when the tree is focused. We set the tabindex to be `0` so that we can capture
// the focus event and redirect it. Otherwise, we unset it.
if (!this._keyManager.getActiveItem()) {
this._elementRef.nativeElement.setAttribute('tabindex', '0');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this bit of tabindex management live on the key manager? since it handles the rest of the tabindex management.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm working on adressing this by seeing if we can avoid setting the tabindex on the role="tree" element.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

src/cdk/tree/tree.ts Outdated Show resolved Hide resolved
src/cdk/tree/tree.ts Outdated Show resolved Hide resolved
Comment on lines 18 to 23
* @docs-private
*
* @deprecated LegacyTreeKeyManager deprecated. Use TreeKeyManager or inject a
* TreeKeyManagerStrategy instead. To be removed in a future version.
*
* @breaking-change 19.0.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is ok. The class is being introduced for backwards compatibility. We don't really want to consider it part of the public API, but also if someone does go and dig it up by looking through the code, we want it to be clear that its deprecated and they shouldn't use it

* an unexpected state. Tabindex to be removed in a future version.
* @breaking-change 19.0.0 Remove this attribute.
*/
defaultTabIndex = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this being introduced as deprecated instead of just private/internal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultTabIndex has been here all along. this PR doesn't actually introduce defaultTabIndex. That's because before it was coming from hasTabindex mixin. This change removes the mixin so we can mark the members as deprecated and change the logic of how tabindex is set – by default MatTreeNode ignores the tabindex Input provided and uses the key manager instead.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved

src/material/tree/node.ts Show resolved Hide resolved
@Attribute('tabindex') tabIndex: string,
) {
super(elementRef, tree);
this.tabIndex = Number(tabIndex) || 0;

this.tabIndex = Number(tabIndex) || this.defaultTabIndex;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? This will assign defaultTabIndex for tabindex="0". If we want to respect the users's explicit 0 setting, we could do: this.tabIndex = tabIndex ? Number(tabIndex) : this.defaultTabIndex

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intention is that the implementation is exactly the same as before, but it's refactored to not use the hasTabindex mixin.

export interface TreeKeyManagerOptions<T extends TreeKeyManagerItem> {
/**
* Sets the predicate function that determines which items should be skipped by the tree key
* manager. By default, disabled items are skipped.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels weird that the interface documentation talks about defaults, since it doesn't have any implementation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

src/cdk/a11y/key-manager/tree-key-manager.ts Outdated Show resolved Hide resolved
src/cdk/a11y/key-manager/tree-key-manager.ts Outdated Show resolved Hide resolved
src/cdk/a11y/key-manager/tree-key-manager.ts Outdated Show resolved Hide resolved
src/cdk/a11y/key-manager/tree-key-manager.ts Outdated Show resolved Hide resolved
private readonly _letterKeyStream = new Subject<string>();
private _typeaheadSubscription = Subscription.EMPTY;

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the rest of the preceding private variables have similar jsdocs?

src/cdk/a11y/key-manager/tree-key-manager.ts Show resolved Hide resolved
src/cdk/a11y/key-manager/tree-key-manager.ts Outdated Show resolved Hide resolved
src/cdk/a11y/key-manager/tree-key-manager.ts Outdated Show resolved Hide resolved
src/cdk/a11y/key-manager/tree-key-manager.ts Outdated Show resolved Hide resolved
Fix miscellaneous PR comments. Take a first pass to fix comments that
are easiest to fix. This commit message will be squashed away when
merging.
 * move type definitions in tree-key-manager.ts to a new file,
   tree-key-manager-strategy.ts
 * remove unnecessary comment
 * fix JSDoc comment syntax
 * remove isNotNullish
 * consolidate event handling code in CdkTreeNodeToggle
 * remove unnecessary type coerscion
 * simply logic for detecting TreeControlMissingError and MultipleTreeControlsError
 * adopt Input transform
 * remove unnecessary nesting in _updateActiveItemIndex
@angular-robot angular-robot bot removed area: docs Related to the documentation area: build & ci Related the build and CI infrastructure of the project labels Nov 10, 2023
Use OnPush change detection in Tree examples.
@angular-robot angular-robot bot added the area: docs Related to the documentation label Nov 10, 2023
zarend and others added 6 commits November 14, 2023 16:01
refactor(cdk/tree): simplify code in lifecycle hooks

Refactor CdkTree implementation to simplify code in lifecycle hooks.
Create a RenderingData type definition to simplify the Tree's rendering
pipeline.

The render pipeline looks like this. It has two entry points
ngAfterContentChecked and whener the datasource is switched.

1. ngAfterContentChecked OR datasource changed

2. call _subscribeToDataChanges

From here, the pipeline diverges. Tree subscribes to changes in the
datasource and expansion state. From there, the pipeline goes on two
paths: emit changes to expansion state, and render the changes from the
datasource.

Add RenderingData type to make it more clear what stages are in the
pipeline.

This commit message will be squashed away.
Fix miscelaneous focus management details in response to PR feedback.

 * omit tabindex attribute on role="tree" element
 * Remove TreeKeyManagerStrategy#onInitialFocus
 * Implement focus/unfocus pattern for TreeKeyManagerOption and use a
   binding to set tabindex on tree nodes
 * algin with https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#focusabilityofdisabledcontrols
 * Add tree-using-legacy-key-manager.spec.ts
Make miscelaneous fixes to documentation. Rename variables. Responding
to PR feedback.

 * remove comment about API contract of CdkTreeNodeToggle
 * rename CdkTree#_groups to CdkTree#_ariaSets
 * move documentation about default key manager configuration from
   TreeKeyManagerStrategy interface to TreeKeyManager class.
 * add JSDoc style comments for NoopTreeKeyManager
Add unit tests to cover the use of LegacyTreeKeyManager, which opts out
of updated focus management behavior.
@zarend
Copy link
Contributor Author

zarend commented Dec 6, 2023

Note to reviewers: see unit tests for opt-out behavior:

@@ -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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this sentence also be updated?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what needs updating here?

Comment on lines +60 to +61
`TreeKeyManager` manages the active option in a tree view. This is intended to be used with
components that correspond to a `role="tree"` pattern.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`TreeKeyManager` manages the active option in a tree view. This is intended to be used with
components that correspond to a `role="tree"` pattern.
`TreeKeyManager` manages the active option in a tree view. USe this key manager for
components that implement a `role="tree"` pattern.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


#### Basic usage

Any component that uses a `TreeKeyManager` will generally do three things:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Any component that uses a `TreeKeyManager` will generally do three things:
Any component that uses a `TreeKeyManager` should do three things:

nit: technical docs should already be present tense. Also replaced "generally do" with the stronger "should" (which I think is accurate here)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -150,6 +150,7 @@ describe('Key managers', () => {

keyManager.setActiveItem(0);
itemList.reset([new FakeFocusable('zero'), ...itemList.toArray()]);
itemList.notifyOnChanges();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this manually calling notifyOnChanges? IIRC, this wasn't meant to be part of the public API of QueryList; tests would generally want to cause change detection instead (though it's possible I'm missing something here)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does notifyOnChanges instead of change detection because there is no test fixture. This test file doesn't create any kind of test component or use TestBed at all. It's vanilla test, so change detection is not available.

skipPredicate?: (item: T) => boolean | undefined;
}

export class Typeahead<T extends TypeaheadItem> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsDoc for the responsibility of this class?

Comment on lines +577 to +581
return (
this.treeControl?.isExpanded(dataNode) ??
this._expansionModel?.isSelected(this._getExpansionKey(dataNode)) ??
false
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return (
this.treeControl?.isExpanded(dataNode) ??
this._expansionModel?.isSelected(this._getExpansionKey(dataNode)) ??
false
);
return !!(
this.treeControl?.isExpanded(dataNode) ||
this._expansionModel?.isSelected(this._getExpansionKey(dataNode)));

optional: I find the logical || with boolean coercion more readable in this situation

Comment on lines +718 to +751
if (levelAccessor) {
return combineLatest([isExpanded, this._flattenedNodes]).pipe(
map(([expanded, flattenedNodes]) => {
if (!expanded) {
return [];
}
const startIndex = flattenedNodes.findIndex(node => this._getExpansionKey(node) === key);
const level = levelAccessor(dataNode) + 1;
const results: T[] = [];

// Goes through flattened tree nodes in the `flattenedNodes` array, and get all direct
// descendants. The level of descendants of a tree node must be equal to the level of the
// given tree node + 1.
// If we reach a node whose level is equal to the level of the tree node, we hit a sibling.
// If we reach a node whose level is greater than the level of the tree node, we hit a
// sibling of an ancestor.
for (let i = startIndex + 1; i < flattenedNodes.length; i++) {
const currentLevel = levelAccessor(flattenedNodes[i]);
if (level > currentLevel) {
break;
}
if (level === currentLevel) {
results.push(flattenedNodes[i]);
}
}
return results;
}),
);
}
const childrenAccessor = this._getChildrenAccessor();
if (childrenAccessor) {
return coerceObservable(childrenAccessor(dataNode) ?? []);
}
throw getTreeControlMissingError();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be a bit easier to follow if there was a breakout function like getDirectChildrenByLevel

if (this.treeControl) {
return observableOf(this.treeControl.getDescendants(dataNode));
}
if (this.levelAccessor) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment here about breakout function like getDescendantsByLevel

*
* This also computes parent, level, and group data.
*/
private _convertData(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function name (and description) is pretty vague- could it be more specific?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That this method actual does is map data from the DataSource into RenderingData type.

It also needs to handle all the different cases for nodeType. The Tree infers the type of the nodes based on the data its DataSource gives it. Handling nodeType is part of why there are so many conditional cases here.


#### Connecting the tree to a data source

Similar to `mat-table`, data is provided to the tree through a `DataSource`. When the tree receives
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Similar to `mat-table`, data is provided to the tree through a `DataSource`. When the tree receives
Similar to `mat-table`, you can provide data to the tree through a `DataSource`. When the tree receives

nit: passive voice

@zarend
Copy link
Contributor Author

zarend commented Dec 12, 2023

#27626 (comment)

@jelbourn what's the intention you have in mind with coerceObservable? It doesn't do anything that's not trivial, so I don't see the value with making it reusable. If we did want to put something in coercion, then it would make sense if it also worked wiht promises ((x: T | Promise<T> | Observable<T>) => Observable<T>). I don't think I quite see how moving it would help giving that rxjs already has library functions for coercion, and I don't think cdk/coercion is intended to be in scope of this pr.

I have a suggestion. Delete coerceObservable because we don't really need it. Instead, repurpose the _getChildrenAccessor method to do the type coercion.

/// **note to reader: rename `_getChildrenAccessor` to `_getChildren()`
  /** Children accessor, used for compatibility between the old Tree and new Tree */
  _getChildren() {
    const getChildren = this.treeControl?.getChildren ?? this.childrenAccessor;
    const children = getChildren();
    return isObservable(children) ? children : observableOf(children);
  }

@zarend
Copy link
Contributor Author

zarend commented Dec 14, 2023

#27626 (comment)

@jelbourn what's the intention you have in mind with coerceObservable? It doesn't do anything that's not trivial, so I don't see the value with making it reusable. If we did want to put something in coercion, then it would make sense if it also worked wiht promises ((x: T | Promise<T> | Observable<T>) => Observable<T>). I don't think I quite see how moving it would help giving that rxjs already has library functions for coercion, and I don't think cdk/coercion is intended to be in scope of this pr.

I have a suggestion. Delete coerceObservable because we don't really need it. Instead, repurpose the _getChildrenAccessor method to do the type coercion.

/// **note to reader: rename `_getChildrenAccessor` to `_getChildren()`
  /** Children accessor, used for compatibility between the old Tree and new Tree */
  _getChildren() {
    const getChildren = this.treeControl?.getChildren ?? this.childrenAccessor;
    const children = getChildren();
    return isObservable(children) ? children : observableOf(children);
  }

I have a better idea, which is to create an adapter for interface TreeKeyManagerItem.

  getChildren<T extends TreeKeyManagerItem>(item: T): Observable<TreeKeyManagerItem[]> {
    const children = item.getChildren();
    return isObservable(children) ? children : observableOf(children);
  },
  isDisabled<T extends TreeKeyManagerItem>(item: T): boolean {
    return typeof item.isDisabled === 'boolean' ? item.isDisabled : !!item.isDisabled?.();
  },
  isExpanded<T extends TreeKeyManagerItem>(item: T): boolean {
    return typeof item.isExpanded === 'boolean' ? item.isExpanded : !!item.isExpanded?.();
  },

BobobUnicorn and others added 2 commits May 15, 2024 16:02
* fix(cdk/tree): fix errors from testing

* fix(cdk/tree): tests

* fix(cdk/tree): update api docs

* fix(cdk/a11y): allows disabled items to receive initial focus

* fix(cdk/tree): don't focus on click, corrected updating aria-sets

* fix(cdk/tree): update api goldens
Implement typeahead labels for Tree. Follow the established pattern that
Menu and List use.
 - implement CdkTreeNode#cdkTreeNodeTypeaheadLabel
 - implement CdkTreeNode#getLabel
@BobobUnicorn
Copy link
Collaborator

re: coerceObservable, I think it's still better off as its own private function and does essentially fit with the rest of cdk/coercion as they're all simple helper functions anyway

@BobobUnicorn
Copy link
Collaborator

This PR will be continued in #29062; I've tried to address the remaining commentary here in that PR, though unfortunately we don't have continuity with the comment chains.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Accessibility This issue is related to accessibility (a11y) area: cdk/a11y area: cdk/tree area: docs Related to the documentation area: material/tree detected: deprecation PR contains a commit with a deprecation dev-app preview When applied, previews of the dev-app are deployed to Firebase target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants