Last active
April 18, 2018 12:35
-
-
Save dmail/78bf6d46bcea7f494053dac64773b6d4 to your computer and use it in GitHub Desktop.
track focus reason
This file contains hidden or 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
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, | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Interesting page with a lot of different focsable elements: https://wicg.github.io/focus-visible/demo/