@samline/notify 0.1.15 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -17
- package/dist/index.cjs.js +60 -9
- package/dist/index.esm.js +60 -9
- package/dist/notify.umd.js +60 -9
- package/docs/browser.md +65 -4
- package/docs/react.md +203 -4
- package/docs/svelte.md +203 -4
- package/docs/vanilla.md +207 -36
- package/docs/vue.md +209 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
## @samline/notify
|
|
2
1
|
|
|
3
|
-
|
|
2
|
+
# Notify
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
A universal toast notification library powered by Sileo, designed to bring the same beautiful, animated experience to React, Vue, Svelte, and Vanilla JS. Built for teams who need Sileo’s quality and API, but require seamless integration across multiple frameworks.
|
|
5
|
+
|
|
6
|
+
Powered by Sileo — see the original project: https://github.com/hiaaryan/sileo
|
|
6
7
|
|
|
7
8
|
Table of Contents
|
|
8
9
|
|
|
@@ -47,20 +48,32 @@ CDN / Browser
|
|
|
47
48
|
|
|
48
49
|
Use the browser build when your project loads scripts directly and cannot compile npm modules (Shopify, WordPress, plain HTML). Example CDN usage (replace version):
|
|
49
50
|
|
|
51
|
+
|
|
50
52
|
```html
|
|
51
|
-
<script src="https://unpkg.com/@samline/notify@0.1
|
|
52
|
-
<link
|
|
53
|
-
rel="stylesheet"
|
|
54
|
-
href="https://unpkg.com/@samline/notify@0.1.15/dist/styles.css"
|
|
55
|
-
/>
|
|
53
|
+
<script src="https://unpkg.com/@samline/notify@0.2.1/dist/notify.umd.js"></script>
|
|
54
|
+
<link rel="stylesheet" href="https://unpkg.com/@samline/notify@0.2.1/dist/styles.css" />
|
|
56
55
|
|
|
57
56
|
<script>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
api.notify
|
|
57
|
+
// Always use an array of strings as the second argument
|
|
58
|
+
// Correct usage:
|
|
59
|
+
const api = window.notify || window.notifications;
|
|
60
|
+
api.initToasters(document.body, ['top-left']);
|
|
61
|
+
api.notify({
|
|
62
|
+
title: 'Saved',
|
|
63
|
+
description: 'Changes saved',
|
|
64
|
+
type: 'success'
|
|
65
|
+
// You can omit position if you only initialized one position
|
|
66
|
+
});
|
|
61
67
|
</script>
|
|
62
68
|
```
|
|
63
69
|
|
|
70
|
+
|
|
71
|
+
> ⚠️ **Important:**
|
|
72
|
+
> - The second argument to `initToasters` **must be an array of strings** with the positions (e.g. `['top-left']`).
|
|
73
|
+
> - **Do not use** `document.body['top-left']` (this is `undefined` and will not work).
|
|
74
|
+
> - If you initialize only one position, toasts without an explicit position will go there automatically (dynamic fallback).
|
|
75
|
+
> - If you initialize multiple positions, toasts without an explicit position will go to `'top-right'` by default.
|
|
76
|
+
|
|
64
77
|
Entry Points
|
|
65
78
|
|
|
66
79
|
Choose the entrypoint matching your stack so you only import what you need.
|
|
@@ -68,7 +81,7 @@ Choose the entrypoint matching your stack so you only import what you need.
|
|
|
68
81
|
| Use case | Import | Guide |
|
|
69
82
|
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
|
|
70
83
|
| Vanilla JS | `import { notify, initToasters } from '@samline/notify/vanilla'` | [docs/vanilla.md](docs/vanilla.md) |
|
|
71
|
-
| Browser / CDN | `<script src="https://unpkg.com/@samline/notify@0.1
|
|
84
|
+
| Browser / CDN | `<script src="https://unpkg.com/@samline/notify@0.2.1/dist/notify.umd.js"></script>`<br/>`const api = window.notify; api.initToasters(...);` | [docs/browser.md](docs/browser.md) |
|
|
72
85
|
| React | `import { Toaster, notify } from '@samline/notify/react'` | [docs/react.md](docs/react.md) |
|
|
73
86
|
| Vue | `import { Toaster, notify } from '@samline/notify/vue'` | [docs/vue.md](docs/vue.md) |
|
|
74
87
|
| Svelte | `import Toaster, { notify } from '@samline/notify/svelte'` | [docs/svelte.md](docs/svelte.md) |
|
|
@@ -100,18 +113,89 @@ notify.clear()
|
|
|
100
113
|
|
|
101
114
|
When using the UMD/browser bundle the global is exposed as `window.notify` (preferred). For compatibility the API object also exposes `window.notifications` which maintains the previous shape.
|
|
102
115
|
|
|
103
|
-
Shared Options
|
|
104
116
|
|
|
105
|
-
|
|
117
|
+
|
|
118
|
+
Shared Options (All Entrypoints)
|
|
119
|
+
|
|
120
|
+
All notification methods accept a rich set of options for full customization. **Position fallback note:**
|
|
121
|
+
|
|
122
|
+
- If you initialize only one position with `initToasters`, toasts without an explicit `position` will go there (dynamic fallback).
|
|
123
|
+
- If you initialize multiple positions, the fallback will be `'top-right'`.
|
|
124
|
+
- If you notify to a position that was not initialized, a warning will appear in the console and the toast will not be shown. This warning is now always triggered, incluso si la posición no existe en el DOM.
|
|
125
|
+
|
|
126
|
+
Example:
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
// Only one position (dynamic fallback)
|
|
130
|
+
api.initToasters(document.body, ['bottom-left']);
|
|
131
|
+
api.notify({ title: 'Will go to bottom-left' });
|
|
132
|
+
|
|
133
|
+
// Multiple positions (classic fallback)
|
|
134
|
+
api.initToasters(document.body, ['top-right', 'bottom-left']);
|
|
135
|
+
api.notify({ title: 'Will go to top-right' });
|
|
136
|
+
api.notify({ title: 'Will go to bottom-left', position: 'bottom-left' });
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
All options:
|
|
106
142
|
|
|
107
143
|
| Property | Type | Default | Description |
|
|
108
144
|
| ------------- | -------------------------------------- | ----------- | ------------------------------------------- |
|
|
109
145
|
| `title` | string | — | Toast title |
|
|
110
|
-
| `description` | string
|
|
111
|
-
| `type` | `info\|success\|error\|warning
|
|
146
|
+
| `description` | string \| ReactNode \| SvelteNode | — | Optional body text (JSX, HTML, or string) |
|
|
147
|
+
| `type` | `info\|success\|error\|warning\|action`| `info` | Visual intent |
|
|
112
148
|
| `position` | string | `top-right` | One of toast positions |
|
|
113
|
-
| `duration` | number
|
|
149
|
+
| `duration` | number \| null | `4000` | Auto-dismiss timeout in ms (null = sticky) |
|
|
114
150
|
| `button` | { title: string, onClick: () => void } | — | Optional action button |
|
|
151
|
+
| `icon` | string \| ReactNode \| SvelteNode | — | Custom icon (SVG, JSX, or node) |
|
|
152
|
+
| `fill` | string | — | Custom background color (SVG or badge) |
|
|
153
|
+
| `styles` | { title, description, badge, button } | — | Custom class overrides for sub-elements |
|
|
154
|
+
| `roundness` | number | 16 | Border radius in pixels |
|
|
155
|
+
| `autopilot` | boolean \| object | true | Auto expand/collapse timing |
|
|
156
|
+
|
|
157
|
+
#### Example: Advanced Toast
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
notify.success({
|
|
161
|
+
title: "Styled!",
|
|
162
|
+
fill: "#222",
|
|
163
|
+
icon: '<svg>...</svg>',
|
|
164
|
+
styles: {
|
|
165
|
+
title: "text-white!",
|
|
166
|
+
badge: "bg-white/20!",
|
|
167
|
+
button: "bg-white/10!"
|
|
168
|
+
},
|
|
169
|
+
roundness: 24,
|
|
170
|
+
autopilot: false
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### SileoPromiseOptions
|
|
175
|
+
|
|
176
|
+
The `promise` method supports advanced flows:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
notify.promise(fetchData(), {
|
|
180
|
+
loading: { title: "Loading..." },
|
|
181
|
+
success: (data) => ({ title: `Loaded ${data.name}` }),
|
|
182
|
+
error: (err) => ({ title: "Error", description: err.message }),
|
|
183
|
+
action: (data) => ({ title: "Action required", button: { title: "Retry", onClick: () => retry() } })
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Toaster Component Props (React, Svelte, Vue, Vanilla)
|
|
188
|
+
|
|
189
|
+
All Toaster components accept the following props for global control:
|
|
190
|
+
|
|
191
|
+
| Prop | Type | Default | Description |
|
|
192
|
+
| --------- | ----------------------------------------- | ------------ | ------------------------------------------- |
|
|
193
|
+
| `position`| string | top-right | Default toast position |
|
|
194
|
+
| `offset` | number \| string \| {top,right,bottom,left}| 0 | Distance from viewport edges |
|
|
195
|
+
| `options` | Partial<Options> | — | Default options for all toasts |
|
|
196
|
+
| `theme` | "light" \| "dark" \| "system" | system | Color scheme (auto, light, dark) |
|
|
197
|
+
|
|
198
|
+
See framework-specific guides for more examples.
|
|
115
199
|
|
|
116
200
|
Notes
|
|
117
201
|
|
package/dist/index.cjs.js
CHANGED
|
@@ -24,8 +24,21 @@ class NotifyController {
|
|
|
24
24
|
return this.toasts.slice();
|
|
25
25
|
}
|
|
26
26
|
show(opts) {
|
|
27
|
+
// Normalizar props avanzadas
|
|
28
|
+
const { icon, fill, styles, roundness, autopilot, ...rest } = opts;
|
|
27
29
|
const id = this.nextId();
|
|
28
|
-
const item = {
|
|
30
|
+
const item = {
|
|
31
|
+
id,
|
|
32
|
+
options: {
|
|
33
|
+
...rest,
|
|
34
|
+
...(icon !== undefined ? { icon } : {}),
|
|
35
|
+
...(fill !== undefined ? { fill } : {}),
|
|
36
|
+
...(styles !== undefined ? { styles } : {}),
|
|
37
|
+
...(roundness !== undefined ? { roundness } : {}),
|
|
38
|
+
...(autopilot !== undefined ? { autopilot } : {}),
|
|
39
|
+
},
|
|
40
|
+
createdAt: Date.now(),
|
|
41
|
+
};
|
|
29
42
|
this.toasts.push(item);
|
|
30
43
|
this.notify();
|
|
31
44
|
return id;
|
|
@@ -65,6 +78,13 @@ class NotifyController {
|
|
|
65
78
|
const loadingId = this.show({ ...(opts.loading || {}), type: 'loading', position: opts.position });
|
|
66
79
|
p.then((res) => {
|
|
67
80
|
this.dismiss(loadingId);
|
|
81
|
+
if (opts.action) {
|
|
82
|
+
const actionOpt = typeof opts.action === 'function' ? opts.action(res) : opts.action;
|
|
83
|
+
if (actionOpt) {
|
|
84
|
+
this.show({ ...(actionOpt || {}), type: 'action', position: opts.position });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
68
88
|
const successOpt = typeof opts.success === 'function' ? opts.success(res) : opts.success;
|
|
69
89
|
if (successOpt)
|
|
70
90
|
this.show({ ...(successOpt || {}), type: 'success', position: opts.position });
|
|
@@ -1006,12 +1026,30 @@ const POSITIONS = [
|
|
|
1006
1026
|
'bottom-center',
|
|
1007
1027
|
'bottom-right'
|
|
1008
1028
|
];
|
|
1009
|
-
function createContainer(position, root) {
|
|
1029
|
+
function createContainer(position, root, opts) {
|
|
1010
1030
|
const c = document.createElement('div');
|
|
1011
1031
|
c.className = 'sileo-toaster';
|
|
1012
1032
|
c.setAttribute('data-position', position);
|
|
1013
1033
|
c.setAttribute('role', 'status');
|
|
1014
1034
|
c.setAttribute('aria-live', 'polite');
|
|
1035
|
+
if (opts === null || opts === void 0 ? void 0 : opts.theme)
|
|
1036
|
+
c.setAttribute('data-theme', opts.theme);
|
|
1037
|
+
// offset
|
|
1038
|
+
if ((opts === null || opts === void 0 ? void 0 : opts.offset) !== undefined) {
|
|
1039
|
+
if (typeof opts.offset === 'number' || typeof opts.offset === 'string') {
|
|
1040
|
+
c.style.margin = typeof opts.offset === 'number' ? `${opts.offset}px` : opts.offset;
|
|
1041
|
+
}
|
|
1042
|
+
else if (typeof opts.offset === 'object') {
|
|
1043
|
+
if (opts.offset.top !== undefined)
|
|
1044
|
+
c.style.marginTop = `${opts.offset.top}px`;
|
|
1045
|
+
if (opts.offset.right !== undefined)
|
|
1046
|
+
c.style.marginRight = `${opts.offset.right}px`;
|
|
1047
|
+
if (opts.offset.bottom !== undefined)
|
|
1048
|
+
c.style.marginBottom = `${opts.offset.bottom}px`;
|
|
1049
|
+
if (opts.offset.left !== undefined)
|
|
1050
|
+
c.style.marginLeft = `${opts.offset.left}px`;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1015
1053
|
return c;
|
|
1016
1054
|
}
|
|
1017
1055
|
function renderToast(item) {
|
|
@@ -1065,33 +1103,46 @@ function renderToast(item) {
|
|
|
1065
1103
|
}
|
|
1066
1104
|
return el;
|
|
1067
1105
|
}
|
|
1068
|
-
function initToasters(root = document.body, positions = ['top-right']) {
|
|
1106
|
+
function initToasters(root = document.body, positions = ['top-right'], opts) {
|
|
1069
1107
|
const containers = {};
|
|
1070
1108
|
positions.forEach((pos) => {
|
|
1071
|
-
const c = createContainer(pos);
|
|
1109
|
+
const c = createContainer(pos, root, opts);
|
|
1072
1110
|
root.appendChild(c);
|
|
1073
1111
|
containers[pos] = c;
|
|
1074
1112
|
});
|
|
1113
|
+
if (opts === null || opts === void 0 ? void 0 : opts.options)
|
|
1114
|
+
window.sileo._globalOptions = opts.options;
|
|
1115
|
+
if (opts === null || opts === void 0 ? void 0 : opts.theme)
|
|
1116
|
+
window.sileo._theme = opts.theme;
|
|
1117
|
+
// fallback dinámico: si solo hay una posición, usarla como default
|
|
1118
|
+
const fallbackPosition = positions.length === 1 ? positions[0] : 'top-right';
|
|
1075
1119
|
function rerender(items) {
|
|
1120
|
+
// Lanzar advertencia para todos los toasts con posición no inicializada
|
|
1121
|
+
items.forEach((t) => {
|
|
1122
|
+
if (t.options.position && !containers[t.options.position]) {
|
|
1123
|
+
console.warn(`[sileo] Toast con posición "${t.options.position}" pero no se inicializó ningún contenedor para esa posición. Inicializa con initToasters(..., ['${t.options.position}']) para mostrarlo.`);
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1076
1126
|
positions.forEach((pos) => {
|
|
1077
1127
|
const container = containers[pos];
|
|
1078
|
-
|
|
1079
|
-
|
|
1128
|
+
// fallback dinámico
|
|
1129
|
+
const visible = items.filter((t) => (t.options.position || fallbackPosition) === pos);
|
|
1130
|
+
// Diff existing children y animar salida de toasts removidos
|
|
1080
1131
|
const visibleIds = new Set(visible.map((v) => v.id));
|
|
1081
1132
|
const existing = Array.from(container.children);
|
|
1082
1133
|
existing.forEach((child) => {
|
|
1083
1134
|
const id = child.dataset.id;
|
|
1084
1135
|
if (!id || !visibleIds.has(id)) {
|
|
1085
|
-
//
|
|
1136
|
+
// animar salida y luego remover
|
|
1086
1137
|
animate(child, { opacity: 0, y: -8 }, { duration: 0.18 }).finished.then(() => child.remove());
|
|
1087
1138
|
}
|
|
1088
1139
|
});
|
|
1089
|
-
//
|
|
1140
|
+
// Añadir nuevos toasts
|
|
1090
1141
|
visible.forEach((t) => {
|
|
1091
1142
|
if (!container.querySelector(`[data-id="${t.id}"]`)) {
|
|
1092
1143
|
const node = renderToast(t);
|
|
1093
1144
|
container.appendChild(node);
|
|
1094
|
-
//
|
|
1145
|
+
// animar entrada
|
|
1095
1146
|
requestAnimationFrame(() => {
|
|
1096
1147
|
animate(node, { opacity: [0, 1], y: [-6, 0] }, { duration: 0.24 });
|
|
1097
1148
|
});
|
package/dist/index.esm.js
CHANGED
|
@@ -20,8 +20,21 @@ class NotifyController {
|
|
|
20
20
|
return this.toasts.slice();
|
|
21
21
|
}
|
|
22
22
|
show(opts) {
|
|
23
|
+
// Normalizar props avanzadas
|
|
24
|
+
const { icon, fill, styles, roundness, autopilot, ...rest } = opts;
|
|
23
25
|
const id = this.nextId();
|
|
24
|
-
const item = {
|
|
26
|
+
const item = {
|
|
27
|
+
id,
|
|
28
|
+
options: {
|
|
29
|
+
...rest,
|
|
30
|
+
...(icon !== undefined ? { icon } : {}),
|
|
31
|
+
...(fill !== undefined ? { fill } : {}),
|
|
32
|
+
...(styles !== undefined ? { styles } : {}),
|
|
33
|
+
...(roundness !== undefined ? { roundness } : {}),
|
|
34
|
+
...(autopilot !== undefined ? { autopilot } : {}),
|
|
35
|
+
},
|
|
36
|
+
createdAt: Date.now(),
|
|
37
|
+
};
|
|
25
38
|
this.toasts.push(item);
|
|
26
39
|
this.notify();
|
|
27
40
|
return id;
|
|
@@ -61,6 +74,13 @@ class NotifyController {
|
|
|
61
74
|
const loadingId = this.show({ ...(opts.loading || {}), type: 'loading', position: opts.position });
|
|
62
75
|
p.then((res) => {
|
|
63
76
|
this.dismiss(loadingId);
|
|
77
|
+
if (opts.action) {
|
|
78
|
+
const actionOpt = typeof opts.action === 'function' ? opts.action(res) : opts.action;
|
|
79
|
+
if (actionOpt) {
|
|
80
|
+
this.show({ ...(actionOpt || {}), type: 'action', position: opts.position });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
64
84
|
const successOpt = typeof opts.success === 'function' ? opts.success(res) : opts.success;
|
|
65
85
|
if (successOpt)
|
|
66
86
|
this.show({ ...(successOpt || {}), type: 'success', position: opts.position });
|
|
@@ -1002,12 +1022,30 @@ const POSITIONS = [
|
|
|
1002
1022
|
'bottom-center',
|
|
1003
1023
|
'bottom-right'
|
|
1004
1024
|
];
|
|
1005
|
-
function createContainer(position, root) {
|
|
1025
|
+
function createContainer(position, root, opts) {
|
|
1006
1026
|
const c = document.createElement('div');
|
|
1007
1027
|
c.className = 'sileo-toaster';
|
|
1008
1028
|
c.setAttribute('data-position', position);
|
|
1009
1029
|
c.setAttribute('role', 'status');
|
|
1010
1030
|
c.setAttribute('aria-live', 'polite');
|
|
1031
|
+
if (opts === null || opts === void 0 ? void 0 : opts.theme)
|
|
1032
|
+
c.setAttribute('data-theme', opts.theme);
|
|
1033
|
+
// offset
|
|
1034
|
+
if ((opts === null || opts === void 0 ? void 0 : opts.offset) !== undefined) {
|
|
1035
|
+
if (typeof opts.offset === 'number' || typeof opts.offset === 'string') {
|
|
1036
|
+
c.style.margin = typeof opts.offset === 'number' ? `${opts.offset}px` : opts.offset;
|
|
1037
|
+
}
|
|
1038
|
+
else if (typeof opts.offset === 'object') {
|
|
1039
|
+
if (opts.offset.top !== undefined)
|
|
1040
|
+
c.style.marginTop = `${opts.offset.top}px`;
|
|
1041
|
+
if (opts.offset.right !== undefined)
|
|
1042
|
+
c.style.marginRight = `${opts.offset.right}px`;
|
|
1043
|
+
if (opts.offset.bottom !== undefined)
|
|
1044
|
+
c.style.marginBottom = `${opts.offset.bottom}px`;
|
|
1045
|
+
if (opts.offset.left !== undefined)
|
|
1046
|
+
c.style.marginLeft = `${opts.offset.left}px`;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1011
1049
|
return c;
|
|
1012
1050
|
}
|
|
1013
1051
|
function renderToast(item) {
|
|
@@ -1061,33 +1099,46 @@ function renderToast(item) {
|
|
|
1061
1099
|
}
|
|
1062
1100
|
return el;
|
|
1063
1101
|
}
|
|
1064
|
-
function initToasters(root = document.body, positions = ['top-right']) {
|
|
1102
|
+
function initToasters(root = document.body, positions = ['top-right'], opts) {
|
|
1065
1103
|
const containers = {};
|
|
1066
1104
|
positions.forEach((pos) => {
|
|
1067
|
-
const c = createContainer(pos);
|
|
1105
|
+
const c = createContainer(pos, root, opts);
|
|
1068
1106
|
root.appendChild(c);
|
|
1069
1107
|
containers[pos] = c;
|
|
1070
1108
|
});
|
|
1109
|
+
if (opts === null || opts === void 0 ? void 0 : opts.options)
|
|
1110
|
+
window.sileo._globalOptions = opts.options;
|
|
1111
|
+
if (opts === null || opts === void 0 ? void 0 : opts.theme)
|
|
1112
|
+
window.sileo._theme = opts.theme;
|
|
1113
|
+
// fallback dinámico: si solo hay una posición, usarla como default
|
|
1114
|
+
const fallbackPosition = positions.length === 1 ? positions[0] : 'top-right';
|
|
1071
1115
|
function rerender(items) {
|
|
1116
|
+
// Lanzar advertencia para todos los toasts con posición no inicializada
|
|
1117
|
+
items.forEach((t) => {
|
|
1118
|
+
if (t.options.position && !containers[t.options.position]) {
|
|
1119
|
+
console.warn(`[sileo] Toast con posición "${t.options.position}" pero no se inicializó ningún contenedor para esa posición. Inicializa con initToasters(..., ['${t.options.position}']) para mostrarlo.`);
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1072
1122
|
positions.forEach((pos) => {
|
|
1073
1123
|
const container = containers[pos];
|
|
1074
|
-
|
|
1075
|
-
|
|
1124
|
+
// fallback dinámico
|
|
1125
|
+
const visible = items.filter((t) => (t.options.position || fallbackPosition) === pos);
|
|
1126
|
+
// Diff existing children y animar salida de toasts removidos
|
|
1076
1127
|
const visibleIds = new Set(visible.map((v) => v.id));
|
|
1077
1128
|
const existing = Array.from(container.children);
|
|
1078
1129
|
existing.forEach((child) => {
|
|
1079
1130
|
const id = child.dataset.id;
|
|
1080
1131
|
if (!id || !visibleIds.has(id)) {
|
|
1081
|
-
//
|
|
1132
|
+
// animar salida y luego remover
|
|
1082
1133
|
animate(child, { opacity: 0, y: -8 }, { duration: 0.18 }).finished.then(() => child.remove());
|
|
1083
1134
|
}
|
|
1084
1135
|
});
|
|
1085
|
-
//
|
|
1136
|
+
// Añadir nuevos toasts
|
|
1086
1137
|
visible.forEach((t) => {
|
|
1087
1138
|
if (!container.querySelector(`[data-id="${t.id}"]`)) {
|
|
1088
1139
|
const node = renderToast(t);
|
|
1089
1140
|
container.appendChild(node);
|
|
1090
|
-
//
|
|
1141
|
+
// animar entrada
|
|
1091
1142
|
requestAnimationFrame(() => {
|
|
1092
1143
|
animate(node, { opacity: [0, 1], y: [-6, 0] }, { duration: 0.24 });
|
|
1093
1144
|
});
|
package/dist/notify.umd.js
CHANGED
|
@@ -26,8 +26,21 @@
|
|
|
26
26
|
return this.toasts.slice();
|
|
27
27
|
}
|
|
28
28
|
show(opts) {
|
|
29
|
+
// Normalizar props avanzadas
|
|
30
|
+
const { icon, fill, styles, roundness, autopilot, ...rest } = opts;
|
|
29
31
|
const id = this.nextId();
|
|
30
|
-
const item = {
|
|
32
|
+
const item = {
|
|
33
|
+
id,
|
|
34
|
+
options: {
|
|
35
|
+
...rest,
|
|
36
|
+
...(icon !== undefined ? { icon } : {}),
|
|
37
|
+
...(fill !== undefined ? { fill } : {}),
|
|
38
|
+
...(styles !== undefined ? { styles } : {}),
|
|
39
|
+
...(roundness !== undefined ? { roundness } : {}),
|
|
40
|
+
...(autopilot !== undefined ? { autopilot } : {}),
|
|
41
|
+
},
|
|
42
|
+
createdAt: Date.now(),
|
|
43
|
+
};
|
|
31
44
|
this.toasts.push(item);
|
|
32
45
|
this.notify();
|
|
33
46
|
return id;
|
|
@@ -67,6 +80,13 @@
|
|
|
67
80
|
const loadingId = this.show({ ...(opts.loading || {}), type: 'loading', position: opts.position });
|
|
68
81
|
p.then((res) => {
|
|
69
82
|
this.dismiss(loadingId);
|
|
83
|
+
if (opts.action) {
|
|
84
|
+
const actionOpt = typeof opts.action === 'function' ? opts.action(res) : opts.action;
|
|
85
|
+
if (actionOpt) {
|
|
86
|
+
this.show({ ...(actionOpt || {}), type: 'action', position: opts.position });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
70
90
|
const successOpt = typeof opts.success === 'function' ? opts.success(res) : opts.success;
|
|
71
91
|
if (successOpt)
|
|
72
92
|
this.show({ ...(successOpt || {}), type: 'success', position: opts.position });
|
|
@@ -1008,12 +1028,30 @@
|
|
|
1008
1028
|
'bottom-center',
|
|
1009
1029
|
'bottom-right'
|
|
1010
1030
|
];
|
|
1011
|
-
function createContainer(position, root) {
|
|
1031
|
+
function createContainer(position, root, opts) {
|
|
1012
1032
|
const c = document.createElement('div');
|
|
1013
1033
|
c.className = 'sileo-toaster';
|
|
1014
1034
|
c.setAttribute('data-position', position);
|
|
1015
1035
|
c.setAttribute('role', 'status');
|
|
1016
1036
|
c.setAttribute('aria-live', 'polite');
|
|
1037
|
+
if (opts === null || opts === void 0 ? void 0 : opts.theme)
|
|
1038
|
+
c.setAttribute('data-theme', opts.theme);
|
|
1039
|
+
// offset
|
|
1040
|
+
if ((opts === null || opts === void 0 ? void 0 : opts.offset) !== undefined) {
|
|
1041
|
+
if (typeof opts.offset === 'number' || typeof opts.offset === 'string') {
|
|
1042
|
+
c.style.margin = typeof opts.offset === 'number' ? `${opts.offset}px` : opts.offset;
|
|
1043
|
+
}
|
|
1044
|
+
else if (typeof opts.offset === 'object') {
|
|
1045
|
+
if (opts.offset.top !== undefined)
|
|
1046
|
+
c.style.marginTop = `${opts.offset.top}px`;
|
|
1047
|
+
if (opts.offset.right !== undefined)
|
|
1048
|
+
c.style.marginRight = `${opts.offset.right}px`;
|
|
1049
|
+
if (opts.offset.bottom !== undefined)
|
|
1050
|
+
c.style.marginBottom = `${opts.offset.bottom}px`;
|
|
1051
|
+
if (opts.offset.left !== undefined)
|
|
1052
|
+
c.style.marginLeft = `${opts.offset.left}px`;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1017
1055
|
return c;
|
|
1018
1056
|
}
|
|
1019
1057
|
function renderToast(item) {
|
|
@@ -1067,33 +1105,46 @@
|
|
|
1067
1105
|
}
|
|
1068
1106
|
return el;
|
|
1069
1107
|
}
|
|
1070
|
-
function initToasters(root = document.body, positions = ['top-right']) {
|
|
1108
|
+
function initToasters(root = document.body, positions = ['top-right'], opts) {
|
|
1071
1109
|
const containers = {};
|
|
1072
1110
|
positions.forEach((pos) => {
|
|
1073
|
-
const c = createContainer(pos);
|
|
1111
|
+
const c = createContainer(pos, root, opts);
|
|
1074
1112
|
root.appendChild(c);
|
|
1075
1113
|
containers[pos] = c;
|
|
1076
1114
|
});
|
|
1115
|
+
if (opts === null || opts === void 0 ? void 0 : opts.options)
|
|
1116
|
+
window.sileo._globalOptions = opts.options;
|
|
1117
|
+
if (opts === null || opts === void 0 ? void 0 : opts.theme)
|
|
1118
|
+
window.sileo._theme = opts.theme;
|
|
1119
|
+
// fallback dinámico: si solo hay una posición, usarla como default
|
|
1120
|
+
const fallbackPosition = positions.length === 1 ? positions[0] : 'top-right';
|
|
1077
1121
|
function rerender(items) {
|
|
1122
|
+
// Lanzar advertencia para todos los toasts con posición no inicializada
|
|
1123
|
+
items.forEach((t) => {
|
|
1124
|
+
if (t.options.position && !containers[t.options.position]) {
|
|
1125
|
+
console.warn(`[sileo] Toast con posición "${t.options.position}" pero no se inicializó ningún contenedor para esa posición. Inicializa con initToasters(..., ['${t.options.position}']) para mostrarlo.`);
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1078
1128
|
positions.forEach((pos) => {
|
|
1079
1129
|
const container = containers[pos];
|
|
1080
|
-
|
|
1081
|
-
|
|
1130
|
+
// fallback dinámico
|
|
1131
|
+
const visible = items.filter((t) => (t.options.position || fallbackPosition) === pos);
|
|
1132
|
+
// Diff existing children y animar salida de toasts removidos
|
|
1082
1133
|
const visibleIds = new Set(visible.map((v) => v.id));
|
|
1083
1134
|
const existing = Array.from(container.children);
|
|
1084
1135
|
existing.forEach((child) => {
|
|
1085
1136
|
const id = child.dataset.id;
|
|
1086
1137
|
if (!id || !visibleIds.has(id)) {
|
|
1087
|
-
//
|
|
1138
|
+
// animar salida y luego remover
|
|
1088
1139
|
animate(child, { opacity: 0, y: -8 }, { duration: 0.18 }).finished.then(() => child.remove());
|
|
1089
1140
|
}
|
|
1090
1141
|
});
|
|
1091
|
-
//
|
|
1142
|
+
// Añadir nuevos toasts
|
|
1092
1143
|
visible.forEach((t) => {
|
|
1093
1144
|
if (!container.querySelector(`[data-id="${t.id}"]`)) {
|
|
1094
1145
|
const node = renderToast(t);
|
|
1095
1146
|
container.appendChild(node);
|
|
1096
|
-
//
|
|
1147
|
+
// animar entrada
|
|
1097
1148
|
requestAnimationFrame(() => {
|
|
1098
1149
|
animate(node, { opacity: [0, 1], y: [-6, 0] }, { duration: 0.24 });
|
|
1099
1150
|
});
|
package/docs/browser.md
CHANGED
|
@@ -8,24 +8,85 @@ Use this package directly in the browser when you cannot use npm modules or a bu
|
|
|
8
8
|
Add the stylesheet and UMD bundle to your HTML:
|
|
9
9
|
|
|
10
10
|
```html
|
|
11
|
-
<link rel="stylesheet" href="https://unpkg.com/@samline/notify@0.1
|
|
12
|
-
<script src="https://unpkg.com/@samline/notify@0.1
|
|
11
|
+
<link rel="stylesheet" href="https://unpkg.com/@samline/notify@0.2.1/dist/styles.css">
|
|
12
|
+
<script src="https://unpkg.com/@samline/notify@0.2.1/dist/notify.umd.js"></script>
|
|
13
13
|
<script>
|
|
14
14
|
document.addEventListener('DOMContentLoaded', () => {
|
|
15
15
|
const api = window.notify; // or window.notifications for legacy
|
|
16
|
-
|
|
16
|
+
// Always use an array of strings as the second argument
|
|
17
|
+
// Correct usage:
|
|
18
|
+
api.initToasters(document.body, ['top-left']);
|
|
17
19
|
api.notify({ title: 'Hello', description: 'No-bundler usage', type: 'info' });
|
|
18
20
|
});
|
|
19
21
|
</script>
|
|
20
22
|
```
|
|
21
23
|
|
|
24
|
+
|
|
25
|
+
|
|
22
26
|
## API
|
|
23
27
|
|
|
24
28
|
- The UMD bundle exposes `window.notify` (recommended) and `window.notifications` (legacy/compatibility).
|
|
25
29
|
- Always include `dist/styles.css` for correct appearance and animations.
|
|
26
|
-
- Use `api.initToasters(container, positions)` to mount containers (usually `document.body`).
|
|
30
|
+
- Use `api.initToasters(container, positions, { offset, options, theme })` to mount containers (usually `document.body`).
|
|
27
31
|
- Use `api.notify({...})` to show a notification.
|
|
28
32
|
|
|
33
|
+
### ⚠️ Warnings and Best Practices
|
|
34
|
+
|
|
35
|
+
- The second argument to `initToasters` **must be an array of strings** (e.g. `['top-left']`).
|
|
36
|
+
- **Do not use** `document.body['top-left']` (this is `undefined` and will not work).
|
|
37
|
+
- If you initialize only one position, toasts without an explicit position will go there automatically (dynamic fallback).
|
|
38
|
+
- If you initialize multiple positions, toasts without an explicit position will go to `'top-right'` by default.
|
|
39
|
+
- If you notify to a position that was not initialized, you will see a warning in the console and the toast will not be shown.
|
|
40
|
+
|
|
41
|
+
### Advanced Options
|
|
42
|
+
|
|
43
|
+
All notification methods accept advanced options:
|
|
44
|
+
|
|
45
|
+
| Property | Type | Default | Description |
|
|
46
|
+
| ------------- | -------------------------------------- | ----------- | ------------------------------------------- |
|
|
47
|
+
| `title` | string | — | Toast title |
|
|
48
|
+
| `description` | string | — | Optional body text (HTML or string) |
|
|
49
|
+
| `type` | `info\|success\|error\|warning\|action`| `info` | Visual intent |
|
|
50
|
+
| `position` | string | `top-right` | One of toast positions |
|
|
51
|
+
| `duration` | number \| null | `4000` | Auto-dismiss timeout in ms (null = sticky) |
|
|
52
|
+
| `button` | { title: string, onClick: () => void } | — | Optional action button |
|
|
53
|
+
| `icon` | string | — | Custom icon (SVG or HTML) |
|
|
54
|
+
| `fill` | string | — | Custom background color (SVG or badge) |
|
|
55
|
+
| `styles` | { title, description, badge, button } | — | Custom class overrides for sub-elements |
|
|
56
|
+
| `roundness` | number | 16 | Border radius in pixels |
|
|
57
|
+
| `autopilot` | boolean \| object | true | Auto expand/collapse timing |
|
|
58
|
+
|
|
59
|
+
#### Example: Advanced Toast
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
api.success({
|
|
63
|
+
title: "Styled!",
|
|
64
|
+
fill: "#222",
|
|
65
|
+
icon: '<svg>...</svg>',
|
|
66
|
+
styles: {
|
|
67
|
+
title: "text-white!",
|
|
68
|
+
badge: "bg-white/20!",
|
|
69
|
+
button: "bg-white/10!"
|
|
70
|
+
},
|
|
71
|
+
roundness: 24,
|
|
72
|
+
autopilot: false
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Toaster Options
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
You can pass advanced options to `initToasters`:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
// Correct example with multiple positions
|
|
83
|
+
api.initToasters(document.body, ['top-right', 'bottom-left'], {
|
|
84
|
+
offset: { top: 32, right: 16 },
|
|
85
|
+
options: { fill: '#222', roundness: 20 },
|
|
86
|
+
theme: 'dark'
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
29
90
|
## Notes
|
|
30
91
|
|
|
31
92
|
- If you need more control or want to avoid global variables, use the ESM/CJS builds with a bundler.
|