accessible-menu
Advanced tools
Comparing version 1.0.3 to 1.0.4
@@ -5,2 +5,21 @@ # Changelog | ||
### [1.0.4](https://github.com/NickDJM/accessible-menu/compare/v1.0.3...v1.0.4) (2019-12-15) | ||
### Bug Fixes | ||
* **menu:** keep focus on root item after left/right arrows ([f0d1499](https://github.com/NickDJM/accessible-menu/commit/f0d149968e153b22b3fdd7ba6dc9c9fda0ed2edb)), closes [#50](https://github.com/NickDJM/accessible-menu/issues/50) | ||
* **menu:** prevent default event on arrow up/down on top level menus ([d9410f3](https://github.com/NickDJM/accessible-menu/commit/d9410f30955110e17fa7500afefb270f41cefbf3)), closes [#47](https://github.com/NickDJM/accessible-menu/issues/47) | ||
### Build System | ||
* **npm:** disallow .files from being packaged ([73e0579](https://github.com/NickDJM/accessible-menu/commit/73e0579a559b3529d65cb201c1aeb8f8f39efaa4)) | ||
* **npm:** ensure build is run and committed with release ([a9f03ff](https://github.com/NickDJM/accessible-menu/commit/a9f03ff2bac8809c16a564f2a7a267569f5d1c79)) | ||
### Code Refactoring | ||
* **main:** change main.js to index.js to keep to internal standard ([c4091ab](https://github.com/NickDJM/accessible-menu/commit/c4091abcc70c978511823911b293a2157ff72f67)) | ||
### [1.0.3](https://github.com/NickDJM/accessible-menu/compare/v1.0.2...v1.0.3) (2019-11-25) | ||
@@ -7,0 +26,0 @@ |
@@ -159,2 +159,3 @@ "use strict"; | ||
this.openClass = openClass; | ||
this.show = false; | ||
this.initialize(); | ||
@@ -196,23 +197,52 @@ } | ||
}, { | ||
key: "open", | ||
key: "expand", | ||
/** | ||
* Expands the submenu. | ||
*/ | ||
value: function expand() { | ||
// Assign new WAI-ARIA/class values. | ||
this.element.setAttribute("aria-expanded", "true"); | ||
this.parentElement.classList.add(this.openClass); | ||
this.menu.element.classList.add(this.openClass); | ||
} | ||
/** | ||
* Opens the submenu. | ||
*/ | ||
}, { | ||
key: "open", | ||
value: function open() { | ||
if (!this.isOpen) { | ||
// Set the open value. | ||
this.isOpen = true; // Assign new WAI-ARIA/class values. | ||
// Set the open value. | ||
this.isOpen = true; // Expand the menu. | ||
this.element.setAttribute("aria-expanded", "true"); | ||
this.parentElement.classList.add(this.openClass); | ||
this.menu.element.classList.add(this.openClass); // Close all sibling menus. | ||
this.expand(); // Close all sibling menus. | ||
this.closeSiblings(); // Set proper focus states to parent & child. | ||
this.closeSiblings(); // Set proper focus states to parent & child. | ||
if (this.parentMenu) this.parentMenu.currentFocus = "child"; | ||
this.menu.currentFocus = "self"; // Set the new focus. | ||
if (this.parentMenu) this.parentMenu.currentFocus = "child"; | ||
this.menu.currentFocus = "self"; // Set the new focus. | ||
this.menu.focusFirstChild(); | ||
this.menu.focusFirstChild(); | ||
} | ||
/** | ||
* Opens the submenu without focus entering it. | ||
*/ | ||
}, { | ||
key: "preview", | ||
value: function preview() { | ||
// Set the open value. | ||
this.isOpen = true; // Expand the menu. | ||
this.expand(); // Close all sibling menus. | ||
this.closeSiblings(); // Set proper focus states to parent & child. | ||
if (this.parentMenu) { | ||
this.parentMenu.currentFocus = "self"; | ||
this.parentMenu.focusCurrentChild(); | ||
} | ||
this.menu.currentFocus = "none"; | ||
} | ||
@@ -860,5 +890,17 @@ /** | ||
// - If focus is on the last item, moves focus to the first item. | ||
preventEvent(event); | ||
// - If focus was on an open submenu and the newly focussed item has a submenu, open the submenu. | ||
preventEvent(event); // Store the current item's info if its an open dropdown. | ||
_this4.focusNextChild(); | ||
var previousChildOpen = _this4.currentMenuItem.isSubmenuItem && _this4.currentMenuItem.toggle.isOpen; | ||
_this4.focusNextChild(); // Open the newly focussed submenu if applicable. | ||
if (previousChildOpen) { | ||
if (_this4.currentMenuItem.isSubmenuItem) { | ||
_this4.currentMenuItem.toggle.preview(); | ||
} else { | ||
_this4.closeChildren(); | ||
} | ||
} | ||
} else if (key === "ArrowLeft") { | ||
@@ -868,5 +910,17 @@ // Hitting the Left Arrow: | ||
// - If focus is on the first item, moves focus to the last item. | ||
preventEvent(event); | ||
// - If focus was on an open submenu and the newly focussed item has a submenu, open the submenu. | ||
preventEvent(event); // Store the current item's info if its an open dropdown. | ||
_this4.focusPreviousChild(); | ||
var _previousChildOpen = _this4.currentMenuItem.isSubmenuItem && _this4.currentMenuItem.toggle.isOpen; | ||
_this4.focusPreviousChild(); // Open the newly focussed submenu if applicable. | ||
if (_previousChildOpen) { | ||
if (_this4.currentMenuItem.isSubmenuItem) { | ||
_this4.currentMenuItem.toggle.preview(); | ||
} else { | ||
_this4.closeChildren(); | ||
} | ||
} | ||
} else if (key === "ArrowDown") { | ||
@@ -876,2 +930,4 @@ // Hitting the Down Arrow: | ||
if (_this4.currentMenuItem.isSubmenuItem) { | ||
preventEvent(event); | ||
_this4.currentMenuItem.toggle.open(); | ||
@@ -885,2 +941,4 @@ | ||
if (_this4.currentMenuItem.isSubmenuItem) { | ||
preventEvent(event); | ||
_this4.currentMenuItem.toggle.open(); | ||
@@ -933,7 +991,9 @@ | ||
// - Opens submenu of newly focused menubar item, keeping focus on that parent menubar item. | ||
preventEvent(event); | ||
if (_this4.currentMenuItem.isSubmenuItem) { | ||
preventEvent(event); | ||
if (_this4.currentMenuItem.isSubmenuItem) { | ||
_this4.currentMenuItem.toggle.open(); | ||
} else { | ||
preventEvent(event); | ||
_this4.rootMenu.closeChildren(); | ||
@@ -944,3 +1004,3 @@ | ||
if (_this4.rootMenu.currentMenuItem.isSubmenuItem) { | ||
_this4.rootMenu.currentMenuItem.toggle.open(); | ||
_this4.rootMenu.currentMenuItem.toggle.preview(); | ||
} | ||
@@ -954,5 +1014,5 @@ } | ||
// - Opens submenu of newly focused menubar item, keeping focus on that parent menubar item. | ||
preventEvent(event); | ||
if (_this4.parentMenu.currentMenuItem.isSubmenuItem) { | ||
preventEvent(event); | ||
if (_this4.parentMenu.currentMenuItem.isSubmenuItem) { | ||
_this4.parentMenu.currentMenuItem.toggle.close(); | ||
@@ -966,3 +1026,3 @@ | ||
if (_this4.rootMenu.currentMenuItem.isSubmenuItem) { | ||
_this4.rootMenu.currentMenuItem.toggle.open(); | ||
_this4.rootMenu.currentMenuItem.toggle.preview(); | ||
} | ||
@@ -969,0 +1029,0 @@ } |
@@ -1,1 +0,1 @@ | ||
"use strict";function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function _createClass(e,t,n){return t&&_defineProperties(e.prototype,t),n&&_defineProperties(e,n),e}var AccessibleMenu=function(){var e={event:function(e){if(!(e instanceof Event))throw new TypeError("event must be an event.")},keyboardEvent:function(e){if(!(e instanceof KeyboardEvent))throw new TypeError("event must be a keyboard event.")}};function t(t){e.event(t),t.preventDefault(),t.stopPropagation()}var n={menuToggleElement:function(e){if(!(e instanceof HTMLElement))throw new TypeError("menuToggleElement must be an HTML Element.")},parentElement:function(e){if(!(e instanceof HTMLElement))throw new TypeError("parentElement must be an HTML Element.")},menu:function(e){if(!(e instanceof i))throw new TypeError("menu must be a Menu.")},openClass:function(e){if("string"!=typeof e)throw TypeError("openClass must be a string.");if(e.replace(/[_a-zA-Z0-9-]/g,"").length>0)throw Error("openClass must be a valid CSS class.")},parentMenu:function(e){if(null!==e&&!(e instanceof i))throw new TypeError("parentMenu must be a Menu.")}},r=function(){function e(t){var r=t.menuToggleElement,s=t.parentElement,o=t.menu,u=t.openClass,i=void 0===u?"show":u,l=t.parentMenu,c=void 0===l?null:l;_classCallCheck(this,e),n.menuToggleElement(r),n.parentElement(s),n.menu(o),n.openClass(i),n.parentMenu(c),this.domElements={toggle:r,parent:s},this.elements={menu:o,parentMenu:c},this.openClass=i,this.initialize()}return _createClass(e,[{key:"initialize",value:function(){if(this.element.setAttribute("aria-haspopup","true"),this.element.setAttribute("aria-expanded","false"),this.element.setAttribute("role","button"),""===this.element.id||""===this.menu.element.id){var e=Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,10),t="".concat(this.element.innerText.toLowerCase().replace(/[^a-zA-Z0-9\s]/g,"").replace(/\s/g,"-"),"-").concat(e);this.element.id=this.element.id||"".concat(t,"-menu-button"),this.menu.element.id=this.menu.element.id||"".concat(t,"-menu")}this.menu.element.setAttribute("aria-labelledby",this.element.id),this.element.setAttribute("aria-controls",this.menu.element.id),this.handleClick()}},{key:"open",value:function(){this.isOpen||(this.isOpen=!0,this.element.setAttribute("aria-expanded","true"),this.parentElement.classList.add(this.openClass),this.menu.element.classList.add(this.openClass),this.closeSiblings(),this.parentMenu&&(this.parentMenu.currentFocus="child"),this.menu.currentFocus="self",this.menu.focusFirstChild())}},{key:"close",value:function(){this.isOpen&&(this.isOpen=!1,this.element.setAttribute("aria-expanded","false"),this.parentElement.classList.remove(this.openClass),this.menu.element.classList.remove(this.openClass),this.closeChildren(),this.menu.blur(),this.parentMenu?(this.parentMenu.currentFocus="self",this.parentMenu.focusCurrentChild()):this.menu.isTopLevel&&this.menu.focusController())}},{key:"toggle",value:function(){this.isOpen?this.close():this.open()}},{key:"closeSiblings",value:function(){var e=this;try{this.parentMenu.menuToggles.forEach((function(t){t!==e&&t.close()}))}catch(e){}}},{key:"closeChildren",value:function(){this.menu.menuToggles.forEach((function(e){return e.close()}))}},{key:"handleClick",value:function(){var e=this;this.element.addEventListener("click",(function(n){t(n),e.toggle()}))}},{key:"element",get:function(){return this.domElements.toggle}},{key:"parentElement",get:function(){return this.domElements.parent}},{key:"menu",get:function(){return this.elements.menu}},{key:"parentMenu",get:function(){return this.elements.parentMenu}},{key:"isOpen",get:function(){return this.show},set:function(e){if("boolean"!=typeof e)throw new TypeError("Open state must be true or false.");this.show=e}}]),e}(),s={menuItemElement:function(e){if(!(e instanceof HTMLElement))throw new TypeError("menuItemElement must be an HTML Element.")},menuLinkElement:function(e){if(!(e instanceof HTMLElement))throw new TypeError("menuLinkElement must be an HTML Element.")},parentMenu:function(e){if(!(e instanceof i))throw new TypeError("parentMenu must be a Menu.")},isSubmenuItem:function(e){if("boolean"!=typeof e)throw new TypeError("isSubmenuItem must be true or false")},childMenu:function(e){if(null!==e&&!(e instanceof i))throw new TypeError("childMenu must be a Menu.")},toggle:function(e){if(null!==e&&!(e instanceof r))throw new TypeError("toggle must be a MenuToggle.")}},o=function(){function e(t){var n=t.menuItemElement,r=t.menuLinkElement,o=t.parentMenu,u=t.isSubmenuItem,i=void 0!==u&&u,l=t.childMenu,c=void 0===l?null:l,m=t.toggle,a=void 0===m?null:m;_classCallCheck(this,e),s.menuItemElement(n),s.menuLinkElement(r),s.parentMenu(o),s.isSubmenuItem(i),s.childMenu(c),s.toggle(a),this.domElements={menuItem:n,link:r},this.elements={parentMenu:o,childMenu:c,toggle:a},this.isController=i,this.initialize()}return _createClass(e,[{key:"initialize",value:function(){this.element.setAttribute("role","menuitem"),this.linkElement.tabIndex=-1}},{key:"focus",value:function(){this.linkElement.focus()}},{key:"blur",value:function(){this.linkElement.blur()}},{key:"element",get:function(){return this.domElements.menuItem}},{key:"linkElement",get:function(){return this.domElements.link}},{key:"parentMenu",get:function(){return this.elements.parentMenu}},{key:"childMenu",get:function(){return this.elements.childMenu}},{key:"toggle",get:function(){return this.elements.toggle}},{key:"isSubmenuItem",get:function(){return this.isController}}]),e}(),u={menuElement:function(e){if(!(e instanceof HTMLElement))throw new TypeError("menuElement must be an HTML Element.")},menuItemSelector:function(e){if("string"!=typeof e)throw new TypeError("menuItemSelector must be a CSS selector string.")},hasSubmenus:function(e,t,n){if(null!==e||null!==t||null!==n){if("string"!=typeof e)throw new TypeError("submenuItemSelector must be a CSS selector string.");if("string"!=typeof e)throw new TypeError("submenuToggleSelector must be a CSS selector string.");if("string"!=typeof n)throw new TypeError("submenuSelector must be a CSS selector string.")}},openClass:function(e){if("string"!=typeof e)throw TypeError("openClass must be a string.");if(e.replace(/[_a-zA-Z0-9-]/g,"").length>0)throw Error("openClass must be a valid CSS class.")},isTopLevel:function(e){if("boolean"!=typeof e)throw new TypeError("isTopLevel must be true of false.")},isDropdown:function(e,t){if(null!==e||null!==t){if(!(e instanceof HTMLElement))throw new TypeError("controllerElement must be an HTML Element if containerElement is provided.");if(!(t instanceof HTMLElement))throw new TypeError("containerElement must be an HTML Element if controllerElement is provided.")}},parentMenu:function(e){if(null!==e&&!(e instanceof i))throw new TypeError("parentMenu must be a Menu.")}},i=function(){function n(e){var t=e.menuElement,r=e.menuItemSelector,s=e.submenuItemSelector,o=void 0===s?null:s,i=e.submenuToggleSelector,l=void 0===i?null:i,c=e.submenuSelector,m=void 0===c?null:c,a=e.openClass,h=void 0===a?"show":a,f=e.isTopLevel,d=void 0===f||f,p=e.controllerElement,g=void 0===p?null:p,E=e.containerElement,C=void 0===E?null:E,y=e.parentMenu,b=void 0===y?null:y;_classCallCheck(this,n),u.menuElement(t),u.menuItemSelector(r),u.hasSubmenus(o,l,m),u.openClass(h),u.isTopLevel(d),u.isDropdown(g,C),u.parentMenu(b),this.domElements={menu:t,controller:g,container:C,menuItems:Array.from(t.querySelectorAll(r)).filter((function(e){return e.parentElement===t})),submenuItems:Array.from(t.querySelectorAll(o)).filter((function(e){return e.parentElement===t}))},this.domSelectors={"menu-items":r,"submenu-items":o,"submenu-toggle":l,submenu:m},this.elements={menuItems:[],menuToggles:[],controller:null,parentMenu:b,rootMenu:d?this:null},this.focussedChild=-1,this.focusState="none",this.openClass=h,this.root=d,this.initialize()}return _createClass(n,[{key:"initialize",value:function(){if(this.element.setAttribute("role","menubar"),this.element.tabIndex=0,null===this.rootMenu&&this.findRootMenu(this),this.createMenuItems(),this.handleKeydown(),this.handleClick(),this.isTopLevel&&this.controllerElement&&this.containerElement){var e=new r({menuToggleElement:this.controllerElement,parentElement:this.containerElement,menu:this,openClass:this.openClass});this.elements.controller=e}}},{key:"findRootMenu",value:function(e){if(e.isTopLevel)this.elements.rootMenu=e;else{if(null===e.parentMenu)throw new Error("Cannot find root menu.");this.findRootMenu(e.parentMenu)}}},{key:"createMenuItems",value:function(){var e=this;this.menuItemElements.forEach((function(t){var s;if(e.submenuItemElements.includes(t)){var u=t.querySelector(e.selector["submenu-toggle"]),i=new n({menuElement:t.querySelector(e.selector.submenu),menuItemSelector:e.selector["menu-items"],submenuItemSelector:e.selector["submenu-items"],submenuToggleSelector:e.selector["submenu-toggle"],submenuSelector:e.selector.submenu,openClass:e.openClass,isTopLevel:!1,parentMenu:e}),l=new r({menuToggleElement:u,parentElement:t,menu:i,openClass:e.openClass,parentMenu:e});e.elements.menuToggles.push(l),s=new o({menuItemElement:t,menuLinkElement:u,parentMenu:e,isSubmenuItem:!0,childMenu:i,toggle:l})}else{var c=t.querySelector("a");s=new o({menuItemElement:t,menuLinkElement:c,parentMenu:e})}e.elements.menuItems.push(s)}))}},{key:"handleKeydown",value:function(){var n=this;this.element.addEventListener("keydown",(function(r){var s=function(t){e.keyboardEvent(t);try{var n=t.key||t.keyCode,r={Enter:"Enter"===n||13===n,Space:" "===n||"Spacebar"===n||32===n,Escape:"Escape"===n||"Esc"===n||27===n,ArrowUp:"ArrowUp"===n||"Up"===n||38===n,ArrowRight:"ArrowRight"===n||"Right"===n||39===n,ArrowDown:"ArrowDown"===n||"Down"===n||40===n,ArrowLeft:"ArrowLeft"===n||"Left"===n||37===n,Home:"Home"===n||36===n,End:"End"===n||35===n,Character:!!n.match(/^[a-zA-Z]{1}$/),Tab:"Tab"===n||9===n};return Object.keys(r).find((function(e){return!0===r[e]}))}catch(e){return""}}(r),o=r.altKey,u=r.crtlKey,i=r.metaKey,l=o||u||i;n.isTopLevel?"none"===n.currentFocus?"Space"!==s&&"Enter"!==s||(t(r),n.currentFocus="self",n.focusFirstChild()):"self"===n.currentFocus&&("ArrowRight"===s?(t(r),n.focusNextChild()):"ArrowLeft"===s?(t(r),n.focusPreviousChild()):"ArrowDown"===s?n.currentMenuItem.isSubmenuItem&&(n.currentMenuItem.toggle.open(),n.currentMenuItem.childMenu.focusFirstChild()):"ArrowUp"===s?n.currentMenuItem.isSubmenuItem&&(n.currentMenuItem.toggle.open(),n.currentMenuItem.childMenu.focusLastChild()):"Home"===s?(t(r),n.focusFirstChild()):"End"===s?(t(r),n.focusLastChild()):"Escape"===s&&null!==n.controller&&n.controller.close()):"Space"===s||"Enter"===s?(t(r),n.currentMenuItem.linkElement.click()):"Escape"===s?(t(r),n.rootMenu.closeChildren(),n.rootMenu.focusCurrentChild()):"ArrowRight"===s?(t(r),n.currentMenuItem.isSubmenuItem?n.currentMenuItem.toggle.open():(n.rootMenu.closeChildren(),n.rootMenu.focusNextChild(),n.rootMenu.currentMenuItem.isSubmenuItem&&n.rootMenu.currentMenuItem.toggle.open())):"ArrowLeft"===s?(t(r),n.parentMenu.currentMenuItem.isSubmenuItem&&(n.parentMenu.currentMenuItem.toggle.close(),n.parentMenu===n.rootMenu&&(n.rootMenu.closeChildren(),n.rootMenu.focusPreviousChild(),n.rootMenu.currentMenuItem.isSubmenuItem&&n.rootMenu.currentMenuItem.toggle.open()))):"ArrowDown"===s?(t(r),n.focusNextChild()):"ArrowUp"===s?(t(r),n.focusPreviousChild()):"Home"===s?(t(r),n.focusFirstChild()):"End"===s&&(t(r),n.focusLastChild()),"Character"!==s||l||(t(r),n.focusNextChildWithCharacter(r.key)),"none"!==n.currentFocus&&"Tab"===s&&(n.rootMenu.blur(),n.rootMenu.closeChildren())}))}},{key:"handleClick",value:function(){var e=this;document.addEventListener("click",(function(t){e.element.contains(t.target)||e.element===t.target||(e.blur(),e.closeChildren(),e.controller&&e.controller.close())}))}},{key:"focus",value:function(){this.focussedChild=0,this.currentFocus="self",this.element.focus()}},{key:"blur",value:function(){this.focussedChild=-1,this.currentFocus="none",this.element.blur()}},{key:"focusFirstChild",value:function(){this.focussedChild=0,this.focusCurrentChild()}},{key:"focusLastChild",value:function(){this.focussedChild=this.menuItems.length-1,this.focusCurrentChild()}},{key:"focusNextChild",value:function(){this.focussedChild===this.menuItems.length-1?this.focusFirstChild():(this.focussedChild=this.focussedChild+1,this.focusCurrentChild())}},{key:"focusPreviousChild",value:function(){0===this.focussedChild?this.focusLastChild():(this.focussedChild=this.focussedChild-1,this.focusCurrentChild())}},{key:"focusCurrentChild",value:function(){-1!==this.focussedChild&&this.menuItems[this.focussedChild].focus()}},{key:"focusNextChildWithCharacter",value:function(e){for(var t=e.toLowerCase(),n=this.focussedChild+1,r=!1;!r&&n<this.menuItems.length;){this.menuItems[n].element.innerText.toLowerCase().startsWith(t)&&(r=!0,this.focussedChild=n,this.focusCurrentChild()),n++}}},{key:"focusController",value:function(){this.controllerElement&&(this.controllerElement.focus(),this.currentFocus="none")}},{key:"focusContainer",value:function(){this.containerElement&&(this.containerElement.focus(),this.currentFocus="none")}},{key:"closeChildren",value:function(){this.menuToggles.forEach((function(e){return e.close()}))}},{key:"element",get:function(){return this.domElements.menu}},{key:"controllerElement",get:function(){return this.domElements.controller}},{key:"containerElement",get:function(){return this.domElements.container}},{key:"menuItemElements",get:function(){return this.domElements.menuItems}},{key:"submenuItemElements",get:function(){return this.domElements.submenuItems}},{key:"menuItems",get:function(){return this.elements.menuItems}},{key:"menuToggles",get:function(){return this.elements.menuToggles}},{key:"parentMenu",get:function(){return this.elements.parentMenu}},{key:"rootMenu",get:function(){return this.elements.rootMenu}},{key:"controller",get:function(){return this.elements.controller}},{key:"selector",get:function(){return this.domSelectors}},{key:"currentFocus",get:function(){return this.focusState},set:function(e){if(!["self","child","none"].includes(e))throw new Error("Focus state must be 'self', 'child', or 'none'.");this.focusState=e}},{key:"openClass",get:function(){return this.submenuOpenClass},set:function(e){if("string"!=typeof e)throw new TypeError("Class must be a string.");this.submenuOpenClass=e}},{key:"currentMenuItem",get:function(){return this.menuItems[this.focussedChild]}},{key:"isTopLevel",get:function(){return this.root}}]),n}();return i}(); | ||
"use strict";function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function _createClass(e,t,n){return t&&_defineProperties(e.prototype,t),n&&_defineProperties(e,n),e}var AccessibleMenu=function(){var e={event:function(e){if(!(e instanceof Event))throw new TypeError("event must be an event.")},keyboardEvent:function(e){if(!(e instanceof KeyboardEvent))throw new TypeError("event must be a keyboard event.")}};function t(t){e.event(t),t.preventDefault(),t.stopPropagation()}var n={menuToggleElement:function(e){if(!(e instanceof HTMLElement))throw new TypeError("menuToggleElement must be an HTML Element.")},parentElement:function(e){if(!(e instanceof HTMLElement))throw new TypeError("parentElement must be an HTML Element.")},menu:function(e){if(!(e instanceof i))throw new TypeError("menu must be a Menu.")},openClass:function(e){if("string"!=typeof e)throw TypeError("openClass must be a string.");if(e.replace(/[_a-zA-Z0-9-]/g,"").length>0)throw Error("openClass must be a valid CSS class.")},parentMenu:function(e){if(null!==e&&!(e instanceof i))throw new TypeError("parentMenu must be a Menu.")}},r=function(){function e(t){var r=t.menuToggleElement,s=t.parentElement,u=t.menu,o=t.openClass,i=void 0===o?"show":o,l=t.parentMenu,c=void 0===l?null:l;_classCallCheck(this,e),n.menuToggleElement(r),n.parentElement(s),n.menu(u),n.openClass(i),n.parentMenu(c),this.domElements={toggle:r,parent:s},this.elements={menu:u,parentMenu:c},this.openClass=i,this.show=!1,this.initialize()}return _createClass(e,[{key:"initialize",value:function(){if(this.element.setAttribute("aria-haspopup","true"),this.element.setAttribute("aria-expanded","false"),this.element.setAttribute("role","button"),""===this.element.id||""===this.menu.element.id){var e=Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,10),t="".concat(this.element.innerText.toLowerCase().replace(/[^a-zA-Z0-9\s]/g,"").replace(/\s/g,"-"),"-").concat(e);this.element.id=this.element.id||"".concat(t,"-menu-button"),this.menu.element.id=this.menu.element.id||"".concat(t,"-menu")}this.menu.element.setAttribute("aria-labelledby",this.element.id),this.element.setAttribute("aria-controls",this.menu.element.id),this.handleClick()}},{key:"expand",value:function(){this.element.setAttribute("aria-expanded","true"),this.parentElement.classList.add(this.openClass),this.menu.element.classList.add(this.openClass)}},{key:"open",value:function(){this.isOpen=!0,this.expand(),this.closeSiblings(),this.parentMenu&&(this.parentMenu.currentFocus="child"),this.menu.currentFocus="self",this.menu.focusFirstChild()}},{key:"preview",value:function(){this.isOpen=!0,this.expand(),this.closeSiblings(),this.parentMenu&&(this.parentMenu.currentFocus="self",this.parentMenu.focusCurrentChild()),this.menu.currentFocus="none"}},{key:"close",value:function(){this.isOpen&&(this.isOpen=!1,this.element.setAttribute("aria-expanded","false"),this.parentElement.classList.remove(this.openClass),this.menu.element.classList.remove(this.openClass),this.closeChildren(),this.menu.blur(),this.parentMenu?(this.parentMenu.currentFocus="self",this.parentMenu.focusCurrentChild()):this.menu.isTopLevel&&this.menu.focusController())}},{key:"toggle",value:function(){this.isOpen?this.close():this.open()}},{key:"closeSiblings",value:function(){var e=this;try{this.parentMenu.menuToggles.forEach((function(t){t!==e&&t.close()}))}catch(e){}}},{key:"closeChildren",value:function(){this.menu.menuToggles.forEach((function(e){return e.close()}))}},{key:"handleClick",value:function(){var e=this;this.element.addEventListener("click",(function(n){t(n),e.toggle()}))}},{key:"element",get:function(){return this.domElements.toggle}},{key:"parentElement",get:function(){return this.domElements.parent}},{key:"menu",get:function(){return this.elements.menu}},{key:"parentMenu",get:function(){return this.elements.parentMenu}},{key:"isOpen",get:function(){return this.show},set:function(e){if("boolean"!=typeof e)throw new TypeError("Open state must be true or false.");this.show=e}}]),e}(),s={menuItemElement:function(e){if(!(e instanceof HTMLElement))throw new TypeError("menuItemElement must be an HTML Element.")},menuLinkElement:function(e){if(!(e instanceof HTMLElement))throw new TypeError("menuLinkElement must be an HTML Element.")},parentMenu:function(e){if(!(e instanceof i))throw new TypeError("parentMenu must be a Menu.")},isSubmenuItem:function(e){if("boolean"!=typeof e)throw new TypeError("isSubmenuItem must be true or false")},childMenu:function(e){if(null!==e&&!(e instanceof i))throw new TypeError("childMenu must be a Menu.")},toggle:function(e){if(null!==e&&!(e instanceof r))throw new TypeError("toggle must be a MenuToggle.")}},u=function(){function e(t){var n=t.menuItemElement,r=t.menuLinkElement,u=t.parentMenu,o=t.isSubmenuItem,i=void 0!==o&&o,l=t.childMenu,c=void 0===l?null:l,m=t.toggle,a=void 0===m?null:m;_classCallCheck(this,e),s.menuItemElement(n),s.menuLinkElement(r),s.parentMenu(u),s.isSubmenuItem(i),s.childMenu(c),s.toggle(a),this.domElements={menuItem:n,link:r},this.elements={parentMenu:u,childMenu:c,toggle:a},this.isController=i,this.initialize()}return _createClass(e,[{key:"initialize",value:function(){this.element.setAttribute("role","menuitem"),this.linkElement.tabIndex=-1}},{key:"focus",value:function(){this.linkElement.focus()}},{key:"blur",value:function(){this.linkElement.blur()}},{key:"element",get:function(){return this.domElements.menuItem}},{key:"linkElement",get:function(){return this.domElements.link}},{key:"parentMenu",get:function(){return this.elements.parentMenu}},{key:"childMenu",get:function(){return this.elements.childMenu}},{key:"toggle",get:function(){return this.elements.toggle}},{key:"isSubmenuItem",get:function(){return this.isController}}]),e}(),o={menuElement:function(e){if(!(e instanceof HTMLElement))throw new TypeError("menuElement must be an HTML Element.")},menuItemSelector:function(e){if("string"!=typeof e)throw new TypeError("menuItemSelector must be a CSS selector string.")},hasSubmenus:function(e,t,n){if(null!==e||null!==t||null!==n){if("string"!=typeof e)throw new TypeError("submenuItemSelector must be a CSS selector string.");if("string"!=typeof e)throw new TypeError("submenuToggleSelector must be a CSS selector string.");if("string"!=typeof n)throw new TypeError("submenuSelector must be a CSS selector string.")}},openClass:function(e){if("string"!=typeof e)throw TypeError("openClass must be a string.");if(e.replace(/[_a-zA-Z0-9-]/g,"").length>0)throw Error("openClass must be a valid CSS class.")},isTopLevel:function(e){if("boolean"!=typeof e)throw new TypeError("isTopLevel must be true of false.")},isDropdown:function(e,t){if(null!==e||null!==t){if(!(e instanceof HTMLElement))throw new TypeError("controllerElement must be an HTML Element if containerElement is provided.");if(!(t instanceof HTMLElement))throw new TypeError("containerElement must be an HTML Element if controllerElement is provided.")}},parentMenu:function(e){if(null!==e&&!(e instanceof i))throw new TypeError("parentMenu must be a Menu.")}},i=function(){function n(e){var t=e.menuElement,r=e.menuItemSelector,s=e.submenuItemSelector,u=void 0===s?null:s,i=e.submenuToggleSelector,l=void 0===i?null:i,c=e.submenuSelector,m=void 0===c?null:c,a=e.openClass,h=void 0===a?"show":a,f=e.isTopLevel,p=void 0===f||f,d=e.controllerElement,g=void 0===d?null:d,E=e.containerElement,C=void 0===E?null:E,M=e.parentMenu,y=void 0===M?null:M;_classCallCheck(this,n),o.menuElement(t),o.menuItemSelector(r),o.hasSubmenus(u,l,m),o.openClass(h),o.isTopLevel(p),o.isDropdown(g,C),o.parentMenu(y),this.domElements={menu:t,controller:g,container:C,menuItems:Array.from(t.querySelectorAll(r)).filter((function(e){return e.parentElement===t})),submenuItems:Array.from(t.querySelectorAll(u)).filter((function(e){return e.parentElement===t}))},this.domSelectors={"menu-items":r,"submenu-items":u,"submenu-toggle":l,submenu:m},this.elements={menuItems:[],menuToggles:[],controller:null,parentMenu:y,rootMenu:p?this:null},this.focussedChild=-1,this.focusState="none",this.openClass=h,this.root=p,this.initialize()}return _createClass(n,[{key:"initialize",value:function(){if(this.element.setAttribute("role","menubar"),this.element.tabIndex=0,null===this.rootMenu&&this.findRootMenu(this),this.createMenuItems(),this.handleKeydown(),this.handleClick(),this.isTopLevel&&this.controllerElement&&this.containerElement){var e=new r({menuToggleElement:this.controllerElement,parentElement:this.containerElement,menu:this,openClass:this.openClass});this.elements.controller=e}}},{key:"findRootMenu",value:function(e){if(e.isTopLevel)this.elements.rootMenu=e;else{if(null===e.parentMenu)throw new Error("Cannot find root menu.");this.findRootMenu(e.parentMenu)}}},{key:"createMenuItems",value:function(){var e=this;this.menuItemElements.forEach((function(t){var s;if(e.submenuItemElements.includes(t)){var o=t.querySelector(e.selector["submenu-toggle"]),i=new n({menuElement:t.querySelector(e.selector.submenu),menuItemSelector:e.selector["menu-items"],submenuItemSelector:e.selector["submenu-items"],submenuToggleSelector:e.selector["submenu-toggle"],submenuSelector:e.selector.submenu,openClass:e.openClass,isTopLevel:!1,parentMenu:e}),l=new r({menuToggleElement:o,parentElement:t,menu:i,openClass:e.openClass,parentMenu:e});e.elements.menuToggles.push(l),s=new u({menuItemElement:t,menuLinkElement:o,parentMenu:e,isSubmenuItem:!0,childMenu:i,toggle:l})}else{var c=t.querySelector("a");s=new u({menuItemElement:t,menuLinkElement:c,parentMenu:e})}e.elements.menuItems.push(s)}))}},{key:"handleKeydown",value:function(){var n=this;this.element.addEventListener("keydown",(function(r){var s=function(t){e.keyboardEvent(t);try{var n=t.key||t.keyCode,r={Enter:"Enter"===n||13===n,Space:" "===n||"Spacebar"===n||32===n,Escape:"Escape"===n||"Esc"===n||27===n,ArrowUp:"ArrowUp"===n||"Up"===n||38===n,ArrowRight:"ArrowRight"===n||"Right"===n||39===n,ArrowDown:"ArrowDown"===n||"Down"===n||40===n,ArrowLeft:"ArrowLeft"===n||"Left"===n||37===n,Home:"Home"===n||36===n,End:"End"===n||35===n,Character:!!n.match(/^[a-zA-Z]{1}$/),Tab:"Tab"===n||9===n};return Object.keys(r).find((function(e){return!0===r[e]}))}catch(e){return""}}(r),u=r.altKey,o=r.crtlKey,i=r.metaKey,l=u||o||i;if(n.isTopLevel){if("none"===n.currentFocus)"Space"!==s&&"Enter"!==s||(t(r),n.currentFocus="self",n.focusFirstChild());else if("self"===n.currentFocus)if("ArrowRight"===s){t(r);var c=n.currentMenuItem.isSubmenuItem&&n.currentMenuItem.toggle.isOpen;n.focusNextChild(),c&&(n.currentMenuItem.isSubmenuItem?n.currentMenuItem.toggle.preview():n.closeChildren())}else if("ArrowLeft"===s){t(r);var m=n.currentMenuItem.isSubmenuItem&&n.currentMenuItem.toggle.isOpen;n.focusPreviousChild(),m&&(n.currentMenuItem.isSubmenuItem?n.currentMenuItem.toggle.preview():n.closeChildren())}else"ArrowDown"===s?n.currentMenuItem.isSubmenuItem&&(t(r),n.currentMenuItem.toggle.open(),n.currentMenuItem.childMenu.focusFirstChild()):"ArrowUp"===s?n.currentMenuItem.isSubmenuItem&&(t(r),n.currentMenuItem.toggle.open(),n.currentMenuItem.childMenu.focusLastChild()):"Home"===s?(t(r),n.focusFirstChild()):"End"===s?(t(r),n.focusLastChild()):"Escape"===s&&null!==n.controller&&n.controller.close()}else"Space"===s||"Enter"===s?(t(r),n.currentMenuItem.linkElement.click()):"Escape"===s?(t(r),n.rootMenu.closeChildren(),n.rootMenu.focusCurrentChild()):"ArrowRight"===s?n.currentMenuItem.isSubmenuItem?(t(r),n.currentMenuItem.toggle.open()):(t(r),n.rootMenu.closeChildren(),n.rootMenu.focusNextChild(),n.rootMenu.currentMenuItem.isSubmenuItem&&n.rootMenu.currentMenuItem.toggle.preview()):"ArrowLeft"===s?n.parentMenu.currentMenuItem.isSubmenuItem&&(t(r),n.parentMenu.currentMenuItem.toggle.close(),n.parentMenu===n.rootMenu&&(n.rootMenu.closeChildren(),n.rootMenu.focusPreviousChild(),n.rootMenu.currentMenuItem.isSubmenuItem&&n.rootMenu.currentMenuItem.toggle.preview())):"ArrowDown"===s?(t(r),n.focusNextChild()):"ArrowUp"===s?(t(r),n.focusPreviousChild()):"Home"===s?(t(r),n.focusFirstChild()):"End"===s&&(t(r),n.focusLastChild());"Character"!==s||l||(t(r),n.focusNextChildWithCharacter(r.key)),"none"!==n.currentFocus&&"Tab"===s&&(n.rootMenu.blur(),n.rootMenu.closeChildren())}))}},{key:"handleClick",value:function(){var e=this;document.addEventListener("click",(function(t){e.element.contains(t.target)||e.element===t.target||(e.blur(),e.closeChildren(),e.controller&&e.controller.close())}))}},{key:"focus",value:function(){this.focussedChild=0,this.currentFocus="self",this.element.focus()}},{key:"blur",value:function(){this.focussedChild=-1,this.currentFocus="none",this.element.blur()}},{key:"focusFirstChild",value:function(){this.focussedChild=0,this.focusCurrentChild()}},{key:"focusLastChild",value:function(){this.focussedChild=this.menuItems.length-1,this.focusCurrentChild()}},{key:"focusNextChild",value:function(){this.focussedChild===this.menuItems.length-1?this.focusFirstChild():(this.focussedChild=this.focussedChild+1,this.focusCurrentChild())}},{key:"focusPreviousChild",value:function(){0===this.focussedChild?this.focusLastChild():(this.focussedChild=this.focussedChild-1,this.focusCurrentChild())}},{key:"focusCurrentChild",value:function(){-1!==this.focussedChild&&this.menuItems[this.focussedChild].focus()}},{key:"focusNextChildWithCharacter",value:function(e){for(var t=e.toLowerCase(),n=this.focussedChild+1,r=!1;!r&&n<this.menuItems.length;){this.menuItems[n].element.innerText.toLowerCase().startsWith(t)&&(r=!0,this.focussedChild=n,this.focusCurrentChild()),n++}}},{key:"focusController",value:function(){this.controllerElement&&(this.controllerElement.focus(),this.currentFocus="none")}},{key:"focusContainer",value:function(){this.containerElement&&(this.containerElement.focus(),this.currentFocus="none")}},{key:"closeChildren",value:function(){this.menuToggles.forEach((function(e){return e.close()}))}},{key:"element",get:function(){return this.domElements.menu}},{key:"controllerElement",get:function(){return this.domElements.controller}},{key:"containerElement",get:function(){return this.domElements.container}},{key:"menuItemElements",get:function(){return this.domElements.menuItems}},{key:"submenuItemElements",get:function(){return this.domElements.submenuItems}},{key:"menuItems",get:function(){return this.elements.menuItems}},{key:"menuToggles",get:function(){return this.elements.menuToggles}},{key:"parentMenu",get:function(){return this.elements.parentMenu}},{key:"rootMenu",get:function(){return this.elements.rootMenu}},{key:"controller",get:function(){return this.elements.controller}},{key:"selector",get:function(){return this.domSelectors}},{key:"currentFocus",get:function(){return this.focusState},set:function(e){if(!["self","child","none"].includes(e))throw new Error("Focus state must be 'self', 'child', or 'none'.");this.focusState=e}},{key:"openClass",get:function(){return this.submenuOpenClass},set:function(e){if("string"!=typeof e)throw new TypeError("Class must be a string.");this.submenuOpenClass=e}},{key:"currentMenuItem",get:function(){return this.menuItems[this.focussedChild]}},{key:"isTopLevel",get:function(){return this.root}}]),n}();return i}(); |
{ | ||
"name": "accessible-menu", | ||
"version": "1.0.3", | ||
"version": "1.0.4", | ||
"description": "A JavaScript library to help you generate WAI-ARIA accessible menus in the DOM.", | ||
"main": "main.js", | ||
"main": "index.js", | ||
"files": [ | ||
"index.js", | ||
"dist/", | ||
"src/" | ||
], | ||
"scripts": { | ||
@@ -10,3 +15,4 @@ "commit": "npx git cz", | ||
"fix": "npm run lint -- --fix", | ||
"release": "npx standard-version", | ||
"prerelease": "npm run build", | ||
"release": "git add dist/. && npx standard-version -a", | ||
"bundle": "npx rollup --config .rollup.config.js", | ||
@@ -13,0 +19,0 @@ "compile": "npx babel dist/accessibleMenu.js -o dist/accessibleMenu.js", |
@@ -451,4 +451,20 @@ import MenuItem from "./menuItem"; | ||
// - If focus is on the last item, moves focus to the first item. | ||
// - If focus was on an open submenu and the newly focussed item has a submenu, open the submenu. | ||
preventEvent(event); | ||
// Store the current item's info if its an open dropdown. | ||
const previousChildOpen = | ||
this.currentMenuItem.isSubmenuItem && | ||
this.currentMenuItem.toggle.isOpen; | ||
this.focusNextChild(); | ||
// Open the newly focussed submenu if applicable. | ||
if (previousChildOpen) { | ||
if (this.currentMenuItem.isSubmenuItem) { | ||
this.currentMenuItem.toggle.preview(); | ||
} else { | ||
this.closeChildren(); | ||
} | ||
} | ||
} else if (key === "ArrowLeft") { | ||
@@ -458,4 +474,20 @@ // Hitting the Left Arrow: | ||
// - If focus is on the first item, moves focus to the last item. | ||
// - If focus was on an open submenu and the newly focussed item has a submenu, open the submenu. | ||
preventEvent(event); | ||
// Store the current item's info if its an open dropdown. | ||
const previousChildOpen = | ||
this.currentMenuItem.isSubmenuItem && | ||
this.currentMenuItem.toggle.isOpen; | ||
this.focusPreviousChild(); | ||
// Open the newly focussed submenu if applicable. | ||
if (previousChildOpen) { | ||
if (this.currentMenuItem.isSubmenuItem) { | ||
this.currentMenuItem.toggle.preview(); | ||
} else { | ||
this.closeChildren(); | ||
} | ||
} | ||
} else if (key === "ArrowDown") { | ||
@@ -465,2 +497,3 @@ // Hitting the Down Arrow: | ||
if (this.currentMenuItem.isSubmenuItem) { | ||
preventEvent(event); | ||
this.currentMenuItem.toggle.open(); | ||
@@ -473,2 +506,3 @@ this.currentMenuItem.childMenu.focusFirstChild(); | ||
if (this.currentMenuItem.isSubmenuItem) { | ||
preventEvent(event); | ||
this.currentMenuItem.toggle.open(); | ||
@@ -515,6 +549,7 @@ this.currentMenuItem.childMenu.focusLastChild(); | ||
// - Opens submenu of newly focused menubar item, keeping focus on that parent menubar item. | ||
preventEvent(event); | ||
if (this.currentMenuItem.isSubmenuItem) { | ||
preventEvent(event); | ||
this.currentMenuItem.toggle.open(); | ||
} else { | ||
preventEvent(event); | ||
this.rootMenu.closeChildren(); | ||
@@ -524,3 +559,3 @@ this.rootMenu.focusNextChild(); | ||
if (this.rootMenu.currentMenuItem.isSubmenuItem) { | ||
this.rootMenu.currentMenuItem.toggle.open(); | ||
this.rootMenu.currentMenuItem.toggle.preview(); | ||
} | ||
@@ -534,4 +569,4 @@ } | ||
// - Opens submenu of newly focused menubar item, keeping focus on that parent menubar item. | ||
preventEvent(event); | ||
if (this.parentMenu.currentMenuItem.isSubmenuItem) { | ||
preventEvent(event); | ||
this.parentMenu.currentMenuItem.toggle.close(); | ||
@@ -544,3 +579,3 @@ | ||
if (this.rootMenu.currentMenuItem.isSubmenuItem) { | ||
this.rootMenu.currentMenuItem.toggle.open(); | ||
this.rootMenu.currentMenuItem.toggle.preview(); | ||
} | ||
@@ -547,0 +582,0 @@ } |
@@ -84,2 +84,3 @@ import Menu from "./menu"; | ||
this.openClass = openClass; | ||
this.show = false; | ||
@@ -182,24 +183,51 @@ this.initialize(); | ||
/** | ||
* Expands the submenu. | ||
*/ | ||
expand() { | ||
// Assign new WAI-ARIA/class values. | ||
this.element.setAttribute("aria-expanded", "true"); | ||
this.parentElement.classList.add(this.openClass); | ||
this.menu.element.classList.add(this.openClass); | ||
} | ||
/** | ||
* Opens the submenu. | ||
*/ | ||
open() { | ||
if (!this.isOpen) { | ||
// Set the open value. | ||
this.isOpen = true; | ||
// Set the open value. | ||
this.isOpen = true; | ||
// Assign new WAI-ARIA/class values. | ||
this.element.setAttribute("aria-expanded", "true"); | ||
this.parentElement.classList.add(this.openClass); | ||
this.menu.element.classList.add(this.openClass); | ||
// Expand the menu. | ||
this.expand(); | ||
// Close all sibling menus. | ||
this.closeSiblings(); | ||
// Close all sibling menus. | ||
this.closeSiblings(); | ||
// Set proper focus states to parent & child. | ||
if (this.parentMenu) this.parentMenu.currentFocus = "child"; | ||
this.menu.currentFocus = "self"; | ||
// Set proper focus states to parent & child. | ||
if (this.parentMenu) this.parentMenu.currentFocus = "child"; | ||
this.menu.currentFocus = "self"; | ||
// Set the new focus. | ||
this.menu.focusFirstChild(); | ||
// Set the new focus. | ||
this.menu.focusFirstChild(); | ||
} | ||
/** | ||
* Opens the submenu without focus entering it. | ||
*/ | ||
preview() { | ||
// Set the open value. | ||
this.isOpen = true; | ||
// Expand the menu. | ||
this.expand(); | ||
// Close all sibling menus. | ||
this.closeSiblings(); | ||
// Set proper focus states to parent & child. | ||
if (this.parentMenu) { | ||
this.parentMenu.currentFocus = "self"; | ||
this.parentMenu.focusCurrentChild(); | ||
} | ||
this.menu.currentFocus = "none"; | ||
} | ||
@@ -206,0 +234,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2414
113606
11