@keenthemes/ktui 1.0.11 → 1.0.12
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/dist/ktui.js +1283 -1096
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/examples/select/basic-usage.html +43 -0
- package/examples/select/combobox-icons.html +58 -0
- package/examples/select/combobox.html +46 -0
- package/examples/select/description.html +69 -0
- package/examples/select/disable-option.html +43 -0
- package/examples/select/disable-select.html +34 -0
- package/examples/select/icon-description.html +56 -0
- package/examples/select/icon-multiple.html +58 -0
- package/examples/select/icon.html +58 -0
- package/examples/select/max-selection.html +39 -0
- package/examples/select/modal.html +70 -0
- package/examples/select/multiple.html +42 -0
- package/examples/select/placeholder.html +43 -0
- package/examples/select/remote-data.html +32 -0
- package/examples/select/search.html +49 -0
- package/examples/select/tags-icons.html +58 -0
- package/examples/select/tags-selected.html +59 -0
- package/examples/select/tags.html +58 -0
- package/examples/select/template-customization.html +65 -0
- package/examples/select/test.html +94 -0
- package/examples/toast/example.html +427 -0
- package/lib/cjs/components/component.js +1 -1
- package/lib/cjs/components/component.js.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +22 -6
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/select/combobox.js +38 -120
- package/lib/cjs/components/select/combobox.js.map +1 -1
- package/lib/cjs/components/select/config.js +4 -16
- package/lib/cjs/components/select/config.js.map +1 -1
- package/lib/cjs/components/select/dropdown.js +10 -49
- package/lib/cjs/components/select/dropdown.js.map +1 -1
- package/lib/cjs/components/select/index.js +2 -1
- package/lib/cjs/components/select/index.js.map +1 -1
- package/lib/cjs/components/select/option.js +21 -4
- package/lib/cjs/components/select/option.js.map +1 -1
- package/lib/cjs/components/select/remote.js +1 -37
- package/lib/cjs/components/select/remote.js.map +1 -1
- package/lib/cjs/components/select/search.js +11 -41
- package/lib/cjs/components/select/search.js.map +1 -1
- package/lib/cjs/components/select/select.js +213 -326
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/select/tags.js +39 -31
- package/lib/cjs/components/select/tags.js.map +1 -1
- package/lib/cjs/components/select/templates.js +120 -179
- package/lib/cjs/components/select/templates.js.map +1 -1
- package/lib/cjs/components/select/types.js +0 -12
- package/lib/cjs/components/select/types.js.map +1 -1
- package/lib/cjs/components/select/utils.js +204 -257
- package/lib/cjs/components/select/utils.js.map +1 -1
- package/lib/cjs/components/toast/index.js +10 -0
- package/lib/cjs/components/toast/index.js.map +1 -0
- package/lib/cjs/components/toast/toast.js +543 -0
- package/lib/cjs/components/toast/toast.js.map +1 -0
- package/lib/cjs/components/toast/types.js +7 -0
- package/lib/cjs/components/toast/types.js.map +1 -0
- package/lib/cjs/helpers/dom.js +24 -0
- package/lib/cjs/helpers/dom.js.map +1 -1
- package/lib/cjs/index.js +5 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/components/component.js +1 -1
- package/lib/esm/components/component.js.map +1 -1
- package/lib/esm/components/datatable/datatable.js +22 -6
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/select/combobox.js +39 -121
- package/lib/esm/components/select/combobox.js.map +1 -1
- package/lib/esm/components/select/config.js +3 -15
- package/lib/esm/components/select/config.js.map +1 -1
- package/lib/esm/components/select/dropdown.js +10 -49
- package/lib/esm/components/select/dropdown.js.map +1 -1
- package/lib/esm/components/select/index.js +1 -1
- package/lib/esm/components/select/index.js.map +1 -1
- package/lib/esm/components/select/option.js +21 -4
- package/lib/esm/components/select/option.js.map +1 -1
- package/lib/esm/components/select/remote.js +1 -37
- package/lib/esm/components/select/remote.js.map +1 -1
- package/lib/esm/components/select/search.js +12 -42
- package/lib/esm/components/select/search.js.map +1 -1
- package/lib/esm/components/select/select.js +214 -327
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/select/tags.js +39 -31
- package/lib/esm/components/select/tags.js.map +1 -1
- package/lib/esm/components/select/templates.js +119 -178
- package/lib/esm/components/select/templates.js.map +1 -1
- package/lib/esm/components/select/types.js +1 -11
- package/lib/esm/components/select/types.js.map +1 -1
- package/lib/esm/components/select/utils.js +201 -255
- package/lib/esm/components/select/utils.js.map +1 -1
- package/lib/esm/components/toast/index.js +6 -0
- package/lib/esm/components/toast/index.js.map +1 -0
- package/lib/esm/components/toast/toast.js +540 -0
- package/lib/esm/components/toast/toast.js.map +1 -0
- package/lib/esm/components/toast/types.js +6 -0
- package/lib/esm/components/toast/types.js.map +1 -0
- package/lib/esm/helpers/dom.js +24 -0
- package/lib/esm/helpers/dom.js.map +1 -1
- package/lib/esm/index.js +3 -0
- package/lib/esm/index.js.map +1 -1
- package/package.json +8 -6
- package/src/components/alert/alert.css +15 -2
- package/src/components/component.ts +4 -0
- package/src/components/datatable/datatable.ts +24 -16
- package/src/components/input/input.css +3 -1
- package/src/components/link/link.css +2 -2
- package/src/components/select/combobox.ts +42 -149
- package/src/components/select/config.ts +38 -33
- package/src/components/select/dropdown.ts +8 -55
- package/src/components/select/index.ts +1 -1
- package/src/components/select/option.ts +28 -7
- package/src/components/select/remote.ts +2 -42
- package/src/components/select/search.ts +14 -54
- package/src/components/select/select.css +49 -0
- package/src/components/select/select.ts +231 -437
- package/src/components/select/tags.ts +40 -37
- package/src/components/select/templates.ts +166 -303
- package/src/components/select/types.ts +0 -10
- package/src/components/select/utils.ts +214 -304
- package/src/components/textarea/textarea.css +2 -1
- package/src/components/toast/index.ts +7 -0
- package/src/components/toast/toast.css +60 -0
- package/src/components/toast/toast.ts +605 -0
- package/src/components/toast/types.ts +169 -0
- package/src/helpers/dom.ts +30 -0
- package/src/index.ts +4 -0
- package/styles/main.css +3 -0
- package/styles/vars.css +138 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Base Styles */
|
|
7
|
+
@layer components {
|
|
8
|
+
/* Container */
|
|
9
|
+
.kt-toast-container {
|
|
10
|
+
@apply fixed;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Toast */
|
|
14
|
+
.kt-toast {
|
|
15
|
+
@apply fixed z-9999 max-w-[95%] w-76 shadow-sm pointer-events-auto overflow-hidden;
|
|
16
|
+
|
|
17
|
+
opacity: 0;
|
|
18
|
+
animation: kt-toast-in 0.28s cubic-bezier(.4,0,.2,1) forwards;
|
|
19
|
+
transition: top 0.28s cubic-bezier(.4,0,.2,1), opacity 0.28s cubic-bezier(.4,0,.2,1);
|
|
20
|
+
|
|
21
|
+
&.kt-toast-top-end { @apply bottom-auto top-0 end-0; }
|
|
22
|
+
&.kt-toast-top-center { @apply bottom-auto top-0 start-1/2 -translate-x-1/2; }
|
|
23
|
+
&.kt-toast-top-start { @apply bottom-auto top-0 start-0; }
|
|
24
|
+
&.kt-toast-bottom-end { @apply top-auto bottom-0 end-0; }
|
|
25
|
+
&.kt-toast-bottom-center { @apply top-auto bottom-0 start-1/2 -translate-x-1/2; }
|
|
26
|
+
&.kt-toast-bottom-start { @apply top-auto bottom-0 start-0; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Progress */
|
|
30
|
+
.kt-toast-progress {
|
|
31
|
+
@apply fixed start-0 bottom-0 w-full h-[3px] bg-primary;
|
|
32
|
+
transform-origin: left;
|
|
33
|
+
animation: kt-toast-progress-line linear forwards;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* RTL Styles */
|
|
38
|
+
@layer components {
|
|
39
|
+
[dir='rtl'] .kt-toast-progress {
|
|
40
|
+
transform-origin: right;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Animations */
|
|
45
|
+
@layer components {
|
|
46
|
+
@keyframes kt-toast-in {
|
|
47
|
+
from { opacity: 0; transform: translateY(-24px); }
|
|
48
|
+
to { opacity: 1; transform: translateY(0); }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@keyframes kt-toast-out {
|
|
52
|
+
from { opacity: 1; }
|
|
53
|
+
to { opacity: 0; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@keyframes kt-toast-progress-line {
|
|
57
|
+
from { transform: scaleX(1); }
|
|
58
|
+
to { transform: scaleX(0); }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import KTComponent from '../component';
|
|
7
|
+
import KTData from '../../helpers/data';
|
|
8
|
+
import {
|
|
9
|
+
KTToastOptions,
|
|
10
|
+
KTToastConfig,
|
|
11
|
+
KTToastInstance,
|
|
12
|
+
KTToastPosition,
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
const DEFAULT_CONFIG: KTToastConfig = {
|
|
16
|
+
position: 'top-end',
|
|
17
|
+
duration: 4000,
|
|
18
|
+
className: '',
|
|
19
|
+
maxToasts: 5,
|
|
20
|
+
offset: 15,
|
|
21
|
+
gap: 10,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const DEFAULT_TOAST_OPTIONS: KTToastOptions = {
|
|
25
|
+
appearance: 'solid',
|
|
26
|
+
progress: false,
|
|
27
|
+
size: 'md',
|
|
28
|
+
action: false,
|
|
29
|
+
cancel: false,
|
|
30
|
+
dismiss: true,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
import type { KTToastConfigInterface, KTToastInterface } from './types';
|
|
34
|
+
|
|
35
|
+
export class KTToast extends KTComponent implements KTToastInterface {
|
|
36
|
+
protected override _name: string = 'toast';
|
|
37
|
+
protected override _defaultConfig: KTToastConfigInterface = DEFAULT_CONFIG;
|
|
38
|
+
protected override _config: KTToastConfigInterface = DEFAULT_CONFIG;
|
|
39
|
+
protected _defaultToastOptions: KTToastOptions = DEFAULT_TOAST_OPTIONS;
|
|
40
|
+
private static containerMap: Map<KTToastPosition, HTMLElement> = new Map();
|
|
41
|
+
private static toasts: Map<string, KTToastInstance> = new Map();
|
|
42
|
+
private static globalConfig: KTToastConfig = { ...DEFAULT_CONFIG };
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a new KTToast instance for a specific element (not commonly used; most use static API).
|
|
46
|
+
* @param element The target HTML element.
|
|
47
|
+
* @param config Optional toast config for this instance.
|
|
48
|
+
*/
|
|
49
|
+
constructor(element: HTMLElement, config?: Partial<KTToastConfigInterface>) {
|
|
50
|
+
super();
|
|
51
|
+
if (KTData.has(element, this._name)) return;
|
|
52
|
+
this._init(element);
|
|
53
|
+
this._buildConfig(config);
|
|
54
|
+
KTData.set(element, this._name, this);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generates the HTML content for a toast based on the provided options.
|
|
59
|
+
* @param options Toast options (message, icon, actions, etc).
|
|
60
|
+
* @returns The toast's HTML markup as a string.
|
|
61
|
+
*/
|
|
62
|
+
static getContent(options?: KTToastOptions) {
|
|
63
|
+
const classNames = {
|
|
64
|
+
...((this.globalConfig.classNames as any) || {}),
|
|
65
|
+
...((options?.classNames as any) || {}),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (options?.content) {
|
|
69
|
+
if (typeof options.content === 'string') {
|
|
70
|
+
return options.content;
|
|
71
|
+
} else if (typeof options.content === 'function') {
|
|
72
|
+
const node = options.content();
|
|
73
|
+
if (node instanceof HTMLElement) {
|
|
74
|
+
return node.outerHTML;
|
|
75
|
+
}
|
|
76
|
+
} else if (options.content instanceof HTMLElement) {
|
|
77
|
+
return options.content.outerHTML;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let template = '';
|
|
82
|
+
|
|
83
|
+
if (options?.icon) {
|
|
84
|
+
template +=
|
|
85
|
+
'<div class="kt-alert-icon ' +
|
|
86
|
+
(classNames.icon || '') +
|
|
87
|
+
'">' +
|
|
88
|
+
options.icon +
|
|
89
|
+
'</div>';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (options?.message) {
|
|
93
|
+
template +=
|
|
94
|
+
'<div class="kt-alert-title ' +
|
|
95
|
+
(classNames.message || '') +
|
|
96
|
+
'">' +
|
|
97
|
+
options.message +
|
|
98
|
+
'</div>';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
options?.action !== false ||
|
|
103
|
+
options?.dismiss !== false ||
|
|
104
|
+
options?.cancel !== false
|
|
105
|
+
) {
|
|
106
|
+
template +=
|
|
107
|
+
'<div class="kt-alert-toolbar ' + (classNames.toolbar || '') + '">';
|
|
108
|
+
template +=
|
|
109
|
+
'<div class="kt-alert-actions ' + (classNames.actions || '') + '">';
|
|
110
|
+
|
|
111
|
+
if (options?.action && typeof options.action === 'object') {
|
|
112
|
+
template +=
|
|
113
|
+
'<button data-kt-toast-action="true" class="' +
|
|
114
|
+
(options.action.className || '') +
|
|
115
|
+
'">' +
|
|
116
|
+
options.action.label +
|
|
117
|
+
'</button>';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (options?.cancel && typeof options.cancel === 'object') {
|
|
121
|
+
template +=
|
|
122
|
+
'<button data-kt-toast-cancel="true" class="' +
|
|
123
|
+
(options.cancel.className || '') +
|
|
124
|
+
'">' +
|
|
125
|
+
options.cancel.label +
|
|
126
|
+
'</button>';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (options?.dismiss !== false) {
|
|
130
|
+
template +=
|
|
131
|
+
'<button data-kt-toast-dismiss="true" class="kt-alert-close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
template += '</div>';
|
|
135
|
+
template += '</div>';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
template += '</div>';
|
|
139
|
+
|
|
140
|
+
return template;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Update all toasts in the container with smooth animation.
|
|
145
|
+
*
|
|
146
|
+
* @param container The toast container element.
|
|
147
|
+
* @param offset Optional offset from the edge.
|
|
148
|
+
*/
|
|
149
|
+
static update(container: HTMLElement | null, offset?: number) {
|
|
150
|
+
if (!container) return;
|
|
151
|
+
offset =
|
|
152
|
+
typeof offset === 'number' ? offset : (this.globalConfig.offset ?? 15);
|
|
153
|
+
requestAnimationFrame(() => {
|
|
154
|
+
const gap = this.globalConfig.gap ?? 8;
|
|
155
|
+
// Group toasts by alignment (top/bottom)
|
|
156
|
+
const positionGroups: Record<string, HTMLElement[]> = {
|
|
157
|
+
top: [],
|
|
158
|
+
bottom: [],
|
|
159
|
+
};
|
|
160
|
+
const toasts = Array.from(container.children) as HTMLElement[];
|
|
161
|
+
toasts.forEach((toast) => {
|
|
162
|
+
if (
|
|
163
|
+
toast.classList.contains('kt-toast-top-end') ||
|
|
164
|
+
toast.classList.contains('kt-toast-top-center') ||
|
|
165
|
+
toast.classList.contains('kt-toast-top-start')
|
|
166
|
+
) {
|
|
167
|
+
positionGroups.top.push(toast);
|
|
168
|
+
} else {
|
|
169
|
+
positionGroups.bottom.push(toast);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Stack top toasts from the top down
|
|
174
|
+
let currentOffset = offset;
|
|
175
|
+
positionGroups.top.forEach((toast) => {
|
|
176
|
+
toast.style.top = `${currentOffset}px`;
|
|
177
|
+
toast.style.bottom = '';
|
|
178
|
+
toast.style.transition =
|
|
179
|
+
'top 0.28s cubic-bezier(.4,0,.2,1), opacity 0.28s cubic-bezier(.4,0,.2,1)';
|
|
180
|
+
currentOffset += toast.offsetHeight + gap;
|
|
181
|
+
|
|
182
|
+
if (toast.classList.contains('kt-toast-top-start')) {
|
|
183
|
+
toast.style.insetInlineStart = `${offset}px`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (toast.classList.contains('kt-toast-top-end')) {
|
|
187
|
+
toast.style.insetInlineEnd = `${offset}px`;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Stack bottom toasts from the bottom up
|
|
192
|
+
currentOffset = offset;
|
|
193
|
+
for (let i = positionGroups.bottom.length - 1; i >= 0; i--) {
|
|
194
|
+
const toast = positionGroups.bottom[i];
|
|
195
|
+
toast.style.bottom = `${currentOffset}px`;
|
|
196
|
+
toast.style.top = '';
|
|
197
|
+
toast.style.transition =
|
|
198
|
+
'bottom 0.28s cubic-bezier(.4,0,.2,1), opacity 0.28s cubic-bezier(.4,0,.2,1)';
|
|
199
|
+
currentOffset += toast.offsetHeight + gap;
|
|
200
|
+
|
|
201
|
+
if (toast.classList.contains('kt-toast-bottom-start')) {
|
|
202
|
+
toast.style.insetInlineStart = `${offset}px`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (toast.classList.contains('kt-toast-bottom-end')) {
|
|
206
|
+
toast.style.insetInlineEnd = `${offset}px`;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Set global toast configuration options.
|
|
214
|
+
* @param options Partial toast config to merge with global config.
|
|
215
|
+
*/
|
|
216
|
+
static config(options: Partial<KTToastConfig>) {
|
|
217
|
+
this.globalConfig = { ...this.globalConfig, ...options };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Show a toast notification.
|
|
222
|
+
* @param inputOptions Toast options (message, duration, variant, etc).
|
|
223
|
+
* @returns Toast instance with dismiss method, or undefined if invalid input.
|
|
224
|
+
*/
|
|
225
|
+
static show(
|
|
226
|
+
inputOptions?: KTToastOptions,
|
|
227
|
+
): (KTToastInstance & { dismiss: () => void }) | undefined {
|
|
228
|
+
const options = { ...DEFAULT_TOAST_OPTIONS, ...inputOptions };
|
|
229
|
+
|
|
230
|
+
if (!options || (!options.message && !options.content)) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Always resolve the id once and use it everywhere
|
|
235
|
+
const id = `kt-toast-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
236
|
+
|
|
237
|
+
const position =
|
|
238
|
+
options.position || this.globalConfig.position || 'top-end';
|
|
239
|
+
|
|
240
|
+
const classNames = {
|
|
241
|
+
...((this.globalConfig.classNames as any) || {}),
|
|
242
|
+
...((options.classNames as any) || {}),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
let container = this.containerMap.get(position);
|
|
246
|
+
|
|
247
|
+
if (!container) {
|
|
248
|
+
container = document.createElement('div');
|
|
249
|
+
const classNames = {
|
|
250
|
+
...((this.globalConfig.classNames as any) || {}),
|
|
251
|
+
...((options.classNames as any) || {}),
|
|
252
|
+
};
|
|
253
|
+
// Fallback to default hardcoded classes if not provided in options or globalConfig
|
|
254
|
+
container.className =
|
|
255
|
+
classNames.container || `kt-toast-container ${position}`;
|
|
256
|
+
document.body.appendChild(container);
|
|
257
|
+
this.containerMap.set(position, container);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Enforce maxToasts
|
|
261
|
+
if (
|
|
262
|
+
container.children.length >=
|
|
263
|
+
(this.globalConfig.maxToasts || DEFAULT_CONFIG.maxToasts)
|
|
264
|
+
) {
|
|
265
|
+
const firstToast = container.firstElementChild;
|
|
266
|
+
if (firstToast) {
|
|
267
|
+
firstToast.classList.add('kt-toast-closing');
|
|
268
|
+
firstToast.addEventListener('animationend', () => {
|
|
269
|
+
firstToast.remove();
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Create toast element
|
|
275
|
+
const variantMap = {
|
|
276
|
+
info: 'kt-alert-info',
|
|
277
|
+
success: 'kt-alert-success',
|
|
278
|
+
error: 'kt-alert-error',
|
|
279
|
+
warning: 'kt-alert-warning',
|
|
280
|
+
primary: 'kt-alert-primary',
|
|
281
|
+
secondary: 'kt-alert-secondary',
|
|
282
|
+
destructive: 'kt-alert-destructive',
|
|
283
|
+
mono: 'kt-alert-mono',
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const appearanceMap = {
|
|
287
|
+
solid: 'kt-alert-solid',
|
|
288
|
+
outline: 'kt-alert-outline',
|
|
289
|
+
light: 'kt-alert-light',
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const sizeMap = {
|
|
293
|
+
sm: 'kt-alert-sm',
|
|
294
|
+
md: 'kt-alert-md',
|
|
295
|
+
lg: 'kt-alert-lg',
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const toast = document.createElement('div');
|
|
299
|
+
toast.className = `kt-toast kt-alert ${variantMap[options.variant] || ''} ${appearanceMap[options.appearance] || ''} ${sizeMap[options.size] || ''} ${options.className || ''} ${classNames.toast || ''}`;
|
|
300
|
+
// ARIA support
|
|
301
|
+
toast.setAttribute('role', options.role || 'status');
|
|
302
|
+
toast.setAttribute('aria-live', 'polite');
|
|
303
|
+
toast.setAttribute('aria-atomic', 'true');
|
|
304
|
+
toast.setAttribute('tabindex', '0');
|
|
305
|
+
|
|
306
|
+
// Always resolve the id once and use it everywhere
|
|
307
|
+
// Always resolve id ONCE at the top, use everywhere
|
|
308
|
+
// (Move this up to replace the previous const id = ... assignment)
|
|
309
|
+
|
|
310
|
+
// Populate content via getContent
|
|
311
|
+
const contentHtml = KTToast.getContent(options);
|
|
312
|
+
toast.innerHTML = contentHtml;
|
|
313
|
+
|
|
314
|
+
// Assign event handlers to buttons by data attribute
|
|
315
|
+
const actionBtn = toast.querySelector(
|
|
316
|
+
'[data-kt-toast-action]',
|
|
317
|
+
) as HTMLButtonElement | null;
|
|
318
|
+
|
|
319
|
+
if (
|
|
320
|
+
actionBtn &&
|
|
321
|
+
options.action &&
|
|
322
|
+
typeof options.action === 'object' &&
|
|
323
|
+
options.action.onClick
|
|
324
|
+
) {
|
|
325
|
+
actionBtn.addEventListener('click', (e) => {
|
|
326
|
+
e.stopPropagation();
|
|
327
|
+
if (typeof options.action === 'object' && options.action.onClick) {
|
|
328
|
+
options.action.onClick(id);
|
|
329
|
+
KTToast.close(id);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const cancelBtn = toast.querySelector(
|
|
335
|
+
'[data-kt-toast-cancel]',
|
|
336
|
+
) as HTMLButtonElement | null;
|
|
337
|
+
|
|
338
|
+
if (cancelBtn && options.cancel && typeof options.cancel === 'object') {
|
|
339
|
+
cancelBtn.addEventListener('click', (e) => {
|
|
340
|
+
e.stopPropagation();
|
|
341
|
+
if (typeof options.cancel === 'object' && options.cancel.onClick) {
|
|
342
|
+
options.cancel.onClick(id);
|
|
343
|
+
KTToast.close(id);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Dismiss button handler
|
|
349
|
+
const dismissBtn = toast.querySelector(
|
|
350
|
+
'[data-kt-toast-dismiss]',
|
|
351
|
+
) as HTMLButtonElement | null;
|
|
352
|
+
|
|
353
|
+
if (dismissBtn && options.dismiss !== false) {
|
|
354
|
+
dismissBtn.addEventListener('click', (e) => {
|
|
355
|
+
e.stopPropagation();
|
|
356
|
+
KTToast.close(id);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// If modal-like, set aria-modal
|
|
361
|
+
if (options.important) toast.setAttribute('aria-modal', 'true');
|
|
362
|
+
toast.style.pointerEvents = 'auto';
|
|
363
|
+
|
|
364
|
+
// Progress line
|
|
365
|
+
const duration = options.important
|
|
366
|
+
? null
|
|
367
|
+
: (options.duration ??
|
|
368
|
+
this.globalConfig.duration ??
|
|
369
|
+
DEFAULT_CONFIG.duration);
|
|
370
|
+
|
|
371
|
+
if (duration && options.progress) {
|
|
372
|
+
const progress = document.createElement('div');
|
|
373
|
+
progress.className = 'kt-toast-progress ' + (classNames.progress || '');
|
|
374
|
+
progress.style.animationDuration = duration + 'ms';
|
|
375
|
+
progress.setAttribute('data-kt-toast-progress', 'true');
|
|
376
|
+
toast.appendChild(progress);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Assign direction class to the toast itself, not the container
|
|
380
|
+
const directionClassMap: Record<string, string> = {
|
|
381
|
+
'top-end': 'kt-toast-top-end',
|
|
382
|
+
'top-center': 'kt-toast-top-center',
|
|
383
|
+
'top-start': 'kt-toast-top-start',
|
|
384
|
+
'bottom-end': 'kt-toast-bottom-end',
|
|
385
|
+
'bottom-center': 'kt-toast-bottom-center',
|
|
386
|
+
'bottom-start': 'kt-toast-bottom-start',
|
|
387
|
+
};
|
|
388
|
+
Object.values(directionClassMap).forEach((cls) =>
|
|
389
|
+
toast.classList.remove(cls),
|
|
390
|
+
);
|
|
391
|
+
const dirClass = directionClassMap[position] || 'kt-toast-top-end';
|
|
392
|
+
toast.classList.add(dirClass);
|
|
393
|
+
|
|
394
|
+
// Enforce maxToasts: remove oldest if needed
|
|
395
|
+
const maxToasts =
|
|
396
|
+
options.maxToasts ??
|
|
397
|
+
this.globalConfig.maxToasts ??
|
|
398
|
+
DEFAULT_CONFIG.maxToasts;
|
|
399
|
+
const currentToasts = Array.from(container.children) as HTMLElement[];
|
|
400
|
+
if (currentToasts.length >= maxToasts && currentToasts.length > 0) {
|
|
401
|
+
const oldestToast = currentToasts[currentToasts.length - 1];
|
|
402
|
+
const oldestId = oldestToast.getAttribute('data-kt-toast-id');
|
|
403
|
+
if (oldestId) {
|
|
404
|
+
KTToast.close(oldestId);
|
|
405
|
+
} else {
|
|
406
|
+
oldestToast.remove();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Insert toast at the top
|
|
411
|
+
container.insertBefore(toast, container.firstChild);
|
|
412
|
+
KTToast.update(container);
|
|
413
|
+
|
|
414
|
+
// Play beep if requested
|
|
415
|
+
if (options.beep) {
|
|
416
|
+
try {
|
|
417
|
+
// Use Web Audio API for a short beep
|
|
418
|
+
const ctx = new (window.AudioContext ||
|
|
419
|
+
(window as any).webkitAudioContext)();
|
|
420
|
+
const o = ctx.createOscillator();
|
|
421
|
+
const g = ctx.createGain();
|
|
422
|
+
o.type = 'sine';
|
|
423
|
+
o.frequency.value = 880;
|
|
424
|
+
g.gain.value = 0.09;
|
|
425
|
+
o.connect(g);
|
|
426
|
+
g.connect(ctx.destination);
|
|
427
|
+
o.start();
|
|
428
|
+
setTimeout(() => {
|
|
429
|
+
o.stop();
|
|
430
|
+
ctx.close();
|
|
431
|
+
}, 120);
|
|
432
|
+
} catch (e) {
|
|
433
|
+
/* ignore */
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
KTToast._fireEventOnElement(toast, 'show', { id });
|
|
438
|
+
KTToast._dispatchEventOnElement(toast, 'show', { id });
|
|
439
|
+
const instance: KTToastInstance = { id, element: toast, timeoutId: 0 };
|
|
440
|
+
KTToast.toasts.set(id, instance);
|
|
441
|
+
|
|
442
|
+
// Auto-dismiss
|
|
443
|
+
let timeoutId: number | undefined = undefined;
|
|
444
|
+
let remaining = duration;
|
|
445
|
+
let startTime: number | undefined;
|
|
446
|
+
let paused = false;
|
|
447
|
+
let progressEl: HTMLElement | null = null;
|
|
448
|
+
if (duration) {
|
|
449
|
+
const startTimer = (ms: number) => {
|
|
450
|
+
startTime = Date.now();
|
|
451
|
+
timeoutId = window.setTimeout(() => {
|
|
452
|
+
options.onAutoClose?.(id);
|
|
453
|
+
KTToast.close(id);
|
|
454
|
+
}, ms);
|
|
455
|
+
instance.timeoutId = timeoutId;
|
|
456
|
+
};
|
|
457
|
+
startTimer(duration);
|
|
458
|
+
|
|
459
|
+
if (options.pauseOnHover) {
|
|
460
|
+
progressEl = toast.querySelector('[data-kt-toast-progress]');
|
|
461
|
+
let progressPausedAt = 0;
|
|
462
|
+
const pause = () => {
|
|
463
|
+
if (!paused && timeoutId) {
|
|
464
|
+
paused = true;
|
|
465
|
+
window.clearTimeout(timeoutId);
|
|
466
|
+
if (startTime) {
|
|
467
|
+
remaining -= Date.now() - startTime;
|
|
468
|
+
}
|
|
469
|
+
// Pause progress bar
|
|
470
|
+
if (progressEl) {
|
|
471
|
+
const computedStyle = window.getComputedStyle(progressEl);
|
|
472
|
+
const matrix = computedStyle.transform;
|
|
473
|
+
let scaleX = 1;
|
|
474
|
+
if (matrix && matrix !== 'none') {
|
|
475
|
+
const values = matrix.match(/matrix\(([^)]+)\)/);
|
|
476
|
+
if (values && values[1]) {
|
|
477
|
+
scaleX = parseFloat(values[1].split(',')[0]);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
progressPausedAt = scaleX;
|
|
481
|
+
progressEl.style.animation = 'none';
|
|
482
|
+
progressEl.style.transition = 'none';
|
|
483
|
+
progressEl.style.transform = `scaleX(${scaleX})`;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
const resume = () => {
|
|
488
|
+
if (paused && remaining > 0) {
|
|
489
|
+
paused = false;
|
|
490
|
+
startTimer(remaining);
|
|
491
|
+
// Resume progress bar
|
|
492
|
+
if (progressEl) {
|
|
493
|
+
progressEl.style.transition = 'transform 0ms';
|
|
494
|
+
progressEl.style.transform = `scaleX(${progressPausedAt})`;
|
|
495
|
+
progressEl.offsetHeight; // force reflow
|
|
496
|
+
progressEl.style.transition = `transform ${remaining}ms linear`;
|
|
497
|
+
progressEl.style.transform = 'scaleX(0)';
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
toast.addEventListener('mouseenter', pause);
|
|
502
|
+
toast.addEventListener('mouseleave', resume);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
KTToast._fireEventOnElement(toast, 'shown', { id });
|
|
507
|
+
KTToast._dispatchEventOnElement(toast, 'shown', { id });
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
...instance,
|
|
511
|
+
dismiss: () => KTToast.close(id),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Close and remove all active toasts.
|
|
517
|
+
*/
|
|
518
|
+
static clearAll() {
|
|
519
|
+
for (const id of Array.from(this.toasts.keys())) {
|
|
520
|
+
console.log('clearAll:', id);
|
|
521
|
+
this.close(id);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Close a toast by ID or instance.
|
|
527
|
+
* @param idOrInstance Toast ID string or KTToastInstance.
|
|
528
|
+
*/
|
|
529
|
+
static close(idOrInstance?: string | KTToastInstance) {
|
|
530
|
+
let inst: (KTToastInstance & { _closing?: boolean }) | undefined;
|
|
531
|
+
let id: string | undefined;
|
|
532
|
+
if (!idOrInstance) return;
|
|
533
|
+
if (typeof idOrInstance === 'string') {
|
|
534
|
+
id = idOrInstance;
|
|
535
|
+
inst = this.toasts.get(id);
|
|
536
|
+
} else if (typeof idOrInstance === 'object' && idOrInstance.id) {
|
|
537
|
+
id = idOrInstance.id;
|
|
538
|
+
inst = idOrInstance as KTToastInstance & { _closing?: boolean };
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (!inst || !id) return;
|
|
542
|
+
if (inst._closing) return; // Prevent double-close
|
|
543
|
+
inst._closing = true;
|
|
544
|
+
|
|
545
|
+
clearTimeout(inst.timeoutId);
|
|
546
|
+
|
|
547
|
+
KTToast._fireEventOnElement(inst.element, 'hide', { id });
|
|
548
|
+
KTToast._dispatchEventOnElement(inst.element, 'hide', { id });
|
|
549
|
+
// Remove progress bar instantly if present
|
|
550
|
+
const progressEl = inst.element.querySelector('[data-kt-toast-progress]');
|
|
551
|
+
if (progressEl) progressEl.remove();
|
|
552
|
+
inst.element.style.animation = 'kt-toast-out 0.25s forwards';
|
|
553
|
+
|
|
554
|
+
setTimeout(() => {
|
|
555
|
+
const parent = inst?.element.parentElement as HTMLElement | null;
|
|
556
|
+
inst?.element.remove();
|
|
557
|
+
KTToast.toasts.delete(id!);
|
|
558
|
+
// Try to call onDismiss if available in the toast instance (if stored)
|
|
559
|
+
if (typeof (inst as any).options?.onDismiss === 'function') {
|
|
560
|
+
(inst as any).options.onDismiss(id);
|
|
561
|
+
}
|
|
562
|
+
KTToast._fireEventOnElement(inst.element, 'hidden', { id });
|
|
563
|
+
KTToast._dispatchEventOnElement(inst.element, 'hidden', { id });
|
|
564
|
+
// update toasts asynchronously after DOM update
|
|
565
|
+
setTimeout(() => {
|
|
566
|
+
KTToast.update(parent);
|
|
567
|
+
}, 0);
|
|
568
|
+
}, 250);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Dispatches a custom 'kt.toast.{eventType}' event on the given element.
|
|
573
|
+
* @param element The toast element.
|
|
574
|
+
* @param eventType The event type (e.g. 'show', 'hide').
|
|
575
|
+
* @param payload Optional event detail payload.
|
|
576
|
+
*/
|
|
577
|
+
private static _fireEventOnElement(
|
|
578
|
+
element: HTMLElement,
|
|
579
|
+
eventType: string,
|
|
580
|
+
payload?: object,
|
|
581
|
+
) {
|
|
582
|
+
const event = new CustomEvent(`kt.toast.${eventType}`, { detail: payload });
|
|
583
|
+
element.dispatchEvent(event);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Dispatches a custom event (not namespaced) on the given element.
|
|
588
|
+
* @param element The toast element.
|
|
589
|
+
* @param eventType The event type.
|
|
590
|
+
* @param payload Optional event detail payload.
|
|
591
|
+
*/
|
|
592
|
+
private static _dispatchEventOnElement(
|
|
593
|
+
element: HTMLElement,
|
|
594
|
+
eventType: string,
|
|
595
|
+
payload?: object,
|
|
596
|
+
) {
|
|
597
|
+
const event = new CustomEvent(eventType, { detail: payload });
|
|
598
|
+
element.dispatchEvent(event);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Initialize toast system (placeholder for future use).
|
|
603
|
+
*/
|
|
604
|
+
public static init(): void {}
|
|
605
|
+
}
|