diff --git a/examples/accordion/accordion.html b/examples/accordion/accordion.html index 06d1e25e85..11697238d6 100644 --- a/examples/accordion/accordion.html +++ b/examples/accordion/accordion.html @@ -29,168 +29,133 @@
The below example section contains a simple personal information input form divided into 3 sections 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. + design pattern for accordion.
- The visual design includes features intended to help users understand that the accordion provides enhanced keyboard navigation functions. - When an accordion header button has keyboard focus, the styling of the accordion container and all its header buttons is changed. -
-When any accordion header button receives focus:
-The focused accordion header button:
-Down Arrow | -
-
|
-
---|---|
Up Arrow | -
-
|
-
Home | -When focus is on an accordion header, moves focus to the first accordion header. | -
End | -When focus is on an accordion header, moves focus to the last accordion header. | -
aria-disabled="true"
button
true
.
- region
diff --git a/examples/accordion/css/accordion.css b/examples/accordion/css/accordion.css
index e95fa5add2..c84ae62233 100644
--- a/examples/accordion/css/accordion.css
+++ b/examples/accordion/css/accordion.css
@@ -1,4 +1,4 @@
-.Accordion {
+.accordion {
margin: 0;
padding: 0;
border: 2px solid hsl(0, 0%, 52%);
@@ -6,24 +6,24 @@
width: 20em;
}
-.Accordion h3 {
+.accordion h3 {
margin: 0;
padding: 0;
}
-.Accordion.focus {
+.accordion:focus-within {
border-color: hsl(216, 94%, 43%);
}
-.Accordion.focus h3 {
+.accordion:focus-within h3 {
background-color: hsl(0, 0%, 97%);
}
-.Accordion > * + * {
+.accordion > * + * {
border-top: 1px solid hsl(0, 0%, 52%);
}
-.Accordion-trigger {
+.accordion-trigger {
background: none;
color: hsl(0, 0%, 13%);
display: block;
@@ -37,28 +37,34 @@
outline: none;
}
-.Accordion-trigger:focus,
-.Accordion-trigger:hover {
+.accordion-trigger:focus,
+.accordion-trigger:hover {
background: hsl(216, 94%, 94%);
}
-.Accordion-trigger:focus {
+.accordion-trigger:focus {
outline: 4px solid transparent;
}
-.Accordion *:first-child .Accordion-trigger {
+.accordion > *:first-child .accordion-trigger,
+.accordion > *:first-child {
border-radius: 5px 5px 0 0;
}
+.accordion > *:last-child .accordion-trigger,
+.accordion > *:last-child {
+ border-radius: 0 0 5px 5px;
+}
+
button {
border-style: none;
}
-.Accordion button::-moz-focus-inner {
+.accordion button::-moz-focus-inner {
border: 0;
}
-.Accordion-title {
+.accordion-title {
display: block;
pointer-events: none;
border: transparent 2px solid;
@@ -67,11 +73,11 @@ button {
outline: none;
}
-.Accordion-trigger:focus .Accordion-title {
+.accordion-trigger:focus .accordion-title {
border-color: hsl(216, 94%, 43%);
}
-.Accordion-icon {
+.accordion-icon {
border: solid currentColor;
border-width: 0 2px 2px 0;
height: 0.5rem;
@@ -83,22 +89,22 @@ button {
width: 0.5rem;
}
-.Accordion-trigger:focus .Accordion-icon,
-.Accordion-trigger:hover .Accordion-icon {
+.accordion-trigger:focus .accordion-icon,
+.accordion-trigger:hover .accordion-icon {
border-color: hsl(216, 94%, 43%);
}
-.Accordion-trigger[aria-expanded="true"] .Accordion-icon {
+.accordion-trigger[aria-expanded="true"] .accordion-icon {
transform: translateY(-50%) rotate(-135deg);
}
-.Accordion-panel {
+.accordion-panel {
margin: 0;
padding: 1em 1.5em;
}
/* For Edge bug https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4806035/ */
-.Accordion-panel[hidden] {
+.accordion-panel[hidden] {
display: none;
}
diff --git a/examples/accordion/js/accordion.js b/examples/accordion/js/accordion.js
index c162b452c5..fd471a9635 100644
--- a/examples/accordion/js/accordion.js
+++ b/examples/accordion/js/accordion.js
@@ -7,129 +7,54 @@
'use strict';
-Array.prototype.slice
- .call(document.querySelectorAll('.Accordion'))
- .forEach(function (accordion) {
- // Allow for multiple accordion sections to be expanded at the same time
- var allowMultiple = accordion.hasAttribute('data-allow-multiple');
- // Allow for each toggle to both open and close individually
- var allowToggle = allowMultiple
- ? allowMultiple
- : accordion.hasAttribute('data-allow-toggle');
+class Accordion {
+ constructor(domNode) {
+ this.rootEl = domNode;
+ this.buttonEl = this.rootEl.querySelector('button[aria-expanded]');
- // Create the array of toggle elements for the accordion group
- var triggers = Array.prototype.slice.call(
- accordion.querySelectorAll('.Accordion-trigger')
- );
+ const controlsId = this.buttonEl.getAttribute('aria-controls');
+ this.contentEl = document.getElementById(controlsId);
- accordion.addEventListener('click', function (event) {
- var target = event.target;
+ this.open = this.buttonEl.getAttribute('aria-expanded') === 'true';
- if (target.classList.contains('Accordion-trigger')) {
- // Check if the current toggle is expanded.
- var isExpanded = target.getAttribute('aria-expanded') == 'true';
- var active = accordion.querySelector('[aria-expanded="true"]');
+ // add event listeners
+ this.buttonEl.addEventListener('click', this.onButtonClick.bind(this));
+ }
- // without allowMultiple, close the open accordion
- if (!allowMultiple && active && active !== target) {
- // Set the expanded state on the triggering element
- active.setAttribute('aria-expanded', 'false');
- // Hide the accordion sections, using aria-controls to specify the desired section
- document
- .getElementById(active.getAttribute('aria-controls'))
- .setAttribute('hidden', '');
+ onButtonClick() {
+ this.toggle(!this.open);
+ }
- // When toggling is not allowed, clean up disabled state
- if (!allowToggle) {
- active.removeAttribute('aria-disabled');
- }
- }
-
- if (!isExpanded) {
- // Set the expanded state on the triggering element
- target.setAttribute('aria-expanded', 'true');
- // Hide the accordion sections, using aria-controls to specify the desired section
- document
- .getElementById(target.getAttribute('aria-controls'))
- .removeAttribute('hidden');
-
- // If toggling is not allowed, set disabled state on trigger
- if (!allowToggle) {
- target.setAttribute('aria-disabled', 'true');
- }
- } else if (allowToggle && isExpanded) {
- // Set the expanded state on the triggering element
- target.setAttribute('aria-expanded', 'false');
- // Hide the accordion sections, using aria-controls to specify the desired section
- document
- .getElementById(target.getAttribute('aria-controls'))
- .setAttribute('hidden', '');
- }
-
- event.preventDefault();
- }
- });
-
- // Bind keyboard behaviors on the main accordion container
- accordion.addEventListener('keydown', function (event) {
- var target = event.target;
- var key = event.which.toString();
-
- // 33 = Page Up, 34 = Page Down
- var 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) {
- var index = triggers.indexOf(target);
- var direction = key.match(/34|40/) ? 1 : -1;
- var length = triggers.length;
- var 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();
- }
- }
- });
-
- // These are used to style the accordion when one of the buttons has focus
- accordion
- .querySelectorAll('.Accordion-trigger')
- .forEach(function (trigger) {
- trigger.addEventListener('focus', function () {
- accordion.classList.add('focus');
- });
-
- trigger.addEventListener('blur', function () {
- accordion.classList.remove('focus');
- });
- });
+ toggle(open) {
+ // don't do anything if the open state doesn't change
+ if (open === this.open) {
+ return;
+ }
- // Minor setup: will set disabled state, via aria-disabled, to an
- // expanded/ active accordion which is not allowed to be toggled close
- if (!allowToggle) {
- // Get the first expanded/ active accordion
- var expanded = accordion.querySelector('[aria-expanded="true"]');
+ // update the internal state
+ this.open = open;
- // If an expanded/ active accordion is found, disable
- if (expanded) {
- expanded.setAttribute('aria-disabled', 'true');
- }
+ // handle DOM updates
+ this.buttonEl.setAttribute('aria-expanded', `${open}`);
+ if (open) {
+ this.contentEl.removeAttribute('hidden');
+ } else {
+ this.contentEl.setAttribute('hidden', '');
}
- });
+ }
+
+ // Add public open and close methods for convenience
+ open() {
+ this.toggle(true);
+ }
+
+ close() {
+ this.toggle(false);
+ }
+}
+
+// init accordions
+const accordions = document.querySelectorAll('.accordion h3');
+accordions.forEach((accordionEl) => {
+ new Accordion(accordionEl);
+});
diff --git a/examples/index.html b/examples/index.html
index 0f478b3984..e253cb61cf 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -547,7 +547,6 @@ aria-disabled