ketekny-ui-kit 1.0.60 → 1.0.62

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 CHANGED
@@ -155,7 +155,7 @@ Components:
155
155
  - `kMessage`, `kCode`, `kToolbar`, `kTable`, `kTabs`, `kChip`
156
156
  - `kSpinner`, `kDatatable`, `kIcon`, `kMenu`, `kSkeleton`
157
157
  - `kProgressBar`, `kTree`, `kButton`, `kSelect`, `kUploader`
158
- - `kToggle`, `kInput`, `kDateSelector`, `kDateSelectorV2`, `kEditor`
158
+ - `kToggle`, `kInput`, `kDateSelector`, `kEditor`
159
159
  - `kSelectButton`, `kTags`, `kSearch`, `kArrayList`, `kList`, `kTextArea`
160
160
  - `kDialog`, `kDrawer`
161
161
  - `kAppHeader`, `kAppFooter`, `kAppMain`, `kHero`
package/index.js CHANGED
@@ -7,7 +7,6 @@ import kDialog from './src/ui/kDialog.vue'
7
7
  import kDrawer from './src/ui/kDrawer.vue'
8
8
  import kInput from './src/ui/kInput.vue'
9
9
  import kDateSelector from './src/ui/kDateSelector.vue'
10
- import kDateSelectorV2 from './src/ui/kDateSelector_v2.vue'
11
10
  import kToolbar from './src/ui/kToolbar.vue'
12
11
  import kSelect from './src/ui/kSelect.vue'
13
12
  import kTable from './src/ui/kTable.vue'
