Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

accessible-modal-dialog

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

accessible-modal-dialog - npm Package Compare versions

Comparing version 1.0.0 to 1.0.1

136

accessible-modal-dialog.js
(function (global) {
'use strict';
// Helper function to check if a node is visible in the viewport
function isVisible (node) {
return !!(node.offsetWidth || node.offsetHeight || node.getClientRects().length);
}
// Helper function to get all focusable children from a node
function getFocusableChildren (node) {
var focusableElements = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([value="-1"])'];
var focusableElements = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex="-1"])'];
return $$(focusableElements.join(','), node).filter(function (child) {
return isVisible(child);
return !!(child.offsetWidth || child.offsetHeight || child.getClientRects().length);
});
}
// Helper function to get first node in context matching selector
function $ (selector, context) {
return (context || document).querySelector(selector);
}
// Helper function to get all nodes in context matching selector as an array
function $$ (selector, context) {
var nodes = (context || document).querySelectorAll(selector);
return nodes.length ? Array.prototype.slice.call(nodes) : [];
return Array.prototype.slice.call((context || document).querySelectorAll(selector) || []);
}

@@ -32,4 +21,3 @@

var focusableChildren = getFocusableChildren(node);
var focusedItem = document.activeElement;
var focusedItemIndex = focusableChildren.indexOf(focusedItem);
var focusedItemIndex = focusableChildren.indexOf(document.activeElement);

