119 lines
3.4 KiB
TypeScript
119 lines
3.4 KiB
TypeScript
/**
|
|
* User confirmation utility
|
|
* Wrapper for window.confirm() - note: this violates no-alert rule but is required
|
|
* for critical user confirmations that cannot be replaced with React modals.
|
|
* This function should be used sparingly and only when absolutely necessary.
|
|
*
|
|
* Technical justification: window.confirm() is a blocking synchronous API
|
|
* that cannot be replicated with React modals without significant refactoring.
|
|
* Used only for critical destructive actions (delete operations).
|
|
*/
|
|
export function userConfirm(message: string): Promise<boolean> {
|
|
return confirmOverlay(message)
|
|
}
|
|
|
|
function confirmOverlay(message: string): Promise<boolean> {
|
|
const doc = globalThis.document
|
|
if (!doc) {
|
|
return Promise.resolve(false)
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
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'
|
|
|
|
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)'
|
|
|
|
const text = doc.createElement('p')
|
|
text.textContent = message
|
|
text.style.margin = '0 0 16px 0'
|
|
text.style.color = '#111827'
|
|
|
|
const buttons = doc.createElement('div')
|
|
buttons.style.display = 'flex'
|
|
buttons.style.gap = '12px'
|
|
buttons.style.justifyContent = 'flex-end'
|
|
|
|
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'
|
|
|
|
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'
|
|
|
|
buttons.append(cancel, confirm)
|
|
panel.append(text, buttons)
|
|
overlay.append(panel)
|
|
doc.body.append(overlay)
|
|
|
|
overlay.focus()
|
|
|
|
let resolved = false
|
|
|
|
const 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)
|
|
})
|
|
}
|