// Reusable Drawer Component for desktop slide-in panels class DrawerComponent { constructor() { this.detailsDrawer = null; this.detailsDrawerContent = null; this.detailsDrawerBackdrop = null; this.activeDrawerComponent = null; this.onCloseCallback = null; } // Determine if we should use desktop drawer behavior isDesktop() { try { return window && window.innerWidth >= 1024; // desktop threshold } catch (_) { return false; } } ensureDrawer() { if (this.detailsDrawer) return; // Create backdrop this.detailsDrawerBackdrop = document.createElement('div'); this.detailsDrawerBackdrop.className = 'details-drawer-backdrop'; document.body.appendChild(this.detailsDrawerBackdrop); // Create drawer this.detailsDrawer = document.createElement('div'); this.detailsDrawer.className = 'details-drawer'; // Header with close button const header = document.createElement('div'); header.className = 'details-drawer-header'; header.innerHTML = `
Node Details
`; this.detailsDrawer.appendChild(header); // Content container this.detailsDrawerContent = document.createElement('div'); this.detailsDrawerContent.className = 'details-drawer-content'; this.detailsDrawer.appendChild(this.detailsDrawerContent); document.body.appendChild(this.detailsDrawer); // Close handlers const close = () => this.closeDrawer(); header.querySelector('.drawer-close').addEventListener('click', close); this.detailsDrawerBackdrop.addEventListener('click', close); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); }); } openDrawer(title, contentCallback, errorCallback, onCloseCallback) { this.ensureDrawer(); this.onCloseCallback = onCloseCallback; // Set drawer title const titleEl = this.detailsDrawer.querySelector('.drawer-title'); if (titleEl) { titleEl.textContent = title; } // Clear previous component if any if (this.activeDrawerComponent && typeof this.activeDrawerComponent.unmount === 'function') { try { this.activeDrawerComponent.unmount(); } catch (_) {} } this.detailsDrawerContent.innerHTML = '
Loading detailed information...
'; // Execute content callback try { contentCallback(this.detailsDrawerContent, (component) => { this.activeDrawerComponent = component; }); } catch (error) { logger.error('Failed to load drawer content:', error); if (errorCallback) { errorCallback(error); } else { this.detailsDrawerContent.innerHTML = `
Error loading content:
${this.escapeHtml ? this.escapeHtml(error.message) : error.message}
`; } } // Open drawer this.detailsDrawer.classList.add('open'); this.detailsDrawerBackdrop.classList.add('visible'); } closeDrawer() { if (this.detailsDrawer) this.detailsDrawer.classList.remove('open'); if (this.detailsDrawerBackdrop) this.detailsDrawerBackdrop.classList.remove('visible'); // Call close callback if provided if (this.onCloseCallback) { this.onCloseCallback(); this.onCloseCallback = null; } } // Clean up drawer elements destroy() { if (this.detailsDrawer && this.detailsDrawer.parentNode) { this.detailsDrawer.parentNode.removeChild(this.detailsDrawer); } if (this.detailsDrawerBackdrop && this.detailsDrawerBackdrop.parentNode) { this.detailsDrawerBackdrop.parentNode.removeChild(this.detailsDrawerBackdrop); } this.detailsDrawer = null; this.detailsDrawerContent = null; this.detailsDrawerBackdrop = null; this.activeDrawerComponent = null; } // Helper method for HTML escaping (can be overridden) escapeHtml(text) { if (typeof text !== 'string') return text; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } window.DrawerComponent = DrawerComponent;