fix(busy): make the page inert so the keyboard cannot reach controls behind the overlay

The overlay covers the page, which stops the mouse but not Tab and Space. The inert attribute removes the subtree from focus, hit-testing and the accessibility tree in one go - applied to every body-level sibling except the overlay, and always cleared in hideBusy.
This commit is contained in:
r-zakarya
2026-08-01 19:09:16 +01:00
parent 4a78dc2e90
commit e07488e8b4
2 changed files with 26 additions and 4 deletions
+16
View File
@@ -5973,16 +5973,32 @@ function promptDialog(opts) {
return new Promise(res => { confirmResolver = res; });
}
// The overlay stops the MOUSE — it covers the page — but not the keyboard:
// Tab still walked into the controls behind it and Space activated them. `inert`
// is the platform's answer: it removes a subtree from focus, hit-testing and
// the accessibility tree in one attribute. Applied to every body-level sibling
// except the overlay itself.
function setAppInert(on) {
Array.from(document.body.children).forEach(node => {
if (node.id === 'busyOverlay') return;
node.inert = !!on;
});
}
// Global busy overlay for long operations (export, big file saves).
function showBusy(text) {
const t = $('#busyText'); if (t) t.textContent = text || 'Working…';
const o = $('#busyOverlay'); if (o) o.classList.remove('is-hidden');
setAppInert(true);
}
function updateBusy(text) {
const t = $('#busyText'); if (t) t.textContent = text || '';
}
function hideBusy() {
const o = $('#busyOverlay'); if (o) o.classList.add('is-hidden');
// Unconditionally, whatever happened above: leaving the page inert would
// look exactly like a frozen app.
setAppInert(false);
}
// True while a blocking operation owns the screen. Anything that would open a
// dialog has to check this: the overlay is above every modal, so a dialog