snackng 0.1.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/CHANGELOG.md +63 -0
- package/LICENSE +201 -0
- package/README.md +242 -0
- package/fesm2022/snackng.mjs +515 -0
- package/fesm2022/snackng.mjs.map +1 -0
- package/package.json +60 -0
- package/themes/daisy.css +35 -0
- package/types/snackng.d.ts +108 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, inject, DOCUMENT, Injectable, signal, computed, input, ElementRef, effect, ChangeDetectionStrategy, Component, afterEveryRender, ApplicationRef, EnvironmentInjector, PLATFORM_ID, createComponent } from '@angular/core';
|
|
3
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
4
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
5
|
+
|
|
6
|
+
const SNACKNG_DEFAULTS = {
|
|
7
|
+
duration: 5000,
|
|
8
|
+
position: 'top-end',
|
|
9
|
+
max: 5,
|
|
10
|
+
overflow: 'queue',
|
|
11
|
+
stagger: 90,
|
|
12
|
+
pauseOnHover: true,
|
|
13
|
+
dismissible: false,
|
|
14
|
+
types: {},
|
|
15
|
+
};
|
|
16
|
+
const SNACKNG_CONFIG = new InjectionToken('snackng.config', {
|
|
17
|
+
providedIn: 'root',
|
|
18
|
+
factory: () => SNACKNG_DEFAULTS,
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Optional. Without it the defaults above apply, so `npm i snackng` and calling
|
|
22
|
+
* the service is enough to get working toasts.
|
|
23
|
+
*/
|
|
24
|
+
function provideSnackng(config = {}) {
|
|
25
|
+
return makeEnvironmentProviders([
|
|
26
|
+
{
|
|
27
|
+
provide: SNACKNG_CONFIG,
|
|
28
|
+
useValue: {
|
|
29
|
+
...SNACKNG_DEFAULTS,
|
|
30
|
+
...config,
|
|
31
|
+
types: { ...SNACKNG_DEFAULTS.types, ...config.types },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Announces toast text to screen readers, in place of the CDK `LiveAnnouncer`.
|
|
39
|
+
*
|
|
40
|
+
* The toast markup carries no `aria-live` of its own: text is copied into this
|
|
41
|
+
* off-screen region instead. Keep it that way — a live region nested inside
|
|
42
|
+
* another throws `HierarchyRequestError` when the outer one relocates its
|
|
43
|
+
* content.
|
|
44
|
+
*/
|
|
45
|
+
class SnackngLiveRegion {
|
|
46
|
+
document = inject(DOCUMENT);
|
|
47
|
+
region;
|
|
48
|
+
clearTimer;
|
|
49
|
+
announce(message, politeness = 'polite') {
|
|
50
|
+
if (politeness === 'off' || !message) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const region = this.ensureRegion();
|
|
54
|
+
region.setAttribute('aria-live', politeness);
|
|
55
|
+
// Clearing first forces assistive tech to treat an identical repeated
|
|
56
|
+
// message as a new announcement.
|
|
57
|
+
region.textContent = '';
|
|
58
|
+
clearTimeout(this.clearTimer);
|
|
59
|
+
this.clearTimer = setTimeout(() => (region.textContent = message), 100);
|
|
60
|
+
}
|
|
61
|
+
ensureRegion() {
|
|
62
|
+
if (this.region?.isConnected) {
|
|
63
|
+
return this.region;
|
|
64
|
+
}
|
|
65
|
+
const region = this.document.createElement('div');
|
|
66
|
+
region.setAttribute('aria-atomic', 'true');
|
|
67
|
+
region.setAttribute('aria-live', 'polite');
|
|
68
|
+
region.classList.add('sng-live-region');
|
|
69
|
+
// Visually hidden but still announced: `display:none` would silence it.
|
|
70
|
+
region.style.cssText =
|
|
71
|
+
'position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;' +
|
|
72
|
+
'clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;';
|
|
73
|
+
this.document.body.appendChild(region);
|
|
74
|
+
this.region = region;
|
|
75
|
+
return region;
|
|
76
|
+
}
|
|
77
|
+
ngOnDestroy() {
|
|
78
|
+
clearTimeout(this.clearTimer);
|
|
79
|
+
this.region?.remove();
|
|
80
|
+
this.region = undefined;
|
|
81
|
+
}
|
|
82
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngLiveRegion, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
83
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngLiveRegion, providedIn: 'root' });
|
|
84
|
+
}
|
|
85
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngLiveRegion, decorators: [{
|
|
86
|
+
type: Injectable,
|
|
87
|
+
args: [{ providedIn: 'root' }]
|
|
88
|
+
}] });
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Path data for the built-in glyphs, inlined so the package never requires an
|
|
92
|
+
* icon font. Bound with `[attr.d]`, so no sanitizer is involved.
|
|
93
|
+
*/
|
|
94
|
+
const SNACKNG_ICONS = {
|
|
95
|
+
success: 'M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',
|
|
96
|
+
warning: 'M1 21h22L12 2zm12-3h-2v-2h2zm0-4h-2v-4h2z',
|
|
97
|
+
danger: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 15h-2v-2h2zm0-4h-2V7h2z',
|
|
98
|
+
info: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 15h-2v-6h2zm0-8h-2V7h2z',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Toast state, and the pacing of how toasts reach the screen.
|
|
103
|
+
*
|
|
104
|
+
* Producer/consumer: `add` only enqueues; `pump` is the sole consumer moving
|
|
105
|
+
* items into view as capacity allows. Releasing directly would let a burst
|
|
106
|
+
* render in one tick, appearing and evicting itself in the same frame.
|
|
107
|
+
*
|
|
108
|
+
* Separate from `SnackngService` so the service can create the host while the
|
|
109
|
+
* host reads state, without an import cycle.
|
|
110
|
+
*/
|
|
111
|
+
class SnackngStore {
|
|
112
|
+
config = inject(SNACKNG_CONFIG);
|
|
113
|
+
resolvers = new Map();
|
|
114
|
+
reasons = new Map();
|
|
115
|
+
pumpTimer;
|
|
116
|
+
lastReleaseAt = 0;
|
|
117
|
+
counter = 0;
|
|
118
|
+
/** Toasts currently rendered (including those playing their exit animation). */
|
|
119
|
+
items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
120
|
+
/** Toasts accepted but still waiting for a free slot. */
|
|
121
|
+
queue = signal([], ...(ngDevMode ? [{ debugName: "queue" }] : /* istanbul ignore next */ []));
|
|
122
|
+
pending = computed(() => this.queue().length, ...(ngDevMode ? [{ debugName: "pending" }] : /* istanbul ignore next */ []));
|
|
123
|
+
stacks = computed(() => {
|
|
124
|
+
const byPosition = new Map();
|
|
125
|
+
for (const item of this.items()) {
|
|
126
|
+
const bucket = byPosition.get(item.position);
|
|
127
|
+
bucket ? bucket.push(item) : byPosition.set(item.position, [item]);
|
|
128
|
+
}
|
|
129
|
+
return [...byPosition].map(([position, items]) => ({ position, items }));
|
|
130
|
+
}, ...(ngDevMode ? [{ debugName: "stacks" }] : /* istanbul ignore next */ []));
|
|
131
|
+
add(item) {
|
|
132
|
+
const id = `sng-${++this.counter}`;
|
|
133
|
+
const afterDismissed = new Promise(resolve => this.resolvers.set(id, resolve));
|
|
134
|
+
this.queue.update(queue => [...queue, { ...item, id, leaving: false }]);
|
|
135
|
+
this.pump();
|
|
136
|
+
return { id, afterDismissed };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Starts the exit animation; the item leaves the DOM on `finalize`. A toast
|
|
140
|
+
* still queued is dropped outright, having never been shown.
|
|
141
|
+
*/
|
|
142
|
+
dismiss(id, reason = 'manual') {
|
|
143
|
+
if (this.queue().some(item => item.id === id)) {
|
|
144
|
+
this.queue.update(queue => queue.filter(item => item.id !== id));
|
|
145
|
+
this.resolve(id, reason);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const target = this.items().find(item => item.id === id);
|
|
149
|
+
if (!target || target.leaving) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.reasons.set(id, reason);
|
|
153
|
+
this.items.update(items => items.map(i => (i.id === id ? { ...i, leaving: true } : i)));
|
|
154
|
+
}
|
|
155
|
+
/** Removes the item and resolves its `afterDismissed` promise. */
|
|
156
|
+
finalize(id) {
|
|
157
|
+
if (!this.resolvers.has(id)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.items.update(items => items.filter(item => item.id !== id));
|
|
161
|
+
this.resolve(id, this.reasons.get(id) ?? 'manual');
|
|
162
|
+
this.reasons.delete(id);
|
|
163
|
+
this.pump();
|
|
164
|
+
}
|
|
165
|
+
dismissAll(reason = 'manual') {
|
|
166
|
+
// Drain the queue first, or it would march onto a screen just cleared.
|
|
167
|
+
clearTimeout(this.pumpTimer);
|
|
168
|
+
this.pumpTimer = undefined;
|
|
169
|
+
for (const item of this.queue()) {
|
|
170
|
+
this.resolve(item.id, reason);
|
|
171
|
+
}
|
|
172
|
+
this.queue.set([]);
|
|
173
|
+
for (const item of this.items()) {
|
|
174
|
+
this.dismiss(item.id, reason);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** The consumer. Runs on every add and every finalize. */
|
|
178
|
+
pump() {
|
|
179
|
+
if (this.pumpTimer) {
|
|
180
|
+
return; // a staggered release is already pending
|
|
181
|
+
}
|
|
182
|
+
while (this.queue().length) {
|
|
183
|
+
// Counts leaving toasts too: they hold their slot until finalize removes
|
|
184
|
+
// them, so ignoring them reads an expiring batch as free space.
|
|
185
|
+
const onScreen = this.items();
|
|
186
|
+
if (onScreen.length >= this.config.max) {
|
|
187
|
+
if (this.config.overflow === 'dismiss-oldest') {
|
|
188
|
+
// Evicting an already-leaving toast frees nothing.
|
|
189
|
+
const victim = onScreen.find(item => !item.leaving);
|
|
190
|
+
if (victim) {
|
|
191
|
+
this.dismiss(victim.id, 'replaced');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// Spaced by elapsed time, not queue length: callers add one at a time, so
|
|
197
|
+
// a burst arrives as N pumps of one item and would never wait.
|
|
198
|
+
const wait = this.config.stagger - (Date.now() - this.lastReleaseAt);
|
|
199
|
+
if (this.config.stagger > 0 && wait > 0) {
|
|
200
|
+
this.pumpTimer = setTimeout(() => {
|
|
201
|
+
this.pumpTimer = undefined;
|
|
202
|
+
this.pump();
|
|
203
|
+
}, wait);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const [next, ...rest] = this.queue();
|
|
207
|
+
this.queue.set(rest);
|
|
208
|
+
this.items.update(items => [...items, next]);
|
|
209
|
+
this.lastReleaseAt = Date.now();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
resolve(id, reason) {
|
|
213
|
+
this.resolvers.get(id)?.(reason);
|
|
214
|
+
this.resolvers.delete(id);
|
|
215
|
+
}
|
|
216
|
+
ngOnDestroy() {
|
|
217
|
+
clearTimeout(this.pumpTimer);
|
|
218
|
+
}
|
|
219
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
220
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngStore, providedIn: 'root' });
|
|
221
|
+
}
|
|
222
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngStore, decorators: [{
|
|
223
|
+
type: Injectable,
|
|
224
|
+
args: [{ providedIn: 'root' }]
|
|
225
|
+
}] });
|
|
226
|
+
|
|
227
|
+
const EXIT_DURATION = 200;
|
|
228
|
+
/** Guards against `animationend` never firing (reduced motion, jsdom, backgrounded tab). */
|
|
229
|
+
const EXIT_FALLBACK = EXIT_DURATION + 150;
|
|
230
|
+
class SnackngToast {
|
|
231
|
+
item = input.required(...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
232
|
+
store = inject(SnackngStore);
|
|
233
|
+
config = inject(SNACKNG_CONFIG);
|
|
234
|
+
sanitizer = inject(DomSanitizer);
|
|
235
|
+
element = inject(ElementRef);
|
|
236
|
+
timer;
|
|
237
|
+
exitTimer;
|
|
238
|
+
armed = false;
|
|
239
|
+
remaining = 0;
|
|
240
|
+
startedAt = 0;
|
|
241
|
+
hostClasses = computed(() => {
|
|
242
|
+
const item = this.item();
|
|
243
|
+
return [
|
|
244
|
+
'sng-toast',
|
|
245
|
+
`sng--${item.type}`,
|
|
246
|
+
`sng-at--${item.position}`,
|
|
247
|
+
item.leaving ? 'sng-leaving' : 'sng-entering',
|
|
248
|
+
...item.panelClass,
|
|
249
|
+
].join(' ');
|
|
250
|
+
}, ...(ngDevMode ? [{ debugName: "hostClasses" }] : /* istanbul ignore next */ []));
|
|
251
|
+
/** Null when the type brings its own SVG; unknown types fall back to `info`. */
|
|
252
|
+
iconPath = computed(() => {
|
|
253
|
+
const type = this.item().type;
|
|
254
|
+
if (this.config.types[type]?.icon) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
return SNACKNG_ICONS[type] ?? SNACKNG_ICONS.info;
|
|
258
|
+
}, ...(ngDevMode ? [{ debugName: "iconPath" }] : /* istanbul ignore next */ []));
|
|
259
|
+
/** Custom types may supply raw SVG markup, trusted as authored config. */
|
|
260
|
+
iconSvg = computed(() => {
|
|
261
|
+
const custom = this.config.types[this.item().type]?.icon;
|
|
262
|
+
return custom ? this.sanitizer.bypassSecurityTrustHtml(custom) : null;
|
|
263
|
+
}, ...(ngDevMode ? [{ debugName: "iconSvg" }] : /* istanbul ignore next */ []));
|
|
264
|
+
constructor() {
|
|
265
|
+
effect(() => {
|
|
266
|
+
if (this.item().leaving) {
|
|
267
|
+
this.stopTimer();
|
|
268
|
+
this.scheduleFinalize();
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
this.startTimer();
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
onAction() {
|
|
276
|
+
const action = this.item().action;
|
|
277
|
+
action?.handler?.();
|
|
278
|
+
if (action?.dismissOnClick !== false) {
|
|
279
|
+
this.store.dismiss(this.item().id, 'action');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
onDismiss() {
|
|
283
|
+
this.store.dismiss(this.item().id, 'manual');
|
|
284
|
+
}
|
|
285
|
+
pause() {
|
|
286
|
+
if (!this.config.pauseOnHover || !this.timer) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
this.remaining -= Date.now() - this.startedAt;
|
|
290
|
+
clearTimeout(this.timer);
|
|
291
|
+
this.timer = undefined;
|
|
292
|
+
}
|
|
293
|
+
resume() {
|
|
294
|
+
if (!this.config.pauseOnHover || this.timer || this.remaining <= 0 || this.item().leaving) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
this.arm(this.remaining);
|
|
298
|
+
}
|
|
299
|
+
/** Arms once per toast: re-running on later item updates would reset a paused timer. */
|
|
300
|
+
startTimer() {
|
|
301
|
+
const duration = this.item().duration;
|
|
302
|
+
if (this.armed || duration <= 0) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
this.armed = true;
|
|
306
|
+
this.remaining = duration;
|
|
307
|
+
this.arm(duration);
|
|
308
|
+
}
|
|
309
|
+
arm(ms) {
|
|
310
|
+
this.startedAt = Date.now();
|
|
311
|
+
this.timer = setTimeout(() => this.store.dismiss(this.item().id, 'timeout'), ms);
|
|
312
|
+
}
|
|
313
|
+
stopTimer() {
|
|
314
|
+
clearTimeout(this.timer);
|
|
315
|
+
this.timer = undefined;
|
|
316
|
+
}
|
|
317
|
+
scheduleFinalize() {
|
|
318
|
+
if (this.exitTimer) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const finalize = () => {
|
|
322
|
+
clearTimeout(this.exitTimer);
|
|
323
|
+
this.store.finalize(this.item().id);
|
|
324
|
+
};
|
|
325
|
+
this.element.nativeElement.addEventListener('animationend', finalize, { once: true });
|
|
326
|
+
this.exitTimer = setTimeout(finalize, EXIT_FALLBACK);
|
|
327
|
+
}
|
|
328
|
+
ngOnDestroy() {
|
|
329
|
+
this.stopTimer();
|
|
330
|
+
clearTimeout(this.exitTimer);
|
|
331
|
+
}
|
|
332
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngToast, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
333
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: SnackngToast, isStandalone: true, selector: "sng-toast", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "mouseenter": "pause()", "mouseleave": "resume()", "focusin": "pause()", "focusout": "resume()" }, properties: { "class": "hostClasses()" } }, ngImport: i0, template: "@let toast = item();\n\n<!-- No aria-live here: SnackngLiveRegion announces instead. -->\n<div class=\"sng-accent\" aria-hidden=\"true\"></div>\n\n<div class=\"sng-icon\" aria-hidden=\"true\">\n @if (iconPath(); as path) {\n <svg viewBox=\"0 0 24 24\" focusable=\"false\">\n <path [attr.d]=\"path\" fill=\"currentColor\" />\n </svg>\n } @else {\n <span class=\"sng-icon-custom\" [innerHTML]=\"iconSvg()\"></span>\n }\n</div>\n\n<div class=\"sng-body\">\n @if (toast.title) {\n <p class=\"sng-title\">{{ toast.title }}</p>\n }\n <p class=\"sng-message\">{{ toast.message }}</p>\n</div>\n\n@if (toast.action; as action) {\n <button type=\"button\" class=\"sng-action\" (click)=\"onAction()\">{{ action.label }}</button>\n}\n\n@if (toast.dismissible) {\n <button type=\"button\" class=\"sng-close\" (click)=\"onDismiss()\" aria-label=\"Close notification\">\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path\n d=\"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"\n fill=\"currentColor\" />\n </svg>\n </button>\n}\n", styles: [":host{--sng-bg: var(--snackng-info-bg, linear-gradient(135deg, rgba(71, 85, 105, .9), rgba(40, 51, 69, .84)));--sng-ink: var(--snackng-info-ink, #ffffff);--sng-solid: var(--snackng-info-solid, #47556a);--sng-shift: var(--snackng-shift, 28px);position:relative;display:flex;align-items:center;gap:var(--snackng-gap, 14px);box-sizing:border-box;min-width:var(--snackng-min-width, 340px);max-width:var(--snackng-max-width, 460px);padding:var(--snackng-padding, 14px 18px 14px 22px);overflow:hidden;pointer-events:auto;border-radius:var(--snackng-radius, 14px);border:var(--snackng-border, 1px solid rgba(255, 255, 255, .22));background-image:var(--sng-bg);backdrop-filter:blur(var(--snackng-blur, 16px)) saturate(var(--snackng-saturate, 180%));-webkit-backdrop-filter:blur(var(--snackng-blur, 16px)) saturate(var(--snackng-saturate, 180%));box-shadow:var( --snackng-shadow, 0 12px 32px -10px rgba(17, 52, 111, .55), inset 0 1px 0 rgba(255, 255, 255, .28) );color:var(--sng-ink);font-family:var(--snackng-font, \"Manrope\", system-ui, sans-serif);line-height:var(--snackng-line-height, 1.45)}:host(.sng--success){--sng-bg: var(--snackng-success-bg, linear-gradient(135deg, rgba(33, 80, 159, .9), rgba(17, 52, 111, .84)));--sng-ink: var(--snackng-success-ink, #ffffff);--sng-solid: var(--snackng-success-solid, #21509f)}:host(.sng--danger){--sng-bg: var(--snackng-danger-bg, linear-gradient(135deg, rgba(192, 57, 43, .9), rgba(140, 35, 29, .84)));--sng-ink: var(--snackng-danger-ink, #ffffff);--sng-solid: var(--snackng-danger-solid, #c0392b)}:host(.sng--warning){--sng-bg: var(--snackng-warning-bg, linear-gradient(135deg, rgba(252, 211, 77, .92), rgba(245, 182, 43, .9)));--sng-ink: var(--snackng-warning-ink, #4a3200);--sng-solid: var(--snackng-warning-solid, #fcd34d);text-shadow:none}@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){:host{background-color:var(--sng-solid)}}@media(max-width:480px){:host{min-width:0;max-width:100%;width:100%}}.sng-accent{position:absolute;inset:0 auto 0 0;width:var(--snackng-accent-width, 4px);background:color-mix(in srgb,var(--sng-ink) 55%,transparent)}.sng-icon{flex:0 0 auto;display:flex;align-items:center;justify-content:center;padding:11px;border-radius:12px;background:color-mix(in srgb,var(--sng-ink) 16%,transparent);color:var(--sng-ink)}.sng-icon svg{display:block;width:24px;height:24px;fill:currentColor}.sng-icon-custom{display:flex}.sng-icon-custom ::ng-deep svg{display:block;width:24px;height:24px;fill:currentColor}.sng-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}.sng-title{margin:0;font-size:var(--snackng-title-size, 14px);font-weight:var(--snackng-title-weight, 700);line-height:1.3;letter-spacing:.1px;color:var(--sng-ink)}.sng-message{margin:0;font-size:var(--snackng-message-size, 13px);font-weight:var(--snackng-message-weight, 400);color:color-mix(in srgb,var(--sng-ink) 85%,transparent)}.sng-action{flex:0 0 auto;padding:7px 12px;border:1px solid color-mix(in srgb,var(--sng-ink) 32%,transparent);border-radius:8px;background:color-mix(in srgb,var(--sng-ink) 12%,transparent);color:var(--sng-ink);font:inherit;font-size:var(--snackng-action-size, 13px);font-weight:var(--snackng-action-weight, 600);cursor:pointer;transition:background .15s ease}.sng-action:hover{background:color-mix(in srgb,var(--sng-ink) 22%,transparent)}.sng-close{flex:0 0 auto;display:flex;padding:4px;border:0;border-radius:6px;background:none;color:color-mix(in srgb,var(--sng-ink) 70%,transparent);cursor:pointer}.sng-close:hover{background:color-mix(in srgb,var(--sng-ink) 14%,transparent);color:var(--sng-ink)}.sng-close svg{display:block;width:18px;height:18px}.sng-action:focus-visible,.sng-close:focus-visible{outline:2px solid var(--sng-ink);outline-offset:2px}@keyframes sng-in-right{0%{opacity:0;transform:translate(var(--sng-shift))}to{opacity:1;transform:translate(0)}}@keyframes sng-out-right{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(var(--sng-shift))}}@keyframes sng-in-left{0%{opacity:0;transform:translate(calc(-1 * var(--sng-shift)))}to{opacity:1;transform:translate(0)}}@keyframes sng-out-left{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(calc(-1 * var(--sng-shift)))}}@keyframes sng-in-top{0%{opacity:0;transform:translateY(calc(-1 * var(--sng-shift)))}to{opacity:1;transform:translateY(0)}}@keyframes sng-out-top{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(calc(-1 * var(--sng-shift)))}}@keyframes sng-in-bottom{0%{opacity:0;transform:translateY(var(--sng-shift))}to{opacity:1;transform:translateY(0)}}@keyframes sng-out-bottom{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(var(--sng-shift))}}:host(.sng-entering){animation-duration:var(--snackng-enter-duration, .4s);animation-timing-function:cubic-bezier(0,0,.2,1);animation-fill-mode:both}:host(.sng-leaving){animation-duration:var(--snackng-exit-duration, .2s);animation-timing-function:cubic-bezier(.4,0,1,1);animation-fill-mode:both}:host(.sng-at--top-end.sng-entering),:host(.sng-at--bottom-end.sng-entering){animation-name:sng-in-right}:host(.sng-at--top-end.sng-leaving),:host(.sng-at--bottom-end.sng-leaving){animation-name:sng-out-right}:host(.sng-at--top-start.sng-entering),:host(.sng-at--bottom-start.sng-entering){animation-name:sng-in-left}:host(.sng-at--top-start.sng-leaving),:host(.sng-at--bottom-start.sng-leaving){animation-name:sng-out-left}:host(.sng-at--top-center.sng-entering){animation-name:sng-in-top}:host(.sng-at--top-center.sng-leaving){animation-name:sng-out-top}:host(.sng-at--bottom-center.sng-entering){animation-name:sng-in-bottom}:host(.sng-at--bottom-center.sng-leaving){animation-name:sng-out-bottom}@media(prefers-reduced-motion:reduce){:host{--sng-shift: 0px;backdrop-filter:blur(var(--snackng-blur-reduced, 10px));-webkit-backdrop-filter:blur(var(--snackng-blur-reduced, 10px))}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
334
|
+
}
|
|
335
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngToast, decorators: [{
|
|
336
|
+
type: Component,
|
|
337
|
+
args: [{ selector: 'sng-toast', changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
338
|
+
'[class]': 'hostClasses()',
|
|
339
|
+
'(mouseenter)': 'pause()',
|
|
340
|
+
'(mouseleave)': 'resume()',
|
|
341
|
+
'(focusin)': 'pause()',
|
|
342
|
+
'(focusout)': 'resume()',
|
|
343
|
+
}, template: "@let toast = item();\n\n<!-- No aria-live here: SnackngLiveRegion announces instead. -->\n<div class=\"sng-accent\" aria-hidden=\"true\"></div>\n\n<div class=\"sng-icon\" aria-hidden=\"true\">\n @if (iconPath(); as path) {\n <svg viewBox=\"0 0 24 24\" focusable=\"false\">\n <path [attr.d]=\"path\" fill=\"currentColor\" />\n </svg>\n } @else {\n <span class=\"sng-icon-custom\" [innerHTML]=\"iconSvg()\"></span>\n }\n</div>\n\n<div class=\"sng-body\">\n @if (toast.title) {\n <p class=\"sng-title\">{{ toast.title }}</p>\n }\n <p class=\"sng-message\">{{ toast.message }}</p>\n</div>\n\n@if (toast.action; as action) {\n <button type=\"button\" class=\"sng-action\" (click)=\"onAction()\">{{ action.label }}</button>\n}\n\n@if (toast.dismissible) {\n <button type=\"button\" class=\"sng-close\" (click)=\"onDismiss()\" aria-label=\"Close notification\">\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path\n d=\"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"\n fill=\"currentColor\" />\n </svg>\n </button>\n}\n", styles: [":host{--sng-bg: var(--snackng-info-bg, linear-gradient(135deg, rgba(71, 85, 105, .9), rgba(40, 51, 69, .84)));--sng-ink: var(--snackng-info-ink, #ffffff);--sng-solid: var(--snackng-info-solid, #47556a);--sng-shift: var(--snackng-shift, 28px);position:relative;display:flex;align-items:center;gap:var(--snackng-gap, 14px);box-sizing:border-box;min-width:var(--snackng-min-width, 340px);max-width:var(--snackng-max-width, 460px);padding:var(--snackng-padding, 14px 18px 14px 22px);overflow:hidden;pointer-events:auto;border-radius:var(--snackng-radius, 14px);border:var(--snackng-border, 1px solid rgba(255, 255, 255, .22));background-image:var(--sng-bg);backdrop-filter:blur(var(--snackng-blur, 16px)) saturate(var(--snackng-saturate, 180%));-webkit-backdrop-filter:blur(var(--snackng-blur, 16px)) saturate(var(--snackng-saturate, 180%));box-shadow:var( --snackng-shadow, 0 12px 32px -10px rgba(17, 52, 111, .55), inset 0 1px 0 rgba(255, 255, 255, .28) );color:var(--sng-ink);font-family:var(--snackng-font, \"Manrope\", system-ui, sans-serif);line-height:var(--snackng-line-height, 1.45)}:host(.sng--success){--sng-bg: var(--snackng-success-bg, linear-gradient(135deg, rgba(33, 80, 159, .9), rgba(17, 52, 111, .84)));--sng-ink: var(--snackng-success-ink, #ffffff);--sng-solid: var(--snackng-success-solid, #21509f)}:host(.sng--danger){--sng-bg: var(--snackng-danger-bg, linear-gradient(135deg, rgba(192, 57, 43, .9), rgba(140, 35, 29, .84)));--sng-ink: var(--snackng-danger-ink, #ffffff);--sng-solid: var(--snackng-danger-solid, #c0392b)}:host(.sng--warning){--sng-bg: var(--snackng-warning-bg, linear-gradient(135deg, rgba(252, 211, 77, .92), rgba(245, 182, 43, .9)));--sng-ink: var(--snackng-warning-ink, #4a3200);--sng-solid: var(--snackng-warning-solid, #fcd34d);text-shadow:none}@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){:host{background-color:var(--sng-solid)}}@media(max-width:480px){:host{min-width:0;max-width:100%;width:100%}}.sng-accent{position:absolute;inset:0 auto 0 0;width:var(--snackng-accent-width, 4px);background:color-mix(in srgb,var(--sng-ink) 55%,transparent)}.sng-icon{flex:0 0 auto;display:flex;align-items:center;justify-content:center;padding:11px;border-radius:12px;background:color-mix(in srgb,var(--sng-ink) 16%,transparent);color:var(--sng-ink)}.sng-icon svg{display:block;width:24px;height:24px;fill:currentColor}.sng-icon-custom{display:flex}.sng-icon-custom ::ng-deep svg{display:block;width:24px;height:24px;fill:currentColor}.sng-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}.sng-title{margin:0;font-size:var(--snackng-title-size, 14px);font-weight:var(--snackng-title-weight, 700);line-height:1.3;letter-spacing:.1px;color:var(--sng-ink)}.sng-message{margin:0;font-size:var(--snackng-message-size, 13px);font-weight:var(--snackng-message-weight, 400);color:color-mix(in srgb,var(--sng-ink) 85%,transparent)}.sng-action{flex:0 0 auto;padding:7px 12px;border:1px solid color-mix(in srgb,var(--sng-ink) 32%,transparent);border-radius:8px;background:color-mix(in srgb,var(--sng-ink) 12%,transparent);color:var(--sng-ink);font:inherit;font-size:var(--snackng-action-size, 13px);font-weight:var(--snackng-action-weight, 600);cursor:pointer;transition:background .15s ease}.sng-action:hover{background:color-mix(in srgb,var(--sng-ink) 22%,transparent)}.sng-close{flex:0 0 auto;display:flex;padding:4px;border:0;border-radius:6px;background:none;color:color-mix(in srgb,var(--sng-ink) 70%,transparent);cursor:pointer}.sng-close:hover{background:color-mix(in srgb,var(--sng-ink) 14%,transparent);color:var(--sng-ink)}.sng-close svg{display:block;width:18px;height:18px}.sng-action:focus-visible,.sng-close:focus-visible{outline:2px solid var(--sng-ink);outline-offset:2px}@keyframes sng-in-right{0%{opacity:0;transform:translate(var(--sng-shift))}to{opacity:1;transform:translate(0)}}@keyframes sng-out-right{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(var(--sng-shift))}}@keyframes sng-in-left{0%{opacity:0;transform:translate(calc(-1 * var(--sng-shift)))}to{opacity:1;transform:translate(0)}}@keyframes sng-out-left{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(calc(-1 * var(--sng-shift)))}}@keyframes sng-in-top{0%{opacity:0;transform:translateY(calc(-1 * var(--sng-shift)))}to{opacity:1;transform:translateY(0)}}@keyframes sng-out-top{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(calc(-1 * var(--sng-shift)))}}@keyframes sng-in-bottom{0%{opacity:0;transform:translateY(var(--sng-shift))}to{opacity:1;transform:translateY(0)}}@keyframes sng-out-bottom{0%{opacity:1;transform:translateY(0)}to{opacity:0;transform:translateY(var(--sng-shift))}}:host(.sng-entering){animation-duration:var(--snackng-enter-duration, .4s);animation-timing-function:cubic-bezier(0,0,.2,1);animation-fill-mode:both}:host(.sng-leaving){animation-duration:var(--snackng-exit-duration, .2s);animation-timing-function:cubic-bezier(.4,0,1,1);animation-fill-mode:both}:host(.sng-at--top-end.sng-entering),:host(.sng-at--bottom-end.sng-entering){animation-name:sng-in-right}:host(.sng-at--top-end.sng-leaving),:host(.sng-at--bottom-end.sng-leaving){animation-name:sng-out-right}:host(.sng-at--top-start.sng-entering),:host(.sng-at--bottom-start.sng-entering){animation-name:sng-in-left}:host(.sng-at--top-start.sng-leaving),:host(.sng-at--bottom-start.sng-leaving){animation-name:sng-out-left}:host(.sng-at--top-center.sng-entering){animation-name:sng-in-top}:host(.sng-at--top-center.sng-leaving){animation-name:sng-out-top}:host(.sng-at--bottom-center.sng-entering){animation-name:sng-in-bottom}:host(.sng-at--bottom-center.sng-leaving){animation-name:sng-out-bottom}@media(prefers-reduced-motion:reduce){:host{--sng-shift: 0px;backdrop-filter:blur(var(--snackng-blur-reduced, 10px));-webkit-backdrop-filter:blur(var(--snackng-blur-reduced, 10px))}}\n"] }]
|
|
344
|
+
}], ctorParameters: () => [], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }] } });
|
|
345
|
+
|
|
346
|
+
const SHIFT_DURATION = 260;
|
|
347
|
+
const SHIFT_EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';
|
|
348
|
+
/** Guarded: test environments such as jsdom render without `matchMedia`. */
|
|
349
|
+
function prefersReducedMotion() {
|
|
350
|
+
return (typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
351
|
+
}
|
|
352
|
+
class SnackngHost {
|
|
353
|
+
store = inject(SnackngStore);
|
|
354
|
+
element = inject(ElementRef);
|
|
355
|
+
stacks = this.store.stacks;
|
|
356
|
+
offsets = new Map();
|
|
357
|
+
shifts = new Map();
|
|
358
|
+
constructor() {
|
|
359
|
+
afterEveryRender(() => this.absorbLayoutShift());
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* FLIP: offset each slot back to where it was, then animate that offset away,
|
|
363
|
+
* so neighbours glide into a freed gap instead of snapping.
|
|
364
|
+
*
|
|
365
|
+
* Measured with `offsetTop` rather than `getBoundingClientRect`: the latter
|
|
366
|
+
* includes transforms and would read an in-flight shift instead of the layout
|
|
367
|
+
* beneath it. Stacks are full height and top-anchored, so a slot's offsetTop
|
|
368
|
+
* only changes when it truly moves. Applied to the slot, never the toast, to
|
|
369
|
+
* leave the toast's own enter/exit transform alone.
|
|
370
|
+
*/
|
|
371
|
+
absorbLayoutShift() {
|
|
372
|
+
const slots = this.element.nativeElement.querySelectorAll('.sng-slot');
|
|
373
|
+
const present = new Set();
|
|
374
|
+
for (const slot of slots) {
|
|
375
|
+
const id = slot.dataset['sngId'];
|
|
376
|
+
if (!id) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
present.add(id);
|
|
380
|
+
const top = slot.offsetTop;
|
|
381
|
+
const previous = this.offsets.get(id);
|
|
382
|
+
this.offsets.set(id, top);
|
|
383
|
+
if (previous === undefined || previous === top || prefersReducedMotion()) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
this.shifts.get(id)?.cancel();
|
|
387
|
+
this.shifts.set(id, slot.animate([{ transform: `translateY(${previous - top}px)` }, { transform: 'none' }], {
|
|
388
|
+
duration: SHIFT_DURATION,
|
|
389
|
+
easing: SHIFT_EASING,
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
for (const id of [...this.offsets.keys()]) {
|
|
393
|
+
if (!present.has(id)) {
|
|
394
|
+
this.offsets.delete(id);
|
|
395
|
+
this.shifts.delete(id);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
400
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: SnackngHost, isStandalone: true, selector: "sng-host", ngImport: i0, template: "@for (stack of stacks(); track stack.position) {\n <div class=\"sng-stack\" [class]=\"'sng-stack--' + stack.position\">\n @for (item of stack.items; track item.id) {\n <div class=\"sng-slot\" [attr.data-sng-id]=\"item.id\">\n <sng-toast [item]=\"item\" />\n </div>\n }\n </div>\n}\n", styles: [":host{position:fixed;inset:0;z-index:var(--snackng-z, 2000);pointer-events:none}.sng-stack{position:absolute;display:flex;flex-direction:column;gap:var(--snackng-stack-gap, 12px);padding:var(--snackng-stack-padding, 16px);max-width:100%}.sng-slot{display:flex;max-width:100%}.sng-stack--top-start,.sng-stack--top-center,.sng-stack--top-end{top:0}.sng-stack--bottom-start,.sng-stack--bottom-center,.sng-stack--bottom-end{top:0;bottom:0;flex-direction:column-reverse;justify-content:flex-start}.sng-stack--top-start,.sng-stack--bottom-start{left:0;align-items:flex-start}.sng-stack--top-end,.sng-stack--bottom-end{right:0;align-items:flex-end}.sng-stack--top-center,.sng-stack--bottom-center{left:50%;transform:translate(-50%);align-items:center}@media(max-width:480px){.sng-stack{left:0;right:0;transform:none;align-items:stretch}}\n"], dependencies: [{ kind: "component", type: SnackngToast, selector: "sng-toast", inputs: ["item"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
401
|
+
}
|
|
402
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngHost, decorators: [{
|
|
403
|
+
type: Component,
|
|
404
|
+
args: [{ selector: 'sng-host', imports: [SnackngToast], changeDetection: ChangeDetectionStrategy.OnPush, template: "@for (stack of stacks(); track stack.position) {\n <div class=\"sng-stack\" [class]=\"'sng-stack--' + stack.position\">\n @for (item of stack.items; track item.id) {\n <div class=\"sng-slot\" [attr.data-sng-id]=\"item.id\">\n <sng-toast [item]=\"item\" />\n </div>\n }\n </div>\n}\n", styles: [":host{position:fixed;inset:0;z-index:var(--snackng-z, 2000);pointer-events:none}.sng-stack{position:absolute;display:flex;flex-direction:column;gap:var(--snackng-stack-gap, 12px);padding:var(--snackng-stack-padding, 16px);max-width:100%}.sng-slot{display:flex;max-width:100%}.sng-stack--top-start,.sng-stack--top-center,.sng-stack--top-end{top:0}.sng-stack--bottom-start,.sng-stack--bottom-center,.sng-stack--bottom-end{top:0;bottom:0;flex-direction:column-reverse;justify-content:flex-start}.sng-stack--top-start,.sng-stack--bottom-start{left:0;align-items:flex-start}.sng-stack--top-end,.sng-stack--bottom-end{right:0;align-items:flex-end}.sng-stack--top-center,.sng-stack--bottom-center{left:50%;transform:translate(-50%);align-items:center}@media(max-width:480px){.sng-stack{left:0;right:0;transform:none;align-items:stretch}}\n"] }]
|
|
405
|
+
}], ctorParameters: () => [] });
|
|
406
|
+
|
|
407
|
+
const NOOP_REF = {
|
|
408
|
+
id: 'sng-noop',
|
|
409
|
+
dismiss: () => { },
|
|
410
|
+
afterDismissed: Promise.resolve('manual'),
|
|
411
|
+
};
|
|
412
|
+
class SnackngService {
|
|
413
|
+
store = inject(SnackngStore);
|
|
414
|
+
config = inject(SNACKNG_CONFIG);
|
|
415
|
+
liveRegion = inject(SnackngLiveRegion);
|
|
416
|
+
appRef = inject(ApplicationRef);
|
|
417
|
+
injector = inject(EnvironmentInjector);
|
|
418
|
+
document = inject(DOCUMENT);
|
|
419
|
+
isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
420
|
+
host;
|
|
421
|
+
/**
|
|
422
|
+
* Toasts accepted but still waiting for a free slot under `overflow: 'queue'`.
|
|
423
|
+
* Useful for a "3 more" affordance.
|
|
424
|
+
*/
|
|
425
|
+
pending = this.store.pending;
|
|
426
|
+
success(message, options) {
|
|
427
|
+
return this.show('success', message, options);
|
|
428
|
+
}
|
|
429
|
+
warning(message, options) {
|
|
430
|
+
return this.show('warning', message, options);
|
|
431
|
+
}
|
|
432
|
+
danger(message, options) {
|
|
433
|
+
return this.show('danger', message, options);
|
|
434
|
+
}
|
|
435
|
+
info(message, options) {
|
|
436
|
+
return this.show('info', message, options);
|
|
437
|
+
}
|
|
438
|
+
show(type, message, options = {}) {
|
|
439
|
+
// Client-only: no-op rather than throw on `document` during SSR.
|
|
440
|
+
if (!this.isBrowser) {
|
|
441
|
+
return NOOP_REF;
|
|
442
|
+
}
|
|
443
|
+
this.ensureHost();
|
|
444
|
+
const politeness = this.resolvePoliteness(type, options);
|
|
445
|
+
const { id, afterDismissed } = this.store.add({
|
|
446
|
+
type,
|
|
447
|
+
message,
|
|
448
|
+
title: options.title,
|
|
449
|
+
duration: options.duration ?? this.config.duration,
|
|
450
|
+
position: options.position ?? this.config.position,
|
|
451
|
+
dismissible: options.dismissible ?? this.config.dismissible,
|
|
452
|
+
action: options.action,
|
|
453
|
+
panelClass: normalizeClasses(options.panelClass),
|
|
454
|
+
politeness,
|
|
455
|
+
});
|
|
456
|
+
this.liveRegion.announce([options.title, message].filter(Boolean).join('. '), politeness);
|
|
457
|
+
return {
|
|
458
|
+
id,
|
|
459
|
+
afterDismissed,
|
|
460
|
+
dismiss: () => this.store.dismiss(id, 'manual'),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
dismissAll() {
|
|
464
|
+
this.store.dismissAll();
|
|
465
|
+
}
|
|
466
|
+
resolvePoliteness(type, options) {
|
|
467
|
+
if (options.politeness) {
|
|
468
|
+
return options.politeness;
|
|
469
|
+
}
|
|
470
|
+
const configured = this.config.types[type]?.politeness;
|
|
471
|
+
if (configured) {
|
|
472
|
+
return configured;
|
|
473
|
+
}
|
|
474
|
+
// Errors interrupt; everything else waits for a pause.
|
|
475
|
+
return type === 'danger' ? 'assertive' : 'polite';
|
|
476
|
+
}
|
|
477
|
+
ensureHost() {
|
|
478
|
+
if (this.host) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
this.host = createComponent(SnackngHost, { environmentInjector: this.injector });
|
|
482
|
+
this.appRef.attachView(this.host.hostView);
|
|
483
|
+
this.document.body.appendChild(this.host.location.nativeElement);
|
|
484
|
+
}
|
|
485
|
+
ngOnDestroy() {
|
|
486
|
+
if (this.host) {
|
|
487
|
+
this.appRef.detachView(this.host.hostView);
|
|
488
|
+
this.host.destroy();
|
|
489
|
+
this.host = undefined;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
493
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngService, providedIn: 'root' });
|
|
494
|
+
}
|
|
495
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: SnackngService, decorators: [{
|
|
496
|
+
type: Injectable,
|
|
497
|
+
args: [{ providedIn: 'root' }]
|
|
498
|
+
}] });
|
|
499
|
+
function normalizeClasses(panelClass) {
|
|
500
|
+
if (!panelClass) {
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
503
|
+
return Array.isArray(panelClass) ? panelClass : [panelClass];
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/*
|
|
507
|
+
* Public API Surface of snackng
|
|
508
|
+
*/
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Generated bundle index. Do not edit.
|
|
512
|
+
*/
|
|
513
|
+
|
|
514
|
+
export { SNACKNG_CONFIG, SNACKNG_DEFAULTS, SnackngService, provideSnackng };
|
|
515
|
+
//# sourceMappingURL=snackng.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snackng.mjs","sources":["../../../projects/snackng/src/lib/snackng.config.ts","../../../projects/snackng/src/lib/live-region.ts","../../../projects/snackng/src/lib/icons.ts","../../../projects/snackng/src/lib/snackng.store.ts","../../../projects/snackng/src/lib/snackng-toast/snackng-toast.ts","../../../projects/snackng/src/lib/snackng-toast/snackng-toast.html","../../../projects/snackng/src/lib/snackng-host/snackng-host.ts","../../../projects/snackng/src/lib/snackng-host/snackng-host.html","../../../projects/snackng/src/lib/snackng.service.ts","../../../projects/snackng/src/public-api.ts","../../../projects/snackng/src/snackng.ts"],"sourcesContent":["import { InjectionToken, EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';\nimport { SnackngOverflow, SnackngPosition, SnackngTypeDef } from './snackng.model';\n\nexport interface SnackngConfig {\n duration: number;\n position: SnackngPosition;\n /** Maximum toasts on screen at once. Extras are handled per `overflow`. */\n max: number;\n /**\n * What happens when a toast arrives while `max` are already visible.\n * `'queue'` waits for a free slot — nothing is ever lost, but a flood becomes\n * a long parade. `'dismiss-oldest'` evicts the oldest to make room, so the\n * newest always wins; useful when an interceptor can emit toasts in bursts.\n */\n overflow: SnackngOverflow;\n /**\n * Milliseconds between releasing consecutive queued toasts. Staggering keeps\n * a burst from firing every enter animation at the same instant. `0` releases\n * as many as fit at once.\n */\n stagger: number;\n pauseOnHover: boolean;\n dismissible: boolean;\n types: Record<string, SnackngTypeDef>;\n}\n\nexport const SNACKNG_DEFAULTS: SnackngConfig = {\n duration: 5000,\n position: 'top-end',\n max: 5,\n overflow: 'queue',\n stagger: 90,\n pauseOnHover: true,\n dismissible: false,\n types: {},\n};\n\nexport const SNACKNG_CONFIG = new InjectionToken<SnackngConfig>('snackng.config', {\n providedIn: 'root',\n factory: () => SNACKNG_DEFAULTS,\n});\n\n/**\n * Optional. Without it the defaults above apply, so `npm i snackng` and calling\n * the service is enough to get working toasts.\n */\nexport function provideSnackng(config: Partial<SnackngConfig> = {}): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: SNACKNG_CONFIG,\n useValue: {\n ...SNACKNG_DEFAULTS,\n ...config,\n types: { ...SNACKNG_DEFAULTS.types, ...config.types },\n } satisfies SnackngConfig,\n },\n ]);\n}\n","import { DOCUMENT, Injectable, OnDestroy, inject } from '@angular/core';\nimport { SnackngPoliteness } from './snackng.model';\n\n/**\n * Announces toast text to screen readers, in place of the CDK `LiveAnnouncer`.\n *\n * The toast markup carries no `aria-live` of its own: text is copied into this\n * off-screen region instead. Keep it that way — a live region nested inside\n * another throws `HierarchyRequestError` when the outer one relocates its\n * content.\n */\n@Injectable({ providedIn: 'root' })\nexport class SnackngLiveRegion implements OnDestroy {\n private readonly document = inject(DOCUMENT);\n private region?: HTMLElement;\n private clearTimer?: ReturnType<typeof setTimeout>;\n\n announce(message: string, politeness: SnackngPoliteness = 'polite'): void {\n if (politeness === 'off' || !message) {\n return;\n }\n\n const region = this.ensureRegion();\n region.setAttribute('aria-live', politeness);\n\n // Clearing first forces assistive tech to treat an identical repeated\n // message as a new announcement.\n region.textContent = '';\n clearTimeout(this.clearTimer);\n this.clearTimer = setTimeout(() => (region.textContent = message), 100);\n }\n\n private ensureRegion(): HTMLElement {\n if (this.region?.isConnected) {\n return this.region;\n }\n\n const region = this.document.createElement('div');\n region.setAttribute('aria-atomic', 'true');\n region.setAttribute('aria-live', 'polite');\n region.classList.add('sng-live-region');\n // Visually hidden but still announced: `display:none` would silence it.\n region.style.cssText =\n 'position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;' +\n 'clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;';\n\n this.document.body.appendChild(region);\n this.region = region;\n return region;\n }\n\n ngOnDestroy(): void {\n clearTimeout(this.clearTimer);\n this.region?.remove();\n this.region = undefined;\n }\n}\n","import { SnackngBuiltInType } from './snackng.model';\n\n/**\n * Path data for the built-in glyphs, inlined so the package never requires an\n * icon font. Bound with `[attr.d]`, so no sanitizer is involved.\n */\nexport const SNACKNG_ICONS: Record<SnackngBuiltInType, string> = {\n success: 'M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',\n warning: 'M1 21h22L12 2zm12-3h-2v-2h2zm0-4h-2v-4h2z',\n danger:\n 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 15h-2v-2h2zm0-4h-2V7h2z',\n info: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 15h-2v-6h2zm0-8h-2V7h2z',\n};\n","import { Injectable, OnDestroy, computed, inject, signal } from '@angular/core';\nimport { SNACKNG_CONFIG } from './snackng.config';\nimport {\n SnackngAction,\n SnackngDismissReason,\n SnackngPoliteness,\n SnackngPosition,\n SnackngType,\n} from './snackng.model';\n\nexport interface SnackngItem {\n readonly id: string;\n readonly type: SnackngType;\n readonly message: string;\n readonly title?: string;\n readonly duration: number;\n readonly position: SnackngPosition;\n readonly dismissible: boolean;\n readonly action?: SnackngAction;\n readonly panelClass: readonly string[];\n readonly politeness: SnackngPoliteness;\n /** Set while the exit animation runs; the item is still in the DOM. */\n readonly leaving: boolean;\n}\n\nexport interface SnackngStack {\n readonly position: SnackngPosition;\n readonly items: readonly SnackngItem[];\n}\n\n/**\n * Toast state, and the pacing of how toasts reach the screen.\n *\n * Producer/consumer: `add` only enqueues; `pump` is the sole consumer moving\n * items into view as capacity allows. Releasing directly would let a burst\n * render in one tick, appearing and evicting itself in the same frame.\n *\n * Separate from `SnackngService` so the service can create the host while the\n * host reads state, without an import cycle.\n */\n@Injectable({ providedIn: 'root' })\nexport class SnackngStore implements OnDestroy {\n private readonly config = inject(SNACKNG_CONFIG);\n private readonly resolvers = new Map<string, (reason: SnackngDismissReason) => void>();\n private readonly reasons = new Map<string, SnackngDismissReason>();\n private pumpTimer?: ReturnType<typeof setTimeout>;\n private lastReleaseAt = 0;\n private counter = 0;\n\n /** Toasts currently rendered (including those playing their exit animation). */\n readonly items = signal<readonly SnackngItem[]>([]);\n /** Toasts accepted but still waiting for a free slot. */\n readonly queue = signal<readonly SnackngItem[]>([]);\n readonly pending = computed(() => this.queue().length);\n\n readonly stacks = computed<readonly SnackngStack[]>(() => {\n const byPosition = new Map<SnackngPosition, SnackngItem[]>();\n for (const item of this.items()) {\n const bucket = byPosition.get(item.position);\n bucket ? bucket.push(item) : byPosition.set(item.position, [item]);\n }\n return [...byPosition].map(([position, items]) => ({ position, items }));\n });\n\n add(item: Omit<SnackngItem, 'id' | 'leaving'>): {\n id: string;\n afterDismissed: Promise<SnackngDismissReason>;\n } {\n const id = `sng-${++this.counter}`;\n const afterDismissed = new Promise<SnackngDismissReason>(resolve =>\n this.resolvers.set(id, resolve),\n );\n\n this.queue.update(queue => [...queue, { ...item, id, leaving: false }]);\n this.pump();\n\n return { id, afterDismissed };\n }\n\n /**\n * Starts the exit animation; the item leaves the DOM on `finalize`. A toast\n * still queued is dropped outright, having never been shown.\n */\n dismiss(id: string, reason: SnackngDismissReason = 'manual'): void {\n if (this.queue().some(item => item.id === id)) {\n this.queue.update(queue => queue.filter(item => item.id !== id));\n this.resolve(id, reason);\n return;\n }\n\n const target = this.items().find(item => item.id === id);\n if (!target || target.leaving) {\n return;\n }\n this.reasons.set(id, reason);\n this.items.update(items => items.map(i => (i.id === id ? { ...i, leaving: true } : i)));\n }\n\n /** Removes the item and resolves its `afterDismissed` promise. */\n finalize(id: string): void {\n if (!this.resolvers.has(id)) {\n return;\n }\n this.items.update(items => items.filter(item => item.id !== id));\n this.resolve(id, this.reasons.get(id) ?? 'manual');\n this.reasons.delete(id);\n this.pump();\n }\n\n dismissAll(reason: SnackngDismissReason = 'manual'): void {\n // Drain the queue first, or it would march onto a screen just cleared.\n clearTimeout(this.pumpTimer);\n this.pumpTimer = undefined;\n for (const item of this.queue()) {\n this.resolve(item.id, reason);\n }\n this.queue.set([]);\n\n for (const item of this.items()) {\n this.dismiss(item.id, reason);\n }\n }\n\n /** The consumer. Runs on every add and every finalize. */\n private pump(): void {\n if (this.pumpTimer) {\n return; // a staggered release is already pending\n }\n\n while (this.queue().length) {\n // Counts leaving toasts too: they hold their slot until finalize removes\n // them, so ignoring them reads an expiring batch as free space.\n const onScreen = this.items();\n\n if (onScreen.length >= this.config.max) {\n if (this.config.overflow === 'dismiss-oldest') {\n // Evicting an already-leaving toast frees nothing.\n const victim = onScreen.find(item => !item.leaving);\n if (victim) {\n this.dismiss(victim.id, 'replaced');\n }\n }\n return;\n }\n\n // Spaced by elapsed time, not queue length: callers add one at a time, so\n // a burst arrives as N pumps of one item and would never wait.\n const wait = this.config.stagger - (Date.now() - this.lastReleaseAt);\n if (this.config.stagger > 0 && wait > 0) {\n this.pumpTimer = setTimeout(() => {\n this.pumpTimer = undefined;\n this.pump();\n }, wait);\n return;\n }\n\n const [next, ...rest] = this.queue();\n this.queue.set(rest);\n this.items.update(items => [...items, next]);\n this.lastReleaseAt = Date.now();\n }\n }\n\n private resolve(id: string, reason: SnackngDismissReason): void {\n this.resolvers.get(id)?.(reason);\n this.resolvers.delete(id);\n }\n\n ngOnDestroy(): void {\n clearTimeout(this.pumpTimer);\n }\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n OnDestroy,\n computed,\n effect,\n inject,\n input,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { SNACKNG_CONFIG } from '../snackng.config';\nimport { SNACKNG_ICONS } from '../icons';\nimport { SnackngBuiltInType } from '../snackng.model';\nimport { SnackngItem, SnackngStore } from '../snackng.store';\n\nconst EXIT_DURATION = 200;\n/** Guards against `animationend` never firing (reduced motion, jsdom, backgrounded tab). */\nconst EXIT_FALLBACK = EXIT_DURATION + 150;\n\n@Component({\n selector: 'sng-toast',\n templateUrl: './snackng-toast.html',\n styleUrl: './snackng-toast.css',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '(mouseenter)': 'pause()',\n '(mouseleave)': 'resume()',\n '(focusin)': 'pause()',\n '(focusout)': 'resume()',\n },\n})\nexport class SnackngToast implements OnDestroy {\n readonly item = input.required<SnackngItem>();\n\n private readonly store = inject(SnackngStore);\n private readonly config = inject(SNACKNG_CONFIG);\n private readonly sanitizer = inject(DomSanitizer);\n private readonly element = inject<ElementRef<HTMLElement>>(ElementRef);\n\n private timer?: ReturnType<typeof setTimeout>;\n private exitTimer?: ReturnType<typeof setTimeout>;\n private armed = false;\n private remaining = 0;\n private startedAt = 0;\n\n protected readonly hostClasses = computed(() => {\n const item = this.item();\n return [\n 'sng-toast',\n `sng--${item.type}`,\n `sng-at--${item.position}`,\n item.leaving ? 'sng-leaving' : 'sng-entering',\n ...item.panelClass,\n ].join(' ');\n });\n\n /** Null when the type brings its own SVG; unknown types fall back to `info`. */\n protected readonly iconPath = computed<string | null>(() => {\n const type = this.item().type;\n if (this.config.types[type]?.icon) {\n return null;\n }\n return SNACKNG_ICONS[type as SnackngBuiltInType] ?? SNACKNG_ICONS.info;\n });\n\n /** Custom types may supply raw SVG markup, trusted as authored config. */\n protected readonly iconSvg = computed<SafeHtml | null>(() => {\n const custom = this.config.types[this.item().type]?.icon;\n return custom ? this.sanitizer.bypassSecurityTrustHtml(custom) : null;\n });\n\n constructor() {\n effect(() => {\n if (this.item().leaving) {\n this.stopTimer();\n this.scheduleFinalize();\n } else {\n this.startTimer();\n }\n });\n }\n\n protected onAction(): void {\n const action = this.item().action;\n action?.handler?.();\n if (action?.dismissOnClick !== false) {\n this.store.dismiss(this.item().id, 'action');\n }\n }\n\n protected onDismiss(): void {\n this.store.dismiss(this.item().id, 'manual');\n }\n\n protected pause(): void {\n if (!this.config.pauseOnHover || !this.timer) {\n return;\n }\n this.remaining -= Date.now() - this.startedAt;\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n protected resume(): void {\n if (!this.config.pauseOnHover || this.timer || this.remaining <= 0 || this.item().leaving) {\n return;\n }\n this.arm(this.remaining);\n }\n\n /** Arms once per toast: re-running on later item updates would reset a paused timer. */\n private startTimer(): void {\n const duration = this.item().duration;\n if (this.armed || duration <= 0) {\n return;\n }\n this.armed = true;\n this.remaining = duration;\n this.arm(duration);\n }\n\n private arm(ms: number): void {\n this.startedAt = Date.now();\n this.timer = setTimeout(() => this.store.dismiss(this.item().id, 'timeout'), ms);\n }\n\n private stopTimer(): void {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n private scheduleFinalize(): void {\n if (this.exitTimer) {\n return;\n }\n const finalize = () => {\n clearTimeout(this.exitTimer);\n this.store.finalize(this.item().id);\n };\n this.element.nativeElement.addEventListener('animationend', finalize, { once: true });\n this.exitTimer = setTimeout(finalize, EXIT_FALLBACK);\n }\n\n ngOnDestroy(): void {\n this.stopTimer();\n clearTimeout(this.exitTimer);\n }\n}\n","@let toast = item();\n\n<!-- No aria-live here: SnackngLiveRegion announces instead. -->\n<div class=\"sng-accent\" aria-hidden=\"true\"></div>\n\n<div class=\"sng-icon\" aria-hidden=\"true\">\n @if (iconPath(); as path) {\n <svg viewBox=\"0 0 24 24\" focusable=\"false\">\n <path [attr.d]=\"path\" fill=\"currentColor\" />\n </svg>\n } @else {\n <span class=\"sng-icon-custom\" [innerHTML]=\"iconSvg()\"></span>\n }\n</div>\n\n<div class=\"sng-body\">\n @if (toast.title) {\n <p class=\"sng-title\">{{ toast.title }}</p>\n }\n <p class=\"sng-message\">{{ toast.message }}</p>\n</div>\n\n@if (toast.action; as action) {\n <button type=\"button\" class=\"sng-action\" (click)=\"onAction()\">{{ action.label }}</button>\n}\n\n@if (toast.dismissible) {\n <button type=\"button\" class=\"sng-close\" (click)=\"onDismiss()\" aria-label=\"Close notification\">\n <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">\n <path\n d=\"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"\n fill=\"currentColor\" />\n </svg>\n </button>\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n afterEveryRender,\n inject,\n} from '@angular/core';\nimport { SnackngToast } from '../snackng-toast/snackng-toast';\nimport { SnackngStore } from '../snackng.store';\n\nconst SHIFT_DURATION = 260;\nconst SHIFT_EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';\n\n/** Guarded: test environments such as jsdom render without `matchMedia`. */\nfunction prefersReducedMotion(): boolean {\n return (\n typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\n@Component({\n selector: 'sng-host',\n imports: [SnackngToast],\n templateUrl: './snackng-host.html',\n styleUrl: './snackng-host.css',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SnackngHost {\n private readonly store = inject(SnackngStore);\n private readonly element = inject<ElementRef<HTMLElement>>(ElementRef);\n\n protected readonly stacks = this.store.stacks;\n\n private readonly offsets = new Map<string, number>();\n private readonly shifts = new Map<string, Animation>();\n\n constructor() {\n afterEveryRender(() => this.absorbLayoutShift());\n }\n\n /**\n * FLIP: offset each slot back to where it was, then animate that offset away,\n * so neighbours glide into a freed gap instead of snapping.\n *\n * Measured with `offsetTop` rather than `getBoundingClientRect`: the latter\n * includes transforms and would read an in-flight shift instead of the layout\n * beneath it. Stacks are full height and top-anchored, so a slot's offsetTop\n * only changes when it truly moves. Applied to the slot, never the toast, to\n * leave the toast's own enter/exit transform alone.\n */\n private absorbLayoutShift(): void {\n const slots = this.element.nativeElement.querySelectorAll<HTMLElement>('.sng-slot');\n const present = new Set<string>();\n\n for (const slot of slots) {\n const id = slot.dataset['sngId'];\n if (!id) {\n continue;\n }\n present.add(id);\n\n const top = slot.offsetTop;\n const previous = this.offsets.get(id);\n this.offsets.set(id, top);\n\n if (previous === undefined || previous === top || prefersReducedMotion()) {\n continue;\n }\n\n this.shifts.get(id)?.cancel();\n this.shifts.set(\n id,\n slot.animate([{ transform: `translateY(${previous - top}px)` }, { transform: 'none' }], {\n duration: SHIFT_DURATION,\n easing: SHIFT_EASING,\n }),\n );\n }\n\n for (const id of [...this.offsets.keys()]) {\n if (!present.has(id)) {\n this.offsets.delete(id);\n this.shifts.delete(id);\n }\n }\n }\n}\n","@for (stack of stacks(); track stack.position) {\n <div class=\"sng-stack\" [class]=\"'sng-stack--' + stack.position\">\n @for (item of stack.items; track item.id) {\n <div class=\"sng-slot\" [attr.data-sng-id]=\"item.id\">\n <sng-toast [item]=\"item\" />\n </div>\n }\n </div>\n}\n","import {\n ApplicationRef,\n ComponentRef,\n DOCUMENT,\n EnvironmentInjector,\n Injectable,\n OnDestroy,\n PLATFORM_ID,\n createComponent,\n inject,\n} from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { SNACKNG_CONFIG } from './snackng.config';\nimport { SnackngLiveRegion } from './live-region';\nimport { SnackngHost } from './snackng-host/snackng-host';\nimport { SnackngStore } from './snackng.store';\nimport {\n SnackngDismissReason,\n SnackngOptions,\n SnackngPoliteness,\n SnackngRef,\n SnackngType,\n} from './snackng.model';\n\nconst NOOP_REF: SnackngRef = {\n id: 'sng-noop',\n dismiss: () => {},\n afterDismissed: Promise.resolve<SnackngDismissReason>('manual'),\n};\n\n@Injectable({ providedIn: 'root' })\nexport class SnackngService implements OnDestroy {\n private readonly store = inject(SnackngStore);\n private readonly config = inject(SNACKNG_CONFIG);\n private readonly liveRegion = inject(SnackngLiveRegion);\n private readonly appRef = inject(ApplicationRef);\n private readonly injector = inject(EnvironmentInjector);\n private readonly document = inject(DOCUMENT);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private host?: ComponentRef<SnackngHost>;\n\n /**\n * Toasts accepted but still waiting for a free slot under `overflow: 'queue'`.\n * Useful for a \"3 more\" affordance.\n */\n readonly pending = this.store.pending;\n\n success(message: string, options?: SnackngOptions): SnackngRef {\n return this.show('success', message, options);\n }\n\n warning(message: string, options?: SnackngOptions): SnackngRef {\n return this.show('warning', message, options);\n }\n\n danger(message: string, options?: SnackngOptions): SnackngRef {\n return this.show('danger', message, options);\n }\n\n info(message: string, options?: SnackngOptions): SnackngRef {\n return this.show('info', message, options);\n }\n\n show(type: SnackngType, message: string, options: SnackngOptions = {}): SnackngRef {\n // Client-only: no-op rather than throw on `document` during SSR.\n if (!this.isBrowser) {\n return NOOP_REF;\n }\n\n this.ensureHost();\n\n const politeness = this.resolvePoliteness(type, options);\n const { id, afterDismissed } = this.store.add({\n type,\n message,\n title: options.title,\n duration: options.duration ?? this.config.duration,\n position: options.position ?? this.config.position,\n dismissible: options.dismissible ?? this.config.dismissible,\n action: options.action,\n panelClass: normalizeClasses(options.panelClass),\n politeness,\n });\n\n this.liveRegion.announce([options.title, message].filter(Boolean).join('. '), politeness);\n\n return {\n id,\n afterDismissed,\n dismiss: () => this.store.dismiss(id, 'manual'),\n };\n }\n\n dismissAll(): void {\n this.store.dismissAll();\n }\n\n private resolvePoliteness(type: SnackngType, options: SnackngOptions): SnackngPoliteness {\n if (options.politeness) {\n return options.politeness;\n }\n const configured = this.config.types[type]?.politeness;\n if (configured) {\n return configured;\n }\n // Errors interrupt; everything else waits for a pause.\n return type === 'danger' ? 'assertive' : 'polite';\n }\n\n private ensureHost(): void {\n if (this.host) {\n return;\n }\n this.host = createComponent(SnackngHost, { environmentInjector: this.injector });\n this.appRef.attachView(this.host.hostView);\n this.document.body.appendChild(this.host.location.nativeElement);\n }\n\n ngOnDestroy(): void {\n if (this.host) {\n this.appRef.detachView(this.host.hostView);\n this.host.destroy();\n this.host = undefined;\n }\n }\n}\n\nfunction normalizeClasses(panelClass: string | string[] | undefined): string[] {\n if (!panelClass) {\n return [];\n }\n return Array.isArray(panelClass) ? panelClass : [panelClass];\n}\n","/*\n * Public API Surface of snackng\n */\n\nexport { SnackngService } from './lib/snackng.service';\nexport { provideSnackng, SNACKNG_CONFIG, SNACKNG_DEFAULTS } from './lib/snackng.config';\nexport type { SnackngConfig } from './lib/snackng.config';\nexport type {\n SnackngAction,\n SnackngBuiltInType,\n SnackngDismissReason,\n SnackngOptions,\n SnackngPoliteness,\n SnackngPosition,\n SnackngRef,\n SnackngType,\n SnackngTypeDef,\n} from './lib/snackng.model';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|