/** * LiveComponent UI Helper * * Provides unified API for UI components (Dialogs, Modals, Notifications) * integrated with LiveComponents. */ import { UIManager } from '../ui/UIManager.js'; export class LiveComponentUIHelper { constructor(liveComponentManager) { this.manager = liveComponentManager; this.uiManager = UIManager; this.activeDialogs = new Map(); // componentId → dialog instances this.activeNotifications = new Map(); // componentId → notification instances } /** * Show dialog from LiveComponent action * * @param {string} componentId - Component ID * @param {Object} options - Dialog options * @returns {Object} Dialog instance */ showDialog(componentId, options = {}) { const { title = '', content = '', size = 'medium', buttons = [], closeOnBackdrop = true, closeOnEscape = true, onClose = null, onConfirm = null } = options; // Create dialog content const dialogContent = this.createDialogContent(title, content, buttons, { onClose, onConfirm }); // Show modal via UIManager const modal = this.uiManager.open('modal', { content: dialogContent, className: `livecomponent-dialog livecomponent-dialog--${size}`, onClose: () => { if (onClose) { onClose(); } this.activeDialogs.delete(componentId); } }); // Store reference this.activeDialogs.set(componentId, modal); return modal; } /** * Show confirmation dialog * * @param {string} componentId - Component ID * @param {Object} options - Confirmation options * @returns {Promise} Promise resolving to true if confirmed */ showConfirm(componentId, options = {}) { return new Promise((resolve) => { const { title = 'Confirm', message = 'Are you sure?', confirmText = 'Confirm', cancelText = 'Cancel', confirmClass = 'btn-primary', cancelClass = 'btn-secondary' } = options; const buttons = [ { text: cancelText, class: cancelClass, action: () => { this.closeDialog(componentId); resolve(false); } }, { text: confirmText, class: confirmClass, action: () => { this.closeDialog(componentId); resolve(true); } } ]; this.showDialog(componentId, { title, content: `

${message}

`, buttons, size: 'small' }); }); } /** * Show alert dialog * * @param {string} componentId - Component ID * @param {Object} options - Alert options */ showAlert(componentId, options = {}) { const { title = 'Alert', message = '', buttonText = 'OK', type = 'info' // info, success, warning, error } = options; const buttons = [ { text: buttonText, class: `btn-${type}`, action: () => { this.closeDialog(componentId); } } ]; return this.showDialog(componentId, { title, content: `

${message}

`, buttons, size: 'small' }); } /** * Close dialog * * @param {string} componentId - Component ID */ closeDialog(componentId) { const dialog = this.activeDialogs.get(componentId); if (dialog && typeof dialog.close === 'function') { dialog.close(); this.activeDialogs.delete(componentId); } } /** * Show notification * * @param {string} componentId - Component ID * @param {Object} options - Notification options */ showNotification(componentId, options = {}) { const { message = '', type = 'info', // info, success, warning, error duration = 5000, position = 'top-right', action = null } = options; // Create notification element const notification = document.createElement('div'); notification.className = `livecomponent-notification livecomponent-notification--${type} livecomponent-notification--${position}`; notification.setAttribute('role', 'alert'); notification.setAttribute('aria-live', 'polite'); notification.innerHTML = `
${message} ${action ? `` : ''}
`; // Add styles notification.style.cssText = ` position: fixed; ${position.includes('top') ? 'top' : 'bottom'}: 1rem; ${position.includes('left') ? 'left' : 'right'}: 1rem; max-width: 400px; padding: 1rem; background: ${this.getNotificationColor(type)}; color: white; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); z-index: 10000; opacity: 0; transform: translateY(${position.includes('top') ? '-20px' : '20px'}); transition: opacity 0.3s ease, transform 0.3s ease; `; // Add to DOM document.body.appendChild(notification); // Animate in requestAnimationFrame(() => { notification.style.opacity = '1'; notification.style.transform = 'translateY(0)'; }); // Setup close button const closeBtn = notification.querySelector('.notification-close'); closeBtn.addEventListener('click', () => { this.hideNotification(notification); }); // Setup action button if (action) { const actionBtn = notification.querySelector('.notification-action'); actionBtn.addEventListener('click', () => { if (action.handler) { action.handler(); } this.hideNotification(notification); }); } // Auto-hide after duration if (duration > 0) { setTimeout(() => { this.hideNotification(notification); }, duration); } // Store reference this.activeNotifications.set(componentId, notification); return notification; } /** * Hide notification * * @param {HTMLElement|string} notificationOrComponentId - Notification element or component ID */ hideNotification(notificationOrComponentId) { const notification = typeof notificationOrComponentId === 'string' ? this.activeNotifications.get(notificationOrComponentId) : notificationOrComponentId; if (!notification) { return; } // Animate out notification.style.opacity = '0'; notification.style.transform = `translateY(${notification.classList.contains('livecomponent-notification--top') ? '-20px' : '20px'})`; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } // Remove from map for (const [componentId, notif] of this.activeNotifications.entries()) { if (notif === notification) { this.activeNotifications.delete(componentId); break; } } }, 300); } /** * Create dialog content HTML * * @param {string} title - Dialog title * @param {string} content - Dialog content * @param {Array} buttons - Button configurations * @param {Object} callbacks - Callback functions * @returns {string} HTML content */ createDialogContent(title, content, buttons, callbacks) { const buttonsHtml = buttons.map((btn, index) => { const btnClass = btn.class || 'btn-secondary'; const btnAction = btn.action || (() => {}); return ``; }).join(''); return `
${title ? `

${title}

` : ''}
${content}
${buttons.length > 0 ? `` : ''}
`; } /** * Get notification color by type * * @param {string} type - Notification type * @returns {string} Color value */ getNotificationColor(type) { const colors = { info: '#3b82f6', success: '#10b981', warning: '#f59e0b', error: '#ef4444' }; return colors[type] || colors.info; } /** * Show loading dialog * * @param {string} componentId - Component ID * @param {string} message - Loading message * @returns {Object} Dialog instance */ showLoadingDialog(componentId, message = 'Loading...') { return this.showDialog(componentId, { title: '', content: `

${message}

`, size: 'small', buttons: [], closeOnBackdrop: false, closeOnEscape: false }); } /** * Cleanup all UI components for component * * @param {string} componentId - Component ID */ cleanup(componentId) { // Close dialogs this.closeDialog(componentId); // Hide notifications this.hideNotification(componentId); } }