@tsirosgeorge/toastnotification 5.6.0 → 6.0.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 CHANGED
@@ -159,6 +159,25 @@ const t = toast.loading('Uploading…', { type: 'info' });
159
159
  t.update('Done!', { type: 'success', duration: 2000 });
160
160
  ```
161
161
 
162
+ ## ⬆️ Upgrading to 6.0.0
163
+
164
+ **`message` is now rendered as text, not HTML.** Before 6.0.0 it went through `innerHTML`,
165
+ so a message assembled from a database field, a form value or an API response could run
166
+ JavaScript on your page — `<img src=x onerror="…">` is enough, and `<script>` tags are not
167
+ required. That default is now off.
168
+
169
+ If you deliberately put markup in a message, add `allowHtml: true` to those calls:
170
+
171
+ ```javascript
172
+ toast('Saved <b>invoice.pdf</b>', { allowHtml: true }); // markup you wrote
173
+ toast(serverResponse.error); // untrusted — leave it as text
174
+ ```
175
+
176
+ **Icons are inline SVG.** The four animated GIFs (323 kB, and a network request per toast)
177
+ are gone; the glyph is now drawn on once as the toast appears. The package went from
178
+ 531 kB to 135 kB, icons appear instantly, and the library no longer fetches images at all.
179
+ `assets/img/` is no longer published.
180
+
162
181
  ## 🛠️ Available Options
163
182
 
164
183
  | Option | Type | Default | Description |
@@ -172,7 +191,7 @@ t.update('Done!', { type: 'success', duration: 2000 });
172
191
  | `pauseOnHover` | `boolean` | `true` | Freeze the countdown while the pointer or keyboard focus is on the toast. |
173
192
  | `showProgress` | `boolean` | `false` | Thin bar counting the remaining time down. Needs `duration > 0`; pauses with `pauseOnHover`. |
174
193
  | `action` | `object` or `null` | `null` | `{ text, onClick }` renders a button inside the toast, e.g. Undo. Clicking it runs `onClick` and closes the toast. |
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. |
194
+ | `allowHtml` | `boolean` | `false` | Render `message` as HTML instead of text. Turn it on only for markup you wrote yourself: a string built from user input becomes executable HTML. |
176
195
  | `closeOnEscape` | `boolean` | `true` | Confirm dialogs only. Escape cancels the dialog. |
177
196
  | `onClick` | `function` or `null` | `null` | Callback function executed when the toast is clicked. |
178
197
  | `onShow` | `function` or `null` | `null` | Callback function executed when the toast appears (after it's added to the DOM and shown). |
@@ -213,14 +213,6 @@
213
213
  font-size: 1.2rem;
214
214
  }
215
215
 
216
- .ts-toast-container .ts-toast-success .ts-toast-icon {
217
- color: var(--toast-success-color, #66ee78);
218
- }
219
-
220
- .ts-toast-container .ts-toast-error .ts-toast-icon {
221
- color: var(--toast-error-color, #ef4444);
222
- }
223
-
224
216
  /* Existing toast styles... */
225
217
 
226
218
  /* Loader styles */
@@ -256,18 +248,6 @@
256
248
  transition: opacity 0.5s ease;
257
249
  }
258
250
 
259
- .ts-toast-container .ts-toast-icon {
260
- font-size: 1.2rem;
261
- opacity: 0;
262
- transition: opacity 0.5s ease;
263
- }
264
-
265
- /* Show the icon after the loader has completed */
266
- .ts-toast-container .ts-toast.ts-toast-show .ts-toast-icon {
267
- opacity: 1;
268
- /* Fade in the icon */
269
- }
270
-
271
251
  @keyframes progress-bar {
272
252
  0% {
273
253
  width: 0%;
@@ -343,15 +323,13 @@
343
323
  .ts-toast-overlay.bottom-center { align-items: flex-end; justify-content: center; padding: 1rem; }
344
324
  .ts-toast-overlay.bottom-right { align-items: flex-end; justify-content: flex-end; padding: 1rem; }
345
325
  .ts-toast-overlay.center { align-items: center; justify-content: center; padding: 1rem; }
326
+ /* A dialog opening over another must not dim the page a second time. */
327
+ .ts-toast-overlay.ts-toast-overlay-stacked { background: transparent; }
346
328
 
347
329
  /* Tiny utility: flex icon container */
348
330
  /* Scoped utility to avoid conflicts */
349
- .ts-toast-d-flex { display: inline-flex; align-items: center; }
350
331
 
351
332
  /* Prevent scroll behind confirm overlay */
352
- body.ts-toast-no-scroll {
353
- overflow: hidden;
354
- }
355
333
 
356
334
  /* Progress bar counting down the remaining time */
357
335
  @keyframes ts-toast-progress {
@@ -398,3 +376,54 @@ body.ts-toast-no-scroll {
398
376
  .ts-toast-container .ts-toast-action:hover {
399
377
  background: var(--toast-action-hover, rgba(59, 130, 246, 0.12));
400
378
  }
379
+
380
+ /* ---- Icons ---- */
381
+ /* Inline SVG, drawn on once when the toast appears. The glyph is a stroked path, so
382
+ animating stroke-dashoffset draws it; the dots fade and pop in alongside. */
383
+ .ts-toast-icon svg {
384
+ width: 30px;
385
+ height: 30px;
386
+ display: block;
387
+ overflow: visible;
388
+ }
389
+
390
+ .ts-toast-icon .ts-toast-ring { fill: currentColor; }
391
+
392
+ .ts-toast-icon .ts-toast-glyph {
393
+ fill: none;
394
+ stroke: var(--toast-glyph-color, #fff);
395
+ stroke-width: 2.6;
396
+ stroke-linecap: round;
397
+ stroke-linejoin: round;
398
+ stroke-dasharray: var(--ts-len);
399
+ animation: ts-toast-draw 420ms ease-out 1 both;
400
+ }
401
+
402
+ .ts-toast-icon .ts-toast-dot {
403
+ fill: var(--toast-glyph-color, #fff);
404
+ transform-origin: center;
405
+ animation: ts-toast-pop 420ms ease-out 1 both;
406
+ }
407
+
408
+ @keyframes ts-toast-draw {
409
+ from { stroke-dashoffset: var(--ts-len); }
410
+ to { stroke-dashoffset: 0; }
411
+ }
412
+
413
+ @keyframes ts-toast-pop {
414
+ from { opacity: 0; transform: scale(0.3); }
415
+ to { opacity: 1; transform: scale(1); }
416
+ }
417
+
418
+ /* Someone who asked for less motion gets the finished icon, not a drawing of it. */
419
+ @media (prefers-reduced-motion: reduce) {
420
+ .ts-toast-icon .ts-toast-glyph,
421
+ .ts-toast-icon .ts-toast-dot { animation: none; }
422
+ .ts-toast-icon .ts-toast-glyph { stroke-dashoffset: 0; }
423
+ }
424
+
425
+ /* Icon colours, matching the palette of the GIFs these replaced. */
426
+ .ts-toast-success .ts-toast-icon { color: var(--toast-success-color, #86e08a); }
427
+ .ts-toast-error .ts-toast-icon { color: var(--toast-error-color, #e8635e); }
428
+ .ts-toast-info .ts-toast-icon { color: var(--toast-info-color, #6fc8ef); }
429
+ .ts-toast-warning .ts-toast-icon { color: var(--toast-warning-color, #f7e07a); }
@@ -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}@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))}
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-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}@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-overlay.ts-toast-overlay-stacked{background:0 0}@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))}.ts-toast-icon svg{width:30px;height:30px;display:block;overflow:visible}.ts-toast-icon .ts-toast-ring{fill:currentColor}.ts-toast-icon .ts-toast-glyph{fill:none;stroke:var(--toast-glyph-color,#fff);stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:var(--ts-len);animation:ts-toast-draw 420ms ease-out 1 both}.ts-toast-icon .ts-toast-dot{fill:var(--toast-glyph-color,#fff);transform-origin:center;animation:ts-toast-pop 420ms ease-out 1 both}@keyframes ts-toast-draw{from{stroke-dashoffset:var(--ts-len)}to{stroke-dashoffset:0}}@keyframes ts-toast-pop{from{opacity:0;transform:scale(.3)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion:reduce){.ts-toast-icon .ts-toast-dot,.ts-toast-icon .ts-toast-glyph{animation:none}.ts-toast-icon .ts-toast-glyph{stroke-dashoffset:0}}.ts-toast-success .ts-toast-icon{color:var(--toast-success-color,#86e08a)}.ts-toast-error .ts-toast-icon{color:var(--toast-error-color,#e8635e)}.ts-toast-info .ts-toast-icon{color:var(--toast-info-color,#6fc8ef)}.ts-toast-warning .ts-toast-icon{color:var(--toast-warning-color,#f7e07a)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsirosgeorge/toastnotification",
3
- "version": "5.6.0",
3
+ "version": "6.0.0",
4
4
  "description": "a toast notification plugin",
5
5
  "main": "toast.min.js",
6
6
  "module": "toast.module.js",
@@ -15,7 +15,9 @@
15
15
  "./package.json": "./package.json"
16
16
  },
17
17
  "scripts": {
18
- "test": "tsc --noEmit --strict --skipLibCheck toast.d.ts",
18
+ "test:types": "tsc --noEmit --strict --skipLibCheck toast.d.ts",
19
+ "test:e2e": "playwright test",
20
+ "test": "npm run test:types && npm run test:e2e",
19
21
  "sync:version": "node scripts/sync-version.mjs",
20
22
  "build:module": "node scripts/build-module.mjs",
21
23
  "build:css": "cleancss -o assets/css/toast.min.css assets/css/toast.css",
@@ -32,8 +34,6 @@
32
34
  "toast.d.ts",
33
35
  "assets/css/toast.css",
34
36
  "assets/css/toast.min.css",
35
- "assets/img/",
36
- "!assets/img/old/",
37
37
  "LICENSE",
38
38
  "Readme.md"
39
39
  ],
@@ -57,7 +57,9 @@
57
57
  },
58
58
  "homepage": "https://github.com/tsirosgeorge/toast-notification#readme",
59
59
  "devDependencies": {
60
+ "@playwright/test": "^1.63.0",
60
61
  "clean-css-cli": "^5.6.3",
62
+ "http-server": "^14.1.1",
61
63
  "terser": "^5.40.0",
62
64
  "typescript": "^5.9.3"
63
65
  },
package/toast.d.ts CHANGED
@@ -38,8 +38,9 @@ export interface ToastOptions {
38
38
  /** Renders a button inside the toast, e.g. Undo. */
39
39
  action?: ToastAction | null;
40
40
  /**
41
- * `message` is written as HTML by default, for backwards compatibility.
42
- * Pass `false` to render it as plain text do that for anything user-supplied.
41
+ * `message` is rendered as plain text. Pass `true` only for markup you wrote
42
+ * yourself a string built from user input becomes executable HTML.
43
+ * @default false
43
44
  */
44
45
  allowHtml?: boolean;
45
46
  /** `'confirm'` (alias `'swal'`) renders a modal dialog instead of a 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.6.0";
5
+ const TS_TOAST_VERSION = "6.0.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';
@@ -28,6 +28,28 @@ const tsToastFocusable = (root) => Array.from(
28
28
 
29
29
  let tsToastIdCounter = 0;
30
30
 
31
+ // Icons are inline SVG rather than animated GIFs. The four GIFs were 323 kB — fifteen
32
+ // times the whole library — were 400x400 for a 30px slot, and each one cost a network
33
+ // round trip before the icon could appear. The glyph is stroked so CSS can draw it on,
34
+ // once, when the toast appears.
35
+ const TS_TOAST_GLYPHS = {
36
+ success: '<path class="ts-toast-glyph" style="--ts-len:26" d="M7 12.6 L10.4 16 L17 8.8"/>',
37
+ error: '<path class="ts-toast-glyph" style="--ts-len:23" d="M8.3 8.3 L15.7 15.7 M15.7 8.3 L8.3 15.7"/>',
38
+ info: '<circle class="ts-toast-dot" cx="12" cy="7.4" r="1.5"/><path class="ts-toast-glyph" style="--ts-len:7" d="M12 11 L12 17"/>',
39
+ warning: '<path class="ts-toast-glyph" style="--ts-len:8" d="M12 6.6 L12 14"/><circle class="ts-toast-dot" cx="12" cy="17.3" r="1.5"/>'
40
+ };
41
+
42
+ // Fills the icon span for a type. Returns false when the type has no icon.
43
+ const tsToastPaintIcon = (el, type) => {
44
+ const glyph = TS_TOAST_GLYPHS[type];
45
+ if (!glyph) return false;
46
+ // Fixed internal markup, never caller input.
47
+ el.innerHTML =
48
+ '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">' +
49
+ '<circle class="ts-toast-ring" cx="12" cy="12" r="12"/>' + glyph + '</svg>';
50
+ return true;
51
+ };
52
+
31
53
  // Load the stylesheet from the CDN, unless the page opted out by importing it itself
32
54
  // (set window.TS_TOAST_NO_CSS = true before loading, or ship assets/css/toast.css yourself).
33
55
  (function loadStylesheet() {
@@ -53,6 +75,7 @@ let tsToastIdCounter = 0;
53
75
  .ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }
54
76
  .ts-toast-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); align-items: center; }
55
77
  .ts-toast-overlay.center { align-items: center; justify-content: center; }
78
+ .ts-toast-overlay.ts-toast-overlay-stacked { background: transparent; }
56
79
  @keyframes ts-toast-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } }
57
80
  .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
81
  .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; }
@@ -70,7 +93,7 @@ let tsToastIdCounter = 0;
70
93
  .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; }
71
94
  .ts-toast.ts-toast-confirm .ts-toast-close:hover { background: rgba(0,0,0,0.06); }
72
95
  .ts-toast.ts-toast-confirm .ts-toast-icon { width: 64px; height: 64px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; }
73
- .ts-toast.ts-toast-confirm .ts-toast-icon img { width: 36px; height: 36px; }
96
+ .ts-toast.ts-toast-confirm .ts-toast-icon svg { width: 36px; height: 36px; }
74
97
  .ts-toast.ts-toast-confirm.ts-toast-success .ts-toast-icon { background: #dcfce7; }
75
98
  .ts-toast.ts-toast-confirm.ts-toast-info .ts-toast-icon { background: #dbeafe; }
76
99
  .ts-toast.ts-toast-confirm.ts-toast-warning .ts-toast-icon { background: #fef3c7; }
@@ -119,9 +142,9 @@ const toast = function (message, options = {}) {
119
142
  action = null,
120
143
  // Escape cancels a confirm dialog
121
144
  closeOnEscape = true,
122
- // `message` is written as HTML for backwards compatibility. Pass false to
123
- // render it as plain text, which is what you want for anything user-supplied.
124
- allowHtml = true,
145
+ // `message` is rendered as plain text. Pass true only for markup you wrote
146
+ // yourself: any string built from user input becomes executable HTML.
147
+ allowHtml = false,
125
148
  // interactions
126
149
  dismissOnClick = true, // ignored if confirm-mode
127
150
  onClick = null, // Custom onClick event listener
@@ -215,27 +238,15 @@ const toast = function (message, options = {}) {
215
238
  if (icon) {
216
239
  iconElement.textContent = icon;
217
240
  } else {
218
- const img = document.createElement('img');
219
- img.alt = '';
220
- img.setAttribute('aria-hidden', 'true');
221
- img.style.width = '30px';
222
- img.style.height = '30px';
223
- img.style.objectFit = 'contain';
224
-
225
- // No cache-buster: these GIFs are immutable per version, so let the
226
- // browser and the CDN actually cache them.
227
- const iconFile = { success: 'success.gif', error: 'error.gif', info: 'info.gif', warning: 'warning.gif' }[type];
228
- if (iconFile) img.src = `${TS_TOAST_CDN}/assets/img/${iconFile}`;
229
-
230
- iconElement.appendChild(img);
241
+ tsToastPaintIcon(iconElement, type);
231
242
  }
232
243
 
233
244
  // Create Body
234
245
  const toastBody = document.createElement('div');
235
246
  toastBody.className = 'ts-toast-body';
236
247
  toastBody.id = `${uid}-body`;
237
- // HTML by default for backwards compatibility; pass allowHtml: false for
238
- // anything that came from a user.
248
+ // Text by default. innerHTML runs event handlers such as <img onerror>, so a
249
+ // message assembled from user input was a scripting hole in every caller.
239
250
  if (allowHtml) toastBody.innerHTML = message;
240
251
  else toastBody.textContent = message;
241
252
 
@@ -383,7 +394,10 @@ const toast = function (message, options = {}) {
383
394
  overlay = document.createElement('div');
384
395
  // A modal centres by default. The `position` default of 'top-right' is meant
385
396
  // for toasts; applying it here parked the dialog in a corner of the backdrop.
386
- overlay.className = 'ts-toast-overlay' + (options.position ? ` ${position}` : '');
397
+ // Each backdrop paints its own 50% black, so a second dialog opening over a
398
+ // first turned the page almost fully dark. Only the bottom one dims.
399
+ const stacked = tsToastOpenModals > 0 ? ' ts-toast-overlay-stacked' : '';
400
+ overlay.className = 'ts-toast-overlay' + stacked + (options.position ? ` ${position}` : '');
387
401
  document.body.appendChild(overlay);
388
402
  overlay.appendChild(toastElement);
389
403
  if (showClose) {
@@ -664,7 +678,7 @@ const toast = function (message, options = {}) {
664
678
  icon = null,
665
679
  showLoader = false,
666
680
  duration = 3000, // Default duration (in ms); 0 keeps the toast on screen
667
- allowHtml = true,
681
+ allowHtml = false,
668
682
  onDismiss = null // Replaces the callback the toast was created with
669
683
  } = options;
670
684
 
@@ -696,25 +710,16 @@ const toast = function (message, options = {}) {
696
710
  iconElement.className = 'ts-toast-icon';
697
711
  iconElement.style.display = 'flex';
698
712
 
713
+ let hasIcon = Boolean(icon);
699
714
  if (icon) {
700
715
  iconElement.textContent = icon;
701
716
  } else {
702
- const img = document.createElement('img');
703
- img.alt = '';
704
- img.setAttribute('aria-hidden', 'true');
705
- img.style.width = '30px';
706
- img.style.height = '30px';
707
- img.style.objectFit = 'contain';
708
-
709
- const iconFile = { success: 'success.gif', error: 'error.gif', info: 'info.gif', warning: 'warning.gif' }[type];
710
- if (iconFile) img.src = `${TS_TOAST_CDN}/assets/img/${iconFile}`;
711
-
712
- iconElement.appendChild(img);
717
+ hasIcon = tsToastPaintIcon(iconElement, type);
713
718
  }
714
719
 
715
720
  // 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]')) {
721
+ // icon this used to append an empty icon element.
722
+ if (hasIcon) {
718
723
  const contentRow = toastElement.querySelector('.ts-toast-content');
719
724
  (contentRow || toastElement).appendChild(iconElement);
720
725
  }
package/toast.min.js CHANGED
@@ -1 +1 @@
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="&times;",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);
1
+ "use strict";const TS_TOAST_VERSION="6.0.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@6.0.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;const TS_TOAST_GLYPHS={success:'<path class="ts-toast-glyph" style="--ts-len:26" d="M7 12.6 L10.4 16 L17 8.8"/>',error:'<path class="ts-toast-glyph" style="--ts-len:23" d="M8.3 8.3 L15.7 15.7 M15.7 8.3 L8.3 15.7"/>',info:'<circle class="ts-toast-dot" cx="12" cy="7.4" r="1.5"/><path class="ts-toast-glyph" style="--ts-len:7" d="M12 11 L12 17"/>',warning:'<path class="ts-toast-glyph" style="--ts-len:8" d="M12 6.6 L12 14"/><circle class="ts-toast-dot" cx="12" cy="17.3" r="1.5"/>'},tsToastPaintIcon=(t,e)=>{const s=TS_TOAST_GLYPHS[e];return!!s&&(t.innerHTML='<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle class="ts-toast-ring" cx="12" cy="12" r="12"/>'+s+"</svg>",!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.ts-toast-overlay-stacked { background: transparent; }\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 svg { 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:s="top-right",animation:o="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:y=null,confirmButtonColor:g=null,cancelButtonBg:h=null,cancelButtonColor:b=null,onConfirm:v=null,onCancel:x=null,onResult:w=null,useOverlay:T=!0,closeOnOverlayClick:C=!0,showClose:E=!1,pauseOnHover:L=!0,showProgress:S=!1,action:k=null,closeOnEscape:_=!0,allowHtml:A=!1,dismissOnClick:M=!0,onClick:N=null,onShow:O=null,onDismiss:P=null}=e,D="confirm"===l||"swal"===l,B=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"}[q=e.animation.trim()]||q:D||"center"===s?"ts-toast-zoom-in":s.startsWith("top")?"ts-toast-slide-top":s.startsWith("bottom")?"ts-toast-slide-bottom":s.endsWith("left")?"ts-toast-slide-left":"ts-toast-slide-right";var q;const R=(t,e)=>{const s=t.dataset&&t.dataset.anim?t.dataset.anim:t.style.animation||"";let o="";s.includes("ts-toast-slide-top")?o="translateY(-100%)":s.includes("ts-toast-slide-bottom")?o="translateY(100%)":(s.includes("ts-toast-slide-left")||s.includes("ts-toast-slide-right"))&&(o="translateX(100%)"),t.classList.add("ts-toast-slide-out"),t.classList.remove("ts-toast-show"),t.style.animation="",o&&(t.style.transform=o),t.style.opacity="0",setTimeout((()=>{t.classList.remove("ts-toast-slide-out"),t.parentNode&&t.parentNode.removeChild(t),"function"==typeof e&&e()}),B?0:500)},j=document.createElement("div");j.className=`ts-toast ts-toast-${n}${D?" ts-toast-confirm":""}`,j.dataset.anim=$,B||(j.style.animation=`${$} 0.5s ease`);const H="ts-toast-"+ ++tsToastIdCounter;D?(j.setAttribute("role","dialog"),j.setAttribute("aria-modal","true"),j.tabIndex=-1):"error"!==n&&"warning"!==n||j.setAttribute("role","alert"),D||(j.style.flexDirection="row-reverse",j.style.justifyContent="flex-end");const I=document.createElement("span");I.className="ts-toast-icon",I.style.display="flex",i?I.textContent=i:tsToastPaintIcon(I,n);const z=document.createElement("div");z.className="ts-toast-body",z.id=`${H}-body`,A?z.innerHTML=t:z.textContent=t;let X=null;if(D){if(X=document.createElement("div"),X.className="ts-toast-content",X.appendChild(I),c){const t=document.createElement("div");t.className="ts-toast-title",t.id=`${H}-title`,t.textContent=c,X.appendChild(t),j.setAttribute("aria-labelledby",t.id)}X.appendChild(z),j.setAttribute("aria-describedby",z.id),j.appendChild(X)}else j.appendChild(z);let Y=null;D&&m&&("textarea"===m?(Y=document.createElement("textarea"),Y.rows=3):(Y=document.createElement("input"),Y.type="text"===m||"email"===m||"password"===m||"number"===m?m:"text"),Y.className="ts-toast-input",Y.placeholder=p,Y.value=f,"textarea"!==m&&Y.addEventListener("keydown",(t=>{"Enter"===t.key&&(t.preventDefault(),K(!0))})),j.appendChild(Y));let W=null,F=null,K=null,G=()=>{};if(D){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,h&&(t.style.background=h),b&&(t.style.color=b),y&&(e.style.background=y),g&&(e.style.color=g),W.appendChild(t),W.appendChild(e),j.appendChild(W),j.result=new Promise((t=>{F=t}));let s=!1;K=t=>{if(s)return;s=!0;const e=t?!Y||Y.value:!!Y&&null;F&&F(e),t&&"function"==typeof v&&v(e,j),t||"function"!=typeof x||x(j),"function"==typeof w&&w(e,j),G(),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(!D&&k&&"object"==typeof k&&k.text){const t=document.createElement("button");t.className="ts-toast-action",t.type="button",t.textContent=k.text,t.addEventListener("click",(t=>{t.stopPropagation(),"function"==typeof k.onClick&&k.onClick(j),j._dismiss()})),j.appendChild(t)}let V=null;!D&&S&&a>0&&!B&&(V=document.createElement("div"),V.className="ts-toast-progress",V.style.animation=`ts-toast-progress ${a}ms linear forwards`,V.style.animationPlayState="paused",j.appendChild(V));let J=null;r&&(J=document.createElement("div"),J.className="ts-toast-loader",j.appendChild(J));let Q=null,U=null;if(D&&T){Q=document.createElement("div");const t=tsToastOpenModals>0?" ts-toast-overlay-stacked":"";if(Q.className="ts-toast-overlay"+t+(e.position?` ${s}`:""),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="&times;",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.${s}`);t||(t=document.createElement("div"),t.className=`ts-toast-container ${s}`,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(D){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},s=t=>{if(!e())return;if("Escape"===t.key&&_)return t.preventDefault(),void K(!1);if("Tab"!==t.key)return;const s=tsToastFocusable(j);if(!s.length)return void t.preventDefault();const o=s[0],n=s[s.length-1];j.contains(document.activeElement)?t.shiftKey&&document.activeElement===o?(t.preventDefault(),n.focus()):t.shiftKey||document.activeElement!==n||(t.preventDefault(),o.focus()):(t.preventDefault(),(t.shiftKey?n:o).focus())};document.addEventListener("keydown",s,!0),G=()=>{document.removeEventListener("keydown",s,!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()},(Y||j.querySelector(".ts-toast-btn.confirm")||j).focus()}O&&"function"==typeof O&&O(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(I)||(D&&X?X.appendChild(I):j.appendChild(I)))}),Z),r||D||j.contains(I)||j.appendChild(I),j._onDismiss="function"==typeof P?P:null;let tt=a,et=null,st=0;const ot=()=>{et&&(clearTimeout(et),et=null)},nt=()=>{D||tt<=0||et||(st=Date.now(),et=setTimeout((()=>{et=null,j._dismiss()}),tt),V&&(V.style.animationPlayState="running"))},at=()=>{et&&(clearTimeout(et),et=null,tt-=Date.now()-st,V&&(V.style.animationPlayState="paused"))};if(j._dismiss=()=>{j._removing||(j._removing=!0,ot(),R(j,(()=>{j._onDismiss&&j._onDismiss(j),U&&!U.children.length&&U.remove()})))},j._setDuration=t=>{ot(),tt=t,nt()},nt(),!D&&L&&(j.addEventListener("mouseenter",at),j.addEventListener("mouseleave",nt),j.addEventListener("focusin",at),j.addEventListener("focusout",nt)),!D&&M&&j.addEventListener("click",(()=>{N&&"function"==typeof N&&N(j),j._dismiss()})),!D){let t=0,e=0,s=0;j.addEventListener("touchstart",(s=>{t=s.changedTouches[0].screenX,e=s.changedTouches[0].screenY}),{passive:!0}),j.addEventListener("touchend",(o=>{s=o.changedTouches[0].screenX;const n=Math.abs(t-s),a=Math.abs(e-o.changedTouches[0].screenY);n>50&&n>a&&j._dismiss()}))}return j.close=()=>{D?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,s={}){const{type:o=null,icon:n=null,showLoader:a=!1,duration:i=3e3,allowHtml:r=!1,onDismiss:l=null}=s,c=t.querySelector(".ts-toast-loader"),d=t.querySelector(".ts-toast-icon");c&&c.remove(),o&&(["success","error","info","warning"].forEach((e=>t.classList.remove(`ts-toast-${e}`))),t.classList.add("ts-toast",`ts-toast-${o}`,"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");m.className="ts-toast-icon",m.style.display="flex";let p=Boolean(n);if(n?m.textContent=n:p=tsToastPaintIcon(m,o),p){(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 s=toast(t,{...e,type:e.type||"info",duration:0,showLoader:!0,icon:null});requestAnimationFrame((()=>{s.classList.add("ts-toast-show")}));const o=s.querySelector(".ts-toast-loader");let n=s.querySelector(".ts-toast-icon");return n||(n=document.createElement("span"),n.className="ts-toast-icon",n.style.display="flex",s.appendChild(n)),s._managedByLoading=!0,o&&setTimeout((()=>{s._managedByLoading||o.classList.add("done")}),2e3),{update:(t,e={})=>{s._managedByLoading=!1,toast.update(s,t,{...e,showLoader:!1})},close:()=>{s._managedByLoading=!1,s.close()}}},toast.confirm=function(t,e={}){return new Promise((s=>{toast(t,{...e,mode:"confirm",duration:0,dismissOnClick:!1,onResult:t=>s(t)})}))},toast.promise=function(t,e={},s={}){const{loading:o="Loading…",success:n="Done",error:a="Something went wrong"}=e,i=toast.loading(o,s),r=(t,e)=>"function"==typeof t?t(e):t;return Promise.resolve(t).then((t=>(i.update(r(n,t),{type:"success",duration:s.duration}),t)),(t=>{throw i.update(r(a,t),{type:"error",duration:s.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.6.0";
5
+ const TS_TOAST_VERSION = "6.0.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';
@@ -28,6 +28,28 @@ const tsToastFocusable = (root) => Array.from(
28
28
 
29
29
  let tsToastIdCounter = 0;
30
30
 
31
+ // Icons are inline SVG rather than animated GIFs. The four GIFs were 323 kB — fifteen
32
+ // times the whole library — were 400x400 for a 30px slot, and each one cost a network
33
+ // round trip before the icon could appear. The glyph is stroked so CSS can draw it on,
34
+ // once, when the toast appears.
35
+ const TS_TOAST_GLYPHS = {
36
+ success: '<path class="ts-toast-glyph" style="--ts-len:26" d="M7 12.6 L10.4 16 L17 8.8"/>',
37
+ error: '<path class="ts-toast-glyph" style="--ts-len:23" d="M8.3 8.3 L15.7 15.7 M15.7 8.3 L8.3 15.7"/>',
38
+ info: '<circle class="ts-toast-dot" cx="12" cy="7.4" r="1.5"/><path class="ts-toast-glyph" style="--ts-len:7" d="M12 11 L12 17"/>',
39
+ warning: '<path class="ts-toast-glyph" style="--ts-len:8" d="M12 6.6 L12 14"/><circle class="ts-toast-dot" cx="12" cy="17.3" r="1.5"/>'
40
+ };
41
+
42
+ // Fills the icon span for a type. Returns false when the type has no icon.
43
+ const tsToastPaintIcon = (el, type) => {
44
+ const glyph = TS_TOAST_GLYPHS[type];
45
+ if (!glyph) return false;
46
+ // Fixed internal markup, never caller input.
47
+ el.innerHTML =
48
+ '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">' +
49
+ '<circle class="ts-toast-ring" cx="12" cy="12" r="12"/>' + glyph + '</svg>';
50
+ return true;
51
+ };
52
+
31
53
  // Load the stylesheet from the CDN, unless the page opted out by importing it itself
32
54
  // (set window.TS_TOAST_NO_CSS = true before loading, or ship assets/css/toast.css yourself).
33
55
  (function loadStylesheet() {
@@ -53,6 +75,7 @@ let tsToastIdCounter = 0;
53
75
  .ts-toast-container.bottom-center { bottom: 1rem; left: 50%; transform: translateX(-50%); align-items: center; }
54
76
  .ts-toast-container.center { top: 50%; left: 50%; transform: translate(-50%, -50%); align-items: center; }
55
77
  .ts-toast-overlay.center { align-items: center; justify-content: center; }
78
+ .ts-toast-overlay.ts-toast-overlay-stacked { background: transparent; }
56
79
  @keyframes ts-toast-progress { from { transform: scaleX(1); } to { transform: scaleX(0); } }
57
80
  .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
81
  .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; }
@@ -70,7 +93,7 @@ let tsToastIdCounter = 0;
70
93
  .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; }
71
94
  .ts-toast.ts-toast-confirm .ts-toast-close:hover { background: rgba(0,0,0,0.06); }
72
95
  .ts-toast.ts-toast-confirm .ts-toast-icon { width: 64px; height: 64px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; }
73
- .ts-toast.ts-toast-confirm .ts-toast-icon img { width: 36px; height: 36px; }
96
+ .ts-toast.ts-toast-confirm .ts-toast-icon svg { width: 36px; height: 36px; }
74
97
  .ts-toast.ts-toast-confirm.ts-toast-success .ts-toast-icon { background: #dcfce7; }
75
98
  .ts-toast.ts-toast-confirm.ts-toast-info .ts-toast-icon { background: #dbeafe; }
76
99
  .ts-toast.ts-toast-confirm.ts-toast-warning .ts-toast-icon { background: #fef3c7; }
@@ -119,9 +142,9 @@ const toast = function (message, options = {}) {
119
142
  action = null,
120
143
  // Escape cancels a confirm dialog
121
144
  closeOnEscape = true,
122
- // `message` is written as HTML for backwards compatibility. Pass false to
123
- // render it as plain text, which is what you want for anything user-supplied.
124
- allowHtml = true,
145
+ // `message` is rendered as plain text. Pass true only for markup you wrote
146
+ // yourself: any string built from user input becomes executable HTML.
147
+ allowHtml = false,
125
148
  // interactions
126
149
  dismissOnClick = true, // ignored if confirm-mode
127
150
  onClick = null, // Custom onClick event listener
@@ -215,27 +238,15 @@ const toast = function (message, options = {}) {
215
238
  if (icon) {
216
239
  iconElement.textContent = icon;
217
240
  } else {
218
- const img = document.createElement('img');
219
- img.alt = '';
220
- img.setAttribute('aria-hidden', 'true');
221
- img.style.width = '30px';
222
- img.style.height = '30px';
223
- img.style.objectFit = 'contain';
224
-
225
- // No cache-buster: these GIFs are immutable per version, so let the
226
- // browser and the CDN actually cache them.
227
- const iconFile = { success: 'success.gif', error: 'error.gif', info: 'info.gif', warning: 'warning.gif' }[type];
228
- if (iconFile) img.src = `${TS_TOAST_CDN}/assets/img/${iconFile}`;
229
-
230
- iconElement.appendChild(img);
241
+ tsToastPaintIcon(iconElement, type);
231
242
  }
232
243
 
233
244
  // Create Body
234
245
  const toastBody = document.createElement('div');
235
246
  toastBody.className = 'ts-toast-body';
236
247
  toastBody.id = `${uid}-body`;
237
- // HTML by default for backwards compatibility; pass allowHtml: false for
238
- // anything that came from a user.
248
+ // Text by default. innerHTML runs event handlers such as <img onerror>, so a
249
+ // message assembled from user input was a scripting hole in every caller.
239
250
  if (allowHtml) toastBody.innerHTML = message;
240
251
  else toastBody.textContent = message;
241
252
 
@@ -383,7 +394,10 @@ const toast = function (message, options = {}) {
383
394
  overlay = document.createElement('div');
384
395
  // A modal centres by default. The `position` default of 'top-right' is meant
385
396
  // for toasts; applying it here parked the dialog in a corner of the backdrop.
386
- overlay.className = 'ts-toast-overlay' + (options.position ? ` ${position}` : '');
397
+ // Each backdrop paints its own 50% black, so a second dialog opening over a
398
+ // first turned the page almost fully dark. Only the bottom one dims.
399
+ const stacked = tsToastOpenModals > 0 ? ' ts-toast-overlay-stacked' : '';
400
+ overlay.className = 'ts-toast-overlay' + stacked + (options.position ? ` ${position}` : '');
387
401
  document.body.appendChild(overlay);
388
402
  overlay.appendChild(toastElement);
389
403
  if (showClose) {
@@ -664,7 +678,7 @@ const toast = function (message, options = {}) {
664
678
  icon = null,
665
679
  showLoader = false,
666
680
  duration = 3000, // Default duration (in ms); 0 keeps the toast on screen
667
- allowHtml = true,
681
+ allowHtml = false,
668
682
  onDismiss = null // Replaces the callback the toast was created with
669
683
  } = options;
670
684
 
@@ -696,25 +710,16 @@ const toast = function (message, options = {}) {
696
710
  iconElement.className = 'ts-toast-icon';
697
711
  iconElement.style.display = 'flex';
698
712
 
713
+ let hasIcon = Boolean(icon);
699
714
  if (icon) {
700
715
  iconElement.textContent = icon;
701
716
  } else {
702
- const img = document.createElement('img');
703
- img.alt = '';
704
- img.setAttribute('aria-hidden', 'true');
705
- img.style.width = '30px';
706
- img.style.height = '30px';
707
- img.style.objectFit = 'contain';
708
-
709
- const iconFile = { success: 'success.gif', error: 'error.gif', info: 'info.gif', warning: 'warning.gif' }[type];
710
- if (iconFile) img.src = `${TS_TOAST_CDN}/assets/img/${iconFile}`;
711
-
712
- iconElement.appendChild(img);
717
+ hasIcon = tsToastPaintIcon(iconElement, type);
713
718
  }
714
719
 
715
720
  // 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]')) {
721
+ // icon this used to append an empty icon element.
722
+ if (hasIcon) {
718
723
  const contentRow = toastElement.querySelector('.ts-toast-content');
719
724
  (contentRow || toastElement).appendChild(iconElement);
720
725
  }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file