Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions src/cdk-experimental/ui-patterns/tree/tree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
55 changes: 22 additions & 33 deletions src/cdk-experimental/ui-patterns/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,23 +207,15 @@ export class TreePattern<V> {
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
Expand Down Expand Up @@ -260,6 +252,8 @@ export class TreePattern<V> {
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())
Expand All @@ -270,11 +264,6 @@ export class TreePattern<V> {
});
}

manager
.on(this.expandKey, () => this.expand())
.on(this.collapseKey, () => this.collapse())
.on(Modifier.Shift, '*', () => this.expandSiblings());

return manager;
});

Expand Down Expand Up @@ -403,38 +392,38 @@ export class TreePattern<V> {
}

/** Expands a tree item. */
expand(item?: TreeItemPattern<V>) {
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);
}
}

/** Expands all sibling tree items including itself. */
expandSiblings(item?: TreeItemPattern<V>) {
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<V>) {
item ??= this.activeItem();
collapse(opts?: SelectOptions) {
const item = this.activeItem();
if (!item || !this.listBehavior.isFocusable(item)) return;

if (item.expandable() && item.expanded()) {
item.expansion.close();
} 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);
}
}
}
Expand Down
Loading