@styloviz/toast 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 StyloViz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @styloviz/toast
2
+
3
+ > Imperative toast stack with 4 variants, 6 anchor positions, action callbacks and an auto-dismiss timer.
4
+
5
+ Part of the **StyloViz UI Kit** — a premium Angular 21 + Tailwind CSS 4 dashboard component library. Standalone, `OnPush`, strongly typed, dark-mode ready.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @styloviz/core @styloviz/toast
11
+ ```
12
+
13
+ Requires `@angular/core` and `@angular/common` >= 21. `@styloviz/core` is a peer dependency shared by every component. Prefer everything at once? `@styloviz/all` installs the whole free tier in one command.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { SvToastHostComponent } from '@styloviz/toast';
19
+
20
+ @Component({
21
+ standalone: true,
22
+ imports: [SvToastHostComponent],
23
+ template: `
24
+ // place one host, then call the service
25
+ <sv-toast-host position="top-right" />
26
+
27
+ toast.success('Saved!');
28
+ `,
29
+ })
30
+ export class DemoComponent {}
31
+ ```
32
+
33
+ ## Inputs
34
+
35
+ | Input | Type | Default | Description |
36
+ | --- | --- | --- | --- |
37
+ | `position` | `ToastPosition` | `'top-right'` | Where the stack is anchored on screen. |
38
+ | `maxVisible` | `number` | `5` | Maximum number of toasts visible simultaneously. |
39
+
40
+ ## Documentation
41
+
42
+ Full API reference and live demos: https://styloviz.dev/docs/toast
43
+
44
+ ## License
45
+
46
+ MIT
@@ -0,0 +1,125 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, input, computed, signal, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NgClass } from '@angular/common';
4
+ import { ToastService } from '@styloviz/core';
5
+
6
+ // ─── Icon paths keyed to variant ──────────────────────────────────────────────
7
+ const VARIANT_ICONS = {
8
+ success: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
9
+ error: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z',
10
+ warning: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z',
11
+ info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
12
+ };
13
+ const VARIANT_COLORS = {
14
+ success: { icon: 'text-emerald-500 dark:text-emerald-400', progress: 'bg-emerald-500' },
15
+ error: { icon: 'text-red-500 dark:text-red-400', progress: 'bg-red-500' },
16
+ warning: { icon: 'text-amber-500 dark:text-amber-400', progress: 'bg-amber-500' },
17
+ info: { icon: 'text-sky-500 dark:text-sky-400', progress: 'bg-sky-500' },
18
+ };
19
+ // ─── Component ───────────────────────────────────────────────────────────────
20
+ /**
21
+ * ToastHostComponent — Renders the active toast stack from `ToastService`.
22
+ *
23
+ * Place **once** in your root shell template (e.g., `AppComponent` or
24
+ * `DashboardShellComponent`):
25
+ *
26
+ * ```html
27
+ * <sv-toast-host />
28
+ * ```
29
+ *
30
+ * Then trigger toasts imperatively from any component or service:
31
+ * ```typescript
32
+ * this.toast.success('Record saved!');
33
+ * this.toast.error('Failed to delete', { title: 'Error', duration: 0 });
34
+ * ```
35
+ */
36
+ class SvToastHostComponent {
37
+ toastService = inject(ToastService);
38
+ // ── Inputs ────────────────────────────────────────────────────────────────
39
+ /** Where the stack is anchored on screen. @default 'top-right' */
40
+ position = input('top-right', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
41
+ /** Maximum number of toasts visible simultaneously. @default 5 */
42
+ maxVisible = input(5, ...(ngDevMode ? [{ debugName: "maxVisible" }] : /* istanbul ignore next */ []));
43
+ // ── Reactive state ────────────────────────────────────────────────────────
44
+ toasts = computed(() => {
45
+ const all = this.toastService.toasts();
46
+ const pos = this.position();
47
+ const filtered = all.filter(t => t.position === pos);
48
+ return filtered.slice(-this.maxVisible());
49
+ }, ...(ngDevMode ? [{ debugName: "toasts" }] : /* istanbul ignore next */ []));
50
+ // ── Position class ────────────────────────────────────────────────────────
51
+ // flex direction is baked in: top positions stack DOWN (flex-col),
52
+ // bottom positions stack UP (flex-col-reverse) so new toasts appear
53
+ // closest to the screen edge.
54
+ positionClass = computed(() => {
55
+ const map = {
56
+ 'top-right': 'top-4 right-4 items-end flex-col',
57
+ 'top-left': 'top-4 left-4 items-start flex-col',
58
+ 'top-center': 'top-4 left-1/2 -translate-x-1/2 items-center flex-col',
59
+ 'bottom-right': 'bottom-4 right-4 items-end flex-col-reverse',
60
+ 'bottom-left': 'bottom-4 left-4 items-start flex-col-reverse',
61
+ 'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2 items-center flex-col-reverse',
62
+ };
63
+ return map[this.position()];
64
+ }, ...(ngDevMode ? [{ debugName: "positionClass" }] : /* istanbul ignore next */ []));
65
+ // ── Hover-pause (WCAG 2.2.1) ──────────────────────────────────────────────
66
+ // Pausing the auto-dismiss countdown while the pointer is over the stack
67
+ // gives users time to read and interact with toasts.
68
+ paused = signal(false, ...(ngDevMode ? [{ debugName: "paused" }] : /* istanbul ignore next */ []));
69
+ pauseAll() {
70
+ this.paused.set(true);
71
+ for (const t of this.toasts())
72
+ this.toastService.pause(t.id);
73
+ }
74
+ resumeAll() {
75
+ this.paused.set(false);
76
+ for (const t of this.toasts())
77
+ this.toastService.resume(t.id);
78
+ }
79
+ // ── Accessibility ─────────────────────────────────────────────────────────
80
+ // error/warning are assertive (interrupt); success/info are polite (status).
81
+ roleFor(toast) {
82
+ return toast.variant === 'error' || toast.variant === 'warning' ? 'alert' : 'status';
83
+ }
84
+ ariaLiveFor(toast) {
85
+ return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';
86
+ }
87
+ // ── Per-toast helpers ─────────────────────────────────────────────────────
88
+ iconPath(toast) {
89
+ return VARIANT_ICONS[toast.variant];
90
+ }
91
+ iconClass(toast) {
92
+ return VARIANT_COLORS[toast.variant].icon;
93
+ }
94
+ progressClass(toast) {
95
+ return VARIANT_COLORS[toast.variant].progress;
96
+ }
97
+ progressStyle(toast) {
98
+ if (!toast.duration)
99
+ return 'width: 0%';
100
+ return `animation: toast-progress ${toast.duration}ms linear forwards`;
101
+ }
102
+ dismiss(toast) {
103
+ this.toastService.dismiss(toast.id);
104
+ }
105
+ onActionClick(toast) {
106
+ toast.action?.callback();
107
+ this.toastService.dismiss(toast.id);
108
+ }
109
+ trackById(_, toast) {
110
+ return toast.id;
111
+ }
112
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvToastHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
113
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: SvToastHostComponent, isStandalone: true, selector: "sv-toast-host", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, maxVisible: { classPropertyName: "maxVisible", publicName: "maxVisible", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!-- Fixed toast stack \u2014 positioned via positionClass() computed from `position` input -->\n<div\n [ngClass]=\"['fixed z-50 flex gap-3 pointer-events-none', positionClass()]\"\n role=\"region\"\n aria-label=\"Notifications\"\n>\n\n @for (toast of toasts(); track trackById($index, toast)) {\n <div\n [attr.id]=\"toast.id\"\n [attr.role]=\"roleFor(toast)\"\n [attr.aria-live]=\"ariaLiveFor(toast)\"\n aria-atomic=\"true\"\n (mouseenter)=\"pauseAll()\"\n (mouseleave)=\"resumeAll()\"\n (focusin)=\"pauseAll()\"\n (focusout)=\"resumeAll()\"\n [ngClass]=\"[\n 'pointer-events-auto w-80 max-w-[calc(100vw-2rem)] rounded-xl shadow-lg border',\n 'bg-white dark:bg-gray-800',\n 'border-gray-200 dark:border-gray-700',\n 'flex flex-col overflow-hidden'\n ]\"\n >\n\n <!-- \u2500\u2500 Main row: icon + content + dismiss \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"flex items-start gap-3 p-4\">\n\n <!-- Variant icon -->\n <span [ngClass]=\"['mt-0.5 shrink-0', iconClass(toast)]\">\n <svg class=\"h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <path [attr.d]=\"iconPath(toast)\" />\n </svg>\n </span>\n\n <!-- Title + message -->\n <div class=\"min-w-0 flex-1\">\n @if (toast.title) {\n <p class=\"text-sm font-semibold text-gray-900 dark:text-white leading-snug\">\n {{ toast.title }}\n </p>\n }\n <p [ngClass]=\"[\n 'text-sm text-gray-600 dark:text-gray-300 leading-snug',\n toast.title ? 'mt-0.5' : ''\n ]\">\n {{ toast.message }}\n </p>\n\n <!-- Action button -->\n @if (toast.action) {\n <button\n type=\"button\"\n (click)=\"onActionClick(toast)\"\n class=\"mt-2 text-xs font-medium text-primary-600 dark:text-primary-400\n hover:text-primary-700 dark:hover:text-primary-300\n focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 rounded\"\n >\n {{ toast.action.label }}\n </button>\n }\n </div>\n\n <!-- Dismiss button -->\n @if (toast.dismissible) {\n <button\n type=\"button\"\n (click)=\"dismiss(toast)\"\n aria-label=\"Dismiss notification\"\n class=\"shrink-0 -mt-0.5 -mr-1 inline-flex h-6 w-6 items-center justify-center\n rounded-md text-gray-500 dark:text-gray-400\n hover:bg-gray-100 dark:hover:bg-gray-700\n hover:text-gray-600 dark:hover:text-gray-300\n focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500\n transition-colors duration-100\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\"/>\n </svg>\n </button>\n }\n\n </div>\n\n <!-- \u2500\u2500 Progress bar (auto-dismiss indicator) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (toast.duration > 0) {\n <div class=\"h-0.5 w-full bg-gray-100 dark:bg-gray-700 overflow-hidden\">\n <div\n [ngClass]=\"['h-full', progressClass(toast)]\"\n [style]=\"progressStyle(toast)\"\n [style.animation-play-state]=\"paused() ? 'paused' : 'running'\"\n ></div>\n </div>\n }\n\n </div>\n }\n\n</div>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
114
+ }
115
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvToastHostComponent, decorators: [{
116
+ type: Component,
117
+ args: [{ selector: 'sv-toast-host', standalone: true, imports: [NgClass], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Fixed toast stack \u2014 positioned via positionClass() computed from `position` input -->\n<div\n [ngClass]=\"['fixed z-50 flex gap-3 pointer-events-none', positionClass()]\"\n role=\"region\"\n aria-label=\"Notifications\"\n>\n\n @for (toast of toasts(); track trackById($index, toast)) {\n <div\n [attr.id]=\"toast.id\"\n [attr.role]=\"roleFor(toast)\"\n [attr.aria-live]=\"ariaLiveFor(toast)\"\n aria-atomic=\"true\"\n (mouseenter)=\"pauseAll()\"\n (mouseleave)=\"resumeAll()\"\n (focusin)=\"pauseAll()\"\n (focusout)=\"resumeAll()\"\n [ngClass]=\"[\n 'pointer-events-auto w-80 max-w-[calc(100vw-2rem)] rounded-xl shadow-lg border',\n 'bg-white dark:bg-gray-800',\n 'border-gray-200 dark:border-gray-700',\n 'flex flex-col overflow-hidden'\n ]\"\n >\n\n <!-- \u2500\u2500 Main row: icon + content + dismiss \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"flex items-start gap-3 p-4\">\n\n <!-- Variant icon -->\n <span [ngClass]=\"['mt-0.5 shrink-0', iconClass(toast)]\">\n <svg class=\"h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <path [attr.d]=\"iconPath(toast)\" />\n </svg>\n </span>\n\n <!-- Title + message -->\n <div class=\"min-w-0 flex-1\">\n @if (toast.title) {\n <p class=\"text-sm font-semibold text-gray-900 dark:text-white leading-snug\">\n {{ toast.title }}\n </p>\n }\n <p [ngClass]=\"[\n 'text-sm text-gray-600 dark:text-gray-300 leading-snug',\n toast.title ? 'mt-0.5' : ''\n ]\">\n {{ toast.message }}\n </p>\n\n <!-- Action button -->\n @if (toast.action) {\n <button\n type=\"button\"\n (click)=\"onActionClick(toast)\"\n class=\"mt-2 text-xs font-medium text-primary-600 dark:text-primary-400\n hover:text-primary-700 dark:hover:text-primary-300\n focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 rounded\"\n >\n {{ toast.action.label }}\n </button>\n }\n </div>\n\n <!-- Dismiss button -->\n @if (toast.dismissible) {\n <button\n type=\"button\"\n (click)=\"dismiss(toast)\"\n aria-label=\"Dismiss notification\"\n class=\"shrink-0 -mt-0.5 -mr-1 inline-flex h-6 w-6 items-center justify-center\n rounded-md text-gray-500 dark:text-gray-400\n hover:bg-gray-100 dark:hover:bg-gray-700\n hover:text-gray-600 dark:hover:text-gray-300\n focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500\n transition-colors duration-100\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\"/>\n </svg>\n </button>\n }\n\n </div>\n\n <!-- \u2500\u2500 Progress bar (auto-dismiss indicator) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (toast.duration > 0) {\n <div class=\"h-0.5 w-full bg-gray-100 dark:bg-gray-700 overflow-hidden\">\n <div\n [ngClass]=\"['h-full', progressClass(toast)]\"\n [style]=\"progressStyle(toast)\"\n [style.animation-play-state]=\"paused() ? 'paused' : 'running'\"\n ></div>\n </div>\n }\n\n </div>\n }\n\n</div>\n" }]
118
+ }], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], maxVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxVisible", required: false }] }] } });
119
+
120
+ /**
121
+ * Generated bundle index. Do not edit.
122
+ */
123
+
124
+ export { SvToastHostComponent };
125
+ //# sourceMappingURL=styloviz-toast.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styloviz-toast.mjs","sources":["../../../../projects/toast/src/lib/toast-host.component.ts","../../../../projects/toast/src/lib/toast-host.component.html","../../../../projects/toast/src/styloviz-toast.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n input,\n signal,\n} from '@angular/core';\nimport { NgClass } from '@angular/common';\nimport { ToastService, Toast, ToastPosition, ToastVariant } from '@styloviz/core';\n\n// ─── Icon paths keyed to variant ──────────────────────────────────────────────\n\nconst VARIANT_ICONS: Record<ToastVariant, string> = {\n success: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',\n error: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z',\n warning: 'M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z',\n info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',\n};\n\nconst VARIANT_COLORS: Record<ToastVariant, { icon: string; progress: string }> = {\n success: { icon: 'text-emerald-500 dark:text-emerald-400', progress: 'bg-emerald-500' },\n error: { icon: 'text-red-500 dark:text-red-400', progress: 'bg-red-500' },\n warning: { icon: 'text-amber-500 dark:text-amber-400', progress: 'bg-amber-500' },\n info: { icon: 'text-sky-500 dark:text-sky-400', progress: 'bg-sky-500' },\n};\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\n/**\n * ToastHostComponent — Renders the active toast stack from `ToastService`.\n *\n * Place **once** in your root shell template (e.g., `AppComponent` or\n * `DashboardShellComponent`):\n *\n * ```html\n * <sv-toast-host />\n * ```\n *\n * Then trigger toasts imperatively from any component or service:\n * ```typescript\n * this.toast.success('Record saved!');\n * this.toast.error('Failed to delete', { title: 'Error', duration: 0 });\n * ```\n */\n@Component({\n selector: 'sv-toast-host',\n standalone: true,\n imports: [NgClass],\n templateUrl: './toast-host.component.html',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SvToastHostComponent {\n private readonly toastService = inject(ToastService);\n\n // ── Inputs ────────────────────────────────────────────────────────────────\n\n /** Where the stack is anchored on screen. @default 'top-right' */\n position = input<ToastPosition>('top-right');\n\n /** Maximum number of toasts visible simultaneously. @default 5 */\n maxVisible = input<number>(5);\n\n // ── Reactive state ────────────────────────────────────────────────────────\n\n toasts = computed(() => {\n const all = this.toastService.toasts();\n const pos = this.position();\n const filtered = all.filter(t => t.position === pos);\n return filtered.slice(-this.maxVisible());\n });\n\n // ── Position class ────────────────────────────────────────────────────────\n // flex direction is baked in: top positions stack DOWN (flex-col),\n // bottom positions stack UP (flex-col-reverse) so new toasts appear\n // closest to the screen edge.\n\n positionClass = computed<string>(() => {\n const map: Record<ToastPosition, string> = {\n 'top-right': 'top-4 right-4 items-end flex-col',\n 'top-left': 'top-4 left-4 items-start flex-col',\n 'top-center': 'top-4 left-1/2 -translate-x-1/2 items-center flex-col',\n 'bottom-right': 'bottom-4 right-4 items-end flex-col-reverse',\n 'bottom-left': 'bottom-4 left-4 items-start flex-col-reverse',\n 'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2 items-center flex-col-reverse',\n };\n return map[this.position()];\n });\n\n // ── Hover-pause (WCAG 2.2.1) ──────────────────────────────────────────────\n // Pausing the auto-dismiss countdown while the pointer is over the stack\n // gives users time to read and interact with toasts.\n\n readonly paused = signal(false);\n\n pauseAll(): void {\n this.paused.set(true);\n for (const t of this.toasts()) this.toastService.pause(t.id);\n }\n\n resumeAll(): void {\n this.paused.set(false);\n for (const t of this.toasts()) this.toastService.resume(t.id);\n }\n\n // ── Accessibility ─────────────────────────────────────────────────────────\n // error/warning are assertive (interrupt); success/info are polite (status).\n\n roleFor(toast: Toast): 'alert' | 'status' {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'alert' : 'status';\n }\n\n ariaLiveFor(toast: Toast): 'assertive' | 'polite' {\n return toast.variant === 'error' || toast.variant === 'warning' ? 'assertive' : 'polite';\n }\n\n // ── Per-toast helpers ─────────────────────────────────────────────────────\n\n iconPath(toast: Toast): string {\n return VARIANT_ICONS[toast.variant];\n }\n\n iconClass(toast: Toast): string {\n return VARIANT_COLORS[toast.variant].icon;\n }\n\n progressClass(toast: Toast): string {\n return VARIANT_COLORS[toast.variant].progress;\n }\n\n progressStyle(toast: Toast): string {\n if (!toast.duration) return 'width: 0%';\n return `animation: toast-progress ${toast.duration}ms linear forwards`;\n }\n\n dismiss(toast: Toast): void {\n this.toastService.dismiss(toast.id);\n }\n\n onActionClick(toast: Toast): void {\n toast.action?.callback();\n this.toastService.dismiss(toast.id);\n }\n\n trackById(_: number, toast: Toast): string {\n return toast.id;\n }\n}\n","<!-- Fixed toast stack — positioned via positionClass() computed from `position` input -->\n<div\n [ngClass]=\"['fixed z-50 flex gap-3 pointer-events-none', positionClass()]\"\n role=\"region\"\n aria-label=\"Notifications\"\n>\n\n @for (toast of toasts(); track trackById($index, toast)) {\n <div\n [attr.id]=\"toast.id\"\n [attr.role]=\"roleFor(toast)\"\n [attr.aria-live]=\"ariaLiveFor(toast)\"\n aria-atomic=\"true\"\n (mouseenter)=\"pauseAll()\"\n (mouseleave)=\"resumeAll()\"\n (focusin)=\"pauseAll()\"\n (focusout)=\"resumeAll()\"\n [ngClass]=\"[\n 'pointer-events-auto w-80 max-w-[calc(100vw-2rem)] rounded-xl shadow-lg border',\n 'bg-white dark:bg-gray-800',\n 'border-gray-200 dark:border-gray-700',\n 'flex flex-col overflow-hidden'\n ]\"\n >\n\n <!-- ── Main row: icon + content + dismiss ─────────────────────── -->\n <div class=\"flex items-start gap-3 p-4\">\n\n <!-- Variant icon -->\n <span [ngClass]=\"['mt-0.5 shrink-0', iconClass(toast)]\">\n <svg class=\"h-5 w-5\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <path [attr.d]=\"iconPath(toast)\" />\n </svg>\n </span>\n\n <!-- Title + message -->\n <div class=\"min-w-0 flex-1\">\n @if (toast.title) {\n <p class=\"text-sm font-semibold text-gray-900 dark:text-white leading-snug\">\n {{ toast.title }}\n </p>\n }\n <p [ngClass]=\"[\n 'text-sm text-gray-600 dark:text-gray-300 leading-snug',\n toast.title ? 'mt-0.5' : ''\n ]\">\n {{ toast.message }}\n </p>\n\n <!-- Action button -->\n @if (toast.action) {\n <button\n type=\"button\"\n (click)=\"onActionClick(toast)\"\n class=\"mt-2 text-xs font-medium text-primary-600 dark:text-primary-400\n hover:text-primary-700 dark:hover:text-primary-300\n focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 rounded\"\n >\n {{ toast.action.label }}\n </button>\n }\n </div>\n\n <!-- Dismiss button -->\n @if (toast.dismissible) {\n <button\n type=\"button\"\n (click)=\"dismiss(toast)\"\n aria-label=\"Dismiss notification\"\n class=\"shrink-0 -mt-0.5 -mr-1 inline-flex h-6 w-6 items-center justify-center\n rounded-md text-gray-500 dark:text-gray-400\n hover:bg-gray-100 dark:hover:bg-gray-700\n hover:text-gray-600 dark:hover:text-gray-300\n focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500\n transition-colors duration-100\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\"/>\n </svg>\n </button>\n }\n\n </div>\n\n <!-- ── Progress bar (auto-dismiss indicator) ──────────────────── -->\n @if (toast.duration > 0) {\n <div class=\"h-0.5 w-full bg-gray-100 dark:bg-gray-700 overflow-hidden\">\n <div\n [ngClass]=\"['h-full', progressClass(toast)]\"\n [style]=\"progressStyle(toast)\"\n [style.animation-play-state]=\"paused() ? 'paused' : 'running'\"\n ></div>\n </div>\n }\n\n </div>\n }\n\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@styloviz/toast",
3
+ "version": "0.1.1",
4
+ "description": "Imperative toast stack with 4 variants, 6 anchor positions, action callbacks and an auto-dismiss timer.",
5
+ "license": "MIT",
6
+ "author": "sazzad-bs23",
7
+ "homepage": "https://styloviz.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/rhossain/angular-tailwind-dashboard-components.git",
11
+ "directory": "projects/toast"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/rhossain/angular-tailwind-dashboard-components/issues"
15
+ },
16
+ "peerDependencies": {
17
+ "@angular/common": ">=21.0.0",
18
+ "@angular/core": ">=21.0.0",
19
+ "@styloviz/core": ">=0.1.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "engines": {
25
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
26
+ },
27
+ "sideEffects": false,
28
+ "module": "fesm2022/styloviz-toast.mjs",
29
+ "typings": "types/styloviz-toast.d.ts",
30
+ "exports": {
31
+ "./package.json": {
32
+ "default": "./package.json"
33
+ },
34
+ ".": {
35
+ "types": "./types/styloviz-toast.d.ts",
36
+ "default": "./fesm2022/styloviz-toast.mjs"
37
+ }
38
+ },
39
+ "type": "module",
40
+ "dependencies": {
41
+ "tslib": "^2.3.0"
42
+ }
43
+ }
@@ -0,0 +1,44 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { ToastPosition, Toast } from '@styloviz/core';
3
+
4
+ /**
5
+ * ToastHostComponent — Renders the active toast stack from `ToastService`.
6
+ *
7
+ * Place **once** in your root shell template (e.g., `AppComponent` or
8
+ * `DashboardShellComponent`):
9
+ *
10
+ * ```html
11
+ * <sv-toast-host />
12
+ * ```
13
+ *
14
+ * Then trigger toasts imperatively from any component or service:
15
+ * ```typescript
16
+ * this.toast.success('Record saved!');
17
+ * this.toast.error('Failed to delete', { title: 'Error', duration: 0 });
18
+ * ```
19
+ */
20
+ declare class SvToastHostComponent {
21
+ private readonly toastService;
22
+ /** Where the stack is anchored on screen. @default 'top-right' */
23
+ position: _angular_core.InputSignal<ToastPosition>;
24
+ /** Maximum number of toasts visible simultaneously. @default 5 */
25
+ maxVisible: _angular_core.InputSignal<number>;
26
+ toasts: _angular_core.Signal<Toast[]>;
27
+ positionClass: _angular_core.Signal<string>;
28
+ readonly paused: _angular_core.WritableSignal<boolean>;
29
+ pauseAll(): void;
30
+ resumeAll(): void;
31
+ roleFor(toast: Toast): 'alert' | 'status';
32
+ ariaLiveFor(toast: Toast): 'assertive' | 'polite';
33
+ iconPath(toast: Toast): string;
34
+ iconClass(toast: Toast): string;
35
+ progressClass(toast: Toast): string;
36
+ progressStyle(toast: Toast): string;
37
+ dismiss(toast: Toast): void;
38
+ onActionClick(toast: Toast): void;
39
+ trackById(_: number, toast: Toast): string;
40
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvToastHostComponent, never>;
41
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvToastHostComponent, "sv-toast-host", never, { "position": { "alias": "position"; "required": false; "isSignal": true; }; "maxVisible": { "alias": "maxVisible"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
42
+ }
43
+
44
+ export { SvToastHostComponent };