Skip to content

Instantly share code, notes, and snippets.

@alex-cory
Last active June 12, 2022 23:13
Show Gist options
  • Save alex-cory/0b0b59cef8cd402b751e6e33bef12238 to your computer and use it in GitHub Desktop.
Save alex-cory/0b0b59cef8cd402b751e6e33bef12238 to your computer and use it in GitHub Desktop.
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