pull/865/head
Cory LaViska 2022-08-17 16:22:03 -04:00
rodzic bcf2139fe7
commit 20e9e3c320
6 zmienionych plików z 51 dodań i 78 usunięć

Wyświetl plik

@ -234,7 +234,7 @@ Use the `lazy` attribute on a tree item to indicate that the content is not yet
If you want to disable this behavior after the first load, simply remove the `lazy` attribute and, on the next expand, the existing content will be shown instead.
```html preview
<sl-tree selection="leaf">
<sl-tree>
<sl-tree-item lazy>Available Trees</sl-tree-item>
</sl-tree>
@ -277,7 +277,7 @@ const App = () => {
};
return (
<SlTree selection="leaf">
<SlTree>
<SlTreeItem lazy={lazy} onSlLazyLoad={handleLazyLoad}>
Available Trees
{childItems.map(item => (
@ -291,12 +291,12 @@ const App = () => {
### Custom expanded/collapsed icons
Use the `expanded-icon` or `collapsed-icon` slots to change the expanded and collapsed tree element icons respectively.
Use the `expand-icon` and `collapse-icon` slots to change the expand and collapse icons, respectively.
```html preview
<sl-tree selection="leaf">
<sl-icon name="plus-square" slot="collapsed-icon"></sl-icon>
<sl-icon name="dash-square" slot="expanded-icon"></sl-icon>
<sl-tree>
<sl-icon name="plus-square" slot="collapse-icon"></sl-icon>
<sl-icon name="dash-square" slot="expand-icon"></sl-icon>
<sl-tree-item>
Deciduous
@ -332,8 +332,8 @@ import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree>
<SlIcon name="plus-square" slot="collapsed-icon"></SlIcon>
<SlIcon name="dash-square" slot="expanded-icon"></SlIcon>
<SlIcon name="plus-square" slot="collapse-icon"></SlIcon>
<SlIcon name="dash-square" slot="expand-icon"></SlIcon>
<SlTreeItem>
Deciduous

Wyświetl plik

@ -10,7 +10,9 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
## Next
- Improved single selection in `<sl-tree>` so the node expands/collapses when clicked
- Fixed a bug in `<sl-tree>` where dynamically changing slotted items wouldn't update the tree properly
- Improved single selection in `<sl-tree>` so nodes expand and collapse and receive selection when clicking on the label
- Renamed `expanded-icon` and `collapsed-icon` slots to `expand-icon` and `collapse-icon` in the experimental `<sl-tree>` and `<sl-tree-item>` components
- Improved RTL support for `<sl-image-comparer>`
- Refactored components to extend from `ShoelaceElement` to make `dir` and `lang` reactive properties in all components

Wyświetl plik

