fix(frontend): reliably restore row focus on panel close (#1602)

fix for the focus-restore@800 E2E test that's currently failing on
master (see runs 26990436988, 26986419081)

Chromium headless is notorious for dropping synchronous or rAF-based
focus restores when elements are hidden. By manually blurring the active
element before hiding the panel, and staggering the focus restore with a
setTimeout macrotask after the rAF, we ensure the focus call lands after
the browser has completed all implicit focus resets and event handlers.

Furthermore, dynamically evaluating the focus resolver directly inside
the deferred focus attempt prevents the target element from becoming
stale if a live WebSocket packet triggers a background table re-render
in the intervening milliseconds.
This commit is contained in:
Eldoon Nemar
2026-06-05 08:44:37 -04:00
committed by GitHub
parent 1f65d7811b
commit 1be0aec808
2 changed files with 56 additions and 5 deletions
+26 -5
View File
@@ -426,16 +426,37 @@
// Defer to next microtask + rAF so the focus call lands AFTER any
// event-handler bookkeeping (e.g. an Escape keydown chain that would
// otherwise see focus snap back to <body> as the key event unwinds).
const target = toFocus;
const tryFocus = function () {
// Munger #1: bail if a newer open() has happened since close-time.
if (openSeq !== seqAtClose) return;
if (document.body.contains(target)) {
try { target.focus(); } catch {}
let t = toFocus;
if (resolver) {
try {
const fresh = resolver();
if (fresh) t = fresh;
} catch (_) {}
}
// MINOR fix: don't steal focus if the user already focused another input
if (document.activeElement &&
document.activeElement !== document.body &&
document.activeElement !== t &&
!panel.contains(document.activeElement)) {
return;
}
if (t && document.body.contains(t)) {
try { t.focus({ preventScroll: true }); } catch (_) {}
}
};
tryFocus();
requestAnimationFrame(tryFocus);
requestAnimationFrame(function () {
if (document.activeElement && panel.contains(document.activeElement)) {
try { document.activeElement.blur(); } catch (_) {}
}
tryFocus();
setTimeout(tryFocus, 10);
});
}
}
+30
View File
@@ -428,6 +428,36 @@ const PAGES = [
assert(r.isActive, 'focus did NOT restore to originating row after Escape: ' + JSON.stringify(r));
});
await step('focus-restore@800: re-renders while open still restore to new row instance', async () => {
const rowKey = await openPanelFromRow();
// Force a re-render of the table so the original DOM node is detached
await page.evaluate(() => {
if (typeof window.renderRows === 'function') {
window.renderRows();
}
});
await page.keyboard.press('Escape');
// Wait for renderRows() + post-rAF focus restore to settle.
await page.waitForFunction((key) => {
const esc = (window.CSS && CSS.escape) ? CSS.escape(key) : key;
const row = document.querySelector('#nodesTable tbody tr[data-value="' + esc + '"]');
return !!row && document.activeElement === row;
}, rowKey, { timeout: 2000 }).catch(() => {});
const r = await page.evaluate((key) => {
const esc = (window.CSS && CSS.escape) ? CSS.escape(key) : key;
const row = document.querySelector('#nodesTable tbody tr[data-value="' + esc + '"]');
return {
rowExists: !!row,
isActive: !!row && document.activeElement === row,
};
}, rowKey);
assert(r.rowExists, 'originating row vanished from DOM after manual re-render');
assert(r.isActive, 'focus did NOT restore to NEW row instance after Escape: ' + JSON.stringify(r));
});
// ------------------------------------------------------------------
// SKIP: tracked in #1172 — flaky in CI Chromium, see issue for repro.
// X-click focus-restore is real and works locally; head-to-head with