@pindoba/astro-tree-view 0.0.0-alpha-20250910225440
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +12 -0
- package/README.md +136 -0
- package/index.ts +11 -0
- package/package.json +49 -0
- package/src/TreeNodeItem.astro +57 -0
- package/src/TreeView.astro +95 -0
- package/src/TreeViewItem.astro +130 -0
- package/src/tree-utils.ts +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Copyright 2025 Eduardo Lopes
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
8
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
9
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
10
|
+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
11
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
12
|
+
DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Tree View Component
|
|
2
|
+
|
|
3
|
+
A flexible and accessible tree view component for Astro applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ Collapsible/expandable tree nodes
|
|
8
|
+
- ✅ Selectable items with keyboard navigation
|
|
9
|
+
- ✅ Customizable styling with variants and sizes
|
|
10
|
+
- ✅ Accessible with proper ARIA attributes
|
|
11
|
+
- ✅ TypeScript support
|
|
12
|
+
- ✅ Custom icons support
|
|
13
|
+
- ✅ Event handling for selection
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @pindoba/ui-astro-tree-view @pindoba/styles-tree-view
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Basic Usage
|
|
22
|
+
|
|
23
|
+
```astro
|
|
24
|
+
---
|
|
25
|
+
import { TreeView, TreeViewItem } from "@pindoba/ui-astro-tree-view";
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<TreeView>
|
|
29
|
+
<TreeViewItem label="Giant planets" collapsible defaultOpen>
|
|
30
|
+
<TreeViewItem slot="children" label="Gas giants" collapsible>
|
|
31
|
+
<TreeViewItem slot="children" label="Jupiter" />
|
|
32
|
+
<TreeViewItem slot="children" label="Saturn" />
|
|
33
|
+
</TreeViewItem>
|
|
34
|
+
<TreeViewItem slot="children" label="Ice giants" collapsible>
|
|
35
|
+
<TreeViewItem slot="children" label="Uranus" />
|
|
36
|
+
<TreeViewItem slot="children" label="Neptune" />
|
|
37
|
+
</TreeViewItem>
|
|
38
|
+
</TreeViewItem>
|
|
39
|
+
</TreeView>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Props
|
|
43
|
+
|
|
44
|
+
### TreeView
|
|
45
|
+
|
|
46
|
+
| Prop | Type | Default | Description |
|
|
47
|
+
| ------------- | ----------------------------------- | ----------- | ------------------------ |
|
|
48
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size variant of the tree |
|
|
49
|
+
| `variant` | `"default" \| "subtle" \| "ghost"` | `"default"` | Visual variant |
|
|
50
|
+
| `passThrough` | `Record<string, SystemStyleObject>` | - | Custom styles for slots |
|
|
51
|
+
|
|
52
|
+
### TreeViewItem
|
|
53
|
+
|
|
54
|
+
| Prop | Type | Default | Description |
|
|
55
|
+
| ------------- | ----------------------------------- | ----------- | -------------------------------------------------------- |
|
|
56
|
+
| `label` | `string` | - | The text content for this tree item |
|
|
57
|
+
| `collapsible` | `boolean` | `false` | Whether this item has children and should be collapsible |
|
|
58
|
+
| `defaultOpen` | `boolean` | `false` | Whether the item is expanded by default |
|
|
59
|
+
| `selectable` | `boolean` | `false` | Whether this item is selectable |
|
|
60
|
+
| `selected` | `boolean` | `false` | Whether this item is currently selected |
|
|
61
|
+
| `icon` | `string` | - | Icon HTML to display before the label |
|
|
62
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size variant |
|
|
63
|
+
| `variant` | `"default" \| "subtle" \| "ghost"` | `"default"` | Visual variant |
|
|
64
|
+
| `passThrough` | `Record<string, SystemStyleObject>` | - | Custom styles for slots |
|
|
65
|
+
|
|
66
|
+
## Examples
|
|
67
|
+
|
|
68
|
+
### With Selection
|
|
69
|
+
|
|
70
|
+
```astro
|
|
71
|
+
<TreeView>
|
|
72
|
+
<TreeViewItem label="Documents" collapsible defaultOpen selectable>
|
|
73
|
+
<TreeViewItem slot="children" label="resume.pdf" selectable selected />
|
|
74
|
+
<TreeViewItem slot="children" label="cover-letter.pdf" selectable />
|
|
75
|
+
</TreeViewItem>
|
|
76
|
+
</TreeView>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### With Icons
|
|
80
|
+
|
|
81
|
+
```astro
|
|
82
|
+
<TreeView>
|
|
83
|
+
<TreeViewItem label="Folder" collapsible icon="<svg>...</svg>">
|
|
84
|
+
<TreeViewItem slot="children" label="file.txt" icon="<svg>...</svg>" />
|
|
85
|
+
</TreeViewItem>
|
|
86
|
+
</TreeView>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Different Sizes and Variants
|
|
90
|
+
|
|
91
|
+
```astro
|
|
92
|
+
<!-- Small subtle tree -->
|
|
93
|
+
<TreeView size="sm" variant="subtle">
|
|
94
|
+
<TreeViewItem label="Small item" />
|
|
95
|
+
</TreeView>
|
|
96
|
+
|
|
97
|
+
<!-- Large ghost tree -->
|
|
98
|
+
<TreeView size="lg" variant="ghost">
|
|
99
|
+
<TreeViewItem label="Large item" />
|
|
100
|
+
</TreeView>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Events
|
|
104
|
+
|
|
105
|
+
### tree-item-select
|
|
106
|
+
|
|
107
|
+
Fired when a selectable tree item is clicked or activated with keyboard.
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
document.addEventListener("tree-item-select", (event) => {
|
|
111
|
+
console.log("Selected item:", event.detail.label);
|
|
112
|
+
console.log("Item element:", event.detail.element);
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Accessibility
|
|
117
|
+
|
|
118
|
+
- Keyboard navigation with Enter and Space for selection
|
|
119
|
+
- Proper focus management with visible focus indicators
|
|
120
|
+
- ARIA attributes for screen readers
|
|
121
|
+
- Semantic HTML with `details`/`summary` for collapsible items
|
|
122
|
+
|
|
123
|
+
## Styling
|
|
124
|
+
|
|
125
|
+
The component uses CSS-in-JS with Panda CSS. You can customize styles using the `passThrough` prop:
|
|
126
|
+
|
|
127
|
+
```astro
|
|
128
|
+
<TreeView
|
|
129
|
+
passThrough={{
|
|
130
|
+
root: { backgroundColor: "gray.50" },
|
|
131
|
+
item: { borderRadius: "lg" },
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<!-- ... -->
|
|
135
|
+
</TreeView>
|
|
136
|
+
```
|
package/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Do not write code directly here, instead use the `src` folder!
|
|
2
|
+
// Then, use this file to export everything you want your user to access.
|
|
3
|
+
|
|
4
|
+
import TreeView from "./src/TreeView.astro";
|
|
5
|
+
import TreeViewItem from "./src/TreeViewItem.astro";
|
|
6
|
+
|
|
7
|
+
export * from "./src/TreeView.astro";
|
|
8
|
+
export * from "./src/TreeViewItem.astro";
|
|
9
|
+
export * from "./src/tree-utils";
|
|
10
|
+
|
|
11
|
+
export { TreeView, TreeViewItem };
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pindoba/astro-tree-view",
|
|
3
|
+
"version": "0.0.0-alpha-20250910225440",
|
|
4
|
+
"homepage": "https://github.com/EduardoLopes/pindoba/tree/main/packages/ui/astro/tree-view#README.md",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/EduardoLopes/pindoba.git",
|
|
8
|
+
"directory": "packages/ui/astro/tree-view"
|
|
9
|
+
},
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./index.ts"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"index.ts"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"astro-component",
|
|
22
|
+
"tree-view",
|
|
23
|
+
"navigation"
|
|
24
|
+
],
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@astrojs/check": "^0.9.4",
|
|
27
|
+
"astro": "^5.13.7",
|
|
28
|
+
"typescript": "^5.9.2"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"astro": "^4.0.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@pindoba/panda": "0.0.0-alpha-20250910225440",
|
|
35
|
+
"@pindoba/utils": "0.0.0-alpha-20250910225440",
|
|
36
|
+
"@pindoba/styles-tree-view": "0.0.0-alpha-20250910225440"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"lint": "TIMING=1 eslint . --max-warnings 0",
|
|
40
|
+
"lint:fix": "TIMING=1 eslint . --fix --max-warnings 0",
|
|
41
|
+
"format": "prettier --write .",
|
|
42
|
+
"format:fix": "prettier --write .",
|
|
43
|
+
"link": "pnpm link",
|
|
44
|
+
"unlink": "pnpm unlink",
|
|
45
|
+
"check": "astro check",
|
|
46
|
+
"dev": "astro check --watch",
|
|
47
|
+
"clean": "rm -rf .astro && rm -rf node_modules && rm -rf .turbo"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { type TreeNode, type TreeState, TreeUtils } from "./tree-utils";
|
|
3
|
+
import {
|
|
4
|
+
type TreeViewStyleProps,
|
|
5
|
+
type TreeViewSlots,
|
|
6
|
+
} from "@pindoba/styles-tree-view";
|
|
7
|
+
import type { SystemStyleObject } from "@pindoba/panda/types";
|
|
8
|
+
import TreeViewItem from "./TreeViewItem.astro";
|
|
9
|
+
|
|
10
|
+
export interface TreeNodeItemProps {
|
|
11
|
+
node: TreeNode;
|
|
12
|
+
state: TreeState;
|
|
13
|
+
size?: TreeViewStyleProps["size"];
|
|
14
|
+
emphasis?: TreeViewStyleProps["emphasis"];
|
|
15
|
+
passThrough?: { [key in TreeViewSlots]?: SystemStyleObject };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type Props = TreeNodeItemProps;
|
|
19
|
+
|
|
20
|
+
const { node, state, size, emphasis, passThrough } = Astro.props;
|
|
21
|
+
|
|
22
|
+
const isExpanded = TreeUtils.isExpanded(state, node.id);
|
|
23
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
hasChildren ? (
|
|
28
|
+
<TreeViewItem
|
|
29
|
+
label={node.label}
|
|
30
|
+
open={isExpanded}
|
|
31
|
+
size={size}
|
|
32
|
+
emphasis={emphasis}
|
|
33
|
+
passThrough={passThrough}
|
|
34
|
+
data-node-id={node.id}
|
|
35
|
+
>
|
|
36
|
+
{node.children &&
|
|
37
|
+
node.children.map((childNode) => (
|
|
38
|
+
<Astro.self
|
|
39
|
+
slot="children"
|
|
40
|
+
node={childNode}
|
|
41
|
+
state={state}
|
|
42
|
+
size={size}
|
|
43
|
+
emphasis={emphasis}
|
|
44
|
+
passThrough={passThrough}
|
|
45
|
+
/>
|
|
46
|
+
))}
|
|
47
|
+
</TreeViewItem>
|
|
48
|
+
) : (
|
|
49
|
+
<TreeViewItem
|
|
50
|
+
label={node.label}
|
|
51
|
+
size={size}
|
|
52
|
+
emphasis={emphasis}
|
|
53
|
+
passThrough={passThrough}
|
|
54
|
+
data-node-id={node.id}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { css, cx } from "@pindoba/panda/css";
|
|
3
|
+
import {
|
|
4
|
+
treeViewStyles,
|
|
5
|
+
type TreeViewSlots,
|
|
6
|
+
type TreeViewStyleProps,
|
|
7
|
+
} from "@pindoba/styles-tree-view";
|
|
8
|
+
import { type TreeNode, type TreeState, TreeUtils } from "./tree-utils";
|
|
9
|
+
import type { HTMLAttributes } from "astro/types";
|
|
10
|
+
import type { SystemStyleObject } from "@pindoba/panda/types";
|
|
11
|
+
import TreeNodeItem from "./TreeNodeItem.astro";
|
|
12
|
+
|
|
13
|
+
export interface TreeViewProps extends HTMLAttributes<"ul"> {
|
|
14
|
+
size?: TreeViewStyleProps["size"];
|
|
15
|
+
emphasis?: TreeViewStyleProps["emphasis"];
|
|
16
|
+
passThrough?: { [key in TreeViewSlots]?: SystemStyleObject };
|
|
17
|
+
// Imperative API props
|
|
18
|
+
data?: TreeNode[];
|
|
19
|
+
onToggle?: (nodeId: string, expanded: boolean) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Props = TreeViewProps;
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
class: className,
|
|
26
|
+
size = "md",
|
|
27
|
+
emphasis = "default",
|
|
28
|
+
passThrough,
|
|
29
|
+
data,
|
|
30
|
+
onToggle,
|
|
31
|
+
...props
|
|
32
|
+
} = Astro.props;
|
|
33
|
+
|
|
34
|
+
const classes = treeViewStyles.raw({ size, emphasis });
|
|
35
|
+
|
|
36
|
+
// Initialize state for imperative API
|
|
37
|
+
let treeState: TreeState | undefined;
|
|
38
|
+
if (data) {
|
|
39
|
+
treeState = TreeUtils.getInitialState(data);
|
|
40
|
+
}
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
<ul
|
|
44
|
+
class={cx(css(classes.root, passThrough?.root), className)}
|
|
45
|
+
data-tree-view-root
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
{
|
|
49
|
+
data && treeState ? (
|
|
50
|
+
data.map((node: TreeNode) => (
|
|
51
|
+
<TreeNodeItem
|
|
52
|
+
node={node}
|
|
53
|
+
state={treeState!}
|
|
54
|
+
size={size}
|
|
55
|
+
emphasis={emphasis}
|
|
56
|
+
passThrough={passThrough}
|
|
57
|
+
/>
|
|
58
|
+
))
|
|
59
|
+
) : (
|
|
60
|
+
<slot />
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
</ul>
|
|
64
|
+
|
|
65
|
+
<script
|
|
66
|
+
is:inline
|
|
67
|
+
define:vars={{ onToggleStr: onToggle?.toString(), hasData: !!data }}
|
|
68
|
+
>
|
|
69
|
+
// Add event handling for toggle interactions in imperative mode
|
|
70
|
+
if (hasData && onToggleStr) {
|
|
71
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
72
|
+
const treeRoot = document.querySelector("[data-tree-view-root]");
|
|
73
|
+
|
|
74
|
+
if (treeRoot) {
|
|
75
|
+
treeRoot.addEventListener("click", (event) => {
|
|
76
|
+
const target = event.target.closest("[data-node-id]");
|
|
77
|
+
if (target) {
|
|
78
|
+
const trigger = event.target.closest("summary");
|
|
79
|
+
if (trigger) {
|
|
80
|
+
const nodeId = target.getAttribute("data-node-id");
|
|
81
|
+
const details = trigger.parentElement;
|
|
82
|
+
const isExpanding = !details.hasAttribute("open");
|
|
83
|
+
|
|
84
|
+
// Dispatch custom event instead of direct function call
|
|
85
|
+
const toggleEvent = new CustomEvent("tree-node-toggle", {
|
|
86
|
+
detail: { nodeId, expanded: isExpanding },
|
|
87
|
+
});
|
|
88
|
+
document.dispatchEvent(toggleEvent);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
</script>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { css, cx } from "@pindoba/panda/css";
|
|
3
|
+
import {
|
|
4
|
+
treeViewStyles,
|
|
5
|
+
type TreeViewSlots,
|
|
6
|
+
type TreeViewStyleProps,
|
|
7
|
+
} from "@pindoba/styles-tree-view";
|
|
8
|
+
import type { HTMLAttributes } from "astro/types";
|
|
9
|
+
import type { SystemStyleObject } from "@pindoba/panda/types";
|
|
10
|
+
|
|
11
|
+
export interface TreeViewItemProps extends HTMLAttributes<"li"> {
|
|
12
|
+
label?: string;
|
|
13
|
+
open?: boolean;
|
|
14
|
+
size?: TreeViewStyleProps["size"];
|
|
15
|
+
emphasis?: TreeViewStyleProps["emphasis"];
|
|
16
|
+
passThrough?: { [key in TreeViewSlots]?: SystemStyleObject };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Props = TreeViewItemProps;
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
class: className,
|
|
23
|
+
label,
|
|
24
|
+
open = false,
|
|
25
|
+
size,
|
|
26
|
+
emphasis,
|
|
27
|
+
passThrough,
|
|
28
|
+
...props
|
|
29
|
+
} = Astro.props;
|
|
30
|
+
|
|
31
|
+
const classes = treeViewStyles.raw({ size, emphasis });
|
|
32
|
+
const hasChildren = Astro.slots.has("children");
|
|
33
|
+
const hasLabel = Astro.slots.has("label");
|
|
34
|
+
const hasLeadingElement = Astro.slots.has("leadingElement");
|
|
35
|
+
const hasTrailingElement = Astro.slots.has("trailingElement");
|
|
36
|
+
const displayLabel = hasLabel ? null : label;
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<li class={cx(css(classes.item, passThrough?.item), className)} {...props}>
|
|
40
|
+
{
|
|
41
|
+
hasChildren ? (
|
|
42
|
+
<details open={open}>
|
|
43
|
+
<summary
|
|
44
|
+
class={cx(
|
|
45
|
+
"tree-view-summary",
|
|
46
|
+
css(classes.summary, passThrough?.summary),
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
<span
|
|
50
|
+
class={cx(
|
|
51
|
+
"tree-view-trigger",
|
|
52
|
+
css(classes.trigger, passThrough?.trigger),
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
<svg
|
|
56
|
+
class={cx("tree-view-icon", css(classes.icon, passThrough?.icon))}
|
|
57
|
+
viewBox="0 0 24 24"
|
|
58
|
+
fill="none"
|
|
59
|
+
stroke="currentColor"
|
|
60
|
+
stroke-width="2"
|
|
61
|
+
stroke-linecap="round"
|
|
62
|
+
stroke-linejoin="round"
|
|
63
|
+
>
|
|
64
|
+
<path d="m9 18 6-6-6-6" />
|
|
65
|
+
</svg>
|
|
66
|
+
</span>
|
|
67
|
+
|
|
68
|
+
{hasLeadingElement && (
|
|
69
|
+
<div
|
|
70
|
+
class={cx(
|
|
71
|
+
"tree-view-leading-element",
|
|
72
|
+
css(classes.leadingElement, passThrough?.leadingElement),
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
<slot name="leadingElement" />
|
|
76
|
+
</div>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
<span class={css(classes.label, passThrough?.label)}>
|
|
80
|
+
{hasLabel ? <slot name="label" /> : displayLabel}
|
|
81
|
+
</span>
|
|
82
|
+
{hasTrailingElement && (
|
|
83
|
+
<div
|
|
84
|
+
class={cx(
|
|
85
|
+
"tree-view-trailing-element",
|
|
86
|
+
css(classes.trailingElement, passThrough?.trailingElement),
|
|
87
|
+
)}
|
|
88
|
+
>
|
|
89
|
+
<slot name="trailingElement" />
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
</summary>
|
|
93
|
+
<ul class={css(classes.children, passThrough?.children)}>
|
|
94
|
+
<slot name="children" />
|
|
95
|
+
</ul>
|
|
96
|
+
</details>
|
|
97
|
+
) : (
|
|
98
|
+
<div
|
|
99
|
+
class={cx(
|
|
100
|
+
"tree-view-summary",
|
|
101
|
+
css(classes.summary, passThrough?.summary),
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
{hasLeadingElement && (
|
|
105
|
+
<div
|
|
106
|
+
class={cx(
|
|
107
|
+
"tree-view-leading-element",
|
|
108
|
+
css(classes.leadingElement, passThrough?.leadingElement),
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
111
|
+
<slot name="leadingElement" />
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
<span class={css(classes.label, passThrough?.label)}>
|
|
115
|
+
{hasLabel ? <slot name="label" /> : displayLabel}
|
|
116
|
+
</span>
|
|
117
|
+
{hasTrailingElement && (
|
|
118
|
+
<div
|
|
119
|
+
class={cx(
|
|
120
|
+
"tree-view-trailing-element",
|
|
121
|
+
css(classes.trailingElement, passThrough?.trailingElement),
|
|
122
|
+
)}
|
|
123
|
+
>
|
|
124
|
+
<slot name="trailingElement" />
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
</li>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Tree data structure interfaces
|
|
2
|
+
export interface TreeNode {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
expanded?: boolean;
|
|
6
|
+
children?: TreeNode[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface TreeState {
|
|
10
|
+
expandedNodes: Set<string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Tree manipulation utilities
|
|
14
|
+
export class TreeUtils {
|
|
15
|
+
static expandNode(state: TreeState, nodeId: string): TreeState {
|
|
16
|
+
const newExpandedNodes = new Set(state.expandedNodes);
|
|
17
|
+
newExpandedNodes.add(nodeId);
|
|
18
|
+
return { expandedNodes: newExpandedNodes };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static collapseNode(state: TreeState, nodeId: string): TreeState {
|
|
22
|
+
const newExpandedNodes = new Set(state.expandedNodes);
|
|
23
|
+
newExpandedNodes.delete(nodeId);
|
|
24
|
+
return { expandedNodes: newExpandedNodes };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static toggleNode(state: TreeState, nodeId: string): TreeState {
|
|
28
|
+
return state.expandedNodes.has(nodeId)
|
|
29
|
+
? this.collapseNode(state, nodeId)
|
|
30
|
+
: this.expandNode(state, nodeId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static isExpanded(state: TreeState, nodeId: string): boolean {
|
|
34
|
+
return state.expandedNodes.has(nodeId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static getInitialState(data: TreeNode[]): TreeState {
|
|
38
|
+
const expandedNodes = new Set<string>();
|
|
39
|
+
|
|
40
|
+
const collectExpanded = (nodes: TreeNode[]) => {
|
|
41
|
+
nodes.forEach((node) => {
|
|
42
|
+
if (node.expanded) {
|
|
43
|
+
expandedNodes.add(node.id);
|
|
44
|
+
}
|
|
45
|
+
if (node.children) {
|
|
46
|
+
collectExpanded(node.children);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
collectExpanded(data);
|
|
52
|
+
return { expandedNodes };
|
|
53
|
+
}
|
|
54
|
+
}
|