Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/stale-regions-go.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte-tree-view': minor
---

invert isCircularNode return value for node collapsing, make DefaultNode ESM export, refactor stores into one
20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,24 @@
"devDependencies": {
"@changesets/cli": "^2.29.5",
"@eslint/compat": "^1.3.1",
"@eslint/js": "^9.30.1",
"@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.35.1",
"@eslint/js": "^9.31.0",
"@typescript-eslint/eslint-plugin": "^8.37.0",
"@typescript-eslint/parser": "^8.37.0",
"concurrently": "^9.2.0",
"eslint": "^9.30.1",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.5.1",
"eslint": "^9.31.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-promise": "^7.2.1",
"eslint-plugin-svelte": "^3.10.1",
"eslint-plugin-svelte": "^3.11.0",
"globals": "^16.3.0",
"husky": "^9.1.7",
"prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.6.13",
"prettier-plugin-tailwindcss": "^0.6.14",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"typescript-eslint": "^8.35.1",
"vite": "^7.0.2",
"typescript-eslint": "^8.37.0",
"vite": "^7.0.5",
"vite-plugin-dts": "^4.5.4"
}
}
6 changes: 3 additions & 3 deletions packages/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "2.22.2",
"@sveltejs/vite-plugin-svelte": "^5.1.0",
"@sveltejs/kit": "2.25.1",
"@sveltejs/vite-plugin-svelte": "^6.1.0",
"@tailwindcss/vite": "^4.1.11",
"svelte": "^5.35.4",
"svelte": "^5.36.10",
"svelte-preprocess": "^6.0.3",
"svelte-tree-view": "workspace:*",
"tailwindcss": "^4.1.11"
Expand Down
6 changes: 2 additions & 4 deletions packages/site/src/components/DiffValue.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<script lang="ts">
import { DefaultNode } from 'svelte-tree-view'
import DefaultNode from 'svelte-tree-view/DefaultNode.svelte'

import type { NodeProps } from 'svelte-tree-view'

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, ' ')
Expand Down
14 changes: 6 additions & 8 deletions packages/site/src/components/TailwindNode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
handleToggleCollapse
}: NodeProps = $props()

const {
propsStore: { props: propsObj }
} = getTreeContext()
const { viewProps } = getTreeContext()

let hasChildren = $derived(node.children.length > 0)
let descend = $derived(!node.collapsed && hasChildren)
Expand Down Expand Up @@ -60,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
Expand All @@ -71,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)
}
}

Expand Down Expand Up @@ -103,7 +101,7 @@
}
</script>

<div class="tree-node-container" data-tree-id={node.id}>
<div class="tree-node-container" data-tree-node-id={node.id}>
<div
class="tree-node-card group"
class:collapsed={node.collapsed && hasChildren}
Expand Down Expand Up @@ -146,7 +144,7 @@