@@ -51,7 +50,7 @@ export {
51
50
  kMessage, kCode, kToolbar, kTable, kTabs, kChip, kSpinner, kDatatable, kIcon, kMenu, kSkeleton, kProgressBar, kTree,
52
51
 
53
52
  // Form Components
54
- kButton, kSelect, kUploader, kToggle, kInput, kDateSelector, kDateSelectorV2, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea,
53
+ kButton, kSelect, kUploader, kToggle, kInput, kDateSelector, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea,
55
54
 
56
55
  // Dialogs
57
56
  kDialog, kDrawer,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ketekny-ui-kit",
3
3
  "type": "module",
4
- "version": "1.0.60",
4
+ "version": "1.0.62",
5
5
  "description": "A Vue 3 UI component library with Tailwind CSS styling",
6
6
  "main": "index.js",
7
7
  "files": [
@@ -1,326 +1,152 @@
1
1
  <template>
2
- <div class="relative w-full">
3
- <label :for="id" class="inputLabel" :class="hasError || inputInvalid ? 'text-red-500' : 'text-primary/90'">
4
- {{ label }}
5
- </label>
6
-
7
- <!-- Date Mode (text input + calendar icon opens Vue Datepicker popup) -->
8
- <div v-if="type === 'date'">
9
- <div class="relative">
10
- <input
11
- :id="id"
12
- type="text"
13
- v-model="textDate"
14
- :placeholder="placeholder || 'dd/mm/yyyy'"
15
- :disabled="disabled"
16
- :class="[defaultStyle, hasError || inputInvalid ? errorStyle : '', disabled ? disabledStyle : '']"
17
- @input="onTextInput"
18
- @blur="commitTextDate"
19
- @keydown.enter.prevent="commitTextDate"
20
- />
21
-
22
- <!-- Right-side calendar icon (opens popup) -->
23
- <button
24
- type="button"
25
- class="absolute p-1 text-primary/70 -translate-y-1/2 right-2 top-1/2 hover:text-primary disabled:opacity-50"
26
- :disabled="disabled"
27
- @click="openCalendar"
28
- aria-label="Open calendar"
29
- >
30
- <Calendar class="w-5 h-5" />
31
- </button>
32
- </div>
33
-
34
- <!-- Hidden input; only used to render the popup -->
35
- <Datepicker ref="dp" v-model="calendarDate" :format="dateFormat" :enable-time-picker="false" auto-apply :disabled="disabled" :hide-input-icon="true" teleport="body" class="sr-only" />
36
- </div>
37
-
38
- <!-- Year-Month Mode -->
39
- <div v-else class="relative">
40
- <div class="relative">
41
- <div ref="trigger" :class="[defaultStyle, hasError ? errorStyle : '', disabled ? disabledStyle : '']" @click="togglePopup">
42
- {{ displayValue || placeholder }}
43
- </div>
44
-
45
- <!-- Right-side calendar icon (opens month grid) -->
46
- <button
47
- type="button"
48
- class="absolute p-1 text-primary/70 -translate-y-1/2 right-2 top-1/2 hover:text-primary disabled:opacity-50"
49
- :disabled="disabled"
50
- @click.stop="togglePopup"
51
- aria-label="Open month selector"
52
- >
53
- <Calendar class="w-5 h-5" />
54
- </button>
55
-
56
- <!-- Clear Button (sits just left of the calendar icon) -->
57
- <button
58
- v-if="displayValue && !disabled"
59
- @click.stop="clearSelection"
60
- type="button"
61
- class="absolute text-sm font-bold text-gray-400 -translate-y-1/2 right-9 top-1/2 hover:text-red-500"
62
- aria-label="Clear"
63
- >
64
- <X class="w-5 h-5" />
65
- </button>
66
- </div>
67
-
68
- <!-- Month Grid Popover (Teleported) -->
69
- <teleport to="body">
70
- <div v-if="showPopup" class="p-4 bg-white dark:bg-slate-800 border border-primary/20 dark:border-slate-600 rounded-lg shadow-lg" :style="popupStyles">
71
- <!-- Year Nav -->
72
- <div class="flex items-center justify-between pb-3 mb-4 border-b border-slate-200 dark:border-slate-600">
73
- <button @click.stop="currentYear--" class="text-primary/80 dark:text-sky-300 hover:text-primary dark:hover:text-sky-200" aria-label="Previous year">
74
- <ChevronLeft />
75
- </button>
76
- <span class="font-medium text-primary dark:text-sky-300">{{ currentYear }}</span>
77
- <button @click.stop="currentYear++" class="text-primary/80 dark:text-sky-300 hover:text-primary dark:hover:text-sky-200" aria-label="Next year">
78
- <ChevronRight />
79
- </button>
80
- </div>
81
-
82
- <!-- Month Grid -->
83
- <div class="grid grid-cols-3 gap-2">
84
- <div
85
- v-for="(month, index) in months"
86
- :key="index"
87
- @click="selectMonth(index + 1)"
88
- class="px-3 py-2 text-center transition rounded-lg cursor-pointer"
89
- :class="[isSelected(index + 1) ? 'bg-primary text-white' : 'text-gray-700 dark:text-slate-200 hover:bg-primary/15 dark:hover:bg-sky-500/30 dark:hover:text-sky-100']"
90
- >
91
- {{ month }}
92
- </div>
93
- </div>
94
- </div>
95
- </teleport>
96
- </div>
97
-
98
- <!-- API-provided error -->
99
- <div class="text-sm text-red-500" v-if="hasError && error !== true && error !== ''">
100
- {{ error }}
101
- </div>
102
-
103
- <!-- Local parse error -->
104
- <div class="text-sm text-red-500" v-if="inputInvalid && !hasError">Μη έγκυρη ημερομηνία. Χρησιμοποιήστε μορφή dd/mm/yyyy.</div>
105
-
106
- <!-- Info message -->
107
- <div class="text-sm text-primary/80" v-if="info != null">
108
- {{ info }}
109
- </div>
2
+ <div>
3
+ <label v-if="label" class="block mb-1 text-sm font-medium text-slate-700 dark:text-slate-300">{{ label }}</label>
4
+ <el-date-picker
5
+ :id="computedId"
6
+ :model-value="normalizedModelValue"
7
+ :type="pickerType"
8
+ :placeholder="placeholder"
9
+ :disabled="disabled"
10
+ :readonly="readonly"
11
+ :clearable="clearable"
12
+ :editable="editable"
13
+ :size="size"
14
+ :format="resolvedFormat"
15
+ :value-format="resolvedValueFormat"
16
+ :disabled-date="disabledDate"
17
+ :start-placeholder="startPlaceholder"
18
+ :end-placeholder="endPlaceholder"
19
+ :range-separator="rangeSeparator"
20
+ class="k-date"
21
+ style="width: 100%"
22
+ @update:model-value="$emit('update:modelValue', $event)"
23
+ @change="$emit('change', $event)"
24
+ />
25
+ <p v-if="hasError" class="mt-1 text-xs text-red-600 dark:text-red-400">{{ typeof error === 'string' ? error : '' }}</p>
26
+ <p v-else-if="info" class="mt-1 text-xs text-slate-500 dark:text-slate-400">{{ info }}</p>
110
27
  </div>
111
28
  </template>
112
29
 
113
- <script>
114
- import Datepicker from "@vuepic/vue-datepicker";
115
- import "@vuepic/vue-datepicker/dist/main.css";
116
- import moment from "moment";
117
- import { ChevronLeft, ChevronRight, X, Calendar } from "lucide-vue-next";
118
-
119
- export default {
120
- name: "kDatePicker",
121
- components: { Datepicker, ChevronLeft, ChevronRight, X, Calendar },
122
- props: {
123
- modelValue: [String, Date],
124
- type: {
125
- type: String,
126
- default: "date",
127
- validator: (val) => ["date", "yearMonth"].includes(val),
128
- },
129
- info: { type: String, default: null },
130
- error: { type: [String, Boolean], default: null },
131
- label: { type: String, default: "Ημερομηνία" },
132
- placeholder: { type: String, default: "Επιλέξτε ημερομηνία" },
133
- disabled: { type: Boolean, default: false },
134
- valueFormat: { type: String, default: null },
135
- id: {
136
- type: String,
137
- default: () => `datepicker-${Math.random().toString(36).substr(2, 9)}`,
138
- },
139
- },
140
- data() {
141
- const parsed = moment(this.modelValue, "YYYY-MM", true);
142
- const current = moment();
143
-
144
- const initText = (() => {
145
- if (!this.modelValue) return "";
146
- const m = moment(this.modelValue);
147
- return m.isValid() ? m.format("DD/MM/YYYY") : "";
148
- })();
30
+ <script setup>
31
+ import { computed } from 'vue'
32
+ import { ElDatePicker } from 'element-plus'
149
33
 
150
- return {
151
- defaultStyle: "w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg transition shadow-sm focus:outline-none text-gray-700 dark:text-slate-200 focus:ring-2 focus:ring-primary/20 focus:border-primary bg-white dark:bg-slate-800 placeholder-gray-400 dark:placeholder-slate-500",
152
- errorStyle: "border-red-500 focus:ring focus:ring-red-300",
153
- disabledStyle: "!bg-gray-100 dark:!bg-slate-700 !text-gray-400 dark:!text-slate-500 !cursor-not-allowed",
154
-
155
- // Year-Month UI state
156
- showPopup: false,
157
- currentYear: parsed.isValid() ? parsed.year() : current.year(),
158
- selectedMonth: parsed.isValid() ? parsed.month() + 1 : null,
159
- months: ["Ιαν", "Φεβ", "Μάρ", "Απρ", "Μάι", "Ιούν", "Ιούλ", "Αυγ", "Σεπ", "Οκτ", "Νοέ", "Δεκ"],
160
- popupStyles: {
161
- position: "absolute",
162
- top: "0px",
163
- left: "0px",
164
- width: "100%",
165
- },
166
-
167
- textDate: initText,
168
- inputInvalid: false,
169
- };
170
- },
171
- computed: {
172
- hasError() {
173
- return this.error != null && this.error !== false;
174
- },
175
- displayValue() {
176
- if (!this.selectedMonth || !this.currentYear) return "";
177
- return moment(`${this.currentYear}-${String(this.selectedMonth).padStart(2, "0")}`).format("MM/YYYY");
178
- },
179
- calendarDate: {
180
- get() {
181
- if (!this.modelValue) return null;
182
- const m = moment(this.modelValue);
183
- return m.isValid() ? m.toDate() : null;
184
- },
185
- set(val) {
186
- const m = moment(val);
187
- if (!m.isValid()) {
188
- this.$emit("update:modelValue", null);
189
- return;
190
- }
191
- const emitValue = this.valueFormat ? m.format(this.valueFormat) : m.toDate();
192
- this.$emit("update:modelValue", emitValue);
193
- },
194
- },
195
- dateFormat() {
196
- return "dd/MM/yyyy";
197
- },
198
- },
199
- watch: {
200
- modelValue(newVal) {
201
- if (!newVal) {
202
- this.textDate = "";
203
- return;
204
- }
205
- const m = moment(newVal);
206
- this.textDate = m.isValid() ? m.format("DD/MM/YYYY") : "";
207
- },
34
+ const props = defineProps({
35
+ modelValue: { type: [String, Date, Array], default: null },
36
+ id: {
37
+ type: [String, Array],
38
+ default: () => `datepicker-${Math.random().toString(36).slice(2, 11)}`,
208
39
  },
209
- mounted() {
210
- this.clickOutsideHandler = (e) => {
211
- if (this.showPopup && !this.$el.contains(e.target)) {
212
- this.showPopup = false;
213
- }
214
- };
215
- document.addEventListener("click", this.clickOutsideHandler);
40
+ label: { type: String, default: '' },
41
+ type: {
42
+ type: String,
43
+ default: 'date',
44
+ validator: (val) => ['year', 'month', 'date', 'dates', 'datetime', 'week', 'datetimerange', 'daterange', 'monthrange', 'yearMonth'].includes(val),
216
45
  },
217
- beforeUnmount() {
218
- document.removeEventListener("click", this.clickOutsideHandler);
219
- },
220
- methods: {
221
- /**
222
- * Live input handler:
223
- * - Keeps digits and slashes
224
- * - Allows user to type `/` manually
225
- * - Auto-inserts missing slashes when needed
226
- * - Emits update when full valid date entered
227
- */
228
- onTextInput(e) {
229
- if (this.disabled) return;
230
-
231
- let value = e.target.value || "";
232
-
233
- // Allow digits and slashes only
234
- value = value.replace(/[^0-9/]/g, "");
235
-
236
- // Auto-add slashes if user omits them
237
- const digits = value.replace(/\D/g, "");
238
- if (digits.length > 2 && !value.includes("/")) {
239
- // add first slash after 2 digits
240
- value = `${digits.slice(0, 2)}/${digits.slice(2)}`;
241
- }
242
- if (digits.length > 4 && (value.match(/\//g) || []).length < 2) {
243
- // ensure second slash exists after month
244
- value = `${digits.slice(0, 2)}/${digits.slice(2, 4)}/${digits.slice(4)}`;
245
- }
246
-
247
- // Prevent double slashes like "12//03"
248
- value = value.replace(/\/{2,}/g, "/");
249
-
250
- this.textDate = value;
251
-
252
- // When 8 digits exist, try to parse automatically
253
- if (digits.length === 8) {
254
- const m = moment(value, "DD/MM/YYYY", true);
255
- if (m.isValid()) {
256
- this.inputInvalid = false;
257
- const emitValue = this.valueFormat ? m.format(this.valueFormat) : m.toDate();
258
- this.$emit("update:modelValue", emitValue);
259
- } else {
260
- this.inputInvalid = true;
261
- }
262
- } else {
263
- this.inputInvalid = false;
264
- }
265
- },
266
-
267
- commitTextDate() {
268
- const raw = (this.textDate || "").trim();
269
- if (!raw) {
270
- this.inputInvalid = false;
271
- this.$emit("update:modelValue", null);
272
- return;
273
- }
274
- const formats = ["DD/MM/YYYY", "D/M/YYYY", "YYYY-MM-DD", moment.ISO_8601];
275
- const m = moment(raw, formats, true);
276
- if (!m.isValid()) {
277
- this.inputInvalid = true;
278
- return;
279
- }
280
- this.inputInvalid = false;
281
- const emitValue = this.valueFormat ? m.format(this.valueFormat) : m.toDate();
282
- this.$emit("update:modelValue", emitValue);
283
- },
284
-
285
- openCalendar() {
286
- if (this.disabled) return;
287
- this.$refs.dp && this.$refs.dp.openMenu && this.$refs.dp.openMenu();
288
- },
289
- togglePopup() {
290
- if (this.disabled) return;
291
- this.showPopup = !this.showPopup;
292
- if (this.showPopup) {
293
- this.$nextTick(() => {
294
- const trigger = this.$refs.trigger;
295
- if (trigger) {
296
- const rect = trigger.getBoundingClientRect();
297
- this.popupStyles = {
298
- position: "absolute",
299
- top: `${rect.bottom + window.scrollY}px`,
300
- left: `${rect.left + window.scrollX}px`,
301
- width: `${rect.width}px`,
302
- zIndex: 9999,
303
- };
304
- }
305
- });
306
- }
307
- },
308
- clearSelection() {
309
- this.selectedMonth = null;
310
- this.$emit("update:modelValue", null);
311
- this.showPopup = false;
312
- },
313
- selectMonth(month) {
314
- this.selectedMonth = month;
315
- const m = moment(`${this.currentYear}-${String(month).padStart(2, "0")}`, "YYYY-MM");
316
- const emitValue = this.valueFormat ? m.format(this.valueFormat) : m.toDate();
317
- this.$emit("update:modelValue", emitValue);
318
- this.showPopup = false;
319
- },
320
- isSelected(month) {
321
- const m = moment(this.modelValue, "YYYY-MM", true);
322
- return this.selectedMonth === month && m.isValid() && m.year() === this.currentYear;
323
- },
46
+ placeholder: { type: String, default: 'Select date' },
47
+ startPlaceholder: { type: String, default: 'Start date' },
48
+ endPlaceholder: { type: String, default: 'End date' },
49
+ rangeSeparator: { type: String, default: 'To' },
50
+ disabled: { type: Boolean, default: false },
51
+ readonly: { type: Boolean, default: false },
52
+ clearable: { type: Boolean, default: true },
53
+ editable: { type: Boolean, default: true },
54
+ size: {
55
+ type: String,
56
+ default: 'default',
57
+ validator: (val) => ['large', 'default', 'small'].includes(val),
324
58
  },
325
- };
59
+ format: { type: String, default: '' },
60
+ valueFormat: { type: String, default: '' },
61
+ disabledDate: { type: Function, default: undefined },
62
+ error: { type: [String, Boolean], default: null },
63
+ info: { type: String, default: '' },
64
+ })
65
+
66
+ defineEmits(['update:modelValue', 'change'])
67
+
68
+ const pickerType = computed(() => props.type === 'yearMonth' ? 'month' : props.type)
69
+ const isRange = computed(() => ['daterange', 'datetimerange', 'monthrange'].includes(pickerType.value))
70
+ const computedId = computed(() => {
71
+ if (Array.isArray(props.id)) return props.id
72
+ return isRange.value ? [`${props.id}-start`, `${props.id}-end`] : props.id
73
+ })
74
+ const hasError = computed(() => props.error != null && props.error !== false)
75
+
76
+ const defaultFormatByType = {
77
+ date: 'YYYY-MM-DD',
78
+ dates: 'YYYY-MM-DD',
79
+ week: 'gggg[w]ww',
80
+ year: 'YYYY',
81
+ month: 'YYYY-MM',
82
+ datetime: 'YYYY-MM-DD HH:mm:ss',
83
+ monthrange: 'YYYY-MM',
84
+ daterange: 'YYYY-MM-DD',
85
+ datetimerange: 'YYYY-MM-DD HH:mm:ss',
86
+ }
87
+
88
+ const resolvedFormat = computed(() => {
89
+ const custom = String(props.format || '').trim()
90
+ if (custom) return custom
91
+ return defaultFormatByType[pickerType.value] || 'YYYY-MM-DD'
92
+ })
93
+
94
+ const resolvedValueFormat = computed(() => {
95
+ const custom = String(props.valueFormat || '').trim()
96
+ if (custom) return custom
97
+ return defaultFormatByType[pickerType.value] || 'YYYY-MM-DD'
98
+ })
99
+
100
+ function normalizeIsoString(value, type) {
101
+ if (typeof value !== 'string' || !value.includes('T')) return value
102
+
103
+ if (type === 'year') return value.slice(0, 4)
104
+ if (type === 'month' || type === 'monthrange') return value.slice(0, 7)
105
+
106
+ if (type === 'datetime' || type === 'datetimerange') {
107
+ const normalized = value.replace('T', ' ').replace(/Z$/, '')
108
+ const fullMatch = normalized.match(/^(\d{4}-\d{2}-\d{2})\s(\d{2}:\d{2}:\d{2})/)
109
+ if (fullMatch) return `${fullMatch[1]} ${fullMatch[2]}`
110
+ const shortMatch = normalized.match(/^(\d{4}-\d{2}-\d{2})\s(\d{2}:\d{2})/)
111
+ if (shortMatch) return `${shortMatch[1]} ${shortMatch[2]}:00`
112
+ return normalized
113
+ }
114
+
115
+ const dateMatch = value.match(/^(\d{4}-\d{2}-\d{2})/)
116
+ return dateMatch ? dateMatch[1] : value
117
+ }
118
+
119
+ const normalizedModelValue = computed(() => {
120
+ if (Array.isArray(props.modelValue)) {
121
+ return props.modelValue.map((item) => normalizeIsoString(item, pickerType.value))
122
+ }
123
+ return normalizeIsoString(props.modelValue, pickerType.value)
124
+ })
326
125
  </script>
126
+
127
+ <style>
128
+ /* Global because Element Plus picker panel is teleported to body. */
129
+ html.dark .el-date-picker,
130
+ html.dark .el-date-range-picker {
131
+ --el-datepicker-inrange-bg-color: rgba(3, 105, 161, 0.22);
132
+ --el-datepicker-inrange-hover-bg-color: rgba(3, 105, 161, 0.32);
133
+ }
134
+
135
+ html.dark .el-date-table td.in-range .el-date-table-cell,
136
+ html.dark .el-month-table td.in-range .el-date-table-cell,
137
+ html.dark .el-year-table td.in-range .el-date-table-cell {
138
+ background-color: rgba(3, 105, 161, 0.22);
139
+ }
140
+
141
+ html.dark .el-date-table td.in-range .el-date-table-cell:hover,
142
+ html.dark .el-month-table td.in-range .el-date-table-cell:hover,
143
+ html.dark .el-year-table td.in-range .el-date-table-cell:hover {
144
+ background-color: rgba(3, 105, 161, 0.32);
145
+ }
146
+
147
+ html.dark .el-month-table td.available:hover .el-date-table-cell,
148
+ html.dark .el-year-table td.available:hover .el-date-table-cell {
149
+ background-color: rgba(148, 163, 184, 0.16);
150
+ border-radius: 24px;
151
+ }
152
+ </style>
package/src/ui/kTree.vue CHANGED
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="k-tree text-primary/90" :class="disabled ? 'k-tree--disabled' : ''">
3
- <TreeBranch
3
+ <kTreeBranch
4
4
  :nodes="treeData"
5
5
  :indent="indent"
6
6
  :disabled="disabled"
@@ -17,112 +17,16 @@
17
17
  {{ resolveNodeLabel(node) }}
18
18
  </slot>
19
19
  </template>
20
- </TreeBranch>
20
+ </kTreeBranch>
21
21
  </div>
22
22
  </template>
23
23
 
24
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">
71
- <div
72
- class="k-tree-node-content"
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)">
83
- <button
84
- v-if="hasChildren(node)"
85
- type="button"
86
- class="k-tree-node-toggler"
87
- :aria-label="node.$folded ? 'Expand node' : 'Collapse node'"
88
- @click.stop="$emit('toggle', node)">
89
- <ChevronRight class="k-tree-node-toggler-icon" :class="node.$folded ? '' : 'k-tree-node-toggler-icon--open'" />
90
- </button>
91
-
92
- <span v-else class="k-tree-node-toggler k-tree-node-toggler--empty"></span>
93
-
94
- <div class="k-tree-node-label">
95
- <slot :node="node.__raw || node" :path="node.__path">
96
- {{ resolveNodeLabel(node.__raw || node) }}
97
- </slot>
98
- </div>
99
- </div>
100
-
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
- }
25
+ import kTreeBranch from './kTreeBranch.vue'
122
26
 
123
27
  export default {
124
28
  name: 'kTree',
125
- components: { TreeBranch },
29
+ components: { kTreeBranch },
126
30
  props: {
127
31
  value: { type: Array, default: () => [] },
128
32
  selectionKeys: { type: [Object, null], default: null },