import { html, render, useEffect, useRef } from './vendor/htm-preact.js'; import { t } from './i18n.js'; /** * `confirm` and `alert`, drawn by the page instead of the browser. * * In the desktop client a native `confirm()` leaves the window unable to type: * once it closes, `document.hasFocus()` stays false, a click still moves * `activeElement` to a field, and every keystroke after it goes nowhere — until * the window loses and regains focus. Found as "the Create group fields are * frozen" right after removing a member, and reproduced against Electron 44 by * real X input events (`test_no_native_dialogs_in_the_spa.py`). `prompt` was * already banned for throwing; the other two fail more quietly, and later. * * Both return a promise, so a call site reads as it did: `if (!(await * ask(msg))) return;`. */ function Dialog({ message, cancellable, onDone }) { const okRef = useRef(null); useEffect(() => { if (okRef.current) okRef.current.focus(); }, []); const onKeyDown = (e) => { if (e.key === 'Escape') { e.preventDefault(); onDone(!cancellable); } }; return html`
`; } function open(message, cancellable) { return new Promise((resolve) => { const host = document.createElement('div'); document.body.appendChild(host); const previous = document.activeElement; let settled = false; const onDone = (value) => { if (settled) return; settled = true; render(null, host); host.remove(); if (previous && previous.isConnected && typeof previous.focus === 'function') { previous.focus(); } resolve(value); }; render(html`<${Dialog} message=${String(message)} cancellable=${cancellable} onDone=${onDone} />`, host); }); } export const ask = (message) => open(message, true); export const tell = (message) => open(message, false);