From e90ed304cfa828ce76f389473caa19ff9e39fcb1 Mon Sep 17 00:00:00 2001 From: Zach Arend Date: Fri, 8 Dec 2023 19:21:26 +0000 Subject: [PATCH] fix(cdk/tree): implement typeahead label Implement typeahead labels for Tree. Follow the established pattern that Menu and List use. - implement CdkTreeNode#cdkTreeNodeTypeaheadLabel - implement CdkTreeNode#getLabel --- src/cdk/tree/tree.spec.ts | 146 +++++++++++++++++- src/cdk/tree/tree.ts | 10 ++ .../cdk-tree-complex-example.html | 1 + .../cdk-tree-custom-key-manager-example.html | 1 + ...k-tree-flat-children-accessor-example.html | 1 + .../cdk-tree-flat-level-accessor-example.html | 1 + .../cdk-tree-flat/cdk-tree-flat-example.html | 2 +- ...tree-nested-children-accessor-example.html | 1 + ...dk-tree-nested-level-accessor-example.html | 1 + .../cdk-tree-nested-example.html | 1 + .../tree-dynamic/tree-dynamic-example.html | 3 +- .../tree-flat-overview-example.html | 3 +- .../tree-harness/tree-harness-example.html | 3 +- .../tree-loadmore/tree-loadmore-example.html | 2 +- .../tree-nested-overview-example.html | 2 +- tools/public_api_guard/cdk/tree.md | 5 +- 16 files changed, 174 insertions(+), 9 deletions(-) diff --git a/src/cdk/tree/tree.spec.ts b/src/cdk/tree/tree.spec.ts index 3e8e33d81daa..54eed53badac 100644 --- a/src/cdk/tree/tree.spec.ts +++ b/src/cdk/tree/tree.spec.ts @@ -5,7 +5,7 @@ * 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 {ComponentFixture, TestBed} from '@angular/core/testing'; +import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; import { Component, ErrorHandler, @@ -15,14 +15,16 @@ import { EventEmitter, ViewChildren, QueryList, + ElementRef, } from '@angular/core'; import {CollectionViewer, DataSource} from '@angular/cdk/collections'; import {Directionality, Direction} from '@angular/cdk/bidi'; -import {combineLatest, BehaviorSubject, Observable} from 'rxjs'; +import {combineLatest, BehaviorSubject, Observable, of} from 'rxjs'; import {map} from 'rxjs/operators'; import {CdkTreeModule, CdkTreeNodePadding} from './index'; import {CdkTree, CdkTreeNode} from './tree'; import {createKeyboardEvent} from '@angular/cdk/testing/testbed/fake-events'; +import {B, C, T} from '../keycodes'; /** * This is a cloned version of `tree.spec.ts` that contains all the same tests, @@ -1285,6 +1287,91 @@ describe('CdkTree', () => { }); }); }); + + describe('typeahead', () => { + describe('Tree with default configuration', () => { + let fixture: ComponentFixture; + let component: FlatTreeWithThreeNodes; + + beforeEach(() => { + configureCdkTreeTestingModule([FlatTreeWithThreeNodes]); + fixture = TestBed.createComponent(FlatTreeWithThreeNodes); + fixture.detectChanges(); + + component = fixture.componentInstance; + }); + describe(`when pressing 'b'`, () => { + beforeEach(fakeAsync(() => { + component.tree.nativeElement.dispatchEvent(createKeyboardEvent('keydown', B)); + fixture.detectChanges(); + tick(1000); + })); + + it('focuses banana', () => { + expect(document.activeElement) + .withContext('expecting banana to be focused') + .toBe(component.treeNodes.get(1)?.nativeElement!); + }); + }); + }); + + describe('Tree with cdkTreeNodeTypeaheadlabel Input binding', () => { + let fixture: ComponentFixture; + let component: TypeaheadLabelFlatTreeWithThreeNodes; + + beforeEach(() => { + configureCdkTreeTestingModule([TypeaheadLabelFlatTreeWithThreeNodes]); + fixture = TestBed.createComponent(TypeaheadLabelFlatTreeWithThreeNodes); + fixture.detectChanges(); + + component = fixture.componentInstance; + }); + + describe(`when pressing 'b'`, () => { + beforeEach(fakeAsync(() => { + component.tree.nativeElement.dispatchEvent(createKeyboardEvent('keydown', B)); + fixture.detectChanges(); + tick(1000); + })); + + it('focuses banana', fakeAsync(() => { + component.tree.nativeElement.dispatchEvent(createKeyboardEvent('keydown', B)); + fixture.detectChanges(); + tick(1000); + + expect(document.activeElement) + .withContext('expecting banana to be focused') + .toBe(component.treeNodes.get(1)?.nativeElement!); + })); + }); + + describe(`when pressing 'c'`, () => { + beforeEach(fakeAsync(() => { + component.tree.nativeElement.dispatchEvent(createKeyboardEvent('keydown', C)); + fixture.detectChanges(); + tick(1000); + })); + it('does not move focus', () => { + expect(document.activeElement) + .withContext('expecting document body to be focused') + .toBe(document.body); + }); + }); + + describe(`when pressing 't'`, () => { + beforeEach(fakeAsync(() => { + component.tree.nativeElement.dispatchEvent(createKeyboardEvent('keydown', T)); + fixture.detectChanges(); + tick(1000); + })); + it('focuses focuses cherry', () => { + expect(document.activeElement) + .withContext('expecting cherry to be focused') + .toBe(component.treeNodes.get(2)?.nativeElement!); + }); + }); + }); + }); }); export class TestData { @@ -1872,3 +1959,58 @@ class NestedCdkTreeAppWithTrackBy { @ViewChild(CdkTree) tree: CdkTree; } + +class MinimalTestData { + constructor( + public name: string, + public typeaheadLabel: string | null = null, + ) {} + children: MinimalTestData[] = []; +} + +@Component({ + template: ` + + + {{node.name}} + + + `, +}) +class TypeaheadLabelFlatTreeWithThreeNodes { + isExpandable = (node: MinimalTestData) => node.children.length > 0; + getChildren = (node: MinimalTestData) => node.children; + + dataSource = of([ + new MinimalTestData('apple'), + new MinimalTestData('banana'), + new MinimalTestData('cherry', 'typeahead'), + ]); + + @ViewChild('tree', {read: ElementRef}) tree: ElementRef; + @ViewChildren('node') treeNodes: QueryList>; +} + +@Component({ + template: ` + + + {{node.name}} + + + `, +}) +class FlatTreeWithThreeNodes { + isExpandable = (node: MinimalTestData) => node.children.length > 0; + getChildren = (node: MinimalTestData) => node.children; + + dataSource = of([ + new MinimalTestData('apple'), + new MinimalTestData('banana'), + new MinimalTestData('cherry'), + ]); + + @ViewChild('tree', {read: ElementRef}) tree: ElementRef; + @ViewChildren('node') treeNodes: QueryList>; +} diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts index 4631a4a16d2b..2adf529b7055 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -1151,6 +1151,16 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI */ @Input({transform: booleanAttribute}) isDisabled: boolean; + /** + * The text used to locate this item during typeahead. If not specified, the `textContent` will + * will be used. + */ + @Input('cdkTreeNodeTypeaheadLabel') typeaheadLabel: string | null; + + getLabel(): string { + return this.typeaheadLabel || this._elementRef.nativeElement.textContent?.trim() || ''; + } + /** This emits when the node has been programatically activated or activated by keyboard. */ @Output() readonly activation: EventEmitter = new EventEmitter(); diff --git a/src/components-examples/cdk/tree/cdk-tree-complex/cdk-tree-complex-example.html b/src/components-examples/cdk/tree/cdk-tree-complex/cdk-tree-complex-example.html index 3796809f4336..cd9a2dfdd240 100644 --- a/src/components-examples/cdk/tree/cdk-tree-complex/cdk-tree-complex-example.html +++ b/src/components-examples/cdk/tree/cdk-tree-complex/cdk-tree-complex-example.html @@ -10,6 +10,7 @@ diff --git a/src/components-examples/cdk/tree/cdk-tree-custom-key-manager/cdk-tree-custom-key-manager-example.html b/src/components-examples/cdk/tree/cdk-tree-custom-key-manager/cdk-tree-custom-key-manager-example.html index faa9ba7490e1..6805e5724198 100644 --- a/src/components-examples/cdk/tree/cdk-tree-custom-key-manager/cdk-tree-custom-key-manager-example.html +++ b/src/components-examples/cdk/tree/cdk-tree-custom-key-manager/cdk-tree-custom-key-manager-example.html @@ -11,6 +11,7 @@ {{node.item}} - +