diff --git a/examples/index.html b/examples/index.html index 727de014bb..a144d718d6 100644 --- a/examples/index.html +++ b/examples/index.html @@ -146,6 +146,7 @@

Examples by Role

  • Auto-Rotating Image Carousel with Buttons for Slide Control
  • Checkbox (Two State)
  • Editor Menubar
  • +
  • Color Viewer Slider
  • Date Picker Spin Button
  • Navigation Treeview
  • @@ -302,8 +303,8 @@

    Examples by Role

    @@ -583,6 +584,7 @@

    Examples By Properties and States

  • Navigation Menubar
  • Radio Group Using aria-activedescendant
  • Radio Group Using Roving tabindex
  • +
  • Color Viewer Slider
  • Date Picker Spin Button
  • Table
  • Tabs with Automatic Activation
  • @@ -619,6 +621,7 @@

    Examples By Properties and States

  • Navigation Menubar
  • Radio Group Using aria-activedescendant
  • Radio Group Using Roving tabindex
  • +
  • Color Viewer Slider
  • Date Picker Spin Button
  • Tabs with Automatic Activation
  • Tabs with Manual Activation
  • @@ -761,8 +764,8 @@

    Examples By Properties and States

    @@ -773,8 +776,8 @@

    Examples By Properties and States

    @@ -786,8 +789,8 @@

    Examples By Properties and States

    diff --git a/examples/slider/css/slider-color-viewer.css b/examples/slider/css/slider-color-viewer.css new file mode 100644 index 0000000000..a6c7ca6d76 --- /dev/null +++ b/examples/slider/css/slider-color-viewer.css @@ -0,0 +1,120 @@ +/* CSS Document */ + +.color-viewer-sliders label { + display: block; +} + +.color-viewer-sliders .color-box { + width: 200px; + height: 200px; + border: black solid medium; + text-align: center; + padding: 0.25em; + forced-color-adjust: none; +} + +.color-viewer-sliders .color-info { + padding-top: 5px; +} + +.color-viewer-sliders .color-info label { + margin-top: 0.25em; + display: block; +} + +.color-slider { + margin: 0; + margin-bottom: 1em; + padding: 2px; + touch-action: pan-y; + width: 350px; + outline: none; +} + +.color-slider div, +.color-slider svg { + display: block; + width: 350px; +} + +.color-slider-label { + margin: 0; + padding: 0; + font-weight: bold; +} + +.color-slider .value { + color: currentColor; + fill: currentColor; +} + +.color-slider .valueBackground { + fill: white; + stroke-width: 0; +} + +.color-slider .rail { + fill: #bbb; + stroke: currentColor; + stroke-width: 2px; + opacity: 0.8; +} + +.color-slider .fill { + stroke-width: 0; + opacity: 0.5; + pointer-events: none; +} + +.color-slider.red .fill { + fill: red; +} + +.color-slider.green .fill { + fill: green; +} + +.color-slider.blue .fill { + fill: blue; +} + +.color-slider .thumb { + fill: #666; + stroke: currentColor; + stroke-width: 2px; +} + +.color-slider .focus { + fill: transparent; + stroke: transparent; + stroke-width: 2px; +} + +.color-slider:focus .value, +.color-slider:hover .value { + font-weight: bold; +} + +.color-slider:focus .fill, +.color-slider:hover .fill { + opacity: 1; +} + +.color-slider:focus .rail, +.color-slider:hover .rail { + fill: transparent; + stroke: currentColor; + opacity: 1; +} + +.color-slider:focus .thumb, +.color-slider:hover .thumb { + fill: #ddd; + stroke: currentColor; + opacity: 1; +} + +.color-slider:focus .focus, +.color-slider:hover .focus { + stroke: currentColor; +} diff --git a/examples/slider/css/slider.css b/examples/slider/css/slider.css deleted file mode 100644 index aaade14b98..0000000000 --- a/examples/slider/css/slider.css +++ /dev/null @@ -1,59 +0,0 @@ -/* CSS Document */ - -div.aria-widget-slider { - margin-top: 0.5em; - margin-bottom: 0.5em; - height: 4em; -} - -div.aria-widget-slider .rail { - margin: 2px; - padding: 1px; - background-color: #eee; - border: 1px solid #888; - position: relative; - top: 2em; - height: 4px; -} - -div.aria-widget-slider .thumb { - border: 1px solid #888; - border-top-color: #666; - border-left-color: #666; - background-color: #ddd; - position: relative; -} - -div.aria-widget-slider .rail .thumb.focus, -div.aria-widget-slider .rail .thumb:hover { - outline: 2px solid #888; - background-color: #def; -} - -div.aria-widget-slider .rail.focus { - background-color: #aaa; -} - -div.aria-widget-slider .value { - width: 2em; - text-align: right; - position: absolute; -} - -label { - display: block; - margin-top: 0.5em; - margin-bottom: 0.5em; -} - -#idColorBox { - width: 200px; - height: 200px; - border: black solid medium; - text-align: center; - padding: 0.25em; -} - -#idColorInfo { - padding-top: 5px; -} diff --git a/examples/slider/js/slider-color-viewer.js b/examples/slider/js/slider-color-viewer.js new file mode 100644 index 0000000000..0d601ca33e --- /dev/null +++ b/examples/slider/js/slider-color-viewer.js @@ -0,0 +1,374 @@ +'use strict'; +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: slider-color-viewer.js + * + * Desc: ColorViewerSliders widget that implements ARIA Authoring Practices + */ + +// Create ColorViewerSliders that contains value, valuemin, valuemax, and valuenow +class ColorViewerSliders { + constructor(domNode) { + this.domNode = domNode; + + this.pointerSlider = false; + + this.sliders = {}; + + this.svgWidth = 310; + this.svgHeight = 50; + this.borderWidth = 2; + + this.valueY = 20; + + this.railX = 15; + this.railY = 26; + this.railWidth = 275; + this.railHeight = 14; + + this.thumbHeight = this.railHeight; + this.thumbWidth = this.thumbHeight; + this.rectRadius = this.railHeight / 4; + + this.focusY = this.borderWidth; + this.focusWidth = 36; + this.focusHeight = 48; + + this.initSliderRefs(this.sliders, 'red'); + this.initSliderRefs(this.sliders, 'green'); + this.initSliderRefs(this.sliders, 'blue'); + + document.body.addEventListener( + 'pointerup', + this.onThumbPointerUp.bind(this) + ); + + this.colorBoxNode = domNode.querySelector('.color-box'); + this.colorValueHexNode = domNode.querySelector('input.color-value-hex'); + this.colorValueRGBNode = domNode.querySelector('input.color-value-rgb'); + } + + initSliderRefs(sliderRef, color) { + sliderRef[color] = {}; + var n = this.domNode.querySelector('.color-slider.' + color); + sliderRef[color].sliderNode = n; + + sliderRef[color].svgNode = n.querySelector('svg'); + sliderRef[color].svgNode.setAttribute('width', this.svgWidth); + sliderRef[color].svgNode.setAttribute('height', this.svgHeight); + sliderRef[color].svgPoint = sliderRef[color].svgNode.createSVGPoint(); + + sliderRef[color].valueNode = n.querySelector('.value'); + sliderRef[color].valueNode.setAttribute('y', this.valueY); + + sliderRef[color].thumbNode = n.querySelector('.thumb'); + sliderRef[color].thumbNode.setAttribute('width', this.thumbWidth); + sliderRef[color].thumbNode.setAttribute('height', this.thumbHeight); + sliderRef[color].thumbNode.setAttribute('y', this.railY); + sliderRef[color].thumbNode.setAttribute('rx', this.rectRadius); + + sliderRef[color].focusNode = n.querySelector('.focus'); + sliderRef[color].focusNode.setAttribute( + 'width', + this.focusWidth - this.borderWidth + ); + sliderRef[color].focusNode.setAttribute( + 'height', + this.focusHeight - this.borderWidth + ); + sliderRef[color].focusNode.setAttribute('y', this.focusY); + sliderRef[color].focusNode.setAttribute('rx', this.rectRadius); + + sliderRef[color].railNode = n.querySelector('.color-slider .rail'); + sliderRef[color].railNode.setAttribute('x', this.railX); + sliderRef[color].railNode.setAttribute('y', this.railY); + sliderRef[color].railNode.setAttribute('width', this.railWidth); + sliderRef[color].railNode.setAttribute('height', this.railHeight); + sliderRef[color].railNode.setAttribute('rx', this.rectRadius); + + sliderRef[color].fillNode = n.querySelector('.color-slider .fill'); + sliderRef[color].fillNode.setAttribute('x', this.railX); + sliderRef[color].fillNode.setAttribute('y', this.railY); + sliderRef[color].fillNode.setAttribute('width', this.railWidth); + sliderRef[color].fillNode.setAttribute('height', this.railHeight); + sliderRef[color].fillNode.setAttribute('rx', this.rectRadius); + } + + // Initialize slider + init() { + for (var slider in this.sliders) { + if (this.sliders[slider].sliderNode.tabIndex != 0) { + this.sliders[slider].sliderNode.tabIndex = 0; + } + + this.sliders[slider].railNode.addEventListener( + 'click', + this.onRailClick.bind(this) + ); + + this.sliders[slider].sliderNode.addEventListener( + 'keydown', + this.onSliderKeyDown.bind(this) + ); + + this.sliders[slider].sliderNode.addEventListener( + 'pointerdown', + this.onThumbPointerDown.bind(this) + ); + + this.sliders[slider].valueNode.addEventListener( + 'keydown', + this.onSliderKeyDown.bind(this) + ); + + this.sliders[slider].valueNode.addEventListener( + 'pointerdown', + this.onThumbPointerDown.bind(this) + ); + + this.sliders[slider].sliderNode.addEventListener( + 'pointermove', + this.onThumbPointerMove.bind(this) + ); + + this.moveSliderTo( + this.sliders[slider], + this.getValueNow(this.sliders[slider]) + ); + } + } + + // Get point in global SVG space + getSVGPoint(slider, event) { + slider.svgPoint.x = event.clientX; + slider.svgPoint.y = event.clientY; + return slider.svgPoint.matrixTransform( + slider.svgNode.getScreenCTM().inverse() + ); + } + + getSlider(domNode) { + if (!domNode.classList.contains('color-slider')) { + if (domNode.tagName.toLowerCase() === 'rect') { + domNode = domNode.parentNode.parentNode; + } else { + domNode = domNode.parentNode.querySelector('.color-slider'); + } + } + + if (this.sliders.red.sliderNode === domNode) { + return this.sliders.red; + } + + if (this.sliders.green.sliderNode === domNode) { + return this.sliders.green; + } + + return this.sliders.blue; + } + + getValueMin(slider) { + return parseInt(slider.sliderNode.getAttribute('aria-valuemin')); + } + + getValueNow(slider) { + return parseInt(slider.sliderNode.getAttribute('aria-valuenow')); + } + + getValueMax(slider) { + return parseInt(slider.sliderNode.getAttribute('aria-valuemax')); + } + + moveSliderTo(slider, value) { + var pos, offsetX, valueWidth; + var valueMin = this.getValueMin(slider); + var valueNow = this.getValueNow(slider); + var valueMax = this.getValueMax(slider); + + value = Math.min(Math.max(value, valueMin), valueMax); + + valueNow = value; + slider.sliderNode.setAttribute('aria-valuenow', value); + + offsetX = Math.round( + (valueNow * (this.railWidth - this.thumbWidth)) / (valueMax - valueMin) + ); + + pos = this.railX + offsetX; + + slider.thumbNode.setAttribute('x', pos); + slider.fillNode.setAttribute('width', offsetX + this.rectRadius); + + slider.valueNode.textContent = valueNow; + valueWidth = slider.valueNode.getBBox().width; + + pos = this.railX + offsetX - (valueWidth - this.thumbWidth) / 2; + slider.valueNode.setAttribute('x', pos); + + pos = this.railX + offsetX - (this.focusWidth - this.thumbWidth) / 2; + slider.focusNode.setAttribute('x', pos); + + this.updateColorBox(); + } + + onSliderKeyDown(event) { + var flag = false; + + var slider = this.getSlider(event.currentTarget); + + var valueMin = this.getValueMin(slider); + var valueNow = this.getValueNow(slider); + var valueMax = this.getValueMax(slider); + + switch (event.key) { + case 'Left': + case 'ArrowLeft': + case 'Down': + case 'ArrowDown': + this.moveSliderTo(slider, valueNow - 1); + flag = true; + break; + + case 'Right': + case 'ArrowRight': + case 'Up': + case 'ArrowUp': + this.moveSliderTo(slider, valueNow + 1); + flag = true; + break; + + case 'PageDown': + this.moveSliderTo(slider, valueNow - 10); + flag = true; + break; + + case 'PageUp': + this.moveSliderTo(slider, valueNow + 10); + flag = true; + break; + + case 'Home': + this.moveSliderTo(slider, valueMin); + flag = true; + break; + + case 'End': + this.moveSliderTo(slider, valueMax); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.preventDefault(); + event.stopPropagation(); + } + } + + onThumbPointerDown(event) { + this.pointerSlider = this.getSlider(event.currentTarget); + + // Set focus to the clicked on + this.pointerSlider.sliderNode.focus(); + + event.preventDefault(); + event.stopPropagation(); + } + + onThumbPointerUp() { + this.pointerSlider = false; + } + + onThumbPointerMove(event) { + if ( + this.pointerSlider && + this.pointerSlider.sliderNode.contains(event.target) + ) { + let x = this.getSVGPoint(this.pointerSlider, event).x; + let min = this.getValueMin(this.pointerSlider); + let max = this.getValueMax(this.pointerSlider); + let diffX = x - this.railX; + let value = Math.round((diffX * (max - min)) / this.railWidth); + this.moveSliderTo(this.pointerSlider, value); + + event.preventDefault(); + event.stopPropagation(); + } + } + + // handle click event on the rail + onRailClick(event) { + var slider = this.getSlider(event.currentTarget); + + var x = this.getSVGPoint(slider, event).x; + var min = this.getValueMin(slider); + var max = this.getValueMax(slider); + var diffX = x - this.railX; + var value = Math.round((diffX * (max - min)) / this.railWidth); + this.moveSliderTo(slider, value); + + event.preventDefault(); + event.stopPropagation(); + + // Set focus to the clicked handle + slider.sliderNode.focus(); + } + + getColorHex() { + var r = parseInt( + this.sliders.red.sliderNode.getAttribute('aria-valuenow') + ).toString(16); + var g = parseInt( + this.sliders.green.sliderNode.getAttribute('aria-valuenow') + ).toString(16); + var b = parseInt( + this.sliders.blue.sliderNode.getAttribute('aria-valuenow') + ).toString(16); + + if (r.length === 1) { + r = '0' + r; + } + if (g.length === 1) { + g = '0' + g; + } + if (b.length === 1) { + b = '0' + b; + } + + return '#' + r + g + b; + } + + getColorRGB() { + var r = this.sliders.red.sliderNode.getAttribute('aria-valuenow'); + var g = this.sliders.green.sliderNode.getAttribute('aria-valuenow'); + var b = this.sliders.blue.sliderNode.getAttribute('aria-valuenow'); + + return r + ', ' + g + ', ' + b; + } + + updateColorBox() { + if (this.colorBoxNode) { + this.colorBoxNode.style.backgroundColor = this.getColorHex(); + } + + if (this.colorValueHexNode) { + this.colorValueHexNode.value = this.getColorHex(); + } + + if (this.colorValueRGBNode) { + this.colorValueRGBNode.value = this.getColorRGB(); + } + } +} +// Initialize ColorViewerSliders on the page +window.addEventListener('load', function () { + var cps = document.querySelectorAll('.color-viewer-sliders'); + for (let i = 0; i < cps.length; i++) { + let s = new ColorViewerSliders(cps[i]); + s.init(); + } +}); diff --git a/examples/slider/js/slider.js b/examples/slider/js/slider.js deleted file mode 100644 index 319981ca5f..0000000000 --- a/examples/slider/js/slider.js +++ /dev/null @@ -1,275 +0,0 @@ -/* - * This content is licensed according to the W3C Software License at - * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document - * - * File: slider.js - * - * Desc: Slider widget that implements ARIA Authoring Practices - */ - -'use strict'; - -// Create Slider that contains value, valuemin, valuemax, and valuenow -var Slider = function (domNode) { - this.domNode = domNode; - this.railDomNode = domNode.parentNode; - - this.valueDomNode = false; - - this.valueMin = 0; - this.valueMax = 100; - this.valueNow = 50; - - this.railWidth = 0; - - this.thumbWidth = 8; - this.thumbHeight = 28; - - this.keyCode = Object.freeze({ - left: 37, - up: 38, - right: 39, - down: 40, - pageUp: 33, - pageDown: 34, - end: 35, - home: 36, - }); -}; - -// Initialize slider -Slider.prototype.init = function () { - if (this.domNode.getAttribute('aria-valuemin')) { - this.valueMin = parseInt(this.domNode.getAttribute('aria-valuemin')); - } - if (this.domNode.getAttribute('aria-valuemax')) { - this.valueMax = parseInt(this.domNode.getAttribute('aria-valuemax')); - } - if (this.domNode.getAttribute('aria-valuenow')) { - this.valueNow = parseInt(this.domNode.getAttribute('aria-valuenow')); - } - - this.railWidth = parseInt(this.railDomNode.style.width.slice(0, -2)); - - this.valueDomNode = this.railDomNode.nextElementSibling; - - if (this.valueDomNode) { - this.valueDomNode.innerHTML = '0'; - this.valueDomNode.style.left = - this.railDomNode.offsetLeft + this.railWidth + 10 + 'px'; - this.valueDomNode.style.top = this.railDomNode.offsetTop - 8 + 'px'; - } - - if (this.domNode.tabIndex != 0) { - this.domNode.tabIndex = 0; - } - - this.domNode.style.width = this.thumbWidth + 'px'; - this.domNode.style.height = this.thumbHeight + 'px'; - this.domNode.style.top = this.thumbHeight / -2 + 'px'; - - this.domNode.addEventListener('keydown', this.handleKeyDown.bind(this)); - // add onmousedown, move, and onmouseup - this.domNode.addEventListener('mousedown', this.handleMouseDown.bind(this)); - - this.domNode.addEventListener('focus', this.handleFocus.bind(this)); - this.domNode.addEventListener('blur', this.handleBlur.bind(this)); - - this.railDomNode.addEventListener('click', this.handleClick.bind(this)); - - this.moveSliderTo(this.valueNow); -}; - -Slider.prototype.moveSliderTo = function (value) { - if (value > this.valueMax) { - value = this.valueMax; - } - - if (value < this.valueMin) { - value = this.valueMin; - } - - this.valueNow = value; - - this.domNode.setAttribute('aria-valuenow', this.valueNow); - - var pos = - Math.round( - (this.valueNow * this.railWidth) / (this.valueMax - this.valueMin) - ) - - this.thumbWidth / 2; - - this.domNode.style.left = pos + 'px'; - - if (this.valueDomNode) { - this.valueDomNode.innerHTML = this.valueNow.toString(); - } - - updateColorBox(); -}; - -Slider.prototype.handleKeyDown = function (event) { - var flag = false; - - switch (event.keyCode) { - case this.keyCode.left: - case this.keyCode.down: - this.moveSliderTo(this.valueNow - 1); - flag = true; - break; - - case this.keyCode.right: - case this.keyCode.up: - this.moveSliderTo(this.valueNow + 1); - flag = true; - break; - - case this.keyCode.pageDown: - this.moveSliderTo(this.valueNow - 10); - flag = true; - break; - - case this.keyCode.pageUp: - this.moveSliderTo(this.valueNow + 10); - flag = true; - break; - - case this.keyCode.home: - this.moveSliderTo(this.valueMin); - flag = true; - break; - - case this.keyCode.end: - this.moveSliderTo(this.valueMax); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.preventDefault(); - event.stopPropagation(); - } -}; - -Slider.prototype.handleFocus = function () { - this.domNode.classList.add('focus'); - this.railDomNode.classList.add('focus'); -}; - -Slider.prototype.handleBlur = function () { - this.domNode.classList.remove('focus'); - this.railDomNode.classList.remove('focus'); -}; - -// Initialize Sliders on the page -window.addEventListener('load', function () { - var sliders = document.querySelectorAll('[role=slider]'); - - for (var i = 0; i < sliders.length; i++) { - var s = new Slider(sliders[i]); - s.init(); - } -}); - -Slider.prototype.handleMouseDown = function (event) { - var self = this; - - var handleMouseMove = function (event) { - var diffX = event.pageX - self.railDomNode.offsetLeft; - self.valueNow = parseInt( - ((self.valueMax - self.valueMin) * diffX) / self.railWidth - ); - self.moveSliderTo(self.valueNow); - - event.preventDefault(); - event.stopPropagation(); - }; - - var handleMouseUp = function () { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - - // bind a mousemove event handler to move pointer - document.addEventListener('mousemove', handleMouseMove); - - // bind a mouseup event handler to stop tracking mouse movements - document.addEventListener('mouseup', handleMouseUp); - - event.preventDefault(); - event.stopPropagation(); - - // Set focus to the clicked handle - this.domNode.focus(); -}; - -// handleMouseMove has the same functionality as we need for handleMouseClick on the rail -Slider.prototype.handleClick = function (event) { - var diffX = event.pageX - this.railDomNode.offsetLeft; - this.valueNow = parseInt( - ((this.valueMax - this.valueMin) * diffX) / this.railWidth - ); - this.moveSliderTo(this.valueNow); - - event.preventDefault(); - event.stopPropagation(); -}; - -/* ---------------------------------------------------------------- */ -/* Change color of the Box */ -/* ---------------------------------------------------------------- */ - -function updateColorBox() { - function getColorHex() { - var r = parseInt( - document.getElementById('idRedValue').getAttribute('aria-valuenow') - ).toString(16); - var g = parseInt( - document.getElementById('idGreenValue').getAttribute('aria-valuenow') - ).toString(16); - var b = parseInt( - document.getElementById('idBlueValue').getAttribute('aria-valuenow') - ).toString(16); - - if (r.length === 1) { - r = '0' + r; - } - if (g.length === 1) { - g = '0' + g; - } - if (b.length === 1) { - b = '0' + b; - } - - return '#' + r + g + b; - } - - function getColorRGB() { - var r = document.getElementById('idRedValue').getAttribute('aria-valuenow'); - var g = document - .getElementById('idGreenValue') - .getAttribute('aria-valuenow'); - var b = document - .getElementById('idBlueValue') - .getAttribute('aria-valuenow'); - - return r + ', ' + g + ', ' + b; - } - - var node = document.getElementById('idColorBox'); - - if (node) { - var color = getColorHex(); - - node.style.backgroundColor = color; - - node = document.getElementById('idColorValueHex'); - node.value = color; - - node = document.getElementById('idColorValueRGB'); - node.value = getColorRGB(); - } -} diff --git a/examples/slider/slider-1.html b/examples/slider/slider-1.html deleted file mode 100644 index 88a8d9bfea..0000000000 --- a/examples/slider/slider-1.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - Horizontal Slider Example | WAI-ARIA Authoring Practices 1.2 - - - - - - - - - - - - - - -
    -

    Horizontal Slider Example

    -

    - Following is an example of a color picker that demonstrates the - slider design pattern. - Change the background color of the box below the picker by adjusting the sliders for red, green, and blue values. - The HEX and RGB values of the chosen color are displayed by the color box. -

    -

    Similar examples include:

    - -
    -
    -

    Example

    -
    - -
    -
    -
    Red
    -
    -
    -
    -
    -
    0
    -
    - -
    Green
    -
    -
    -
    -
    -
    0
    -
    - -
    Blue
    -
    -
    -
    -
    -
    0
    -
    -
    - -

    Color Box

    -
    -
    - - -
    -
    - -
    - -
    -

    Keyboard Support

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    KeyFunction
    Right ArrowIncreases slider value one step.
    Up ArrowIncreases slider value one step.
    Left ArrowDecreases slider value one step.
    Down ArrowDecreases slider value one step.
    Page UpIncreases slider value multiple steps. In this slider, jumps ten steps.
    Page DownDecreases slider value multiple steps. In this slider, jumps ten steps.
    HomeSets slider to its minimum value.
    EndSets slider to its maximum value.
    -
    - -
    -

    Role, Property, State, and Tabindex Attributes

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    RoleAttributeElementUsage
    - slider - - div - -
      -
    • Identifies the element as a slider.
    • -
    • Set on the div that represents as the movable thumb because it is the operable element that represents the slider value.
    • -
    -
    - tabindex=0 - - div - Includes the slider thumb in the page tab sequence.
    - aria-valuemax=255 - - div - Specifies the maximum value of the slider.
    - aria-valuemin=0 - - div - Specifies the minimum value of the slider.
    - aria-valuenow=NUMBER - - div - Indicates the current value of the slider.
    - aria-labelledby=IDREF - - div - Refers to the element containing the name of the slider.
    -
    - -
    -

    Javascript and CSS Source Code

    - -
    - -
    -

    HTML Source Code

    - -
    - - -
    - -
    - - - diff --git a/examples/slider/slider-color-viewer.html b/examples/slider/slider-color-viewer.html new file mode 100644 index 0000000000..dac18635a9 --- /dev/null +++ b/examples/slider/slider-color-viewer.html @@ -0,0 +1,326 @@ + + + + + + Color Viewer Slider Example | WAI-ARIA Authoring Practices 1.2 + + + + + + + + + + + + + + +
    +

    Color Viewer Slider Example

    +
    +

    + WARNING! Some users of touch-based assistive technologies may experience difficulty utilizing widgets that implement this slider pattern because the gestures their assistive technology provides for operating sliders may not yet generate the necessary output. + To change the slider value, touch-based assistive technologies need to respond to user gestures for incrementing and decrementing the value by synthesizing key events. + This is a new convention that may not be fully implemented by some assistive technologies. + Authors should fully test slider widgets using assistive technologies on devices where touch is a primary input mechanism before considering incorporation into production systems. +

    +
    +

    + Following is an example of a color viewer that demonstrates the + slider design pattern. + Change the background of the color view box by adjusting the sliders for red, green, and blue values. + The HEX and RGB values of the chosen color are displayed by the color view box. +

    +

    Similar examples include:

    + +
    +
    +

    Example

    +
    + +
    +
    +

    Color Viewer

    + +
    Red
    +
    + + + + +
    + +
    Green
    +
    + + + + +
    + +
    Blue
    +
    + + + + +
    + +

    Color View Box

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

    Accessibility Features

    + +
    + +
    +

    Keyboard Support

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyFunction
    Right ArrowIncreases slider value one step.
    Up ArrowIncreases slider value one step.
    Left ArrowDecreases slider value one step.
    Down ArrowDecreases slider value one step.
    Page UpIncreases slider value multiple steps. In this slider, jumps ten steps.
    Page DownDecreases slider value multiple steps. In this slider, jumps ten steps.
    HomeSets slider to its minimum value.
    EndSets slider to its maximum value.
    + +
    + +
    +

    Role, Property, State, and Tabindex Attributes

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    groupdiv +
      +
    • Identifies the div as a group.
    • +
    • The group and its accessible name inform assistive technology users that the three sliders are related to one another and serve the purpose of choosing a color.
    • +
    +
    aria-labelledby="IDREF"divRefers to the heading element that provides the accessible name for the group.
    + slider + + div + +
      +
    • Identifies the element as a slider.
    • +
    • Set on the div that represents the movable thumb because it is the operable element that controls the slider value.
    • +
    +
    + tabindex="0" + + div + Includes the slider thumb in the page tab sequence.
    + aria-valuemax="255" + + div + Specifies the maximum value of the slider.
    + aria-valuemin="0" + + div + Specifies the minimum value of the slider.
    + aria-valuenow="NUMBER" + + div + Indicates the current value of the slider.
    + aria-labelledby="IDREF" + + div + Refers to the element containing the name of the slider.
    aria-hidden="true"svgRemoves the SVG elements from the accessibility tree. Some assistive technologies will describe the SVG elements, unless they are explicitly hidden.
    +
    + +
    +

    Javascript and CSS Source Code

    + +
    + +
    +

    HTML Source Code

    + +
    + + +
    + +
    + + + diff --git a/test/tests/slider_slider-1.js b/test/tests/slider_slider-color-viewer.js similarity index 84% rename from test/tests/slider_slider-1.js rename to test/tests/slider_slider-color-viewer.js index da4399249f..d63d0d1001 100644 --- a/test/tests/slider_slider-1.js +++ b/test/tests/slider_slider-color-viewer.js @@ -4,13 +4,15 @@ const assertAttributeValues = require('../util/assertAttributeValues'); const assertAriaLabelledby = require('../util/assertAriaLabelledby'); const assertAriaRoles = require('../util/assertAriaRoles'); -const exampleFile = 'slider/slider-1.html'; +const exampleFile = 'slider/slider-color-viewer.html'; const ex = { + groupSelector: '#ex1 [role="group"]', sliderSelector: '#ex1 [role="slider"]', - hexTextInput: '#idColorValueHex', - rgbTextInput: '#idColorValueRGB', - colorBox: '#idColorBox', + svgSelector: '#ex1 [role="slider"] svg', + hexTextInput: '#ex1 .color-info .color-value-hex', + rgbTextInput: '#ex1 .color-info .color-value-rgb', + colorBox: '#ex1 .color-info .color-box', }; const testDisplayMatchesValue = async function (t, rgbString) { @@ -72,8 +74,43 @@ const sendAllSlidersToEnd = async function (t) { } }; +const sendAllSlidersToBeginning = async function (t) { + const sliders = await t.context.queryElements(t, ex.sliderSelector); + + for (let slider of sliders) { + await slider.sendKeys(Key.HOME); + } +}; + // Attributes +ariaTest( + '"aria-hidden" set to "true" on SVG elements', + exampleFile, + 'svg-aria-hidden', + async (t) => { + await assertAttributeValues(t, ex.svgSelector, 'aria-hidden', 'true'); + } +); + +ariaTest( + 'role="group" on div element', + exampleFile, + 'group-role', + async (t) => { + await assertAriaRoles(t, 'ex1', 'group', '1', 'div'); + } +); + +ariaTest( + '"aria-labelledby" set on group', + exampleFile, + 'aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.groupSelector); + } +); + ariaTest( 'role="slider" on div element', exampleFile, @@ -115,7 +152,7 @@ ariaTest( exampleFile, 'aria-valuenow', async (t) => { - await assertAttributeValues(t, ex.sliderSelector, 'aria-valuenow', '0'); + await assertAttributeValues(t, ex.sliderSelector, 'aria-valuenow', '128'); } ); @@ -143,12 +180,12 @@ ariaTest( t.is( await redSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow right key to the red slider, the value of the red slider should be 1' + '129', + 'After sending 1 arrow right key to the red slider, the value of the red slider should be 129' ); t.true( - await testDisplayMatchesValue(t, '1, 0, 0'), - 'Display should match rgb(1, 0, 0)' + await testDisplayMatchesValue(t, '129, 128, 128'), + 'Display should match rgb(129, 128, 128)' ); // Send more than 255 keys to red slider @@ -162,8 +199,8 @@ ariaTest( 'After sending 260 arrow right key, the value of the red slider should be 255' ); t.true( - await testDisplayMatchesValue(t, '255, 0, 0'), - 'Display should match rgb(255, 0, 0)' + await testDisplayMatchesValue(t, '255, 128, 128'), + 'Display should match rgb(255, 128, 128)' ); // Send 1 key to green slider @@ -172,12 +209,12 @@ ariaTest( t.is( await greenSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow right key to the blue slider, the value of the green slider should be 1' + '129', + 'After sending 1 arrow right key to the green slider, the value of the green slider should be 129' ); t.true( - await testDisplayMatchesValue(t, '255, 1, 0'), - 'Display should match rgb(255, 1, 0)' + await testDisplayMatchesValue(t, '255, 129, 128'), + 'Display should match rgb(255, 129, 128)' ); // Send more than 255 keys to green slider @@ -191,8 +228,8 @@ ariaTest( 'After sending 260 arrow right key, the value of the green slider should be 255' ); t.true( - await testDisplayMatchesValue(t, '255, 255, 0'), - 'Display should match rgb(255, 255, 0)' + await testDisplayMatchesValue(t, '255, 255, 128'), + 'Display should match rgb(255, 255, 128)' ); // Send 1 key to blue slider @@ -201,12 +238,12 @@ ariaTest( t.is( await blueSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow right key to the blue slider, the value of the blue slider should be 1' + '129', + 'After sending 1 arrow right key to the blue slider, the value of the blue slider should be 129' ); t.true( - await testDisplayMatchesValue(t, '255, 255, 1'), - 'Display should match rgb(255, 255, 1)' + await testDisplayMatchesValue(t, '255, 255, 129'), + 'Display should match rgb(255, 255, 129)' ); // Send more than 255 keys to blue slider @@ -239,15 +276,15 @@ ariaTest( t.is( await redSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow up key to the red slider, the value of the red slider should be 1' + '129', + 'After sending 1 arrow up key to the red slider, the value of the red slider should be 129' ); t.true( - await testDisplayMatchesValue(t, '1, 0, 0'), - 'Display should match rgb(1, 0, 0)' + await testDisplayMatchesValue(t, '129, 128, 128'), + 'Display should match rgb(129, 128, 128)' ); - // Send more than 255 keys to red slider + // Sen over 255 arrow up keys to the red slider for (let i = 0; i < 260; i++) { await redSlider.sendKeys(Key.ARROW_UP); } @@ -258,8 +295,8 @@ ariaTest( 'After sending 260 arrow up key, the value of the red slider should be 255' ); t.true( - await testDisplayMatchesValue(t, '255, 0, 0'), - 'Display should match rgb(255, 0, 0)' + await testDisplayMatchesValue(t, '255, 128, 128'), + 'Display should match rgb(255, 128, 128)' ); // Send 1 key to green slider @@ -268,12 +305,12 @@ ariaTest( t.is( await greenSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow up key to the blue slider, the value of the green slider should be 1' + '129', + 'After sending 1 arrow up key to the blue slider, the value of the green slider should be 129' ); t.true( - await testDisplayMatchesValue(t, '255, 1, 0'), - 'Display should match rgb(255, 1, 0)' + await testDisplayMatchesValue(t, '255, 129, 128'), + 'Display should match rgb(255, 129, 128)' ); // Send more than 255 keys to green slider @@ -287,8 +324,8 @@ ariaTest( 'After sending 260 arrow up key, the value of the green slider should be 255' ); t.true( - await testDisplayMatchesValue(t, '255, 255, 0'), - 'Display should match rgb(255, 255, 0)' + await testDisplayMatchesValue(t, '255, 255, 128'), + 'Display should match rgb(255, 255, 128)' ); // Send 1 key to blue slider @@ -297,12 +334,12 @@ ariaTest( t.is( await blueSlider.getAttribute('aria-valuenow'), - '1', - 'After sending 1 arrow up key to the blue slider, the value of the blue slider should be 1' + '129', + 'After sending 1 arrow up key to the blue slider, the value of the blue slider should be 129' ); t.true( - await testDisplayMatchesValue(t, '255, 255, 1'), - 'Display should match rgb(255, 255, 1)' + await testDisplayMatchesValue(t, '255, 255, 129'), + 'Display should match rgb(255, 255, 129)' ); // Send more than 255 keys to blue slider @@ -335,15 +372,15 @@ ariaTest( t.is( await redSlider.getAttribute('aria-valuenow'), - '10', - 'After sending 1 page up key to the red slider, the value of the red slider should be 10' + '138', + 'After sending 1 page up key to the red slider, the value of the red slider should be 138' ); t.true( - await testDisplayMatchesValue(t, '10, 0, 0'), - 'Display should match rgb(10, 0, 0)' + await testDisplayMatchesValue(t, '138, 128, 128'), + 'Display should match rgb(138, 128, 128)' ); - // Send more than 26 keys to red slider + // Send over 26 page up keys to the red slider for (let i = 0; i < 26; i++) { await redSlider.sendKeys(Key.PAGE_UP); } @@ -354,8 +391,8 @@ ariaTest( 'After sending 26 page up key, the value of the red slider should be 255' ); t.true( - await testDisplayMatchesValue(t, '255, 0, 0'), - 'Display should match rgb(255, 0, 0)' + await testDisplayMatchesValue(t, '255, 128, 128'), + 'Display should match rgb(255, 128, 128)' ); // Send 1 key to green slider @@ -364,12 +401,12 @@ ariaTest( t.is( await greenSlider.getAttribute('aria-valuenow'), - '10', - 'After sending 1 page up key to the blue slider, the value of the green slider should be 10' + '138', + 'After sending 1 page up key to the blue slider, the value of the green slider should be 138' ); t.true( - await testDisplayMatchesValue(t, '255, 10, 0'), - 'Display should match rgb(255, 10, 0)' + await testDisplayMatchesValue(t, '255, 138, 128'), + 'Display should match rgb(255, 138, 128)' ); // Send more than 26 keys to green slider @@ -383,8 +420,8 @@ ariaTest( 'After sending 260 page up key, the value of the green slider should be 255' ); t.true( - await testDisplayMatchesValue(t, '255, 255, 0'), - 'Display should match rgb(255, 255, 0)' + await testDisplayMatchesValue(t, '255, 255, 128'), + 'Display should match rgb(255, 255, 128)' ); // Send 1 key to blue slider @@ -393,12 +430,12 @@ ariaTest( t.is( await blueSlider.getAttribute('aria-valuenow'), - '10', - 'After sending 1 page up key to the blue slider, the value of the blue slider should be 10' + '138', + 'After sending 1 page up key to the blue slider, the value of the blue slider should be 138' ); t.true( - await testDisplayMatchesValue(t, '255, 255, 10'), - 'Display should match rgb(255, 255, 10)' + await testDisplayMatchesValue(t, '255, 255, 138'), + 'Display should match rgb(255, 255, 138)' ); // Send more than 26 keys to blue slider @@ -423,6 +460,8 @@ ariaTest( exampleFile, 'key-end', async (t) => { + sendAllSlidersToBeginning(t); + const sliders = await t.context.queryElements(t, ex.sliderSelector); // Send key end to red slider @@ -432,7 +471,7 @@ ariaTest( t.is( await redSlider.getAttribute('aria-valuenow'), '255', - 'After sending 1 end key to the red slider, the value of the red slider should be 255' + 'After sending end key to the red slider, the value of the red slider should be 255' ); t.true( await testDisplayMatchesValue(t, '255, 0, 0'), @@ -710,7 +749,7 @@ ariaTest( t.is( await greenSlider.getAttribute('aria-valuenow'), '245', - 'After sending 1 page down key to the blue slider, the value of the green slider should be 245' + 'After sending 1 page down key to the green slider, the value of the green slider should be 245' ); t.true( await testDisplayMatchesValue(t, '0, 245, 255'), @@ -739,7 +778,7 @@ ariaTest( t.is( await blueSlider.getAttribute('aria-valuenow'), '245', - 'After sending 1 page down key to the blue slider, the value of the blue slider should be 245' + 'After sending 1 page down key to the blue slider, the value of the green slider should be 245' ); t.true( await testDisplayMatchesValue(t, '0, 0, 245'), @@ -752,7 +791,7 @@ ariaTest( } t.is( - await blueSlider.getAttribute('aria-valuenow'), + await greenSlider.getAttribute('aria-valuenow'), '0', 'After sending 26 page down key, the value of the blue slider should be 0' );