Files
michaelschiemer/resources/js/modules/ui/components/Dialog.js
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

128 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {useEvent} from "../../../core/useEvent";
export class Dialog {
constructor({content = '', className = '', onClose = null} = {}) {
this.onClose = onClose;
this.className = className;
this.isOpenState = false;
this.dialog = document.createElement('dialog');
this.dialog.className = className;
this.eventCleanup = []; // Track cleanup functions
this.updateContent(content);
this.bindEvents();
}
/**
* Bind event handlers to the dialog
*/
bindEvents() {
// Clean up any existing event listeners first
this.cleanupEvents();
// Store references to bound event handlers for cleanup
this.clickHandler = (e) => {
const isOutside = !e.target.closest('.' + this.className + '-content');
if (isOutside) this.close();
};
this.cancelHandler = (e) => {
e.preventDefault();
this.close();
};
// Use direct event listeners instead of useEvent to avoid module-based cleanup issues
this.dialog.addEventListener('click', this.clickHandler);
this.dialog.addEventListener('cancel', this.cancelHandler);
// Store cleanup functions
this.eventCleanup = [
() => this.dialog.removeEventListener('click', this.clickHandler),
() => this.dialog.removeEventListener('cancel', this.cancelHandler)
];
}
/**
* Clean up existing event listeners
*/
cleanupEvents() {
this.eventCleanup.forEach(cleanup => cleanup());
this.eventCleanup = [];
}
/**
* Update the content of the dialog without recreating it
* @param {string} content - New HTML content
*/
updateContent(content) {
this.dialog.innerHTML = `
<form method="dialog" class="${this.className}-content">
${content}
<button class="${this.className}-close" value="close">×</button>
</form>
`;
// No need to rebind events - they're attached to the dialog element itself
// which doesn't get replaced by innerHTML
}
open() {
// Always ensure dialog is in DOM
if (!this.dialog.parentElement) {
document.body.appendChild(this.dialog);
}
// Force close if somehow already open
if (this.dialog.hasAttribute('open') || this.dialog.open) {
this.dialog.close?.() || this.dialog.removeAttribute('open');
}
// Now open fresh
this.dialog.showModal?.() || this.dialog.setAttribute('open', '');
document.documentElement.dataset[`${this.dialog.className}Open`] = 'true';
this.isOpenState = true;
}
close() {
if (this.isOpenState) {
this.dialog.close?.() || this.dialog.removeAttribute('open');
delete document.documentElement.dataset[`${this.dialog.className}Open`];
this.isOpenState = false;
// Keep dialog in DOM for reuse, just hide it
// Don't remove from DOM to enable singleton pattern
this.onClose?.();
}
}
/**
* Check if dialog is currently open (checks both state and DOM)
* @returns {boolean}
*/
isOpen() {
// Check both our internal state and the actual DOM state
const domIsOpen = this.dialog && (this.dialog.hasAttribute('open') || this.dialog.open);
// Sync our internal state with DOM reality
if (domIsOpen !== this.isOpenState) {
this.isOpenState = domIsOpen;
}
return this.isOpenState;
}
/**
* Destroy the dialog completely (for cleanup)
*/
destroy() {
if (this.isOpenState) {
this.close();
}
this.cleanupEvents();
if (this.dialog.parentElement) {
this.dialog.remove();
}
}
}