Skip to content

Commit 4b483bf

Browse files
authored
Merge pull request #37 from abdel-17/preserve-state
Preserve State
2 parents bfe6b04 + f8dc2f4 commit 4b483bf

29 files changed

+1819
-1729
lines changed

eslint.config.js

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ export default tseslint.config(
1414
languageOptions: {
1515
globals: {
1616
...globals.browser,
17+
...globals.node,
1718
},
1819
parserOptions: {
1920
projectService: true,
21+
tsconfigRootDir: import.meta.dirname,
2022
extraFileExtensions,
2123
},
2224
},
@@ -33,7 +35,6 @@ export default tseslint.config(
3335
},
3436
{
3537
rules: {
36-
"@typescript-eslint/no-non-null-assertion": "off",
3738
"@typescript-eslint/no-unnecessary-condition": [
3839
"error",
3940
{
@@ -54,27 +55,19 @@ export default tseslint.config(
5455
},
5556
],
5657
"@typescript-eslint/consistent-type-definitions": "off",
57-
"@typescript-eslint/unbound-method": "off",
58-
"@typescript-eslint/no-namespace": [
58+
"@typescript-eslint/strict-boolean-expressions": [
5959
"error",
6060
{
61-
allowDeclarations: true,
61+
allowNullableObject: false,
62+
allowNumber: false,
63+
allowString: false,
6264
},
6365
],
6466
},
6567
},
6668
{
6769
files: ["**/*.svelte"],
68-
rules: {
69-
// Typed linting is not fully supported in Svelte files,
70-
// which causes a lot of false positives.
71-
"@typescript-eslint/no-confusing-void-expression": "off",
72-
"@typescript-eslint/no-unsafe-argument": "off",
73-
"@typescript-eslint/no-unsafe-assignment": "off",
74-
"@typescript-eslint/no-unsafe-call": "off",
75-
"@typescript-eslint/no-unsafe-member-access": "off",
76-
"@typescript-eslint/no-unsafe-return": "off",
77-
},
70+
extends: [tseslint.configs.disableTypeChecked],
7871
},
7972
{
8073
ignores: ["**/.svelte-kit", "**/dist", "**/build"],

packages/svelte-file-tree/package.json

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,27 @@
1414
"test": "vitest --run",
1515
"test:watch": "vitest --watch"
1616
},
17-
"exports": {
18-
".": {
19-
"types": "./dist/index.d.ts",
20-
"svelte": "./dist/index.js"
21-
}
22-
},
23-
"sideEffects": [
24-
"**/*.css"
25-
],
2617
"files": [
2718
"dist",
2819
"!dist/**/*.spec.*",
2920
"!dist/**/*.test.*"
3021
],
31-
"repository": {
32-
"type": "git",
33-
"url": "git+https://github.com/abdel-17/svelte-file-tree.git",
34-
"directory": "packages/svelte-file-tree"
22+
"sideEffects": [
23+
"**/*.css"
24+
],
25+
"exports": {
26+
".": {
27+
"types": "./dist/index.d.ts",
28+
"svelte": "./dist/index.js"
29+
}
3530
},
3631
"peerDependencies": {
3732
"svelte": "^5.20.0"
3833
},
34+
"dependencies": {
35+
"esm-env": "^1.2.2",
36+
"svelte-signals": "^0.0.2"
37+
},
3938
"devDependencies": {
4039
"@sveltejs/kit": "^2.18.0",
4140
"@sveltejs/package": "^2.3.10",
@@ -52,7 +51,9 @@
5251
"vitest": "3.0.7",
5352
"vitest-browser-svelte": "^0.1.0"
5453
},
55-
"dependencies": {
56-
"esm-env": "^1.2.2"
54+
"repository": {
55+
"type": "git",
56+
"url": "git+https://github.com/abdel-17/svelte-file-tree.git",
57+
"directory": "packages/svelte-file-tree"
5758
}
5859
}
Lines changed: 46 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,74 @@
1-
<script lang="ts" module>
2-
import type { FileTree } from "$lib/tree.svelte.js";
3-
import { DEV } from "esm-env";
4-
import { getContext, hasContext, setContext } from "svelte";
5-
import TreeItemContextProvider from "./TreeItemContextProvider.svelte";
6-
import { TreeContext } from "./state.svelte.js";
1+
<script lang="ts">
2+
import { SvelteSet } from "svelte/reactivity";
3+
import TreeItemProvider from "./TreeItemProvider.svelte";
4+
import { createTreeState } from "./state.svelte.js";
75
import type { TreeProps } from "./types.js";
86
9-
const CONTEXT_KEY = Symbol("Tree");
10-
11-
export function getTreeContext(): TreeContext {
12-
if (DEV && !hasContext(CONTEXT_KEY)) {
13-
throw new Error("No parent <Tree> found");
14-
}
15-
return getContext(CONTEXT_KEY);
16-
}
17-
</script>
18-
19-
<script lang="ts">
207
const defaultId = $props.id();
218
let {
229
tree,
2310
item,
11+
defaultSelectedIds,
12+
selectedIds = new SvelteSet(defaultSelectedIds),
13+
defaultExpandedIds,
14+
expandedIds = new SvelteSet(defaultExpandedIds),
15+
defaultClipboardIds,
16+
clipboardIds = new SvelteSet(defaultClipboardIds),
2417
pasteOperation = $bindable(),
25-
editable = false,
26-
disabled = false,
18+
isItemEditable = false,
19+
isItemDisabled = false,
2720
id = defaultId,
28-
element = $bindable(null),
21+
ref = $bindable(null),
2922
generateCopyId = () => crypto.randomUUID(),
30-
onRenameItem = (args) => {
31-
args.target.name = args.name;
23+
onRenameItem = ({ target, name }) => {
24+
target.name = name;
3225
return true;
3326
},
3427
onRenameError,
35-
onMoveItems = (args) => {
36-
for (const { target, children } of args.updates) {
28+
onMoveItems = ({ updates }) => {
29+
for (const { target, children } of updates) {
3730
target.children = children;
3831
}
3932
return true;
4033
},
4134
onMoveError,
42-
onInsertItems = (args) => {
43-
args.target.children.splice(args.start, 0, ...args.inserted);
35+
onInsertItems = ({ target, start, inserted }) => {
36+
target.children.splice(start, 0, ...inserted);
4437
return true;
4538
},
4639
onNameConflict = () => "cancel",
47-
onDeleteItems = (args) => {
48-
for (const { target, children } of args.updates) {
40+
onDeleteItems = ({ updates }) => {
41+
for (const { target, children } of updates) {
4942
target.children = children;
5043
}
5144
return true;
5245
},
5346
...rest
5447
}: TreeProps = $props();
5548
56-
const context = new TreeContext({
49+
const treeState = createTreeState({
5750
tree: () => tree,
51+
selectedIds: () => selectedIds,
52+
expandedIds: () => expandedIds,
53+
clipboardIds: () => clipboardIds,
5854
pasteOperation: () => pasteOperation,
5955
setPasteOperation: (value) => {
6056
pasteOperation = value;
6157
},
58+
isItemEditable: (node) => {
59+
if (typeof isItemEditable === "function") {
60+
return isItemEditable(node);
61+
}
62+
63+
return isItemEditable;
64+
},
65+
isItemDisabled: (node) => {
66+
if (typeof isItemDisabled === "function") {
67+
return isItemDisabled(node);
68+
}
69+
70+
return isItemDisabled;
71+
},
6272
id: () => id,
6373
generateCopyId: () => generateCopyId(),
6474
onRenameItem: (args) => onRenameItem(args),
@@ -69,28 +79,14 @@
6979
onNameConflict: (args) => onNameConflict(args),
7080
onDeleteItems: (args) => onDeleteItems(args),
7181
});
72-
setContext(CONTEXT_KEY, context);
7382
</script>
7483

75-
{#snippet items(nodes: Array<FileTree.Node>)}
76-
{#each nodes as node, index (node.id)}
77-
<TreeItemContextProvider
78-
{node}
79-
{index}
80-
editable={typeof editable === "function" ? editable(node) : editable}
81-
disabled={typeof disabled === "function" ? disabled(node) : disabled}
82-
>
83-
{#snippet children(args)}
84-
{@render item(args)}
85-
86-
{#if node.type === "folder" && node.expanded}
87-
{@render items(node.children)}
88-
{/if}
89-
{/snippet}
90-
</TreeItemContextProvider>
84+
<div {...rest} bind:this={ref} {id} role="tree" aria-multiselectable="true">
85+
{#each treeState.items() as i (i.node.id)}
86+
<TreeItemProvider {treeState} item={i}>
87+
{#if i.visible()}
88+
{@render item(i)}
89+
{/if}
90+
</TreeItemProvider>
9191
{/each}
92-
{/snippet}
93-
94-
<div {...rest} bind:this={element} {id} role="tree" aria-multiselectable="true">
95-
{@render items(tree.children)}
9692
</div>

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

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script lang="ts" module>
2+
import { DEV } from "esm-env";
3+
import { getContext, hasContext, setContext, type Snippet } from "svelte";
4+
import type { TreeState } from "./state.svelte.js";
5+
import type { TreeItemState } from "./types.js";
6+
7+
const CONTEXT_KEY = Symbol("TreeItemProvider");
8+
9+
export type TreeItemProviderContext = {
10+
treeState: TreeState;
11+
item: () => TreeItemState;
12+
};
13+
14+
export function getTreeItemProviderContext(): TreeItemProviderContext {
15+
if (DEV && !hasContext(CONTEXT_KEY)) {
16+
throw new Error("No parent <Tree> found");
17+
}
18+
19+
return getContext(CONTEXT_KEY);
20+
}
21+
22+
function setTreeItemProviderContext(context: TreeItemProviderContext): void {
23+
setContext(CONTEXT_KEY, context);
24+
}
25+
</script>
26+
27+
<script lang="ts">
28+
const {
29+
treeState,
30+
item,
31+
children,
32+
}: {
33+
treeState: TreeState;
34+
item: TreeItemState;
35+
children: Snippet;
36+
} = $props();
37+
38+
setTreeItemProviderContext({
39+
treeState,
40+
item: () => item,
41+
});
42+
43+
$effect(() => {
44+
return () => {
45+
treeState.onDeleteItem(item.node.id);
46+
};
47+
});
48+
</script>
49+
50+
{@render children()}

0 commit comments

Comments
 (0)