/** * @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.dev/license */ import { CDK_TREE_NODE_OUTLET_NODE, CdkNestedTreeNode, CdkTreeNode, CdkTreeNodeDef, } from '@angular/cdk/tree'; import { AfterContentInit, Directive, Input, OnDestroy, OnInit, booleanAttribute, numberAttribute, inject, HostAttributeToken, } from '@angular/core'; import {NoopTreeKeyManager, TreeKeyManagerItem, TreeKeyManagerStrategy} from '@angular/cdk/a11y'; /** * Determinte if argument TreeKeyManager is the NoopTreeKeyManager. This function is safe to use with SSR. */ function isNoopTreeKeyManager( keyManager: TreeKeyManagerStrategy, ): keyManager is NoopTreeKeyManager { return !!(keyManager as any)._isNoopTreeKeyManager; } /** * Wrapper for the CdkTree node with Material design styles. */ @Directive({ selector: 'mat-tree-node', exportAs: 'matTreeNode', outputs: ['activation', 'expandedChange'], providers: [{provide: CdkTreeNode, useExisting: MatTreeNode}], host: { 'class': 'mat-tree-node', '[attr.aria-expanded]': '_getAriaExpanded()', '[attr.aria-level]': 'level + 1', '[attr.aria-posinset]': '_getPositionInSet()', '[attr.aria-setsize]': '_getSetSize()', '(click)': '_focusItem()', '[tabindex]': '_getTabindexAttribute()', }, }) export class MatTreeNode extends CdkTreeNode implements OnInit, OnDestroy { /** * The tabindex of the tree node. * * @deprecated By default MatTreeNode manages focus using TreeKeyManager instead of tabIndex. * Recommend to avoid setting tabIndex directly to prevent TreeKeyManager form getting into * an unexpected state. Tabindex to be removed in a future version. * @breaking-change 21.0.0 Remove this attribute. */ @Input({ transform: (value: unknown) => (value == null ? 0 : numberAttribute(value)), alias: 'tabIndex', }) get tabIndexInputBinding(): number { return this._tabIndexInputBinding; } set tabIndexInputBinding(value: number) { // If the specified tabIndex value is null or undefined, fall back to the default value. this._tabIndexInputBinding = value; } private _tabIndexInputBinding: number; /** * The default tabindex of the tree node. * * @deprecated By default MatTreeNode manages focus using TreeKeyManager instead of tabIndex. * Recommend to avoid setting tabIndex directly to prevent TreeKeyManager form getting into * an unexpected state. Tabindex to be removed in a future version. * @breaking-change 21.0.0 Remove this attribute. */ defaultTabIndex = 0; protected _getTabindexAttribute() { if (isNoopTreeKeyManager(this._tree._keyManager)) { return this.tabIndexInputBinding; } return this._tabindex; } /** * Whether the component is disabled. * * @deprecated This is an alias for `isDisabled`. * @breaking-change 21.0.0 Remove this input */ @Input({transform: booleanAttribute}) get disabled(): boolean { return this.isDisabled; } set disabled(value: boolean) { this.isDisabled = value; } constructor(...args: unknown[]); constructor() { super(); const tabIndex = inject(new HostAttributeToken('tabindex'), {optional: true}); this.tabIndexInputBinding = Number(tabIndex) || this.defaultTabIndex; } // This is a workaround for https://github.com/angular/angular/issues/23091 // In aot mode, the lifecycle hooks from parent class are not called. override ngOnInit() { super.ngOnInit(); } override ngOnDestroy() { super.ngOnDestroy(); } } /** * Wrapper for the CdkTree node definition with Material design styles. * Captures the node's template and a when predicate that describes when this node should be used. */ @Directive({ selector: '[matTreeNodeDef]', inputs: [{name: 'when', alias: 'matTreeNodeDefWhen'}], providers: [{provide: CdkTreeNodeDef, useExisting: MatTreeNodeDef}], }) export class MatTreeNodeDef extends CdkTreeNodeDef { @Input('matTreeNode') data: T; } /** * Wrapper for the CdkTree nested node with Material design styles. */ @Directive({ selector: 'mat-nested-tree-node', exportAs: 'matNestedTreeNode', outputs: ['activation', 'expandedChange'], providers: [ {provide: CdkNestedTreeNode, useExisting: MatNestedTreeNode}, {provide: CdkTreeNode, useExisting: MatNestedTreeNode}, {provide: CDK_TREE_NODE_OUTLET_NODE, useExisting: MatNestedTreeNode}, ], host: { 'class': 'mat-nested-tree-node', }, }) export class MatNestedTreeNode extends CdkNestedTreeNode implements AfterContentInit, OnDestroy, OnInit { @Input('matNestedTreeNode') node: T; /** * Whether the node is disabled. * * @deprecated This is an alias for `isDisabled`. * @breaking-change 21.0.0 Remove this input */ @Input({transform: booleanAttribute}) get disabled(): boolean { return this.isDisabled; } set disabled(value: boolean) { this.isDisabled = value; } /** Tabindex of the node. */ @Input({ transform: (value: unknown) => (value == null ? 0 : numberAttribute(value)), }) get tabIndex(): number { return this.isDisabled ? -1 : this._tabIndex; } set tabIndex(value: number) { // If the specified tabIndex value is null or undefined, fall back to the default value. this._tabIndex = value; } private _tabIndex: number; // This is a workaround for https://github.com/angular/angular/issues/19145 // In aot mode, the lifecycle hooks from parent class are not called. // TODO(tinayuangao): Remove when the angular issue #19145 is fixed override ngOnInit() { super.ngOnInit(); } override ngAfterContentInit() { super.ngAfterContentInit(); } override ngOnDestroy() { super.ngOnDestroy(); } }