diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..a274f9c
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Changelog
+
+## v2.0.0
+
+- Removed `onCreate`, `onDestroy`, `onOpen` and `onClose` callbacks.
+- Added `create`, `destroy`, `open` and `close` events.
+- Added event subscription via `on` and `off` methods.
+
+## v1.0.0
+
+- Initial release
diff --git a/README.md b/README.md
index 1bde382..9011235 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,26 @@
# Dialog
-- Automatically handles adding and toggling the appropiate aria roles, states and properties.
-- Can be used as an accesible baseline for any dialog or dialog like like UI e.g lightbox's or fullscreen menus.
+Dialog provides a bare bones baseline for building accessible modals or modal like UI elements such as fullscreen menus.
[View demo on CodePen](https://codepen.io/rynpsc/pen/YVVGdr)
## Install
-### Yarn
+### npm
```
-yarn add @rynpsc/dialog
+npm install @rynpsc/dialog
```
-### NPM
+## Usage
-```
-npm install --save @rynpsc/dialog
-```
+The `Dialog` constructor takes three parameters:
-## Usage
+* `dialog` - The element ID of the dialog element
+* `main` - The element ID of the main page content
+* `options` - Configuration Object ([see options](#options))
-Dialog requires that the dialog element lives outside of the main page content.
+### HTML
```html
@@ -37,47 +36,28 @@ Dialog requires that the dialog element lives outside of the main page content.
```
-Alternatively the dialog element can be placed within `#main` and the script will move the `#dialog` element outside of the `#main` element.
-
-```html
-
-
-
-
-
-```
-
-The `Dialog` constructor takes three parameters:
-
-* `dialog` - The element ID of the dialog element
-* `main` - The element ID of the main page content
-* `options` - Configuration Object ([see options](#options))
+### JavaScript
```js
import Dialog from '@rynpsc/dialog';
-const dialog = Dialog(dialog, main, options);
+const dialog = dialog(dialog, main, options);
```
-Dialog does not provide and styling of its own, instead this is left to the user to implement.
+### CSS
- ```css
+```css
.dialog {
display: none;
}
-.dialog[aria-hidden="false"] {
+.dialog.is-open {
display: block;
}
```
+[For an example on how to animate the dialog see the CodePen demo](https://codepen.io/rynpsc/pen/YVVGdr).
+
## Options
```js
@@ -94,22 +74,10 @@ const dialog = Dialog('dialog', 'main', {
// Whether dialog is of type alertdialog
alert: false,
- // Callback on initialisation
- onCreate: (dialog, main) => {},
-
- // Callback on open
- onOpen: (dialog, main) => {},
-
- // Callback on close
- onClose: (dialog, main) => {},
-
- // Callback on destroy
- onDestroy: (dialog, main) => {},
+ autoInit: false,
});
```
-The callbacks , `onCreate`, `onOpen`, `onClose` and `onDestroy` each access to two parameters, `dialog` and `main` which reference the respective `HTMLElements`.
-
## API
### open
@@ -130,7 +98,7 @@ dialog.close();
### toggle
-Toggle the dialog between open and close.
+Toggle the dialog between opened and closed.
```js
dialog.toggle(force);
@@ -140,13 +108,13 @@ If the optional `force` parameter evaluates to true, open the dialog, if false,
### destroy
-Destroy the dialog.
+Destroy the dialog.
```js
dialog.destroy();
```
-Note: If relying on the library to move the `dialog` element outsideof the `main` element the method does not currently restore the `dialog` element to it's previous DOM position.
+Note: If relying on the library to move the `dialog` element outsideof the `main` element the method does not currently restore the `dialog` element to it's original DOM position.
### create
@@ -156,6 +124,22 @@ Create the dialog after destroying it.
dialog.create();
```
+### on
+
+Subscribe to an event.
+
+```js
+dialog.on(event);
+```
+
+### off
+
+Unsubscribe to an event.
+
+```js
+dialog.off(event);
+```
+
### isOpen
Returns a Boolean indicating if the dialog is currently open.
@@ -164,6 +148,60 @@ Returns a Boolean indicating if the dialog is currently open.
dialog.isOpen;
```
+### elements
+
+An object containing the dialog and main elements.
+
+```js
+const { dialog, main } = dialog.elements;
+```
+
+## Events
+
+### open
+
+Emitted when the dialog opens.
+
+```js
+dialog.on('open', listener);
+```
+
+### close
+
+Emitted when the dialog closes.
+
+```js
+dialog.on('close', listener);
+```
+
+### create
+
+```js
+dialog.on('create', listener);
+```
+
+Emitted after the dialog is created.
+
+Note in order to listen for the initial create event the dialog must be manually created via the `create()` method.
+
+```js
+const dialog = dialog(dialog, main, {
+ autoInit: false
+});
+
+dialog.on('create', () => console.log('Dialog created'));
+
+dialog.create();
+```
+
+### destroy
+
+```js
+dialog.on('destroy', listener);
+```
+
+Emitted after the dialog is destroyed.
+
## License
-MIT © 2017 [Ryan Pascoe](https://github.com/rynpsc)
+MIT © [Ryan Pascoe](https://github.com/rynpsc)
diff --git a/dist/dialog.js b/dist/dialog.js
index d5cef8a..a9ab9cc 100644
--- a/dist/dialog.js
+++ b/dist/dialog.js
@@ -1,199 +1 @@
-var defaults = {
- label: 'Dialog',
- description: '',
- focus: '',
- alert: false,
- onCreate: function onCreate(dialog, main) {},
- onOpen: function onOpen(dialog, main) {},
- onClose: function onClose(dialog, main) {},
- onDestroy: function onDestroy(dialog, main) {}
-};
-
-/**
- * List of selectors for elements that are focusable via the keyborad.
- */
-var selectors = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', 'iframe', 'object', 'embed', '[contenteditable]', '[tabindex]:not([tabindex^="-"])'];
-
-/**
- * Get a NodeList of all focusable child nodes
- * @param {HTMLElement} node
- * @return {NodeList}
- */
-function getFocusableElements(node) {
- return node.querySelectorAll(selectors);
-}
-
-/**
- * Focuses the first focusable element within a node
- * @param {HTMLElement} node
- */
-function focusFirstElement(node) {
- var nodes = node.querySelectorAll(selectors);
- nodes.length ? nodes[0].focus() : node.focus();
-}
-
-/**
- * Traps tab key within a given node
- * @param {HTMLElment} node
- */
-function trapTabKey(node, event) {
- var activeElement = document.activeElement;
- var focusableElements = getFocusableElements(node);
- var firstTabStop = focusableElements[0];
- var lastTabStop = focusableElements[focusableElements.length - 1];
-
- if (event.shiftKey && activeElement === firstTabStop) {
- lastTabStop.focus();
- event.preventDefault();
- } else if (!event.shiftKey && activeElement === lastTabStop) {
- firstTabStop.focus();
- event.preventDefault();
- }
-}
-
-function Dialog(modal, main, options) {
- var mainElement = document.getElementById(main);
- var modalElement = document.getElementById(modal);
- var config = Object.assign({}, defaults, options);
-
- var isOpen = false;
- var initialFocusedElement = null;
-
- if (!mainElement) {
- throw new Error('No element with the id "' + main + '"');
- }
-
- if (!modalElement) {
- throw new Error('No element with the id "' + modal + '"');
- }
-
- function create() {
- var role = config.alert ? 'alertdialog' : 'dialog';
-
- modalElement.setAttribute('role', role);
- modalElement.setAttribute('tabindex', -1);
- modalElement.setAttribute('aria-modal', true);
-
- toggleAriaHidden(isOpen);
-
- var matchesID = document.getElementById(config.label);
- var attr = matchesID ? 'labeledby' : 'label';
- modalElement.setAttribute('aria-' + attr, config.label);
-
- if (document.getElementById(config.description)) {
- modalElement.setAttribute('aria-describedby', config.description);
- }
-
- if (mainElement.contains(modalElement)) {
- document.body.appendChild(modalElement);
- }
-
- if (typeof config.onCreate === 'function') {
- config.onCreate(modalElement, mainElement);
- }
- }
-
- function onKeydown(event) {
- if (event.key === 'Escape' && isOpen) {
- close();
- }
-
- if (event.key === 'Tab' && isOpen) {
- trapTabKey(modalElement, event);
- }
- }
-
- function trapFocus(event) {
- if (!modalElement.contains(document.activeElement)) {
- event.preventDefault();
- focusFirstElement(modalElement);
- }
- }
-
- function toggleAriaHidden(toggle) {
- mainElement.setAttribute('aria-hidden', toggle);
- modalElement.setAttribute('aria-hidden', !toggle);
- }
-
- function setModalFocus() {
- if (config.focus instanceof HTMLElement && modalElement.contains(config.focus)) {
- config.focus.focus();
- } else {
- focusFirstElement(modalElement);
- }
- }
-
- function open() {
- if (isOpen) return;
-
- isOpen = true;
- toggleAriaHidden(isOpen);
-
- initialFocusedElement = document.activeElement;
-
- setModalFocus();
-
- if (!modalElement.contains(document.activeElement)) {
- modalElement.addEventListener('transitionend', onTransitionEnd);
- }
-
- document.addEventListener('keydown', onKeydown);
- document.addEventListener('focus', trapFocus, true);
-
- if (typeof config.onOpen === 'function') {
- config.onOpen(modalElement, mainElement);
- }
- }
-
- function close() {
- if (!isOpen) return;
-
- isOpen = false;
- toggleAriaHidden(isOpen);
-
- document.removeEventListener('keydown', onKeydown);
- document.removeEventListener('focus', trapFocus, true);
-
- initialFocusedElement.focus();
- initialFocusedElement = null;
-
- if (typeof config.onClose === 'function') {
- config.onClose(modalElement, mainElement);
- }
- }
-
- function toggle() {
- var toggle = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : !isOpen;
-
- toggle ? open() : close();
- }
-
- function onTransitionEnd(event) {
- if (event.propertyName === 'visibility') {
- setModalFocus();
- modalElement.removeEventListener('transitionend', onTransitionEnd);
- }
- }
-
- function destroy() {
- var modalAttrs = ['aria-describedby', 'aria-hidden', 'aria-label', 'aria-labeledby', 'aria-modal', 'role', 'tabindex'];
-
- modalAttrs.forEach(function (attr) {
- return modalElement.removeAttribute(attr);
- });
-
- mainElement.removeAttribute('aria-hidden');
- document.removeEventListener('keydown', onKeydown);
- document.removeEventListener('focus', trapFocus, true);
-
- if (typeof config.onDestroy === 'function') {
- config.onDestroy(modalElement, mainElement);
- }
- }
-
- create();
-
- return { create: create, open: open, close: close, toggle: toggle, isOpen: isOpen, destroy: destroy };
-}
-
-export default Dialog;
+var defaults={label:'Dialog',description:'',focus:'',alert:!1,openClass:'is-open',autoInit:!0},events=Object.create(null);function on(a,b){if(a&&b){var c=events[a]=events[a]||[];-1==c.indexOf(b)&&c.push(b)}}function off(a){var b=1 0 && arguments[0] !== undefined ? arguments[0] : !isOpen;
-
- toggle ? open() : close();
- }
-
- function onTransitionEnd(event) {
- if (event.propertyName === 'visibility') {
- setModalFocus();
- modalElement.removeEventListener('transitionend', onTransitionEnd);
- }
- }
-
- function destroy() {
- var modalAttrs = ['aria-describedby', 'aria-hidden', 'aria-label', 'aria-labeledby', 'aria-modal', 'role', 'tabindex'];
-
- modalAttrs.forEach(function (attr) {
- return modalElement.removeAttribute(attr);
- });
-
- mainElement.removeAttribute('aria-hidden');
- document.removeEventListener('keydown', onKeydown);
- document.removeEventListener('focus', trapFocus, true);
-
- if (typeof config.onDestroy === 'function') {
- config.onDestroy(modalElement, mainElement);
- }
- }
-
- create();
-
- return { create: create, open: open, close: close, toggle: toggle, isOpen: isOpen, destroy: destroy };
-}
-
-return Dialog;
-
-})));
+(function(a,b){'object'==typeof exports&&'undefined'!=typeof module?module.exports=b():'function'==typeof define&&define.amd?define(b):a.dialog=b()})(this,function(){'use strict';function a(a,b){if(a&&b){var c=l[a]=l[a]||[];-1==c.indexOf(b)&&c.push(b)}}function b(a){var b=1 {},
- onOpen: (dialog, main) => {},
- onClose: (dialog, main) => {},
- onDestroy: (dialog, main) => {},
+
+ openClass: 'is-open',
+
+ /**
+ * Auto initiate on instantiation
+ */
autoInit: true,
};
diff --git a/src/dialog.js b/src/dialog.js
deleted file mode 100644
index 697b98b..0000000
--- a/src/dialog.js
+++ /dev/null
@@ -1,151 +0,0 @@
-import defaults from './defaults';
-import * as Utils from './utils';
-
-function Dialog(modal, main, options) {
- const mainElement = document.getElementById(main);
- const modalElement = document.getElementById(modal);
- const config = Object.assign({}, defaults, options);
-
- let isOpen = false;
- let initiated = false;
- let initialFocusedElement = null;
-
- if (!mainElement) {
- throw new Error(`No element with the id "${main}"`);
- }
-
- if (!modalElement) {
- throw new Error(`No element with the id "${modal}"`);
- }
-
- function create() {
- const role = config.alert ? 'alertdialog' : 'dialog';
-
- modalElement.setAttribute('role', role);
- modalElement.setAttribute('tabindex', -1);
- modalElement.setAttribute('aria-modal', true);
-
- toggleAriaHidden(isOpen);
-
- let matchesID = document.getElementById(config.label);
- let attr = matchesID ? 'labeledby' : 'label';
- modalElement.setAttribute(`aria-${attr}`, config.label);
-
- if (document.getElementById(config.description)) {
- modalElement.setAttribute('aria-describedby', config.description);
- }
-
- if (mainElement.contains(modalElement)) {
- document.body.appendChild(modalElement);
- }
-
- if (typeof config.onCreate === 'function') {
- config.onCreate(modalElement, mainElement);
- }
- }
-
- function onKeydown(event) {
- if (event.key === 'Escape' && isOpen) {
- close();
- }
-
- if (event.key === 'Tab' && isOpen) {
- Utils.trapTabKey(modalElement, event);
- }
- }
-
- function trapFocus(event) {
- if (!modalElement.contains(document.activeElement)) {
- event.preventDefault();
- Utils.focusFirstElement(modalElement);
- }
- }
-
- function toggleAriaHidden(toggle) {
- mainElement.setAttribute('aria-hidden', toggle);
- modalElement.setAttribute('aria-hidden', !toggle);
- }
-
- function setModalFocus() {
- if (config.focus instanceof HTMLElement && modalElement.contains(config.focus)) {
- config.focus.focus();
- } else {
- Utils.focusFirstElement(modalElement);
- }
- }
-
- initiated = true;
- function open() {
- if (isOpen || !initiated) return;
-
- isOpen = true;
- toggleAriaHidden(isOpen);
-
- initialFocusedElement = document.activeElement;
-
- setModalFocus();
-
- if (!modalElement.contains(document.activeElement)) {
- modalElement.addEventListener('transitionend', onTransitionEnd);
- }
-
- document.addEventListener('keydown', onKeydown);
- document.addEventListener('focus', trapFocus, true);
-
- if (typeof config.onOpen === 'function') {
- config.onOpen(modalElement, mainElement);
- }
- }
-
- function close() {
- if (!isOpen || !initiated) return;
-
- isOpen = false;
- toggleAriaHidden(isOpen);
-
- document.removeEventListener('keydown', onKeydown);
- document.removeEventListener('focus', trapFocus, true);
-
- initialFocusedElement.focus();
- initialFocusedElement = null;
-
- if (typeof config.onClose === 'function') {
- config.onClose(modalElement, mainElement);
- }
- }
-
- function toggle(toggle = !isOpen) {
- toggle ? open() : close();
- }
-
- function onTransitionEnd(event) {
- if (event.propertyName === 'visibility') {
- setModalFocus();
- modalElement.removeEventListener('transitionend', onTransitionEnd);
- }
- }
-
- function destroy() {
- let modalAttrs = [ 'aria-describedby', 'aria-hidden', 'aria-label', 'aria-labeledby', 'aria-modal', 'role', 'tabindex' ];
-
- modalAttrs.forEach(attr => modalElement.removeAttribute(attr));
-
- mainElement.removeAttribute('aria-hidden');
- document.removeEventListener('keydown', onKeydown);
- document.removeEventListener('focus', trapFocus, true);
-
- if (typeof config.onDestroy === 'function') {
- config.onDestroy(modalElement, mainElement);
- }
- initiated = false;
- }
-
- if (config.autoInit) {
- create();
- }
-
- return { create, open, close, toggle, isOpen, destroy }
-
-};
-
-export default Dialog;
diff --git a/src/emitter.js b/src/emitter.js
new file mode 100644
index 0000000..cef5d9a
--- /dev/null
+++ b/src/emitter.js
@@ -0,0 +1,37 @@
+const events = Object.create(null);
+
+/**
+ * Subscribe to an event
+ * @param {String} type
+ * @param {Function} handler
+ */
+export function on(type, handler) {
+ if (type && handler) {
+ const handlers = events[type] = events[type] || [];
+ if (handlers.indexOf(handler) == -1) {
+ handlers.push(handler);
+ }
+ }
+}
+
+/**
+ * Unsubscribe from an event
+ * @param {String} event
+ * @param {Function} handler
+ */
+export function off(event, handler = false) {
+ if (handler) {
+ events[event].splice(events[event].indexOf(handler), 1);
+ } else {
+ delete events[event];
+ }
+}
+
+/**
+ * Emit an event
+ * @param {String} event
+ * @param {*} args
+ */
+export function emit(event, ...args) {
+ (events[event] || []).forEach(handler => handler.apply(handler, args));
+}
diff --git a/src/focus-trap.js b/src/focus-trap.js
new file mode 100644
index 0000000..4498f7d
--- /dev/null
+++ b/src/focus-trap.js
@@ -0,0 +1,99 @@
+const selectors = [
+ '[contenteditable]',
+ '[tabindex]:not([tabindex^="-"])',
+ 'a[href]',
+ 'area[href]',
+ 'button:not([disabled])',
+ 'embed',
+ 'iframe',
+ 'input:not([disabled])',
+ 'object',
+ 'select:not([disabled])',
+ 'textarea:not([disabled])'
+];
+
+function isVisible(element) {
+ const isHidden = !(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
+ const isInvisible = window.getComputedStyle(element).visibility === 'hidden';
+
+ return !(isHidden || isInvisible);
+}
+
+function getFocusableElements(node) {
+ return [...node.querySelectorAll(selectors)].filter(elem => isVisible(elem));
+}
+
+function focus(node) {
+ if (node && node.focus) node.focus();
+}
+
+function focusFirstElement(node) {
+ const nodes = getFocusableElements(node);
+ if (nodes.length) focus(nodes[0]);
+}
+
+function trapTab(element, event) {
+ const activeElement = document.activeElement;
+
+ const elements = getFocusableElements(element);
+ const firstTabStop = elements[0];
+ const lastTabStop = elements[elements.length - 1];
+
+ if (event.shiftKey && activeElement === firstTabStop) {
+ focus(lastTabStop);
+ event.preventDefault();
+ }
+
+ if (!event.shiftKey && activeElement === lastTabStop) {
+ focus(firstTabStop);
+ event.preventDefault();
+ }
+}
+
+function focusTrap(element, initialElement) {
+ let trapActivated = false;
+ let initialActiveElement = undefined;
+
+ function onFocus(event) {
+ const focusLost = !element.contains(document.activeElement);
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ if (focusLost && trapActivated) focusFirstElement(element);
+ }
+
+ function onKeydown(event) {
+ if (event.key === 'Tab') trapTab(element, event);
+ }
+
+ function activate() {
+ if (trapActivated) return;
+
+ trapActivated = true;
+ initialActiveElement = document.activeElement;
+
+ if (initialElement && element.contains(initialElement)) {
+ focus(initialElement);
+ } else {
+ focusFirstElement(element);
+ }
+
+ document.addEventListener('focus', onFocus, true);
+ document.addEventListener('keydown', onKeydown, true);
+ }
+
+ function deactivate() {
+ if (!trapActivated) return;
+
+ trapActivated = false;
+ focus(initialActiveElement);
+ initialActiveElement = undefined;
+
+ document.removeEventListener('focus', onFocus, true);
+ document.removeEventListener('keydown', onKeydown, true);
+ }
+
+ return { activate, deactivate }
+
+}
+
+export default focusTrap;
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..34b0d69
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,111 @@
+import defaults from './defaults';
+import * as Emitter from './emitter';
+import focusTrap from './focus-trap';
+
+function dialog(dialog, main, options) {
+
+ const elements = {
+ main: document.getElementById(main),
+ dialog: document.getElementById(dialog),
+ };
+
+ if (!elements.dialog) {
+ throw new Error(`No element with the id "${dialog}"`);
+ }
+
+ if (!elements.main) {
+ throw new Error(`No element with the id "${main}"`);
+ }
+
+ const config = Object.assign({}, defaults, options);
+ const trap = focusTrap(elements.dialog, config.focus);
+
+ let isOpen = false;
+ let initiated = false;
+
+ function onKeydown(event) {
+ if (event.key === 'Escape') close();
+ }
+
+ /**
+ * Create
+ */
+ function create() {
+ const role = config.alert ? 'alertdialog' : 'dialog';
+ const labeledby = document.getElementById(config.label);
+ const attr = labeledby ? 'labeledby' : 'label';
+
+ elements.dialog.setAttribute('role', role);
+ elements.dialog.setAttribute('aria-modal', true);
+ elements.dialog.setAttribute(`aria-${attr}`, config.label);
+
+ if (document.getElementById(config.description)) {
+ elements.dialog.setAttribute('aria-describedby', config.description);
+ } else if (config.description) {
+ throw new Error(`Invalid element: No element with the id "${config.description}"`);
+ }
+
+ initiated = true;
+
+ Emitter.emit('create', elements.dialog);
+ }
+
+ /**
+ * Open
+ */
+ function open() {
+ if (isOpen || !initiated) return;
+
+ isOpen = true;
+
+ elements.dialog.classList.add(config.openClass);
+ Emitter.emit('open', elements.dialog);
+
+ document.addEventListener('keydown', onKeydown, true);
+
+ trap.activate();
+ }
+
+ /**
+ * Close
+ */
+ function close() {
+ if (!isOpen || !initiated) return;
+
+ trap.deactivate();
+
+ isOpen = false;
+
+ document.removeEventListener('keydown', onKeydown, true);
+
+ elements.dialog.classList.remove(config.openClass);
+ Emitter.emit('close', elements.dialog);
+ }
+
+ /**
+ * Toggle
+ */
+ function toggle(toggle = !isOpen) {
+ toggle ? open() : close();
+ }
+
+ /**
+ * Destroy
+ */
+ function destroy() {
+ const attributes = [ 'aria-describedby', 'aria-label', 'aria-labeledby', 'aria-modal', 'role' ];
+
+ close();
+ initiated = false;
+ attributes.forEach(attr => elements.dialog.removeAttribute(attr));
+ Emitter.emit('destroy', elements.dialog);
+ }
+
+ if (config.autoInit) {
+ create();
+ }
+
+ return { elements, create, destroy, open, close, toggle, isOpen, on: Emitter.on, off: Emitter.off };
+}
+
+export default dialog;
diff --git a/src/selectors.js b/src/selectors.js
deleted file mode 100644
index 8b57323..0000000
--- a/src/selectors.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * List of selectors for elements that are focusable via the keyborad.
- */
-const selectors = [
- '[contenteditable]',
- '[tabindex]:not([tabindex^="-"])',
- 'a[href]',
- 'area[href]',
- 'button:not([disabled])',
- 'embed',
- 'iframe',
- 'input:not([disabled])',
- 'object',
- 'select:not([disabled])',
- 'textarea:not([disabled])',
-];
-
-export default selectors;
diff --git a/src/utils.js b/src/utils.js
deleted file mode 100644
index dc4b350..0000000
--- a/src/utils.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import selectors from './selectors';
-
-/**
- * Get a NodeList of all focusable child nodes
- * @param {HTMLElement} node
- * @return {NodeList}
- */
-export function getFocusableElements(node) {
- return node.querySelectorAll(selectors);
-}
-
-/**
- * Focuses the first focusable element within a node
- * @param {HTMLElement} node
- */
-export function focusFirstElement(node) {
- const nodes = node.querySelectorAll(selectors);
- nodes.length ? nodes[0].focus() : node.focus();
-}
-
-/**
- * Traps tab key within a given node
- * @param {HTMLElment} node
- */
-export function trapTabKey(node, event) {
- const activeElement = document.activeElement;
- const focusableElements = getFocusableElements(node);
- const firstTabStop = focusableElements[0];
- const lastTabStop = focusableElements[focusableElements.length - 1];
-
- if (event.shiftKey && activeElement === firstTabStop) {
- lastTabStop.focus();
- event.preventDefault();
- } else if (!event.shiftKey && activeElement === lastTabStop) {
- firstTabStop.focus();
- event.preventDefault();
- }
-}