@doyosi/laraisy 1.0.2 → 1.0.3
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/LICENSE +1 -1
- package/package.json +1 -1
- package/src/CodeInput.js +48 -48
- package/src/DSAlert.js +352 -352
- package/src/DSAvatar.js +207 -207
- package/src/DSDelete.js +274 -274
- package/src/DSForm.js +568 -568
- package/src/DSGridOrTable.js +453 -453
- package/src/DSLocaleSwitcher.js +239 -239
- package/src/DSLogout.js +293 -293
- package/src/DSNotifications.js +365 -365
- package/src/DSRestore.js +181 -181
- package/src/DSSelect.js +1071 -1071
- package/src/DSSelectBox.js +563 -563
- package/src/DSSimpleSlider.js +517 -517
- package/src/DSSvgFetch.js +69 -69
- package/src/DSTable/DSTableExport.js +68 -68
- package/src/DSTable/DSTableFilter.js +224 -224
- package/src/DSTable/DSTablePagination.js +136 -136
- package/src/DSTable/DSTableSearch.js +40 -40
- package/src/DSTable/DSTableSelection.js +192 -192
- package/src/DSTable/DSTableSort.js +58 -58
- package/src/DSTable.js +353 -353
- package/src/DSTabs.js +488 -488
- package/src/DSUpload.js +887 -887
- package/dist/CodeInput.d.ts +0 -10
- package/dist/DSAlert.d.ts +0 -112
- package/dist/DSAvatar.d.ts +0 -45
- package/dist/DSDelete.d.ts +0 -61
- package/dist/DSForm.d.ts +0 -151
- package/dist/DSGridOrTable/DSGOTRenderer.d.ts +0 -60
- package/dist/DSGridOrTable/DSGOTViewToggle.d.ts +0 -26
- package/dist/DSGridOrTable.d.ts +0 -296
- package/dist/DSLocaleSwitcher.d.ts +0 -71
- package/dist/DSLogout.d.ts +0 -76
- package/dist/DSNotifications.d.ts +0 -54
- package/dist/DSRestore.d.ts +0 -56
- package/dist/DSSelect.d.ts +0 -221
- package/dist/DSSelectBox.d.ts +0 -123
- package/dist/DSSimpleSlider.d.ts +0 -136
- package/dist/DSSvgFetch.d.ts +0 -17
- package/dist/DSTable/DSTableExport.d.ts +0 -11
- package/dist/DSTable/DSTableFilter.d.ts +0 -40
- package/dist/DSTable/DSTablePagination.d.ts +0 -12
- package/dist/DSTable/DSTableSearch.d.ts +0 -8
- package/dist/DSTable/DSTableSelection.d.ts +0 -46
- package/dist/DSTable/DSTableSort.d.ts +0 -8
- package/dist/DSTable.d.ts +0 -116
- package/dist/DSTabs.d.ts +0 -156
- package/dist/DSUpload.d.ts +0 -220
- package/dist/index.d.ts +0 -17
package/src/DSAlert.js
CHANGED
|
@@ -1,353 +1,353 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DSAlert (Class-based)
|
|
3
|
-
* A lightweight, native JavaScript alert/toast system using Tailwind CSS.
|
|
4
|
-
* API designed to mimic SweetAlert2 for easy migration.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* import { DSAlert } from './DSAlert.js';
|
|
8
|
-
* DSAlert.fire({ title: 'Hello', icon: 'success' });
|
|
9
|
-
* // OR shorthand
|
|
10
|
-
* DSAlert.fire('Deleted!', 'Your file has been deleted.', 'success');
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export class DSAlert {
|
|
14
|
-
/**
|
|
15
|
-
* Default Icons - Can be overridden by modifying DSAlert.icons
|
|
16
|
-
*/
|
|
17
|
-
static icons = {
|
|
18
|
-
success: `<svg class="w-6 h-6 text-green-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>`,
|
|
19
|
-
error: `<svg class="w-6 h-6 text-red-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" /></svg>`,
|
|
20
|
-
warning: `<svg class="w-6 h-6 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.008v.008H12v-.008z" /></svg>`,
|
|
21
|
-
info: `<svg class="w-6 h-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" /></svg>`,
|
|
22
|
-
question: `<svg class="w-6 h-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" /></svg>`
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Default defaults
|
|
27
|
-
*/
|
|
28
|
-
static defaults = {
|
|
29
|
-
title: '',
|
|
30
|
-
text: '',
|
|
31
|
-
html: '',
|
|
32
|
-
icon: '',
|
|
33
|
-
toast: false,
|
|
34
|
-
position: 'top-end',
|
|
35
|
-
timer: 0,
|
|
36
|
-
timerProgressBar: false,
|
|
37
|
-
showConfirmButton: true,
|
|
38
|
-
showCancelButton: false,
|
|
39
|
-
showCloseButton: true,
|
|
40
|
-
allowOutsideClick: true,
|
|
41
|
-
allowEscapeKey: true,
|
|
42
|
-
confirmButtonText: 'OK',
|
|
43
|
-
cancelButtonText: 'Cancel',
|
|
44
|
-
confirmButtonColor: 'btn btn-sm btn-primary',
|
|
45
|
-
cancelButtonColor: 'btn btn-sm btn-soft btn-neutral',
|
|
46
|
-
buttonsAlign: 'end', // 'start' | 'center' | 'end'
|
|
47
|
-
backdrop: true,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
constructor(...args) {
|
|
51
|
-
this.config = this._parseArgs(args);
|
|
52
|
-
this.domElement = null;
|
|
53
|
-
this.container = null;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Static helper to fire the alert immediately
|
|
58
|
-
* @param {...any} args - Options object OR (title, text, icon)
|
|
59
|
-
* @returns {Promise}
|
|
60
|
-
*/
|
|
61
|
-
static fire(...args) {
|
|
62
|
-
const instance = new DSAlert(...args);
|
|
63
|
-
return instance.fire();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Triggers the alert/toast
|
|
68
|
-
* @returns {Promise}
|
|
69
|
-
*/
|
|
70
|
-
fire() {
|
|
71
|
-
return new Promise((resolve) => {
|
|
72
|
-
if (this.config.toast) {
|
|
73
|
-
this._initToast(resolve);
|
|
74
|
-
} else {
|
|
75
|
-
this._initModal(resolve);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Parse constructor arguments to support shorthand (Title, Text, Icon)
|
|
82
|
-
*/
|
|
83
|
-
_parseArgs(args) {
|
|
84
|
-
let userConfig = {};
|
|
85
|
-
|
|
86
|
-
// Handle: fire('Title', 'Text', 'success')
|
|
87
|
-
if (typeof args[0] === 'string') {
|
|
88
|
-
userConfig.title = args[0];
|
|
89
|
-
if (args[1]) userConfig.text = args[1];
|
|
90
|
-
if (args[2]) userConfig.icon = args[2];
|
|
91
|
-
}
|
|
92
|
-
// Handle: fire({ ...options })
|
|
93
|
-
else if (typeof args[0] === 'object' && args[0] !== null) {
|
|
94
|
-
userConfig = args[0];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return { ...DSAlert.defaults, ...userConfig };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/* ================= TOAST LOGIC ================= */
|
|
101
|
-
|
|
102
|
-
_initToast(resolve) {
|
|
103
|
-
this.container = this._ensureToastContainer();
|
|
104
|
-
this.domElement = document.createElement('div');
|
|
105
|
-
|
|
106
|
-
// Base classes
|
|
107
|
-
// Replaced 'w-full' with 'w-80' (20rem/320px) to prevent collapse in centered containers
|
|
108
|
-
// Added 'relative' to support absolute positioning of progress bar
|
|
109
|
-
this.domElement.className = `ds-alert-toast pointer-events-auto w-80 max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black/5 transform transition-all duration-300 translate-x-10 opacity-0 relative`;
|
|
110
|
-
|
|
111
|
-
const iconHtml = DSAlert.icons[this.config.icon] || '';
|
|
112
|
-
const contentHtml = this.config.html || `<p class="mt-1 text-sm text-gray-500">${this.config.text}</p>`;
|
|
113
|
-
const titleHtml = this.config.title ? `<p class="text-sm font-medium text-gray-900">${this.config.title}</p>` : '';
|
|
114
|
-
|
|
115
|
-
// Close Button (Conditional)
|
|
116
|
-
const closeBtnHtml = this.config.showCloseButton
|
|
117
|
-
? `<div class="ml-4 flex flex-shrink-0">
|
|
118
|
-
<button type="button" class="ds-close-btn inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
|
119
|
-
<span class="sr-only">Close</span>
|
|
120
|
-
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
121
|
-
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
|
122
|
-
</svg>
|
|
123
|
-
</button>
|
|
124
|
-
</div>`
|
|
125
|
-
: '';
|
|
126
|
-
|
|
127
|
-
// Progress Bar
|
|
128
|
-
const progressBarHtml = (this.config.timer && this.config.timerProgressBar)
|
|
129
|
-
? `<div class="ds-progress-bar absolute bottom-0 left-0 h-1 bg-blue-500 z-10" style="width: 100%;"></div>`
|
|
130
|
-
: '';
|
|
131
|
-
|
|
132
|
-
this.domElement.innerHTML = `
|
|
133
|
-
<div class="p-4">
|
|
134
|
-
<div class="flex items-start">
|
|
135
|
-
<div class="flex-shrink-0">${iconHtml}</div>
|
|
136
|
-
<div class="ml-3 w-0 flex-1 pt-0.5">
|
|
137
|
-
${titleHtml}
|
|
138
|
-
${contentHtml}
|
|
139
|
-
</div>
|
|
140
|
-
${closeBtnHtml}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
${progressBarHtml}
|
|
144
|
-
`;
|
|
145
|
-
|
|
146
|
-
// Append and animate
|
|
147
|
-
this.container.appendChild(this.domElement);
|
|
148
|
-
requestAnimationFrame(() => {
|
|
149
|
-
this.domElement.classList.remove('translate-x-10', 'opacity-0');
|
|
150
|
-
|
|
151
|
-
// Animate progress bar
|
|
152
|
-
if (this.config.timer && this.config.timerProgressBar) {
|
|
153
|
-
const bar = this.domElement.querySelector('.ds-progress-bar');
|
|
154
|
-
if (bar) {
|
|
155
|
-
bar.style.transition = `width ${this.config.timer}ms linear`;
|
|
156
|
-
bar.style.width = '0%';
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// Close handlers
|
|
162
|
-
const closeFn = () => this._closeToast(resolve);
|
|
163
|
-
const closeBtn = this.domElement.querySelector('.ds-close-btn');
|
|
164
|
-
if (closeBtn) {
|
|
165
|
-
closeBtn.addEventListener('click', closeFn);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (this.config.timer) {
|
|
169
|
-
setTimeout(closeFn, this.config.timer);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
_closeToast(resolve) {
|
|
174
|
-
if (!this.domElement) return;
|
|
175
|
-
|
|
176
|
-
this.domElement.classList.add('opacity-0', 'scale-95'); // Exit animation
|
|
177
|
-
setTimeout(() => {
|
|
178
|
-
if (this.domElement && this.domElement.parentElement) {
|
|
179
|
-
this.domElement.parentElement.removeChild(this.domElement);
|
|
180
|
-
}
|
|
181
|
-
// Cleanup container if empty
|
|
182
|
-
if (this.container && this.container.children.length === 0) {
|
|
183
|
-
this.container.remove();
|
|
184
|
-
}
|
|
185
|
-
}, 300);
|
|
186
|
-
|
|
187
|
-
resolve({ isDismissed: true });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
_ensureToastContainer() {
|
|
191
|
-
const posClass = this._getToastPositionClass(this.config.position);
|
|
192
|
-
let container = document.querySelector(`.ds-toast-container.${this.config.position}`);
|
|
193
|
-
|
|
194
|
-
if (!container) {
|
|
195
|
-
container = document.createElement('div');
|
|
196
|
-
container.className = `ds-toast-container ${this.config.position} fixed z-[9999] pointer-events-none p-4 gap-3 flex flex-col ${posClass}`;
|
|
197
|
-
document.body.appendChild(container);
|
|
198
|
-
}
|
|
199
|
-
return container;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
_getToastPositionClass(pos) {
|
|
203
|
-
switch (pos) {
|
|
204
|
-
case 'top-start': return 'top-0 left-0 items-start';
|
|
205
|
-
case 'top-center': return 'top-0 left-1/2 -translate-x-1/2 items-center';
|
|
206
|
-
case 'top-end': return 'top-0 right-0 items-end';
|
|
207
|
-
case 'center': return 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 items-center justify-center';
|
|
208
|
-
case 'bottom-start': return 'bottom-0 left-0 items-start flex-col-reverse';
|
|
209
|
-
case 'bottom-center': return 'bottom-0 left-1/2 -translate-x-1/2 items-center flex-col-reverse';
|
|
210
|
-
case 'bottom-end': return 'bottom-0 right-0 items-end flex-col-reverse';
|
|
211
|
-
default: return 'top-0 right-0 items-end';
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/* ================= MODAL LOGIC ================= */
|
|
216
|
-
|
|
217
|
-
_initModal(resolve) {
|
|
218
|
-
// Overlay
|
|
219
|
-
const overlay = document.createElement('div');
|
|
220
|
-
overlay.className = `ds-modal-overlay fixed inset-0 z-[10000] bg-gray-500/75 transition-opacity opacity-0`;
|
|
221
|
-
if (!this.config.backdrop) overlay.className = 'fixed inset-0 z-[10000] flex items-center justify-center pointer-events-none';
|
|
222
|
-
|
|
223
|
-
// Panel
|
|
224
|
-
// Fix: Added z-[10001] to sit on top of the overlay
|
|
225
|
-
const panel = document.createElement('div');
|
|
226
|
-
panel.className = `ds-modal-panel pointer-events-auto relative z-[10001] transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg scale-95 opacity-0`;
|
|
227
|
-
|
|
228
|
-
const iconColor = this._getIconColorName(this.config.icon);
|
|
229
|
-
const iconHtml = this.config.icon ?
|
|
230
|
-
`<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-${iconColor}-100">
|
|
231
|
-
${DSAlert.icons[this.config.icon]}
|
|
232
|
-
</div>` : '';
|
|
233
|
-
|
|
234
|
-
// Buttons
|
|
235
|
-
let buttonsHtml = '';
|
|
236
|
-
if (this.config.showCancelButton) {
|
|
237
|
-
// Note: Classes are appended. If config provided full class string, it works.
|
|
238
|
-
// If just "btn-soft", we might need default "btn".
|
|
239
|
-
// But we changed defaults to include "btn".
|
|
240
|
-
buttonsHtml += `<button type="button" class="ds-cancel-btn ${this.config.cancelButtonColor}">${this.config.cancelButtonText}</button>`;
|
|
241
|
-
}
|
|
242
|
-
if (this.config.showConfirmButton) {
|
|
243
|
-
buttonsHtml += `<button type="button" class="ds-confirm-btn ${this.config.confirmButtonColor}">${this.config.confirmButtonText}</button>`;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Close Button (Modal Top Right) - Moved to prevent overlapping by content
|
|
247
|
-
const closeBtnHtml = this.config.showCloseButton ?
|
|
248
|
-
`<button type="button" class="ds-modal-close-btn absolute top-3 right-3 z-10 text-gray-400 hover:text-gray-500 focus:outline-none transition-colors">
|
|
249
|
-
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
250
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
251
|
-
</svg>
|
|
252
|
-
</button>` : '';
|
|
253
|
-
|
|
254
|
-
// Progress Bar
|
|
255
|
-
const progressBarHtml = (this.config.timer && this.config.timerProgressBar)
|
|
256
|
-
? `<div class="ds-progress-bar absolute bottom-0 left-0 h-1 bg-blue-500 z-20" style="width: 100%;"></div>`
|
|
257
|
-
: '';
|
|
258
|
-
|
|
259
|
-
// Map alignment to class
|
|
260
|
-
const justifyClass = {
|
|
261
|
-
'start': 'justify-start',
|
|
262
|
-
'center': 'justify-center',
|
|
263
|
-
'end': 'justify-end'
|
|
264
|
-
}[this.config.buttonsAlign] || 'justify-end';
|
|
265
|
-
|
|
266
|
-
// Updated Layout: Icon + Title row, then content
|
|
267
|
-
const contentHtml = `
|
|
268
|
-
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
|
|
269
|
-
<div class="flex flex-col gap-3">
|
|
270
|
-
<div class="flex items-center gap-3">
|
|
271
|
-
${iconHtml}
|
|
272
|
-
<h3 class="text-lg font-bold text-gray-900" id="modal-title">${this.config.title}</h3>
|
|
273
|
-
</div>
|
|
274
|
-
<div class="text-sm text-gray-500 ml-1">
|
|
275
|
-
${this.config.html || this.config.text}
|
|
276
|
-
</div>
|
|
277
|
-
</div>
|
|
278
|
-
</div>
|
|
279
|
-
${closeBtnHtml}
|
|
280
|
-
<div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row gap-1 ${justifyClass} sm:px-6">
|
|
281
|
-
${buttonsHtml}
|
|
282
|
-
</div>
|
|
283
|
-
${progressBarHtml}
|
|
284
|
-
`;
|
|
285
|
-
|
|
286
|
-
panel.innerHTML = contentHtml;
|
|
287
|
-
|
|
288
|
-
// Wrapper
|
|
289
|
-
this.domElement = document.createElement('div');
|
|
290
|
-
this.domElement.className = 'fixed inset-0 z-[10000] flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0';
|
|
291
|
-
this.domElement.appendChild(overlay);
|
|
292
|
-
this.domElement.appendChild(panel);
|
|
293
|
-
document.body.appendChild(this.domElement);
|
|
294
|
-
|
|
295
|
-
// Animate In
|
|
296
|
-
requestAnimationFrame(() => {
|
|
297
|
-
overlay.classList.remove('opacity-0');
|
|
298
|
-
panel.classList.remove('scale-95', 'opacity-0');
|
|
299
|
-
|
|
300
|
-
// Animate progress bar
|
|
301
|
-
if (this.config.timer && this.config.timerProgressBar) {
|
|
302
|
-
const bar = panel.querySelector('.ds-progress-bar');
|
|
303
|
-
if (bar) {
|
|
304
|
-
bar.style.transition = `width ${this.config.timer}ms linear`;
|
|
305
|
-
bar.style.width = '0%';
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// Close Helper - Cleans up listeners
|
|
311
|
-
const close = (result) => {
|
|
312
|
-
document.removeEventListener('keydown', keydownHandler);
|
|
313
|
-
overlay.classList.add('opacity-0');
|
|
314
|
-
panel.classList.add('scale-95', 'opacity-0');
|
|
315
|
-
setTimeout(() => {
|
|
316
|
-
this.domElement.remove();
|
|
317
|
-
resolve(result);
|
|
318
|
-
}, 200);
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
// Escape Key Handler
|
|
322
|
-
const keydownHandler = (e) => {
|
|
323
|
-
if (e.key === 'Escape' && this.config.allowEscapeKey) {
|
|
324
|
-
close({ isDismissed: true });
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
document.addEventListener('keydown', keydownHandler);
|
|
328
|
-
|
|
329
|
-
// Bind Events
|
|
330
|
-
const confirmBtn = panel.querySelector('.ds-confirm-btn');
|
|
331
|
-
const cancelBtn = panel.querySelector('.ds-cancel-btn');
|
|
332
|
-
const closeBtn = panel.querySelector('.ds-modal-close-btn');
|
|
333
|
-
|
|
334
|
-
if (confirmBtn) confirmBtn.addEventListener('click', () => close({ isConfirmed: true, isDismissed: false }));
|
|
335
|
-
if (cancelBtn) cancelBtn.addEventListener('click', () => close({ isConfirmed: false, isDismissed: true }));
|
|
336
|
-
if (closeBtn) closeBtn.addEventListener('click', () => close({ isDismissed: true }));
|
|
337
|
-
|
|
338
|
-
// Backdrop Click logic
|
|
339
|
-
if (this.config.backdrop && this.config.allowOutsideClick) {
|
|
340
|
-
overlay.addEventListener('click', () => close({ isConfirmed: false, isDismissed: true }));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Timer Logic
|
|
344
|
-
if (this.config.timer) {
|
|
345
|
-
setTimeout(() => close({ isDismissed: true, timer: true }), this.config.timer);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
_getIconColorName(iconType) {
|
|
350
|
-
const map = { success: 'green', error: 'red', warning: 'yellow', info: 'blue', question: 'gray' };
|
|
351
|
-
return map[iconType] || 'gray';
|
|
352
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* DSAlert (Class-based)
|
|
3
|
+
* A lightweight, native JavaScript alert/toast system using Tailwind CSS.
|
|
4
|
+
* API designed to mimic SweetAlert2 for easy migration.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* import { DSAlert } from './DSAlert.js';
|
|
8
|
+
* DSAlert.fire({ title: 'Hello', icon: 'success' });
|
|
9
|
+
* // OR shorthand
|
|
10
|
+
* DSAlert.fire('Deleted!', 'Your file has been deleted.', 'success');
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export class DSAlert {
|
|
14
|
+
/**
|
|
15
|
+
* Default Icons - Can be overridden by modifying DSAlert.icons
|
|
16
|
+
*/
|
|
17
|
+
static icons = {
|
|
18
|
+
success: `<svg class="w-6 h-6 text-green-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>`,
|
|
19
|
+
error: `<svg class="w-6 h-6 text-red-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" /></svg>`,
|
|
20
|
+
warning: `<svg class="w-6 h-6 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.008v.008H12v-.008z" /></svg>`,
|
|
21
|
+
info: `<svg class="w-6 h-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" /></svg>`,
|
|
22
|
+
question: `<svg class="w-6 h-6 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" /></svg>`
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default defaults
|
|
27
|
+
*/
|
|
28
|
+
static defaults = {
|
|
29
|
+
title: '',
|
|
30
|
+
text: '',
|
|
31
|
+
html: '',
|
|
32
|
+
icon: '',
|
|
33
|
+
toast: false,
|
|
34
|
+
position: 'top-end',
|
|
35
|
+
timer: 0,
|
|
36
|
+
timerProgressBar: false,
|
|
37
|
+
showConfirmButton: true,
|
|
38
|
+
showCancelButton: false,
|
|
39
|
+
showCloseButton: true,
|
|
40
|
+
allowOutsideClick: true,
|
|
41
|
+
allowEscapeKey: true,
|
|
42
|
+
confirmButtonText: 'OK',
|
|
43
|
+
cancelButtonText: 'Cancel',
|
|
44
|
+
confirmButtonColor: 'btn btn-sm btn-primary',
|
|
45
|
+
cancelButtonColor: 'btn btn-sm btn-soft btn-neutral',
|
|
46
|
+
buttonsAlign: 'end', // 'start' | 'center' | 'end'
|
|
47
|
+
backdrop: true,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
constructor(...args) {
|
|
51
|
+
this.config = this._parseArgs(args);
|
|
52
|
+
this.domElement = null;
|
|
53
|
+
this.container = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Static helper to fire the alert immediately
|
|
58
|
+
* @param {...any} args - Options object OR (title, text, icon)
|
|
59
|
+
* @returns {Promise}
|
|
60
|
+
*/
|
|
61
|
+
static fire(...args) {
|
|
62
|
+
const instance = new DSAlert(...args);
|
|
63
|
+
return instance.fire();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Triggers the alert/toast
|
|
68
|
+
* @returns {Promise}
|
|
69
|
+
*/
|
|
70
|
+
fire() {
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
if (this.config.toast) {
|
|
73
|
+
this._initToast(resolve);
|
|
74
|
+
} else {
|
|
75
|
+
this._initModal(resolve);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse constructor arguments to support shorthand (Title, Text, Icon)
|
|
82
|
+
*/
|
|
83
|
+
_parseArgs(args) {
|
|
84
|
+
let userConfig = {};
|
|
85
|
+
|
|
86
|
+
// Handle: fire('Title', 'Text', 'success')
|
|
87
|
+
if (typeof args[0] === 'string') {
|
|
88
|
+
userConfig.title = args[0];
|
|
89
|
+
if (args[1]) userConfig.text = args[1];
|
|
90
|
+
if (args[2]) userConfig.icon = args[2];
|
|
91
|
+
}
|
|
92
|
+
// Handle: fire({ ...options })
|
|
93
|
+
else if (typeof args[0] === 'object' && args[0] !== null) {
|
|
94
|
+
userConfig = args[0];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { ...DSAlert.defaults, ...userConfig };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ================= TOAST LOGIC ================= */
|
|
101
|
+
|
|
102
|
+
_initToast(resolve) {
|
|
103
|
+
this.container = this._ensureToastContainer();
|
|
104
|
+
this.domElement = document.createElement('div');
|
|
105
|
+
|
|
106
|
+
// Base classes
|
|
107
|
+
// Replaced 'w-full' with 'w-80' (20rem/320px) to prevent collapse in centered containers
|
|
108
|
+
// Added 'relative' to support absolute positioning of progress bar
|
|
109
|
+
this.domElement.className = `ds-alert-toast pointer-events-auto w-80 max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black/5 transform transition-all duration-300 translate-x-10 opacity-0 relative`;
|
|
110
|
+
|
|
111
|
+
const iconHtml = DSAlert.icons[this.config.icon] || '';
|
|
112
|
+
const contentHtml = this.config.html || `<p class="mt-1 text-sm text-gray-500">${this.config.text}</p>`;
|
|
113
|
+
const titleHtml = this.config.title ? `<p class="text-sm font-medium text-gray-900">${this.config.title}</p>` : '';
|
|
114
|
+
|
|
115
|
+
// Close Button (Conditional)
|
|
116
|
+
const closeBtnHtml = this.config.showCloseButton
|
|
117
|
+
? `<div class="ml-4 flex flex-shrink-0">
|
|
118
|
+
<button type="button" class="ds-close-btn inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
|
|
119
|
+
<span class="sr-only">Close</span>
|
|
120
|
+
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
|
121
|
+
<path d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" />
|
|
122
|
+
</svg>
|
|
123
|
+
</button>
|
|
124
|
+
</div>`
|
|
125
|
+
: '';
|
|
126
|
+
|
|
127
|
+
// Progress Bar
|
|
128
|
+
const progressBarHtml = (this.config.timer && this.config.timerProgressBar)
|
|
129
|
+
? `<div class="ds-progress-bar absolute bottom-0 left-0 h-1 bg-blue-500 z-10" style="width: 100%;"></div>`
|
|
130
|
+
: '';
|
|
131
|
+
|
|
132
|
+
this.domElement.innerHTML = `
|
|
133
|
+
<div class="p-4">
|
|
134
|
+
<div class="flex items-start">
|
|
135
|
+
<div class="flex-shrink-0">${iconHtml}</div>
|
|
136
|
+
<div class="ml-3 w-0 flex-1 pt-0.5">
|
|
137
|
+
${titleHtml}
|
|
138
|
+
${contentHtml}
|
|
139
|
+
</div>
|
|
140
|
+
${closeBtnHtml}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
${progressBarHtml}
|
|
144
|
+
`;
|
|
145
|
+
|
|
146
|
+
// Append and animate
|
|
147
|
+
this.container.appendChild(this.domElement);
|
|
148
|
+
requestAnimationFrame(() => {
|
|
149
|
+
this.domElement.classList.remove('translate-x-10', 'opacity-0');
|
|
150
|
+
|
|
151
|
+
// Animate progress bar
|
|
152
|
+
if (this.config.timer && this.config.timerProgressBar) {
|
|
153
|
+
const bar = this.domElement.querySelector('.ds-progress-bar');
|
|
154
|
+
if (bar) {
|
|
155
|
+
bar.style.transition = `width ${this.config.timer}ms linear`;
|
|
156
|
+
bar.style.width = '0%';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Close handlers
|
|
162
|
+
const closeFn = () => this._closeToast(resolve);
|
|
163
|
+
const closeBtn = this.domElement.querySelector('.ds-close-btn');
|
|
164
|
+
if (closeBtn) {
|
|
165
|
+
closeBtn.addEventListener('click', closeFn);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (this.config.timer) {
|
|
169
|
+
setTimeout(closeFn, this.config.timer);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
_closeToast(resolve) {
|
|
174
|
+
if (!this.domElement) return;
|
|
175
|
+
|
|
176
|
+
this.domElement.classList.add('opacity-0', 'scale-95'); // Exit animation
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
if (this.domElement && this.domElement.parentElement) {
|
|
179
|
+
this.domElement.parentElement.removeChild(this.domElement);
|
|
180
|
+
}
|
|
181
|
+
// Cleanup container if empty
|
|
182
|
+
if (this.container && this.container.children.length === 0) {
|
|
183
|
+
this.container.remove();
|
|
184
|
+
}
|
|
185
|
+
}, 300);
|
|
186
|
+
|
|
187
|
+
resolve({ isDismissed: true });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_ensureToastContainer() {
|
|
191
|
+
const posClass = this._getToastPositionClass(this.config.position);
|
|
192
|
+
let container = document.querySelector(`.ds-toast-container.${this.config.position}`);
|
|
193
|
+
|
|
194
|
+
if (!container) {
|
|
195
|
+
container = document.createElement('div');
|
|
196
|
+
container.className = `ds-toast-container ${this.config.position} fixed z-[9999] pointer-events-none p-4 gap-3 flex flex-col ${posClass}`;
|
|
197
|
+
document.body.appendChild(container);
|
|
198
|
+
}
|
|
199
|
+
return container;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
_getToastPositionClass(pos) {
|
|
203
|
+
switch (pos) {
|
|
204
|
+
case 'top-start': return 'top-0 left-0 items-start';
|
|
205
|
+
case 'top-center': return 'top-0 left-1/2 -translate-x-1/2 items-center';
|
|
206
|
+
case 'top-end': return 'top-0 right-0 items-end';
|
|
207
|
+
case 'center': return 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 items-center justify-center';
|
|
208
|
+
case 'bottom-start': return 'bottom-0 left-0 items-start flex-col-reverse';
|
|
209
|
+
case 'bottom-center': return 'bottom-0 left-1/2 -translate-x-1/2 items-center flex-col-reverse';
|
|
210
|
+
case 'bottom-end': return 'bottom-0 right-0 items-end flex-col-reverse';
|
|
211
|
+
default: return 'top-0 right-0 items-end';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* ================= MODAL LOGIC ================= */
|
|
216
|
+
|
|
217
|
+
_initModal(resolve) {
|
|
218
|
+
// Overlay
|
|
219
|
+
const overlay = document.createElement('div');
|
|
220
|
+
overlay.className = `ds-modal-overlay fixed inset-0 z-[10000] bg-gray-500/75 transition-opacity opacity-0`;
|
|
221
|
+
if (!this.config.backdrop) overlay.className = 'fixed inset-0 z-[10000] flex items-center justify-center pointer-events-none';
|
|
222
|
+
|
|
223
|
+
// Panel
|
|
224
|
+
// Fix: Added z-[10001] to sit on top of the overlay
|
|
225
|
+
const panel = document.createElement('div');
|
|
226
|
+
panel.className = `ds-modal-panel pointer-events-auto relative z-[10001] transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg scale-95 opacity-0`;
|
|
227
|
+
|
|
228
|
+
const iconColor = this._getIconColorName(this.config.icon);
|
|
229
|
+
const iconHtml = this.config.icon ?
|
|
230
|
+
`<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-${iconColor}-100">
|
|
231
|
+
${DSAlert.icons[this.config.icon]}
|
|
232
|
+
</div>` : '';
|
|
233
|
+
|
|
234
|
+
// Buttons
|
|
235
|
+
let buttonsHtml = '';
|
|
236
|
+
if (this.config.showCancelButton) {
|
|
237
|
+
// Note: Classes are appended. If config provided full class string, it works.
|
|
238
|
+
// If just "btn-soft", we might need default "btn".
|
|
239
|
+
// But we changed defaults to include "btn".
|
|
240
|
+
buttonsHtml += `<button type="button" class="ds-cancel-btn ${this.config.cancelButtonColor}">${this.config.cancelButtonText}</button>`;
|
|
241
|
+
}
|
|
242
|
+
if (this.config.showConfirmButton) {
|
|
243
|
+
buttonsHtml += `<button type="button" class="ds-confirm-btn ${this.config.confirmButtonColor}">${this.config.confirmButtonText}</button>`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Close Button (Modal Top Right) - Moved to prevent overlapping by content
|
|
247
|
+
const closeBtnHtml = this.config.showCloseButton ?
|
|
248
|
+
`<button type="button" class="ds-modal-close-btn absolute top-3 right-3 z-10 text-gray-400 hover:text-gray-500 focus:outline-none transition-colors">
|
|
249
|
+
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
250
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
251
|
+
</svg>
|
|
252
|
+
</button>` : '';
|
|
253
|
+
|
|
254
|
+
// Progress Bar
|
|
255
|
+
const progressBarHtml = (this.config.timer && this.config.timerProgressBar)
|
|
256
|
+
? `<div class="ds-progress-bar absolute bottom-0 left-0 h-1 bg-blue-500 z-20" style="width: 100%;"></div>`
|
|
257
|
+
: '';
|
|
258
|
+
|
|
259
|
+
// Map alignment to class
|
|
260
|
+
const justifyClass = {
|
|
261
|
+
'start': 'justify-start',
|
|
262
|
+
'center': 'justify-center',
|
|
263
|
+
'end': 'justify-end'
|
|
264
|
+
}[this.config.buttonsAlign] || 'justify-end';
|
|
265
|
+
|
|
266
|
+
// Updated Layout: Icon + Title row, then content
|
|
267
|
+
const contentHtml = `
|
|
268
|
+
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4">
|
|
269
|
+
<div class="flex flex-col gap-3">
|
|
270
|
+
<div class="flex items-center gap-3">
|
|
271
|
+
${iconHtml}
|
|
272
|
+
<h3 class="text-lg font-bold text-gray-900" id="modal-title">${this.config.title}</h3>
|
|
273
|
+
</div>
|
|
274
|
+
<div class="text-sm text-gray-500 ml-1">
|
|
275
|
+
${this.config.html || this.config.text}
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
${closeBtnHtml}
|
|
280
|
+
<div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row gap-1 ${justifyClass} sm:px-6">
|
|
281
|
+
${buttonsHtml}
|
|
282
|
+
</div>
|
|
283
|
+
${progressBarHtml}
|
|
284
|
+
`;
|
|
285
|
+
|
|
286
|
+
panel.innerHTML = contentHtml;
|
|
287
|
+
|
|
288
|
+
// Wrapper
|
|
289
|
+
this.domElement = document.createElement('div');
|
|
290
|
+
this.domElement.className = 'fixed inset-0 z-[10000] flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0';
|
|
291
|
+
this.domElement.appendChild(overlay);
|
|
292
|
+
this.domElement.appendChild(panel);
|
|
293
|
+
document.body.appendChild(this.domElement);
|
|
294
|
+
|
|
295
|
+
// Animate In
|
|
296
|
+
requestAnimationFrame(() => {
|
|
297
|
+
overlay.classList.remove('opacity-0');
|
|
298
|
+
panel.classList.remove('scale-95', 'opacity-0');
|
|
299
|
+
|
|
300
|
+
// Animate progress bar
|
|
301
|
+
if (this.config.timer && this.config.timerProgressBar) {
|
|
302
|
+
const bar = panel.querySelector('.ds-progress-bar');
|
|
303
|
+
if (bar) {
|
|
304
|
+
bar.style.transition = `width ${this.config.timer}ms linear`;
|
|
305
|
+
bar.style.width = '0%';
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Close Helper - Cleans up listeners
|
|
311
|
+
const close = (result) => {
|
|
312
|
+
document.removeEventListener('keydown', keydownHandler);
|
|
313
|
+
overlay.classList.add('opacity-0');
|
|
314
|
+
panel.classList.add('scale-95', 'opacity-0');
|
|
315
|
+
setTimeout(() => {
|
|
316
|
+
this.domElement.remove();
|
|
317
|
+
resolve(result);
|
|
318
|
+
}, 200);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Escape Key Handler
|
|
322
|
+
const keydownHandler = (e) => {
|
|
323
|
+
if (e.key === 'Escape' && this.config.allowEscapeKey) {
|
|
324
|
+
close({ isDismissed: true });
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
document.addEventListener('keydown', keydownHandler);
|
|
328
|
+
|
|
329
|
+
// Bind Events
|
|
330
|
+
const confirmBtn = panel.querySelector('.ds-confirm-btn');
|
|
331
|
+
const cancelBtn = panel.querySelector('.ds-cancel-btn');
|
|
332
|
+
const closeBtn = panel.querySelector('.ds-modal-close-btn');
|
|
333
|
+
|
|
334
|
+
if (confirmBtn) confirmBtn.addEventListener('click', () => close({ isConfirmed: true, isDismissed: false }));
|
|
335
|
+
if (cancelBtn) cancelBtn.addEventListener('click', () => close({ isConfirmed: false, isDismissed: true }));
|
|
336
|
+
if (closeBtn) closeBtn.addEventListener('click', () => close({ isDismissed: true }));
|
|
337
|
+
|
|
338
|
+
// Backdrop Click logic
|
|
339
|
+
if (this.config.backdrop && this.config.allowOutsideClick) {
|
|
340
|
+
overlay.addEventListener('click', () => close({ isConfirmed: false, isDismissed: true }));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Timer Logic
|
|
344
|
+
if (this.config.timer) {
|
|
345
|
+
setTimeout(() => close({ isDismissed: true, timer: true }), this.config.timer);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
_getIconColorName(iconType) {
|
|
350
|
+
const map = { success: 'green', error: 'red', warning: 'yellow', info: 'blue', question: 'gray' };
|
|
351
|
+
return map[iconType] || 'gray';
|
|
352
|
+
}
|
|
353
353
|
}
|