/** * User confirmation utility * Non-blocking confirmation overlay to avoid `window.confirm()` (`no-alert`). * Used for critical confirmations (e.g. destructive actions) without requiring * a React modal or additional global state. */ export function userConfirm(message: string): Promise { return confirmOverlay(message) } type ConfirmOverlayElements = { overlay: HTMLDivElement cancel: HTMLButtonElement confirm: HTMLButtonElement } type ConfirmOverlayHandlerParams = ConfirmOverlayElements & { resolve: (value: boolean) => void } function confirmOverlay(message: string): Promise { const doc = globalThis.document if (!doc) { return Promise.resolve(false) } return new Promise((resolve) => { const { overlay, cancel, confirm } = buildConfirmOverlay(doc, message) doc.body.append(overlay) overlay.focus() attachConfirmOverlayHandlers({ overlay, cancel, confirm, resolve }) }) } function buildConfirmOverlay(doc: Document, message: string): ConfirmOverlayElements { const overlay = createOverlay(doc) const panel = createPanel(doc) const text = createText(doc, message) const buttons = createButtonsContainer(doc) const cancel = createCancelButton(doc) const confirm = createConfirmButton(doc) buttons.append(cancel, confirm) panel.append(text, buttons) overlay.append(panel) return { overlay, cancel, confirm } } function createOverlay(doc: Document): HTMLDivElement { const overlay = doc.createElement('div') overlay.setAttribute('role', 'dialog') overlay.setAttribute('aria-modal', 'true') overlay.tabIndex = -1 overlay.style.position = 'fixed' overlay.style.inset = '0' overlay.style.background = 'rgba(0,0,0,0.6)' overlay.style.display = 'flex' overlay.style.alignItems = 'center' overlay.style.justifyContent = 'center' overlay.style.zIndex = '9999' return overlay } function createPanel(doc: Document): HTMLDivElement { const panel = doc.createElement('div') panel.style.background = '#fff' panel.style.borderRadius = '12px' panel.style.padding = '16px' panel.style.maxWidth = '520px' panel.style.width = 'calc(100% - 32px)' panel.style.boxShadow = '0 10px 30px rgba(0,0,0,0.35)' return panel } function createText(doc: Document, message: string): HTMLParagraphElement { const text = doc.createElement('p') text.textContent = message text.style.margin = '0 0 16px 0' text.style.color = '#111827' return text } function createButtonsContainer(doc: Document): HTMLDivElement { const buttons = doc.createElement('div') buttons.style.display = 'flex' buttons.style.gap = '12px' buttons.style.justifyContent = 'flex-end' return buttons } function createCancelButton(doc: Document): HTMLButtonElement { const cancel = doc.createElement('button') cancel.type = 'button' cancel.textContent = 'Cancel' cancel.style.padding = '8px 12px' cancel.style.borderRadius = '10px' cancel.style.border = '1px solid #e5e7eb' cancel.style.background = '#f3f4f6' return cancel } function createConfirmButton(doc: Document): HTMLButtonElement { const confirm = doc.createElement('button') confirm.type = 'button' confirm.textContent = 'Confirm' confirm.style.padding = '8px 12px' confirm.style.borderRadius = '10px' confirm.style.border = '1px solid #ef4444' confirm.style.background = '#fee2e2' confirm.style.color = '#991b1b' return confirm } function attachConfirmOverlayHandlers(params: ConfirmOverlayHandlerParams): void { const { overlay, cancel, confirm, resolve } = params let resolved = false function resolveOnce(next: boolean): void { if (resolved) { return } resolved = true cleanup() resolve(next) } function onCancel(): void { resolveOnce(false) } function onConfirm(): void { resolveOnce(true) } function onKeyDown(e: KeyboardEvent): void { if (e.key === 'Escape') { e.preventDefault() resolveOnce(false) return } if (e.key === 'Enter') { e.preventDefault() resolveOnce(true) } } function cleanup(): void { overlay.removeEventListener('keydown', onKeyDown) cancel.removeEventListener('click', onCancel) confirm.removeEventListener('click', onConfirm) overlay.remove() } cancel.addEventListener('click', onCancel) confirm.addEventListener('click', onConfirm) overlay.addEventListener('keydown', onKeyDown) }