@r01al/simple-toast 1.0.0

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,406 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SimpleToast = {}));
5
+ })(this, (function (exports) { 'use strict';
6
+
7
+ const defaultConfig = {
8
+ position: 'tr',
9
+ duration: 3000,
10
+ theme: 'l',
11
+ dismissible: true,
12
+ maxToasts: 10
13
+ };
14
+
15
+ let globalConfig = { ...defaultConfig };
16
+
17
+ function configure(config) {
18
+ globalConfig = { ...globalConfig, ...config };
19
+ }
20
+
21
+ function getConfig() {
22
+ return globalConfig;
23
+ }
24
+
25
+ let stylesInjected = false;
26
+
27
+ function injectStyles() {
28
+ if (stylesInjected) return;
29
+
30
+ const style = document.createElement('style');
31
+ style.setAttribute('data-r01st', '');
32
+ style.textContent = `
33
+ .r01st-container {
34
+ position: fixed;
35
+ z-index: 9999;
36
+ pointer-events: none;
37
+ padding: 16px;
38
+ }
39
+ .r01st-container[data-position="tl"] {
40
+ top: 0;
41
+ left: 0;
42
+ }
43
+
44
+ .r01st-container[data-position="tc"] {
45
+ top: 0;
46
+ left: 50%;
47
+ margin-left: -210px;
48
+ }
49
+
50
+ .r01st-container[data-position="tr"] {
51
+ top: 0;
52
+ right: 0;
53
+ }
54
+ .r01st-container[data-position="ml"] {
55
+ top: 50%;
56
+ left: 0;
57
+ margin-top: -50px;
58
+ }
59
+
60
+ .r01st-container[data-position="mc"] {
61
+ top: 50%;
62
+ left: 50%;
63
+ margin-left: -210px;
64
+ margin-top: -50px;
65
+ }
66
+
67
+ .r01st-container[data-position="mr"] {
68
+ top: 50%;
69
+ right: 0;
70
+ margin-top: -50px;
71
+ }
72
+ .r01st-container[data-position="bl"] {
73
+ bottom: 0;
74
+ left: 0;
75
+ }
76
+
77
+ .r01st-container[data-position="bc"] {
78
+ bottom: 0;
79
+ left: 50%;
80
+ margin-left: -210px;
81
+ }
82
+
83
+ .r01st-container[data-position="br"] {
84
+ bottom: 0;
85
+ right: 0;
86
+ }
87
+ .r01st {
88
+ pointer-events: auto;
89
+ position: relative;
90
+ min-width: 280px;
91
+ max-width: 420px;
92
+ padding: 16px;
93
+ margin-bottom: 16px;
94
+ border-radius: 8px;
95
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
96
+ font-size: 14px;
97
+ line-height: 1.5;
98
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
99
+ color: #ffffff;
100
+ opacity: 0;
101
+ transition: opacity 0.3s ease, margin-top 0.3s ease;
102
+ margin-top: -20px;
103
+ }
104
+ .r01st-show {
105
+ opacity: 1;
106
+ margin-top: 0;
107
+ }
108
+ .r01st-container[data-theme="l"] .r01st-success {
109
+ background-color: #10b981;
110
+ }
111
+
112
+ .r01st-container[data-theme="l"] .r01st-error {
113
+ background-color: #ef4444;
114
+ }
115
+
116
+ .r01st-container[data-theme="l"] .r01st-info {
117
+ background-color: #3b82f6;
118
+ }
119
+
120
+ .r01st-container[data-theme="l"] .r01st-warning {
121
+ background-color: #f59e0b;
122
+ }
123
+ .r01st-container[data-theme="d"] .r01st-success {
124
+ background-color: #059669;
125
+ }
126
+
127
+ .r01st-container[data-theme="d"] .r01st-error {
128
+ background-color: #dc2626;
129
+ }
130
+
131
+ .r01st-container[data-theme="d"] .r01st-info {
132
+ background-color: #2563eb;
133
+ }
134
+
135
+ .r01st-container[data-theme="d"] .r01st-warning {
136
+ background-color: #d97706;
137
+ }
138
+ .r01st-content {
139
+ display: inline-block;
140
+ width: 100%;
141
+ vertical-align: top;
142
+ padding-right: 30px;
143
+ }
144
+ .r01st-icon {
145
+ display: inline-block;
146
+ width: 20px;
147
+ height: 20px;
148
+ vertical-align: middle;
149
+ margin-right: 12px;
150
+ }
151
+
152
+ .r01st-icon svg {
153
+ width: 100%;
154
+ height: 100%;
155
+ display: block;
156
+ }
157
+ .r01st-message {
158
+ display: inline-block;
159
+ vertical-align: middle;
160
+ word-wrap: break-word;
161
+ max-width: calc(100% - 32px);
162
+ }
163
+ .r01st-close {
164
+ position: absolute;
165
+ top: 16px;
166
+ right: 16px;
167
+ background: transparent;
168
+ border: none;
169
+ color: #ffffff;
170
+ font-size: 24px;
171
+ line-height: 1;
172
+ cursor: pointer;
173
+ padding: 0;
174
+ width: 24px;
175
+ height: 24px;
176
+ text-align: center;
177
+ border-radius: 4px;
178
+ opacity: 0.8;
179
+ }
180
+
181
+ .r01st-close:hover {
182
+ background-color: rgba(255, 255, 255, 0.2);
183
+ opacity: 1;
184
+ }
185
+
186
+ .r01st-close:focus {
187
+ outline: 2px solid #ffffff;
188
+ outline-offset: 2px;
189
+ }
190
+ .r01st-container[data-position^="b"] .r01st {
191
+ margin-top: 0;
192
+ margin-bottom: -20px;
193
+ }
194
+
195
+ .r01st-container[data-position^="b"] .r01st-show {
196
+ margin-bottom: 16px;
197
+ }
198
+ @media (max-width: 480px) {
199
+ .r01st-container {
200
+ left: 0 !important;
201
+ right: 0 !important;
202
+ margin-left: 0 !important;
203
+ padding: 8px;
204
+ }
205
+
206
+ .r01st {
207
+ min-width: auto;
208
+ max-width: none;
209
+ }
210
+ }
211
+ @media (prefers-reduced-motion: reduce) {
212
+ .r01st {
213
+ transition: opacity 0.2s;
214
+ margin-top: 0;
215
+ }
216
+ }
217
+ @media print {
218
+ .r01st-container {
219
+ display: none;
220
+ }
221
+ }
222
+ `;
223
+
224
+ document.head.appendChild(style);
225
+ stylesInjected = true;
226
+ }
227
+
228
+ let container = null;
229
+
230
+ function getContainer(position, theme) {
231
+ injectStyles();
232
+
233
+ if (container && (container.getAttribute('data-position') !== position || container.getAttribute('data-theme') !== theme)) {
234
+ container.parentNode.removeChild(container);
235
+ container = null;
236
+ }
237
+
238
+ if (!container) {
239
+ container = document.createElement('div');
240
+ container.className = 'r01st-container';
241
+ container.setAttribute('role', 'region');
242
+ container.setAttribute('aria-label', 'Notifications');
243
+ container.setAttribute('aria-live', 'polite');
244
+ container.setAttribute('data-position', position);
245
+ container.setAttribute('data-theme', theme);
246
+ document.body.appendChild(container);
247
+ }
248
+
249
+ return container;
250
+ }
251
+
252
+ function cleanupContainer(activeCount) {
253
+ if (container && activeCount === 0) {
254
+ container.parentNode.removeChild(container);
255
+ container = null;
256
+ }
257
+ }
258
+
259
+ function getIcon(type) {
260
+ const icons = {
261
+ success: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>',
262
+ error: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>',
263
+ info: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/></svg>',
264
+ warning: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>'
265
+ };
266
+ return icons[type] || icons.info;
267
+ }
268
+
269
+ function createToastElement(message, type, dismissible, className, id, onClose) {
270
+ const toastEl = document.createElement('div');
271
+ toastEl.className = 'r01st r01st-' + type + (className ? ' ' + className : '');
272
+ toastEl.setAttribute('role', 'alert');
273
+ toastEl.setAttribute('data-toast-id', id);
274
+
275
+ const content = document.createElement('div');
276
+ content.className = 'r01st-content';
277
+
278
+ const icon = document.createElement('span');
279
+ icon.className = 'r01st-icon';
280
+ icon.innerHTML = getIcon(type);
281
+ icon.setAttribute('aria-hidden', 'true');
282
+ content.appendChild(icon);
283
+
284
+ const messageEl = document.createElement('span');
285
+ messageEl.className = 'r01st-message';
286
+ messageEl.textContent = message;
287
+ content.appendChild(messageEl);
288
+
289
+ toastEl.appendChild(content);
290
+
291
+ if (dismissible) {
292
+ const closeBtn = document.createElement('button');
293
+ closeBtn.className = 'r01st-close';
294
+ closeBtn.innerHTML = '&times;';
295
+ closeBtn.setAttribute('aria-label', 'Close notification');
296
+ closeBtn.onclick = onClose;
297
+ toastEl.appendChild(closeBtn);
298
+ }
299
+
300
+ return toastEl;
301
+ }
302
+
303
+ function showToast(element) {
304
+ requestAnimationFrame(() => {
305
+ element.className = element.className + ' r01st-show';
306
+ });
307
+ }
308
+
309
+ function hideToast(element, callback) {
310
+ element.className = element.className.replace(' r01st-show', '');
311
+
312
+ setTimeout(() => {
313
+ if (element.parentNode) {
314
+ element.parentNode.removeChild(element);
315
+ }
316
+ if (callback) callback();
317
+ }, 300);
318
+ }
319
+
320
+ let toastCounter = 0;
321
+ const activeToasts = new Map();
322
+
323
+ function createToast(message, options = {}) {
324
+ const config = { ...getConfig(), ...options };
325
+ const { position, duration, dismissible, theme, className, type = 'info' } = config;
326
+
327
+ const id = ++toastCounter;
328
+
329
+ if (activeToasts.size >= getConfig().maxToasts) {
330
+ const firstToast = activeToasts.keys().next().value;
331
+ dismissToast(firstToast);
332
+ }
333
+
334
+ const container = getContainer(position, theme);
335
+
336
+ const handleClose = () => dismissToast(id);
337
+
338
+ const toastEl = createToastElement(message, type, dismissible, className, id, handleClose);
339
+
340
+ container.appendChild(toastEl);
341
+
342
+ showToast(toastEl);
343
+
344
+ const toastData = {
345
+ element: toastEl,
346
+ timeoutId: null
347
+ };
348
+
349
+ if (duration > 0) {
350
+ toastData.timeoutId = setTimeout(() => dismissToast(id), duration);
351
+ }
352
+
353
+ activeToasts.set(id, toastData);
354
+
355
+ return id;
356
+ }
357
+
358
+ function dismissToast(id) {
359
+ const toastData = activeToasts.get(id);
360
+ if (!toastData) return;
361
+
362
+ const { element, timeoutId } = toastData;
363
+
364
+ if (timeoutId) {
365
+ clearTimeout(timeoutId);
366
+ }
367
+
368
+ activeToasts.delete(id);
369
+
370
+ hideToast(element, () => {
371
+ cleanupContainer(activeToasts.size);
372
+ });
373
+ }
374
+
375
+ function dismissAllToasts() {
376
+ const ids = Array.from(activeToasts.keys());
377
+ ids.forEach(id => dismissToast(id));
378
+ }
379
+
380
+ /**
381
+ * Display a toast notification
382
+ * @param {string} message - The message to display
383
+ * @param {Object} options - Toast options
384
+ * @returns {number} Toast ID
385
+ */
386
+ function toast(message, options = {}) {
387
+ return createToast(message, options);
388
+ }
389
+
390
+ // Shorthand methods
391
+ toast.success = (message, options = {}) => toast(message, { ...options, type: 'success' });
392
+ toast.error = (message, options = {}) => toast(message, { ...options, type: 'error' });
393
+ toast.info = (message, options = {}) => toast(message, { ...options, type: 'info' });
394
+ toast.warning = (message, options = {}) => toast(message, { ...options, type: 'warning' });
395
+
396
+ // Export dismiss methods
397
+ toast.dismiss = dismissToast;
398
+ toast.dismissAll = dismissAllToasts;
399
+
400
+ exports.configure = configure;
401
+ exports.default = toast;
402
+ exports.toast = toast;
403
+
404
+ Object.defineProperty(exports, '__esModule', { value: true });
405
+
406
+ }));
@@ -0,0 +1,110 @@
1
+ export interface ToastOptions {
2
+ /**
3
+ * Type of toast notification
4
+ * @default 'info'
5
+ */
6
+ type?: 'success' | 'error' | 'info' | 'warning';
7
+
8
+ /**
9
+ * Duration in milliseconds before auto-dismiss (0 = no auto-dismiss)
10
+ * @default 3000
11
+ */
12
+ duration?: number;
13
+
14
+ /**
15
+ * Position of the toast on screen
16
+ * @default 'top-right'
17
+ */
18
+ position?:
19
+ | 'top-left'
20
+ | 'top-center'
21
+ | 'top-right'
22
+ | 'bottom-left'
23
+ | 'bottom-center'
24
+ | 'bottom-right'
25
+ | 'middle-left'
26
+ | 'middle-center'
27
+ | 'middle-right';
28
+
29
+ /**
30
+ * Whether to show a close button
31
+ * @default true
32
+ */
33
+ dismissible?: boolean;
34
+
35
+ /**
36
+ * Theme to apply
37
+ * @default 'light'
38
+ */
39
+ theme?: 'light' | 'dark' | 'custom';
40
+
41
+ /**
42
+ * Custom CSS class to add to the toast
43
+ */
44
+ className?: string;
45
+ }
46
+
47
+ export interface ToastConfig extends ToastOptions {
48
+ /**
49
+ * Maximum number of toasts to show at once
50
+ * @default 10
51
+ */
52
+ maxToasts?: number;
53
+ }
54
+
55
+ /**
56
+ * Configure global toast settings
57
+ */
58
+ export function configure(config: ToastConfig): void;
59
+
60
+ /**
61
+ * Display a toast notification
62
+ * @param message - The message to display
63
+ * @param options - Toast options
64
+ * @returns Toast ID
65
+ */
66
+ export function toast(message: string, options?: ToastOptions): number;
67
+
68
+ export namespace toast {
69
+ /**
70
+ * Display a success toast
71
+ */
72
+ export function success(message: string, options?: ToastOptions): number;
73
+
74
+ /**
75
+ * Display an error toast
76
+ */
77
+ export function error(message: string, options?: ToastOptions): number;
78
+
79
+ /**
80
+ * Display an info toast
81
+ */
82
+ export function info(message: string, options?: ToastOptions): number;
83
+
84
+ /**
85
+ * Display a warning toast
86
+ */
87
+ export function warning(message: string, options?: ToastOptions): number;
88
+
89
+ /**
90
+ * Dismiss a specific toast by ID
91
+ */
92
+ export function dismiss(id: number): void;
93
+
94
+ /**
95
+ * Dismiss all active toasts
96
+ */
97
+ export function dismissAll(): void;
98
+ }
99
+
100
+ /**
101
+ * Dismiss a specific toast by ID
102
+ */
103
+ export function dismiss(id: number): void;
104
+
105
+ /**
106
+ * Dismiss all active toasts
107
+ */
108
+ export function dismissAll(): void;
109
+
110
+ export default toast;
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@r01al/simple-toast",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight, framework-agnostic toast notification library with zero dependencies",
5
+ "keywords": [
6
+ "toast",
7
+ "notification",
8
+ "alert",
9
+ "message",
10
+ "vanilla-js",
11
+ "framework-agnostic"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "r01al",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/r01al/simple-toast.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/r01al/simple-toast/issues"
21
+ },
22
+ "homepage": "https://github.com/r01al/simple-toast#readme",
23
+ "main": "dist/simple-toast.cjs.js",
24
+ "module": "dist/simple-toast.esm.js",
25
+ "browser": "dist/simple-toast.umd.js",
26
+ "types": "dist/toast.d.ts",
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "scripts": {
31
+ "build": "rollup -c && cp src/toast.d.ts dist/toast.d.ts",
32
+ "dev": "rollup -c -w",
33
+ "test": "echo \"Error: no test specified\" && exit 1"
34
+ },
35
+ "devDependencies": {
36
+ "@babel/core": "^7.28.5",
37
+ "@babel/preset-env": "^7.28.5",
38
+ "@rollup/plugin-babel": "^6.1.0",
39
+ "@rollup/plugin-node-resolve": "^15.2.3",
40
+ "@rollup/plugin-terser": "^0.4.4",
41
+ "rollup": "^4.9.4"
42
+ }
43
+ }