@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 CHANGED
@@ -1,8 +1,9 @@
1
- ## @samline/notify
2
1
 
3
- A Sileo-inspired notifications engine providing a framework-agnostic core and lightweight adapters for Vanilla JS, React, Vue and Svelte.
2
+ # Notify
4
3
 
5
- Inspired by Sileo see the original project: https://github.com/hiaaryan/sileo
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.15/dist/notify.umd.js"></script>
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
- const api = window.notify || window.notifications
59
- api.initToasters(document.body, ['top-right'])
60
- api.notify({ title: 'Saved', description: 'Changes saved', type: 'success' })
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.13/dist/notify.umd.js"></script>`<br/>`const api = window.notify; api.initToasters(...);` | [docs/browser.md](docs/browser.md) |
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
- Common `options` across entrypoints:
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 | — | Optional body text |
111
- | `type` | `info\|success\|error\|warning` | `info` | Visual intent |
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 | `4000` | Auto-dismiss timeout in ms (0 = persistent) |
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 = { id, options: { ...opts }, createdAt: Date.now() };
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
- const visible = items.filter((t) => (t.options.position || 'top-right') === pos);
1079
- // Diff existing children and animate out removed toasts
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
- // animate out then remove
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
- // Add new items
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
- // animate in
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 = { id, options: { ...opts }, createdAt: Date.now() };
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
- const visible = items.filter((t) => (t.options.position || 'top-right') === pos);
1075
- // Diff existing children and animate out removed toasts
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
- // animate out then remove
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
- // Add new items
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
- // animate in
1141
+ // animar entrada
1091
1142
  requestAnimationFrame(() => {
1092
1143
  animate(node, { opacity: [0, 1], y: [-6, 0] }, { duration: 0.24 });
1093
1144
  });
@@ -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 = { id, options: { ...opts }, createdAt: Date.now() };
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
- const visible = items.filter((t) => (t.options.position || 'top-right') === pos);
1081
- // Diff existing children and animate out removed toasts
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
- // animate out then remove
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
- // Add new items
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
- // animate in
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.15/dist/styles.css">
12
- <script src="https://unpkg.com/@samline/notify@0.1.15/dist/notify.umd.js"></script>
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
- api.initToasters(document.body, ['top-right']);
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.