diff --git a/aria-practices.html b/aria-practices.html index b030c3da9e..10de549342 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -1935,8 +1935,8 @@

Menu or Menu bar

Examples

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

diff --git a/examples/menubar/css/menubar-navigation.css b/examples/menubar/css/menubar-navigation.css new file mode 100644 index 0000000000..90b8f004b2 --- /dev/null +++ b/examples/menubar/css/menubar-navigation.css @@ -0,0 +1,121 @@ +.menubar-navigation { + margin: 0; + margin-top: 0.5em; + margin-bottom: 0.5em; + padding: 7px; + font-size: 110%; + list-style: none; + background-color: #eee; + border: #eee solid 1px; + border-radius: 5px; +} + +.menubar-navigation li { + margin: 0; + padding: 0; + border: 0 solid black; + list-style: none; +} + +.menubar-navigation > li { + display: inline-block; + position: relative; +} + +.menubar-navigation > li li { + display: block; +} + +.menubar-navigation [role="menu"] [role="menuitem"], +.menubar-navigation [role="menu"] [role="separator"] { + display: block; + width: 12em; + margin: 0; +} + +.menubar-navigation [role="menuitem"], +.menubar-navigation [role="separator"] { + padding: 6px; + background-color: #eee; + border: 0px solid #eee; + color: black; + border-radius: 5px; +} + +.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 [role="menuitem"][aria-expanded="true"] svg.down { + transform: rotate(180deg); +} + +.menubar-navigation [role="menuitem"][aria-expanded="true"] svg.right { + transform: rotate(90deg) translate(5px, -5px); +} + +.menubar-navigation > li > [role="menuitem"] { + display: inline-block; +} + +.menubar-navigation [role="menu"] { + display: none; + position: absolute; + margin: 0; + padding: 0; +} + +.menubar-navigation [role="group"] { + margin: 0; + padding: 0; +} + +.menubar-navigation [role="menu"] { + display: none; +} + +.menubar-navigation [role="separator"] { + padding-top: 3px; + background-image: url('../images/separator.svg'); + background-position: center; + background-repeat: repeat-x; +} + +/* focus styling */ + +.menubar-navigation.focus { + padding: 6px; + border: #034575 solid 2px; +} + +.menubar-navigation [role="menu"] { + padding: 7px 4px; + border: 2px solid #034575; + border-radius: 5px; + background-color: #eee; +} + +.menubar-navigation [role="menuitem"][aria-expanded="true"], +.menubar-navigation [role="menuitem"]:focus, +.menubar-navigation [role="menuitem"]:hover { + background: #034575; + color: #fff; + outline: none; +} + +.menubar-navigation [role="menuitem"]:focus, +.menubar-navigation [role="menuitem"]:hover { + padding: 2px; + border: 4px solid #034575; +} diff --git a/examples/menubar/images/down-arrow-focus.svg b/examples/menubar/images/down-arrow-focus.svg index f637806269..8f6ea66370 100644 --- a/examples/menubar/images/down-arrow-focus.svg +++ b/examples/menubar/images/down-arrow-focus.svg @@ -1,4 +1,4 @@ - + - - \ No newline at end of file + + diff --git a/examples/menubar/images/down-arrow.svg b/examples/menubar/images/down-arrow.svg index c30c32e123..a0a2556a5e 100644 --- a/examples/menubar/images/down-arrow.svg +++ b/examples/menubar/images/down-arrow.svg @@ -1,4 +1,4 @@ - + - - \ No newline at end of file + + diff --git a/examples/menubar/images/separator.svg b/examples/menubar/images/separator.svg index db78906f72..b4b39760b2 100644 --- a/examples/menubar/images/separator.svg +++ b/examples/menubar/images/separator.svg @@ -1,4 +1,4 @@ - - - - + + + + diff --git a/examples/menubar/images/up-arrow.svg b/examples/menubar/images/up-arrow.svg new file mode 100644 index 0000000000..d034dad152 --- /dev/null +++ b/examples/menubar/images/up-arrow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/menubar/js/menubar-navigation.js b/examples/menubar/js/menubar-navigation.js new file mode 100644 index 0000000000..860943ec4f --- /dev/null +++ b/examples/menubar/js/menubar-navigation.js @@ -0,0 +1,619 @@ +/* +* 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 +*/ + +'use strict'; + +var MenubarNavigation = function (domNode) { + + this.domNode = domNode; + + + this.popups = []; + this.menuitemGroups = {}; + this.menuOrientation = {}; + this.isPopup = {}; + this.isPopout = {}; + this.openPopups = false; + + this.firstChars = {}; // see Menubar init method + this.firstMenuitem = {}; // see Menubar init method + this.lastMenuitem = {}; // see Menubar init method + + 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, depth) { + var nodes = []; + + var initMenu = this.initMenu.bind(this); + var menuitemGroups = this.menuitemGroups; + var popups = this.popups; + + function findMenuitems(node) { + var role, flag; + + while (node) { + flag = true; + role = node.getAttribute('role'); + + if (role) { + role = role.trim().toLowerCase(); + } + + switch (role) { + case 'menu': + node.tabIndex = -1; + initMenu(node, depth + 1); + flag = false; + break; + + case 'menuitem': + if (node.getAttribute('aria-haspopup') === 'true') { + popups.push(node); + } + nodes.push(node); + break; + + default: + break; + } + + if (flag && node.firstElementChild && node.firstElementChild.tagName !== 'svg') { + findMenuitems(node.firstElementChild); + } + + node = node.nextElementSibling; + } + } + + findMenuitems(domNode.firstElementChild); + + return nodes; +}; + +MenubarNavigation.prototype.initMenu = function (menu, depth) { + var menuitems, menuitem, role, nextElement; + + var menuId = this.getMenuId(menu); + + menuitems = this.getMenuitems(menu, depth); + this.menuOrientation[menuId] = this.getMenuOrientation(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] = []; + this.firstMenuitem[menuId] = null; + this.lastMenuitem[menuId] = null; + + for(var 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.trim().toLowerCase()[0]); + + 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.prototype.setFocusToMenuitem = function (menuId, newMenuitem) { + + this.closePopupAll(newMenuitem); + + if (this.menuitemGroups[menuId]) { + this.menuitemGroups[menuId].forEach(function(item) { + if (item === newMenuitem) { + item.tabIndex = 0; + newMenuitem.focus(); + } + else { + item.tabIndex = -1; + } + }); + } +}; + +MenubarNavigation.prototype.setFocusToFirstMenuitem = function (menuId, currentMenuitem) { + this.setFocusToMenuitem(menuId, this.firstMenuitem[menuId]); +}; + +MenubarNavigation.prototype.setFocusToLastMenuitem = function (menuId, currentMenuitem) { + this.setFocusToMenuitem(menuId, this.lastMenuitem[menuId]); +}; + +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); + + 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); + + 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]); + } +}; + +// 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.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; +}; + +// 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 + var popupMenu = menuitem.nextElementSibling; + + var rect = menuitem.getBoundingClientRect(); + + // 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 + 6) + '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 + 8)+ 'px'; + popupMenu.style.zIndex = 100; + } + + menuitem.setAttribute('aria-expanded', 'true'); + + return this.getMenuId(popupMenu); + +}; + +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, + menuId = this.getMenuId(menuitem), + cmi = menuitem; + + if (this.isMenubar(menuId)) { + if (this.isOpen(menuitem)) { + menuitem.setAttribute('aria-expanded', 'false'); + menuitem.nextElementSibling.style.display = 'none'; + } + } + else { + menu = this.getMenu(menuitem); + cmi = menu.previousElementSibling; + cmi.setAttribute('aria-expanded', 'false'); + cmi.focus(); + menu.style.display = 'none'; + } + + return cmi; +}; + +MenubarNavigation.prototype.doesNotContain = function (popup, menuitem) { + if (menuitem) { + return !popup.nextElementSibling.contains(menuitem); + } + return true; +}; + +MenubarNavigation.prototype.closePopupAll = function (menuitem) { + if (typeof menuitem !== 'object') { + menuitem = false; + } + for (var i = 0; i < this.popups.length; i++) { + var popup = this.popups[i]; + if (this.doesNotContain(popup, menuitem) && this.isOpen(popup)) { + var cmi = popup.nextElementSibling; + if (cmi) { + popup.setAttribute('aria-expanded', 'false'); + cmi.style.display = 'none'; + } + } + } +}; + +MenubarNavigation.prototype.hasPopup = function (menuitem) { + return menuitem.getAttribute('aria-haspopup') === 'true'; +}; + +MenubarNavigation.prototype.isOpen = function (menuitem) { + return menuitem.getAttribute('aria-expanded') === 'true'; +}; + +MenubarNavigation.prototype.isMenubar = function (menuId) { + return !this.isPopup[menuId] && !this.isPopout[menuId]; +}; + +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) { + // 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.handleKeydown = function (event) { + var tgt = event.currentTarget, + key = event.key, + flag = false, + menuId = this.getMenuId(tgt), + id, + popupMenuId, + mi, + role, + option, + value; + + var isAnyPopupOpen = this.isAnyPopupOpen(); + + switch (key) { + case ' ': + case 'Enter': + if (this.hasPopup(tgt)) { + this.openPopups = true; + popupMenuId = this.openPopup(menuId, tgt); + this.setFocusToFirstMenuitem(popupMenuId); + } + else { + 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; + + case 'ArrowDown': + case 'Down': + if (this.isMenuHorizontal(menuId)) { + if (this.hasPopup(tgt)) { + this.openPopups = true; + popupMenuId = this.openPopup(menuId, tgt); + this.setFocusToFirstMenuitem(popupMenuId); + } + } + else { + this.setFocusToNextMenuitem(menuId, tgt); + } + flag = true; + break; + + case 'Left': + case 'ArrowLeft': + if (this.isMenuHorizontal(menuId)) { + mi = this.setFocusToPreviousMenuitem(menuId, tgt); + if (isAnyPopupOpen) { + this.openPopup(menuId, mi); + } + } + else { + 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.isMenuHorizontal(menuId)) { + mi = this.setFocusToNextMenuitem(menuId, tgt); + if (isAnyPopupOpen) { + this.openPopup(menuId, mi); + } + } + else { + if (this.hasPopup(tgt)) { + 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': + 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; + var menuId = this.getMenuId(tgt); + + if (this.hasPopup(tgt)) { + if (this.isOpen(tgt)) { + this.closePopup(tgt); + } + else { + this.closePopupAll(tgt); + this.openPopup(menuId, tgt); + } + event.stopPropagation(); + event.preventDefault(); + } +}; + +MenubarNavigation.prototype.handleMenuitemMouseover = function (event) { + var tgt = event.currentTarget; + var menuId = this.getMenuId(tgt); + + if (this.hasFocus()) { + this.setFocusToMenuitem(menuId, tgt); + } + + if (this.isAnyPopupOpen() || this.hasFocus()) { + this.closePopupAll(tgt); + if (this.hasPopup(tgt)) { + this.openPopup(menuId, tgt); + } + } +}; + +MenubarNavigation.prototype.handleBackgroundMousedown = function (event) { + if (!this.domNode.contains(event.target)) { + this.closePopupAll(); + } +}; + +// Initialize menubar editor + +window.addEventListener('load', function () { + 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/mb-about.html b/examples/menubar/mb-about.html new file mode 100644 index 0000000000..4ba20cae85 --- /dev/null +++ b/examples/menubar/mb-about.html @@ -0,0 +1,32 @@ + + + + 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/mb-academics.html b/examples/menubar/mb-academics.html new file mode 100644 index 0000000000..6befba7337 --- /dev/null +++ b/examples/menubar/mb-academics.html @@ -0,0 +1,52 @@ + + + + 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/mb-admissions.html b/examples/menubar/mb-admissions.html new file mode 100644 index 0000000000..9c7cba1aec --- /dev/null +++ b/examples/menubar/mb-admissions.html @@ -0,0 +1,39 @@ + + + + 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 + + +

+ + + 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 fe3e38aa31..0000000000 Binary files a/examples/menubar/menubar-1/images/down-arrow-brown.png and /dev/null differ 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 f5d34f2e07..0000000000 Binary files a/examples/menubar/menubar-1/images/down-arrow-gray.png and /dev/null differ 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 9808455aea..0000000000 Binary files a/examples/menubar/menubar-1/images/right-arrow-brown.png and /dev/null differ diff --git a/examples/menubar/menubar-1/images/right-arrow-gray.png b/examples/menubar/menubar-1/images/right-arrow-gray.png deleted file mode 100644 index b2fe4e3401..0000000000 Binary files a/examples/menubar/menubar-1/images/right-arrow-gray.png and /dev/null differ diff --git a/examples/menubar/menubar-1/images/separator.paint b/examples/menubar/menubar-1/images/separator.paint deleted file mode 100644 index 9e313a1cc1..0000000000 Binary files a/examples/menubar/menubar-1/images/separator.paint and /dev/null differ diff --git a/examples/menubar/menubar-1/images/separator.png b/examples/menubar/menubar-1/images/separator.png deleted file mode 100644 index a837fbce93..0000000000 Binary files a/examples/menubar/menubar-1/images/separator.png and /dev/null differ 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-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-1/menubar-1.html b/examples/menubar/menubar-navigation.html similarity index 80% rename from examples/menubar/menubar-1/menubar-1.html rename to examples/menubar/menubar-navigation.html index 9b72510beb..fb7f7024dc 100644 --- a/examples/menubar/menubar-1/menubar-1.html +++ b/examples/menubar/menubar-navigation.html @@ -1,55 +1,59 @@ - -Navigation Menubar Example | WAI-ARIA Authoring Practices 1.2 + + Navigation Menubar Example | WAI-ARIA Authoring Practices 1.2 - - - - - - + + + + + + - - - - - - - + + + + + +

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

- - -
@@ -168,8 +199,14 @@

Example

Accessibility Features

    +
  1. To help communicate that the arrow keys are available for directional navigation within the menubar and its submenus, a border is added to the menubar container when focus is within the menubar.
  2. +
  3. To support operating system high contrast settings: +
      +
    • Focus is highlighted by adding and removing a border around the menu item with focus.
    • +
    • The arrow icons indicating the current state of expandable menus use in-line SVG images with the CSS currentColor value for the fill and stroke properties of the polygon element.
    • +
    +
  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. -
  6. 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.
@@ -249,7 +286,8 @@

Menubar

-

Submenu

+ +

Submenu

@@ -264,8 +302,8 @@

Submenu

@@ -617,22 +655,8 @@

Submenu

Javascript and CSS Source Code

@@ -652,7 +676,7 @@

HTML Source Code

diff --git a/test/tests/menubar_menubar-1.js b/test/tests/menubar_menubar-navigation.js similarity index 98% rename from test/tests/menubar_menubar-1.js rename to test/tests/menubar_menubar-navigation.js index ebaae9b8be..c620015b6e 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 @@ -116,7 +116,7 @@ ariaTest('Test aria-label on menubar', exampleFile, 'menubar-aria-label', async }); ariaTest('Test for role="menuitem" in menubar', exampleFile, 'menuitem-role', async (t) => { - + const menuitems = await t.context.queryElements(t, ex.menubarMenuitemSelector); t.is( @@ -135,7 +135,7 @@ ariaTest('Test for role="menuitem" in menubar', exampleFile, 'menuitem-role', as ariaTest('Test roving tabindex', exampleFile, 'menuitem-tabindex', async (t) => { - + // Wait for roving tabindex to be initialized by the javascript await exampleInitialized(t); @@ -145,12 +145,12 @@ ariaTest('Test roving tabindex', exampleFile, 'menuitem-tabindex', async (t) => 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'); @@ -196,7 +196,7 @@ ariaTest('"aria-expanded" attribute on menubar menuitem', exampleFile, 'menuitem }); 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) { @@ -218,7 +218,7 @@ ariaTest('Test for aria-label on role="menu"', exampleFile, 'menu-aria-label', a 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( @@ -248,7 +248,7 @@ ariaTest('Test tabindex="-1" on submenu role="menuitem"s', exampleFile, 'sub-men }); 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++) { @@ -273,7 +273,7 @@ ariaTest('Test aria-haspopup on menuitems with submenus', exampleFile, 'sub-menu }); 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) { @@ -319,7 +319,7 @@ ariaTest('Test aria-expanded on menuitems with submenus', exampleFile, 'sub-menu }); 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) { @@ -337,7 +337,7 @@ ariaTest('Test for role="none" on menu lis', exampleFile, 'sub-none-role', async // 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++) { @@ -360,13 +360,12 @@ ariaTest('Key ENTER open submenu', exampleFile, 'menubar-space-or-enter', async 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); + await menubaritems[menuIndex].sendKeys(' '); // Test that the submenu is displayed t.true( @@ -385,7 +384,7 @@ ariaTest('Key SPACE open submenu', exampleFile, 'menubar-space-or-enter', async 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++) { @@ -407,7 +406,7 @@ ariaTest('Key ARROW_RIGHT moves focus to next menubar item', 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 @@ -436,7 +435,7 @@ ariaTest('Key ARROW_RIGHT moves focus to next menubar item', 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++) { @@ -462,7 +461,7 @@ ariaTest('Key ARROW_UP opens submenu, focus on last item', 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++) { @@ -485,7 +484,7 @@ ariaTest('Key ARROW_DOWN opens submenu, focus on first item', }); 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++) { @@ -500,13 +499,13 @@ ariaTest('Key HOME goes to first item in menubar', exampleFile, 'menubar-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' + 'Sending key "HOME" to menuitem ' + menuIndex + ' in menubar should move the foucs 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++) { @@ -521,7 +520,7 @@ ariaTest('Key END goes to last item in menubar', exampleFile, 'menubar-end', asy // 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' + 'Sending key "END" to menuitem ' + menuIndex + ' in menubar should move the foucs to the last menuitem' ); } }); @@ -529,7 +528,7 @@ ariaTest('Key END goes to last item in menubar', exampleFile, 'menubar-end', asy 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 }, @@ -546,14 +545,14 @@ ariaTest('Character sends to menubar changes focus in menubar', // 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 + 'Sending characther ' + test.sendChar + ' to menuitem ' + test.sendIndex + ' in menubar should move the foucs 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++) { @@ -613,7 +612,7 @@ ariaTest.failing('ENTER in submenu selects item', exampleFile, 'submenu-space-or // 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++) { @@ -629,7 +628,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 +656,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( @@ -673,7 +672,7 @@ ariaTest.failing('SPACE in submenu selects item', exampleFile, 'submenu-space-or 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); @@ -739,7 +738,7 @@ ariaTest('ESCAPE to submenu closes submenu', exampleFile, 'submenu-escape', asyn 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); @@ -831,7 +830,7 @@ ariaTest('ARROW_RIGHT to submenu closes submenu and opens next', exampleFile, 's }); 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); @@ -900,7 +899,7 @@ ariaTest('ARROW_LEFT to submenu closes submenu and opens next', exampleFile, 'su }); 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); @@ -966,7 +965,7 @@ ariaTest('ARROW_DOWN moves focus in menu', exampleFile, 'submenu-down-arrow', as 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); @@ -1031,7 +1030,7 @@ ariaTest('ARROW_UP moves focus in menu', exampleFile, 'submenu-up-arrow', async }); 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); @@ -1086,7 +1085,7 @@ ariaTest('HOME moves focus in menu', exampleFile, 'submenu-home', async (t) => { 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); @@ -1141,7 +1140,7 @@ ariaTest('END moves focus in menu', exampleFile, 'submenu-end', async (t) => { 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 }, @@ -1175,7 +1174,7 @@ ariaTest('Character sends to menubar changes focus in menubar', exampleFile, 'su // 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 + 'Sending characther ' + test.sendChar + ' to menuitem ' + itemText + ' should move the focus to menuitem ' + test.endIndex ); } } @@ -1217,7 +1216,7 @@ ariaTest('Character sends to menubar changes focus in menubar', exampleFile, 'su // 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 + 'Sending characther ' + test.sendChar + ' to menuitem ' + itemText + ' should move the focus to menuitem ' + test.endIndex ); }
    -
  • 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 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.