/** * Sistema de modales centralizado para reemplazar alert() nativos * Uso: * import { modal } from './lib/modal.js'; * modal.success("Guardado correctamente"); * modal.error("Error: " + e.message); * modal.info("Información importante"); * modal.warn("Advertencia"); * const ok = await modal.confirm("¿Estás seguro?"); */ const STYLES = ` .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 10000; animation: fadeIn 0.15s ease-out; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideIn { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .modal-box { background: #1e1e1e; border-radius: 8px; padding: 24px; min-width: 320px; max-width: 480px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); animation: slideIn 0.2s ease-out; border: 1px solid #333; } .modal-header { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } .modal-icon { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; } .modal-icon.success { background: #22c55e20; color: #22c55e; } .modal-icon.error { background: #ef444420; color: #ef4444; } .modal-icon.warn { background: #f59e0b20; color: #f59e0b; } .modal-icon.info { background: #3b82f620; color: #3b82f6; } .modal-icon.confirm { background: #8b5cf620; color: #8b5cf6; } .modal-title { font-size: 16px; font-weight: 600; color: #fff; margin: 0; } .modal-message { color: #ccc; font-size: 14px; line-height: 1.5; margin-bottom: 20px; word-break: break-word; } .modal-buttons { display: flex; gap: 12px; justify-content: flex-end; } .modal-btn { padding: 8px 16px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; border: none; transition: all 0.15s; } .modal-btn:hover { filter: brightness(1.1); } .modal-btn.primary { background: #3b82f6; color: white; } .modal-btn.secondary { background: #333; color: #ccc; border: 1px solid #444; } .modal-btn.danger { background: #ef4444; color: white; } `; // Inyectar estilos una sola vez let stylesInjected = false; function injectStyles() { if (stylesInjected) return; const style = document.createElement("style"); style.textContent = STYLES; document.head.appendChild(style); stylesInjected = true; } const ICONS = { success: "✓", error: "✕", warn: "!", info: "i", confirm: "?", }; const TITLES = { success: "Éxito", error: "Error", warn: "Advertencia", info: "Información", confirm: "Confirmar", }; function createModal({ type, message, showCancel = false, confirmText = "Aceptar", cancelText = "Cancelar" }) { injectStyles(); return new Promise((resolve) => { const overlay = document.createElement("div"); overlay.className = "modal-overlay"; const box = document.createElement("div"); box.className = "modal-box"; box.innerHTML = ` `; overlay.appendChild(box); document.body.appendChild(overlay); // Focus en el botón principal const confirmBtn = box.querySelector('[data-action="confirm"]'); confirmBtn?.focus(); const close = (result) => { overlay.style.animation = "fadeIn 0.15s ease-out reverse"; setTimeout(() => { overlay.remove(); resolve(result); }, 140); }; // Click en botones box.addEventListener("click", (e) => { const action = e.target.dataset?.action; if (action === "confirm") close(true); if (action === "cancel") close(false); }); // Click fuera cierra (solo para mensajes, no confirms) overlay.addEventListener("click", (e) => { if (e.target === overlay && !showCancel) { close(true); } }); // Escape cierra const handleKeydown = (e) => { if (e.key === "Escape") { close(showCancel ? false : true); document.removeEventListener("keydown", handleKeydown); } if (e.key === "Enter") { close(true); document.removeEventListener("keydown", handleKeydown); } }; document.addEventListener("keydown", handleKeydown); }); } function escapeHtml(text) { const div = document.createElement("div"); div.textContent = text; return div.innerHTML; } export const modal = { success: (message) => createModal({ type: "success", message }), error: (message) => createModal({ type: "error", message }), warn: (message) => createModal({ type: "warn", message }), info: (message) => createModal({ type: "info", message }), confirm: (message, { confirmText = "Confirmar", cancelText = "Cancelar" } = {}) => createModal({ type: "confirm", message, showCancel: true, confirmText, cancelText }), }; // También exportar como default para conveniencia export default modal;