├── .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 | 
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 |
2 | .VirtualizationList.virtualization-list(:is="listTag" @scroll.passive="update")
3 | .vl-placeholder.top(:style="{height: top+'px'}")
4 | template(v-for="info in visibleItems")
5 | slot(:item="info.item" :index="info.index")
6 | .vl-placeholder.bottom(:style="{height: bottom+'px'}")
7 |
8 |
9 |
172 |
173 |
179 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 | #app: .row
3 | .col-2
4 | h3 Base
5 | button(@click="$refs.tree1.unfoldAll()") unfold all
6 | button(@click="$refs.tree1.foldAll()") fold all
7 | BaseTree(:treeData="treeData1" ref="tree1")
8 | template(v-slot="{node, index, tree}")
9 | b(v-if="node.$children.length > 0" @click="node.$folded=!node.$folded") {{node.$folded ? '+' : '-'}}
10 | input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
11 | |
12 | span {{node.text}}
13 | .col-2
14 | h3 Flat Data
15 | BaseTree(:flatData="flatData1")
16 | template(v-slot="{node, index, tree}")
17 | b(v-if="node.$children.length > 0" @click="node.$folded=!node.$folded") {{node.$folded ? '+' : '-'}}
18 | input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
19 | |
20 | span {{node.text}}
21 | h3 Virtualization
22 | BaseTree(:treeData="treeData2" virtualization style="height:400px;overflow:auto" ref="tree2")
23 | template(v-slot="{node, index, tree}")
24 | b(v-if="node.$children.length > 0" @click="node.$folded=!node.$folded") {{node.$folded ? '+' : '-'}}
25 | input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
26 | |
27 | span {{node.text}}
28 | h3 Children Lazy Loading
29 | button(@click="$refs.tree3.unfoldAll()") unfold all
30 | button(@click="$refs.tree3.foldAll()") fold all
31 | BaseTree(:treeData="treeData3" virtualization style="height:200px;overflow:auto" :childrenLoader="childrenLoader" childrenLazyLoading ref="tree3")
32 | template(v-slot="{node, index, tree}")
33 | b(@click="tree.toggleFold(node)") {{node.$folded ? '+' : '-'}}
34 | input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
35 | |
36 | span {{node.text}}
37 | .col-2
38 | h3 Drag & Drop
39 | Draggable(:treeData="treeData4" virtualization style="height:200px;overflow:auto" :childrenLoader="childrenLoader" childrenLazyLoading)
40 | template(v-slot="{node, index, tree}")
41 | b(@click="tree.toggleFold(node)") {{node.$folded ? '+' : '-'}}
42 | input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
43 | |
44 | span {{node.text}}{{node.$childrenLoading ? '-loading' : ''}}
45 | h3 Edge Scroll
46 | Draggable(:treeData="treeData5" virtualization style="height:200px;overflow:auto" edgeScroll)
47 | template(v-slot="{node, index, tree}")
48 | b(@click="tree.toggleFold(node)") {{node.$folded ? '+' : '-'}}
49 | input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
50 | |
51 | span {{node.text}}
52 | h3 RTL
53 | Draggable(:treeData="treeData5" virtualization style="height:200px;overflow:auto" edgeScroll rtl)
54 | template(v-slot="{node, index, tree}")
55 | b(@click="tree.toggleFold(node)") {{node.$folded ? '+' : '-'}}
56 | input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
57 | |
58 | span {{node.text}}
59 | .col-3
60 | h3 Style
61 | Draggable.tree6(:treeData="treeData6" virtualization style="height:200px;overflow:auto" edgeScroll)
62 | template(v-slot="{node, index, tree}")
63 | b(@click="tree.toggleFold(node)") {{node.$folded ? '+' : '-'}}
64 | input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
65 | |
66 | span {{node.text}}
67 | br
68 | Draggable.tree7(:treeData="treeData7" virtualization style="height:200px;overflow:auto" edgeScroll)
69 | template(v-slot="{node, index, tree}")
70 | b(@click="tree.toggleFold(node)") {{node.$folded ? '+' : '-'}}
71 | input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
72 | |
73 | span {{node.text}}
74 | h3 Custome Trigger
75 | Draggable.tree8(:treeData="treeData8" virtualization style="height:500px;overflow:auto" triggerClass="drag-trigger")
76 | template(v-slot="{node, index, tree}")
77 | button.drag-trigger(style="margin-right:.5em") drag
78 | b(@click="tree.toggleFold(node)") {{node.$folded ? '+' : '-'}}
79 | input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
80 | |
81 | span {{node.text}}
82 | //- .col-3
83 | //- h3 Pro
84 | //- DraggablePro.tree6(:treeData="treeData6" virtualization style="height:200px;overflow:auto" edgeScroll crossTree)
85 | //- template(v-slot="{node, index, tree}")
86 | //- b(@click="tree.toggleFold(node)") {{node.$folded ? '+' : '-'}}
87 | //- input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
88 | //- |
89 | //- span {{node.text}}
90 | //- hr
91 | //- DraggablePro.tree6(:treeData="treeData6" virtualization style="height:200px;overflow:auto" edgeScroll crossTree)
92 | //- template(v-slot="{node, index, tree}")
93 | //- b(@click="tree.toggleFold(node)") {{node.$folded ? '+' : '-'}}
94 | //- input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
95 | //- |
96 | //- span {{node.text}}
97 | //- hr
98 | //- h4 clone when drag
99 | //- DraggablePro.tree6(:treeData="treeData6" virtualization style="height:200px;overflow:auto" edgeScroll crossTree cloneWhenDrag)
100 | //- template(v-slot="{node, index, tree}")
101 | //- b(@click="tree.toggleFold(node)") {{node.$folded ? '+' : '-'}}
102 | //- input(type="checkbox" v-model="node.$checked" @change="tree.updateChecked(node)")
103 | //- |
104 | //- span {{node.text}}
105 |
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 |
2 | VirtualizationList.he-tree(:id="treeID" ref="virtualizationList" :items="visibleNodes" :enabled="virtualization" :prerender="virtualizationPrerender" :class="{'he-tree-rtl': rtl, 'he-tree-dragging':dragging}")
3 | template(v-slot="info")
4 | .tree-node-outer.vl-item(:key="info.item.$id" :data-vindex="info.index" :data-id="info.item.$id" :style="[nodeIndentStyle(info.item), info.item.$outerStyle]" :class="info.item.$outerClass")
5 | .tree-node(:class="info.item.$nodeClass" :style="info.item.$nodeStyle")
6 | slot(:node="info.item" :tree="tree") {{info.item[textKey]}}
7 |
8 |
9 |
509 |
510 |
515 |
--------------------------------------------------------------------------------
/src/draggable/Draggable.vue:
--------------------------------------------------------------------------------
1 |
624 |
625 |
635 |
--------------------------------------------------------------------------------