|
/*! |
|
* A minimal polyfill for :focus-visible using a .focus-visible class |
|
* Automatically applies to document and Shadow DOM |
|
*/ |
|
|
|
(function focusVisiblePolyfill() { |
|
const KEY_SET = new Set([ |
|
"Tab", |
|
"ArrowUp", |
|
"ArrowDown", |
|
"ArrowLeft", |
|
"ArrowRight", |
|
"Enter", |
|
" " |
|
]); |
|
const trackedRoots = new WeakSet(); |
|
let usingKeyboard = false; |
|
|
|
const handleKey = (e) => (usingKeyboard = KEY_SET.has(e.key)); |
|
const handlePointer = () => (usingKeyboard = false); |
|
const handleFocus = (e) => |
|
e.target.classList.toggle("focus-visible", usingKeyboard); |
|
const handleBlur = (e) => e.target.classList.remove("focus-visible"); |
|
|
|
const attachFocusTracking = (root) => { |
|
if (trackedRoots.has(root)) return; |
|
trackedRoots.add(root); |
|
root.addEventListener("focusin", handleFocus); |
|
root.addEventListener("focusout", handleBlur); |
|
}; |
|
|
|
const processAddedNodes = (nodes) => { |
|
const shadowRootsToAttach = new Set(); |
|
nodes.forEach((node) => { |
|
if (node.nodeType !== Node.ELEMENT_NODE) return; |
|
if (node.shadowRoot) shadowRootsToAttach.add(node.shadowRoot); |
|
node.querySelectorAll?.("[data-has-shadow]").forEach((el) => { |
|
if (el.shadowRoot) shadowRootsToAttach.add(el.shadowRoot); |
|
}); |
|
}); |
|
shadowRootsToAttach.forEach((root) => { |
|
attachFocusTracking(root); |
|
setupShadowObserver(root); |
|
}); |
|
}; |
|
|
|
const setupShadowObserver = (root) => { |
|
const observer = new MutationObserver((mutations) => { |
|
const allAddedNodes = mutations.flatMap((m) => [...m.addedNodes]); |
|
if (window.requestIdleCallback) { |
|
requestIdleCallback(() => processAddedNodes(allAddedNodes)); |
|
} else { |
|
processAddedNodes(allAddedNodes); |
|
} |
|
}); |
|
observer.observe(root, { childList: true, subtree: true }); |
|
}; |
|
|
|
document.addEventListener("keydown", handleKey); |
|
document.addEventListener("pointerdown", handlePointer, { passive: true }); |
|
attachFocusTracking(document); |
|
document.querySelectorAll("[data-has-shadow]").forEach((el) => { |
|
if (el.shadowRoot) attachFocusTracking(el.shadowRoot); |
|
}); |
|
setupShadowObserver(document); |
|
})(); |