ketekny-ui-kit 1.0.50 → 1.0.51

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.
Files changed (2) hide show
  1. package/package.json +1 -4
  2. package/src/ui/kTree.vue +194 -38
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ketekny-ui-kit",
3
3
  "type": "module",
4
- "version": "1.0.50",
4
+ "version": "1.0.51",
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
- <HETree
4
- ref="treeRef"
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
- :edge-scroll="edgeScroll"
11
- :trigger-class="triggerClass"
12
- @nodeFoldedChanged="syncExpandedFromTree"
13
- @drop="onDrop"
14
- @change="onChange">
15
- <template #default="{ node, path, tree }">
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="isSelected(node.key) ? 'k-tree-node-content--selected' : ''"
19
- @click="onNodeClick($event, node)">
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="toggleNode(tree, node, path)">
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 name="default" :node="node.__raw || node" :path="path" :tree="tree">
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
- <script>
43
- import { ChevronRight } from 'lucide-vue-next'
44
- import { Tree as BaseTree, Fold, Draggable } from 'he-tree-vue'
45
- import 'he-tree-vue/dist/he-tree-vue.css'
46
-
47
- const HETree = BaseTree.mixPlugins([Fold, Draggable])
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: { HETree, ChevronRight },
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 children = this.buildTreeData(node.children || [], key, expandedMap)
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(tree, node, path) {
152
- tree.toggleFold(node, path)
153
- this.$nextTick(() => this.syncExpandedFromTree(tree))
235
+ toggleNode(node) {
236
+ node.$folded = !node.$folded
237
+ this.syncExpandedFromTree()
238
+ },
239
+ onDragStart(node) {
240
+ this.draggedNode = node
154
241
  },
155
- onDrop(store) {
156
- this.$emit('drop', store)
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
- onChange(store) {
159
- this.$nextTick(() => this.syncExpandedFromTree(store?.targetTree || this.$refs.treeRef))
160
- this.$emit('change', store)
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
- syncExpandedFromTree(treeVm) {
163
- const source = treeVm?.treeData || this.treeData || []
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 scoped>
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;