mirror of
https://github.com/carbon-design-system/carbon-components-svelte.git
synced 2025-09-15 02:11:05 +00:00
TreeView (#725)
* feat(tree-view): add TreeView * fix(tree-view): select initial active node, correct typedefs * docs(tree-view): update examples * chore(tree-view): add test for types * docs(tree-view): rename example * docs(tree-view): improve docs * docs(tree-view): refine examples * docs: fix invalid syntax * chore: rebuild component index/api * docs(layout): increase height of sidenav menu [ci skip]
This commit is contained in:
parent
f4a3646cb4
commit
6ed4aaa86e
25 changed files with 1176 additions and 4 deletions
120
src/TreeView/TreeView.svelte
Normal file
120
src/TreeView/TreeView.svelte
Normal file
|
@ -0,0 +1,120 @@
|
|||
<script>
|
||||
// TODO: add function to programmatically expand/collapse parent nodes
|
||||
|
||||
/**
|
||||
* @typedef {string | number} TreeNodeId
|
||||
* @typedef {{ id: TreeNodeId; text: string; icon?: typeof import("carbon-icons-svelte").CarbonIcon; disabled?: boolean; expanded?: boolean; }} TreeNode
|
||||
* @event {TreeNode & { expanded: boolean; leaf: boolean; }} select
|
||||
* @event {TreeNode & { expanded: boolean; leaf: boolean; }} toggle
|
||||
* @event {TreeNode & { expanded: boolean; leaf: boolean; }} focus
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provide an array of children nodes to render
|
||||
* @type {Array<TreeNode & { children?: TreeNode[] }>}
|
||||
*/
|
||||
export let children = [];
|
||||
|
||||
/**
|
||||
* Set the current active node id
|
||||
* Only one node can be active
|
||||
* @type {TreeNodeId}
|
||||
*/
|
||||
export let activeId = "";
|
||||
|
||||
/**
|
||||
* Set the node ids to be selected
|
||||
* @type {TreeNodeId[]}
|
||||
*/
|
||||
export let selectedIds = [];
|
||||
|
||||
/**
|
||||
* Specify the TreeView size
|
||||
* @type {"default" | "compact"}
|
||||
*/
|
||||
export let size = "default";
|
||||
|
||||
/** Specify the label text */
|
||||
export let labelText = "";
|
||||
|
||||
/** Set to `true` to visually hide the label text */
|
||||
export let hideLabel = false;
|
||||
|
||||
import { createEventDispatcher, setContext } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import TreeViewNodeList from "./TreeViewNodeList.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const labelId = `label-${Math.random().toString(36)}`;
|
||||
const activeNodeId = writable(activeId);
|
||||
const selectedNodeIds = writable(selectedIds);
|
||||
|
||||
let ref = null;
|
||||
let treeWalker = null;
|
||||
|
||||
setContext("TreeView", {
|
||||
activeNodeId,
|
||||
selectedNodeIds,
|
||||
clickNode: (node) => {
|
||||
activeId = node.id;
|
||||
selectedIds = [node.id];
|
||||
dispatch("select", node);
|
||||
},
|
||||
selectNode: (node) => {
|
||||
selectedIds = [node.id];
|
||||
},
|
||||
focusNode: (node) => dispatch("focus", node),
|
||||
toggleNode: (node) => dispatch("toggle", node),
|
||||
});
|
||||
|
||||
function handleKeyDown(e) {
|
||||
if (e.key === "ArrowUp" || e.key === "ArrowDown") e.preventDefault();
|
||||
|
||||
treeWalker.currentNode = e.target;
|
||||
|
||||
let node = null;
|
||||
|
||||
if (e.key === "ArrowUp") node = treeWalker.previousNode();
|
||||
if (e.key === "ArrowDown") node = treeWalker.nextNode();
|
||||
if (node && node !== e.target) {
|
||||
node.tabIndex = "0";
|
||||
node.focus();
|
||||
}
|
||||
}
|
||||
|
||||
$: activeNodeId.set(activeId);
|
||||
$: selectedNodeIds.set(selectedIds);
|
||||
$: if (ref) {
|
||||
treeWalker = document.createTreeWalker(ref, NodeFilter.SHOW_ELEMENT, {
|
||||
acceptNode: (node) => {
|
||||
if (node.classList.contains("bx--tree-node--disabled"))
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
if (node.matches("li.bx--tree-node")) return NodeFilter.FILTER_ACCEPT;
|
||||
return NodeFilter.FILTER_SKIP;
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !hideLabel}
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label id="{labelId}" class:bx--label="{true}">
|
||||
<slot name="labelText">{labelText}</slot>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
<ul
|
||||
{...$$restProps}
|
||||
role="tree"
|
||||
bind:this="{ref}"
|
||||
class:bx--tree="{true}"
|
||||
class:bx--tree--default="{size === 'default'}"
|
||||
class:bx--tree--compact="{size === 'compact'}"
|
||||
aria-label="{hideLabel ? labelText : undefined}"
|
||||
aria-labelledby="{!hideLabel ? labelId : undefined}"
|
||||
aria-multiselectable="{selectedIds.length > 1 || undefined}"
|
||||
on:keydown
|
||||
on:keydown|stopPropagation="{handleKeyDown}"
|
||||
>
|
||||
<TreeViewNodeList root children="{children}" />
|
||||
</ul>
|
Loading…
Add table
Add a link
Reference in a new issue