From 4be2854814ddaed074d1ad96e9124854b295270d Mon Sep 17 00:00:00 2001 From: Michael Dow Date: Sat, 24 Feb 2018 16:41:50 -0600 Subject: [PATCH 1/2] Added accessibility features for tabbing and aria labels --- src/vex.js | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/src/vex.js b/src/vex.js index 7b3fcf1..0c4ed76 100644 --- a/src/vex.js +++ b/src/vex.js @@ -36,11 +36,11 @@ var addClasses = function addClasses (el, classStr) { var animationEndEvent = (function detectAnimationEndEvent () { var el = document.createElement('div') var eventNames = { - 'animation': 'animationend', 'WebkitAnimation': 'webkitAnimationEnd', 'MozAnimation': 'animationend', 'OAnimation': 'oanimationend', - 'msAnimation': 'MSAnimationEnd' + 'msAnimation': 'MSAnimationEnd', + 'animation': 'animationend' } for (var i in eventNames) { if (el.style[i] !== undefined) { @@ -162,6 +162,12 @@ var vex = { if (Object.keys(vexes).length === 0) { document.body.classList.remove(baseClassNames.open) } + //Give focus back to initial element + if (options.giveBackFocus) { + focusedElementBefore.focus(); + } + document.querySelector(options.ariaContentMain).setAttribute('aria-hidden', false) + }.bind(this) // Close the vex @@ -208,6 +214,7 @@ var vex = { // Overlay var overlayEl = vexInstance.overlayEl = document.createElement('div') overlayEl.classList.add(baseClassNames.overlay) + overlayEl.tabIndex = -1 addClasses(overlayEl, options.overlayClassName) if (options.overlayClosesOnClick) { rootEl.addEventListener('click', function overlayClickListener (e) { @@ -221,13 +228,58 @@ var vex = { // Content var contentEl = vexInstance.contentEl = document.createElement('div') contentEl.classList.add(baseClassNames.content) + contentEl.setAttribute('role', 'dialog') + contentEl.setAttribute('aria-hidden', false) + contentEl.setAttribute('aria-labelledBy', options.message) addClasses(contentEl, options.contentClassName) contentEl.appendChild(options.content instanceof window.Node ? options.content : domify(options.content)) rootEl.appendChild(contentEl) + + //Accessibility features + //Get initial element + var focusedElementBefore = document.activeElement; + + document.querySelector(options.ariaContentMain).setAttribute('aria-hidden', true) + + if (options.trapTabKey) { + contentEl.onkeydown = function(event) { + var focusableElementsString = "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea, button, iframe, object, embed, *[tabindex], *[contenteditable]" + var focusableElements = contentEl.querySelectorAll(focusableElementsString) + + //Tab key pressed + if (event.which == 9) { + console.log(focusableElements.length) + var visibleFocusableElements = focusableElements + + var focusedElement = document.activeElement + var numberOfFocusableELements = visibleFocusableElements.length + + var focusedElementIndex = Array.prototype.indexOf.call(visibleFocusableElements, focusedElement) + + if (event.shiftKey) { + //back tab + // if focused on first item and user preses back-tab, go to the last focusable item + if (focusedElementIndex == 0) { + visibleFocusableElements.item(numberOfFocusableELements - 1).focus() + event.preventDefault() + } + } else { + console.log("tab overrided") + //forward tab + // if focused on the last item and user preses tab, go to the first focusable item + if (focusedElementIndex == numberOfFocusableELements - 1) { + visibleFocusableElements.item(0).focus(); + event.preventDefault(); + } + } + } + } + } // Close button if (options.showCloseButton) { - var closeEl = vexInstance.closeEl = document.createElement('div') + var closeEl = vexInstance.closeEl = document.createElement('button') + closeEl.setAttribute('aria-label', 'close') closeEl.classList.add(baseClassNames.close) addClasses(closeEl, options.closeClassName) closeEl.addEventListener('click', vexInstance.close.bind(vexInstance)) @@ -319,7 +371,10 @@ vex.defaultOptions = { overlayClassName: '', contentClassName: '', closeClassName: '', - closeAllOnPopState: true + closeAllOnPopState: true, + giveBackFocus: true, + trapTabKey: true, + ariaContentMain: 'body:not([vex])' } // TODO Loading symbols? From 7e1df7b08b45e2c3aa3a81801d47f4ed6de964aa Mon Sep 17 00:00:00 2001 From: Michael Dow Date: Sat, 24 Feb 2018 18:13:25 -0600 Subject: [PATCH 2/2] Accessibility fixes --- dist/js/vex.combined.js | 484 ++++++++++++++++++++---------------- dist/js/vex.combined.min.js | 3 +- dist/js/vex.js | 484 ++++++++++++++++++++---------------- dist/js/vex.min.js | 3 +- src/vex.js | 6 +- test/build-test.html | 21 +- 6 files changed, 556 insertions(+), 445 deletions(-) diff --git a/dist/js/vex.combined.js b/dist/js/vex.combined.js index 2ab581e..9776fec 100644 --- a/dist/js/vex.combined.js +++ b/dist/js/vex.combined.js @@ -1,244 +1,243 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vex = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o :not(.vex)' } // TODO Loading symbols? diff --git a/dist/js/vex.combined.min.js b/dist/js/vex.combined.min.js index a40cda1..e9e6b18 100644 --- a/dist/js/vex.combined.min.js +++ b/dist/js/vex.combined.min.js @@ -1,2 +1,3 @@ /*! vex.combined.js: vex 4.0.1, vex-dialog 1.0.7 */ -!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.vex=a()}}(function(){var a;return function b(a,c,d){function e(g,h){if(!c[g]){if(!a[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};a[g][0].call(k.exports,function(b){var c=a[g][1][b];return e(c?c:b)},k,k.exports,b,a,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g
a',f=!e.getElementsByTagName("link").length,e=void 0);var g={legend:[1,"
","
"],tr:[2,"","
"],col:[2,"","
"],_default:f?[1,"X
","
"]:[0,"",""]};g.td=g.th=[3,"","
"],g.option=g.optgroup=[1,'"],g.thead=g.tbody=g.colgroup=g.caption=g.tfoot=[1,"","
"],g.polyline=g.ellipse=g.polygon=g.circle=g.text=g.line=g.path=g.rect=g.g=[1,'',""]},{}],3:[function(a,b,c){"use strict";function d(a,b){if(void 0===a||null===a)throw new TypeError("Cannot convert first argument to object");for(var c=Object(a),d=1;d
a',f=!e.getElementsByTagName("link").length,e=void 0);var g={legend:[1,"
","
"],tr:[2,"","
"],col:[2,"","
"],_default:f?[1,"X
","
"]:[0,"",""]};g.td=g.th=[3,"","
"],g.option=g.optgroup=[1,'"],g.thead=g.tbody=g.colgroup=g.caption=g.tfoot=[1,"","
"],g.polyline=g.ellipse=g.polygon=g.circle=g.text=g.line=g.path=g.rect=g.g=[1,'',""]},{}],2:[function(a,b,c){function d(a,b){"object"!=typeof b?b={hash:!!b}:void 0===b.hash&&(b.hash=!0);for(var c=b.hash?{}:"",d=b.serializer||(b.hash?g:h),e=a&&a.elements?a.elements:[],f=Object.create(null),k=0;k'+a._escapeHtml(b.label||c.label)+"",input:''};b=Object.assign(c,d,b);var e=b.callback;return b.callback=function(a){if("object"==typeof a){var b=Object.keys(a);a=b.length?a[b[0]]:""}e(a)},this.open(b)}};return b.buttons={YES:{text:"OK",type:"submit",className:"vex-dialog-button-primary",click:function(){this.value=!0}},NO:{text:"Cancel",type:"button",className:"vex-dialog-button-secondary",click:function(){this.value=!1,this.close()}}},b.defaultOptions={callback:function(){},afterOpen:function(){},message:"",input:"",buttons:[b.buttons.YES,b.buttons.NO],showCloseButton:!1,onSubmit:function(a){return a.preventDefault(),this.options.input&&(this.value=e(this.form,{hash:!0})),this.close()},focusFirstInput:!0},b.defaultAlertOptions={buttons:[b.buttons.YES]},b.defaultPromptOptions={label:"Prompt:",placeholder:"",value:""},b.defaultConfirmOptions={},b};b.exports=h},{domify:1,"form-serialize":2}]},{},[3])(3)})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{domify:2,"form-serialize":4}],6:[function(a,b,c){var d=a("./vex");d.registerPlugin(a("vex-dialog")),b.exports=d},{"./vex":7,"vex-dialog":5}],7:[function(a,b,c){a("classlist-polyfill"),a("es6-object-assign").polyfill();var d=a("domify"),e=function(a){if("undefined"!=typeof a){var b=document.createElement("div");return b.appendChild(document.createTextNode(a)),b.innerHTML}return""},f=function(a,b){if("string"==typeof b&&0!==b.length)for(var c=b.split(" "),d=0;d
a',o=!i.getElementsByTagName("link").length,i=void 0);var r={legend:[1,"
","
"],tr:[2,"","
"],col:[2,"","
"],_default:o?[1,"X
","
"]:[0,"",""]};r.td=r.th=[3,"","
"],r.option=r.optgroup=[1,'"],r.thead=r.tbody=r.colgroup=r.caption=r.tfoot=[1,"","
"],r.polyline=r.ellipse=r.polygon=r.circle=r.text=r.line=r.path=r.rect=r.g=[1,'',""]},{}],3:[function(e,t,n){"use strict";function i(e,t){if(null==e)throw new TypeError("Cannot convert first argument to object");for(var n=Object(e),i=1;i
a',o=!i.getElementsByTagName("link").length,i=void 0);var r={legend:[1,"
","
"],tr:[2,"","
"],col:[2,"","
"],_default:o?[1,"X
","
"]:[0,"",""]};r.td=r.th=[3,"","
"],r.option=r.optgroup=[1,'"],r.thead=r.tbody=r.colgroup=r.caption=r.tfoot=[1,"","
"],r.polyline=r.ellipse=r.polygon=r.circle=r.text=r.line=r.path=r.rect=r.g=[1,'',""]},{}],2:[function(e,t,n){var i=/^(?:submit|button|image|reset|file)$/i,o=/^(?:input|select|textarea|keygen)/i,r=/(\[[^\[\]]*\])/g;function a(e,t,n){if(t.match(r)){!function e(t,n,i){if(0===n.length)return t=i;var o=n.shift(),r=o.match(/^\[(.+?)\]$/);if("[]"===o)return t=t||[],Array.isArray(t)?t.push(e(null,n,i)):(t._values=t._values||[],t._values.push(e(null,n,i))),t;if(r){var a=r[1],s=+a;isNaN(s)?(t=t||{})[a]=e(t[a],n,i):(t=t||[])[s]=e(t[s],n,i)}else t[o]=e(t[o],n,i);return t}(e,function(e){var t=[],n=new RegExp(r),i=/^([^\[\]]*)/.exec(e);for(i[1]&&t.push(i[1]);null!==(i=n.exec(e));)t.push(i[1]);return t}(t),n)}else{var i=e[t];i?(Array.isArray(i)||(e[t]=[i]),e[t].push(n)):e[t]=n}return e}function s(e,t,n){return n=n.replace(/(\r)?\n/g,"\r\n"),n=(n=encodeURIComponent(n)).replace(/%20/g,"+"),e+(e?"&":"")+encodeURIComponent(t)+"="+n}t.exports=function(e,t){"object"!=typeof t?t={hash:!!t}:void 0===t.hash&&(t.hash=!0);for(var n=t.hash?{}:"",r=t.serializer||(t.hash?a:s),l=e&&e.elements?e.elements:[],c=Object.create(null),u=0;u'+e._escapeHtml(t.label||n.label)+"",input:''},o=(t=Object.assign(n,i,t)).callback;return t.callback=function(e){if("object"==typeof e){var t=Object.keys(e);e=t.length?e[t[0]]:""}o(e)},this.open(t)},buttons:{YES:{text:"OK",type:"submit",className:"vex-dialog-button-primary",click:function(){this.value=!0}},NO:{text:"Cancel",type:"button",className:"vex-dialog-button-secondary",click:function(){this.value=!1,this.close()}}}};return t.defaultOptions={callback:function(){},afterOpen:function(){},message:"",input:"",buttons:[t.buttons.YES,t.buttons.NO],showCloseButton:!1,onSubmit:function(e){return e.preventDefault(),this.options.input&&(this.value=o(this.form,{hash:!0})),this.close()},focusFirstInput:!0},t.defaultAlertOptions={buttons:[t.buttons.YES]},t.defaultPromptOptions={label:"Prompt:",placeholder:"",value:""},t.defaultConfirmOptions={},t}},{domify:1,"form-serialize":2}]},{},[3])(3)})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{domify:2,"form-serialize":4}],6:[function(e,t,n){var i=e("./vex");i.registerPlugin(e("vex-dialog")),t.exports=i},{"./vex":7,"vex-dialog":5}],7:[function(e,t,n){e("classlist-polyfill"),e("es6-object-assign").polyfill();var i=e("domify"),o=function(e){if(void 0!==e){var t=document.createElement("div");return t.appendChild(document.createTextNode(e)),t.innerHTML}return""},r=function(e,t){if("string"==typeof t&&0!==t.length)for(var n=t.split(" "),i=0;i :not(.vex)"},Object.defineProperty(m,"_escapeHtml",{configurable:!1,enumerable:!1,writable:!1,value:o}),m.registerPlugin=function(e,t){var n=e(m),i=t||n.name;if(m[i])throw new Error("Plugin "+t+" is already registered.");m[i]=n},t.exports=m},{"classlist-polyfill":1,domify:2,"es6-object-assign":3}]},{},[6])(6)}); \ No newline at end of file diff --git a/dist/js/vex.js b/dist/js/vex.js index 9ba0248..f325a95 100644 --- a/dist/js/vex.js +++ b/dist/js/vex.js @@ -1,244 +1,243 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vex = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o :not(.vex)' } // TODO Loading symbols? diff --git a/dist/js/vex.min.js b/dist/js/vex.min.js index e770d50..3a9f571 100644 --- a/dist/js/vex.min.js +++ b/dist/js/vex.min.js @@ -1,2 +1,3 @@ /*! vex.js 4.0.1 */ -!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.vex=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g
a',f=!e.getElementsByTagName("link").length,e=void 0);var g={legend:[1,"
","
"],tr:[2,"","
"],col:[2,"","
"],_default:f?[1,"X
","
"]:[0,"",""]};g.td=g.th=[3,"","
"],g.option=g.optgroup=[1,'"],g.thead=g.tbody=g.colgroup=g.caption=g.tfoot=[1,"","
"],g.polyline=g.ellipse=g.polygon=g.circle=g.text=g.line=g.path=g.rect=g.g=[1,'',""]},{}],3:[function(a,b,c){"use strict";function d(a,b){if(void 0===a||null===a)throw new TypeError("Cannot convert first argument to object");for(var c=Object(a),d=1;d
a',i=!o.getElementsByTagName("link").length,o=void 0);var r={legend:[1,"
","
"],tr:[2,"","
"],col:[2,"","
"],_default:i?[1,"X
","
"]:[0,"",""]};r.td=r.th=[3,"","
"],r.option=r.optgroup=[1,'"],r.thead=r.tbody=r.colgroup=r.caption=r.tfoot=[1,"","
"],r.polyline=r.ellipse=r.polygon=r.circle=r.text=r.line=r.path=r.rect=r.g=[1,'',""]},{}],3:[function(e,t,n){"use strict";function o(e,t){if(null==e)throw new TypeError("Cannot convert first argument to object");for(var n=Object(e),o=1;o :not(.vex)"},Object.defineProperty(m,"_escapeHtml",{configurable:!1,enumerable:!1,writable:!1,value:i}),m.registerPlugin=function(e,t){var n=e(m),o=t||n.name;if(m[o])throw new Error("Plugin "+t+" is already registered.");m[o]=n},t.exports=m},{"classlist-polyfill":1,domify:2,"es6-object-assign":3}]},{},[4])(4)}); \ No newline at end of file diff --git a/src/vex.js b/src/vex.js index 0c4ed76..2b85bc1 100644 --- a/src/vex.js +++ b/src/vex.js @@ -248,7 +248,6 @@ var vex = { //Tab key pressed if (event.which == 9) { - console.log(focusableElements.length) var visibleFocusableElements = focusableElements var focusedElement = document.activeElement @@ -264,7 +263,6 @@ var vex = { event.preventDefault() } } else { - console.log("tab overrided") //forward tab // if focused on the last item and user preses tab, go to the first focusable item if (focusedElementIndex == numberOfFocusableELements - 1) { @@ -278,7 +276,7 @@ var vex = { // Close button if (options.showCloseButton) { - var closeEl = vexInstance.closeEl = document.createElement('button') + var closeEl = vexInstance.closeEl = document.createElement('div') closeEl.setAttribute('aria-label', 'close') closeEl.classList.add(baseClassNames.close) addClasses(closeEl, options.closeClassName) @@ -374,7 +372,7 @@ vex.defaultOptions = { closeAllOnPopState: true, giveBackFocus: true, trapTabKey: true, - ariaContentMain: 'body:not([vex])' + ariaContentMain: 'body > :not(.vex)' } // TODO Loading symbols? diff --git a/test/build-test.html b/test/build-test.html index 7c770f5..c6505a2 100644 --- a/test/build-test.html +++ b/test/build-test.html @@ -14,31 +14,38 @@