// Shared modal overlay utility (extracted from Sidebar.js in H4 4.0). // Builds a single `#app-modal-overlay` over the app, stopping propagation so // inputs/clicks inside the modal never reach the 3D controls. /** @typedef {import('../types.js').ModalOptions} ModalOptions */ /** * Show a custom modal overlay. Replaces any existing overlay. * @param {ModalOptions} options * @returns {void} */ export function showCustomModal({ title, contentHTML, onConfirm, onCancel, confirmText = "Confirm", cancelText = "Cancel", isAlert = false }) { // Clean old modal overlays const oldOverlay = document.getElementById('app-modal-overlay'); if (oldOverlay) oldOverlay.remove(); const overlay = document.createElement('div'); overlay.id = 'app-modal-overlay'; overlay.className = 'modal-overlay'; document.body.appendChild(overlay); overlay.innerHTML = ` `; // Prevent propagation so inputs/clicks inside modal don't affect 3D controls ['mousedown', 'click', 'keydown', 'keyup'].forEach(evt => { overlay.addEventListener(evt, (e) => e.stopPropagation()); }); const closeOverlay = () => { overlay.classList.remove('active'); setTimeout(() => overlay.remove(), 300); }; const confirmBtn = overlay.querySelector('#modal-confirm-btn'); if (confirmBtn) { confirmBtn.onclick = () => { if (onConfirm) onConfirm(overlay); closeOverlay(); }; } const cancelBtn = overlay.querySelector('#modal-cancel-btn'); if (cancelBtn) { cancelBtn.onclick = () => { if (onCancel) onCancel(); closeOverlay(); }; } const closeX = overlay.querySelector('#modal-close-x'); if (closeX) { closeX.onclick = () => { if (onCancel) onCancel(); closeOverlay(); }; } // Force reflow and activate transition overlay.offsetHeight; overlay.classList.add('active'); } /** * Open the bookmark naming modal and lock/unlock PointerLock camera flight controls. * @param {{ docId: string, chunkIndex: number, dimensionIndex: number, value: number }} coordinates * @param {function(string): void} callback * @param {object} [controls] * @returns {void} */ export function showBookmarkNamingModal(coordinates, callback, controls = null) { if (controls && controls.isLocked) { controls.unlock(); } const valStr = typeof coordinates.value === 'number' ? coordinates.value.toFixed(4) : coordinates.value; const contentHTML = `

Bookmark: Document ${coordinates.docId} · Chunk ${coordinates.chunkIndex} · Dimension ${coordinates.dimensionIndex} (Value: ${valStr})

`; showCustomModal({ title: "New Bookmark", contentHTML: contentHTML, confirmText: "Save", cancelText: "Cancel", onConfirm: () => { // Handled by custom validation below }, onCancel: () => { if (controls) { controls.lock(); } } }); // Custom validation: override confirmBtn.onclick to prevent closing if input is empty const overlay = document.getElementById('app-modal-overlay'); if (overlay) { const confirmBtn = overlay.querySelector('#modal-confirm-btn'); const input = overlay.querySelector('#bookmark-name-input'); if (confirmBtn && input) { confirmBtn.onclick = () => { const name = input.value.trim(); if (!name) { input.style.borderColor = 'var(--color-semantic-error)'; input.focus(); return; // Prevent closing } callback(name); // Destroy the modal overlay.classList.remove('active'); setTimeout(() => overlay.remove(), 300); // Restore flight controls if (controls) { controls.lock(); } }; } // Focus the input field automatically setTimeout(() => { if (input) { input.focus(); input.select(); } }, 50); } }