various updates

pull/823/head
Cory LaViska 2022-07-26 15:53:24 -04:00
rodzic 33587f51d3
commit ce09869ea2
12 zmienionych plików z 580 dodań i 519 usunięć

Wyświetl plik

@ -79,6 +79,7 @@
"monospace",
"mousedown",
"mouseup",
"multiselectable",
"nextjs",
"nocheck",
"noopener",
@ -116,6 +117,7 @@
"textareas",
"textfield",
"transitionend",
"treeitem",
"Triaging",
"turbolinks",
"unbundles",

Wyświetl plik

@ -2,94 +2,164 @@
[component-header:sl-tree-item]
A tree item is a hierarchical node of a tree.
A tree item serves as a hierarchical node that lives inside a [tree](/components/tree).
```html preview
<sl-tree-item> Tree node </sl-tree-item>
<sl-tree>
<sl-tree-item>
Item 1
<sl-tree-item>Item A</sl-tree-item>
<sl-tree-item>Item B</sl-tree-item>
<sl-tree-item>Item C</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Item 2</sl-tree-item>
<sl-tree-item>Item 3</sl-tree-item>
</sl-tree>
```
<!-- prettier-ignore -->
```jsx react
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree>
<SlTreeItem>
Item 1
<SlTreeItem>Item A</SlTreeItem>
<SlTreeItem>Item B</SlTreeItem>
<SlTreeItem>Item C</SlTreeItem>
</SlTreeItem>
<SlTreeItem>Item 2</SlTreeItem>
<SlTreeItem>Item 3</SlTreeItem>
</SlTree>
);
```
## Examples
### Nested tree items
A tree item can contain other items, this allow the user to expand or collapse nested nodes accordingly.
A tree item can contain other tree items. This allows the node to be expanded or collapsed by the user.
```html preview
<sl-tree-item>
Parent Node
<sl-tree-item> Child 1 </sl-tree-item>
<sl-tree-item> Child 2 </sl-tree-item>
<sl-tree-item> Child 3 </sl-tree-item>
</sl-tree-item>
<sl-tree>
<sl-tree-item>
Item 1
<sl-tree-item>
Item A
<sl-tree-item>Item Z</sl-tree-item>
<sl-tree-item>Item Y</sl-tree-item>
<sl-tree-item>Item X</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Item B</sl-tree-item>
<sl-tree-item>Item C</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Item 2</sl-tree-item>
<sl-tree-item>Item 3</sl-tree-item>
</sl-tree>
```
### Expanded
<!-- prettier-ignore -->
```jsx react
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
Use the `expanded` attribute to display the nested items.
```html preview
<sl-tree-item expanded>
Parent Node
<sl-tree-item> Child 1 </sl-tree-item>
<sl-tree-item> Child 2 </sl-tree-item>
<sl-tree-item> Child 3 </sl-tree-item>
</sl-tree-item>
const App = () => (
<SlTree>
<SlTreeItem>
Item 1
<SlTreeItem>
Item A
<SlTreeItem>Item Z</SlTreeItem>
<SlTreeItem>Item Y</SlTreeItem>
<SlTreeItem>Item X</SlTreeItem>
</SlTreeItem>
<SlTreeItem>Item B</SlTreeItem>
<SlTreeItem>Item C</SlTreeItem>
</SlTreeItem>
<SlTreeItem>Item 2</SlTreeItem>
<SlTreeItem>Item 3</SlTreeItem>
</SlTree>
);
```
### Selected
Use the `selected` attribute to the mark the item as selected.
Use the `selected` attribute to select a tree item initially.
```html preview
<sl-tree-item expanded>
Parent Node
<sl-tree-item> Child 1 </sl-tree-item>
<sl-tree-item selected> Child 2 </sl-tree-item>
<sl-tree-item> Child 3 </sl-tree-item>
</sl-tree-item>
<sl-tree>
<sl-tree-item selected>
Item 1
<sl-tree-item>Item A</sl-tree-item>
<sl-tree-item>Item B</sl-tree-item>
<sl-tree-item>Item C</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Item 2</sl-tree-item>
<sl-tree-item>Item 3</sl-tree-item>
</sl-tree>
```
### Selectable
<!-- prettier-ignore -->
```jsx react
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
Use the `selectable` attribute to display the checkbox.
```html preview
<sl-tree-item class="selectable" selectable>
Parent Node
<sl-tree-item selectable> Child 1 </sl-tree-item>
<sl-tree-item selectable> Child 2 </sl-tree-item>
<sl-tree-item selectable> Child 3 </sl-tree-item>
</sl-tree-item>
<script>
document.querySelector('sl-tree-item.selectable').addEventListener('click', ({ target }) => {
if (target.hasAttribute('selectable')) {
target.selected = !target.selected;
}
});
</script>
const App = () => (
<SlTree>
<SlTreeItem selected>
Item 1
<SlTreeItem>Item A</SlTreeItem>
<SlTreeItem>Item B</SlTreeItem>
<SlTreeItem>Item C</SlTreeItem>
</SlTreeItem>
<SlTreeItem>Item 2</SlTreeItem>
<SlTreeItem>Item 3</SlTreeItem>
</SlTree>
);
```
### Lazy
### Expanded
Use the `lazy` to specify that the content is not yet loaded. When the user tries to expand the node,
a `sl-lazy-load` event is emitted.
Use the `expanded` attribute to expand a tree item initially.
```html preview
<sl-tree-item lazy> Parent Node </sl-tree-item>
<sl-tree>
<sl-tree-item expanded>
Item 1
<sl-tree-item expanded>
Item A
<sl-tree-item>Item Z</sl-tree-item>
<sl-tree-item>Item Y</sl-tree-item>
<sl-tree-item>Item X</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Item B</sl-tree-item>
<sl-tree-item>Item C</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Item 2</sl-tree-item>
<sl-tree-item>Item 3</sl-tree-item>
</sl-tree>
```
### Indentation size
<!-- prettier-ignore -->
```jsx react
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
Use the `--indentation-size` custom property to set the tree item's indentation.
```html preview
<sl-tree-item style="--indentation-size: 3rem" expanded>
Parent Node
<sl-tree-item> Child 1 </sl-tree-item>
<sl-tree-item> Child 2 </sl-tree-item>
<sl-tree-item> Child 3 </sl-tree-item>
</sl-tree-item>
const App = () => (
<SlTree>
<SlTreeItem expanded>
Item 1
<SlTreeItem expanded>
Item A
<SlTreeItem>Item Z</SlTreeItem>
<SlTreeItem>Item Y</SlTreeItem>
<SlTreeItem>Item X</SlTreeItem>
</SlTreeItem>
<SlTreeItem>Item B</SlTreeItem>
<SlTreeItem>Item C</SlTreeItem>
</SlTreeItem>
<SlTreeItem>Item 2</SlTreeItem>
<SlTreeItem>Item 3</SlTreeItem>
</SlTree>
);
```
[component-metadata:sl-tree-item]

Wyświetl plik

@ -2,181 +2,259 @@
[component-header:sl-tree]
A tree component allow the user to display a hierarchical list of items, expanding and collapsing the nodes that have nested items.
The user can select one or more items from the list.
Trees allow you to display a hierarchical list of selectable [tree items](/components/tree-item). Items with children can be expanded and collapsed as desired by the user.
```html preview
<sl-tree>
<sl-tree-item expanded>
Getting Started
<sl-tree-item>
Deciduous
<sl-tree-item>Birch</sl-tree-item>
<sl-tree-item>
Overview
<sl-tree-item>Quick Start</sl-tree-item>
<sl-tree-item>New to Web Components?</sl-tree-item>
<sl-tree-item>What Problem Does This Solve?</sl-tree-item>
<sl-tree-item>Browser Support</sl-tree-item>
<sl-tree-item>License</sl-tree-item>
<sl-tree-item>Attribution</sl-tree-item>
Maple
<sl-tree-item>Field maple</sl-tree-item>
<sl-tree-item>Red maple</sl-tree-item>
<sl-tree-item>Sugar maple</sl-tree-item>
</sl-tree-item>
<sl-tree-item> Installation </sl-tree-item>
<sl-tree-item> Usage </sl-tree-item>
<sl-tree-item>Oak</sl-tree-item>
</sl-tree-item>
<sl-tree-item>
Frameworks
<sl-tree-item> React</sl-tree-item>
<sl-tree-item> Vue</sl-tree-item>
<sl-tree-item> Angular</sl-tree-item>
Coniferous
<sl-tree-item>Cedar</sl-tree-item>
<sl-tree-item>Pine</sl-tree-item>
<sl-tree-item>Spruce</sl-tree-item>
</sl-tree-item>
<sl-tree-item disabled> Resources </sl-tree-item>
<sl-tree-item>
Non-trees
<sl-tree-item>Bamboo</sl-tree-item>
<sl-tree-item>Cactus</sl-tree-item>
<sl-tree-item>Fern</sl-tree-item>
</sl-tree-item>
</sl-tree>
```
<!-- prettier-ignore -->
```jsx react
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree>
<SlTreeItem expanded>
Getting Started
<SlTreeItem> Overview </SlTreeItem>
<SlTreeItem> Installation </SlTreeItem>
<SlTreeItem> Usage </SlTreeItem>
<SlTreeItem>
Deciduous
<SlTreeItem>Birch</SlTreeItem>
<SlTreeItem>
Maple
<SlTreeItem>Field maple</SlTreeItem>
<SlTreeItem>Red maple</SlTreeItem>
<SlTreeItem>Sugar maple</SlTreeItem>
</SlTreeItem>
<SlTreeItem>Oak</SlTreeItem>
</SlTreeItem>
<SlTreeItem>
Frameworks
<SlTreeItem> React</SlTreeItem>
<SlTreeItem> Vue</SlTreeItem>
<SlTreeItem> Angular</SlTreeItem>
Coniferous
<SlTreeItem>Cedar</SlTreeItem>
<SlTreeItem>Pine</SlTreeItem>
<SlTreeItem>Spruce</SlTreeItem>
</SlTreeItem>
<SlTreeItem disabled> Resources </SlTreeItem>
<SlTreeItem>
Non-trees
<SlTreeItem>Bamboo</SlTreeItem>
<SlTreeItem>Cactus</SlTreeItem>
<SlTreeItem>Fern</SlTreeItem>
</SlTreeItem>
</SlTree>
);
```
## Examples
### Selection modes
### Selection Modes
Use the `selection` attribute to specify the selection behavior of the tree
Use the `selection` attribute to change the selection behavior of the tree.
- Set `none` (_default_) to disable the selection.
- Set `single` to allow the selection of a single item.
- Set `leaf` to allow the selection of a single leaf node. Clicking on a parent node will expand/collapse the node.
- Set `single` to allow the selection of a single item (default).
- Set `multiple` to allow the selection of multiple items.
- Set `leaf` to allow the selection of a single leaf node. Clicking on a parent node will expand/collapse the node.
```html preview
<sl-select id="selection-mode" value="none" label="Selection">
<sl-menu-item value="none">none</sl-menu-item>
<sl-menu-item value="single">single</sl-menu-item>
<sl-menu-item value="leaf">leaf</sl-menu-item>
<sl-menu-item value="multiple">multiple</sl-menu-item>
<sl-select id="selection-mode" value="single" label="Selection">
<sl-menu-item value="single">Single</sl-menu-item>
<sl-menu-item value="multiple">Multiple</sl-menu-item>
<sl-menu-item value="leaf">Leaf</sl-menu-item>
</sl-select>
<br />
<sl-tree class="selectable">
<sl-tree-item expanded>
Parent
<sl-tree-item expanded>
Parent 1
<sl-tree-item> Child 1 </sl-tree-item>
<sl-tree-item> Child 2 </sl-tree-item>
</sl-tree-item>
<br />
<sl-tree class="tree-selectable">
<sl-tree-item>
Item 1
<sl-tree-item>
Parent 2
<sl-tree-item> Child 1</sl-tree-item>
<sl-tree-item> Child 2</sl-tree-item>
<sl-tree-item> Child 3</sl-tree-item>
Item A
<sl-tree-item>Item Z</sl-tree-item>
<sl-tree-item>Item Y</sl-tree-item>
<sl-tree-item>Item X</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Item B</sl-tree-item>
<sl-tree-item>Item C</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Item 2</sl-tree-item>
<sl-tree-item>Item 3</sl-tree-item>
</sl-tree>
<style>
.selectable sl-tree-item::part(item--selected) {
color: var(--sl-color-primary-600);
}
</style>
<script>
(() => {
const selectionMode = document.querySelector('#selection-mode');
const treeItem = document.querySelector('.selectable');
selectionMode.addEventListener('sl-change', () => {
treeItem.selection = selectionMode.value;
});
})();
const selectionMode = document.querySelector('#selection-mode');
const tree = document.querySelector('.tree-selectable');
selectionMode.addEventListener('sl-change', () => {
tree.querySelectorAll('sl-tree-item').forEach(item => (item.selected = false));
tree.selection = selectionMode.value;
});
</script>
```
<!-- prettier-ignore -->
```jsx react
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [selection, setSelection] = useState('none');
const [selection, setSelection] = useState('single');
return (
<>
<SlSelect label="Selection" value={value} onSlChange={event => setSelection(event.target.value)}>
<SlMenuItem value="none">none</SlMenuItem>
<SlMenuItem value="single">single</SlMenuItem>
<SlMenuItem value="leaf">leaf</SlMenuItem>
<SlMenuItem value="multiple">multiple</SlMenuItem>
<SlMenuItem value="leaf">leaf</SlMenuItem>
</SlSelect>
<br />
<SlTree selection={selection} class="selectable">
<SlTreeItem expanded>
Parent
<SlTreeItem expanded>
Parent 1<SlTreeItem> Child 1 </SlTreeItem>
<SlTreeItem> Child 2 </SlTreeItem>
</SlTreeItem>
<SlTree class="tree-selectable">
<SlTreeItem>
Item 1
<SlTreeItem>
Parent 2<SlTreeItem> Child 1</SlTreeItem>
<SlTreeItem> Child 2</SlTreeItem>
<SlTreeItem> Child 3</SlTreeItem>
Item A
<SlTreeItem>Item Z</SlTreeItem>
<SlTreeItem>Item Y</SlTreeItem>
<SlTreeItem>Item X</SlTreeItem>
</SlTreeItem>
<SlTreeItem>Item B</SlTreeItem>
<SlTreeItem>Item C</SlTreeItem>
</SlTreeItem>
<SlTreeItem>Item 2</SlTreeItem>
<SlTreeItem>Item 3</SlTreeItem>
</SlTree>
</>
);
};
```
### Lazy loading
### Showing Indent Guides
Use the `lazy` attribute on a item to indicate that the content is not yet present and will be loaded later.
When the user tries to expand the node, the `loading` state is set to `true` and a special event named
`sl-lazy-load` is emitted to let the loading of the content. The item will remain in a loading state until its content
is changed.
If you want to disable this behavior, for example after the content has been loaded, it will be sufficient to set
`lazy` to `false`.
Indent guides can be drawn by setting `--indent-guide-width`. You can also change the color, offset, and style, using `--indent-guide-color`, `--indent-guide-style`, and `--indent-guide-offset`, respectively.
```html preview
<sl-tree>
<sl-tree-item lazy> Getting Started </sl-tree-item>
<sl-tree class="tree-with-lines">
<sl-tree-item expanded>
Deciduous
<sl-tree-item>Birch</sl-tree-item>
<sl-tree-item expanded>
Maple
<sl-tree-item>Field maple</sl-tree-item>
<sl-tree-item>Red maple</sl-tree-item>
<sl-tree-item>Sugar maple</sl-tree-item>
</sl-tree-item>
<sl-tree-item>Oak</sl-tree-item>
</sl-tree-item>
<sl-tree-item>
Coniferous
<sl-tree-item>Cedar</sl-tree-item>
<sl-tree-item>Pine</sl-tree-item>
<sl-tree-item>Spruce</sl-tree-item>
</sl-tree-item>
<sl-tree-item>
Non-trees
<sl-tree-item>Bamboo</sl-tree-item>
<sl-tree-item>Cactus</sl-tree-item>
<sl-tree-item>Fern</sl-tree-item>
</sl-tree-item>
</sl-tree>
<style>
.tree-with-lines {
--indent-guide-width: 1px;
}
</style>
```
<!-- prettier-ignore -->
```jsx react
import { SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => (
<SlTree class="tree-with-lines" style={{ '--indent-guide-width': '1px' }}>
<SlTreeItem expanded>
Deciduous
<SlTreeItem>Birch</SlTreeItem>
<SlTreeItem expanded>
Maple
<SlTreeItem>Field maple</SlTreeItem>
<SlTreeItem>Red maple</SlTreeItem>
<SlTreeItem>Sugar maple</SlTreeItem>
</SlTreeItem>
<SlTreeItem>Oak</SlTreeItem>
</SlTreeItem>
<SlTreeItem>
Coniferous
<SlTreeItem>Cedar</SlTreeItem>
<SlTreeItem>Pine</SlTreeItem>
<SlTreeItem>Spruce</SlTreeItem>
</SlTreeItem>
<SlTreeItem>
Non-trees
<SlTreeItem>Bamboo</SlTreeItem>
<SlTreeItem>Cactus</SlTreeItem>
<SlTreeItem>Fern</SlTreeItem>
</SlTreeItem>
</SlTree>
);
```
### Lazy Loading
Use the `lazy` attribute on a tree item to indicate that the content is not yet present and will be loaded later. When the user tries to expand the node, the `loading` state is set to `true` and the `sl-lazy-load` event will be emitted to allow you to load data asynchronously. The item will remain in a loading state until its content is changed.
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-item lazy>Available Trees</sl-tree-item>
</sl-tree>
<script type="module">
const lazyItem = document.querySelector('sl-tree-item[lazy]');
lazyItem.addEventListener('sl-lazy-load', () => {
// Simulate an asynchronous loading
setTimeout(() => {
const subItems = ['Overview', 'Installation', 'Usage'];
const fragment = document.createDocumentFragment();
lazyItem.addEventListener('sl-lazy-load', () => {
// Simulate asynchronous loading
setTimeout(() => {
const subItems = ['Birch', 'Cedar', 'Maple', 'Pine'];
for (const item of subItems) {
const treeItem = document.createElement('sl-tree-item');
treeItem.innerText = item;
fragment.appendChild(treeItem);
lazyItem.append(treeItem);
}
lazyItem.appendChild(fragment);
// Disable lazy mode since the content has been loaded
// Disable lazy mode once the content has been loaded
lazyItem.lazy = false;
}, 2000);
}, 1000);
});
</script>
```
@ -191,17 +269,17 @@ const App = () => {
const handleLazyLoad = () => {
// Simulate asynchronous loading
setTimeout(() => {
setChildItems(['Overview', 'Installation', 'Usage']);
setChildItems(['Birch', 'Cedar', 'Maple', 'Pine']);
// Disable lazy mode since the content has been loaded
// Disable lazy mode once the content has been loaded
setLazy(false);
}, 2000);
}, 1000);
};
return (
<SlTree>
<SlTree selection="leaf">
<SlTreeItem lazy={lazy} onSlLazyLoad={handleLazyLoad}>
Getting Started
Available Trees
{childItems.map(item => (
<SlTreeItem>{item}</SlTreeItem>
))}
@ -211,133 +289,109 @@ const App = () => {
};
```
### Styling trees
### With Icons
Using CSS parts is possible to apply custom styles to the tree.
For example, it is possible to change the hover effect and to highlight the selected item.
Decorative icons can be used before labels to provide hints for each node.
```html preview
<style>
.with-custom-style sl-tree-item::part(item) {
border-left: 2px solid transparent;
}
.with-custom-style sl-tree-item:not([disabled])::part(item):hover,
.with-custom-style sl-tree-item:focus-visible::part(item) {
color: var(--sl-color-primary-1000);
background-color: var(--sl-color-neutral-200);
}
.with-custom-style sl-tree-item::part(item--selected),
.with-custom-style sl-tree-item::part(item--selected):hover,
.with-custom-style sl-tree-item:focus-visible::part(item--selected) {
color: var(--sl-color-neutral-1000);
background-color: var(--sl-color-neutral-100);
border-left-color: var(--sl-color-primary-600);
}
</style>
<sl-tree selection="leaf" class="with-custom-style">
<sl-tree class="tree-with-icons">
<sl-tree-item expanded>
Getting Started
<sl-icon name="folder"></sl-icon>
Root
<sl-tree-item>
Overview
<sl-tree-item>Quick Start</sl-tree-item>
<sl-tree-item>New to Web Components?</sl-tree-item>
<sl-tree-item>What Problem Does This Solve?</sl-tree-item>
<sl-tree-item>Browser Support</sl-tree-item>
<sl-tree-item>License</sl-tree-item>
<sl-tree-item>Attribution</sl-tree-item>
<sl-icon name="folder"> </sl-icon>
Folder 1
<sl-tree-item>
<sl-icon name="files"></sl-icon>
File 1 - 1
</sl-tree-item>
<sl-tree-item disabled>
<sl-icon name="files"></sl-icon>
File 1 - 2
</sl-tree-item>
<sl-tree-item>
<sl-icon name="files"></sl-icon>
File 1 - 3
</sl-tree-item>
</sl-tree-item>
<sl-tree-item selected> Installation </sl-tree-item>
<sl-tree-item> Usage </sl-tree-item>
</sl-tree-item>
<sl-tree-item>
Frameworks
<sl-tree-item> React</sl-tree-item>
<sl-tree-item> Vue</sl-tree-item>
<sl-tree-item> Angular</sl-tree-item>
</sl-tree-item>
<sl-tree-item disabled> Resources </sl-tree-item>
</sl-tree>
```
### With indentation lines
```html preview
<style>
.with-indentation-lines sl-tree-item[expanded]::part(children) {
position: relative;
}
.with-indentation-lines sl-tree-item[expanded]::part(children)::before {
content: '';
position: absolute;
left: 1em;
top: var(--sl-spacing-2x-small);
bottom: var(--sl-spacing-2x-small);
border-right: 1px solid var(--sl-color-neutral-100);
transition: 0.2s border-right ease-in-out;
}
.with-indentation-lines sl-tree-item[expanded]::part(children):hover::before {
border-right: 1px solid var(--sl-color-neutral-600);
}
</style>
<sl-tree class="with-indentation-lines">
<sl-tree-item expanded>
Getting Started
<sl-tree-item>
Overview
<sl-tree-item>Quick Start</sl-tree-item>
<sl-tree-item>New to Web Components?</sl-tree-item>
<sl-tree-item>What Problem Does This Solve?</sl-tree-item>
<sl-tree-item>Browser Support</sl-tree-item>
<sl-tree-item>License</sl-tree-item>
<sl-tree-item>Attribution</sl-tree-item>
</sl-tree-item>
<sl-tree-item> Installation </sl-tree-item>
<sl-tree-item> Usage </sl-tree-item>
</sl-tree-item>
<sl-tree-item>
Frameworks
<sl-tree-item> React</sl-tree-item>
<sl-tree-item> Vue</sl-tree-item>
<sl-tree-item> Angular</sl-tree-item>
</sl-tree-item>
<sl-tree-item disabled> Resources </sl-tree-item>
</sl-tree>
```
### With icons
```html preview
<style>
sl-icon {
margin-right: var(--sl-spacing-x-small);
}
</style>
<sl-tree>
<sl-tree-item expanded>
<sl-icon name="folder"></sl-icon>Root
<sl-tree-item>
<sl-icon name="folder"> </sl-icon>Folder 1
<sl-tree-item> <sl-icon name="files"></sl-icon>File 1 - 1 </sl-tree-item>
<sl-tree-item disabled> <sl-icon name="files"></sl-icon>File 1 - 2 </sl-tree-item>
<sl-tree-item> <sl-icon name="files"></sl-icon>File 1 - 3 </sl-tree-item>
</sl-tree-item>
<sl-tree-item>
<sl-icon name="files"></sl-icon>
Folder 2
<sl-tree-item selected> <sl-icon name="files"></sl-icon>File 2 - 1 </sl-tree-item>
<sl-tree-item> <sl-icon name="files"></sl-icon>File 2 - 2 </sl-tree-item>
<sl-tree-item>
<sl-icon name="files"></sl-icon>
File 2 - 1
</sl-tree-item>
<sl-tree-item>
<sl-icon name="files"></sl-icon>
File 2 - 2
</sl-tree-item>
</sl-tree-item>
<sl-tree-item>
<sl-icon name="files"></sl-icon>
File 1
</sl-tree-item>
<sl-tree-item> <sl-icon name="files"></sl-icon>File 1 </sl-tree-item>
</sl-tree-item>
</sl-tree>
```
```jsx react
import { SlIcon, SlTree, SlTreeItem } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const [childItems, setChildItems] = useState([]);
const [lazy, setLazy] = useState(true);
const handleLazyLoad = () => {
// Simulate asynchronous loading
setTimeout(() => {
setChildItems(['Overview', 'Installation', 'Usage']);
// Disable lazy mode once the content has been loaded
setLazy(false);
}, 1000);
};
return (
<SlTree class="tree-with-icons">
<SlTreeItem expanded>
<SlIcon name="folder" />
Root
<SlTreeItem>
<SlIcon name="folder" />
Folder 1<SlTreeItem>
<SlIcon name="files" />
File 1 - 1
</SlTreeItem>
<SlTreeItem disabled>
<SlIcon name="files" />
File 1 - 2
</SlTreeItem>
<SlTreeItem>
<SlIcon name="files" />
File 1 - 3
</SlTreeItem>
</SlTreeItem>
<SlTreeItem>
<SlIcon name="files" />
Folder 2<SlTreeItem>
<SlIcon name="files" />
File 2 - 1
</SlTreeItem>
<SlTreeItem>
<SlIcon name="files" />
File 2 - 2
</SlTreeItem>
</SlTreeItem>
<SlTreeItem>
<SlIcon name="files" />
File 1
</SlTreeItem>
</SlTreeItem>
</SlTree>
);
};
```
[component-metadata:sl-tree]

Wyświetl plik

@ -10,6 +10,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
## Next
- Added experimental `<sl-tree>` and `<sl-tree-item>` components [#823](https://github.com/shoelace-style/shoelace/pull/823)
- Added `--indicator-width` custom property to `<sl-progress-ring>` [#837](https://github.com/shoelace-style/shoelace/issues/837)
- Added Swedish translation [#838](https://github.com/shoelace-style/shoelace/pull/838)
- Changed the type of component styles from `CSSResult` to `CSSResultGroup` [#828](https://github.com/shoelace-style/shoelace/issues/828)

Wyświetl plik

@ -7,23 +7,25 @@ export default css`
:host {
display: block;
outline: 0;
z-index: 0;
}
:host(:focus) {
outline: 0;
}
::slotted(sl-icon) {
margin-inline-end: var(--sl-spacing-x-small);
}
.tree-item {
position: relative;
display: flex;
align-items: stretch;
flex-direction: column;
color: var(--sl-color-neutral-700);
user-select: none;
white-space: nowrap;
cursor: pointer;
}
.tree-item__checkbox {
@ -56,33 +58,46 @@ export default css`
align-items: center;
justify-content: center;
box-sizing: content-box;
color: var(--sl-color-neutral-400);
color: var(--sl-color-neutral-500);
padding: var(--sl-spacing-x-small);
width: 1rem;
height: 1rem;
cursor: pointer;
}
.tree-item__expand-button--visible {
cursor: pointer;
}
.tree-item__item {
display: flex;
align-items: center;
cursor: pointer;
border-inline-start: solid 3px transparent;
}
.tree-item__item--disabled {
color: var(--sl-color-neutral-400);
.tree-item--disabled .tree-item__item {
opacity: 0.5;
outline: none;
cursor: not-allowed;
}
:host(:not([aria-disabled='true'])) .tree-item__item:hover {
background-color: var(--sl-color-neutral-100);
}
:host(:not([aria-disabled='true']):focus-visible) .tree-item__item {
outline: var(--sl-focus-ring);
outline-offset: var(--sl-focus-ring-offset);
z-index: 2;
}
:host(:not([aria-disabled='true'])) .tree-item__item--selected,
:host(:not([aria-disabled='true'])) .tree-item__item:hover,
:host(:not([aria-disabled='true'])) .tree-item__item:hover sl-checkbox::part(label) {
color: var(--sl-color-primary-600);
:host(:not([aria-disabled='true'])) .tree-item--selected .tree-item__item {
background-color: var(--sl-color-neutral-100);
border-inline-start-color: var(--sl-color-primary-600);
}
:host(:not([aria-disabled='true'])) .tree-item__expand-button {
color: var(--sl-color-neutral-600);
}
.tree-item__label {
@ -92,6 +107,26 @@ export default css`
}
.tree-item__children {
font-size: calc(1em + var(--indentation-size, var(--sl-spacing-medium)));
font-size: calc(1em + var(--indent-size, var(--sl-spacing-medium)));
}
/* Indentation lines */
.tree-item__children {
position: relative;
}
.tree-item__children::before {
content: '';
position: absolute;
top: var(--indent-guide-offset);
bottom: var(--indent-guide-offset);
left: calc(1em - (var(--indent-guide-width) / 2) - 1px);
border-inline-end: var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color);
z-index: 1;
}
.tree-item--rtl .tree-item__children::before {
left: auto;
right: 1em;
}
`;

Wyświetl plik

@ -26,7 +26,7 @@ describe('<sl-tree-item>', () => {
expect(leafItem).to.have.attribute('aria-disabled', 'false');
});
describe('when contain child tree items', () => {
describe('when it contains child tree items', () => {
it('should set isLeaf to false', () => {
// Assert
expect(parentItem.isLeaf).to.be.false;
@ -156,7 +156,7 @@ describe('<sl-tree-item>', () => {
});
});
describe('when a item is disabled', () => {
describe('when the item is disabled', () => {
it('should update the aria-disabled attribute', async () => {
// Act
leafItem.disabled = true;
@ -175,4 +175,15 @@ describe('<sl-tree-item>', () => {
expect(leafItem.shadowRoot?.querySelector('.tree-item__item')?.part.contains('item--disabled')).to.be.true;
});
});
describe('when the item is expanded', () => {
it('should set item--expanded part', async () => {
// Act
leafItem.expanded = true;
await leafItem.updateComplete;
// Assert
expect(leafItem.shadowRoot?.querySelector('.tree-item__item')?.part.contains('item--expanded')).to.be.true;
});
});
});

Wyświetl plik

@ -1,16 +1,16 @@
import { LocalizeController } from '@shoelace-style/localize';
import { LitElement, html } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { live } from 'lit/directives/live.js';
import { when } from 'lit/directives/when.js';
import { animateTo, shimKeyframesHeightAuto, stopAnimations } from 'src/internal/animate';
import { stringMap } from 'src/internal/string';
import { getAnimation, setDefaultAnimation } from 'src/utilities/animation-registry';
import '../../components/checkbox/checkbox';
import '../../components/icon/icon';
import '../../components/spinner/spinner';
import { emit } from '../../internal/event';
import { watch } from '../../internal/watch';
import { LocalizeController } from '../../utilities/localize';
import styles from './tree-item.styles';
import type { PropertyValueMap } from 'lit';
@ -23,6 +23,7 @@ export function isTreeItem(element: Element) {
* @status experimental
*
* @dependency sl-checkbox
* @dependency sl-icon
* @dependency sl-spinner
*
* @event sl-expand - Emitted when the item expands.
@ -35,13 +36,13 @@ export function isTreeItem(element: Element) {
*
* @csspart base - The component's internal wrapper.
* @csspart item - The item main container.
* @csspart item--selected - The `selected` state of the main container.
* @csspart item--disabled - The `disabled` state of the main container.
* @csspart indentation - The item indentation.
* @csspart label - The item label.
* @csspart children - The item children container.
*
* @cssproperty --indentation-size - The size of the indentation for nested items. (Default: --sl-spacing-medium)
* @csspart item--disabled - Applied when the item is disabled.
* @csspart item--expanded - Applied when the item is expanded.
* @csspart item--indeterminate - Applied when the selection is indeterminate.
* @csspart item--selected - Applied when the item is selected.
* @csspart indentation - The item's indentation container.
* @csspart label - The item's label.
* @csspart children - The item's children container.
*/
@customElement('sl-tree-item')
export default class SlTreeItem extends LitElement {
@ -49,30 +50,23 @@ export default class SlTreeItem extends LitElement {
private readonly localize = new LocalizeController(this);
/** Expands the item when is set */
@state() indeterminate = false;
@state() isLeaf = false;
@state() loading = false;
@state() selectable = false;
/** Expands the tree item. */
@property({ type: Boolean, reflect: true }) expanded = false;
/** Sets the treeitem's selected state */
/** Draws the tree item in a selected state. */
@property({ type: Boolean, reflect: true }) selected = false;
/** Disables the treeitem */
/** Disables the tree item. */
@property({ type: Boolean, reflect: true }) disabled = false;
/** When set, enables the lazy mode behavior */
/** Enables lazy loading behavior. */
@property({ type: Boolean, reflect: true }) lazy = false;
/** Shows the checkbox when set */
@property({ type: Boolean }) selectable = false;
/** Draws the checkbox in a indeterminate state. */
@state() indeterminate = false;
/** Specifies whether the node has children nodes */
@state() isLeaf = false;
/** Draws the expand button in a loading state. */
@state() loading = false;
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
@query('slot[name=children]') childrenSlot: HTMLSlotElement;
@query('.tree-item__item') itemElement: HTMLDivElement;
@ -173,9 +167,7 @@ export default class SlTreeItem extends LitElement {
emit(this, 'sl-after-collapse');
}
/**
* @internal Gets all the nested tree items
*/
// Gets all the nested tree items
getChildrenItems({ includeDisabled = true }: { includeDisabled?: boolean } = {}): SlTreeItem[] {
return this.childrenSlot
? ([...this.childrenSlot.assignedElements({ flatten: true })].filter(
@ -184,17 +176,15 @@ export default class SlTreeItem extends LitElement {
: [];
}
/**
* @internal Checks whether the item is nested into an item
*/
// Checks whether the item is nested into an item
private isNestedItem(): boolean {
const parent = this.parentElement;
return !!parent && isTreeItem(parent);
}
handleToggleExpand(e: Event) {
e.preventDefault();
e.stopImmediatePropagation();
handleToggleExpand(event: Event) {
event.preventDefault();
event.stopImmediatePropagation();
if (!this.disabled) {
this.expanded = !this.expanded;
@ -203,7 +193,6 @@ export default class SlTreeItem extends LitElement {
handleChildrenSlotChange() {
this.loading = false;
this.isLeaf = this.getChildrenItems().length === 0;
}
@ -214,28 +203,47 @@ export default class SlTreeItem extends LitElement {
}
render() {
const isRtl = this.localize.dir() === 'rtl';
return html`
<div part="base" class="tree-item">
<div
part="base"
class="${classMap({
'tree-item': true,
'tree-item--selected': this.selected,
'tree-item--disabled': this.disabled,
'tree-item--leaf': this.isLeaf,
'tree-item--rtl': this.localize.dir() === 'rtl'
})}"
>
<div
class="${classMap({
'tree-item__item': true,
'tree-item__item--selected': this.selected,
'tree-item__item--disabled': this.disabled
})}"
part="${stringMap({
item: true,
'item--selected': this.selected,
'item--disabled': this.disabled
})}"
class="tree-item__item"
part="
item
${this.disabled ? 'item--disabled' : ''}
${this.expanded ? 'item--expanded' : ''}
${this.indeterminate ? 'item--indeterminate' : ''}
${this.selected ? 'item--selected' : ''}
"
>
<div class="tree-item__indentation" part="indentation"></div>
<div class="tree-item__expand-button" aria-hidden="true" @click="${this.handleToggleExpand}">
<div
class=${classMap({
'tree-item__expand-button': true,
'tree-item__expand-button--visible': !this.loading && (!this.isLeaf || this.lazy)
})}
aria-hidden="true"
@click="${this.handleToggleExpand}"
>
${when(this.loading, () => html` <sl-spinner></sl-spinner> `)}
${when(
!this.loading && (!this.isLeaf || this.lazy),
() => html`
<sl-icon library="system" name="${this.expanded ? 'chevron-down' : 'chevron-right'}"></sl-icon>
<sl-icon
library="system"
name="${this.expanded ? 'chevron-down' : isRtl ? 'chevron-left' : 'chevron-right'}"
></sl-icon>
`
)}
</div>

Wyświetl plik

@ -5,10 +5,22 @@ export default css`
${componentStyles}
:host {
/*
* These are actually used by tree item, but we define them here so they can more easily be set and all tree items
* stay consistent.
*/
--indent-guide-color: var(--sl-color-neutral-200);
--indent-guide-offset: 0;
--indent-guide-style: solid;
--indent-guide-width: 0;
--indent-size: var(--sl-spacing-large);
display: block;
/**
* tree-item indentation uses the "em" unit in order to increment its width on each level,
* so setting the font size to zero here, removes the indentation for all the nodes in the first level.
isolation: isolate;
/*
* Tree item indentation uses the "em" unit to increment its width on each level, so setting the font size to zero
* here removes the indentation for all the nodes on the first level.
*/
font-size: 0;
}

Wyświetl plik

@ -207,78 +207,6 @@ describe('<sl-tree>', () => {
});
describe('when Enter is pressed', () => {
describe('and selection is "none"', () => {
describe('and node is expanded', () => {
it('should not select the tree item', async () => {
// Arrange
const parentNode = el.children[2] as SlTreeItem;
parentNode.focus();
await el.updateComplete;
// Act
await sendKeys({ press: 'Enter' });
// Assert
expect(el).to.have.attribute('tabindex', '-1');
expect(parentNode).to.have.attribute('tabindex', '0');
expect(parentNode).to.have.attribute('expanded');
expect(parentNode).not.to.have.attribute('selected');
});
it('should collapse the tree item', async () => {
// Arrange
const parentNode = el.children[2] as SlTreeItem;
parentNode.expanded = true;
parentNode.focus();
await el.updateComplete;
// Act
await sendKeys({ press: 'Enter' });
// Assert
expect(el).to.have.attribute('tabindex', '-1');
expect(parentNode).to.have.attribute('tabindex', '0');
expect(parentNode).not.to.have.attribute('expanded');
});
});
describe('and node is collapsed', () => {
describe('and selection is "none"', () => {
it('should not select the tree item', async () => {
// Arrange
const parentNode = el.children[2] as SlTreeItem;
parentNode.focus();
await el.updateComplete;
// Act
await sendKeys({ press: 'Enter' });
// Assert
expect(el).to.have.attribute('tabindex', '-1');
expect(parentNode).to.have.attribute('tabindex', '0');
expect(parentNode).to.have.attribute('expanded');
expect(parentNode).not.to.have.attribute('selected');
});
it('should expand the tree item', async () => {
// Arrange
const parentNode = el.children[2] as SlTreeItem;
parentNode.focus();
await el.updateComplete;
// Act
await sendKeys({ press: 'Enter' });
// Assert
expect(el).to.have.attribute('tabindex', '-1');
expect(parentNode).to.have.attribute('tabindex', '0');
expect(parentNode).to.have.attribute('expanded');
});
});
});
});
describe('and selection is "single"', () => {
it('should select only one tree item', async () => {
// Arrange
@ -353,25 +281,6 @@ describe('<sl-tree>', () => {
});
describe('when Space is pressed', () => {
describe('and selection is "none"', () => {
it('should not select the tree item', async () => {
// Arrange
el.selection = 'none';
const node = el.children[0] as SlTreeItem;
node.focus();
await el.updateComplete;
// Act
await sendKeys({ press: ' ' });
// Assert
expect(el).to.have.attribute('tabindex', '-1');
expect(node).to.have.attribute('tabindex', '0');
expect(node).not.to.have.attribute('expanded');
expect(node).not.to.have.attribute('selected');
});
});
describe('and selection is "single"', () => {
it('should select only one tree item', async () => {
// Arrange
@ -484,26 +393,6 @@ describe('<sl-tree>', () => {
});
describe('when the user clicks on a tree item', () => {
describe('and selection is "none"', () => {
it('should not select the tree item', async () => {
// Arrange
const node = el.children[1] as SlTreeItem;
const selectedChangeSpy = sinon.spy();
el.addEventListener('sl-selected-change', selectedChangeSpy);
// Act
node.focus();
node.click();
await el.updateComplete;
// Assert
expect(el).to.have.attribute('tabindex', '-1');
expect(node).to.have.attribute('tabindex', '0');
expect(node).not.to.have.attribute('selected');
expect(selectedChangeSpy).not.to.have.been.called;
});
});
describe('and selection is "single"', () => {
it('should select only one tree item', async () => {
// Arrange
@ -622,13 +511,13 @@ describe('<sl-tree>', () => {
});
describe('when an tree item gets selected or deselected', () => {
it('should emit a `sl-selected-change` event', async () => {
it('should emit a `sl-selection-change` event', async () => {
// Arrange
el.selection = 'single';
await el.updateComplete;
const selectedChangeSpy = sinon.spy();
el.addEventListener('sl-selected-change', selectedChangeSpy);
el.addEventListener('sl-selection-change', selectedChangeSpy);
const node = el.children[0] as SlTreeItem;

Wyświetl plik

@ -3,6 +3,7 @@ import { customElement, property, query } from 'lit/decorators.js';
import { emit } from 'src/internal/event';
import { clamp } from 'src/internal/math';
import { watch } from 'src/internal/watch';
import { LocalizeController } from '../../utilities/localize';
import { isTreeItem } from '../tree-item/tree-item';
import styles from './tree.styles';
import type SlTreeItem from '../tree-item/tree-item';
@ -38,12 +39,17 @@ function syncCheckboxes(changedTreeItem: SlTreeItem) {
* @since 2.0
* @status experimental
*
* @event sl-selected-change - Emitted when an item gets selected or deselected
* @event {{ selection: this.selectedItems }} sl-selection-change - Emitted when an item gets selected or deselected
*
* @slot - The default slot.
*
* @csspart base - The component's internal wrapper.
*
* @cssproperty [--indent-size=var(--sl-spacing-medium)] - The size of the indentation for nested items.
* @cssproperty [--indent-guide-color=var(--sl-color-neutral-200)] - The color of the indentation line.
* @cssproperty [--indent-guide-offset=0] - The amount of vertical spacing to leave between the top and bottom of the indentation line's starting position.
* @cssproperty [--indent-guide-style=solid] - The style of the indentation line, e.g. solid, dotted, dashed.
* @cssproperty [--indent-guide-width=0] - The width of the indentation line.
*/
@customElement('sl-tree')
export default class SlTree extends LitElement {
@ -52,14 +58,15 @@ export default class SlTree extends LitElement {
@query('slot') defaultSlot: HTMLSlotElement;
/** Specifies the selection behavior of the Tree */
@property() selection: 'none' | 'single' | 'multiple' | 'leaf' = 'none';
@property() selection: 'single' | 'multiple' | 'leaf' = 'single';
/**
* @internal A collection of all the items in the tree, in the order they appear.
* The collection is live, it means that it is automatically updated when the underlying document is changed.
*/
//
// 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 lastFocusedItem: SlTreeItem;
private readonly localize = new LocalizeController(this);
private mutationObserver: MutationObserver;
connectedCallback(): void {
@ -125,8 +132,6 @@ export default class SlTree extends LitElement {
}
selectItem(selectedItem: SlTreeItem) {
if (this.selection === 'none') return;
if (this.selection === 'multiple') {
selectedItem.selected = !selectedItem.selected;
if (selectedItem.lazy) {
@ -141,12 +146,10 @@ export default class SlTree extends LitElement {
selectedItem.expanded = !selectedItem.expanded;
}
emit(this, 'sl-selected-change', { detail: this.selectedItems });
emit(this, 'sl-selection-change', { detail: { selection: this.selectedItems } });
}
/**
* Returns the list of tree items that are selected in the tree
*/
// Returns the list of tree items that are selected in the tree.
get selectedItems(): SlTreeItem[] {
const items = [...this.treeItems];
const isSelected = (item: SlTreeItem) => item.selected;
@ -171,8 +174,13 @@ export default class SlTree extends LitElement {
}
handleKeyDown(event: KeyboardEvent) {
if (!['ArrowDown', 'ArrowUp', 'ArrowRight', 'ArrowLeft', 'Home', 'End', 'Enter', ' '].includes(event.key)) return;
if (!['ArrowDown', 'ArrowUp', 'ArrowRight', 'ArrowLeft', 'Home', 'End', 'Enter', ' '].includes(event.key)) {
return;
}
const items = this.getFocusableItems();
const isLtr = this.localize.dir() === 'ltr';
const isRtl = this.localize.dir() === 'rtl';
if (items.length > 0) {
event.preventDefault();
@ -188,69 +196,48 @@ export default class SlTree extends LitElement {
};
if (event.key === 'ArrowDown') {
/**
* Moves focus to the next node that is focusable without opening or closing a node.
*/
// Moves focus to the next node that is focusable without opening or closing a node.
focusItemAt(activeItemIndex + 1);
} else if (event.key === 'ArrowUp') {
/**
* Moves focus to the next node that is focusable without opening or closing a node.
*/
// Moves focus to the next node that is focusable without opening or closing a node.
focusItemAt(activeItemIndex - 1);
} else if (event.key === 'ArrowRight') {
/**
* When focus is on a closed node, opens the node; focus does not move.
* When focus is on a open node, moves focus to the first child node.
* When focus is on an end node (a tree item with no children), does nothing.
*/
} else if ((isLtr && event.key === 'ArrowRight') || (isRtl && event.key === 'ArrowLeft')) {
//
// When focus is on a closed node, opens the node; focus does not move.
// When focus is on a open node, moves focus to the first child node.
// When focus is on an end node (a tree item with no children), does nothing.
//
if (!activeItem || activeItem.expanded || (activeItem.isLeaf && !activeItem.lazy)) {
focusItemAt(activeItemIndex + 1);
} else {
toggleExpand(true);
}
} else if (event.key === 'ArrowLeft') {
/**
* When focus is on an open node, closes the node.
* When focus is on a child node that is also either an end node or a closed node, moves focus to its parent node.
* When focus is on a closed `tree`, does nothing.
*/
} else if ((isLtr && event.key === 'ArrowLeft') || (isRtl && event.key === 'ArrowRight')) {
//
// When focus is on an open node, closes the node.
// When focus is on a child node that is also either an end node or a closed node, moves focus to its parent node.
// When focus is on a closed `tree`, does nothing.
//
if (!activeItem || activeItem.isLeaf || !activeItem.expanded) {
focusItemAt(activeItemIndex - 1);
} else {
toggleExpand(false);
}
} else if (event.key === 'Home') {
/**
* Moves focus to the first node in the tree without opening or closing a node.
*/
// Moves focus to the first node in the tree without opening or closing a node.
focusItemAt(0);
} else if (event.key === 'End') {
/**
* Moves focus to the last node in the tree that is focusable without opening the node.
*/
// Moves focus to the last node in the tree that is focusable without opening the node.
focusItemAt(items.length - 1);
} else if (event.key === 'Enter') {
/**
* Performs the default action of the currently focused node. For parent nodes, it opens or closes the node.
* In single-select trees, if the node has no children, selects the current node if not already selected (which
* is the default action).
*/
if (['none', 'leaf'].includes(this.selection) && !activeItem.isLeaf) {
toggleExpand(!activeItem.expanded);
} else {
this.selectItem(activeItem);
}
} else if (event.key === ' ') {
/**
* Toggles the selection state of the focused node.
*/
} else if (event.key === 'Enter' || event.key === ' ') {
// Selects the focused node.
this.selectItem(activeItem);
}
}
}
handleClick(e: Event) {
const target = e.target as HTMLElement;
handleClick(event: Event) {
const target = event.target as HTMLElement;
const treeItem = target.closest('sl-tree-item')!;
if (!treeItem.disabled) {
@ -258,8 +245,8 @@ export default class SlTree extends LitElement {
}
}
handleFocusOut = (e: FocusEvent) => {
const relatedTarget = e.relatedTarget as HTMLElement;
handleFocusOut = (event: FocusEvent) => {
const relatedTarget = event.relatedTarget as HTMLElement;
// If the element that got the focus is not in the tree
if (!relatedTarget || !this.contains(relatedTarget)) {
@ -267,11 +254,11 @@ export default class SlTree extends LitElement {
}
};
handleFocusIn = (e: FocusEvent) => {
const target = e.target as SlTreeItem;
handleFocusIn = (event: FocusEvent) => {
const target = event.target as SlTreeItem;
// If the tree has been focused, move the focus to the last focused item
if (e.target === this) {
if (event.target === this) {
this.focusItem(this.lastFocusedItem || this.treeItems[0]);
}

Wyświetl plik

@ -1,11 +1,3 @@
export function uppercaseFirstLetter(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
export function stringMap(...parts: (string | Record<string, boolean>)[]): string | undefined {
return parts
.map(part => (typeof part === 'object' ? Object.keys(part).filter((key: string) => part[key]) : part))
.flat()
.filter(Boolean)
.join(' ');
}

Wyświetl plik

@ -50,9 +50,9 @@ export { default as SlTabPanel } from './components/tab-panel/tab-panel';
export { default as SlTag } from './components/tag/tag';
export { default as SlTextarea } from './components/textarea/textarea';
export { default as SlTooltip } from './components/tooltip/tooltip';
export { default as SlVisuallyHidden } from './components/visually-hidden/visually-hidden';
export { default as SlTree } from './components/tree/tree';
export { default as SlTreeItem } from './components/tree-item/tree-item';
export { default as SlVisuallyHidden } from './components/visually-hidden/visually-hidden';
/* plop:component */
// Utilities