Last active
January 30, 2024 09:37
-
-
Save tannerlinsley/1d3a2122332107fcd8c9cc379be10d88 to your computer and use it in GitHub Desktop.
A utility function to detect window focusing without false positives from iframe focus events
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type State = { | |
added: boolean; | |
interval: false | ReturnType<typeof setInterval>; | |
inFrame: boolean; | |
callbacks: Array<SetFocusedCallback>; | |
}; | |
type EnrichedHTMLIFrameElement = HTMLIFrameElement & { ___onWindowFocusHandled: boolean }; | |
type SetFocusedCallback = (focused?: boolean) => void; | |
const state: State = { | |
added: false, | |
interval: false, | |
inFrame: false, | |
callbacks: [], | |
}; | |
export const onWindowFocus = (newCallback: SetFocusedCallback) => { | |
state.callbacks.push(newCallback); | |
start(); | |
return () => { | |
state.callbacks = state.callbacks.filter( | |
(registeredCallback) => registeredCallback !== newCallback | |
); | |
stop(); | |
}; | |
}; | |
const runIFrameCheck = () => { | |
const iframes = Array.from(document.getElementsByTagName('iframe')); | |
console.debug('Polling iframes... found: ', iframes.length); | |
(iframes as EnrichedHTMLIFrameElement[]).forEach((iframe) => { | |
if (iframe.___onWindowFocusHandled) { | |
return; | |
} | |
iframe.___onWindowFocusHandled = true; | |
iframe.addEventListener('touchend', () => { | |
state.inFrame = true; | |
}); | |
iframe.addEventListener('mouseup', () => { | |
state.inFrame = true; | |
}); | |
iframe.addEventListener('focus', () => { | |
state.inFrame = true; | |
}); | |
}); | |
}; | |
const start = () => { | |
if (state.interval) { | |
clearInterval(state.interval); | |
} | |
if (!state.added) { | |
state.added = true; | |
window.addEventListener('focus', () => { | |
if (state.inFrame) { | |
state.inFrame = false; | |
return; | |
} else { | |
state.callbacks.forEach((callback) => callback(true)); | |
} | |
}); | |
} | |
state.interval = setInterval(runIFrameCheck, 500); | |
}; | |
const stop = () => { | |
if (!state.callbacks.length && state.interval) { | |
clearInterval(state.interval); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I tweaked above code a bit and it works on react-query v2.