diff --git a/aria-practices.html b/aria-practices.html index e92f03cfb7..c819211f80 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -543,6 +543,99 @@

WAI-ARIA Roles, States, and Properties

+ + + +

Combo Box

diff --git a/examples/carousel/carousel-1/carousel-1.html b/examples/carousel/carousel-1/carousel-1.html new file mode 100644 index 0000000000..1d00a509f9 --- /dev/null +++ b/examples/carousel/carousel-1/carousel-1.html @@ -0,0 +1,469 @@ + + + + + Example of Image Carousel with Automatically Rotating Images | WAI-ARIA Authoring Practices 1.1 + + + + + + + + + + + + + + + + + +

+
+

Example of Image Carousel with Automatically Rotating Images

+

+ This example section demonstrates a design pattern for a image carousel consisting of a pause button, next and previous slide buttons. + In this example, when the page is loaded the carousel images start rotating automatically. + The auto rotation can be stopped by activating the pause button, just moving keyboard focus to any control or link in the carousel or hovering the over carousel content with a mouse. There are two ways for the user to restart rotation, pressing the pause button again or moving the pointer outside the carousel image area. + For additional guidance, see Deciding When to Make Selection Automatically Follow Focus. +

+ + + +
+

Example

+ +
+ + +
+ +
+ +
+ + +

Accessibility Features

+ + +
+

Keyboard Support

+ +
+

Pause Slide Rotation Button

+ + + + + + + + + + + + + + + + +
KeyFunction
Tab + The button becomes visible when it receives keyboard focus. +
Enter or Space +
    +
  • Activating the button will toggle the auto rotation of slides in the carousel.
  • +
  • The purpose is to help screen reader users know there is a way to pause the auto rotation of images when they are in review mode before they get to the rotating content (e.g. not stopping the images may cause screen reader to move reading back to the top of the page when they are reading content that becomes hidden).
  • +
  • +
+
+
+ +
+

Next/Previous Slide Buttons

+ + + + + + + + + + + + + +
KeyFunction
Enter or Space + Moves content to the next or previous slide in the image carousel. +
+
+ +
+ +
+

Role, Property, State, and Tabindex Attributes

+ +
+

Region Landmark

+ + + + + + + + + + + + + + + + + + + + + + + +
RoleAttributeElementUsage
+ region + + section + +
    +
  • The region landmark is defined by a section element with a accessible name.
  • +
  • The accessible name of the region landmark is defined using the aria-label attribute.
  • +
+
+ aria-label=Image Carousel + + section + Provides a label that describes the carousel region.
+
+ +
+

Start/Pause Slide Rotation

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RoleAttributeElementUsage
+ button + + button + +
    +
  • The default role for the button element is button role.
  • +
  • Accessible name comes from the text content of the button (e.g. Pause Carousel).
  • +
+
+ aria-pressed=false + + button + +
    +
  • The aria-pressed attribute identified the button as a toggle button.
  • +
  • When aria-pressed=false the carousel auto-rotate is enabled.
  • +
+
+ aria-pressed=false + + button + When aria-pressed=true the carousel auto-rotation is disabled.
+
+ +
+

Next/Previous Slide Buttons

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RoleAttributeElementUsage
+ button + + a + +
    +
  • Overrides the default role of link with button role.
  • +
  • Accessible name is defined using aria-label.
  • +
+
+ aria-label=name + + a + Defines the accessible name for the next and previous slide buttons.
+ aria-controls=IDREF + + a + +
    +
  • Identifies the content on the page that the button controls.
  • +
  • Relationship can be used by screen readers to provide navigation to the controlled content.
  • +
+
+
+
+ +
+

Javascript and CSS Source Code

+ +
+ +
+

HTML Source Code

