UNPKG

react-native-turbo-toast

Version:

High-performance toast notifications for React Native with TurboModules

297 lines (285 loc) 8.39 kB
"use strict"; export class ToastQueue { queue = []; activeToasts = new Map(); groupToasts = new Map(); isProcessing = false; constructor(config = {}) { this.maxConcurrent = config.maxConcurrent || 1; this.maxQueueSize = config.maxQueueSize || 50; this.groupDeduplication = config.groupDeduplication || false; this.queueTimeout = config.queueTimeout || 30000; // 30 seconds this.eventHandler = config.onQueueEvent; // Start cleanup timer this.startCleanupTimer(); } getQueue() { return [...this.queue]; } enqueue(toast) { // Check queue size limit if (this.queue.length >= this.maxQueueSize) { // Remove oldest low-priority toast const lowestPriorityIndex = this.findLowestPriorityIndex(); if (lowestPriorityIndex !== -1) { const removed = this.queue.splice(lowestPriorityIndex, 1)[0]; this.removeFromGroup(removed); this.emitEvent('removed', removed); } else { return false; // Queue full with high priority items } } // Check for group deduplication if (this.groupDeduplication && toast.group) { const existing = this.findInGroup(toast.group, toast.message); if (existing) { // Update existing toast instead of adding new one this.updateToast(existing.id, { ...toast, id: existing.id }); return true; } } // Check for message deduplication if (toast.preventDuplicate) { const duplicate = this.findDuplicate(toast.message); if (duplicate) { return false; } } // Set expiration time toast.expiresAt = Date.now() + this.queueTimeout; toast.priority = toast.priority || 0; // Insert by priority const insertIndex = this.findInsertIndex(toast.priority); this.queue.splice(insertIndex, 0, toast); // Update queue positions this.updateQueuePositions(); // Add to group tracking if (toast.group) { this.addToGroup(toast); } this.emitEvent('added', toast); return true; } dequeue() { if (this.activeToasts.size >= this.maxConcurrent) { return undefined; } return this.queue.shift(); } addActive(toast) { this.activeToasts.set(toast.id, toast); if (toast.group) { this.addToGroup(toast); } } removeActive(id) { const toast = this.activeToasts.get(id); if (toast) { this.activeToasts.delete(id); this.removeFromGroup(toast); this.emitEvent('removed', toast); } return toast; } getActive(id) { return this.activeToasts.get(id); } getAllActive() { return Array.from(this.activeToasts.values()); } findDuplicate(message) { // Check active toasts const activeMatch = Array.from(this.activeToasts.values()).find(t => t.message === message); if (activeMatch) return activeMatch; // Check queued toasts return this.queue.find(t => t.message === message); } findInGroup(group, message) { const groupToastIds = this.groupToasts.get(group); if (!groupToastIds) return undefined; for (const id of groupToastIds) { const toast = this.activeToasts.get(id) || this.queue.find(t => t.id === id); if (toast && (!message || toast.message === message)) { return toast; } } return undefined; } findByGroup(group) { const groupToastIds = this.groupToasts.get(group); if (!groupToastIds) return []; const results = []; for (const id of groupToastIds) { const toast = this.activeToasts.get(id) || this.queue.find(t => t.id === id); if (toast) { results.push(toast); } } return results; } clearGroup(group) { const toasts = this.findByGroup(group); toasts.forEach(toast => { this.removeActive(toast.id); this.queue = this.queue.filter(t => t.id !== toast.id); }); this.groupToasts.delete(group); this.updateQueuePositions(); this.emitEvent('cleared'); return toasts; } clear() { this.queue = []; this.activeToasts.clear(); this.groupToasts.clear(); this.emitEvent('cleared'); } hasCapacity() { return this.activeToasts.size < this.maxConcurrent; } get size() { return this.queue.length; } get activeSize() { return this.activeToasts.size; } setProcessing(value) { this.isProcessing = value; } get processing() { return this.isProcessing; } getStats() { const stats = { total: this.queue.length + this.activeToasts.size, active: this.activeToasts.size, pending: this.queue.length, byPriority: {}, byGroup: {} }; // Calculate priority distribution const allToasts = [...this.queue, ...Array.from(this.activeToasts.values())]; allToasts.forEach(toast => { const priority = toast.priority; stats.byPriority[priority] = (stats.byPriority[priority] || 0) + 1; if (toast.group) { stats.byGroup[toast.group] = (stats.byGroup[toast.group] || 0) + 1; } }); // Find oldest and newest timestamps if (allToasts.length > 0) { const timestamps = allToasts.map(t => t.timestamp); stats.oldestTimestamp = Math.min(...timestamps); stats.newestTimestamp = Math.max(...timestamps); } return stats; } updateToast(id, updates) { const activeToast = this.activeToasts.get(id); if (activeToast) { Object.assign(activeToast, updates); this.emitEvent('updated', activeToast); return true; } const queueIndex = this.queue.findIndex(t => t.id === id); if (queueIndex !== -1) { Object.assign(this.queue[queueIndex], updates); // Re-sort if priority changed if (updates.priority !== undefined) { const toast = this.queue.splice(queueIndex, 1)[0]; const insertIndex = this.findInsertIndex(toast.priority); this.queue.splice(insertIndex, 0, toast); this.updateQueuePositions(); } this.emitEvent('updated', this.queue[queueIndex]); return true; } return false; } findInsertIndex(priority) { const index = this.queue.findIndex(t => t.priority < priority); return index === -1 ? this.queue.length : index; } findLowestPriorityIndex() { if (this.queue.length === 0) return -1; let lowestPriority = this.queue[0].priority; let lowestIndex = 0; for (let i = 1; i < this.queue.length; i++) { if (this.queue[i].priority < lowestPriority) { lowestPriority = this.queue[i].priority; lowestIndex = i; } } return lowestIndex; } updateQueuePositions() { this.queue.forEach((toast, index) => { toast.queuePosition = index; }); } addToGroup(toast) { if (!toast.group) return; if (!this.groupToasts.has(toast.group)) { this.groupToasts.set(toast.group, new Set()); } this.groupToasts.get(toast.group)?.add(toast.id); } removeFromGroup(toast) { if (!toast.group) return; const groupSet = this.groupToasts.get(toast.group); if (groupSet) { groupSet.delete(toast.id); if (groupSet.size === 0) { this.groupToasts.delete(toast.group); } } } emitEvent(type, toast) { if (!this.eventHandler) return; const event = { type, toast, stats: this.getStats(), timestamp: Date.now() }; try { this.eventHandler(event); } catch { // Silently handle event handler errors } } startCleanupTimer() { this.cleanupTimer = setInterval(() => { this.cleanupExpiredToasts(); }, 5000); // Check every 5 seconds } cleanupExpiredToasts() { const now = Date.now(); const expiredIndexes = []; this.queue.forEach((toast, index) => { if (toast.expiresAt && toast.expiresAt < now) { expiredIndexes.push(index); } }); // Remove expired toasts (reverse order to maintain indexes) expiredIndexes.reverse().forEach(index => { const expired = this.queue.splice(index, 1)[0]; this.removeFromGroup(expired); this.emitEvent('removed', expired); }); if (expiredIndexes.length > 0) { this.updateQueuePositions(); } } destroy() { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = undefined; } this.clear(); } } //# sourceMappingURL=queue.js.map