From 2edd0b98c9fcd9411ff636e198336bd3795f0ae3 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 19 Mar 2020 19:10:57 -0500 Subject: [PATCH 01/62] initial files for navigation menubar --- .../menubar-navigation.css} | 0 .../images/down-arrow-brown.png | Bin .../images/down-arrow-gray.png | Bin .../images/right-arrow-brown.png | Bin .../images/right-arrow-gray.png | Bin .../{menubar-1 => }/images/separator.paint | Bin .../{menubar-1 => }/images/separator.png | Bin examples/menubar/js/menubar-navigation.js | 733 ++++++++++++++++++ .../menubar/{menubar-1 => }/mb-about.html | 0 .../menubar/{menubar-1 => }/mb-academics.html | 0 .../{menubar-1 => }/mb-admissions.html | 0 .../menubar/menubar-1/js/MenubarItemLinks.js | 159 ---- examples/menubar/menubar-1/js/MenubarLinks.js | 182 ----- .../menubar-1/js/PopupMenuItemLinks.js | 209 ----- .../menubar/menubar-1/js/PopupMenuLinks.js | 261 ------- ...menubar-1.html => menubar-navigation.html} | 33 +- ...bar-1.js => menubar_menubar-navigation.js} | 2 +- 17 files changed, 746 insertions(+), 833 deletions(-) rename examples/menubar/{menubar-1/css/menubarLinks.css => css/menubar-navigation.css} (100%) rename examples/menubar/{menubar-1 => }/images/down-arrow-brown.png (100%) rename examples/menubar/{menubar-1 => }/images/down-arrow-gray.png (100%) rename examples/menubar/{menubar-1 => }/images/right-arrow-brown.png (100%) rename examples/menubar/{menubar-1 => }/images/right-arrow-gray.png (100%) rename examples/menubar/{menubar-1 => }/images/separator.paint (100%) rename examples/menubar/{menubar-1 => }/images/separator.png (100%) create mode 100644 examples/menubar/js/menubar-navigation.js rename examples/menubar/{menubar-1 => }/mb-about.html (100%) rename examples/menubar/{menubar-1 => }/mb-academics.html (100%) rename examples/menubar/{menubar-1 => }/mb-admissions.html (100%) delete mode 100644 examples/menubar/menubar-1/js/MenubarItemLinks.js delete mode 100644 examples/menubar/menubar-1/js/MenubarLinks.js delete mode 100644 examples/menubar/menubar-1/js/PopupMenuItemLinks.js delete mode 100644 examples/menubar/menubar-1/js/PopupMenuLinks.js rename examples/menubar/{menubar-1/menubar-1.html => menubar-navigation.html} (94%) rename test/tests/{menubar_menubar-1.js => menubar_menubar-navigation.js} (99%) diff --git a/examples/menubar/menubar-1/css/menubarLinks.css b/examples/menubar/css/menubar-navigation.css similarity index 100% rename from examples/menubar/menubar-1/css/menubarLinks.css rename to examples/menubar/css/menubar-navigation.css diff --git a/examples/menubar/menubar-1/images/down-arrow-brown.png b/examples/menubar/images/down-arrow-brown.png similarity index 100% rename from examples/menubar/menubar-1/images/down-arrow-brown.png rename to examples/menubar/images/down-arrow-brown.png diff --git a/examples/menubar/menubar-1/images/down-arrow-gray.png b/examples/menubar/images/down-arrow-gray.png similarity index 100% rename from examples/menubar/menubar-1/images/down-arrow-gray.png rename to examples/menubar/images/down-arrow-gray.png diff --git a/examples/menubar/menubar-1/images/right-arrow-brown.png b/examples/menubar/images/right-arrow-brown.png similarity index 100% rename from examples/menubar/menubar-1/images/right-arrow-brown.png rename to examples/menubar/images/right-arrow-brown.png diff --git a/examples/menubar/menubar-1/images/right-arrow-gray.png b/examples/menubar/images/right-arrow-gray.png similarity index 100% rename from examples/menubar/menubar-1/images/right-arrow-gray.png rename to examples/menubar/images/right-arrow-gray.png diff --git a/examples/menubar/menubar-1/images/separator.paint b/examples/menubar/images/separator.paint similarity index 100% rename from examples/menubar/menubar-1/images/separator.paint rename to examples/menubar/images/separator.paint diff --git a/examples/menubar/menubar-1/images/separator.png b/examples/menubar/images/separator.png similarity index 100% rename from examples/menubar/menubar-1/images/separator.png rename to examples/menubar/images/separator.png diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js new file mode 100644 index 0000000000..5b12640290 --- /dev/null +++ b/examples/menubar/js/menubar-navigation.js @@ -0,0 +1,733 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: menubar-navigation.js +* +* Desc: Creates a menubar of hierarchical set of links +*/ + +var MenubarNavigation = function (domNode, actionManager) { + + this.domNode = domNode; + this.actionManager = actionManager; + + this.menuitemGroups = {}; + this.menuOrientation = {}; + this.isPopup = {}; + this.openPopups = false; + + this.firstChars = {}; // see Menubar init method + this.firstMenuitem = {}; // see Menubar init method + this.lastMenuitem = {}; // see Menubar init method + + this.initMenu(domNode) + domNode.addEventListener('focusin', this.handleMenubarFocusin.bind(this)); + domNode.addEventListener('focusout', this.handleMenubarFocusout.bind(this)); + + window.addEventListener('mousedown', this.handleBackgroundMousedown.bind(this), true); +}; + +MenubarNavigation.prototype.getMenuitems = function(domNode) { + var nodes = []; + + var initMenu = this.initMenu.bind(this); + var getGroupId = this.getGroupId.bind(this); + var menuitemGroups = this.menuitemGroups; + + function findMenuitems(node, group) { + var role, flag, groupId; + + while (node) { + flag = true; + role = node.getAttribute('role'); + + if (role) { + role = role.trim().toLowerCase(); + } + + switch (role) { + case 'menu': + node.tabIndex = -1; + initMenu(node); + flag = false; + break; + + case 'group': + groupId = getGroupId(node); + menuitemGroups[groupId] = []; + break; + + case 'menuitem': + case 'menuitemradio': + case 'menuitemcheckbox': + nodes.push(node); + if (group) { + group.push(node); + } + break; + + default: + break; + } + + if (flag && node.firstElementChild) { + findMenuitems(node.firstElementChild, menuitemGroups[groupId]); + } + + node = node.nextElementSibling; + } + } + + findMenuitems(domNode.firstElementChild, false); + + return nodes; +}; + +MenubarNavigation.prototype.initMenu = function (menu) { + var i, menuitems, menuitem, role, nextElement; + + var menuId = this.getMenuId(menu); + + menuitems = this.getMenuitems(menu); + this.menuOrientation[menuId] = this.getMenuOrientation(menu); + this.isPopup[menuId] = menu.getAttribute('role') === 'menu'; + + this.menuitemGroups[menuId] = []; + this.firstChars[menuId] = []; + this.firstMenuitem[menuId] = null; + this.lastMenuitem[menuId] = null; + + for(i = 0; i < menuitems.length; i++) { + menuitem = menuitems[i]; + role = menuitem.getAttribute('role'); + + if (role.indexOf('menuitem') < 0) { + continue; + } + + menuitem.tabIndex = -1; + this.menuitemGroups[menuId].push(menuitem); + this.firstChars[menuId].push(menuitem.textContent[0].toLowerCase()); + + menuitem.addEventListener('keydown', this.handleKeydown.bind(this)); + menuitem.addEventListener('click', this.handleMenuitemClick.bind(this)); + menuitem.addEventListener('mouseover', this.handleMenuitemMouseover.bind(this)); + + if( !this.firstMenuitem[menuId]) { + if (this.hasPopup(menuitem)) { + menuitem.tabIndex = 0; + } + this.firstMenuitem[menuId] = menuitem; + } + this.lastMenuitem[menuId] = menuitem; + + } +}; + +/* MenubarNavigation FOCUS MANAGEMENT METHODS */ + +MenubarNavigation.prototype.setFocusToMenuitem = function (menuId, newMenuitem, currentMenuitem) { + + if (typeof currentMenuitem !== 'object') { + currentMenuitem = false; + } + + if (currentMenuitem && + this.hasPopup(currentMenuitem) && + this.isOpen(currentMenuitem)) { + this.closePopup(currentMenuitem); + } + + if (this.hasPopup(newMenuitem)) { + if (this.openPopups) { + this.openPopup(newMenuitem); + } + } + else { + var menu = this.getMenu(newMenuitem); + var cmi = menu.previousElementSibling; + if (!this.isOpen(cmi)) { + this.openPopup(cmi); + } + } + + if (this.hasPopup(newMenuitem)) { + this.menuitemGroups[menuId].forEach(function(item) { + item.tabIndex = -1; + }); + + newMenuitem.tabIndex = 0; + } + + newMenuitem.focus(); + +}; + +MenubarNavigation.prototype.setFocusToFirstMenuitem = function (menuId, currentMenuitem) { + this.setFocusToMenuitem(menuId, this.firstMenuitem[menuId], currentMenuitem); +}; + +MenubarNavigation.prototype.setFocusToLastMenuitem = function (menuId, currentMenuitem) { + this.setFocusToMenuitem(menuId, this.lastMenuitem[menuId], currentMenuitem); +}; + +MenubarNavigation.prototype.setFocusToPreviousMenuitem = function (menuId, currentMenuitem) { + var newMenuitem, index; + + if (currentMenuitem === this.firstMenuitem[menuId]) { + newMenuitem = this.lastMenuitem[menuId]; + } + else { + index = this.menuitemGroups[menuId].indexOf(currentMenuitem); + newMenuitem = this.menuitemGroups[menuId][ index - 1 ]; + } + + this.setFocusToMenuitem(menuId, newMenuitem, currentMenuitem); + + return newMenuitem; +}; + +MenubarNavigation.prototype.setFocusToNextMenuitem = function (menuId, currentMenuitem) { + var newMenuitem, index; + + if (currentMenuitem === this.lastMenuitem[menuId]) { + newMenuitem = this.firstMenuitem[menuId]; + } + else { + index = this.menuitemGroups[menuId].indexOf(currentMenuitem); + newMenuitem = this.menuitemGroups[menuId][ index + 1 ]; + } + this.setFocusToMenuitem(menuId, newMenuitem, currentMenuitem); + + return newMenuitem; +}; + +MenubarNavigation.prototype.setFocusByFirstCharacter = function (menuId, currentMenuitem, char) { + var start, index; + + char = char.toLowerCase(); + + // Get start index for search based on position of currentItem + start = this.menuitemGroups[menuId].indexOf(currentMenuitem) + 1; + if (start >= this.menuitemGroups[menuId].length) { + start = 0; + } + + // Check remaining slots in the menu + index = this.getIndexFirstChars(menuId, start, char); + + // If not found in remaining slots, check from beginning + if (index === -1) { + index = this.getIndexFirstChars(menuId, 0, char); + } + + // If match was found... + if (index > -1) { + this.setFocusToMenuitem(menuId, this.menuitemGroups[menuId][index], currentMenuitem); + } +}; + +// Utitlities + +MenubarNavigation.prototype.getIndexFirstChars = function (menuId, startIndex, char) { + for (var i = startIndex; i < this.firstChars[menuId].length; i++) { + if (char === this.firstChars[menuId][i]) { + return i; + } + } + return -1; +}; + +MenubarNavigation.prototype.isPrintableCharacter = function(str) { + return str.length === 1 && str.match(/\S/); +}; + +MenubarNavigation.prototype.getIdFromAriaLabel = function(node) { + var id = node.getAttribute('aria-label') + if (id) { + id = id.trim().toLowerCase().replace(' ', '-').replace('/', '-'); + } + return id; +}; + + +MenubarNavigation.prototype.getMenuOrientation = function(node) { + + var orientation = node.getAttribute('aria-orientation'); + + if (!orientation) { + var role = node.getAttribute('role'); + + switch (role) { + case 'menubar': + orientation = 'horizontal'; + break; + + case 'menu': + orientation = 'vertical'; + break; + + default: + break; + } + } + + return orientation; +}; + +MenubarNavigation.prototype.getDataOption = function(node) { + + var option = false; + var hasOption = node.hasAttribute('data-option'); + var role = node.hasAttribute('role'); + + if (!hasOption) { + + while (node && !hasOption && + (role !== 'menu') && + (role !== 'menubar')) { + node = node.parentNode; + if (node) { + role = node.getAttribute('role'); + hasOption = node.hasAttribute('data-option'); + } + } + } + + if (node) { + option = node.getAttribute('data-option'); + } + + return option; +}; + +MenubarNavigation.prototype.getGroupId = function(node) { + + var id = false; + var role = node.getAttribute('role'); + + while (node && (role !== 'group') && + (role !== 'menu') && + (role !== 'menubar')) { + node = node.parentNode; + if (node) { + role = node.getAttribute('role'); + } + } + + if (node) { + id = role + '-' + this.getIdFromAriaLabel(node); + } + + return id; +}; + +MenubarNavigation.prototype.getMenuId = function(node) { + + var id = false; + var role = node.getAttribute('role'); + + while (node && (role !== 'menu') && (role !== 'menubar')) { + node = node.parentNode; + if (node) { + role = node.getAttribute('role'); + } + } + + if (node) { + id = role + '-' + this.getIdFromAriaLabel(node); + } + + return id; +}; + +MenubarNavigation.prototype.getMenu = function(menuitem) { + + var id = false; + var menu = menuitem; + var role = menuitem.getAttribute('role'); + + while (menu && (role !== 'menu') && (role !== 'menubar')) { + menu = menu.parentNode + if (menu) { + role = menu.getAttribute('role'); + } + } + + return menu; +}; + +MenubarNavigation.prototype.toggleCheckbox = function(menuitem) { + if (menuitem.getAttribute('aria-checked') === 'true') { + menuitem.setAttribute('aria-checked', 'false'); + return false; + } + menuitem.setAttribute('aria-checked', 'true'); + return true; +}; + +MenubarNavigation.prototype.setRadioButton = function(menuitem) { + var groupId = this.getGroupId(menuitem); + var radiogroupItems = this.menuitemGroups[groupId]; + radiogroupItems.forEach( function (item) { + item.setAttribute('aria-checked', 'false') + }); + menuitem.setAttribute('aria-checked', 'true'); + return menuitem.textContent; +}; + +MenubarNavigation.prototype.updateFontSizeMenu = function(menuId) { + + var fontSizeMenuitems = this.menuitemGroups[menuId]; + var currentValue = this.actionManager.getFontSize(); + + for (var i = 0; i < fontSizeMenuitems.length; i++) { + var mi = fontSizeMenuitems[i]; + var dataOption = mi.getAttribute('data-option'); + var value = mi.textContent.trim().toLowerCase(); + + switch (dataOption) { + case 'font-smaller': + if (currentValue === 'x-small') { + mi.setAttribute('aria-disabled', 'true'); + } + else { + mi.removeAttribute('aria-disabled'); + } + break; + + case 'font-larger': + if (currentValue === 'x-large') { + mi.setAttribute('aria-disabled', 'true'); + } + else { + mi.removeAttribute('aria-disabled'); + } + break; + + default: + if (currentValue === value) { + mi.setAttribute('aria-checked', 'true'); + } + else { + mi.setAttribute('aria-checked', 'false'); + } + break; + + } + } + + +} + +// Popup menu methods + +MenubarNavigation.prototype.openPopup = function (menuitem) { + + // set aria-expanded attribute + var popupMenu = menuitem.nextElementSibling; + + var rect = menuitem.getBoundingClientRect(); + + // set CSS properties + popupMenu.style.position = 'absolute'; + popupMenu.style.top = (rect.height + 1) + 'px'; + popupMenu.style.left = '0px'; + popupMenu.style.zIndex = 100; + popupMenu.style.display = 'block'; + + menuitem.setAttribute('aria-expanded', 'true'); + + return this.getMenuId(popupMenu); + +}; + +MenubarNavigation.prototype.closePopup = function (menuitem) { + var menu, cmi; + + if (this.hasPopup(menuitem)) { + if (this.isOpen(menuitem)) { + menuitem.setAttribute('aria-expanded', 'false'); + menuitem.nextElementSibling.style.display = 'none'; + menuitem.nextElementSibling.style.zIndex = 0; + + } + } + else { + menu = this.getMenu(menuitem); + cmi = menu.previousElementSibling; + cmi.setAttribute('aria-expanded', 'false'); + cmi.focus(); + menu.style.display = 'none'; + menu.style.zIndex = 0; + } + return cmi; +}; + +MenubarNavigation.prototype.closePopupAll = function () { + + var popups = this.domNode.querySelectorAll('[aria-haspopup]'); + + for (var i = 0; i < popups.length; i++) { + var popup = popups[i]; + if (this.isOpen(popup)) { + this.closePopup(popup); + event.stopPropagation(); + event.preventDefault(); + } + } + +}; + +MenubarNavigation.prototype.hasPopup = function (menuitem) { + return menuitem.getAttribute('aria-haspopup') === 'true'; +}; + +MenubarNavigation.prototype.isOpen = function (menuitem) { + return menuitem.getAttribute('aria-expanded') === 'true'; +}; + +// Menu event handlers + +MenubarNavigation.prototype.handleMenubarFocusin = function (event) { + // if the menubar or any of its menus has focus, add styling hook for hover + this.domNode.classList.add('focus'); +}; + +MenubarNavigation.prototype.handleMenubarFocusout = function (event) { + // remove styling hook for hover on menubar item + this.domNode.classList.remove('focus'); +}; + +MenubarNavigation.prototype.handleBackgroundMousedown = function (event) { + if (!this.domNode.contains(event.target)) { + this.closePopupAll(); + } +}; + +MenubarNavigation.prototype.handleKeydown = function (event) { + var tgt = event.currentTarget, + key = event.key, + flag = false, + menuId = this.getMenuId(tgt), + id, + popupMenuId, + mi, + role, + option, + value; + + console.log('[handleMenubarKeydown][key]: ' + key); + console.log('[handleMenubarKeydown][menuId]: ' + menuId); + + // This fixes a problem with regression tests using Key.SPACE + if (event.keyCode === 32) { + key = ' '; + } + + switch (key) { + case ' ': + case 'Enter': + if (this.hasPopup(tgt)) { + this.openPopups = true; + popupMenuId = this.openPopup(tgt); + this.setFocusToFirstMenuitem(popupMenuId); + } + else { + role = tgt.getAttribute('role'); + option = this.getDataOption(tgt); + switch(role) { + case 'menuitem': + this.actionManager.setOption(option, tgt.textContent); + break; + + case 'menuitemcheckbox': + value = this.toggleCheckbox(tgt); + this.actionManager.setOption(option, value); + break; + + case 'menuitemradio': + value = this.setRadioButton(tgt); + this.actionManager.setOption(option, value); + break; + + default: + break; + } + + if (this.getMenuId(tgt) === 'menu-size') { + this.updateFontSizeMenu('menu-size'); + } + this.openPopups = false; + this.closePopup(tgt); + } + flag = true; + break; + + case 'ArrowDown': + case 'Down': + if (this.menuOrientation[menuId] === 'vertical') { + this.setFocusToNextMenuitem(menuId, tgt); + flag = true; + } + else { + if (this.hasPopup(tgt)) { + this.openPopups = true; + popupMenuId = this.openPopup(tgt); + this.setFocusToFirstMenuitem(popupMenuId); + flag = true; + } + } + break; + + case 'Esc': + case 'Escape': + this.openPopups = false; + this.closePopup(tgt); + flag = true; + break; + + case 'Left': + case 'ArrowLeft': + if (this.menuOrientation[menuId] === 'horizontal') { + this.setFocusToPreviousMenuitem(menuId, tgt); + flag = true; + } + else { + mi = this.closePopup(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToPreviousMenuitem(id, mi); + this.openPopup(mi); + } + break; + + case 'Right': + case 'ArrowRight': + if (this.menuOrientation[menuId] === 'horizontal') { + this.setFocusToNextMenuitem(menuId, tgt); + flag = true; + } + else { + mi = this.closePopup(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToNextMenuitem(id, mi); + this.openPopup(mi); + } + break; + + case 'Up': + case 'ArrowUp': + if (this.menuOrientation[menuId] === 'vertical') { + this.setFocusToPreviousMenuitem(menuId, tgt); + flag = true; + } + else { + if (this.hasPopup(tgt)) { + this.openPopups = true; + popupMenuId = this.openPopup(tgt); + this.setFocusToLastMenuitem(popupMenuId); + flag = true; + } + } + break; + + case 'Home': + case 'PageUp': + this.setFocusToFirstMenuitem(menuId, tgt); + flag = true; + break; + + case 'End': + case 'PageDown': + this.setFocusToLastMenuitem(menuId, tgt); + flag = true; + break; + + case 'Tab': + this.openPopups = false; + this.closePopup(tgt); + break; + + default: + if (this.isPrintableCharacter(key)) { + this.setFocusByFirstCharacter(menuId, tgt, key); + flag = true; + } + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +MenubarNavigation.prototype.handleMenuitemClick = function (event) { + var tgt = event.currentTarget, + role, + option, + value; + + if (this.hasPopup(tgt)) { + if (this.isOpen(tgt)) { + this.openPopups = false; + this.closePopup(tgt); + } + else { + this.closePopupAll(); + this.openPopups = true; + this.openPopup(tgt); + } + } + else { + role = tgt.getAttribute('role'); + option = this.getDataOption(tgt); + switch(role) { + case 'menuitem': + this.actionManager.setOption(option, tgt.textContent); + break; + + case 'menuitemcheckbox': + value = this.toggleCheckbox(tgt); + this.actionManager.setOption(option, value); + break; + + case 'menuitemradio': + value = this.setRadioButton(tgt); + this.actionManager.setOption(option, value); + break; + + default: + break; + } + + if (this.getMenuId(tgt) === 'menu-size') { + this.updateFontSizeMenu('menu-size'); + } + this.openPopups = false; + this.closePopup(tgt); + } + + event.stopPropagation(); + event.preventDefault(); + +}; + +MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { + var tgt = event.currentTarget; + + if (this.hasPopup(tgt) && this.openPopups) { + if (!this.isOpen(tgt)) { + this.closePopupAll(); + this.openPopups = true; + this.openPopup(tgt); + } + } +}; + +// Initialize menubar editor + +window.addEventListener('load', function () { + var navbar = new MenubarNavigation(document.getElementById('menubar1')); +}); diff --git a/examples/menubar/menubar-1/mb-about.html b/examples/menubar/mb-about.html similarity index 100% rename from examples/menubar/menubar-1/mb-about.html rename to examples/menubar/mb-about.html diff --git a/examples/menubar/menubar-1/mb-academics.html b/examples/menubar/mb-academics.html similarity index 100% rename from examples/menubar/menubar-1/mb-academics.html rename to examples/menubar/mb-academics.html diff --git a/examples/menubar/menubar-1/mb-admissions.html b/examples/menubar/mb-admissions.html similarity index 100% rename from examples/menubar/menubar-1/mb-admissions.html rename to examples/menubar/mb-admissions.html diff --git a/examples/menubar/menubar-1/js/MenubarItemLinks.js b/examples/menubar/menubar-1/js/MenubarItemLinks.js deleted file mode 100644 index 5a9ab2d074..0000000000 --- a/examples/menubar/menubar-1/js/MenubarItemLinks.js +++ /dev/null @@ -1,159 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ -var MenubarItem = function (domNode, menuObj) { - - this.menu = menuObj; - this.domNode = domNode; - this.popupMenu = false; - - this.hasFocus = false; - this.hasHover = false; - - this.isMenubarItem = true; - - this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 - }); -}; - -MenubarItem.prototype.init = function () { - this.domNode.tabIndex = -1; - - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); - - // Initialize pop up menus - - var nextElement = this.domNode.nextElementSibling; - - if (nextElement && nextElement.tagName === 'UL') { - this.popupMenu = new PopupMenu(nextElement, this); - this.popupMenu.init(); - } - -}; - -MenubarItem.prototype.handleKeydown = function (event) { - var tgt = event.currentTarget, - char = event.key, - flag = false, - clickEvent; - - function isPrintableCharacter (str) { - return str.length === 1 && str.match(/\S/); - } - - switch (event.keyCode) { - case this.keyCode.SPACE: - case this.keyCode.RETURN: - case this.keyCode.DOWN: - if (this.popupMenu) { - this.popupMenu.open(); - this.popupMenu.setFocusToFirstItem(); - flag = true; - } - break; - - case this.keyCode.LEFT: - this.menu.setFocusToPreviousItem(this); - flag = true; - break; - - case this.keyCode.RIGHT: - this.menu.setFocusToNextItem(this); - flag = true; - break; - - case this.keyCode.UP: - if (this.popupMenu) { - this.popupMenu.open(); - this.popupMenu.setFocusToLastItem(); - flag = true; - } - break; - - case this.keyCode.HOME: - case this.keyCode.PAGEUP: - this.menu.setFocusToFirstItem(); - flag = true; - break; - - case this.keyCode.END: - case this.keyCode.PAGEDOWN: - this.menu.setFocusToLastItem(); - flag = true; - break; - - case this.keyCode.TAB: - this.popupMenu.close(true); - break; - - case this.keyCode.ESC: - this.popupMenu.close(true); - break; - - default: - if (isPrintableCharacter(char)) { - this.menu.setFocusByFirstCharacter(this, char); - flag = true; - } - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -MenubarItem.prototype.handleClick = function (event) { - if (this.popupMenu) { - // for menuitem with menu, prevent default anchor behavior on click - // (which jumps to top of page for href="#" in some browsers) - event.preventDefault(); - } -}; - -MenubarItem.prototype.setExpanded = function (value) { - if (value) { - this.domNode.setAttribute('aria-expanded', 'true'); - } - else { - this.domNode.setAttribute('aria-expanded', 'false'); - } -}; - -MenubarItem.prototype.handleFocus = function (event) { - this.menu.hasFocus = true; -}; - -MenubarItem.prototype.handleBlur = function (event) { - this.menu.hasFocus = false; -}; - -MenubarItem.prototype.handleMouseover = function (event) { - this.hasHover = true; - this.popupMenu.open(); -}; - -MenubarItem.prototype.handleMouseout = function (event) { - this.hasHover = false; - setTimeout(this.popupMenu.close.bind(this.popupMenu, false), 300); -}; diff --git a/examples/menubar/menubar-1/js/MenubarLinks.js b/examples/menubar/menubar-1/js/MenubarLinks.js deleted file mode 100644 index e75d664c90..0000000000 --- a/examples/menubar/menubar-1/js/MenubarLinks.js +++ /dev/null @@ -1,182 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ - -var Menubar = function (domNode) { - var elementChildren, - msgPrefix = 'Menubar constructor argument menubarNode '; - - // Check whether menubarNode is a DOM element - if (!(domNode instanceof Element)) { - throw new TypeError(msgPrefix + 'is not a DOM Element.'); - } - - // Check whether menubarNode has descendant elements - if (domNode.childElementCount === 0) { - throw new Error(msgPrefix + 'has no element children.'); - } - - // Check whether menubarNode has A elements - e = domNode.firstElementChild; - while (e) { - var menubarItem = e.firstElementChild; - if (e && menubarItem && menubarItem.tagName !== 'A') { - throw new Error(msgPrefix + 'has child elements are not A elements.'); - } - e = e.nextElementSibling; - } - - this.isMenubar = true; - - this.domNode = domNode; - - this.menubarItems = []; // See Menubar init method - this.firstChars = []; // See Menubar init method - - this.firstItem = null; // See Menubar init method - this.lastItem = null; // See Menubar init method - - this.hasFocus = false; // See MenubarItem handleFocus, handleBlur - this.hasHover = false; // See Menubar handleMouseover, handleMouseout -}; - -/* -* @method Menubar.prototype.init -* -* @desc -* Adds ARIA role to the menubar node -* Traverse menubar children for A elements to configure each A element as a ARIA menuitem -* and populate menuitems array. Initialize firstItem and lastItem properties. -*/ -Menubar.prototype.init = function () { - var menubarItem, childElement, menuElement, textContent, numItems; - - - // Traverse the element children of menubarNode: configure each with - // menuitem role behavior and store reference in menuitems array. - elem = this.domNode.firstElementChild; - - while (elem) { - menuElement = elem.firstElementChild; - - if (elem && menuElement && menuElement.tagName === 'A') { - menubarItem = new MenubarItem(menuElement, this); - menubarItem.init(); - this.menubarItems.push(menubarItem); - textContent = menuElement.textContent.trim(); - this.firstChars.push(textContent.substring(0, 1).toLowerCase()); - } - - elem = elem.nextElementSibling; - } - - // Use populated menuitems array to initialize firstItem and lastItem. - numItems = this.menubarItems.length; - if (numItems > 0) { - this.firstItem = this.menubarItems[ 0 ]; - this.lastItem = this.menubarItems[ numItems - 1 ]; - } - this.firstItem.domNode.tabIndex = 0; -}; - -/* FOCUS MANAGEMENT METHODS */ - -Menubar.prototype.setFocusToItem = function (newItem) { - - var flag = false; - - for (var i = 0; i < this.menubarItems.length; i++) { - var mbi = this.menubarItems[i]; - - if (mbi.domNode.tabIndex == 0) { - flag = mbi.domNode.getAttribute('aria-expanded') === 'true'; - } - - mbi.domNode.tabIndex = -1; - if (mbi.popupMenu) { - mbi.popupMenu.close(); - } - } - - newItem.domNode.focus(); - newItem.domNode.tabIndex = 0; - - if (flag && newItem.popupMenu) { - newItem.popupMenu.open(); - } -}; - -Menubar.prototype.setFocusToFirstItem = function (flag) { - this.setFocusToItem(this.firstItem); -}; - -Menubar.prototype.setFocusToLastItem = function (flag) { - this.setFocusToItem(this.lastItem); -}; - -Menubar.prototype.setFocusToPreviousItem = function (currentItem) { - var index; - - if (currentItem === this.firstItem) { - newItem = this.lastItem; - } - else { - index = this.menubarItems.indexOf(currentItem); - newItem = this.menubarItems[ index - 1 ]; - } - - this.setFocusToItem(newItem); - -}; - -Menubar.prototype.setFocusToNextItem = function (currentItem) { - var index; - - if (currentItem === this.lastItem) { - newItem = this.firstItem; - } - else { - index = this.menubarItems.indexOf(currentItem); - newItem = this.menubarItems[ index + 1 ]; - } - - this.setFocusToItem(newItem); - -}; - -Menubar.prototype.setFocusByFirstCharacter = function (currentItem, char) { - var start, index; - var flag = currentItem.domNode.getAttribute('aria-expanded') === 'true'; - - char = char.toLowerCase(); - - // Get start index for search based on position of currentItem - start = this.menubarItems.indexOf(currentItem) + 1; - if (start === this.menubarItems.length) { - start = 0; - } - - // Check remaining slots in the menu - index = this.getIndexFirstChars(start, char); - - // If not found in remaining slots, check from beginning - if (index === -1) { - index = this.getIndexFirstChars(0, char); - } - - // If match was found... - if (index > -1) { - this.setFocusToItem(this.menubarItems[ index ]); - } -}; - -Menubar.prototype.getIndexFirstChars = function (startIndex, char) { - for (var i = startIndex; i < this.firstChars.length; i++) { - if (char === this.firstChars[ i ]) { - return i; - } - } - return -1; -}; - diff --git a/examples/menubar/menubar-1/js/PopupMenuItemLinks.js b/examples/menubar/menubar-1/js/PopupMenuItemLinks.js deleted file mode 100644 index 2244d7067c..0000000000 --- a/examples/menubar/menubar-1/js/PopupMenuItemLinks.js +++ /dev/null @@ -1,209 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ -var MenuItem = function (domNode, menuObj) { - - if (typeof popupObj !== 'object') { - popupObj = false; - } - - this.domNode = domNode; - this.menu = menuObj; - this.popupMenu = false; - this.isMenubarItem = false; - - this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 - }); -}; - -MenuItem.prototype.init = function () { - this.domNode.tabIndex = -1; - - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); - - // Initialize flyout menu - - var nextElement = this.domNode.nextElementSibling; - - if (nextElement && nextElement.tagName === 'UL') { - this.popupMenu = new PopupMenu(nextElement, this); - this.popupMenu.init(); - } - -}; - -MenuItem.prototype.isExpanded = function () { - return this.domNode.getAttribute('aria-expanded') === 'true'; -}; - -/* EVENT HANDLERS */ - -MenuItem.prototype.handleKeydown = function (event) { - var tgt = event.currentTarget, - char = event.key, - flag = false, - clickEvent; - - function isPrintableCharacter (str) { - return str.length === 1 && str.match(/\S/); - } - - switch (event.keyCode) { - case this.keyCode.SPACE: - case this.keyCode.RETURN: - if (this.popupMenu) { - this.popupMenu.open(); - this.popupMenu.setFocusToFirstItem(); - } - else { - - // Create simulated mouse event to mimic the behavior of ATs - // and let the event handler handleClick do the housekeeping. - try { - clickEvent = new MouseEvent('click', { - 'view': window, - 'bubbles': true, - 'cancelable': true - }); - } - catch (err) { - if (document.createEvent) { - // DOM Level 3 for IE 9+ - clickEvent = document.createEvent('MouseEvents'); - clickEvent.initEvent('click', true, true); - } - } - tgt.dispatchEvent(clickEvent); - } - - flag = true; - break; - - case this.keyCode.UP: - this.menu.setFocusToPreviousItem(this); - flag = true; - break; - - case this.keyCode.DOWN: - this.menu.setFocusToNextItem(this); - flag = true; - break; - - case this.keyCode.LEFT: - this.menu.setFocusToController('previous', true); - this.menu.close(true); - flag = true; - break; - - case this.keyCode.RIGHT: - if (this.popupMenu) { - this.popupMenu.open(); - this.popupMenu.setFocusToFirstItem(); - } - else { - this.menu.setFocusToController('next', true); - this.menu.close(true); - } - flag = true; - break; - - case this.keyCode.HOME: - case this.keyCode.PAGEUP: - this.menu.setFocusToFirstItem(); - flag = true; - break; - - case this.keyCode.END: - case this.keyCode.PAGEDOWN: - this.menu.setFocusToLastItem(); - flag = true; - break; - - case this.keyCode.ESC: - this.menu.setFocusToController(); - this.menu.close(true); - flag = true; - break; - - case this.keyCode.TAB: - this.menu.setFocusToController(); - break; - - default: - if (isPrintableCharacter(char)) { - this.menu.setFocusByFirstCharacter(this, char); - flag = true; - } - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -MenuItem.prototype.setExpanded = function (value) { - if (value) { - this.domNode.setAttribute('aria-expanded', 'true'); - } - else { - this.domNode.setAttribute('aria-expanded', 'false'); - } -}; - -MenuItem.prototype.handleClick = function (event) { - this.menu.setFocusToController(); - this.menu.close(true); - if (this.popupMenu) { - // for menuitem with menu, prevent default anchor behavior on click - // (which jumps to top of page for href="#" in some browsers) - event.preventDefault(); - } -}; - -MenuItem.prototype.handleFocus = function (event) { - this.menu.hasFocus = true; -}; - -MenuItem.prototype.handleBlur = function (event) { - this.menu.hasFocus = false; - setTimeout(this.menu.close.bind(this.menu, false), 300); -}; - -MenuItem.prototype.handleMouseover = function (event) { - this.menu.hasHover = true; - this.menu.open(); - if (this.popupMenu) { - this.popupMenu.hasHover = true; - this.popupMenu.open(); - } -}; - -MenuItem.prototype.handleMouseout = function (event) { - if (this.popupMenu) { - this.popupMenu.hasHover = false; - this.popupMenu.close(true); - } - - this.menu.hasHover = false; - setTimeout(this.menu.close.bind(this.menu, false), 300); -}; diff --git a/examples/menubar/menubar-1/js/PopupMenuLinks.js b/examples/menubar/menubar-1/js/PopupMenuLinks.js deleted file mode 100644 index f402005c4e..0000000000 --- a/examples/menubar/menubar-1/js/PopupMenuLinks.js +++ /dev/null @@ -1,261 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ -var PopupMenu = function (domNode, controllerObj) { - var elementChildren, - msgPrefix = 'PopupMenu constructor argument domNode '; - - // Check whether domNode is a DOM element - if (!(domNode instanceof Element)) { - throw new TypeError(msgPrefix + 'is not a DOM Element.'); - } - // Check whether domNode has child elements - if (domNode.childElementCount === 0) { - throw new Error(msgPrefix + 'has no element children.'); - } - // Check whether domNode descendant elements have A elements - var childElement = domNode.firstElementChild; - while (childElement) { - var menuitem = childElement.firstElementChild; - if (menuitem && menuitem === 'A') { - throw new Error(msgPrefix + 'has descendant elements that are not A elements.'); - } - childElement = childElement.nextElementSibling; - } - - this.isMenubar = false; - - this.domNode = domNode; - this.controller = controllerObj; - - this.menuitems = []; // See PopupMenu init method - this.firstChars = []; // See PopupMenu init method - - this.firstItem = null; // See PopupMenu init method - this.lastItem = null; // See PopupMenu init method - - this.hasFocus = false; // See MenuItem handleFocus, handleBlur - this.hasHover = false; // See PopupMenu handleMouseover, handleMouseout -}; - -/* -* @method PopupMenu.prototype.init -* -* @desc -* Add domNode event listeners for mouseover and mouseout. Traverse -* domNode children to configure each menuitem and populate menuitems -* array. Initialize firstItem and lastItem properties. -*/ -PopupMenu.prototype.init = function () { - var childElement, menuElement, menuItem, textContent, numItems, label; - - // Configure the domNode itself - - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); - - // Traverse the element children of domNode: configure each with - // menuitem role behavior and store reference in menuitems array. - childElement = this.domNode.firstElementChild; - - while (childElement) { - menuElement = childElement.firstElementChild; - - if (menuElement && menuElement.tagName === 'A') { - menuItem = new MenuItem(menuElement, this); - menuItem.init(); - this.menuitems.push(menuItem); - textContent = menuElement.textContent.trim(); - this.firstChars.push(textContent.substring(0, 1).toLowerCase()); - } - childElement = childElement.nextElementSibling; - } - - // Use populated menuitems array to initialize firstItem and lastItem. - numItems = this.menuitems.length; - if (numItems > 0) { - this.firstItem = this.menuitems[ 0 ]; - this.lastItem = this.menuitems[ numItems - 1 ]; - } -}; - -/* EVENT HANDLERS */ - -PopupMenu.prototype.handleMouseover = function (event) { - this.hasHover = true; -}; - -PopupMenu.prototype.handleMouseout = function (event) { - this.hasHover = false; - setTimeout(this.close.bind(this, false), 1); -}; - -/* FOCUS MANAGEMENT METHODS */ - -PopupMenu.prototype.setFocusToController = function (command, flag) { - - if (typeof command !== 'string') { - command = ''; - } - - function setFocusToMenubarItem (controller, close) { - while (controller) { - if (controller.isMenubarItem) { - controller.domNode.focus(); - return controller; - } - else { - if (close) { - controller.menu.close(true); - } - controller.hasFocus = false; - } - controller = controller.menu.controller; - } - return false; - } - - if (command === '') { - if (this.controller && this.controller.domNode) { - this.controller.domNode.focus(); - } - return; - } - - if (!this.controller.isMenubarItem) { - this.controller.domNode.focus(); - this.close(); - - if (command === 'next') { - var menubarItem = setFocusToMenubarItem(this.controller, false); - if (menubarItem) { - menubarItem.menu.setFocusToNextItem(menubarItem, flag); - } - } - } - else { - if (command === 'previous') { - this.controller.menu.setFocusToPreviousItem(this.controller, flag); - } - else if (command === 'next') { - this.controller.menu.setFocusToNextItem(this.controller, flag); - } - } - -}; - -PopupMenu.prototype.setFocusToFirstItem = function () { - this.firstItem.domNode.focus(); -}; - -PopupMenu.prototype.setFocusToLastItem = function () { - this.lastItem.domNode.focus(); -}; - -PopupMenu.prototype.setFocusToPreviousItem = function (currentItem) { - var index; - - if (currentItem === this.firstItem) { - this.lastItem.domNode.focus(); - } - else { - index = this.menuitems.indexOf(currentItem); - this.menuitems[ index - 1 ].domNode.focus(); - } -}; - -PopupMenu.prototype.setFocusToNextItem = function (currentItem) { - var index; - - if (currentItem === this.lastItem) { - this.firstItem.domNode.focus(); - } - else { - index = this.menuitems.indexOf(currentItem); - this.menuitems[ index + 1 ].domNode.focus(); - } -}; - -PopupMenu.prototype.setFocusByFirstCharacter = function (currentItem, char) { - var start, index; - - char = char.toLowerCase(); - - // Get start index for search based on position of currentItem - start = this.menuitems.indexOf(currentItem) + 1; - if (start === this.menuitems.length) { - start = 0; - } - - // Check remaining slots in the menu - index = this.getIndexFirstChars(start, char); - - // If not found in remaining slots, check from beginning - if (index === -1) { - index = this.getIndexFirstChars(0, char); - } - - // If match was found... - if (index > -1) { - this.menuitems[ index ].domNode.focus(); - } -}; - -PopupMenu.prototype.getIndexFirstChars = function (startIndex, char) { - for (var i = startIndex; i < this.firstChars.length; i++) { - if (char === this.firstChars[ i ]) { - return i; - } - } - return -1; -}; - -/* MENU DISPLAY METHODS */ - -PopupMenu.prototype.open = function () { - // Get position and bounding rectangle of controller object's DOM node - var rect = this.controller.domNode.getBoundingClientRect(); - - // Set CSS properties - if (!this.controller.isMenubarItem) { - this.domNode.parentNode.style.position = 'relative'; - this.domNode.style.display = 'block'; - this.domNode.style.position = 'absolute'; - this.domNode.style.left = rect.width + 'px'; - this.domNode.style.zIndex = 100; - } - else { - this.domNode.style.display = 'block'; - this.domNode.style.position = 'absolute'; - this.domNode.style.top = (rect.height - 1) + 'px'; - this.domNode.style.zIndex = 100; - } - - this.controller.setExpanded(true); - -}; - -PopupMenu.prototype.close = function (force) { - - var controllerHasHover = this.controller.hasHover; - - var hasFocus = this.hasFocus; - - for (var i = 0; i < this.menuitems.length; i++) { - var mi = this.menuitems[i]; - if (mi.popupMenu) { - hasFocus = hasFocus | mi.popupMenu.hasFocus; - } - } - - if (!this.controller.isMenubarItem) { - controllerHasHover = false; - } - - if (force || (!hasFocus && !this.hasHover && !controllerHasHover)) { - this.domNode.style.display = 'none'; - this.domNode.style.zIndex = 0; - this.controller.setExpanded(false); - } -}; diff --git a/examples/menubar/menubar-1/menubar-1.html b/examples/menubar/menubar-navigation.html similarity index 94% rename from examples/menubar/menubar-1/menubar-1.html rename to examples/menubar/menubar-navigation.html index 9b72510beb..3d1ec3c940 100644 --- a/examples/menubar/menubar-1/menubar-1.html +++ b/examples/menubar/menubar-navigation.html @@ -6,42 +6,39 @@ - - - - + + + + - - - - - + +

Navigation Menubar Example

The following implementation of the - design pattern for menubar + design pattern for menubar demonstrates a menubar that provides site navigation menus. Each item in the menubar represents a section of a web site for a mythical university and opens a submenu containing menu items that link to pages within that section.

Similar examples include:

- +

Example

@@ -155,12 +152,6 @@

Example

- - -
diff --git a/test/tests/menubar_menubar-1.js b/test/tests/menubar_menubar-navigation.js similarity index 99% rename from test/tests/menubar_menubar-1.js rename to test/tests/menubar_menubar-navigation.js index f19a8998b9..f27148146f 100644 --- a/test/tests/menubar_menubar-1.js +++ b/test/tests/menubar_menubar-navigation.js @@ -7,7 +7,7 @@ const assertAriaLabelExists = require('../util/assertAriaLabelExists'); const assertAriaRoles = require('../util/assertAriaRoles'); const assertRovingTabindex = require('../util/assertRovingTabindex'); -const exampleFile = 'menubar/menubar-1/menubar-1.html'; +const exampleFile = 'menubar/menubar-navigation.html'; const ex = { // menubar selector From 9e9fdf26d0bda1a109ffbb9bb6391a1320795b7b Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 19 Mar 2020 19:18:22 -0500 Subject: [PATCH 02/62] updated script --- examples/menubar/js/menubar-navigation.js | 145 +--------------------- 1 file changed, 5 insertions(+), 140 deletions(-) diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index 5b12640290..dbda99ff21 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -15,6 +15,7 @@ var MenubarNavigation = function (domNode, actionManager) { this.menuitemGroups = {}; this.menuOrientation = {}; this.isPopup = {}; + this.isPopout = {}; this.openPopups = false; this.firstChars = {}; // see Menubar init method @@ -32,11 +33,10 @@ MenubarNavigation.prototype.getMenuitems = function(domNode) { var nodes = []; var initMenu = this.initMenu.bind(this); - var getGroupId = this.getGroupId.bind(this); var menuitemGroups = this.menuitemGroups; - function findMenuitems(node, group) { - var role, flag, groupId; + function findMenuitems(node) { + var role, flag; while (node) { flag = true; @@ -53,18 +53,8 @@ MenubarNavigation.prototype.getMenuitems = function(domNode) { flag = false; break; - case 'group': - groupId = getGroupId(node); - menuitemGroups[groupId] = []; - break; - case 'menuitem': - case 'menuitemradio': - case 'menuitemcheckbox': nodes.push(node); - if (group) { - group.push(node); - } break; default: @@ -72,14 +62,14 @@ MenubarNavigation.prototype.getMenuitems = function(domNode) { } if (flag && node.firstElementChild) { - findMenuitems(node.firstElementChild, menuitemGroups[groupId]); + findMenuitems(node.firstElementChild); } node = node.nextElementSibling; } } - findMenuitems(domNode.firstElementChild, false); + findMenuitems(domNode.firstElementChild); return nodes; }; @@ -276,53 +266,6 @@ MenubarNavigation.prototype.getMenuOrientation = function(node) { return orientation; }; -MenubarNavigation.prototype.getDataOption = function(node) { - - var option = false; - var hasOption = node.hasAttribute('data-option'); - var role = node.hasAttribute('role'); - - if (!hasOption) { - - while (node && !hasOption && - (role !== 'menu') && - (role !== 'menubar')) { - node = node.parentNode; - if (node) { - role = node.getAttribute('role'); - hasOption = node.hasAttribute('data-option'); - } - } - } - - if (node) { - option = node.getAttribute('data-option'); - } - - return option; -}; - -MenubarNavigation.prototype.getGroupId = function(node) { - - var id = false; - var role = node.getAttribute('role'); - - while (node && (role !== 'group') && - (role !== 'menu') && - (role !== 'menubar')) { - node = node.parentNode; - if (node) { - role = node.getAttribute('role'); - } - } - - if (node) { - id = role + '-' + this.getIdFromAriaLabel(node); - } - - return id; -}; - MenubarNavigation.prototype.getMenuId = function(node) { var id = false; @@ -358,69 +301,6 @@ MenubarNavigation.prototype.getMenu = function(menuitem) { return menu; }; -MenubarNavigation.prototype.toggleCheckbox = function(menuitem) { - if (menuitem.getAttribute('aria-checked') === 'true') { - menuitem.setAttribute('aria-checked', 'false'); - return false; - } - menuitem.setAttribute('aria-checked', 'true'); - return true; -}; - -MenubarNavigation.prototype.setRadioButton = function(menuitem) { - var groupId = this.getGroupId(menuitem); - var radiogroupItems = this.menuitemGroups[groupId]; - radiogroupItems.forEach( function (item) { - item.setAttribute('aria-checked', 'false') - }); - menuitem.setAttribute('aria-checked', 'true'); - return menuitem.textContent; -}; - -MenubarNavigation.prototype.updateFontSizeMenu = function(menuId) { - - var fontSizeMenuitems = this.menuitemGroups[menuId]; - var currentValue = this.actionManager.getFontSize(); - - for (var i = 0; i < fontSizeMenuitems.length; i++) { - var mi = fontSizeMenuitems[i]; - var dataOption = mi.getAttribute('data-option'); - var value = mi.textContent.trim().toLowerCase(); - - switch (dataOption) { - case 'font-smaller': - if (currentValue === 'x-small') { - mi.setAttribute('aria-disabled', 'true'); - } - else { - mi.removeAttribute('aria-disabled'); - } - break; - - case 'font-larger': - if (currentValue === 'x-large') { - mi.setAttribute('aria-disabled', 'true'); - } - else { - mi.removeAttribute('aria-disabled'); - } - break; - - default: - if (currentValue === value) { - mi.setAttribute('aria-checked', 'true'); - } - else { - mi.setAttribute('aria-checked', 'false'); - } - break; - - } - } - - -} - // Popup menu methods MenubarNavigation.prototype.openPopup = function (menuitem) { @@ -536,29 +416,14 @@ MenubarNavigation.prototype.handleKeydown = function (event) { } else { role = tgt.getAttribute('role'); - option = this.getDataOption(tgt); switch(role) { case 'menuitem': - this.actionManager.setOption(option, tgt.textContent); - break; - - case 'menuitemcheckbox': - value = this.toggleCheckbox(tgt); - this.actionManager.setOption(option, value); - break; - - case 'menuitemradio': - value = this.setRadioButton(tgt); - this.actionManager.setOption(option, value); break; default: break; } - if (this.getMenuId(tgt) === 'menu-size') { - this.updateFontSizeMenu('menu-size'); - } this.openPopups = false; this.closePopup(tgt); } From 4b308a75f2de290fa82afc7d8214fa0a068c0750 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 24 Mar 2020 13:46:14 -0500 Subject: [PATCH 03/62] update menubar navigation --- examples/menubar/css/menubar-navigation.css | 126 +++++--- examples/menubar/js/menubar-navigation.js | 327 ++++++++++++-------- examples/menubar/menubar-navigation.html | 2 +- 3 files changed, 296 insertions(+), 159 deletions(-) diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index 7e7869e8ea..c2115e8157 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -1,42 +1,19 @@ ul[role="menubar"] { - margin: 10px; - padding: 10px; + margin: 0; + margin-top: 0.5em; + margin-bottom: 0.5em; + padding: 8px; font-size: 110%; list-style: none; background-color: #eee; -} - -ul[role="menubar"] [role="menuitem"], -ul[role="menubar"] [role="separator"] { - padding: 0.25em; - background-color: #eee; - border: 2px solid #eee; -} - -ul[role="menubar"] [role="separator"] { - padding-top: 0.15em; - background-image: url('../images/separator.png'); - background-position: center; - background-repeat: repeat-x; -} - -ul[role="menubar"] [role="menuitem"]:focus, -ul[role="menubar"] [role="menuitem"]:hover, -ul[role="menubar"] [role="separator"]:focus, -ul[role="menubar"] [role="separator"]:hover { - background-color: black; - color: white; -} - -ul[role="menubar"] a[role="menuitem"] { - text-decoration: none; - color: black; + border: #eee solid 0px; + border-radius: 5px; } ul[role="menubar"] li { - list-style: none; margin: 0; padding: 0; + list-style: none; } ul[role="menubar"] > li { @@ -44,26 +21,101 @@ ul[role="menubar"] > li { position: relative; } -ul[role="menubar"] > li > a::after { +ul[role="menubar"] > li > [role="menuitem"]::after { content: url('../images/down-arrow-brown.png'); padding-left: 0.25em; } -ul[role="menubar"] ul[role="menu"] { +ul[role="menubar"] [role="menu"] { display: none; position: absolute; - top: -2px; - left: 0; margin: 0; padding: 0; } -ul[role="menubar"] ul[role="menu"] li a { +ul[role="menubar"] [role="group"] { + margin: 0; + padding: 0; +} + +ul[role="menubar"] [role="menuitem"], +ul[role="menubar"] [role="separator"] { + margin: 3px; + padding: 6px; + background-color: #eee; + border: 0px solid #eee; + color: black; + border-radius: 5px; +} + +ul[role="menubar"] [role="menu"] [role="menuitem"], +ul[role="menubar"] [role="menu"] [role="separator"] { display: block; - width: 10em; + width: 12em; } -ul[role="menubar"] ul[role="menu"] a[aria-haspopup="true"]::after { +ul[role="menubar"] [role="menu"] { + display: none; +} + + +ul[role="menubar"] [role="separator"] { + padding-top: 3px; + background-image: url('../images/separator.png'); + background-position: center; + background-repeat: repeat-x; +} + +ul[role="menubar"] [role="menuitem"][aria-expanded="true"] { + padding: 4px; + border: 2px solid #034575; +} + +ul[role="menubar"] [role="menu"] [role="menuitem"][aria-haspopup="true"]::after { content: url('../images/right-arrow-brown.png'); padding-right: 2em; } + +/* focus styling */ + +ul[role="menubar"].focus { + padding: 6px; + border: #034575 solid 2px; +} + +ul[role="menubar"].focus.item { + padding: 5px; + border: #034575 solid 3px; +} + +ul[role="menubar"].focus [role="menu"] { + padding: 8px 5px; + border: 1px solid #034575; + border-radius: 5px; + background-color: #eee; +} + +ul[role="menubar"] [role="menu"].hover { + padding: 9px 6px; + border: 0px solid #034575; + border-radius: 5px; + background-color: #eee; +} + +ul[role="menubar"].focus [role="menu"].item { + padding: 6px 3px; + border: 3px solid #034575; + border-radius: 5px; + background-color: #eee; +} + +ul[role="menubar"] [role="menuitem"]:focus { + padding: 2px; + border: 4px dotted #034575; + outline: none; +} + +ul[role="menubar"] [role="menuitem"]:hover { + padding: 4px; + border: 2px solid #034575; +} diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index dbda99ff21..70f00463e8 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -22,18 +22,22 @@ var MenubarNavigation = function (domNode, actionManager) { this.firstMenuitem = {}; // see Menubar init method this.lastMenuitem = {}; // see Menubar init method - this.initMenu(domNode) + this.initMenu(domNode, 0); + domNode.addEventListener('focusin', this.handleMenubarFocusin.bind(this)); domNode.addEventListener('focusout', this.handleMenubarFocusout.bind(this)); window.addEventListener('mousedown', this.handleBackgroundMousedown.bind(this), true); }; -MenubarNavigation.prototype.getMenuitems = function(domNode) { +MenubarNavigation.prototype.getMenuitems = function(domNode, depth) { var nodes = []; var initMenu = this.initMenu.bind(this); var menuitemGroups = this.menuitemGroups; + var handleMenuMouseover = this.handleMenuMouseover.bind(this); + var handleMenuMouseout = this.handleMenuMouseout.bind(this); + function findMenuitems(node) { var role, flag; @@ -49,7 +53,9 @@ MenubarNavigation.prototype.getMenuitems = function(domNode) { switch (role) { case 'menu': node.tabIndex = -1; - initMenu(node); + node.addEventListener('mouseover', handleMenuMouseover); + node.addEventListener('mouseout', handleMenuMouseout); + initMenu(node, depth + 1); flag = false; break; @@ -74,14 +80,16 @@ MenubarNavigation.prototype.getMenuitems = function(domNode) { return nodes; }; -MenubarNavigation.prototype.initMenu = function (menu) { +MenubarNavigation.prototype.initMenu = function (menu, depth) { var i, menuitems, menuitem, role, nextElement; var menuId = this.getMenuId(menu); - menuitems = this.getMenuitems(menu); + menuitems = this.getMenuitems(menu, depth); this.menuOrientation[menuId] = this.getMenuOrientation(menu); - this.isPopup[menuId] = menu.getAttribute('role') === 'menu'; + + this.isPopup[menuId] = (menu.getAttribute('role') === 'menu') && (depth === 1); + this.isPopout[menuId] = (menu.getAttribute('role') === 'menu') && (depth > 1); this.menuitemGroups[menuId] = []; this.firstChars[menuId] = []; @@ -102,7 +110,12 @@ MenubarNavigation.prototype.initMenu = function (menu) { menuitem.addEventListener('keydown', this.handleKeydown.bind(this)); menuitem.addEventListener('click', this.handleMenuitemClick.bind(this)); + menuitem.addEventListener('focus', this.handleMenuitemFocus.bind(this)); + menuitem.addEventListener('blur', this.handleMenuitemBlur.bind(this)); + menuitem.addEventListener('mouseover', this.handleMenuitemMouseover.bind(this)); + menuitem.addEventListener('mouseout', this.handleMenuitemMouseout.bind(this)); + if( !this.firstMenuitem[menuId]) { if (this.hasPopup(menuitem)) { @@ -129,25 +142,15 @@ MenubarNavigation.prototype.setFocusToMenuitem = function (menuId, newMenuitem, this.closePopup(currentMenuitem); } - if (this.hasPopup(newMenuitem)) { - if (this.openPopups) { - this.openPopup(newMenuitem); - } - } - else { - var menu = this.getMenu(newMenuitem); - var cmi = menu.previousElementSibling; - if (!this.isOpen(cmi)) { - this.openPopup(cmi); - } - } - - if (this.hasPopup(newMenuitem)) { + if (this.isMenubar(menuId)) { this.menuitemGroups[menuId].forEach(function(item) { item.tabIndex = -1; }); - newMenuitem.tabIndex = 0; + + if (this.hasPopup(newMenuitem) && this.openPopups) { + this.openPopup(menuId, newMenuitem); + } } newMenuitem.focus(); @@ -303,19 +306,29 @@ MenubarNavigation.prototype.getMenu = function(menuitem) { // Popup menu methods -MenubarNavigation.prototype.openPopup = function (menuitem) { +MenubarNavigation.prototype.openPopup = function (menuId, menuitem) { // set aria-expanded attribute var popupMenu = menuitem.nextElementSibling; var rect = menuitem.getBoundingClientRect(); - // set CSS properties - popupMenu.style.position = 'absolute'; - popupMenu.style.top = (rect.height + 1) + 'px'; - popupMenu.style.left = '0px'; - popupMenu.style.zIndex = 100; - popupMenu.style.display = 'block'; + // Set CSS properties + if (this.isPopup[menuId]) { + popupMenu.parentNode.style.position = 'relative'; + popupMenu.style.display = 'block'; + popupMenu.style.position = 'absolute'; + popupMenu.style.left = (rect.width + 16) + 'px'; + popupMenu.style.top = '0px'; + popupMenu.style.zIndex = 100; + } + else { + popupMenu.style.display = 'block'; + popupMenu.style.position = 'absolute'; + popupMenu.style.left = '0px'; + popupMenu.style.top = (rect.height + 1) + 'px'; + popupMenu.style.zIndex = 100; + } menuitem.setAttribute('aria-expanded', 'true'); @@ -323,15 +336,31 @@ MenubarNavigation.prototype.openPopup = function (menuitem) { }; +MenubarNavigation.prototype.closePopout = function (menuitem) { + var menu, + menuId = this.getMenuId(menuitem), + cmi = menuitem; + + while (this.isPopup[menuId] || this.isPopout[menuId]) { + menu = this.getMenu(cmi); + cmi = menu.previousElementSibling; + menuId = this.getMenuId(cmi); + cmi.setAttribute('aria-expanded', 'false'); + menu.style.display = 'none'; + } + cmi.focus(); + return cmi; +}; + MenubarNavigation.prototype.closePopup = function (menuitem) { - var menu, cmi; + var menu, + menuId = this.getMenuId(menuitem), + cmi = menuitem; - if (this.hasPopup(menuitem)) { + if (this.isMenubar(menuId)) { if (this.isOpen(menuitem)) { menuitem.setAttribute('aria-expanded', 'false'); menuitem.nextElementSibling.style.display = 'none'; - menuitem.nextElementSibling.style.zIndex = 0; - } } else { @@ -340,8 +369,8 @@ MenubarNavigation.prototype.closePopup = function (menuitem) { cmi.setAttribute('aria-expanded', 'false'); cmi.focus(); menu.style.display = 'none'; - menu.style.zIndex = 0; } + return cmi; }; @@ -352,12 +381,25 @@ MenubarNavigation.prototype.closePopupAll = function () { for (var i = 0; i < popups.length; i++) { var popup = popups[i]; if (this.isOpen(popup)) { - this.closePopup(popup); - event.stopPropagation(); - event.preventDefault(); + popup.setAttribute('aria-expanded', 'false'); + popup.nextElementSibling.style.display = 'none'; } } +}; + +MenubarNavigation.prototype.closePopupHover = function () { + var menus = this.domNode.querySelectorAll('[role="menu'); + + for (var i = 0; i < menus.length; i++) { + var menu = menus[i]; + var focus = menu.parentNode.querySelector('.item'); + var hover = menu.classList.contains('hover'); + if (!focus && !hover) { + menu.style.display = 'none'; + menu.previousElementSibling.setAttribute('aria-expanded', 'false'); + } + } }; MenubarNavigation.prototype.hasPopup = function (menuitem) { @@ -368,6 +410,14 @@ MenubarNavigation.prototype.isOpen = function (menuitem) { return menuitem.getAttribute('aria-expanded') === 'true'; }; +MenubarNavigation.prototype.isMenubar = function (menuiId) { + return !this.isPopup[menuId] && !this.isPopout[menuId]; +}; + +MenubarNavigation.prototype.isMenuHorizontal = function (menuitem) { + return this.menuOrientation[menuitem] === 'horizontal'; +}; + // Menu event handlers MenubarNavigation.prototype.handleMenubarFocusin = function (event) { @@ -380,9 +430,22 @@ MenubarNavigation.prototype.handleMenubarFocusout = function (event) { this.domNode.classList.remove('focus'); }; +MenubarNavigation.prototype.handleMenuitemFocus = function (event) { + var menu = this.getMenu(event.target); + menu.classList.add('item'); +}; + +MenubarNavigation.prototype.handleMenuitemBlur = function (event) { + var menu = this.getMenu(event.target); + menu.classList.remove('item'); +}; + + MenubarNavigation.prototype.handleBackgroundMousedown = function (event) { if (!this.domNode.contains(event.target)) { this.closePopupAll(); + event.stopPropagation(); + event.preventDefault(); } }; @@ -398,9 +461,6 @@ MenubarNavigation.prototype.handleKeydown = function (event) { option, value; - console.log('[handleMenubarKeydown][key]: ' + key); - console.log('[handleMenubarKeydown][menuId]: ' + menuId); - // This fixes a problem with regression tests using Key.SPACE if (event.keyCode === 32) { key = ' '; @@ -411,90 +471,94 @@ MenubarNavigation.prototype.handleKeydown = function (event) { case 'Enter': if (this.hasPopup(tgt)) { this.openPopups = true; - popupMenuId = this.openPopup(tgt); + popupMenuId = this.openPopup(menuId, tgt); this.setFocusToFirstMenuitem(popupMenuId); } else { - role = tgt.getAttribute('role'); - switch(role) { - case 'menuitem': - break; - - default: - break; + if (tgt.href !== '#') { + this.closePopupAll(); + window.location.href=tgt.href; } + } + flag = true; + break; + case 'Esc': + case 'Escape': this.openPopups = false; this.closePopup(tgt); + flag = true; + break; + + case 'Up': + case 'ArrowUp': + if (this.isMenuHorizontal(menuId)) { + if (this.hasPopup(tgt)) { + this.openPopups = true; + popupMenuId = this.openPopup(menuId, tgt); + this.setFocusToLastMenuitem(popupMenuId); + } + } + else { + this.setFocusToPreviousMenuitem(menuId, tgt); } flag = true; - break; + break; case 'ArrowDown': case 'Down': - if (this.menuOrientation[menuId] === 'vertical') { - this.setFocusToNextMenuitem(menuId, tgt); - flag = true; - } - else { + if (this.isMenuHorizontal(menuId)) { if (this.hasPopup(tgt)) { this.openPopups = true; - popupMenuId = this.openPopup(tgt); + popupMenuId = this.openPopup(menuId, tgt); this.setFocusToFirstMenuitem(popupMenuId); - flag = true; } } - break; - - case 'Esc': - case 'Escape': - this.openPopups = false; - this.closePopup(tgt); - flag = true; + else { + this.setFocusToNextMenuitem(menuId, tgt); + } + flag = true; break; case 'Left': case 'ArrowLeft': - if (this.menuOrientation[menuId] === 'horizontal') { + if (this.isMenuHorizontal(menuId)) { this.setFocusToPreviousMenuitem(menuId, tgt); - flag = true; } else { - mi = this.closePopup(tgt); - id = this.getMenuId(mi); - mi = this.setFocusToPreviousMenuitem(id, mi); - this.openPopup(mi); + if (this.isPopout[menuId]) { + mi = this.closePopup(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToMenuitem(id, mi); + } + else { + mi = this.closePopup(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToPreviousMenuitem(id, mi); + this.openPopup(id, mi); + } } + flag = true; break; case 'Right': case 'ArrowRight': - if (this.menuOrientation[menuId] === 'horizontal') { + if (this.isMenuHorizontal(menuId)) { this.setFocusToNextMenuitem(menuId, tgt); - flag = true; - } - else { - mi = this.closePopup(tgt); - id = this.getMenuId(mi); - mi = this.setFocusToNextMenuitem(id, mi); - this.openPopup(mi); - } - break; - - case 'Up': - case 'ArrowUp': - if (this.menuOrientation[menuId] === 'vertical') { - this.setFocusToPreviousMenuitem(menuId, tgt); - flag = true; } else { if (this.hasPopup(tgt)) { - this.openPopups = true; - popupMenuId = this.openPopup(tgt); - this.setFocusToLastMenuitem(popupMenuId); - flag = true; + popupMenuId = this.openPopup(menuId, tgt); + this.setFocusToFirstMenuitem(popupMenuId); + } + else { + mi = this.closePopout(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToNextMenuitem(id, mi); + this.openPopup(id, mi); } } + flag = true; break; case 'Home': @@ -530,6 +594,7 @@ MenubarNavigation.prototype.handleKeydown = function (event) { MenubarNavigation.prototype.handleMenuitemClick = function (event) { var tgt = event.currentTarget, + menuId = this.getMenuId(tgt), role, option, value; @@ -542,53 +607,73 @@ MenubarNavigation.prototype.handleMenuitemClick = function (event) { else { this.closePopupAll(); this.openPopups = true; - this.openPopup(tgt); + this.openPopup(menuId, tgt); } + event.stopPropagation(); + event.preventDefault(); } - else { - role = tgt.getAttribute('role'); - option = this.getDataOption(tgt); - switch(role) { - case 'menuitem': - this.actionManager.setOption(option, tgt.textContent); - break; +}; - case 'menuitemcheckbox': - value = this.toggleCheckbox(tgt); - this.actionManager.setOption(option, value); - break; +MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { + var tgt = event.currentTarget, + menuId, + menu; - case 'menuitemradio': - value = this.setRadioButton(tgt); - this.actionManager.setOption(option, value); - break; + if (this.hasPopup(tgt)) { + menuId = this.getMenuId(tgt); + menu = this.getMenu(tgt); + this.openPopup(menuId, tgt); + tgt.nextElementSibling.classList.add('hover'); + } +}; - default: - break; - } +MenubarNavigation.prototype.handleMenuitemMouseout = function (event) { + var tgt = event.currentTarget, + menuId, + menu; - if (this.getMenuId(tgt) === 'menu-size') { - this.updateFontSizeMenu('menu-size'); - } - this.openPopups = false; - this.closePopup(tgt); + if (this.hasPopup(tgt)) { + menuId = this.getMenuId(tgt); + menu = this.getMenu(tgt); + tgt.nextElementSibling.classList.remove('hover'); } - event.stopPropagation(); - event.preventDefault(); + var closePopupHover = this.closePopupHover.bind(this); + setTimeout(function(){ closePopupHover() }, 400); +}; + +MenubarNavigation.prototype.handleMenuMouseover = function (event) { + var menu = event.currentTarget, + menuId; + + while( menu) { + menu.classList.add('hover'); + if (menu.previousElementSibling) { + menu = this.getMenu(menu.previousElementSibling); + } + else { + menu = false; + } + } + var closePopupHover = this.closePopupHover.bind(this); + setTimeout(function(){ closePopupHover() }, 500); }; -MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { +MenubarNavigation.prototype.handleMenuMouseout = function (event) { var tgt = event.currentTarget; + var menu = this.getMenu(tgt); + menu.classList.remove('hover'); - if (this.hasPopup(tgt) && this.openPopups) { - if (!this.isOpen(tgt)) { - this.closePopupAll(); - this.openPopups = true; - this.openPopup(tgt); - } + var menus = menu.querySelectorAll('[role="menu"]'); + + for (var i = 0; i < menus.length; i++) { + menus[i].classList.remove('hover'); } + + var closePopupHover = this.closePopupHover.bind(this); + setTimeout(function(){ closePopupHover() }, 400); + }; // Initialize menubar editor diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 3d1ec3c940..7c8b71cfc3 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -92,7 +92,7 @@

Example

Apply
  • - +
    • Undergraduate From fc2074ed4255f9a34c78cf23b9203f08afd24ffd Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 24 Mar 2020 15:25:06 -0500 Subject: [PATCH 04/62] udpated menubar navigation --- examples/menubar/js/menubar-navigation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index 70f00463e8..ec2a2cd1ae 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -410,7 +410,7 @@ MenubarNavigation.prototype.isOpen = function (menuitem) { return menuitem.getAttribute('aria-expanded') === 'true'; }; -MenubarNavigation.prototype.isMenubar = function (menuiId) { +MenubarNavigation.prototype.isMenubar = function (menuId) { return !this.isPopup[menuId] && !this.isPopout[menuId]; }; @@ -673,7 +673,7 @@ MenubarNavigation.prototype.handleMenuMouseout = function (event) { var closePopupHover = this.closePopupHover.bind(this); setTimeout(function(){ closePopupHover() }, 400); - + }; // Initialize menubar editor From 5ddc2cd90d7cc80e60b473cd6e07e56929d51894 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 24 Mar 2020 15:35:59 -0500 Subject: [PATCH 05/62] updated documentation on keyboard support --- examples/menubar/menubar-navigation.html | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 7c8b71cfc3..b27df4219e 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -159,6 +159,8 @@

      Example

      Accessibility Features

        +
      1. To support high contrast settings the keyboard focus is highlighted by adding and removing a dashed border around the menu item with keyboard focus.
      2. +
      3. When any menu item receives focus a border is added to the menubar or menu container to provide a visual afforance to use the cursor keys to navigate the menu items.
      4. Since the menubar presents a site navigation system, it is wrapped in a navigation region implemented with a nav element that has an aria-label that matches the label on the menubar.
      5. The down arrow and right arrow icons are made compatible with high contrast mode and hidden from screen readers by using the CSS content property to render images.
      @@ -240,7 +242,8 @@

      Menubar

      -

      Submenu

      + +

      Submenu

      @@ -255,8 +258,17 @@

      Submenu

      From f52ee725c390c92bf4669ca1e772513bc6bd8612 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 31 Mar 2020 15:42:22 -0500 Subject: [PATCH 06/62] updated tests to identify failing tests due to Issue 1358 --- examples/menubar/js/menubar-navigation.js | 5 ----- test/tests/menubar_menubar-navigation.js | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index ec2a2cd1ae..bf4d51a65f 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -461,11 +461,6 @@ MenubarNavigation.prototype.handleKeydown = function (event) { option, value; - // This fixes a problem with regression tests using Key.SPACE - if (event.keyCode === 32) { - key = ' '; - } - switch (key) { case ' ': case 'Enter': diff --git a/test/tests/menubar_menubar-navigation.js b/test/tests/menubar_menubar-navigation.js index f27148146f..72ae9db2e7 100644 --- a/test/tests/menubar_menubar-navigation.js +++ b/test/tests/menubar_menubar-navigation.js @@ -374,7 +374,7 @@ ariaTest('Key ENTER open submenu', exampleFile, 'menubar-space-or-enter', async }); -ariaTest('Key SPACE open submenu', exampleFile, 'menubar-space-or-enter', async (t) => { +ariaTest.failing('Key SPACE open submenu', exampleFile, 'menubar-space-or-enter', async (t) => { t.plan(6); const menubaritems = await t.context.session.findElements(By.css(ex.menubarMenuitemSelector)); From 4cd1d1d32c8ab489640a5cd0a8adfd52bfb02a6d Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 1 Apr 2020 16:32:15 -0500 Subject: [PATCH 07/62] updated test with SPACE key --- test/tests/menubar_menubar-navigation.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/tests/menubar_menubar-navigation.js b/test/tests/menubar_menubar-navigation.js index f27148146f..670e615b29 100644 --- a/test/tests/menubar_menubar-navigation.js +++ b/test/tests/menubar_menubar-navigation.js @@ -382,7 +382,7 @@ ariaTest('Key SPACE open submenu', exampleFile, 'menubar-space-or-enter', async for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { // Send the SPACE key - await menubaritems[menuIndex].sendKeys(Key.SPACE); + await menubaritems[menuIndex].sendKeys(' '); // Test that the submenu is displayed t.true( @@ -655,7 +655,7 @@ ariaTest.failing('SPACE in submenu selects item', exampleFile, 'submenu-space-or const itemText = await items[itemIndex].getText(); // send SPACE to the item - await items[itemIndex].sendKeys(Key.SPACE); + await items[itemIndex].sendKeys(' '); await waitForUrlChange(t); t.not( @@ -683,7 +683,7 @@ ariaTest.failing('SPACE in submenu selects item', exampleFile, 'submenu-space-or // send SPACE to the item we are testing const items = await t.context.session.findElements(By.css(submenuMenuitemSelector)); const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.SPACE); + await items[itemIndex].sendKeys(' '); await waitForUrlChange(t); t.not( From a7a83c51c5c292468f073c9bece3f28d3aedf0ad Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Fri, 3 Apr 2020 14:36:26 -0500 Subject: [PATCH 08/62] updated focus styling --- examples/menubar/css/menubar-navigation.css | 14 +++++++------- examples/menubar/js/menubar-navigation.js | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index c2115e8157..16352978da 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -68,7 +68,7 @@ ul[role="menubar"] [role="separator"] { ul[role="menubar"] [role="menuitem"][aria-expanded="true"] { padding: 4px; - border: 2px solid #034575; + border: 2px dashed #034575; } ul[role="menubar"] [role="menu"] [role="menuitem"][aria-haspopup="true"]::after { @@ -88,16 +88,16 @@ ul[role="menubar"].focus.item { border: #034575 solid 3px; } -ul[role="menubar"].focus [role="menu"] { - padding: 8px 5px; - border: 1px solid #034575; +ul[role="menubar"] [role="menu"].hover { + padding: 9px 6px; + border: 0px solid #034575; border-radius: 5px; background-color: #eee; } -ul[role="menubar"] [role="menu"].hover { - padding: 9px 6px; - border: 0px solid #034575; +ul[role="menubar"].focus [role="menu"] { + padding: 7px 4px; + border: 2px solid #034575; border-radius: 5px; background-color: #eee; } diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index ec2a2cd1ae..069618947c 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -318,7 +318,7 @@ MenubarNavigation.prototype.openPopup = function (menuId, menuitem) { popupMenu.parentNode.style.position = 'relative'; popupMenu.style.display = 'block'; popupMenu.style.position = 'absolute'; - popupMenu.style.left = (rect.width + 16) + 'px'; + popupMenu.style.left = (rect.width + 12) + 'px'; popupMenu.style.top = '0px'; popupMenu.style.zIndex = 100; } @@ -326,7 +326,7 @@ MenubarNavigation.prototype.openPopup = function (menuId, menuitem) { popupMenu.style.display = 'block'; popupMenu.style.position = 'absolute'; popupMenu.style.left = '0px'; - popupMenu.style.top = (rect.height + 1) + 'px'; + popupMenu.style.top = rect.height + 'px'; popupMenu.style.zIndex = 100; } From 63026bb2fccb35995e2412a584ff7d7c1c9c62f9 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Mon, 20 Apr 2020 16:46:44 -0500 Subject: [PATCH 09/62] added a 1 pixel border to menubar to improve high contrast support --- examples/menubar/css/menubar-navigation.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index 16352978da..9f119710e9 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -2,11 +2,11 @@ ul[role="menubar"] { margin: 0; margin-top: 0.5em; margin-bottom: 0.5em; - padding: 8px; + padding: 7px; font-size: 110%; list-style: none; background-color: #eee; - border: #eee solid 0px; + border: #eee solid 1px; border-radius: 5px; } From a3eadca717ba09362ec18f4d2784501174d95a66 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 21 Apr 2020 12:30:33 -0500 Subject: [PATCH 10/62] updated the example to use SVG images to improve color contrast in high contrast mode --- examples/menubar/css/menubar-navigation.css | 23 +++++++++++++++--- examples/menubar/images/down-arrow-brown.png | Bin 1362 -> 0 bytes examples/menubar/images/down-arrow-focus.svg | 4 +++ examples/menubar/images/down-arrow-gray.png | Bin 1325 -> 0 bytes examples/menubar/images/down-arrow.svg | 4 +++ examples/menubar/images/right-arrow-brown.png | Bin 1401 -> 0 bytes examples/menubar/images/right-arrow-gray.png | Bin 1357 -> 0 bytes examples/menubar/images/right-arrow.svg | 4 +++ examples/menubar/images/separator.paint | Bin 4059 -> 0 bytes examples/menubar/images/separator.png | Bin 1134 -> 0 bytes examples/menubar/images/up-arrow.svg | 4 +++ 11 files changed, 36 insertions(+), 3 deletions(-) delete mode 100644 examples/menubar/images/down-arrow-brown.png create mode 100644 examples/menubar/images/down-arrow-focus.svg delete mode 100644 examples/menubar/images/down-arrow-gray.png create mode 100644 examples/menubar/images/down-arrow.svg delete mode 100644 examples/menubar/images/right-arrow-brown.png delete mode 100644 examples/menubar/images/right-arrow-gray.png create mode 100644 examples/menubar/images/right-arrow.svg delete mode 100644 examples/menubar/images/separator.paint delete mode 100644 examples/menubar/images/separator.png create mode 100644 examples/menubar/images/up-arrow.svg diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index 9f119710e9..89695bef6a 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -22,8 +22,18 @@ ul[role="menubar"] > li { } ul[role="menubar"] > li > [role="menuitem"]::after { - content: url('../images/down-arrow-brown.png'); + content: url('../images/down-arrow.svg'); padding-left: 0.25em; + position: relative; + top: 2px; +} + +ul[role="menubar"].focus > li > [role="menuitem"]::after { + content: url('../images/down-arrow-focus.svg'); +} + +ul[role="menubar"] > li > [role="menuitem"][aria-expanded="true"]::after { + content: url('../images/up-arrow.svg'); } ul[role="menubar"] [role="menu"] { @@ -72,8 +82,15 @@ ul[role="menubar"] [role="menuitem"][aria-expanded="true"] { } ul[role="menubar"] [role="menu"] [role="menuitem"][aria-haspopup="true"]::after { - content: url('../images/right-arrow-brown.png'); - padding-right: 2em; + content: url('../images/right-arrow.svg'); + position: relative; + left: 8px; +} + +ul[role="menubar"] [role="menu"] [role="menuitem"][aria-expanded="true"]::after { + content: url('../images/down-arrow-focus.svg'); + top: 2px; + left: 4px; } /* focus styling */ diff --git a/examples/menubar/images/down-arrow-brown.png b/examples/menubar/images/down-arrow-brown.png deleted file mode 100644 index fe3e38aa3137fad169770d5cb788db37a5d15390..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1362 zcmV-Y1+DstP)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%R z07*naR45f=U>J0OG0m8P3+SrfM7oTXg^`(UCBxreTK^ds{@~NZ$iTqDz|3}yg&KnbyBWT}SO14Y2NOt~Q}8B-ifI;5BLhRezn=N8SGRZm`}Lh4XcGzmDPUsb{KzJz z>OOJfrITRI6E|Kw#jD|1$jlD(4zdm)n}vg$0VD@f12PG$fq{YXf7ku3i`hk$rm?cI zLTmskW@Tez;8wJl*7arUB9Iy+4M64r#y=_r`^q`xjE^!iGBU8RurhEdTOE~8o>b1T zn(;5h6u1o#ZnmS?7a;?WBxYXGSIoR3uOuuJld>JPzrYmZq)QWAy-H(Uy|5_&0JyeA U9gsdwRR91007*qoM6N<$g5(>R-T(jq diff --git a/examples/menubar/images/down-arrow-focus.svg b/examples/menubar/images/down-arrow-focus.svg new file mode 100644 index 0000000000..1978a27bbf --- /dev/null +++ b/examples/menubar/images/down-arrow-focus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/menubar/images/down-arrow-gray.png b/examples/menubar/images/down-arrow-gray.png deleted file mode 100644 index f5d34f2e07d8f85cfb228ca7a587a94204f5790a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1325 zcmV+|1=9M7P)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z+et)0R45f=U>J0Ov7w=XOHxwuH!&`I{`@&B3mY5T%J=WzYyJQK{|`P*jEsyRwbxkw z{ri{2#Kg3lot<6%_wU~fKnWCmK!X@qSy>r={rYtiq!ujO*4Adu%*?zKsFEKjh@t^# z7>NJz@87@fRaI3d!8{NHC{PGA{XdFoAO|D{k^`v$$uYwe?A^Qff`fyD7|@-jFk4ty zSQ!5N`7;HmxE-brYy*q~^g}ri9|kFeDn1N!dlgI`M5Fo;C;;@28qnQ`K$1Y4?0}}< j000000NkvXXu0mjf+hB%b diff --git a/examples/menubar/images/down-arrow.svg b/examples/menubar/images/down-arrow.svg new file mode 100644 index 0000000000..a953c85c5f --- /dev/null +++ b/examples/menubar/images/down-arrow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/menubar/images/right-arrow-brown.png b/examples/menubar/images/right-arrow-brown.png deleted file mode 100644 index 9808455aea8f66c00b9b50251a823befc107fe89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1401 zcmV-<1%~>GP)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%R zCrLy>R45f=U_b(C#tdBPW^9{_65V8xgi)AGC_Dy+-wc0$Y5ll!dUw9Ro;d@!Q{F*2zA1L*((AkD(U%D~Jk z@`^)BClRL&5Y<2uXypHYe}MQe0|U@E_%#4cXJut&;8rp}D&?AG+r41-HWs)^Xbg~B znK`%^*hH14DQ0de&vq310#bri1IXI{Y@8o?)Ex@D?r&Yp;K=v~q!4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z`$y})6b&H3|NsC00otO@%*?zKs3{Sq5JqEY07?A*{ToE_18tfM)HDg`R34B32w>H~ z$jHb5^asP=zkeB7Sy^-0+1a-NHLBs%05%Fr{Q2{T0cevg&~Jx;n!K>ufUE(XO;7{S zNen<+4g;NNS5;ND4XXxVNHGANzyJ)X|382JoCd^RK*hH}wy~gF3F3oH1%}i|pvi^R z)z$Nn)nI4 + + + diff --git a/examples/menubar/images/separator.paint b/examples/menubar/images/separator.paint deleted file mode 100644 index 9e313a1cc163211b5a31090cce65926fb882864c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4059 zcmbtXd2AHd8UN)1I#3lIeMSl!`e2o)g(EVOd#a3R ztO&RlU!BXzb@W!LaYeb1m%pf>KM<-N6AiJiDy)dQ-c{&4Il54>dE|dneA-=>le@Th z>Em@9w|iSUp6nakGcg&B8T$_<%w1;Y98QJ;_Ke%XjLiQ&l!EgDzHk{%5C zM0Hx!L-GM>xJ}eE{2{+4?$f=RCeGxVr6$QW>A=Cd`f8!B{@Dv9`K9GU7)mnmxw0jX zn7p~jbeSbrmOYwZv0PZOva)KGb9LoVlXO6qv~JOe>}giTQAG+FT69txWR`qUC8`a# z>n*aPFz;H&jKP+uCRy~MggE1wwmdW6%*!=%@-D4?qB_5(_H&-yflx&BB3ij zF_wZMX<|xJ4c$%LT$g(mOaSn@0k|~SfykmXrdW-L5|lLS)fDAy3fxWtFQfo!wN`nB z6!4@#V+wSqz|$!ZO93+hO#6j(>oOA$rCr-dk%v^M1MZT+l0x+Qh4wY_10;U-a+-dnaDi@##0(;4WhGz)TbO{eL| zyNo3Z=GWAOdRWnR$*R;Zj~S6JcYUTb-ef8`3ka!VA)v^ylAIQ<-mEP7d5zL8s&MGt1-kcFAv`^R+10m!+ETI@JSE6ERsHrWSx zVl1W*0boisH0)wwU~ovtxdsFtfkjXbi^Q-V^LBUdV#}E<`(60leb0&jF4jDpzfk&t z*`hH?52Js%NRN)`;VH!X0c@{?W14~f6(fD)J|o63^PyNn2N6Hb@I(S54~j<<_$5oG zC+I_&R}eeH5pfLhDa6&IS@el4obUl9P*1C*N}3!NST4G?=(wy%79%ly20Kgg)8Ld6 z$C#~WIgSPTWN0V;uEE$}7!&;r*CIX@7Mnwes}NtGmiq!pd_QJ11ra|6Kvu>ld%cLU zn@QWa*3yd@^$;x*Z%g5C9*FexBfS*qZ>gh!uH@RQVcmzhgq29YACcNwek-t@1=(ot zL5zCnb}brY^5_E{ACvseh^rBQVKTat`G7j<8~SvJ(V0J24@7*-Pey;|8L^`qaXI2Y zN=hq}M_csYF{3-_17D#k0cIC%@qS6S;zd1tg%Rm#Lps{!6+`PuuH{dR%Ps9m`5#8K zHb!S_ug4UtN6|MM(c(cCJKD0>=aS3-(}vy@DYwN8ggq{}dE@pK%!Ey={nl&k;~NLAqjOEwI?iq|2I znj-KKQX^@d0{CziN7}?E=t=nUVNy?&FXKJ@di-wTck$c!jr=AM_#J#Bzn%9ZbrZiO zt-Bl7q#|Pee27*OdiJ74$&%~C*x$q$4G?jBA0?uor!_3d%8By$nhfWtHX?`5f9Y>o zxsq|^WLlzTK}NeiE9bU9**>sc#_^i%W?F|}yJ5SDpKF+#Of1n1Kbd7#DH$soT_E>h zEmBfmj8w5!PGJ^gj9JQxxh7l3xkEo+lCgG~KeOjz!MQ`@>WT6Um1RLX7PuRLqtzLz z=Zfbqp39!^cwY1TnSPc2nErzPg#MJi0|Gru-=x2yzoDn;&(iXr)gpcg%sVIS($FyE}-RG2~8?0;S|BEEvJ0NnfX{QSRW=jZRu zq5hiyXB9k8?$t)zZal=w#*`Sd&Rlp9n%k>4;F&!&MX~s_b#AjLuf_MZHw?35To7+n zqB@~SC^2*3sX(YRZm=tabt{>hIoNCO;r=kYAFw$lK&9xlV47o8%U` zP2MLTk$=!Sx|wdJKH5e*Xcrx%!&Ify^bkEvpQA_Ui}WRWoW4xINWVsZN3YOp^cMX) z{h0o<;6}mif;$BtJBTC4QRpajR5>1ZY;BP*BpNN}QdK z`}-1iYm=*NWUp3y-0aYpU8gO$^|U|$dR^3IbLG2ATotZrm&euMYIFr%gRZbkbxphW SyN)E*r{h|dn5Rb9^Zx-D`F5@V diff --git a/examples/menubar/images/separator.png b/examples/menubar/images/separator.png deleted file mode 100644 index a837fbce93afde0ba8abc83520cbf7bf42b1e924..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1134 zcmV-!1d;oRP)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z97#k$R5%f1U>F6%AOsk5a&rC;1AmNqVSqyb04qBJMQeR0N&o-=07*qoM6N<$g4BN| AA^-pY diff --git a/examples/menubar/images/up-arrow.svg b/examples/menubar/images/up-arrow.svg new file mode 100644 index 0000000000..b110cf21db --- /dev/null +++ b/examples/menubar/images/up-arrow.svg @@ -0,0 +1,4 @@ + + + + From db7e292ee034af10f5a2810a439260493ae618b2 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 21 Apr 2020 12:40:09 -0500 Subject: [PATCH 11/62] preload images --- examples/menubar/menubar-navigation.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index b27df4219e..9845ae8abd 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -17,6 +17,13 @@ + +
      + + + + +
      From 4c7497a2cc0ebb8cce298b0e52b66e17a1bb7a3a Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 21 Apr 2020 10:41:48 -0700 Subject: [PATCH 14/62] Update links to source js and css --- examples/menubar/menubar-navigation.html | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 63c21ad0f9..1195825608 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -618,22 +618,8 @@

      Submenu

      Javascript and CSS Source Code

      From 3495c5718b312435841e74460c1e53e852e484f1 Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 21 Apr 2020 10:43:09 -0700 Subject: [PATCH 15/62] Fix href in footer link to design pattern --- examples/menubar/menubar-navigation.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 1195825608..4f7c4f775c 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -639,7 +639,7 @@

      HTML Source Code

      From 71cd9c32c7f57d670113adec776d2ee3ae742ea5 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 22 Apr 2020 11:04:40 -0500 Subject: [PATCH 16/62] updated focus styling to use solid borders --- examples/menubar/css/menubar-navigation.css | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index 89695bef6a..b27bc89f0c 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -76,11 +76,6 @@ ul[role="menubar"] [role="separator"] { background-repeat: repeat-x; } -ul[role="menubar"] [role="menuitem"][aria-expanded="true"] { - padding: 4px; - border: 2px dashed #034575; -} - ul[role="menubar"] [role="menu"] [role="menuitem"][aria-haspopup="true"]::after { content: url('../images/right-arrow.svg'); position: relative; @@ -128,7 +123,7 @@ ul[role="menubar"].focus [role="menu"].item { ul[role="menubar"] [role="menuitem"]:focus { padding: 2px; - border: 4px dotted #034575; + border: 4px solid #034575; outline: none; } From ba5aabd275a06f64014fa63d4671eaf65fa9a3d3 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 22 Apr 2020 11:12:15 -0500 Subject: [PATCH 17/62] add SVG separator --- examples/menubar/css/menubar-navigation.css | 2 +- examples/menubar/images/separator.svg | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 examples/menubar/images/separator.svg diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index b27bc89f0c..6b52bc5939 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -71,7 +71,7 @@ ul[role="menubar"] [role="menu"] { ul[role="menubar"] [role="separator"] { padding-top: 3px; - background-image: url('../images/separator.png'); + background-image: url('../images/separator.svg'); background-position: center; background-repeat: repeat-x; } diff --git a/examples/menubar/images/separator.svg b/examples/menubar/images/separator.svg new file mode 100644 index 0000000000..b4b39760b2 --- /dev/null +++ b/examples/menubar/images/separator.svg @@ -0,0 +1,4 @@ + + + + From 7ea221bec016e6541a18fd645f37f9a8c3c1a0fe Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 30 Apr 2020 11:06:05 -0500 Subject: [PATCH 18/62] started updating mouse behavior --- examples/menubar/js/menubar-navigation.js | 200 +++++++++------------- examples/menubar/menubar-navigation.html | 2 +- 2 files changed, 78 insertions(+), 124 deletions(-) diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index 78b4c1383a..6c1a85636c 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -7,11 +7,12 @@ * Desc: Creates a menubar of hierarchical set of links */ -var MenubarNavigation = function (domNode, actionManager) { +var MenubarNavigation = function (domNode) { this.domNode = domNode; - this.actionManager = actionManager; + + this.popups = []; this.menuitemGroups = {}; this.menuOrientation = {}; this.isPopup = {}; @@ -35,9 +36,7 @@ MenubarNavigation.prototype.getMenuitems = function(domNode, depth) { var initMenu = this.initMenu.bind(this); var menuitemGroups = this.menuitemGroups; - var handleMenuMouseover = this.handleMenuMouseover.bind(this); - var handleMenuMouseout = this.handleMenuMouseout.bind(this); - + var popups = this.popups; function findMenuitems(node) { var role, flag; @@ -53,13 +52,14 @@ MenubarNavigation.prototype.getMenuitems = function(domNode, depth) { switch (role) { case 'menu': node.tabIndex = -1; - node.addEventListener('mouseover', handleMenuMouseover); - node.addEventListener('mouseout', handleMenuMouseout); initMenu(node, depth + 1); flag = false; break; case 'menuitem': + if (node.getAttribute('aria-haspopup') === 'true') { + popups.push(node); + } nodes.push(node); break; @@ -81,7 +81,7 @@ MenubarNavigation.prototype.getMenuitems = function(domNode, depth) { }; MenubarNavigation.prototype.initMenu = function (menu, depth) { - var i, menuitems, menuitem, role, nextElement; + var menuitems, menuitem, role, nextElement; var menuId = this.getMenuId(menu); @@ -96,7 +96,7 @@ MenubarNavigation.prototype.initMenu = function (menu, depth) { this.firstMenuitem[menuId] = null; this.lastMenuitem[menuId] = null; - for(i = 0; i < menuitems.length; i++) { + for(var i = 0; i < menuitems.length; i++) { menuitem = menuitems[i]; role = menuitem.getAttribute('role'); @@ -110,11 +110,8 @@ MenubarNavigation.prototype.initMenu = function (menu, depth) { menuitem.addEventListener('keydown', this.handleKeydown.bind(this)); menuitem.addEventListener('click', this.handleMenuitemClick.bind(this)); - menuitem.addEventListener('focus', this.handleMenuitemFocus.bind(this)); - menuitem.addEventListener('blur', this.handleMenuitemBlur.bind(this)); menuitem.addEventListener('mouseover', this.handleMenuitemMouseover.bind(this)); - menuitem.addEventListener('mouseout', this.handleMenuitemMouseout.bind(this)); if( !this.firstMenuitem[menuId]) { @@ -126,43 +123,51 @@ MenubarNavigation.prototype.initMenu = function (menu, depth) { this.lastMenuitem[menuId] = menuitem; } + +// console.log('[initMenu][menuId]: ' + menuId + ' ' + this.isPopup[menuId] + ' ' + this.isPopout[menuId]); +// menuitems.forEach(item => console.log('[menuitem]: ' + item.textContent + ' ' + this.hasPopup(item))); }; /* MenubarNavigation FOCUS MANAGEMENT METHODS */ -MenubarNavigation.prototype.setFocusToMenuitem = function (menuId, newMenuitem, currentMenuitem) { - - if (typeof currentMenuitem !== 'object') { - currentMenuitem = false; - } +MenubarNavigation.prototype.setFocusToMenuitem = function (menuId, newMenuitem) { - if (currentMenuitem && - this.hasPopup(currentMenuitem) && - this.isOpen(currentMenuitem)) { - this.closePopup(currentMenuitem); - } + var isAnyPopupOpen = this.isAnyPopupOpen(); - if (this.isMenubar(menuId)) { - this.menuitemGroups[menuId].forEach(function(item) { - item.tabIndex = -1; - }); - newMenuitem.tabIndex = 0; + this.closePopupAll(newMenuitem); - if (this.hasPopup(newMenuitem) && this.openPopups) { + if (this.hasPopup(newMenuitem)) { + if (isAnyPopupOpen) { this.openPopup(menuId, newMenuitem); } } + else { + var menu = this.getMenu(newMenuitem); + var cmi = menu.previousElementSibling; + if (!this.isOpen(cmi)) { + this.openPopup(menuId, cmi); + } + } + + if (this.hasPopup(newMenuitem)) { + if (this.menuitemGroups[menuId]) { + this.menuitemGroups[menuId].forEach(function(item) { + item.tabIndex = -1; + }); + } + newMenuitem.tabIndex = 0; + } newMenuitem.focus(); }; MenubarNavigation.prototype.setFocusToFirstMenuitem = function (menuId, currentMenuitem) { - this.setFocusToMenuitem(menuId, this.firstMenuitem[menuId], currentMenuitem); + this.setFocusToMenuitem(menuId, this.firstMenuitem[menuId]); }; MenubarNavigation.prototype.setFocusToLastMenuitem = function (menuId, currentMenuitem) { - this.setFocusToMenuitem(menuId, this.lastMenuitem[menuId], currentMenuitem); + this.setFocusToMenuitem(menuId, this.lastMenuitem[menuId]); }; MenubarNavigation.prototype.setFocusToPreviousMenuitem = function (menuId, currentMenuitem) { @@ -176,7 +181,7 @@ MenubarNavigation.prototype.setFocusToPreviousMenuitem = function (menuId, curre newMenuitem = this.menuitemGroups[menuId][ index - 1 ]; } - this.setFocusToMenuitem(menuId, newMenuitem, currentMenuitem); + this.setFocusToMenuitem(menuId, newMenuitem); return newMenuitem; }; @@ -191,7 +196,7 @@ MenubarNavigation.prototype.setFocusToNextMenuitem = function (menuId, currentMe index = this.menuitemGroups[menuId].indexOf(currentMenuitem); newMenuitem = this.menuitemGroups[menuId][ index + 1 ]; } - this.setFocusToMenuitem(menuId, newMenuitem, currentMenuitem); + this.setFocusToMenuitem(menuId, newMenuitem); return newMenuitem; }; @@ -217,7 +222,7 @@ MenubarNavigation.prototype.setFocusByFirstCharacter = function (menuId, current // If match was found... if (index > -1) { - this.setFocusToMenuitem(menuId, this.menuitemGroups[menuId][index], currentMenuitem); + this.setFocusToMenuitem(menuId, this.menuitemGroups[menuId][index]); } }; @@ -306,6 +311,15 @@ MenubarNavigation.prototype.getMenu = function(menuitem) { // Popup menu methods +MenubarNavigation.prototype.isAnyPopupOpen = function () { + for (var i = 0; i < this.popups.length; i++) { + if (this.popups[i].getAttribute('aria-expanded') === 'true') { + return true; + } + } + return false; +}; + MenubarNavigation.prototype.openPopup = function (menuId, menuitem) { // set aria-expanded attribute @@ -374,32 +388,34 @@ MenubarNavigation.prototype.closePopup = function (menuitem) { return cmi; }; -MenubarNavigation.prototype.closePopupAll = function () { - - var popups = this.domNode.querySelectorAll('[aria-haspopup]'); - - for (var i = 0; i < popups.length; i++) { - var popup = popups[i]; - if (this.isOpen(popup)) { - popup.setAttribute('aria-expanded', 'false'); - popup.nextElementSibling.style.display = 'none'; - } +MenubarNavigation.prototype.doesNotContain = function (popup, menuitem) { + if (menuitem) { + return !popup.nextElementSibling.contains(menuitem); } + return true; }; -MenubarNavigation.prototype.closePopupHover = function () { - - var menus = this.domNode.querySelectorAll('[role="menu'); +MenubarNavigation.prototype.closePopupAll = function (menuitem) { + if (typeof menuitem !== 'object') { + menuitem = false; + } - for (var i = 0; i < menus.length; i++) { - var menu = menus[i]; - var focus = menu.parentNode.querySelector('.item'); - var hover = menu.classList.contains('hover'); - if (!focus && !hover) { - menu.style.display = 'none'; - menu.previousElementSibling.setAttribute('aria-expanded', 'false'); + for (var i = 0; i < this.popups.length; i++) { + var popup = this.popups[i]; + console.log('[closePopupAll][doesNotContain]: ' + this.doesNotContain(popup, menuitem)); + console.log('[closePopupAll][isOpen]: ' + this.isOpen(popup)); + if (this.doesNotContain(popup, menuitem) && this.isOpen(popup)) { + console.log('[closePopupAll][A]'); + var cmi = popup.previousElementSibling; + if (cmi) { + cmi.setAttribute('aria-expanded', 'false'); + } + popup.style.display = 'none'; } } + if (menuitem) { + mennuitem.focus(); + } }; MenubarNavigation.prototype.hasPopup = function (menuitem) { @@ -430,17 +446,6 @@ MenubarNavigation.prototype.handleMenubarFocusout = function (event) { this.domNode.classList.remove('focus'); }; -MenubarNavigation.prototype.handleMenuitemFocus = function (event) { - var menu = this.getMenu(event.target); - menu.classList.add('item'); -}; - -MenubarNavigation.prototype.handleMenuitemBlur = function (event) { - var menu = this.getMenu(event.target); - menu.classList.remove('item'); -}; - - MenubarNavigation.prototype.handleBackgroundMousedown = function (event) { if (!this.domNode.contains(event.target)) { this.closePopupAll(); @@ -588,20 +593,15 @@ MenubarNavigation.prototype.handleKeydown = function (event) { }; MenubarNavigation.prototype.handleMenuitemClick = function (event) { - var tgt = event.currentTarget, - menuId = this.getMenuId(tgt), - role, - option, - value; + var tgt = event.currentTarget; + var menuId = this.getMenuId(tgt); if (this.hasPopup(tgt)) { if (this.isOpen(tgt)) { - this.openPopups = false; this.closePopup(tgt); } else { this.closePopupAll(); - this.openPopups = true; this.openPopup(menuId, tgt); } event.stopPropagation(); @@ -614,65 +614,19 @@ MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { menuId, menu; - if (this.hasPopup(tgt)) { + if (this.isAnyPopupOpen() && this.hasPopup(tgt)) { + this.closePopupAll(tgt); menuId = this.getMenuId(tgt); menu = this.getMenu(tgt); this.openPopup(menuId, tgt); - tgt.nextElementSibling.classList.add('hover'); - } -}; - -MenubarNavigation.prototype.handleMenuitemMouseout = function (event) { - var tgt = event.currentTarget, - menuId, - menu; - - if (this.hasPopup(tgt)) { - menuId = this.getMenuId(tgt); - menu = this.getMenu(tgt); - tgt.nextElementSibling.classList.remove('hover'); } - - var closePopupHover = this.closePopupHover.bind(this); - setTimeout(function(){ closePopupHover() }, 400); -}; - -MenubarNavigation.prototype.handleMenuMouseover = function (event) { - var menu = event.currentTarget, - menuId; - - while( menu) { - menu.classList.add('hover'); - if (menu.previousElementSibling) { - menu = this.getMenu(menu.previousElementSibling); - } - else { - menu = false; - } - } - - var closePopupHover = this.closePopupHover.bind(this); - setTimeout(function(){ closePopupHover() }, 500); -}; - -MenubarNavigation.prototype.handleMenuMouseout = function (event) { - var tgt = event.currentTarget; - var menu = this.getMenu(tgt); - menu.classList.remove('hover'); - - var menus = menu.querySelectorAll('[role="menu"]'); - - for (var i = 0; i < menus.length; i++) { - menus[i].classList.remove('hover'); - } - - var closePopupHover = this.closePopupHover.bind(this); - setTimeout(function(){ closePopupHover() }, 400); - }; // Initialize menubar editor window.addEventListener('load', function () { - var navbar = new MenubarNavigation(document.getElementById('menubar1')); + var menubarNavs = document.querySelectorAll('.menubar-navigation'); + for(var i=0; i < menubarNavs.length; i++) { + var menubarNav = new MenubarNavigation(menubarNavs[i]); + } }); diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 4f7c4f775c..7a9f8c15d7 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -51,7 +51,7 @@

      Example

      - - -
      diff --git a/test/tests/menubar_menubar-1.js b/test/tests/menubar_menubar-navigation.js similarity index 99% rename from test/tests/menubar_menubar-1.js rename to test/tests/menubar_menubar-navigation.js index 42b4f1c33f..10f0d7baa1 100644 --- a/test/tests/menubar_menubar-1.js +++ b/test/tests/menubar_menubar-navigation.js @@ -7,7 +7,7 @@ const assertAriaLabelExists = require('../util/assertAriaLabelExists'); const assertAriaRoles = require('../util/assertAriaRoles'); const assertRovingTabindex = require('../util/assertRovingTabindex'); -const exampleFile = 'menubar/menubar-1/menubar-1.html'; +const exampleFile = 'menubar/menubar-navigation.html'; const ex = { // menubar selector From 2415e704e672b308cc9e3b84beb94aeb4f155802 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 19 Mar 2020 19:18:22 -0500 Subject: [PATCH 24/62] updated script --- examples/menubar/js/menubar-navigation.js | 145 +--------------------- 1 file changed, 5 insertions(+), 140 deletions(-) diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index 5b12640290..dbda99ff21 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -15,6 +15,7 @@ var MenubarNavigation = function (domNode, actionManager) { this.menuitemGroups = {}; this.menuOrientation = {}; this.isPopup = {}; + this.isPopout = {}; this.openPopups = false; this.firstChars = {}; // see Menubar init method @@ -32,11 +33,10 @@ MenubarNavigation.prototype.getMenuitems = function(domNode) { var nodes = []; var initMenu = this.initMenu.bind(this); - var getGroupId = this.getGroupId.bind(this); var menuitemGroups = this.menuitemGroups; - function findMenuitems(node, group) { - var role, flag, groupId; + function findMenuitems(node) { + var role, flag; while (node) { flag = true; @@ -53,18 +53,8 @@ MenubarNavigation.prototype.getMenuitems = function(domNode) { flag = false; break; - case 'group': - groupId = getGroupId(node); - menuitemGroups[groupId] = []; - break; - case 'menuitem': - case 'menuitemradio': - case 'menuitemcheckbox': nodes.push(node); - if (group) { - group.push(node); - } break; default: @@ -72,14 +62,14 @@ MenubarNavigation.prototype.getMenuitems = function(domNode) { } if (flag && node.firstElementChild) { - findMenuitems(node.firstElementChild, menuitemGroups[groupId]); + findMenuitems(node.firstElementChild); } node = node.nextElementSibling; } } - findMenuitems(domNode.firstElementChild, false); + findMenuitems(domNode.firstElementChild); return nodes; }; @@ -276,53 +266,6 @@ MenubarNavigation.prototype.getMenuOrientation = function(node) { return orientation; }; -MenubarNavigation.prototype.getDataOption = function(node) { - - var option = false; - var hasOption = node.hasAttribute('data-option'); - var role = node.hasAttribute('role'); - - if (!hasOption) { - - while (node && !hasOption && - (role !== 'menu') && - (role !== 'menubar')) { - node = node.parentNode; - if (node) { - role = node.getAttribute('role'); - hasOption = node.hasAttribute('data-option'); - } - } - } - - if (node) { - option = node.getAttribute('data-option'); - } - - return option; -}; - -MenubarNavigation.prototype.getGroupId = function(node) { - - var id = false; - var role = node.getAttribute('role'); - - while (node && (role !== 'group') && - (role !== 'menu') && - (role !== 'menubar')) { - node = node.parentNode; - if (node) { - role = node.getAttribute('role'); - } - } - - if (node) { - id = role + '-' + this.getIdFromAriaLabel(node); - } - - return id; -}; - MenubarNavigation.prototype.getMenuId = function(node) { var id = false; @@ -358,69 +301,6 @@ MenubarNavigation.prototype.getMenu = function(menuitem) { return menu; }; -MenubarNavigation.prototype.toggleCheckbox = function(menuitem) { - if (menuitem.getAttribute('aria-checked') === 'true') { - menuitem.setAttribute('aria-checked', 'false'); - return false; - } - menuitem.setAttribute('aria-checked', 'true'); - return true; -}; - -MenubarNavigation.prototype.setRadioButton = function(menuitem) { - var groupId = this.getGroupId(menuitem); - var radiogroupItems = this.menuitemGroups[groupId]; - radiogroupItems.forEach( function (item) { - item.setAttribute('aria-checked', 'false') - }); - menuitem.setAttribute('aria-checked', 'true'); - return menuitem.textContent; -}; - -MenubarNavigation.prototype.updateFontSizeMenu = function(menuId) { - - var fontSizeMenuitems = this.menuitemGroups[menuId]; - var currentValue = this.actionManager.getFontSize(); - - for (var i = 0; i < fontSizeMenuitems.length; i++) { - var mi = fontSizeMenuitems[i]; - var dataOption = mi.getAttribute('data-option'); - var value = mi.textContent.trim().toLowerCase(); - - switch (dataOption) { - case 'font-smaller': - if (currentValue === 'x-small') { - mi.setAttribute('aria-disabled', 'true'); - } - else { - mi.removeAttribute('aria-disabled'); - } - break; - - case 'font-larger': - if (currentValue === 'x-large') { - mi.setAttribute('aria-disabled', 'true'); - } - else { - mi.removeAttribute('aria-disabled'); - } - break; - - default: - if (currentValue === value) { - mi.setAttribute('aria-checked', 'true'); - } - else { - mi.setAttribute('aria-checked', 'false'); - } - break; - - } - } - - -} - // Popup menu methods MenubarNavigation.prototype.openPopup = function (menuitem) { @@ -536,29 +416,14 @@ MenubarNavigation.prototype.handleKeydown = function (event) { } else { role = tgt.getAttribute('role'); - option = this.getDataOption(tgt); switch(role) { case 'menuitem': - this.actionManager.setOption(option, tgt.textContent); - break; - - case 'menuitemcheckbox': - value = this.toggleCheckbox(tgt); - this.actionManager.setOption(option, value); - break; - - case 'menuitemradio': - value = this.setRadioButton(tgt); - this.actionManager.setOption(option, value); break; default: break; } - if (this.getMenuId(tgt) === 'menu-size') { - this.updateFontSizeMenu('menu-size'); - } this.openPopups = false; this.closePopup(tgt); } From cd7b4132ec79db10f3519e2f5dc4e7ffab54f074 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 24 Mar 2020 13:46:14 -0500 Subject: [PATCH 25/62] update menubar navigation --- examples/menubar/css/menubar-navigation.css | 126 +++++--- examples/menubar/js/menubar-navigation.js | 327 ++++++++++++-------- examples/menubar/menubar-navigation.html | 2 +- 3 files changed, 296 insertions(+), 159 deletions(-) diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index 7e7869e8ea..c2115e8157 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -1,42 +1,19 @@ ul[role="menubar"] { - margin: 10px; - padding: 10px; + margin: 0; + margin-top: 0.5em; + margin-bottom: 0.5em; + padding: 8px; font-size: 110%; list-style: none; background-color: #eee; -} - -ul[role="menubar"] [role="menuitem"], -ul[role="menubar"] [role="separator"] { - padding: 0.25em; - background-color: #eee; - border: 2px solid #eee; -} - -ul[role="menubar"] [role="separator"] { - padding-top: 0.15em; - background-image: url('../images/separator.png'); - background-position: center; - background-repeat: repeat-x; -} - -ul[role="menubar"] [role="menuitem"]:focus, -ul[role="menubar"] [role="menuitem"]:hover, -ul[role="menubar"] [role="separator"]:focus, -ul[role="menubar"] [role="separator"]:hover { - background-color: black; - color: white; -} - -ul[role="menubar"] a[role="menuitem"] { - text-decoration: none; - color: black; + border: #eee solid 0px; + border-radius: 5px; } ul[role="menubar"] li { - list-style: none; margin: 0; padding: 0; + list-style: none; } ul[role="menubar"] > li { @@ -44,26 +21,101 @@ ul[role="menubar"] > li { position: relative; } -ul[role="menubar"] > li > a::after { +ul[role="menubar"] > li > [role="menuitem"]::after { content: url('../images/down-arrow-brown.png'); padding-left: 0.25em; } -ul[role="menubar"] ul[role="menu"] { +ul[role="menubar"] [role="menu"] { display: none; position: absolute; - top: -2px; - left: 0; margin: 0; padding: 0; } -ul[role="menubar"] ul[role="menu"] li a { +ul[role="menubar"] [role="group"] { + margin: 0; + padding: 0; +} + +ul[role="menubar"] [role="menuitem"], +ul[role="menubar"] [role="separator"] { + margin: 3px; + padding: 6px; + background-color: #eee; + border: 0px solid #eee; + color: black; + border-radius: 5px; +} + +ul[role="menubar"] [role="menu"] [role="menuitem"], +ul[role="menubar"] [role="menu"] [role="separator"] { display: block; - width: 10em; + width: 12em; } -ul[role="menubar"] ul[role="menu"] a[aria-haspopup="true"]::after { +ul[role="menubar"] [role="menu"] { + display: none; +} + + +ul[role="menubar"] [role="separator"] { + padding-top: 3px; + background-image: url('../images/separator.png'); + background-position: center; + background-repeat: repeat-x; +} + +ul[role="menubar"] [role="menuitem"][aria-expanded="true"] { + padding: 4px; + border: 2px solid #034575; +} + +ul[role="menubar"] [role="menu"] [role="menuitem"][aria-haspopup="true"]::after { content: url('../images/right-arrow-brown.png'); padding-right: 2em; } + +/* focus styling */ + +ul[role="menubar"].focus { + padding: 6px; + border: #034575 solid 2px; +} + +ul[role="menubar"].focus.item { + padding: 5px; + border: #034575 solid 3px; +} + +ul[role="menubar"].focus [role="menu"] { + padding: 8px 5px; + border: 1px solid #034575; + border-radius: 5px; + background-color: #eee; +} + +ul[role="menubar"] [role="menu"].hover { + padding: 9px 6px; + border: 0px solid #034575; + border-radius: 5px; + background-color: #eee; +} + +ul[role="menubar"].focus [role="menu"].item { + padding: 6px 3px; + border: 3px solid #034575; + border-radius: 5px; + background-color: #eee; +} + +ul[role="menubar"] [role="menuitem"]:focus { + padding: 2px; + border: 4px dotted #034575; + outline: none; +} + +ul[role="menubar"] [role="menuitem"]:hover { + padding: 4px; + border: 2px solid #034575; +} diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index dbda99ff21..70f00463e8 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -22,18 +22,22 @@ var MenubarNavigation = function (domNode, actionManager) { this.firstMenuitem = {}; // see Menubar init method this.lastMenuitem = {}; // see Menubar init method - this.initMenu(domNode) + this.initMenu(domNode, 0); + domNode.addEventListener('focusin', this.handleMenubarFocusin.bind(this)); domNode.addEventListener('focusout', this.handleMenubarFocusout.bind(this)); window.addEventListener('mousedown', this.handleBackgroundMousedown.bind(this), true); }; -MenubarNavigation.prototype.getMenuitems = function(domNode) { +MenubarNavigation.prototype.getMenuitems = function(domNode, depth) { var nodes = []; var initMenu = this.initMenu.bind(this); var menuitemGroups = this.menuitemGroups; + var handleMenuMouseover = this.handleMenuMouseover.bind(this); + var handleMenuMouseout = this.handleMenuMouseout.bind(this); + function findMenuitems(node) { var role, flag; @@ -49,7 +53,9 @@ MenubarNavigation.prototype.getMenuitems = function(domNode) { switch (role) { case 'menu': node.tabIndex = -1; - initMenu(node); + node.addEventListener('mouseover', handleMenuMouseover); + node.addEventListener('mouseout', handleMenuMouseout); + initMenu(node, depth + 1); flag = false; break; @@ -74,14 +80,16 @@ MenubarNavigation.prototype.getMenuitems = function(domNode) { return nodes; }; -MenubarNavigation.prototype.initMenu = function (menu) { +MenubarNavigation.prototype.initMenu = function (menu, depth) { var i, menuitems, menuitem, role, nextElement; var menuId = this.getMenuId(menu); - menuitems = this.getMenuitems(menu); + menuitems = this.getMenuitems(menu, depth); this.menuOrientation[menuId] = this.getMenuOrientation(menu); - this.isPopup[menuId] = menu.getAttribute('role') === 'menu'; + + this.isPopup[menuId] = (menu.getAttribute('role') === 'menu') && (depth === 1); + this.isPopout[menuId] = (menu.getAttribute('role') === 'menu') && (depth > 1); this.menuitemGroups[menuId] = []; this.firstChars[menuId] = []; @@ -102,7 +110,12 @@ MenubarNavigation.prototype.initMenu = function (menu) { menuitem.addEventListener('keydown', this.handleKeydown.bind(this)); menuitem.addEventListener('click', this.handleMenuitemClick.bind(this)); + menuitem.addEventListener('focus', this.handleMenuitemFocus.bind(this)); + menuitem.addEventListener('blur', this.handleMenuitemBlur.bind(this)); + menuitem.addEventListener('mouseover', this.handleMenuitemMouseover.bind(this)); + menuitem.addEventListener('mouseout', this.handleMenuitemMouseout.bind(this)); + if( !this.firstMenuitem[menuId]) { if (this.hasPopup(menuitem)) { @@ -129,25 +142,15 @@ MenubarNavigation.prototype.setFocusToMenuitem = function (menuId, newMenuitem, this.closePopup(currentMenuitem); } - if (this.hasPopup(newMenuitem)) { - if (this.openPopups) { - this.openPopup(newMenuitem); - } - } - else { - var menu = this.getMenu(newMenuitem); - var cmi = menu.previousElementSibling; - if (!this.isOpen(cmi)) { - this.openPopup(cmi); - } - } - - if (this.hasPopup(newMenuitem)) { + if (this.isMenubar(menuId)) { this.menuitemGroups[menuId].forEach(function(item) { item.tabIndex = -1; }); - newMenuitem.tabIndex = 0; + + if (this.hasPopup(newMenuitem) && this.openPopups) { + this.openPopup(menuId, newMenuitem); + } } newMenuitem.focus(); @@ -303,19 +306,29 @@ MenubarNavigation.prototype.getMenu = function(menuitem) { // Popup menu methods -MenubarNavigation.prototype.openPopup = function (menuitem) { +MenubarNavigation.prototype.openPopup = function (menuId, menuitem) { // set aria-expanded attribute var popupMenu = menuitem.nextElementSibling; var rect = menuitem.getBoundingClientRect(); - // set CSS properties - popupMenu.style.position = 'absolute'; - popupMenu.style.top = (rect.height + 1) + 'px'; - popupMenu.style.left = '0px'; - popupMenu.style.zIndex = 100; - popupMenu.style.display = 'block'; + // Set CSS properties + if (this.isPopup[menuId]) { + popupMenu.parentNode.style.position = 'relative'; + popupMenu.style.display = 'block'; + popupMenu.style.position = 'absolute'; + popupMenu.style.left = (rect.width + 16) + 'px'; + popupMenu.style.top = '0px'; + popupMenu.style.zIndex = 100; + } + else { + popupMenu.style.display = 'block'; + popupMenu.style.position = 'absolute'; + popupMenu.style.left = '0px'; + popupMenu.style.top = (rect.height + 1) + 'px'; + popupMenu.style.zIndex = 100; + } menuitem.setAttribute('aria-expanded', 'true'); @@ -323,15 +336,31 @@ MenubarNavigation.prototype.openPopup = function (menuitem) { }; +MenubarNavigation.prototype.closePopout = function (menuitem) { + var menu, + menuId = this.getMenuId(menuitem), + cmi = menuitem; + + while (this.isPopup[menuId] || this.isPopout[menuId]) { + menu = this.getMenu(cmi); + cmi = menu.previousElementSibling; + menuId = this.getMenuId(cmi); + cmi.setAttribute('aria-expanded', 'false'); + menu.style.display = 'none'; + } + cmi.focus(); + return cmi; +}; + MenubarNavigation.prototype.closePopup = function (menuitem) { - var menu, cmi; + var menu, + menuId = this.getMenuId(menuitem), + cmi = menuitem; - if (this.hasPopup(menuitem)) { + if (this.isMenubar(menuId)) { if (this.isOpen(menuitem)) { menuitem.setAttribute('aria-expanded', 'false'); menuitem.nextElementSibling.style.display = 'none'; - menuitem.nextElementSibling.style.zIndex = 0; - } } else { @@ -340,8 +369,8 @@ MenubarNavigation.prototype.closePopup = function (menuitem) { cmi.setAttribute('aria-expanded', 'false'); cmi.focus(); menu.style.display = 'none'; - menu.style.zIndex = 0; } + return cmi; }; @@ -352,12 +381,25 @@ MenubarNavigation.prototype.closePopupAll = function () { for (var i = 0; i < popups.length; i++) { var popup = popups[i]; if (this.isOpen(popup)) { - this.closePopup(popup); - event.stopPropagation(); - event.preventDefault(); + popup.setAttribute('aria-expanded', 'false'); + popup.nextElementSibling.style.display = 'none'; } } +}; + +MenubarNavigation.prototype.closePopupHover = function () { + var menus = this.domNode.querySelectorAll('[role="menu'); + + for (var i = 0; i < menus.length; i++) { + var menu = menus[i]; + var focus = menu.parentNode.querySelector('.item'); + var hover = menu.classList.contains('hover'); + if (!focus && !hover) { + menu.style.display = 'none'; + menu.previousElementSibling.setAttribute('aria-expanded', 'false'); + } + } }; MenubarNavigation.prototype.hasPopup = function (menuitem) { @@ -368,6 +410,14 @@ MenubarNavigation.prototype.isOpen = function (menuitem) { return menuitem.getAttribute('aria-expanded') === 'true'; }; +MenubarNavigation.prototype.isMenubar = function (menuiId) { + return !this.isPopup[menuId] && !this.isPopout[menuId]; +}; + +MenubarNavigation.prototype.isMenuHorizontal = function (menuitem) { + return this.menuOrientation[menuitem] === 'horizontal'; +}; + // Menu event handlers MenubarNavigation.prototype.handleMenubarFocusin = function (event) { @@ -380,9 +430,22 @@ MenubarNavigation.prototype.handleMenubarFocusout = function (event) { this.domNode.classList.remove('focus'); }; +MenubarNavigation.prototype.handleMenuitemFocus = function (event) { + var menu = this.getMenu(event.target); + menu.classList.add('item'); +}; + +MenubarNavigation.prototype.handleMenuitemBlur = function (event) { + var menu = this.getMenu(event.target); + menu.classList.remove('item'); +}; + + MenubarNavigation.prototype.handleBackgroundMousedown = function (event) { if (!this.domNode.contains(event.target)) { this.closePopupAll(); + event.stopPropagation(); + event.preventDefault(); } }; @@ -398,9 +461,6 @@ MenubarNavigation.prototype.handleKeydown = function (event) { option, value; - console.log('[handleMenubarKeydown][key]: ' + key); - console.log('[handleMenubarKeydown][menuId]: ' + menuId); - // This fixes a problem with regression tests using Key.SPACE if (event.keyCode === 32) { key = ' '; @@ -411,90 +471,94 @@ MenubarNavigation.prototype.handleKeydown = function (event) { case 'Enter': if (this.hasPopup(tgt)) { this.openPopups = true; - popupMenuId = this.openPopup(tgt); + popupMenuId = this.openPopup(menuId, tgt); this.setFocusToFirstMenuitem(popupMenuId); } else { - role = tgt.getAttribute('role'); - switch(role) { - case 'menuitem': - break; - - default: - break; + if (tgt.href !== '#') { + this.closePopupAll(); + window.location.href=tgt.href; } + } + flag = true; + break; + case 'Esc': + case 'Escape': this.openPopups = false; this.closePopup(tgt); + flag = true; + break; + + case 'Up': + case 'ArrowUp': + if (this.isMenuHorizontal(menuId)) { + if (this.hasPopup(tgt)) { + this.openPopups = true; + popupMenuId = this.openPopup(menuId, tgt); + this.setFocusToLastMenuitem(popupMenuId); + } + } + else { + this.setFocusToPreviousMenuitem(menuId, tgt); } flag = true; - break; + break; case 'ArrowDown': case 'Down': - if (this.menuOrientation[menuId] === 'vertical') { - this.setFocusToNextMenuitem(menuId, tgt); - flag = true; - } - else { + if (this.isMenuHorizontal(menuId)) { if (this.hasPopup(tgt)) { this.openPopups = true; - popupMenuId = this.openPopup(tgt); + popupMenuId = this.openPopup(menuId, tgt); this.setFocusToFirstMenuitem(popupMenuId); - flag = true; } } - break; - - case 'Esc': - case 'Escape': - this.openPopups = false; - this.closePopup(tgt); - flag = true; + else { + this.setFocusToNextMenuitem(menuId, tgt); + } + flag = true; break; case 'Left': case 'ArrowLeft': - if (this.menuOrientation[menuId] === 'horizontal') { + if (this.isMenuHorizontal(menuId)) { this.setFocusToPreviousMenuitem(menuId, tgt); - flag = true; } else { - mi = this.closePopup(tgt); - id = this.getMenuId(mi); - mi = this.setFocusToPreviousMenuitem(id, mi); - this.openPopup(mi); + if (this.isPopout[menuId]) { + mi = this.closePopup(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToMenuitem(id, mi); + } + else { + mi = this.closePopup(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToPreviousMenuitem(id, mi); + this.openPopup(id, mi); + } } + flag = true; break; case 'Right': case 'ArrowRight': - if (this.menuOrientation[menuId] === 'horizontal') { + if (this.isMenuHorizontal(menuId)) { this.setFocusToNextMenuitem(menuId, tgt); - flag = true; - } - else { - mi = this.closePopup(tgt); - id = this.getMenuId(mi); - mi = this.setFocusToNextMenuitem(id, mi); - this.openPopup(mi); - } - break; - - case 'Up': - case 'ArrowUp': - if (this.menuOrientation[menuId] === 'vertical') { - this.setFocusToPreviousMenuitem(menuId, tgt); - flag = true; } else { if (this.hasPopup(tgt)) { - this.openPopups = true; - popupMenuId = this.openPopup(tgt); - this.setFocusToLastMenuitem(popupMenuId); - flag = true; + popupMenuId = this.openPopup(menuId, tgt); + this.setFocusToFirstMenuitem(popupMenuId); + } + else { + mi = this.closePopout(tgt); + id = this.getMenuId(mi); + mi = this.setFocusToNextMenuitem(id, mi); + this.openPopup(id, mi); } } + flag = true; break; case 'Home': @@ -530,6 +594,7 @@ MenubarNavigation.prototype.handleKeydown = function (event) { MenubarNavigation.prototype.handleMenuitemClick = function (event) { var tgt = event.currentTarget, + menuId = this.getMenuId(tgt), role, option, value; @@ -542,53 +607,73 @@ MenubarNavigation.prototype.handleMenuitemClick = function (event) { else { this.closePopupAll(); this.openPopups = true; - this.openPopup(tgt); + this.openPopup(menuId, tgt); } + event.stopPropagation(); + event.preventDefault(); } - else { - role = tgt.getAttribute('role'); - option = this.getDataOption(tgt); - switch(role) { - case 'menuitem': - this.actionManager.setOption(option, tgt.textContent); - break; +}; - case 'menuitemcheckbox': - value = this.toggleCheckbox(tgt); - this.actionManager.setOption(option, value); - break; +MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { + var tgt = event.currentTarget, + menuId, + menu; - case 'menuitemradio': - value = this.setRadioButton(tgt); - this.actionManager.setOption(option, value); - break; + if (this.hasPopup(tgt)) { + menuId = this.getMenuId(tgt); + menu = this.getMenu(tgt); + this.openPopup(menuId, tgt); + tgt.nextElementSibling.classList.add('hover'); + } +}; - default: - break; - } +MenubarNavigation.prototype.handleMenuitemMouseout = function (event) { + var tgt = event.currentTarget, + menuId, + menu; - if (this.getMenuId(tgt) === 'menu-size') { - this.updateFontSizeMenu('menu-size'); - } - this.openPopups = false; - this.closePopup(tgt); + if (this.hasPopup(tgt)) { + menuId = this.getMenuId(tgt); + menu = this.getMenu(tgt); + tgt.nextElementSibling.classList.remove('hover'); } - event.stopPropagation(); - event.preventDefault(); + var closePopupHover = this.closePopupHover.bind(this); + setTimeout(function(){ closePopupHover() }, 400); +}; + +MenubarNavigation.prototype.handleMenuMouseover = function (event) { + var menu = event.currentTarget, + menuId; + + while( menu) { + menu.classList.add('hover'); + if (menu.previousElementSibling) { + menu = this.getMenu(menu.previousElementSibling); + } + else { + menu = false; + } + } + var closePopupHover = this.closePopupHover.bind(this); + setTimeout(function(){ closePopupHover() }, 500); }; -MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { +MenubarNavigation.prototype.handleMenuMouseout = function (event) { var tgt = event.currentTarget; + var menu = this.getMenu(tgt); + menu.classList.remove('hover'); - if (this.hasPopup(tgt) && this.openPopups) { - if (!this.isOpen(tgt)) { - this.closePopupAll(); - this.openPopups = true; - this.openPopup(tgt); - } + var menus = menu.querySelectorAll('[role="menu"]'); + + for (var i = 0; i < menus.length; i++) { + menus[i].classList.remove('hover'); } + + var closePopupHover = this.closePopupHover.bind(this); + setTimeout(function(){ closePopupHover() }, 400); + }; // Initialize menubar editor diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 3d1ec3c940..7c8b71cfc3 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -92,7 +92,7 @@

      Example

      Apply
    • - +
      • Undergraduate From b1463649217f328fac3a61302070edf6c9f100fe Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 24 Mar 2020 15:25:06 -0500 Subject: [PATCH 26/62] udpated menubar navigation --- examples/menubar/js/menubar-navigation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index 70f00463e8..ec2a2cd1ae 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -410,7 +410,7 @@ MenubarNavigation.prototype.isOpen = function (menuitem) { return menuitem.getAttribute('aria-expanded') === 'true'; }; -MenubarNavigation.prototype.isMenubar = function (menuiId) { +MenubarNavigation.prototype.isMenubar = function (menuId) { return !this.isPopup[menuId] && !this.isPopout[menuId]; }; @@ -673,7 +673,7 @@ MenubarNavigation.prototype.handleMenuMouseout = function (event) { var closePopupHover = this.closePopupHover.bind(this); setTimeout(function(){ closePopupHover() }, 400); - + }; // Initialize menubar editor From 0b1e03aff088b97bec851b2c6c4dc69696b03b0a Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 24 Mar 2020 15:35:59 -0500 Subject: [PATCH 27/62] updated documentation on keyboard support --- examples/menubar/menubar-navigation.html | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 7c8b71cfc3..b27df4219e 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -159,6 +159,8 @@

        Example

        Accessibility Features

          +
        1. To support high contrast settings the keyboard focus is highlighted by adding and removing a dashed border around the menu item with keyboard focus.
        2. +
        3. When any menu item receives focus a border is added to the menubar or menu container to provide a visual afforance to use the cursor keys to navigate the menu items.
        4. Since the menubar presents a site navigation system, it is wrapped in a navigation region implemented with a nav element that has an aria-label that matches the label on the menubar.
        5. The down arrow and right arrow icons are made compatible with high contrast mode and hidden from screen readers by using the CSS content property to render images.
        @@ -240,7 +242,8 @@

        Menubar

      • -
      • Activates menu item, causing the link to be activated.
      • -
      • NOTE: the links go to dummy pages; use the browser go-back function to return to this menubar example page.
      • +
      • If the menuitem is a link: +
          +
        • Activates menu item, causing the link to be activated.
        • +
        • NOTE: the links go to dummy pages; use the browser go-back function to return to this menubar example page.
        • +
        +
      • +
      • If the menuitem has a popup menu: +
          +
        • Opens submenu and moves focus to first item in the submenu.
        • +
        +
        -
      • If the menuitem is a link: -
          -
        • Activates menu item, causing the link to be activated.
        • -
        • NOTE: the links go to dummy pages; use the browser go-back function to return to this menubar example page.
        • -
        -
      • -
      • If the menuitem has a popup menu: -
          -
        • Opens submenu and moves focus to first item in the submenu.
        • -
        -
      • +
      • If the item is a parent menu item, opens submenu and moves focus to first item in the submenu.
      • +
      • Otherwise, activates menu item, which navigates to a dummy page. NOTE: use browser go-back function to return to this page.
      -

      Submenu

      + +

      Submenu

      @@ -255,8 +258,17 @@

      Submenu

      From ae62001ebf804148dfc6316b03e67c3b5435a2be Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 31 Mar 2020 15:42:22 -0500 Subject: [PATCH 28/62] updated tests to identify failing tests due to Issue 1358 --- examples/menubar/js/menubar-navigation.js | 5 ----- test/tests/menubar_menubar-navigation.js | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index ec2a2cd1ae..bf4d51a65f 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -461,11 +461,6 @@ MenubarNavigation.prototype.handleKeydown = function (event) { option, value; - // This fixes a problem with regression tests using Key.SPACE - if (event.keyCode === 32) { - key = ' '; - } - switch (key) { case ' ': case 'Enter': diff --git a/test/tests/menubar_menubar-navigation.js b/test/tests/menubar_menubar-navigation.js index 10f0d7baa1..5430ecfa5c 100644 --- a/test/tests/menubar_menubar-navigation.js +++ b/test/tests/menubar_menubar-navigation.js @@ -359,7 +359,7 @@ ariaTest('Key ENTER open submenu', exampleFile, 'menubar-space-or-enter', async }); -ariaTest('Key SPACE open submenu', exampleFile, 'menubar-space-or-enter', async (t) => { +ariaTest.failing('Key SPACE open submenu', exampleFile, 'menubar-space-or-enter', async (t) => { const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); const menus = await t.context.queryElements(t, ex.menuSelector); From e2dc4792b43e24de10a27eb4e017d46f0061e8e0 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 1 Apr 2020 16:32:15 -0500 Subject: [PATCH 29/62] updated test with SPACE key --- test/tests/menubar_menubar-navigation.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/tests/menubar_menubar-navigation.js b/test/tests/menubar_menubar-navigation.js index 5430ecfa5c..c0ae365895 100644 --- a/test/tests/menubar_menubar-navigation.js +++ b/test/tests/menubar_menubar-navigation.js @@ -366,7 +366,7 @@ ariaTest.failing('Key SPACE open submenu', exampleFile, 'menubar-space-or-enter' for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { // Send the SPACE key - await menubaritems[menuIndex].sendKeys(Key.SPACE); + await menubaritems[menuIndex].sendKeys(' '); // Test that the submenu is displayed t.true( @@ -629,7 +629,7 @@ ariaTest.failing('SPACE in submenu selects item', exampleFile, 'submenu-space-or const itemText = await items[itemIndex].getText(); // send SPACE to the item - await items[itemIndex].sendKeys(Key.SPACE); + await items[itemIndex].sendKeys(' '); await waitForUrlChange(t); t.not( @@ -657,7 +657,7 @@ ariaTest.failing('SPACE in submenu selects item', exampleFile, 'submenu-space-or // send SPACE to the item we are testing const items = await t.context.queryElements(t, submenuMenuitemSelector); const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.SPACE); + await items[itemIndex].sendKeys(' '); await waitForUrlChange(t); t.not( From 7a14422629f30411940514398db961d4ce1ebe2d Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Fri, 3 Apr 2020 14:36:26 -0500 Subject: [PATCH 30/62] updated focus styling --- examples/menubar/css/menubar-navigation.css | 14 +++++++------- examples/menubar/js/menubar-navigation.js | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index c2115e8157..16352978da 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -68,7 +68,7 @@ ul[role="menubar"] [role="separator"] { ul[role="menubar"] [role="menuitem"][aria-expanded="true"] { padding: 4px; - border: 2px solid #034575; + border: 2px dashed #034575; } ul[role="menubar"] [role="menu"] [role="menuitem"][aria-haspopup="true"]::after { @@ -88,16 +88,16 @@ ul[role="menubar"].focus.item { border: #034575 solid 3px; } -ul[role="menubar"].focus [role="menu"] { - padding: 8px 5px; - border: 1px solid #034575; +ul[role="menubar"] [role="menu"].hover { + padding: 9px 6px; + border: 0px solid #034575; border-radius: 5px; background-color: #eee; } -ul[role="menubar"] [role="menu"].hover { - padding: 9px 6px; - border: 0px solid #034575; +ul[role="menubar"].focus [role="menu"] { + padding: 7px 4px; + border: 2px solid #034575; border-radius: 5px; background-color: #eee; } diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index bf4d51a65f..78b4c1383a 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -318,7 +318,7 @@ MenubarNavigation.prototype.openPopup = function (menuId, menuitem) { popupMenu.parentNode.style.position = 'relative'; popupMenu.style.display = 'block'; popupMenu.style.position = 'absolute'; - popupMenu.style.left = (rect.width + 16) + 'px'; + popupMenu.style.left = (rect.width + 12) + 'px'; popupMenu.style.top = '0px'; popupMenu.style.zIndex = 100; } @@ -326,7 +326,7 @@ MenubarNavigation.prototype.openPopup = function (menuId, menuitem) { popupMenu.style.display = 'block'; popupMenu.style.position = 'absolute'; popupMenu.style.left = '0px'; - popupMenu.style.top = (rect.height + 1) + 'px'; + popupMenu.style.top = rect.height + 'px'; popupMenu.style.zIndex = 100; } From 975c59b8c69b5ca30315ff2a1c36966112a90366 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 21 Apr 2020 12:30:33 -0500 Subject: [PATCH 31/62] updated the example to use SVG images to improve color contrast in high contrast mode --- examples/menubar/css/menubar-navigation.css | 23 +++++++++++++++--- examples/menubar/images/down-arrow-brown.png | Bin 1362 -> 0 bytes examples/menubar/images/down-arrow-focus.svg | 4 +++ examples/menubar/images/down-arrow-gray.png | Bin 1325 -> 0 bytes examples/menubar/images/down-arrow.svg | 4 +++ examples/menubar/images/right-arrow-brown.png | Bin 1401 -> 0 bytes examples/menubar/images/right-arrow-gray.png | Bin 1357 -> 0 bytes examples/menubar/images/right-arrow.svg | 4 +++ examples/menubar/images/separator.paint | Bin 4059 -> 0 bytes examples/menubar/images/separator.png | Bin 1134 -> 0 bytes examples/menubar/images/up-arrow.svg | 4 +++ 11 files changed, 36 insertions(+), 3 deletions(-) delete mode 100644 examples/menubar/images/down-arrow-brown.png create mode 100644 examples/menubar/images/down-arrow-focus.svg delete mode 100644 examples/menubar/images/down-arrow-gray.png create mode 100644 examples/menubar/images/down-arrow.svg delete mode 100644 examples/menubar/images/right-arrow-brown.png delete mode 100644 examples/menubar/images/right-arrow-gray.png create mode 100644 examples/menubar/images/right-arrow.svg delete mode 100644 examples/menubar/images/separator.paint delete mode 100644 examples/menubar/images/separator.png create mode 100644 examples/menubar/images/up-arrow.svg diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index 16352978da..ece611c006 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -22,8 +22,18 @@ ul[role="menubar"] > li { } ul[role="menubar"] > li > [role="menuitem"]::after { - content: url('../images/down-arrow-brown.png'); + content: url('../images/down-arrow.svg'); padding-left: 0.25em; + position: relative; + top: 2px; +} + +ul[role="menubar"].focus > li > [role="menuitem"]::after { + content: url('../images/down-arrow-focus.svg'); +} + +ul[role="menubar"] > li > [role="menuitem"][aria-expanded="true"]::after { + content: url('../images/up-arrow.svg'); } ul[role="menubar"] [role="menu"] { @@ -72,8 +82,15 @@ ul[role="menubar"] [role="menuitem"][aria-expanded="true"] { } ul[role="menubar"] [role="menu"] [role="menuitem"][aria-haspopup="true"]::after { - content: url('../images/right-arrow-brown.png'); - padding-right: 2em; + content: url('../images/right-arrow.svg'); + position: relative; + left: 8px; +} + +ul[role="menubar"] [role="menu"] [role="menuitem"][aria-expanded="true"]::after { + content: url('../images/down-arrow-focus.svg'); + top: 2px; + left: 4px; } /* focus styling */ diff --git a/examples/menubar/images/down-arrow-brown.png b/examples/menubar/images/down-arrow-brown.png deleted file mode 100644 index fe3e38aa3137fad169770d5cb788db37a5d15390..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1362 zcmV-Y1+DstP)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%R z07*naR45f=U>J0OG0m8P3+SrfM7oTXg^`(UCBxreTK^ds{@~NZ$iTqDz|3}yg&KnbyBWT}SO14Y2NOt~Q}8B-ifI;5BLhRezn=N8SGRZm`}Lh4XcGzmDPUsb{KzJz z>OOJfrITRI6E|Kw#jD|1$jlD(4zdm)n}vg$0VD@f12PG$fq{YXf7ku3i`hk$rm?cI zLTmskW@Tez;8wJl*7arUB9Iy+4M64r#y=_r`^q`xjE^!iGBU8RurhEdTOE~8o>b1T zn(;5h6u1o#ZnmS?7a;?WBxYXGSIoR3uOuuJld>JPzrYmZq)QWAy-H(Uy|5_&0JyeA U9gsdwRR91007*qoM6N<$g5(>R-T(jq diff --git a/examples/menubar/images/down-arrow-focus.svg b/examples/menubar/images/down-arrow-focus.svg new file mode 100644 index 0000000000..1978a27bbf --- /dev/null +++ b/examples/menubar/images/down-arrow-focus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/menubar/images/down-arrow-gray.png b/examples/menubar/images/down-arrow-gray.png deleted file mode 100644 index f5d34f2e07d8f85cfb228ca7a587a94204f5790a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1325 zcmV+|1=9M7P)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z+et)0R45f=U>J0Ov7w=XOHxwuH!&`I{`@&B3mY5T%J=WzYyJQK{|`P*jEsyRwbxkw z{ri{2#Kg3lot<6%_wU~fKnWCmK!X@qSy>r={rYtiq!ujO*4Adu%*?zKsFEKjh@t^# z7>NJz@87@fRaI3d!8{NHC{PGA{XdFoAO|D{k^`v$$uYwe?A^Qff`fyD7|@-jFk4ty zSQ!5N`7;HmxE-brYy*q~^g}ri9|kFeDn1N!dlgI`M5Fo;C;;@28qnQ`K$1Y4?0}}< j000000NkvXXu0mjf+hB%b diff --git a/examples/menubar/images/down-arrow.svg b/examples/menubar/images/down-arrow.svg new file mode 100644 index 0000000000..a953c85c5f --- /dev/null +++ b/examples/menubar/images/down-arrow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/menubar/images/right-arrow-brown.png b/examples/menubar/images/right-arrow-brown.png deleted file mode 100644 index 9808455aea8f66c00b9b50251a823befc107fe89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1401 zcmV-<1%~>GP)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%R zCrLy>R45f=U_b(C#tdBPW^9{_65V8xgi)AGC_Dy+-wc0$Y5ll!dUw9Ro;d@!Q{F*2zA1L*((AkD(U%D~Jk z@`^)BClRL&5Y<2uXypHYe}MQe0|U@E_%#4cXJut&;8rp}D&?AG+r41-HWs)^Xbg~B znK`%^*hH14DQ0de&vq310#bri1IXI{Y@8o?)Ex@D?r&Yp;K=v~q!4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z`$y})6b&H3|NsC00otO@%*?zKs3{Sq5JqEY07?A*{ToE_18tfM)HDg`R34B32w>H~ z$jHb5^asP=zkeB7Sy^-0+1a-NHLBs%05%Fr{Q2{T0cevg&~Jx;n!K>ufUE(XO;7{S zNen<+4g;NNS5;ND4XXxVNHGANzyJ)X|382JoCd^RK*hH}wy~gF3F3oH1%}i|pvi^R z)z$Nn)nI4 + + + diff --git a/examples/menubar/images/separator.paint b/examples/menubar/images/separator.paint deleted file mode 100644 index 9e313a1cc163211b5a31090cce65926fb882864c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4059 zcmbtXd2AHd8UN)1I#3lIeMSl!`e2o)g(EVOd#a3R ztO&RlU!BXzb@W!LaYeb1m%pf>KM<-N6AiJiDy)dQ-c{&4Il54>dE|dneA-=>le@Th z>Em@9w|iSUp6nakGcg&B8T$_<%w1;Y98QJ;_Ke%XjLiQ&l!EgDzHk{%5C zM0Hx!L-GM>xJ}eE{2{+4?$f=RCeGxVr6$QW>A=Cd`f8!B{@Dv9`K9GU7)mnmxw0jX zn7p~jbeSbrmOYwZv0PZOva)KGb9LoVlXO6qv~JOe>}giTQAG+FT69txWR`qUC8`a# z>n*aPFz;H&jKP+uCRy~MggE1wwmdW6%*!=%@-D4?qB_5(_H&-yflx&BB3ij zF_wZMX<|xJ4c$%LT$g(mOaSn@0k|~SfykmXrdW-L5|lLS)fDAy3fxWtFQfo!wN`nB z6!4@#V+wSqz|$!ZO93+hO#6j(>oOA$rCr-dk%v^M1MZT+l0x+Qh4wY_10;U-a+-dnaDi@##0(;4WhGz)TbO{eL| zyNo3Z=GWAOdRWnR$*R;Zj~S6JcYUTb-ef8`3ka!VA)v^ylAIQ<-mEP7d5zL8s&MGt1-kcFAv`^R+10m!+ETI@JSE6ERsHrWSx zVl1W*0boisH0)wwU~ovtxdsFtfkjXbi^Q-V^LBUdV#}E<`(60leb0&jF4jDpzfk&t z*`hH?52Js%NRN)`;VH!X0c@{?W14~f6(fD)J|o63^PyNn2N6Hb@I(S54~j<<_$5oG zC+I_&R}eeH5pfLhDa6&IS@el4obUl9P*1C*N}3!NST4G?=(wy%79%ly20Kgg)8Ld6 z$C#~WIgSPTWN0V;uEE$}7!&;r*CIX@7Mnwes}NtGmiq!pd_QJ11ra|6Kvu>ld%cLU zn@QWa*3yd@^$;x*Z%g5C9*FexBfS*qZ>gh!uH@RQVcmzhgq29YACcNwek-t@1=(ot zL5zCnb}brY^5_E{ACvseh^rBQVKTat`G7j<8~SvJ(V0J24@7*-Pey;|8L^`qaXI2Y zN=hq}M_csYF{3-_17D#k0cIC%@qS6S;zd1tg%Rm#Lps{!6+`PuuH{dR%Ps9m`5#8K zHb!S_ug4UtN6|MM(c(cCJKD0>=aS3-(}vy@DYwN8ggq{}dE@pK%!Ey={nl&k;~NLAqjOEwI?iq|2I znj-KKQX^@d0{CziN7}?E=t=nUVNy?&FXKJ@di-wTck$c!jr=AM_#J#Bzn%9ZbrZiO zt-Bl7q#|Pee27*OdiJ74$&%~C*x$q$4G?jBA0?uor!_3d%8By$nhfWtHX?`5f9Y>o zxsq|^WLlzTK}NeiE9bU9**>sc#_^i%W?F|}yJ5SDpKF+#Of1n1Kbd7#DH$soT_E>h zEmBfmj8w5!PGJ^gj9JQxxh7l3xkEo+lCgG~KeOjz!MQ`@>WT6Um1RLX7PuRLqtzLz z=Zfbqp39!^cwY1TnSPc2nErzPg#MJi0|Gru-=x2yzoDn;&(iXr)gpcg%sVIS($FyE}-RG2~8?0;S|BEEvJ0NnfX{QSRW=jZRu zq5hiyXB9k8?$t)zZal=w#*`Sd&Rlp9n%k>4;F&!&MX~s_b#AjLuf_MZHw?35To7+n zqB@~SC^2*3sX(YRZm=tabt{>hIoNCO;r=kYAFw$lK&9xlV47o8%U` zP2MLTk$=!Sx|wdJKH5e*Xcrx%!&Ify^bkEvpQA_Ui}WRWoW4xINWVsZN3YOp^cMX) z{h0o<;6}mif;$BtJBTC4QRpajR5>1ZY;BP*BpNN}QdK z`}-1iYm=*NWUp3y-0aYpU8gO$^|U|$dR^3IbLG2ATotZrm&euMYIFr%gRZbkbxphW SyN)E*r{h|dn5Rb9^Zx-D`F5@V diff --git a/examples/menubar/images/separator.png b/examples/menubar/images/separator.png deleted file mode 100644 index a837fbce93afde0ba8abc83520cbf7bf42b1e924..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1134 zcmV-!1d;oRP)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z97#k$R5%f1U>F6%AOsk5a&rC;1AmNqVSqyb04qBJMQeR0N&o-=07*qoM6N<$g4BN| AA^-pY diff --git a/examples/menubar/images/up-arrow.svg b/examples/menubar/images/up-arrow.svg new file mode 100644 index 0000000000..b110cf21db --- /dev/null +++ b/examples/menubar/images/up-arrow.svg @@ -0,0 +1,4 @@ + + + + From 3adcbd5a56ce40c045b82bb5afb0cf480c9e5d6a Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 21 Apr 2020 12:40:09 -0500 Subject: [PATCH 32/62] preload images --- examples/menubar/menubar-navigation.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index b27df4219e..9845ae8abd 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -17,6 +17,13 @@ + +
      + + + + +
      From c13212e20679081d33f3c7faa0300297d6f424ea Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 21 Apr 2020 10:41:48 -0700 Subject: [PATCH 35/62] Update links to source js and css --- examples/menubar/menubar-navigation.html | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 63c21ad0f9..1195825608 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -618,22 +618,8 @@

      Submenu

      Javascript and CSS Source Code

      From eb21c4f4690998b0f314cc680984fb6f6caf52e7 Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 21 Apr 2020 10:43:09 -0700 Subject: [PATCH 36/62] Fix href in footer link to design pattern --- examples/menubar/menubar-navigation.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 1195825608..4f7c4f775c 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -639,7 +639,7 @@

      HTML Source Code

      From 58bfc96ec7b544228b3926b777a8187e0f50ee9b Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 22 Apr 2020 11:12:15 -0500 Subject: [PATCH 37/62] add SVG separator --- examples/menubar/css/menubar-navigation.css | 2 +- examples/menubar/images/separator.svg | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 examples/menubar/images/separator.svg diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index ece611c006..8ea0944e17 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -71,7 +71,7 @@ ul[role="menubar"] [role="menu"] { ul[role="menubar"] [role="separator"] { padding-top: 3px; - background-image: url('../images/separator.png'); + background-image: url('../images/separator.svg'); background-position: center; background-repeat: repeat-x; } diff --git a/examples/menubar/images/separator.svg b/examples/menubar/images/separator.svg new file mode 100644 index 0000000000..b4b39760b2 --- /dev/null +++ b/examples/menubar/images/separator.svg @@ -0,0 +1,4 @@ + + + + From f7e9401c5dc0ae0370894e0711fd5a963534a86c Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 30 Apr 2020 11:06:05 -0500 Subject: [PATCH 38/62] started updating mouse behavior --- examples/menubar/js/menubar-navigation.js | 200 +++++++++------------- examples/menubar/menubar-navigation.html | 2 +- 2 files changed, 78 insertions(+), 124 deletions(-) diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index 78b4c1383a..6c1a85636c 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -7,11 +7,12 @@ * Desc: Creates a menubar of hierarchical set of links */ -var MenubarNavigation = function (domNode, actionManager) { +var MenubarNavigation = function (domNode) { this.domNode = domNode; - this.actionManager = actionManager; + + this.popups = []; this.menuitemGroups = {}; this.menuOrientation = {}; this.isPopup = {}; @@ -35,9 +36,7 @@ MenubarNavigation.prototype.getMenuitems = function(domNode, depth) { var initMenu = this.initMenu.bind(this); var menuitemGroups = this.menuitemGroups; - var handleMenuMouseover = this.handleMenuMouseover.bind(this); - var handleMenuMouseout = this.handleMenuMouseout.bind(this); - + var popups = this.popups; function findMenuitems(node) { var role, flag; @@ -53,13 +52,14 @@ MenubarNavigation.prototype.getMenuitems = function(domNode, depth) { switch (role) { case 'menu': node.tabIndex = -1; - node.addEventListener('mouseover', handleMenuMouseover); - node.addEventListener('mouseout', handleMenuMouseout); initMenu(node, depth + 1); flag = false; break; case 'menuitem': + if (node.getAttribute('aria-haspopup') === 'true') { + popups.push(node); + } nodes.push(node); break; @@ -81,7 +81,7 @@ MenubarNavigation.prototype.getMenuitems = function(domNode, depth) { }; MenubarNavigation.prototype.initMenu = function (menu, depth) { - var i, menuitems, menuitem, role, nextElement; + var menuitems, menuitem, role, nextElement; var menuId = this.getMenuId(menu); @@ -96,7 +96,7 @@ MenubarNavigation.prototype.initMenu = function (menu, depth) { this.firstMenuitem[menuId] = null; this.lastMenuitem[menuId] = null; - for(i = 0; i < menuitems.length; i++) { + for(var i = 0; i < menuitems.length; i++) { menuitem = menuitems[i]; role = menuitem.getAttribute('role'); @@ -110,11 +110,8 @@ MenubarNavigation.prototype.initMenu = function (menu, depth) { menuitem.addEventListener('keydown', this.handleKeydown.bind(this)); menuitem.addEventListener('click', this.handleMenuitemClick.bind(this)); - menuitem.addEventListener('focus', this.handleMenuitemFocus.bind(this)); - menuitem.addEventListener('blur', this.handleMenuitemBlur.bind(this)); menuitem.addEventListener('mouseover', this.handleMenuitemMouseover.bind(this)); - menuitem.addEventListener('mouseout', this.handleMenuitemMouseout.bind(this)); if( !this.firstMenuitem[menuId]) { @@ -126,43 +123,51 @@ MenubarNavigation.prototype.initMenu = function (menu, depth) { this.lastMenuitem[menuId] = menuitem; } + +// console.log('[initMenu][menuId]: ' + menuId + ' ' + this.isPopup[menuId] + ' ' + this.isPopout[menuId]); +// menuitems.forEach(item => console.log('[menuitem]: ' + item.textContent + ' ' + this.hasPopup(item))); }; /* MenubarNavigation FOCUS MANAGEMENT METHODS */ -MenubarNavigation.prototype.setFocusToMenuitem = function (menuId, newMenuitem, currentMenuitem) { - - if (typeof currentMenuitem !== 'object') { - currentMenuitem = false; - } +MenubarNavigation.prototype.setFocusToMenuitem = function (menuId, newMenuitem) { - if (currentMenuitem && - this.hasPopup(currentMenuitem) && - this.isOpen(currentMenuitem)) { - this.closePopup(currentMenuitem); - } + var isAnyPopupOpen = this.isAnyPopupOpen(); - if (this.isMenubar(menuId)) { - this.menuitemGroups[menuId].forEach(function(item) { - item.tabIndex = -1; - }); - newMenuitem.tabIndex = 0; + this.closePopupAll(newMenuitem); - if (this.hasPopup(newMenuitem) && this.openPopups) { + if (this.hasPopup(newMenuitem)) { + if (isAnyPopupOpen) { this.openPopup(menuId, newMenuitem); } } + else { + var menu = this.getMenu(newMenuitem); + var cmi = menu.previousElementSibling; + if (!this.isOpen(cmi)) { + this.openPopup(menuId, cmi); + } + } + + if (this.hasPopup(newMenuitem)) { + if (this.menuitemGroups[menuId]) { + this.menuitemGroups[menuId].forEach(function(item) { + item.tabIndex = -1; + }); + } + newMenuitem.tabIndex = 0; + } newMenuitem.focus(); }; MenubarNavigation.prototype.setFocusToFirstMenuitem = function (menuId, currentMenuitem) { - this.setFocusToMenuitem(menuId, this.firstMenuitem[menuId], currentMenuitem); + this.setFocusToMenuitem(menuId, this.firstMenuitem[menuId]); }; MenubarNavigation.prototype.setFocusToLastMenuitem = function (menuId, currentMenuitem) { - this.setFocusToMenuitem(menuId, this.lastMenuitem[menuId], currentMenuitem); + this.setFocusToMenuitem(menuId, this.lastMenuitem[menuId]); }; MenubarNavigation.prototype.setFocusToPreviousMenuitem = function (menuId, currentMenuitem) { @@ -176,7 +181,7 @@ MenubarNavigation.prototype.setFocusToPreviousMenuitem = function (menuId, curre newMenuitem = this.menuitemGroups[menuId][ index - 1 ]; } - this.setFocusToMenuitem(menuId, newMenuitem, currentMenuitem); + this.setFocusToMenuitem(menuId, newMenuitem); return newMenuitem; }; @@ -191,7 +196,7 @@ MenubarNavigation.prototype.setFocusToNextMenuitem = function (menuId, currentMe index = this.menuitemGroups[menuId].indexOf(currentMenuitem); newMenuitem = this.menuitemGroups[menuId][ index + 1 ]; } - this.setFocusToMenuitem(menuId, newMenuitem, currentMenuitem); + this.setFocusToMenuitem(menuId, newMenuitem); return newMenuitem; }; @@ -217,7 +222,7 @@ MenubarNavigation.prototype.setFocusByFirstCharacter = function (menuId, current // If match was found... if (index > -1) { - this.setFocusToMenuitem(menuId, this.menuitemGroups[menuId][index], currentMenuitem); + this.setFocusToMenuitem(menuId, this.menuitemGroups[menuId][index]); } }; @@ -306,6 +311,15 @@ MenubarNavigation.prototype.getMenu = function(menuitem) { // Popup menu methods +MenubarNavigation.prototype.isAnyPopupOpen = function () { + for (var i = 0; i < this.popups.length; i++) { + if (this.popups[i].getAttribute('aria-expanded') === 'true') { + return true; + } + } + return false; +}; + MenubarNavigation.prototype.openPopup = function (menuId, menuitem) { // set aria-expanded attribute @@ -374,32 +388,34 @@ MenubarNavigation.prototype.closePopup = function (menuitem) { return cmi; }; -MenubarNavigation.prototype.closePopupAll = function () { - - var popups = this.domNode.querySelectorAll('[aria-haspopup]'); - - for (var i = 0; i < popups.length; i++) { - var popup = popups[i]; - if (this.isOpen(popup)) { - popup.setAttribute('aria-expanded', 'false'); - popup.nextElementSibling.style.display = 'none'; - } +MenubarNavigation.prototype.doesNotContain = function (popup, menuitem) { + if (menuitem) { + return !popup.nextElementSibling.contains(menuitem); } + return true; }; -MenubarNavigation.prototype.closePopupHover = function () { - - var menus = this.domNode.querySelectorAll('[role="menu'); +MenubarNavigation.prototype.closePopupAll = function (menuitem) { + if (typeof menuitem !== 'object') { + menuitem = false; + } - for (var i = 0; i < menus.length; i++) { - var menu = menus[i]; - var focus = menu.parentNode.querySelector('.item'); - var hover = menu.classList.contains('hover'); - if (!focus && !hover) { - menu.style.display = 'none'; - menu.previousElementSibling.setAttribute('aria-expanded', 'false'); + for (var i = 0; i < this.popups.length; i++) { + var popup = this.popups[i]; + console.log('[closePopupAll][doesNotContain]: ' + this.doesNotContain(popup, menuitem)); + console.log('[closePopupAll][isOpen]: ' + this.isOpen(popup)); + if (this.doesNotContain(popup, menuitem) && this.isOpen(popup)) { + console.log('[closePopupAll][A]'); + var cmi = popup.previousElementSibling; + if (cmi) { + cmi.setAttribute('aria-expanded', 'false'); + } + popup.style.display = 'none'; } } + if (menuitem) { + mennuitem.focus(); + } }; MenubarNavigation.prototype.hasPopup = function (menuitem) { @@ -430,17 +446,6 @@ MenubarNavigation.prototype.handleMenubarFocusout = function (event) { this.domNode.classList.remove('focus'); }; -MenubarNavigation.prototype.handleMenuitemFocus = function (event) { - var menu = this.getMenu(event.target); - menu.classList.add('item'); -}; - -MenubarNavigation.prototype.handleMenuitemBlur = function (event) { - var menu = this.getMenu(event.target); - menu.classList.remove('item'); -}; - - MenubarNavigation.prototype.handleBackgroundMousedown = function (event) { if (!this.domNode.contains(event.target)) { this.closePopupAll(); @@ -588,20 +593,15 @@ MenubarNavigation.prototype.handleKeydown = function (event) { }; MenubarNavigation.prototype.handleMenuitemClick = function (event) { - var tgt = event.currentTarget, - menuId = this.getMenuId(tgt), - role, - option, - value; + var tgt = event.currentTarget; + var menuId = this.getMenuId(tgt); if (this.hasPopup(tgt)) { if (this.isOpen(tgt)) { - this.openPopups = false; this.closePopup(tgt); } else { this.closePopupAll(); - this.openPopups = true; this.openPopup(menuId, tgt); } event.stopPropagation(); @@ -614,65 +614,19 @@ MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { menuId, menu; - if (this.hasPopup(tgt)) { + if (this.isAnyPopupOpen() && this.hasPopup(tgt)) { + this.closePopupAll(tgt); menuId = this.getMenuId(tgt); menu = this.getMenu(tgt); this.openPopup(menuId, tgt); - tgt.nextElementSibling.classList.add('hover'); - } -}; - -MenubarNavigation.prototype.handleMenuitemMouseout = function (event) { - var tgt = event.currentTarget, - menuId, - menu; - - if (this.hasPopup(tgt)) { - menuId = this.getMenuId(tgt); - menu = this.getMenu(tgt); - tgt.nextElementSibling.classList.remove('hover'); } - - var closePopupHover = this.closePopupHover.bind(this); - setTimeout(function(){ closePopupHover() }, 400); -}; - -MenubarNavigation.prototype.handleMenuMouseover = function (event) { - var menu = event.currentTarget, - menuId; - - while( menu) { - menu.classList.add('hover'); - if (menu.previousElementSibling) { - menu = this.getMenu(menu.previousElementSibling); - } - else { - menu = false; - } - } - - var closePopupHover = this.closePopupHover.bind(this); - setTimeout(function(){ closePopupHover() }, 500); -}; - -MenubarNavigation.prototype.handleMenuMouseout = function (event) { - var tgt = event.currentTarget; - var menu = this.getMenu(tgt); - menu.classList.remove('hover'); - - var menus = menu.querySelectorAll('[role="menu"]'); - - for (var i = 0; i < menus.length; i++) { - menus[i].classList.remove('hover'); - } - - var closePopupHover = this.closePopupHover.bind(this); - setTimeout(function(){ closePopupHover() }, 400); - }; // Initialize menubar editor window.addEventListener('load', function () { - var navbar = new MenubarNavigation(document.getElementById('menubar1')); + var menubarNavs = document.querySelectorAll('.menubar-navigation'); + for(var i=0; i < menubarNavs.length; i++) { + var menubarNav = new MenubarNavigation(menubarNavs[i]); + } }); diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 4f7c4f775c..7a9f8c15d7 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -51,7 +51,7 @@

      Example

        -
      • Activates menu item, causing the link to be activated.
      • -
      • NOTE: the links go to dummy pages; use the browser go-back function to return to this menubar example page.
      • +
      • If the menuitem is a link: +
          +
        • Activates menu item, causing the link to be activated.
        • +
        • NOTE: the links go to dummy pages; use the browser go-back function to return to this menubar example page.
        • +
        +
      • +
      • If the menuitem has a popup menu: +
          +
        • Opens submenu and moves focus to first item in the submenu.
        • +
        +
        -
      • If the menuitem is a link: -
          -
        • Activates menu item, causing the link to be activated.
        • -
        • NOTE: the links go to dummy pages; use the browser go-back function to return to this menubar example page.
        • -
        -
      • -
      • If the menuitem has a popup menu: -
          -
        • Opens submenu and moves focus to first item in the submenu.
        • -
        -
      • +
      • If the item is a parent menu item, opens submenu and moves focus to first item in the submenu.
      • +
      • Otherwise, activates menu item, which navigates to a dummy page. NOTE: use browser go-back function to return to this page.
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      KeyFunction
      + Space
      Enter +
      Opens submenu and moves focus to first item in the submenu.
      + Right Arrow + +
        +
      • Moves focus to the next item in the menubar.
      • +
      • If focus is on the last item, moves focus to the first item.
      • +
      +
      + Left Arrow + +
        +
      • Moves focus to the previous item in the menubar.
      • +
      • If focus is on the first item, moves focus to the last item.
      • +
      +
      + Down Arrow + Opens submenu and moves focus to first item in the submenu.
      + Up Arrow + Opens submenu and moves focus to last item in the submenu.
      + Home + Moves focus to first item in the menubar.
      + End + Moves focus to last item in the menubar.
      + Character + +
        +
      • Moves focus to next item in the menubar having a name that starts with the typed character.
      • +
      • If none of the items have a name starting with the typed character, focus does not move.
      • +
      +
      +

      Submenu

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      KeyFunction
      + Space
      Enter +
      +
        +
      • Activates menu item, causing the link to be activated.
      • +
      • NOTE: the links go to dummy pages; use the browser go-back function to return to this menubar example page.
      • +
      +
      + Escape + +
        +
      • Closes submenu.
      • +
      • Moves focus to parent menubar item.
      • +
      +
      + Right Arrow + +
        +
      • If focus is on an item with a submenu, opens the submenu and places focus on the first item.
      • +
      • If focus is on an item that does not have a submenu: +
          +
        • Closes submenu.
        • +
        • Moves focus to next item in the menubar.
        • +
        • Opens submenu of newly focused menubar item, keeping focus on that parent menubar item.
        • +
        +
      • +
      +
      + Left Arrow + +
        +
      • Closes submenu and moves focus to parent menu item.
      • +
      • If parent menu item is in the menubar, also: +
          +
        • moves focus to previous item in the menubar.
        • +
        • Opens submenu of newly focused menubar item, keeping focus on that parent menubar item.
        • +
        +
      • +
      +
      + Down Arrow + +
        +
      • Moves focus to the next item in the submenu.
      • +
      • If focus is on the last item, moves focus to the first item.
      • +
      +
      + Up Arrow + +
        +
      • Moves focus to previous item in the submenu.
      • +
      • If focus is on the first item, moves focus to the last item.
      • +
      +
      + Home + Moves focus to the first item in the submenu.
      + End + Moves focus to the last item in the submenu.
      + Character + +
        +
      • Moves focus to the next item having a name that starts with the typed character.
      • +
      • If none of the items have a name starting with the typed character, focus does not move.
      • +
      +
      +
      + +
      +

      Role, Property, State, and Tabindex Attributes

      +

      Menubar

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      RoleAttributeElementUsage
      + menubar + + ul + +
        +
      • Identifies the element as a menubar container for a set of menuitem elements.
      • +
      • Is not focusable because focus is managed using roving tabindex.
      • +
      +
      + aria-label="string" + + + ul + +
        +
      • + Defines an accessible name for the menubar. +
      • +
      • Helps assistive technology users understand the purpose of the menubar and + distinguish it from any other menubars or similar elements on the page.
      • +
      +
      + menuitem + + a + +
        +
      • Identifies the element as a menu item.
      • +
      • The accessible name is calculated from the text content of the a element.
      • +
      +
      + tabindex="-1" + + a + + Makes the a element keyboard focusable, but not part of the tab sequence. +
      + tabindex="0" + + a + +
        +
      • Includes the element in the Tab sequence.
      • +
      • Only one menubar item has tabindex="0".
      • +
      • On page load, the first menubar item has tabindex="0".
      • +
      • Focus is managed using roving tabindex.
      • +
      +
      + aria-haspopup="true" + + a + Indicates the menuitem has a submenu.
      + aria-expanded="true" + + a + Indicates the submenu is open.
      + aria-expanded="false" + + a + Indicates the submenu is closed.
      + none + + li + +
        +
      • Removes the implied listitem role of the li element.
      • +
      • Necessary because the parent ul is serving as a menu so the li elements are not in their required list context.
      • +
      +
      +

      Submenu

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      RoleAttributeElementUsage
      + menu + + ul + Identifies the element as a menu container for a set of menu items.
      + aria-label="string" + + + ul + +
        +
      • + Defines an accessible name for the menu. +
      • +
      • Helps assistive technology users understand the purpose of the menu and + distinguish it from any other menu or similar elements (e.g. menubar) on the page.
      • +
      +
      + menuitem + + a + +
        +
      • Identifies the element as a menu item.
      • +
      • The accessible name is calculated from the text content of the a element.
      • +
      +
      + tabindex="-1" + + a + Keeps the a element focusable but removes it from the Tab sequence.
      + aria-haspopup="true" + + a + Indicates the menu item has a submenu.
      + aria-expanded="true" + + a + Indicates the submenu is open.
      + aria-expanded="false" + + a + Indicates the submenu is closed.
      + none + + li + +
        +
      • Removes the implied listitem role of the li element.
      • +
      • Necessary because the parent ul is serving as a menu so the li elements are not in their required list context.
      • +
      +
      +
      + +
      +

      Javascript and CSS Source Code

      + +
      + +
      +

      HTML Source Code

      + +
      + + + + + +
      +
  • + + + diff --git a/test/tests/menubar_menubar-1.js b/test/tests/menubar_menubar-1.js new file mode 100644 index 0000000000..ebaae9b8be --- /dev/null +++ b/test/tests/menubar_menubar-1.js @@ -0,0 +1,1226 @@ +'use strict'; + +const { ariaTest } = require('..'); +const { By, Key } = require('selenium-webdriver'); +const assertAttributeValues = require('../util/assertAttributeValues'); +const assertAriaLabelExists = require('../util/assertAriaLabelExists'); +const assertAriaRoles = require('../util/assertAriaRoles'); +const assertRovingTabindex = require('../util/assertRovingTabindex'); + +const exampleFile = 'menubar/menubar-1/menubar-1.html'; + +const ex = { + // menubar selector + menubarSelector: '#ex1 [role="menubar"]', + + // menu selectors + anyMenuSelector: '#ex1 [role="menu"]', + menuSelector: '#ex1 [role="menubar"]>li>[role="menu"]', + + // menuitem selectors + menubarMenuitemSelector: '#ex1 [role="menubar"]>li>[role="menuitem"]', + anyMenuMenuitemSelector: '#ex1 [role="menu"]>li>[role="menuitem"]', + menuMenuitemSelectors: [ + '#ex1 [role="menubar"]>li:nth-of-type(1)>[role="menu"]>li>[role="menuitem"]', + '#ex1 [role="menubar"]>li:nth-of-type(2)>[role="menu"]>li>[role="menuitem"]', + '#ex1 [role="menubar"]>li:nth-of-type(3)>[role="menu"]>li>[role="menuitem"]' + ], + groupSelector: '#ex1 [role="group"]', + numMenus: 3, + numSubmenus: 3, + numTotalMenus: 6, + submenuLocations: [ + // [, ] + [0, 2], + [0, 3], + [1, 1] + ], + numMenuMenuitems: [4, 6, 8] +}; + +// Returns specified submenu +const getSubmenuSelector = function (menuIndex, menuitemIndex) { + return '#ex1 [role="menubar"]>li:nth-of-type(' + + (menuIndex + 1) + + ')>[role="menu"]>li:nth-of-type(' + + (menuitemIndex + 1) + + ')>[role="menu"]'; +}; + +// Returns the menuitems of a specified submenu +const getSubmenuMenuitemSelector = function (menuIndex, menuitemIndex) { + return '#ex1 [role="menubar"]>li:nth-of-type(' + + (menuIndex + 1) + + ')>[role="menu"]>li:nth-of-type(' + + (menuitemIndex + 1) + + ')>[role="menu"] [role="menuitem"]'; +}; + +const openSubmenu = async function (t, menuIndex, menuitemIndex) { + // Send ARROW_DOWN to open menu + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + await menubaritems[menuIndex].sendKeys(Key.ARROW_DOWN); + + // Get the menuitems for that menu and send ARROW_RIGHT to open the submenu + const menuitems = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + await menuitems[menuitemIndex].sendKeys(Key.ARROW_RIGHT); + return; +}; + +const waitForUrlChange = async function (t) { + return t.context.session.wait(() => { + return t.context.session.getCurrentUrl().then(url => { + return url != t.context.url; + }); + }, t.context.waitTime).catch(() => {}); +}; + +const exampleInitialized = async function (t) { + const initializedSelector = ex.menubarMenuitemSelector + '[tabindex="0"]'; + + await t.context.session.wait(async function () { + const els = await t.context.queryElements(t, initializedSelector); + return els.length === 1; + }, t.context.waitTime, 'Timeout waiting for example to initialize'); +}; + +const checkFocus = async function (t, selector, index) { + return t.context.session.executeScript(function () { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, selector, index); +}; + +const doesMenuitemHaveSubmenu = function (menuIndex, menuitemIndex) { + for (let submenuLocation of ex.submenuLocations) { + if ( + submenuLocation[0] === menuIndex && + submenuLocation[1] === menuitemIndex + ) { + return true; + } + } + return false; +}; + + +// Attributes + +ariaTest('Test for role="menubar" on ul', exampleFile, 'menubar-role', async (t) => { + await assertAriaRoles(t, 'ex1', 'menubar', 1, 'ul'); +}); + +ariaTest('Test aria-label on menubar', exampleFile, 'menubar-aria-label', async (t) => { + await assertAriaLabelExists(t, ex.menubarSelector); +}); + +ariaTest('Test for role="menuitem" in menubar', exampleFile, 'menuitem-role', async (t) => { + + const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + + t.is( + menuitems.length, + ex.numMenus, + '"role=menuitem" elements should be found by selector: ' + ex.menubarMenuitemSelector + ); + + for (let menuitem of menuitems) { + t.truthy( + await menuitem.getText(), + '"role=menuitem" elements should all have accessible text content: ' + ex.menubarMenuitemSelector + ); + } +}); + +ariaTest('Test roving tabindex', exampleFile, 'menuitem-tabindex', async (t) => { + + + // Wait for roving tabindex to be initialized by the javascript + await exampleInitialized(t); + + await assertRovingTabindex(t, ex.menubarMenuitemSelector, Key.ARROW_RIGHT); +}); + +ariaTest('Test aria-haspopup set to true on menuitems', + exampleFile, 'menuitem-aria-haspopup', async (t) => { + + + await assertAttributeValues(t, ex.menubarMenuitemSelector, 'aria-haspopup', 'true'); + }); + +ariaTest('"aria-expanded" attribute on menubar menuitem', exampleFile, 'menuitem-aria-expanded', async (t) => { + + // Before interating with page, make sure aria-expanded is set to false + await assertAttributeValues(t, ex.menubarMenuitemSelector, 'aria-expanded', 'false'); + + // AND make sure no submenus are visible + const submenus = await t.context.queryElements(t, ex.menuSelector); + for (let submenu of submenus) { + t.false( + await submenu.isDisplayed(), + 'No submenus (found by selector: ' + ex.menuSelector + ') should be displayed on load' + ); + } + + const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + + for (let menuIndex = 0; menuIndex < menuitems.length; menuIndex++) { + + // Send ARROW_DOWN to open submenu + await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); + + for (let item = 0; item < menuitems.length; item++) { + + // Test attribute "aria-expanded" is only set for the opened submenu + const displayed = menuIndex === item ? true : false; + t.is( + await menuitems[item].getAttribute('aria-expanded'), + displayed.toString(), + 'focus is on element ' + menuIndex + ' of elements "' + ex.menubarMenuitemSelector + + '", therefore "aria-expanded" on menuitem ' + item + ' should be ' + displayed + ); + + // Test the submenu is indeed displayed + t.is( + await submenus[item].isDisplayed(), + displayed, + 'focus is on element ' + menuIndex + ' of elements "' + ex.menubarMenuitemSelector + + '", therefore isDisplay of submenu ' + item + ' should return ' + displayed + ); + } + + // Send the ESCAPE to close submenu + await menuitems[menuIndex].sendKeys(Key.ESCAPE); + } +}); + +ariaTest('Test for role="none" on menubar li', exampleFile, 'none-role', async (t) => { + + const liElements = await t.context.queryElements(t, ex.menubarSelector + '>li'); + + for (let liElement of liElements) { + t.is( + await liElement.getAttribute('role'), + 'none', + '"role=none" should be found on all list elements that are immediate descendants of: ' + ex.menubarSelector + ); + } +}); + +ariaTest('Test for role="menu" on ul', exampleFile, 'menu-role', async (t) => { + await assertAriaRoles(t, 'ex1', 'menu', ex.numTotalMenus, 'ul'); +}); + +ariaTest('Test for aria-label on role="menu"', exampleFile, 'menu-aria-label', async (t) => { + await assertAriaLabelExists(t, ex.anyMenuSelector); +}); + +ariaTest('Test for submenu role="menuitem"s with accessible names', exampleFile, 'sub-menuitem-role', async (t) => { + + + const menuitems = await t.context.queryElements(t, ex.anyMenuMenuitemSelector); + + t.truthy( + menuitems.length, + '"role=menuitem" elements should be found by selector: ' + ex.anyMenuMenuitemSelector + ); + + // Test the accessible name of each menuitem + + for (let menuitem of menuitems) { + + // The menuitem is not visible, so we cannot use selenium's "getText" function + const menutext = await t.context.session.executeScript(function () { + const el = arguments[0]; + return el.innerHTML; + }, menuitem); + + t.truthy( + menutext, + '"role=menuitem" elements should all have accessible text content: ' + ex.anyMenuMenuitemSelector + ); + } +}); + +ariaTest('Test tabindex="-1" on submenu role="menuitem"s', exampleFile, 'sub-menuitem-tabindex', async (t) => { + await assertAttributeValues(t, ex.anyMenuMenuitemSelector, 'tabindex', '-1'); +}); + +ariaTest('Test aria-haspopup on menuitems with submenus', exampleFile, 'sub-menuitem-aria-haspopup', async (t) => { + + const menubarMenuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + const menuItems = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + + for (let menuitemIndex = 0; menuitemIndex < menuItems.length; menuitemIndex++) { + const menuitemHasSubmenu = doesMenuitemHaveSubmenu(menuIndex, menuitemIndex); + + const ariaPopup = menuitemHasSubmenu ? 'true' : null; + const hasAriaPopupMsg = menuitemHasSubmenu ? + 'aria-haspop set to "true".' : + 'no aria-haspop attribute.'; + + t.is( + await menuItems[menuitemIndex].getAttribute('aria-haspopup'), + ariaPopup, + 'menuitem at index ' + menuitemIndex + ' in menu at index ' + menuIndex + ' is expected ' + + 'to have ' + hasAriaPopupMsg + ); + } + } +}); + +ariaTest('Test aria-expanded on menuitems with submenus', exampleFile, 'sub-menuitem-aria-expanded', async (t) => { + + const menubarMenuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + // Send ARROW_DOWN to open menu + await menubarMenuitems[menuIndex].sendKeys(Key.ARROW_DOWN); + + // Get the menuitems for that menu + const menuitems = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + + // Get the submenu associate with the menuitem we are testing + const submenuSelector = getSubmenuSelector(menuIndex, menuitemIndex); + + t.is( + await menuitems[menuitemIndex].getAttribute('aria-expanded'), + 'false', + 'menuitem at index ' + menuitemIndex + ' in menu at index ' + menuIndex + ' is expected ' + + 'to have aria-expanded="false" after opening the menu that contains it' + ); + t.false( + await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), + 'submenu attached to menuitem at index ' + menuitemIndex + ' in menu at index ' + + menuIndex + ' is expected to not be displayed after opening the menu that contains the menuitem' + ); + + // Send ARROW_RIGHT to the menuitem we are testing + await menuitems[menuitemIndex].sendKeys(Key.ARROW_RIGHT); + + t.is( + await menuitems[menuitemIndex].getAttribute('aria-expanded'), + 'true', + 'menuitem at index ' + menuitemIndex + ' in menu at index ' + menuIndex + ' is expected ' + + 'to have aria-expanded="true" after sending right arrow to it' + ); + t.true( + await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), + 'submenu attached to menuitem at index ' + menuitemIndex + ' in menu at index ' + + menuIndex + ' is expected to be displayed after sending left arrow to associated menuitem' + ); + } + +}); + +ariaTest('Test for role="none" on menu lis', exampleFile, 'sub-none-role', async (t) => { + + const liElements = await t.context.queryElements(t, ex.anyMenuSelector + '>li'); + + for (let liElement of liElements) { + if (await liElement.getAttribute('role') !== 'separator') { + t.is( + await liElement.getAttribute('role'), + 'none', + '"role=none" should be found on all list elements that are immediate descendants of: ' + ex.anyMenuSelector + ); + } + } +}); + + +// KEYS + +ariaTest('Key ENTER open submenu', exampleFile, 'menubar-space-or-enter', async (t) => { + + const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + + // Send the ENTER key + await menuitems[menuIndex].sendKeys(Key.ENTER); + + // Test that the submenu is displayed + t.true( + await menus[menuIndex].isDisplayed(), + 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' + ); + + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), + 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' + ); + } +}); + + +ariaTest('Key SPACE open submenu', exampleFile, 'menubar-space-or-enter', async (t) => { + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + + // Send the SPACE key + await menubaritems[menuIndex].sendKeys(Key.SPACE); + + // Test that the submenu is displayed + t.true( + await menus[menuIndex].isDisplayed(), + 'Sending key "SPACE" to menuitem ' + menuIndex + ' in menubar should display submenu' + ); + + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), + 'Sending key "SPACE" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' + ); + + } +}); + +ariaTest('Key ARROW_RIGHT moves focus to next menubar item', + exampleFile, 'menubar-right-arrow', async (t) => { + + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + + for (let menuIndex = 0; menuIndex < ex.numMenus + 1; menuIndex++) { + + const currentIndex = menuIndex % ex.numMenus; + const nextIndex = (menuIndex + 1) % ex.numMenus; + + // Send the ARROW_RIGHT key + await menubaritems[currentIndex].sendKeys(Key.ARROW_RIGHT); + + // Test the focus is on the next item mod the number of items to account for wrapping + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, nextIndex), + 'Sending key "ARROW_RIGHT" to menuitem ' + currentIndex + ' should move focus to menuitem ' + nextIndex + ); + } + }); + +ariaTest('Key ARROW_RIGHT moves focus to next menubar item', + exampleFile, 'menubar-left-arrow', async (t) => { + + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + + // Send the ARROW_LEFT key to the first menuitem + await menubaritems[0].sendKeys(Key.ARROW_LEFT); + + // Test the focus is on the last menu item + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, ex.numMenus - 1), + 'Sending key "ARROW_LEFT" to menuitem 0 will change focus to menu item 3' + ); + + + for (let menuIndex = ex.numMenus - 1; menuIndex > 0; menuIndex--) { + + // Send the ARROW_LEFT key + await menubaritems[menuIndex].sendKeys(Key.ARROW_LEFT); + + // Test the focus is on the previous menuitem + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, menuIndex - 1), + 'Sending key "ARROW_RIGHT" to menuitem ' + menuIndex + ' should move focus to menuitem ' + (menuIndex - 1) + ); + } + }); + +ariaTest('Key ARROW_UP opens submenu, focus on last item', + exampleFile, 'menubar-up-arrow', async (t) => { + + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + + // Send the ENTER key + await menubaritems[menuIndex].sendKeys(Key.UP); + + // Test that the submenu is displayed + t.true( + await menus[menuIndex].isDisplayed(), + 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' + ); + + const numSubItems = (await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex])).length; + + // Test that the focus is on the last item in the list + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], numSubItems - 1), + 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' + ); + } + }); + +ariaTest('Key ARROW_DOWN opens submenu, focus on first item', + exampleFile, 'menubar-down-arrow', async (t) => { + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + + // Send the ENTER key + await menubaritems[menuIndex].sendKeys(Key.DOWN); + + // Test that the submenu is displayed + t.true( + await menus[menuIndex].isDisplayed(), + 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' + ); + + // Test that the focus is on the first item in the list + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), + 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' + ); + } + }); + +ariaTest('Key HOME goes to first item in menubar', exampleFile, 'menubar-home', async (t) => { + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + + // Send the ARROW_RIGHT key to move the focus to later menu item for every test + for (let i = 0; i < menuIndex; i++) { + await menubaritems[i].sendKeys(Key.ARROW_RIGHT); + } + + // Send the key HOME + await menubaritems[menuIndex].sendKeys(Key.HOME); + + // Test that the focus is on the first item in the list + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, 0), + 'Sending key "HOME" to menuitem ' + menuIndex + ' in menubar should move the focus to the first menuitem' + ); + } +}); + +ariaTest('Key END goes to last item in menubar', exampleFile, 'menubar-end', async (t) => { + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + + // Send the ARROW_RIGHT key to move the focus to later menu item for every test + for (let i = 0; i < menuIndex; i++) { + await menubaritems[i].sendKeys(Key.ARROW_RIGHT); + } + + // Send the key END + await menubaritems[menuIndex].sendKeys(Key.END); + + // Test that the focus is on the last item in the list + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, ex.numMenus - 1), + 'Sending key "END" to menuitem ' + menuIndex + ' in menubar should move the focus to the last menuitem' + ); + } +}); + +ariaTest('Character sends to menubar changes focus in menubar', + exampleFile, 'menubar-character', async (t) => { + + + const charIndexTest = [ + { sendChar: 'z', sendIndex: 0, endIndex: 0 }, + { sendChar: 'a', sendIndex: 0, endIndex: 1 }, + { sendChar: 'a', sendIndex: 1, endIndex: 2 }, + { sendChar: 'a', sendIndex: 2, endIndex: 0 } + ]; + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + for (let test of charIndexTest) { + + // Send character to menuitem + await menubaritems[test.sendIndex].sendKeys(test.sendChar); + + // Test that the focus switches to the appropriate menuitem + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, test.endIndex), + 'Sending character ' + test.sendChar + ' to menuitem ' + test.sendIndex + ' in menubar should move the focus to menuitem ' + test.endIndex + ); + } + }); + +// This test is failing due to a bug reported in issue: https://github.com/w3c/aria-practices/issues/907 +ariaTest.failing('ENTER in submenu selects item', exampleFile, 'submenu-space-or-enter', async (t) => { + + // Test all the level one menuitems + + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { + + await t.context.session.get(t.context.url); + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const itemText = await items[itemIndex].getText(); + + // send ENTER to the item + await items[itemIndex].sendKeys(Key.ENTER); + await waitForUrlChange(t); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'Sending key "ENTER" to menuitem "' + itemText + '" should navigate to a new webpage.' + ); + } + } + + // Test all the submenu menuitems + + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + // Get the submenu associate with the menuitem we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const numItems = (await t.context.queryElements(t, submenuMenuitemSelector)).length; + + // Test all the items in the submenu + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + + await openSubmenu(t, ...submenuLocation); + + // send ENTER to the item we are testing + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ENTER); + await waitForUrlChange(t); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'Sending key "ENTER" to menuitem ' + itemText + '" should navigate to a new webpage.' + ); + + await t.context.session.get(t.context.url); + } + } +}); + +// This test is failing due to a bug reported in issue: https://github.com/w3c/aria-practices/issues/907 +ariaTest.failing('SPACE in submenu selects item', exampleFile, 'submenu-space-or-enter', async (t) => { + + // Test all the level one menuitems + + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { + + await t.context.session.get(t.context.url); + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const itemText = await items[itemIndex].getText(); + + // send SPACE to the item + await items[itemIndex].sendKeys(Key.SPACE); + await waitForUrlChange(t); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'Sending key "SPACE" to menuitem "' + itemText + '" should navigate to a new webpage.' + ); + } + } + + // Test all the submenu menuitems + + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + // Get the submenu associate with the menuitem we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const numItems = (await t.context.queryElements(t, submenuMenuitemSelector)).length; + + // Test all the items in the submenu + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + + await openSubmenu(t, ...submenuLocation); + + // send SPACE to the item we are testing + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.SPACE); + await waitForUrlChange(t); + + t.not( + await t.context.session.getCurrentUrl(), + t.context.url, + 'Sending key "SPACE" to menuitem ' + itemText + '" should navigate to a new webpage.' + ); + + await t.context.session.get(t.context.url); + } + } +}); + + +ariaTest('ESCAPE to submenu closes submenu', exampleFile, 'submenu-escape', async (t) => { + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + + // Test all the level one menuitems + + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { + + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const itemText = await items[itemIndex].getText(); + + // send ARROW_RIGHT to the item + await items[itemIndex].sendKeys(Key.ESCAPE); + + t.false( + await menus[menuIndex].isDisplayed(), + 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should close the menu' + ); + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, menuIndex), + 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should change the focus to menuitem ' + + menuIndex + ' in the menubar' + ); + } + } + + // Test all the submenu menuitems + + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + // Get the submenu items we are testing + let submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; + + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + + await openSubmenu(t, ...submenuLocation); + + // send ESCAPE to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ESCAPE); + + const submenuSelector = getSubmenuSelector(...submenuLocation); + t.false( + await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), + 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should close the menu' + ); + + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], menuitemIndex), + 'Sending key "ESCAPE" to submenuitem "' + itemText + + '" should send focus to menuitem ' + menuitemIndex + ' in the parent menu' + ); + } + } + +}); + +ariaTest('ARROW_RIGHT to submenu closes submenu and opens next', exampleFile, 'submenu-right-arrow', async (t) => { + + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + + // Test all the level one menuitems + + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { + + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const itemText = await items[itemIndex].getText(); + const hasSubmenu = await items[itemIndex].getAttribute('aria-haspopup'); + + // send ARROW_RIGHT to the item + await items[itemIndex].sendKeys(Key.ARROW_RIGHT); + + if (hasSubmenu) { + const submenuSelector = getSubmenuSelector(menuIndex, itemIndex); + const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, itemIndex); + + t.true( + await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), + 'Sending key "ARROW_RIGHT" to menuitem "' + itemText + + '" should open the submenu: ' + submenuSelector + ); + t.true( + await checkFocus(t, submenuMenuitemSelector, 0), + 'Sending key "ARROW_RIGHT" to menuitem "' + itemIndex + + '" should put focus on first item in submenu: ' + submenuSelector + ); + } + else { + + // Account for wrapping (index 0 should go to 3) + const nextMenuIndex = menuIndex === 2 ? 0 : menuIndex + 1; + + // Test that the submenu is closed + t.false( + await menus[menuIndex].isDisplayed(), + 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + '" should close list' + ); + + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), + 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + + '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' + ); + } + } + } + + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; + + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + + await openSubmenu(t, ...submenuLocation); + + // send ARROW_RIGHT to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ARROW_RIGHT); + + // Account for wrapping (index 0 should go to 3) + const nextMenuIndex = menuIndex === 2 ? 0 : menuIndex + 1; + + // Test that the submenu is closed + t.false( + await menus[menuIndex].isDisplayed(), + 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + '" should close list' + ); + + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), + 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + + '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' + ); + } + } +}); + +ariaTest('ARROW_LEFT to submenu closes submenu and opens next', exampleFile, 'submenu-left-arrow', async (t) => { + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + + // Test all the level one menuitems + + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { + + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const itemText = await items[itemIndex].getText(); + + // send ARROW_LEFT to the item + await items[itemIndex].sendKeys(Key.ARROW_LEFT); + + // Account for wrapping (index 0 should go to 3) + const nextMenuIndex = menuIndex === 0 ? 2 : menuIndex - 1; + + // Test that the submenu is closed + t.false( + await menus[menuIndex].isDisplayed(), + 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + '" should close list' + ); + + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), + 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + + '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' + ); + } + } + + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; + + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + + await openSubmenu(t, ...submenuLocation); + + // send ARROW_LEFT to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ARROW_LEFT); + + const submenuSelector = getSubmenuSelector(...submenuLocation); + t.false( + await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), + 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + '" should close the menu' + ); + + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], menuitemIndex), + 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + + '" should send focus to menuitem ' + menuitemIndex + ' in the parent menu' + ); + } + } +}); + +ariaTest('ARROW_DOWN moves focus in menu', exampleFile, 'submenu-down-arrow', async (t) => { + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + + // Test all the level one menuitems + + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { + + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const itemText = await items[itemIndex].getText(); + + // send ARROW_DOWN to the item + await items[itemIndex].sendKeys(Key.ARROW_DOWN); + + // Account for wrapping (last item should move to first item) + const nextItemIndex = itemIndex === ex.numMenuMenuitems[menuIndex] - 1 ? + 0 : + itemIndex + 1; + + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], nextItemIndex), + 'Sending key "ARROW_DOWN" to submenuitem "' + itemText + + '" should send focus to menuitem' + nextItemIndex + ' in the same menu' + ); + } + } + + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; + + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + + await openSubmenu(t, ...submenuLocation); + + // send ARROW_DOWN to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ARROW_DOWN); + + // Account for wrapping (last item should move to first item) + const nextItemIndex = itemIndex === numItems - 1 ? + 0 : + itemIndex + 1; + + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, submenuMenuitemSelector, nextItemIndex), + 'Sending key "ARROW_DOWN" to submenuitem "' + itemText + + '" should send focus to menuitem' + nextItemIndex + ' in the same menu' + ); + } + } +}); + + +ariaTest('ARROW_UP moves focus in menu', exampleFile, 'submenu-up-arrow', async (t) => { + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + + // Test all the level one menuitems + + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { + + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const itemText = await items[itemIndex].getText(); + + // send ARROW_UP to the item + await items[itemIndex].sendKeys(Key.ARROW_UP); + + // Account for wrapping (last item should move to first item) + const nextItemIndex = itemIndex === 0 ? + ex.numMenuMenuitems[menuIndex] - 1 : + itemIndex - 1; + + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], nextItemIndex), + 'Sending key "ARROW_UP" to submenuitem "' + itemText + + '" should send focus to menuitem' + nextItemIndex + ' in the same menu' + ); + } + } + + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; + + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + + await openSubmenu(t, ...submenuLocation); + + // send ARROW_UP to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.ARROW_UP); + + // Account for wrapping (last item should move to first item) + const nextItemIndex = itemIndex === 0 ? + numItems - 1 : + itemIndex - 1; + + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, submenuMenuitemSelector, nextItemIndex), + 'Sending key "ARROW_UP" to submenuitem "' + itemText + + '" should send focus to menuitem' + nextItemIndex + ' in the same menu' + ); + } + } +}); + +ariaTest('HOME moves focus in menu', exampleFile, 'submenu-home', async (t) => { + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + + // Test all the level one menuitems + + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { + + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const itemText = await items[itemIndex].getText(); + + // send HOME to the item + await items[itemIndex].sendKeys(Key.HOME); + + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), + 'Sending key "HOME" to submenuitem "' + itemText + + '" should send focus to first menuitem in the same menu' + ); + } + } + + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; + + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + + await openSubmenu(t, ...submenuLocation); + + // send HOME to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.HOME); + + t.true( + await checkFocus(t, submenuMenuitemSelector, 0), + 'Sending key "HOME" to submenuitem "' + itemText + + '" should send focus to the first menuitem in the same menu' + ); + } + } +}); + + +ariaTest('END moves focus in menu', exampleFile, 'submenu-end', async (t) => { + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + const menus = await t.context.queryElements(t, ex.menuSelector); + + // Test all the level one menuitems + + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { + + // Open the submenu + await menubaritems[menuIndex].sendKeys(Key.ENTER); + + const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + const itemText = await items[itemIndex].getText(); + + // send END to the item + await items[itemIndex].sendKeys(Key.END); + + // Test that the focus is on the menuitem in the menubar + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], ex.numMenuMenuitems[menuIndex] - 1), + 'Sending key "END" to submenuitem "' + itemText + + '" should send focus to last menuitem in the same menu' + ); + } + } + + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + const numItems = items.length; + + for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { + + await openSubmenu(t, ...submenuLocation); + + // send END to the item + const itemText = await items[itemIndex].getText(); + await items[itemIndex].sendKeys(Key.END); + + t.true( + await checkFocus(t, submenuMenuitemSelector, numItems - 1), + 'Sending key "END" to submenuitem "' + itemText + + '" should send focus to the last menuitem in the same menu' + ); + } + } +}); + + +ariaTest('Character sends to menubar changes focus in menubar', exampleFile, 'submenu-character', async (t) => { + + const charIndexTest = [ + [ // Tests for menu dropdown 0 + { sendChar: 'a', sendIndex: 0, endIndex: 1 }, + { sendChar: 'x', sendIndex: 1, endIndex: 1 }, + { sendChar: 'o', sendIndex: 1, endIndex: 0 } + ], + [ // Tests for menu dropdown 1 + { sendChar: 'c', sendIndex: 0, endIndex: 5 }, + { sendChar: 'y', sendIndex: 5, endIndex: 5 } + ], + [ // Tests for menu dropdown 2 + { sendChar: 'c', sendIndex: 0, endIndex: 4 }, + { sendChar: 'r', sendIndex: 4, endIndex: 5 }, + { sendChar: 'z', sendIndex: 5, endIndex: 5 } + ] + ]; + + const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); + for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { + + // Open the dropdown + await menubaritems[menuIndex].sendKeys(Key.ARROW_DOWN); + const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); + + for (let test of charIndexTest[menuIndex]) { + + // Send character to menuitem + const itemText = await items[test.sendIndex].getText(); + await items[test.sendIndex].sendKeys(test.sendChar); + + // Test that the focus switches to the appropriate menuitem + t.true( + await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], test.endIndex), + 'Sending character ' + test.sendChar + ' to menuitem ' + itemText + ' should move the focus to menuitem ' + test.endIndex + ); + } + } + + const subCharIndexTest = [ + [ // Tests for menu dropdown 0 + { sendChar: 'c', sendIndex: 0, endIndex: 1 }, + { sendChar: 'h', sendIndex: 1, endIndex: 0 }, + { sendChar: 'x', sendIndex: 0, endIndex: 0 } + ], + [ // Tests for menu dropdown 1 + { sendChar: 'f', sendIndex: 0, endIndex: 1 }, + { sendChar: 'f', sendIndex: 1, endIndex: 2 } + ], + [ // Tests for menu dropdown 2 + { sendChar: 'p', sendIndex: 0, endIndex: 2 }, + { sendChar: 'z', sendIndex: 2, endIndex: 2 } + ] + ]; + + let testIndex = 0; + + // Test all the submenu menuitems + for (let submenuLocation of ex.submenuLocations) { + const [menuIndex, menuitemIndex] = submenuLocation; + + await openSubmenu(t, ...submenuLocation); + + // Get the submenu items we are testing + const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); + const items = await t.context.queryElements(t, submenuMenuitemSelector); + + for (let test of subCharIndexTest[testIndex]) { + + // Send character to menuitem + const itemText = await items[test.sendIndex].getText(); + await items[test.sendIndex].sendKeys(test.sendChar); + + // Test that the focus switches to the appropriate menuitem + t.true( + await checkFocus(t, submenuMenuitemSelector, test.endIndex), + 'Sending character ' + test.sendChar + ' to menuitem ' + itemText + ' should move the focus to menuitem ' + test.endIndex + ); + } + + testIndex++; + } +}); From 443dd81fb7c017a39a74af561a8fdd7f53b8bb1a Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Mon, 13 Jul 2020 14:34:35 -0500 Subject: [PATCH 48/62] removed original menubar navigation example files and test --- .../menubar/menubar-1/css/menubarLinks.css | 69 - .../menubar-1/images/down-arrow-brown.png | Bin 1362 -> 0 bytes .../menubar-1/images/down-arrow-gray.png | Bin 1325 -> 0 bytes .../menubar-1/images/right-arrow-brown.png | Bin 1401 -> 0 bytes .../menubar-1/images/right-arrow-gray.png | Bin 1357 -> 0 bytes .../menubar/menubar-1/images/separator.paint | Bin 4059 -> 0 bytes .../menubar/menubar-1/images/separator.png | Bin 1134 -> 0 bytes .../menubar/menubar-1/js/MenubarItemLinks.js | 162 --- examples/menubar/menubar-1/js/MenubarLinks.js | 183 --- .../menubar-1/js/PopupMenuItemLinks.js | 212 --- .../menubar/menubar-1/js/PopupMenuLinks.js | 264 ---- examples/menubar/menubar-1/mb-about.html | 32 - examples/menubar/menubar-1/mb-academics.html | 52 - examples/menubar/menubar-1/mb-admissions.html | 39 - examples/menubar/menubar-1/menubar-1.html | 658 --------- test/tests/menubar_menubar-1.js | 1226 ----------------- 16 files changed, 2897 deletions(-) delete mode 100644 examples/menubar/menubar-1/css/menubarLinks.css delete mode 100644 examples/menubar/menubar-1/images/down-arrow-brown.png delete mode 100644 examples/menubar/menubar-1/images/down-arrow-gray.png delete mode 100644 examples/menubar/menubar-1/images/right-arrow-brown.png delete mode 100644 examples/menubar/menubar-1/images/right-arrow-gray.png delete mode 100644 examples/menubar/menubar-1/images/separator.paint delete mode 100644 examples/menubar/menubar-1/images/separator.png delete mode 100644 examples/menubar/menubar-1/js/MenubarItemLinks.js delete mode 100644 examples/menubar/menubar-1/js/MenubarLinks.js delete mode 100644 examples/menubar/menubar-1/js/PopupMenuItemLinks.js delete mode 100644 examples/menubar/menubar-1/js/PopupMenuLinks.js delete mode 100644 examples/menubar/menubar-1/mb-about.html delete mode 100644 examples/menubar/menubar-1/mb-academics.html delete mode 100644 examples/menubar/menubar-1/mb-admissions.html delete mode 100644 examples/menubar/menubar-1/menubar-1.html delete mode 100644 test/tests/menubar_menubar-1.js diff --git a/examples/menubar/menubar-1/css/menubarLinks.css b/examples/menubar/menubar-1/css/menubarLinks.css deleted file mode 100644 index 7e7869e8ea..0000000000 --- a/examples/menubar/menubar-1/css/menubarLinks.css +++ /dev/null @@ -1,69 +0,0 @@ -ul[role="menubar"] { - margin: 10px; - padding: 10px; - font-size: 110%; - list-style: none; - background-color: #eee; -} - -ul[role="menubar"] [role="menuitem"], -ul[role="menubar"] [role="separator"] { - padding: 0.25em; - background-color: #eee; - border: 2px solid #eee; -} - -ul[role="menubar"] [role="separator"] { - padding-top: 0.15em; - background-image: url('../images/separator.png'); - background-position: center; - background-repeat: repeat-x; -} - -ul[role="menubar"] [role="menuitem"]:focus, -ul[role="menubar"] [role="menuitem"]:hover, -ul[role="menubar"] [role="separator"]:focus, -ul[role="menubar"] [role="separator"]:hover { - background-color: black; - color: white; -} - -ul[role="menubar"] a[role="menuitem"] { - text-decoration: none; - color: black; -} - -ul[role="menubar"] li { - list-style: none; - margin: 0; - padding: 0; -} - -ul[role="menubar"] > li { - display: inline; - position: relative; -} - -ul[role="menubar"] > li > a::after { - content: url('../images/down-arrow-brown.png'); - padding-left: 0.25em; -} - -ul[role="menubar"] ul[role="menu"] { - display: none; - position: absolute; - top: -2px; - left: 0; - margin: 0; - padding: 0; -} - -ul[role="menubar"] ul[role="menu"] li a { - display: block; - width: 10em; -} - -ul[role="menubar"] ul[role="menu"] a[aria-haspopup="true"]::after { - content: url('../images/right-arrow-brown.png'); - padding-right: 2em; -} diff --git a/examples/menubar/menubar-1/images/down-arrow-brown.png b/examples/menubar/menubar-1/images/down-arrow-brown.png deleted file mode 100644 index fe3e38aa3137fad169770d5cb788db37a5d15390..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1362 zcmV-Y1+DstP)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%R z07*naR45f=U>J0OG0m8P3+SrfM7oTXg^`(UCBxreTK^ds{@~NZ$iTqDz|3}yg&KnbyBWT}SO14Y2NOt~Q}8B-ifI;5BLhRezn=N8SGRZm`}Lh4XcGzmDPUsb{KzJz z>OOJfrITRI6E|Kw#jD|1$jlD(4zdm)n}vg$0VD@f12PG$fq{YXf7ku3i`hk$rm?cI zLTmskW@Tez;8wJl*7arUB9Iy+4M64r#y=_r`^q`xjE^!iGBU8RurhEdTOE~8o>b1T zn(;5h6u1o#ZnmS?7a;?WBxYXGSIoR3uOuuJld>JPzrYmZq)QWAy-H(Uy|5_&0JyeA U9gsdwRR91007*qoM6N<$g5(>R-T(jq diff --git a/examples/menubar/menubar-1/images/down-arrow-gray.png b/examples/menubar/menubar-1/images/down-arrow-gray.png deleted file mode 100644 index f5d34f2e07d8f85cfb228ca7a587a94204f5790a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1325 zcmV+|1=9M7P)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z+et)0R45f=U>J0Ov7w=XOHxwuH!&`I{`@&B3mY5T%J=WzYyJQK{|`P*jEsyRwbxkw z{ri{2#Kg3lot<6%_wU~fKnWCmK!X@qSy>r={rYtiq!ujO*4Adu%*?zKsFEKjh@t^# z7>NJz@87@fRaI3d!8{NHC{PGA{XdFoAO|D{k^`v$$uYwe?A^Qff`fyD7|@-jFk4ty zSQ!5N`7;HmxE-brYy*q~^g}ri9|kFeDn1N!dlgI`M5Fo;C;;@28qnQ`K$1Y4?0}}< j000000NkvXXu0mjf+hB%b diff --git a/examples/menubar/menubar-1/images/right-arrow-brown.png b/examples/menubar/menubar-1/images/right-arrow-brown.png deleted file mode 100644 index 9808455aea8f66c00b9b50251a823befc107fe89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1401 zcmV-<1%~>GP)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%R zCrLy>R45f=U_b(C#tdBPW^9{_65V8xgi)AGC_Dy+-wc0$Y5ll!dUw9Ro;d@!Q{F*2zA1L*((AkD(U%D~Jk z@`^)BClRL&5Y<2uXypHYe}MQe0|U@E_%#4cXJut&;8rp}D&?AG+r41-HWs)^Xbg~B znK`%^*hH14DQ0de&vq310#bri1IXI{Y@8o?)Ex@D?r&Yp;K=v~q!4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z`$y})6b&H3|NsC00otO@%*?zKs3{Sq5JqEY07?A*{ToE_18tfM)HDg`R34B32w>H~ z$jHb5^asP=zkeB7Sy^-0+1a-NHLBs%05%Fr{Q2{T0cevg&~Jx;n!K>ufUE(XO;7{S zNen<+4g;NNS5;ND4XXxVNHGANzyJ)X|382JoCd^RK*hH}wy~gF3F3oH1%}i|pvi^R z)z$Nn)nI4)1I#3lIeMSl!`e2o)g(EVOd#a3R ztO&RlU!BXzb@W!LaYeb1m%pf>KM<-N6AiJiDy)dQ-c{&4Il54>dE|dneA-=>le@Th z>Em@9w|iSUp6nakGcg&B8T$_<%w1;Y98QJ;_Ke%XjLiQ&l!EgDzHk{%5C zM0Hx!L-GM>xJ}eE{2{+4?$f=RCeGxVr6$QW>A=Cd`f8!B{@Dv9`K9GU7)mnmxw0jX zn7p~jbeSbrmOYwZv0PZOva)KGb9LoVlXO6qv~JOe>}giTQAG+FT69txWR`qUC8`a# z>n*aPFz;H&jKP+uCRy~MggE1wwmdW6%*!=%@-D4?qB_5(_H&-yflx&BB3ij zF_wZMX<|xJ4c$%LT$g(mOaSn@0k|~SfykmXrdW-L5|lLS)fDAy3fxWtFQfo!wN`nB z6!4@#V+wSqz|$!ZO93+hO#6j(>oOA$rCr-dk%v^M1MZT+l0x+Qh4wY_10;U-a+-dnaDi@##0(;4WhGz)TbO{eL| zyNo3Z=GWAOdRWnR$*R;Zj~S6JcYUTb-ef8`3ka!VA)v^ylAIQ<-mEP7d5zL8s&MGt1-kcFAv`^R+10m!+ETI@JSE6ERsHrWSx zVl1W*0boisH0)wwU~ovtxdsFtfkjXbi^Q-V^LBUdV#}E<`(60leb0&jF4jDpzfk&t z*`hH?52Js%NRN)`;VH!X0c@{?W14~f6(fD)J|o63^PyNn2N6Hb@I(S54~j<<_$5oG zC+I_&R}eeH5pfLhDa6&IS@el4obUl9P*1C*N}3!NST4G?=(wy%79%ly20Kgg)8Ld6 z$C#~WIgSPTWN0V;uEE$}7!&;r*CIX@7Mnwes}NtGmiq!pd_QJ11ra|6Kvu>ld%cLU zn@QWa*3yd@^$;x*Z%g5C9*FexBfS*qZ>gh!uH@RQVcmzhgq29YACcNwek-t@1=(ot zL5zCnb}brY^5_E{ACvseh^rBQVKTat`G7j<8~SvJ(V0J24@7*-Pey;|8L^`qaXI2Y zN=hq}M_csYF{3-_17D#k0cIC%@qS6S;zd1tg%Rm#Lps{!6+`PuuH{dR%Ps9m`5#8K zHb!S_ug4UtN6|MM(c(cCJKD0>=aS3-(}vy@DYwN8ggq{}dE@pK%!Ey={nl&k;~NLAqjOEwI?iq|2I znj-KKQX^@d0{CziN7}?E=t=nUVNy?&FXKJ@di-wTck$c!jr=AM_#J#Bzn%9ZbrZiO zt-Bl7q#|Pee27*OdiJ74$&%~C*x$q$4G?jBA0?uor!_3d%8By$nhfWtHX?`5f9Y>o zxsq|^WLlzTK}NeiE9bU9**>sc#_^i%W?F|}yJ5SDpKF+#Of1n1Kbd7#DH$soT_E>h zEmBfmj8w5!PGJ^gj9JQxxh7l3xkEo+lCgG~KeOjz!MQ`@>WT6Um1RLX7PuRLqtzLz z=Zfbqp39!^cwY1TnSPc2nErzPg#MJi0|Gru-=x2yzoDn;&(iXr)gpcg%sVIS($FyE}-RG2~8?0;S|BEEvJ0NnfX{QSRW=jZRu zq5hiyXB9k8?$t)zZal=w#*`Sd&Rlp9n%k>4;F&!&MX~s_b#AjLuf_MZHw?35To7+n zqB@~SC^2*3sX(YRZm=tabt{>hIoNCO;r=kYAFw$lK&9xlV47o8%U` zP2MLTk$=!Sx|wdJKH5e*Xcrx%!&Ify^bkEvpQA_Ui}WRWoW4xINWVsZN3YOp^cMX) z{h0o<;6}mif;$BtJBTC4QRpajR5>1ZY;BP*BpNN}QdK z`}-1iYm=*NWUp3y-0aYpU8gO$^|U|$dR^3IbLG2ATotZrm&euMYIFr%gRZbkbxphW SyN)E*r{h|dn5Rb9^Zx-D`F5@V diff --git a/examples/menubar/menubar-1/images/separator.png b/examples/menubar/menubar-1/images/separator.png deleted file mode 100644 index a837fbce93afde0ba8abc83520cbf7bf42b1e924..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1134 zcmV-!1d;oRP)4Tx062|}Rb6NtRTMtEb7vzY&QokOg>Hg1+lHrgWS zWcKdPn90sKGrRqvPeo9CG3uKX#J{(IASm?@+di}}l?o-=)F3E6wD^Ni=!>T7nL9I? zX}YoAW$t|Qo$sD|?zw001?ah|SeB6#0T!CBEf+H4bBB+JJu8rehoBb*p;u8ID_yBf z0ya+zcePvJL&AGs+11_tpRKn>9TgyPA7ZoSs0)aX0r00)%XR^J`jH<$>RKN5V(7Oq zK*TS4xZz{h!*f1C3ECFkK$#7nA@pGN!$;%jYvwjAKwmYb0gKL(K8 z-kPtb5${A?tlI~wzMrJ6wTdBr=Y%%%EaEMQ&o}4FQ^DA)s*}Z>!FI&AHCpoWI|RUq zx?7s@$8!5^Q=anY%X@i5{QA6kNcMelpE>R6eCYFpmMsVTrI(b06~u#xf1yS} z_UGdMvD``!0~u->P=lA4?YN`hilQ|3tHka)7T{2CGqw zjZfMwx$5irQN_*|e4l)UHmiYuz74Yp1t^#>hrJ3-SOXDcC_o0^7T9R1gAN8V6s;5) zieI5-7aQlmJn}lUna#nz!j%5V$X|o`xX!dHWQRV27P1=rj;t2bW$~+pTw@bIek?Zv zKPDL<64`^#UNTAck#RBsB6*5DP4<%UA_FqU$I>2EH_cM;u)Q~SI+rg`Rn{L_AC5qq~L$#SMj%U z$6Cz0vP{G5Y*=%5RT^yu;}-DInZ=349rJPVM6C3K^oO)8y(fJr{l>k`ead~!ea?NsT>_Ci%bnxC;Vy6=b6>{xYV#Ue-+LB$ z7`JEXmTRm^AtP)R9u{)KHsMiWGV&)32xCG~*nyU<>-!d;FP=Re4r3qYr~6#KE>;1F z`>_J_P5xC?ROxV(DIHdCO*p$HRQI@7^PwV@Pvuf+5K}u-6REM(K@W$s zrgorh0{i?O)v0c>QtHxU-hBdD(>iYJ4b2sIOVX2K8m~4gmYVA5h^QEb$V`rCQ-|7Z zS{nuL-t>?3n=-o(6I(7vocj#GzCZEo`!3>+v;dYIfPu#&ZWzzX2i^rZ^Mu;6+rb@? zNPG+6)c5T6zxpzGe*M(x+{AON=PiJ>H#?ob-|uwRK0yDg0B4PV0id6JRZw95ZvX%Q z97#k$R5%f1U>F6%AOsk5a&rC;1AmNqVSqyb04qBJMQeR0N&o-=07*qoM6N<$g4BN| AA^-pY diff --git a/examples/menubar/menubar-1/js/MenubarItemLinks.js b/examples/menubar/menubar-1/js/MenubarItemLinks.js deleted file mode 100644 index 091c44df7c..0000000000 --- a/examples/menubar/menubar-1/js/MenubarItemLinks.js +++ /dev/null @@ -1,162 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ - -'use strict'; - -var MenubarItem = function (domNode, menuObj) { - - this.menu = menuObj; - this.domNode = domNode; - this.popupMenu = false; - - this.hasFocus = false; - this.hasHover = false; - - this.isMenubarItem = true; - - this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 - }); -}; - -MenubarItem.prototype.init = function () { - this.domNode.tabIndex = -1; - - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); - - // Initialize pop up menus - - var nextElement = this.domNode.nextElementSibling; - - if (nextElement && nextElement.tagName === 'UL') { - this.popupMenu = new PopupMenu(nextElement, this); - this.popupMenu.init(); - } - -}; - -MenubarItem.prototype.handleKeydown = function (event) { - var tgt = event.currentTarget, - char = event.key, - flag = false, - clickEvent; - - function isPrintableCharacter (str) { - return str.length === 1 && str.match(/\S/); - } - - switch (event.keyCode) { - case this.keyCode.SPACE: - case this.keyCode.RETURN: - case this.keyCode.DOWN: - if (this.popupMenu) { - this.popupMenu.open(); - this.popupMenu.setFocusToFirstItem(); - flag = true; - } - break; - - case this.keyCode.LEFT: - this.menu.setFocusToPreviousItem(this); - flag = true; - break; - - case this.keyCode.RIGHT: - this.menu.setFocusToNextItem(this); - flag = true; - break; - - case this.keyCode.UP: - if (this.popupMenu) { - this.popupMenu.open(); - this.popupMenu.setFocusToLastItem(); - flag = true; - } - break; - - case this.keyCode.HOME: - case this.keyCode.PAGEUP: - this.menu.setFocusToFirstItem(); - flag = true; - break; - - case this.keyCode.END: - case this.keyCode.PAGEDOWN: - this.menu.setFocusToLastItem(); - flag = true; - break; - - case this.keyCode.TAB: - this.popupMenu.close(true); - break; - - case this.keyCode.ESC: - this.popupMenu.close(true); - break; - - default: - if (isPrintableCharacter(char)) { - this.menu.setFocusByFirstCharacter(this, char); - flag = true; - } - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -MenubarItem.prototype.handleClick = function (event) { - if (this.popupMenu) { - // for menuitem with menu, prevent default anchor behavior on click - // (which jumps to top of page for href="#" in some browsers) - event.preventDefault(); - } -}; - -MenubarItem.prototype.setExpanded = function (value) { - if (value) { - this.domNode.setAttribute('aria-expanded', 'true'); - } - else { - this.domNode.setAttribute('aria-expanded', 'false'); - } -}; - -MenubarItem.prototype.handleFocus = function (event) { - this.menu.hasFocus = true; -}; - -MenubarItem.prototype.handleBlur = function (event) { - this.menu.hasFocus = false; -}; - -MenubarItem.prototype.handleMouseover = function (event) { - this.hasHover = true; - this.popupMenu.open(); -}; - -MenubarItem.prototype.handleMouseout = function (event) { - this.hasHover = false; - setTimeout(this.popupMenu.close.bind(this.popupMenu, false), 300); -}; diff --git a/examples/menubar/menubar-1/js/MenubarLinks.js b/examples/menubar/menubar-1/js/MenubarLinks.js deleted file mode 100644 index ef5e68ee01..0000000000 --- a/examples/menubar/menubar-1/js/MenubarLinks.js +++ /dev/null @@ -1,183 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ - -'use strict'; - -var Menubar = function (domNode) { - var elementChildren, - msgPrefix = 'Menubar constructor argument menubarNode '; - - // Check whether menubarNode is a DOM element - if (!(domNode instanceof Element)) { - throw new TypeError(msgPrefix + 'is not a DOM Element.'); - } - - // Check whether menubarNode has descendant elements - if (domNode.childElementCount === 0) { - throw new Error(msgPrefix + 'has no element children.'); - } - - // Check whether menubarNode has A elements - var e = domNode.firstElementChild; - while (e) { - var menubarItem = e.firstElementChild; - if (e && menubarItem && menubarItem.tagName !== 'A') { - throw new Error(msgPrefix + 'has child elements are not A elements.'); - } - e = e.nextElementSibling; - } - - this.isMenubar = true; - - this.domNode = domNode; - - this.menubarItems = []; // See Menubar init method - this.firstChars = []; // See Menubar init method - - this.firstItem = null; // See Menubar init method - this.lastItem = null; // See Menubar init method - - this.hasFocus = false; // See MenubarItem handleFocus, handleBlur - this.hasHover = false; // See Menubar handleMouseover, handleMouseout -}; - -/* -* @method Menubar.prototype.init -* -* @desc -* Adds ARIA role to the menubar node -* Traverse menubar children for A elements to configure each A element as a ARIA menuitem -* and populate menuitems array. Initialize firstItem and lastItem properties. -*/ -Menubar.prototype.init = function () { - var menubarItem, childElement, menuElement, textContent, numItems; - - - // Traverse the element children of menubarNode: configure each with - // menuitem role behavior and store reference in menuitems array. - var elem = this.domNode.firstElementChild; - - while (elem) { - menuElement = elem.firstElementChild; - - if (elem && menuElement && menuElement.tagName === 'A') { - menubarItem = new MenubarItem(menuElement, this); - menubarItem.init(); - this.menubarItems.push(menubarItem); - textContent = menuElement.textContent.trim(); - this.firstChars.push(textContent.substring(0, 1).toLowerCase()); - } - - elem = elem.nextElementSibling; - } - - // Use populated menuitems array to initialize firstItem and lastItem. - numItems = this.menubarItems.length; - if (numItems > 0) { - this.firstItem = this.menubarItems[ 0 ]; - this.lastItem = this.menubarItems[ numItems - 1 ]; - } - this.firstItem.domNode.tabIndex = 0; -}; - -/* FOCUS MANAGEMENT METHODS */ - -Menubar.prototype.setFocusToItem = function (newItem) { - var flag = false; - - for (var i = 0; i < this.menubarItems.length; i++) { - var mbi = this.menubarItems[i]; - - if (mbi.domNode.tabIndex == 0) { - flag = mbi.domNode.getAttribute('aria-expanded') === 'true'; - } - - mbi.domNode.tabIndex = -1; - if (mbi.popupMenu) { - mbi.popupMenu.close(); - } - } - - newItem.domNode.focus(); - newItem.domNode.tabIndex = 0; - - if (flag && newItem.popupMenu) { - newItem.popupMenu.open(); - } -}; - -Menubar.prototype.setFocusToFirstItem = function (flag) { - this.setFocusToItem(this.firstItem); -}; - -Menubar.prototype.setFocusToLastItem = function (flag) { - this.setFocusToItem(this.lastItem); -}; - -Menubar.prototype.setFocusToPreviousItem = function (currentItem) { - var index, newItem; - - if (currentItem === this.firstItem) { - newItem = this.lastItem; - } - else { - index = this.menubarItems.indexOf(currentItem); - newItem = this.menubarItems[ index - 1 ]; - } - - this.setFocusToItem(newItem); - -}; - -Menubar.prototype.setFocusToNextItem = function (currentItem) { - var index, newItem; - - if (currentItem === this.lastItem) { - newItem = this.firstItem; - } - else { - index = this.menubarItems.indexOf(currentItem); - newItem = this.menubarItems[ index + 1 ]; - } - - this.setFocusToItem(newItem); - -}; - -Menubar.prototype.setFocusByFirstCharacter = function (currentItem, char) { - var start, index; - var flag = currentItem.domNode.getAttribute('aria-expanded') === 'true'; - - char = char.toLowerCase(); - - // Get start index for search based on position of currentItem - start = this.menubarItems.indexOf(currentItem) + 1; - if (start === this.menubarItems.length) { - start = 0; - } - - // Check remaining slots in the menu - index = this.getIndexFirstChars(start, char); - - // If not found in remaining slots, check from beginning - if (index === -1) { - index = this.getIndexFirstChars(0, char); - } - - // If match was found... - if (index > -1) { - this.setFocusToItem(this.menubarItems[ index ]); - } -}; - -Menubar.prototype.getIndexFirstChars = function (startIndex, char) { - for (var i = startIndex; i < this.firstChars.length; i++) { - if (char === this.firstChars[ i ]) { - return i; - } - } - return -1; -}; - diff --git a/examples/menubar/menubar-1/js/PopupMenuItemLinks.js b/examples/menubar/menubar-1/js/PopupMenuItemLinks.js deleted file mode 100644 index 2e9e661580..0000000000 --- a/examples/menubar/menubar-1/js/PopupMenuItemLinks.js +++ /dev/null @@ -1,212 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ - -'use strict'; - -var MenuItem = function (domNode, menuObj) { - - if (typeof menuObj !== 'object') { - menuObj = false; - } - - this.domNode = domNode; - this.menu = menuObj; - this.popupMenu = false; - this.isMenubarItem = false; - - this.keyCode = Object.freeze({ - 'TAB': 9, - 'RETURN': 13, - 'ESC': 27, - 'SPACE': 32, - 'PAGEUP': 33, - 'PAGEDOWN': 34, - 'END': 35, - 'HOME': 36, - 'LEFT': 37, - 'UP': 38, - 'RIGHT': 39, - 'DOWN': 40 - }); -}; - -MenuItem.prototype.init = function () { - this.domNode.tabIndex = -1; - - this.domNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.domNode.addEventListener('click', this.handleClick.bind(this)); - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); - - // Initialize flyout menu - - var nextElement = this.domNode.nextElementSibling; - - if (nextElement && nextElement.tagName === 'UL') { - this.popupMenu = new PopupMenu(nextElement, this); - this.popupMenu.init(); - } - -}; - -MenuItem.prototype.isExpanded = function () { - return this.domNode.getAttribute('aria-expanded') === 'true'; -}; - -/* EVENT HANDLERS */ - -MenuItem.prototype.handleKeydown = function (event) { - var tgt = event.currentTarget, - char = event.key, - flag = false, - clickEvent; - - function isPrintableCharacter (str) { - return str.length === 1 && str.match(/\S/); - } - - switch (event.keyCode) { - case this.keyCode.SPACE: - case this.keyCode.RETURN: - if (this.popupMenu) { - this.popupMenu.open(); - this.popupMenu.setFocusToFirstItem(); - } - else { - - // Create simulated mouse event to mimic the behavior of ATs - // and let the event handler handleClick do the housekeeping. - try { - clickEvent = new MouseEvent('click', { - 'view': window, - 'bubbles': true, - 'cancelable': true - }); - } - catch (err) { - if (document.createEvent) { - // DOM Level 3 for IE 9+ - clickEvent = document.createEvent('MouseEvents'); - clickEvent.initEvent('click', true, true); - } - } - tgt.dispatchEvent(clickEvent); - } - - flag = true; - break; - - case this.keyCode.UP: - this.menu.setFocusToPreviousItem(this); - flag = true; - break; - - case this.keyCode.DOWN: - this.menu.setFocusToNextItem(this); - flag = true; - break; - - case this.keyCode.LEFT: - this.menu.setFocusToController('previous', true); - this.menu.close(true); - flag = true; - break; - - case this.keyCode.RIGHT: - if (this.popupMenu) { - this.popupMenu.open(); - this.popupMenu.setFocusToFirstItem(); - } - else { - this.menu.setFocusToController('next', true); - this.menu.close(true); - } - flag = true; - break; - - case this.keyCode.HOME: - case this.keyCode.PAGEUP: - this.menu.setFocusToFirstItem(); - flag = true; - break; - - case this.keyCode.END: - case this.keyCode.PAGEDOWN: - this.menu.setFocusToLastItem(); - flag = true; - break; - - case this.keyCode.ESC: - this.menu.setFocusToController(); - this.menu.close(true); - flag = true; - break; - - case this.keyCode.TAB: - this.menu.setFocusToController(); - break; - - default: - if (isPrintableCharacter(char)) { - this.menu.setFocusByFirstCharacter(this, char); - flag = true; - } - break; - } - - if (flag) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -MenuItem.prototype.setExpanded = function (value) { - if (value) { - this.domNode.setAttribute('aria-expanded', 'true'); - } - else { - this.domNode.setAttribute('aria-expanded', 'false'); - } -}; - -MenuItem.prototype.handleClick = function (event) { - this.menu.setFocusToController(); - this.menu.close(true); - if (this.popupMenu) { - // for menuitem with menu, prevent default anchor behavior on click - // (which jumps to top of page for href="#" in some browsers) - event.preventDefault(); - } -}; - -MenuItem.prototype.handleFocus = function (event) { - this.menu.hasFocus = true; -}; - -MenuItem.prototype.handleBlur = function (event) { - this.menu.hasFocus = false; - setTimeout(this.menu.close.bind(this.menu, false), 300); -}; - -MenuItem.prototype.handleMouseover = function (event) { - this.menu.hasHover = true; - this.menu.open(); - if (this.popupMenu) { - this.popupMenu.hasHover = true; - this.popupMenu.open(); - } -}; - -MenuItem.prototype.handleMouseout = function (event) { - if (this.popupMenu) { - this.popupMenu.hasHover = false; - this.popupMenu.close(true); - } - - this.menu.hasHover = false; - setTimeout(this.menu.close.bind(this.menu, false), 300); -}; diff --git a/examples/menubar/menubar-1/js/PopupMenuLinks.js b/examples/menubar/menubar-1/js/PopupMenuLinks.js deleted file mode 100644 index d0edfd6404..0000000000 --- a/examples/menubar/menubar-1/js/PopupMenuLinks.js +++ /dev/null @@ -1,264 +0,0 @@ -/* -* This content is licensed according to the W3C Software License at -* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document -*/ - -'use strict'; - -var PopupMenu = function (domNode, controllerObj) { - var elementChildren, - msgPrefix = 'PopupMenu constructor argument domNode '; - - // Check whether domNode is a DOM element - if (!(domNode instanceof Element)) { - throw new TypeError(msgPrefix + 'is not a DOM Element.'); - } - // Check whether domNode has child elements - if (domNode.childElementCount === 0) { - throw new Error(msgPrefix + 'has no element children.'); - } - // Check whether domNode descendant elements have A elements - var childElement = domNode.firstElementChild; - while (childElement) { - var menuitem = childElement.firstElementChild; - if (menuitem && menuitem === 'A') { - throw new Error(msgPrefix + 'has descendant elements that are not A elements.'); - } - childElement = childElement.nextElementSibling; - } - - this.isMenubar = false; - - this.domNode = domNode; - this.controller = controllerObj; - - this.menuitems = []; // See PopupMenu init method - this.firstChars = []; // See PopupMenu init method - - this.firstItem = null; // See PopupMenu init method - this.lastItem = null; // See PopupMenu init method - - this.hasFocus = false; // See MenuItem handleFocus, handleBlur - this.hasHover = false; // See PopupMenu handleMouseover, handleMouseout -}; - -/* -* @method PopupMenu.prototype.init -* -* @desc -* Add domNode event listeners for mouseover and mouseout. Traverse -* domNode children to configure each menuitem and populate menuitems -* array. Initialize firstItem and lastItem properties. -*/ -PopupMenu.prototype.init = function () { - var childElement, menuElement, menuItem, textContent, numItems, label; - - // Configure the domNode itself - - this.domNode.addEventListener('mouseover', this.handleMouseover.bind(this)); - this.domNode.addEventListener('mouseout', this.handleMouseout.bind(this)); - - // Traverse the element children of domNode: configure each with - // menuitem role behavior and store reference in menuitems array. - childElement = this.domNode.firstElementChild; - - while (childElement) { - menuElement = childElement.firstElementChild; - - if (menuElement && menuElement.tagName === 'A') { - menuItem = new MenuItem(menuElement, this); - menuItem.init(); - this.menuitems.push(menuItem); - textContent = menuElement.textContent.trim(); - this.firstChars.push(textContent.substring(0, 1).toLowerCase()); - } - childElement = childElement.nextElementSibling; - } - - // Use populated menuitems array to initialize firstItem and lastItem. - numItems = this.menuitems.length; - if (numItems > 0) { - this.firstItem = this.menuitems[ 0 ]; - this.lastItem = this.menuitems[ numItems - 1 ]; - } -}; - -/* EVENT HANDLERS */ - -PopupMenu.prototype.handleMouseover = function (event) { - this.hasHover = true; -}; - -PopupMenu.prototype.handleMouseout = function (event) { - this.hasHover = false; - setTimeout(this.close.bind(this, false), 1); -}; - -/* FOCUS MANAGEMENT METHODS */ - -PopupMenu.prototype.setFocusToController = function (command, flag) { - - if (typeof command !== 'string') { - command = ''; - } - - function setFocusToMenubarItem (controller, close) { - while (controller) { - if (controller.isMenubarItem) { - controller.domNode.focus(); - return controller; - } - else { - if (close) { - controller.menu.close(true); - } - controller.hasFocus = false; - } - controller = controller.menu.controller; - } - return false; - } - - if (command === '') { - if (this.controller && this.controller.domNode) { - this.controller.domNode.focus(); - } - return; - } - - if (!this.controller.isMenubarItem) { - this.controller.domNode.focus(); - this.close(); - - if (command === 'next') { - var menubarItem = setFocusToMenubarItem(this.controller, false); - if (menubarItem) { - menubarItem.menu.setFocusToNextItem(menubarItem, flag); - } - } - } - else { - if (command === 'previous') { - this.controller.menu.setFocusToPreviousItem(this.controller, flag); - } - else if (command === 'next') { - this.controller.menu.setFocusToNextItem(this.controller, flag); - } - } - -}; - -PopupMenu.prototype.setFocusToFirstItem = function () { - this.firstItem.domNode.focus(); -}; - -PopupMenu.prototype.setFocusToLastItem = function () { - this.lastItem.domNode.focus(); -}; - -PopupMenu.prototype.setFocusToPreviousItem = function (currentItem) { - var index; - - if (currentItem === this.firstItem) { - this.lastItem.domNode.focus(); - } - else { - index = this.menuitems.indexOf(currentItem); - this.menuitems[ index - 1 ].domNode.focus(); - } -}; - -PopupMenu.prototype.setFocusToNextItem = function (currentItem) { - var index; - - if (currentItem === this.lastItem) { - this.firstItem.domNode.focus(); - } - else { - index = this.menuitems.indexOf(currentItem); - this.menuitems[ index + 1 ].domNode.focus(); - } -}; - -PopupMenu.prototype.setFocusByFirstCharacter = function (currentItem, char) { - var start, index; - - char = char.toLowerCase(); - - // Get start index for search based on position of currentItem - start = this.menuitems.indexOf(currentItem) + 1; - if (start === this.menuitems.length) { - start = 0; - } - - // Check remaining slots in the menu - index = this.getIndexFirstChars(start, char); - - // If not found in remaining slots, check from beginning - if (index === -1) { - index = this.getIndexFirstChars(0, char); - } - - // If match was found... - if (index > -1) { - this.menuitems[ index ].domNode.focus(); - } -}; - -PopupMenu.prototype.getIndexFirstChars = function (startIndex, char) { - for (var i = startIndex; i < this.firstChars.length; i++) { - if (char === this.firstChars[ i ]) { - return i; - } - } - return -1; -}; - -/* MENU DISPLAY METHODS */ - -PopupMenu.prototype.open = function () { - // Get position and bounding rectangle of controller object's DOM node - var rect = this.controller.domNode.getBoundingClientRect(); - - // Set CSS properties - if (!this.controller.isMenubarItem) { - this.domNode.parentNode.style.position = 'relative'; - this.domNode.style.display = 'block'; - this.domNode.style.position = 'absolute'; - this.domNode.style.left = rect.width + 'px'; - this.domNode.style.zIndex = 100; - } - else { - this.domNode.style.display = 'block'; - this.domNode.style.position = 'absolute'; - this.domNode.style.top = (rect.height - 1) + 'px'; - this.domNode.style.zIndex = 100; - } - - this.controller.setExpanded(true); - -}; - -PopupMenu.prototype.close = function (force) { - - var controllerHasHover = this.controller.hasHover; - - var hasFocus = this.hasFocus; - - for (var i = 0; i < this.menuitems.length; i++) { - var mi = this.menuitems[i]; - if (mi.popupMenu) { - hasFocus = hasFocus | mi.popupMenu.hasFocus; - } - } - - if (!this.controller.isMenubarItem) { - controllerHasHover = false; - } - - if (force || (!hasFocus && !this.hasHover && !controllerHasHover)) { - this.domNode.style.display = 'none'; - this.domNode.style.zIndex = 0; - this.controller.setExpanded(false); - } -}; diff --git a/examples/menubar/menubar-1/mb-about.html b/examples/menubar/menubar-1/mb-about.html deleted file mode 100644 index c8a2d369cb..0000000000 --- a/examples/menubar/menubar-1/mb-about.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - Menubar Example Landing Page: About - - - - - -
    -

    Menubar Example Landing Page

    -
    -
    -

    About

    -

    Back to menubar example - -

    Overview

    -

    Back to menubar example - -

    Administration

    -

    Back to menubar example - -

    Facts

    -

    Back to menubar example - -

    Campus Tours

    -

    Back to menubar example - -

    - - - diff --git a/examples/menubar/menubar-1/mb-academics.html b/examples/menubar/menubar-1/mb-academics.html deleted file mode 100644 index c0a80d69d0..0000000000 --- a/examples/menubar/menubar-1/mb-academics.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - Menubar Example Landing Page: Academics - - - - - -
    -

    Menubar Example Landing Page

    -
    - -
    -

    Academics

    -

    Back to menubar example - -

    Colleges & Schools

    -

    Back to menubar example - - -

    Programs of Study

    -

    Back to menubar example - - -

    Honors Programs

    -

    Back to menubar example - - -

    Online Courses

    -

    Back to menubar example - - -

    Course Explorer

    -

    Back to menubar example - - -

    Register for Class

    -

    Back to menubar example - - -

    Academic Calendar

    -

    Back to menubar example - - -

    Transcripts

    -

    Back to menubar example - -

    - - - diff --git a/examples/menubar/menubar-1/mb-admissions.html b/examples/menubar/menubar-1/mb-admissions.html deleted file mode 100644 index b2805416c6..0000000000 --- a/examples/menubar/menubar-1/mb-admissions.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - Menubar Example Landing Page: Admissions - - - - - -
    -

    Menubar Example Landing Page

    -
    -
    -

    Admissions

    -

    Back to menubar example - -

    Apply

    -

    Back to menubar example - -

    Tuition

    -

    Back to menubar example - -

    Sign Up

    -

    Back to menubar example - -

    Visit

    -

    Back to menubar example - -

    Photo Tour

    -

    Back to menubar example - -

    Connect

    -

    Back to menubar example - - -

    - - - \ No newline at end of file diff --git a/examples/menubar/menubar-1/menubar-1.html b/examples/menubar/menubar-1/menubar-1.html deleted file mode 100644 index 9b72510beb..0000000000 --- a/examples/menubar/menubar-1/menubar-1.html +++ /dev/null @@ -1,658 +0,0 @@ - - - - -Navigation Menubar Example | WAI-ARIA Authoring Practices 1.2 - - - - - - - - - - - - - - - - - - -
    -

    Navigation Menubar Example

    -

    - The following implementation of the - design pattern for menubar - demonstrates a menubar that provides site navigation menus. - Each item in the menubar represents a section of a web site for a mythical university and opens a submenu containing menu items that link to pages within that section. -

    -

    Similar examples include:

    - - -
    -

    Example

    - -
    - - - - -
    - -
    - -
    -

    Accessibility Features

    -
      -
    1. Since the menubar presents a site navigation system, it is wrapped in a navigation region implemented with a nav element that has an aria-label that matches the label on the menubar.
    2. -
    3. The down arrow and right arrow icons are made compatible with high contrast mode and hidden from screen readers by using the CSS content property to render images.
    4. -
    -
    - -
    -

    Keyboard Support

    -

    Menubar

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    KeyFunction
    - Space
    Enter -
    Opens submenu and moves focus to first item in the submenu.
    - Right Arrow - -
      -
    • Moves focus to the next item in the menubar.
    • -
    • If focus is on the last item, moves focus to the first item.
    • -
    -
    - Left Arrow - -
      -
    • Moves focus to the previous item in the menubar.
    • -
    • If focus is on the first item, moves focus to the last item.
    • -
    -
    - Down Arrow - Opens submenu and moves focus to first item in the submenu.
    - Up Arrow - Opens submenu and moves focus to last item in the submenu.
    - Home - Moves focus to first item in the menubar.
    - End - Moves focus to last item in the menubar.
    - Character - -
      -
    • Moves focus to next item in the menubar having a name that starts with the typed character.
    • -
    • If none of the items have a name starting with the typed character, focus does not move.
    • -
    -
    -

    Submenu

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    KeyFunction
    - Space
    Enter -
    -
      -
    • Activates menu item, causing the link to be activated.
    • -
    • NOTE: the links go to dummy pages; use the browser go-back function to return to this menubar example page.
    • -
    -
    - Escape - -
      -
    • Closes submenu.
    • -
    • Moves focus to parent menubar item.
    • -
    -
    - Right Arrow - -
      -
    • If focus is on an item with a submenu, opens the submenu and places focus on the first item.
    • -
    • If focus is on an item that does not have a submenu: -
        -
      • Closes submenu.
      • -
      • Moves focus to next item in the menubar.
      • -
      • Opens submenu of newly focused menubar item, keeping focus on that parent menubar item.
      • -
      -
    • -
    -
    - Left Arrow - -
      -
    • Closes submenu and moves focus to parent menu item.
    • -
    • If parent menu item is in the menubar, also: -
        -
      • moves focus to previous item in the menubar.
      • -
      • Opens submenu of newly focused menubar item, keeping focus on that parent menubar item.
      • -
      -
    • -
    -
    - Down Arrow - -
      -
    • Moves focus to the next item in the submenu.
    • -
    • If focus is on the last item, moves focus to the first item.
    • -
    -
    - Up Arrow - -
      -
    • Moves focus to previous item in the submenu.
    • -
    • If focus is on the first item, moves focus to the last item.
    • -
    -
    - Home - Moves focus to the first item in the submenu.
    - End - Moves focus to the last item in the submenu.
    - Character - -
      -
    • Moves focus to the next item having a name that starts with the typed character.
    • -
    • If none of the items have a name starting with the typed character, focus does not move.
    • -
    -
    -
    - -
    -

    Role, Property, State, and Tabindex Attributes

    -

    Menubar

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    RoleAttributeElementUsage
    - menubar - - ul - -
      -
    • Identifies the element as a menubar container for a set of menuitem elements.
    • -
    • Is not focusable because focus is managed using roving tabindex.
    • -
    -
    - aria-label="string" - - - ul - -
      -
    • - Defines an accessible name for the menubar. -
    • -
    • Helps assistive technology users understand the purpose of the menubar and - distinguish it from any other menubars or similar elements on the page.
    • -
    -
    - menuitem - - a - -
      -
    • Identifies the element as a menu item.
    • -
    • The accessible name is calculated from the text content of the a element.
    • -
    -
    - tabindex="-1" - - a - - Makes the a element keyboard focusable, but not part of the tab sequence. -
    - tabindex="0" - - a - -
      -
    • Includes the element in the Tab sequence.
    • -
    • Only one menubar item has tabindex="0".
    • -
    • On page load, the first menubar item has tabindex="0".
    • -
    • Focus is managed using roving tabindex.
    • -
    -
    - aria-haspopup="true" - - a - Indicates the menuitem has a submenu.
    - aria-expanded="true" - - a - Indicates the submenu is open.
    - aria-expanded="false" - - a - Indicates the submenu is closed.
    - none - - li - -
      -
    • Removes the implied listitem role of the li element.
    • -
    • Necessary because the parent ul is serving as a menu so the li elements are not in their required list context.
    • -
    -
    -

    Submenu

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    RoleAttributeElementUsage
    - menu - - ul - Identifies the element as a menu container for a set of menu items.
    - aria-label="string" - - - ul - -
      -
    • - Defines an accessible name for the menu. -
    • -
    • Helps assistive technology users understand the purpose of the menu and - distinguish it from any other menu or similar elements (e.g. menubar) on the page.
    • -
    -
    - menuitem - - a - -
      -
    • Identifies the element as a menu item.
    • -
    • The accessible name is calculated from the text content of the a element.
    • -
    -
    - tabindex="-1" - - a - Keeps the a element focusable but removes it from the Tab sequence.
    - aria-haspopup="true" - - a - Indicates the menu item has a submenu.
    - aria-expanded="true" - - a - Indicates the submenu is open.
    - aria-expanded="false" - - a - Indicates the submenu is closed.
    - none - - li - -
      -
    • Removes the implied listitem role of the li element.
    • -
    • Necessary because the parent ul is serving as a menu so the li elements are not in their required list context.
    • -
    -
    -
    - -
    -

    Javascript and CSS Source Code

    - -
    - -
    -

    HTML Source Code

    - -
    - - - - - -
    -
    - - - diff --git a/test/tests/menubar_menubar-1.js b/test/tests/menubar_menubar-1.js deleted file mode 100644 index ebaae9b8be..0000000000 --- a/test/tests/menubar_menubar-1.js +++ /dev/null @@ -1,1226 +0,0 @@ -'use strict'; - -const { ariaTest } = require('..'); -const { By, Key } = require('selenium-webdriver'); -const assertAttributeValues = require('../util/assertAttributeValues'); -const assertAriaLabelExists = require('../util/assertAriaLabelExists'); -const assertAriaRoles = require('../util/assertAriaRoles'); -const assertRovingTabindex = require('../util/assertRovingTabindex'); - -const exampleFile = 'menubar/menubar-1/menubar-1.html'; - -const ex = { - // menubar selector - menubarSelector: '#ex1 [role="menubar"]', - - // menu selectors - anyMenuSelector: '#ex1 [role="menu"]', - menuSelector: '#ex1 [role="menubar"]>li>[role="menu"]', - - // menuitem selectors - menubarMenuitemSelector: '#ex1 [role="menubar"]>li>[role="menuitem"]', - anyMenuMenuitemSelector: '#ex1 [role="menu"]>li>[role="menuitem"]', - menuMenuitemSelectors: [ - '#ex1 [role="menubar"]>li:nth-of-type(1)>[role="menu"]>li>[role="menuitem"]', - '#ex1 [role="menubar"]>li:nth-of-type(2)>[role="menu"]>li>[role="menuitem"]', - '#ex1 [role="menubar"]>li:nth-of-type(3)>[role="menu"]>li>[role="menuitem"]' - ], - groupSelector: '#ex1 [role="group"]', - numMenus: 3, - numSubmenus: 3, - numTotalMenus: 6, - submenuLocations: [ - // [, ] - [0, 2], - [0, 3], - [1, 1] - ], - numMenuMenuitems: [4, 6, 8] -}; - -// Returns specified submenu -const getSubmenuSelector = function (menuIndex, menuitemIndex) { - return '#ex1 [role="menubar"]>li:nth-of-type(' + - (menuIndex + 1) + - ')>[role="menu"]>li:nth-of-type(' + - (menuitemIndex + 1) + - ')>[role="menu"]'; -}; - -// Returns the menuitems of a specified submenu -const getSubmenuMenuitemSelector = function (menuIndex, menuitemIndex) { - return '#ex1 [role="menubar"]>li:nth-of-type(' + - (menuIndex + 1) + - ')>[role="menu"]>li:nth-of-type(' + - (menuitemIndex + 1) + - ')>[role="menu"] [role="menuitem"]'; -}; - -const openSubmenu = async function (t, menuIndex, menuitemIndex) { - // Send ARROW_DOWN to open menu - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - await menubaritems[menuIndex].sendKeys(Key.ARROW_DOWN); - - // Get the menuitems for that menu and send ARROW_RIGHT to open the submenu - const menuitems = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - await menuitems[menuitemIndex].sendKeys(Key.ARROW_RIGHT); - return; -}; - -const waitForUrlChange = async function (t) { - return t.context.session.wait(() => { - return t.context.session.getCurrentUrl().then(url => { - return url != t.context.url; - }); - }, t.context.waitTime).catch(() => {}); -}; - -const exampleInitialized = async function (t) { - const initializedSelector = ex.menubarMenuitemSelector + '[tabindex="0"]'; - - await t.context.session.wait(async function () { - const els = await t.context.queryElements(t, initializedSelector); - return els.length === 1; - }, t.context.waitTime, 'Timeout waiting for example to initialize'); -}; - -const checkFocus = async function (t, selector, index) { - return t.context.session.executeScript(function () { - const [selector, index] = arguments; - const items = document.querySelectorAll(selector); - return items[index] === document.activeElement; - }, selector, index); -}; - -const doesMenuitemHaveSubmenu = function (menuIndex, menuitemIndex) { - for (let submenuLocation of ex.submenuLocations) { - if ( - submenuLocation[0] === menuIndex && - submenuLocation[1] === menuitemIndex - ) { - return true; - } - } - return false; -}; - - -// Attributes - -ariaTest('Test for role="menubar" on ul', exampleFile, 'menubar-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'menubar', 1, 'ul'); -}); - -ariaTest('Test aria-label on menubar', exampleFile, 'menubar-aria-label', async (t) => { - await assertAriaLabelExists(t, ex.menubarSelector); -}); - -ariaTest('Test for role="menuitem" in menubar', exampleFile, 'menuitem-role', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - t.is( - menuitems.length, - ex.numMenus, - '"role=menuitem" elements should be found by selector: ' + ex.menubarMenuitemSelector - ); - - for (let menuitem of menuitems) { - t.truthy( - await menuitem.getText(), - '"role=menuitem" elements should all have accessible text content: ' + ex.menubarMenuitemSelector - ); - } -}); - -ariaTest('Test roving tabindex', exampleFile, 'menuitem-tabindex', async (t) => { - - - // Wait for roving tabindex to be initialized by the javascript - await exampleInitialized(t); - - await assertRovingTabindex(t, ex.menubarMenuitemSelector, Key.ARROW_RIGHT); -}); - -ariaTest('Test aria-haspopup set to true on menuitems', - exampleFile, 'menuitem-aria-haspopup', async (t) => { - - - await assertAttributeValues(t, ex.menubarMenuitemSelector, 'aria-haspopup', 'true'); - }); - -ariaTest('"aria-expanded" attribute on menubar menuitem', exampleFile, 'menuitem-aria-expanded', async (t) => { - - // Before interating with page, make sure aria-expanded is set to false - await assertAttributeValues(t, ex.menubarMenuitemSelector, 'aria-expanded', 'false'); - - // AND make sure no submenus are visible - const submenus = await t.context.queryElements(t, ex.menuSelector); - for (let submenu of submenus) { - t.false( - await submenu.isDisplayed(), - 'No submenus (found by selector: ' + ex.menuSelector + ') should be displayed on load' - ); - } - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - for (let menuIndex = 0; menuIndex < menuitems.length; menuIndex++) { - - // Send ARROW_DOWN to open submenu - await menuitems[menuIndex].sendKeys(Key.ARROW_DOWN); - - for (let item = 0; item < menuitems.length; item++) { - - // Test attribute "aria-expanded" is only set for the opened submenu - const displayed = menuIndex === item ? true : false; - t.is( - await menuitems[item].getAttribute('aria-expanded'), - displayed.toString(), - 'focus is on element ' + menuIndex + ' of elements "' + ex.menubarMenuitemSelector + - '", therefore "aria-expanded" on menuitem ' + item + ' should be ' + displayed - ); - - // Test the submenu is indeed displayed - t.is( - await submenus[item].isDisplayed(), - displayed, - 'focus is on element ' + menuIndex + ' of elements "' + ex.menubarMenuitemSelector + - '", therefore isDisplay of submenu ' + item + ' should return ' + displayed - ); - } - - // Send the ESCAPE to close submenu - await menuitems[menuIndex].sendKeys(Key.ESCAPE); - } -}); - -ariaTest('Test for role="none" on menubar li', exampleFile, 'none-role', async (t) => { - - const liElements = await t.context.queryElements(t, ex.menubarSelector + '>li'); - - for (let liElement of liElements) { - t.is( - await liElement.getAttribute('role'), - 'none', - '"role=none" should be found on all list elements that are immediate descendants of: ' + ex.menubarSelector - ); - } -}); - -ariaTest('Test for role="menu" on ul', exampleFile, 'menu-role', async (t) => { - await assertAriaRoles(t, 'ex1', 'menu', ex.numTotalMenus, 'ul'); -}); - -ariaTest('Test for aria-label on role="menu"', exampleFile, 'menu-aria-label', async (t) => { - await assertAriaLabelExists(t, ex.anyMenuSelector); -}); - -ariaTest('Test for submenu role="menuitem"s with accessible names', exampleFile, 'sub-menuitem-role', async (t) => { - - - const menuitems = await t.context.queryElements(t, ex.anyMenuMenuitemSelector); - - t.truthy( - menuitems.length, - '"role=menuitem" elements should be found by selector: ' + ex.anyMenuMenuitemSelector - ); - - // Test the accessible name of each menuitem - - for (let menuitem of menuitems) { - - // The menuitem is not visible, so we cannot use selenium's "getText" function - const menutext = await t.context.session.executeScript(function () { - const el = arguments[0]; - return el.innerHTML; - }, menuitem); - - t.truthy( - menutext, - '"role=menuitem" elements should all have accessible text content: ' + ex.anyMenuMenuitemSelector - ); - } -}); - -ariaTest('Test tabindex="-1" on submenu role="menuitem"s', exampleFile, 'sub-menuitem-tabindex', async (t) => { - await assertAttributeValues(t, ex.anyMenuMenuitemSelector, 'tabindex', '-1'); -}); - -ariaTest('Test aria-haspopup on menuitems with submenus', exampleFile, 'sub-menuitem-aria-haspopup', async (t) => { - - const menubarMenuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - const menuItems = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - - for (let menuitemIndex = 0; menuitemIndex < menuItems.length; menuitemIndex++) { - const menuitemHasSubmenu = doesMenuitemHaveSubmenu(menuIndex, menuitemIndex); - - const ariaPopup = menuitemHasSubmenu ? 'true' : null; - const hasAriaPopupMsg = menuitemHasSubmenu ? - 'aria-haspop set to "true".' : - 'no aria-haspop attribute.'; - - t.is( - await menuItems[menuitemIndex].getAttribute('aria-haspopup'), - ariaPopup, - 'menuitem at index ' + menuitemIndex + ' in menu at index ' + menuIndex + ' is expected ' + - 'to have ' + hasAriaPopupMsg - ); - } - } -}); - -ariaTest('Test aria-expanded on menuitems with submenus', exampleFile, 'sub-menuitem-aria-expanded', async (t) => { - - const menubarMenuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Send ARROW_DOWN to open menu - await menubarMenuitems[menuIndex].sendKeys(Key.ARROW_DOWN); - - // Get the menuitems for that menu - const menuitems = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - - // Get the submenu associate with the menuitem we are testing - const submenuSelector = getSubmenuSelector(menuIndex, menuitemIndex); - - t.is( - await menuitems[menuitemIndex].getAttribute('aria-expanded'), - 'false', - 'menuitem at index ' + menuitemIndex + ' in menu at index ' + menuIndex + ' is expected ' + - 'to have aria-expanded="false" after opening the menu that contains it' - ); - t.false( - await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), - 'submenu attached to menuitem at index ' + menuitemIndex + ' in menu at index ' + - menuIndex + ' is expected to not be displayed after opening the menu that contains the menuitem' - ); - - // Send ARROW_RIGHT to the menuitem we are testing - await menuitems[menuitemIndex].sendKeys(Key.ARROW_RIGHT); - - t.is( - await menuitems[menuitemIndex].getAttribute('aria-expanded'), - 'true', - 'menuitem at index ' + menuitemIndex + ' in menu at index ' + menuIndex + ' is expected ' + - 'to have aria-expanded="true" after sending right arrow to it' - ); - t.true( - await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), - 'submenu attached to menuitem at index ' + menuitemIndex + ' in menu at index ' + - menuIndex + ' is expected to be displayed after sending left arrow to associated menuitem' - ); - } - -}); - -ariaTest('Test for role="none" on menu lis', exampleFile, 'sub-none-role', async (t) => { - - const liElements = await t.context.queryElements(t, ex.anyMenuSelector + '>li'); - - for (let liElement of liElements) { - if (await liElement.getAttribute('role') !== 'separator') { - t.is( - await liElement.getAttribute('role'), - 'none', - '"role=none" should be found on all list elements that are immediate descendants of: ' + ex.anyMenuSelector - ); - } - } -}); - - -// KEYS - -ariaTest('Key ENTER open submenu', exampleFile, 'menubar-space-or-enter', async (t) => { - - const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the ENTER key - await menuitems[menuIndex].sendKeys(Key.ENTER); - - // Test that the submenu is displayed - t.true( - await menus[menuIndex].isDisplayed(), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' - ); - - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' - ); - } -}); - - -ariaTest('Key SPACE open submenu', exampleFile, 'menubar-space-or-enter', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the SPACE key - await menubaritems[menuIndex].sendKeys(Key.SPACE); - - // Test that the submenu is displayed - t.true( - await menus[menuIndex].isDisplayed(), - 'Sending key "SPACE" to menuitem ' + menuIndex + ' in menubar should display submenu' - ); - - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), - 'Sending key "SPACE" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' - ); - - } -}); - -ariaTest('Key ARROW_RIGHT moves focus to next menubar item', - exampleFile, 'menubar-right-arrow', async (t) => { - - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - for (let menuIndex = 0; menuIndex < ex.numMenus + 1; menuIndex++) { - - const currentIndex = menuIndex % ex.numMenus; - const nextIndex = (menuIndex + 1) % ex.numMenus; - - // Send the ARROW_RIGHT key - await menubaritems[currentIndex].sendKeys(Key.ARROW_RIGHT); - - // Test the focus is on the next item mod the number of items to account for wrapping - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, nextIndex), - 'Sending key "ARROW_RIGHT" to menuitem ' + currentIndex + ' should move focus to menuitem ' + nextIndex - ); - } - }); - -ariaTest('Key ARROW_RIGHT moves focus to next menubar item', - exampleFile, 'menubar-left-arrow', async (t) => { - - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - // Send the ARROW_LEFT key to the first menuitem - await menubaritems[0].sendKeys(Key.ARROW_LEFT); - - // Test the focus is on the last menu item - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, ex.numMenus - 1), - 'Sending key "ARROW_LEFT" to menuitem 0 will change focus to menu item 3' - ); - - - for (let menuIndex = ex.numMenus - 1; menuIndex > 0; menuIndex--) { - - // Send the ARROW_LEFT key - await menubaritems[menuIndex].sendKeys(Key.ARROW_LEFT); - - // Test the focus is on the previous menuitem - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, menuIndex - 1), - 'Sending key "ARROW_RIGHT" to menuitem ' + menuIndex + ' should move focus to menuitem ' + (menuIndex - 1) - ); - } - }); - -ariaTest('Key ARROW_UP opens submenu, focus on last item', - exampleFile, 'menubar-up-arrow', async (t) => { - - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the ENTER key - await menubaritems[menuIndex].sendKeys(Key.UP); - - // Test that the submenu is displayed - t.true( - await menus[menuIndex].isDisplayed(), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' - ); - - const numSubItems = (await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex])).length; - - // Test that the focus is on the last item in the list - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], numSubItems - 1), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' - ); - } - }); - -ariaTest('Key ARROW_DOWN opens submenu, focus on first item', - exampleFile, 'menubar-down-arrow', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the ENTER key - await menubaritems[menuIndex].sendKeys(Key.DOWN); - - // Test that the submenu is displayed - t.true( - await menus[menuIndex].isDisplayed(), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should display submenu' - ); - - // Test that the focus is on the first item in the list - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), - 'Sending key "ENTER" to menuitem ' + menuIndex + ' in menubar should send focus to the first element in the submenu' - ); - } - }); - -ariaTest('Key HOME goes to first item in menubar', exampleFile, 'menubar-home', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the ARROW_RIGHT key to move the focus to later menu item for every test - for (let i = 0; i < menuIndex; i++) { - await menubaritems[i].sendKeys(Key.ARROW_RIGHT); - } - - // Send the key HOME - await menubaritems[menuIndex].sendKeys(Key.HOME); - - // Test that the focus is on the first item in the list - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, 0), - 'Sending key "HOME" to menuitem ' + menuIndex + ' in menubar should move the focus to the first menuitem' - ); - } -}); - -ariaTest('Key END goes to last item in menubar', exampleFile, 'menubar-end', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Send the ARROW_RIGHT key to move the focus to later menu item for every test - for (let i = 0; i < menuIndex; i++) { - await menubaritems[i].sendKeys(Key.ARROW_RIGHT); - } - - // Send the key END - await menubaritems[menuIndex].sendKeys(Key.END); - - // Test that the focus is on the last item in the list - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, ex.numMenus - 1), - 'Sending key "END" to menuitem ' + menuIndex + ' in menubar should move the focus to the last menuitem' - ); - } -}); - -ariaTest('Character sends to menubar changes focus in menubar', - exampleFile, 'menubar-character', async (t) => { - - - const charIndexTest = [ - { sendChar: 'z', sendIndex: 0, endIndex: 0 }, - { sendChar: 'a', sendIndex: 0, endIndex: 1 }, - { sendChar: 'a', sendIndex: 1, endIndex: 2 }, - { sendChar: 'a', sendIndex: 2, endIndex: 0 } - ]; - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - for (let test of charIndexTest) { - - // Send character to menuitem - await menubaritems[test.sendIndex].sendKeys(test.sendChar); - - // Test that the focus switches to the appropriate menuitem - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to menuitem ' + test.sendIndex + ' in menubar should move the focus to menuitem ' + test.endIndex - ); - } - }); - -// This test is failing due to a bug reported in issue: https://github.com/w3c/aria-practices/issues/907 -ariaTest.failing('ENTER in submenu selects item', exampleFile, 'submenu-space-or-enter', async (t) => { - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - await t.context.session.get(t.context.url); - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - - // send ENTER to the item - await items[itemIndex].sendKeys(Key.ENTER); - await waitForUrlChange(t); - - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'Sending key "ENTER" to menuitem "' + itemText + '" should navigate to a new webpage.' - ); - } - } - - // Test all the submenu menuitems - - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu associate with the menuitem we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const numItems = (await t.context.queryElements(t, submenuMenuitemSelector)).length; - - // Test all the items in the submenu - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); - - // send ENTER to the item we are testing - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ENTER); - await waitForUrlChange(t); - - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'Sending key "ENTER" to menuitem ' + itemText + '" should navigate to a new webpage.' - ); - - await t.context.session.get(t.context.url); - } - } -}); - -// This test is failing due to a bug reported in issue: https://github.com/w3c/aria-practices/issues/907 -ariaTest.failing('SPACE in submenu selects item', exampleFile, 'submenu-space-or-enter', async (t) => { - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - await t.context.session.get(t.context.url); - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - - // send SPACE to the item - await items[itemIndex].sendKeys(Key.SPACE); - await waitForUrlChange(t); - - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'Sending key "SPACE" to menuitem "' + itemText + '" should navigate to a new webpage.' - ); - } - } - - // Test all the submenu menuitems - - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu associate with the menuitem we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const numItems = (await t.context.queryElements(t, submenuMenuitemSelector)).length; - - // Test all the items in the submenu - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); - - // send SPACE to the item we are testing - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.SPACE); - await waitForUrlChange(t); - - t.not( - await t.context.session.getCurrentUrl(), - t.context.url, - 'Sending key "SPACE" to menuitem ' + itemText + '" should navigate to a new webpage.' - ); - - await t.context.session.get(t.context.url); - } - } -}); - - -ariaTest('ESCAPE to submenu closes submenu', exampleFile, 'submenu-escape', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - - // send ARROW_RIGHT to the item - await items[itemIndex].sendKeys(Key.ESCAPE); - - t.false( - await menus[menuIndex].isDisplayed(), - 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should close the menu' - ); - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, menuIndex), - 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should change the focus to menuitem ' + - menuIndex + ' in the menubar' - ); - } - } - - // Test all the submenu menuitems - - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - let submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; - - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); - - // send ESCAPE to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ESCAPE); - - const submenuSelector = getSubmenuSelector(...submenuLocation); - t.false( - await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), - 'Sending key "ESCAPE" to submenuitem "' + itemText + '" should close the menu' - ); - - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], menuitemIndex), - 'Sending key "ESCAPE" to submenuitem "' + itemText + - '" should send focus to menuitem ' + menuitemIndex + ' in the parent menu' - ); - } - } - -}); - -ariaTest('ARROW_RIGHT to submenu closes submenu and opens next', exampleFile, 'submenu-right-arrow', async (t) => { - - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - const hasSubmenu = await items[itemIndex].getAttribute('aria-haspopup'); - - // send ARROW_RIGHT to the item - await items[itemIndex].sendKeys(Key.ARROW_RIGHT); - - if (hasSubmenu) { - const submenuSelector = getSubmenuSelector(menuIndex, itemIndex); - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, itemIndex); - - t.true( - await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), - 'Sending key "ARROW_RIGHT" to menuitem "' + itemText + - '" should open the submenu: ' + submenuSelector - ); - t.true( - await checkFocus(t, submenuMenuitemSelector, 0), - 'Sending key "ARROW_RIGHT" to menuitem "' + itemIndex + - '" should put focus on first item in submenu: ' + submenuSelector - ); - } - else { - - // Account for wrapping (index 0 should go to 3) - const nextMenuIndex = menuIndex === 2 ? 0 : menuIndex + 1; - - // Test that the submenu is closed - t.false( - await menus[menuIndex].isDisplayed(), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + '" should close list' - ); - - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' - ); - } - } - } - - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; - - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); - - // send ARROW_RIGHT to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ARROW_RIGHT); - - // Account for wrapping (index 0 should go to 3) - const nextMenuIndex = menuIndex === 2 ? 0 : menuIndex + 1; - - // Test that the submenu is closed - t.false( - await menus[menuIndex].isDisplayed(), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + '" should close list' - ); - - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), - 'Sending key "ARROW_RIGHT" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' - ); - } - } -}); - -ariaTest('ARROW_LEFT to submenu closes submenu and opens next', exampleFile, 'submenu-left-arrow', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - - // send ARROW_LEFT to the item - await items[itemIndex].sendKeys(Key.ARROW_LEFT); - - // Account for wrapping (index 0 should go to 3) - const nextMenuIndex = menuIndex === 0 ? 2 : menuIndex - 1; - - // Test that the submenu is closed - t.false( - await menus[menuIndex].isDisplayed(), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + '" should close list' - ); - - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menubarMenuitemSelector, nextMenuIndex), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextMenuIndex + ' in the menubar' - ); - } - } - - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; - - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); - - // send ARROW_LEFT to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ARROW_LEFT); - - const submenuSelector = getSubmenuSelector(...submenuLocation); - t.false( - await t.context.session.findElement(By.css(submenuSelector)).isDisplayed(), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + '" should close the menu' - ); - - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], menuitemIndex), - 'Sending key "ARROW_LEFT" to submenuitem "' + itemText + - '" should send focus to menuitem ' + menuitemIndex + ' in the parent menu' - ); - } - } -}); - -ariaTest('ARROW_DOWN moves focus in menu', exampleFile, 'submenu-down-arrow', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - - // send ARROW_DOWN to the item - await items[itemIndex].sendKeys(Key.ARROW_DOWN); - - // Account for wrapping (last item should move to first item) - const nextItemIndex = itemIndex === ex.numMenuMenuitems[menuIndex] - 1 ? - 0 : - itemIndex + 1; - - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], nextItemIndex), - 'Sending key "ARROW_DOWN" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextItemIndex + ' in the same menu' - ); - } - } - - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; - - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); - - // send ARROW_DOWN to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ARROW_DOWN); - - // Account for wrapping (last item should move to first item) - const nextItemIndex = itemIndex === numItems - 1 ? - 0 : - itemIndex + 1; - - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, submenuMenuitemSelector, nextItemIndex), - 'Sending key "ARROW_DOWN" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextItemIndex + ' in the same menu' - ); - } - } -}); - - -ariaTest('ARROW_UP moves focus in menu', exampleFile, 'submenu-up-arrow', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - - // send ARROW_UP to the item - await items[itemIndex].sendKeys(Key.ARROW_UP); - - // Account for wrapping (last item should move to first item) - const nextItemIndex = itemIndex === 0 ? - ex.numMenuMenuitems[menuIndex] - 1 : - itemIndex - 1; - - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], nextItemIndex), - 'Sending key "ARROW_UP" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextItemIndex + ' in the same menu' - ); - } - } - - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; - - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); - - // send ARROW_UP to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.ARROW_UP); - - // Account for wrapping (last item should move to first item) - const nextItemIndex = itemIndex === 0 ? - numItems - 1 : - itemIndex - 1; - - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, submenuMenuitemSelector, nextItemIndex), - 'Sending key "ARROW_UP" to submenuitem "' + itemText + - '" should send focus to menuitem' + nextItemIndex + ' in the same menu' - ); - } - } -}); - -ariaTest('HOME moves focus in menu', exampleFile, 'submenu-home', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - - // send HOME to the item - await items[itemIndex].sendKeys(Key.HOME); - - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], 0), - 'Sending key "HOME" to submenuitem "' + itemText + - '" should send focus to first menuitem in the same menu' - ); - } - } - - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; - - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); - - // send HOME to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.HOME); - - t.true( - await checkFocus(t, submenuMenuitemSelector, 0), - 'Sending key "HOME" to submenuitem "' + itemText + - '" should send focus to the first menuitem in the same menu' - ); - } - } -}); - - -ariaTest('END moves focus in menu', exampleFile, 'submenu-end', async (t) => { - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - const menus = await t.context.queryElements(t, ex.menuSelector); - - // Test all the level one menuitems - - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - for (let itemIndex = 0; itemIndex < ex.numMenuMenuitems[menuIndex]; itemIndex++) { - - // Open the submenu - await menubaritems[menuIndex].sendKeys(Key.ENTER); - - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - const itemText = await items[itemIndex].getText(); - - // send END to the item - await items[itemIndex].sendKeys(Key.END); - - // Test that the focus is on the menuitem in the menubar - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], ex.numMenuMenuitems[menuIndex] - 1), - 'Sending key "END" to submenuitem "' + itemText + - '" should send focus to last menuitem in the same menu' - ); - } - } - - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - const numItems = items.length; - - for (let itemIndex = 0; itemIndex < numItems; itemIndex++) { - - await openSubmenu(t, ...submenuLocation); - - // send END to the item - const itemText = await items[itemIndex].getText(); - await items[itemIndex].sendKeys(Key.END); - - t.true( - await checkFocus(t, submenuMenuitemSelector, numItems - 1), - 'Sending key "END" to submenuitem "' + itemText + - '" should send focus to the last menuitem in the same menu' - ); - } - } -}); - - -ariaTest('Character sends to menubar changes focus in menubar', exampleFile, 'submenu-character', async (t) => { - - const charIndexTest = [ - [ // Tests for menu dropdown 0 - { sendChar: 'a', sendIndex: 0, endIndex: 1 }, - { sendChar: 'x', sendIndex: 1, endIndex: 1 }, - { sendChar: 'o', sendIndex: 1, endIndex: 0 } - ], - [ // Tests for menu dropdown 1 - { sendChar: 'c', sendIndex: 0, endIndex: 5 }, - { sendChar: 'y', sendIndex: 5, endIndex: 5 } - ], - [ // Tests for menu dropdown 2 - { sendChar: 'c', sendIndex: 0, endIndex: 4 }, - { sendChar: 'r', sendIndex: 4, endIndex: 5 }, - { sendChar: 'z', sendIndex: 5, endIndex: 5 } - ] - ]; - - const menubaritems = await t.context.queryElements(t, ex.menubarMenuitemSelector); - for (let menuIndex = 0; menuIndex < ex.numMenus; menuIndex++) { - - // Open the dropdown - await menubaritems[menuIndex].sendKeys(Key.ARROW_DOWN); - const items = await t.context.queryElements(t, ex.menuMenuitemSelectors[menuIndex]); - - for (let test of charIndexTest[menuIndex]) { - - // Send character to menuitem - const itemText = await items[test.sendIndex].getText(); - await items[test.sendIndex].sendKeys(test.sendChar); - - // Test that the focus switches to the appropriate menuitem - t.true( - await checkFocus(t, ex.menuMenuitemSelectors[menuIndex], test.endIndex), - 'Sending character ' + test.sendChar + ' to menuitem ' + itemText + ' should move the focus to menuitem ' + test.endIndex - ); - } - } - - const subCharIndexTest = [ - [ // Tests for menu dropdown 0 - { sendChar: 'c', sendIndex: 0, endIndex: 1 }, - { sendChar: 'h', sendIndex: 1, endIndex: 0 }, - { sendChar: 'x', sendIndex: 0, endIndex: 0 } - ], - [ // Tests for menu dropdown 1 - { sendChar: 'f', sendIndex: 0, endIndex: 1 }, - { sendChar: 'f', sendIndex: 1, endIndex: 2 } - ], - [ // Tests for menu dropdown 2 - { sendChar: 'p', sendIndex: 0, endIndex: 2 }, - { sendChar: 'z', sendIndex: 2, endIndex: 2 } - ] - ]; - - let testIndex = 0; - - // Test all the submenu menuitems - for (let submenuLocation of ex.submenuLocations) { - const [menuIndex, menuitemIndex] = submenuLocation; - - await openSubmenu(t, ...submenuLocation); - - // Get the submenu items we are testing - const submenuMenuitemSelector = getSubmenuMenuitemSelector(menuIndex, menuitemIndex); - const items = await t.context.queryElements(t, submenuMenuitemSelector); - - for (let test of subCharIndexTest[testIndex]) { - - // Send character to menuitem - const itemText = await items[test.sendIndex].getText(); - await items[test.sendIndex].sendKeys(test.sendChar); - - // Test that the focus switches to the appropriate menuitem - t.true( - await checkFocus(t, submenuMenuitemSelector, test.endIndex), - 'Sending character ' + test.sendChar + ' to menuitem ' + itemText + ' should move the focus to menuitem ' + test.endIndex - ); - } - - testIndex++; - } -}); From af2214ba7f05397756dc617bf87ac416aabc761b Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 15 Jul 2020 09:30:01 -0500 Subject: [PATCH 49/62] fixed mouse hover issue --- examples/menubar/css/menubar-navigation.css | 8 +++++++- examples/menubar/js/menubar-navigation.js | 14 ++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index 22e085f9a8..63faf5437c 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -116,7 +116,8 @@ } .menubar-navigation [role="menuitem"][aria-expanded="true"], -.menubar-navigation [role="menuitem"]:focus { +.menubar-navigation [role="menuitem"]:focus, +.menubar-navigation [role="menuitem"]:hover { padding: 2px; border: 4px solid #034575; background: #034575; @@ -124,3 +125,8 @@ outline: none; margin: 0; } + +.menubar-navigation > li > [role="menuitem"]:hover::after { + content: url('../images/down-arrow-focus.svg'); + padding-left: 0.25em; +} diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index d6ae84eee2..de6c1b0573 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -115,7 +115,6 @@ MenubarNavigation.prototype.initMenu = function (menu, depth) { menuitem.addEventListener('mouseover', this.handleMenuitemMouseover.bind(this)); - if( !this.firstMenuitem[menuId]) { if (this.hasPopup(menuitem)) { menuitem.tabIndex = 0; @@ -409,6 +408,10 @@ MenubarNavigation.prototype.isMenuHorizontal = function (menuitem) { return this.menuOrientation[menuitem] === 'horizontal'; }; +MenubarNavigation.prototype.hasFocus = function () { + return this.domNode.classList.contains('focus'); +}; + // Menu event handlers MenubarNavigation.prototype.handleMenubarFocusin = function (event) { @@ -586,13 +589,16 @@ MenubarNavigation.prototype.handleMenuitemClick = function (event) { MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { var tgt = event.currentTarget; + var menuId = this.getMenuId(tgt); - tgt.focus(); + if (this.hasFocus()) { + this.setFocusToMenuitem(menuId, tgt); + } - if (this.isAnyPopupOpen()) { + if (this.isAnyPopupOpen() || this.hasFocus()) { this.closePopupAll(tgt); if (this.hasPopup(tgt)) { - this.openPopup(this.getMenuId(tgt), tgt); + this.openPopup(menuId, tgt); } } }; From 910176c2584a3047cb76f6371adf9bd3883f3aaa Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 15 Jul 2020 09:33:14 -0500 Subject: [PATCH 50/62] fixed cross referencing links --- examples/menubar/menubar-editor.html | 2 +- examples/menubar/menubar-navigation.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/menubar/menubar-editor.html b/examples/menubar/menubar-editor.html index 8414e8efc8..563f2e688e 100644 --- a/examples/menubar/menubar-editor.html +++ b/examples/menubar/menubar-editor.html @@ -46,7 +46,7 @@

    Editor Menubar Example

    Similar examples include:

    diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index fce816c489..37af4abcfe 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -52,7 +52,7 @@

    Navigation Menubar Example

    Similar examples include:

    From ebbfd5efdcbf8b7d8bb43ff93a1a312743bcfea3 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 15 Jul 2020 10:30:24 -0500 Subject: [PATCH 51/62] fixed background mouse down event to not cancel event propagation --- examples/menubar/js/menubar-navigation.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index de6c1b0573..4dc42847ee 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -606,8 +606,6 @@ MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { MenubarNavigation.prototype.handleBackgroundMousedown = function (event) { if (!this.domNode.contains(event.target)) { this.closePopupAll(); - event.stopPropagation(); - event.preventDefault(); } }; From d68fe777821dbd31a32eb789e42fc7762c41e359 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 15 Jul 2020 10:40:11 -0500 Subject: [PATCH 52/62] fixed broken link in disclosure menu button example --- examples/disclosure/disclosure-navigation.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/disclosure/disclosure-navigation.html b/examples/disclosure/disclosure-navigation.html index a87830fb22..d8406a6918 100644 --- a/examples/disclosure/disclosure-navigation.html +++ b/examples/disclosure/disclosure-navigation.html @@ -38,7 +38,7 @@

    Example Disclosure for Navigation Menus

    Example Usage Options

    @@ -47,9 +47,9 @@

    Example Usage Options

    - +

    Example

    From b64a364bc4d608dd7edeea97fad05782cda4b58d Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Thu, 16 Jul 2020 11:08:06 -0500 Subject: [PATCH 53/62] improved color contrast support --- examples/menubar/css/menubar-navigation.css | 52 ++++++----------- examples/menubar/images/right-arrow-focus.svg | 4 -- examples/menubar/images/right-arrow.svg | 4 -- examples/menubar/js/menubar-navigation.js | 4 +- examples/menubar/menubar-navigation.html | 58 ++++++++++++++----- 5 files changed, 66 insertions(+), 56 deletions(-) delete mode 100644 examples/menubar/images/right-arrow-focus.svg delete mode 100644 examples/menubar/images/right-arrow.svg diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css index 63faf5437c..b242f6da03 100644 --- a/examples/menubar/css/menubar-navigation.css +++ b/examples/menubar/css/menubar-navigation.css @@ -42,21 +42,31 @@ border-radius: 5px; } -.menubar-navigation > li > [role="menuitem"] { - display: inline-block; +.menubar-navigation [role="menuitem"] svg { + fill: currentColor; + stroke: currentColor; +} + +.menubar-navigation [role="menuitem"] svg.down { + padding-left: 0.125em; +} + +.menubar-navigation [role="menuitem"] svg.right { + position: absolute; + padding-top: 0.35em; + right: 0.75em; } -.menubar-navigation > li > [role="menuitem"]::after { - content: url('../images/down-arrow.svg'); - padding-left: 0.25em; +.menubar-navigation [role="menuitem"][aria-expanded="true"] svg.down { + transform: rotate(180deg); } -.menubar-navigation > li > [role="menuitem"]:focus::after { - content: url('../images/down-arrow-focus.svg'); +.menubar-navigation [role="menuitem"][aria-expanded="true"] svg.right { + transform: rotate(90deg) translate(5px, -5px); } -.menubar-navigation > li > [role="menuitem"][aria-expanded="true"]::after { - content: url('../images/up-arrow-focus.svg'); +.menubar-navigation > li > [role="menuitem"] { + display: inline-block; } .menubar-navigation [role="menu"] { @@ -82,25 +92,6 @@ background-repeat: repeat-x; } -.menubar-navigation [role="menu"] [role="menuitem"][aria-haspopup="true"]::after { - content: url('../images/right-arrow.svg'); - position: absolute; - left: 11em; -} - -.menubar-navigation [role="menu"] [role="menuitem"][aria-haspopup="true"]:focus::after { - content: url('../images/right-arrow-focus.svg'); - position: absolute; - left: 11em; -} - -.menubar-navigation [role="menu"] [role="menuitem"][aria-haspopup="true"]:hover::after, -.menubar-navigation [role="menu"] [role="menuitem"][aria-expanded="true"]::after { - content: url('../images/down-arrow-focus.svg'); - position: absolute; - left: 11em; -} - /* focus styling */ .menubar-navigation.focus { @@ -125,8 +116,3 @@ outline: none; margin: 0; } - -.menubar-navigation > li > [role="menuitem"]:hover::after { - content: url('../images/down-arrow-focus.svg'); - padding-left: 0.25em; -} diff --git a/examples/menubar/images/right-arrow-focus.svg b/examples/menubar/images/right-arrow-focus.svg deleted file mode 100644 index ac516fd572..0000000000 --- a/examples/menubar/images/right-arrow-focus.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/menubar/images/right-arrow.svg b/examples/menubar/images/right-arrow.svg deleted file mode 100644 index 806280cef0..0000000000 --- a/examples/menubar/images/right-arrow.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js index 4dc42847ee..860943ec4f 100644 --- a/examples/menubar/js/menubar-navigation.js +++ b/examples/menubar/js/menubar-navigation.js @@ -69,7 +69,7 @@ MenubarNavigation.prototype.getMenuitems = function(domNode, depth) { break; } - if (flag && node.firstElementChild) { + if (flag && node.firstElementChild && node.firstElementChild.tagName !== 'svg') { findMenuitems(node.firstElementChild); } @@ -108,7 +108,7 @@ MenubarNavigation.prototype.initMenu = function (menu, depth) { menuitem.tabIndex = -1; this.menuitemGroups[menuId].push(menuitem); - this.firstChars[menuId].push(menuitem.textContent[0].toLowerCase()); + this.firstChars[menuId].push(menuitem.textContent.trim().toLowerCase()[0]); menuitem.addEventListener('keydown', this.handleKeydown.bind(this)); menuitem.addEventListener('click', this.handleMenuitemClick.bind(this)); diff --git a/examples/menubar/menubar-navigation.html b/examples/menubar/menubar-navigation.html index 37af4abcfe..3294c200c4 100644 --- a/examples/menubar/menubar-navigation.html +++ b/examples/menubar/menubar-navigation.html @@ -16,13 +16,7 @@ - - - - - - @@ -63,7 +57,12 @@

    Example