kopia lustrzana https://github.com/shoelace-style/shoelace
tree updates
rodzic
bcf2139fe7
commit
20e9e3c320
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue