@tsirosgeorge/toastnotification 5.5.0 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +34 -0
- package/assets/css/toast.css +47 -1
- package/assets/css/toast.min.css +1 -1
- package/package.json +2 -2
- package/toast.d.ts +29 -0
- package/toast.js +167 -57
- package/toast.min.js +1 -1
- package/toast.module.js +167 -57
package/Readme.md
CHANGED
|
@@ -103,6 +103,37 @@ toast('Are you sure?', {
|
|
|
103
103
|
});
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
### 🔄 Wrapping an async action
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
const order = await toast.promise(saveOrder(cart), {
|
|
110
|
+
loading: 'Saving your order…',
|
|
111
|
+
success: (order) => `Order #${order.id} saved.`,
|
|
112
|
+
error: (err) => `Could not save: ${err.message}`,
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
One loading toast becomes the success or error message. It resolves with the promise's
|
|
117
|
+
value and re-throws its rejection, so the caller still handles the failure.
|
|
118
|
+
|
|
119
|
+
### ↩️ Action button
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
toast.success('Item removed from your cart.', {
|
|
123
|
+
duration: 8000,
|
|
124
|
+
showProgress: true,
|
|
125
|
+
action: { text: 'Undo', onClick: () => restoreItem() },
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### ⚙️ Site-wide defaults
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
toast.defaults = { position: 'bottom-right', showProgress: true, duration: 4000 };
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Applied to every toast unless the individual call overrides them.
|
|
136
|
+
|
|
106
137
|
### ⌨️ Keyboard and screen readers
|
|
107
138
|
|
|
108
139
|
Confirm dialogs take focus when they open, keep Tab inside themselves while they are
|
|
@@ -138,6 +169,9 @@ t.update('Done!', { type: 'success', duration: 2000 });
|
|
|
138
169
|
| `duration` | `number` | `3000` | Duration in milliseconds before the toast automatically dismisses. |
|
|
139
170
|
| `icon` | `string` or `null` | `null` | Optional custom icon displayed as text (e.g., emoji) before the toast message. If not set, a default GIF icon is used based on the `type`. |
|
|
140
171
|
| `showLoader` | `boolean` | `false` | Whether to show a loader/progress bar animation on the toast during its visible duration. |
|
|
172
|
+
| `pauseOnHover` | `boolean` | `true` | Freeze the countdown while the pointer or keyboard focus is on the toast. |
|
|
173
|
+
| `showProgress` | `boolean` | `false` | Thin bar counting the remaining time down. Needs `duration > 0`; pauses with `pauseOnHover`. |
|
|
174
|
+
| `action` | `object` or `null` | `null` | `{ text, onClick }` renders a button inside the toast, e.g. Undo. Clicking it runs `onClick` and closes the toast. |
|
|
141
175
|
| `allowHtml` | `boolean` | `true` | `message` is written as HTML. Pass `false` to render it as plain text — do that for anything user-supplied, or you have an XSS hole. |
|
|
142
176
|
| `closeOnEscape` | `boolean` | `true` | Confirm dialogs only. Escape cancels the dialog. |
|
|
143
177
|
| `onClick` | `function` or `null` | `null` | Callback function executed when the toast is clicked. |
|
package/assets/css/toast.css
CHANGED
|
@@ -351,4 +351,50 @@
|
|
|
351
351
|
/* Prevent scroll behind confirm overlay */
|
|
352
352
|
body.ts-toast-no-scroll {
|
|
353
353
|
overflow: hidden;
|
|
354
|
-
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* Progress bar counting down the remaining time */
|
|
357
|
+
@keyframes ts-toast-progress {
|
|
358
|
+
from { transform: scaleX(1); }
|
|
359
|
+
to { transform: scaleX(0); }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.ts-toast-container .ts-toast-progress {
|
|
363
|
+
position: absolute;
|
|
364
|
+
left: 0;
|
|
365
|
+
right: 0;
|
|
366
|
+
bottom: 0;
|
|
367
|
+
height: 3px;
|
|
368
|
+
transform-origin: left center;
|
|
369
|
+
border-radius: 0 0 8px 8px;
|
|
370
|
+
background: var(--toast-progress-color, currentColor);
|
|
371
|
+
opacity: 0.35;
|
|
372
|
+
pointer-events: none;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.ts-toast-container .ts-toast-success .ts-toast-progress { background: var(--toast-success-color, #17a35c); }
|
|
376
|
+
.ts-toast-container .ts-toast-error .ts-toast-progress { background: var(--toast-error-color, #ef4444); }
|
|
377
|
+
.ts-toast-container .ts-toast-warning .ts-toast-progress { background: var(--toast-warning-color, #f0a020); }
|
|
378
|
+
.ts-toast-container .ts-toast-info .ts-toast-progress { background: var(--toast-info-color, #3b82f6); }
|
|
379
|
+
|
|
380
|
+
/* Action button inside a toast (e.g. Undo) */
|
|
381
|
+
.ts-toast-container .ts-toast-action {
|
|
382
|
+
/* Toasts lay out row-reverse, so the lowest order sits at the right-hand end:
|
|
383
|
+
icon, message, then the action button. */
|
|
384
|
+
order: -1;
|
|
385
|
+
flex: none;
|
|
386
|
+
appearance: none;
|
|
387
|
+
border: 0;
|
|
388
|
+
background: transparent;
|
|
389
|
+
color: var(--toast-action-color, #3b82f6);
|
|
390
|
+
font: inherit;
|
|
391
|
+
font-weight: 600;
|
|
392
|
+
padding: 4px 8px;
|
|
393
|
+
margin-left: 4px;
|
|
394
|
+
border-radius: 6px;
|
|
395
|
+
cursor: pointer;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.ts-toast-container .ts-toast-action:hover {
|
|
399
|
+
background: var(--toast-action-hover, rgba(59, 130, 246, 0.12));
|
|
400
|
+
}
|
package/assets/css/toast.min.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.ts-toast-container{position:fixed;z-index:9999;display:flex;flex-direction:column;gap:10px;pointer-events:none;width:fit-content;max-width:calc(100% - 2rem)}.ts-toast-container>:not(:last-child){margin-bottom:0!important}.ts-toast-container.top-right{top:1rem;right:1rem;align-items:flex-end}.ts-toast-container.top-left{top:1rem;left:1rem;align-items:flex-start}.ts-toast-container.top-center{top:1rem;left:50%;transform:translateX(-50%);align-items:center}.ts-toast-container.bottom-right{bottom:1rem;right:1rem;align-items:flex-end}.ts-toast-container.bottom-left{bottom:1rem;left:1rem;align-items:flex-start}.ts-toast-container.bottom-center{bottom:1rem;left:50%;transform:translateX(-50%);align-items:center}.ts-toast-container.center{top:50%;left:50%;transform:translate(-50%,-50%);align-items:center}.ts-toast-container .ts-toast{display:flex;align-items:center;gap:.75rem;background-color:var(--toast-bg,#fff);color:var(--toast-color,#000);padding:.75rem 1rem;border-radius:8px;border:1px solid var(--toast-border,#e5e7eb);box-shadow:var(--toast-shadow,0 10px 15px -3px rgba(0,0,0,.1));font-family:sans-serif;font-size:.95rem;pointer-events:all;position:relative;opacity:0;transform:translateY(20px);transition:opacity .5s ease,transform .5s ease;width:auto!important}.ts-toast.ts-toast-confirm{display:flex;align-items:center;justify-content:center;background-color:var(--toast-bg,#fff);color:var(--toast-color,#000);border:1px solid var(--toast-border,#e5e7eb);border-radius:12px;padding:16px 20px;box-shadow:var(--toast-shadow,0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1));transition:opacity .5s ease,transform .5s ease}.ts-toast.ts-toast-confirm .ts-toast-content{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;text-align:center}.ts-toast.ts-toast-confirm .ts-toast-body{text-align:center}.ts-toast-actions{display:flex;gap:10px;justify-content:center;margin-top:12px}.ts-toast-input{width:100%;padding:8px 12px;border:1px solid var(--toast-border,#e5e7eb);border-radius:8px;font-family:inherit;font-size:.95rem;margin-top:12px;background:var(--toast-bg,#fff);color:var(--toast-color,#000);outline:0;transition:border-color .2s ease}.ts-toast-input:focus{border-color:#3b82f6}.ts-toast-container .ts-toast.ts-toast-show{opacity:1;transform:translateY(0)}@keyframes ts-toast-slide-left{from{transform:translateX(-100%)}to{transform:translateX(0)}}@keyframes ts-toast-slide-right{from{transform:translateX(100%)}to{transform:translateX(0)}}@keyframes ts-toast-slide-top{from{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes ts-toast-slide-bottom{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes ts-toast-zoom-in{from{transform:scale(0)}to{transform:scale(1)}}@keyframes ts-toast-zoom-out{from{transform:scale(1)}to{transform:scale(0)}}@keyframes ts-toast-flip{from{transform:rotateY(90deg)}to{transform:rotateY(0)}}.ts-toast-container .ts-toast-icon{font-size:1.2rem}.ts-toast-container .ts-toast-success .ts-toast-icon{color:var(--toast-success-color,#66ee78)}.ts-toast-container .ts-toast-error .ts-toast-icon{color:var(--toast-error-color,#ef4444)}.ts-toast-container .ts-toast-loader{width:20px;height:20px;border:3px solid #f3f3f3;border-top:3px solid var(--toast-loader-color,#66ee78);border-radius:50%;animation:ts-toast-spin 1s linear infinite}@keyframes ts-toast-spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.ts-toast-container .ts-toast-loader.done{animation:none;opacity:0;transition:opacity .5s ease}.ts-toast-container .ts-toast-icon{font-size:1.2rem;opacity:0;transition:opacity .5s ease}.ts-toast-container .ts-toast.ts-toast-show .ts-toast-icon{opacity:1}@keyframes progress-bar{0%{width:0%}100%{width:100%}}@keyframes ts-toast-slide-top-reverse{from{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes ts-toast-slide-bottom-reverse{from{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes ts-toast-slide-left-reverse{from{transform:translateX(0)}to{transform:translateX(-100%)}}@keyframes ts-toast-slide-right-reverse{from{transform:translateX(0)}to{transform:translateX(100%)}}.ts-toast.ts-toast-slide-out{opacity:0}.ts-toast-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:2147483646}.ts-toast-overlay.top-left{align-items:flex-start;justify-content:flex-start;padding:1rem}.ts-toast-overlay.top-center{align-items:flex-start;justify-content:center;padding:1rem}.ts-toast-overlay.top-right{align-items:flex-start;justify-content:flex-end;padding:1rem}.ts-toast-overlay.bottom-left{align-items:flex-end;justify-content:flex-start;padding:1rem}.ts-toast-overlay.bottom-center{align-items:flex-end;justify-content:center;padding:1rem}.ts-toast-overlay.bottom-right{align-items:flex-end;justify-content:flex-end;padding:1rem}.ts-toast-overlay.center{align-items:center;justify-content:center;padding:1rem}.ts-toast-d-flex{display:inline-flex;align-items:center}body.ts-toast-no-scroll{overflow:hidden}
|
|
1
|
+
.ts-toast-container{position:fixed;z-index:9999;display:flex;flex-direction:column;gap:10px;pointer-events:none;width:fit-content;max-width:calc(100% - 2rem)}.ts-toast-container>:not(:last-child){margin-bottom:0!important}.ts-toast-container.top-right{top:1rem;right:1rem;align-items:flex-end}.ts-toast-container.top-left{top:1rem;left:1rem;align-items:flex-start}.ts-toast-container.top-center{top:1rem;left:50%;transform:translateX(-50%);align-items:center}.ts-toast-container.bottom-right{bottom:1rem;right:1rem;align-items:flex-end}.ts-toast-container.bottom-left{bottom:1rem;left:1rem;align-items:flex-start}.ts-toast-container.bottom-center{bottom:1rem;left:50%;transform:translateX(-50%);align-items:center}.ts-toast-container.center{top:50%;left:50%;transform:translate(-50%,-50%);align-items:center}.ts-toast-container .ts-toast{display:flex;align-items:center;gap:.75rem;background-color:var(--toast-bg,#fff);color:var(--toast-color,#000);padding:.75rem 1rem;border-radius:8px;border:1px solid var(--toast-border,#e5e7eb);box-shadow:var(--toast-shadow,0 10px 15px -3px rgba(0,0,0,.1));font-family:sans-serif;font-size:.95rem;pointer-events:all;position:relative;opacity:0;transform:translateY(20px);transition:opacity .5s ease,transform .5s ease;width:auto!important}.ts-toast.ts-toast-confirm{display:flex;align-items:center;justify-content:center;background-color:var(--toast-bg,#fff);color:var(--toast-color,#000);border:1px solid var(--toast-border,#e5e7eb);border-radius:12px;padding:16px 20px;box-shadow:var(--toast-shadow,0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1));transition:opacity .5s ease,transform .5s ease}.ts-toast.ts-toast-confirm .ts-toast-content{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;text-align:center}.ts-toast.ts-toast-confirm .ts-toast-body{text-align:center}.ts-toast-actions{display:flex;gap:10px;justify-content:center;margin-top:12px}.ts-toast-input{width:100%;padding:8px 12px;border:1px solid var(--toast-border,#e5e7eb);border-radius:8px;font-family:inherit;font-size:.95rem;margin-top:12px;background:var(--toast-bg,#fff);color:var(--toast-color,#000);outline:0;transition:border-color .2s ease}.ts-toast-input:focus{border-color:#3b82f6}.ts-toast-container .ts-toast.ts-toast-show{opacity:1;transform:translateY(0)}@keyframes ts-toast-slide-left{from{transform:translateX(-100%)}to{transform:translateX(0)}}@keyframes ts-toast-slide-right{from{transform:translateX(100%)}to{transform:translateX(0)}}@keyframes ts-toast-slide-top{from{transform:translateY(-100%)}to{transform:translateY(0)}}@keyframes ts-toast-slide-bottom{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes ts-toast-zoom-in{from{transform:scale(0)}to{transform:scale(1)}}@keyframes ts-toast-zoom-out{from{transform:scale(1)}to{transform:scale(0)}}@keyframes ts-toast-flip{from{transform:rotateY(90deg)}to{transform:rotateY(0)}}.ts-toast-container .ts-toast-icon{font-size:1.2rem}.ts-toast-container .ts-toast-success .ts-toast-icon{color:var(--toast-success-color,#66ee78)}.ts-toast-container .ts-toast-error .ts-toast-icon{color:var(--toast-error-color,#ef4444)}.ts-toast-container .ts-toast-loader{width:20px;height:20px;border:3px solid #f3f3f3;border-top:3px solid var(--toast-loader-color,#66ee78);border-radius:50%;animation:ts-toast-spin 1s linear infinite}@keyframes ts-toast-spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.ts-toast-container .ts-toast-loader.done{animation:none;opacity:0;transition:opacity .5s ease}.ts-toast-container .ts-toast-icon{font-size:1.2rem;opacity:0;transition:opacity .5s ease}.ts-toast-container .ts-toast.ts-toast-show .ts-toast-icon{opacity:1}@keyframes progress-bar{0%{width:0%}100%{width:100%}}@keyframes ts-toast-slide-top-reverse{from{transform:translateY(0)}to{transform:translateY(-100%)}}@keyframes ts-toast-slide-bottom-reverse{from{transform:translateY(0)}to{transform:translateY(100%)}}@keyframes ts-toast-slide-left-reverse{from{transform:translateX(0)}to{transform:translateX(-100%)}}@keyframes ts-toast-slide-right-reverse{from{transform:translateX(0)}to{transform:translateX(100%)}}.ts-toast.ts-toast-slide-out{opacity:0}.ts-toast-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:2147483646}.ts-toast-overlay.top-left{align-items:flex-start;justify-content:flex-start;padding:1rem}.ts-toast-overlay.top-center{align-items:flex-start;justify-content:center;padding:1rem}.ts-toast-overlay.top-right{align-items:flex-start;justify-content:flex-end;padding:1rem}.ts-toast-overlay.bottom-left{align-items:flex-end;justify-content:flex-start;padding:1rem}.ts-toast-overlay.bottom-center{align-items:flex-end;justify-content:center;padding:1rem}.ts-toast-overlay.bottom-right{align-items:flex-end;justify-content:flex-end;padding:1rem}.ts-toast-overlay.center{align-items:center;justify-content:center;padding:1rem}.ts-toast-d-flex{display:inline-flex;align-items:center}body.ts-toast-no-scroll{overflow:hidden}@keyframes ts-toast-progress{from{transform:scaleX(1)}to{transform:scaleX(0)}}.ts-toast-container .ts-toast-progress{position:absolute;left:0;right:0;bottom:0;height:3px;transform-origin:left center;border-radius:0 0 8px 8px;background:var(--toast-progress-color,currentColor);opacity:.35;pointer-events:none}.ts-toast-container .ts-toast-success .ts-toast-progress{background:var(--toast-success-color,#17a35c)}.ts-toast-container .ts-toast-error .ts-toast-progress{background:var(--toast-error-color,#ef4444)}.ts-toast-container .ts-toast-warning .ts-toast-progress{background:var(--toast-warning-color,#f0a020)}.ts-toast-container .ts-toast-info .ts-toast-progress{background:var(--toast-info-color,#3b82f6)}.ts-toast-container .ts-toast-action{order:-1;flex:none;appearance:none;border:0;background:0 0;color:var(--toast-action-color,#3b82f6);font:inherit;font-weight:600;padding:4px 8px;margin-left:4px;border-radius:6px;cursor:pointer}.ts-toast-container .ts-toast-action:hover{background:var(--toast-action-hover,rgba(59,130,246,.12))}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsirosgeorge/toastnotification",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.0",
|
|
4
4
|
"description": "a toast notification plugin",
|
|
5
5
|
"main": "toast.min.js",
|
|
6
6
|
"module": "toast.module.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"build:css": "cleancss -o assets/css/toast.min.css assets/css/toast.css",
|
|
22
22
|
"build:js": "terser toast.js --compress --mangle --output toast.min.js",
|
|
23
23
|
"build": "npm run sync:version && npm run build:module && npm run build:css && npm run build:js",
|
|
24
|
-
"version": "npm run build && git add -A",
|
|
24
|
+
"version": "node scripts/stamp-changelog.mjs && npm run build && git add -A",
|
|
25
25
|
"prepublishOnly": "npm run build",
|
|
26
26
|
"release": "npm version ${V:-patch} && git push origin main --follow-tags"
|
|
27
27
|
},
|
package/toast.d.ts
CHANGED
|
@@ -31,6 +31,12 @@ export interface ToastOptions {
|
|
|
31
31
|
/** Emoji or text used instead of the bundled animated icon. */
|
|
32
32
|
icon?: string | null;
|
|
33
33
|
showLoader?: boolean;
|
|
34
|
+
/** Freeze the countdown while the pointer or keyboard focus is on the toast. */
|
|
35
|
+
pauseOnHover?: boolean;
|
|
36
|
+
/** Thin bar counting the remaining time down. Needs `duration > 0`. */
|
|
37
|
+
showProgress?: boolean;
|
|
38
|
+
/** Renders a button inside the toast, e.g. Undo. */
|
|
39
|
+
action?: ToastAction | null;
|
|
34
40
|
/**
|
|
35
41
|
* `message` is written as HTML by default, for backwards compatibility.
|
|
36
42
|
* Pass `false` to render it as plain text — do that for anything user-supplied.
|
|
@@ -66,6 +72,21 @@ export interface ConfirmOptions extends ToastOptions {
|
|
|
66
72
|
onResult?: (result: ConfirmResult, el: ToastElement) => void;
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
export interface ToastAction {
|
|
76
|
+
/** Button label. */
|
|
77
|
+
text: string;
|
|
78
|
+
/** Runs on click; the toast closes afterwards. */
|
|
79
|
+
onClick?: (el: ToastElement) => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface PromiseMessages<T = unknown> {
|
|
83
|
+
loading?: string;
|
|
84
|
+
/** A function receives the resolved value. */
|
|
85
|
+
success?: string | ((value: T) => string);
|
|
86
|
+
/** A function receives the rejection reason. */
|
|
87
|
+
error?: string | ((err: unknown) => string);
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
export interface LoadingHandle {
|
|
70
91
|
update(message: string, options?: ToastOptions): void;
|
|
71
92
|
close(): void;
|
|
@@ -87,6 +108,14 @@ export interface Toast {
|
|
|
87
108
|
confirm(message: string, options?: ConfirmOptions): Promise<ConfirmResult>;
|
|
88
109
|
/** Dismiss every toast on screen; open confirm dialogs settle as a cancel. */
|
|
89
110
|
dismissAll(): void;
|
|
111
|
+
/**
|
|
112
|
+
* Shows a loading toast that becomes the success or error message when
|
|
113
|
+
* `promise` settles. Resolves with the promise's value, and re-throws its
|
|
114
|
+
* rejection so the caller still handles the failure.
|
|
115
|
+
*/
|
|
116
|
+
promise<T>(promise: Promise<T>, messages?: PromiseMessages<T>, options?: ToastOptions): Promise<T>;
|
|
117
|
+
/** Options applied to every toast unless the call overrides them. */
|
|
118
|
+
defaults: ToastOptions;
|
|
90
119
|
}
|
|
91
120
|
|
|
92
121
|
declare const toast: Toast;
|
package/toast.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// Single source of truth for the CDN this build points at.
|
|
4
4
|
// `npm run sync:version` rewrites it from package.json, so it can never go stale.
|
|
5
|
-
const TS_TOAST_VERSION = "5.
|
|
5
|
+
const TS_TOAST_VERSION = "5.6.0";
|
|
6
6
|
// Point this at your own copy of assets/ to self-host the CSS and icons
|
|
7
7
|
// (useful offline, behind a strict CSP, or when you don't want a CDN dependency):
|
|
8
8
|
// window.TS_TOAST_ASSET_BASE = '/vendor/toastnotification';
|
|
@@ -14,6 +14,7 @@ const TS_TOAST_CDN = (typeof window !== 'undefined' && window.TS_TOAST_ASSET_BAS
|
|
|
14
14
|
// group, so only the last dialog to close may release it.
|
|
15
15
|
let tsToastOpenModals = 0;
|
|
16
16
|
let tsToastPrevOverflow = '';
|
|
17
|
+
let tsToastPrevPaddingRight = '';
|
|
17
18
|
|
|
18
19
|
const tsToastReducedMotion = () =>
|
|
19
20
|
typeof window !== 'undefined' &&
|
|
@@ -52,6 +53,10 @@ let tsToastIdCounter = 0;
|
|
|
52
53
|
.ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }
|
|
53
54
|
.ts-toast-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); align-items: center; }
|
|
54
55
|
.ts-toast-overlay.center { align-items: center; justify-content: center; }
|
|
56
|
+
@keyframes ts-toast-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } }
|
|
57
|
+
.ts-toast .ts-toast-progress { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; transform-origin: left center; border-radius: 0 0 8px 8px; background: currentColor; opacity: 0.35; pointer-events: none; }
|
|
58
|
+
.ts-toast .ts-toast-action { order: -1; flex: none; appearance: none; border: 0; background: transparent; color: #3b82f6; font: inherit; font-weight: 600; padding: 4px 8px; margin-left: 4px; border-radius: 6px; cursor: pointer; }
|
|
59
|
+
.ts-toast .ts-toast-action:hover { background: rgba(59,130,246,0.12); }
|
|
55
60
|
.ts-toast-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2147483646; }
|
|
56
61
|
.ts-toast.ts-toast-confirm { max-width: min(92vw, 440px); width: max(320px, 60%); flex-direction: column; gap: 12px; padding: 16px 20px; background: var(--toast-bg, #fff); color: var(--toast-color, #000); border: 1px solid var(--toast-border, #e5e7eb); border-radius: 12px; box-shadow: var(--toast-shadow, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)); text-align: center; }
|
|
57
62
|
.ts-toast.ts-toast-confirm .ts-toast-content { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }
|
|
@@ -75,6 +80,9 @@ let tsToastIdCounter = 0;
|
|
|
75
80
|
})();
|
|
76
81
|
|
|
77
82
|
const toast = function (message, options = {}) {
|
|
83
|
+
// Site-wide defaults, so a page can turn on things like showProgress once
|
|
84
|
+
// instead of repeating them at every call site.
|
|
85
|
+
options = { ...(toast.defaults || {}), ...options };
|
|
78
86
|
const {
|
|
79
87
|
position = 'top-right',
|
|
80
88
|
animation = 'slide-right', // Default fallback animation
|
|
@@ -103,6 +111,12 @@ const toast = function (message, options = {}) {
|
|
|
103
111
|
useOverlay = true,
|
|
104
112
|
closeOnOverlayClick = true,
|
|
105
113
|
showClose = false,
|
|
114
|
+
// Freeze the countdown while the pointer or keyboard focus is on the toast
|
|
115
|
+
pauseOnHover = true,
|
|
116
|
+
// Thin bar counting the remaining time down
|
|
117
|
+
showProgress = false,
|
|
118
|
+
// { text, onClick } renders a button inside the toast, e.g. Undo
|
|
119
|
+
action = null,
|
|
106
120
|
// Escape cancels a confirm dialog
|
|
107
121
|
closeOnEscape = true,
|
|
108
122
|
// `message` is written as HTML for backwards compatibility. Pass false to
|
|
@@ -329,6 +343,31 @@ const toast = function (message, options = {}) {
|
|
|
329
343
|
confirmBtn.addEventListener('click', (e) => { e.stopPropagation(); resolveAndClose(true); });
|
|
330
344
|
}
|
|
331
345
|
|
|
346
|
+
// Action button (e.g. Undo). Clicking it runs the callback and closes the toast.
|
|
347
|
+
if (!isConfirm && action && typeof action === 'object' && action.text) {
|
|
348
|
+
const actionBtn = document.createElement('button');
|
|
349
|
+
actionBtn.className = 'ts-toast-action';
|
|
350
|
+
actionBtn.type = 'button';
|
|
351
|
+
actionBtn.textContent = action.text;
|
|
352
|
+
actionBtn.addEventListener('click', (e) => {
|
|
353
|
+
e.stopPropagation(); // do not also trigger dismissOnClick
|
|
354
|
+
if (typeof action.onClick === 'function') action.onClick(toastElement);
|
|
355
|
+
toastElement._dismiss();
|
|
356
|
+
});
|
|
357
|
+
toastElement.appendChild(actionBtn);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Progress bar. The width is driven by a CSS animation whose duration is the
|
|
361
|
+
// toast's own, so pausing it is one property and never drifts from the timer.
|
|
362
|
+
let progressBar = null;
|
|
363
|
+
if (!isConfirm && showProgress && duration > 0 && !reducedMotion) {
|
|
364
|
+
progressBar = document.createElement('div');
|
|
365
|
+
progressBar.className = 'ts-toast-progress';
|
|
366
|
+
progressBar.style.animation = `ts-toast-progress ${duration}ms linear forwards`;
|
|
367
|
+
progressBar.style.animationPlayState = 'paused';
|
|
368
|
+
toastElement.appendChild(progressBar);
|
|
369
|
+
}
|
|
370
|
+
|
|
332
371
|
// Loader Element
|
|
333
372
|
let loader = null;
|
|
334
373
|
if (showLoader) {
|
|
@@ -339,6 +378,7 @@ const toast = function (message, options = {}) {
|
|
|
339
378
|
|
|
340
379
|
// Container/Overlay
|
|
341
380
|
let overlay = null;
|
|
381
|
+
let containerEl = null;
|
|
342
382
|
if (isConfirm && useOverlay) {
|
|
343
383
|
overlay = document.createElement('div');
|
|
344
384
|
// A modal centres by default. The `position` default of 'top-right' is meant
|
|
@@ -387,6 +427,7 @@ const toast = function (message, options = {}) {
|
|
|
387
427
|
container.setAttribute('aria-relevant', 'additions');
|
|
388
428
|
}
|
|
389
429
|
container.appendChild(toastElement);
|
|
430
|
+
containerEl = container;
|
|
390
431
|
}
|
|
391
432
|
|
|
392
433
|
if (isConfirm) {
|
|
@@ -399,6 +440,15 @@ const toast = function (message, options = {}) {
|
|
|
399
440
|
tsToastOpenModals += 1;
|
|
400
441
|
if (tsToastOpenModals === 1) {
|
|
401
442
|
tsToastPrevOverflow = document.body.style.overflow;
|
|
443
|
+
tsToastPrevPaddingRight = document.body.style.paddingRight;
|
|
444
|
+
// Hiding the scrollbar makes the page wider, which shifts the whole
|
|
445
|
+
// layout sideways as the dialog opens. Pad by the scrollbar's width to
|
|
446
|
+
// hold it still. A no-op where scrollbars are overlays, as on macOS.
|
|
447
|
+
const scrollbar = window.innerWidth - document.documentElement.clientWidth;
|
|
448
|
+
if (scrollbar > 0) {
|
|
449
|
+
const current = parseFloat(window.getComputedStyle(document.body).paddingRight) || 0;
|
|
450
|
+
document.body.style.paddingRight = `${current + scrollbar}px`;
|
|
451
|
+
}
|
|
402
452
|
document.body.style.overflow = 'hidden';
|
|
403
453
|
}
|
|
404
454
|
|
|
@@ -441,7 +491,10 @@ const toast = function (message, options = {}) {
|
|
|
441
491
|
releaseModal = () => {
|
|
442
492
|
document.removeEventListener('keydown', onKeydown, true);
|
|
443
493
|
tsToastOpenModals = Math.max(0, tsToastOpenModals - 1);
|
|
444
|
-
if (tsToastOpenModals === 0)
|
|
494
|
+
if (tsToastOpenModals === 0) {
|
|
495
|
+
document.body.style.overflow = tsToastPrevOverflow;
|
|
496
|
+
document.body.style.paddingRight = tsToastPrevPaddingRight;
|
|
497
|
+
}
|
|
445
498
|
// Hand the keyboard back to whatever opened the dialog.
|
|
446
499
|
if (previouslyFocused && typeof previouslyFocused.focus === 'function' &&
|
|
447
500
|
document.contains(previouslyFocused)) {
|
|
@@ -463,6 +516,10 @@ const toast = function (message, options = {}) {
|
|
|
463
516
|
toastElement.classList.add('ts-toast-show');
|
|
464
517
|
}, 100);
|
|
465
518
|
|
|
519
|
+
// The loader always ran for 2s, so with a shorter duration the toast was gone
|
|
520
|
+
// before the icon it reveals ever appeared.
|
|
521
|
+
const loaderMs = duration > 0 ? Math.min(2000, Math.max(0, duration - 500)) : 2000;
|
|
522
|
+
|
|
466
523
|
// Handle Loader and Icon
|
|
467
524
|
if (showLoader && loader) {
|
|
468
525
|
setTimeout(() => {
|
|
@@ -474,7 +531,7 @@ const toast = function (message, options = {}) {
|
|
|
474
531
|
if (isConfirm && contentRow) contentRow.appendChild(iconElement);
|
|
475
532
|
else toastElement.appendChild(iconElement); // Add icon only if not present
|
|
476
533
|
}
|
|
477
|
-
},
|
|
534
|
+
}, loaderMs);
|
|
478
535
|
}
|
|
479
536
|
if (!showLoader) {
|
|
480
537
|
// For confirm, icon already added above inside contentRow; avoid moving it
|
|
@@ -483,26 +540,70 @@ const toast = function (message, options = {}) {
|
|
|
483
540
|
}
|
|
484
541
|
}
|
|
485
542
|
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
543
|
+
// Dismissal state lives on the element so toast.update() can rebind the callback
|
|
544
|
+
// and reuse this exact removal path instead of keeping its own copy of it.
|
|
545
|
+
toastElement._onDismiss = typeof onDismiss === 'function' ? onDismiss : null;
|
|
546
|
+
|
|
547
|
+
// A plain setTimeout cannot be paused, so the countdown is tracked by hand:
|
|
548
|
+
// `remaining` is what is left, and hovering banks it.
|
|
549
|
+
let remaining = duration;
|
|
550
|
+
let timerId = null;
|
|
551
|
+
let startedAt = 0;
|
|
552
|
+
|
|
553
|
+
const stopTimer = () => {
|
|
554
|
+
if (timerId) { clearTimeout(timerId); timerId = null; }
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const startTimer = () => {
|
|
558
|
+
if (isConfirm || remaining <= 0 || timerId) return;
|
|
559
|
+
startedAt = Date.now();
|
|
560
|
+
timerId = setTimeout(() => { timerId = null; toastElement._dismiss(); }, remaining);
|
|
561
|
+
if (progressBar) progressBar.style.animationPlayState = 'running';
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const pauseTimer = () => {
|
|
565
|
+
if (!timerId) return;
|
|
566
|
+
clearTimeout(timerId);
|
|
567
|
+
timerId = null;
|
|
568
|
+
remaining -= Date.now() - startedAt;
|
|
569
|
+
if (progressBar) progressBar.style.animationPlayState = 'paused';
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
toastElement._dismiss = () => {
|
|
573
|
+
if (toastElement._removing) return; // never run the exit twice
|
|
574
|
+
toastElement._removing = true;
|
|
575
|
+
stopTimer();
|
|
576
|
+
removeWithAnimation(toastElement, () => {
|
|
577
|
+
if (toastElement._onDismiss) toastElement._onDismiss(toastElement);
|
|
578
|
+
// Containers used to pile up in the DOM, one per position, forever.
|
|
579
|
+
if (containerEl && !containerEl.children.length) containerEl.remove();
|
|
580
|
+
});
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// Lets toast.update() re-arm the countdown without reaching into internals.
|
|
584
|
+
toastElement._setDuration = (ms) => {
|
|
585
|
+
stopTimer();
|
|
586
|
+
remaining = ms;
|
|
587
|
+
startTimer();
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
startTimer();
|
|
591
|
+
|
|
592
|
+
if (!isConfirm && pauseOnHover) {
|
|
593
|
+
// Give the reader a chance to finish the sentence.
|
|
594
|
+
toastElement.addEventListener('mouseenter', pauseTimer);
|
|
595
|
+
toastElement.addEventListener('mouseleave', startTimer);
|
|
596
|
+
toastElement.addEventListener('focusin', pauseTimer);
|
|
597
|
+
toastElement.addEventListener('focusout', startTimer);
|
|
494
598
|
}
|
|
495
599
|
|
|
496
600
|
// Add event listener for closing the toast when clicked (disabled in confirm mode)
|
|
497
601
|
if (!isConfirm && dismissOnClick) {
|
|
498
602
|
toastElement.addEventListener('click', () => {
|
|
499
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove); // Clear the auto-remove timeout
|
|
500
603
|
// onClick belongs to the click, not to the end of the exit animation,
|
|
501
604
|
// which is where it used to fire half a second late.
|
|
502
605
|
if (onClick && typeof onClick === 'function') onClick(toastElement);
|
|
503
|
-
|
|
504
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
505
|
-
});
|
|
606
|
+
toastElement._dismiss();
|
|
506
607
|
});
|
|
507
608
|
}
|
|
508
609
|
|
|
@@ -525,23 +626,15 @@ const toast = function (message, options = {}) {
|
|
|
525
626
|
const dy = Math.abs(touchStartY - e.changedTouches[0].screenY);
|
|
526
627
|
// Only a mostly-horizontal swipe dismisses, so scrolling the page past a
|
|
527
628
|
// toast no longer throws it away on a bit of sideways drift.
|
|
528
|
-
if (dx > 50 && dx > dy)
|
|
529
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove);
|
|
530
|
-
removeWithAnimation(toastElement, () => {
|
|
531
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
532
|
-
});
|
|
533
|
-
}
|
|
629
|
+
if (dx > 50 && dx > dy) toastElement._dismiss();
|
|
534
630
|
});
|
|
535
631
|
}
|
|
536
632
|
|
|
537
633
|
// Let callers dismiss a toast they are holding, instead of only waiting out
|
|
538
634
|
// the duration or making the user click it.
|
|
539
635
|
toastElement.close = () => {
|
|
540
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove);
|
|
541
636
|
if (isConfirm) { resolveAndClose(false); return; }
|
|
542
|
-
|
|
543
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
544
|
-
});
|
|
637
|
+
toastElement._dismiss();
|
|
545
638
|
};
|
|
546
639
|
|
|
547
640
|
return toastElement;
|
|
@@ -570,10 +663,9 @@ const toast = function (message, options = {}) {
|
|
|
570
663
|
type = null,
|
|
571
664
|
icon = null,
|
|
572
665
|
showLoader = false,
|
|
573
|
-
duration = 3000, // Default duration (in ms)
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
onDismiss = null // Custom onDismiss event listener
|
|
666
|
+
duration = 3000, // Default duration (in ms); 0 keeps the toast on screen
|
|
667
|
+
allowHtml = true,
|
|
668
|
+
onDismiss = null // Replaces the callback the toast was created with
|
|
577
669
|
} = options;
|
|
578
670
|
|
|
579
671
|
// Remove old loader (if any)
|
|
@@ -591,7 +683,8 @@ const toast = function (message, options = {}) {
|
|
|
591
683
|
}
|
|
592
684
|
const toastBody = toastElement.querySelector('.ts-toast-body');
|
|
593
685
|
if (toastBody) {
|
|
594
|
-
toastBody.innerHTML = message;
|
|
686
|
+
if (allowHtml) toastBody.innerHTML = message;
|
|
687
|
+
else toastBody.textContent = message;
|
|
595
688
|
}
|
|
596
689
|
|
|
597
690
|
// Handle Icon update only if it's new or hasn't been set yet
|
|
@@ -619,9 +712,12 @@ const toast = function (message, options = {}) {
|
|
|
619
712
|
iconElement.appendChild(img);
|
|
620
713
|
}
|
|
621
714
|
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
(
|
|
715
|
+
// Only attach an icon we actually have. Without a type and without an explicit
|
|
716
|
+
// icon this used to append an empty <span><img></span>.
|
|
717
|
+
if (icon || iconElement.querySelector('img[src]')) {
|
|
718
|
+
const contentRow = toastElement.querySelector('.ts-toast-content');
|
|
719
|
+
(contentRow || toastElement).appendChild(iconElement);
|
|
720
|
+
}
|
|
625
721
|
|
|
626
722
|
// Handle loader if requested
|
|
627
723
|
if (showLoader) {
|
|
@@ -630,33 +726,19 @@ const toast = function (message, options = {}) {
|
|
|
630
726
|
toastElement.appendChild(loader);
|
|
631
727
|
setTimeout(() => {
|
|
632
728
|
loader.classList.add('done');
|
|
633
|
-
}, 2000
|
|
729
|
+
}, duration > 0 ? Math.min(2000, Math.max(0, duration - 500)) : 2000);
|
|
634
730
|
}
|
|
635
731
|
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
732
|
+
// Rebind the dismiss callback rather than leaving the creation-time one in place,
|
|
733
|
+
// which meant an updated toast fired two different onDismiss handlers.
|
|
734
|
+
if (typeof onDismiss === 'function') toastElement._onDismiss = onDismiss;
|
|
640
735
|
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
setTimeout(() => {
|
|
648
|
-
el.classList.remove('ts-toast-slide-out');
|
|
649
|
-
if (el.parentNode) el.parentNode.removeChild(el);
|
|
650
|
-
if (typeof cb === 'function') cb();
|
|
651
|
-
}, 500);
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
removeWithAnimation(toastElement, () => {
|
|
655
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
656
|
-
});
|
|
657
|
-
}, duration);
|
|
658
|
-
|
|
659
|
-
toastElement._autoRemove = autoRemove; // Re-set the auto-remove timer
|
|
736
|
+
// Re-arm the countdown through the toast's own timer, so duration: 0 keeps the
|
|
737
|
+
// toast on screen. This used to schedule setTimeout(..., 0) and remove it at once,
|
|
738
|
+
// which broke every `toast.loading(...).update(msg, { duration: 0 })`.
|
|
739
|
+
if (typeof toastElement._setDuration === 'function') {
|
|
740
|
+
toastElement._setDuration(duration);
|
|
741
|
+
}
|
|
660
742
|
};
|
|
661
743
|
|
|
662
744
|
toast.loading = function (message, options = {}) {
|
|
@@ -730,6 +812,34 @@ const toast = function (message, options = {}) {
|
|
|
730
812
|
});
|
|
731
813
|
};
|
|
732
814
|
|
|
815
|
+
// Wraps an async action: one loading toast that becomes the success or error
|
|
816
|
+
// message, instead of hand-rolling loading/update/catch at every call site.
|
|
817
|
+
toast.promise = function (promise, messages = {}, options = {}) {
|
|
818
|
+
const {
|
|
819
|
+
loading = 'Loading…',
|
|
820
|
+
success = 'Done',
|
|
821
|
+
error = 'Something went wrong'
|
|
822
|
+
} = messages;
|
|
823
|
+
|
|
824
|
+
const handle = toast.loading(loading, options);
|
|
825
|
+
// Messages may be functions so they can name what actually came back.
|
|
826
|
+
const text = (msg, value) => (typeof msg === 'function' ? msg(value) : msg);
|
|
827
|
+
|
|
828
|
+
return Promise.resolve(promise).then(
|
|
829
|
+
(value) => {
|
|
830
|
+
handle.update(text(success, value), { type: 'success', duration: options.duration });
|
|
831
|
+
return value;
|
|
832
|
+
},
|
|
833
|
+
(err) => {
|
|
834
|
+
handle.update(text(error, err), { type: 'error', duration: options.duration });
|
|
835
|
+
throw err; // the caller still owns the failure
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
// Options applied to every toast unless the call overrides them.
|
|
841
|
+
toast.defaults = {};
|
|
842
|
+
|
|
733
843
|
// Close every toast currently on screen. Confirm dialogs settle as a cancel.
|
|
734
844
|
toast.dismissAll = function () {
|
|
735
845
|
document.querySelectorAll('.ts-toast').forEach((el) => {
|
package/toast.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const TS_TOAST_VERSION="5.5.0",TS_TOAST_CDN="undefined"!=typeof window&&window.TS_TOAST_ASSET_BASE?String(window.TS_TOAST_ASSET_BASE).replace(/\/+$/,""):"https://cdn.jsdelivr.net/npm/@tsirosgeorge/toastnotification@5.5.0";let tsToastOpenModals=0,tsToastPrevOverflow="";const tsToastReducedMotion=()=>"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,tsToastFocusable=t=>Array.from(t.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')).filter((t=>!t.disabled&&null!==t.offsetParent));let tsToastIdCounter=0;!function(){const t="ts-toast-stylesheet";if("undefined"!=typeof window&&window.TS_TOAST_NO_CSS)return;if(document.getElementById(t))return;const e=document.createElement("link");e.id=t,e.rel="stylesheet",e.href=`${TS_TOAST_CDN}/assets/css/toast.min.css`,document.head.appendChild(e)}(),function(){const t="ts-toast-inline-extras";if(document.getElementById(t))return;const e=document.createElement("style");e.id=t,e.textContent="\n /* Ensure center positions exist even if external CSS lacks them */\n .ts-toast-container.top-center { top: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }\n .ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }\n .ts-toast-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); align-items: center; }\n .ts-toast-overlay.center { align-items: center; justify-content: center; }\n .ts-toast-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2147483646; }\n .ts-toast.ts-toast-confirm { max-width: min(92vw, 440px); width: max(320px, 60%); flex-direction: column; gap: 12px; padding: 16px 20px; background: var(--toast-bg, #fff); color: var(--toast-color, #000); border: 1px solid var(--toast-border, #e5e7eb); border-radius: 12px; box-shadow: var(--toast-shadow, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)); text-align: center; }\n .ts-toast.ts-toast-confirm .ts-toast-content { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }\n .ts-toast-actions { display: flex; gap: 10px; justify-content: center; margin-top: 12px; }\n .ts-toast-btn { appearance: none; border: 0; padding: 8px 12px; border-radius: 8px; font-weight: 600; cursor: pointer; }\n .ts-toast-btn.cancel { background: #e9ecef; color: #1f2937; }\n .ts-toast-btn.confirm { background: #3b82f6; color: #fff; }\n .ts-toast.ts-toast-error .ts-toast-btn.confirm,\n .ts-toast.ts-toast-warning .ts-toast-btn.confirm { background: #ef4444; color: #fff; }\n .ts-toast.ts-toast-confirm .ts-toast-title { font-weight: 700; font-size: 1.05rem; margin-top: 4px; }\n .ts-toast.ts-toast-confirm .ts-toast-close { position: absolute; top: 8px; right: 8px; width: 28px; height: 28px; border-radius: 999px; border: 0; background: transparent; color: #6b7280; font-size: 20px; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }\n .ts-toast.ts-toast-confirm .ts-toast-close:hover { background: rgba(0,0,0,0.06); }\n .ts-toast.ts-toast-confirm .ts-toast-icon { width: 64px; height: 64px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; }\n .ts-toast.ts-toast-confirm .ts-toast-icon img { width: 36px; height: 36px; }\n .ts-toast.ts-toast-confirm.ts-toast-success .ts-toast-icon { background: #dcfce7; }\n .ts-toast.ts-toast-confirm.ts-toast-info .ts-toast-icon { background: #dbeafe; }\n .ts-toast.ts-toast-confirm.ts-toast-warning .ts-toast-icon { background: #fef3c7; }\n .ts-toast.ts-toast-confirm.ts-toast-error .ts-toast-icon { background: #fee2e2; }\n ",document.head.appendChild(e)}();const toast=function(t,e={}){const{position:o="top-right",animation:s="slide-right",type:n="info",duration:a=3e3,icon:i=null,showLoader:r=!1,mode:l="alert",title:c=null,confirmText:d="Yes",cancelText:u="No",input:m=!1,inputPlaceholder:f="",inputValue:p="",confirmButtonBg:y=null,confirmButtonColor:h=null,cancelButtonBg:g=null,cancelButtonColor:b=null,onConfirm:v=null,onCancel:x=null,onResult:w=null,useOverlay:T=!0,closeOnOverlayClick:C=!0,showClose:E=!1,closeOnEscape:S=!0,allowHtml:L=!0,dismissOnClick:_=!0,onClick:k=null,onShow:A=null,onDismiss:N=null}=e,O="confirm"===l||"swal"===l,M=tsToastReducedMotion(),$="string"==typeof e.animation&&e.animation.trim()?{"slide-top":"ts-toast-slide-top","slide-bottom":"ts-toast-slide-bottom","slide-left":"ts-toast-slide-left","slide-right":"ts-toast-slide-right","zoom-in":"ts-toast-zoom-in","zoom-out":"ts-toast-zoom-out",flip:"ts-toast-flip"}[B=e.animation.trim()]||B:O||"center"===o?"ts-toast-zoom-in":o.startsWith("top")?"ts-toast-slide-top":o.startsWith("bottom")?"ts-toast-slide-bottom":o.endsWith("left")?"ts-toast-slide-left":"ts-toast-slide-right";var B;const R=(t,e)=>{const o=t.dataset&&t.dataset.anim?t.dataset.anim:t.style.animation||"";let s="";o.includes("ts-toast-slide-top")?s="translateY(-100%)":o.includes("ts-toast-slide-bottom")?s="translateY(100%)":(o.includes("ts-toast-slide-left")||o.includes("ts-toast-slide-right"))&&(s="translateX(100%)"),t.classList.add("ts-toast-slide-out"),t.classList.remove("ts-toast-show"),t.style.animation="",s&&(t.style.transform=s),t.style.opacity="0",setTimeout((()=>{t.classList.remove("ts-toast-slide-out"),t.parentNode&&t.parentNode.removeChild(t),"function"==typeof e&&e()}),M?0:500)},D=document.createElement("div");D.className=`ts-toast ts-toast-${n}${O?" ts-toast-confirm":""}`,D.dataset.anim=$,M||(D.style.animation=`${$} 0.5s ease`);const q="ts-toast-"+ ++tsToastIdCounter;O?(D.setAttribute("role","dialog"),D.setAttribute("aria-modal","true"),D.tabIndex=-1):"error"!==n&&"warning"!==n||D.setAttribute("role","alert"),O||(D.style.flexDirection="row-reverse",D.style.justifyContent="flex-end");const j=document.createElement("span");if(j.className="ts-toast-icon",j.style.display="flex",i)j.textContent=i;else{const t=document.createElement("img");t.alt="",t.setAttribute("aria-hidden","true"),t.style.width="30px",t.style.height="30px",t.style.objectFit="contain";const e={success:"success.gif",error:"error.gif",info:"info.gif",warning:"warning.gif"}[n];e&&(t.src=`${TS_TOAST_CDN}/assets/img/${e}`),j.appendChild(t)}const P=document.createElement("div");P.className="ts-toast-body",P.id=`${q}-body`,L?P.innerHTML=t:P.textContent=t;let z=null;if(O){if(z=document.createElement("div"),z.className="ts-toast-content",z.appendChild(j),c){const t=document.createElement("div");t.className="ts-toast-title",t.id=`${q}-title`,t.textContent=c,z.appendChild(t),D.setAttribute("aria-labelledby",t.id)}z.appendChild(P),D.setAttribute("aria-describedby",P.id),D.appendChild(z)}else D.appendChild(P);let I=null;O&&m&&("textarea"===m?(I=document.createElement("textarea"),I.rows=3):(I=document.createElement("input"),I.type="text"===m||"email"===m||"password"===m||"number"===m?m:"text"),I.className="ts-toast-input",I.placeholder=f,I.value=p,"textarea"!==m&&I.addEventListener("keydown",(t=>{"Enter"===t.key&&(t.preventDefault(),Y(!0))})),D.appendChild(I));let F=null,X=null,Y=null,H=()=>{};if(O){F=document.createElement("div"),F.className="ts-toast-actions";const t=document.createElement("button");t.className="ts-toast-btn cancel",t.textContent=u;const e=document.createElement("button");e.className="ts-toast-btn confirm",e.textContent=d,g&&(t.style.background=g),b&&(t.style.color=b),y&&(e.style.background=y),h&&(e.style.color=h),F.appendChild(t),F.appendChild(e),D.appendChild(F),D.result=new Promise((t=>{X=t}));let o=!1;Y=t=>{if(o)return;o=!0;const e=t?!I||I.value:!!I&&null;X&&X(e),t&&"function"==typeof v&&v(e,D),t||"function"!=typeof x||x(D),"function"==typeof w&&w(e,D),H(),R(D,(()=>{N&&"function"==typeof N&&N(D),W&&W.parentNode&&W.parentNode.removeChild(W)}))},t.addEventListener("click",(t=>{t.stopPropagation(),Y(!1)})),e.addEventListener("click",(t=>{t.stopPropagation(),Y(!0)}))}let K=null;r&&(K=document.createElement("div"),K.className="ts-toast-loader",D.appendChild(K));let W=null;if(O&&T){if(W=document.createElement("div"),W.className="ts-toast-overlay"+(e.position?` ${o}`:""),document.body.appendChild(W),W.appendChild(D),E){const t=document.createElement("button");t.className="ts-toast-close",t.setAttribute("aria-label","Close"),t.innerHTML="×",t.addEventListener("click",(t=>{t.stopPropagation(),Y(!1)})),D.appendChild(t)}if(C){let t=!1;W.addEventListener("pointerdown",(e=>{t=e.target===W})),W.addEventListener("click",(e=>{e.target===W&&t&&Y(!1),t=!1}))}}else{let t=document.querySelector(`.ts-toast-container.${o}`);t||(t=document.createElement("div"),t.className=`ts-toast-container ${o}`,document.body.appendChild(t)),t.hasAttribute("aria-live")||(t.setAttribute("role","status"),t.setAttribute("aria-live","polite"),t.setAttribute("aria-relevant","additions")),t.appendChild(D)}if(O){const t=document.activeElement;tsToastOpenModals+=1,1===tsToastOpenModals&&(tsToastPrevOverflow=document.body.style.overflow,document.body.style.overflow="hidden");const e=()=>{const t=document.querySelectorAll(".ts-toast.ts-toast-confirm");return 0===t.length||t[t.length-1]===D},o=t=>{if(!e())return;if("Escape"===t.key&&S)return t.preventDefault(),void Y(!1);if("Tab"!==t.key)return;const o=tsToastFocusable(D);if(!o.length)return void t.preventDefault();const s=o[0],n=o[o.length-1];D.contains(document.activeElement)?t.shiftKey&&document.activeElement===s?(t.preventDefault(),n.focus()):t.shiftKey||document.activeElement!==n||(t.preventDefault(),s.focus()):(t.preventDefault(),(t.shiftKey?n:s).focus())};document.addEventListener("keydown",o,!0),H=()=>{document.removeEventListener("keydown",o,!0),tsToastOpenModals=Math.max(0,tsToastOpenModals-1),0===tsToastOpenModals&&(document.body.style.overflow=tsToastPrevOverflow),t&&"function"==typeof t.focus&&document.contains(t)&&t.focus()},(I||D.querySelector(".ts-toast-btn.confirm")||D).focus()}if(A&&"function"==typeof A&&A(D),setTimeout((()=>{D.classList.add("ts-toast-show")}),100),r&&K&&setTimeout((()=>{D._managedByLoading||(K.classList.add("done"),K.remove(),D.contains(j)||(O&&z?z.appendChild(j):D.appendChild(j)))}),2e3),r||O||D.contains(j)||D.appendChild(j),!O&&a>0){const t=setTimeout((()=>{R(D,(()=>{N&&"function"==typeof N&&N(D)}))}),a);D._autoRemove=t}if(!O&&_&&D.addEventListener("click",(()=>{D._autoRemove&&clearTimeout(D._autoRemove),k&&"function"==typeof k&&k(D),R(D,(()=>{N&&"function"==typeof N&&N(D)}))})),!O){let t=0,e=0,o=0;D.addEventListener("touchstart",(o=>{t=o.changedTouches[0].screenX,e=o.changedTouches[0].screenY}),{passive:!0}),D.addEventListener("touchend",(s=>{o=s.changedTouches[0].screenX;const n=Math.abs(t-o),a=Math.abs(e-s.changedTouches[0].screenY);n>50&&n>a&&(D._autoRemove&&clearTimeout(D._autoRemove),R(D,(()=>{N&&"function"==typeof N&&N(D)})))}))}return D.close=()=>{D._autoRemove&&clearTimeout(D._autoRemove),O?Y(!1):R(D,(()=>{N&&"function"==typeof N&&N(D)}))},D};toast.success=function(t,e){return toast(t,{...e,type:"success"})},toast.error=function(t,e){return toast(t,{...e,type:"error"})},toast.warning=function(t,e){return toast(t,{...e,type:"warning"})},toast.info=function(t,e){return toast(t,{...e,type:"info"})},toast.update=function(t,e,o={}){const{type:s=null,icon:n=null,showLoader:a=!1,duration:i=3e3,onClick:r=null,onShow:l=null,onDismiss:c=null}=o,d=t.querySelector(".ts-toast-loader"),u=t.querySelector(".ts-toast-icon");d&&d.remove(),s&&(["success","error","info","warning"].forEach((e=>t.classList.remove(`ts-toast-${e}`))),t.classList.add("ts-toast",`ts-toast-${s}`,"ts-toast-show"));const m=t.querySelector(".ts-toast-body");m&&(m.innerHTML=e),u&&u.remove();const f=document.createElement("span");if(f.className="ts-toast-icon",f.style.display="flex",n)f.textContent=n;else{const t=document.createElement("img");t.alt="",t.setAttribute("aria-hidden","true"),t.style.width="30px",t.style.height="30px",t.style.objectFit="contain";const e={success:"success.gif",error:"error.gif",info:"info.gif",warning:"warning.gif"}[s];e&&(t.src=`${TS_TOAST_CDN}/assets/img/${e}`),f.appendChild(t)}if((t.querySelector(".ts-toast-content")||t).appendChild(f),a){const e=document.createElement("div");e.className="ts-toast-loader",t.appendChild(e),setTimeout((()=>{e.classList.add("done")}),2e3)}t._autoRemove&&clearTimeout(t._autoRemove);const p=setTimeout((()=>{var e,o;o=()=>{c&&"function"==typeof c&&c(t)},(e=t).classList.add("ts-toast-slide-out"),e.classList.remove("ts-toast-show"),e.style.animation="",setTimeout((()=>{e.classList.remove("ts-toast-slide-out"),e.parentNode&&e.parentNode.removeChild(e),"function"==typeof o&&o()}),500)}),i);t._autoRemove=p},toast.loading=function(t,e={}){const o=toast(t,{...e,type:e.type||"info",duration:0,showLoader:!0,icon:null});requestAnimationFrame((()=>{o.classList.add("ts-toast-show")}));const s=o.querySelector(".ts-toast-loader");let n=o.querySelector(".ts-toast-icon");return n||(n=document.createElement("span"),n.className="ts-toast-icon",n.style.display="flex",o.appendChild(n)),o._managedByLoading=!0,s&&setTimeout((()=>{o._managedByLoading||s.classList.add("done")}),2e3),{update:(t,e={})=>{o._managedByLoading=!1,toast.update(o,t,{...e,showLoader:!1})},close:()=>{o._managedByLoading=!1,o.close()}}},toast.confirm=function(t,e={}){return new Promise((o=>{toast(t,{...e,mode:"confirm",duration:0,dismissOnClick:!1,onResult:t=>o(t)})}))},toast.dismissAll=function(){document.querySelectorAll(".ts-toast").forEach((t=>{"function"==typeof t.close&&t.close()}))},"undefined"!=typeof window&&(window.toast=toast),"undefined"!=typeof module&&module.exports&&(module.exports=toast);
|
|
1
|
+
"use strict";const TS_TOAST_VERSION="5.6.0",TS_TOAST_CDN="undefined"!=typeof window&&window.TS_TOAST_ASSET_BASE?String(window.TS_TOAST_ASSET_BASE).replace(/\/+$/,""):"https://cdn.jsdelivr.net/npm/@tsirosgeorge/toastnotification@5.6.0";let tsToastOpenModals=0,tsToastPrevOverflow="",tsToastPrevPaddingRight="";const tsToastReducedMotion=()=>"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,tsToastFocusable=t=>Array.from(t.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')).filter((t=>!t.disabled&&null!==t.offsetParent));let tsToastIdCounter=0;!function(){const t="ts-toast-stylesheet";if("undefined"!=typeof window&&window.TS_TOAST_NO_CSS)return;if(document.getElementById(t))return;const e=document.createElement("link");e.id=t,e.rel="stylesheet",e.href=`${TS_TOAST_CDN}/assets/css/toast.min.css`,document.head.appendChild(e)}(),function(){const t="ts-toast-inline-extras";if(document.getElementById(t))return;const e=document.createElement("style");e.id=t,e.textContent="\n /* Ensure center positions exist even if external CSS lacks them */\n .ts-toast-container.top-center { top: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }\n .ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }\n .ts-toast-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); align-items: center; }\n .ts-toast-overlay.center { align-items: center; justify-content: center; }\n @keyframes ts-toast-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } }\n .ts-toast .ts-toast-progress { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; transform-origin: left center; border-radius: 0 0 8px 8px; background: currentColor; opacity: 0.35; pointer-events: none; }\n .ts-toast .ts-toast-action { order: -1; flex: none; appearance: none; border: 0; background: transparent; color: #3b82f6; font: inherit; font-weight: 600; padding: 4px 8px; margin-left: 4px; border-radius: 6px; cursor: pointer; }\n .ts-toast .ts-toast-action:hover { background: rgba(59,130,246,0.12); }\n .ts-toast-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2147483646; }\n .ts-toast.ts-toast-confirm { max-width: min(92vw, 440px); width: max(320px, 60%); flex-direction: column; gap: 12px; padding: 16px 20px; background: var(--toast-bg, #fff); color: var(--toast-color, #000); border: 1px solid var(--toast-border, #e5e7eb); border-radius: 12px; box-shadow: var(--toast-shadow, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)); text-align: center; }\n .ts-toast.ts-toast-confirm .ts-toast-content { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }\n .ts-toast-actions { display: flex; gap: 10px; justify-content: center; margin-top: 12px; }\n .ts-toast-btn { appearance: none; border: 0; padding: 8px 12px; border-radius: 8px; font-weight: 600; cursor: pointer; }\n .ts-toast-btn.cancel { background: #e9ecef; color: #1f2937; }\n .ts-toast-btn.confirm { background: #3b82f6; color: #fff; }\n .ts-toast.ts-toast-error .ts-toast-btn.confirm,\n .ts-toast.ts-toast-warning .ts-toast-btn.confirm { background: #ef4444; color: #fff; }\n .ts-toast.ts-toast-confirm .ts-toast-title { font-weight: 700; font-size: 1.05rem; margin-top: 4px; }\n .ts-toast.ts-toast-confirm .ts-toast-close { position: absolute; top: 8px; right: 8px; width: 28px; height: 28px; border-radius: 999px; border: 0; background: transparent; color: #6b7280; font-size: 20px; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }\n .ts-toast.ts-toast-confirm .ts-toast-close:hover { background: rgba(0,0,0,0.06); }\n .ts-toast.ts-toast-confirm .ts-toast-icon { width: 64px; height: 64px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; }\n .ts-toast.ts-toast-confirm .ts-toast-icon img { width: 36px; height: 36px; }\n .ts-toast.ts-toast-confirm.ts-toast-success .ts-toast-icon { background: #dcfce7; }\n .ts-toast.ts-toast-confirm.ts-toast-info .ts-toast-icon { background: #dbeafe; }\n .ts-toast.ts-toast-confirm.ts-toast-warning .ts-toast-icon { background: #fef3c7; }\n .ts-toast.ts-toast-confirm.ts-toast-error .ts-toast-icon { background: #fee2e2; }\n ",document.head.appendChild(e)}();const toast=function(t,e={}){e={...toast.defaults||{},...e};const{position:o="top-right",animation:s="slide-right",type:n="info",duration:a=3e3,icon:i=null,showLoader:r=!1,mode:l="alert",title:c=null,confirmText:d="Yes",cancelText:u="No",input:m=!1,inputPlaceholder:p="",inputValue:f="",confirmButtonBg:g=null,confirmButtonColor:h=null,cancelButtonBg:y=null,cancelButtonColor:b=null,onConfirm:x=null,onCancel:v=null,onResult:w=null,useOverlay:T=!0,closeOnOverlayClick:C=!0,showClose:E=!1,pauseOnHover:S=!0,showProgress:L=!1,action:_=null,closeOnEscape:k=!0,allowHtml:A=!0,dismissOnClick:N=!0,onClick:O=null,onShow:D=null,onDismiss:P=null}=e,M="confirm"===l||"swal"===l,$=tsToastReducedMotion(),B="string"==typeof e.animation&&e.animation.trim()?{"slide-top":"ts-toast-slide-top","slide-bottom":"ts-toast-slide-bottom","slide-left":"ts-toast-slide-left","slide-right":"ts-toast-slide-right","zoom-in":"ts-toast-zoom-in","zoom-out":"ts-toast-zoom-out",flip:"ts-toast-flip"}[q=e.animation.trim()]||q:M||"center"===o?"ts-toast-zoom-in":o.startsWith("top")?"ts-toast-slide-top":o.startsWith("bottom")?"ts-toast-slide-bottom":o.endsWith("left")?"ts-toast-slide-left":"ts-toast-slide-right";var q;const R=(t,e)=>{const o=t.dataset&&t.dataset.anim?t.dataset.anim:t.style.animation||"";let s="";o.includes("ts-toast-slide-top")?s="translateY(-100%)":o.includes("ts-toast-slide-bottom")?s="translateY(100%)":(o.includes("ts-toast-slide-left")||o.includes("ts-toast-slide-right"))&&(s="translateX(100%)"),t.classList.add("ts-toast-slide-out"),t.classList.remove("ts-toast-show"),t.style.animation="",s&&(t.style.transform=s),t.style.opacity="0",setTimeout((()=>{t.classList.remove("ts-toast-slide-out"),t.parentNode&&t.parentNode.removeChild(t),"function"==typeof e&&e()}),$?0:500)},j=document.createElement("div");j.className=`ts-toast ts-toast-${n}${M?" ts-toast-confirm":""}`,j.dataset.anim=B,$||(j.style.animation=`${B} 0.5s ease`);const z="ts-toast-"+ ++tsToastIdCounter;M?(j.setAttribute("role","dialog"),j.setAttribute("aria-modal","true"),j.tabIndex=-1):"error"!==n&&"warning"!==n||j.setAttribute("role","alert"),M||(j.style.flexDirection="row-reverse",j.style.justifyContent="flex-end");const X=document.createElement("span");if(X.className="ts-toast-icon",X.style.display="flex",i)X.textContent=i;else{const t=document.createElement("img");t.alt="",t.setAttribute("aria-hidden","true"),t.style.width="30px",t.style.height="30px",t.style.objectFit="contain";const e={success:"success.gif",error:"error.gif",info:"info.gif",warning:"warning.gif"}[n];e&&(t.src=`${TS_TOAST_CDN}/assets/img/${e}`),X.appendChild(t)}const F=document.createElement("div");F.className="ts-toast-body",F.id=`${z}-body`,A?F.innerHTML=t:F.textContent=t;let H=null;if(M){if(H=document.createElement("div"),H.className="ts-toast-content",H.appendChild(X),c){const t=document.createElement("div");t.className="ts-toast-title",t.id=`${z}-title`,t.textContent=c,H.appendChild(t),j.setAttribute("aria-labelledby",t.id)}H.appendChild(F),j.setAttribute("aria-describedby",F.id),j.appendChild(H)}else j.appendChild(F);let I=null;M&&m&&("textarea"===m?(I=document.createElement("textarea"),I.rows=3):(I=document.createElement("input"),I.type="text"===m||"email"===m||"password"===m||"number"===m?m:"text"),I.className="ts-toast-input",I.placeholder=p,I.value=f,"textarea"!==m&&I.addEventListener("keydown",(t=>{"Enter"===t.key&&(t.preventDefault(),K(!0))})),j.appendChild(I));let W=null,Y=null,K=null,V=()=>{};if(M){W=document.createElement("div"),W.className="ts-toast-actions";const t=document.createElement("button");t.className="ts-toast-btn cancel",t.textContent=u;const e=document.createElement("button");e.className="ts-toast-btn confirm",e.textContent=d,y&&(t.style.background=y),b&&(t.style.color=b),g&&(e.style.background=g),h&&(e.style.color=h),W.appendChild(t),W.appendChild(e),j.appendChild(W),j.result=new Promise((t=>{Y=t}));let o=!1;K=t=>{if(o)return;o=!0;const e=t?!I||I.value:!!I&&null;Y&&Y(e),t&&"function"==typeof x&&x(e,j),t||"function"!=typeof v||v(j),"function"==typeof w&&w(e,j),V(),R(j,(()=>{P&&"function"==typeof P&&P(j),Q&&Q.parentNode&&Q.parentNode.removeChild(Q)}))},t.addEventListener("click",(t=>{t.stopPropagation(),K(!1)})),e.addEventListener("click",(t=>{t.stopPropagation(),K(!0)}))}if(!M&&_&&"object"==typeof _&&_.text){const t=document.createElement("button");t.className="ts-toast-action",t.type="button",t.textContent=_.text,t.addEventListener("click",(t=>{t.stopPropagation(),"function"==typeof _.onClick&&_.onClick(j),j._dismiss()})),j.appendChild(t)}let G=null;!M&&L&&a>0&&!$&&(G=document.createElement("div"),G.className="ts-toast-progress",G.style.animation=`ts-toast-progress ${a}ms linear forwards`,G.style.animationPlayState="paused",j.appendChild(G));let J=null;r&&(J=document.createElement("div"),J.className="ts-toast-loader",j.appendChild(J));let Q=null,U=null;if(M&&T){if(Q=document.createElement("div"),Q.className="ts-toast-overlay"+(e.position?` ${o}`:""),document.body.appendChild(Q),Q.appendChild(j),E){const t=document.createElement("button");t.className="ts-toast-close",t.setAttribute("aria-label","Close"),t.innerHTML="×",t.addEventListener("click",(t=>{t.stopPropagation(),K(!1)})),j.appendChild(t)}if(C){let t=!1;Q.addEventListener("pointerdown",(e=>{t=e.target===Q})),Q.addEventListener("click",(e=>{e.target===Q&&t&&K(!1),t=!1}))}}else{let t=document.querySelector(`.ts-toast-container.${o}`);t||(t=document.createElement("div"),t.className=`ts-toast-container ${o}`,document.body.appendChild(t)),t.hasAttribute("aria-live")||(t.setAttribute("role","status"),t.setAttribute("aria-live","polite"),t.setAttribute("aria-relevant","additions")),t.appendChild(j),U=t}if(M){const t=document.activeElement;if(tsToastOpenModals+=1,1===tsToastOpenModals){tsToastPrevOverflow=document.body.style.overflow,tsToastPrevPaddingRight=document.body.style.paddingRight;const t=window.innerWidth-document.documentElement.clientWidth;if(t>0){const e=parseFloat(window.getComputedStyle(document.body).paddingRight)||0;document.body.style.paddingRight=`${e+t}px`}document.body.style.overflow="hidden"}const e=()=>{const t=document.querySelectorAll(".ts-toast.ts-toast-confirm");return 0===t.length||t[t.length-1]===j},o=t=>{if(!e())return;if("Escape"===t.key&&k)return t.preventDefault(),void K(!1);if("Tab"!==t.key)return;const o=tsToastFocusable(j);if(!o.length)return void t.preventDefault();const s=o[0],n=o[o.length-1];j.contains(document.activeElement)?t.shiftKey&&document.activeElement===s?(t.preventDefault(),n.focus()):t.shiftKey||document.activeElement!==n||(t.preventDefault(),s.focus()):(t.preventDefault(),(t.shiftKey?n:s).focus())};document.addEventListener("keydown",o,!0),V=()=>{document.removeEventListener("keydown",o,!0),tsToastOpenModals=Math.max(0,tsToastOpenModals-1),0===tsToastOpenModals&&(document.body.style.overflow=tsToastPrevOverflow,document.body.style.paddingRight=tsToastPrevPaddingRight),t&&"function"==typeof t.focus&&document.contains(t)&&t.focus()},(I||j.querySelector(".ts-toast-btn.confirm")||j).focus()}D&&"function"==typeof D&&D(j),setTimeout((()=>{j.classList.add("ts-toast-show")}),100);const Z=a>0?Math.min(2e3,Math.max(0,a-500)):2e3;r&&J&&setTimeout((()=>{j._managedByLoading||(J.classList.add("done"),J.remove(),j.contains(X)||(M&&H?H.appendChild(X):j.appendChild(X)))}),Z),r||M||j.contains(X)||j.appendChild(X),j._onDismiss="function"==typeof P?P:null;let tt=a,et=null,ot=0;const st=()=>{et&&(clearTimeout(et),et=null)},nt=()=>{M||tt<=0||et||(ot=Date.now(),et=setTimeout((()=>{et=null,j._dismiss()}),tt),G&&(G.style.animationPlayState="running"))},at=()=>{et&&(clearTimeout(et),et=null,tt-=Date.now()-ot,G&&(G.style.animationPlayState="paused"))};if(j._dismiss=()=>{j._removing||(j._removing=!0,st(),R(j,(()=>{j._onDismiss&&j._onDismiss(j),U&&!U.children.length&&U.remove()})))},j._setDuration=t=>{st(),tt=t,nt()},nt(),!M&&S&&(j.addEventListener("mouseenter",at),j.addEventListener("mouseleave",nt),j.addEventListener("focusin",at),j.addEventListener("focusout",nt)),!M&&N&&j.addEventListener("click",(()=>{O&&"function"==typeof O&&O(j),j._dismiss()})),!M){let t=0,e=0,o=0;j.addEventListener("touchstart",(o=>{t=o.changedTouches[0].screenX,e=o.changedTouches[0].screenY}),{passive:!0}),j.addEventListener("touchend",(s=>{o=s.changedTouches[0].screenX;const n=Math.abs(t-o),a=Math.abs(e-s.changedTouches[0].screenY);n>50&&n>a&&j._dismiss()}))}return j.close=()=>{M?K(!1):j._dismiss()},j};toast.success=function(t,e){return toast(t,{...e,type:"success"})},toast.error=function(t,e){return toast(t,{...e,type:"error"})},toast.warning=function(t,e){return toast(t,{...e,type:"warning"})},toast.info=function(t,e){return toast(t,{...e,type:"info"})},toast.update=function(t,e,o={}){const{type:s=null,icon:n=null,showLoader:a=!1,duration:i=3e3,allowHtml:r=!0,onDismiss:l=null}=o,c=t.querySelector(".ts-toast-loader"),d=t.querySelector(".ts-toast-icon");c&&c.remove(),s&&(["success","error","info","warning"].forEach((e=>t.classList.remove(`ts-toast-${e}`))),t.classList.add("ts-toast",`ts-toast-${s}`,"ts-toast-show"));const u=t.querySelector(".ts-toast-body");u&&(r?u.innerHTML=e:u.textContent=e),d&&d.remove();const m=document.createElement("span");if(m.className="ts-toast-icon",m.style.display="flex",n)m.textContent=n;else{const t=document.createElement("img");t.alt="",t.setAttribute("aria-hidden","true"),t.style.width="30px",t.style.height="30px",t.style.objectFit="contain";const e={success:"success.gif",error:"error.gif",info:"info.gif",warning:"warning.gif"}[s];e&&(t.src=`${TS_TOAST_CDN}/assets/img/${e}`),m.appendChild(t)}if(n||m.querySelector("img[src]")){(t.querySelector(".ts-toast-content")||t).appendChild(m)}if(a){const e=document.createElement("div");e.className="ts-toast-loader",t.appendChild(e),setTimeout((()=>{e.classList.add("done")}),i>0?Math.min(2e3,Math.max(0,i-500)):2e3)}"function"==typeof l&&(t._onDismiss=l),"function"==typeof t._setDuration&&t._setDuration(i)},toast.loading=function(t,e={}){const o=toast(t,{...e,type:e.type||"info",duration:0,showLoader:!0,icon:null});requestAnimationFrame((()=>{o.classList.add("ts-toast-show")}));const s=o.querySelector(".ts-toast-loader");let n=o.querySelector(".ts-toast-icon");return n||(n=document.createElement("span"),n.className="ts-toast-icon",n.style.display="flex",o.appendChild(n)),o._managedByLoading=!0,s&&setTimeout((()=>{o._managedByLoading||s.classList.add("done")}),2e3),{update:(t,e={})=>{o._managedByLoading=!1,toast.update(o,t,{...e,showLoader:!1})},close:()=>{o._managedByLoading=!1,o.close()}}},toast.confirm=function(t,e={}){return new Promise((o=>{toast(t,{...e,mode:"confirm",duration:0,dismissOnClick:!1,onResult:t=>o(t)})}))},toast.promise=function(t,e={},o={}){const{loading:s="Loading…",success:n="Done",error:a="Something went wrong"}=e,i=toast.loading(s,o),r=(t,e)=>"function"==typeof t?t(e):t;return Promise.resolve(t).then((t=>(i.update(r(n,t),{type:"success",duration:o.duration}),t)),(t=>{throw i.update(r(a,t),{type:"error",duration:o.duration}),t}))},toast.defaults={},toast.dismissAll=function(){document.querySelectorAll(".ts-toast").forEach((t=>{"function"==typeof t.close&&t.close()}))},"undefined"!=typeof window&&(window.toast=toast),"undefined"!=typeof module&&module.exports&&(module.exports=toast);
|
package/toast.module.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// Single source of truth for the CDN this build points at.
|
|
4
4
|
// `npm run sync:version` rewrites it from package.json, so it can never go stale.
|
|
5
|
-
const TS_TOAST_VERSION = "5.
|
|
5
|
+
const TS_TOAST_VERSION = "5.6.0";
|
|
6
6
|
// Point this at your own copy of assets/ to self-host the CSS and icons
|
|
7
7
|
// (useful offline, behind a strict CSP, or when you don't want a CDN dependency):
|
|
8
8
|
// window.TS_TOAST_ASSET_BASE = '/vendor/toastnotification';
|
|
@@ -14,6 +14,7 @@ const TS_TOAST_CDN = (typeof window !== 'undefined' && window.TS_TOAST_ASSET_BAS
|
|
|
14
14
|
// group, so only the last dialog to close may release it.
|
|
15
15
|
let tsToastOpenModals = 0;
|
|
16
16
|
let tsToastPrevOverflow = '';
|
|
17
|
+
let tsToastPrevPaddingRight = '';
|
|
17
18
|
|
|
18
19
|
const tsToastReducedMotion = () =>
|
|
19
20
|
typeof window !== 'undefined' &&
|
|
@@ -52,6 +53,10 @@ let tsToastIdCounter = 0;
|
|
|
52
53
|
.ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }
|
|
53
54
|
.ts-toast-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); align-items: center; }
|
|
54
55
|
.ts-toast-overlay.center { align-items: center; justify-content: center; }
|
|
56
|
+
@keyframes ts-toast-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } }
|
|
57
|
+
.ts-toast .ts-toast-progress { position: absolute; left: 0; right: 0; bottom: 0; height: 3px; transform-origin: left center; border-radius: 0 0 8px 8px; background: currentColor; opacity: 0.35; pointer-events: none; }
|
|
58
|
+
.ts-toast .ts-toast-action { order: -1; flex: none; appearance: none; border: 0; background: transparent; color: #3b82f6; font: inherit; font-weight: 600; padding: 4px 8px; margin-left: 4px; border-radius: 6px; cursor: pointer; }
|
|
59
|
+
.ts-toast .ts-toast-action:hover { background: rgba(59,130,246,0.12); }
|
|
55
60
|
.ts-toast-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 2147483646; }
|
|
56
61
|
.ts-toast.ts-toast-confirm { max-width: min(92vw, 440px); width: max(320px, 60%); flex-direction: column; gap: 12px; padding: 16px 20px; background: var(--toast-bg, #fff); color: var(--toast-color, #000); border: 1px solid var(--toast-border, #e5e7eb); border-radius: 12px; box-shadow: var(--toast-shadow, 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)); text-align: center; }
|
|
57
62
|
.ts-toast.ts-toast-confirm .ts-toast-content { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; }
|
|
@@ -75,6 +80,9 @@ let tsToastIdCounter = 0;
|
|
|
75
80
|
})();
|
|
76
81
|
|
|
77
82
|
const toast = function (message, options = {}) {
|
|
83
|
+
// Site-wide defaults, so a page can turn on things like showProgress once
|
|
84
|
+
// instead of repeating them at every call site.
|
|
85
|
+
options = { ...(toast.defaults || {}), ...options };
|
|
78
86
|
const {
|
|
79
87
|
position = 'top-right',
|
|
80
88
|
animation = 'slide-right', // Default fallback animation
|
|
@@ -103,6 +111,12 @@ const toast = function (message, options = {}) {
|
|
|
103
111
|
useOverlay = true,
|
|
104
112
|
closeOnOverlayClick = true,
|
|
105
113
|
showClose = false,
|
|
114
|
+
// Freeze the countdown while the pointer or keyboard focus is on the toast
|
|
115
|
+
pauseOnHover = true,
|
|
116
|
+
// Thin bar counting the remaining time down
|
|
117
|
+
showProgress = false,
|
|
118
|
+
// { text, onClick } renders a button inside the toast, e.g. Undo
|
|
119
|
+
action = null,
|
|
106
120
|
// Escape cancels a confirm dialog
|
|
107
121
|
closeOnEscape = true,
|
|
108
122
|
// `message` is written as HTML for backwards compatibility. Pass false to
|
|
@@ -329,6 +343,31 @@ const toast = function (message, options = {}) {
|
|
|
329
343
|
confirmBtn.addEventListener('click', (e) => { e.stopPropagation(); resolveAndClose(true); });
|
|
330
344
|
}
|
|
331
345
|
|
|
346
|
+
// Action button (e.g. Undo). Clicking it runs the callback and closes the toast.
|
|
347
|
+
if (!isConfirm && action && typeof action === 'object' && action.text) {
|
|
348
|
+
const actionBtn = document.createElement('button');
|
|
349
|
+
actionBtn.className = 'ts-toast-action';
|
|
350
|
+
actionBtn.type = 'button';
|
|
351
|
+
actionBtn.textContent = action.text;
|
|
352
|
+
actionBtn.addEventListener('click', (e) => {
|
|
353
|
+
e.stopPropagation(); // do not also trigger dismissOnClick
|
|
354
|
+
if (typeof action.onClick === 'function') action.onClick(toastElement);
|
|
355
|
+
toastElement._dismiss();
|
|
356
|
+
});
|
|
357
|
+
toastElement.appendChild(actionBtn);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Progress bar. The width is driven by a CSS animation whose duration is the
|
|
361
|
+
// toast's own, so pausing it is one property and never drifts from the timer.
|
|
362
|
+
let progressBar = null;
|
|
363
|
+
if (!isConfirm && showProgress && duration > 0 && !reducedMotion) {
|
|
364
|
+
progressBar = document.createElement('div');
|
|
365
|
+
progressBar.className = 'ts-toast-progress';
|
|
366
|
+
progressBar.style.animation = `ts-toast-progress ${duration}ms linear forwards`;
|
|
367
|
+
progressBar.style.animationPlayState = 'paused';
|
|
368
|
+
toastElement.appendChild(progressBar);
|
|
369
|
+
}
|
|
370
|
+
|
|
332
371
|
// Loader Element
|
|
333
372
|
let loader = null;
|
|
334
373
|
if (showLoader) {
|
|
@@ -339,6 +378,7 @@ const toast = function (message, options = {}) {
|
|
|
339
378
|
|
|
340
379
|
// Container/Overlay
|
|
341
380
|
let overlay = null;
|
|
381
|
+
let containerEl = null;
|
|
342
382
|
if (isConfirm && useOverlay) {
|
|
343
383
|
overlay = document.createElement('div');
|
|
344
384
|
// A modal centres by default. The `position` default of 'top-right' is meant
|
|
@@ -387,6 +427,7 @@ const toast = function (message, options = {}) {
|
|
|
387
427
|
container.setAttribute('aria-relevant', 'additions');
|
|
388
428
|
}
|
|
389
429
|
container.appendChild(toastElement);
|
|
430
|
+
containerEl = container;
|
|
390
431
|
}
|
|
391
432
|
|
|
392
433
|
if (isConfirm) {
|
|
@@ -399,6 +440,15 @@ const toast = function (message, options = {}) {
|
|
|
399
440
|
tsToastOpenModals += 1;
|
|
400
441
|
if (tsToastOpenModals === 1) {
|
|
401
442
|
tsToastPrevOverflow = document.body.style.overflow;
|
|
443
|
+
tsToastPrevPaddingRight = document.body.style.paddingRight;
|
|
444
|
+
// Hiding the scrollbar makes the page wider, which shifts the whole
|
|
445
|
+
// layout sideways as the dialog opens. Pad by the scrollbar's width to
|
|
446
|
+
// hold it still. A no-op where scrollbars are overlays, as on macOS.
|
|
447
|
+
const scrollbar = window.innerWidth - document.documentElement.clientWidth;
|
|
448
|
+
if (scrollbar > 0) {
|
|
449
|
+
const current = parseFloat(window.getComputedStyle(document.body).paddingRight) || 0;
|
|
450
|
+
document.body.style.paddingRight = `${current + scrollbar}px`;
|
|
451
|
+
}
|
|
402
452
|
document.body.style.overflow = 'hidden';
|
|
403
453
|
}
|
|
404
454
|
|
|
@@ -441,7 +491,10 @@ const toast = function (message, options = {}) {
|
|
|
441
491
|
releaseModal = () => {
|
|
442
492
|
document.removeEventListener('keydown', onKeydown, true);
|
|
443
493
|
tsToastOpenModals = Math.max(0, tsToastOpenModals - 1);
|
|
444
|
-
if (tsToastOpenModals === 0)
|
|
494
|
+
if (tsToastOpenModals === 0) {
|
|
495
|
+
document.body.style.overflow = tsToastPrevOverflow;
|
|
496
|
+
document.body.style.paddingRight = tsToastPrevPaddingRight;
|
|
497
|
+
}
|
|
445
498
|
// Hand the keyboard back to whatever opened the dialog.
|
|
446
499
|
if (previouslyFocused && typeof previouslyFocused.focus === 'function' &&
|
|
447
500
|
document.contains(previouslyFocused)) {
|
|
@@ -463,6 +516,10 @@ const toast = function (message, options = {}) {
|
|
|
463
516
|
toastElement.classList.add('ts-toast-show');
|
|
464
517
|
}, 100);
|
|
465
518
|
|
|
519
|
+
// The loader always ran for 2s, so with a shorter duration the toast was gone
|
|
520
|
+
// before the icon it reveals ever appeared.
|
|
521
|
+
const loaderMs = duration > 0 ? Math.min(2000, Math.max(0, duration - 500)) : 2000;
|
|
522
|
+
|
|
466
523
|
// Handle Loader and Icon
|
|
467
524
|
if (showLoader && loader) {
|
|
468
525
|
setTimeout(() => {
|
|
@@ -474,7 +531,7 @@ const toast = function (message, options = {}) {
|
|
|
474
531
|
if (isConfirm && contentRow) contentRow.appendChild(iconElement);
|
|
475
532
|
else toastElement.appendChild(iconElement); // Add icon only if not present
|
|
476
533
|
}
|
|
477
|
-
},
|
|
534
|
+
}, loaderMs);
|
|
478
535
|
}
|
|
479
536
|
if (!showLoader) {
|
|
480
537
|
// For confirm, icon already added above inside contentRow; avoid moving it
|
|
@@ -483,26 +540,70 @@ const toast = function (message, options = {}) {
|
|
|
483
540
|
}
|
|
484
541
|
}
|
|
485
542
|
|
|
486
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
543
|
+
// Dismissal state lives on the element so toast.update() can rebind the callback
|
|
544
|
+
// and reuse this exact removal path instead of keeping its own copy of it.
|
|
545
|
+
toastElement._onDismiss = typeof onDismiss === 'function' ? onDismiss : null;
|
|
546
|
+
|
|
547
|
+
// A plain setTimeout cannot be paused, so the countdown is tracked by hand:
|
|
548
|
+
// `remaining` is what is left, and hovering banks it.
|
|
549
|
+
let remaining = duration;
|
|
550
|
+
let timerId = null;
|
|
551
|
+
let startedAt = 0;
|
|
552
|
+
|
|
553
|
+
const stopTimer = () => {
|
|
554
|
+
if (timerId) { clearTimeout(timerId); timerId = null; }
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const startTimer = () => {
|
|
558
|
+
if (isConfirm || remaining <= 0 || timerId) return;
|
|
559
|
+
startedAt = Date.now();
|
|
560
|
+
timerId = setTimeout(() => { timerId = null; toastElement._dismiss(); }, remaining);
|
|
561
|
+
if (progressBar) progressBar.style.animationPlayState = 'running';
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const pauseTimer = () => {
|
|
565
|
+
if (!timerId) return;
|
|
566
|
+
clearTimeout(timerId);
|
|
567
|
+
timerId = null;
|
|
568
|
+
remaining -= Date.now() - startedAt;
|
|
569
|
+
if (progressBar) progressBar.style.animationPlayState = 'paused';
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
toastElement._dismiss = () => {
|
|
573
|
+
if (toastElement._removing) return; // never run the exit twice
|
|
574
|
+
toastElement._removing = true;
|
|
575
|
+
stopTimer();
|
|
576
|
+
removeWithAnimation(toastElement, () => {
|
|
577
|
+
if (toastElement._onDismiss) toastElement._onDismiss(toastElement);
|
|
578
|
+
// Containers used to pile up in the DOM, one per position, forever.
|
|
579
|
+
if (containerEl && !containerEl.children.length) containerEl.remove();
|
|
580
|
+
});
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// Lets toast.update() re-arm the countdown without reaching into internals.
|
|
584
|
+
toastElement._setDuration = (ms) => {
|
|
585
|
+
stopTimer();
|
|
586
|
+
remaining = ms;
|
|
587
|
+
startTimer();
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
startTimer();
|
|
591
|
+
|
|
592
|
+
if (!isConfirm && pauseOnHover) {
|
|
593
|
+
// Give the reader a chance to finish the sentence.
|
|
594
|
+
toastElement.addEventListener('mouseenter', pauseTimer);
|
|
595
|
+
toastElement.addEventListener('mouseleave', startTimer);
|
|
596
|
+
toastElement.addEventListener('focusin', pauseTimer);
|
|
597
|
+
toastElement.addEventListener('focusout', startTimer);
|
|
494
598
|
}
|
|
495
599
|
|
|
496
600
|
// Add event listener for closing the toast when clicked (disabled in confirm mode)
|
|
497
601
|
if (!isConfirm && dismissOnClick) {
|
|
498
602
|
toastElement.addEventListener('click', () => {
|
|
499
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove); // Clear the auto-remove timeout
|
|
500
603
|
// onClick belongs to the click, not to the end of the exit animation,
|
|
501
604
|
// which is where it used to fire half a second late.
|
|
502
605
|
if (onClick && typeof onClick === 'function') onClick(toastElement);
|
|
503
|
-
|
|
504
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
505
|
-
});
|
|
606
|
+
toastElement._dismiss();
|
|
506
607
|
});
|
|
507
608
|
}
|
|
508
609
|
|
|
@@ -525,23 +626,15 @@ const toast = function (message, options = {}) {
|
|
|
525
626
|
const dy = Math.abs(touchStartY - e.changedTouches[0].screenY);
|
|
526
627
|
// Only a mostly-horizontal swipe dismisses, so scrolling the page past a
|
|
527
628
|
// toast no longer throws it away on a bit of sideways drift.
|
|
528
|
-
if (dx > 50 && dx > dy)
|
|
529
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove);
|
|
530
|
-
removeWithAnimation(toastElement, () => {
|
|
531
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
532
|
-
});
|
|
533
|
-
}
|
|
629
|
+
if (dx > 50 && dx > dy) toastElement._dismiss();
|
|
534
630
|
});
|
|
535
631
|
}
|
|
536
632
|
|
|
537
633
|
// Let callers dismiss a toast they are holding, instead of only waiting out
|
|
538
634
|
// the duration or making the user click it.
|
|
539
635
|
toastElement.close = () => {
|
|
540
|
-
if (toastElement._autoRemove) clearTimeout(toastElement._autoRemove);
|
|
541
636
|
if (isConfirm) { resolveAndClose(false); return; }
|
|
542
|
-
|
|
543
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
544
|
-
});
|
|
637
|
+
toastElement._dismiss();
|
|
545
638
|
};
|
|
546
639
|
|
|
547
640
|
return toastElement;
|
|
@@ -570,10 +663,9 @@ const toast = function (message, options = {}) {
|
|
|
570
663
|
type = null,
|
|
571
664
|
icon = null,
|
|
572
665
|
showLoader = false,
|
|
573
|
-
duration = 3000, // Default duration (in ms)
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
onDismiss = null // Custom onDismiss event listener
|
|
666
|
+
duration = 3000, // Default duration (in ms); 0 keeps the toast on screen
|
|
667
|
+
allowHtml = true,
|
|
668
|
+
onDismiss = null // Replaces the callback the toast was created with
|
|
577
669
|
} = options;
|
|
578
670
|
|
|
579
671
|
// Remove old loader (if any)
|
|
@@ -591,7 +683,8 @@ const toast = function (message, options = {}) {
|
|
|
591
683
|
}
|
|
592
684
|
const toastBody = toastElement.querySelector('.ts-toast-body');
|
|
593
685
|
if (toastBody) {
|
|
594
|
-
toastBody.innerHTML = message;
|
|
686
|
+
if (allowHtml) toastBody.innerHTML = message;
|
|
687
|
+
else toastBody.textContent = message;
|
|
595
688
|
}
|
|
596
689
|
|
|
597
690
|
// Handle Icon update only if it's new or hasn't been set yet
|
|
@@ -619,9 +712,12 @@ const toast = function (message, options = {}) {
|
|
|
619
712
|
iconElement.appendChild(img);
|
|
620
713
|
}
|
|
621
714
|
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
(
|
|
715
|
+
// Only attach an icon we actually have. Without a type and without an explicit
|
|
716
|
+
// icon this used to append an empty <span><img></span>.
|
|
717
|
+
if (icon || iconElement.querySelector('img[src]')) {
|
|
718
|
+
const contentRow = toastElement.querySelector('.ts-toast-content');
|
|
719
|
+
(contentRow || toastElement).appendChild(iconElement);
|
|
720
|
+
}
|
|
625
721
|
|
|
626
722
|
// Handle loader if requested
|
|
627
723
|
if (showLoader) {
|
|
@@ -630,33 +726,19 @@ const toast = function (message, options = {}) {
|
|
|
630
726
|
toastElement.appendChild(loader);
|
|
631
727
|
setTimeout(() => {
|
|
632
728
|
loader.classList.add('done');
|
|
633
|
-
}, 2000
|
|
729
|
+
}, duration > 0 ? Math.min(2000, Math.max(0, duration - 500)) : 2000);
|
|
634
730
|
}
|
|
635
731
|
|
|
636
|
-
//
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
732
|
+
// Rebind the dismiss callback rather than leaving the creation-time one in place,
|
|
733
|
+
// which meant an updated toast fired two different onDismiss handlers.
|
|
734
|
+
if (typeof onDismiss === 'function') toastElement._onDismiss = onDismiss;
|
|
640
735
|
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
setTimeout(() => {
|
|
648
|
-
el.classList.remove('ts-toast-slide-out');
|
|
649
|
-
if (el.parentNode) el.parentNode.removeChild(el);
|
|
650
|
-
if (typeof cb === 'function') cb();
|
|
651
|
-
}, 500);
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
removeWithAnimation(toastElement, () => {
|
|
655
|
-
if (onDismiss && typeof onDismiss === 'function') onDismiss(toastElement);
|
|
656
|
-
});
|
|
657
|
-
}, duration);
|
|
658
|
-
|
|
659
|
-
toastElement._autoRemove = autoRemove; // Re-set the auto-remove timer
|
|
736
|
+
// Re-arm the countdown through the toast's own timer, so duration: 0 keeps the
|
|
737
|
+
// toast on screen. This used to schedule setTimeout(..., 0) and remove it at once,
|
|
738
|
+
// which broke every `toast.loading(...).update(msg, { duration: 0 })`.
|
|
739
|
+
if (typeof toastElement._setDuration === 'function') {
|
|
740
|
+
toastElement._setDuration(duration);
|
|
741
|
+
}
|
|
660
742
|
};
|
|
661
743
|
|
|
662
744
|
toast.loading = function (message, options = {}) {
|
|
@@ -730,6 +812,34 @@ const toast = function (message, options = {}) {
|
|
|
730
812
|
});
|
|
731
813
|
};
|
|
732
814
|
|
|
815
|
+
// Wraps an async action: one loading toast that becomes the success or error
|
|
816
|
+
// message, instead of hand-rolling loading/update/catch at every call site.
|
|
817
|
+
toast.promise = function (promise, messages = {}, options = {}) {
|
|
818
|
+
const {
|
|
819
|
+
loading = 'Loading…',
|
|
820
|
+
success = 'Done',
|
|
821
|
+
error = 'Something went wrong'
|
|
822
|
+
} = messages;
|
|
823
|
+
|
|
824
|
+
const handle = toast.loading(loading, options);
|
|
825
|
+
// Messages may be functions so they can name what actually came back.
|
|
826
|
+
const text = (msg, value) => (typeof msg === 'function' ? msg(value) : msg);
|
|
827
|
+
|
|
828
|
+
return Promise.resolve(promise).then(
|
|
829
|
+
(value) => {
|
|
830
|
+
handle.update(text(success, value), { type: 'success', duration: options.duration });
|
|
831
|
+
return value;
|
|
832
|
+
},
|
|
833
|
+
(err) => {
|
|
834
|
+
handle.update(text(error, err), { type: 'error', duration: options.duration });
|
|
835
|
+
throw err; // the caller still owns the failure
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
// Options applied to every toast unless the call overrides them.
|
|
841
|
+
toast.defaults = {};
|
|
842
|
+
|
|
733
843
|
// Close every toast currently on screen. Confirm dialogs settle as a cancel.
|
|
734
844
|
toast.dismissAll = function () {
|
|
735
845
|
document.querySelectorAll('.ts-toast').forEach((el) => {
|