|
@@ -479,7 +495,7 @@ Role, Property, State, and Tabindex Attributes
aria-controls="IDREF"
- a
+ button
|
@@ -495,7 +511,7 @@ Role, Property, State, and Tabindex Attributes
Javascript and CSS Source Code
- - CSS: carousel.css
+ - CSS: carousel-1.css
- Javascript: carousel.js
- Javascript: carouselItem.js
- Javascript: carouselButtons.js
diff --git a/examples/carousel/carousel-1/css/carousel.css b/examples/carousel/carousel-1/css/carousel.css
deleted file mode 100644
index 11ce7cd722..0000000000
--- a/examples/carousel/carousel-1/css/carousel.css
+++ /dev/null
@@ -1,176 +0,0 @@
-
-/* .carousel */
-.carousel-item {
- display: none;
- max-height: 400px;
- max-width: 900px;
- position: relative;
- overflow: hidden;
- width: 100%;
-}
-
-.carousel .carousel-item.active {
- display: block;
-}
-
-/* More like bootstrap, less accessible */
-
-.carousel .carousel-inner {
- max-width: 900px;
- position: relative;
-}
-
-.carousel button.pause {
- display: block;
- font-size: 20px;
- width: auto;
- left: -300em;
- margin-bottom: 10px;
- height: auto;
- position: relative;
- top: 5px;
- right: -20px;
- border: thin solid outset;
-}
-
-.carousel button[aria-disabled=true] {
- color: #666;
-}
-
-.carousel button.pause:focus {
- display: block;
- position: relative;
- font-size: 20px;
- width: auto;
- left: 0;
- margin-bottom: 10px;
- height: auto;
- top: 5px;
- right: -20px;
-}
-
-.carousel .carousel-items {
- border: solid 2px transparent;
-}
-
-.carousel .carousel-items.focus {
- border-color: white;
- outline: solid 3px #005a9c;
-}
-
-.carousel .carousel-inner .carousel-image a img {
- height: 100%;
- width: 100%;
-}
-
-.carousel .carousel-inner .carousel-caption a {
- text-decoration: underline;
- border: none;
-}
-
-.carousel .carousel-inner .carousel-caption h3 a {
- color: #fff;
- font-weight: 600;
-}
-
-.carousel .carousel-inner .carousel-caption a:focus,
-.carousel .carousel-inner .carousel-caption a:hover {
- outline: solid 2px #fff;
- outline-offset: 1px;
-}
-
-.carousel .carousel-inner .carousel-caption p {
- font-size: 1em;
- line-height: 1.5;
- margin-bottom: 0;
-}
-
-.carousel .carousel-caption {
- position: absolute;
- right: 15%;
- bottom: 0;
- left: 15%;
- padding-top: 20px;
- padding-bottom: 20px;
- color: #fff;
- text-align: center;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
-}
-
-.carousel .carousel-inner .carousel-caption {
- bottom: 0;
- left: 0;
- padding: 3% 3% 50px;
- right: 0;
- text-shadow: none;
-}
-
-.carousel:hover .carousel-inner .carousel-caption,
-.carousel .carousel-item.focus .carousel-caption {
- background-color: rgba(0, 0, 0, 0.4);
-}
-
-.carousel .carousel-inner,
-.carousel .carousel-item,
-.carousel .carousel-slide {
- max-height: 400px;
-}
-
-.carousel .carousel-control {
- position: absolute;
- top: 0;
- z-index: 10;
- font-size: 200%;
- font-weight: bold;
- color: #fff;
- text-align: center;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
-}
-
-.carousel a.carousel-control svg {
- position: relative;
- display: inline-block;
- top: 45%;
-}
-
-.carousel a.carousel-control svg polygon {
- opacity: 0.7;
-}
-
-.carousel a.carousel-control:focus {
- border: 3px solid #fff;
- outline: 1px solid #005a9c;
-}
-
-.carousel a.carousel-control:focus svg polygon,
-.carousel a.carousel-control:hover svg polygon {
- opacity: 1;
-}
-
-.carousel a.carousel-control.previous {
- bottom: 0;
- width: 15%;
- background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
-}
-
-.carousel a.carousel-control.previous:focus,
-.carousel a.carousel-control.previous:hover {
- bottom: 0;
- width: 15%;
- background-image: linear-gradient(to right, rgba(0, 0, 0, 0.7) 0, rgba(0, 0, 0, 0.0001) 100%);
-}
-
-.carousel a.carousel-control.next {
- right: 0;
- bottom: 0;
- width: 15%;
- background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
-}
-
-.carousel a.carousel-control.next:focus,
-.carousel a.carousel-control.next:hover {
- right: 0;
- bottom: 0;
- width: 15%;
- background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.7) 100%);
-}
diff --git a/examples/carousel/carousel-1/js/pauseButton.js b/examples/carousel/carousel-1/js/pauseButton.js
deleted file mode 100644
index ebd875ee56..0000000000
--- a/examples/carousel/carousel-1/js/pauseButton.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
-* File: pasueButton.js
-*
-* Desc: Implements the pause button for the carousel widget
-*
-*/
-
-var PauseButton = function (domNode, carouselObj) {
- this.domNode = domNode;
-
- this.carousel = carouselObj;
-};
-
-var StartButton = function (domNode, carouselObj) {
- this.domNode = domNode;
-
- this.carousel = carouselObj;
-};
-
-PauseButton.prototype.init = function () {
- this.domNode.addEventListener('click', this.handleClick.bind(this));
- this.domNode.addEventListener('focus', this.handleFocus.bind(this));
- this.domNode.addEventListener('blur', this.handleBlur.bind(this));
-};
-
-/* EVENT HANDLERS */
-
-PauseButton.prototype.handleClick = function (event) {
- this.carousel.toggleRotation();
-};
-
-PauseButton.prototype.handleFocus = function (event) {
- this.domNode.classList.add('focus');
-};
-
-PauseButton.prototype.handleBlur = function (event) {
- this.domNode.classList.remove('focus');
-};
diff --git a/examples/carousel/css/carousel-1-more-accessible.css b/examples/carousel/css/carousel-1-more-accessible.css
new file mode 100644
index 0000000000..4342f03dd6
--- /dev/null
+++ b/examples/carousel/css/carousel-1-more-accessible.css
@@ -0,0 +1,185 @@
+/* .carousel */
+
+.carousel .carousel-inner {
+ display: static;
+}
+
+.carousel .carousel-item {
+ display: none;
+ max-width: 900px;
+ width: 100%;
+}
+
+.carousel .carousel-item.active {
+ display: block;
+}
+
+/* More like bootstrap, less accessible */
+
+/* Shared CSS for Pause, Next and Previous Slide Controls */
+
+.carousel .controls {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ position: relative;
+ height: 36px;
+ background-color: #eee;
+ border: 4px solid #eee;
+ border-radius: 5px 5px 0 0;
+}
+
+.carousel .controls button {
+ position: absolute;
+ top: 6px;
+ display: block;
+ background-color: transparent;
+ border: none;
+ outline: none;
+}
+
+.carousel .controls button.previous {
+ right: 58px;
+}
+
+.carousel .controls button.next {
+ right: 4px;
+}
+
+.carousel .controls button.rotation {
+ left: 4px;
+}
+
+.carousel .controls button svg rect.background {
+ stroke: black;
+ fill: black;
+ stroke-width: 1px;
+ opacity: 0.8;
+}
+
+.carousel .controls button svg rect.border {
+ fill: transparent;
+ stroke: transparent;
+ stroke-width: 2px;
+}
+
+/* Next and Previous Slide Controls */
+
+.carousel .controls button svg polygon {
+ stroke: white;
+ fill: white;
+ stroke-width: 2;
+ opacity: 1;
+}
+
+.carousel .controls button.rotation svg polygon.pause {
+ stroke-width: 4;
+ fill: transparent;
+ stroke: transparent;
+}
+
+.carousel .controls button.rotation svg polygon.play {
+ stroke-width: 1;
+ fill: transparent;
+ stroke: transparent;
+}
+
+.carousel .controls button.rotation.pause svg polygon.pause,
+.carousel .controls button.rotation.play svg polygon.play {
+ fill: white;
+ stroke: white;
+}
+
+/* Common focus styling for svg buttons */
+
+.carousel .controls button:focus rect.background,
+.carousel .controls button:hover rect.background,
+.carousel .controls button:focus rect.border,
+.carousel .controls button:hover rect.border {
+ fill: #005a9c;
+ stroke: #005a9c;
+ opacity: 1;
+}
+
+.carousel .controls button:focus rect.border {
+ stroke: white;
+}
+
+/* Caption Positioning */
+
+.carousel .carousel-items {
+ width: 100%;
+ background-color: #eee;
+ border: solid 4px #eee;
+ border-radius: 0 0 5px 5px;
+}
+
+.carousel .carousel-items.focus {
+ border-color: #005a9c;
+}
+
+.carousel .carousel-item .carousel-image {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+
+.carousel .carousel-item .carousel-image a {
+ margin: 0;
+ padding: 0;
+}
+
+.carousel .carousel-item .carousel-image a img {
+ margin: 0;
+ padding: 0;
+ display: block;
+ overflow: hidden;
+ max-height: 100%;
+ max-width: 100%;
+}
+
+.carousel .carousel-item .carousel-caption {
+ margin: 0;
+ padding: 0.5em;
+ width: 100%;
+ height: 3em;
+ text-align: center;
+}
+
+.carousel .carousel-item .carousel-caption a {
+ display: inline-block;
+ background-color: rgba(0, 0, 0, 0);
+ padding-left: 0.25em;
+ padding-right: 0.25em;
+ padding-top: 0.125em;
+ padding-bottom: 0.125em;
+ border-radius: 5px;
+ border: 2px solid transparent;
+ margin: 0;
+ text-decoration: underline;
+}
+
+.carousel .carousel-item .carousel-caption h3 {
+ margin: 0;
+ padding: 0;
+ font-weight: bold;
+}
+
+.carousel .carousel-item .carousel-caption h3 a {
+ color: black;
+}
+
+.carousel .carousel-item .carousel-caption a:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.carousel .carousel-item .carousel-caption a:focus {
+ border-color: #005a9c;
+ background-color: rgba(0, 0, 0, 0.1);
+ outline: none;
+}
+
+.carousel .carousel-item .carousel-caption p {
+ margin: 0;
+ padding: 0;
+}
diff --git a/examples/carousel/css/carousel-1.css b/examples/carousel/css/carousel-1.css
new file mode 100644
index 0000000000..735f61e3db
--- /dev/null
+++ b/examples/carousel/css/carousel-1.css
@@ -0,0 +1,165 @@
+
+/* .carousel */
+
+.carousel .carousel-inner {
+ position: relative;
+}
+
+.carousel .carousel-item {
+ display: none;
+ max-height: 400px;
+ max-width: 900px;
+ position: relative;
+ overflow: hidden;
+ width: 100%;
+}
+
+.carousel .carousel-item.active {
+ display: block;
+}
+
+/* More like bootstrap, less accessible */
+
+.carousel .carousel-items {
+ border: solid 2px transparent;
+}
+
+.carousel .carousel-items.focus {
+ border-color: white;
+ outline: solid 3px #005a9c;
+}
+
+.carousel .carousel-item .carousel-image a img {
+ height: 100%;
+ width: 100%;
+}
+
+.carousel .carousel-item .carousel-caption a {
+ text-decoration: underline;
+}
+
+.carousel .carousel-item .carousel-caption a,
+.carousel .carousel-item .carousel-caption span.contrast {
+ display: inline-block;
+ background-color: rgba(0, 0, 0, 0.65);
+ padding-left: 0.25em;
+ padding-right: 0.25em;
+ border-radius: 5px;
+ border: 2px solid transparent;
+ margin: 0;
+}
+
+.carousel .carousel-item .carousel-caption h3 a {
+ color: #fff;
+ font-weight: 600;
+}
+
+.carousel .carousel-item .carousel-caption a:hover,
+.carousel .carousel-item .carousel-caption span.contrast:hover {
+ background-color: rgba(0, 0, 0, 1);
+ margin: 0;
+}
+
+.carousel .carousel-item .carousel-caption a:focus {
+ background-color: rgba(0, 0, 0, 1);
+ border-color: #fff;
+ margin: 0;
+}
+
+.carousel .carousel-item .carousel-caption p {
+ font-size: 1em;
+ line-height: 1.5;
+ margin-bottom: 0;
+}
+
+.carousel .carousel-item .carousel-caption {
+ position: absolute;
+ right: 15%;
+ bottom: 0;
+ left: 15%;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ color: #fff;
+ text-align: center;
+}
+
+/* Shared CSS for Pause, Next and Previous Slide Controls */
+
+.carousel .controls button {
+ padding: 0;
+ position: absolute;
+ top: 5px;
+ z-index: 10;
+ background-color: transparent;
+ border: none;
+ outline: none;
+}
+
+.carousel .controls button svg rect.background {
+ stroke: black;
+ fill: black;
+ stroke-width: 1px;
+ opacity: 0.6;
+}
+
+.carousel .controls button svg rect.border {
+ fill: transparent;
+ stroke: transparent;
+ stroke-width: 2px;
+}
+
+/* Next and Previous Slide Controls */
+
+.carousel .controls button svg polygon {
+ stroke: white;
+ fill: white;
+ stroke-width: 2;
+ opacity: 1;
+}
+
+.carousel .controls button.previous {
+ right: 50px;
+}
+
+.carousel .controls button.next {
+ right: 6px;
+}
+
+/* Pause Control */
+
+.carousel .controls button.rotation {
+ left: 6px;
+}
+
+.carousel .controls button.rotation svg polygon.pause {
+ stroke-width: 4;
+ fill: transparent;
+ stroke: transparent;
+}
+
+.carousel .controls button.rotation svg polygon.play {
+ stroke-width: 1;
+ fill: transparent;
+ stroke: transparent;
+}
+
+.carousel .controls button.rotation.pause svg polygon.pause,
+.carousel .controls button.rotation.play svg polygon.play {
+ fill: white;
+ stroke: white;
+}
+
+/* Common focus styling for svg buttons */
+
+.carousel .controls button:focus rect.background,
+.carousel .controls button:hover rect.background,
+.carousel .controls button:focus rect.border,
+.carousel .controls button:hover rect.border {
+ fill: #005a9c;
+ stroke: #005a9c;
+ opacity: 1;
+}
+
+.carousel .controls button:focus rect.border {
+ stroke: white;
+}
diff --git a/examples/carousel/carousel-1/images/amsterdamslide__800x600.jpg b/examples/carousel/images/amsterdamslide__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/amsterdamslide__800x600.jpg
rename to examples/carousel/images/amsterdamslide__800x600.jpg
diff --git a/examples/carousel/carousel-1/images/britcomdavidslide__800x600.jpg b/examples/carousel/images/britcomdavidslide__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/britcomdavidslide__800x600.jpg
rename to examples/carousel/images/britcomdavidslide__800x600.jpg
diff --git a/examples/carousel/carousel-1/images/foyleswarslide__800x600.jpg b/examples/carousel/images/foyleswarslide__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/foyleswarslide__800x600.jpg
rename to examples/carousel/images/foyleswarslide__800x600.jpg
diff --git a/examples/carousel/carousel-1/images/lands-endslide__800x600.jpg b/examples/carousel/images/lands-endslide__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/lands-endslide__800x600.jpg
rename to examples/carousel/images/lands-endslide__800x600.jpg
diff --git a/examples/carousel/carousel-1/images/mag800-2__800x600.jpg b/examples/carousel/images/mag800-2__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/mag800-2__800x600.jpg
rename to examples/carousel/images/mag800-2__800x600.jpg
diff --git a/examples/carousel/carousel-1/images/trustslide-2__800x600.jpg b/examples/carousel/images/trustslide-2__800x600.jpg
similarity index 100%
rename from examples/carousel/carousel-1/images/trustslide-2__800x600.jpg
rename to examples/carousel/images/trustslide-2__800x600.jpg
diff --git a/examples/carousel/carousel-1/js/carousel.js b/examples/carousel/js/carousel.js
similarity index 68%
rename from examples/carousel/carousel-1/js/carousel.js
rename to examples/carousel/js/carousel.js
index 9e3071e921..96e79aeaef 100644
--- a/examples/carousel/carousel-1/js/carousel.js
+++ b/examples/carousel/js/carousel.js
@@ -22,8 +22,8 @@ var Carousel = function (domNode) {
this.currentItem = null;
this.pauseButton = null;
- this.startLabel = 'Start automatic slide show';
- this.stopLabel = 'Stop automatic slide show';
+ this.playLabel = 'Start automatic slide show';
+ this.pauseLabel = 'Stop automatic slide show';
this.rotate = true;
this.hasFocus = false;
@@ -34,12 +34,14 @@ var Carousel = function (domNode) {
Carousel.prototype.init = function () {
+ var elems, elem, button, items, item, imageLinks, i;
+
this.liveRegionNode = this.domNode.querySelector('.carousel-items');
- var items = this.domNode.querySelectorAll('.carousel-item');
+ items = this.domNode.querySelectorAll('.carousel-item');
- for (var i = 0; i < items.length; i++) {
- var item = new CarouselItem(items[i], this);
+ for (i = 0; i < items.length; i++) {
+ item = new CarouselItem(items[i], this);
item.init();
this.items.push(item);
@@ -50,7 +52,7 @@ Carousel.prototype.init = function () {
}
this.lastItem = item;
- var imageLinks = items[i].querySelectorAll('.carousel-image a');
+ imageLinks = items[i].querySelectorAll('.carousel-image a');
if (imageLinks && imageLinks[0]) {
imageLinks[0].addEventListener('focus', this.handleImageLinkFocus.bind(this));
@@ -59,27 +61,28 @@ Carousel.prototype.init = function () {
}
- // Next Slide and Previous Slide Buttons
+ // Pause, Next Slide and Previous Slide Buttons
- var elems = document.querySelectorAll('.carousel a.carousel-control');
+ elems = document.querySelectorAll('.carousel .controls button');
- for (var i = 0; i < elems.length; i++) {
- if (elems[i].tagName.toLowerCase() == 'a') {
- var button = new CarouselButton(elems[i], this);
+ for (i = 0; i < elems.length; i++) {
+ elem = elems[i];
- button.init();
+ if (elem.classList.contains('rotation')) {
+ button = new PauseButton(elem, this);
+ this.pauseButton = elem;
+ this.pauseButton.classList.add('pause');
+ this.pauseButton.setAttribute('aria-label', this.pauseLabel);
+ }
+ else {
+ button = new CarouselButton(elem, this);
}
- }
-
- this.currentItem = this.firstItem;
- this.pauseButton = this.domNode.parentNode.parentNode.querySelector('button.pause');
- if (this.pauseButton) {
- var button = new PauseButton(this.pauseButton, this);
button.init();
- this.pauseButton.innerHTML = this.stopLabel;
}
+ this.currentItem = this.firstItem;
+
this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this));
this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this));
@@ -151,42 +154,42 @@ Carousel.prototype.rotateSlides = function () {
setTimeout(this.rotateSlides.bind(this), this.timeInterval);
};
-Carousel.prototype.startRotation = function () {
+Carousel.prototype.updateRotation = function () {
+
if (!this.hasHover && !this.hasFocus && !this.isStopped) {
this.rotate = true;
this.liveRegionNode.setAttribute('aria-live', 'off');
- this.pauseButton.innerHTML = this.stopLabel;
}
- this.disablePauseButton();
-};
-
-Carousel.prototype.stopRotation = function () {
- this.rotate = false;
- this.liveRegionNode.setAttribute('aria-live', 'polite');
- this.pauseButton.innerHTML = this.startLabel;
- this.disablePauseButton();
-};
+ else {
+ this.rotate = false;
+ this.liveRegionNode.setAttribute('aria-live', 'polite');
+ }
-Carousel.prototype.disablePauseButton = function () {
- if (this.hasHover || this.hasFocus) {
- this.pauseButton.setAttribute('aria-disabled', 'true');
+ if (this.isStopped) {
+ this.pauseButton.setAttribute('aria-label', this.playLabel);
+ this.pauseButton.classList.remove('pause');
+ this.pauseButton.classList.add('play');
}
else {
- this.pauseButton.removeAttribute('aria-disabled');
+ this.pauseButton.setAttribute('aria-label', this.pauseLabel);
+ this.pauseButton.classList.remove('play');
+ this.pauseButton.classList.add('pause');
}
+
};
Carousel.prototype.toggleRotation = function () {
if (this.isStopped) {
- if (this.pauseButton.getAttribute('aria-disabled') !== 'true') {
+ if (!this.hasHover && !this.hasFocus) {
this.isStopped = false;
- this.startRotation();
}
}
else {
this.isStopped = true;
- this.stopRotation();
}
+
+ this.updateRotation();
+
};
Carousel.prototype.handleImageLinkFocus = function () {
@@ -197,19 +200,21 @@ Carousel.prototype.handleImageLinkBlur = function () {
this.liveRegionNode.classList.remove('focus');
};
-Carousel.prototype.handleMouseOver = function () {
- this.hasHover = true;
- this.stopRotation();
+Carousel.prototype.handleMouseOver = function (event) {
+ if (!this.pauseButton.contains(event.target)) {
+ this.hasHover = true;
+ }
+ this.updateRotation();
};
Carousel.prototype.handleMouseOut = function () {
this.hasHover = false;
- this.startRotation();
+ this.updateRotation();
};
/* Initialize Carousel Tablists */
-window.addEventListener('load', function (event) {
+window.addEventListener('load', function () {
var carousels = document.querySelectorAll('.carousel');
for (var i = 0; i < carousels.length; i++) {
diff --git a/examples/carousel/carousel-1/js/carouselButtons.js b/examples/carousel/js/carouselButtons.js
similarity index 73%
rename from examples/carousel/carousel-1/js/carouselButtons.js
rename to examples/carousel/js/carouselButtons.js
index 22bde956e9..82c6396775 100644
--- a/examples/carousel/carousel-1/js/carouselButtons.js
+++ b/examples/carousel/js/carouselButtons.js
@@ -33,7 +33,6 @@ var CarouselButton = function (domNode, carouselObj) {
};
CarouselButton.prototype.init = function () {
- 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));
@@ -51,26 +50,6 @@ CarouselButton.prototype.changeItem = function () {
/* EVENT HANDLERS */
-CarouselButton.prototype.handleKeydown = function (event) {
- var flag = false;
-
- switch (event.keyCode) {
- case this.keyCode.SPACE:
- case this.keyCode.RETURN:
- this.changeItem();
- this.domNode.focus();
- flag = true;
- break;
-
- default:
- break;
- }
-
- if (flag) {
- event.stopPropagation();
- event.preventDefault();
- }
-};
CarouselButton.prototype.handleClick = function (event) {
this.changeItem();
@@ -79,11 +58,11 @@ CarouselButton.prototype.handleClick = function (event) {
CarouselButton.prototype.handleFocus = function (event) {
this.carousel.hasFocus = true;
this.domNode.classList.add('focus');
- this.carousel.stopRotation();
+ this.carousel.updateRotation();
};
CarouselButton.prototype.handleBlur = function (event) {
this.carousel.hasFocus = false;
this.domNode.classList.remove('focus');
- this.carousel.startRotation();
+ this.carousel.updateRotation();
};
diff --git a/examples/carousel/carousel-1/js/carouselItem.js b/examples/carousel/js/carouselItem.js
similarity index 93%
rename from examples/carousel/carousel-1/js/carouselItem.js
rename to examples/carousel/js/carouselItem.js
index 7ed5417f63..dc1f1d1513 100644
--- a/examples/carousel/carousel-1/js/carouselItem.js
+++ b/examples/carousel/js/carouselItem.js
@@ -32,11 +32,11 @@ CarouselItem.prototype.show = function () {
CarouselItem.prototype.handleFocusIn = function (event) {
this.domNode.classList.add('focus');
this.carousel.hasFocus = true;
- this.carousel.stopRotation();
+ this.carousel.updateRotation();
};
CarouselItem.prototype.handleFocusOut = function (event) {
this.domNode.classList.remove('focus');
this.carousel.hasFocus = false;
- this.carousel.startRotation();
+ this.carousel.updateRotation();
};
diff --git a/examples/carousel/js/pauseButton.js b/examples/carousel/js/pauseButton.js
new file mode 100644
index 0000000000..833e274307
--- /dev/null
+++ b/examples/carousel/js/pauseButton.js
@@ -0,0 +1,22 @@
+/*
+* File: pasueButton.js
+*
+* Desc: Implements the pause button for the carousel widget
+*
+*/
+
+var PauseButton = function (domNode, carouselObj) {
+ this.domNode = domNode;
+
+ this.carousel = carouselObj;
+};
+
+PauseButton.prototype.init = function () {
+ this.domNode.addEventListener('click', this.handleClick.bind(this));
+};
+
+/* EVENT HANDLERS */
+
+PauseButton.prototype.handleClick = function () {
+ this.carousel.toggleRotation();
+};
diff --git a/test/tests/carousel-1.js b/test/tests/carousel-1.js
index 1ef6cc95c8..6799670016 100644
--- a/test/tests/carousel-1.js
+++ b/test/tests/carousel-1.js
@@ -2,75 +2,251 @@
const { ariaTest } = require('..');
const { By, Key } = require('selenium-webdriver');
+const assertAttributeDNE = require('../util/assertAttributeDNE');
const assertAttributeValues = require('../util/assertAttributeValues');
const assertAriaControls = require('../util/assertAriaControls');
-const assertAriaLabelledby = require('../util/assertAriaLabelledby');
-const assertAriaDescribedby = require('../util/assertAriaDescribedby');
const assertAriaLabelExists = require('../util/assertAriaLabelExists');
const assertAriaRoles = require('../util/assertAriaRoles');
const assertTabOrder = require('../util/assertTabOrder');
-
-const exampleFile = 'carousel/carousel-1/carousel-1.html';
+const exampleFile = 'carousel/carousel-1.html';
const ex = {
landmarkSelector: '#myCarousel',
- previousNextButtonSelector: '#ex1 .carousel-control',
+ buttonSelector: '#ex1 button',
+ pausePlayButtonSelector: '#ex1 button:first-of-type',
+ previousButtonSelector: '#ex1 .previous',
+ nextButtonSelector: '#ex1 .next',
slideContainerSelector: '#ex1 .carousel-items',
- slideSelector: '#ex1 .carousel-item'
+ slideSelector: '#ex1 .carousel-item',
+ allFocusableItems: [
+ '#ex1 button:first-of-type',
+ '#ex1 .previous',
+ '#ex1 .next',
+ '#ex1 .active .carousel-image a',
+ '#ex1 .active .carousel-caption a'
+ ],
+ activeCarouselItem: '#ex1 .active'
};
// Attributes
-ariaTest('Carousel 1: section has aria-label', exampleFile, 'carousel-region-role', async (t) => {
+ariaTest('section element used to contain slider', exampleFile, 'carousel-region-role', async (t) => {
t.plan(1);
- await assertAriaLabelExists(t, ex.landmarkSelector);
+
+ // This test primarially tests that the ex.landmarkSelector points to a `section` element
+ const landmarkEl = await t.context.session.findElement(By.css(ex.landmarkSelector));
+ t.is(
+ await landmarkEl.getTagName(),
+ 'section',
+ ex.landmarkSelector + ' selector should select `section` element'
+ );
});
-ariaTest('Carousel 1: section has aria-roledescription set to carousel', exampleFile, 'carousel-region-aria-roledescription', async (t) => {
+ariaTest('section has aria-roledescription set to carousel', exampleFile, 'carousel-region-aria-roledescription', async (t) => {
t.plan(1);
// check the aria-roledescrption set to carousel
await assertAttributeValues(t, ex.landmarkSelector, 'aria-roledescription', 'carousel');
});
-ariaTest('Carousel 1: previous and next buttons have role button', exampleFile, 'carousel-button-role-link', async (t) => {
+ariaTest('section has aria-label', exampleFile, 'carousel-region-aria-label', async (t) => {
t.plan(1);
- await assertAriaRoles(t, 'myCarousel', 'button', 2, 'a');
-});
-ariaTest('Carousel 1: previous and next buttons have aria-label', exampleFile, 'carousel-button-aria-label-next-previous', async (t) => {
- t.plan(1);
- await assertAriaLabelExists(t, ex.previousNextButtonSelector);
+ await assertAriaLabelExists(t, ex.landmarkSelector);
});
-ariaTest('Carousel 1: previous and next buttons have aria-controls', exampleFile, 'carousel-button-aria-controls', async (t) => {
- t.plan(1);
- await assertAriaControls(t, ex.previousNextButtonSelector);
+ariaTest('slide container have aria-live initially set to off', exampleFile, 'carousel-aria-live', async (t) => {
+ t.plan(4);
+
+ // On page load, `aria-level` is `off`
+ await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'off');
+
+ // Focus on the widget, and aria-selected should change to 'polite'
+ await t.context.session.findElement(By.css(ex.nextButtonSelector)).sendKeys(Key.ENTER);
+
+ await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'polite');
+
+ // Move focus off the widget, and the aria-selected should change to 'off' again
+ await t.context.session.findElement(By.css(ex.nextButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB));
+ await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB));
+ await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'off');
+
+ // Click the pause button, and the aria-selected should change to 'polite' again
+ await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).click();
+ await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'polite');
});
-ariaTest('Carousel 1: slide container have aria-live initially set to off', exampleFile, 'carousel-group-aria-roledescription', async (t) => {
+ariaTest('pause, previous and next buttons have aria-label', exampleFile, 'carousel-button-aria-label', async (t) => {
t.plan(1);
+ await assertAriaLabelExists(t, ex.buttonSelector);
+});
- // check the aria-roledescrption set to carousel
- await assertAttributeValues(t, ex.slideContainerSelector, 'aria-live', 'off');
+ariaTest('previous and next buttons have aria-controls', exampleFile, 'carousel-button-aria-controls', async (t) => {
+ t.plan(2);
+ await assertAriaControls(t, ex.previousButtonSelector);
+ await assertAriaControls(t, ex.nextButtonSelector);
});
-ariaTest('Carousel 1: slides have role group', exampleFile, 'carousel-group-role', async (t) => {
+ariaTest('slides have role group', exampleFile, 'carousel-group-role', async (t) => {
t.plan(1);
await assertAriaRoles(t, 'myCarousel', 'group', 6, 'div');
});
-ariaTest('Carousel 1: slides have aria-label', exampleFile, 'carousel-group-aria-label', async (t) => {
+ariaTest('slides have aria-label', exampleFile, 'carousel-group-aria-label', async (t) => {
t.plan(1);
await assertAriaLabelExists(t, ex.slideSelector);
});
-ariaTest('Carousel 1: slides have aria-roledescription set to slide', exampleFile, 'carousel-group-aria-roledescription', async (t) => {
+ariaTest('slides have aria-roledescription set to slide', exampleFile, 'carousel-group-aria-roledescription', async (t) => {
t.plan(1);
// check the aria-roledescrption set to carousel
await assertAttributeValues(t, ex.slideSelector, 'aria-roledescription', 'slide');
});
+// Keyboard interaction
+
+ariaTest('TAB moves key through buttons', exampleFile, 'carousel-key-tab', async (t) => {
+ t.plan(1);
+
+ await assertTabOrder(t, ex.allFocusableItems);
+});
+
+ariaTest('ENTER pause and start carousel motion', exampleFile, 'carousel-enter-or-space-toggle', async (t) => {
+ t.plan(2);
+
+ let activeElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label');
+
+ await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.ENTER);
+ // Move focus from widget
+ await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB));
+
+ let compareWithNextElement = await t.context.session.wait(async function () {
+ let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label');
+ return activeElement === newActiveElement;
+ }, t.context.WaitTime);
+
+ t.true(
+ compareWithNextElement,
+ 'The active elements should stay the same when the pause button has been sent ENTER'
+ );
+
+ await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.ENTER);
+ // Move focus from widget
+ await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB));
+
+ compareWithNextElement = await t.context.session.wait(async function () {
+ let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label');
+ return activeElement !== newActiveElement;
+ }, t.context.WaitTime);
+
+ t.true(
+ compareWithNextElement,
+ 'The active elements should change when the play button has been sent ENTER'
+ );
+
+});
+
+
+ariaTest('SPACE pause and start carousel motion', exampleFile, 'carousel-enter-or-space-toggle', async (t) => {
+ t.plan(2);
+
+ let activeElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label');
+
+ await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.SPACE);
+ // Move focus from widget
+ await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB));
+
+ let compareWithNextElement = await t.context.session.wait(async function () {
+ let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label');
+ return activeElement === newActiveElement;
+ }, t.context.WaitTime);
+
+ t.true(
+ compareWithNextElement,
+ 'The active elements should stay the same when the pause button has been sent SPACE'
+ );
+
+ await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.SPACE);
+ // Move focus from widget
+ await t.context.session.findElement(By.css(ex.pausePlayButtonSelector)).sendKeys(Key.chord(Key.SHIFT, Key.TAB));
+
+ compareWithNextElement = await t.context.session.wait(async function () {
+ let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem)).getAttribute('aria-label');
+ return activeElement !== newActiveElement;
+ }, t.context.WaitTime);
+
+ t.true(
+ compareWithNextElement,
+ 'The active elements should change when the play button has been sent SPACE'
+ );
+});
+
+
+ariaTest('SPACE on previous and next', exampleFile, 'carousel-key-enter-or-space-move', async (t) => {
+ t.plan(2);
+
+ let activeElement = await t.context.session.findElement(By.css(ex.activeCarouselItem))
+ .getAttribute('aria-label');
+
+ await t.context.session.findElement(By.css(ex.previousButtonSelector)).sendKeys(Key.SPACE);
+
+ let compareWithNextElement = await t.context.session.wait(async function () {
+ let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem))
+ .getAttribute('aria-label');
+ return activeElement !== newActiveElement;
+ }, t.context.WaitTime);
+
+ t.true(
+ compareWithNextElement,
+ 'After sending SPACE to previous button, the carousel should show a different element'
+ );
+
+ await t.context.session.findElement(By.css(ex.nextButtonSelector)).sendKeys(Key.SPACE);
+
+ compareWithNextElement = await t.context.session.wait(async function () {
+ let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem))
+ .getAttribute('aria-label');
+ return activeElement === newActiveElement;
+ }, t.context.WaitTime);
+
+ t.true(
+ compareWithNextElement,
+ 'After sending SPACE to previous button then SPACE to next button, the carousel should show the first carousel item'
+ );
+});
+
+ariaTest('ENTER on previous and next', exampleFile, 'carousel-key-enter-or-space-move', async (t) => {
+ t.plan(2);
+
+ let activeElement = await t.context.session.findElement(By.css(ex.activeCarouselItem))
+ .getAttribute('aria-label');
+
+ await t.context.session.findElement(By.css(ex.previousButtonSelector)).sendKeys(Key.ENTER);
+
+ let compareWithNextElement = await t.context.session.wait(async function () {
+ let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem))
+ .getAttribute('aria-label');
+ return activeElement !== newActiveElement;
+ }, t.context.WaitTime);
+
+ t.true(
+ compareWithNextElement,
+ 'After sending ENTER to previous button, the carousel should show a different element'
+ );
+
+ await t.context.session.findElement(By.css(ex.nextButtonSelector)).sendKeys(Key.ENTER);
+
+ compareWithNextElement = await t.context.session.wait(async function () {
+ let newActiveElement = await t.context.session.findElement(By.css(ex.activeCarouselItem))
+ .getAttribute('aria-label');
+ return activeElement === newActiveElement;
+ }, t.context.WaitTime);
+
+ t.true(
+ compareWithNextElement,
+ 'After sending ENTER to previous button then ENTER to next button, the carousel should show the first carousel item'
+ );
+});
|