/** * LiveComponent UI Helper * * Provides unified API for UI components (Dialogs, Modals, Notifications) * integrated with LiveComponents. */ import { UIManager } from '../ui/UIManager.js'; import { ToastQueue } from './ToastQueue.js'; import { ModalManager } from './ModalManager.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 this.toastQueue = new ToastQueue(5); // Max 5 toasts // Use ModalManager with native element // Native provides automatic backdrop, focus management, and ESC handling this.modalManager = new ModalManager(); } /** * 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; // Use ModalManager for better stack management const modal = this.modalManager.open(componentId, { title: title, content: content, size: size, buttons: buttons, closeOnBackdrop: closeOnBackdrop, closeOnEscape: closeOnEscape }); // Store reference this.activeDialogs.set(componentId, modal); // Setup onClose callback if (onClose && modal.dialog) { const originalClose = modal.close; modal.close = () => { originalClose.call(modal); onClose(); this.activeDialogs.delete(componentId); }; } 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) { // Use ModalManager to close this.modalManager.close(componentId); 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; // Use ToastQueue for better management const toast = this.toastQueue.add(componentId, { message: message, type: type, duration: duration, position: position }); // Store reference for compatibility if (toast) { this.activeNotifications.set(componentId, toast); } return toast; } /** * Hide notification * * @param {HTMLElement|string} notificationOrComponentId - Notification element or component ID */ hideNotification(notificationOrComponentId) { const componentId = typeof notificationOrComponentId === 'string' ? notificationOrComponentId : notificationOrComponentId.dataset?.componentId; if (!componentId) { return; } // Use ToastQueue to remove this.toastQueue.remove(componentId); this.activeNotifications.delete(componentId); } /** * 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); } }