@ -49,19 +49,6 @@ describe('<sl-tree-item>', () => {
describe('when the user clicks the expand button', () => {
describe('and the item is collapsed', () => {
it('should expand the item', async () => {
// Arrange
const expandButton: HTMLElement = parentItem.shadowRoot!.querySelector('.tree-item__expand-button')!;
// Act
expandButton.click();
await parentItem.updateComplete;
// Assert
expect(parentItem).to.have.attribute('expanded');
expect(parentItem).to.have.attribute('aria-expanded', 'true');
});
it('should emit sl-expand and sl-after-expand events', async () => {
// Arrange
const expandSpy = sinon.spy();
@ -82,21 +69,6 @@ describe('<sl-tree-item>', () => {
});
describe('and the item is expanded', () => {
it('should collapse the item', async () => {
// Arrange
const expandButton: HTMLElement = parentItem.shadowRoot!.querySelector('.tree-item__expand-button')!;
parentItem.expanded = true;
await parentItem.updateComplete;
// Act
expandButton.click();
await parentItem.updateComplete;
// Assert
expect(parentItem).not.to.have.attribute('expanded');
expect(parentItem).to.have.attribute('aria-expanded', 'false');
});
it('should emit sl-collapse and sl-after-collapse events', async () => {
// Arrange
const collapseSpy = sinon.spy();

Wyświetl plik

@ -185,15 +185,6 @@ export default class SlTreeItem extends ShoelaceElement {
return !!parent && isTreeItem(parent);
}
handleToggleExpand(event: Event) {
event.preventDefault();
event.stopImmediatePropagation();
if (!this.disabled) {
this.expanded = !this.expanded;
}
}
handleChildrenSlotChange() {
this.loading = false;
this.isLeaf = this.getChildrenItems().length === 0;
@ -238,7 +229,6 @@ export default class SlTreeItem extends ShoelaceElement {
'tree-item__expand-button--visible': showExpandButton
})}
aria-hidden="true"
@click="${this.handleToggleExpand}"
>
${when(this.loading, () => html` <sl-spinner></sl-spinner> `)}
${when(

Wyświetl plik

@ -58,8 +58,8 @@ describe('<sl-tree>', () => {
beforeEach(async () => {
el = await fixture(html`
<sl-tree>
<div slot="expanded-icon"></div>
<div slot="collapsed-icon"></div>
<div slot="expand-icon"></div>
<div slot="collapse-icon"></div>
<sl-tree-item>Node 1</sl-tree-item>
<sl-tree-item>Node 2</sl-tree-item>
@ -76,8 +76,8 @@ describe('<sl-tree>', () => {
// Assert
treeItems.forEach(treeItem => {
expect(treeItem.querySelector('div[slot="expanded-icon"]')).to.be.ok;
expect(treeItem.querySelector('div[slot="collapsed-icon"]')).to.be.ok;
expect(treeItem.querySelector('div[slot="expand-icon"]')).to.be.ok;
expect(treeItem.querySelector('div[slot="collapse-icon"]')).to.be.ok;
});
});
});
@ -437,7 +437,7 @@ describe('<sl-tree>', () => {
await el.updateComplete;
// Assert
expect(node).not.to.have.attribute('selected');
expect(node).to.have.attribute('selected');
expect(node).to.have.attribute('expanded');
});
});

Wyświetl plik

@ -40,11 +40,11 @@ function syncCheckboxes(changedTreeItem: SlTreeItem) {
* @since 2.0
* @status experimental
*
* @event {{ selection: this.selectedItems }} sl-selection-change - Emitted when an item gets selected or deselected
* @event {{ selection: TreeItem[] }} sl-selection-change - Emitted when an item gets selected or deselected
*
* @slot - The default slot.
* @slot expanded-icon - The icon to show when the tree item is expanded.
* @slot collapsed-icon - The icon to show when the tree item is collapsed.
* @slot expand-icon - The icon to show when the tree item is expanded.
* @slot collapse-icon - The icon to show when the tree item is collapsed.
*
* @csspart base - The component's internal wrapper.
*
@ -58,9 +58,9 @@ function syncCheckboxes(changedTreeItem: SlTreeItem) {
export default class SlTree extends ShoelaceElement {
static styles = styles;
@query('slot') defaultSlot: HTMLSlotElement;
@query('slot[name=expanded-icon]') expandedIconSlot: HTMLSlotElement;
@query('slot[name=collapsed-icon]') collapsedIconSlot: HTMLSlotElement;
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
@query('slot[name=expand-icon]') expandedIconSlot: HTMLSlotElement;
@query('slot[name=collapse-icon]') collapsedIconSlot: HTMLSlotElement;
/** Specifies the selection behavior of the Tree */
@property() selection: 'single' | 'multiple' | 'leaf' = 'single';
@ -69,34 +69,35 @@ export default class SlTree extends ShoelaceElement {
// A collection of all the items in the tree, in the order they appear. The collection is live, meaning it is
// automatically updated when the underlying document is changed.
//
private treeItems: HTMLCollectionOf<SlTreeItem> = this.getElementsByTagName('sl-tree-item');
private treeItems: SlTreeItem[] = [];
private lastFocusedItem: SlTreeItem;
private readonly localize = new LocalizeController(this);
private mutationObserver: MutationObserver;
connectedCallback(): void {
async connectedCallback() {
super.connectedCallback();
this.setAttribute('role', 'tree');
this.setAttribute('tabindex', '0');
this.mutationObserver = new MutationObserver(this.handleTreeChanged);
this.addEventListener('focusin', this.handleFocusIn);
this.addEventListener('focusout', this.handleFocusOut);
await this.updateComplete;
this.mutationObserver = new MutationObserver(this.handleTreeChanged);
this.mutationObserver.observe(this, { childList: true, subtree: true });
}
disconnectedCallback(): void {
disconnectedCallback() {
super.disconnectedCallback();
this.mutationObserver.disconnect();
this.removeEventListener('focusin', this.handleFocusIn);
this.removeEventListener('focusout', this.handleFocusOut);
}
// Generates a clone of the expand icon element to use for each tree item
private getExpandButtonIcon(status: 'expanded' | 'collapsed') {
const slot = status === 'expanded' ? this.expandedIconSlot : this.collapsedIconSlot;
private getExpandButtonIcon(status: 'expand' | 'collapse') {
const slot = status === 'expand' ? this.expandedIconSlot : this.collapsedIconSlot;
const icon = slot.assignedElements({ flatten: true })[0] as HTMLElement;
// Clone it, remove ids, and slot it
@ -116,9 +117,9 @@ export default class SlTree extends ShoelaceElement {
private initTreeItem = (item: SlTreeItem) => {
item.selectable = this.selection === 'multiple';
['expanded', 'collapsed']
['expand', 'collapse']
.filter(status => !!this.querySelector(`[slot="${status}-icon"]`))
.forEach((status: 'expanded' | 'collapsed') => {
.forEach((status: 'expand' | 'collapse') => {
const existingIcon = item.querySelector(`[slot="${status}-icon"]`);
if (existingIcon === null) {
@ -133,12 +134,6 @@ export default class SlTree extends ShoelaceElement {
});
};
protected firstUpdated(): void {
[...this.treeItems].forEach(this.initTreeItem);
this.mutationObserver.observe(this, { childList: true, subtree: true });
}
handleTreeChanged = (mutations: MutationRecord[]) => {
for (const mutation of mutations) {
const addedNodes: SlTreeItem[] = [...mutation.addedNodes].filter(isTreeItem) as SlTreeItem[];
@ -289,12 +284,26 @@ export default class SlTree extends ShoelaceElement {
handleClick(event: Event) {
const target = event.target as HTMLElement;
const treeItem = target.closest('sl-tree-item')!;
const isExpandButton = event
.composedPath()
.some((el: HTMLElement) => el?.classList?.contains('tree-item__expand-button'));
if (!treeItem.disabled) {
if (!treeItem || treeItem.disabled) {
return;
}
if (this.selection === 'multiple' && isExpandButton) {
treeItem.expanded = !treeItem.expanded;
} else {
this.selectItem(treeItem);
}
}
handleDefaultSlotChange() {
this.treeItems = [...this.querySelectorAll('sl-tree-item')];
[...this.treeItems].forEach(this.initTreeItem);
}
handleFocusOut = (event: FocusEvent) => {
const relatedTarget = event.relatedTarget as HTMLElement;
@ -327,9 +336,9 @@ export default class SlTree extends ShoelaceElement {
render() {
return html`
<div part="base" class="tree" @click="${this.handleClick}" @keydown="${this.handleKeyDown}">
<slot></slot>
<slot name="expanded-icon" hidden aria-hidden="true"> </slot>
<slot name="collapsed-icon" hidden aria-hidden="true"> </slot>
<slot @slotchange=${this.handleDefaultSlotChange}></slot>
<slot name="expand-icon" hidden aria-hidden="true"> </slot>
<slot name="collapse-icon" hidden aria-hidden="true"> </slot>
</div>
`;
}