diff --git a/examples/accordion/accordion.html b/examples/accordion/accordion.html index 2b02f0f349..368a425b60 100644 --- a/examples/accordion/accordion.html +++ b/examples/accordion/accordion.html @@ -13,8 +13,6 @@ - - @@ -25,34 +23,47 @@

Accordion Example

that demonstrates the design pattern for accordion. In this implementation, one panel of the accordion is always expanded, and only one panel may - be expanded at a time. This example does not implement any of the optional keystrokes included in the accordion pattern. + be expanded at a time.

Example

-
@@ -267,7 +323,7 @@

Javascript and CSS Source Code

CSS:
  1. - customize.css + accordion.css
@@ -275,10 +331,7 @@

Javascript and CSS Source Code

JavaScript:
  1. - helpers.js -
  2. -
  3. - setup.js + accordion.js
@@ -300,5 +353,7 @@

HTML Source Code

+ + diff --git a/examples/accordion/css/accordion.css b/examples/accordion/css/accordion.css index 006eca6b8c..d71b76dfa8 100644 --- a/examples/accordion/css/accordion.css +++ b/examples/accordion/css/accordion.css @@ -1,92 +1,71 @@ -/* Contains the accordion elements */ -.accordion { - border: 1px solid hsl(0, 0%, 82%); - border-radius: .3em; - box-shadow: 0 1px 2px hsl(0, 0%, 82%); +.Accordion { + border: 1px solid hsl(0, 0%, 82%); + border-radius: .3em; + box-shadow: 0 1px 2px hsl(0, 0%, 82%); } -.accordion > * + * { - border-top: 1px solid hsl(0, 0%, 82%); +.Accordion > * + * { + border-top: 1px solid hsl(0, 0%, 82%); } -/* Heading containing accordion button */ -.accordion dt[role="heading"] { - -} - -.accordion dt[role="heading"]:first-of-type > [role="button"] { - border-radius: .3em .3em 0 0; -} - -.accordion dt[role="heading"]:last-of-type > [role="button"] { - border-radius: 0 0 .3em .3em; -} - -.accordion dt[role="heading"]:last-of-type > [role="button"][aria-expanded="true"] { - border-radius: 0; -} - -/* Accordion button */ -.accordion dt > [role="button"] { - display: block; - position: relative; - margin: 0; - padding: 1em 1.5em; - border-bottom: 0; - color: hsl(0, 0%, 13%); - font-weight: normal; +.Accordion-trigger { + background: none; + border: 0; + color: hsl(0, 0%, 13%); + display: block; + font-size: 1rem; + font-weight: normal; + margin: 0; + padding: 1em 1.5em; + position: relative; + text-align: left; + width: 100%; } -.accordion dt > [role="button"]:hover, -.accordion dt > [role="button"]:focus { - margin: inherit; - background: hsl(0, 0%, 93%); +.Accordion dt:first-child .Accordion-trigger { + border-radius: .3em .3em 0 0; } -/* Accordion button icon */ -.accordion dt > [role="button"] .icon { - position: absolute; - top: 50%; - right: 1.5em; - transform: translateY(-60%) rotate(45deg); - width: 8px; - height: 8px; - border: solid hsl(0, 0%, 62%); - border-width: 0 2px 2px 0; +.Accordion-trigger:focus, +.Accordion-trigger:hover { + background: hsl(0, 0%, 93%); } -.accordion dt > [role="button"][aria-expanded="true"] .icon { - transform: translateY(-50%) rotate(-135deg); +.Accordion-icon { + border: solid hsl(0, 0%, 62%); + border-width: 0 2px 2px 0; + height: .5rem; + position: absolute; + right: 1.5em; + top: 50%; + transform: translateY(-60%) rotate(45deg); + width: .5rem; } -.accordion dt > [role="button"]:hover .icon, -.accordion dt > [role="button"]:focus .icon { - border-color: hsl(0, 0%, 13%); +.Accordion-trigger:focus .Accordion-icon, +.Accordion-trigger:hover .Accordion-icon { + border-color: hsl(0, 0%, 13%); } -/* Accordion panel */ -.accordion dd[role="region"] { - margin: 0; - padding: 1em 1.5em; - /*border: solid hsl(0, 0%, 82%); - border-width: 0 1px;*/ +.Accordion-trigger[aria-expanded="true"] .Accordion-icon { + transform: translateY(-50%) rotate(-135deg); } -.accordion dd[role="region"].hidden { - display: none; +.Accordion-panel { + margin: 0; + padding: 1em 1.5em; } -/* Basic stuff */ fieldset { - border: 0; - margin: 0; - padding: 0; + border: 0; + margin: 0; + padding: 0; } -input[type="text"] { - display: block; - padding: .3em .5em; - border: 1px solid hsl(0, 0%, 62%); - border-radius: .3em; - font-size: inherit; +input { + border: 1px solid hsl(0, 0%, 62%); + border-radius: .3em; + display: block; + font-size: inherit; + padding: .3em .5em; } diff --git a/examples/accordion/css/customize.css b/examples/accordion/css/customize.css deleted file mode 100644 index 7af0ed8463..0000000000 --- a/examples/accordion/css/customize.css +++ /dev/null @@ -1,52 +0,0 @@ -/* Accordion Header */ - -#accordionGroup dt { - position: relative; - text-transform: uppercase; - background: #63B7C2; - padding: 0.3em 0.5em; - margin-bottom: 1px; -} - -/* Accordion Panel */ - -#accordionGroup dd { - text-align: center; - background: #FFF; -} - -/* Image Sprite for the expanded/collapse icon */ - -.accAccordion, a.accAccordion:link, a.accAccordion:visited { - display: block; - font-size: 100%; - text-align: left; - text-decoration: none; - color: #000; - background-image: url(../img/accordion/sprite.svg); - background-position: right 0.35em; - background-repeat: no-repeat; - padding: 0.3em 0.5em; - cursor: pointer; -} - -.accAccordion.open, .accAccordion.open:link, .accAccordion.open:visited { - background-position: right -119px; -} - -.hidden {display: none;} - -fieldset.flex p { - display: flex; - flex-wrap: wrap; - text-align: left; -} - -fieldset.flex > p > label -{flex: 1 0 120px; - max-width: 220px;} - -*:focus { - color: #000; - background: #FC0; -} diff --git a/examples/accordion/img/accordion/left.png b/examples/accordion/img/accordion/left.png deleted file mode 100644 index 595680db4c..0000000000 Binary files a/examples/accordion/img/accordion/left.png and /dev/null differ diff --git a/examples/accordion/img/accordion/right.png b/examples/accordion/img/accordion/right.png deleted file mode 100644 index 7e9f6cf182..0000000000 Binary files a/examples/accordion/img/accordion/right.png and /dev/null differ diff --git a/examples/accordion/img/accordion/sprite.svg b/examples/accordion/img/accordion/sprite.svg deleted file mode 100644 index 810c2afb57..0000000000 --- a/examples/accordion/img/accordion/sprite.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/accordion/img/keyboard.svg b/examples/accordion/img/keyboard.svg deleted file mode 100644 index de56462e00..0000000000 --- a/examples/accordion/img/keyboard.svg +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/accordion/img/sprite.svg b/examples/accordion/img/sprite.svg deleted file mode 100644 index cd5cb50c78..0000000000 --- a/examples/accordion/img/sprite.svg +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/accordion/js/accordion.js b/examples/accordion/js/accordion.js new file mode 100644 index 0000000000..943cc40208 --- /dev/null +++ b/examples/accordion/js/accordion.js @@ -0,0 +1,103 @@ +/* +Simple accordion pattern example +Gerard K. Cohen, 05/20/2017 +*/ + +Array.from(document.querySelectorAll('.Accordion')).forEach(function (accordion) { + + // Allow for each toggle to both open and close individually + const allowToggle = accordion.hasAttribute('data-allow-toggle'); + // Allow for multiple accordion sections to be expanded at the same time + const allowMultiple = accordion.hasAttribute('data-allow-multiple'); + + // Create the array of toggle elements for the accordion group + const triggers = Array.from(accordion.querySelectorAll('.Accordion-trigger')); + const panels = Array.from(accordion.querySelectorAll('.Accordion-panel')); + + accordion.addEventListener('click', function (event) { + const target = event.target; + + if (target.classList.contains('Accordion-trigger')) { + // Check if the current toggle is expanded. + const isExpanded = target.getAttribute('aria-expanded') == 'true'; + + if (!allowMultiple) { + // Close all previously open accordion toggles + triggers.forEach(function (trigger) { + if (trigger.getAttribute('aria-expanded') == 'true') { + // Hide all accordion sections, using aria-controls to specify the desired section + document.getElementById(trigger.getAttribute('aria-controls')).setAttribute('hidden', ''); + // Set the expanded state on the triggering element + trigger.setAttribute('aria-expanded', 'false'); + } + }); + } + + if (allowToggle && isExpanded) { + // Close the activated accordion if allowToggle=true, using aria-controls to specify the desired section + document.getElementById(target.getAttribute('aria-controls')).setAttribute('hidden', ''); + // Set the expanded state on the triggering element + target.setAttribute('aria-expanded', 'false'); + } + else if (!allowToggle && !isExpanded) { + // Otherwise open the activated accordion, using aria-controls to specify the desired section + document.getElementById(target.getAttribute('aria-controls')).removeAttribute('hidden'); + // Set the expanded state on the triggering element + target.setAttribute('aria-expanded', 'true'); + } + + event.preventDefault(); + } + }); + + // Bind keyboard behaviors on the main accordion container + accordion.addEventListener('keydown', function (event) { + const target = event.target; + const key = event.which.toString(); + // 33 = Page Up, 34 = Page Down + const ctrlModifier = (event.ctrlKey && key.match(/33|34/)); + + // Is this coming from an accordion header? + if (target.classList.contains('Accordion-trigger')) { + // Up/ Down arrow and Control + Page Up/ Page Down keyboard operations + // 38 = Up, 40 = Down + if (key.match(/38|40/) || ctrlModifier) { + const index = triggers.indexOf(target); + const direction = (key.match(/34|40/)) ? 1 : -1; + const length = triggers.length; + const newIndex = (index + length + direction) % length; + + triggers[newIndex].focus(); + + event.preventDefault(); + } + else if (key.match(/35|36/)) { + // 35 = End, 36 = Home keyboard operations + switch (key) { + // Go to first accordion + case '36': + triggers[0].focus(); + break; + // Go to last accordion + case '35': + triggers[triggers.length - 1].focus(); + break; + } + + event.preventDefault(); + } + } + else if (ctrlModifier) { + // Control + Page Up/ Page Down keyboard operations + // Catches events that happen inside of panels + panels.forEach(function (panel, index) { + if (panel.contains(target)) { + triggers[index].focus(); + + event.preventDefault(); + } + }); + } + }); + +}); diff --git a/examples/accordion/js/helpers.js b/examples/accordion/js/helpers.js deleted file mode 100644 index 74492cab63..0000000000 --- a/examples/accordion/js/helpers.js +++ /dev/null @@ -1,448 +0,0 @@ -/* -Helper JavaScript functions Lib -Bryan Garaventa, 07/17/2016 -*/ -var query = function (s, o, fn) { - if (!o) { - o = document; - } - - if (typeof fn == 'function') { - return forEach(o.querySelectorAll(s), fn); - } - else { - return o.querySelectorAll(s); - } - }, - forEach = function (obj, fn) { - var a = obj; - - if (typeof fn != 'function') { - return obj; - } - - if (obj && obj.nodeType === 1) { - a = [obj]; - } - - if (isArray(a) && a.length) { - for (var i = 0; i < a.length; i++) { - fn.apply(a[i], [i, a[i]]); - } - } - else if (typeof a == 'object') { - for (var n in a) { - fn.apply(a, [n, a[n]]); - } - } - - return obj; - }, - bind = function (obj, type, fn) { - if (obj.attachEvent) { - obj['e' + type + fn] = fn; - - obj[type + fn] = function () { - obj['e' + type + fn](window.event); - }; - - obj.attachEvent('on' + type, obj[type + fn]); - } - else if (obj.addEventListener) { - obj.addEventListener(type, fn, false); - } - - return obj; - }, - unbind = function (obj, type, fn) { - if (obj.detachEvent) { - obj.detachEvent('on' + type, obj[type + fn]); - obj[type + fn] = null; - } - else if (obj.removeEventListener) { - obj.removeEventListener(type, fn, false); - } - - return obj; - }, - getEl = function (e) { - if (document.getElementById) { - return document.getElementById(e); - } - else if (document.all) { - return document.all[e]; - } - else { - return null; - } - }, - getElsByTag = function (tag, root) { - var l = [], - t = tag || '*', - r = root || document; - - if (r.getElementsByTagName) { - l = r.getElementsByTagName(t); - } - - if (!l || !l.length) { - if (t === '*' && r.all) { - l = r.all; - } - else if (r.all && r.all.tags) { - l = r.all.tags(t); - } - } - - return l; - }, - createEl = function (t) { - var o = document.createElement(t); - - if (arguments.length === 1) { - return o; - } - - if (arguments[1]) { - setAttr(o, arguments[1]); - } - - if (arguments[2]) { - css(o, arguments[2]); - } - - if (arguments[3]) { - addClass(o, arguments[3]); - } - - if (arguments[4]) { - o.appendChild(createText(arguments[4])); - } - - return o; - }, - createText = function (s) { - return document.createTextNode(s); - }, - getAttr = function (e, n) { - if (!e) { - return null; - } - - var a; - - if (e.getAttribute) { - a = e.getAttribute(n); - } - - if (!a && e.getAttributeNode) { - a = e.getAttributeNode(n); - } - - if (!a && e[n]) { - a = e[n]; - } - - return a; - }, - remAttr = function (e, n) { - if (!e) { - return false; - } - - var a = (isArray(n) ? n : [n]); - - for (var i = 0; i < a.length; i++) { - if (e.removeAttribute) { - e.removeAttribute(a[i]); - } - } - - return e; - }, - setAttr = function (obj, name, value) { - if (!obj) { - return null; - } - - if (typeof name === 'string') { - if (obj.setAttribute) { - obj.setAttribute(name, value); - } - } - else if (typeof name === 'object') { - for (n in name) { - if (obj.setAttribute) { - obj.setAttribute(n, name[n]); - } - } - } - - return obj; - }, - css = function (obj, p, v) { - if (!obj) { - return null; - } - - if (obj.nodeName && typeof p === 'string' && !v) { - return (obj.style && obj.style[p] ? obj.style[p] : xGetComputedStyle(obj, p)); - } - - var o = (isArray(obj) ? obj : [obj]), - check = 'top left bottom right width height'; - - for (var i = 0; i < o.length; i++) { - if (typeof p === 'string') { - try { - o[i].style[xCamelize(p)] = (check.indexOf(p) !== -1 && typeof v === 'number' ? v + 'px' : v); - } catch (ex) {} - } - else if (typeof p === 'object') { - for (var a = 1; a < arguments.length; a++) { - for (var n in arguments[a]) { - try { - o[i].style[xCamelize(n)] = (check.indexOf(n) !== -1 && typeof arguments[a][n] === 'number' ? arguments[a][n] + 'px' : arguments[a][n]); - } catch (ex) {} - } - } - } - } - - return obj; - }, - trim = function (str) { - return str.replace(/^\s+|\s+$/g, ''); - }, - isArray = function (v) { - return v && typeof v === 'object' && typeof v.length === 'number' && typeof v.splice === 'function' && !v.propertyIsEnumerable('length'); - }, - inArray = function (search, stack) { - if (stack.indexOf) { - return stack.indexOf(search); - } - - for (var i = 0; i < stack.length; i++) { - if (stack[i] === search) { - return i; - } - } - - return -1; - }, - hasClass = function (obj, cn) { - if (!obj || !obj.className) { - return false; - } - - var names = cn.split(' '), - i = 0; - - for (var n = 0; n < names.length; n++) { - if (obj.className.indexOf(names[n]) !== -1) { - i += 1; - } - } - - if (i === names.length) { - return true; - } - - return false; - }, - addClass = function (obj, cn) { - if (!obj) { - return null; - } - - var o = (isArray(obj) ? obj : [obj]), - names = cn.split(' '); - - for (var i = 0; i < o.length; i++) { - for (var n = 0; n < names.length; n++) { - if (!hasClass(o[i], names[n])) { - o[i].className = trim(o[i].className + ' ' + names[n]); - } - } - } - - return obj; - }, - remClass = function (obj, cn) { - if (!obj) { - return null; - } - - var o = (isArray(obj) ? obj : [obj]), - names = cn.split(' '); - - for (var i = 0; i < o.length; i++) { - if (o[i].nodeType === 1 && o[i].className) { - for (var n = 0; n < names.length; n++) { - var classes = o[i].className.split(' '); - var a = inArray(names[n], classes); - - if (a !== -1) { - classes.splice(a, 1); - - if (classes.length) { - o[i].className = trim(classes.join(' ')); - } - else { - o[i].className = ''; - } - } - } - } - } - - return obj; - }, - firstChild = function (e, t) { - var e = (e ? e.firstChild : null); - - while (e) { - if (e.nodeType === 1 && (!t || t.toLowerCase() === e.nodeName.toLowerCase())) { - break; - } - - e = e.nextSibling; - } - - return e; - }, - lastChild = function (e, t) { - var e = (e ? e.lastChild : null); - - while (e) { - if (e.nodeType === 1 && (!t || t.toLowerCase() === e.nodeName.toLowerCase())) { - break; - } - - e = e.previousSibling; - } - - return e; - }, - nextSib = function (e, t) { - var e = (e ? e.nextSibling : null); - - while (e) { - if (e.nodeType === 1 && (!t || t.toLowerCase() === e.nodeName.toLowerCase())) { - break; - } - - e = e.nextSibling; - } - - return e; - }, - prevSib = function (e, t) { - var e = (e ? e.previousSibling : null); - - while (e) { - if (e.nodeType === 1 && (!t || t.toLowerCase() === e.nodeName.toLowerCase())) { - break; - } - - e = e.previousSibling; - } - - return e; - }, - getClosest = function (start, targ) { - while (start) { - start = start.parentNode; - - if (typeof targ === 'string') { - if (start.nodeName.toLowerCase() === targ.toLowerCase()) { - return start; - } - } - else if (targ.nodeType === 1) { - if (start == targ) { - return start; - } - } - } - - return null; - }, - insertBefore = function (f, s) { - if (!f) { - return s; - } - - f.parentNode.insertBefore(s, f); - return s; - }, - insertAfter = function (f, s) { - if (!f) { - return s; - } - - if (nextSib(f)) { - f.parentNode.insertBefore(s, nextSib(f)); - } - else { - f.parentNode.appendChild(s); - } - - return s; - }, - xCamelize = function (cssPropStr) { - var i, c, a, s; - a = cssPropStr.split('-'); - s = a[0]; - - for (i = 1; i < a.length; i++) { - c = a[i].charAt(0); - s += a[i].replace(c, c.toUpperCase()); - } - - return s; - }, - xGetComputedStyle = function (e, p, i) { - if (!e) { - return null; - } - - var s, - v = 'undefined', - dv = document.defaultView; - - if (dv && dv.getComputedStyle) { - if (e == document) { - e = document.body; - } - - s = dv.getComputedStyle(e, ''); - - if (s) { - v = s.getPropertyValue(p); - } - } - else if (e.currentStyle) { - v = e.currentStyle[xCamelize(p)]; - } - else { - return null; - } - - return (i ? parseInt(v) || 0 : v); - }, - xOffset = function (c, p) { - var o = { - left: 0, - top: 0 - }, - p = p || document.body; - - while (c && c != p) { - o.left += c.offsetLeft; - o.top += c.offsetTop; - c = c.offsetParent; - } - - return o; - }; diff --git a/examples/accordion/js/setup.js b/examples/accordion/js/setup.js deleted file mode 100644 index 505536a970..0000000000 --- a/examples/accordion/js/setup.js +++ /dev/null @@ -1,104 +0,0 @@ -/* -Setup for the Accordion demo -Uses functions from the Helper JavaScript Functions Lib -Bryan Garaventa, 07/21/2016 -*/ - -// Execute when the page finishes loading -bind(window, 'load', function () { - - // Configure optional functionality - - // Allow for each toggle to both open and close individually - var allowToggle = false, - - // Allow for multiple accordion sections to be expanded at the same time - allowMultiple = false; - - // Declare function for toggling the activated accordion toggle - var toggleAccordion = function (o) { - - // Check if the current toggle is expanded. - var isOpen = getAttr(o, 'aria-expanded') == 'true' ? true : false; - - if (!allowMultiple) { - // Close all previously open accordion toggles - forEach(toggles, function (i, p) { - // Hide all accordion sections, using aria-controls to specify the desired section - addClass(getEl(getAttr(p, 'aria-controls')), 'hidden'); - // Set the expanded state on the triggering element - setAttr(p, 'aria-expanded', 'false'); - remClass(p, 'open'); - - if (!allowToggle) { - remAttr(p, 'aria-disabled'); - } - }); - } - - if (allowToggle && isOpen) { - // Close the activated accordion if allowToggle=true, using aria-controls to specify the desired section - addClass(getEl(getAttr(o, 'aria-controls')), 'hidden'); - // Set the expanded state on the triggering element - setAttr(o, 'aria-expanded', 'false'); - remClass(o, 'open'); - } - - else { - // Otherwise open the activated accordion, using aria-controls to specify the desired section - remClass(getEl(getAttr(o, 'aria-controls')), 'hidden'); - // Set the expanded state on the triggering element - setAttr(o, 'aria-expanded', 'true'); - addClass(o, 'open'); - } - - if (!allowToggle) { - // Set aria-disabled='true' if the accordion toggle is locked in an open state - setAttr(o, 'aria-disabled', 'true'); - } - }, - - // Create the array of toggle elements for the accordion group, using the shared 'accAccordion' class name - toggles = query('*.accAccordion', document, function (i, o) { - - // Create a click binding for mouse and touch device support, plus works for keyboard support on all native active elements like links and buttons - bind(o, 'click', function (ev) { - toggleAccordion(this); - - if (ev.preventDefault) { - ev.preventDefault(); - } - - else { - return false; - } - }); - - // Now create a redundant keyDown binding to support all simulated elements such as all focusable divs and spans to provide the same functionality and accessibility - // tabindex="0" is required on such simulated elements - // Adding arrow key support here is okay, though all accordion toggles must also be in the regular tab order too. - bind(o, 'keydown', function (ev) { - var k = ev.which || ev.keyCode; - - // 13 = Enter, 32 = Spacebar - if (k == '13' || k == '32') { - toggleAccordion(this); - - if (ev.preventDefault) { - ev.preventDefault(); - } - - else { - return false; - } - } - }); - }); - - // Check for the presence of data-defaultopen="true" and automatically open that accordion if found - forEach(toggles, function (i, o) { - if (getAttr(o, 'data-defaultopen') == 'true') { - toggleAccordion(o); - } - }); -});