Some checks failed
Deploy Application / deploy (push) Has been cancelled
530 lines
17 KiB
JavaScript
530 lines
17 KiB
JavaScript
/**
|
|
* UI Event Handler for LiveComponents
|
|
*
|
|
* Listens to UI-related events from LiveComponents and automatically
|
|
* displays Toasts, Modals, and other UI components.
|
|
*
|
|
* Supported Events:
|
|
* - toast:show - Show toast notification
|
|
* - toast:hide - Hide toast notification
|
|
* - modal:show - Show modal dialog
|
|
* - modal:close - Close modal dialog
|
|
* - modal:confirm - Show confirmation dialog
|
|
* - modal:alert - Show alert dialog
|
|
*/
|
|
|
|
import { LiveComponentUIHelper } from './LiveComponentUIHelper.js';
|
|
|
|
export class UIEventHandler {
|
|
constructor(liveComponentManager) {
|
|
this.manager = liveComponentManager;
|
|
this.uiHelper = liveComponentManager.uiHelper || new LiveComponentUIHelper(liveComponentManager);
|
|
this.eventListeners = new Map();
|
|
this.isInitialized = false;
|
|
this.drawerManager = null; // Lazy-loaded when needed
|
|
this.popoverManager = null; // Lazy-loaded when needed
|
|
}
|
|
|
|
/**
|
|
* Initialize event listeners
|
|
*/
|
|
init() {
|
|
if (this.isInitialized) {
|
|
return;
|
|
}
|
|
|
|
// Toast events
|
|
this.addEventListener('toast:show', (e) => this.handleToastShow(e));
|
|
this.addEventListener('toast:hide', (e) => this.handleToastHide(e));
|
|
|
|
// Modal events
|
|
this.addEventListener('modal:show', (e) => this.handleModalShow(e));
|
|
this.addEventListener('modal:close', (e) => this.handleModalClose(e));
|
|
this.addEventListener('modal:confirm', (e) => this.handleModalConfirm(e));
|
|
this.addEventListener('modal:alert', (e) => this.handleModalAlert(e));
|
|
|
|
// Also listen to livecomponent: prefixed events for compatibility
|
|
this.addEventListener('livecomponent:toast:show', (e) => this.handleToastShow(e));
|
|
this.addEventListener('livecomponent:toast:hide', (e) => this.handleToastHide(e));
|
|
this.addEventListener('livecomponent:modal:show', (e) => this.handleModalShow(e));
|
|
this.addEventListener('livecomponent:modal:close', (e) => this.handleModalClose(e));
|
|
this.addEventListener('livecomponent:modal:confirm', (e) => this.handleModalConfirm(e));
|
|
this.addEventListener('livecomponent:modal:alert', (e) => this.handleModalAlert(e));
|
|
|
|
// Drawer events
|
|
this.addEventListener('drawer:open', (e) => this.handleDrawerOpen(e));
|
|
this.addEventListener('drawer:close', (e) => this.handleDrawerClose(e));
|
|
this.addEventListener('drawer:toggle', (e) => this.handleDrawerToggle(e));
|
|
this.addEventListener('livecomponent:drawer:open', (e) => this.handleDrawerOpen(e));
|
|
this.addEventListener('livecomponent:drawer:close', (e) => this.handleDrawerClose(e));
|
|
this.addEventListener('livecomponent:drawer:toggle', (e) => this.handleDrawerToggle(e));
|
|
|
|
// Popover events
|
|
this.addEventListener('popover:show', (e) => this.handlePopoverShow(e));
|
|
this.addEventListener('popover:hide', (e) => this.handlePopoverHide(e));
|
|
this.addEventListener('popover:toggle', (e) => this.handlePopoverToggle(e));
|
|
this.addEventListener('livecomponent:popover:show', (e) => this.handlePopoverShow(e));
|
|
this.addEventListener('livecomponent:popover:hide', (e) => this.handlePopoverHide(e));
|
|
this.addEventListener('livecomponent:popover:toggle', (e) => this.handlePopoverToggle(e));
|
|
|
|
this.isInitialized = true;
|
|
console.log('[UIEventHandler] Initialized');
|
|
}
|
|
|
|
/**
|
|
* Add event listener
|
|
* @param {string} eventName - Event name
|
|
* @param {Function} handler - Event handler
|
|
*/
|
|
addEventListener(eventName, handler) {
|
|
const wrappedHandler = (e) => {
|
|
try {
|
|
handler(e);
|
|
} catch (error) {
|
|
console.error(`[UIEventHandler] Error handling event ${eventName}:`, error);
|
|
}
|
|
};
|
|
|
|
document.addEventListener(eventName, wrappedHandler);
|
|
this.eventListeners.set(eventName, wrappedHandler);
|
|
}
|
|
|
|
/**
|
|
* Remove event listener
|
|
* @param {string} eventName - Event name
|
|
*/
|
|
removeEventListener(eventName) {
|
|
const handler = this.eventListeners.get(eventName);
|
|
if (handler) {
|
|
document.removeEventListener(eventName, handler);
|
|
this.eventListeners.delete(eventName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle toast:show event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
handleToastShow(e) {
|
|
const payload = e.detail || {};
|
|
const {
|
|
message = '',
|
|
type = 'info',
|
|
duration = 5000,
|
|
position = 'top-right',
|
|
componentId = 'global'
|
|
} = payload;
|
|
|
|
if (!message) {
|
|
console.warn('[UIEventHandler] toast:show event missing message');
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIEventHandler] Showing toast: ${message} (${type})`);
|
|
|
|
this.uiHelper.showNotification(componentId, {
|
|
message: message,
|
|
type: type,
|
|
duration: duration,
|
|
position: position
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle toast:hide event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
handleToastHide(e) {
|
|
const payload = e.detail || {};
|
|
const { componentId = 'global' } = payload;
|
|
|
|
console.log(`[UIEventHandler] Hiding toast for component: ${componentId}`);
|
|
|
|
this.uiHelper.hideNotification(componentId);
|
|
}
|
|
|
|
/**
|
|
* Handle modal:show event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
handleModalShow(e) {
|
|
const payload = e.detail || {};
|
|
const {
|
|
componentId,
|
|
title = '',
|
|
content = '',
|
|
size = 'medium',
|
|
buttons = [],
|
|
closeOnBackdrop = true,
|
|
closeOnEscape = true,
|
|
onClose = null,
|
|
onConfirm = null
|
|
} = payload;
|
|
|
|
if (!componentId) {
|
|
console.warn('[UIEventHandler] modal:show event missing componentId');
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIEventHandler] Showing modal for component: ${componentId}`);
|
|
|
|
this.uiHelper.showDialog(componentId, {
|
|
title: title,
|
|
content: content,
|
|
size: size,
|
|
buttons: buttons,
|
|
closeOnBackdrop: closeOnBackdrop,
|
|
closeOnEscape: closeOnEscape,
|
|
onClose: onClose,
|
|
onConfirm: onConfirm
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle modal:close event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
handleModalClose(e) {
|
|
const payload = e.detail || {};
|
|
const { componentId } = payload;
|
|
|
|
if (!componentId) {
|
|
console.warn('[UIEventHandler] modal:close event missing componentId');
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIEventHandler] Closing modal for component: ${componentId}`);
|
|
|
|
this.uiHelper.closeDialog(componentId);
|
|
}
|
|
|
|
/**
|
|
* Handle modal:confirm event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
async handleModalConfirm(e) {
|
|
const payload = e.detail || {};
|
|
const {
|
|
componentId,
|
|
title = 'Confirm',
|
|
message = 'Are you sure?',
|
|
confirmText = 'Confirm',
|
|
cancelText = 'Cancel',
|
|
confirmClass = 'btn-primary',
|
|
cancelClass = 'btn-secondary',
|
|
confirmAction = null,
|
|
confirmParams = null,
|
|
onConfirm = null,
|
|
onCancel = null
|
|
} = payload;
|
|
|
|
if (!componentId) {
|
|
console.warn('[UIEventHandler] modal:confirm event missing componentId');
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIEventHandler] Showing confirmation dialog for component: ${componentId}`);
|
|
|
|
try {
|
|
const confirmed = await this.uiHelper.showConfirm(componentId, {
|
|
title: title,
|
|
message: message,
|
|
confirmText: confirmText,
|
|
cancelText: cancelText,
|
|
confirmClass: confirmClass,
|
|
cancelClass: cancelClass
|
|
});
|
|
|
|
// If user confirmed and confirmAction is provided, call the LiveComponent action
|
|
if (confirmed && confirmAction) {
|
|
console.log(`[UIEventHandler] User confirmed, calling action: ${confirmAction}`, confirmParams);
|
|
|
|
// Call LiveComponent action via manager
|
|
if (this.manager && typeof this.manager.callAction === 'function') {
|
|
await this.manager.callAction(componentId, confirmAction, confirmParams || {});
|
|
} else if (this.manager && typeof this.manager.executeAction === 'function') {
|
|
// Fallback to executeAction for backward compatibility
|
|
await this.manager.executeAction(componentId, confirmAction, confirmParams || {});
|
|
} else {
|
|
console.warn('[UIEventHandler] Cannot execute confirmAction: manager.callAction/executeAction not available');
|
|
}
|
|
}
|
|
|
|
// Legacy callback support
|
|
if (confirmed && onConfirm) {
|
|
onConfirm();
|
|
} else if (!confirmed && onCancel) {
|
|
onCancel();
|
|
}
|
|
|
|
// Dispatch result event
|
|
document.dispatchEvent(new CustomEvent('modal:confirm:result', {
|
|
detail: {
|
|
componentId: componentId,
|
|
confirmed: confirmed
|
|
}
|
|
}));
|
|
} catch (error) {
|
|
console.error('[UIEventHandler] Error showing confirmation dialog:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle modal:alert event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
handleModalAlert(e) {
|
|
const payload = e.detail || {};
|
|
const {
|
|
componentId,
|
|
title = 'Alert',
|
|
message = '',
|
|
buttonText = 'OK',
|
|
type = 'info',
|
|
onClose = null
|
|
} = payload;
|
|
|
|
if (!componentId) {
|
|
console.warn('[UIEventHandler] modal:alert event missing componentId');
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIEventHandler] Showing alert dialog for component: ${componentId}`);
|
|
|
|
this.uiHelper.showAlert(componentId, {
|
|
title: title,
|
|
message: message,
|
|
buttonText: buttonText,
|
|
type: type
|
|
});
|
|
|
|
if (onClose) {
|
|
// Note: showAlert doesn't return a promise, so we'll call onClose after a delay
|
|
// This is a limitation - ideally showAlert would return a promise
|
|
setTimeout(() => {
|
|
onClose();
|
|
}, 100);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle drawer:open event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
async handleDrawerOpen(e) {
|
|
const payload = e.detail || {};
|
|
const {
|
|
componentId,
|
|
title = '',
|
|
content = '',
|
|
position = 'left',
|
|
width = '400px',
|
|
showOverlay = true,
|
|
closeOnOverlay = true,
|
|
closeOnEscape = true,
|
|
animation = 'slide'
|
|
} = payload;
|
|
|
|
if (!componentId) {
|
|
console.warn('[UIEventHandler] drawer:open event missing componentId');
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIEventHandler] Opening drawer for component: ${componentId}`);
|
|
|
|
// Initialize DrawerManager if not already done
|
|
if (!this.drawerManager) {
|
|
const { DrawerManager } = await import('./DrawerManager.js');
|
|
this.drawerManager = new DrawerManager();
|
|
}
|
|
|
|
this.drawerManager.open(componentId, {
|
|
title: title,
|
|
content: content,
|
|
position: position,
|
|
width: width,
|
|
showOverlay: showOverlay,
|
|
closeOnOverlay: closeOnOverlay,
|
|
closeOnEscape: closeOnEscape,
|
|
animation: animation
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle drawer:close event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
handleDrawerClose(e) {
|
|
const payload = e.detail || {};
|
|
const { componentId } = payload;
|
|
|
|
if (!componentId) {
|
|
console.warn('[UIEventHandler] drawer:close event missing componentId');
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIEventHandler] Closing drawer for component: ${componentId}`);
|
|
|
|
if (this.drawerManager) {
|
|
this.drawerManager.close(componentId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle drawer:toggle event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
handleDrawerToggle(e) {
|
|
const payload = e.detail || {};
|
|
const { componentId } = payload;
|
|
|
|
if (!componentId) {
|
|
console.warn('[UIEventHandler] drawer:toggle event missing componentId');
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIEventHandler] Toggling drawer for component: ${componentId}`);
|
|
|
|
if (this.drawerManager) {
|
|
const isOpen = this.drawerManager.isOpen(componentId);
|
|
if (isOpen) {
|
|
this.drawerManager.close(componentId);
|
|
} else {
|
|
// For toggle, we need the full options - this is a limitation
|
|
// In practice, components should use open/close explicitly
|
|
console.warn('[UIEventHandler] drawer:toggle requires full options - use drawer:open/close instead');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle popover:show event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
async handlePopoverShow(e) {
|
|
const payload = e.detail || {};
|
|
const {
|
|
componentId,
|
|
anchorId,
|
|
content = '',
|
|
title = '',
|
|
position = 'top',
|
|
showArrow = true,
|
|
offset = 8,
|
|
closeOnOutsideClick = true
|
|
} = payload;
|
|
|
|
if (!componentId || !anchorId) {
|
|
console.warn('[UIEventHandler] popover:show event missing componentId or anchorId');
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIEventHandler] Showing popover for component: ${componentId}`);
|
|
|
|
// Initialize PopoverManager if not already done
|
|
if (!this.popoverManager) {
|
|
const { PopoverManager } = await import('./PopoverManager.js');
|
|
this.popoverManager = new PopoverManager();
|
|
}
|
|
|
|
this.popoverManager.show(componentId, {
|
|
anchorId: anchorId,
|
|
content: content,
|
|
title: title,
|
|
position: position,
|
|
showArrow: showArrow,
|
|
offset: offset,
|
|
closeOnOutsideClick: closeOnOutsideClick
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle popover:hide event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
handlePopoverHide(e) {
|
|
const payload = e.detail || {};
|
|
const { componentId } = payload;
|
|
|
|
if (!componentId) {
|
|
console.warn('[UIEventHandler] popover:hide event missing componentId');
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIEventHandler] Hiding popover for component: ${componentId}`);
|
|
|
|
if (this.popoverManager) {
|
|
this.popoverManager.hide(componentId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle popover:toggle event
|
|
* @param {CustomEvent} e - Event object
|
|
*/
|
|
async handlePopoverToggle(e) {
|
|
const payload = e.detail || {};
|
|
const {
|
|
componentId,
|
|
anchorId,
|
|
content = '',
|
|
title = '',
|
|
position = 'top',
|
|
showArrow = true,
|
|
offset = 8,
|
|
closeOnOutsideClick = true
|
|
} = payload;
|
|
|
|
if (!componentId || !anchorId) {
|
|
console.warn('[UIEventHandler] popover:toggle event missing componentId or anchorId');
|
|
return;
|
|
}
|
|
|
|
// Initialize PopoverManager if not already done
|
|
if (!this.popoverManager) {
|
|
const { PopoverManager } = await import('./PopoverManager.js');
|
|
this.popoverManager = new PopoverManager();
|
|
}
|
|
|
|
// Check if popover is visible
|
|
const isVisible = this.popoverManager.popovers.has(componentId);
|
|
|
|
if (isVisible) {
|
|
this.popoverManager.hide(componentId);
|
|
} else {
|
|
this.popoverManager.show(componentId, {
|
|
anchorId: anchorId,
|
|
content: content,
|
|
title: title,
|
|
position: position,
|
|
showArrow: showArrow,
|
|
offset: offset,
|
|
closeOnOutsideClick: closeOnOutsideClick
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup all event listeners
|
|
*/
|
|
destroy() {
|
|
for (const [eventName, handler] of this.eventListeners.entries()) {
|
|
document.removeEventListener(eventName, handler);
|
|
}
|
|
this.eventListeners.clear();
|
|
|
|
// Cleanup managers
|
|
if (this.drawerManager) {
|
|
this.drawerManager.destroy();
|
|
}
|
|
|
|
if (this.popoverManager) {
|
|
this.popoverManager.destroy();
|
|
}
|
|
|
|
this.isInitialized = false;
|
|
console.log('[UIEventHandler] Destroyed');
|
|
}
|
|
}
|
|
|