| 1 | // Custom confirmation dialog. CSS in /css/modals.css |
| 2 | |
| 3 | import { callJsExtensions } from "/js/extensions.js"; |
| 4 | |
| 5 | const DIALOG_TYPES = { |
| 6 | warning: { icon: 'warning', color: 'var(--color-warning, #f59e0b)' }, |
| 7 | danger: { icon: 'error', color: 'var(--color-error, #ef4444)' }, |
| 8 | info: { icon: 'info', color: 'var(--color-primary, #3b82f6)' } |
| 9 | }; |
| 10 | |
| 11 | export function showConfirmDialog(options) { |
| 12 | const { |
| 13 | title = 'Confirm', |
| 14 | message = '', |
| 15 | confirmText = 'Confirm', |
| 16 | cancelText = 'Cancel', |
| 17 | type = 'warning', |
| 18 | extensionContext = null, |
| 19 | } = options; |
| 20 | |
| 21 | const typeConfig = DIALOG_TYPES[type] || DIALOG_TYPES.warning; |
| 22 | |
| 23 | return new Promise((resolve) => { |
| 24 | // Create backdrop |
| 25 | const backdrop = document.createElement('div'); |
| 26 | backdrop.className = 'confirm-dialog-backdrop'; |
| 27 | |
| 28 | // Create dialog |
| 29 | const dialog = document.createElement('div'); |
| 30 | dialog.className = 'confirm-dialog'; |
| 31 | dialog.innerHTML = ` |
| 32 | <div class="confirm-dialog-header"> |
| 33 | <x-icon class="confirm-dialog-icon" style="color: ${typeConfig.color}" name="${typeConfig.icon}"></x-icon> |
| 34 | <span class="confirm-dialog-title">${title}</span> |
| 35 | </div> |
| 36 | <div class="confirm-dialog-body">${message}</div> |
| 37 | <div class="confirm-dialog-footer"> |
| 38 | <button class="button cancel confirm-dialog-cancel">${cancelText}</button> |
| 39 | <button class="button confirm confirm-dialog-confirm">${confirmText}</button> |
| 40 | </div> |
| 41 | `; |
| 42 | |
| 43 | backdrop.appendChild(dialog); |
| 44 | document.body.appendChild(backdrop); |
| 45 | |
| 46 | const titleElement = dialog.querySelector('.confirm-dialog-title'); |
| 47 | const bodyElement = dialog.querySelector('.confirm-dialog-body'); |
| 48 | const footerElement = dialog.querySelector('.confirm-dialog-footer'); |
| 49 | const cancelButton = dialog.querySelector('.confirm-dialog-cancel'); |
| 50 | const confirmButton = dialog.querySelector('.confirm-dialog-confirm'); |
| 51 | let isClosed = false; |
| 52 | |
| 53 | // Show with animation |
| 54 | const showDialog = () => { |
| 55 | requestAnimationFrame(() => { |
| 56 | backdrop.classList.add('visible'); |
| 57 | cancelButton?.focus(); |
| 58 | }); |
| 59 | }; |
| 60 | |
| 61 | // Close handler |
| 62 | const close = (result) => { |
| 63 | if (isClosed) return; |
| 64 | isClosed = true; |
| 65 | backdrop.classList.remove('visible'); |
| 66 | document.removeEventListener('keydown', handleKeydown); |
| 67 | setTimeout(() => { |
| 68 | backdrop.remove(); |
| 69 | resolve(result); |
| 70 | }, 200); |
| 71 | }; |
| 72 | |
| 73 | // Event listeners |
| 74 | cancelButton.addEventListener('click', () => close(false)); |
| 75 | confirmButton.addEventListener('click', () => close(true)); |
| 76 | backdrop.addEventListener('click', (e) => e.target === backdrop && close(false)); |
| 77 | |
| 78 | // Keyboard handling |
| 79 | const handleKeydown = (e) => { |
| 80 | if (e.key === 'Escape') { |
| 81 | e.preventDefault(); |
| 82 | close(false); |
| 83 | return; |
| 84 | } |
| 85 | |
| 86 | if (e.key !== 'Enter') return; |
| 87 | |
| 88 | const activeElement = document.activeElement; |
| 89 | if ( |
| 90 | activeElement |
| 91 | && dialog.contains(activeElement) |
| 92 | && ( |
| 93 | activeElement.tagName === 'BUTTON' |
| 94 | || activeElement.tagName === 'A' |
| 95 | || activeElement.tagName === 'INPUT' |
| 96 | || activeElement.tagName === 'TEXTAREA' |
| 97 | || activeElement.tagName === 'SELECT' |
| 98 | || activeElement.isContentEditable |
| 99 | ) |
| 100 | ) { |
| 101 | return; |
| 102 | } |
| 103 | |
| 104 | e.preventDefault(); |
| 105 | close(true); |
| 106 | }; |
| 107 | document.addEventListener('keydown', handleKeydown); |
| 108 | |
| 109 | const extensionData = { |
| 110 | options, |
| 111 | dialog, |
| 112 | backdrop, |
| 113 | titleElement, |
| 114 | bodyElement, |
| 115 | footerElement, |
| 116 | confirmButton, |
| 117 | cancelButton, |
| 118 | close, |
| 119 | title, |
| 120 | message, |
| 121 | type, |
| 122 | confirmText, |
| 123 | cancelText, |
| 124 | extensionContext, |
| 125 | }; |
| 126 | |
| 127 | callJsExtensions('confirm_dialog_after_render', extensionData) |
| 128 | .finally(showDialog); |
| 129 | }); |
| 130 | } |