@tsirosgeorge/toastnotification 5.6.1 → 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%;
@@ -348,12 +328,8 @@
348
328
 
349
329
  /* Tiny utility: flex icon container */
350
330
  /* Scoped utility to avoid conflicts */
351
- .ts-toast-d-flex { display: inline-flex; align-items: center; }
352
331
 
353
332
  /* Prevent scroll behind confirm overlay */
354
- body.ts-toast-no-scroll {
355
- overflow: hidden;
356
- }
357
333
 
358
334
  /* Progress bar counting down the remaining time */
359
335
  @keyframes ts-toast-progress {
@@ -400,3 +376,54 @@ body.ts-toast-no-scroll {
400
376
  .ts-toast-container .ts-toast-action:hover {
401
377
  background: var(--toast-action-hover, rgba(59, 130, 246, 0.12));
402
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-overlay.ts-toast-overlay-stacked{background:0 0}.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.1",
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.1";
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() {
@@ -71,7 +93,7 @@ let tsToastIdCounter = 0;
71
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; }
72
94
  .ts-toast.ts-toast-confirm .ts-toast-close:hover { background: rgba(0,0,0,0.06); }
73
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; }
74
- .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; }
75
97
  .ts-toast.ts-toast-confirm.ts-toast-success .ts-toast-icon { background: #dcfce7; }
76
98
  .ts-toast.ts-toast-confirm.ts-toast-info .ts-toast-icon { background: #dbeafe; }
77
99
  .ts-toast.ts-toast-confirm.ts-toast-warning .ts-toast-icon { background: #fef3c7; }
@@ -120,9 +142,9 @@ const toast = function (message, options = {}) {
120
142
  action = null,
121
143
  // Escape cancels a confirm dialog
122
144
  closeOnEscape = true,
123
- // `message` is written as HTML for backwards compatibility. Pass false to
124
- // render it as plain text, which is what you want for anything user-supplied.
125
- 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,
126
148
  // interactions
127
149
  dismissOnClick = true, // ignored if confirm-mode
128
150
  onClick = null, // Custom onClick event listener
@@ -216,27 +238,15 @@ const toast = function (message, options = {}) {
216
238
  if (icon) {
217
239
  iconElement.textContent = icon;
218
240
  } else {
219
- const img = document.createElement('img');
220
- img.alt = '';
221
- img.setAttribute('aria-hidden', 'true');
222
- img.style.width = '30px';
223
- img.style.height = '30px';
224
- img.style.objectFit = 'contain';
225
-
226
- // No cache-buster: these GIFs are immutable per version, so let the
227
- // browser and the CDN actually cache them.
228
- const iconFile = { success: 'success.gif', error: 'error.gif', info: 'info.gif', warning: 'warning.gif' }[type];
229
- if (iconFile) img.src = `${TS_TOAST_CDN}/assets/img/${iconFile}`;
230
-
231
- iconElement.appendChild(img);
241
+ tsToastPaintIcon(iconElement, type);
232
242
  }
233
243
 
234
244
  // Create Body
235
245
  const toastBody = document.createElement('div');
236
246
  toastBody.className = 'ts-toast-body';
237
247
  toastBody.id = `${uid}-body`;
238
- // HTML by default for backwards compatibility; pass allowHtml: false for
239
- // 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.
240
250
  if (allowHtml) toastBody.innerHTML = message;
241
251
  else toastBody.textContent = message;
242
252
 
@@ -668,7 +678,7 @@ const toast = function (message, options = {}) {
668
678
  icon = null,
669
679
  showLoader = false,
670
680
  duration = 3000, // Default duration (in ms); 0 keeps the toast on screen
671
- allowHtml = true,
681
+ allowHtml = false,
672
682
  onDismiss = null // Replaces the callback the toast was created with
673
683
  } = options;
674
684
 
@@ -700,25 +710,16 @@ const toast = function (message, options = {}) {
700
710
  iconElement.className = 'ts-toast-icon';
701
711
  iconElement.style.display = 'flex';
702
712
 
713
+ let hasIcon = Boolean(icon);
703
714
  if (icon) {
704
715
  iconElement.textContent = icon;
705
716
  } else {
706
- const img = document.createElement('img');
707
- img.alt = '';
708
- img.setAttribute('aria-hidden', 'true');
709
- img.style.width = '30px';
710
- img.style.height = '30px';
711
- img.style.objectFit = 'contain';
712
-
713
- const iconFile = { success: 'success.gif', error: 'error.gif', info: 'info.gif', warning: 'warning.gif' }[type];
714
- if (iconFile) img.src = `${TS_TOAST_CDN}/assets/img/${iconFile}`;
715
-
716
- iconElement.appendChild(img);
717
+ hasIcon = tsToastPaintIcon(iconElement, type);
717
718
  }
718
719
 
719
720
  // Only attach an icon we actually have. Without a type and without an explicit
720
- // icon this used to append an empty <span><img></span>.
721
- if (icon || iconElement.querySelector('img[src]')) {
721
+ // icon this used to append an empty icon element.
722
+ if (hasIcon) {
722
723
  const contentRow = toastElement.querySelector('.ts-toast-content');
723
724
  (contentRow || toastElement).appendChild(iconElement);
724
725
  }
package/toast.min.js CHANGED
@@ -1 +1 @@
1
- "use strict";const TS_TOAST_VERSION="5.6.1",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.1";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 .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 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:y=null,cancelButtonBg:h=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:k=!1,action:L=null,closeOnEscape:_=!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,h&&(t.style.background=h),b&&(t.style.color=b),g&&(e.style.background=g),y&&(e.style.color=y),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&&L&&"object"==typeof L&&L.text){const t=document.createElement("button");t.className="ts-toast-action",t.type="button",t.textContent=L.text,t.addEventListener("click",(t=>{t.stopPropagation(),"function"==typeof L.onClick&&L.onClick(j),j._dismiss()})),j.appendChild(t)}let G=null;!M&&k&&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){Q=document.createElement("div");const t=tsToastOpenModals>0?" ts-toast-overlay-stacked":"";if(Q.className="ts-toast-overlay"+t+(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&&_)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.1";
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() {
@@ -71,7 +93,7 @@ let tsToastIdCounter = 0;
71
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; }
72
94
  .ts-toast.ts-toast-confirm .ts-toast-close:hover { background: rgba(0,0,0,0.06); }
73
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; }
74
- .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; }
75
97
  .ts-toast.ts-toast-confirm.ts-toast-success .ts-toast-icon { background: #dcfce7; }
76
98
  .ts-toast.ts-toast-confirm.ts-toast-info .ts-toast-icon { background: #dbeafe; }
77
99
  .ts-toast.ts-toast-confirm.ts-toast-warning .ts-toast-icon { background: #fef3c7; }
@@ -120,9 +142,9 @@ const toast = function (message, options = {}) {
120
142
  action = null,
121
143
  // Escape cancels a confirm dialog
122
144
  closeOnEscape = true,
123
- // `message` is written as HTML for backwards compatibility. Pass false to
124
- // render it as plain text, which is what you want for anything user-supplied.
125
- 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,
126
148
  // interactions
127
149
  dismissOnClick = true, // ignored if confirm-mode
128
150
  onClick = null, // Custom onClick event listener
@@ -216,27 +238,15 @@ const toast = function (message, options = {}) {
216
238
  if (icon) {
217
239
  iconElement.textContent = icon;
218
240
  } else {
219
- const img = document.createElement('img');
220
- img.alt = '';
221
- img.setAttribute('aria-hidden', 'true');
222
- img.style.width = '30px';
223
- img.style.height = '30px';
224
- img.style.objectFit = 'contain';
225
-
226
- // No cache-buster: these GIFs are immutable per version, so let the
227
- // browser and the CDN actually cache them.
228
- const iconFile = { success: 'success.gif', error: 'error.gif', info: 'info.gif', warning: 'warning.gif' }[type];
229
- if (iconFile) img.src = `${TS_TOAST_CDN}/assets/img/${iconFile}`;
230
-
231
- iconElement.appendChild(img);
241
+ tsToastPaintIcon(iconElement, type);
232
242
  }
233
243
 
234
244
  // Create Body
235
245
  const toastBody = document.createElement('div');
236
246
  toastBody.className = 'ts-toast-body';
237
247
  toastBody.id = `${uid}-body`;
238
- // HTML by default for backwards compatibility; pass allowHtml: false for
239
- // 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.
240
250
  if (allowHtml) toastBody.innerHTML = message;
241
251
  else toastBody.textContent = message;
242
252
 
@@ -668,7 +678,7 @@ const toast = function (message, options = {}) {
668
678
  icon = null,
669
679
  showLoader = false,
670
680
  duration = 3000, // Default duration (in ms); 0 keeps the toast on screen
671
- allowHtml = true,
681
+ allowHtml = false,
672
682
  onDismiss = null // Replaces the callback the toast was created with
673
683
  } = options;
674
684
 
@@ -700,25 +710,16 @@ const toast = function (message, options = {}) {
700
710
  iconElement.className = 'ts-toast-icon';
701
711
  iconElement.style.display = 'flex';
702
712
 
713
+ let hasIcon = Boolean(icon);
703
714
  if (icon) {
704
715
  iconElement.textContent = icon;
705
716
  } else {
706
- const img = document.createElement('img');
707
- img.alt = '';
708
- img.setAttribute('aria-hidden', 'true');
709
- img.style.width = '30px';
710
- img.style.height = '30px';
711
- img.style.objectFit = 'contain';
712
-
713
- const iconFile = { success: 'success.gif', error: 'error.gif', info: 'info.gif', warning: 'warning.gif' }[type];
714
- if (iconFile) img.src = `${TS_TOAST_CDN}/assets/img/${iconFile}`;
715
-
716
- iconElement.appendChild(img);
717
+ hasIcon = tsToastPaintIcon(iconElement, type);
717
718
  }
718
719
 
719
720
  // Only attach an icon we actually have. Without a type and without an explicit
720
- // icon this used to append an empty <span><img></span>.
721
- if (icon || iconElement.querySelector('img[src]')) {
721
+ // icon this used to append an empty icon element.
722
+ if (hasIcon) {
722
723
  const contentRow = toastElement.querySelector('.ts-toast-content');
723
724
  (contentRow || toastElement).appendChild(iconElement);
724
725
  }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file