focus-hunter
v1.0.12
Published
A tiny focus trapping utility that respects shadow DOMs and slots.
Downloads
218
Maintainers
Readme
Purpose
Focus trapping made easy for things like Dialogs.
Why?
Because focus trapping sucks. But its a necessary evil.
Demo
https://konnorrogers.github.io/focus-hunter
Prior Art
Focus Trap was attempted to be used, but was quite big (~5kb) and didn't handle multiple levels of shadow DOM. It is however a big inspiration for this library.
This solution has been largely extracted from Shoelace
Differences from Focus Trap
Focus Hunter doesn't aim to do everything. It tries its best to keep a small minimal API and get out of your way. This is reflected in bundle size.
focus-hunter is ~1.5kb minified + gzipped.
focus-trap is ~5.5kb minified + gzipped.
Installation
npm install focus-hunterAdding a trap
// Create a trap
const trap = new Trap({ rootElement: document.querySelector("my-trap") })
// Start the trap
trap.start()
// Stop the trap
trap.stop()All Options
const trap = new Trap({
rootElement,
preventScroll, // Passed to `element.focus({ preventScroll })` for programmatically focused elements
})Multiple Traps
Focus Trap is allowed to have multiple traps. It keeps track of the stacks using window.focusHunter.trapStack which
is implemented via a Set.
There is also a stack of rootElements at window.focusHunter.rootElementStack
There 2 stacks are checked when you call trap.start() to ensure the rootElement isn't already being trapped and that
the trap isn't already active.
window.focusHunter.trapStack // => Set
window.focusHunter.rootElementStack // => SetA note on iframes
While the focus trap can get to an <iframe> it cannot find elements within a cross origin iframe
so they are excluded from the focus trap.
Differences from Shoelace
This library is largely me experimenting with generators. Beyond internal implementation details, here are some differences:
- // Elements with aria-disabled are not tabbable
- if (el.hasAttribute('aria-disabled') && el.getAttribute('aria-disabled') !== 'false') {
- return false;
- }The above was removed from exports/tabbable.js because aria-disabled elements are tabbable.
+ // Anchor tags with no hrefs arent focusable.
+ // This is focusable: <a href="">Stuff</a>
+ // This is not: <a>Stuff</a>
+ if ("a" === tag && el.getAttribute("href") == null) return falseWhile not a big deal, anchor elements without an href attribute were getting tripped up.
So we added a check to make sure it has an href.
+iframe, object, embedThe additional elements were found here: https://github.com/gdkraus/accessible-modal-dialog/blob/d2a9c13de65028cda917279246346a277509fda0/modal-window.js#L38
Structure
exports/ is publicly available files
internal/ is...well...internal.
exports and internal shouldn't write their own .d.ts that are co-located.
types/ is where you place your handwritten .d.ts files.
