minisnackbar 1.0.2 → 3.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.
@@ -1,382 +1,425 @@
1
- /**
2
- * MiniSnackbar - A simple vanilla JavaScript snackbar/toast library
3
- *
4
- * @version 1.0.0
5
- * @author Shanto Islam <shantoislamdev@gmail.com>
6
- * @license MIT
7
- * @description A lightweight, zero-dependency snackbar library with Material Design integration
8
- * @repository https://github.com/shantoislamdev/minisnackbar
9
- * @homepage https://github.com/shantoislamdev/minisnackbar#readme
10
- */
11
-
12
- class Snackbar {
13
- static _queue = []
14
- static _isShowing = false
15
- static _currentTimeout = null
16
- static _state = 'idle' // idle, showing, transitioning
17
- static _currentActionHandler = null
18
- static _transitionDuration = 250
19
- static _initialized = false
20
-
21
- static init(options = {}) {
22
- if (this._initialized) return
23
-
24
- if (typeof document === 'undefined' || !document.body) {
25
- console.error('Snackbar: DOM is not available');
26
- return
27
- }
28
-
29
- if (options.transitionDuration && typeof options.transitionDuration === 'number') {
30
- this._transitionDuration = options.transitionDuration;
31
- }
32
-
33
- if (document.getElementById('mini-snackbar')) {
34
- this._initialized = true;
35
- return
36
- }
37
-
38
- if (!document.getElementById('mini-snackbar-styles')) {
39
- const style = document.createElement('style');
40
- style.id = 'mini-snackbar-styles';
41
- style.textContent = `
42
- .mini-snackbar {
43
- /* Positioning */
44
- position: fixed;
45
- z-index: 1000;
46
- left: 50%;
47
- bottom: 30px;
48
- transform: translateX(-50%) translateY(100%);
49
-
50
- /* Sizing */
51
- min-width: 250px;
52
- max-width: 90%;
53
-
54
- /* Layout */
55
- display: flex;
56
- align-items: center;
57
- justify-content: space-between;
58
- gap: 8px;
59
- padding: 0.875rem 1rem;
60
-
61
- /* Visibility */
62
- visibility: hidden;
63
-
64
- /* Theming */
65
- background-color: var(--mini-snackbar-bg, var(--md-sys-color-inverse-surface, rgba(255, 255, 255, 1)));
66
- color: var(--mini-snackbar-text, var(--md-sys-color-inverse-on-surface, rgba(27, 27, 27, 1)));
67
- border: var(--mini-snackbar-border, none);
68
- font-family: var(--mini-snackbar-font-family, inherit);
69
- font-size: 0.875rem;
70
- text-align: left;
71
- border-radius: var(--mini-snackbar-radius, 1rem);
72
- box-shadow: var(--mini-snackbar-shadow, 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12));
73
-
74
- /* Animation */
75
- transition: var(--mini-snackbar-transition, transform ${this._transitionDuration}ms ease-in-out);
76
- }
77
-
78
- .mini-snackbar.show {
79
- visibility: visible;
80
- transform: translateX(-50%) translateY(0);
81
- }
82
-
83
- /* Material Component Action Button */
84
- .mini-snackbar .mini-snackbar-action {
85
- flex-shrink: 0;
86
- padding: 0.5rem 1rem;
87
- margin: -0.5rem -0.5rem -0.5rem 0;
88
- }
89
-
90
- /* Fallback Action Button (when Material Components unavailable) */
91
- .mini-snackbar md-text-button[data-fallback] {
92
- display: inline-block;
93
- flex-shrink: 0;
94
- padding: 0.5rem 1rem;
95
- margin: -0.5rem -0.5rem -0.5rem 0;
96
- border: none;
97
- background: var(--mini-snackbar-btn-bg, transparent);
98
- font-size: inherit;
99
- font-family: inherit;
100
- font-weight: 500;
101
- letter-spacing: 0.0892857143em;
102
- text-transform: uppercase;
103
- color: var(--mini-snackbar-btn-text, inherit);
104
- cursor: pointer;
105
- user-select: none;
106
- border-radius: var(--mini-snackbar-btn-radius, 1rem);
107
- transition: opacity 0.2s ease;
108
- }
109
-
110
- .mini-snackbar md-text-button[data-fallback]:hover {
111
- opacity: var(--mini-snackbar-btn-hover-opacity, 0.8);
112
- outline: var(--mini-snackbar-btn-hover-outline, 2px solid var(--mini-snackbar-btn-text, inherit));
113
- outline-offset: var(--mini-snackbar-btn-outline-offset, 2px);
114
- background-color: var(--mini-snackbar-btn-hover-bg, transparent);
115
- }
116
-
117
- .mini-snackbar md-text-button[data-fallback]:focus {
118
- outline: var(--mini-snackbar-btn-focus-outline, 2px solid var(--mini-snackbar-btn-text, inherit));
119
- outline-offset: var(--mini-snackbar-btn-outline-offset, 2px);
120
- }
121
-
122
- /* Mobile responsive */
123
- @media (max-width: 600px) {
124
- .mini-snackbar {
125
- bottom: 90px;
126
- }
127
- }
128
-
129
- /* Accessibility: Reduced motion */
130
- @media (prefers-reduced-motion: reduce) {
131
- .mini-snackbar {
132
- transition: opacity 0.15s ease;
133
- }
134
- }
135
- `;
136
- document.head.appendChild(style);
137
- }
138
-
139
- const snackbar = document.createElement('div');
140
- snackbar.id = 'mini-snackbar';
141
- snackbar.className = 'mini-snackbar';
142
- snackbar.setAttribute('role', 'alert');
143
- snackbar.setAttribute('aria-live', 'assertive');
144
- snackbar.setAttribute('aria-atomic', 'true');
145
-
146
- const snackbarText = document.createElement('span');
147
- snackbarText.className = 'mini-snackbar-text';
148
- snackbar.appendChild(snackbarText);
149
-
150
- document.body.appendChild(snackbar);
151
- this._initialized = true;
152
- }
153
-
154
- static destroy() {
155
- this.hideCurrent();
156
- this.clearQueue();
157
-
158
- const snackbar = document.getElementById('mini-snackbar');
159
- if (snackbar) snackbar.remove();
160
-
161
- const styles = document.getElementById('mini-snackbar-styles');
162
- if (styles) styles.remove();
163
-
164
- this._state = 'idle';
165
- this._isShowing = false;
166
- this._currentActionHandler = null;
167
- this._currentTimeout = null;
168
- this._initialized = false;
169
- }
170
-
171
- static getTransitionDuration() {
172
- const snackbar = document.getElementById('mini-snackbar');
173
- if (!snackbar) return this._transitionDuration
174
-
175
- try {
176
- const computedStyle = window.getComputedStyle(snackbar);
177
- const duration = computedStyle.transitionDuration;
178
- if (duration && duration !== '0s') {
179
- const value = parseFloat(duration);
180
- return duration.includes('ms') ? value : value * 1000
181
- }
182
- } catch (e) {
183
- console.warn('Snackbar: Could not read transition duration from CSS', e);
184
- }
185
-
186
- return this._transitionDuration
187
- }
188
-
189
- static add(message, action = null, duration = 3000) {
190
- if (!this._initialized) {
191
- console.warn('Snackbar: Not initialized. Call Snackbar.init() first.');
192
- return
193
- }
194
-
195
- if (typeof message !== 'string' || message.trim() === '') {
196
- console.warn('Snackbar: Message must be a non-empty string');
197
- return
198
- }
199
- if (action !== null && (typeof action !== 'object' || typeof action.text !== 'string' || typeof action.handler !== 'function')) {
200
- console.warn('Snackbar: Action must be an object with "text" (string) and "handler" (function) properties');
201
- return
202
- }
203
- if (typeof duration !== 'number' || duration <= 0) {
204
- console.warn('Snackbar: Duration must be a positive number');
205
- return
206
- }
207
-
208
- this._queue.push({ message, action, duration });
209
- if (this._state === 'idle') this.showNext();
210
- }
211
-
212
- static _cleanupAction() {
213
- const snackbar = document.getElementById('mini-snackbar');
214
- if (!snackbar) return
215
-
216
- const actionButton = snackbar.querySelector('.mini-snackbar-action');
217
- if (actionButton && this._currentActionHandler) {
218
- actionButton.removeEventListener('click', this._currentActionHandler);
219
- this._currentActionHandler = null;
220
- actionButton.remove();
221
- }
222
- }
223
-
224
- static _showSnackbar(message, action, duration, onHide = null) {
225
- const snackbar = document.getElementById('mini-snackbar');
226
- if (!snackbar) {
227
- console.error('Snackbar: Snackbar element not found. Ensure init() has been called.');
228
- return
229
- }
230
-
231
- this._state = 'showing';
232
- this._isShowing = true;
233
- const snackbarText = snackbar.querySelector('.mini-snackbar-text');
234
- snackbarText.textContent = message;
235
-
236
- if (action) {
237
- const actionButton = document.createElement('md-text-button');
238
- actionButton.classList.add('mini-snackbar-action');
239
-
240
- // Fallback for when Material Components are not available
241
- if (customElements.get('md-text-button') === undefined) {
242
- actionButton.setAttribute('data-fallback', '');
243
- }
244
-
245
- actionButton.textContent = action.text;
246
-
247
- this._currentActionHandler = () => {
248
- action.handler();
249
- this._hideSnackbar(onHide);
250
- };
251
-
252
- actionButton.addEventListener('click', this._currentActionHandler);
253
- snackbar.appendChild(actionButton);
254
- }
255
-
256
- snackbar.classList.add('show');
257
-
258
- this._currentTimeout = setTimeout(() => {
259
- this._hideSnackbar(onHide);
260
- }, duration);
261
- }
262
-
263
- static _hideSnackbar(onHide = null) {
264
- if (this._currentTimeout) {
265
- clearTimeout(this._currentTimeout);
266
- this._currentTimeout = null;
267
- }
268
-
269
- this._state = 'transitioning';
270
- const snackbar = document.getElementById('mini-snackbar');
271
- if (snackbar) {
272
- snackbar.classList.remove('show');
273
- }
274
-
275
- this._cleanupAction();
276
-
277
- // Wait for CSS transition to complete
278
- const transitionDuration = this.getTransitionDuration();
279
- setTimeout(() => {
280
- this._isShowing = false;
281
- this._state = 'idle';
282
- if (onHide) onHide();
283
- }, transitionDuration);
284
- }
285
-
286
- static show(message, action = null, duration = 3000) {
287
- if (!this._initialized) {
288
- console.warn('Snackbar: Not initialized. Call Snackbar.init() first.');
289
- return
290
- }
291
-
292
- if (typeof message !== 'string' || message.trim() === '') {
293
- console.warn('Snackbar: Message must be a non-empty string');
294
- return
295
- }
296
- if (action !== null && (typeof action !== 'object' || typeof action.text !== 'string' || typeof action.handler !== 'function')) {
297
- console.warn('Snackbar: Action must be an object with "text" (string) and "handler" (function) properties');
298
- return
299
- }
300
- if (typeof duration !== 'number' || duration <= 0) {
301
- console.warn('Snackbar: Duration must be a positive number');
302
- return
303
- }
304
-
305
- // Queue message if currently transitioning
306
- if (this._state === 'transitioning') {
307
- this.add(message, action, duration);
308
- return
309
- }
310
-
311
- // Interrupt current snackbar if showing
312
- if (this._isShowing) {
313
- this._state = 'transitioning';
314
- if (this._currentTimeout) {
315
- clearTimeout(this._currentTimeout);
316
- this._currentTimeout = null;
317
- }
318
- const snackbar = document.getElementById('mini-snackbar');
319
- if (snackbar) {
320
- snackbar.classList.remove('show');
321
- }
322
- this._cleanupAction();
323
-
324
- const transitionDuration = this.getTransitionDuration();
325
- setTimeout(() => {
326
- this._isShowing = false;
327
- this._state = 'idle';
328
- this._showSnackbar(message, action, duration);
329
- }, transitionDuration);
330
- } else {
331
- this._showSnackbar(message, action, duration);
332
- }
333
- }
334
-
335
- static showNext() {
336
- if (this._queue.length === 0) {
337
- this._isShowing = false;
338
- this._state = 'idle';
339
- return
340
- }
341
-
342
- const { message, action, duration } = this._queue.shift();
343
- this._showSnackbar(message, action, duration, () => {
344
- setTimeout(() => this.showNext(), 200);
345
- });
346
- }
347
-
348
- static clearQueue() {
349
- this._queue = [];
350
- }
351
-
352
- static hideCurrent() {
353
- if (this._isShowing && this._state !== 'transitioning') {
354
- this._hideSnackbar();
355
- }
356
- }
357
-
358
- static isInitialized() {
359
- return this._initialized
360
- }
361
-
362
- // Getters/setters for testing
363
- static get queue() { return this._queue }
364
- static set queue(value) { this._queue = value; }
365
- static get isShowing() { return this._isShowing }
366
- static set isShowing(value) { this._isShowing = value; }
367
- static get currentTimeout() { return this._currentTimeout }
368
- static set currentTimeout(value) { this._currentTimeout = value; }
369
- static get state() { return this._state }
370
- static set state(value) { this._state = value; }
371
- }
372
-
373
- // Module exports
374
- if (typeof module !== 'undefined' && module.exports) {
375
- module.exports = Snackbar;
376
- }
377
-
378
- // Make available globally in browser
379
- if (typeof window !== 'undefined') {
380
- window.Snackbar = Snackbar;
1
+ const SNACKBAR_ID = 'mini-snackbar';
2
+ const STYLES_ID = 'mini-snackbar-styles';
3
+ const SNACKBAR_CLASS = 'mini-snackbar';
4
+ const SNACKBAR_VISIBLE_CLASS = 'show';
5
+ const SNACKBAR_TEXT_CLASS = 'mini-snackbar-text';
6
+ const SNACKBAR_ACTION_CLASS = 'mini-snackbar-action';
7
+ const DEFAULT_DURATION = 3000;
8
+ const DEFAULT_TRANSITION_DURATION = 250;
9
+ const QUEUE_GAP_DURATION = 200;
10
+
11
+ function createSnackbarStyles(transitionDuration) {
12
+ return `
13
+ .mini-snackbar {
14
+ position: fixed;
15
+ z-index: 1000;
16
+ left: 50%;
17
+ bottom: 30px;
18
+ transform: translateX(-50%) translateY(100%);
19
+ min-width: 250px;
20
+ max-width: 90%;
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: space-between;
24
+ gap: 8px;
25
+ padding: 0.875rem 1rem;
26
+ visibility: hidden;
27
+ background-color: var(--mini-snackbar-bg, var(--md-sys-color-inverse-surface, rgba(255, 255, 255, 1)));
28
+ color: var(--mini-snackbar-text, var(--md-sys-color-inverse-on-surface, rgba(27, 27, 27, 1)));
29
+ border: var(--mini-snackbar-border, none);
30
+ font-family: var(--mini-snackbar-font-family, inherit);
31
+ font-size: 0.875rem;
32
+ text-align: left;
33
+ border-radius: var(--mini-snackbar-radius, 1rem);
34
+ box-shadow: var(--mini-snackbar-shadow, 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12));
35
+ transition: var(--mini-snackbar-transition, transform ${transitionDuration}ms ease-in-out);
36
+ }
37
+
38
+ .mini-snackbar.show {
39
+ visibility: visible;
40
+ transform: translateX(-50%) translateY(0);
41
+ }
42
+
43
+ .mini-snackbar .mini-snackbar-action {
44
+ flex-shrink: 0;
45
+ padding: 0.5rem 1rem;
46
+ margin: -0.5rem -0.5rem -0.5rem 0;
47
+ }
48
+
49
+ .mini-snackbar button.mini-snackbar-action {
50
+ border: none;
51
+ background: var(--mini-snackbar-btn-bg, transparent);
52
+ font-size: inherit;
53
+ font-family: inherit;
54
+ font-weight: 500;
55
+ letter-spacing: 0.0892857143em;
56
+ text-transform: uppercase;
57
+ color: var(--mini-snackbar-btn-text, inherit);
58
+ cursor: pointer;
59
+ user-select: none;
60
+ border-radius: var(--mini-snackbar-btn-radius, 1rem);
61
+ transition: opacity 0.2s ease;
62
+ }
63
+
64
+ .mini-snackbar button.mini-snackbar-action:hover {
65
+ opacity: var(--mini-snackbar-btn-hover-opacity, 0.8);
66
+ outline: var(--mini-snackbar-btn-hover-outline, 2px solid var(--mini-snackbar-btn-text, inherit));
67
+ outline-offset: var(--mini-snackbar-btn-outline-offset, 2px);
68
+ background-color: var(--mini-snackbar-btn-hover-bg, transparent);
69
+ }
70
+
71
+ .mini-snackbar button.mini-snackbar-action:focus {
72
+ outline: var(--mini-snackbar-btn-focus-outline, 2px solid var(--mini-snackbar-btn-text, inherit));
73
+ outline-offset: var(--mini-snackbar-btn-outline-offset, 2px);
74
+ }
75
+
76
+ @media (max-width: 600px) {
77
+ .mini-snackbar {
78
+ bottom: 90px;
79
+ }
80
+ }
81
+
82
+ @media (prefers-reduced-motion: reduce) {
83
+ .mini-snackbar {
84
+ transition: opacity 0.15s ease;
85
+ }
86
+ }
87
+ `;
381
88
  }
89
+ function installSnackbarStyles(document, transitionDuration) {
90
+ const existingStyles = document.getElementById(STYLES_ID);
91
+ if (existingStyles)
92
+ return;
93
+ const styles = document.createElement('style');
94
+ styles.id = STYLES_ID;
95
+ styles.textContent = createSnackbarStyles(transitionDuration);
96
+ document.head.appendChild(styles);
97
+ }
98
+ function removeSnackbarStyles(document) {
99
+ document.getElementById(STYLES_ID)?.remove();
100
+ }
101
+
102
+ class SnackbarRenderer {
103
+ constructor(document) {
104
+ this.actionButton = null;
105
+ this.actionHandler = null;
106
+ this.document = document;
107
+ }
108
+ ensureRoot(transitionDuration) {
109
+ if (!this.document.body)
110
+ return false;
111
+ installSnackbarStyles(this.document, transitionDuration);
112
+ if (this.getRoot())
113
+ return true;
114
+ const snackbar = this.document.createElement('div');
115
+ snackbar.id = SNACKBAR_ID;
116
+ snackbar.className = SNACKBAR_CLASS;
117
+ snackbar.setAttribute('role', 'alert');
118
+ snackbar.setAttribute('aria-live', 'assertive');
119
+ snackbar.setAttribute('aria-atomic', 'true');
120
+ const text = this.document.createElement('span');
121
+ text.className = SNACKBAR_TEXT_CLASS;
122
+ snackbar.appendChild(text);
123
+ this.document.body.appendChild(snackbar);
124
+ return true;
125
+ }
126
+ destroy() {
127
+ this.cleanupAction();
128
+ this.getRoot()?.remove();
129
+ removeSnackbarStyles(this.document);
130
+ }
131
+ setMessage(message) {
132
+ const text = this.getRoot()?.querySelector(`.${SNACKBAR_TEXT_CLASS}`);
133
+ if (text)
134
+ text.textContent = message;
135
+ }
136
+ setAction(action, onClick) {
137
+ this.cleanupAction();
138
+ if (!action)
139
+ return;
140
+ const actionButton = this.createActionButton();
141
+ actionButton.classList.add(SNACKBAR_ACTION_CLASS);
142
+ actionButton.textContent = action.text;
143
+ this.actionHandler = onClick;
144
+ actionButton.addEventListener('click', this.actionHandler);
145
+ this.actionButton = actionButton;
146
+ this.getRoot()?.appendChild(actionButton);
147
+ }
148
+ show() {
149
+ this.getRoot()?.classList.add(SNACKBAR_VISIBLE_CLASS);
150
+ }
151
+ hide() {
152
+ this.getRoot()?.classList.remove(SNACKBAR_VISIBLE_CLASS);
153
+ }
154
+ cleanupAction() {
155
+ if (this.actionButton && this.actionHandler) {
156
+ this.actionButton.removeEventListener('click', this.actionHandler);
157
+ }
158
+ this.actionButton?.remove();
159
+ this.actionButton = null;
160
+ this.actionHandler = null;
161
+ }
162
+ getTransitionDuration(fallback) {
163
+ const snackbar = this.getRoot();
164
+ const view = this.document.defaultView;
165
+ if (!snackbar || !view)
166
+ return fallback;
167
+ try {
168
+ const duration = view.getComputedStyle(snackbar).transitionDuration;
169
+ return parseTransitionDuration(duration, fallback);
170
+ }
171
+ catch (error) {
172
+ console.warn('Snackbar: Could not read transition duration from CSS', error);
173
+ return fallback;
174
+ }
175
+ }
176
+ getRoot() {
177
+ return this.document.getElementById(SNACKBAR_ID);
178
+ }
179
+ createActionButton() {
180
+ const registry = this.document.defaultView?.customElements;
181
+ if (registry?.get('md-text-button')) {
182
+ return this.document.createElement('md-text-button');
183
+ }
184
+ const button = this.document.createElement('button');
185
+ button.type = 'button';
186
+ return button;
187
+ }
188
+ }
189
+ function parseTransitionDuration(duration, fallback) {
190
+ if (!duration || duration === '0s')
191
+ return fallback;
192
+ const firstDuration = duration.split(',')[0]?.trim();
193
+ if (!firstDuration)
194
+ return fallback;
195
+ const value = Number.parseFloat(firstDuration);
196
+ if (!Number.isFinite(value))
197
+ return fallback;
198
+ return firstDuration.endsWith('ms') ? value : value * 1000;
199
+ }
200
+
201
+ const isValidDuration = (duration) => typeof duration === 'number' && Number.isFinite(duration) && duration > 0;
202
+ function normalizeItem(message, action = null, duration = DEFAULT_DURATION, warn = console.warn) {
203
+ if (typeof message !== 'string' || message.trim() === '') {
204
+ warn('Snackbar: Message must be a non-empty string');
205
+ return null;
206
+ }
207
+ if (action !== null &&
208
+ (typeof action !== 'object' ||
209
+ typeof action.text !== 'string' ||
210
+ typeof action.handler !== 'function')) {
211
+ warn('Snackbar: Action must be an object with "text" (string) and "handler" (function) properties');
212
+ return null;
213
+ }
214
+ if (!isValidDuration(duration)) {
215
+ warn('Snackbar: Duration must be a positive number');
216
+ return null;
217
+ }
218
+ return {
219
+ message,
220
+ action: action,
221
+ duration
222
+ };
223
+ }
224
+ function normalizeTransitionDuration(duration, warn = console.warn) {
225
+ if (duration === undefined)
226
+ return null;
227
+ if (typeof duration !== 'number' || !Number.isFinite(duration) || duration < 0) {
228
+ warn('Snackbar: transitionDuration must be a non-negative number');
229
+ return null;
230
+ }
231
+ return duration;
232
+ }
233
+
234
+ class SnackbarController {
235
+ constructor() {
236
+ this.queue = [];
237
+ this.state = 'idle';
238
+ this.renderer = null;
239
+ this.displayTimer = null;
240
+ this.transitionTimer = null;
241
+ this.queueGapTimer = null;
242
+ this.transitionDuration = DEFAULT_TRANSITION_DURATION;
243
+ this.initialized = false;
244
+ }
245
+ init(options = {}) {
246
+ if (this.initialized)
247
+ return;
248
+ if (typeof document === 'undefined') {
249
+ console.error('Snackbar: DOM is not available');
250
+ return;
251
+ }
252
+ const transitionDuration = normalizeTransitionDuration(options.transitionDuration);
253
+ if (transitionDuration !== null)
254
+ this.transitionDuration = transitionDuration;
255
+ this.renderer = new SnackbarRenderer(document);
256
+ if (!this.renderer.ensureRoot(this.transitionDuration)) {
257
+ console.error('Snackbar: DOM is not available');
258
+ this.renderer = null;
259
+ return;
260
+ }
261
+ this.initialized = true;
262
+ }
263
+ destroy() {
264
+ this.clearTimers();
265
+ this.clearQueue();
266
+ this.renderer?.destroy();
267
+ this.renderer = null;
268
+ this.state = 'idle';
269
+ this.transitionDuration = DEFAULT_TRANSITION_DURATION;
270
+ this.initialized = false;
271
+ }
272
+ add(message, action = null, duration) {
273
+ if (!this.ensureInitialized())
274
+ return;
275
+ const item = normalizeItem(message, action, duration);
276
+ if (!item)
277
+ return;
278
+ this.queue.push(item);
279
+ if (this.state === 'idle')
280
+ this.showNext();
281
+ }
282
+ show(message, action = null, duration) {
283
+ if (!this.ensureInitialized())
284
+ return;
285
+ const item = normalizeItem(message, action, duration);
286
+ if (!item)
287
+ return;
288
+ this.clearQueue();
289
+ if (this.state === 'idle') {
290
+ this.showItem(item);
291
+ return;
292
+ }
293
+ if (this.state === 'transitioning') {
294
+ this.clearTimer('transitionTimer');
295
+ this.state = 'idle';
296
+ this.showItem(item);
297
+ return;
298
+ }
299
+ this.hideActiveItem(() => this.showItem(item));
300
+ }
301
+ clearQueue() {
302
+ this.queue = [];
303
+ this.clearTimer('queueGapTimer');
304
+ }
305
+ hideCurrent() {
306
+ if (this.state === 'showing') {
307
+ this.hideActiveItem(() => this.finishCurrentItem());
308
+ }
309
+ }
310
+ isInitialized() {
311
+ return this.initialized;
312
+ }
313
+ getTransitionDuration() {
314
+ return this.renderer?.getTransitionDuration(this.transitionDuration) ?? this.transitionDuration;
315
+ }
316
+ showNext() {
317
+ if (this.state !== 'idle')
318
+ return;
319
+ const item = this.queue.shift();
320
+ if (!item)
321
+ return;
322
+ this.showItem(item);
323
+ }
324
+ showItem(item) {
325
+ if (!this.renderer)
326
+ return;
327
+ this.clearTimers();
328
+ this.state = 'showing';
329
+ this.renderer.setMessage(item.message);
330
+ this.renderer.setAction(item.action, () => {
331
+ try {
332
+ item.action?.handler();
333
+ }
334
+ finally {
335
+ this.hideActiveItem(() => this.finishCurrentItem());
336
+ }
337
+ });
338
+ this.renderer.show();
339
+ this.displayTimer = setTimeout(() => {
340
+ this.hideActiveItem(() => this.finishCurrentItem());
341
+ }, item.duration);
342
+ }
343
+ hideActiveItem(afterHidden) {
344
+ if (!this.renderer || this.state === 'transitioning')
345
+ return;
346
+ this.clearTimer('displayTimer');
347
+ this.state = 'transitioning';
348
+ this.renderer.hide();
349
+ this.renderer.cleanupAction();
350
+ this.transitionTimer = setTimeout(() => {
351
+ this.transitionTimer = null;
352
+ this.state = 'idle';
353
+ afterHidden?.();
354
+ }, this.getTransitionDuration());
355
+ }
356
+ finishCurrentItem() {
357
+ if (this.state !== 'idle')
358
+ return;
359
+ if (this.queue.length === 0)
360
+ return;
361
+ this.clearTimer('queueGapTimer');
362
+ this.queueGapTimer = setTimeout(() => {
363
+ this.queueGapTimer = null;
364
+ this.showNext();
365
+ }, QUEUE_GAP_DURATION);
366
+ }
367
+ ensureInitialized() {
368
+ if (this.initialized)
369
+ return true;
370
+ console.warn('Snackbar: Not initialized. Call Snackbar.init() first.');
371
+ return false;
372
+ }
373
+ clearTimers() {
374
+ this.clearTimer('displayTimer');
375
+ this.clearTimer('transitionTimer');
376
+ this.clearTimer('queueGapTimer');
377
+ }
378
+ clearTimer(timer) {
379
+ const timeout = this[timer];
380
+ if (timeout)
381
+ clearTimeout(timeout);
382
+ this[timer] = null;
383
+ }
384
+ }
385
+
386
+ /**
387
+ * MiniSnackbar - A simple vanilla JavaScript snackbar/toast library
388
+ *
389
+ * @version 3.0.0
390
+ * @author Shanto Islam <shantoislamdev@gmail.com>
391
+ * @license MIT
392
+ * @description A lightweight, zero-dependency snackbar library with Material Design integration
393
+ * @repository https://github.com/shantoislamdev/minisnackbar
394
+ * @homepage https://github.com/shantoislamdev/minisnackbar#readme
395
+ */
396
+ const controller = new SnackbarController();
397
+ class Snackbar {
398
+ static init(options = {}) {
399
+ controller.init(options);
400
+ }
401
+ static destroy() {
402
+ controller.destroy();
403
+ }
404
+ static getTransitionDuration() {
405
+ return controller.getTransitionDuration();
406
+ }
407
+ static add(message, action = null, duration) {
408
+ controller.add(message, action, duration);
409
+ }
410
+ static show(message, action = null, duration) {
411
+ controller.show(message, action, duration);
412
+ }
413
+ static clearQueue() {
414
+ controller.clearQueue();
415
+ }
416
+ static hideCurrent() {
417
+ controller.hideCurrent();
418
+ }
419
+ static isInitialized() {
420
+ return controller.isInitialized();
421
+ }
422
+ }
423
+
424
+ export { Snackbar, Snackbar as default };
382
425
  //# sourceMappingURL=minisnackbar.esm.js.map