Skip to content

Commit 8bf23e3

Browse files
authored
Merge pull request #61 from abdel-17/extendable-node
feat: allow extending `FileNode` and `FolderNode` classes
2 parents f5baffc + 7d162a5 commit 8bf23e3

28 files changed

+1795
-1838
lines changed

packages/svelte-file-tree/package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,19 @@
3838
"svelte-signals": "^0.0.2"
3939
},
4040
"devDependencies": {
41-
"@sveltejs/kit": "^2.20.2",
41+
"@sveltejs/kit": "^2.20.4",
4242
"@sveltejs/package": "^2.3.10",
4343
"@sveltejs/vite-plugin-svelte": "5.0.3",
44-
"@types/node": "^22.13.14",
45-
"@vitest/browser": "^3.0.9",
44+
"@types/node": "^22.14.0",
45+
"@vitest/browser": "^3.1.1",
4646
"jsdom": "^26.0.0",
4747
"prettier": "^3.5.3",
4848
"prettier-plugin-svelte": "^3.3.3",
49-
"publint": "^0.3.9",
50-
"svelte": "5.25.3",
49+
"publint": "^0.3.10",
50+
"svelte": "5.25.6",
5151
"svelte-check": "^4.1.5",
52-
"vite": "^6.2.3",
53-
"vitest": "3.0.9",
52+
"vite": "^6.2.5",
53+
"vitest": "3.1.1",
5454
"vitest-browser-svelte": "^0.1.0"
5555
},
5656
"repository": {

packages/svelte-file-tree/src/lib/components/Tree/Tree.svelte

+33-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
<script lang="ts" generics="TData extends FileTreeNodeData = FileTreeNodeData">
2-
import type { FileTreeNodeData } from "$lib/tree.svelte.js";
1+
<script lang="ts" generics="TNode extends FileNode | FolderNode<TNode> = FileTreeNode">
2+
import { FileNode, FolderNode, type FileTreeNode } from "$lib/tree.svelte.js";
3+
import { DEV } from "esm-env";
34
import { SvelteSet } from "svelte/reactivity";
45
import TreeItemProvider from "./TreeItemProvider.svelte";
56
import { createTreeState } from "./state.svelte.js";
@@ -16,13 +17,35 @@
1617
defaultClipboardIds,
1718
clipboardIds = new SvelteSet(defaultClipboardIds),
1819
pasteOperation = $bindable(),
19-
isItemEditable = false,
20+
isItemEditable = true,
2021
isItemDisabled = false,
2122
id = defaultId,
2223
ref = $bindable(null),
23-
generateCopyId = () => crypto.randomUUID(),
24+
copyNode = function copyNode(node): TNode {
25+
if (DEV && node.constructor !== FileNode && node.constructor !== FolderNode) {
26+
throw new Error(
27+
"Cannot copy an object that extends from `FileNode` or `FolderNode`. Pass a `copyNode` prop to specify how the object should be copied.",
28+
);
29+
}
30+
31+
switch (node.type) {
32+
case "file": {
33+
return new FileNode({
34+
id: crypto.randomUUID(),
35+
name: node.name,
36+
}) as TNode;
37+
}
38+
case "folder": {
39+
return new FolderNode({
40+
id: crypto.randomUUID(),
41+
name: node.name,
42+
children: node.children.map(copyNode),
43+
}) as TNode;
44+
}
45+
}
46+
},
2447
onRenameItem = ({ target, name }) => {
25-
target.data.name = name;
48+
target.name = name;
2649
return true;
2750
},
2851
onMoveItems = ({ updates }) => {
@@ -45,7 +68,7 @@
4568
onAlreadyExistsError,
4669
onCircularReferenceError,
4770
...rest
48-
}: TreeProps<TData> = $props();
71+
}: TreeProps<TNode> = $props();
4972
5073
const treeState = createTreeState({
5174
tree: () => tree,
@@ -71,7 +94,7 @@
7194
return isItemDisabled;
7295
},
7396
id: () => id,
74-
generateCopyId: () => generateCopyId(),
97+
copyNode: (node) => copyNode(node),
7598
onRenameItem: (args) => onRenameItem(args),
7699
onMoveItems: (args) => onMoveItems(args),
77100
onCopyPasteItems: (args) => onCopyPasteItems(args),
@@ -80,23 +103,15 @@
80103
onAlreadyExistsError: (args) => onAlreadyExistsError?.(args),
81104
onCircularReferenceError: (args) => onCircularReferenceError?.(args),
82105
});
106+
107+
export const { rename, copy, paste, remove } = treeState;
83108
</script>
84109

85110
<div {...rest} bind:this={ref} {id} role="tree" aria-multiselectable="true">
86111
{#each treeState.items() as i (i.node.id)}
87112
<TreeItemProvider {treeState} item={i}>
88113
{#if i.visible()}
89-
{@render item({
90-
item: i,
91-
select: () => selectedIds.add(i.node.id),
92-
deselect: () => selectedIds.delete(i.node.id),
93-
expand: () => expandedIds.add(i.node.id),
94-
collapse: () => expandedIds.delete(i.node.id),
95-
rename: (name) => treeState.rename(i, name),
96-
copy: (operation) => treeState.copy(i, operation),
97-
paste: (position) => treeState.paste(i, position),
98-
remove: () => treeState.remove(i),
99-
})}
114+
{@render item({ item: i })}
100115
{/if}
101116
</TreeItemProvider>
102117
{/each}

packages/svelte-file-tree/src/lib/components/Tree/TreeItemProvider.svelte

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
<script lang="ts" module>
2-
import type { FileTreeNodeData } from "$lib/tree.svelte.js";
2+
import type { FileNode, FileTreeNode, FolderNode } from "$lib/tree.svelte.js";
33
import { DEV } from "esm-env";
44
import { getContext, hasContext, setContext, type Snippet } from "svelte";
55
import type { TreeState } from "./state.svelte.js";
66
import type { TreeItemState } from "./types.js";
77
88
const CONTEXT_KEY = Symbol("TreeItemProvider");
99
10-
export type TreeItemProviderContext<TData extends FileTreeNodeData = FileTreeNodeData> = {
11-
treeState: TreeState<TData>;
12-
item: () => TreeItemState<TData>;
10+
export type TreeItemProviderContext<TNode extends FileNode | FolderNode<TNode> = FileTreeNode> = {
11+
treeState: TreeState<TNode>;
12+
item: () => TreeItemState<TNode>;
1313
};
1414
1515
export function getTreeItemProviderContext(): TreeItemProviderContext {
@@ -21,18 +21,18 @@
2121
}
2222
</script>
2323

24-
<script lang="ts" generics="TData extends FileTreeNodeData = FileTreeNodeData">
24+
<script lang="ts" generics="TNode extends FileNode | FolderNode<TNode> = FileTreeNode">
2525
const {
2626
treeState,
2727
item,
2828
children,
2929
}: {
30-
treeState: TreeState<TData>;
31-
item: TreeItemState<TData>;
30+
treeState: TreeState<TNode>;
31+
item: TreeItemState<TNode>;
3232
children: Snippet;
3333
} = $props();
3434
35-
const context: TreeItemProviderContext<TData> = {
35+
const context: TreeItemProviderContext<TNode> = {
3636
treeState,
3737
item: () => item,
3838
};

0 commit comments

Comments
 (0)