@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.
Files changed (51) hide show
  1. package/LICENSE +1 -1
  2. package/package.json +1 -1
  3. package/src/CodeInput.js +48 -48
  4. package/src/DSAlert.js +352 -352
  5. package/src/DSAvatar.js +207 -207
  6. package/src/DSDelete.js +274 -274
  7. package/src/DSForm.js +568 -568
  8. package/src/DSGridOrTable.js +453 -453
  9. package/src/DSLocaleSwitcher.js +239 -239
  10. package/src/DSLogout.js +293 -293
  11. package/src/DSNotifications.js +365 -365
  12. package/src/DSRestore.js +181 -181
  13. package/src/DSSelect.js +1071 -1071
  14. package/src/DSSelectBox.js +563 -563
  15. package/src/DSSimpleSlider.js +517 -517
  16. package/src/DSSvgFetch.js +69 -69
  17. package/src/DSTable/DSTableExport.js +68 -68
  18. package/src/DSTable/DSTableFilter.js +224 -224
  19. package/src/DSTable/DSTablePagination.js +136 -136
  20. package/src/DSTable/DSTableSearch.js +40 -40
  21. package/src/DSTable/DSTableSelection.js +192 -192
  22. package/src/DSTable/DSTableSort.js +58 -58
  23. package/src/DSTable.js +353 -353
  24. package/src/DSTabs.js +488 -488
  25. package/src/DSUpload.js +887 -887
  26. package/dist/CodeInput.d.ts +0 -10
  27. package/dist/DSAlert.d.ts +0 -112
  28. package/dist/DSAvatar.d.ts +0 -45
  29. package/dist/DSDelete.d.ts +0 -61
  30. package/dist/DSForm.d.ts +0 -151
  31. package/dist/DSGridOrTable/DSGOTRenderer.d.ts +0 -60
  32. package/dist/DSGridOrTable/DSGOTViewToggle.d.ts +0 -26
  33. package/dist/DSGridOrTable.d.ts +0 -296
  34. package/dist/DSLocaleSwitcher.d.ts +0 -71
  35. package/dist/DSLogout.d.ts +0 -76
  36. package/dist/DSNotifications.d.ts +0 -54
  37. package/dist/DSRestore.d.ts +0 -56
  38. package/dist/DSSelect.d.ts +0 -221
  39. package/dist/DSSelectBox.d.ts +0 -123
  40. package/dist/DSSimpleSlider.d.ts +0 -136
  41. package/dist/DSSvgFetch.d.ts +0 -17
  42. package/dist/DSTable/DSTableExport.d.ts +0 -11
  43. package/dist/DSTable/DSTableFilter.d.ts +0 -40
  44. package/dist/DSTable/DSTablePagination.d.ts +0 -12
  45. package/dist/DSTable/DSTableSearch.d.ts +0 -8
  46. package/dist/DSTable/DSTableSelection.d.ts +0 -46
  47. package/dist/DSTable/DSTableSort.d.ts +0 -8
  48. package/dist/DSTable.d.ts +0 -116
  49. package/dist/DSTabs.d.ts +0 -156
  50. package/dist/DSUpload.d.ts +0 -220
  51. 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
  }