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.
- package/ng-package.json +7 -0
- package/package.json +38 -14
- package/src/lib/constants.ts +18 -0
- package/src/lib/dynamic-toast-viewport.component.html +24 -0
- package/src/lib/dynamic-toast-viewport.component.ts +121 -0
- package/src/lib/dynamic-toast.component.html +148 -0
- package/src/lib/dynamic-toast.component.ts +515 -0
- package/src/lib/dynamic-toast.service.ts +346 -0
- package/src/lib/icons.ts +26 -0
- package/src/lib/index.ts +34 -0
- package/src/lib/styles.css +580 -0
- package/src/lib/toast.ts +38 -0
- package/src/lib/types.ts +75 -0
- package/src/public-api.ts +10 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +15 -0
- package/fesm2022/ngx-dynamic-toast.mjs +0 -791
- package/fesm2022/ngx-dynamic-toast.mjs.map +0 -1
- package/types/ngx-dynamic-toast.d.ts +0 -238
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ngx-dynamic-toast",
|
|
3
|
-
"version": "0.0.
|
|
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
|
-
|
|
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
|
+
|