├── .browserslistrc ├── public ├── demo.gif └── index.html ├── npm-scripts └── postinstall.js ├── babel.config.js ├── src ├── shims-vue.d.ts ├── main.ts ├── index.ts ├── shims-tsx.d.ts ├── types.ts ├── utils.ts ├── assets │ └── simple-grid.scss ├── components │ └── VirtualizationList.vue ├── App.vue ├── draggable │ ├── draggable.ts │ └── Draggable.vue └── BaseTree.vue ├── .gitignore ├── dist └── he-tree-vue.css ├── .eslintrc.js ├── README.md ├── tsconfig.json ├── package.json ├── scripts ├── build.ts └── build.js └── CHANGELOG.md /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /public/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phphe/he-tree-vue/HEAD/public/demo.gif -------------------------------------------------------------------------------- /npm-scripts/postinstall.js: -------------------------------------------------------------------------------- 1 | console.log(`he-tree-vue@2 is for Vue2. he-tree-vue@3 is for Vue3. Beware!`) -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | Vue.config.productionTip = false 5 | 6 | new Vue({ 7 | render: h => h(App), 8 | }).$mount('#app') 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // export { default as BaseTree } from "./BaseTree.vue"; 2 | export { default as Draggable } from "./draggable/Draggable.vue"; 3 | // export { obj, BaseNode, Node } from "./types"; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /dist/he-tree-vue.css: -------------------------------------------------------------------------------- 1 | .vl-items { 2 | overflow: hidden; 3 | box-sizing: border-box; 4 | } 5 | 6 | .he-tree-rtl { 7 | direction: rtl; 8 | } 9 | 10 | 11 | .he-tree .tree-placeholder { 12 | background: #ddf2f9; 13 | border: 1px dashed #00d9ff; 14 | height: 20px; 15 | } 16 | .he-tree .dragging .tree-node:hover { 17 | background-color: inherit; 18 | } 19 | -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | "plugin:vue/essential", 8 | "eslint:recommended", 9 | "@vue/typescript/recommended", 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2020, 13 | }, 14 | rules: { 15 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", 16 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", 17 | "@typescript-eslint/ban-ts-comment": "off", 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type obj = Record; // equal to object 2 | 3 | export interface BaseNode { 4 | $id: string | number; 5 | $pid?: string | number; 6 | $level: number; // 0 is root 7 | $hidden?: boolean; 8 | $folded?: boolean; 9 | $checked?: boolean | 0; 10 | $children: Node[]; 11 | $childrenLoading?: boolean; 12 | $childrenLoadStaus?: obj; // private 13 | $draggable?: boolean; 14 | $droppable?: boolean; 15 | // style 16 | $nodeStyle?: string | Record | unknown; 17 | $nodeClass?: string | unknown; 18 | $outerStyle?: string | Record | unknown; 19 | $outerClass?: string | unknown; 20 | } 21 | 22 | export type Node = obj & BaseNode; 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # he-tree-vue 2 | 3 | **This project is no longer maintained, please move to the new monorepo [he-tree](https://github.com/phphe/he-tree).** 4 | 5 | **本项目不再维护, 请移步到新的 monorepo [he-tree](https://github.com/phphe/he-tree).** 6 | 7 | A draggable sortable vue tree component, with dragging placeholder, types definition. Vue3 supported. [Demo](https://he-tree-vue.phphe.com), [Document](https://he-tree-vue.phphe.com) 8 | 9 | 可拖拽可排序 vue 树组件, 支持拖拽占位盒, typescript 定义文件, vue3. [演示](https://he-tree-vue.phphe.com/zh), [文档](https://he-tree-vue.phphe.com/zh) 10 | 11 | ![image](https://github.com/phphe/he-tree-vue/blob/master/public/demo.gif?raw=true) 12 | 13 | ## License 14 | 15 | [MIT](http://opensource.org/licenses/MIT) 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "declaration": true, 16 | "types": ["webpack-env"], 17 | "paths": { 18 | "@/*": ["src/*"] 19 | }, 20 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 21 | }, 22 | "include": [ 23 | "src/**/*.ts", 24 | "src/**/*.tsx", 25 | "src/**/*.vue", 26 | "tests/**/*.ts", 27 | "tests/**/*.tsx" 28 | ], 29 | "exclude": ["node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "he-tree-vue", 3 | "version": "4.0.0-beta.3", 4 | "description": "A draggable sortable nested vue tree component.", 5 | "files": [ 6 | "dist", 7 | "npm-scripts" 8 | ], 9 | "unpkg": "dist/he-tree-vue.js", 10 | "author": "phphe (https://github.com/phphe)", 11 | "homepage": "https://he-tree-vue.phphe.com", 12 | "bugs": { 13 | "url": "https://github.com/phphe/he-tree-vue/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/phphe/he-tree-vue.git" 18 | }, 19 | "scripts": { 20 | "postinstall": "node npm-scripts/postinstall.js", 21 | "dev": "vue-cli-service serve", 22 | "build:web": "vue-cli-service build", 23 | "lint": "vue-cli-service lint", 24 | "compile-scripts": "tsc --target ES6 --module CommonJS scripts/build.ts", 25 | "build": "rollup -c scripts/build.js & node scripts/build.js --report", 26 | "commit": "git-cz" 27 | }, 28 | "dependencies": { 29 | "@babel/runtime": "^7.7.7", 30 | "draggable-helper": "^6.0.1", 31 | "helper-js": "^2.0.4", 32 | "tslib": "^1.14.1", 33 | "vue-class-component": "^7.2.3", 34 | "vue-property-decorator": "^9.1.2", 35 | "vue-runtime-helpers": "^1.1.2" 36 | }, 37 | "devDependencies": { 38 | "@typescript-eslint/eslint-plugin": "^4.18.0", 39 | "@typescript-eslint/parser": "^4.18.0", 40 | "@vue/babel-preset-app": "^4.2.3", 41 | "@vue/cli-plugin-babel": "~4.5.0", 42 | "@vue/cli-plugin-eslint": "~4.5.0", 43 | "@vue/cli-plugin-typescript": "~4.5.0", 44 | "@vue/cli-service": "~4.5.0", 45 | "@vue/compiler-sfc": "^3.1.5", 46 | "@vue/eslint-config-typescript": "^7.0.0", 47 | "commitizen": "^4.1.2", 48 | "conventional-changelog-cli": "^2.0.34", 49 | "cz-conventional-changelog": "^3.2.0", 50 | "eslint": "^6.7.2", 51 | "eslint-plugin-vue": "^6.2.2", 52 | "pug": "^3.0.2", 53 | "pug-plain-loader": "^1.1.0", 54 | "rogo": "^2.0.2", 55 | "rollup-plugin-postcss": "^2.4.1", 56 | "rollup-plugin-typescript2": "^0.27.1", 57 | "rollup-plugin-vue": "^6.0.0-beta.10", 58 | "sass": "^1.26.5", 59 | "sass-loader": "^8.0.2", 60 | "standard-version": "^8.0.0", 61 | "typescript": "~4.1.5", 62 | "vue": "^2.6.11", 63 | "vue-template-compiler": "^2.6.11" 64 | }, 65 | "peerDependencies": { 66 | "vue": "^2.6.11" 67 | } 68 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as hp from "helper-js"; 2 | import { obj, BaseNode } from "./types"; 3 | 4 | export function genNodeID() { 5 | return `ht_${hp.randString(12)}`; 6 | } 7 | 8 | export function initNode(node: obj) { 9 | if (!node.$id) { 10 | node.$id = genNodeID(); 11 | } 12 | if (!node.$children) { 13 | node.$children = []; 14 | } 15 | } 16 | 17 | export function convertTreeDataToFlat( 18 | data: T[], 19 | childrenKey = "children", 20 | idKey = "id" 21 | ) { 22 | const flatData: T[] = []; 23 | const mapForPid = new Map(); 24 | hp.walkTreeData( 25 | data, 26 | (node, index, parent) => { 27 | const newNode = { $id: node[idKey], $pid: "", ...node }; 28 | initNode(newNode); 29 | mapForPid.set(node, newNode.$id); 30 | newNode.$pid = (parent && mapForPid.get(parent)) || null; 31 | flatData.push(newNode); 32 | }, 33 | childrenKey 34 | ); 35 | return convertFlatDataToStandard(flatData, "$id", "$pid"); 36 | } 37 | 38 | export function convertFlatDataToStandard( 39 | data: T[], 40 | idKey = "id", 41 | pidKey = "parent_id" 42 | ) { 43 | const nodesByID: Record = {}; 44 | let nodes = data.map((node) => { 45 | // @ts-ignore 46 | const newNode: T & BaseNode = { 47 | $id: node[idKey], 48 | $pid: node[pidKey], 49 | ...node, 50 | $children: [], 51 | }; 52 | initNode(newNode); 53 | nodesByID[newNode.$id] = newNode; 54 | return newNode; 55 | }); 56 | const top = []; 57 | for (const node of nodes) { 58 | if (node.$level == null) { 59 | node.$level = resolveLevel(node); 60 | } 61 | const parent = node.$pid && nodesByID[node.$pid]; 62 | if (parent) { 63 | parent.$children.push(node); 64 | } 65 | if (node.$level === 1) { 66 | top.push(node); 67 | } 68 | } 69 | nodes = []; 70 | hp.walkTreeData( 71 | top, 72 | (node) => { 73 | nodes.push(node); 74 | }, 75 | "$children" 76 | ); 77 | return { nodes, nodesByID }; 78 | // 79 | function resolveLevel(node: T & BaseNode): number { 80 | if (node.$level && node.$level > 0) { 81 | return node.$level; 82 | } else { 83 | const parent = nodesByID[node.$pid || ""]; 84 | if (!parent) { 85 | return 1; 86 | } else { 87 | return resolveLevel(parent) + 1; 88 | } 89 | } 90 | } 91 | } 92 | 93 | export function getOuterHeight(el: Element) { 94 | return ( 95 | hp.getBoundingClientRect(el).height + 96 | hp.getOuterAttachedWidth(el, { margin: true, border: false }) 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /src/assets/simple-grid.scss: -------------------------------------------------------------------------------- 1 | // SIMPLE GRID - SASS/SCSS 2 | 3 | // @import url( 4 | // https://fonts.googleapis.com/css?family=Lato:400, 5 | // 300, 6 | // 300italic, 7 | // 400italic, 8 | // 700, 9 | // 700italic 10 | // ); 11 | 12 | // fonts 13 | $font-family: "Lato", Helvetica, sans-serif; 14 | $font-weight-light: 300; 15 | $font-weight-regular: 400; 16 | $font-weight-heavy: 700; 17 | $font-height: 1.5; 18 | 19 | // colors 20 | $dark-grey: #333447; 21 | $dark-gray: #333447; // for the Americans 22 | 23 | // universal 24 | 25 | html, 26 | body { 27 | height: 100%; 28 | width: 100%; 29 | margin: 0; 30 | padding: 0; 31 | left: 0; 32 | top: 0; 33 | font-size: 100%; 34 | } 35 | 36 | * { 37 | font-family: $font-family; 38 | color: $dark-grey; 39 | line-height: $font-height; 40 | } 41 | 42 | // typography 43 | 44 | h1 { 45 | font-size: 2.5rem; 46 | } 47 | 48 | h2 { 49 | font-size: 2rem; 50 | } 51 | 52 | h3 { 53 | font-size: 1.375rem; 54 | } 55 | 56 | h4 { 57 | font-size: 1.125rem; 58 | } 59 | 60 | h5 { 61 | font-size: 1rem; 62 | } 63 | 64 | h6 { 65 | font-size: 0.875rem; 66 | } 67 | 68 | p { 69 | font-size: 1.125rem; 70 | font-weight: 200; 71 | line-height: 1.8; 72 | } 73 | 74 | .font-light { 75 | font-weight: $font-weight-light; 76 | } 77 | 78 | .font-regular { 79 | font-weight: $font-weight-regular; 80 | } 81 | 82 | .font-heavy { 83 | font-weight: $font-weight-heavy; 84 | } 85 | 86 | // utility 87 | 88 | .left { 89 | text-align: left; 90 | } 91 | 92 | .right { 93 | text-align: right; 94 | } 95 | 96 | .center { 97 | text-align: center; 98 | margin-left: auto; 99 | margin-right: auto; 100 | } 101 | 102 | .justify { 103 | text-align: justify; 104 | } 105 | 106 | .hidden-sm { 107 | display: none; 108 | } 109 | 110 | // grid 111 | 112 | $width: 96%; 113 | $gutter: 4%; 114 | $breakpoint-small: 33.75em; // 540px 115 | $breakpoint-med: 45em; // 720px 116 | $breakpoint-large: 60em; // 960px 117 | 118 | .container { 119 | width: 90%; 120 | margin-left: auto; 121 | margin-right: auto; 122 | 123 | @media only screen and (min-width: $breakpoint-small) { 124 | width: 80%; 125 | } 126 | 127 | @media only screen and (min-width: $breakpoint-large) { 128 | width: 75%; 129 | max-width: 60rem; 130 | } 131 | } 132 | 133 | .row { 134 | position: relative; 135 | width: 100%; 136 | } 137 | 138 | .row [class^="col"] { 139 | float: left; 140 | margin: 0.5rem 2%; 141 | min-height: 0.125rem; 142 | } 143 | 144 | .row::after { 145 | content: ""; 146 | display: table; 147 | clear: both; 148 | } 149 | 150 | .col-1, 151 | .col-2, 152 | .col-3, 153 | .col-4, 154 | .col-5, 155 | .col-6, 156 | .col-7, 157 | .col-8, 158 | .col-9, 159 | .col-10, 160 | .col-11, 161 | .col-12 { 162 | width: $width; 163 | } 164 | 165 | .col-1-sm { 166 | width: ($width / 12) - ($gutter * 11 / 12); 167 | } 168 | .col-2-sm { 169 | width: ($width / 6) - ($gutter * 10 / 12); 170 | } 171 | .col-3-sm { 172 | width: ($width / 4) - ($gutter * 9 / 12); 173 | } 174 | .col-4-sm { 175 | width: ($width / 3) - ($gutter * 8 / 12); 176 | } 177 | .col-5-sm { 178 | width: ($width / (12 / 5)) - ($gutter * 7 / 12); 179 | } 180 | .col-6-sm { 181 | width: ($width / 2) - ($gutter * 6 / 12); 182 | } 183 | .col-7-sm { 184 | width: ($width / (12 / 7)) - ($gutter * 5 / 12); 185 | } 186 | .col-8-sm { 187 | width: ($width / (12 / 8)) - ($gutter * 4 / 12); 188 | } 189 | .col-9-sm { 190 | width: ($width / (12 / 9)) - ($gutter * 3 / 12); 191 | } 192 | .col-10-sm { 193 | width: ($width / (12 / 10)) - ($gutter * 2 / 12); 194 | } 195 | .col-11-sm { 196 | width: ($width / (12 / 11)) - ($gutter * 1 / 12); 197 | } 198 | .col-12-sm { 199 | width: $width; 200 | } 201 | 202 | @media only screen and (min-width: $breakpoint-med) { 203 | .col-1 { 204 | width: ($width / 12) - ($gutter * 11 / 12); 205 | } 206 | .col-2 { 207 | width: ($width / 6) - ($gutter * 10 / 12); 208 | } 209 | .col-3 { 210 | width: ($width / 4) - ($gutter * 9 / 12); 211 | } 212 | .col-4 { 213 | width: ($width / 3) - ($gutter * 8 / 12); 214 | } 215 | .col-5 { 216 | width: ($width / (12 / 5)) - ($gutter * 7 / 12); 217 | } 218 | .col-6 { 219 | width: ($width / 2) - ($gutter * 6 / 12); 220 | } 221 | .col-7 { 222 | width: ($width / (12 / 7)) - ($gutter * 5 / 12); 223 | } 224 | .col-8 { 225 | width: ($width / (12 / 8)) - ($gutter * 4 / 12); 226 | } 227 | .col-9 { 228 | width: ($width / (12 / 9)) - ($gutter * 3 / 12); 229 | } 230 | .col-10 { 231 | width: ($width / (12 / 10)) - ($gutter * 2 / 12); 232 | } 233 | .col-11 { 234 | width: ($width / (12 / 11)) - ($gutter * 1 / 12); 235 | } 236 | .col-12 { 237 | width: $width; 238 | } 239 | 240 | .hidden-sm { 241 | display: block; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { belongsTo, report, camelize } from "rogo"; 2 | import * as rollup from "rollup"; 3 | import * as path from "path"; 4 | const babel = require("rollup-plugin-babel"); 5 | const node = require("@rollup/plugin-node-resolve"); 6 | const cjs = require("@rollup/plugin-commonjs"); 7 | const json = require("@rollup/plugin-json"); 8 | import { terser } from "rollup-plugin-terser"; // to minify bundle 9 | const typescript = require("rollup-plugin-typescript2"); 10 | const vue = require("rollup-plugin-vue"); 11 | const postcss = require("rollup-plugin-postcss"); 12 | const pkg = require("../package.json"); 13 | const external = ["tslib"]; 14 | 15 | // quick config 16 | const input = "src/index.ts"; 17 | const outDir = "dist"; 18 | const outputName = pkg.name; // the built file name is outDir/outputName.format.js 19 | const moduleName = camelize(pkg.name); // for umd, amd 20 | const extractCssPath = path.resolve(outDir, `${outputName}.css`); 21 | 22 | const getBabelConfig = () => ({ 23 | // .babelrc 24 | presets: [ 25 | [ 26 | "@vue/cli-plugin-babel/preset", 27 | { 28 | useBuiltIns: false, 29 | polyfills: [], 30 | targets: { browsers: "defaults" }, // default browsers, coverage 90% 31 | }, 32 | ], 33 | ], 34 | plugins: [ 35 | "@babel/plugin-transform-runtime", 36 | ["@babel/plugin-proposal-optional-chaining", { loose: false }], 37 | ], 38 | // for rollup babel plugin 39 | runtimeHelpers: true, 40 | exclude: [/@babel\/runtime/, /@babel\\runtime/, /regenerator-runtime/], 41 | extensions: [".js", ".jsx", ".es6", ".es", ".mjs", ".vue", ".ts", ".tsx"], 42 | babelrc: false, 43 | }); 44 | 45 | const esmBabelConfig = getBabelConfig(); 46 | 47 | const cjsBabelConfig = getBabelConfig(); 48 | cjsBabelConfig.plugins.push(["module-extension", { mjs: "js" }]); // replace .mjs to .js 49 | 50 | const umdBabelConfig = getBabelConfig(); 51 | 52 | export default [ 53 | // esm 54 | { 55 | input, 56 | external: (source) => 57 | belongsTo(source, Object.keys(pkg.dependencies || {})) || 58 | belongsTo(source, Object.keys(pkg.peerDependencies || {})) || 59 | belongsTo(source, external), 60 | plugins: [ 61 | vue(), 62 | postcss({ extract: extractCssPath }), 63 | typescript(), // node must be in front of typescript. babel must behind typescript. 64 | babel(esmBabelConfig), 65 | node(), 66 | cjs(), 67 | json(), 68 | ], 69 | output: { 70 | dir: `${outDir}/esm`, 71 | format: "esm", 72 | banner: getBanner(pkg), 73 | sourcemap: false, 74 | }, 75 | }, 76 | // cjs 77 | { 78 | input, 79 | external: (source) => 80 | belongsTo(source, Object.keys(pkg.dependencies || {})) || 81 | belongsTo(source, Object.keys(pkg.peerDependencies || {})) || 82 | belongsTo(source, external), 83 | plugins: [ 84 | vue(), 85 | postcss({ extract: extractCssPath }), 86 | typescript(), // node must be in front of typescript. babel must behind typescript. 87 | babel(cjsBabelConfig), 88 | node(), 89 | cjs(), 90 | json(), 91 | ], 92 | output: { 93 | dir: `${outDir}/cjs`, 94 | format: "cjs", 95 | banner: getBanner(pkg), 96 | sourcemap: false, 97 | }, 98 | }, 99 | // umd 100 | { 101 | input, 102 | external: (source) => 103 | belongsTo(source, Object.keys(pkg.peerDependencies || {})), 104 | plugins: [ 105 | vue(), 106 | postcss({ extract: extractCssPath }), 107 | typescript(), // node must be in front of typescript. babel must behind typescript. 108 | babel(umdBabelConfig), 109 | node(), 110 | cjs(), 111 | json(), 112 | ], 113 | output: { 114 | dir: `${outDir}/umd`, 115 | format: "umd", 116 | banner: getBanner(pkg), 117 | sourcemap: false, 118 | name: moduleName, 119 | }, 120 | }, 121 | // umd min 122 | { 123 | input, 124 | external: (source) => 125 | belongsTo(source, Object.keys(pkg.peerDependencies || {})), 126 | plugins: [ 127 | vue(), 128 | postcss({ extract: extractCssPath }), 129 | typescript(), // node must be in front of typescript. babel must behind typescript. 130 | babel(umdBabelConfig), 131 | node(), 132 | cjs(), 133 | json(), 134 | terser(), // to minify bundle 135 | ], 136 | output: { 137 | dir: `${outDir}/umd-min`, 138 | format: "umd", 139 | banner: getBanner(pkg), 140 | sourcemap: false, 141 | name: moduleName, 142 | }, 143 | }, 144 | ]; 145 | 146 | if (process.argv.includes("--report")) { 147 | report(outDir); 148 | } 149 | 150 | function getBanner(pkg) { 151 | return ` 152 | /*! 153 | * ${pkg.name} v${pkg.version} 154 | * (c) ${pkg.author} 155 | * Homepage: ${pkg.homepage} 156 | * Released under the ${pkg.license} License. 157 | */`.trim(); 158 | } 159 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const rogo_1 = require("rogo"); 4 | const path = require("path"); 5 | const babel = require("rollup-plugin-babel"); 6 | const node = require("@rollup/plugin-node-resolve"); 7 | const cjs = require("@rollup/plugin-commonjs"); 8 | const json = require("@rollup/plugin-json"); 9 | const rollup_plugin_terser_1 = require("rollup-plugin-terser"); // to minify bundle 10 | const typescript = require("rollup-plugin-typescript2"); 11 | const vue = require("rollup-plugin-vue"); 12 | const postcss = require("rollup-plugin-postcss"); 13 | const pkg = require("../package.json"); 14 | const external = ["tslib"]; 15 | // quick config 16 | const input = "src/index.ts"; 17 | const outDir = "dist"; 18 | const outputName = pkg.name; // the built file name is outDir/outputName.format.js 19 | const moduleName = rogo_1.camelize(pkg.name); // for umd, amd 20 | const extractCssPath = path.resolve(outDir, `${outputName}.css`); 21 | const getBabelConfig = () => ({ 22 | // .babelrc 23 | presets: [ 24 | [ 25 | "@vue/cli-plugin-babel/preset", 26 | { 27 | useBuiltIns: false, 28 | polyfills: [], 29 | targets: { browsers: "defaults" }, 30 | }, 31 | ], 32 | ], 33 | plugins: [ 34 | "@babel/plugin-transform-runtime", 35 | ["@babel/plugin-proposal-optional-chaining", { loose: false }], 36 | ], 37 | // for rollup babel plugin 38 | runtimeHelpers: true, 39 | exclude: [/@babel\/runtime/, /@babel\\runtime/, /regenerator-runtime/], 40 | extensions: [".js", ".jsx", ".es6", ".es", ".mjs", ".vue", ".ts", ".tsx"], 41 | babelrc: false, 42 | }); 43 | const esmBabelConfig = getBabelConfig(); 44 | const cjsBabelConfig = getBabelConfig(); 45 | cjsBabelConfig.plugins.push(["module-extension", { mjs: "js" }]); // replace .mjs to .js 46 | const umdBabelConfig = getBabelConfig(); 47 | exports.default = [ 48 | // esm 49 | { 50 | input, 51 | external: (source) => rogo_1.belongsTo(source, Object.keys(pkg.dependencies || {})) || 52 | rogo_1.belongsTo(source, Object.keys(pkg.peerDependencies || {})) || 53 | rogo_1.belongsTo(source, external), 54 | plugins: [ 55 | vue(), 56 | postcss({ extract: extractCssPath }), 57 | typescript(), 58 | babel(esmBabelConfig), 59 | node(), 60 | cjs(), 61 | json(), 62 | ], 63 | output: { 64 | dir: `${outDir}/esm`, 65 | format: "esm", 66 | banner: getBanner(pkg), 67 | sourcemap: false, 68 | }, 69 | }, 70 | // cjs 71 | { 72 | input, 73 | external: (source) => rogo_1.belongsTo(source, Object.keys(pkg.dependencies || {})) || 74 | rogo_1.belongsTo(source, Object.keys(pkg.peerDependencies || {})) || 75 | rogo_1.belongsTo(source, external), 76 | plugins: [ 77 | vue(), 78 | postcss({ extract: extractCssPath }), 79 | typescript(), 80 | babel(cjsBabelConfig), 81 | node(), 82 | cjs(), 83 | json(), 84 | ], 85 | output: { 86 | dir: `${outDir}/cjs`, 87 | format: "cjs", 88 | banner: getBanner(pkg), 89 | sourcemap: false, 90 | }, 91 | }, 92 | // umd 93 | { 94 | input, 95 | external: (source) => rogo_1.belongsTo(source, Object.keys(pkg.peerDependencies || {})), 96 | plugins: [ 97 | vue(), 98 | postcss({ extract: extractCssPath }), 99 | typescript(), 100 | babel(umdBabelConfig), 101 | node(), 102 | cjs(), 103 | json(), 104 | ], 105 | output: { 106 | dir: `${outDir}/umd`, 107 | format: "umd", 108 | banner: getBanner(pkg), 109 | sourcemap: false, 110 | name: moduleName, 111 | }, 112 | }, 113 | // umd min 114 | { 115 | input, 116 | external: (source) => rogo_1.belongsTo(source, Object.keys(pkg.peerDependencies || {})), 117 | plugins: [ 118 | vue(), 119 | postcss({ extract: extractCssPath }), 120 | typescript(), 121 | babel(umdBabelConfig), 122 | node(), 123 | cjs(), 124 | json(), 125 | rollup_plugin_terser_1.terser(), 126 | ], 127 | output: { 128 | dir: `${outDir}/umd-min`, 129 | format: "umd", 130 | banner: getBanner(pkg), 131 | sourcemap: false, 132 | name: moduleName, 133 | }, 134 | }, 135 | ]; 136 | if (process.argv.includes("--report")) { 137 | rogo_1.report(outDir); 138 | } 139 | function getBanner(pkg) { 140 | return ` 141 | /*! 142 | * ${pkg.name} v${pkg.version} 143 | * (c) ${pkg.author} 144 | * Homepage: ${pkg.homepage} 145 | * Released under the ${pkg.license} License. 146 | */`.trim(); 147 | } 148 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [4.0.0-beta](https://github.com/phphe/he-tree-vue/compare/v2.0.10...v4.0.0-beta) (2021-08-03) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * **draggable plugin:** correct the value of store.targetPath ([21a5bf7](https://github.com/phphe/he-tree-vue/commit/21a5bf700048e90ed03a31397c797aa41c104fbe)) 11 | 12 | ### [3.0.3](https://github.com/phphe/he-tree-vue/compare/v3.0.2...v3.0.3) (2021-04-16) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * **draggable plugin:** fix node insert error after drop ([04de941](https://github.com/phphe/he-tree-vue/commit/04de9418e893fa3390de6a3863e9abcc47d200c1)), closes [#51](https://github.com/phphe/he-tree-vue/issues/51) 18 | 19 | ### [3.0.2](https://github.com/phphe/he-tree-vue/compare/v3.0.1...v3.0.2) (2021-04-12) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * **draggable plugin:** wrong result when move downwards in same level ([9124ec1](https://github.com/phphe/he-tree-vue/commit/9124ec19c72836b3927d1c4065fa1fd135689ab6)) 25 | 26 | ### [3.0.1](https://github.com/phphe/he-tree-vue/compare/v3.0.0...v3.0.1) (2021-03-30) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * **draggable plugin:** fix change even ([72c1b08](https://github.com/phphe/he-tree-vue/commit/72c1b08785a895040ce65f6175cf5db093383173)) 32 | 33 | ## [3.0.0](https://github.com/phphe/he-tree-vue/compare/v2.0.7-beta.3...v3.0.0) (2020-12-13) 34 | 35 | 36 | ### Features 37 | 38 | * **all:** update for vue3 ([fd2e1ac](https://github.com/phphe/he-tree-vue/commit/fd2e1acdf001f23a5e87528a13361eb7828fa62b)) 39 | * **types:** update types declaration for vue3 ([175c0f2](https://github.com/phphe/he-tree-vue/commit/175c0f27e7a19f17befe8784f1b289a90cb50500)) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * **dist:** use .js instead of .vue to fix 'render overrided issue' ([5b2fcc3](https://github.com/phphe/he-tree-vue/commit/5b2fcc3ca3d209999cd9ce400f0af65cb9421dad)) 45 | 46 | ### [2.0.7-beta.3](https://github.com/phphe/he-tree-vue/compare/v2.0.7-beta.2...v2.0.7-beta.3) (2020-12-13) 47 | 48 | ### [2.0.7-beta.2](https://github.com/phphe/he-tree-vue/compare/v2.0.7-beta.1...v2.0.7-beta.2) (2020-12-13) 49 | 50 | ### [2.0.7-beta.1](https://github.com/phphe/he-tree-vue/compare/v2.0.6...v2.0.7-beta.1) (2020-12-13) 51 | 52 | ### [2.0.6](https://github.com/phphe/he-tree-vue/compare/v2.0.5...v2.0.6) (2020-12-13) 53 | 54 | 55 | ### Features 56 | 57 | * **draggable plugin:** add alias after-placeholder-created for event afterPlaceholderCreated ([9c64bcc](https://github.com/phphe/he-tree-vue/commit/9c64bcce6a576beb49bebd56c4126057466da366)) 58 | * **fold plugin:** add alias node-folded-changed for event nodeFoldedChanged ([b70fc80](https://github.com/phphe/he-tree-vue/commit/b70fc80a40b035aad2cbd79d0f40b990375074a6)) 59 | 60 | 61 | ### Bug Fixes 62 | 63 | * **draggable plugin:** update dependence draggable-helper ([de98c11](https://github.com/phphe/he-tree-vue/commit/de98c114e1a22cf32d0521c86d016779028c50cd)) 64 | 65 | ### [2.0.5](https://github.com/phphe/he-tree-vue/compare/v2.0.5-beta.1...v2.0.5) (2020-11-26) 66 | 67 | ### [2.0.5-beta.1](https://github.com/phphe/he-tree-vue/compare/v2.0.4-beta2...v2.0.5-beta.1) (2020-11-26) 68 | 69 | ### [2.0.4-beta2](https://github.com/phphe/he-tree-vue/compare/v2.0.4-beta1...v2.0.4-beta2) (2020-09-29) 70 | 71 | 72 | ### Features 73 | 74 | * **draggable plugin:** add prop: edgeScrollSpecifiedContainerX, edgeScrollSpecifiedContainerY ([ae18703](https://github.com/phphe/he-tree-vue/commit/ae187039baf5ecb99d2006183254441bbfb2d644)) 75 | 76 | ### [2.0.4-beta1](https://github.com/phphe/he-tree-vue/compare/v2.0.3...v2.0.4-beta1) (2020-09-29) 77 | 78 | 79 | ### Features 80 | 81 | * **draggable plugin:** add event after-move ([b6aa068](https://github.com/phphe/he-tree-vue/commit/b6aa068b5eff1260808fc5401e44f2d5a8287fef)) 82 | 83 | ### [2.0.3](https://github.com/phphe/he-tree-vue/compare/v2.0.1...v2.0.3) (2020-08-08) 84 | 85 | 86 | ### Features 87 | 88 | * **draggable plugin:** up draggable-helper,add opt preventTextSelection fix about stacking context ([9df8670](https://github.com/phphe/he-tree-vue/commit/9df8670fa95b7bbdb4cac72cb533bccffdb90a36)) 89 | 90 | 91 | ### Bug Fixes 92 | 93 | * **draggable plugin:** update dependence draggable-helper to 5.0.3 ([7cc19de](https://github.com/phphe/he-tree-vue/commit/7cc19deb1cb5360f85898bbf8414a24c93668c31)) 94 | 95 | ### [2.0.1](https://github.com/phphe/he-tree-vue/compare/v2.0.0...v2.0.1) (2020-06-11) 96 | 97 | ## 2.0.0 (2020-06-11) 98 | 99 | 100 | ### Features 101 | 102 | * **base tree, draggable plugin:** RTL support ([bbdcba4](https://github.com/phphe/he-tree-vue/commit/bbdcba4b1eceef6596e3628f1dd5180ddc4dc090)), closes [#17](https://github.com/phphe/he-tree-vue/issues/17) 103 | * **draggable plugin:** add option triggerBySelf ([b0be699](https://github.com/phphe/he-tree-vue/commit/b0be699d27f309d634a25aa2a88a074d5d6693b4)) 104 | * **draggable plugin:** edge scroll ([843510e](https://github.com/phphe/he-tree-vue/commit/843510e1d66e1e66abf4c9643490fd8d65fe514e)) 105 | 106 | 107 | ### Bug Fixes 108 | 109 | * **base tree, draggable plugin:** update the usage of hp.binarySearch and hp.strRand ([877afc3](https://github.com/phphe/he-tree-vue/commit/877afc30983e1049d66438814f514e6ac5b27e51)) 110 | * **draggable plugin:** add event before-drop, move drop event to correct position ([2bad9f7](https://github.com/phphe/he-tree-vue/commit/2bad9f7ea91ae70380a6015acd4a3c1bfc109b62)) 111 | * **draggable plugin:** fix logic ([3ca59ea](https://github.com/phphe/he-tree-vue/commit/3ca59eae745a1ebca79a4054b896fed9288d0bd3)) 112 | * **type definition:** update type definition for v2 ([f66a38a](https://github.com/phphe/he-tree-vue/commit/f66a38a0131a8b42375f88f9fc75c8a906ed18e7)) -------------------------------------------------------------------------------- /src/components/VirtualizationList.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 172 | 173 | 179 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 106 | 107 | 232 | 233 | 264 | -------------------------------------------------------------------------------- /src/draggable/draggable.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-const */ 2 | import * as hp from "helper-js"; 3 | import draggableHelper, { Options, Store } from "draggable-helper"; 4 | import { obj, BaseNode } from "../types"; 5 | 6 | export interface Options2 extends Options { 7 | treeClass: string; 8 | nodeClass: string; 9 | nodeOuterClass: string; 10 | placeholderClass: string; 11 | rtl: boolean; 12 | draggingNodePositionMode: "top_left_corner" | "mouse"; 13 | } 14 | 15 | export interface Store2 extends Store { 16 | startTreeEl: HTMLElement; 17 | targetTreeEl: HTMLElement; 18 | placeholder: HTMLElement; 19 | } 20 | 21 | interface Hooks { 22 | beforeFirstMove?: (store: Store2, options: Options2) => boolean; 23 | afterFirstMove?: (store: Store2, options: Options2) => void; 24 | getNodeLevelByEl: (el: HTMLElement) => number; 25 | createPlaceholder: () => HTMLElement; 26 | setPlaceholderLevel: (placeholder: HTMLElement, level: number) => void; 27 | filterTargetTreeEl?: (el: HTMLElement, store: Store2) => boolean; 28 | afterTargetTreeElUpdated?: (store: Store2) => void; 29 | insertedPlaceholderAfterCreated: (store: Store2) => void; 30 | getTreeIndent: (treeEl: HTMLElement, store: Store2) => number; 31 | moveEnd: (action: string, info?: obj) => void; 32 | movePlaceholder: ( 33 | store: Store2, 34 | el: HTMLElement | undefined, 35 | targetLevel: number 36 | ) => void; 37 | onDrop: (store: Store2, restoreStyle: () => void) => void; 38 | } 39 | 40 | export default function makeTreeListDraggable( 41 | treeEl: HTMLElement, 42 | options: Options2, 43 | hooks: Hooks 44 | ) { 45 | const defaultOptions: Options = { 46 | updateMovedElementStyleManually: true, 47 | getMovedOrClonedElement: (directTriggerElement, store) => { 48 | // find closest node from parents 49 | const el = hp.findParent( 50 | store.triggerElement, 51 | (el) => hp.hasClass(el, options.nodeOuterClass), 52 | { withSelf: true } 53 | ); 54 | return el; 55 | }, 56 | // @ts-ignore 57 | beforeFirstMove(store: Store2, options: Options2) { 58 | store.startTreeEl = treeEl; 59 | if ( 60 | hooks.beforeFirstMove && 61 | hooks.beforeFirstMove(store, options) === false 62 | ) { 63 | return false; 64 | } 65 | }, 66 | // it means onMove 67 | // @ts-ignore 68 | beforeMove(store: Store2) { 69 | // first move 70 | // 第一次移动 71 | if (store.movedCount === 0) { 72 | // create placeholder 73 | // 创建占位元素 74 | const placeholder = hooks.createPlaceholder(); 75 | store.placeholder = placeholder; 76 | store.targetTreeEl = store.startTreeEl; 77 | hooks.afterTargetTreeElUpdated?.(store); 78 | hooks.insertedPlaceholderAfterCreated(store); 79 | hooks.setPlaceholderLevel( 80 | store.placeholder, 81 | hooks.getNodeLevelByEl(store.movedOrClonedElement) 82 | ); 83 | store.updateMovedElementStyle(); 84 | hooks.afterFirstMove?.(store, options); 85 | // skip first move 86 | // 跳过第一次移动 87 | hooks.moveEnd("first_move"); 88 | return; 89 | } 90 | // 91 | store.updateMovedElementStyle(); 92 | // 93 | const movingEl = store.movedElement; // node 94 | // find closest node and hovering tree 95 | let tree; 96 | const movingNode = movingEl; 97 | // movingNodeOf and movingNodeRect are not always real. when RTL, there 'x' is top right. when draggingNodePositionMode is mouse, there x and y are mouse position. So don't calc them with their width or height. 98 | // movingNodeOf 和 movingNodeRect并非一直如字面意义是movingNode真实坐标. RTL时, x坐标是右上角. draggingNodePositionMode是mouse时, x和y是鼠标坐标. 99 | const movingNodeRealEl = movingNode.querySelector( 100 | `.${options.nodeClass}` 101 | ) as HTMLElement; // movingNode is node outer 102 | let movingNodeOf = hp.getOffset(movingNodeRealEl); 103 | let movingNodeRect = hp.getBoundingClientRect(movingNodeRealEl); 104 | if (options.draggingNodePositionMode === "mouse") { 105 | // use mouse position as dragging node position 106 | const { moveEvent } = store; 107 | // @ts-ignore 108 | movingNodeOf = { x: moveEvent.pageX, y: moveEvent.pageY }; 109 | // @ts-ignore 110 | movingNodeRect = { x: moveEvent.clientX, y: moveEvent.clientY }; 111 | } else if (options.rtl) { 112 | movingNodeOf.x += movingNode.offsetWidth; 113 | movingNodeRect.x += movingNode.offsetWidth; 114 | } 115 | // find tree with elementsFromPoint 116 | let found; 117 | let firstElement; 118 | for (const itemEl of hp.elementsFromPoint( 119 | movingNodeRect.x, 120 | movingNodeRect.y 121 | )) { 122 | if (!firstElement) { 123 | firstElement = itemEl; 124 | } 125 | if (hp.hasClass(itemEl, options.treeClass)) { 126 | found = itemEl; 127 | break; 128 | } 129 | } 130 | // check if the found element is covered by other elements 131 | if ( 132 | firstElement !== found && 133 | !hp.isDescendantOf(firstElement, found) 134 | ) { 135 | found = null; 136 | } 137 | tree = found as HTMLElement; 138 | if (!tree) { 139 | // out of tree or tree is covered by other elements 140 | hooks.moveEnd("no_target_tree"); 141 | return; 142 | } 143 | // check if target tree right 144 | if ( 145 | hooks.filterTargetTreeEl && 146 | hooks.filterTargetTreeEl(tree, store) === false 147 | ) { 148 | hooks.moveEnd("disallowed_tree"); 149 | return; 150 | } 151 | store.targetTreeEl = tree; 152 | hooks.afterTargetTreeElUpdated?.(store); 153 | const indent = hooks.getTreeIndent(store.targetTreeEl, store); 154 | // 155 | class DecisionInfo { 156 | private _visibleNodesElements?: NodeList; 157 | public get visibleNodesElementsExcludeDragging() { 158 | if (!this._visibleNodesElements) { 159 | this._visibleNodesElements = store.targetTreeEl.querySelectorAll( 160 | `.${options.nodeClass}:not(.${options.draggingClassName} .${options.nodeClass})` 161 | ); 162 | } 163 | return this._visibleNodesElements!; 164 | } 165 | 166 | // index is for visibleNodesElementsExcludeDragging 167 | private _closestNodeElAndIndex?: { 168 | el: HTMLElement; 169 | index: number; 170 | } | null; 171 | public get closestNodeElAndIndex() { 172 | if (this._closestNodeElAndIndex === undefined) { 173 | const nodes = this.visibleNodesElementsExcludeDragging; 174 | // 175 | if (nodes.length === 0) { 176 | this._closestNodeElAndIndex = null; 177 | } else { 178 | let found, index; 179 | const t = hp.binarySearch( 180 | nodes, 181 | (node) => hp.getOffset(node).y - movingNodeOf.y, 182 | { returnNearestIfNoHit: true } 183 | )!; 184 | if (t.hit) { 185 | found = t.value; 186 | index = t.index; 187 | } else { 188 | if (t.greater) { 189 | index = t.index - 1; 190 | found = nodes[index] || t.value; 191 | } else { 192 | index = t.index; 193 | found = t.value; 194 | } 195 | } 196 | this._closestNodeElAndIndex = { el: found, index: index }; 197 | } 198 | } 199 | return this._closestNodeElAndIndex!; 200 | } 201 | 202 | // prev node is closest node when closest node is not placeholder, or is prev node of closest node when it is placeholder 203 | // closest node不是placeholder时, prev node是closest node, 否则是closest node上方的node 204 | private _prevNodeAndIndex?: { 205 | el: HTMLElement; 206 | index: number; 207 | } | null; 208 | public get prevNodeAndIndex() { 209 | if (this._prevNodeAndIndex === undefined) { 210 | let el, index; 211 | if (this.closestNodeElAndIndex) { 212 | index = this.closestNodeElAndIndex.index; // can't reduce 1; 不能减1 213 | el = this.visibleNodesElementsExcludeDragging[index]; 214 | if ( 215 | el && 216 | hp.hasClass(el, options.placeholderClass) 217 | ) { 218 | index--; 219 | el = this.visibleNodesElementsExcludeDragging[index]; 220 | } 221 | if (el) { 222 | this._prevNodeAndIndex = { el: el, index }; 223 | } else { 224 | this._prevNodeAndIndex = null; 225 | } 226 | } else { 227 | this._prevNodeAndIndex = null; 228 | } 229 | } 230 | return this._prevNodeAndIndex; 231 | } 232 | 233 | private _prevNodeOffset?: ReturnType | null; 234 | public get prevtNodeOffset() { 235 | if (this._prevNodeOffset === undefined) { 236 | this._prevNodeOffset = this.prevNodeAndIndex 237 | ? hp.getOffset(this.prevNodeAndIndex.el) 238 | : null; 239 | } 240 | return this._prevNodeOffset; 241 | } 242 | 243 | private _prevNodeLevel?: number | null; 244 | public get prevNodeLevel() { 245 | if (this._prevNodeLevel === undefined) { 246 | this._prevNodeLevel = this.prevNodeAndIndex 247 | ? hooks.getNodeLevelByEl(this.prevNodeAndIndex.el) 248 | : null; 249 | } 250 | return this._prevNodeLevel; 251 | } 252 | 253 | private _nextNodeAndIndex?: { 254 | el: HTMLElement; 255 | index: number; 256 | } | null; 257 | public get nextNodeAndIndex() { 258 | if (this._nextNodeAndIndex === undefined) { 259 | let el, index; 260 | if (this.closestNodeElAndIndex) { 261 | index = this.closestNodeElAndIndex.index + 1; 262 | el = this.visibleNodesElementsExcludeDragging[index]; 263 | if ( 264 | el && 265 | hp.hasClass(el, options.placeholderClass) 266 | ) { 267 | index++; 268 | el = this.visibleNodesElementsExcludeDragging[index]; 269 | } 270 | if (el) { 271 | this._nextNodeAndIndex = { el: el, index }; 272 | } else { 273 | this._nextNodeAndIndex = null; 274 | } 275 | } else { 276 | this._nextNodeAndIndex = null; 277 | } 278 | } 279 | return this._nextNodeAndIndex; 280 | } 281 | } 282 | const info = new DecisionInfo(); 283 | const onMiddleOfPrevNode = () => 284 | movingNodeOf.y < 285 | info.prevtNodeOffset!.y + info.prevNodeAndIndex!.el.offsetHeight / 2; 286 | // Positive number mean moving node at left of prev node 287 | const prevNodeLeftXReduceMovingNodeX = () => 288 | !options.rtl 289 | ? info.prevtNodeOffset!.x - movingNodeOf.x 290 | : movingNodeOf.x - 291 | (info.prevtNodeOffset!.x + info.prevNodeAndIndex!.el.offsetWidth); 292 | const atRightOfPrevNodeIndent = () => 293 | !options.rtl 294 | ? movingNodeOf.x > info.prevtNodeOffset!.x + indent 295 | : movingNodeOf.x < 296 | info.prevtNodeOffset!.x + 297 | info.prevNodeAndIndex!.el.offsetWidth - 298 | indent; 299 | // 300 | let targetLevel: number | undefined; 301 | 302 | let nextNodeLevel = info.nextNodeAndIndex 303 | ? hooks.getNodeLevelByEl(info.nextNodeAndIndex.el) 304 | : 1; // targetLevel max is nextNodeLevel 305 | let prevNodeAndIndex = info.prevNodeAndIndex; 306 | if ( 307 | !prevNodeAndIndex || 308 | (prevNodeAndIndex.index === 0 && onMiddleOfPrevNode()) 309 | ) { 310 | targetLevel = 1; 311 | prevNodeAndIndex = null; 312 | nextNodeLevel = 1; 313 | } else { 314 | const atLeft = prevNodeLeftXReduceMovingNodeX(); 315 | if (atLeft > 0) { 316 | targetLevel = info.prevNodeLevel! - Math.ceil(atLeft / indent); 317 | } else if (atRightOfPrevNodeIndent()) { 318 | targetLevel = info.prevNodeLevel! + 1; 319 | } else { 320 | targetLevel = info.prevNodeLevel!; 321 | } 322 | } 323 | if (targetLevel < nextNodeLevel) { 324 | targetLevel = nextNodeLevel; 325 | } 326 | hooks.movePlaceholder(store, prevNodeAndIndex?.el, targetLevel); 327 | }, 328 | // @ts-ignore 329 | beforeDrop(store: Store2) { 330 | const { endEvent } = store; 331 | const movingEl = store.movedElement; 332 | const { placeholder, movedCount, targetTreeEl, startTreeEl } = store; 333 | // destroy placeholder 334 | const restoreStyle = () => { 335 | hp.removeEl(placeholder); 336 | store.updateMovedElementStyle(); 337 | }; 338 | // 339 | hooks.onDrop(store, restoreStyle); 340 | }, 341 | }; 342 | Object.keys(defaultOptions).forEach((key) => { 343 | // @ts-ignore 344 | if (options[key] === undefined) { 345 | // @ts-ignore 346 | options[key] = defaultOptions[key]; 347 | } 348 | }); 349 | const { destroy, options: draggableHelperOptions } = draggableHelper( 350 | treeEl, 351 | options 352 | ); 353 | return { destroy, options: draggableHelperOptions, hooks }; 354 | } 355 | -------------------------------------------------------------------------------- /src/BaseTree.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 509 | 510 | 515 | -------------------------------------------------------------------------------- /src/draggable/Draggable.vue: -------------------------------------------------------------------------------- 1 | 624 | 625 | 635 | --------------------------------------------------------------------------------