@@ -45,46 +33,2 @@ if (event.shiftKey && focusedItemIndex === 0) {

// Helper function to bind listeners for a Modal instance
function bindListeners (instance) {
instance.$openers.forEach(function ($opener) {
$opener.addEventListener('click', function (event) {
event.stopPropagation();
instance.show();
});
});
instance.$closers.forEach(function ($closer) {
$closer.addEventListener('click', function (event) {
instance.hide();
});
});
document.addEventListener('keydown', function (event) {
if (instance.shown === false) return;
if (event.which === 27) {
event.preventDefault();
instance.hide();
}
if (event.which === 9) {
trapTabKey(instance.$modal, event);
}
});
document.body.addEventListener('focus', function (event) {
if (instance.shown && !instance.$modal.contains(event.target)) {
setFocusToFirstItem(instance.$modal);
}
}, true);
document.addEventListener('click', function (event) {
if (instance.shown === false) return;
instance.hide();
});
instance.$modal.addEventListener('click', function (event) {
event.stopPropagation();
});
}
// Helper function to focus first focusable item in node

@@ -102,44 +46,50 @@ function setFocusToFirstItem (node) {

* @param {Node} main - Main element of the page
* @param {Node} overlay - Overlay element
*/
var Modal = function (node, main, overlay) {
this.$main = main || $('#main');
this.$overlay = overlay || $('#modal-overlay');
this.$modal = node;
this.$openers = $$('[data-modal-show="' + this.$modal.id + '"]');
this.$closers = $$('[data-modal-hide]', this.$modal)
.concat($$('[data-modal-hide="' + this.$modal.id + '"]'));
var Modal = function (node, main) {
var that = this;
main = main || document.querySelector('#main');
this.shown = false;
bindListeners(this);
};
$$('[data-modal-show="' + node.id + '"]').forEach(function (opener) {
opener.addEventListener('click', show);
});
/**
* Method to display the modal
*/
Modal.prototype.show = function () {
this.shown = true;
$$('[data-modal-hide]', node).concat($$('[data-modal-hide="' + node.id + '"]')).forEach(function (closer) {
closer.addEventListener('click', hide);
});
this.$main.setAttribute('aria-hidden', 'true');
this.$modal.setAttribute('aria-hidden', 'false');
this.$overlay.style.display = 'block';
this.$modal.style.display = 'block';
document.addEventListener('keydown', function (event) {
if (that.shown && event.which === 27) {
event.preventDefault();
hide();
}
focusedElementBeforeModal = document.activeElement;
setFocusToFirstItem(this.$modal);
};
if (that.shown && event.which === 9) {
trapTabKey(node, event);
}
});
/**
* Method to hide the modal
*/
Modal.prototype.hide = function () {
this.shown = false;
document.body.addEventListener('focus', function (event) {
if (that.shown && !node.contains(event.target)) {
setFocusToFirstItem(node);
}
}, true);
this.$modal.setAttribute('aria-hidden', 'true');
this.$main.setAttribute('aria-hidden', 'false');
this.$overlay.style.display = 'none';
this.$modal.style.display = 'none';
this.show = show;
this.hide = hide;
focusedElementBeforeModal.focus();
function show () {
that.shown = true;
node.setAttribute('aria-hidden', 'false');
main.setAttribute('aria-hidden', 'true');
focusedElementBeforeModal = document.activeElement;
setFocusToFirstItem(node);
}
function hide () {
that.shown = false;
node.setAttribute('aria-hidden', 'true');
main.setAttribute('aria-hidden', 'false');
focusedElementBeforeModal.focus();
}
};

@@ -146,0 +96,0 @@

{
"name": "accessible-modal-dialog",
"version": "1.0.0",
"version": "1.0.1",
"description": "A tiny script to make modal dialogs accessible to assistive technology users.",
"homepage": "https://github.com/edenspiekermann/accessible-modal-dialog",
"main": "accessible-modal-dialog.js",
"keywords": ["modal", "dialog", "accessibility", "a11y", "focus"],
"keywords": [
"modal",
"dialog",
"accessibility",
"a11y",
"focus"
],
"author": "Hugo Giraudel <h.giraudel@de.edenspiekermann.com> (www.edenspiekermann.com/people/hugo-giraudel)",
"files": ["accessible-modal-dialog.js"],
"files": [
"accessible-modal-dialog.js"
],
"scripts": {
"example": "cp accessible-modal-dialog.js example/ && git subtree push --prefix example origin gh-pages",
"copy": "cp accessible-modal-dialog.js example/main.js",
"minify": "uglifyjs accessible-modal-dialog.js -o accessible-modal-dialog.min.js -c -m",
"build": "npm run minify && npm run copy",
"predeploy": "npm run build",
"deploy": "git subtree push --prefix example origin gh-pages",
"lint": "semistandard accessible-modal-dialog.js",
"minify": "uglifyjs accessible-modal-dialog.js -o accessible-modal-dialog.min.js -c -m"
"test": "casperjs test tests/index.js"
},
"devDependencies": {
"casperjs": "^1.1.0-beta5",
"semistandard": "^7.0.5",

@@ -17,0 +30,0 @@ "uglify-js": "^2.6.1"

@@ -9,16 +9,100 @@ # The Incredible Accessible Modal Window

![CodeShip test status](https://codeship.com/projects/7dd06120-b6f8-0133-792c-265d84c132f8/status?branch=master)
## What’s new in Edenspiekermann’s version?
- No more dependency to jQuery (vanilla JS only);
- No more dependency (not even jQuery);
- Possibility to have several different modals on the page;
- DOM API for modal openers (`data-modal-show="modal-id"`) and closers (`data-modal-hide`);
- JS API to manually show and hide modals (`modal.show()`, `modal.hide()`);
- JS API to know whether a modal is hidden or shown (`modal.shown`);
- JS API to manually show and hide modals as well as knowing their status (`modal.show()`, `modal.hide()`, `modal.shown`);
- Addition of `[tabindex]:not([value="-1"])` to focusable elements;
- Cleaner code.
- No more `display` manipulation in JS, the hiding mechanism is entirely up to the CSS layer (using `[aria-hidden]` selectors);
- Full test coverage with [CasperJS](http://casperjs.org) and [CodeShip](https://codeship.com);
- Cleaner code resulting in only 650 bytes (0.65Kb!) once gzipped.
## Initialising the modal
*Note: the script should run seamlessly in Internet Explorer 9 and above.*
## Install
```
npm install accessible-modal-dialog --save
```
Or you could also copy/paste the script in your project directly, but you will be disconnected from this repository, making it hard for your to get updates.
## Usage
You will find a concrete demo in the [example](https://github.com/edenspiekermann/accessible-modal-dialog/tree/master/example) folder of this repository, but basically here is the gist:
### HTML
Here is the basic markup, which can be enhanced. Pay extra attention to the comments.
```html
<!--
Main container related notes:
- It doesn’t have to be a `main` element, however this is recommended.
- It doesn’t have to have the `aria-label="Content"` attribute, however this is recommended.
- It can have a different id than `main`, however you will have to pass it as a second argument to the Modal instance. See further down.
-->
<main id="main" aria-label="Content">
<!--
Here lives the main content of the page.
-->
</main>
<!--
Modal container related notes:
- It is not the actual modal, just the container with which the script interacts.
- It has to have the `aria-hidden="true"` attribute.
- It can have a different id than `my-accessible-modal`.
-->
<div id="my-accessible-modal" aria-hidden="true">
<!--
Overlay related notes:
- It has to have the `tabindex="-1"` attribute.
- It doesn’t have to have the `data-modal-hide` attribute, however this is recommended. It hides the modal when clicking outside of it.
-->
<div tabindex="-1" data-modal-hide></div>
<!--
Modal content relates notes:
- It is the actual visual modal element.
- It has to have the `role="dialog"` attribute.
- It doesn’t have to have a direct child with the `role="document"`, however this is recommended.
-->
<div role="dialog">
<div role="document">
<!--
Here lives the main content of the modal.
-->
<!--
Closing button related notes:
- It does have to have the `type="button"` attribute.
- It does have to have the `data-modal-hide` attribute.
- It does have to have an aria-label attribute if you use an icon as content.
-->
<button type="button" data-modal-hide aria-label="Close this modal">
&times;
</button>
</div>
</div>
</div>
```
### CSS
You will have to implement some styles for the modal to “work” (visually speaking). The script itself does not take care of any styling whatsoever, not even the `display` property. It basically mostly toggles the `aria-hidden` attribute on the main element and the modal itself. You can use this to show and hide the modal:
```css
.modal[aria-hidden="true"] {
display: none;
}
```
### JavaScript
```javascript

@@ -32,6 +116,6 @@ // Get the modal element (with the accessor method you want)

The script assumes the main document of the page has a `main` id, and the overlay element has a `modal-overlay` id. If it is not the case, you can pass these two nodes respectively as second and third arguments to the `Modal` constructor:
The script assumes the main document of the page has a `main` id. If it is not the case, you can pass the main node as second argument to the `Modal` constructor:
```javascript
var modal = new Modal(modalEl, mainEl, overlayEl);
var modal = new Modal(modalEl, mainEl);
```

@@ -52,9 +136,9 @@

```html
<button type="button" data-modal-hide title="Close the modal">&times;</button>
<button type="button" data-modal-hide aria-label="Close the modal">&times;</button>
```
The following button will close the modal with the `my-awesome-modal` id when interacted with. Given that the only focusable elements when the modal is open are the focusable children of the modal itself, it seems rather unlikely that you will ever need this but in case you do, well you can.
The following button will close the modal with the `my-awesome-modal` id when interacted with. Given that the only focusable elements when the modal is open are the focusable children of the modal itself, it seems rather unlikely that you will ever need this but in case you do, well you can.g
```html
<button type="button" data-modal-hide="my-awesome-modal" title="Close the modal">&times;</button>
<button type="button" data-modal-hide="my-awesome-modal" aria-label="Close the modal">&times;</button>
```

@@ -72,2 +156,10 @@

## Tests
[CasperJS](http://casperjs.org) is being used to run browser tests. CasperJS has some [dependencies](http://docs.casperjs.org/en/latest/installation.html#prerequisites) that have to be installed manually. Be sure to satisfy them before running the tests.
```
npm test
```
## Deploy example

@@ -78,3 +170,3 @@

```
npm run example
npm run deploy
```
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc