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

@hdsydsvenskan/local-modal

v0.5.0

Published

A reusable modal dialog component, initially loosely based on the [WAI-ARIA dialog examples](https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/dialog.html), with added considerations for current screen reader behaviors (as far as can reasonab

Downloads

467

Readme

local-modal

A reusable modal dialog component, initially loosely based on the WAI-ARIA dialog examples, with added considerations for current screen reader behaviors (as far as can reasonably be accomodated) – and, most notably, falling back to focusing the dialog element itself if a suitable first focus point cannot be established.

In general, this implementation has a few different parts:

  • A modal dialog implementation
  • ...which implements focus management via a focus trap implementation
  • ...with optionally, backdrop behavior implementation
  • ...and, also optionally, a scroll-locking implementation
  • A ”modal controller“ which opens, replaces or closes modals on the page, managing the overall state.
  • A data-attribute-API, which allows for easily building controls to open/close/replace modals when interacting with buttons etc.

These parts are intended to be as loosely coupled as possible.

Current status

Basically functional, but needs testing

This is what we’re aiming for in a 1.0 release:

Design goals

  • [x] Simple to use via custom elements
  • [x] Well-documented in API and usage instructions
  • [x] Data-attribute api for bits outside modal itself
  • [ ] Optional dependency injection, in order to re-use e.g. already existing polyfills or helpers - somewhat undecided/unfinished
  • [x] Neutral with regards to styling
  • [x] Prepared for async behaviors (animating in/out etc) and callbacks/events – somewhat undecided/unfinished

Feature set

  • [x] Proper handling of focus (tab order, focusing inside modal when opened, focus back to opener when closed)
  • [x] Handling replacing a modal
  • [x] Handling opening up and closing nested modals (potentially with limitations)
  • [x] Managing ARIA states for modality in an automated way
  • [x] Handling of backdrop element
  • [ ] Having (and passing) a robust set of unit- and end-to-end tests, in several browsers. – has reasonable coverage, needs more e2e-tests
  • [ ] Compatibility tested with at least 2 popular browser/screen reader combos, on 2 different platforms – performs reasonably in Safari+VoiceOver/Mac so far

Installation and usage instructions

Installing

Install the component using yarn add @hdsydsvenskan/local-modal or npm install @hdsydsvenskan/local-modal.

Importing

You can use the component and its pieces in a few different ways. The easiest way:

import '@hdsydsvenskan/local-modal';

...imports the index file, which sets up the modal custom element along with its controller and data--attribute API.

This modal is as small as possible, and does not include a backdrop implementation or a scroll lock implementation.


Please note that this may demand more of how you implement e.g. styling in order to make the modal accessible & usable.


What it does give your is the following:

  • The ability to use <local-modal>-elements in your code and automatically have them act as modal dialogs
  • A delegated click handler which will allow you to control any modal on the page via the data-local-modal API.

Kitchen sink example

For full control over imports and which pieces get used, you can create your own setup, preferrably in its own file like this:

import modalDialogFactory from '@hdsydsvenskan/local-modal/src/modal-dialog';
import ModalController from '@hdsydsvenskan/local-modal/src/modal-controller';
import { FocusTrap } from '@hdsydsvenskan/local-modal/src/focus-trap';
import Backdrop from '@hdsydsvenskan/local-modal/src/backdrop';
import ScrollLock from '@hdsydsvenskan/local-modal/src/scroll-lock';

export const ModalDialog = modalDialogFactory({
  FocusTrap,
  BackDrop,
  ScrollLock
});

export const controller = new ModalController();

Then, your can import your modal class elswhere, and define your element name as well as set up the data- api.

import { ModalDialog } from './my-modal';
import { dataApiSetup } from '@hdsydsvenskan/local-modal/src/data-attr-api';

dataApiSetup();

window.customElements.define('my-local-modal', ModalDialog);

Using the modal

Custom element markup requirements

The following attributes are required when using the custom element:

  • A unique ID in the id attribute
  • The hidden boolean attribute

The following patterns are strongly recommended:

  • The aria-labelledby attribute, with an IDRef value pointing to a heading element inside the modal of the same ID.
  • A heading element (visible, or accessible only by screen readers) as early as possible in the modal contents, describing the purpose of the dialog.

Example markup:

<local-modal id="modal-example-a" hidden aria-labelledby="modal-example-a-heading">
  <h2>Here’s what this modal does</h2>
  <p>Further text content</p>
  <div>
    <label for="modal-a-text">Enter your name:</label>
    <input type="text" id="modal-a-text" name="name">
  </div>
  <button type="submit">Save</button>
</local-modal>

Data-attribute API

To control the modal, you need to trigger custom events that get picked up by the modal controller instance. The modal controller then opens, closes or replaces the relevant modals.

In order to help you trigger these events, a simple data--attribute API is provided. It works something like this:

<button data-local-modal="open modal-example-a">Update settings</button>

...where the value of the attribute is split by spaces and converted to data sent via the event.

Clicking this button will trigger an event for the controller to open the modal with ID modal-example-a.

If you want to close a modal, put a button inside the modal and change the value to close:

<button data-local-modal="close">Cancel</button>

If there is a button inside an already open modal and you want to replace it with another, use the following syntax:

<button data-local-modal="replace modal-example-a">Show help text</button>

There is a small algorithm for what gains focus when the modal is opened. If you want to tell it to focus a specific element when opening, pass the id of that element as the third bit of the value.

<button data-local-modal="open modal-example-a modal-a-text">Update settings</button>

...which would then automatically focus the input field when the modal opens.

Finally, you can adress where focus should go when the modal is closed – by default, the opening element receives focus when the modal closes, but if you pass another ID, the modal will find that element and configure itself to focus it once the modal closes:

<button data-local-modal="open modal-example-a modal-a-text some-id-to-focus-after-closing">Update settings</button>

Note This edge case is a bit clunky, and currently depends on all pieces of the data-API parameters being filled in. That said, it could be useful if the button somehow is inaccessible after the modal is closed.


Custom event API

The same type of events triggered via the data-attribute declarative API can also be triggered programatically.

See the src/data-attr-api.js file for examples, but here's an example of how to open a certain modal:

const id = 'my-element-id';
const focusAfter = 'some-element-id';
const focusFirst = 'some-other-element-id';

const controllerEvent = new CustomEvent(`lcl-modal:open`, {
    detail: {
      id,
      focusAfter, // optional
      focusFirst // optional
    }
  });
  document.dispatchEvent(controllerEvent);

Tests

Unit tests

  • Tests are run on Mocha via the Karma test-runner (and karma-mocha plugin).
  • JS code is bundled before tests via Parcel (karma-parcel plugin).
  • Mocha has chai, chai-as-expected (expectations, async), sinon and sinon-chai (test sandbox stuff) integrations.
  • Karma launches headless browsers via Playwright – similar (very much so) to Puppeteer, but running on both Chromium, Firefox and WebKit (karma-launcher-webkit and karma-launcher-firefox plugins).
  • Coverage is reported via Istanbul (and the karma-coverage plugin), in console and to the .coverage dir.
  • NOTE: due to a problem with how Parcel (mis-)reads advanced features of .babelrc files, there is a small CLI utility to backup, change and restore the .babelrc when running tests. Not ideal, but works, for now. The issue is reportedly fixed in Parcel@v2, but so far there is no karma-parcel plugin compatible with that.

End-to-end tests

  • Tests are run on Mocha/chai/sinon, which launches headless browsers via Playwright
  • Currently only runs one browser (currently Chromium) but can easily be configured to run more via BROWSER env var. For example, if you want to run the e2e-tests via Firefox, you could do BROWSER=firefox yarn test-e2e. Supports firefox and webkit env-var names, default is Chromium.
  • Starts a dev-server via Parcel, closes it when done.