ngx-dynamic-toast 0.0.1 → 0.0.3

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../dist/ngx-dynamic-toast",
4
+ "lib": {
5
+ "entryFile": "src/public-api.ts"
6
+ }
7
+ }
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "ngx-dynamic-toast",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
+ "description": "Dynamic toast notifications for Angular",
5
+ "keywords": [
6
+ "angular",
7
+ "ngx",
8
+ "toast",
9
+ "notification",
10
+ "dynamic",
11
+ "ngx-dynamic-toast",
12
+ "alerts",
13
+ "sonner",
14
+ "sonner-angular",
15
+ "sonner-ng",
16
+ "dynamic-toast",
17
+ "dynamic-alerts",
18
+ "dynamic-notification",
19
+ "dynamic-ngx-toast",
20
+ "dynamic-ngx-alerts",
21
+ "dynamic-ngx-notification",
22
+ "dynamic-ngx-toast",
23
+ "dynamic-ngx-alerts",
24
+ "dynamic-ngx-notification",
25
+ "dynamic-island",
26
+ "dynamic-island-angular",
27
+ "dynamic-island-ng"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/ederjavs/ngx-dynamic-toast.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/ederjavs/ngx-dynamic-toast/issues"
35
+ },
36
+ "homepage": "https://github.com/ederjavs/ngx-dynamic-toast#readme",
37
+ "author": "ederjavs",
38
+ "license": "MIT",
4
39
  "peerDependencies": {
5
40
  "@angular/common": "^21.1.0",
6
41
  "@angular/core": "^21.1.0"
@@ -8,16 +43,5 @@
8
43
  "dependencies": {
9
44
  "tslib": "^2.3.0"
10
45
  },
11
- "sideEffects": false,
12
- "module": "fesm2022/ngx-dynamic-toast.mjs",
13
- "typings": "types/ngx-dynamic-toast.d.ts",
14
- "exports": {
15
- "./package.json": {
16
- "default": "./package.json"
17
- },
18
- ".": {
19
- "types": "./types/ngx-dynamic-toast.d.ts",
20
- "default": "./fesm2022/ngx-dynamic-toast.mjs"
21
- }
22
- }
23
- }
46
+ "sideEffects": false
47
+ }
@@ -0,0 +1,18 @@
1
+ export const HEIGHT = 40;
2
+ export const WIDTH = 350;
3
+ export const DEFAULT_ROUNDNESS = 16;
4
+
5
+ export const DURATION_MS = 600;
6
+ export const DURATION_S = DURATION_MS / 1000;
7
+
8
+ export const DEFAULT_TOAST_DURATION = 6000;
9
+ export const EXIT_DURATION = DEFAULT_TOAST_DURATION * 0.1;
10
+ export const AUTO_EXPAND_DELAY = DEFAULT_TOAST_DURATION * 0.025;
11
+ export const AUTO_COLLAPSE_DELAY = DEFAULT_TOAST_DURATION - 2000;
12
+
13
+ export const BLUR_RATIO = 0.5;
14
+ export const PILL_PADDING = 10;
15
+ export const MIN_EXPAND_RATIO = 2.25;
16
+ export const SWAP_COLLAPSE_MS = 200;
17
+ export const HEADER_EXIT_MS = DURATION_MS * 0.7;
18
+
@@ -0,0 +1,24 @@
1
+ @for (group of groups(); track group.pos) {
2
+ <section
3
+ data-dt-viewport
4
+ [attr.data-position]="group.pos"
5
+ aria-live="polite"
6
+ [style.top]="group.style?.top"
7
+ [style.right]="group.style?.right"
8
+ [style.bottom]="group.style?.bottom"
9
+ [style.left]="group.style?.left"
10
+ >
11
+ @for (t of group.items; track t.id) {
12
+ <dt-toast
13
+ [toast]="t"
14
+ [pillAlign]="group.pill"
15
+ [expandEdge]="group.expand"
16
+ [canExpand]="activeId() === undefined || activeId() === t.id"
17
+ (entered)="onToastEnter($event)"
18
+ (left)="onToastLeave($event)"
19
+ (dismissed)="service.dismiss($event)"
20
+ />
21
+ }
22
+ </section>
23
+ }
24
+
@@ -0,0 +1,121 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ computed,
5
+ inject,
6
+ input,
7
+ signal,
8
+ ViewEncapsulation,
9
+ } from "@angular/core";
10
+ import { DynamicToastComponent } from "./dynamic-toast.component";
11
+ import { DynamicToastService } from "./dynamic-toast.service";
12
+ import type { DynamicToastOffsetConfig, DynamicToastPosition } from "./types";
13
+
14
+ type PillAlign = "left" | "center" | "right";
15
+ type ExpandEdge = "top" | "bottom";
16
+
17
+ const pillAlign = (pos: DynamicToastPosition): PillAlign =>
18
+ pos.includes("right") ? "right" : pos.includes("center") ? "center" : "left";
19
+
20
+ const expandDir = (pos: DynamicToastPosition): ExpandEdge =>
21
+ pos.startsWith("top") ? "bottom" : "top";
22
+
23
+ @Component({
24
+ selector: "dt-viewport",
25
+ standalone: true,
26
+ imports: [DynamicToastComponent],
27
+ changeDetection: ChangeDetectionStrategy.OnPush,
28
+ encapsulation: ViewEncapsulation.None,
29
+ templateUrl: "./dynamic-toast-viewport.component.html",
30
+ styleUrls: ["./styles.css"],
31
+ })
32
+ export class DynamicToastViewportComponent {
33
+ position = input<DynamicToastPosition>("top-right");
34
+ offset = input<DynamicToastOffsetConfig | string | number | undefined>(undefined);
35
+
36
+ readonly service = inject(DynamicToastService);
37
+ private hovering = signal(new Set<string>());
38
+
39
+ activeId = signal<string | undefined>(undefined);
40
+
41
+ private latestId = computed(() => {
42
+ const list = this.service.toasts();
43
+ for (let i = list.length - 1; i >= 0; i--) {
44
+ if (!list[i].exiting) return list[i].id;
45
+ }
46
+ return undefined;
47
+ });
48
+
49
+ groups = computed(() => {
50
+ const toasts = this.service.toasts();
51
+ const byPos = new Map<DynamicToastPosition, typeof toasts>();
52
+ for (const t of toasts) {
53
+ const p = (t.position ?? this.position()) as DynamicToastPosition;
54
+ const arr = byPos.get(p);
55
+ if (arr) arr.push(t);
56
+ else byPos.set(p, [t]);
57
+ }
58
+
59
+ const offset = this.offset();
60
+ const off: DynamicToastOffsetConfig | undefined =
61
+ offset === undefined
62
+ ? undefined
63
+ : typeof offset === "object"
64
+ ? offset
65
+ : { top: offset, right: offset, bottom: offset, left: offset };
66
+
67
+ const px = (v: string | number | undefined) =>
68
+ v === undefined ? undefined : typeof v === "number" ? `${v}px` : v;
69
+
70
+ const res: Array<{
71
+ pos: DynamicToastPosition;
72
+ items: typeof toasts;
73
+ pill: PillAlign;
74
+ expand: ExpandEdge;
75
+ style?: { top?: string; right?: string; bottom?: string; left?: string };
76
+ }> = [];
77
+
78
+ for (const [pos, items] of byPos) {
79
+ const style: any = {};
80
+ if (off) {
81
+ if (pos.startsWith("top") && off.top !== undefined) style.top = px(off.top);
82
+ if (pos.startsWith("bottom") && off.bottom !== undefined)
83
+ style.bottom = px(off.bottom);
84
+ if (pos.endsWith("left") && off.left !== undefined) style.left = px(off.left);
85
+ if (pos.endsWith("right") && off.right !== undefined)
86
+ style.right = px(off.right);
87
+ }
88
+
89
+ res.push({
90
+ pos,
91
+ items,
92
+ pill: pillAlign(pos),
93
+ expand: expandDir(pos),
94
+ style: off ? style : undefined,
95
+ });
96
+ }
97
+
98
+ return res;
99
+ });
100
+
101
+ onToastEnter(id: string) {
102
+ const next = new Set(this.hovering());
103
+ next.add(id);
104
+ this.hovering.set(next);
105
+ this.activeId.set(id);
106
+ this.service.pauseTimers();
107
+ }
108
+
109
+ onToastLeave(id: string) {
110
+ const next = new Set(this.hovering());
111
+ next.delete(id);
112
+ this.hovering.set(next);
113
+ if (next.size > 0) return;
114
+
115
+ queueMicrotask(() => {
116
+ if (this.hovering().size > 0) return;
117
+ this.activeId.set(this.latestId());
118
+ this.service.resumeTimers();
119
+ });
120
+ }
121
+ }
@@ -0,0 +1,148 @@
1
+ <button
2
+ type="button"
3
+ data-dt-toast
4
+ [attr.data-ready]="ready()"
5
+ [attr.data-expanded]="open()"
6
+ [attr.data-exiting]="toast().exiting || false"
7
+ [attr.data-edge]="expandEdge()"
8
+ [attr.data-position]="pillAlign()"
9
+ [attr.data-state]="view().state"
10
+ [class]="toast().className || ''"
11
+ [style.--_h]="HEIGHT + 'px'"
12
+ [style.--_pw]="(pillWidth() || HEIGHT) + 'px'"
13
+ [style.--_px]="pillX() + 'px'"
14
+ [style.--_ht]="headerTransform()"
15
+ [style.--_co]="open() ? 1 : 0"
16
+ [style.animation-duration]="(toast().duration || 6000) + 'ms'"
17
+ (mouseenter)="handleEnter()"
18
+ (mouseleave)="handleLeave()"
19
+ (transitionend)="handleTransitionEnd($event)"
20
+ (pointerdown)="handlePointerDown($event)"
21
+ >
22
+ <div
23
+ data-dt-canvas
24
+ [attr.data-edge]="expandEdge()"
25
+ [style.filter]="'url(#' + filterId + ')'"
26
+ >
27
+ <svg
28
+ data-dt-svg
29
+ [attr.width]="WIDTH"
30
+ [attr.height]="svgHeight()"
31
+ [attr.viewBox]="'0 0 ' + WIDTH + ' ' + svgHeight()"
32
+ >
33
+ <defs>
34
+ <filter
35
+ [id]="filterId"
36
+ x="-20%"
37
+ y="-20%"
38
+ width="140%"
39
+ height="140%"
40
+ colorInterpolationFilters="sRGB"
41
+ >
42
+ <feGaussianBlur
43
+ in="SourceGraphic"
44
+ [attr.stdDeviation]="blur()"
45
+ result="blur"
46
+ />
47
+ <feColorMatrix
48
+ in="blur"
49
+ mode="matrix"
50
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -10"
51
+ result="goo"
52
+ />
53
+ <feComposite in="SourceGraphic" in2="goo" operator="atop" />
54
+ </filter>
55
+ </defs>
56
+
57
+ <path
58
+ data-dt-pill
59
+ [attr.d]="pillPath()"
60
+ [attr.fill]="view().fill"
61
+ />
62
+
63
+ <path
64
+ data-dt-body
65
+ [attr.d]="bodyPath()"
66
+ [attr.fill]="view().fill"
67
+ [attr.opacity]="open() ? 1 : 0"
68
+ />
69
+ </svg>
70
+ </div>
71
+
72
+ <div #headerRef data-dt-header [attr.data-edge]="expandEdge()">
73
+ <div data-dt-header-stack>
74
+ <div
75
+ #innerRef
76
+ data-dt-header-inner
77
+ data-layer="current"
78
+ >
79
+ <div
80
+ data-dt-badge
81
+ [attr.data-state]="view().state"
82
+ [class]="view().styles?.badge || ''"
83
+ [innerHTML]="resolvedIcon(view())"
84
+ ></div>
85
+ <span
86
+ data-dt-title
87
+ [attr.data-state]="view().state"
88
+ [class]="view().styles?.title || ''"
89
+ >
90
+ {{ view().title }}
91
+ </span>
92
+ </div>
93
+
94
+ <!-- @if (headerLayerPrev(); as prev) {
95
+ <div data-dt-header-inner data-layer="prev" data-exiting="true">
96
+ <div
97
+ data-dt-badge
98
+ [attr.data-state]="prev.view.state"
99
+ [class]="prev.view.styles?.badge || ''"
100
+ [innerHTML]="resolvedIcon(prev.view)"
101
+ ></div>
102
+ <span
103
+ data-dt-title
104
+ [attr.data-state]="prev.view.state"
105
+ [class]="prev.view.styles?.title || ''"
106
+ >
107
+ {{ prev.view.title }}
108
+ </span>
109
+ </div>
110
+ } -->
111
+ </div>
112
+ </div>
113
+
114
+ @if (hasDesc()) {
115
+ <div
116
+ data-dt-content
117
+ [attr.data-edge]="expandEdge()"
118
+ [attr.data-visible]="open()"
119
+ >
120
+ <div
121
+ #contentRef
122
+ data-dt-description
123
+ [class]="view().styles?.description || ''"
124
+ >
125
+ @if (view().description) {
126
+ <span>{{ view().description }}</span>
127
+ }
128
+ @if (view().contentTemplate) {
129
+ <ng-container
130
+ [ngTemplateOutlet]="view().contentTemplate"
131
+ ></ng-container>
132
+ }
133
+ @if (view().button) {
134
+ <a
135
+ href="#"
136
+ data-dt-button
137
+ [attr.data-state]="view().state"
138
+ [class]="view().styles?.button || ''"
139
+ (click)="handleButtonClick($event)"
140
+ >
141
+ {{ view().button?.title }}
142
+ </a>
143
+ }
144
+ </div>
145
+ </div>
146
+ }
147
+ </button>
148
+