Skip to content

Instantly share code, notes, and snippets.

@dmail
Last active April 18, 2018 12:35
Show Gist options
  • Save dmail/78bf6d46bcea7f494053dac64773b6d4 to your computer and use it in GitHub Desktop.
Save dmail/78bf6d46bcea7f494053dac64773b6d4 to your computer and use it in GitHub Desktop.
track focus reason
const isAncestorOf = (node, possibleDescendantNode) => {
let ancestor = possibleDescendantNode.parentNode
while (ancestor) {
if (ancestor === node) {
return true
}
ancestor = ancestor.parentNode
}
return false
}
const isDocumentOrBody = node => {
const documentElement = node.ownerDocument.documentElement
return node === documentElement || node === documentElement.body
}
const storeVolatileValue = (value) => {
let storedValue = value
setTimeout(() => {
storedValue = null
})
return () => storedValue
}
const createTracker = ({ install, matcher }) => {
let getVolatileValue
const track = () => install((reason) => {
getVolatileValue = storeVolatileValue(reason)
})
const match = focusEvent => {
const volatileValue = getVolatileValue()
if (volatileValue && matcher(focusEvent, volatileValue)) {
return {
match: true,
value: volatileValue,
}
}
return {
match: false,
value: undefined,
}
}
return { track, match }
}
const trackFocusReason = (node) => {
const mousedownTracker = createTracker({
install: (storeVolatile) => {
node.addEventListener("mousedown", storeVolatile, true)
return () => node.removeEventListener("mousedown", storeVolatile, true)
},
matcher: (focusEvent, mousedownEvent) => {
const focusTarget = focusEvent.target
const mousedownTarget = mousedownEvent.target
return focusTarget === mousedownTarget || isAncestorOf(focusTarget, mousedownTarget)
},
})
const touchstartTracker = createTracker({
install: (storeVolatile) => {
node.addEventListener("touchstart", storeVolatile, true)
return () => node.removeEventListener("touchstart", storeVolatile, true)
},
matcher: (focusEvent, touchstartEvent) => {
const focusTarget = focusEvent.target
const touchstartTarget = touchstartEvent.target
return focusTarget === touchstartTarget || isAncestorOf(focusTarget, touchstartTarget)
},
})
const keydownTracker = createTracker({
install: (storeVolatile) => {
node.addEventListener("keydown", storeVolatile, true)
return () => node.removeEventListener("keydown", storeVolatile, true)
},
matcher: (focusEvent, keydownEvent) => {
const keydownTarget = keydownEvent.target
const focusTarget = focusEvent.target
if (focusTarget === keydownTarget) {
return false
}
const focusRelatedTarget = focusEvent.relatedTarget
if (focusRelatedTarget && focusRelatedTarget !== keydownTarget) {
return false
}
if (!focusRelatedTarget && isDocumentOrBody(keydownTarget) === false) {
return false
}
const key = keydownEvent.keyCode ? keydownEvent.keyCode : keydownEvent.which
if (key === 9) {
return true
}
return false
},
})
const focusTracker = createTracker({
install: (storeVolatile) => {
const { focus } = HTMLElement.prototype
HTMLElement.prototype.focus = function() {
storeVolatile({
type: "focus-programmatic",
args: arguments,
})
return focus.apply(this, arguments)
}
return () => {
HTMLElement.prototype.focus = focus
}
},
matcher: () => true,
})
const trackers = [focusTracker, mousedownTracker, touchstartTracker, keydownTracker]
const install = () => {
const uninstallers = trackers.map(({ track }) => track())
return () => uninstallers.map(uninstaller => uninstaller())
}
const uninstall = install()
const getFocusRelatedEvent = (focusEvent) => {
let matchResult
trackers.find(({ match }) => {
matchResult = match(focusEvent)
return matchResult.match
})
return matchResult && matchResult.match ? matchResult.value : null
}
return {
getFocusRelatedEvent,
uninstall,
}
}
const { getFocusRelatedEvent } = trackFocusReason(document.body)
document.body.addEventListener(
"focus",
(e) => {
const relatedEvent = getFocusRelatedEvent(e)
console.log(
`focus on ${e.target.nodeName} given because ${
relatedEvent ? relatedEvent.type : "unknown (probably tab outside of document)"
}`,
)
},
true,
)
@dmail
Copy link
Author

dmail commented Apr 13, 2018

Interesting page with a lot of different focsable elements: https://wicg.github.io/focus-visible/demo/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment