From afb7c32532bae955369341ec9ee25d7a914894b2 Mon Sep 17 00:00:00 2001 From: Teemu Koivisto Date: Sun, 20 Jul 2025 11:49:33 +0300 Subject: [PATCH 01/10] refactor stores into one --- packages/site/src/components/DiffValue.svelte | 4 +- .../site/src/components/TailwindNode.svelte | 4 +- .../src/lib/DefaultNode.svelte | 4 +- .../svelte-tree-view/src/lib/TreeView.svelte | 25 +++---- .../src/lib/TreeViewNode.svelte | 18 +++-- .../tree.svelte.ts => store.svelte.ts} | 70 +++++++++++++++---- .../svelte-tree-view/src/lib/stores/index.ts | 13 ---- .../svelte-tree-view/src/lib/stores/props.ts | 48 ------------- .../src/lib/stores/root-element.ts | 12 ---- packages/svelte-tree-view/src/lib/types.ts | 4 +- 10 files changed, 81 insertions(+), 121 deletions(-) rename packages/svelte-tree-view/src/lib/{stores/tree.svelte.ts => store.svelte.ts} (55%) delete mode 100644 packages/svelte-tree-view/src/lib/stores/index.ts delete mode 100644 packages/svelte-tree-view/src/lib/stores/props.ts delete mode 100644 packages/svelte-tree-view/src/lib/stores/root-element.ts diff --git a/packages/site/src/components/DiffValue.svelte b/packages/site/src/components/DiffValue.svelte index 2bae307..ba6d48a 100644 --- a/packages/site/src/components/DiffValue.svelte +++ b/packages/site/src/components/DiffValue.svelte @@ -5,9 +5,7 @@ let props: NodeProps = $props() let value = $derived(props.node.getValue()) - const { - propsStore: { formatValue } - } = props.getTreeContext() + const { formatValue } = props.getTreeContext() function replaceSpacesWithNonBreakingSpace(value: string) { return value.replace(/\s/gm, ' ') diff --git a/packages/site/src/components/TailwindNode.svelte b/packages/site/src/components/TailwindNode.svelte index 6d8884b..27e9550 100644 --- a/packages/site/src/components/TailwindNode.svelte +++ b/packages/site/src/components/TailwindNode.svelte @@ -10,9 +10,7 @@ handleToggleCollapse }: NodeProps = $props() - const { - propsStore: { props: propsObj } - } = getTreeContext() + const { props: propsObj } = getTreeContext() let hasChildren = $derived(node.children.length > 0) let descend = $derived(!node.collapsed && hasChildren) diff --git a/packages/svelte-tree-view/src/lib/DefaultNode.svelte b/packages/svelte-tree-view/src/lib/DefaultNode.svelte index 690c25c..ca7516c 100644 --- a/packages/svelte-tree-view/src/lib/DefaultNode.svelte +++ b/packages/svelte-tree-view/src/lib/DefaultNode.svelte @@ -17,9 +17,7 @@ handleCopyNodeToClipboard, handleToggleCollapse }: DefaultNodeProps = $props() - const { - propsStore: { props: propsObj, formatValue } - } = getTreeContext() + const { props: propsObj, formatValue } = getTreeContext() let hasChildren = $derived(node.children.length > 0) let descend = $derived(!node.collapsed && hasChildren) let valueStr = $derived(formatValue(node.getValue(), node)) diff --git a/packages/svelte-tree-view/src/lib/TreeView.svelte b/packages/svelte-tree-view/src/lib/TreeView.svelte index 491c588..9d2d0dd 100644 --- a/packages/svelte-tree-view/src/lib/TreeView.svelte +++ b/packages/svelte-tree-view/src/lib/TreeView.svelte @@ -3,9 +3,8 @@ import { get } from 'svelte/store' import TreeViewNode from './TreeViewNode.svelte' - import { createPropsStore, createRootElementStore, createTreeStore } from './stores' + import { createStore, type TreeStore } from './store.svelte' - import type { Stores } from './stores' import type { Props, TreeViewProps } from './types' const DEFAULT_RECURSION_OPTS = { @@ -38,20 +37,14 @@ onUpdate } let rootElement: HTMLElement - const propsStore = createPropsStore(propsObj) - const rootElementStore = createRootElementStore() - const treeStore = createTreeStore(propsStore) + const store = createStore(propsObj) const newRecOpts = $derived({ ...DEFAULT_RECURSION_OPTS, ...recursionOpts }) - const treeChildren = $derived(treeStore.rootNode.children) + const treeChildren = $derived(store.rootNode.children) - setContext('svelte-tree-view', { - propsStore, - rootElementStore, - treeStore - }) + setContext('svelte-tree-view', store) onMount(() => { - rootElementStore.set(rootElement) + store.setRootElement(rootElement) }) $effect(() => { @@ -66,19 +59,19 @@ valueFormatter, onUpdate } - propsStore.setProps(propsObj) + store.setProps(propsObj) }) $effect(() => { - const oldRecOptions = get(propsStore.recursionOpts) + const oldRecOptions = get(store.recursionOpts) // Destruct recursionOpts to unwrap from proxy const opts = { ...newRecOpts } const newData = data const shouldRecompute = oldRecOptions?.shouldExpandNode !== opts.shouldExpandNode // Use untrack to prevent triggering this effect again untrack(() => { - treeStore.recompute(newData, opts, shouldRecompute) - propsStore.setProps(propsObj) + store.recompute(newData, opts, shouldRecompute) + store.setProps(propsObj) propsObj.recursionOpts = opts }) }) diff --git a/packages/svelte-tree-view/src/lib/TreeViewNode.svelte b/packages/svelte-tree-view/src/lib/TreeViewNode.svelte index 8b81102..b57edb0 100644 --- a/packages/svelte-tree-view/src/lib/TreeViewNode.svelte +++ b/packages/svelte-tree-view/src/lib/TreeViewNode.svelte @@ -3,7 +3,7 @@ import TreeViewNode from './TreeViewNode.svelte' - import type { Stores } from './stores' + import type { TreeStore } from './store.svelte' import type { TreeNode } from './types' interface Props { @@ -12,13 +12,17 @@ let { id }: Props = $props() - const { treeStore, propsStore, rootElementStore } = getContext('svelte-tree-view') - let { props: propsObj } = propsStore - let node = $state(treeStore.treeMap[id] as TreeNode) + const { + rootElementStore, + treeMap, + props: propsObj, + ...rest + } = getContext('svelte-tree-view') + let node = $state(treeMap[id] as TreeNode) let hasChildren = $derived(node && node.children.length > 0) let nodeProps = $derived({ node, - getTreeContext: () => getContext('svelte-tree-view'), + getTreeContext: () => getContext('svelte-tree-view'), TreeViewNode: TreeViewNode, handleLogNode() { console.info('%c [svelte-tree-view]: Property added to window._node', 'color: #b8e248') @@ -38,9 +42,9 @@ }, handleToggleCollapse() { if (hasChildren) { - treeStore.toggleCollapse(node.id) + rest.toggleCollapse(node.id) } else if (node.circularOfId) { - treeStore.expandAllNodesToNode(node.circularOfId) + rest.expandAllNodesToNode(node.circularOfId) $rootElementStore ?.querySelector(`li[data-tree-id="${node.circularOfId}"]`) ?.scrollIntoView() diff --git a/packages/svelte-tree-view/src/lib/stores/tree.svelte.ts b/packages/svelte-tree-view/src/lib/store.svelte.ts similarity index 55% rename from packages/svelte-tree-view/src/lib/stores/tree.svelte.ts rename to packages/svelte-tree-view/src/lib/store.svelte.ts index d32c66c..ffb6c43 100644 --- a/packages/svelte-tree-view/src/lib/stores/tree.svelte.ts +++ b/packages/svelte-tree-view/src/lib/store.svelte.ts @@ -1,22 +1,64 @@ -import { get } from 'svelte/store' +import { derived, get, writable } from 'svelte/store' -import { createNode, recurseObjectProperties } from '../tree-utils.svelte' -import type { TreeNode, TreeRecursionOpts } from '../types' -import type { PropsStore } from './props' +import { createNode, recurseObjectProperties } from './tree-utils.svelte' +import type { TreeNode, TreeRecursionOpts, TreeViewProps } from './types' -export type TreeStore = ReturnType +export type TreeStore = ReturnType -export const createTreeStore = (propsStore: PropsStore) => { +export const createStore = (initialProps: Omit) => { const [defaultRootNode] = createNode(-1, 'root', [], 0, null, {}) const treeMap = $state>({ [defaultRootNode.id]: defaultRootNode }) const rootNode = $derived(treeMap[defaultRootNode.id]) + const rootElementStore = writable(null) + const props = writable>(initialProps) + const recursionOpts = derived(props, p => p.recursionOpts) const iteratedValues = new Map() return { - treeMap, + props, + recursionOpts, + rootElementStore, rootNode, + treeMap, + + setProps(newProps: Omit) { + props.set(newProps) + }, + + setRootElement(el: HTMLElement | null) { + rootElementStore.set(el) + }, + + formatValue(val: any, node: TreeNode): string { + const { valueFormatter } = get(props) + const customFormat = valueFormatter ? valueFormatter(val, node) : undefined + if (customFormat) { + return customFormat + } + switch (node.type) { + case 'array': + return `${node.circularOfId ? 'circular' : ''} [] ${val.length} items` + case 'object': + return `${node.circularOfId ? 'circular' : ''} {} ${Object.keys(val).length} keys` + case 'map': + case 'set': + return `${node.circularOfId ? 'circular' : ''} () ${val.size} entries` + case 'date': + return `${val.toISOString()}` + case 'string': + return `"${val}"` + case 'number': + return val + case 'boolean': + return val ? 'true' : 'false' + case 'symbol': + return String(val) + default: + return node.type + } + }, recompute(data: unknown, recursionOpts: TreeRecursionOpts, recomputeExpandNode: boolean) { const oldIds = new Set(Object.keys(treeMap)) @@ -37,7 +79,7 @@ export const createTreeStore = (propsStore: PropsStore) => { for (const id of oldIds) { delete treeMap[id] } - get(propsStore.props).onUpdate?.(treeMap) + get(props).onUpdate?.(treeMap) }, toggleCollapse(id: string) { @@ -46,11 +88,11 @@ export const createTreeStore = (propsStore: PropsStore) => { return console.warn(`Attempted to collapse non-existent node: ${id}`) } node.collapsed = !node.collapsed - const recursionOpts = get(propsStore.recursionOpts) - if (recursionOpts) { - this.expandNodeChildren(node, recursionOpts) + const recurOpts = get(recursionOpts) + if (recurOpts) { + this.expandNodeChildren(node, recurOpts) } else { - get(propsStore.props).onUpdate?.(treeMap) + get(props).onUpdate?.(treeMap) } }, @@ -77,7 +119,7 @@ export const createTreeStore = (propsStore: PropsStore) => { if (!nodeWithUpdatedChildren) return treeMap[nodeWithUpdatedChildren.id] = nodeWithUpdatedChildren treeMap[parent.id] = parent - get(propsStore.props).onUpdate?.(treeMap) + get(props).onUpdate?.(treeMap) }, expandAllNodesToNode(id: string) { @@ -93,7 +135,7 @@ export const createTreeStore = (propsStore: PropsStore) => { } const updated = treeMap recurseNodeUpwards(updated, updated[id]) - get(propsStore.props).onUpdate?.(updated) + get(props).onUpdate?.(updated) } } } diff --git a/packages/svelte-tree-view/src/lib/stores/index.ts b/packages/svelte-tree-view/src/lib/stores/index.ts deleted file mode 100644 index d261522..0000000 --- a/packages/svelte-tree-view/src/lib/stores/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { PropsStore } from './props' -import type { RootElementStore } from './root-element' -import type { TreeStore } from './tree.svelte' - -export { createPropsStore } from './props' -export { createRootElementStore } from './root-element' -export { createTreeStore } from './tree.svelte' - -export interface Stores { - propsStore: PropsStore - rootElementStore: RootElementStore - treeStore: TreeStore -} diff --git a/packages/svelte-tree-view/src/lib/stores/props.ts b/packages/svelte-tree-view/src/lib/stores/props.ts deleted file mode 100644 index 44c7672..0000000 --- a/packages/svelte-tree-view/src/lib/stores/props.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { derived, get, writable } from 'svelte/store' - -import type { TreeNode, TreeViewProps } from '../types' - -export type PropsStore = ReturnType - -export const createPropsStore = (initialProps: Omit) => { - const props = writable>(initialProps) - const recursionOpts = derived(props, p => p.recursionOpts) - - return { - props, - recursionOpts, - - setProps(newProps: Omit) { - props.set(newProps) - }, - - formatValue(val: any, node: TreeNode): string { - const { valueFormatter } = get(props) - const customFormat = valueFormatter ? valueFormatter(val, node) : undefined - if (customFormat) { - return customFormat - } - switch (node.type) { - case 'array': - return `${node.circularOfId ? 'circular' : ''} [] ${val.length} items` - case 'object': - return `${node.circularOfId ? 'circular' : ''} {} ${Object.keys(val).length} keys` - case 'map': - case 'set': - return `${node.circularOfId ? 'circular' : ''} () ${val.size} entries` - case 'date': - return `${val.toISOString()}` - case 'string': - return `"${val}"` - case 'number': - return val - case 'boolean': - return val ? 'true' : 'false' - case 'symbol': - return String(val) - default: - return node.type - } - } - } -} diff --git a/packages/svelte-tree-view/src/lib/stores/root-element.ts b/packages/svelte-tree-view/src/lib/stores/root-element.ts deleted file mode 100644 index e7ef22c..0000000 --- a/packages/svelte-tree-view/src/lib/stores/root-element.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { writable } from 'svelte/store' - -export type RootElementStore = ReturnType - -export const createRootElementStore = () => { - const rootElementStore = writable(null) - - return { - set: rootElementStore.set, - subscribe: rootElementStore.subscribe - } -} diff --git a/packages/svelte-tree-view/src/lib/types.ts b/packages/svelte-tree-view/src/lib/types.ts index 2b278b0..aa972da 100644 --- a/packages/svelte-tree-view/src/lib/types.ts +++ b/packages/svelte-tree-view/src/lib/types.ts @@ -1,6 +1,6 @@ import type { Component, Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' -import type { Stores } from './stores' +import type { TreeStore } from './store.svelte' export type ValueType = | 'array' @@ -155,7 +155,7 @@ export interface TreeRecursionOpts { export interface NodeProps { node: TreeNode TreeViewNode: Component<{ id: string }> - getTreeContext: () => Stores + getTreeContext: () => TreeStore handleLogNode(): void handleCopyNodeToClipboard(): void handleToggleCollapse(): void From a05edc8c4ad21c1a8d094673ed5127d5eef7eb77 Mon Sep 17 00:00:00 2001 From: Teemu Koivisto Date: Sun, 20 Jul 2025 11:56:58 +0300 Subject: [PATCH 02/10] rename props to viewProps --- .../site/src/components/TailwindNode.svelte | 10 ++++---- .../src/lib/DefaultNode.svelte | 6 ++--- .../src/lib/TreeViewNode.svelte | 13 +++------- .../svelte-tree-view/src/lib/store.svelte.ts | 24 +++++++++---------- 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/packages/site/src/components/TailwindNode.svelte b/packages/site/src/components/TailwindNode.svelte index 27e9550..42d7a01 100644 --- a/packages/site/src/components/TailwindNode.svelte +++ b/packages/site/src/components/TailwindNode.svelte @@ -10,7 +10,7 @@ handleToggleCollapse }: NodeProps = $props() - const { props: propsObj } = getTreeContext() + const { viewProps } = getTreeContext() let hasChildren = $derived(node.children.length > 0) let descend = $derived(!node.collapsed && hasChildren) @@ -58,7 +58,7 @@ } // For other types, use the default formatter or string representation - return $propsObj.valueFormatter?.(value, node) ?? String(value) + return $viewProps.valueFormatter?.(value, node) ?? String(value) } // Get the appropriate value to display @@ -69,7 +69,7 @@ return createTruncatedPreview(val, node.type) } else { // Show full value when expanded or for leaf nodes - return $propsObj.valueFormatter?.(val, node) ?? String(val) + return $viewProps.valueFormatter?.(val, node) ?? String(val) } } @@ -144,7 +144,7 @@
- {#if $propsObj.showLogButton} + {#if $viewProps.showLogButton} {/if} - {#if $propsObj.showCopyButton} + {#if $viewProps.showCopyButton}
- {#if $propsObj.showLogButton} + {#if $viewProps.showLogButton} {/if} - {#if $propsObj.showCopyButton} + {#if $viewProps.showCopyButton} {/if}
diff --git a/packages/svelte-tree-view/src/lib/TreeViewNode.svelte b/packages/svelte-tree-view/src/lib/TreeViewNode.svelte index b57edb0..3673cb1 100644 --- a/packages/svelte-tree-view/src/lib/TreeViewNode.svelte +++ b/packages/svelte-tree-view/src/lib/TreeViewNode.svelte @@ -12,12 +12,7 @@ let { id }: Props = $props() - const { - rootElementStore, - treeMap, - props: propsObj, - ...rest - } = getContext('svelte-tree-view') + const { rootElement, treeMap, viewProps, ...rest } = getContext('svelte-tree-view') let node = $state(treeMap[id] as TreeNode) let hasChildren = $derived(node && node.children.length > 0) let nodeProps = $derived({ @@ -45,12 +40,10 @@ rest.toggleCollapse(node.id) } else if (node.circularOfId) { rest.expandAllNodesToNode(node.circularOfId) - $rootElementStore - ?.querySelector(`li[data-tree-id="${node.circularOfId}"]`) - ?.scrollIntoView() + $rootElement?.querySelector(`li[data-tree-id="${node.circularOfId}"]`)?.scrollIntoView() } } }) -{@render $propsObj.treeNode(nodeProps)} +{@render $viewProps.treeNode(nodeProps)} diff --git a/packages/svelte-tree-view/src/lib/store.svelte.ts b/packages/svelte-tree-view/src/lib/store.svelte.ts index ffb6c43..736db0e 100644 --- a/packages/svelte-tree-view/src/lib/store.svelte.ts +++ b/packages/svelte-tree-view/src/lib/store.svelte.ts @@ -11,28 +11,28 @@ export const createStore = (initialProps: Omit) => { [defaultRootNode.id]: defaultRootNode }) const rootNode = $derived(treeMap[defaultRootNode.id]) - const rootElementStore = writable(null) - const props = writable>(initialProps) - const recursionOpts = derived(props, p => p.recursionOpts) + const rootElement = writable(null) + const viewProps = writable>(initialProps) + const recursionOpts = derived(viewProps, p => p.recursionOpts) const iteratedValues = new Map() return { - props, recursionOpts, - rootElementStore, + rootElement, rootNode, treeMap, + viewProps, setProps(newProps: Omit) { - props.set(newProps) + viewProps.set(newProps) }, setRootElement(el: HTMLElement | null) { - rootElementStore.set(el) + rootElement.set(el) }, formatValue(val: any, node: TreeNode): string { - const { valueFormatter } = get(props) + const { valueFormatter } = get(viewProps) const customFormat = valueFormatter ? valueFormatter(val, node) : undefined if (customFormat) { return customFormat @@ -79,7 +79,7 @@ export const createStore = (initialProps: Omit) => { for (const id of oldIds) { delete treeMap[id] } - get(props).onUpdate?.(treeMap) + get(viewProps).onUpdate?.(treeMap) }, toggleCollapse(id: string) { @@ -92,7 +92,7 @@ export const createStore = (initialProps: Omit) => { if (recurOpts) { this.expandNodeChildren(node, recurOpts) } else { - get(props).onUpdate?.(treeMap) + get(viewProps).onUpdate?.(treeMap) } }, @@ -119,7 +119,7 @@ export const createStore = (initialProps: Omit) => { if (!nodeWithUpdatedChildren) return treeMap[nodeWithUpdatedChildren.id] = nodeWithUpdatedChildren treeMap[parent.id] = parent - get(props).onUpdate?.(treeMap) + get(viewProps).onUpdate?.(treeMap) }, expandAllNodesToNode(id: string) { @@ -135,7 +135,7 @@ export const createStore = (initialProps: Omit) => { } const updated = treeMap recurseNodeUpwards(updated, updated[id]) - get(props).onUpdate?.(updated) + get(viewProps).onUpdate?.(updated) } } } From b93c543ee09c5d2e89324d33064f01da79c49f9b Mon Sep 17 00:00:00 2001 From: Teemu Koivisto Date: Sun, 20 Jul 2025 12:53:11 +0300 Subject: [PATCH 03/10] initialize store methods before to remove use of this --- .../src/lib/TreeViewNode.svelte | 5 +- .../svelte-tree-view/src/lib/store.svelte.ts | 233 +++++++++--------- 2 files changed, 123 insertions(+), 115 deletions(-) diff --git a/packages/svelte-tree-view/src/lib/TreeViewNode.svelte b/packages/svelte-tree-view/src/lib/TreeViewNode.svelte index 3673cb1..931f586 100644 --- a/packages/svelte-tree-view/src/lib/TreeViewNode.svelte +++ b/packages/svelte-tree-view/src/lib/TreeViewNode.svelte @@ -4,7 +4,6 @@ import TreeViewNode from './TreeViewNode.svelte' import type { TreeStore } from './store.svelte' - import type { TreeNode } from './types' interface Props { id: string @@ -13,8 +12,8 @@ let { id }: Props = $props() const { rootElement, treeMap, viewProps, ...rest } = getContext('svelte-tree-view') - let node = $state(treeMap[id] as TreeNode) - let hasChildren = $derived(node && node.children.length > 0) + let node = $derived(treeMap[id]) + let hasChildren = $derived(node.children.length > 0) let nodeProps = $derived({ node, getTreeContext: () => getContext('svelte-tree-view'), diff --git a/packages/svelte-tree-view/src/lib/store.svelte.ts b/packages/svelte-tree-view/src/lib/store.svelte.ts index 736db0e..838a912 100644 --- a/packages/svelte-tree-view/src/lib/store.svelte.ts +++ b/packages/svelte-tree-view/src/lib/store.svelte.ts @@ -16,126 +16,135 @@ export const createStore = (initialProps: Omit) => { const recursionOpts = derived(viewProps, p => p.recursionOpts) const iteratedValues = new Map() - return { - recursionOpts, - rootElement, - rootNode, - treeMap, - viewProps, + function setProps(newProps: Omit) { + viewProps.set(newProps) + } - setProps(newProps: Omit) { - viewProps.set(newProps) - }, + function setRootElement(el: HTMLElement | null) { + rootElement.set(el) + } - setRootElement(el: HTMLElement | null) { - rootElement.set(el) - }, + function formatValue(val: any, node: TreeNode): string { + const { valueFormatter } = get(viewProps) + const customFormat = valueFormatter ? valueFormatter(val, node) : undefined + if (customFormat) { + return customFormat + } + switch (node.type) { + case 'array': + return `${node.circularOfId ? 'circular' : ''} [] ${val.length} items` + case 'object': + return `${node.circularOfId ? 'circular' : ''} {} ${Object.keys(val).length} keys` + case 'map': + case 'set': + return `${node.circularOfId ? 'circular' : ''} () ${val.size} entries` + case 'date': + return `${val.toISOString()}` + case 'string': + return `"${val}"` + case 'number': + return val + case 'boolean': + return val ? 'true' : 'false' + case 'symbol': + return String(val) + default: + return node.type + } + } - formatValue(val: any, node: TreeNode): string { - const { valueFormatter } = get(viewProps) - const customFormat = valueFormatter ? valueFormatter(val, node) : undefined - if (customFormat) { - return customFormat - } - switch (node.type) { - case 'array': - return `${node.circularOfId ? 'circular' : ''} [] ${val.length} items` - case 'object': - return `${node.circularOfId ? 'circular' : ''} {} ${Object.keys(val).length} keys` - case 'map': - case 'set': - return `${node.circularOfId ? 'circular' : ''} () ${val.size} entries` - case 'date': - return `${val.toISOString()}` - case 'string': - return `"${val}"` - case 'number': - return val - case 'boolean': - return val ? 'true' : 'false' - case 'symbol': - return String(val) - default: - return node.type - } - }, + function recompute( + data: unknown, + recursionOpts: TreeRecursionOpts, + recomputeExpandNode: boolean + ) { + const oldIds = new Set(Object.keys(treeMap)) + iteratedValues.clear() + recurseObjectProperties( + defaultRootNode.index, + defaultRootNode.key, + data, + defaultRootNode.depth, + true, + null, + treeMap, + oldIds, + iteratedValues, + recomputeExpandNode, + recursionOpts + ) + for (const id of oldIds) { + delete treeMap[id] + } + get(viewProps).onUpdate?.(treeMap) + } - recompute(data: unknown, recursionOpts: TreeRecursionOpts, recomputeExpandNode: boolean) { - const oldIds = new Set(Object.keys(treeMap)) - iteratedValues.clear() - recurseObjectProperties( - defaultRootNode.index, - defaultRootNode.key, - data, - defaultRootNode.depth, - true, - null, - treeMap, - oldIds, - iteratedValues, - recomputeExpandNode, - recursionOpts - ) - for (const id of oldIds) { - delete treeMap[id] - } + function toggleCollapse(id: string) { + const node = treeMap[id] + if (!node) { + return console.warn(`Attempted to collapse non-existent node: ${id}`) + } + node.collapsed = !node.collapsed + const recurOpts = get(recursionOpts) + if (recurOpts) { + expandNodeChildren(node, recurOpts) + } else { get(viewProps).onUpdate?.(treeMap) - }, - - toggleCollapse(id: string) { - const node = treeMap[id] - if (!node) { - return console.warn(`Attempted to collapse non-existent node: ${id}`) - } - node.collapsed = !node.collapsed - const recurOpts = get(recursionOpts) - if (recurOpts) { - this.expandNodeChildren(node, recurOpts) - } else { - get(viewProps).onUpdate?.(treeMap) - } - }, + } + } - expandNodeChildren(node: TreeNode, recursionOpts: TreeRecursionOpts) { - const oldTreeMap = treeMap - const parent = oldTreeMap[node?.parentId || ''] || null - if (!parent) { - // Only root node has no parent and it should not be expandable - throw Error('No parent in expandNodeChildren for node: ' + node) - } - const nodeWithUpdatedChildren = recurseObjectProperties( - node.index, - node.key, - node.getValue(), - node.depth, - !node.collapsed, // Ensure that when uncollapsed the node's children are always recursed - parent, - treeMap, - new Set(), - iteratedValues, - false, // Never recompute shouldExpandNode since it may override the collapsing of this node - recursionOpts - ) - if (!nodeWithUpdatedChildren) return - treeMap[nodeWithUpdatedChildren.id] = nodeWithUpdatedChildren - treeMap[parent.id] = parent - get(viewProps).onUpdate?.(treeMap) - }, + function expandNodeChildren(node: TreeNode, recursionOpts: TreeRecursionOpts) { + const oldTreeMap = treeMap + const parent = oldTreeMap[node?.parentId || ''] || null + if (!parent) { + // Only root node has no parent and it should not be expandable + throw Error('No parent in expandNodeChildren for node: ' + node) + } + const nodeWithUpdatedChildren = recurseObjectProperties( + node.index, + node.key, + node.getValue(), + node.depth, + !node.collapsed, // Ensure that when uncollapsed the node's children are always recursed + parent, + treeMap, + new Set(), + iteratedValues, + false, // Never recompute shouldExpandNode since it may override the collapsing of this node + recursionOpts + ) + if (!nodeWithUpdatedChildren) return + treeMap[nodeWithUpdatedChildren.id] = nodeWithUpdatedChildren + treeMap[parent.id] = parent + get(viewProps).onUpdate?.(treeMap) + } - expandAllNodesToNode(id: string) { - function recurseNodeUpwards( - updated: Record, - node?: TreeNode | null - ) { - if (!node) return - updated[node.id]!.collapsed = false - if (node.parentId) { - recurseNodeUpwards(updated, updated[node.parentId]) - } + function expandAllNodesToNode(id: string) { + function recurseNodeUpwards(updated: Record, node?: TreeNode | null) { + if (!node) return + updated[node.id]!.collapsed = false + if (node.parentId) { + recurseNodeUpwards(updated, updated[node.parentId]) } - const updated = treeMap - recurseNodeUpwards(updated, updated[id]) - get(viewProps).onUpdate?.(updated) } + const updated = treeMap + recurseNodeUpwards(updated, updated[id]) + get(viewProps).onUpdate?.(updated) + } + + return { + recursionOpts, + rootElement, + rootNode, + treeMap, + viewProps, + + setProps, + setRootElement, + formatValue, + recompute, + toggleCollapse, + expandNodeChildren, + expandAllNodesToNode } } From 5e4aa2c92c5b8c674d512819d30ebbb7bddf1127 Mon Sep 17 00:00:00 2001 From: Teemu Koivisto Date: Sun, 20 Jul 2025 13:02:43 +0300 Subject: [PATCH 04/10] update utility methods, change tree-id to tree-node-id --- .../site/src/components/TailwindNode.svelte | 2 +- .../cypress/e2e/examples.spec.cy.ts | 2 +- .../src/lib/DefaultNode.svelte | 2 +- .../src/lib/TreeViewNode.svelte | 10 +- .../__snapshots__/TreeView.spec.ts.snap | 638 +++++++++--------- 5 files changed, 327 insertions(+), 327 deletions(-) diff --git a/packages/site/src/components/TailwindNode.svelte b/packages/site/src/components/TailwindNode.svelte index 42d7a01..346752e 100644 --- a/packages/site/src/components/TailwindNode.svelte +++ b/packages/site/src/components/TailwindNode.svelte @@ -101,7 +101,7 @@ } -
+
{ cy.get('a').contains('(3) Circular').click() cy.get('.svelte-tree-view').find('li').should('have.length', 126) cy.get('a').contains('(4) Tailwind').click() - cy.get('.svelte-tree-view').find('div[data-tree-id]').should('have.length', 130) + cy.get('.svelte-tree-view').find('div[data-tree-node-id]').should('have.length', 130) cy.get('a').contains('(1) Basic').click() cy.get('[data-test-id="input-textarea"]').focus().type(TEST_DATA) cy.get('.svelte-tree-view').find('li').should('have.length', 19) diff --git a/packages/svelte-tree-view/src/lib/DefaultNode.svelte b/packages/svelte-tree-view/src/lib/DefaultNode.svelte index 77694eb..7ea3e93 100644 --- a/packages/svelte-tree-view/src/lib/DefaultNode.svelte +++ b/packages/svelte-tree-view/src/lib/DefaultNode.svelte @@ -23,7 +23,7 @@ let valueStr = $derived(formatValue(node.getValue(), node)) -
  • +
  • {#if hasChildren}