+ +
+ + +
+ +
+ + + + + diff --git a/examples/carousel/carousel-1/css/carousel.css b/examples/carousel/carousel-1/css/carousel.css new file mode 100644 index 0000000000..63dae70eb3 --- /dev/null +++ b/examples/carousel/carousel-1/css/carousel.css @@ -0,0 +1,174 @@ +.carousel-item { /*.carousel*/ + 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-pressed=true].pause { + border-style: inset; + color: #666; +} + +.carousel button.pause:focus{ + display: block; + position: relative; + font-size: 20px; + width: auto; + left: 0px; + margin-bottom: 10px; + height: auto; + position: relative; + top: 5px; + right: -20px; +} + + +.carousel .carousel-inner .carousel-image a { + display: block; +} + +.carousel .carousel-inner .carousel-image a:focus { + border: solid 2px #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 a:focus, +.carousel .carousel-inner .carousel-caption a:hover { + outline: solid 2px #FFF; + outline-offset: 1px; +} + +.carousel .carousel-inner .carousel-caption h2 a { + color: #fff; + font-weight: 600; +} + +.carousel .carousel-inner .carousel-caption p { + font-size: 1em; + line-height: 1.5; + margin-bottom: 0; +} + +.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-caption { + position: absolute; + right: 15%; + bottom: 0px; + left: 15%; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0,0,0,.6); +} + +.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,.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 { + opacity: 1.0; +} + +.carousel a.carousel-control.previous { + bottom: 0; + width: 15%; + background-image: linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%); +} + +.carousel a.carousel-control.previous:focus { + bottom: 0; + width: 15%; + background-image: linear-gradient(to right,rgba(0,0,0,.7) 0,rgba(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,.0001) 0,rgba(0,0,0,.5) 100%); +} + +.carousel a.carousel-control.next:focus { + right: 0; + bottom: 0; + width: 15%; + background-image: linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.7) 100%); +} diff --git a/examples/carousel/carousel-1/images/amsterdamslide__800x600.jpg b/examples/carousel/carousel-1/images/amsterdamslide__800x600.jpg new file mode 100644 index 0000000000..a8f78e9751 Binary files /dev/null and b/examples/carousel/carousel-1/images/amsterdamslide__800x600.jpg differ diff --git a/examples/carousel/carousel-1/images/britcomdavidslide__800x600.jpg b/examples/carousel/carousel-1/images/britcomdavidslide__800x600.jpg new file mode 100644 index 0000000000..74bfae55d7 Binary files /dev/null and b/examples/carousel/carousel-1/images/britcomdavidslide__800x600.jpg differ diff --git a/examples/carousel/carousel-1/images/foyleswarslide__800x600.jpg b/examples/carousel/carousel-1/images/foyleswarslide__800x600.jpg new file mode 100644 index 0000000000..83a056a3bc Binary files /dev/null and b/examples/carousel/carousel-1/images/foyleswarslide__800x600.jpg differ diff --git a/examples/carousel/carousel-1/images/lands-endslide__800x600.jpg b/examples/carousel/carousel-1/images/lands-endslide__800x600.jpg new file mode 100644 index 0000000000..7d25fc4b40 Binary files /dev/null and b/examples/carousel/carousel-1/images/lands-endslide__800x600.jpg differ diff --git a/examples/carousel/carousel-1/images/mag800-2__800x600.jpg b/examples/carousel/carousel-1/images/mag800-2__800x600.jpg new file mode 100644 index 0000000000..a0201e1a36 Binary files /dev/null and b/examples/carousel/carousel-1/images/mag800-2__800x600.jpg differ diff --git a/examples/carousel/carousel-1/images/trustslide-2__800x600.jpg b/examples/carousel/carousel-1/images/trustslide-2__800x600.jpg new file mode 100644 index 0000000000..fb260a8f49 Binary files /dev/null and b/examples/carousel/carousel-1/images/trustslide-2__800x600.jpg differ diff --git a/examples/carousel/carousel-1/js/carousel.js b/examples/carousel/carousel-1/js/carousel.js new file mode 100644 index 0000000000..477889cc2b --- /dev/null +++ b/examples/carousel/carousel-1/js/carousel.js @@ -0,0 +1,189 @@ +/* +* File: Carousel.js +* +* Desc: Carousel Tablist group widget that implements ARIA Authoring Practices +* +* Author(s): Jon Gunderson, Nicholas Hoyt, and Mark McCarthy +*/ + +/* +* @constructor CarouselTablist +* +* +*/ +var Carousel = function (domNode) { + this.domNode = domNode; + + this.items = []; + + this.firstItem = null; + this.lastItem = null; + this.currentDomNode = null; + this.currentItem = null; + this.pauseButton = null; + + this.rotate = true; + this.hasFocus = null; + this.timeInterval = 5000; +}; + +Carousel.prototype.init = function () { + + var items = this.domNode.querySelectorAll('.carousel-item'); + + for (var i = 0; i < items.length; i++) { + var item = new CarouselItem(items[i], this); + + console.log('[item]: ' + item.domNode); + + item.init(); + this.items.push(item); + + if (!this.firstItem) { + this.firstItem = item; + this.currentDomNode = item.domNode; + } + this.lastItem = item; + } + + // Next Slide and Previous Slide Buttons + + var elems = document.querySelectorAll('.carousel a.carousel-control'); + + for (var i = 0; i < elems.length; i++) { + if (elems[i].tagName.toLowerCase() == 'a') { + var button = new CarouselButton(elems[i], this); + + button.init(); + } + } + + 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.carouselContainer = this.domNode.parentNode.parentNode; + this.carouselContainer.addEventListener('mouseover', this.handleMouseOver.bind(this)); + this.carouselContainer.addEventListener('mouseout', this.handleMouseOut.bind(this)); + + // Start rotation + setTimeout(this.rotateSlides.bind(this), this.timeInterval); +}; + + + +Carousel.prototype.setSelected = function (newItem, moveFocus) { + if (typeof moveFocus != 'boolean') { + moveFocus = false; + } + + for (var i = 0; i < this.items.length; i++) { + this.items[i].hide(); + } + + this.currentItem = newItem; + this.currentItem.show(); + + if (moveFocus) { + this.currentItem.domNode.focus(); + } +}; + +Carousel.prototype.setSelectedToPreviousItem = function (currentItem, moveFocus) { + if (typeof moveFocus != 'boolean') { + moveFocus = false; + } + + var index; + + if (typeof currentItem !== 'object') { + currentItem = this.currentItem; + } + + if (currentItem === this.firstItem) { + this.setSelected(this.lastItem, moveFocus); + } + else { + index = this.items.indexOf(currentItem); + this.setSelected(this.items[index - 1], moveFocus); + } +}; + +Carousel.prototype.setSelectedToNextItem = function (currentItem, moveFocus) { + if (typeof moveFocus != 'boolean') { + moveFocus = false; + } + + var index; + + if (typeof currentItem !== 'object') { + currentItem = this.currentItem; + } + + if (currentItem === this.lastItem) { + this.setSelected(this.firstItem, moveFocus); + } + else { + index = this.items.indexOf(currentItem); + this.setSelected(this.items[index + 1], moveFocus); + } +}; + +Carousel.prototype.rotateSlides = function () { + if (this.rotate) { + this.setSelectedToNextItem(); + } + setTimeout(this.rotateSlides.bind(this), this.timeInterval); +}; + + +Carousel.prototype.startRotation = function (force) { + if ((this.pauseButton.getAttribute('aria-pressed') === 'false') && !this.hasFocus && !this.hasHover) { + this.rotate = true; + } + else { + this.rotate = false; + } +}; + +Carousel.prototype.stopRotation = function () { + this.rotate = false; +}; + +Carousel.prototype.toggleRotation = function () { + if ((this.pauseButton.getAttribute('aria-pressed') === 'false')) { + this.pauseButton.setAttribute('aria-pressed', 'true'); + this.stopRotation(); + } + else { + this.pauseButton.setAttribute('aria-pressed', 'false'); + this.startRotation(); + this.hasFocus = false; + } +}; + +Carousel.prototype.handleMouseOver = function () { + this.stopRotation(); +}; + +Carousel.prototype.handleMouseOut = function () { + this.startRotation(); +}; + +/* Initialize Carousel Tablists */ + +window.addEventListener('load', function (event) { + var carousels = document.querySelectorAll('.carousel'); + + for (var i = 0; i < carousels.length; i++) { + console.log("[Carousel]: " + carousels[i]); + var carousel = new Carousel(carousels[i]); + + carousel.init(); + } +}, false); diff --git a/examples/carousel/carousel-1/js/carouselButtons.js b/examples/carousel/carousel-1/js/carouselButtons.js new file mode 100644 index 0000000000..2513271c31 --- /dev/null +++ b/examples/carousel/carousel-1/js/carouselButtons.js @@ -0,0 +1,89 @@ +/* +* File: carouselButton.js +* +* Desc: Carousel Button widget that implements ARIA Authoring Practices +* +* Author(s): Jon Gunderson, Nicholas Hoyt, and Mark McCarthy +*/ + +/* +* @constructor CarouselButton +* +* +*/ +var CarouselButton = function (domNode, carouselObj) { + this.domNode = domNode; + + this.carousel = carouselObj; + + this.direction = 'previous'; + + if (this.domNode.classList.contains('next')) { + this.direction = 'next'; + } + + this.keyCode = Object.freeze({ + 'RETURN': 13, + 'SPACE': 32, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +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)); +}; + +CarouselButton.prototype.changeItem = function () { + if (this.direction === 'previous') { + this.carousel.setSelectedToPreviousItem(); + } + else { + this.carousel.setSelectedToNextItem(); + } +}; + + +/* 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(); +}; + +CarouselButton.prototype.handleFocus = function (event) { + this.domNode.classList.add('focus'); + this.carousel.stopRotation(); +}; + +CarouselButton.prototype.handleBlur = function (event) { + this.domNode.classList.remove('focus'); + this.carousel.startRotation(); +}; diff --git a/examples/carousel/carousel-1/js/carouselItem.js b/examples/carousel/carousel-1/js/carouselItem.js new file mode 100644 index 0000000000..5953351958 --- /dev/null +++ b/examples/carousel/carousel-1/js/carouselItem.js @@ -0,0 +1,47 @@ +/* +* File: CarouselItem.js +* +* Desc: Carousel Tab widget that implements ARIA Authoring Practices +* +* Author(s): Jon Gunderson, Nicholas Hoyt, and Mark McCarthy +*/ + +/* +* @constructor CarouselItem +* +* +*/ +var CarouselItem = function (domNode, carouselObj) { + console.log("[CarouselItem]: " + domNode); + + this.domNode = domNode; + this.carousel = carouselObj; + +}; + +CarouselItem.prototype.init = function () { + this.domNode.addEventListener('focusin', this.handleFocusIn.bind(this)); + this.domNode.addEventListener('focusout', this.handleFocusOut.bind(this)); +}; + +CarouselItem.prototype.hide = function () { + this.domNode.classList.remove('active'); +}; + +CarouselItem.prototype.show = function () { + this.domNode.classList.add('active'); +}; + +/* EVENT HANDLERS */ + +CarouselItem.prototype.handleFocusIn = function (event) { + this.domNode.classList.add('focus'); + this.carousel.hasFocus = true; + this.carousel.stopRotation(); +}; + +CarouselItem.prototype.handleFocusOut = function (event) { + this.domNode.classList.remove('focus'); + this.carousel.hasFocus = false; + this.carousel.startRotation(); +}; diff --git a/examples/carousel/carousel-1/js/pauseButton.js b/examples/carousel/carousel-1/js/pauseButton.js new file mode 100644 index 0000000000..d5d9e96ed4 --- /dev/null +++ b/examples/carousel/carousel-1/js/pauseButton.js @@ -0,0 +1,41 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: pauseButton.js +* +* Desc: Button to start and stop carousel image rotation +* +*/ + +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/carousel-2/carousel-2.html b/examples/carousel/carousel-2/carousel-2.html new file mode 100644 index 0000000000..254c5fe01a --- /dev/null +++ b/examples/carousel/carousel-2/carousel-2.html @@ -0,0 +1,736 @@ + + + + + Example of Image Carousel with Automatically Rotating Images using Tablist | WAI-ARIA Authoring Practices 1.1 + + + + + + + + + + + + + + + + + + +
+

Example of Image Carousel with Automatically Rotating Images using Tablist

+

+ This example section demonstrates a design pattern for a image carousel consisting of a pause button, next and previous slide buttons and a the use of tabs to navigate the list of carousel items. + In this example, when the page is loaded the carousel images start rotating automatically. + The auto rotation can be stopped by pressing the pause button, just moving keyboard focus to any control or link in the carousel or hovering over the image with a mouse. There are two ways for the user to restart rotation, pressing the pause button again or moving the pointer outside the carousel image area. + For additional guidance, see Deciding When to Make Selection Automatically Follow Focus. +

+ +
+

Example

+ +
+ + +
+ +
+ +
+ + +

Accessibility Features

+ +
+

Keyboard Support

+ +
+

Tabs for Selecting Slides

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyFunction
Tab +
    +
  • When focus moves into the tab list, places focus on the active tab element .
  • +
  • When the tab list contains the focus, moves focus to the next element in the tab sequence, which is the tabpanel element.
  • +
  • When any element in the carousel example receives focus, auto rotation of the images is stopped.
  • +
+
Right Arrow +
    +
  • Moves focus to the next tab.
  • +
  • If focus is on the last tab, moves focus to the first tab.
  • +
  • Activates the newly focused tab.
  • +
+
Left Arrow +
    +
  • Moves focus to the previous tab.
  • +
  • If focus is on the first tab, moves focus to the last tab.
  • +
  • Activates the newly focused tab.
  • +
+
HomeMoves focus to the first tab and activates it.
EndMoves focus to the last tab and activates it.
+
+ +
+

Pause/Start Slide Rotation Button

+ + + + + + + + + + + + + + + + +
KeyFunction
Tab + The button becomes visible when it receives keyboard focus. +
Enter or Space +
    +
  • Activating the button will toggle the auto rotation of slides in the carousel.
  • +
  • The purpose is to help screen reader users know there is a way to pause the auto rotation of images when they are in review mode before they get to the rotating content (e.g. not stopping the images may cause screen reader to move reading back to the top of the page when they are reading content that becomes hidden).
  • +
  • +
+
+
+ +
+

Next/Previous Slide Buttons

+ + + + + + + + + + + + + +
KeyFunction
Enter or Space + Moves content to the next or previous slide in the image carousel. +
+
+ +
+ +
+

Role, Property, State, and Tabindex Attributes

+ +
+

Region Landmark

+ + + + + + + + + + + + + + + + + + + + + + + +
RoleAttributeElementUsage
+ region + + section + +
    +
  • The region landmark is defined by a section element with a accessible name.
  • +
  • The accessible name of the region landmark is defined using the aria-label attribute.
  • +
+
+ aria-label=Image Carousel + + section + Provides a label that describes the carousel region.
+
+ +
+

Tabs for Selecting Slides

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RoleAttributeElementUsage
+ tablist + + ol + Indicates that the element serves as a container for a set of tabs.
+ aria-label=Image Carousel + + div + Provides a label that describes the purpose of the set of tabs.
+ tab + + li + +
    +
  • Indicates the element serves as a tab control.
  • +
  • + When focused, is automatically activated, causing its associated tabpanel (e.g. slide) + to be displayed. +
  • +
+
+ + aria-labelledby=IDREF + + li + + Defines a label for the tab. +
+ aria-selected=true + + li + +
    +
  • Indicates the tab control is activated and its associated panel (e.g slide) is displayed.
  • +
  • Set when a tab receives focus.
  • +
+
+ aria-selected=false + + li + +
    +
  • + Indicates the tab control is not active and its associated panel is NOT + displayed. +
  • +
  • Set for all tab elements in the tab set except the focused tab.
  • +
+
+ tabindex=-1 + + li + +
    +
  • Makes the element focusable.
  • +
  • Set when a tab is not selected so that only the selected tab is in the page Tab sequence.
  • +
  • Since an HTML LI element is used for the tab, it is necessary to set tabindex=0 on the selected (active) tab element.
  • +
  • This approach to managing focus is described in the section on roving tabindex.
  • +
+
+ aria-controls=IDREF + + li + + Refers to the tabpanel element associated with the tab. +
+ tabpanel + + div + +
    +
  • Indicates the element serves as a container for tab panel content.
  • +
  • + Is hidden unless its associated tab control is activated. +
  • +
+
+ aria-labelledby=IDREF + + div + +
    +
  • + Refers to the tab element that controls the panel. +
  • +
  • Provides an accessible name for the tab panel.
  • +
+
+ tabindex=0 + + div + +
    +
  • Puts the tabpanel in the page Tab sequence.
  • +
  • Facilitates movement to panel content for assistive technology users.
  • +
  • Especially helpful if there are panels that do not contain a focusable element.
  • +
+
+
+ +
+

Start/Pause Slide Rotation

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RoleAttributeElementUsage
+ button + + button + +
    +
  • The default role for the button element is button role.
  • +
  • Accessible name comes from the text content of the button (e.g. Pause Carousel).
  • +
+
+ aria-pressed=false + + button + +
    +
  • The aria-pressed attribute identified the button as a toggle button.
  • +
  • When aria-pressed=false the carousel auto-rotate is enabled.
  • +
+
+ aria-pressed=false + + button + When aria-pressed=true the carousel auto-rotation is disabled.
+
+ +
+

Next/Previous Slide Buttons

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RoleAttributeElementUsage
+ button + + a + +
    +
  • Overrides the default role of link with button role.
  • +
  • Accessible name is defined using aria-label.
  • +
+
+ aria-label=name + + a + Defines the accessible name for the next and previous slide buttons.
+ aria-controls=IDREF + + a + +
    +
  • Identifies the content on the page that the button controls.
  • +
  • Relationship can be used by screen readers to provide navigation to the controlled content.
  • +
+
+
+ +
+ +
+

Javascript and CSS Source Code

+ +
+ +
+

HTML Source Code

+ +
+ + +
+ +
+ + + + + diff --git a/examples/carousel/carousel-2/css/carousel.css b/examples/carousel/carousel-2/css/carousel.css new file mode 100644 index 0000000000..664ec9ea09 --- /dev/null +++ b/examples/carousel/carousel-2/css/carousel.css @@ -0,0 +1,212 @@ +.carousel-item { /*.carousel*/ + 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-pressed=true].pause { + border-style: inset; + color: #666; +} + +.carousel button.pause:focus{ + display: block; + position: relative; + font-size: 20px; + width: auto; + left: 0px; + margin-bottom: 10px; + height: auto; + position: relative; + top: 5px; + right: -20px; +} + + +.carousel .carousel-inner .carousel-image a { + display: block; +} + +.carousel .carousel-inner .carousel-image a:focus { + border: solid 2px #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 a:focus, +.carousel .carousel-inner .carousel-caption a:hover { + outline: solid 2px #FFF; + outline-offset: 1px; +} + +.carousel .carousel-inner .carousel-caption h2 a { + color: #fff; + font-weight: 600; +} + +.carousel .carousel-inner .carousel-caption p { + font-size: 1em; + line-height: 1.5; + margin-bottom: 0; +} + +.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-caption { + position: absolute; + right: 15%; + bottom: 0px; + left: 15%; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0,0,0,.6); +} + +.carousel .carousel-inner, +.carousel .carousel-item, +.carousel .carousel-slide { + max-height: 400px; +} + +.carousel .carousel-indicators { + position: absolute; + bottom: 0; + left: 50%; + transform: translate(-50%, -50%); + z-index: 15; + text-align: center; + list-style: none; +} + +.carousel [role="tab"] { + border: 2px solid white; + border-radius: 50%; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + -webkit-box-shadow: 0 0 1px 0px rgb(255, 255, 255); + box-shadow: 0 0 1px 0px rgb(255, 255, 255); + width: 10px; + height: 10px; + z-index: 15; + bottom: 0; + margin: 0 4px; + display: inline-block; + float: left; + padding-right:2px; +} + +.carousel [role="tab"][aria-selected="true"] { + background-color: white; + border-radius: 50%; +} + +.carousel [role="tab"]:focus { + outline-offset: 1px; + outline-style: solid; + outline-color: white; + outline-width: 2px; +} + +.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,.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 { + opacity: 1.0; +} + +.carousel a.carousel-control.previous { + bottom: 0; + width: 15%; + background-image: linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%); +} + +.carousel a.carousel-control.previous:focus { + bottom: 0; + width: 15%; + background-image: linear-gradient(to right,rgba(0,0,0,.7) 0,rgba(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,.0001) 0,rgba(0,0,0,.5) 100%); +} + +.carousel a.carousel-control.next:focus { + right: 0; + bottom: 0; + width: 15%; + background-image: linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.7) 100%); +} diff --git a/examples/carousel/carousel-2/images/amsterdamslide__800x600.jpg b/examples/carousel/carousel-2/images/amsterdamslide__800x600.jpg new file mode 100644 index 0000000000..a8f78e9751 Binary files /dev/null and b/examples/carousel/carousel-2/images/amsterdamslide__800x600.jpg differ diff --git a/examples/carousel/carousel-2/images/britcomdavidslide__800x600.jpg b/examples/carousel/carousel-2/images/britcomdavidslide__800x600.jpg new file mode 100644 index 0000000000..74bfae55d7 Binary files /dev/null and b/examples/carousel/carousel-2/images/britcomdavidslide__800x600.jpg differ diff --git a/examples/carousel/carousel-2/images/foyleswarslide__800x600.jpg b/examples/carousel/carousel-2/images/foyleswarslide__800x600.jpg new file mode 100644 index 0000000000..83a056a3bc Binary files /dev/null and b/examples/carousel/carousel-2/images/foyleswarslide__800x600.jpg differ diff --git a/examples/carousel/carousel-2/images/lands-endslide__800x600.jpg b/examples/carousel/carousel-2/images/lands-endslide__800x600.jpg new file mode 100644 index 0000000000..7d25fc4b40 Binary files /dev/null and b/examples/carousel/carousel-2/images/lands-endslide__800x600.jpg differ diff --git a/examples/carousel/carousel-2/images/mag800-2__800x600.jpg b/examples/carousel/carousel-2/images/mag800-2__800x600.jpg new file mode 100644 index 0000000000..a0201e1a36 Binary files /dev/null and b/examples/carousel/carousel-2/images/mag800-2__800x600.jpg differ diff --git a/examples/carousel/carousel-2/images/trustslide-2__800x600.jpg b/examples/carousel/carousel-2/images/trustslide-2__800x600.jpg new file mode 100644 index 0000000000..fb260a8f49 Binary files /dev/null and b/examples/carousel/carousel-2/images/trustslide-2__800x600.jpg differ diff --git a/examples/carousel/carousel-2/js/carouselButtons.js b/examples/carousel/carousel-2/js/carouselButtons.js new file mode 100644 index 0000000000..0ed0de11d8 --- /dev/null +++ b/examples/carousel/carousel-2/js/carouselButtons.js @@ -0,0 +1,89 @@ +/* +* File: carouselButton.js +* +* Desc: Carousel Button widget that implements ARIA Authoring Practices +* +* Author(s): Jon Gunderson, Nicholas Hoyt, and Mark McCarthy +*/ + +/* +* @constructor CarouselButton +* +* +*/ +var CarouselButton = function (domNode, tablist) { + this.domNode = domNode; + + this.tablist = tablist; + + this.direction = 'previous'; + + if (this.domNode.classList.contains('next')) { + this.direction = 'next'; + } + + this.keyCode = Object.freeze({ + 'RETURN': 13, + 'SPACE': 32, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +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)); +}; + +CarouselButton.prototype.changeTab = function () { + if (this.direction === 'previous') { + this.tablist.setSelectedToPreviousItem(); + } + else { + this.tablist.setSelectedToNextItem(); + } +}; + + +/* EVENT HANDLERS */ + +CarouselButton.prototype.handleKeydown = function (event) { + var flag = false; + + switch (event.keyCode) { + case this.keyCode.SPACE: + case this.keyCode.RETURN: + this.changeTab(); + this.domNode.focus(); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +CarouselButton.prototype.handleClick = function (event) { + this.changeTab(); +}; + +CarouselButton.prototype.handleFocus = function (event) { + this.domNode.classList.add('focus'); + this.tablist.stopRotation(); +}; + +CarouselButton.prototype.handleBlur = function (event) { + this.domNode.classList.remove('focus'); + this.tablist.startRotation(); +}; diff --git a/examples/carousel/carousel-2/js/carouselTab.js b/examples/carousel/carousel-2/js/carouselTab.js new file mode 100644 index 0000000000..967b0b644f --- /dev/null +++ b/examples/carousel/carousel-2/js/carouselTab.js @@ -0,0 +1,122 @@ +/* +* File: CarouselTab.js +* +* Desc: Carousel Tab widget that implements ARIA Authoring Practices +* +* Author(s): Jon Gunderson, Nicholas Hoyt, and Mark McCarthy +*/ + +/* +* @constructor CarouselTab +* +* +*/ +var CarouselTab = function (domNode, groupObj) { + this.domNode = domNode; + this.tablist = groupObj; + + this.tabpanelDomNode = null; + + this.keyCode = Object.freeze({ + 'RETURN': 13, + 'SPACE': 32, + 'END': 35, + 'HOME': 36, + 'LEFT': 37, + 'UP': 38, + 'RIGHT': 39, + 'DOWN': 40 + }); +}; + +CarouselTab.prototype.init = function () { + this.domNode.tabIndex = -1; + if (!this.domNode.hasAttribute('aria-selected')) { + this.domNode.setAttribute('aria-selected', 'false'); + } + this.tabpanelDomNode = document.getElementById(this.domNode.getAttribute('aria-controls')); + + 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('focusin', this.handleFocusIn.bind(this)); + this.domNode.addEventListener('focusout', this.handleFocusOut.bind(this)); + + this.tabpanelDomNode.addEventListener('focusin', this.handleFocusIn.bind(this)); + this.tabpanelDomNode.addEventListener('focusout', this.handleFocusOut.bind(this)); + +}; + +CarouselTab.prototype.hideTabPanel = function () { + this.tabpanelDomNode.classList.remove('active'); + this.domNode.setAttribute('aria-selected', 'false'); + this.domNode.tabIndex = -1; + this.domNode.classList.remove('focus'); +}; + +CarouselTab.prototype.showTabPanel = function () { + this.tabpanelDomNode.classList.add('active'); + this.domNode.setAttribute('aria-selected', 'true'); + this.domNode.tabIndex = 0; + this.domNode.classList.add('focus'); +}; + +/* EVENT HANDLERS */ + +CarouselTab.prototype.handleKeydown = function (event) { + var flag = false; + + switch (event.keyCode) { + case this.keyCode.UP: + this.tablist.setSelectedToPreviousItem(this, true); + flag = true; + break; + case this.keyCode.DOWN: + this.tablist.setSelectedToNextItem(this, true); + flag = true; + break; + case this.keyCode.LEFT: + this.tablist.setSelectedToPreviousItem(this, true); + flag = true; + break; + case this.keyCode.RIGHT: + this.tablist.setSelectedToNextItem(this, true); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +CarouselTab.prototype.handleClick = function (event) { + this.tablist.setSelected(this); + console.log('pause status is ' + this.tablist.rotate); +}; + +CarouselTab.prototype.handleFocus = function (event) { + this.domNode.classList.add('focus'); + this.tablist.hasFocus = true; + this.tablist.stopRotation(); +}; + +CarouselTab.prototype.handleBlur = function (event) { + this.domNode.classList.remove('focus'); + this.tablist.hasFocus = false; + this.tablist.startRotation(); +}; + +CarouselTab.prototype.handleFocusIn = function (event) { + this.tabpanelDomNode.classList.add('focus'); +}; + +CarouselTab.prototype.handleFocusOut = function (event) { + this.tabpanelDomNode.classList.remove('focus'); +}; diff --git a/examples/carousel/carousel-2/js/carouselTablist.js b/examples/carousel/carousel-2/js/carouselTablist.js new file mode 100644 index 0000000000..685cf66678 --- /dev/null +++ b/examples/carousel/carousel-2/js/carouselTablist.js @@ -0,0 +1,185 @@ +/* +* File: carouselTablist.js +* +* Desc: Carousel Tablist group widget that implements ARIA Authoring Practices +* +* Author(s): Jon Gunderson, Nicholas Hoyt, and Mark McCarthy +*/ + +/* +* @constructor CarouselTablist +* +* +*/ +var CarouselTablist = function (domNode) { + this.domNode = domNode; + + this.tabs = []; + + this.firstTab = null; + this.lastTab = null; + this.currentDomNode = null; + this.currentTab = null; + this.pauseButton = null; + + this.rotate = true; + this.hasFocus = null; + this.timeInterval = 5000; +}; + +CarouselTablist.prototype.init = function () { + // Initialize pop up menus + if (!this.domNode.getAttribute('role')) { + this.domNode.setAttribute('role', 'tablists'); + } + + var tabs = this.domNode.querySelectorAll('.carousel [role=tab]'); + + for (var i = 0; i < tabs.length; i++) { + var tab = new CarouselTab(tabs[i], this); + + tab.init(); + this.tabs.push(tab); + + if (!this.firstTab) { + this.firstTab = tab; + this.currentDomNode = tab.domNode; + } + this.lastTab = tab; + } + this.firstTab.domNode.tabIndex = 0; + + // Next Slide and Previous Slide Buttons + + var elems = document.querySelectorAll('.carousel a.carousel-control'); + + for (var i = 0; i < elems.length; i++) { + if (elems[i].tagName.toLowerCase() == 'a') { + var button = new CarouselButton(elems[i], this); + + button.init(); + } + } + + this.currentTab = this.firstTab; + + this.pauseButton = new PauseButton(this.domNode.parentNode.parentNode.querySelector('button.pause'), this); + this.pauseButton.init(); + + this.carouselContainer = this.domNode.parentNode.parentNode; + this.carouselContainer.addEventListener('mouseover', this.handleMouseOver.bind(this)); + this.carouselContainer.addEventListener('mouseout', this.handleMouseOut.bind(this)); + + // Start rotation + setTimeout(this.rotateSlides.bind(this), this.timeInterval); +}; + +CarouselTablist.prototype.rotateSlides = function () { + if (this.rotate) { + this.setSelectedToNextItem(); + } + setTimeout(this.rotateSlides.bind(this), this.timeInterval); +}; + +CarouselTablist.prototype.setSelected = function (newTab, moveFocus) { + if (typeof moveFocus != 'boolean') { + moveFocus = false; + } + + for (var i = 0; i < this.tabs.length; i++) { + var tab = this.tabs[i]; + + this.currentTab.hideTabPanel(); + } + + this.currentTab = newTab; + this.currentTab.showTabPanel(); + + if (moveFocus) { + this.currentTab.domNode.focus(); + } +}; + +CarouselTablist.prototype.setSelectedToPreviousItem = function (currentTab, moveFocus) { + if (typeof moveFocus != 'boolean') { + moveFocus = false; + } + + var index; + + if (typeof currentTab !== 'object') { + currentTab = this.currentTab; + } + + if (currentTab === this.firstTab) { + this.setSelected(this.lastTab, moveFocus); + } + else { + index = this.tabs.indexOf(currentTab); + this.setSelected(this.tabs[index - 1], moveFocus); + } +}; + +CarouselTablist.prototype.setSelectedToNextItem = function (currentTab, moveFocus) { + if (typeof moveFocus != 'boolean') { + moveFocus = false; + } + + var index; + + if (typeof currentTab !== 'object') { + currentTab = this.currentTab; + } + + if (currentTab === this.lastTab) { + this.setSelected(this.firstTab, moveFocus); + } + else { + index = this.tabs.indexOf(currentTab); + this.setSelected(this.tabs[index + 1], moveFocus); + } +}; + +CarouselTablist.prototype.startRotation = function (force) { + if (this.pauseButton.allowRotation() && !this.hasFocus && !this.hasHover) { + this.rotate = true; + } + else { + this.rotate = false; + } +}; + +CarouselTablist.prototype.stopRotation = function () { + this.rotate = false; +}; + +CarouselTablist.prototype.toggleRotation = function () { + if (this.pauseButton.allowRotation()) { + this.pauseButton.disableRotation(); + this.stopRotation(); + } + else { + this.pauseButton.enableRotation(); + this.startRotation(); + } +}; + +CarouselTablist.prototype.handleMouseOver = function () { + this.stopRotation(); +}; + +CarouselTablist.prototype.handleMouseOut = function () { + this.startRotation(); +}; + +/* Initialize Carousel Tablists */ + +window.addEventListener('load', function (event) { + var carouselTablists = document.querySelectorAll('.carousel .carousel-indicators'); + + for (var i = 0; i < carouselTablists.length; i++) { + var ctl = new CarouselTablist(carouselTablists[i]); + + ctl.init(); + } +}, false); diff --git a/examples/carousel/carousel-2/js/pauseButton.js b/examples/carousel/carousel-2/js/pauseButton.js new file mode 100644 index 0000000000..5b3b24ad4b --- /dev/null +++ b/examples/carousel/carousel-2/js/pauseButton.js @@ -0,0 +1,53 @@ +/* +* This content is licensed according to the W3C Software License at +* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document +* +* File: pauseButton.js +* +* Desc: Button to start and stop carousel image rotation +* +*/ + +var PauseButton = function (domNode, tablist) { + this.domNode = domNode; + + this.tablist = tablist; +}; + +PauseButton.prototype.init = function () { + + if (!this.domNode.hasAttribute('aria-pressed')) { + this.domNode.setAttribute('aria-pressed', 'false'); + } + + this.domNode.addEventListener('click', this.handleClick.bind(this)); + this.domNode.addEventListener('focus', this.handleFocus.bind(this)); + this.domNode.addEventListener('blur', this.handleBlur.bind(this)); +}; + +PauseButton.prototype.allowRotation = function () { + return this.domNode.getAttribute('aria-pressed') === 'false'; +}; + +PauseButton.prototype.enableRotation = function () { + this.domNode.setAttribute('aria-pressed', 'false'); +}; + +PauseButton.prototype.disableRotation = function () { + this.domNode.setAttribute('aria-pressed', 'true'); +}; + + +/* EVENT HANDLERS */ + +PauseButton.prototype.handleClick = function (event) { + this.tablist.toggleRotation(); +}; + +PauseButton.prototype.handleFocus = function (event) { + this.domNode.classList.add('focus'); +}; + +PauseButton.prototype.handleBlur = function (event) { + this.domNode.classList.remove('focus'); +};