ketekny-ui-kit 1.0.50 → 1.0.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +119 -114
- package/package.json +1 -4
- package/src/ui/kTree.vue +194 -38
package/README.md
CHANGED
|
@@ -1,56 +1,95 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ketekny-ui-kit
|
|
2
2
|
|
|
3
|
-
Vue 3 UI component library
|
|
3
|
+
Vue 3 UI component library with Tailwind CSS styling and utility plugins.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- `vue`: `^3.0.0` (peer dependency)
|
|
8
|
+
- Tailwind CSS build pipeline in your app (`tailwindcss`, `postcss`, `autoprefixer`)
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
## Installation
|
|
8
11
|
|
|
9
12
|
```bash
|
|
10
13
|
npm install ketekny-ui-kit
|
|
11
14
|
```
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
Required peer dependency:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install vue
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
If your app does not already use Tailwind CSS:
|
|
14
23
|
|
|
15
24
|
```bash
|
|
16
|
-
npm install
|
|
25
|
+
npm install -D tailwindcss postcss autoprefixer
|
|
17
26
|
```
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
In your app entry (`main.js` / `main.ts`):
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { createApp } from "vue";
|
|
34
|
+
import App from "./App.vue";
|
|
35
|
+
import "ketekny-ui-kit/styles.css";
|
|
36
|
+
|
|
37
|
+
import {
|
|
38
|
+
kButton,
|
|
39
|
+
kInput,
|
|
40
|
+
kSelect,
|
|
41
|
+
kDateSelector,
|
|
42
|
+
kToggle,
|
|
43
|
+
toastPlugin,
|
|
44
|
+
confirmPlugin,
|
|
45
|
+
alertPlugin,
|
|
46
|
+
inputDialogPlugin,
|
|
47
|
+
tooltipPlugin,
|
|
48
|
+
} from "ketekny-ui-kit";
|
|
49
|
+
|
|
50
|
+
const app = createApp(App);
|
|
20
51
|
|
|
21
|
-
|
|
52
|
+
app.component("kButton", kButton);
|
|
53
|
+
app.component("kInput", kInput);
|
|
54
|
+
app.component("kSelect", kSelect);
|
|
55
|
+
app.component("kDateSelector", kDateSelector);
|
|
56
|
+
app.component("kToggle", kToggle);
|
|
22
57
|
|
|
23
|
-
|
|
58
|
+
app.use(toastPlugin);
|
|
59
|
+
app.use(confirmPlugin);
|
|
60
|
+
app.use(alertPlugin);
|
|
61
|
+
app.use(inputDialogPlugin);
|
|
62
|
+
app.use(tooltipPlugin);
|
|
24
63
|
|
|
25
|
-
|
|
64
|
+
app.mount("#app");
|
|
65
|
+
```
|
|
26
66
|
|
|
27
|
-
|
|
28
|
-
2. Set permissions to `Read and write`.
|
|
29
|
-
3. Under package access, allow `ketekny-ui-kit`.
|
|
30
|
-
4. Enable `Bypass two-factor authentication (2FA)`.
|
|
31
|
-
5. Create token and copy it (shown only once).
|
|
67
|
+
Example component usage:
|
|
32
68
|
|
|
33
|
-
|
|
69
|
+
```vue
|
|
70
|
+
<template>
|
|
71
|
+
<div class="p-6 space-y-3">
|
|
72
|
+
<kInput v-model="name" label="Name" />
|
|
73
|
+
<kButton label="Save" success @click="save" />
|
|
74
|
+
</div>
|
|
75
|
+
</template>
|
|
34
76
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- Key: `NPM_TOKEN`
|
|
38
|
-
- Value: `<your npm token>`
|
|
39
|
-
- Type: `Variable`
|
|
40
|
-
- Masked: `true`
|
|
41
|
-
- Protected: `true` (if publishing only from protected branches, e.g. `main`)
|
|
42
|
-
3. Save and rerun pipeline.
|
|
77
|
+
<script setup>
|
|
78
|
+
import { ref } from "vue";
|
|
43
79
|
|
|
44
|
-
|
|
80
|
+
const name = ref("");
|
|
45
81
|
|
|
46
|
-
|
|
47
|
-
|
|
82
|
+
function save() {
|
|
83
|
+
console.log("Saved:", name.value);
|
|
84
|
+
}
|
|
85
|
+
</script>
|
|
86
|
+
```
|
|
48
87
|
|
|
49
|
-
## Tailwind Setup
|
|
88
|
+
## Tailwind Setup
|
|
50
89
|
|
|
51
|
-
Use the provided
|
|
90
|
+
Use the provided preset to include semantic tokens and safelist patterns used by dynamic component classes.
|
|
52
91
|
|
|
53
|
-
|
|
92
|
+
`tailwind.config.js` (ESM):
|
|
54
93
|
|
|
55
94
|
```js
|
|
56
95
|
import tailwindPreset from "ketekny-ui-kit/tailwind-preset.js";
|
|
@@ -66,7 +105,7 @@ export default {
|
|
|
66
105
|
};
|
|
67
106
|
```
|
|
68
107
|
|
|
69
|
-
|
|
108
|
+
`tailwind.config.cjs` (CommonJS):
|
|
70
109
|
|
|
71
110
|
```js
|
|
72
111
|
const tailwindPreset = require("ketekny-ui-kit/tailwind-preset.js").default;
|
|
@@ -82,101 +121,66 @@ module.exports = {
|
|
|
82
121
|
};
|
|
83
122
|
```
|
|
84
123
|
|
|
85
|
-
|
|
86
|
-
- The preset contains the semantic color definitions and safelist patterns used by dynamic classes (alerts/toasts/button intents).
|
|
87
|
-
- If you replace `safelist` in your own config, keep equivalent patterns or dynamic semantic classes may be purged.
|
|
124
|
+
## Plugins
|
|
88
125
|
|
|
89
|
-
|
|
126
|
+
- `toastPlugin`: adds `$toast.success|error|warning|info|show(...)`
|
|
127
|
+
- `confirmPlugin`: adds `$confirm(options)`
|
|
128
|
+
- `alertPlugin`: adds `$alert.success|warning|error|info(...)`
|
|
129
|
+
- `inputDialogPlugin`: adds `$prompt(options)`
|
|
130
|
+
- `tooltipPlugin`: registers `v-tooltip`
|
|
90
131
|
|
|
91
|
-
|
|
132
|
+
### Plugin API (quick reference)
|
|
92
133
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
134
|
+
- `$toast.success(message, options?)`
|
|
135
|
+
- `$toast.error(message, options?)`
|
|
136
|
+
- `$toast.warning(message, options?)`
|
|
137
|
+
- `$toast.info(message, options?)`
|
|
138
|
+
- `$toast.show({ type?, message, duration?, ... })`
|
|
139
|
+
- `$confirm({ title?, message, onConfirm?, onCancel? })`
|
|
140
|
+
- `$alert.success(message)`
|
|
141
|
+
- `$alert.error(message)`
|
|
142
|
+
- `$alert.warning(message)`
|
|
143
|
+
- `$alert.info(message)`
|
|
144
|
+
- `$prompt({ title?, message?, placeholder?, onConfirm? })`
|
|
96
145
|
|
|
97
|
-
|
|
146
|
+
Note: exact option keys may vary by component/plugin implementation version.
|
|
98
147
|
|
|
99
|
-
|
|
148
|
+
## Exports
|
|
100
149
|
|
|
101
|
-
|
|
102
|
-
import { createApp } from "vue";
|
|
103
|
-
import App from "./App.vue";
|
|
104
|
-
import "./style.css";
|
|
150
|
+
Components:
|
|
105
151
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
confirmPlugin,
|
|
114
|
-
alertPlugin,
|
|
115
|
-
inputDialogPlugin,
|
|
116
|
-
tooltipPlugin,
|
|
117
|
-
} from "ketekny-ui-kit";
|
|
152
|
+
- `kMessage`, `kCode`, `kToolbar`, `kTable`, `kTabs`, `kChip`
|
|
153
|
+
- `kSpinner`, `kDatatable`, `kIcon`, `kMenu`, `kSkeleton`
|
|
154
|
+
- `kProgressBar`, `kTree`, `kButton`, `kSelect`, `kUploader`
|
|
155
|
+
- `kToggle`, `kInput`, `kDateSelector`, `kDateSelectorV2`, `kEditor`
|
|
156
|
+
- `kSelectButton`, `kTags`, `kSearch`, `kArrayList`, `kList`, `kTextArea`
|
|
157
|
+
- `kDialog`, `kDrawer`
|
|
158
|
+
- `kAppHeader`, `kAppFooter`, `kAppMain`, `kHero`
|
|
118
159
|
|
|
119
|
-
|
|
160
|
+
Other exports:
|
|
120
161
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
app.component("kSelect", kSelect);
|
|
124
|
-
app.component("kDateSelector", kDateSelector);
|
|
125
|
-
app.component("kToggle", kToggle);
|
|
162
|
+
- `toastPlugin`, `confirmPlugin`, `alertPlugin`, `tooltipPlugin`, `inputDialogPlugin`
|
|
163
|
+
- `tailwindPreset`
|
|
126
164
|
|
|
127
|
-
|
|
128
|
-
app.use(confirmPlugin);
|
|
129
|
-
app.use(alertPlugin);
|
|
130
|
-
app.use(inputDialogPlugin);
|
|
131
|
-
app.use(tooltipPlugin);
|
|
165
|
+
## Common Issues
|
|
132
166
|
|
|
133
|
-
|
|
134
|
-
|
|
167
|
+
- Styles look missing:
|
|
168
|
+
- Confirm `import "ketekny-ui-kit/styles.css";` is in your app entry file.
|
|
169
|
+
- Tailwind classes from the kit are not applied:
|
|
170
|
+
- Confirm `tailwind.config` includes `./node_modules/ketekny-ui-kit/**/*.{vue,js,ts,jsx,tsx}` in `content`.
|
|
171
|
+
- Confirm `presets: [tailwindPreset]` is present.
|
|
172
|
+
- Vue peer dependency warning:
|
|
173
|
+
- Install/update Vue 3 in your app: `npm install vue@^3`.
|
|
174
|
+
- Dynamic semantic classes are purged:
|
|
175
|
+
- Keep the provided preset safelist patterns, or replicate them in your own Tailwind config.
|
|
135
176
|
|
|
136
|
-
##
|
|
137
|
-
|
|
138
|
-
- `toastPlugin` adds `$toast` (`success`, `error`, `warning`, `info`, `show`)
|
|
139
|
-
- `confirmPlugin` adds `$confirm(options)`
|
|
140
|
-
- `alertPlugin` adds `$alert.success|warning|error|info(...)`
|
|
141
|
-
- `inputDialogPlugin` adds `$prompt(options)`
|
|
142
|
-
- `tooltipPlugin` registers `v-tooltip`
|
|
143
|
-
|
|
144
|
-
## Available Exports
|
|
145
|
-
|
|
146
|
-
### Components
|
|
147
|
-
|
|
148
|
-
- `kMessage`
|
|
149
|
-
- `kButton`
|
|
150
|
-
- `kChip`
|
|
151
|
-
- `kCode`
|
|
152
|
-
- `kDialog`
|
|
153
|
-
- `kDrawer`
|
|
154
|
-
- `kInput`
|
|
155
|
-
- `kDateSelector`
|
|
156
|
-
- `kToolbar`
|
|
157
|
-
- `kSelect`
|
|
158
|
-
- `kTable`
|
|
159
|
-
- `kTabs`
|
|
160
|
-
- `kToggle`
|
|
161
|
-
- `kUploader`
|
|
162
|
-
- `kEditor`
|
|
163
|
-
- `kSpinner`
|
|
164
|
-
- `kSelectButton`
|
|
165
|
-
- `kDatatable`
|
|
166
|
-
- `kIcon`
|
|
167
|
-
- `kMenu`
|
|
168
|
-
- `kTags`
|
|
169
|
-
- `kSearch`
|
|
170
|
-
- `kArrayList`
|
|
171
|
-
- `kSkeleton`
|
|
172
|
-
- `kAppHeader`
|
|
173
|
-
- `kAppFooter`
|
|
174
|
-
- `kAppMain`
|
|
175
|
-
- `kHero`
|
|
176
|
-
|
|
177
|
-
### Tailwind preset export
|
|
177
|
+
## Development
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
```bash
|
|
180
|
+
npm run dev
|
|
181
|
+
npm run build
|
|
182
|
+
npm run preview
|
|
183
|
+
```
|
|
180
184
|
|
|
181
185
|
## License
|
|
182
186
|
|
|
@@ -184,4 +188,5 @@ ISC
|
|
|
184
188
|
|
|
185
189
|
## Links
|
|
186
190
|
|
|
187
|
-
-
|
|
191
|
+
- Live demo/docs: https://ui.ketekny.gr
|
|
192
|
+
- npm: https://www.npmjs.com/package/ketekny-ui-kit
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ketekny-ui-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.52",
|
|
5
5
|
"description": "A Vue 3 UI component library with Tailwind CSS styling",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"files": [
|
|
@@ -43,13 +43,10 @@
|
|
|
43
43
|
"class-variance-authority": "^0.7.1",
|
|
44
44
|
"clsx": "^2.1.1",
|
|
45
45
|
"element-plus": "^2.13.5",
|
|
46
|
-
"he-tree-vue": "^3.1.2",
|
|
47
|
-
"json-editor-vue": "^0.18.1",
|
|
48
46
|
"lucide-vue-next": "^0.511.0",
|
|
49
47
|
"moment": "^2.30.1",
|
|
50
48
|
"primeicons": "^7.0.0",
|
|
51
49
|
"primevue": "^4.3.4",
|
|
52
|
-
"quill": "2.0.2",
|
|
53
50
|
"reka-ui": "^2.8.2",
|
|
54
51
|
"simple-code-editor": "^2.0.9",
|
|
55
52
|
"tailwind-merge": "^3.5.0",
|
package/src/ui/kTree.vue
CHANGED
|
@@ -1,54 +1,128 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="k-tree text-primary/90" :class="disabled ? 'k-tree--disabled' : ''">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
class="k-tree__root"
|
|
6
|
-
:value="treeData"
|
|
3
|
+
<TreeBranch
|
|
4
|
+
:nodes="treeData"
|
|
7
5
|
:indent="indent"
|
|
6
|
+
:disabled="disabled"
|
|
8
7
|
:draggable="draggable"
|
|
9
8
|
:droppable="droppable"
|
|
10
|
-
:
|
|
11
|
-
:
|
|
12
|
-
@
|
|
13
|
-
@
|
|
14
|
-
@
|
|
15
|
-
|
|
9
|
+
:selected-keys="resolvedSelectionKeys"
|
|
10
|
+
:resolve-node-label="resolveNodeLabel"
|
|
11
|
+
@node-click="onNodeClick"
|
|
12
|
+
@toggle="toggleNode"
|
|
13
|
+
@drag-start="onDragStart"
|
|
14
|
+
@node-drop="onNodeDrop">
|
|
15
|
+
<template #default="{ node, path }">
|
|
16
|
+
<slot name="default" :node="node" :path="path" :tree="treeApi">
|
|
17
|
+
{{ resolveNodeLabel(node) }}
|
|
18
|
+
</slot>
|
|
19
|
+
</template>
|
|
20
|
+
</TreeBranch>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script>
|
|
25
|
+
import { ChevronRight } from 'lucide-vue-next'
|
|
26
|
+
|
|
27
|
+
const TreeBranch = {
|
|
28
|
+
name: 'TreeBranch',
|
|
29
|
+
components: { ChevronRight },
|
|
30
|
+
props: {
|
|
31
|
+
nodes: { type: Array, default: () => [] },
|
|
32
|
+
indent: { type: Number, default: 20 },
|
|
33
|
+
disabled: { type: Boolean, default: false },
|
|
34
|
+
draggable: { type: [Boolean, Function], default: false },
|
|
35
|
+
droppable: { type: [Boolean, Function], default: false },
|
|
36
|
+
selectedKeys: { type: Object, default: () => ({}) },
|
|
37
|
+
resolveNodeLabel: { type: Function, required: true },
|
|
38
|
+
},
|
|
39
|
+
emits: ['node-click', 'toggle', 'drag-start', 'node-drop'],
|
|
40
|
+
methods: {
|
|
41
|
+
hasChildren(node) {
|
|
42
|
+
return Array.isArray(node?.children) && node.children.length > 0
|
|
43
|
+
},
|
|
44
|
+
canDrag(node) {
|
|
45
|
+
return this.draggable === true || (typeof this.draggable === 'function' && this.draggable(node.__raw || node))
|
|
46
|
+
},
|
|
47
|
+
canDrop(node) {
|
|
48
|
+
return this.droppable === true || (typeof this.droppable === 'function' && this.droppable(node.__raw || node))
|
|
49
|
+
},
|
|
50
|
+
isSelected(key) {
|
|
51
|
+
return this.selectedKeys?.[String(key)] === true
|
|
52
|
+
},
|
|
53
|
+
onDragStart(event, node) {
|
|
54
|
+
if (!this.canDrag(node)) {
|
|
55
|
+
event.preventDefault()
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
event.dataTransfer.effectAllowed = 'move'
|
|
59
|
+
event.dataTransfer.setData('text/plain', String(node.key))
|
|
60
|
+
this.$emit('drag-start', node)
|
|
61
|
+
},
|
|
62
|
+
onDrop(event, node) {
|
|
63
|
+
if (!this.canDrop(node)) return
|
|
64
|
+
event.preventDefault()
|
|
65
|
+
this.$emit('node-drop', node)
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
template: `
|
|
69
|
+
<ul class="k-tree-branch">
|
|
70
|
+
<li v-for="node in nodes" :key="node.key" class="k-tree-node">
|
|
16
71
|
<div
|
|
17
72
|
class="k-tree-node-content"
|
|
18
|
-
:class="
|
|
19
|
-
|
|
73
|
+
:class="[
|
|
74
|
+
isSelected(node.key) ? 'k-tree-node-content--selected' : '',
|
|
75
|
+
canDrop(node) ? 'k-tree-node-content--droppable' : '',
|
|
76
|
+
]"
|
|
77
|
+
:style="{ paddingLeft: node.__level ? (node.__level * indent + 7) + 'px' : undefined }"
|
|
78
|
+
:draggable="canDrag(node)"
|
|
79
|
+
@dragstart="onDragStart($event, node)"
|
|
80
|
+
@dragover.prevent="canDrop(node)"
|
|
81
|
+
@drop="onDrop($event, node)"
|
|
82
|
+
@click="$emit('node-click', $event, node)">
|
|
20
83
|
<button
|
|
21
84
|
v-if="hasChildren(node)"
|
|
22
85
|
type="button"
|
|
23
86
|
class="k-tree-node-toggler"
|
|
24
87
|
:aria-label="node.$folded ? 'Expand node' : 'Collapse node'"
|
|
25
|
-
@click.stop="
|
|
88
|
+
@click.stop="$emit('toggle', node)">
|
|
26
89
|
<ChevronRight class="k-tree-node-toggler-icon" :class="node.$folded ? '' : 'k-tree-node-toggler-icon--open'" />
|
|
27
90
|
</button>
|
|
28
91
|
|
|
29
92
|
<span v-else class="k-tree-node-toggler k-tree-node-toggler--empty"></span>
|
|
30
93
|
|
|
31
94
|
<div class="k-tree-node-label">
|
|
32
|
-
<slot
|
|
95
|
+
<slot :node="node.__raw || node" :path="node.__path">
|
|
33
96
|
{{ resolveNodeLabel(node.__raw || node) }}
|
|
34
97
|
</slot>
|
|
35
98
|
</div>
|
|
36
99
|
</div>
|
|
37
|
-
</template>
|
|
38
|
-
</HETree>
|
|
39
|
-
</div>
|
|
40
|
-
</template>
|
|
41
100
|
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
101
|
+
<TreeBranch
|
|
102
|
+
v-if="hasChildren(node) && !node.$folded"
|
|
103
|
+
:nodes="node.children"
|
|
104
|
+
:indent="indent"
|
|
105
|
+
:disabled="disabled"
|
|
106
|
+
:draggable="draggable"
|
|
107
|
+
:droppable="droppable"
|
|
108
|
+
:selected-keys="selectedKeys"
|
|
109
|
+
:resolve-node-label="resolveNodeLabel"
|
|
110
|
+
@node-click="(...args) => $emit('node-click', ...args)"
|
|
111
|
+
@toggle="(...args) => $emit('toggle', ...args)"
|
|
112
|
+
@drag-start="(...args) => $emit('drag-start', ...args)"
|
|
113
|
+
@node-drop="(...args) => $emit('node-drop', ...args)">
|
|
114
|
+
<template #default="slotProps">
|
|
115
|
+
<slot v-bind="slotProps" />
|
|
116
|
+
</template>
|
|
117
|
+
</TreeBranch>
|
|
118
|
+
</li>
|
|
119
|
+
</ul>
|
|
120
|
+
`,
|
|
121
|
+
}
|
|
48
122
|
|
|
49
123
|
export default {
|
|
50
124
|
name: 'kTree',
|
|
51
|
-
components: {
|
|
125
|
+
components: { TreeBranch },
|
|
52
126
|
props: {
|
|
53
127
|
value: { type: Array, default: () => [] },
|
|
54
128
|
selectionKeys: { type: [Object, null], default: null },
|
|
@@ -67,6 +141,7 @@ export default {
|
|
|
67
141
|
data() {
|
|
68
142
|
return {
|
|
69
143
|
internalExpandedKeys: { ...(this.expandedKeys || {}) },
|
|
144
|
+
draggedNode: null,
|
|
70
145
|
treeData: [],
|
|
71
146
|
}
|
|
72
147
|
},
|
|
@@ -74,6 +149,12 @@ export default {
|
|
|
74
149
|
resolvedSelectionKeys() {
|
|
75
150
|
return this.selectionKeys ?? this.modelValue ?? {}
|
|
76
151
|
},
|
|
152
|
+
treeApi() {
|
|
153
|
+
return {
|
|
154
|
+
toggleFold: this.toggleNode,
|
|
155
|
+
treeData: this.treeData,
|
|
156
|
+
}
|
|
157
|
+
},
|
|
77
158
|
},
|
|
78
159
|
watch: {
|
|
79
160
|
value: {
|
|
@@ -95,15 +176,18 @@ export default {
|
|
|
95
176
|
rebuildTreeData() {
|
|
96
177
|
this.treeData = this.buildTreeData(this.value || [], '', this.internalExpandedKeys)
|
|
97
178
|
},
|
|
98
|
-
buildTreeData(nodes, parentPath, expandedMap) {
|
|
179
|
+
buildTreeData(nodes, parentPath, expandedMap, level = 0) {
|
|
99
180
|
return (nodes || []).map((node, index) => {
|
|
100
181
|
const key = this.getNodeKey(node, parentPath, index)
|
|
101
|
-
const
|
|
182
|
+
const path = parentPath ? `${parentPath}-${index}` : String(index)
|
|
183
|
+
const children = this.buildTreeData(node.children || [], key, expandedMap, level + 1)
|
|
102
184
|
return {
|
|
103
185
|
...node,
|
|
104
186
|
key,
|
|
105
187
|
children,
|
|
106
188
|
__raw: node,
|
|
189
|
+
__level: level,
|
|
190
|
+
__path: path,
|
|
107
191
|
$folded: this.hasChildren(node) ? expandedMap?.[key] !== true : false,
|
|
108
192
|
}
|
|
109
193
|
})
|
|
@@ -148,19 +232,76 @@ export default {
|
|
|
148
232
|
this.emitSelection(next)
|
|
149
233
|
this.$emit('nodeSelect', { originalEvent, node: node.__raw || node, data: node.__raw || node })
|
|
150
234
|
},
|
|
151
|
-
toggleNode(
|
|
152
|
-
|
|
153
|
-
this
|
|
235
|
+
toggleNode(node) {
|
|
236
|
+
node.$folded = !node.$folded
|
|
237
|
+
this.syncExpandedFromTree()
|
|
238
|
+
},
|
|
239
|
+
onDragStart(node) {
|
|
240
|
+
this.draggedNode = node
|
|
154
241
|
},
|
|
155
|
-
|
|
156
|
-
this
|
|
242
|
+
onNodeDrop(targetNode) {
|
|
243
|
+
if (!this.draggedNode || this.draggedNode.key === targetNode.key) return
|
|
244
|
+
const nextTree = this.cloneWithoutNode(this.treeData, this.draggedNode.key)
|
|
245
|
+
const moved = this.stripInternalFields(this.draggedNode)
|
|
246
|
+
const changedTree = this.appendChild(nextTree, targetNode.key, moved)
|
|
247
|
+
|
|
248
|
+
this.treeData = changedTree
|
|
249
|
+
this.draggedNode = null
|
|
250
|
+
this.syncExpandedFromTree()
|
|
251
|
+
|
|
252
|
+
const payload = {
|
|
253
|
+
dragNode: moved,
|
|
254
|
+
dropNode: this.stripInternalFields(targetNode),
|
|
255
|
+
treeData: this.toRawNodes(this.treeData),
|
|
256
|
+
}
|
|
257
|
+
this.$emit('drop', payload)
|
|
258
|
+
this.$emit('change', payload)
|
|
157
259
|
},
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
260
|
+
cloneWithoutNode(nodes, key) {
|
|
261
|
+
return (nodes || [])
|
|
262
|
+
.filter((node) => String(node.key) !== String(key))
|
|
263
|
+
.map((node) => ({
|
|
264
|
+
...node,
|
|
265
|
+
children: this.cloneWithoutNode(node.children || [], key),
|
|
266
|
+
}))
|
|
267
|
+
},
|
|
268
|
+
appendChild(nodes, targetKey, child) {
|
|
269
|
+
return (nodes || []).map((node) => {
|
|
270
|
+
if (String(node.key) === String(targetKey)) {
|
|
271
|
+
const nextChildren = [...(node.children || []), this.decorateMovedNode(child, node)]
|
|
272
|
+
return { ...node, children: nextChildren, $folded: false }
|
|
273
|
+
}
|
|
274
|
+
return { ...node, children: this.appendChild(node.children || [], targetKey, child) }
|
|
275
|
+
})
|
|
161
276
|
},
|
|
162
|
-
|
|
163
|
-
const
|
|
277
|
+
decorateMovedNode(node, parent) {
|
|
278
|
+
const key = this.getNodeKey(node, String(parent.key), (parent.children || []).length)
|
|
279
|
+
const children = (node.children || []).map((child, index) => ({
|
|
280
|
+
...this.decorateMovedNode(child, { key, children: node.children }),
|
|
281
|
+
__path: `${key}-${index}`,
|
|
282
|
+
}))
|
|
283
|
+
return {
|
|
284
|
+
...node,
|
|
285
|
+
key,
|
|
286
|
+
children,
|
|
287
|
+
__raw: node,
|
|
288
|
+
__level: (parent.__level || 0) + 1,
|
|
289
|
+
__path: `${parent.__path}-${(parent.children || []).length}`,
|
|
290
|
+
$folded: this.hasChildren(node) ? false : false,
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
stripInternalFields(node) {
|
|
294
|
+
const { __raw, __level, __path, $folded, ...rest } = node || {}
|
|
295
|
+
return {
|
|
296
|
+
...rest,
|
|
297
|
+
children: (rest.children || []).map((child) => this.stripInternalFields(child)),
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
toRawNodes(nodes) {
|
|
301
|
+
return (nodes || []).map((node) => this.stripInternalFields(node))
|
|
302
|
+
},
|
|
303
|
+
syncExpandedFromTree() {
|
|
304
|
+
const source = this.treeData || []
|
|
164
305
|
const next = {}
|
|
165
306
|
const walk = (nodes) => {
|
|
166
307
|
;(nodes || []).forEach((node) => {
|
|
@@ -179,7 +320,7 @@ export default {
|
|
|
179
320
|
}
|
|
180
321
|
</script>
|
|
181
322
|
|
|
182
|
-
<style
|
|
323
|
+
<style>
|
|
183
324
|
.k-tree {
|
|
184
325
|
background: #ffffff;
|
|
185
326
|
border: 0;
|
|
@@ -188,6 +329,17 @@ export default {
|
|
|
188
329
|
overflow: auto;
|
|
189
330
|
}
|
|
190
331
|
|
|
332
|
+
.k-tree-branch {
|
|
333
|
+
list-style: none;
|
|
334
|
+
margin: 0;
|
|
335
|
+
padding: 0;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.k-tree-node {
|
|
339
|
+
margin: 0;
|
|
340
|
+
padding: 0;
|
|
341
|
+
}
|
|
342
|
+
|
|
191
343
|
.k-tree--disabled {
|
|
192
344
|
opacity: 0.6;
|
|
193
345
|
pointer-events: none;
|
|
@@ -211,6 +363,10 @@ export default {
|
|
|
211
363
|
background: #f8fafc;
|
|
212
364
|
}
|
|
213
365
|
|
|
366
|
+
.k-tree-node-content--droppable {
|
|
367
|
+
cursor: grab;
|
|
368
|
+
}
|
|
369
|
+
|
|
214
370
|
.k-tree-node-content--selected {
|
|
215
371
|
background: #ecfdf3;
|
|
216
372
|
color: #15803d;
|