ketekny-ui-kit 1.0.43 → 1.0.45
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/index.js +2 -1
- package/package.json +1 -1
- package/src/ui/kEditor.vue +68 -19
- package/src/ui/kMessageNew.vue +381 -0
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// UI Components
|
|
2
2
|
import kMessage from './src/ui/kMessage.vue'
|
|
3
|
+
import kMessageNew from './src/ui/kMessageNew.vue'
|
|
3
4
|
import kButton from './src/ui/kButton.vue'
|
|
4
5
|
import kChip from './src/ui/kChip.vue'
|
|
5
6
|
import kCode from './src/ui/kCode.vue'
|
|
@@ -48,7 +49,7 @@ import tailwindPreset from './tailwind-preset.js'
|
|
|
48
49
|
// Named exports (tree-shaking friendly)
|
|
49
50
|
export {
|
|
50
51
|
// UI Components
|
|
51
|
-
kMessage, kCode, kToolbar, kTable, kTabs, kChip, kSpinner, kDatatable, kIcon, kMenu, kSkeleton, kProgressBar, kTree,
|
|
52
|
+
kMessage, kMessageNew, kCode, kToolbar, kTable, kTabs, kChip, kSpinner, kDatatable, kIcon, kMenu, kSkeleton, kProgressBar, kTree,
|
|
52
53
|
|
|
53
54
|
// Form Components
|
|
54
55
|
kButton, kSelect, kUploader, kToggle, kInput, kDateSelector, kDateSelectorV2, kEditor, kSelectButton, kTags, kSearch, kArrayList, kList, kTextArea,
|
package/package.json
CHANGED
package/src/ui/kEditor.vue
CHANGED
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
<div
|
|
22
22
|
class="editor-content"
|
|
23
23
|
:class="[defaultStyle, hasError ? errorStyle : '', disabled ? disabledStyle : '']"
|
|
24
|
-
contenteditable
|
|
24
|
+
:contenteditable="!disabled"
|
|
25
25
|
ref="editor"
|
|
26
|
-
@
|
|
27
|
-
@
|
|
26
|
+
@focus="handleFocus"
|
|
27
|
+
@input="handleInput"
|
|
28
|
+
@blur="handleBlur"
|
|
28
29
|
:placeholder="placeholder"
|
|
29
30
|
></div>
|
|
30
31
|
<div class="text-sm text-red-500 dark:text-rose-400" v-if="hasError && error !== true && error !== ''">
|
|
@@ -61,13 +62,15 @@ export default {
|
|
|
61
62
|
default: "Πληκτρολογήστε κείμενο...",
|
|
62
63
|
},
|
|
63
64
|
},
|
|
64
|
-
emits: ["update:modelValue"],
|
|
65
|
+
emits: ["update:modelValue", "input", "focus", "blur", "change"],
|
|
65
66
|
data() {
|
|
66
67
|
return {
|
|
67
68
|
defaultStyle:
|
|
68
69
|
"w-full px-3 py-2 border rounded-b-lg transition shadow-sm focus:outline-none focus:ring-2 focus:ring-primary/20 min-h-[150px] focus:border-primary bg-white placeholder-gray-400 !list-disc !list-inside prose dark:bg-slate-800 dark:text-slate-100 dark:border-slate-600 dark:placeholder-slate-500",
|
|
69
70
|
errorStyle: "border-red-500 focus:ring focus:ring-red-300 dark:border-rose-500 dark:focus:ring-rose-500/20",
|
|
70
71
|
disabledStyle: "!bg-gray-100 !text-gray-400 !cursor-not-allowed editor pointer-events-none select-none dark:!bg-slate-900 dark:!text-slate-500",
|
|
72
|
+
isFocused: false,
|
|
73
|
+
pendingModelValue: null,
|
|
71
74
|
toolbar: [
|
|
72
75
|
{ command: "bold", icon: Bold, title: "Bold" },
|
|
73
76
|
{ command: "italic", icon: Italic, title: "Italic" },
|
|
@@ -77,34 +80,80 @@ export default {
|
|
|
77
80
|
};
|
|
78
81
|
},
|
|
79
82
|
mounted() {
|
|
80
|
-
this
|
|
83
|
+
this.setEditorHtml(this.modelValue || "");
|
|
81
84
|
},
|
|
82
85
|
methods: {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
getEditorHtml() {
|
|
87
|
+
return this.$refs.editor?.innerHTML || "";
|
|
88
|
+
},
|
|
89
|
+
setEditorHtml(value) {
|
|
90
|
+
if (!this.$refs.editor) return;
|
|
91
|
+
this.$refs.editor.innerHTML = value || "";
|
|
86
92
|
},
|
|
87
|
-
|
|
93
|
+
normalizeEditorDom() {
|
|
88
94
|
const el = this.$refs.editor;
|
|
95
|
+
if (!el) return "";
|
|
89
96
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
+
Array.from(el.children).forEach((child) => {
|
|
98
|
+
if (child.tagName !== "DIV" || !child.innerText.trim().length) return;
|
|
99
|
+
|
|
100
|
+
const p = document.createElement("p");
|
|
101
|
+
p.innerHTML = child.innerHTML;
|
|
102
|
+
child.replaceWith(p);
|
|
97
103
|
});
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
return this.getEditorHtml();
|
|
106
|
+
},
|
|
107
|
+
exec(command, value = null) {
|
|
108
|
+
this.$refs.editor?.focus();
|
|
109
|
+
document.execCommand(command, false, value);
|
|
110
|
+
this.handleInput();
|
|
111
|
+
},
|
|
112
|
+
handleFocus(event) {
|
|
113
|
+
this.isFocused = true;
|
|
114
|
+
this.$emit("focus", event);
|
|
115
|
+
},
|
|
116
|
+
handleInput() {
|
|
117
|
+
const html = this.getEditorHtml();
|
|
118
|
+
this.pendingModelValue = null;
|
|
119
|
+
this.$emit("input", html);
|
|
100
120
|
this.$emit("update:modelValue", html);
|
|
101
121
|
},
|
|
122
|
+
handleBlur(event) {
|
|
123
|
+
this.isFocused = false;
|
|
124
|
+
this.$emit("blur", event);
|
|
125
|
+
|
|
126
|
+
if (this.pendingModelValue != null) {
|
|
127
|
+
const nextValue = this.pendingModelValue;
|
|
128
|
+
this.pendingModelValue = null;
|
|
129
|
+
this.setEditorHtml(nextValue);
|
|
130
|
+
this.$emit("update:modelValue", nextValue);
|
|
131
|
+
this.$emit("change", nextValue);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const normalized = this.normalizeEditorDom();
|
|
136
|
+
this.$emit("change", normalized);
|
|
137
|
+
this.$emit("update:modelValue", normalized);
|
|
138
|
+
},
|
|
102
139
|
},
|
|
103
140
|
watch: {
|
|
104
141
|
modelValue(newVal) {
|
|
105
|
-
if (this.$refs.editor
|
|
106
|
-
|
|
142
|
+
if (!this.$refs.editor) return;
|
|
143
|
+
|
|
144
|
+
const nextValue = newVal || "";
|
|
145
|
+
if (this.getEditorHtml() === nextValue) {
|
|
146
|
+
this.pendingModelValue = null;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (this.isFocused) {
|
|
151
|
+
this.pendingModelValue = nextValue;
|
|
152
|
+
return;
|
|
107
153
|
}
|
|
154
|
+
|
|
155
|
+
this.pendingModelValue = null;
|
|
156
|
+
this.setEditorHtml(nextValue);
|
|
108
157
|
},
|
|
109
158
|
},
|
|
110
159
|
};
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Transition name="kmessagenew">
|
|
3
|
+
<div
|
|
4
|
+
v-if="isVisible"
|
|
5
|
+
ref="messageRef"
|
|
6
|
+
:class="[
|
|
7
|
+
colors.container,
|
|
8
|
+
'relative overflow-hidden shadow-sm ring-1 ring-black/5 transition-[box-shadow,transform] duration-200 dark:ring-white/10',
|
|
9
|
+
'border',
|
|
10
|
+
accent ? 'border-l-4 rounded-r-md' : 'rounded-md',
|
|
11
|
+
accent ? accentStripes[type] : '',
|
|
12
|
+
'focus-within:ring-2 focus-within:ring-primary/30 focus-within:ring-offset-1 focus-within:ring-offset-white dark:focus-within:ring-offset-slate-900',
|
|
13
|
+
sizes[size].padding,
|
|
14
|
+
$attrs.class,
|
|
15
|
+
]"
|
|
16
|
+
:role="type === 'error' ? 'alert' : 'status'"
|
|
17
|
+
:aria-live="type === 'error' ? 'assertive' : 'polite'"
|
|
18
|
+
aria-atomic="true"
|
|
19
|
+
@mouseenter="pauseDismissTimer"
|
|
20
|
+
@mouseleave="resumeDismissTimer"
|
|
21
|
+
@focusin="pauseDismissTimer"
|
|
22
|
+
@focusout="handleFocusOut"
|
|
23
|
+
>
|
|
24
|
+
<div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
|
25
|
+
<!-- Left: icon + content -->
|
|
26
|
+
<div class="flex min-w-0 items-start gap-3">
|
|
27
|
+
<!-- Custom icon slot -->
|
|
28
|
+
<span
|
|
29
|
+
v-if="hasIconSlot"
|
|
30
|
+
:class="['inline-flex shrink-0 items-center justify-center', sizes[size].customIcon]"
|
|
31
|
+
aria-hidden="true"
|
|
32
|
+
>
|
|
33
|
+
<slot name="icon" />
|
|
34
|
+
</span>
|
|
35
|
+
<!-- Badge icon -->
|
|
36
|
+
<span
|
|
37
|
+
v-else-if="iconStyle === 'badge'"
|
|
38
|
+
:class="[
|
|
39
|
+
'inline-flex shrink-0 items-center justify-center rounded-full border font-semibold',
|
|
40
|
+
sizes[size].badge,
|
|
41
|
+
colors.icon,
|
|
42
|
+
]"
|
|
43
|
+
aria-hidden="true"
|
|
44
|
+
>
|
|
45
|
+
{{ iconLetters[type] }}
|
|
46
|
+
</span>
|
|
47
|
+
<!-- Lucide icon -->
|
|
48
|
+
<component
|
|
49
|
+
v-else-if="iconStyle === 'lucide'"
|
|
50
|
+
:is="lucideIcons[type]"
|
|
51
|
+
:class="['shrink-0', sizes[size].lucide]"
|
|
52
|
+
aria-hidden="true"
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
<div :class="['min-w-0 flex-1', sizes[size].text, colors.body]">
|
|
56
|
+
<p v-if="title" :class="['font-semibold leading-tight', colors.title]">{{ title }}</p>
|
|
57
|
+
<div
|
|
58
|
+
v-if="hasDefaultSlot || description"
|
|
59
|
+
:class="['leading-relaxed break-words', title ? 'mt-1' : '']"
|
|
60
|
+
>
|
|
61
|
+
<slot v-if="hasDefaultSlot"></slot>
|
|
62
|
+
<span v-else>{{ description }}</span>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<!-- Right: action slot | label | close -->
|
|
68
|
+
<div
|
|
69
|
+
v-if="hasActionSlot || label || isClosable"
|
|
70
|
+
class="flex shrink-0 items-center gap-2 self-end sm:self-start sm:pl-3"
|
|
71
|
+
>
|
|
72
|
+
<slot name="action" />
|
|
73
|
+
<span
|
|
74
|
+
v-if="!hasActionSlot && label"
|
|
75
|
+
:class="['text-xs font-medium', colors.label]"
|
|
76
|
+
>
|
|
77
|
+
{{ label }}
|
|
78
|
+
</span>
|
|
79
|
+
<button
|
|
80
|
+
v-if="isClosable"
|
|
81
|
+
type="button"
|
|
82
|
+
@click="close"
|
|
83
|
+
:class="[
|
|
84
|
+
'rounded-md p-0.5 opacity-70 transition-all hover:bg-black/5 hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35 dark:hover:bg-white/10',
|
|
85
|
+
colors.label,
|
|
86
|
+
]"
|
|
87
|
+
:aria-label="`Close ${type} message`"
|
|
88
|
+
>
|
|
89
|
+
<X :class="sizes[size].close" />
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</Transition>
|
|
95
|
+
</template>
|
|
96
|
+
|
|
97
|
+
<script setup>
|
|
98
|
+
import { computed, onBeforeUnmount, ref, useSlots, watch } from 'vue'
|
|
99
|
+
import { X, Info, CircleCheckBig, TriangleAlert, OctagonX } from 'lucide-vue-next'
|
|
100
|
+
|
|
101
|
+
// ─── Color config ────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
const colorConfig = {
|
|
104
|
+
info: {
|
|
105
|
+
default: {
|
|
106
|
+
container: 'border-sky-300/90 bg-sky-50/70 dark:border-sky-500/50 dark:bg-sky-950/40',
|
|
107
|
+
icon: 'bg-sky-100/60 border-sky-500 text-sky-600 dark:bg-sky-900/50 dark:border-sky-400 dark:text-sky-300',
|
|
108
|
+
title: 'text-sky-700 dark:text-sky-300',
|
|
109
|
+
body: 'text-slate-700 dark:text-sky-50',
|
|
110
|
+
label: 'text-slate-500 dark:text-slate-300',
|
|
111
|
+
},
|
|
112
|
+
outlined: {
|
|
113
|
+
container: 'border-sky-400 bg-white dark:border-sky-500/70 dark:bg-slate-900',
|
|
114
|
+
icon: 'bg-sky-100/60 border-sky-500 text-sky-600 dark:bg-sky-900/50 dark:border-sky-400 dark:text-sky-300',
|
|
115
|
+
title: 'text-sky-700 dark:text-sky-300',
|
|
116
|
+
body: 'text-slate-600 dark:text-slate-200',
|
|
117
|
+
label: 'text-slate-400 dark:text-slate-400',
|
|
118
|
+
},
|
|
119
|
+
filled: {
|
|
120
|
+
container: 'bg-sky-700 border-transparent dark:bg-sky-700',
|
|
121
|
+
icon: 'bg-white/20 border-white/50 text-white',
|
|
122
|
+
title: 'text-white',
|
|
123
|
+
body: 'text-white',
|
|
124
|
+
label: 'text-white/70',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
success: {
|
|
128
|
+
default: {
|
|
129
|
+
container: 'border-emerald-300/90 bg-emerald-50/70 dark:border-emerald-500/50 dark:bg-emerald-950/40',
|
|
130
|
+
icon: 'bg-emerald-100/60 border-emerald-500 text-emerald-600 dark:bg-emerald-900/50 dark:border-emerald-400 dark:text-emerald-300',
|
|
131
|
+
title: 'text-emerald-700 dark:text-emerald-300',
|
|
132
|
+
body: 'text-slate-700 dark:text-emerald-50',
|
|
133
|
+
label: 'text-slate-500 dark:text-slate-300',
|
|
134
|
+
},
|
|
135
|
+
outlined: {
|
|
136
|
+
container: 'border-emerald-400 bg-white dark:border-emerald-500/70 dark:bg-slate-900',
|
|
137
|
+
icon: 'bg-emerald-100/60 border-emerald-500 text-emerald-600 dark:bg-emerald-900/50 dark:border-emerald-400 dark:text-emerald-300',
|
|
138
|
+
title: 'text-emerald-700 dark:text-emerald-300',
|
|
139
|
+
body: 'text-slate-600 dark:text-slate-200',
|
|
140
|
+
label: 'text-slate-400 dark:text-slate-400',
|
|
141
|
+
},
|
|
142
|
+
filled: {
|
|
143
|
+
container: 'bg-emerald-700 border-transparent dark:bg-emerald-700',
|
|
144
|
+
icon: 'bg-white/20 border-white/50 text-white',
|
|
145
|
+
title: 'text-white',
|
|
146
|
+
body: 'text-white',
|
|
147
|
+
label: 'text-white/70',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
warning: {
|
|
151
|
+
default: {
|
|
152
|
+
container: 'border-amber-300/90 bg-amber-50/70 dark:border-amber-500/50 dark:bg-amber-950/40',
|
|
153
|
+
icon: 'bg-amber-100/60 border-amber-500 text-amber-600 dark:bg-amber-900/50 dark:border-amber-400 dark:text-amber-300',
|
|
154
|
+
title: 'text-amber-700 dark:text-amber-300',
|
|
155
|
+
body: 'text-slate-700 dark:text-amber-50',
|
|
156
|
+
label: 'text-slate-500 dark:text-slate-300',
|
|
157
|
+
},
|
|
158
|
+
outlined: {
|
|
159
|
+
container: 'border-amber-400 bg-white dark:border-amber-500/70 dark:bg-slate-900',
|
|
160
|
+
icon: 'bg-amber-100/60 border-amber-500 text-amber-600 dark:bg-amber-900/50 dark:border-amber-400 dark:text-amber-300',
|
|
161
|
+
title: 'text-amber-700 dark:text-amber-300',
|
|
162
|
+
body: 'text-slate-600 dark:text-slate-200',
|
|
163
|
+
label: 'text-slate-400 dark:text-slate-400',
|
|
164
|
+
},
|
|
165
|
+
filled: {
|
|
166
|
+
container: 'bg-amber-600 border-transparent dark:bg-amber-600',
|
|
167
|
+
icon: 'bg-white/20 border-white/50 text-white',
|
|
168
|
+
title: 'text-white',
|
|
169
|
+
body: 'text-white',
|
|
170
|
+
label: 'text-white/70',
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
error: {
|
|
174
|
+
default: {
|
|
175
|
+
container: 'border-rose-300/90 bg-rose-50/70 dark:border-rose-500/50 dark:bg-rose-950/40',
|
|
176
|
+
icon: 'bg-rose-100/60 border-rose-500 text-rose-600 dark:bg-rose-900/50 dark:border-rose-400 dark:text-rose-300',
|
|
177
|
+
title: 'text-rose-700 dark:text-rose-300',
|
|
178
|
+
body: 'text-slate-700 dark:text-rose-50',
|
|
179
|
+
label: 'text-slate-500 dark:text-slate-300',
|
|
180
|
+
},
|
|
181
|
+
outlined: {
|
|
182
|
+
container: 'border-rose-400 bg-white dark:border-rose-500/70 dark:bg-slate-900',
|
|
183
|
+
icon: 'bg-rose-100/60 border-rose-500 text-rose-600 dark:bg-rose-900/50 dark:border-rose-400 dark:text-rose-300',
|
|
184
|
+
title: 'text-rose-700 dark:text-rose-300',
|
|
185
|
+
body: 'text-slate-600 dark:text-slate-200',
|
|
186
|
+
label: 'text-slate-400 dark:text-slate-400',
|
|
187
|
+
},
|
|
188
|
+
filled: {
|
|
189
|
+
container: 'bg-rose-700 border-transparent dark:bg-rose-700',
|
|
190
|
+
icon: 'bg-white/20 border-white/50 text-white',
|
|
191
|
+
title: 'text-white',
|
|
192
|
+
body: 'text-white',
|
|
193
|
+
label: 'text-white/70',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const accentStripes = {
|
|
199
|
+
info: 'border-l-sky-500 dark:border-l-sky-400',
|
|
200
|
+
success: 'border-l-emerald-500 dark:border-l-emerald-400',
|
|
201
|
+
warning: 'border-l-amber-500 dark:border-l-amber-400',
|
|
202
|
+
error: 'border-l-rose-500 dark:border-l-rose-400',
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const sizes = {
|
|
206
|
+
sm: {
|
|
207
|
+
padding: 'px-3 py-2',
|
|
208
|
+
text: 'text-xs sm:text-sm',
|
|
209
|
+
badge: 'h-4 w-4 text-[10px]',
|
|
210
|
+
customIcon: 'text-sm leading-none',
|
|
211
|
+
lucide: 'h-3.5 w-3.5',
|
|
212
|
+
close: 'h-3 w-3',
|
|
213
|
+
},
|
|
214
|
+
md: {
|
|
215
|
+
padding: 'px-4 py-3',
|
|
216
|
+
text: 'text-sm sm:text-base',
|
|
217
|
+
badge: 'h-5 w-5 text-xs',
|
|
218
|
+
customIcon: 'text-base leading-none',
|
|
219
|
+
lucide: 'h-4 w-4',
|
|
220
|
+
close: 'h-3.5 w-3.5',
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const iconLetters = { info: 'i', success: '✓', warning: '!', error: '✕' }
|
|
225
|
+
const lucideIcons = { info: Info, success: CircleCheckBig, warning: TriangleAlert, error: OctagonX }
|
|
226
|
+
|
|
227
|
+
// ─── Props / emits ────────────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
const props = defineProps({
|
|
230
|
+
type: {
|
|
231
|
+
type: String,
|
|
232
|
+
default: 'info',
|
|
233
|
+
validator: (val) => ['info', 'success', 'warning', 'error'].includes(val),
|
|
234
|
+
},
|
|
235
|
+
variant: {
|
|
236
|
+
type: String,
|
|
237
|
+
default: 'default',
|
|
238
|
+
validator: (val) => ['default', 'outlined', 'filled'].includes(val),
|
|
239
|
+
},
|
|
240
|
+
iconStyle: {
|
|
241
|
+
type: String,
|
|
242
|
+
default: 'badge',
|
|
243
|
+
validator: (val) => ['badge', 'lucide', 'none'].includes(val),
|
|
244
|
+
},
|
|
245
|
+
accent: { type: Boolean, default: false },
|
|
246
|
+
size: {
|
|
247
|
+
type: String,
|
|
248
|
+
default: 'md',
|
|
249
|
+
validator: (val) => ['sm', 'md'].includes(val),
|
|
250
|
+
},
|
|
251
|
+
title: { type: String, default: '' },
|
|
252
|
+
description: { type: String, default: '' },
|
|
253
|
+
label: { type: String, default: '' },
|
|
254
|
+
visible: { type: Boolean, default: null },
|
|
255
|
+
closable: { type: Boolean, default: false },
|
|
256
|
+
dismissAfter: {
|
|
257
|
+
type: Number,
|
|
258
|
+
default: 0,
|
|
259
|
+
validator: (val) => Number.isFinite(val) && val >= 0,
|
|
260
|
+
},
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const emit = defineEmits(['update:visible', 'close'])
|
|
264
|
+
|
|
265
|
+
// ─── State ────────────────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
const slots = useSlots()
|
|
268
|
+
const internalVisible = ref(true)
|
|
269
|
+
const messageRef = ref(null)
|
|
270
|
+
const dismissTimer = ref(null)
|
|
271
|
+
const dismissStartedAt = ref(0)
|
|
272
|
+
const dismissRemaining = ref(0)
|
|
273
|
+
|
|
274
|
+
const isVisible = computed(() => (props.visible !== null ? props.visible : internalVisible.value))
|
|
275
|
+
const isClosable = computed(() => props.closable || props.visible !== null)
|
|
276
|
+
const hasActionSlot = computed(() => !!slots.action)
|
|
277
|
+
const hasDefaultSlot = computed(() => !!slots.default)
|
|
278
|
+
const hasIconSlot = computed(() => !!slots.icon)
|
|
279
|
+
const colors = computed(() => colorConfig[props.type][props.variant])
|
|
280
|
+
const dismissAfterMs = computed(() => {
|
|
281
|
+
const value = Number(props.dismissAfter)
|
|
282
|
+
return Number.isFinite(value) && value > 0 ? value : 0
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
function clearDismissTimer() {
|
|
286
|
+
if (dismissTimer.value) {
|
|
287
|
+
clearTimeout(dismissTimer.value)
|
|
288
|
+
dismissTimer.value = null
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function startDismissTimer(duration) {
|
|
293
|
+
clearDismissTimer()
|
|
294
|
+
if (!isVisible.value || duration <= 0) return
|
|
295
|
+
|
|
296
|
+
dismissRemaining.value = duration
|
|
297
|
+
dismissStartedAt.value = Date.now()
|
|
298
|
+
dismissTimer.value = setTimeout(() => {
|
|
299
|
+
dismissTimer.value = null
|
|
300
|
+
dismissRemaining.value = 0
|
|
301
|
+
close()
|
|
302
|
+
}, duration)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function pauseDismissTimer() {
|
|
306
|
+
if (!dismissTimer.value || dismissAfterMs.value <= 0) return
|
|
307
|
+
|
|
308
|
+
const elapsed = Date.now() - dismissStartedAt.value
|
|
309
|
+
dismissRemaining.value = Math.max(0, dismissRemaining.value - elapsed)
|
|
310
|
+
clearDismissTimer()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function resumeDismissTimer() {
|
|
314
|
+
if (!isVisible.value || dismissAfterMs.value <= 0) return
|
|
315
|
+
|
|
316
|
+
if (dismissRemaining.value <= 0) {
|
|
317
|
+
close()
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
startDismissTimer(dismissRemaining.value)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function handleFocusOut(event) {
|
|
325
|
+
const nextFocused = event.relatedTarget
|
|
326
|
+
if (nextFocused && messageRef.value && messageRef.value.contains(nextFocused)) return
|
|
327
|
+
resumeDismissTimer()
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function close() {
|
|
331
|
+
clearDismissTimer()
|
|
332
|
+
dismissRemaining.value = dismissAfterMs.value
|
|
333
|
+
if (props.visible !== null) {
|
|
334
|
+
emit('update:visible', false)
|
|
335
|
+
} else {
|
|
336
|
+
internalVisible.value = false
|
|
337
|
+
}
|
|
338
|
+
emit('close')
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
watch(
|
|
342
|
+
[isVisible, dismissAfterMs],
|
|
343
|
+
([visible, timeout]) => {
|
|
344
|
+
clearDismissTimer()
|
|
345
|
+
dismissRemaining.value = timeout
|
|
346
|
+
if (visible && timeout > 0) {
|
|
347
|
+
startDismissTimer(timeout)
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
{ immediate: true }
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
onBeforeUnmount(() => {
|
|
354
|
+
clearDismissTimer()
|
|
355
|
+
})
|
|
356
|
+
</script>
|
|
357
|
+
|
|
358
|
+
<style scoped>
|
|
359
|
+
.kmessagenew-enter-active {
|
|
360
|
+
transition: all 0.2s ease-out;
|
|
361
|
+
}
|
|
362
|
+
.kmessagenew-leave-active {
|
|
363
|
+
transition: all 0.15s ease-in;
|
|
364
|
+
}
|
|
365
|
+
.kmessagenew-enter-from,
|
|
366
|
+
.kmessagenew-leave-to {
|
|
367
|
+
opacity: 0;
|
|
368
|
+
transform: translateY(-4px);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
@media (prefers-reduced-motion: reduce) {
|
|
372
|
+
.kmessagenew-enter-active,
|
|
373
|
+
.kmessagenew-leave-active {
|
|
374
|
+
transition: opacity 0.1s;
|
|
375
|
+
}
|
|
376
|
+
.kmessagenew-enter-from,
|
|
377
|
+
.kmessagenew-leave-to {
|
|
378
|
+
transform: none;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
</style>
|