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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- Tree: Added `toggleNodeOnClick` property that determines whether clicking over a node will change its expanded state or not. Defaults to `false`.

### Changed
- Improved WAI-ARIA compliance for Avatar, Badge and Combo components [#1007](https://github.com/IgniteUI/igniteui-webcomponents/pull/1007)

Expand Down
25 changes: 20 additions & 5 deletions src/components/tree/tree-item.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LitElement, html } from 'lit';
import { LitElement, html, nothing } from 'lit';
import {
property,
query,
Expand Down Expand Up @@ -238,7 +238,7 @@ export default class IgcTreeItemComponent extends LitElement {
this.setAttribute('role', 'treeitem');
this.addEventListener('blur', this.onBlur);
this.addEventListener('focus', this.onFocus);
this.addEventListener('pointerdown', this.pointerDown);
this.addEventListener('click', this.itemClick);
this.activeChange();
// if the item is not added/moved runtime
if (this.init) {
Expand Down Expand Up @@ -291,12 +291,19 @@ export default class IgcTreeItemComponent extends LitElement {
return this.parent?.path ? [...this.parent.path, this] : [this];
}

private pointerDown(event: MouseEvent): void {
private itemClick(event: MouseEvent): void {
event.stopPropagation();
if (this.disabled) {
return;
}
this.tabIndex = 0;
if (this.tree?.toggleNodeOnClick && event.button === 0) {
if (this.expanded) {
this.collapseWithEvent();
} else {
this.expandWithEvent();
}
}
this.navService?.setFocusedAndActiveItem(this, true, false);
}

Expand All @@ -313,6 +320,9 @@ export default class IgcTreeItemComponent extends LitElement {

private selectorClick(event: MouseEvent): void {
event.preventDefault();
if (this.tree?.toggleNodeOnClick) {
event.stopPropagation();
}
if (event.shiftKey) {
this.selectionService?.selectMultipleItems(this);
return;
Expand Down Expand Up @@ -376,7 +386,7 @@ export default class IgcTreeItemComponent extends LitElement {
});

if (this.navService?.focusedItem === this) {
// called twice when clicking on already focused item with link (pointerDown handler)
// called twice when clicking on already focused item with link (itemClick handler)
this.setAttribute('tabindex', '0');
}
}
Expand Down Expand Up @@ -521,7 +531,12 @@ export default class IgcTreeItemComponent extends LitElement {
</slot>
`
: html`
<slot name="indicator" @click=${this.expandIndicatorClick}>
<slot
name="indicator"
@click=${this.tree?.toggleNodeOnClick
? nothing
: this.expandIndicatorClick}
>
${this.hasChildren
? html`
<igc-icon
Expand Down
92 changes: 90 additions & 2 deletions src/components/tree/tree.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ describe('Tree', () => {
expect(selectionPart).to.be.null;
});

tree.items[0].dispatchEvent(new PointerEvent('pointerdown'));
tree.items[0].dispatchEvent(new PointerEvent('click'));
await elementUpdated(tree);
expect(tree.navService.activeItem).to.equal(tree.items[0]);
expect(eventSpy).calledOnceWithExactly('igcActiveItem', {
Expand Down Expand Up @@ -772,6 +772,94 @@ describe('Tree', () => {
expect(eventSpy.called).to.be.false;
});

it('Should expand/collapse nodes when clicking over them if `toggleNodeOnClick` is set to `true`', async () => {
tree.toggleNodeOnClick = true;
topLevelItems[0].active = true;
await elementUpdated(tree);
expect(topLevelItems[0].expanded).to.be.false;

topLevelItems[0].dispatchEvent(new MouseEvent('click'));
await elementUpdated(tree);

TreeTestFunctions.verifyExpansionState(topLevelItems[0], true);
await waitUntil(() => eventSpy.calledWith('igcItemExpanded'));

// Should emit ing and ed events when item state is toggled through UI
const expandingArgs = {
detail: topLevelItems[0],
cancelable: true,
};
expect(eventSpy.callCount).to.equal(2);
expect(eventSpy.firstCall).calledWith('igcItemExpanding', expandingArgs);
expect(eventSpy.secondCall).calledWith('igcItemExpanded', {
detail: topLevelItems[0],
});

eventSpy.resetHistory();

topLevelItems[0].dispatchEvent(new MouseEvent('click'));
await elementUpdated(tree);

TreeTestFunctions.verifyExpansionState(topLevelItems[0], false);
await waitUntil(() => eventSpy.calledWith('igcItemCollapsed'));

// Should emit ing and ed events when item state is toggled through UI
const collapsingArgs = {
detail: topLevelItems[0],
cancelable: true,
};
expect(eventSpy.callCount).to.equal(2);
expect(eventSpy.firstCall).calledWith(
'igcItemCollapsing',
collapsingArgs
);
expect(eventSpy.secondCall).calledWith('igcItemCollapsed', {
detail: topLevelItems[0],
});
});

it('Should expand/collapse nodes only when clicking the expand indicator if `toggleNodeOnClick` is set to `false`', async () => {
expect(topLevelItems[0].expanded).to.be.false;

topLevelItems[0].dispatchEvent(new MouseEvent('click'));
await elementUpdated(tree);

TreeTestFunctions.verifyExpansionState(topLevelItems[0], false);
});

it('Should not expand/collapse nodes on right click', async () => {
tree.toggleNodeOnClick = true;
await elementUpdated(tree);

expect(topLevelItems[0].expanded).to.be.false;

topLevelItems[0].dispatchEvent(new MouseEvent('click', { button: 2 }));
await elementUpdated(tree);

TreeTestFunctions.verifyExpansionState(topLevelItems[0], false);
});

it('Should not be able to expand/collapse nodes when clicking over nodes` checkbox if `toggleNodeOnClick` is set to `true`', async () => {
tree.toggleNodeOnClick = true;
tree.selection = 'multiple';
await elementUpdated(tree);

expect(topLevelItems[0].expanded).to.be.false;

TreeTestFunctions.verifyItemSelection(topLevelItems[0], false);

const selectionPart = topLevelItems[0].shadowRoot!.querySelector(
PARTS.select
);
const cb = selectionPart?.children[0] as IgcCheckboxComponent;
cb?.dispatchEvent(new MouseEvent('click'));
await elementUpdated(tree);

TreeTestFunctions.verifyExpansionState(topLevelItems[0], false);
TreeTestFunctions.verifyItemSelection(topLevelItems[0], true);
expect(cb.checked).to.be.true;
});

it('Should toggle item state when item.toggle() is called', async () => {
expect(topLevelItems[1].expanded).to.be.true;

Expand Down Expand Up @@ -1068,7 +1156,7 @@ describe('Tree', () => {
expect(item11.active).to.be.false;
expect(eventSpy).not.to.be.called;

item11.dispatchEvent(new MouseEvent('pointerdown'));
item11.dispatchEvent(new MouseEvent('click'));
await elementUpdated(tree);

expect(item11.active).to.be.false;
Expand Down
7 changes: 7 additions & 0 deletions src/components/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ export default class IgcTreeComponent extends SizableMixin(
@property({ attribute: 'single-branch-expand', reflect: true, type: Boolean })
public singleBranchExpand = false;

/**
* Whether clicking over nodes will change their expanded state or not.
* @attr toggle-node-on-click
*/
@property({ attribute: 'toggle-node-on-click', reflect: true, type: Boolean })
public toggleNodeOnClick = false;

/**
* The selection state of the tree.
* @attr
Expand Down
22 changes: 20 additions & 2 deletions stories/tree.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ const metadata: Meta<IgcTreeComponent> = {
control: 'boolean',
table: { defaultValue: { summary: false } },
},
toggleNodeOnClick: {
type: 'boolean',
description:
'Whether clicking over nodes will change their expanded state or not.',
control: 'boolean',
table: { defaultValue: { summary: false } },
},
selection: {
type: '"none" | "multiple" | "cascade"',
description: 'The selection state of the tree.',
Expand All @@ -47,14 +54,20 @@ const metadata: Meta<IgcTreeComponent> = {
table: { defaultValue: { summary: 'none' } },
},
},
args: { singleBranchExpand: false, selection: 'none' },
args: {
singleBranchExpand: false,
toggleNodeOnClick: false,
selection: 'none',
},
};

export default metadata;

interface IgcTreeArgs {
/** Whether a single or multiple of a parent's child items can be expanded. */
singleBranchExpand: boolean;
/** Whether clicking over nodes will change their expanded state or not. */
toggleNodeOnClick: boolean;
/** The selection state of the tree. */
selection: 'none' | 'multiple' | 'cascade';
}
Expand Down Expand Up @@ -152,12 +165,17 @@ const log4 = () => {
);
};

const BasicTemplate = ({ singleBranchExpand, selection }: IgcTreeArgs) => {
const BasicTemplate = ({
singleBranchExpand,
selection,
toggleNodeOnClick,
}: IgcTreeArgs) => {
return html`
<igc-tree style="height: 250px"
id="tree"
.selection=${selection}
.singleBranchExpand=${singleBranchExpand}
.toggleNodeOnClick = ${toggleNodeOnClick}
>
<igc-tree-item expanded active selected label="Tree Node 1">
<igc-tree-item expanded id="parent" label="Tree Node 1.1">
Expand Down