diff --git a/src/cdk-experimental/ui-patterns/tree/tree.spec.ts b/src/cdk-experimental/ui-patterns/tree/tree.spec.ts index bc7c66d706e8..dc05ac5abfd2 100644 --- a/src/cdk-experimental/ui-patterns/tree/tree.spec.ts +++ b/src/cdk-experimental/ui-patterns/tree/tree.spec.ts @@ -1216,6 +1216,74 @@ describe('Tree Pattern', () => { tree.onPointerdown(createClickEvent(item0.element())); expect(item0.expanded()).toBe(false); }); + + describe('follows focus & single select', () => { + beforeEach(() => { + treeInputs.selectionMode.set('follow'); + treeInputs.multi.set(false); + }); + + it('should navigate and select the first child on expandKey if expanded and has children (vertical)', () => { + treeInputs.orientation.set('vertical'); + const {tree, allItems} = createTree(treeExample, treeInputs); + const item0 = getItemByValue(allItems(), 'Item 0'); + const item0_0 = getItemByValue(allItems(), 'Item 0-0'); + tree.listBehavior.goto(item0); + item0.expansion.open(); + + tree.onKeydown(right()); + expect(tree.activeItem()).toBe(item0_0); + expect(tree.inputs.value()).toEqual(['Item 0-0']); + }); + + it('should navigate and select the parent on collapseKey if collapsed (vertical)', () => { + treeInputs.orientation.set('vertical'); + const {tree, allItems} = createTree(treeExample, treeInputs); + const item0 = getItemByValue(allItems(), 'Item 0'); + const item0_0 = getItemByValue(allItems(), 'Item 0-0'); + item0.expansion.open(); + tree.listBehavior.goto(item0_0); + + tree.onKeydown(left()); + expect(tree.activeItem()).toBe(item0); + expect(tree.inputs.value()).toEqual(['Item 0']); + }); + }); + + describe('follows focus & multi select', () => { + beforeEach(() => { + treeInputs.selectionMode.set('follow'); + treeInputs.multi.set(true); + }); + + it('should navigate without select the first child on Ctrl + expandKey if expanded and has children (vertical)', () => { + treeInputs.orientation.set('vertical'); + const {tree, allItems} = createTree(treeExample, treeInputs); + const item0 = getItemByValue(allItems(), 'Item 0'); + const item0_0 = getItemByValue(allItems(), 'Item 0-0'); + tree.listBehavior.goto(item0); + item0.expansion.open(); + tree.inputs.value.set(['Item 1']); // pre-select something else + + tree.onKeydown(right({control: true})); + expect(tree.activeItem()).toBe(item0_0); + expect(tree.inputs.value()).toEqual(['Item 1']); + }); + + it('should navigate without select the parent on Ctrl + collapseKey if collapsed (vertical)', () => { + treeInputs.orientation.set('vertical'); + const {tree, allItems} = createTree(treeExample, treeInputs); + const item0 = getItemByValue(allItems(), 'Item 0'); + const item0_0 = getItemByValue(allItems(), 'Item 0-0'); + item0.expansion.open(); + tree.listBehavior.goto(item0_0); + tree.inputs.value.set(['Item 1']); // pre-select something else + + tree.onKeydown(left({control: true})); + expect(tree.activeItem()).toBe(item0); + expect(tree.inputs.value()).toEqual(['Item 1']); + }); + }); }); describe('#setDefaultState', () => { diff --git a/src/cdk-experimental/ui-patterns/tree/tree.ts b/src/cdk-experimental/ui-patterns/tree/tree.ts index 41acfbd7d8ad..b348c9c57741 100644 --- a/src/cdk-experimental/ui-patterns/tree/tree.ts +++ b/src/cdk-experimental/ui-patterns/tree/tree.ts @@ -207,23 +207,15 @@ export class TreePattern { const manager = new KeyboardEventManager(); const list = this.listBehavior; - if (!this.followFocus()) { - manager - .on(this.prevKey, () => list.prev()) - .on(this.nextKey, () => list.next()) - .on('Home', () => list.first()) - .on('End', () => list.last()) - .on(this.typeaheadRegexp, e => list.search(e.key)); - } - - if (this.followFocus()) { - manager - .on(this.prevKey, () => list.prev({selectOne: true})) - .on(this.nextKey, () => list.next({selectOne: true})) - .on('Home', () => list.first({selectOne: true})) - .on('End', () => list.last({selectOne: true})) - .on(this.typeaheadRegexp, e => list.search(e.key, {selectOne: true})); - } + manager + .on(this.prevKey, () => list.prev({selectOne: this.followFocus()})) + .on(this.nextKey, () => list.next({selectOne: this.followFocus()})) + .on('Home', () => list.first({selectOne: this.followFocus()})) + .on('End', () => list.last({selectOne: this.followFocus()})) + .on(this.typeaheadRegexp, e => list.search(e.key, {selectOne: this.followFocus()})) + .on(this.expandKey, () => this.expand({selectOne: this.followFocus()})) + .on(this.collapseKey, () => this.collapse({selectOne: this.followFocus()})) + .on(Modifier.Shift, '*', () => this.expandSiblings()); if (this.inputs.multi()) { manager @@ -260,6 +252,8 @@ export class TreePattern { manager .on([Modifier.Ctrl, Modifier.Meta], this.prevKey, () => list.prev()) .on([Modifier.Ctrl, Modifier.Meta], this.nextKey, () => list.next()) + .on([Modifier.Ctrl, Modifier.Meta], this.expandKey, () => this.expand()) + .on([Modifier.Ctrl, Modifier.Meta], this.collapseKey, () => this.collapse()) .on([Modifier.Ctrl, Modifier.Meta], ' ', () => list.toggle()) .on([Modifier.Ctrl, Modifier.Meta], 'Enter', () => list.toggle()) .on([Modifier.Ctrl, Modifier.Meta], 'Home', () => list.first()) @@ -270,11 +264,6 @@ export class TreePattern { }); } - manager - .on(this.expandKey, () => this.expand()) - .on(this.collapseKey, () => this.collapse()) - .on(Modifier.Shift, '*', () => this.expandSiblings()); - return manager; }); @@ -403,17 +392,17 @@ export class TreePattern { } /** Expands a tree item. */ - expand(item?: TreeItemPattern) { - item ??= this.activeItem(); + expand(opts?: SelectOptions) { + const item = this.activeItem(); if (!item || !this.listBehavior.isFocusable(item)) return; if (item.expandable() && !item.expanded()) { item.expansion.open(); - } else if (item.expanded() && item.children().length > 0) { - const firstChild = item.children()[0]; - if (this.listBehavior.isFocusable(firstChild)) { - this.listBehavior.goto(firstChild); - } + } else if ( + item.expanded() && + item.children().some(item => this.listBehavior.isFocusable(item)) + ) { + this.listBehavior.next(opts); } } @@ -421,12 +410,12 @@ export class TreePattern { expandSiblings(item?: TreeItemPattern) { item ??= this.activeItem(); const siblings = item?.parent()?.children(); - siblings?.forEach(item => this.expand(item)); + siblings?.forEach(item => item.expansion.open()); } /** Collapses a tree item. */ - collapse(item?: TreeItemPattern) { - item ??= this.activeItem(); + collapse(opts?: SelectOptions) { + const item = this.activeItem(); if (!item || !this.listBehavior.isFocusable(item)) return; if (item.expandable() && item.expanded()) { @@ -434,7 +423,7 @@ export class TreePattern { } else if (item.parent() && item.parent() !== this) { const parentItem = item.parent(); if (parentItem instanceof TreeItemPattern && this.listBehavior.isFocusable(parentItem)) { - this.listBehavior.goto(parentItem); + this.listBehavior.goto(parentItem, opts); } } }