a11y-dialog
Advanced tools
Comparing version 5.4.2 to 5.4.3
/* global NodeList, Element, Event, define */ | ||
(function(global) { | ||
'use strict'; | ||
;(function (global) { | ||
'use strict' | ||
@@ -17,7 +17,7 @@ var FOCUSABLE_ELEMENTS = [ | ||
'[contenteditable]:not([tabindex^="-"]):not([inert])', | ||
'[tabindex]:not([tabindex^="-"]):not([inert])' | ||
]; | ||
var TAB_KEY = 9; | ||
var ESCAPE_KEY = 27; | ||
var focusedBeforeDialog; | ||
'[tabindex]:not([tabindex^="-"]):not([inert])', | ||
] | ||
var TAB_KEY = 9 | ||
var ESCAPE_KEY = 27 | ||
var focusedBeforeDialog | ||
@@ -34,21 +34,22 @@ /** | ||
// removeEventListener to avoid losing references | ||
this._show = this.show.bind(this); | ||
this._hide = this.hide.bind(this); | ||
this._maintainFocus = this._maintainFocus.bind(this); | ||
this._bindKeypress = this._bindKeypress.bind(this); | ||
this._show = this.show.bind(this) | ||
this._hide = this.hide.bind(this) | ||
this._maintainFocus = this._maintainFocus.bind(this) | ||
this._bindKeypress = this._bindKeypress.bind(this) | ||
// Keep a reference of the node and the actual dialog on the instance | ||
this.container = node; | ||
this.dialog = node.querySelector('dialog, [role="dialog"], [role="alertdialog"]'); | ||
this.role = this.dialog.getAttribute('role') || 'dialog'; | ||
this.useDialog = ( | ||
this.container = node | ||
this.dialog = node.querySelector( | ||
'dialog, [role="dialog"], [role="alertdialog"]' | ||
) | ||
this.role = this.dialog.getAttribute('role') || 'dialog' | ||
this.useDialog = | ||
'show' in document.createElement('dialog') && | ||
this.dialog.nodeName === 'DIALOG' | ||
); | ||
// Keep an object of listener types mapped to callback functions | ||
this._listeners = {}; | ||
this._listeners = {} | ||
// Initialise everything needed for the dialog to work properly | ||
this.create(targets); | ||
this.create(targets) | ||
} | ||
@@ -62,9 +63,9 @@ | ||
*/ | ||
A11yDialog.prototype.create = function(targets) { | ||
A11yDialog.prototype.create = function (targets) { | ||
// Keep a collection of nodes to disable/enable when toggling the dialog | ||
this._targets = | ||
this._targets || collect(targets) || getSiblings(this.container); | ||
this._targets || collect(targets) || getSiblings(this.container) | ||
// Set the `shown` property to match the status from the DOM | ||
this.shown = this.dialog.hasAttribute('open'); | ||
this.shown = this.dialog.hasAttribute('open') | ||
@@ -74,15 +75,15 @@ // Despite using a `<dialog>` element, `role="dialog"` is not necessarily | ||
// See: https://github.com/edenspiekermann/a11y-dialog/commit/6ba711a777aed0dbda0719a18a02f742098c64d9#commitcomment-28694166 | ||
this.dialog.setAttribute('role', this.role); | ||
this.dialog.setAttribute('role', this.role) | ||
if (!this.useDialog) { | ||
if (this.shown) { | ||
this.container.removeAttribute('aria-hidden'); | ||
this.container.removeAttribute('aria-hidden') | ||
} else { | ||
this.container.setAttribute('aria-hidden', true); | ||
this.container.setAttribute('aria-hidden', true) | ||
} | ||
} else { | ||
this.container.setAttribute('data-a11y-dialog-native', ''); | ||
this.container.setAttribute('data-a11y-dialog-native', '') | ||
// Remove initial `aria-hidden` from container | ||
// See: https://github.com/edenspiekermann/a11y-dialog/pull/117#issuecomment-706056246 | ||
this.container.removeAttribute('aria-hidden'); | ||
this.container.removeAttribute('aria-hidden') | ||
} | ||
@@ -92,8 +93,8 @@ | ||
// event listener to open the dialog | ||
this._openers = $$('[data-a11y-dialog-show="' + this.container.id + '"]'); | ||
this._openers = $$('[data-a11y-dialog-show="' + this.container.id + '"]') | ||
this._openers.forEach( | ||
function(opener) { | ||
opener.addEventListener('click', this._show); | ||
function (opener) { | ||
opener.addEventListener('click', this._show) | ||
}.bind(this) | ||
); | ||
) | ||
@@ -104,14 +105,14 @@ // Keep a collection of dialog closers, each of which will be bound a click | ||
$$('[data-a11y-dialog-hide="' + this.container.id + '"]') | ||
); | ||
) | ||
this._closers.forEach( | ||
function(closer) { | ||
closer.addEventListener('click', this._hide); | ||
function (closer) { | ||
closer.addEventListener('click', this._hide) | ||
}.bind(this) | ||
); | ||
) | ||
// Execute all callbacks registered for the `create` event | ||
this._fire('create'); | ||
this._fire('create') | ||
return this; | ||
}; | ||
return this | ||
} | ||
@@ -126,32 +127,35 @@ /** | ||
*/ | ||
A11yDialog.prototype.show = function(event) { | ||
A11yDialog.prototype.show = function (event) { | ||
// If the dialog is already open, abort | ||
if (this.shown) { | ||
return this; | ||
return this | ||
} | ||
this.shown = true; | ||
this.shown = true | ||
// Keep a reference to the currently focused element to be able to restore | ||
// it later | ||
focusedBeforeDialog = document.activeElement; | ||
focusedBeforeDialog = document.activeElement | ||
if (this.useDialog) { | ||
this.dialog.showModal(event instanceof Event ? void 0 : event); | ||
this.dialog.showModal(event instanceof Event ? void 0 : event) | ||
} else { | ||
this.dialog.setAttribute('open', ''); | ||
this.container.removeAttribute('aria-hidden'); | ||
this.dialog.setAttribute('open', '') | ||
this.container.removeAttribute('aria-hidden') | ||
// Iterate over the targets to disable them by setting their `aria-hidden` | ||
// attribute to `true` and, if present, storing the current value of `aria-hidden` | ||
this._targets.forEach(function(target) { | ||
this._targets.forEach(function (target) { | ||
if (target.hasAttribute('aria-hidden')) { | ||
target.setAttribute('data-a11y-dialog-original-aria-hidden', target.getAttribute('aria-hidden')); | ||
target.setAttribute( | ||
'data-a11y-dialog-original-aria-hidden', | ||
target.getAttribute('aria-hidden') | ||
) | ||
} | ||
target.setAttribute('aria-hidden', 'true'); | ||
}); | ||
target.setAttribute('aria-hidden', 'true') | ||
}) | ||
} | ||
// Set the focus to the first focusable child of the dialog element | ||
setFocusToFirstItem(this.dialog); | ||
setFocusToFirstItem(this.dialog) | ||
@@ -161,10 +165,10 @@ // Bind a focus event listener to the body element to make sure the focus | ||
// specific key presses (TAB and ESC) | ||
document.body.addEventListener('focus', this._maintainFocus, true); | ||
document.addEventListener('keydown', this._bindKeypress); | ||
document.body.addEventListener('focus', this._maintainFocus, true) | ||
document.addEventListener('keydown', this._bindKeypress) | ||
// Execute all callbacks registered for the `show` event | ||
this._fire('show', event); | ||
this._fire('show', event) | ||
return this; | ||
}; | ||
return this | ||
} | ||
@@ -179,26 +183,29 @@ /** | ||
*/ | ||
A11yDialog.prototype.hide = function(event) { | ||
A11yDialog.prototype.hide = function (event) { | ||
// If the dialog is already closed, abort | ||
if (!this.shown) { | ||
return this; | ||
return this | ||
} | ||
this.shown = false; | ||
this.shown = false | ||
if (this.useDialog) { | ||
this.dialog.close(event instanceof Event ? void 0 : event); | ||
this.dialog.close(event instanceof Event ? void 0 : event) | ||
} else { | ||
this.dialog.removeAttribute('open'); | ||
this.container.setAttribute('aria-hidden', 'true'); | ||
this.dialog.removeAttribute('open') | ||
this.container.setAttribute('aria-hidden', 'true') | ||
// Iterate over the targets to enable them by removing their `aria-hidden` | ||
// attribute or resetting it to its original value | ||
this._targets.forEach(function(target) { | ||
this._targets.forEach(function (target) { | ||
if (target.hasAttribute('data-a11y-dialog-original-aria-hidden')) { | ||
target.setAttribute('aria-hidden', target.getAttribute('data-a11y-dialog-original-aria-hidden')); | ||
target.removeAttribute('data-a11y-dialog-original-aria-hidden'); | ||
target.setAttribute( | ||
'aria-hidden', | ||
target.getAttribute('data-a11y-dialog-original-aria-hidden') | ||
) | ||
target.removeAttribute('data-a11y-dialog-original-aria-hidden') | ||
} else { | ||
target.removeAttribute('aria-hidden'); | ||
target.removeAttribute('aria-hidden') | ||
} | ||
}); | ||
}) | ||
} | ||
@@ -210,3 +217,3 @@ | ||
if (focusedBeforeDialog && focusedBeforeDialog.focus) { | ||
focusedBeforeDialog.focus(); | ||
focusedBeforeDialog.focus() | ||
} | ||
@@ -216,10 +223,10 @@ | ||
// for specific key presses | ||
document.body.removeEventListener('focus', this._maintainFocus, true); | ||
document.removeEventListener('keydown', this._bindKeypress); | ||
document.body.removeEventListener('focus', this._maintainFocus, true) | ||
document.removeEventListener('keydown', this._bindKeypress) | ||
// Execute all callbacks registered for the `hide` event | ||
this._fire('hide', event); | ||
this._fire('hide', event) | ||
return this; | ||
}; | ||
return this | ||
} | ||
@@ -232,28 +239,28 @@ /** | ||
*/ | ||
A11yDialog.prototype.destroy = function() { | ||
A11yDialog.prototype.destroy = function () { | ||
// Hide the dialog to avoid destroying an open instance | ||
this.hide(); | ||
this.hide() | ||
// Remove the click event listener from all dialog openers | ||
this._openers.forEach( | ||
function(opener) { | ||
opener.removeEventListener('click', this._show); | ||
function (opener) { | ||
opener.removeEventListener('click', this._show) | ||
}.bind(this) | ||
); | ||
) | ||
// Remove the click event listener from all dialog closers | ||
this._closers.forEach( | ||
function(closer) { | ||
closer.removeEventListener('click', this._hide); | ||
function (closer) { | ||
closer.removeEventListener('click', this._hide) | ||
}.bind(this) | ||
); | ||
) | ||
// Execute all callbacks registered for the `destroy` event | ||
this._fire('destroy'); | ||
this._fire('destroy') | ||
// Keep an object of listener types mapped to callback functions | ||
this._listeners = {}; | ||
this._listeners = {} | ||
return this; | ||
}; | ||
return this | ||
} | ||
@@ -266,11 +273,11 @@ /** | ||
*/ | ||
A11yDialog.prototype.on = function(type, handler) { | ||
A11yDialog.prototype.on = function (type, handler) { | ||
if (typeof this._listeners[type] === 'undefined') { | ||
this._listeners[type] = []; | ||
this._listeners[type] = [] | ||
} | ||
this._listeners[type].push(handler); | ||
this._listeners[type].push(handler) | ||
return this; | ||
}; | ||
return this | ||
} | ||
@@ -283,11 +290,11 @@ /** | ||
*/ | ||
A11yDialog.prototype.off = function(type, handler) { | ||
var index = this._listeners[type].indexOf(handler); | ||
A11yDialog.prototype.off = function (type, handler) { | ||
var index = this._listeners[type].indexOf(handler) | ||
if (index > -1) { | ||
this._listeners[type].splice(index, 1); | ||
this._listeners[type].splice(index, 1) | ||
} | ||
return this; | ||
}; | ||
return this | ||
} | ||
@@ -302,11 +309,11 @@ /** | ||
*/ | ||
A11yDialog.prototype._fire = function(type, event) { | ||
var listeners = this._listeners[type] || []; | ||
A11yDialog.prototype._fire = function (type, event) { | ||
var listeners = this._listeners[type] || [] | ||
listeners.forEach( | ||
function(listener) { | ||
listener(this.container, event); | ||
function (listener) { | ||
listener(this.container, event) | ||
}.bind(this) | ||
); | ||
}; | ||
) | ||
} | ||
@@ -320,9 +327,13 @@ /** | ||
*/ | ||
A11yDialog.prototype._bindKeypress = function(event) { | ||
A11yDialog.prototype._bindKeypress = function (event) { | ||
// If the dialog is shown and the ESCAPE key is being pressed, prevent any | ||
// further effects from the ESCAPE key and hide the dialog, unless its role | ||
// is 'alertdialog', which should be modal | ||
if (this.shown && event.which === ESCAPE_KEY && this.role !== 'alertdialog') { | ||
event.preventDefault(); | ||
this.hide(event); | ||
if ( | ||
this.shown && | ||
event.which === ESCAPE_KEY && | ||
this.role !== 'alertdialog' | ||
) { | ||
event.preventDefault() | ||
this.hide(event) | ||
} | ||
@@ -333,5 +344,5 @@ | ||
if (this.shown && event.which === TAB_KEY) { | ||
trapTabKey(this.dialog, event); | ||
trapTabKey(this.dialog, event) | ||
} | ||
}; | ||
} | ||
@@ -345,9 +356,9 @@ /** | ||
*/ | ||
A11yDialog.prototype._maintainFocus = function(event) { | ||
A11yDialog.prototype._maintainFocus = function (event) { | ||
// If the dialog is shown and the focus is not within the dialog element, | ||
// move it back to its first focusable child | ||
if (this.shown && !this.container.contains(event.target)) { | ||
setFocusToFirstItem(this.dialog); | ||
setFocusToFirstItem(this.dialog) | ||
} | ||
}; | ||
} | ||
@@ -361,3 +372,3 @@ /** | ||
function toArray(collection) { | ||
return Array.prototype.slice.call(collection); | ||
return Array.prototype.slice.call(collection) | ||
} | ||
@@ -374,3 +385,3 @@ | ||
function $$(selector, context) { | ||
return toArray((context || document).querySelectorAll(selector)); | ||
return toArray((context || document).querySelectorAll(selector)) | ||
} | ||
@@ -387,11 +398,11 @@ | ||
if (NodeList.prototype.isPrototypeOf(target)) { | ||
return toArray(target); | ||
return toArray(target) | ||
} | ||
if (Element.prototype.isPrototypeOf(target)) { | ||
return [target]; | ||
return [target] | ||
} | ||
if (typeof target === 'string') { | ||
return $$(target); | ||
return $$(target) | ||
} | ||
@@ -407,7 +418,7 @@ } | ||
function setFocusToFirstItem(node) { | ||
var focusableChildren = getFocusableChildren(node); | ||
var focused = node.querySelector('[autofocus]') || focusableChildren[0]; | ||
var focusableChildren = getFocusableChildren(node) | ||
var focused = node.querySelector('[autofocus]') || focusableChildren[0] | ||
if (focused) { | ||
focused.focus(); | ||
focused.focus() | ||
} | ||
@@ -423,3 +434,3 @@ } | ||
function getFocusableChildren(node) { | ||
return $$(FOCUSABLE_ELEMENTS.join(','), node).filter(function(child) { | ||
return $$(FOCUSABLE_ELEMENTS.join(','), node).filter(function (child) { | ||
return !!( | ||
@@ -429,4 +440,4 @@ child.offsetWidth || | ||
child.getClientRects().length | ||
); | ||
}); | ||
) | ||
}) | ||
} | ||
@@ -441,4 +452,4 @@ | ||
function trapTabKey(node, event) { | ||
var focusableChildren = getFocusableChildren(node); | ||
var focusedItemIndex = focusableChildren.indexOf(document.activeElement); | ||
var focusableChildren = getFocusableChildren(node) | ||
var focusedItemIndex = focusableChildren.indexOf(document.activeElement) | ||
@@ -449,4 +460,4 @@ // If the SHIFT key is being pressed while tabbing (moving backwards) and | ||
if (event.shiftKey && focusedItemIndex === 0) { | ||
focusableChildren[focusableChildren.length - 1].focus(); | ||
event.preventDefault(); | ||
focusableChildren[focusableChildren.length - 1].focus() | ||
event.preventDefault() | ||
// If the SHIFT key is not being pressed (moving forwards) and the currently | ||
@@ -459,4 +470,4 @@ // focused item is the last one, move the focus to the first focusable item | ||
) { | ||
focusableChildren[0].focus(); | ||
event.preventDefault(); | ||
focusableChildren[0].focus() | ||
event.preventDefault() | ||
} | ||
@@ -472,21 +483,21 @@ } | ||
function getSiblings(node) { | ||
var nodes = toArray(node.parentNode.childNodes); | ||
var siblings = nodes.filter(function(node) { | ||
return node.nodeType === 1; | ||
}); | ||
var nodes = toArray(node.parentNode.childNodes) | ||
var siblings = nodes.filter(function (node) { | ||
return node.nodeType === 1 | ||
}) | ||
siblings.splice(siblings.indexOf(node), 1); | ||
siblings.splice(siblings.indexOf(node), 1) | ||
return siblings; | ||
return siblings | ||
} | ||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { | ||
module.exports = A11yDialog; | ||
module.exports = A11yDialog | ||
} else if (typeof define === 'function' && define.amd) { | ||
define('A11yDialog', [], function() { | ||
return A11yDialog; | ||
}); | ||
define('A11yDialog', [], function () { | ||
return A11yDialog | ||
}) | ||
} else if (typeof global === 'object') { | ||
global.A11yDialog = A11yDialog; | ||
global.A11yDialog = A11yDialog | ||
} | ||
})(typeof global !== 'undefined' ? global : window); | ||
})(typeof global !== 'undefined' ? global : window) |
@@ -1,2 +0,2 @@ | ||
/*! a11y-dialog 5.4.2 — © Edenspiekermann */ | ||
!function(t){"use strict";function i(t,i){this._show=this.show.bind(this),this._hide=this.hide.bind(this),this._maintainFocus=this._maintainFocus.bind(this),this._bindKeypress=this._bindKeypress.bind(this),this.container=t,this.dialog=t.querySelector('dialog, [role="dialog"], [role="alertdialog"]'),this.role=this.dialog.getAttribute("role")||"dialog",this.useDialog="show"in document.createElement("dialog")&&"DIALOG"===this.dialog.nodeName,this._listeners={},this.create(i)}function e(t){return Array.prototype.slice.call(t)}function n(t,i){return e((i||document).querySelectorAll(t))}function o(t){return NodeList.prototype.isPrototypeOf(t)?e(t):Element.prototype.isPrototypeOf(t)?[t]:"string"==typeof t?n(t):void 0}function s(t){var i=r(t),e=t.querySelector("[autofocus]")||i[0];e&&e.focus()}function r(t){return n(c.join(","),t).filter(function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)})}function a(t,i){var e=r(t),n=e.indexOf(document.activeElement);i.shiftKey&&0===n?(e[e.length-1].focus(),i.preventDefault()):i.shiftKey||n!==e.length-1||(e[0].focus(),i.preventDefault())}function h(t){var i=e(t.parentNode.childNodes),n=i.filter(function(t){return 1===t.nodeType});return n.splice(n.indexOf(t),1),n}var d,c=['a[href]:not([tabindex^="-"]):not([inert])','area[href]:not([tabindex^="-"]):not([inert])',"input:not([disabled]):not([inert])","select:not([disabled]):not([inert])","textarea:not([disabled]):not([inert])","button:not([disabled]):not([inert])",'iframe:not([tabindex^="-"]):not([inert])','audio:not([tabindex^="-"]):not([inert])','video:not([tabindex^="-"]):not([inert])','[contenteditable]:not([tabindex^="-"]):not([inert])','[tabindex]:not([tabindex^="-"]):not([inert])'];i.prototype.create=function(t){return this._targets=this._targets||o(t)||h(this.container),this.shown=this.dialog.hasAttribute("open"),this.dialog.setAttribute("role",this.role),this.useDialog?(this.container.setAttribute("data-a11y-dialog-native",""),this.container.removeAttribute("aria-hidden")):this.shown?this.container.removeAttribute("aria-hidden"):this.container.setAttribute("aria-hidden",!0),this._openers=n('[data-a11y-dialog-show="'+this.container.id+'"]'),this._openers.forEach(function(t){t.addEventListener("click",this._show)}.bind(this)),this._closers=n("[data-a11y-dialog-hide]",this.container).concat(n('[data-a11y-dialog-hide="'+this.container.id+'"]')),this._closers.forEach(function(t){t.addEventListener("click",this._hide)}.bind(this)),this._fire("create"),this},i.prototype.show=function(t){return this.shown?this:(this.shown=!0,d=document.activeElement,this.useDialog?this.dialog.showModal(t instanceof Event?void 0:t):(this.dialog.setAttribute("open",""),this.container.removeAttribute("aria-hidden"),this._targets.forEach(function(t){t.hasAttribute("aria-hidden")&&t.setAttribute("data-a11y-dialog-original-aria-hidden",t.getAttribute("aria-hidden")),t.setAttribute("aria-hidden","true")})),s(this.dialog),document.body.addEventListener("focus",this._maintainFocus,!0),document.addEventListener("keydown",this._bindKeypress),this._fire("show",t),this)},i.prototype.hide=function(t){return this.shown?(this.shown=!1,this.useDialog?this.dialog.close(t instanceof Event?void 0:t):(this.dialog.removeAttribute("open"),this.container.setAttribute("aria-hidden","true"),this._targets.forEach(function(t){t.hasAttribute("data-a11y-dialog-original-aria-hidden")?(t.setAttribute("aria-hidden",t.getAttribute("data-a11y-dialog-original-aria-hidden")),t.removeAttribute("data-a11y-dialog-original-aria-hidden")):t.removeAttribute("aria-hidden")})),d&&d.focus&&d.focus(),document.body.removeEventListener("focus",this._maintainFocus,!0),document.removeEventListener("keydown",this._bindKeypress),this._fire("hide",t),this):this},i.prototype.destroy=function(){return this.hide(),this._openers.forEach(function(t){t.removeEventListener("click",this._show)}.bind(this)),this._closers.forEach(function(t){t.removeEventListener("click",this._hide)}.bind(this)),this._fire("destroy"),this._listeners={},this},i.prototype.on=function(t,i){return void 0===this._listeners[t]&&(this._listeners[t]=[]),this._listeners[t].push(i),this},i.prototype.off=function(t,i){var e=this._listeners[t].indexOf(i);return e>-1&&this._listeners[t].splice(e,1),this},i.prototype._fire=function(t,i){(this._listeners[t]||[]).forEach(function(t){t(this.container,i)}.bind(this))},i.prototype._bindKeypress=function(t){this.shown&&27===t.which&&"alertdialog"!==this.role&&(t.preventDefault(),this.hide(t)),this.shown&&9===t.which&&a(this.dialog,t)},i.prototype._maintainFocus=function(t){this.shown&&!this.container.contains(t.target)&&s(this.dialog)},"undefined"!=typeof module&&void 0!==module.exports?module.exports=i:"function"==typeof define&&define.amd?define("A11yDialog",[],function(){return i}):"object"==typeof t&&(t.A11yDialog=i)}("undefined"!=typeof global?global:window); | ||
/*! a11y-dialog 5.4.3 — © Edenspiekermann */ | ||
!function(t){"use strict";var i,e=['a[href]:not([tabindex^="-"]):not([inert])','area[href]:not([tabindex^="-"]):not([inert])',"input:not([disabled]):not([inert])","select:not([disabled]):not([inert])","textarea:not([disabled]):not([inert])","button:not([disabled]):not([inert])",'iframe:not([tabindex^="-"]):not([inert])','audio:not([tabindex^="-"]):not([inert])','video:not([tabindex^="-"]):not([inert])','[contenteditable]:not([tabindex^="-"]):not([inert])','[tabindex]:not([tabindex^="-"]):not([inert])'];function n(t,i){this._show=this.show.bind(this),this._hide=this.hide.bind(this),this._maintainFocus=this._maintainFocus.bind(this),this._bindKeypress=this._bindKeypress.bind(this),this.container=t,this.dialog=t.querySelector('dialog, [role="dialog"], [role="alertdialog"]'),this.role=this.dialog.getAttribute("role")||"dialog",this.useDialog="show"in document.createElement("dialog")&&"DIALOG"===this.dialog.nodeName,this._listeners={},this.create(i)}function o(t){return Array.prototype.slice.call(t)}function s(t,i){return o((i||document).querySelectorAll(t))}function r(t){var i=a(t),i=t.querySelector("[autofocus]")||i[0];i&&i.focus()}function a(t){return s(e.join(","),t).filter(function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)})}n.prototype.create=function(t){var i;return this._targets=this._targets||function(t){if(NodeList.prototype.isPrototypeOf(t))return o(t);if(Element.prototype.isPrototypeOf(t))return[t];if("string"==typeof t)return s(t)}(t)||(i=this.container,(t=o(i.parentNode.childNodes).filter(function(t){return 1===t.nodeType})).splice(t.indexOf(i),1),t),this.shown=this.dialog.hasAttribute("open"),this.dialog.setAttribute("role",this.role),this.useDialog?(this.container.setAttribute("data-a11y-dialog-native",""),this.container.removeAttribute("aria-hidden")):this.shown?this.container.removeAttribute("aria-hidden"):this.container.setAttribute("aria-hidden",!0),this._openers=s('[data-a11y-dialog-show="'+this.container.id+'"]'),this._openers.forEach(function(t){t.addEventListener("click",this._show)}.bind(this)),this._closers=s("[data-a11y-dialog-hide]",this.container).concat(s('[data-a11y-dialog-hide="'+this.container.id+'"]')),this._closers.forEach(function(t){t.addEventListener("click",this._hide)}.bind(this)),this._fire("create"),this},n.prototype.show=function(t){return this.shown||(this.shown=!0,i=document.activeElement,this.useDialog?this.dialog.showModal(t instanceof Event?void 0:t):(this.dialog.setAttribute("open",""),this.container.removeAttribute("aria-hidden"),this._targets.forEach(function(t){t.hasAttribute("aria-hidden")&&t.setAttribute("data-a11y-dialog-original-aria-hidden",t.getAttribute("aria-hidden")),t.setAttribute("aria-hidden","true")})),r(this.dialog),document.body.addEventListener("focus",this._maintainFocus,!0),document.addEventListener("keydown",this._bindKeypress),this._fire("show",t)),this},n.prototype.hide=function(t){return this.shown&&(this.shown=!1,this.useDialog?this.dialog.close(t instanceof Event?void 0:t):(this.dialog.removeAttribute("open"),this.container.setAttribute("aria-hidden","true"),this._targets.forEach(function(t){t.hasAttribute("data-a11y-dialog-original-aria-hidden")?(t.setAttribute("aria-hidden",t.getAttribute("data-a11y-dialog-original-aria-hidden")),t.removeAttribute("data-a11y-dialog-original-aria-hidden")):t.removeAttribute("aria-hidden")})),i&&i.focus&&i.focus(),document.body.removeEventListener("focus",this._maintainFocus,!0),document.removeEventListener("keydown",this._bindKeypress),this._fire("hide",t)),this},n.prototype.destroy=function(){return this.hide(),this._openers.forEach(function(t){t.removeEventListener("click",this._show)}.bind(this)),this._closers.forEach(function(t){t.removeEventListener("click",this._hide)}.bind(this)),this._fire("destroy"),this._listeners={},this},n.prototype.on=function(t,i){return void 0===this._listeners[t]&&(this._listeners[t]=[]),this._listeners[t].push(i),this},n.prototype.off=function(t,i){i=this._listeners[t].indexOf(i);return-1<i&&this._listeners[t].splice(i,1),this},n.prototype._fire=function(t,i){(this._listeners[t]||[]).forEach(function(t){t(this.container,i)}.bind(this))},n.prototype._bindKeypress=function(t){var i,e;this.shown&&27===t.which&&"alertdialog"!==this.role&&(t.preventDefault(),this.hide(t)),this.shown&&9===t.which&&(i=this.dialog,e=t,t=a(i),i=t.indexOf(document.activeElement),e.shiftKey&&0===i?(t[t.length-1].focus(),e.preventDefault()):e.shiftKey||i!==t.length-1||(t[0].focus(),e.preventDefault()))},n.prototype._maintainFocus=function(t){this.shown&&!this.container.contains(t.target)&&r(this.dialog)},"undefined"!=typeof module&&void 0!==module.exports?module.exports=n:"function"==typeof define&&define.amd?define("A11yDialog",[],function(){return n}):"object"==typeof t&&(t.A11yDialog=n)}("undefined"!=typeof global?global:window); |
{ | ||
"name": "a11y-dialog", | ||
"version": "5.4.2", | ||
"version": "5.4.3", | ||
"description": "A tiny script to make dialog windows accessible to assistive technology users.", | ||
@@ -16,3 +16,3 @@ "homepage": "https://github.com/edenspiekermann/a11y-dialog", | ||
], | ||
"author": "Hugo Giraudel (https://hugogiraudel.com)", | ||
"author": "Hugo “Kitty” Giraudel (https://hugogiraudel.com)", | ||
"repository": { | ||
@@ -32,3 +32,2 @@ "type": "git", | ||
"postbuild": "npm run add-version", | ||
"lint": "semistandard a11y-dialog.js", | ||
"test": "open tests/index.html", | ||
@@ -40,3 +39,3 @@ "extract-version": "cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]'", | ||
"size": "npm run build --silent && npm run compress --silent && npm run show --silent && rm a11y-dialog.min.js.gz", | ||
"docs:clean": "rimraf _book", | ||
"docs:clean": "rm -rf _book", | ||
"docs:prepare": "gitbook install", | ||
@@ -47,17 +46,18 @@ "docs:build": "npm run docs:prepare && gitbook build", | ||
}, | ||
"husky": { | ||
"pre-commit": "lint-staged", | ||
"post-rewrite": "lint-staged" | ||
}, | ||
"lint-staged": { | ||
"*.js": [ | ||
"prettier --no-semi --single-quote --print-width 80 --write", | ||
"git add", | ||
"eslint --fix", | ||
"git add" | ||
"prettier --write" | ||
] | ||
}, | ||
"devDependencies": { | ||
"husky": "^0.14.3", | ||
"lint-staged": "^6.0.0", | ||
"prettier": "^1.10.2", | ||
"semistandard": "^7.0.5", | ||
"uglify-js": "^2.6.1" | ||
"gitbook-cli": "^2.3.2", | ||
"husky": "^4.3.8", | ||
"lint-staged": "^10.5.4", | ||
"prettier": "^2.2.1", | ||
"uglify-js": "^3.12.6" | ||
} | ||
} |
103
README.md
@@ -12,3 +12,3 @@ # [A11y Dialog](http://edenspiekermann.github.io/a11y-dialog/) | ||
✔︎ DOM and JS APIs | ||
✔︎ Fast and tiny | ||
✔︎ Fast and tiny | ||
@@ -25,3 +25,3 @@ You can try the [live demo ↗](http://edenspiekermann.github.io/a11y-dialog/example/). | ||
You will find a concrete demo in the [example](https://github.com/edenspiekermann/a11y-dialog/tree/master/example) folder of this repository, but basically here is the gist: | ||
You will find a concrete demo in the [example](https://github.com/edenspiekermann/a11y-dialog/tree/main/example) folder of this repository, but basically here is the gist: | ||
@@ -51,3 +51,2 @@ ### Expected DOM structure | ||
<div class="dialog-container" id="my-accessible-dialog" aria-hidden="true"> | ||
<!-- | ||
@@ -64,27 +63,38 @@ Overlay related notes: | ||
- It may have the `alertdialog` role to make it behave like a “modal”. See the “Usage as a modal” section of the docs. | ||
- It doesn’t have to be a `<dialog>` element, it can be a `<div>` element with the `dialog` or `alertdialog` role (e.g. `<div role="dialog">`). | ||
- It can be a `<dialog>` element without `role="dialog"`, but there might be browsers inconsistencies. | ||
- It doesn’t have to have the `aria-labelledby` attribute however this is recommended. It should match the `id` of the dialog title. | ||
--> | ||
<dialog aria-labelledby="dialog-title"> | ||
<div role="dialog" aria-labelledby="dialog-title"> | ||
<!-- | ||
Closing button related notes: | ||
- It does have to have the `type="button"` attribute. | ||
- It does have to have the `data-a11y-dialog-hide` attribute. | ||
- It does have to have an aria-label attribute if you use an icon as content. | ||
Inner document related notes: | ||
- It doesn’t have to exist but improves support in NVDA. | ||
- It doesn’t have to exist when using <dialog> because is implied. | ||
--> | ||
<button type="button" data-a11y-dialog-hide aria-label="Close this dialog window"> | ||
× | ||
</button> | ||
<div role="document"> | ||
<!-- | ||
Closing button related notes: | ||
- It does have to have the `type="button"` attribute. | ||
- It does have to have the `data-a11y-dialog-hide` attribute. | ||
- It does have to have an aria-label attribute if you use an icon as content. | ||
--> | ||
<button | ||
type="button" | ||
data-a11y-dialog-hide | ||
aria-label="Close this dialog window" | ||
> | ||
× | ||
</button> | ||
<!-- | ||
Dialog title related notes: | ||
- It should have a different content than `Dialog Title`. | ||
- It can have a different id than `dialog-title`. | ||
--> | ||
<h1 id="dialog-title">Dialog Title</h1> | ||
<!-- | ||
Dialog title related notes: | ||
- It should have a different content than `Dialog Title`. | ||
- It can have a different id than `dialog-title`. | ||
--> | ||
<h1 id="dialog-title">Dialog Title</h1> | ||
<!-- | ||
Here lives the main content of the dialog. | ||
--> | ||
</dialog> | ||
<!-- | ||
Here lives the main content of the dialog. | ||
--> | ||
</div> | ||
</div> | ||
</div> | ||
@@ -141,6 +151,6 @@ ``` | ||
// Get the dialog element (with the accessor method you want) | ||
const el = document.getElementById('my-accessible-dialog'); | ||
const el = document.getElementById('my-accessible-dialog') | ||
// Instantiate a new A11yDialog module | ||
const dialog = new A11yDialog(el); | ||
const dialog = new A11yDialog(el) | ||
``` | ||
@@ -151,3 +161,3 @@ | ||
```javascript | ||
const dialog = new A11yDialog(el, containers); | ||
const dialog = new A11yDialog(el, containers) | ||
``` | ||
@@ -161,3 +171,3 @@ | ||
1. Add `role="alertdialog"` to the `<dialog>` element or replace`role="dialog"` with `role="alertdialog"` in case you don’t use the `<dialog>` element. This will make sure <kbd>ESC</kbd> doesn’t close the modal. | ||
1. Replace `role="dialog"` with `role="alertdialog"`. This will make sure <kbd>ESC</kbd> doesn’t close the modal. Note that this role does not work properly with the native `<dialog>` element so make sure to use `<div role="alertdialog">`. | ||
2. Remove `data-a11y-dialog-hide` from the overlay element. This makes sure it is not possible to close the modal by clicking outside of it. | ||
@@ -172,4 +182,4 @@ 3. In case the user actively needs to operate with the modal, you might consider removing the close button from it. Be sure to still offer a way to eventually close the modal. | ||
* `data-a11y-dialog-show`: the `id` of the dialog element is expected as a value | ||
* `data-a11y-dialog-hide`: the `id` of the dialog element is expected as a value; if omitted, the closest parent dialog element (if any) will be the target | ||
- `data-a11y-dialog-show`: the `id` of the dialog element is expected as a value | ||
- `data-a11y-dialog-hide`: the `id` of the dialog element is expected as a value; if omitted, the closest parent dialog element (if any) will be the target | ||
@@ -179,3 +189,5 @@ The following button will open the dialog with the `my-accessible-dialog` id when interacted with. | ||
```html | ||
<button type="button" data-a11y-dialog-show="my-accessible-dialog">Open the dialog</button> | ||
<button type="button" data-a11y-dialog-show="my-accessible-dialog"> | ||
Open the dialog | ||
</button> | ||
``` | ||
@@ -186,3 +198,5 @@ | ||
```html | ||
<button type="button" data-a11y-dialog-hide aria-label="Close the dialog">×</button> | ||
<button type="button" data-a11y-dialog-hide aria-label="Close the dialog"> | ||
× | ||
</button> | ||
``` | ||
@@ -193,3 +207,9 @@ | ||
```html | ||
<button type="button" data-a11y-dialog-hide="my-accessible-dialog" aria-label="Close the dialog">×</button> | ||
<button | ||
type="button" | ||
data-a11y-dialog-hide="my-accessible-dialog" | ||
aria-label="Close the dialog" | ||
> | ||
× | ||
</button> | ||
``` | ||
@@ -205,6 +225,6 @@ | ||
// Show the dialog | ||
dialog.show(); | ||
dialog.show() | ||
// Hide the dialog | ||
dialog.hide(); | ||
dialog.hide() | ||
``` | ||
@@ -219,6 +239,6 @@ | ||
// custom event listeners registered with `.on()` | ||
dialog.destroy(); | ||
dialog.destroy() | ||
// Bind click listeners to dialog openers and closers | ||
dialog.create(); | ||
dialog.create() | ||
``` | ||
@@ -238,3 +258,3 @@ | ||
// Note: opener is `event.currentTarget` | ||
}); | ||
}) | ||
@@ -244,7 +264,7 @@ dialog.on('hide', function (dialogEl, event) { | ||
// Note: closer is `event.currentTarget` | ||
}); | ||
}) | ||
dialog.on('destroy', function (dialogEl) { | ||
// Do something when dialog gets destroyed | ||
}); | ||
}) | ||
@@ -256,3 +276,3 @@ dialog.on('create', function (dialogEl) { | ||
// done after instantiation) | ||
}); | ||
}) | ||
``` | ||
@@ -263,8 +283,7 @@ | ||
```javascript | ||
dialog.on('show', doSomething); | ||
dialog.on('show', doSomething) | ||
// … | ||
dialog.off('show', doSomething); | ||
dialog.off('show', doSomething) | ||
``` | ||
## Nested dialogs | ||
@@ -271,0 +290,0 @@ |
35692
451
286