ngx-dynamic-toast 0.0.1
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 +41 -0
- package/fesm2022/ngx-dynamic-toast.mjs +791 -0
- package/fesm2022/ngx-dynamic-toast.mjs.map +1 -0
- package/package.json +23 -0
- package/types/ngx-dynamic-toast.d.ts +238 -0
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, inject, ElementRef, NgZone, ChangeDetectorRef, signal, computed, effect, ViewEncapsulation, ChangeDetectionStrategy, Component, ApplicationRef, EnvironmentInjector, createComponent, Injectable } from '@angular/core';
|
|
3
|
+
import { NgTemplateOutlet, DOCUMENT } from '@angular/common';
|
|
4
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
5
|
+
|
|
6
|
+
const HEIGHT = 40;
|
|
7
|
+
const WIDTH = 350;
|
|
8
|
+
const DEFAULT_ROUNDNESS = 16;
|
|
9
|
+
const DURATION_MS = 600;
|
|
10
|
+
const DURATION_S = DURATION_MS / 1000;
|
|
11
|
+
const DEFAULT_TOAST_DURATION = 6000;
|
|
12
|
+
const EXIT_DURATION = DEFAULT_TOAST_DURATION * 0.1;
|
|
13
|
+
const AUTO_EXPAND_DELAY = DEFAULT_TOAST_DURATION * 0.025;
|
|
14
|
+
const AUTO_COLLAPSE_DELAY = DEFAULT_TOAST_DURATION - 2000;
|
|
15
|
+
const BLUR_RATIO = 0.5;
|
|
16
|
+
const PILL_PADDING = 10;
|
|
17
|
+
const MIN_EXPAND_RATIO = 2.25;
|
|
18
|
+
const SWAP_COLLAPSE_MS = 200;
|
|
19
|
+
const HEADER_EXIT_MS = DURATION_MS * 0.7;
|
|
20
|
+
|
|
21
|
+
const baseSvg = (title, body, extra) => `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ${extra ?? ""}><title>${title}</title>${body}</svg>`;
|
|
22
|
+
const ICONS = {
|
|
23
|
+
arrowRight: baseSvg("Arrow Right", `<path d="M5 12h14" /><path d="m12 5 7 7-7 7" />`),
|
|
24
|
+
lifeBuoy: baseSvg("Life Buoy", `<circle cx="12" cy="12" r="10" /><path d="m4.93 4.93 4.24 4.24" /><path d="m14.83 9.17 4.24-4.24" /><path d="m14.83 14.83 4.24 4.24" /><path d="m9.17 14.83-4.24 4.24" /><circle cx="12" cy="12" r="4" />`),
|
|
25
|
+
loaderCircle: (extra) => baseSvg("Loader Circle", `<path d="M21 12a9 9 0 1 1-6.219-8.56" />`, extra),
|
|
26
|
+
x: baseSvg("X", `<path d="M18 6 6 18" /><path d="m6 6 12 12" />`),
|
|
27
|
+
circleAlert: baseSvg("Circle Alert", `<circle cx="12" cy="12" r="10" /><line x1="12" x2="12" y1="8" y2="12" /><line x1="12" x2="12.01" y1="16" y2="16" />`),
|
|
28
|
+
check: baseSvg("Check", `<path d="M20 6 9 17l-5-5" />`),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
class DynamicToastComponent {
|
|
32
|
+
toast = input.required(...(ngDevMode ? [{ debugName: "toast" }] : []));
|
|
33
|
+
pillAlign = input("left", ...(ngDevMode ? [{ debugName: "pillAlign" }] : []));
|
|
34
|
+
expandEdge = input("bottom", ...(ngDevMode ? [{ debugName: "expandEdge" }] : []));
|
|
35
|
+
canExpand = input(true, ...(ngDevMode ? [{ debugName: "canExpand" }] : []));
|
|
36
|
+
interruptKey = input(undefined, ...(ngDevMode ? [{ debugName: "interruptKey" }] : []));
|
|
37
|
+
dismissed = output();
|
|
38
|
+
entered = output();
|
|
39
|
+
left = output();
|
|
40
|
+
HEIGHT = HEIGHT;
|
|
41
|
+
WIDTH = WIDTH;
|
|
42
|
+
sanitizer = inject(DomSanitizer);
|
|
43
|
+
hostEl = inject(ElementRef);
|
|
44
|
+
ngZone = inject(NgZone);
|
|
45
|
+
cdr = inject(ChangeDetectorRef);
|
|
46
|
+
hasMeasured = false;
|
|
47
|
+
roHeader = null;
|
|
48
|
+
roContent = null;
|
|
49
|
+
headerExitTimer = null;
|
|
50
|
+
autoExpandTimer = null;
|
|
51
|
+
autoCollapseTimer = null;
|
|
52
|
+
swapTimer = null;
|
|
53
|
+
lastRefreshKey;
|
|
54
|
+
pendingSwap = null;
|
|
55
|
+
pointerStartY = null;
|
|
56
|
+
ready = signal(false, ...(ngDevMode ? [{ debugName: "ready" }] : []));
|
|
57
|
+
isExpanded = signal(true, ...(ngDevMode ? [{ debugName: "isExpanded" }] : [])); // Default to expanded if description exists
|
|
58
|
+
pillWidth = signal(0, ...(ngDevMode ? [{ debugName: "pillWidth" }] : []));
|
|
59
|
+
contentHeight = signal(0, ...(ngDevMode ? [{ debugName: "contentHeight" }] : []));
|
|
60
|
+
// view = signal<View>({
|
|
61
|
+
// title: "",
|
|
62
|
+
// description: "",
|
|
63
|
+
// state: "success",
|
|
64
|
+
// iconSvg: null,
|
|
65
|
+
// fill: "#FFFFFF",
|
|
66
|
+
// });
|
|
67
|
+
// Use a default toast state to avoid NG0950 during initialization
|
|
68
|
+
view = computed(() => {
|
|
69
|
+
try {
|
|
70
|
+
const t = this.toast();
|
|
71
|
+
return {
|
|
72
|
+
title: t.title,
|
|
73
|
+
description: t.description,
|
|
74
|
+
contentTemplate: t.contentTemplate,
|
|
75
|
+
state: t.state,
|
|
76
|
+
iconSvg: t.iconSvg,
|
|
77
|
+
styles: t.styles,
|
|
78
|
+
button: t.button,
|
|
79
|
+
fill: t.fill ?? "#151515",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
// Return safe default if input is not yet bound
|
|
84
|
+
return {
|
|
85
|
+
title: "",
|
|
86
|
+
state: "info",
|
|
87
|
+
fill: "#151515",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}, ...(ngDevMode ? [{ debugName: "view" }] : []));
|
|
91
|
+
// headerLayerCurrent = signal<HeaderLayer>({
|
|
92
|
+
// key: "",
|
|
93
|
+
// view: this.view(),
|
|
94
|
+
// });
|
|
95
|
+
// headerLayerPrev = signal<HeaderLayer | null>(null);
|
|
96
|
+
filterId = `dt-gooey-${Math.random().toString(36).slice(2, 8)}`;
|
|
97
|
+
hasDesc = computed(() => {
|
|
98
|
+
const v = this.view();
|
|
99
|
+
return (Boolean(v.description) || Boolean(v.contentTemplate) || Boolean(v.button));
|
|
100
|
+
}, ...(ngDevMode ? [{ debugName: "hasDesc" }] : []));
|
|
101
|
+
isLoading = computed(() => this.view().state === "loading", ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
102
|
+
allowExpand = computed(() => {
|
|
103
|
+
if (this.isLoading())
|
|
104
|
+
return false;
|
|
105
|
+
const interrupt = this.interruptKey();
|
|
106
|
+
const id = this.toast().id;
|
|
107
|
+
return this.canExpand() ?? (!interrupt || interrupt === id);
|
|
108
|
+
}, ...(ngDevMode ? [{ debugName: "allowExpand" }] : []));
|
|
109
|
+
open = computed(() => this.hasDesc() && this.isExpanded() && !this.isLoading(), ...(ngDevMode ? [{ debugName: "open" }] : []));
|
|
110
|
+
resolvedRoundness = computed(() => {
|
|
111
|
+
try {
|
|
112
|
+
return Math.max(0, this.toast().roundness ?? DEFAULT_ROUNDNESS);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return DEFAULT_ROUNDNESS;
|
|
116
|
+
}
|
|
117
|
+
}, ...(ngDevMode ? [{ debugName: "resolvedRoundness" }] : []));
|
|
118
|
+
blur = computed(() => this.resolvedRoundness() * BLUR_RATIO, ...(ngDevMode ? [{ debugName: "blur" }] : []));
|
|
119
|
+
minExpanded = computed(() => HEIGHT * MIN_EXPAND_RATIO, ...(ngDevMode ? [{ debugName: "minExpanded" }] : []));
|
|
120
|
+
rawExpanded = computed(() => {
|
|
121
|
+
const min = this.minExpanded();
|
|
122
|
+
try {
|
|
123
|
+
if (!this.hasDesc())
|
|
124
|
+
return min;
|
|
125
|
+
return Math.max(min, HEIGHT + this.contentHeight());
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return min;
|
|
129
|
+
}
|
|
130
|
+
}, ...(ngDevMode ? [{ debugName: "rawExpanded" }] : []));
|
|
131
|
+
frozenExpanded = signal(HEIGHT * MIN_EXPAND_RATIO, ...(ngDevMode ? [{ debugName: "frozenExpanded" }] : []));
|
|
132
|
+
expanded = computed(() => this.open() ? this.rawExpanded() : this.frozenExpanded(), ...(ngDevMode ? [{ debugName: "expanded" }] : []));
|
|
133
|
+
svgHeight = computed(() => this.hasDesc() ? Math.max(this.expanded(), this.minExpanded()) : HEIGHT, ...(ngDevMode ? [{ debugName: "svgHeight" }] : []));
|
|
134
|
+
expandedContent = computed(() => Math.max(0, this.expanded() - HEIGHT), ...(ngDevMode ? [{ debugName: "expandedContent" }] : []));
|
|
135
|
+
resolvedPillWidth = computed(() => Math.max(this.pillWidth() || HEIGHT, HEIGHT), ...(ngDevMode ? [{ debugName: "resolvedPillWidth" }] : []));
|
|
136
|
+
pillHeight = computed(() => HEIGHT + this.blur() * 3, ...(ngDevMode ? [{ debugName: "pillHeight" }] : []));
|
|
137
|
+
pillX = computed(() => {
|
|
138
|
+
const w = this.resolvedPillWidth();
|
|
139
|
+
const pos = this.pillAlign();
|
|
140
|
+
if (pos === "right")
|
|
141
|
+
return WIDTH - w;
|
|
142
|
+
if (pos === "center")
|
|
143
|
+
return (WIDTH - w) / 2;
|
|
144
|
+
return 0;
|
|
145
|
+
}, ...(ngDevMode ? [{ debugName: "pillX" }] : []));
|
|
146
|
+
pillPath = computed(() => {
|
|
147
|
+
const w = this.resolvedPillWidth();
|
|
148
|
+
const h = HEIGHT;
|
|
149
|
+
const r = this.resolvedRoundness();
|
|
150
|
+
const align = this.pillAlign();
|
|
151
|
+
const open = this.open();
|
|
152
|
+
const x = this.pillX();
|
|
153
|
+
// Helper for rounded corner arc relative
|
|
154
|
+
const arc = (dx, dy) => `a ${r} ${r} 0 0 1 ${dx} ${dy}`;
|
|
155
|
+
let d = "";
|
|
156
|
+
// Start at Top-Left of the pill (relative to x)
|
|
157
|
+
// We'll draw in absolute coordinates or relative? Path commands usually absolute M x y is easiest.
|
|
158
|
+
// Top Left Corner
|
|
159
|
+
d += `M ${x} ${h - r}`; // Start before top-left corner? No, let's start top-left.
|
|
160
|
+
// Actually, standard rect path:
|
|
161
|
+
// Move to x, y+r
|
|
162
|
+
// Arc to x+r, y
|
|
163
|
+
// Line to x+w-r, y
|
|
164
|
+
// Arc to x+w, y+r
|
|
165
|
+
// Line to x+w, y+h-r
|
|
166
|
+
// Arc to x+w-r, y+h
|
|
167
|
+
// Line to x+r, y+h
|
|
168
|
+
// Arc to x, y+h-r
|
|
169
|
+
// Close
|
|
170
|
+
// Top Left
|
|
171
|
+
d += `M ${x} ${r} ${arc(r, -r)}`;
|
|
172
|
+
// Top Right
|
|
173
|
+
d += `L ${x + w - r} 0 ${arc(r, r)}`;
|
|
174
|
+
// Bottom Right
|
|
175
|
+
if (open && align === "right") {
|
|
176
|
+
d += `L ${x + w} ${h}`; // Sharp corner
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
d += `L ${x + w} ${h - r} ${arc(-r, r)}`; // Rounded
|
|
180
|
+
}
|
|
181
|
+
// Bottom Left
|
|
182
|
+
if (open && align === "left") {
|
|
183
|
+
d += `L ${x} ${h}`; // Sharp corner
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
d += `L ${x + r} ${h} ${arc(-r, -r)}`; // Rounded
|
|
187
|
+
}
|
|
188
|
+
d += "Z";
|
|
189
|
+
return d;
|
|
190
|
+
}, ...(ngDevMode ? [{ debugName: "pillPath" }] : []));
|
|
191
|
+
headerTransform = computed(() => {
|
|
192
|
+
const open = this.open();
|
|
193
|
+
const expand = this.expandEdge();
|
|
194
|
+
const ty = open ? (expand === "bottom" ? 3 : -3) : 0;
|
|
195
|
+
const scale = open ? 0.9 : 1;
|
|
196
|
+
return `translateY(${ty}px) scale(${scale})`;
|
|
197
|
+
}, ...(ngDevMode ? [{ debugName: "headerTransform" }] : []));
|
|
198
|
+
bodyPath = computed(() => {
|
|
199
|
+
const w = WIDTH;
|
|
200
|
+
const h = this.open() ? this.expandedContent() : 0;
|
|
201
|
+
const r = this.resolvedRoundness();
|
|
202
|
+
const y = HEIGHT - 1; // 1px overlap to ensure connection
|
|
203
|
+
const align = this.pillAlign();
|
|
204
|
+
if (h <= 0)
|
|
205
|
+
return "";
|
|
206
|
+
// Helper for rounded corner arc
|
|
207
|
+
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
|
|
208
|
+
const arc = (dx, dy) => `a ${r} ${r} 0 0 1 ${dx} ${dy}`;
|
|
209
|
+
let d = "";
|
|
210
|
+
// Top Left
|
|
211
|
+
if (align === "left") {
|
|
212
|
+
d += `M 0 ${y}`; // Sharp corner
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
d += `M 0 ${y + r} ${arc(r, -r)}`; // Rounded start
|
|
216
|
+
}
|
|
217
|
+
// Top Right
|
|
218
|
+
if (align === "right") {
|
|
219
|
+
d += `L ${w} ${y}`; // Sharp line to corner
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
d += `L ${w - r} ${y} ${arc(r, r)}`; // Line then rounded
|
|
223
|
+
}
|
|
224
|
+
// Bottom Right (Always rounded)
|
|
225
|
+
d += `L ${w} ${y + h - r} ${arc(-r, r)}`;
|
|
226
|
+
// Bottom Left (Always rounded)
|
|
227
|
+
d += `L ${r} ${y + h} ${arc(-r, -r)}`;
|
|
228
|
+
// Close path
|
|
229
|
+
d += "Z";
|
|
230
|
+
return d;
|
|
231
|
+
}, ...(ngDevMode ? [{ debugName: "bodyPath" }] : []));
|
|
232
|
+
constructor() {
|
|
233
|
+
// If no description, collapse immediately
|
|
234
|
+
effect(() => {
|
|
235
|
+
if (!this.hasDesc()) {
|
|
236
|
+
this.isExpanded.set(false);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// If has description, start expanded by default
|
|
240
|
+
// But we might want to respect auto-collapse later
|
|
241
|
+
this.isExpanded.set(true);
|
|
242
|
+
}
|
|
243
|
+
}, { allowSignalWrites: true });
|
|
244
|
+
queueMicrotask(() => {
|
|
245
|
+
this.ready.set(true);
|
|
246
|
+
this.ensureMeasurements();
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// private applyView(next: View) {
|
|
250
|
+
// const prev = this.view();
|
|
251
|
+
// const headerKey = `${next.state}-${next.title}`;
|
|
252
|
+
// const prevKey = `${prev.state}-${prev.title}`;
|
|
253
|
+
// this.view.set(next);
|
|
254
|
+
// if (headerKey !== prevKey) {
|
|
255
|
+
// const currentLayer: HeaderLayer = { key: headerKey, view: next };
|
|
256
|
+
// const prevLayer: HeaderLayer = { key: prevKey, view: prev };
|
|
257
|
+
// this.headerLayerPrev.set(prevLayer);
|
|
258
|
+
// this.headerLayerCurrent.set(currentLayer);
|
|
259
|
+
// if (this.headerExitTimer) window.clearTimeout(this.headerExitTimer);
|
|
260
|
+
// this.headerExitTimer = window.setTimeout(() => {
|
|
261
|
+
// this.headerExitTimer = null;
|
|
262
|
+
// this.headerLayerPrev.set(null);
|
|
263
|
+
// }, HEADER_EXIT_MS);
|
|
264
|
+
// } else {
|
|
265
|
+
// this.headerLayerCurrent.set({ key: headerKey, view: next });
|
|
266
|
+
// }
|
|
267
|
+
// }
|
|
268
|
+
ngOnDestroy() {
|
|
269
|
+
this.roHeader?.disconnect();
|
|
270
|
+
this.roContent?.disconnect();
|
|
271
|
+
if (this.headerExitTimer)
|
|
272
|
+
window.clearTimeout(this.headerExitTimer);
|
|
273
|
+
if (this.autoExpandTimer)
|
|
274
|
+
window.clearTimeout(this.autoExpandTimer);
|
|
275
|
+
if (this.autoCollapseTimer)
|
|
276
|
+
window.clearTimeout(this.autoCollapseTimer);
|
|
277
|
+
if (this.swapTimer)
|
|
278
|
+
window.clearTimeout(this.swapTimer);
|
|
279
|
+
}
|
|
280
|
+
ensureMeasurements() {
|
|
281
|
+
if (this.hasMeasured)
|
|
282
|
+
return;
|
|
283
|
+
this.hasMeasured = true;
|
|
284
|
+
const root = this.hostEl.nativeElement;
|
|
285
|
+
const inner = root.querySelector("[data-dt-header-inner][data-layer='current']");
|
|
286
|
+
const header = root.querySelector("[data-dt-header]");
|
|
287
|
+
const content = root.querySelector("[data-dt-description]");
|
|
288
|
+
if (!inner || !header)
|
|
289
|
+
return;
|
|
290
|
+
// Throttled measurement
|
|
291
|
+
let rafHeader = null;
|
|
292
|
+
const measureHeader = () => {
|
|
293
|
+
if (rafHeader)
|
|
294
|
+
return;
|
|
295
|
+
// Run outside angular to avoid infinite CD loops from RO
|
|
296
|
+
this.ngZone.runOutsideAngular(() => {
|
|
297
|
+
rafHeader = requestAnimationFrame(() => {
|
|
298
|
+
rafHeader = null;
|
|
299
|
+
const cs = getComputedStyle(header);
|
|
300
|
+
const pad = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight);
|
|
301
|
+
const w = inner.scrollWidth + pad + PILL_PADDING;
|
|
302
|
+
// Only update if difference is significant to stop 1px loops
|
|
303
|
+
if (Math.abs(w - this.pillWidth()) > 1.5) {
|
|
304
|
+
this.pillWidth.set(w);
|
|
305
|
+
this.cdr.detectChanges(); // Local detect changes only
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
let rafContent = null;
|
|
311
|
+
const measureContent = () => {
|
|
312
|
+
const el = root.querySelector("[data-dt-description]");
|
|
313
|
+
if (!el)
|
|
314
|
+
return;
|
|
315
|
+
if (rafContent)
|
|
316
|
+
return;
|
|
317
|
+
this.ngZone.runOutsideAngular(() => {
|
|
318
|
+
rafContent = requestAnimationFrame(() => {
|
|
319
|
+
rafContent = null;
|
|
320
|
+
if (Math.abs(el.scrollHeight - this.contentHeight()) > 1.5) {
|
|
321
|
+
this.contentHeight.set(el.scrollHeight);
|
|
322
|
+
this.cdr.detectChanges();
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
measureHeader();
|
|
328
|
+
measureContent();
|
|
329
|
+
this.roHeader = new ResizeObserver(() => measureHeader());
|
|
330
|
+
this.roHeader.observe(inner);
|
|
331
|
+
if (content) {
|
|
332
|
+
this.roContent = new ResizeObserver(() => measureContent());
|
|
333
|
+
this.roContent.observe(content);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
resolvedIcon(v) {
|
|
337
|
+
const icon = v.iconSvg ??
|
|
338
|
+
(v.state === "success"
|
|
339
|
+
? ICONS.check
|
|
340
|
+
: v.state === "loading"
|
|
341
|
+
? ICONS.loaderCircle('data-dt-icon="spin" aria-hidden="true"')
|
|
342
|
+
: v.state === "error"
|
|
343
|
+
? ICONS.x
|
|
344
|
+
: v.state === "warning"
|
|
345
|
+
? ICONS.circleAlert
|
|
346
|
+
: v.state === "info"
|
|
347
|
+
? ICONS.lifeBuoy
|
|
348
|
+
: ICONS.arrowRight);
|
|
349
|
+
return this.sanitizer.bypassSecurityTrustHtml(icon);
|
|
350
|
+
}
|
|
351
|
+
handleEnter() {
|
|
352
|
+
this.entered.emit(this.toast().id);
|
|
353
|
+
if (this.hasDesc())
|
|
354
|
+
this.isExpanded.set(true);
|
|
355
|
+
}
|
|
356
|
+
handleLeave() {
|
|
357
|
+
this.left.emit(this.toast().id);
|
|
358
|
+
this.isExpanded.set(false);
|
|
359
|
+
}
|
|
360
|
+
handleTransitionEnd(e) {
|
|
361
|
+
if (e.propertyName !== "height" && e.propertyName !== "transform")
|
|
362
|
+
return;
|
|
363
|
+
if (this.open())
|
|
364
|
+
return;
|
|
365
|
+
const pending = this.pendingSwap;
|
|
366
|
+
if (!pending)
|
|
367
|
+
return;
|
|
368
|
+
if (this.swapTimer) {
|
|
369
|
+
window.clearTimeout(this.swapTimer);
|
|
370
|
+
this.swapTimer = null;
|
|
371
|
+
}
|
|
372
|
+
// this.applyView(pending.payload);
|
|
373
|
+
this.pendingSwap = null;
|
|
374
|
+
}
|
|
375
|
+
handleButtonClick(e) {
|
|
376
|
+
e.preventDefault();
|
|
377
|
+
e.stopPropagation();
|
|
378
|
+
this.view().button?.onClick();
|
|
379
|
+
}
|
|
380
|
+
handlePointerDown(e) {
|
|
381
|
+
if (this.toast().exiting)
|
|
382
|
+
return;
|
|
383
|
+
const target = e.target;
|
|
384
|
+
if (target.closest("[data-dt-button]"))
|
|
385
|
+
return;
|
|
386
|
+
this.pointerStartY = e.clientY;
|
|
387
|
+
const el = e.currentTarget;
|
|
388
|
+
const SWIPE_DISMISS = 30;
|
|
389
|
+
const SWIPE_MAX = 20;
|
|
390
|
+
const onMove = (ev) => {
|
|
391
|
+
if (this.pointerStartY == null)
|
|
392
|
+
return;
|
|
393
|
+
const dy = ev.clientY - this.pointerStartY;
|
|
394
|
+
const sign = dy > 0 ? 1 : -1;
|
|
395
|
+
const clamped = Math.min(Math.abs(dy), SWIPE_MAX) * sign;
|
|
396
|
+
el.style.transform = `translateY(${clamped}px)`;
|
|
397
|
+
};
|
|
398
|
+
const onUp = (ev) => {
|
|
399
|
+
if (this.pointerStartY == null)
|
|
400
|
+
return;
|
|
401
|
+
const dy = ev.clientY - this.pointerStartY;
|
|
402
|
+
this.pointerStartY = null;
|
|
403
|
+
el.style.transform = "";
|
|
404
|
+
el.removeEventListener("pointermove", onMove);
|
|
405
|
+
el.removeEventListener("pointerup", onUp);
|
|
406
|
+
if (Math.abs(dy) > SWIPE_DISMISS) {
|
|
407
|
+
this.dismissed.emit(this.toast().id);
|
|
408
|
+
}
|
|
409
|
+
else if (Math.abs(dy) < 5) {
|
|
410
|
+
// It's a tap
|
|
411
|
+
this.handleTap();
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
el.setPointerCapture(e.pointerId);
|
|
415
|
+
el.addEventListener("pointermove", onMove, { passive: true });
|
|
416
|
+
el.addEventListener("pointerup", onUp, { passive: true });
|
|
417
|
+
}
|
|
418
|
+
handleTap() {
|
|
419
|
+
if (this.hasDesc()) {
|
|
420
|
+
if (this.isExpanded()) {
|
|
421
|
+
// 1 click to collapse if it has description and is open
|
|
422
|
+
this.isExpanded.set(false);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
// If already collapsed, another click closes the toast
|
|
426
|
+
this.dismissed.emit(this.toast().id);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
// If no description, a click simply closes it
|
|
431
|
+
this.dismissed.emit(this.toast().id);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: DynamicToastComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
435
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: DynamicToastComponent, isStandalone: true, selector: "dt-toast", inputs: { toast: { classPropertyName: "toast", publicName: "toast", isSignal: true, isRequired: true, transformFunction: null }, pillAlign: { classPropertyName: "pillAlign", publicName: "pillAlign", isSignal: true, isRequired: false, transformFunction: null }, expandEdge: { classPropertyName: "expandEdge", publicName: "expandEdge", isSignal: true, isRequired: false, transformFunction: null }, canExpand: { classPropertyName: "canExpand", publicName: "canExpand", isSignal: true, isRequired: false, transformFunction: null }, interruptKey: { classPropertyName: "interruptKey", publicName: "interruptKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismissed: "dismissed", entered: "entered", left: "left" }, ngImport: i0, template: "<button\n type=\"button\"\n data-dt-toast\n [attr.data-ready]=\"ready()\"\n [attr.data-expanded]=\"open()\"\n [attr.data-exiting]=\"toast().exiting || false\"\n [attr.data-edge]=\"expandEdge()\"\n [attr.data-position]=\"pillAlign()\"\n [attr.data-state]=\"view().state\"\n [class]=\"toast().className || ''\"\n [style.--_h]=\"HEIGHT + 'px'\"\n [style.--_pw]=\"(pillWidth() || HEIGHT) + 'px'\"\n [style.--_px]=\"pillX() + 'px'\"\n [style.--_ht]=\"headerTransform()\"\n [style.--_co]=\"open() ? 1 : 0\"\n [style.animation-duration]=\"(toast().duration || 6000) + 'ms'\"\n (mouseenter)=\"handleEnter()\"\n (mouseleave)=\"handleLeave()\"\n (transitionend)=\"handleTransitionEnd($event)\"\n (pointerdown)=\"handlePointerDown($event)\"\n>\n <div\n data-dt-canvas\n [attr.data-edge]=\"expandEdge()\"\n [style.filter]=\"'url(#' + filterId + ')'\"\n >\n <svg\n data-dt-svg\n [attr.width]=\"WIDTH\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"'0 0 ' + WIDTH + ' ' + svgHeight()\"\n >\n <defs>\n <filter\n [id]=\"filterId\"\n x=\"-20%\"\n y=\"-20%\"\n width=\"140%\"\n height=\"140%\"\n colorInterpolationFilters=\"sRGB\"\n >\n <feGaussianBlur\n in=\"SourceGraphic\"\n [attr.stdDeviation]=\"blur()\"\n result=\"blur\"\n />\n <feColorMatrix\n in=\"blur\"\n mode=\"matrix\"\n values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10\"\n result=\"goo\"\n />\n <feComposite in=\"SourceGraphic\" in2=\"goo\" operator=\"atop\" />\n </filter>\n </defs>\n\n <path\n data-dt-pill\n [attr.d]=\"pillPath()\"\n [attr.fill]=\"view().fill\"\n />\n\n <path\n data-dt-body\n [attr.d]=\"bodyPath()\"\n [attr.fill]=\"view().fill\"\n [attr.opacity]=\"open() ? 1 : 0\"\n />\n </svg>\n </div>\n\n <div #headerRef data-dt-header [attr.data-edge]=\"expandEdge()\">\n <div data-dt-header-stack>\n <div\n #innerRef\n data-dt-header-inner\n data-layer=\"current\"\n >\n <div\n data-dt-badge\n [attr.data-state]=\"view().state\"\n [class]=\"view().styles?.badge || ''\"\n [innerHTML]=\"resolvedIcon(view())\"\n ></div>\n <span\n data-dt-title\n [attr.data-state]=\"view().state\"\n [class]=\"view().styles?.title || ''\"\n >\n {{ view().title }}\n </span>\n </div>\n\n <!-- @if (headerLayerPrev(); as prev) {\n <div data-dt-header-inner data-layer=\"prev\" data-exiting=\"true\">\n <div\n data-dt-badge\n [attr.data-state]=\"prev.view.state\"\n [class]=\"prev.view.styles?.badge || ''\"\n [innerHTML]=\"resolvedIcon(prev.view)\"\n ></div>\n <span\n data-dt-title\n [attr.data-state]=\"prev.view.state\"\n [class]=\"prev.view.styles?.title || ''\"\n >\n {{ prev.view.title }}\n </span>\n </div>\n } -->\n </div>\n </div>\n\n @if (hasDesc()) {\n <div\n data-dt-content\n [attr.data-edge]=\"expandEdge()\"\n [attr.data-visible]=\"open()\"\n >\n <div\n #contentRef\n data-dt-description\n [class]=\"view().styles?.description || ''\"\n >\n @if (view().description) {\n <span>{{ view().description }}</span>\n }\n @if (view().contentTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"view().contentTemplate\"\n ></ng-container>\n }\n @if (view().button) {\n <a\n href=\"#\"\n data-dt-button\n [attr.data-state]=\"view().state\"\n [class]=\"view().styles?.button || ''\"\n (click)=\"handleButtonClick($event)\"\n >\n {{ view().button?.title }}\n </a>\n }\n </div>\n </div>\n }\n</button>\n\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
436
|
+
}
|
|
437
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: DynamicToastComponent, decorators: [{
|
|
438
|
+
type: Component,
|
|
439
|
+
args: [{ selector: "dt-toast", standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<button\n type=\"button\"\n data-dt-toast\n [attr.data-ready]=\"ready()\"\n [attr.data-expanded]=\"open()\"\n [attr.data-exiting]=\"toast().exiting || false\"\n [attr.data-edge]=\"expandEdge()\"\n [attr.data-position]=\"pillAlign()\"\n [attr.data-state]=\"view().state\"\n [class]=\"toast().className || ''\"\n [style.--_h]=\"HEIGHT + 'px'\"\n [style.--_pw]=\"(pillWidth() || HEIGHT) + 'px'\"\n [style.--_px]=\"pillX() + 'px'\"\n [style.--_ht]=\"headerTransform()\"\n [style.--_co]=\"open() ? 1 : 0\"\n [style.animation-duration]=\"(toast().duration || 6000) + 'ms'\"\n (mouseenter)=\"handleEnter()\"\n (mouseleave)=\"handleLeave()\"\n (transitionend)=\"handleTransitionEnd($event)\"\n (pointerdown)=\"handlePointerDown($event)\"\n>\n <div\n data-dt-canvas\n [attr.data-edge]=\"expandEdge()\"\n [style.filter]=\"'url(#' + filterId + ')'\"\n >\n <svg\n data-dt-svg\n [attr.width]=\"WIDTH\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"'0 0 ' + WIDTH + ' ' + svgHeight()\"\n >\n <defs>\n <filter\n [id]=\"filterId\"\n x=\"-20%\"\n y=\"-20%\"\n width=\"140%\"\n height=\"140%\"\n colorInterpolationFilters=\"sRGB\"\n >\n <feGaussianBlur\n in=\"SourceGraphic\"\n [attr.stdDeviation]=\"blur()\"\n result=\"blur\"\n />\n <feColorMatrix\n in=\"blur\"\n mode=\"matrix\"\n values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10\"\n result=\"goo\"\n />\n <feComposite in=\"SourceGraphic\" in2=\"goo\" operator=\"atop\" />\n </filter>\n </defs>\n\n <path\n data-dt-pill\n [attr.d]=\"pillPath()\"\n [attr.fill]=\"view().fill\"\n />\n\n <path\n data-dt-body\n [attr.d]=\"bodyPath()\"\n [attr.fill]=\"view().fill\"\n [attr.opacity]=\"open() ? 1 : 0\"\n />\n </svg>\n </div>\n\n <div #headerRef data-dt-header [attr.data-edge]=\"expandEdge()\">\n <div data-dt-header-stack>\n <div\n #innerRef\n data-dt-header-inner\n data-layer=\"current\"\n >\n <div\n data-dt-badge\n [attr.data-state]=\"view().state\"\n [class]=\"view().styles?.badge || ''\"\n [innerHTML]=\"resolvedIcon(view())\"\n ></div>\n <span\n data-dt-title\n [attr.data-state]=\"view().state\"\n [class]=\"view().styles?.title || ''\"\n >\n {{ view().title }}\n </span>\n </div>\n\n <!-- @if (headerLayerPrev(); as prev) {\n <div data-dt-header-inner data-layer=\"prev\" data-exiting=\"true\">\n <div\n data-dt-badge\n [attr.data-state]=\"prev.view.state\"\n [class]=\"prev.view.styles?.badge || ''\"\n [innerHTML]=\"resolvedIcon(prev.view)\"\n ></div>\n <span\n data-dt-title\n [attr.data-state]=\"prev.view.state\"\n [class]=\"prev.view.styles?.title || ''\"\n >\n {{ prev.view.title }}\n </span>\n </div>\n } -->\n </div>\n </div>\n\n @if (hasDesc()) {\n <div\n data-dt-content\n [attr.data-edge]=\"expandEdge()\"\n [attr.data-visible]=\"open()\"\n >\n <div\n #contentRef\n data-dt-description\n [class]=\"view().styles?.description || ''\"\n >\n @if (view().description) {\n <span>{{ view().description }}</span>\n }\n @if (view().contentTemplate) {\n <ng-container\n [ngTemplateOutlet]=\"view().contentTemplate\"\n ></ng-container>\n }\n @if (view().button) {\n <a\n href=\"#\"\n data-dt-button\n [attr.data-state]=\"view().state\"\n [class]=\"view().styles?.button || ''\"\n (click)=\"handleButtonClick($event)\"\n >\n {{ view().button?.title }}\n </a>\n }\n </div>\n </div>\n }\n</button>\n\n" }]
|
|
440
|
+
}], ctorParameters: () => [], propDecorators: { toast: [{ type: i0.Input, args: [{ isSignal: true, alias: "toast", required: true }] }], pillAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "pillAlign", required: false }] }], expandEdge: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandEdge", required: false }] }], canExpand: [{ type: i0.Input, args: [{ isSignal: true, alias: "canExpand", required: false }] }], interruptKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "interruptKey", required: false }] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], entered: [{ type: i0.Output, args: ["entered"] }], left: [{ type: i0.Output, args: ["left"] }] } });
|
|
441
|
+
|
|
442
|
+
const pillAlign = (pos) => pos.includes("right") ? "right" : pos.includes("center") ? "center" : "left";
|
|
443
|
+
const expandDir = (pos) => pos.startsWith("top") ? "bottom" : "top";
|
|
444
|
+
class DynamicToastViewportComponent {
|
|
445
|
+
position = input("top-right", ...(ngDevMode ? [{ debugName: "position" }] : []));
|
|
446
|
+
offset = input(undefined, ...(ngDevMode ? [{ debugName: "offset" }] : []));
|
|
447
|
+
service = inject(DynamicToastService);
|
|
448
|
+
hovering = signal(new Set(), ...(ngDevMode ? [{ debugName: "hovering" }] : []));
|
|
449
|
+
activeId = signal(undefined, ...(ngDevMode ? [{ debugName: "activeId" }] : []));
|
|
450
|
+
latestId = computed(() => {
|
|
451
|
+
const list = this.service.toasts();
|
|
452
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
453
|
+
if (!list[i].exiting)
|
|
454
|
+
return list[i].id;
|
|
455
|
+
}
|
|
456
|
+
return undefined;
|
|
457
|
+
}, ...(ngDevMode ? [{ debugName: "latestId" }] : []));
|
|
458
|
+
groups = computed(() => {
|
|
459
|
+
const toasts = this.service.toasts();
|
|
460
|
+
const byPos = new Map();
|
|
461
|
+
for (const t of toasts) {
|
|
462
|
+
const p = (t.position ?? this.position());
|
|
463
|
+
const arr = byPos.get(p);
|
|
464
|
+
if (arr)
|
|
465
|
+
arr.push(t);
|
|
466
|
+
else
|
|
467
|
+
byPos.set(p, [t]);
|
|
468
|
+
}
|
|
469
|
+
const offset = this.offset();
|
|
470
|
+
const off = offset === undefined
|
|
471
|
+
? undefined
|
|
472
|
+
: typeof offset === "object"
|
|
473
|
+
? offset
|
|
474
|
+
: { top: offset, right: offset, bottom: offset, left: offset };
|
|
475
|
+
const px = (v) => v === undefined ? undefined : typeof v === "number" ? `${v}px` : v;
|
|
476
|
+
const res = [];
|
|
477
|
+
for (const [pos, items] of byPos) {
|
|
478
|
+
const style = {};
|
|
479
|
+
if (off) {
|
|
480
|
+
if (pos.startsWith("top") && off.top !== undefined)
|
|
481
|
+
style.top = px(off.top);
|
|
482
|
+
if (pos.startsWith("bottom") && off.bottom !== undefined)
|
|
483
|
+
style.bottom = px(off.bottom);
|
|
484
|
+
if (pos.endsWith("left") && off.left !== undefined)
|
|
485
|
+
style.left = px(off.left);
|
|
486
|
+
if (pos.endsWith("right") && off.right !== undefined)
|
|
487
|
+
style.right = px(off.right);
|
|
488
|
+
}
|
|
489
|
+
res.push({
|
|
490
|
+
pos,
|
|
491
|
+
items,
|
|
492
|
+
pill: pillAlign(pos),
|
|
493
|
+
expand: expandDir(pos),
|
|
494
|
+
style: off ? style : undefined,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
return res;
|
|
498
|
+
}, ...(ngDevMode ? [{ debugName: "groups" }] : []));
|
|
499
|
+
onToastEnter(id) {
|
|
500
|
+
const next = new Set(this.hovering());
|
|
501
|
+
next.add(id);
|
|
502
|
+
this.hovering.set(next);
|
|
503
|
+
this.activeId.set(id);
|
|
504
|
+
this.service.pauseTimers();
|
|
505
|
+
}
|
|
506
|
+
onToastLeave(id) {
|
|
507
|
+
const next = new Set(this.hovering());
|
|
508
|
+
next.delete(id);
|
|
509
|
+
this.hovering.set(next);
|
|
510
|
+
if (next.size > 0)
|
|
511
|
+
return;
|
|
512
|
+
queueMicrotask(() => {
|
|
513
|
+
if (this.hovering().size > 0)
|
|
514
|
+
return;
|
|
515
|
+
this.activeId.set(this.latestId());
|
|
516
|
+
this.service.resumeTimers();
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: DynamicToastViewportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
520
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: DynamicToastViewportComponent, isStandalone: true, selector: "dt-viewport", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@for (group of groups(); track group.pos) {\n <section\n data-dt-viewport\n [attr.data-position]=\"group.pos\"\n aria-live=\"polite\"\n [style.top]=\"group.style?.top\"\n [style.right]=\"group.style?.right\"\n [style.bottom]=\"group.style?.bottom\"\n [style.left]=\"group.style?.left\"\n >\n @for (t of group.items; track t.id) {\n <dt-toast\n [toast]=\"t\"\n [pillAlign]=\"group.pill\"\n [expandEdge]=\"group.expand\"\n [canExpand]=\"activeId() === undefined || activeId() === t.id\"\n (entered)=\"onToastEnter($event)\"\n (left)=\"onToastLeave($event)\"\n (dismissed)=\"service.dismiss($event)\"\n />\n }\n </section>\n}\n\n", styles: ["[data-dt-viewport]{--sileo-spring-easing: linear( 0, .002 .6%, .007 1.2%, .015 1.8%, .026 2.4%, .041 3.1%, .06 3.8%, .108 5.3%, .157 6.6%, .214 8%, .467 13.7%, .577 16.3%, .631 17.7%, .682 19.1%, .73 20.5%, .771 21.8%, .808 23.1%, .844 24.5%, .874 25.8%, .903 27.2%, .928 28.6%, .952 30.1%, .972 31.6%, .988 33.1%, 1.01 35.7%, 1.025 38.5%, 1.034 41.6%, 1.038 45%, 1.035 50.1%, 1.012 64.2%, 1.003 73%, .999 83.7%, 1 );--sileo-duration: .6s;--sileo-height: 40px;--sileo-width: 350px;--sileo-state-success: oklch(.723 .219 142.136);--sileo-state-loading: oklch(.556 0 0);--sileo-state-error: oklch(.637 .237 25.331);--sileo-state-warning: oklch(.795 .184 86.047);--sileo-state-info: oklch(.685 .169 237.323);--sileo-state-action: oklch(.623 .214 259.815)}[data-dt-toast]{position:relative;cursor:pointer;pointer-events:auto;touch-action:none;border:0;background:transparent;padding:0;width:var(--sileo-width);height:var(--_h, var(--sileo-height));opacity:0;transform:translateZ(0) scale(.95);transform-origin:center;contain:layout style;overflow:visible;color:#fff;transition:all var(--sileo-duration) var(--sileo-spring-easing);filter:drop-shadow(0 4px 6px rgba(0,0,0,.1)) drop-shadow(0 10px 15px rgba(0,0,0,.2))}[data-dt-toast]:after{display:none!important}[data-dt-viewport]:not(:hover) [data-dt-toast]{position:absolute;right:1.5rem;margin:0!important;transition:all .6s cubic-bezier(.16,1,.3,1)}[data-dt-viewport][data-position$=left]:not(:hover) [data-dt-toast]{right:auto;left:1.5rem}[data-dt-viewport][data-position$=center]:not(:hover) [data-dt-toast]{right:auto;left:auto;position:relative;margin-bottom:-30px!important}[data-dt-viewport][data-position^=top]:not(:hover) [data-dt-toast]{top:1.5rem!important}[data-dt-viewport][data-position^=bottom]:not(:hover) [data-dt-toast]{bottom:1.5rem!important}[data-dt-viewport]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(1){z-index:5;transform:translateY(0) scale(1);opacity:1}[data-dt-viewport][data-position^=top]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(2){z-index:4;transform:translateY(-14px) scale(.95);opacity:1;filter:brightness(.9)}[data-dt-viewport][data-position^=bottom]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(2){z-index:4;transform:translateY(14px) scale(.95);opacity:1;filter:brightness(.9)}[data-dt-viewport][data-position^=top]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(3){z-index:3;transform:translateY(-28px) scale(.9);opacity:.8;filter:brightness(.8)}[data-dt-viewport][data-position^=bottom]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(3){z-index:3;transform:translateY(28px) scale(.9);opacity:.8;filter:brightness(.8)}[data-dt-viewport]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(n+4){opacity:0;pointer-events:none;transform:translateY(0) scale(.8);z-index:0}[data-dt-viewport]:hover [data-dt-toast]{position:relative;transform:none;opacity:1;filter:none;z-index:auto}[data-dt-viewport][data-position^=top]:hover [data-dt-toast]+[data-dt-toast]{margin-top:10px}[data-dt-viewport][data-position^=bottom]:hover [data-dt-toast]+[data-dt-toast]{margin-bottom:10px}[data-dt-toast][data-state=loading]{cursor:default}[data-dt-toast][data-ready=true]{opacity:1;transform:translateZ(0) scale(1);transition:transform calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),margin-bottom calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),margin-top calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing)}[data-dt-toast][data-ready=true][data-exiting=true]{opacity:0!important;pointer-events:none}[data-dt-viewport][data-position^=top] [data-dt-toast]:not([data-ready=true]){transform:translateY(-6px) scale(.95);opacity:0!important}[data-dt-viewport][data-position^=bottom] [data-dt-toast]:not([data-ready=true]){transform:translateY(6px) scale(.95);opacity:0!important}[data-dt-viewport][data-position^=top] [data-dt-toast][data-ready=true][data-exiting=true]{transform:translateY(-6px) scale(.95)}[data-dt-viewport][data-position^=bottom] [data-dt-toast][data-ready=true][data-exiting=true]{transform:translateY(6px) scale(.95)}[data-dt-canvas]{position:absolute;left:0;right:0;pointer-events:none;transform:translateZ(0);contain:layout style;overflow:visible}[data-dt-canvas][data-edge=top]{bottom:0;transform:scaleY(-1) translateZ(0)}[data-dt-canvas][data-edge=bottom]{top:0}[data-dt-svg]{overflow:visible}[data-dt-header]{position:absolute;z-index:20;display:flex;align-items:center;padding:.5rem;height:var(--sileo-height);overflow:hidden;left:var(--_px, 0px);transform:var(--_ht);max-width:var(--_pw)}[data-dt-toast][data-ready=true] [data-dt-header]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),left var(--sileo-duration) var(--sileo-spring-easing),max-width var(--sileo-duration) var(--sileo-spring-easing)}[data-dt-header][data-edge=top]{bottom:0}[data-dt-header][data-edge=bottom]{top:0}[data-dt-header-stack]{position:relative;display:inline-flex;align-items:center;height:100%}[data-dt-header-inner]{display:flex;align-items:center;gap:.5rem;white-space:nowrap;opacity:1;filter:blur(0px);transform:translateZ(0)}[data-dt-header-inner][data-layer=current]{position:relative;z-index:1;animation:dt-header-enter var(--sileo-duration) var(--sileo-spring-easing) both}[data-dt-header-inner][data-layer=prev]{position:absolute;left:0;top:0;z-index:0;pointer-events:none}[data-dt-header-inner][data-exiting=true]{animation:dt-header-exit calc(var(--sileo-duration) * .7) ease forwards}[data-dt-badge]{display:flex;height:24px;width:24px;flex-shrink:0;align-items:center;justify-content:center;padding:2px;box-sizing:border-box;border-radius:9999px;color:var(--sileo-tone, currentColor);background-color:var(--sileo-tone-bg, transparent)}[data-dt-title]{font-size:.825rem;line-height:1rem;font-weight:500;text-transform:capitalize;color:var(--sileo-tone, currentColor)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state]{--_c: var(--sileo-state-success)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state=loading]{--_c: var(--sileo-state-loading)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state=error]{--_c: var(--sileo-state-error)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state=warning]{--_c: var(--sileo-state-warning)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state=info]{--_c: var(--sileo-state-info)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state=action]{--_c: var(--sileo-state-action)}:is([data-dt-badge],[data-dt-title])[data-state]{--sileo-tone: var(--_c);--sileo-tone-bg: color-mix(in oklch, var(--_c) 20%, transparent)}[data-dt-content]{position:absolute;left:0;z-index:10;width:100%;pointer-events:none;opacity:var(--_co, 0)}[data-dt-content]:not([data-visible=true]){content-visibility:hidden}[data-dt-toast][data-ready=true] [data-dt-content]{transition:opacity calc(var(--sileo-duration) * .08) ease calc(var(--sileo-duration) * .04)}[data-dt-content][data-edge=top]{top:0}[data-dt-content][data-edge=bottom]{top:var(--sileo-height)}[data-dt-content][data-visible=true]{pointer-events:auto}[data-dt-toast][data-ready=true] [data-dt-content][data-visible=true]{transition:opacity calc(var(--sileo-duration) * .6) ease calc(var(--sileo-duration) * .3)}[data-dt-description]{width:100%;text-align:left;padding:1rem;font-size:.875rem;line-height:1.25rem;contain:layout style paint;content-visibility:auto}[data-dt-button]{display:flex;align-items:center;justify-content:center;height:1.75rem;padding:0 .625rem;margin-top:.75rem;border-radius:9999px;border:0;font-size:.75rem;font-weight:500;cursor:pointer;color:var(--sileo-btn-color, currentColor);background-color:var(--sileo-btn-bg, transparent);transition:background-color .15s ease}[data-dt-button]:hover{background-color:var(--sileo-btn-bg-hover, transparent)}[data-dt-button][data-state]{--sileo-btn-color: var(--_c);--sileo-btn-bg: color-mix(in oklch, var(--_c) 15%, transparent);--sileo-btn-bg-hover: color-mix(in oklch, var(--_c) 25%, transparent)}[data-dt-icon=spin]{animation:dt-spin 1s linear infinite}@keyframes dt-spin{to{transform:rotate(360deg)}}@keyframes dt-header-enter{0%{opacity:0;filter:blur(6px)}to{opacity:1;filter:blur(0px)}}@keyframes dt-header-exit{0%{opacity:1;filter:blur(0px)}to{opacity:0;filter:blur(6px)}}[data-dt-viewport]{position:fixed;z-index:50;display:flex;gap:0;padding:1.5rem;pointer-events:auto;max-width:calc(100vw - 1.5rem);contain:layout style;transition:all .3s ease;height:auto}[data-dt-viewport]{pointer-events:none}[data-dt-toast]{pointer-events:auto}[data-dt-viewport][data-position^=top] [data-dt-toast]:not([data-ready=true]){margin-bottom:calc(-1 * (var(--sileo-height) + .75rem))}[data-dt-viewport][data-position^=bottom] [data-dt-toast]:not([data-ready=true]){margin-top:calc(-1 * (var(--sileo-height) + .75rem))}[data-dt-viewport][data-position^=top]{top:0;flex-direction:column-reverse}[data-dt-viewport][data-position^=bottom]{bottom:0;flex-direction:column}[data-dt-viewport][data-position$=left]{left:0;align-items:flex-start}[data-dt-viewport][data-position$=right]{right:0;align-items:flex-end}[data-dt-viewport][data-position$=center]{left:50%;transform:translate(-50%);align-items:center}@media(prefers-reduced-motion:no-preference){[data-dt-toast][data-ready=true]:hover,[data-dt-toast][data-ready=true][data-exiting=true]{will-change:transform,opacity,height}}@media(prefers-reduced-motion:reduce){[data-dt-viewport],[data-dt-viewport] *,[data-dt-viewport] *:before,[data-dt-viewport] *:after{animation-duration:.01ms;animation-iteration-count:1;transition-duration:.01ms}}\n"], dependencies: [{ kind: "component", type: DynamicToastComponent, selector: "dt-toast", inputs: ["toast", "pillAlign", "expandEdge", "canExpand", "interruptKey"], outputs: ["dismissed", "entered", "left"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
521
|
+
}
|
|
522
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: DynamicToastViewportComponent, decorators: [{
|
|
523
|
+
type: Component,
|
|
524
|
+
args: [{ selector: "dt-viewport", standalone: true, imports: [DynamicToastComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "@for (group of groups(); track group.pos) {\n <section\n data-dt-viewport\n [attr.data-position]=\"group.pos\"\n aria-live=\"polite\"\n [style.top]=\"group.style?.top\"\n [style.right]=\"group.style?.right\"\n [style.bottom]=\"group.style?.bottom\"\n [style.left]=\"group.style?.left\"\n >\n @for (t of group.items; track t.id) {\n <dt-toast\n [toast]=\"t\"\n [pillAlign]=\"group.pill\"\n [expandEdge]=\"group.expand\"\n [canExpand]=\"activeId() === undefined || activeId() === t.id\"\n (entered)=\"onToastEnter($event)\"\n (left)=\"onToastLeave($event)\"\n (dismissed)=\"service.dismiss($event)\"\n />\n }\n </section>\n}\n\n", styles: ["[data-dt-viewport]{--sileo-spring-easing: linear( 0, .002 .6%, .007 1.2%, .015 1.8%, .026 2.4%, .041 3.1%, .06 3.8%, .108 5.3%, .157 6.6%, .214 8%, .467 13.7%, .577 16.3%, .631 17.7%, .682 19.1%, .73 20.5%, .771 21.8%, .808 23.1%, .844 24.5%, .874 25.8%, .903 27.2%, .928 28.6%, .952 30.1%, .972 31.6%, .988 33.1%, 1.01 35.7%, 1.025 38.5%, 1.034 41.6%, 1.038 45%, 1.035 50.1%, 1.012 64.2%, 1.003 73%, .999 83.7%, 1 );--sileo-duration: .6s;--sileo-height: 40px;--sileo-width: 350px;--sileo-state-success: oklch(.723 .219 142.136);--sileo-state-loading: oklch(.556 0 0);--sileo-state-error: oklch(.637 .237 25.331);--sileo-state-warning: oklch(.795 .184 86.047);--sileo-state-info: oklch(.685 .169 237.323);--sileo-state-action: oklch(.623 .214 259.815)}[data-dt-toast]{position:relative;cursor:pointer;pointer-events:auto;touch-action:none;border:0;background:transparent;padding:0;width:var(--sileo-width);height:var(--_h, var(--sileo-height));opacity:0;transform:translateZ(0) scale(.95);transform-origin:center;contain:layout style;overflow:visible;color:#fff;transition:all var(--sileo-duration) var(--sileo-spring-easing);filter:drop-shadow(0 4px 6px rgba(0,0,0,.1)) drop-shadow(0 10px 15px rgba(0,0,0,.2))}[data-dt-toast]:after{display:none!important}[data-dt-viewport]:not(:hover) [data-dt-toast]{position:absolute;right:1.5rem;margin:0!important;transition:all .6s cubic-bezier(.16,1,.3,1)}[data-dt-viewport][data-position$=left]:not(:hover) [data-dt-toast]{right:auto;left:1.5rem}[data-dt-viewport][data-position$=center]:not(:hover) [data-dt-toast]{right:auto;left:auto;position:relative;margin-bottom:-30px!important}[data-dt-viewport][data-position^=top]:not(:hover) [data-dt-toast]{top:1.5rem!important}[data-dt-viewport][data-position^=bottom]:not(:hover) [data-dt-toast]{bottom:1.5rem!important}[data-dt-viewport]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(1){z-index:5;transform:translateY(0) scale(1);opacity:1}[data-dt-viewport][data-position^=top]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(2){z-index:4;transform:translateY(-14px) scale(.95);opacity:1;filter:brightness(.9)}[data-dt-viewport][data-position^=bottom]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(2){z-index:4;transform:translateY(14px) scale(.95);opacity:1;filter:brightness(.9)}[data-dt-viewport][data-position^=top]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(3){z-index:3;transform:translateY(-28px) scale(.9);opacity:.8;filter:brightness(.8)}[data-dt-viewport][data-position^=bottom]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(3){z-index:3;transform:translateY(28px) scale(.9);opacity:.8;filter:brightness(.8)}[data-dt-viewport]:not(:hover) [data-dt-toast][data-ready=true]:not([data-exiting=true]):nth-last-child(n+4){opacity:0;pointer-events:none;transform:translateY(0) scale(.8);z-index:0}[data-dt-viewport]:hover [data-dt-toast]{position:relative;transform:none;opacity:1;filter:none;z-index:auto}[data-dt-viewport][data-position^=top]:hover [data-dt-toast]+[data-dt-toast]{margin-top:10px}[data-dt-viewport][data-position^=bottom]:hover [data-dt-toast]+[data-dt-toast]{margin-bottom:10px}[data-dt-toast][data-state=loading]{cursor:default}[data-dt-toast][data-ready=true]{opacity:1;transform:translateZ(0) scale(1);transition:transform calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),opacity calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),margin-bottom calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),margin-top calc(var(--sileo-duration) * .66) var(--sileo-spring-easing),height var(--sileo-duration) var(--sileo-spring-easing)}[data-dt-toast][data-ready=true][data-exiting=true]{opacity:0!important;pointer-events:none}[data-dt-viewport][data-position^=top] [data-dt-toast]:not([data-ready=true]){transform:translateY(-6px) scale(.95);opacity:0!important}[data-dt-viewport][data-position^=bottom] [data-dt-toast]:not([data-ready=true]){transform:translateY(6px) scale(.95);opacity:0!important}[data-dt-viewport][data-position^=top] [data-dt-toast][data-ready=true][data-exiting=true]{transform:translateY(-6px) scale(.95)}[data-dt-viewport][data-position^=bottom] [data-dt-toast][data-ready=true][data-exiting=true]{transform:translateY(6px) scale(.95)}[data-dt-canvas]{position:absolute;left:0;right:0;pointer-events:none;transform:translateZ(0);contain:layout style;overflow:visible}[data-dt-canvas][data-edge=top]{bottom:0;transform:scaleY(-1) translateZ(0)}[data-dt-canvas][data-edge=bottom]{top:0}[data-dt-svg]{overflow:visible}[data-dt-header]{position:absolute;z-index:20;display:flex;align-items:center;padding:.5rem;height:var(--sileo-height);overflow:hidden;left:var(--_px, 0px);transform:var(--_ht);max-width:var(--_pw)}[data-dt-toast][data-ready=true] [data-dt-header]{transition:transform var(--sileo-duration) var(--sileo-spring-easing),left var(--sileo-duration) var(--sileo-spring-easing),max-width var(--sileo-duration) var(--sileo-spring-easing)}[data-dt-header][data-edge=top]{bottom:0}[data-dt-header][data-edge=bottom]{top:0}[data-dt-header-stack]{position:relative;display:inline-flex;align-items:center;height:100%}[data-dt-header-inner]{display:flex;align-items:center;gap:.5rem;white-space:nowrap;opacity:1;filter:blur(0px);transform:translateZ(0)}[data-dt-header-inner][data-layer=current]{position:relative;z-index:1;animation:dt-header-enter var(--sileo-duration) var(--sileo-spring-easing) both}[data-dt-header-inner][data-layer=prev]{position:absolute;left:0;top:0;z-index:0;pointer-events:none}[data-dt-header-inner][data-exiting=true]{animation:dt-header-exit calc(var(--sileo-duration) * .7) ease forwards}[data-dt-badge]{display:flex;height:24px;width:24px;flex-shrink:0;align-items:center;justify-content:center;padding:2px;box-sizing:border-box;border-radius:9999px;color:var(--sileo-tone, currentColor);background-color:var(--sileo-tone-bg, transparent)}[data-dt-title]{font-size:.825rem;line-height:1rem;font-weight:500;text-transform:capitalize;color:var(--sileo-tone, currentColor)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state]{--_c: var(--sileo-state-success)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state=loading]{--_c: var(--sileo-state-loading)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state=error]{--_c: var(--sileo-state-error)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state=warning]{--_c: var(--sileo-state-warning)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state=info]{--_c: var(--sileo-state-info)}:is([data-dt-badge],[data-dt-title],[data-dt-button])[data-state=action]{--_c: var(--sileo-state-action)}:is([data-dt-badge],[data-dt-title])[data-state]{--sileo-tone: var(--_c);--sileo-tone-bg: color-mix(in oklch, var(--_c) 20%, transparent)}[data-dt-content]{position:absolute;left:0;z-index:10;width:100%;pointer-events:none;opacity:var(--_co, 0)}[data-dt-content]:not([data-visible=true]){content-visibility:hidden}[data-dt-toast][data-ready=true] [data-dt-content]{transition:opacity calc(var(--sileo-duration) * .08) ease calc(var(--sileo-duration) * .04)}[data-dt-content][data-edge=top]{top:0}[data-dt-content][data-edge=bottom]{top:var(--sileo-height)}[data-dt-content][data-visible=true]{pointer-events:auto}[data-dt-toast][data-ready=true] [data-dt-content][data-visible=true]{transition:opacity calc(var(--sileo-duration) * .6) ease calc(var(--sileo-duration) * .3)}[data-dt-description]{width:100%;text-align:left;padding:1rem;font-size:.875rem;line-height:1.25rem;contain:layout style paint;content-visibility:auto}[data-dt-button]{display:flex;align-items:center;justify-content:center;height:1.75rem;padding:0 .625rem;margin-top:.75rem;border-radius:9999px;border:0;font-size:.75rem;font-weight:500;cursor:pointer;color:var(--sileo-btn-color, currentColor);background-color:var(--sileo-btn-bg, transparent);transition:background-color .15s ease}[data-dt-button]:hover{background-color:var(--sileo-btn-bg-hover, transparent)}[data-dt-button][data-state]{--sileo-btn-color: var(--_c);--sileo-btn-bg: color-mix(in oklch, var(--_c) 15%, transparent);--sileo-btn-bg-hover: color-mix(in oklch, var(--_c) 25%, transparent)}[data-dt-icon=spin]{animation:dt-spin 1s linear infinite}@keyframes dt-spin{to{transform:rotate(360deg)}}@keyframes dt-header-enter{0%{opacity:0;filter:blur(6px)}to{opacity:1;filter:blur(0px)}}@keyframes dt-header-exit{0%{opacity:1;filter:blur(0px)}to{opacity:0;filter:blur(6px)}}[data-dt-viewport]{position:fixed;z-index:50;display:flex;gap:0;padding:1.5rem;pointer-events:auto;max-width:calc(100vw - 1.5rem);contain:layout style;transition:all .3s ease;height:auto}[data-dt-viewport]{pointer-events:none}[data-dt-toast]{pointer-events:auto}[data-dt-viewport][data-position^=top] [data-dt-toast]:not([data-ready=true]){margin-bottom:calc(-1 * (var(--sileo-height) + .75rem))}[data-dt-viewport][data-position^=bottom] [data-dt-toast]:not([data-ready=true]){margin-top:calc(-1 * (var(--sileo-height) + .75rem))}[data-dt-viewport][data-position^=top]{top:0;flex-direction:column-reverse}[data-dt-viewport][data-position^=bottom]{bottom:0;flex-direction:column}[data-dt-viewport][data-position$=left]{left:0;align-items:flex-start}[data-dt-viewport][data-position$=right]{right:0;align-items:flex-end}[data-dt-viewport][data-position$=center]{left:50%;transform:translate(-50%);align-items:center}@media(prefers-reduced-motion:no-preference){[data-dt-toast][data-ready=true]:hover,[data-dt-toast][data-ready=true][data-exiting=true]{will-change:transform,opacity,height}}@media(prefers-reduced-motion:reduce){[data-dt-viewport],[data-dt-viewport] *,[data-dt-viewport] *:before,[data-dt-viewport] *:after{animation-duration:.01ms;animation-iteration-count:1;transition-duration:.01ms}}\n"] }]
|
|
525
|
+
}], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }] } });
|
|
526
|
+
|
|
527
|
+
const timeoutKey = (t) => `${t.id}:${t.instanceId}`;
|
|
528
|
+
let idCounter = 0;
|
|
529
|
+
const generateId = () => `${++idCounter}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
530
|
+
class DynamicToastService {
|
|
531
|
+
_toasts = signal([], ...(ngDevMode ? [{ debugName: "_toasts" }] : []));
|
|
532
|
+
toasts = this._toasts.asReadonly();
|
|
533
|
+
config = { position: "top-right" };
|
|
534
|
+
viewportRef = null;
|
|
535
|
+
timers = new Map();
|
|
536
|
+
paused = false;
|
|
537
|
+
isPendingCD = false;
|
|
538
|
+
loopCount = 0;
|
|
539
|
+
loopResetTimer;
|
|
540
|
+
appRef = inject(ApplicationRef);
|
|
541
|
+
envInjector = inject(EnvironmentInjector);
|
|
542
|
+
document = inject(DOCUMENT);
|
|
543
|
+
ngZone = inject(NgZone);
|
|
544
|
+
configure(cfg) {
|
|
545
|
+
this.config = { ...this.config, ...cfg };
|
|
546
|
+
if (this.viewportRef) {
|
|
547
|
+
this.viewportRef.setInput("position", this.config.position ?? "top-right");
|
|
548
|
+
this.viewportRef.setInput("offset", this.config.offset);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
ensureViewport() {
|
|
552
|
+
if (this.viewportRef)
|
|
553
|
+
return;
|
|
554
|
+
const ref = createComponent(DynamicToastViewportComponent, {
|
|
555
|
+
environmentInjector: this.envInjector,
|
|
556
|
+
});
|
|
557
|
+
ref.setInput("position", this.config.position ?? "top-right");
|
|
558
|
+
ref.setInput("offset", this.config.offset);
|
|
559
|
+
const el = ref.location.nativeElement;
|
|
560
|
+
el.setAttribute("data-dt-root", "");
|
|
561
|
+
this.appRef.attachView(ref.hostView);
|
|
562
|
+
this.document.body.appendChild(el);
|
|
563
|
+
this.viewportRef = ref;
|
|
564
|
+
}
|
|
565
|
+
resolveAutopilot(opts, duration) {
|
|
566
|
+
if (opts.autopilot === false || !duration || duration <= 0)
|
|
567
|
+
return {};
|
|
568
|
+
const cfg = typeof opts.autopilot === "object" ? opts.autopilot : undefined;
|
|
569
|
+
const clamp = (v) => Math.min(duration, Math.max(0, v));
|
|
570
|
+
return {
|
|
571
|
+
expandDelayMs: clamp(cfg?.expand ?? AUTO_EXPAND_DELAY),
|
|
572
|
+
collapseDelayMs: clamp(cfg?.collapse ?? AUTO_COLLAPSE_DELAY),
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
mergeOptions(options) {
|
|
576
|
+
return {
|
|
577
|
+
...(this.config.options ?? {}),
|
|
578
|
+
...options,
|
|
579
|
+
styles: { ...(this.config.options?.styles ?? {}), ...(options.styles ?? {}) },
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
buildItem(merged, id, state, fallbackPosition) {
|
|
583
|
+
const duration = merged.duration ?? DEFAULT_TOAST_DURATION;
|
|
584
|
+
const auto = this.resolveAutopilot(merged, duration);
|
|
585
|
+
return {
|
|
586
|
+
...merged,
|
|
587
|
+
id,
|
|
588
|
+
instanceId: generateId(),
|
|
589
|
+
state,
|
|
590
|
+
position: merged.position ?? fallbackPosition ?? this.config.position ?? "top-right",
|
|
591
|
+
duration,
|
|
592
|
+
autoExpandDelayMs: auto.expandDelayMs,
|
|
593
|
+
autoCollapseDelayMs: auto.collapseDelayMs,
|
|
594
|
+
exiting: false,
|
|
595
|
+
fill: merged.fill ?? "#151515",
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
upsert(item) {
|
|
599
|
+
// console.log("[DynamicToastService] upsert", item.id, item.state);
|
|
600
|
+
// Circuit breaker for infinite loops
|
|
601
|
+
this.loopCount++;
|
|
602
|
+
if (this.loopCount > 50) {
|
|
603
|
+
console.warn("[DynamicToast] Loop detected (50+ updates/sec). Blocking update.");
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (!this.loopResetTimer) {
|
|
607
|
+
this.loopResetTimer = setTimeout(() => {
|
|
608
|
+
this.loopCount = 0;
|
|
609
|
+
this.loopResetTimer = null;
|
|
610
|
+
}, 1000);
|
|
611
|
+
}
|
|
612
|
+
// Optimization: check if item really changed before anything else
|
|
613
|
+
const currentList = this._toasts();
|
|
614
|
+
const existing = currentList.find((t) => t.id === item.id);
|
|
615
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(item)) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
this.ensureViewport();
|
|
619
|
+
// Update state outside of Angular Zone to prevent change detection loops
|
|
620
|
+
// We will manually trigger CD only when necessary
|
|
621
|
+
this.ngZone.runOutsideAngular(() => {
|
|
622
|
+
this._toasts.update((prev) => {
|
|
623
|
+
const live = prev.filter((t) => !t.exiting);
|
|
624
|
+
const exist = live.find((t) => t.id === item.id);
|
|
625
|
+
if (exist) {
|
|
626
|
+
return prev.map((t) => (t.id === item.id ? { ...item } : t));
|
|
627
|
+
}
|
|
628
|
+
return [...prev.filter((t) => t.id !== item.id), item];
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
// Coalesce CD updates to one per frame
|
|
632
|
+
if (!this.isPendingCD) {
|
|
633
|
+
this.isPendingCD = true;
|
|
634
|
+
this.ngZone.runOutsideAngular(() => {
|
|
635
|
+
requestAnimationFrame(() => {
|
|
636
|
+
this.isPendingCD = false;
|
|
637
|
+
// Only re-enter zone for CD if really needed, or run detectChanges directly
|
|
638
|
+
this.viewportRef?.changeDetectorRef.detectChanges();
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
this.scheduleAll();
|
|
643
|
+
}
|
|
644
|
+
show(options) {
|
|
645
|
+
const merged = this.mergeOptions(options);
|
|
646
|
+
const id = merged.id ?? "dynamicToast-default";
|
|
647
|
+
const prev = this._toasts().find((t) => t.id === id && !t.exiting);
|
|
648
|
+
const item = this.buildItem(merged, id, options.state, prev?.position);
|
|
649
|
+
this.upsert(item);
|
|
650
|
+
return id;
|
|
651
|
+
}
|
|
652
|
+
info(a, b) {
|
|
653
|
+
const opts = typeof a === "string" ? { ...(b ?? {}), title: a } : a;
|
|
654
|
+
return this.show({ ...opts, state: "info" });
|
|
655
|
+
}
|
|
656
|
+
success(a, b) {
|
|
657
|
+
const opts = typeof a === "string" ? { ...(b ?? {}), title: a } : a;
|
|
658
|
+
return this.show({ ...opts, state: "success" });
|
|
659
|
+
}
|
|
660
|
+
warning(a, b) {
|
|
661
|
+
const opts = typeof a === "string" ? { ...(b ?? {}), title: a } : a;
|
|
662
|
+
return this.show({ ...opts, state: "warning" });
|
|
663
|
+
}
|
|
664
|
+
error(a, b) {
|
|
665
|
+
const opts = typeof a === "string" ? { ...(b ?? {}), title: a } : a;
|
|
666
|
+
return this.show({ ...opts, state: "error" });
|
|
667
|
+
}
|
|
668
|
+
loading(a, b) {
|
|
669
|
+
const opts = typeof a === "string" ? { ...(b ?? {}), title: a } : a;
|
|
670
|
+
return this.show({ ...opts, state: "loading", duration: null });
|
|
671
|
+
}
|
|
672
|
+
action(a, b) {
|
|
673
|
+
const opts = typeof a === "string" ? { ...(b ?? {}), title: a } : a;
|
|
674
|
+
return this.show({ ...opts, state: "action", duration: null });
|
|
675
|
+
}
|
|
676
|
+
update(id, patch) {
|
|
677
|
+
const existing = this._toasts().find((t) => t.id === id);
|
|
678
|
+
if (!existing)
|
|
679
|
+
return;
|
|
680
|
+
const merged = this.mergeOptions({ ...existing, ...patch, id });
|
|
681
|
+
const state = patch.state ?? existing.state;
|
|
682
|
+
const item = this.buildItem(merged, id, state, existing.position);
|
|
683
|
+
this.upsert(item);
|
|
684
|
+
}
|
|
685
|
+
promise(promise, opts) {
|
|
686
|
+
const id = this.show({
|
|
687
|
+
...opts.loading,
|
|
688
|
+
state: "loading",
|
|
689
|
+
duration: null,
|
|
690
|
+
position: opts.position,
|
|
691
|
+
});
|
|
692
|
+
const p = typeof promise === "function" ? promise() : promise;
|
|
693
|
+
p.then((data) => {
|
|
694
|
+
if (opts.action) {
|
|
695
|
+
const actionOpts = typeof opts.action === "function" ? opts.action(data) : opts.action;
|
|
696
|
+
this.update(id, { ...actionOpts, state: "action", id });
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
const successOpts = typeof opts.success === "function" ? opts.success(data) : opts.success;
|
|
700
|
+
this.update(id, { ...successOpts, state: "success", id });
|
|
701
|
+
}
|
|
702
|
+
}).catch((err) => {
|
|
703
|
+
const errorOpts = typeof opts.error === "function" ? opts.error(err) : opts.error;
|
|
704
|
+
this.update(id, { ...errorOpts, state: "error", id });
|
|
705
|
+
});
|
|
706
|
+
return p;
|
|
707
|
+
}
|
|
708
|
+
dismiss(id) {
|
|
709
|
+
const item = this._toasts().find((t) => t.id === id);
|
|
710
|
+
if (!item || item.exiting)
|
|
711
|
+
return;
|
|
712
|
+
this.ngZone.runOutsideAngular(() => {
|
|
713
|
+
this._toasts.update((prev) => prev.map((t) => (t.id === id ? { ...t, exiting: true } : t)));
|
|
714
|
+
});
|
|
715
|
+
this.viewportRef?.changeDetectorRef.detectChanges();
|
|
716
|
+
this.ngZone.runOutsideAngular(() => {
|
|
717
|
+
window.setTimeout(() => {
|
|
718
|
+
this._toasts.update((prev) => prev.filter((t) => t.id !== id));
|
|
719
|
+
this.clearTimers();
|
|
720
|
+
this.scheduleAll();
|
|
721
|
+
this.viewportRef?.changeDetectorRef.detectChanges();
|
|
722
|
+
}, EXIT_DURATION);
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
clear(position) {
|
|
726
|
+
this._toasts.update((prev) => position ? prev.filter((t) => t.position !== position) : []);
|
|
727
|
+
this.clearTimers();
|
|
728
|
+
}
|
|
729
|
+
pauseTimers() {
|
|
730
|
+
if (this.paused)
|
|
731
|
+
return;
|
|
732
|
+
this.paused = true;
|
|
733
|
+
this.clearTimers();
|
|
734
|
+
}
|
|
735
|
+
resumeTimers() {
|
|
736
|
+
if (!this.paused)
|
|
737
|
+
return;
|
|
738
|
+
this.paused = false;
|
|
739
|
+
this.scheduleAll();
|
|
740
|
+
}
|
|
741
|
+
clearTimers() {
|
|
742
|
+
for (const t of this.timers.values())
|
|
743
|
+
window.clearTimeout(t);
|
|
744
|
+
this.timers.clear();
|
|
745
|
+
}
|
|
746
|
+
scheduleAll() {
|
|
747
|
+
if (this.paused)
|
|
748
|
+
return;
|
|
749
|
+
this.ngZone.runOutsideAngular(() => {
|
|
750
|
+
const items = this._toasts();
|
|
751
|
+
for (const item of items) {
|
|
752
|
+
if (item.exiting)
|
|
753
|
+
continue;
|
|
754
|
+
const key = timeoutKey(item);
|
|
755
|
+
if (this.timers.has(key))
|
|
756
|
+
continue;
|
|
757
|
+
const dur = item.duration ?? DEFAULT_TOAST_DURATION;
|
|
758
|
+
if (dur === null || dur <= 0)
|
|
759
|
+
continue;
|
|
760
|
+
this.timers.set(key, window.setTimeout(() => {
|
|
761
|
+
// Re-enter zone only if needed, but dismiss handles zone internally now
|
|
762
|
+
this.dismiss(item.id);
|
|
763
|
+
}, dur));
|
|
764
|
+
}
|
|
765
|
+
const alive = new Set(items.map(timeoutKey));
|
|
766
|
+
for (const [key, timer] of this.timers) {
|
|
767
|
+
if (!alive.has(key)) {
|
|
768
|
+
window.clearTimeout(timer);
|
|
769
|
+
this.timers.delete(key);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: DynamicToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
775
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: DynamicToastService, providedIn: "root" });
|
|
776
|
+
}
|
|
777
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: DynamicToastService, decorators: [{
|
|
778
|
+
type: Injectable,
|
|
779
|
+
args: [{ providedIn: "root" }]
|
|
780
|
+
}] });
|
|
781
|
+
|
|
782
|
+
/*
|
|
783
|
+
* Public API Surface of ngx-dynamic-toast
|
|
784
|
+
*/
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Generated bundle index. Do not edit.
|
|
788
|
+
*/
|
|
789
|
+
|
|
790
|
+
export { AUTO_COLLAPSE_DELAY, AUTO_EXPAND_DELAY, BLUR_RATIO, DEFAULT_ROUNDNESS, DEFAULT_TOAST_DURATION, DURATION_MS, DURATION_S, DynamicToastComponent, DynamicToastService, DynamicToastViewportComponent, EXIT_DURATION, HEADER_EXIT_MS, HEIGHT, ICONS, MIN_EXPAND_RATIO, PILL_PADDING, SWAP_COLLAPSE_MS, WIDTH };
|
|
791
|
+
//# sourceMappingURL=ngx-dynamic-toast.mjs.map
|