A tiny focus trapping utility that respects shadow DOMs and slots.
npm install focus-hunterFocus trapping made easy for things like Dialogs.
Because focus trapping sucks. But its a necessary evil.
- 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
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.
``bash`
npm install focus-hunter
`js
// Create a trap
const trap = new Trap({ rootElement: document.querySelector("my-trap") })
// Start the trap
trap.start()
// Stop the trap
trap.stop()
`
`jselement.focus({ preventScroll })
const trap = new Trap({
rootElement,
preventScroll, // Passed to for programmatically focused elements`
})
Focus Trap is allowed to have multiple traps. It keeps track of the stacks using window.focusHunter.trapStack whichSet
is implemented via a .
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.
`js`
window.focusHunter.trapStack // => Set
window.focusHunter.rootElementStack // => Set
While the focus trap can get to an
This library is largely me experimenting with generators. Beyond internal implementation details, here are some differences:
`diff`
- // 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.
`diff`
+ // Anchor tags with no hrefs arent focusable.
+ // This is focusable: Stuff
+ // This is not: Stuff
+ if ("a" === tag && el.getAttribute("href") == null) return false
While not a big deal, anchor elements without an href attribute were getting tripped up.href
So we added a check to make sure it has an .
`diff`
+iframe, object, embed
The additional elements were found here:
exports/ is publicly available filesinternal/ 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.