Vue Draggable Sortable Tree Component
he-tree-vue is a robust Vue 3 component library designed for rendering draggable, sortable, and nested tree structures. Currently at stable version 3.1.2, it is actively maintained with a development-driven release cadence, focusing on bug fixes, performance improvements, and alignment with modern Vue ecosystem features. Key differentiators include comprehensive TypeScript definitions for enhanced developer experience, a built-in dragging placeholder for intuitive user interaction, and full compatibility with Vue 3's Composition API. It provides a powerful solution for managing hierarchical data with advanced interaction capabilities, distinguishing itself from simpler tree components by its focus on comprehensive drag-and-drop functionality.
Common errors
-
Uncaught ReferenceError: require is not defined
cause Attempting to use CommonJS `require()` syntax in a modern Vue 3 project (e.g., with Vite or a browser-based bundler that expects ES Modules).fixChange `const { Draggable } = require('he-tree-vue')` to `import { Draggable } from 'he-tree-vue'` and ensure your project's build configuration (e.g., `vite.config.ts`) is set up for ES Modules. -
npm ERR! ERESOLVE unable to resolve dependency tree \n npm ERR! Found: vue@^2.x.x \n npm ERR! Could not resolve dependency: \n npm ERR! peer vue@"^3.0.0" from he-tree-vue@3.x.x
cause `he-tree-vue` is a Vue 3 component, but your project is either using Vue 2 or has a conflicting Vue 2 dependency in its tree.fixEnsure your project explicitly installs Vue 3 (`npm install vue@^3.0.0`) and that no other dependencies are forcing Vue 2. If migrating, fully upgrade your project to Vue 3. Use `npm install --legacy-peer-deps` as a temporary workaround if direct resolution is difficult, but prioritize fixing the dependency tree. -
Property 'text' does not exist on type 'TreeDataItem' or 'Property 'children' does not exist on type 'TreeDataItem'
cause Your tree data items do not conform to the expected type `TreeDataItem` or your custom interface for `he-tree-vue`.fixDefine a TypeScript interface for your tree nodes that extends `TreeDataItem` and explicitly includes properties like `text` and `children`, as shown in the quickstart. Ensure all your data objects adhere to this interface.
Warnings
- breaking This library is designed for Vue 3. Migrating an existing Vue 2 application or components to use `he-tree-vue` will require a full Vue 3 migration, which involves significant breaking changes to global APIs, template directives, component options, and reactivity system. It is not a drop-in replacement for Vue 2 tree components.
- gotcha The `updateBehavior` prop controls how `v-model` data changes are emitted. By default ('modify'), it directly modifies the bound data. If set to 'new', it emits a completely new data object. This is crucial for state management patterns (e.g., Vuex/Pinia) where immutability might be preferred or required.
- gotcha The component relies on JavaScript to set the `padding-left` for indentation of `tree-node-back` elements. Overriding this with custom CSS `padding-left` will break the tree's visual structure and indentation logic.
Install
-
npm install he-tree-vue -
yarn add he-tree-vue -
pnpm add he-tree-vue
Imports
- Draggable
const Draggable = require('he-tree-vue').Draggableimport { Draggable } from 'he-tree-vue' - BaseTree
import { BaseTree } from 'he-tree-vue' - TreeDataItem
import type { TreeDataItem } from 'he-tree-vue' - CSS Styles
import 'he-tree-vue/style/default.css'
Quickstart
<template>
<div id="app">
<h1>My Draggable Tree</h1>
<Draggable
v-model="treeData"
:indent="20"
:eachNodeAttributes="(node) => ({ 'data-id': node.id })"
@change="onTreeChange"
@node-drop="onNodeDrop"
class="my-custom-tree"
>
<template #default="{ node, index, path, tree }">
<div class="tree-node-back">
{{ node.text }}
<button @click="addNode(node)">Add Child</button>
</div>
</template>
</Draggable>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Draggable, type TreeDataItem } from 'he-tree-vue';
import 'he-tree-vue/style/default.css';
interface MyTreeDataItem extends TreeDataItem {
id: number;
text: string;
children?: MyTreeDataItem[];
}
let nextId = 4;
const treeData = ref<MyTreeDataItem[]>([
{ id: 1, text: 'Node 1', children: [
{ id: 2, text: 'Node 1.1' },
{ id: 3, text: 'Node 1.2' }
]}
]);
const onTreeChange = (data: MyTreeDataItem[]) => {
console.log('Tree data changed:', data);
};
const onNodeDrop = (dragInfo: any) => {
console.log('Node dropped:', dragInfo);
// You might want to persist the updated treeData to a backend here
};
const addNode = (parentNode: MyTreeDataItem) => {
if (!parentNode.children) {
parentNode.children = [];
}
const newNode: MyTreeDataItem = { id: nextId++, text: `New Child of ${parentNode.text}` };
parentNode.children.push(newNode);
console.log('Added new node:', newNode);
};
</script>
<style>
.my-custom-tree {
border: 1px solid #eee;
padding: 10px;
min-height: 100px;
}
.tree-node-back {
padding: 5px;
border: 1px solid #ccc;
margin: 2px 0;
background-color: #f9f9f9;
display: flex;
justify-content: space-between;
align-items: center;
}
.he-tree-drag-placeholder {
background-color: #e0f2f7 !important; /* Light blue placeholder */
border: 1px dashed #90caf9 !important;
}
</style>