<!-- Action buttons -->
<div class="action-buttons">
{#if $propsObj.showLogButton}
{#if $viewProps.showLogButton}
<button
class="action-button log-button"
onclick={handleLogNode}
Expand All @@ -162,7 +160,7 @@
</svg>
</button>
{/if}
{#if $propsObj.showCopyButton}
{#if $viewProps.showCopyButton}
<button
class="action-button copy-button"
onclick={handleCopyNodeToClipboard}
Expand Down
4 changes: 2 additions & 2 deletions packages/site/src/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ const DEFAULT_RECUR_OPTS: TreeRecursionOpts = {
const existingNodeWithValue = iteratedValues.get(node.getValue())
if (existingNodeWithValue && node.id !== existingNodeWithValue.id) {
node.circularOfId = existingNodeWithValue.id
return false
return true
}
iteratedValues.set(node.getValue(), node)
}
return true
return false
},
shouldExpandNode(node) {
return true
Expand Down
3 changes: 2 additions & 1 deletion packages/site/src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte'
import TreeView, { DefaultNode } from 'svelte-tree-view'
import TreeView from 'svelte-tree-view'
import DefaultNode from 'svelte-tree-view/DefaultNode.svelte'

import { mapDocDeltaChildren } from '$lib/mapDocDeltaChildren'
import {
Expand Down
3 changes: 2 additions & 1 deletion packages/site/src/routes/circular/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte'
import TreeView, { DefaultNode } from 'svelte-tree-view'
import TreeView from 'svelte-tree-view'
import DefaultNode from 'svelte-tree-view/DefaultNode.svelte'

import { mapDocDeltaChildren } from '$lib/mapDocDeltaChildren'
import {
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte-tree-view/cypress/e2e/examples.spec.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('# UI', () => {
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)
Expand Down
20 changes: 14 additions & 6 deletions packages/svelte-tree-view/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
"svelte": "./pkg/index.js",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./TreeView.svelte": {
"types": "./pkg/TreeView.svelte.d.ts",
"svelte": "./pkg/TreeView.svelte"
},
"./DefaultNode.svelte": {
"types": "./pkg/DefaultNode.svelte.d.ts",
"svelte": "./pkg/DefaultNode.svelte"
}
},
"scripts": {
Expand All @@ -35,20 +43,20 @@
"cy": "cypress"
},
"devDependencies": {
"@sveltejs/kit": "^2.22.2",
"@sveltejs/package": "^2.3.12",
"@sveltejs/vite-plugin-svelte": "^5.1.0",
"@sveltejs/kit": "^2.25.1",
"@sveltejs/package": "^2.4.0",
"@sveltejs/vite-plugin-svelte": "^6.1.0",
"@testing-library/cypress": "^10.0.3",
"@testing-library/svelte": "^5.2.8",
"@types/testing-library__jest-dom": "^5.14.9",
"cypress": "14.5.1",
"jsdom": "^26.1.0",
"postcss": "^8.5.6",
"sass": "^1.89.2",
"svelte": "^5.35.4",
"svelte-check": "^4.2.2",
"svelte": "^5.36.10",
"svelte-check": "^4.3.0",
"svelte-preprocess": "^6.0.3",
"svelte2tsx": "^0.7.40",
"svelte2tsx": "^0.7.41",
"vitest": "^3.2.4"
},
"dependencies": {},
Expand Down
10 changes: 4 additions & 6 deletions packages/svelte-tree-view/src/lib/DefaultNode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@
handleCopyNodeToClipboard,
handleToggleCollapse
}: DefaultNodeProps = $props()
const {
propsStore: { props: propsObj, formatValue }
} = getTreeContext()
const { viewProps, formatValue } = getTreeContext()
let hasChildren = $derived(node.children.length > 0)
let descend = $derived(!node.collapsed && hasChildren)
let valueStr = $derived(formatValue(node.getValue(), node))
</script>

<li class="row" class:collapsed={node.collapsed && hasChildren} data-tree-id={node.id}>
<li class="row" class:collapsed={node.collapsed && hasChildren} data-tree-node-id={node.id}>
{#if hasChildren}
<button class={`arrow-btn ${node.collapsed ? 'collapsed' : ''}`} onclick={handleToggleCollapse}>
Expand Down Expand Up @@ -59,10 +57,10 @@
{/if}
</div>
<div class="buttons">
{#if $propsObj.showLogButton}
{#if $viewProps.showLogButton}
<button class="log-copy-button" onclick={handleLogNode}>log</button>
{/if}
{#if $propsObj.showCopyButton}
{#if $viewProps.showCopyButton}
<button class="log-copy-button" onclick={handleCopyNodeToClipboard}>copy</button>
{/if}
</div>
Expand Down
27 changes: 10 additions & 17 deletions packages/svelte-tree-view/src/lib/TreeView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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<Stores>('svelte-tree-view', {
propsStore,
rootElementStore,
treeStore
})
setContext<TreeStore>('svelte-tree-view', store)

onMount(() => {
rootElementStore.set(rootElement)
store.setRootElement(rootElement)
})

$effect(() => {
Expand All @@ -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
const recomputeExpandNode = oldRecOptions?.shouldExpandNode !== opts.shouldExpandNode
// Use untrack to prevent triggering this effect again
untrack(() => {
treeStore.recompute(newData, opts, shouldRecompute)
propsStore.setProps(propsObj)
store.createTree(newData, opts, recomputeExpandNode)
store.setProps(propsObj)
propsObj.recursionOpts = opts
})
})
Expand Down
30 changes: 13 additions & 17 deletions packages/svelte-tree-view/src/lib/TreeViewNode.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,46 @@

import TreeViewNode from './TreeViewNode.svelte'

import type { Stores } from './stores'
import type { TreeNode } from './types'
import type { TreeStore } from './store.svelte'

interface Props {
id: string
}

let { id }: Props = $props()

const { treeStore, propsStore, rootElementStore } = getContext<Stores>('svelte-tree-view')
let { props: propsObj } = propsStore
let node = $state(treeStore.treeMap[id] as TreeNode<any>)
let hasChildren = $derived(node && node.children.length > 0)
const { rootElement, treeMap, viewProps, ...rest } = getContext<TreeStore>('svelte-tree-view')
let node = $derived(treeMap[id])
let hasChildren = $derived(node.children.length > 0)
let nodeProps = $derived({
node,
getTreeContext: () => getContext<Stores>('svelte-tree-view'),
getTreeContext: () => getContext<TreeStore>('svelte-tree-view'),
TreeViewNode: TreeViewNode,
handleLogNode() {
console.info('%c [svelte-tree-view]: Property added to window._node', 'color: #b8e248')
console.log(node.getValue())
try {
if (typeof window !== 'undefined') window._node = node.getValue()
window._node = node.getValue()
console.info('%c [svelte-tree-view]: Property added to window._node', 'color: #b8e248')
} catch (err) {
console.error('Failed to set _node, window was undefined')
console.error('[svelte-tree-view]: handleLogNode() errored', err)
}
},
handleCopyNodeToClipboard() {
try {
navigator.clipboard.writeText(JSON.stringify(node.getValue()))
} catch (err) {
console.error('Copying node to clipboard failed: ', err)
console.error('[svelte-tree-view]: handleCopyNodeToClipboard() errored', err)
}
},
handleToggleCollapse() {
if (hasChildren) {
treeStore.toggleCollapse(node.id)
rest.toggleCollapse(node.id)
} else if (node.circularOfId) {
treeStore.expandAllNodesToNode(node.circularOfId)
$rootElementStore
?.querySelector(`li[data-tree-id="${node.circularOfId}"]`)
?.scrollIntoView()
rest.expandAllNodesToNode(node.circularOfId)
$rootElement?.querySelector(`[data-tree-node-id="${node.circularOfId}"]`)?.scrollIntoView()
}
}
})
</script>

{@render $propsObj.treeNode(nodeProps)}
{@render $viewProps.treeNode(nodeProps)}
4 changes: 2 additions & 2 deletions packages/svelte-tree-view/src/lib/__tests__/TreeView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ describe('TreeView', () => {
const existingNodeWithValue = iteratedValues.get(val)
if (existingNodeWithValue) {
node.circularOfId = existingNodeWithValue.id
return false
return true
}
iteratedValues.set(val, node)
}
return true
return false
},
shouldExpandNode: () => true
},
Expand Down
Loading
Loading