Files
michaelschiemer/resources/js/modules/livecomponent/LiveComponentUIHelper.js
2025-11-24 21:28:25 +01:00

287 lines
8.2 KiB
JavaScript

/**
* 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 <dialog> element
// Native <dialog> 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<boolean>} 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: `<p>${message}</p>`,
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: `<p class="alert-message alert-message--${type}">${message}</p>`,
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 `<button class="btn ${btnClass}" data-dialog-action="${index}">${btn.text || 'Button'}</button>`;
}).join('');
return `
<div class="livecomponent-dialog-content">
${title ? `<div class="dialog-header"><h3>${title}</h3></div>` : ''}
<div class="dialog-body">${content}</div>
${buttons.length > 0 ? `<div class="dialog-footer">${buttonsHtml}</div>` : ''}
</div>
`;
}
/**
* 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: `
<div class="loading-dialog">
<div class="spinner"></div>
<p>${message}</p>
</div>
`,
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);
}
}