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

accessible-menu

Package Overview
Dependencies
Maintainers
1
Versions
47
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

accessible-menu - npm Package Compare versions

Comparing version 1.0.3 to 1.0.4

index.js

19

CHANGELOG.md

@@ -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 @@

102

dist/accessibleMenu.js

@@ -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 @@

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc