diff --git a/public/packets.js b/public/packets.js index b2b230dc..2b2f366b 100644 --- a/public/packets.js +++ b/public/packets.js @@ -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 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); + }); } } diff --git a/test-slideover-1056-e2e.js b/test-slideover-1056-e2e.js index f5439a90..3581c4b5 100644 --- a/test-slideover-1056-e2e.js +++ b/test-slideover-1056-e2e.js @@ -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