/**
* LEDMatrix Notification Widget
*
* Global notification/toast system for displaying messages to users.
* Consolidates all notification functionality into a single widget.
*
* Usage:
* window.showNotification('Message here', 'success');
* window.showNotification('Error occurred', 'error');
* window.LEDMatrixWidgets.get('notification').show('Custom message', { type: 'warning', duration: 5000 });
*
* Types: success, error, warning, info (default)
*
* @module NotificationWidget
*/
(function() {
'use strict';
// Ensure LEDMatrixWidgets registry exists
if (typeof window.LEDMatrixWidgets === 'undefined') {
console.error('[NotificationWidget] LEDMatrixWidgets registry not found. Load registry.js first.');
return;
}
// Configuration
const CONFIG = {
containerId: 'notifications',
defaultDuration: 4000,
fadeOutDuration: 300,
maxNotifications: 5,
position: 'top-right' // top-right, top-left, bottom-right, bottom-left
};
// Type-specific styling
const TYPE_STYLES = {
success: {
bg: 'bg-green-500',
icon: 'fa-check-circle',
label: 'Success'
},
error: {
bg: 'bg-red-500',
icon: 'fa-exclamation-circle',
label: 'Error'
},
warning: {
bg: 'bg-yellow-500',
icon: 'fa-exclamation-triangle',
label: 'Warning'
},
info: {
bg: 'bg-blue-500',
icon: 'fa-info-circle',
label: 'Info'
}
};
// Track active notifications
let activeNotifications = [];
let notificationCounter = 0;
/**
* Get or create the notifications container
* @returns {HTMLElement} Container element
*/
function getContainer() {
let container = document.getElementById(CONFIG.containerId);
if (!container) {
container = document.createElement('div');
container.id = CONFIG.containerId;
container.className = 'fixed top-4 right-4 z-50 space-y-2 pointer-events-none';
container.setAttribute('aria-live', 'polite');
container.setAttribute('aria-label', 'Notifications');
document.body.appendChild(container);
}
return container;
}
/**
* Escape HTML to prevent XSS
* @param {string} text - Text to escape
* @returns {string} Escaped text
*/
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = String(text);
return div.innerHTML;
}
/**
* Remove a notification by ID
* @param {string} notificationId - Notification ID
* @param {boolean} immediate - Skip fade animation
*/
function removeNotification(notificationId, immediate = false) {
const notification = document.getElementById(notificationId);
if (!notification) return;
if (immediate) {
notification.remove();
} else {
notification.style.transition = `opacity ${CONFIG.fadeOutDuration}ms, transform ${CONFIG.fadeOutDuration}ms`;
notification.style.opacity = '0';
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
notification.remove();
}, CONFIG.fadeOutDuration);
}
// Remove from tracking array
activeNotifications = activeNotifications.filter(id => id !== notificationId);
}
/**
* Show a notification
* @param {string} message - Message to display
* @param {Object|string} options - Options object or type string
* @returns {string} Notification ID (for manual dismissal)
*/
function showNotification(message, options = {}) {
// Handle legacy call signature: showNotification(message, type)
if (typeof options === 'string') {
options = { type: options };
}
const type = options.type || 'info';
const duration = options.duration !== undefined ? options.duration : CONFIG.defaultDuration;
const showIcon = options.showIcon !== false;
const dismissible = options.dismissible !== false;
const style = TYPE_STYLES[type] || TYPE_STYLES.info;
const container = getContainer();
const notificationId = `notification_${++notificationCounter}`;
// Enforce max notifications limit
while (activeNotifications.length >= CONFIG.maxNotifications) {
removeNotification(activeNotifications[0], true);
}
// Create notification element
const notification = document.createElement('div');
notification.id = notificationId;
notification.className = `${style.bg} text-white px-4 py-3 rounded-lg shadow-lg flex items-center gap-3 pointer-events-auto transform transition-all duration-300 ease-out`;
notification.style.opacity = '0';
notification.style.transform = 'translateX(100%)';
notification.setAttribute('role', 'alert');
// Build content
let html = '';
if (showIcon) {
html += ``;
}
html += `${escapeHtml(message)}`;
if (dismissible) {
html += `
`;
}
notification.innerHTML = html;
container.appendChild(notification);
activeNotifications.push(notificationId);
// Trigger animation (need to wait for DOM update)
requestAnimationFrame(() => {
notification.style.opacity = '1';
notification.style.transform = 'translateX(0)';
});
// Auto-dismiss (0 = no auto-dismiss)
if (duration > 0) {
setTimeout(() => {
removeNotification(notificationId);
}, duration);
}
// Log for debugging
console.log(`[${type.toUpperCase()}]`, message);
return notificationId;
}
/**
* Clear all active notifications
*/
function clearAll() {
const ids = [...activeNotifications];
ids.forEach(id => removeNotification(id, true));
}
// Register the widget
window.LEDMatrixWidgets.register('notification', {
name: 'Notification Widget',
version: '1.0.0',
/**
* Show a notification
* @param {string} message - Message to display
* @param {Object} options - Configuration options
* @param {string} options.type - Notification type: success, error, warning, info
* @param {number} options.duration - Auto-dismiss duration in ms (0 = no auto-dismiss)
* @param {boolean} options.showIcon - Show type icon (default: true)
* @param {boolean} options.dismissible - Show dismiss button (default: true)
* @returns {string} Notification ID
*/
show: showNotification,
/**
* Dismiss a specific notification
* @param {string} notificationId - Notification ID to dismiss
*/
dismiss: function(notificationId) {
removeNotification(notificationId);
},
/**
* Clear all notifications
*/
clearAll: clearAll,
/**
* Get active notification count
* @returns {number} Number of active notifications
*/
getActiveCount: function() {
return activeNotifications.length;
},
// Widget interface methods (for consistency with other widgets)
render: function() {
// Notification widget doesn't render into a container
// It manages its own container
getContainer();
},
getValue: function() {
return activeNotifications.length;
},
setValue: function() {
// No-op for notification widget
},
handlers: {
dismiss: function(notificationId) {
removeNotification(notificationId);
}
}
});
// Global shorthand function (backwards compatible with existing code)
window.showNotification = function(message, type = 'info') {
return showNotification(message, { type: type });
};
// Initialize container on load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', getContainer);
} else {
getContainer();
}
console.log('[NotificationWidget] Notification widget registered');
})();