Last active
June 12, 2022 23:13
-
-
Save alex-cory/0b0b59cef8cd402b751e6e33bef12238 to your computer and use it in GitHub Desktop.
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
import { useEffect, useRef } from 'react' | |
import { isMobile } from 'react-device-detect' | |
export const getElement = (id = '') => { | |
if (typeof window === 'undefined') return console.warn('getElement - no window object available') | |
// if we are passing an element as the ID | |
if (typeof id !== 'string') return id | |
return document[id.startsWith('.') ? 'querySelector' : 'getElementById'](id) | |
} | |
// let observer | |
/** | |
* Used to add/remove event listeners more cleanly | |
* | |
* @example | |
* const remove = listen('video-element-id', 'canplaythrough', e => { | |
* // removes the event listener the first time | |
* // the video can be played | |
* remove() | |
* }) | |
* | |
* @param {string|html} idOrEl | |
* @param {string} eventName | |
* @param {function} callback | |
* @returns remove event listner function | |
*/ | |
export const listener = (idOrEl, eventName, callback) => { | |
const el = getElement(idOrEl) | |
el?.addEventListener(eventName, callback) | |
return () => el?.removeEventListener(eventName, callback) | |
} | |
const usePageFocus = (callback, deps = []) => { | |
/** | |
* The commented out code below is WIP. It is a potential | |
* way for the case where a file input was added to the | |
* page after this has run. The listeners will not attach | |
* with the current implementation so we need to watch the | |
* page for added file inputs, and attach the necessary listeners. | |
* Reference: https://bit.ly/3dDu8O9 | |
*/ | |
// const hook = useLastUnmount('use-page-focus', () => { | |
// observer.disconnect() | |
// observer = null | |
// }) | |
// // the 1st render of the 1st instance of this hook on the page | |
// if (hook.firstMount) { | |
// observer = new MutationObserver(mutations => { | |
// for (var i=0; i < mutations.length; i++) { | |
// for (var j=0; j < mutations[i].addedNodes.length; j++) { | |
// const addedNode = mutations[i].addedNodes[j] | |
// if (addedNode.nodeType === 1 && addedNode.tagName === 'INPUT' && addedNode.type === 'file'){ | |
// console.log('FILE INPUT ADDED', hook.id) | |
// // addedNode.src = optimizeSrc(addedNode.src) | |
// } | |
// // checkNode() | |
// } | |
// } | |
// }) | |
// observer.observe(document.documentElement, { | |
// childList: true, | |
// subtree: true | |
// }) | |
// } | |
const state = useRef({ | |
filePicker: { | |
isOpen: false, | |
// the little dialog that comes up when clicking file input. Pic: https://bit.ly/3ps8QZb | |
isMobileDialogOpen: false | |
}, | |
page: { | |
isVisible: true, | |
isFocused: true | |
}, | |
listeners: [], | |
has: { ran: false } | |
}) | |
useEffect(() => { | |
const { filePicker, page, listeners, has } = state.current | |
if (!isSupportedLocal || has.ran) return | |
has.ran = true | |
const log = (key, obj = {}) => { | |
// const [visible] = getVisibility() | |
// console.log(key, { | |
// ...obj, | |
// pickerOpen: filePicker.isOpen, | |
// dialogOpen: filePicker.isMobileDialogOpen, | |
// visible, | |
// docFocused: document.hasFocus(), | |
// docVisibility: document.visibilityState, | |
// }) | |
} | |
const listen = (...args) => { | |
const l = listener(...args) | |
listeners.push(l) | |
return l | |
} | |
const fileInputs = Array.from(document.querySelectorAll("input[type='file']")) | |
fileInputs.forEach(input => { | |
listen(input, 'click', () => { | |
if (isMobile && !filePicker.isMobileDialogOpen) { | |
const rmTouchStart = listen(window, 'touchstart', () => { | |
rmTouchStart() | |
rmVis() | |
filePicker.isMobileDialogOpen = false | |
log('touchstart π₯π₯π₯ - CLOSING mobile file picker dialog') | |
}) | |
const rmVis = listen(document, visibility.event, () => { | |
const [visible] = getVisibility() | |
let msg = 'OPENING' | |
if (!visible) { | |
filePicker.isOpen = true | |
filePicker.isMobileDialogOpen = false | |
rmTouchStart() | |
} else { | |
if (!input.files.length) { | |
// did not select any files - canceled the file picker | |
msg = 'CLOSING (canceled)' | |
removeChange() | |
} else { | |
msg = 'CLOSING (file selected)' | |
} | |
rmVis() | |
filePicker.isOpen = false | |
filePicker.isMobileDialogOpen = false | |
// rmTouchStart() | |
} | |
const files = input.files.length ? { files: input.files } : {} | |
log(`visibility change - ${msg} file picker`, { ...files }) | |
}) | |
// if a file is selected, the file picker is no longer open | |
const removeChange = listen(input, 'change', () => { | |
removeChange() | |
log('πππ file selected') | |
filePicker.isOpen = false | |
}) | |
} | |
if (!isMobile) { | |
filePicker.isOpen = true | |
log('πππ opening picker') | |
} else { | |
filePicker.isMobileDialogOpen = true | |
log('πππ opening picker dialog') | |
} | |
}) | |
}) | |
if (!isMobile) { | |
listen(window, 'focus', () => { | |
if (document.hasFocus()) filePicker.isOpen = false | |
// console.log('π΅ FOCUS', document.hasFocus()) | |
}) | |
} | |
listen(document, visibility.event, () => { | |
log('π’ VISIBILITY CHANGE') | |
const [visible] = getVisibility() | |
const { isMobileDialogOpen, isOpen } = filePicker | |
const isFocused = visible || isMobileDialogOpen || isOpen | |
const hasChanged = page.isFocused !== isFocused | |
page.isFocused = isFocused | |
page.isVisible = visible | |
if (!hasChanged) return | |
callback({ | |
isFocused, | |
page, | |
filePicker | |
}) | |
}) | |
return () => { | |
// console.log('UNMOUNT usePageFocus', listeners) | |
listeners.forEach(rm => rm()) | |
} | |
}, deps) | |
} | |
const hasDocument = typeof document !== 'undefined' | |
const vendorEvents = [ | |
{ | |
hidden: 'hidden', | |
event: 'visibilitychange', | |
state: 'visibilityState' | |
}, | |
{ | |
hidden: 'webkitHidden', | |
event: 'webkitvisibilitychange', | |
state: 'webkitVisibilityState' | |
}, | |
{ | |
hidden: 'mozHidden', | |
event: 'mozvisibilitychange', | |
state: 'mozVisibilityState' | |
}, | |
{ | |
hidden: 'msHidden', | |
event: 'msvisibilitychange', | |
state: 'msVisibilityState' | |
}, | |
{ | |
hidden: 'oHidden', | |
event: 'ovisibilitychange', | |
state: 'oVisibilityState' | |
} | |
] | |
const isSupported = hasDocument && Boolean(document.addEventListener) | |
const visibility = (() => { | |
if (!isSupported) return null | |
for (let event of vendorEvents) { | |
if (event.hidden in document) return event | |
} | |
// otherwise it's not supported | |
return null | |
})() | |
export const getVisibility = () => { | |
if (!visibility) return [true, 'visible'] | |
const { hidden, state } = visibility | |
return [!document[hidden], document[state]] | |
} | |
const isSupportedLocal = isSupported && visibility | |
export default usePageFocus |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment