diff --git a/packages/repl/src/lib/Output/srcdoc/index.html b/packages/repl/src/lib/Output/srcdoc/index.html index f89f687249..07d900ac0e 100644 --- a/packages/repl/src/lib/Output/srcdoc/index.html +++ b/packages/repl/src/lib/Output/srcdoc/index.html @@ -43,6 +43,15 @@ } (0, eval)(data.args.script); + + // hand focus back to the editor if it's taken in an effect + // that runs immediately (for focuses in effects, this is + // more effective than listening for the focusin event) + Promise.resolve().then(() => { + if (document.activeElement !== null && document.activeElement !== document.body) { + send({ type: 'iframe_took_focus' }); + } + }); } if (action === 'catch_clicks') { @@ -244,6 +253,26 @@ original(...args); }; } + + // Focus management + let can_focus = false; + + window.addEventListener('pointerdown', (e) => (can_focus = true)); + window.addEventListener('pointerup', (e) => (can_focus = false)); + window.addEventListener('keydown', (e) => (can_focus = true)); + window.addEventListener('keyup', (e) => (can_focus = false)); + + window.addEventListener('focusin', (e) => { + // if focusin happened as a result of a mouse/keyboard event, allow it + if (can_focus) return; + + // if `e.target` is the `` and there's a `relatedTarget`, + // assume the focusin was the result of a user navigation — allow it + if (e.target.tagName === 'BODY' && e.relatedTarget) return; + + // otherwise, broadcast an event that causes the editor to reclaim focus + send({ type: 'iframe_took_focus' }); + }); })();