npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@davidevmod/focus-trap

v4.0.1

Published

A tiny and performant library to trap the focus within your DOM elements.

Downloads

9

Readme

@davidevmod/focus-trap

ci codecov npm version license

A tiny and performant library to trap the focus within your DOM elements.

Features

  • Trap the focus within a group of DOM elements
  • Choose an element receiving the initial focus
  • Prevent clicks on elements outside of the trap
  • Demolish a trap after an Esc key press
  • Focus a given element once the trap is demolished
  • Build, demolish, pause and resume your focus trap

Contents

Installation

# Install with
npm add @davidevmod/focus-trap
# or
yarn add @davidevmod/focus-trap
# or
pnpm add @davidevmod/focus-trap

Usage

import { focusTrap } from '@davidevmod/focus-trap'; and call it with an argument of type TrapArg:

import { focusTrap } from '@davidevmod/focus-trap';

const myElement = document.getElementById('myID');

// You can build a focus trap in different ways:

focusTrap(['myID']);

focustrap([myElement]);

focustrap({ roots: ['myID'] });

focustrap({ roots: [myElement] });

// All of the above calls would build the very same trap.

// Pause the trap.
focusTrap('PAUSE');

// Resume the trap.
focusTrap('RESUME');

// Demolish the trap.
focusTrap('DEMOLISH');

Default behaviour

By default, when building a focus trap by providing only an array of roots, this is what happens:

  • The focus is given to the first tabbable element contained in the roots
  • Tab and Shift+Tab keys cycle through the roots' tabbable elements
  • Click events outside of the focus trap are prevented
  • Whenever the Esc key is pressed, the trap is demolished
  • Once the trap is demolished, focus is returned to what was the activeElement at the time the trap was built

API

type Focusable = HTMLElement | SVGElement;

type Roots = (Focusable | string)[];

interface TrapConfig {
  roots: Roots;
  initialFocus?: boolean | Focusable | string;
  returnFocus?: boolean | Focusable | string;
  lock?: boolean;
  escape?: boolean;
}

type TrapAction = 'PAUSE' | 'RESUME' | 'DEMOLISH';

type TrapArg = Roots | TrapConfig | TrapAction;

TrapConfig

You can tweak the behaviour of your trap by calling focusTrap with a TrapConfig object:

| Property | Required | Type | Default value | | ------------ | -------- | -------------------------------- | :-----------: | | roots | Yes | (Focusable \| string)[] | - | | initialFocus | No | boolean \| Focusable \| string | true | | returnFocus | No | boolean \| Focusable \| string | true | | lock | No | boolean | true | | escape | No | boolean | true |

  • roots
    The array of elements (and/or IDs) within which the focus has to be trapped.

  • initialFocus
    The element receiving the focus as soon as the trap is built.
    By default it will be the first tabbable element in the trap.
    You can provide your designated element (or ID) or the boolean false to switch off the default behaviour.

  • returnFocus
    The element that will receive the focus once the trap is demolished.
    By default it will be the element that was the activeElement right before the trap was built.
    You can provide your designated element (or ID) or the boolean false to switch off the default behaviour.

  • lock
    The behavior for clicks outside of the trap.
    By default clicks on elements outside of the trap are prevented.
    You can provide the boolean false to switch off the default behaviour.

    Note
    Only mousedown, touchstart, click and the browser default behavior are prevented.
    So, if you need to, you can make an element outside of the trap clickable even when lock is true, for example, by listening for mouseup events.

  • escape
    The behaviour for Esc key presses.
    By default the trap is demolished Whenever the Esc key is pressed.
    You can provide the boolean false to switch off the default behaviour.

TrapAction

Calling focusTrap with "PAUSE", "RESUME" or "DEMOLISH" will pause, resume or demolish the focus trap.

Return value

A shollow copy of the NormalisedTrapConfig used internally by the library, which is the provided TrapConfig with IDs resolved to actual elements and default values set:

type Focusable = HTMLElement | SVGElement;

interface NormalisedTrapConfig {
  roots: Focusable[];
  initialFocus: boolean | Focusable;
  returnFocus: Focusable | null;
  lock: boolean;
  escape: boolean;
}

Note
The normalised roots are updated at every Tab key press to account for any relevant mutaion (eg, elements attached to or detached from the DOM) so they only represent a snapshot of an ever changing array of elements.

This value is rarely useful, it may be used to eg, implement a stack of focus traps.

Dependencies

The only dependency is true-myth, used simply to liberate funcitons from exceptions (as side effects) by including them in the return value.

It makes the codebase more robust and self-explanatory.

Browser Support

The library can run in any major browser.

Note
The codebase is tested only against Chromium-based browsers. That's because the e2e tests use Cypress, which does not support native browser events (in particular Tab key presses), problem that is solved by using the Cypress Real Events plugin which does allow for native browser events in Cypress, but only in the presence of Chrome Devtools.

Demo

There is a live demo in which you can play around with focus traps to appreciate the way they work: https://focus-trap-demo.vercel.app/

The source code can be found in this repo.

Special thanks :heart:

The logic for the treatement of edge cases, in matter of browser consistency, regarding tab indexes and tabbability (found in tabbability.ts) is took from tabbable.

This small library has been around for many years and, at the time of writing, can boast 180 dependant packages and one million weekly downloads while having zero open issues :scream: which makes feel safe about the reliability of the edge case logic.

The reason why tabbable is not being used as a dependency is that it would be an overkill and @davidevmod/focus-trap aims to be as simple and lightweight as possible.

Also much obliged to the whole focus-trap project in general, which has been a huge point of reference.

:earth_americas: Contributing

Any kind of contribution is more than welcome.
Check out the CONTRIBUTING.md to get started.