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