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 +1 -1
- package/index.js +1 -2
- package/package.json +1 -1
- package/src/ui/kDateSelector.vue +143 -317
- package/src/ui/kTree.vue +4 -100
- package/src/ui/kTreeBranch.vue +100 -0
- package/src/ui/kButton_v2.vue +0 -124
- package/src/ui/kDateSelector_v2.vue +0 -152
- package/src/ui/kDialog_v2.vue +0 -71
- package/src/ui/kDialog_v3.vue +0 -128
- package/src/ui/kDialog_v4.vue +0 -140
- package/src/ui/kDrawer_v2.vue +0 -77
- package/src/ui/kInput_v2.vue +0 -188
- package/src/ui/kMessage_v2.vue +0 -67
- package/src/ui/kMessage_v3.vue +0 -152
- package/src/ui/kMessage_v4.vue +0 -133
- package/src/ui/kProgress_v2.vue +0 -45
- package/src/ui/kSelect_v2.vue +0 -124
- package/src/ui/kSwitch_v2.vue +0 -49
- package/src/ui/kTag_v2.vue +0 -44
- package/src/ui/kToggle_v2.vue +0 -77
- package/src/ui/kToggle_v3.vue +0 -132
- package/src/ui/kToggle_v4.vue +0 -122
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`, `
|
|
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,
|
|
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
package/src/ui/kDateSelector.vue
CHANGED
|
@@ -1,326 +1,152 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
<label
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
115
|
-
import
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
},
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
<
|
|
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
|
-
</
|
|
20
|
+
</kTreeBranch>
|
|
21
21
|
</div>
|
|
22
22
|
</template>
|
|
23
23
|
|
|
24
24
|
<script>
|
|
25
|
-
import
|
|
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: {
|
|
29
|
+
components: { kTreeBranch },
|
|
126
30
|
props: {
|
|
127
31
|
value: { type: Array, default: () => [] },
|
|
128
32
|
selectionKeys: { type: [Object, null], default: null },
|