From 94d9d87eb4153c3a9a6cb26a87e22089b11488d4 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 20 Apr 2021 20:23:42 -0500 Subject: [PATCH 01/17] Added temperature slider --- examples/index.html | 15 +- examples/slider/css/slider-temperature.css | 107 +++ examples/slider/css/text-slider.css | 48 -- examples/slider/css/vertical-slider.css | 51 -- examples/slider/js/slider-temperature.js | 256 ++++++ examples/slider/js/text-slider.js | 210 ----- examples/slider/js/vertical-slider.js | 221 ----- ...{slider-2.html => slider-temperature.html} | 156 ++-- test/tests/slider_slider-2.js | 795 ------------------ test/tests/slider_slider-temperature.js | 473 +++++++++++ 10 files changed, 937 insertions(+), 1395 deletions(-) create mode 100644 examples/slider/css/slider-temperature.css delete mode 100644 examples/slider/css/text-slider.css delete mode 100644 examples/slider/css/vertical-slider.css create mode 100644 examples/slider/js/slider-temperature.js delete mode 100644 examples/slider/js/text-slider.js delete mode 100644 examples/slider/js/vertical-slider.js rename examples/slider/{slider-2.html => slider-temperature.html} (53%) delete mode 100644 test/tests/slider_slider-2.js create mode 100644 test/tests/slider_slider-temperature.js diff --git a/examples/index.html b/examples/index.html index 13eb4991ed..57c7518408 100644 --- a/examples/index.html +++ b/examples/index.html @@ -237,6 +237,7 @@

Examples by Role

@@ -303,8 +304,8 @@

Examples by Role

@@ -588,6 +589,7 @@

Examples By Properties and States

  • Radio Group Using aria-activedescendant
  • Radio Group Using Roving tabindex
  • Color Viewer Slider
  • +
  • Slider with aria-orientation and aria-valuetext
  • Date Picker Spin Button
  • Table
  • Tabs with Automatic Activation
  • @@ -625,6 +627,7 @@

    Examples By Properties and States

  • Radio Group Using aria-activedescendant
  • Radio Group Using Roving tabindex
  • Color Viewer Slider
  • +
  • Slider with aria-orientation and aria-valuetext
  • Date Picker Spin Button
  • Tabs with Automatic Activation
  • Tabs with Manual Activation
  • @@ -678,7 +681,7 @@

    Examples By Properties and States

    aria-orientation - Slider with aria-orientation and aria-valuetext + Slider with aria-orientation and aria-valuetext aria-owns @@ -767,8 +770,8 @@

    Examples By Properties and States

    @@ -779,8 +782,8 @@

    Examples By Properties and States

    @@ -792,8 +795,8 @@

    Examples By Properties and States

    @@ -804,7 +807,7 @@

    Examples By Properties and States

    diff --git a/examples/slider/css/slider-temperature.css b/examples/slider/css/slider-temperature.css new file mode 100644 index 0000000000..55a2d52d7a --- /dev/null +++ b/examples/slider/css/slider-temperature.css @@ -0,0 +1,107 @@ +/* CSS Document */ + +.slider-valuetext h3 { + color: black; + font-weight: bold; + font-size: 150%; +} + +.slider-temperature .label, +.slider-seek .label { + font-weight: bold; + font-size: 125%; +} + +.slider-temperature svg, +.slider-seek svg { + forced-color-adjust: auto; +} + +.slider-temperature text, +.slider-seek text { + font-weight: bold; + fill: currentColor; + font-family: sans-serif; +} + +.slider-temperature { + width: 12em; +} + +.slider-temperature, +.slider-seek { + margin-top: 1em; + padding: 6px; + color: black; +} + +.slider-temperature .value, +.slider-slider .value { + position: relative; + top: 20px; + height: 1.5em; + font-size: 80%; +} + +.slider-temperature .temp-value, +.slider-seek .temp-value { + padding-left: 24px; + font-size: 200%; +} + +.slider-temperature .rail, +.slider-seek .rail { + stroke: currentColor; + stroke-width: 2px; + fill: currentColor; + fill-opacity: 25%; +} + +.slider-temperature .thumb, +.slider-seek .thumb { + stroke-width: 0; + fill: currentColor; +} + +.slider-temperature .focus-ring, +.slider-seek .focus-ring { + stroke: currentColor; + stroke-opacity: 0; + fill-opacity: 0; + stroke-width: 3px; + display: none; +} + +.slider-temperature .slider-group { + touch-action: pan-x; +} + +.slider-seek .slider-group { + touch-action: pan-y; +} + +.slider-seek .slider-group .value { + display: none; +} + +/* Focus and hover styling */ + +.slider-seek.focus [role="slider"], +.slider-temperature.focus [role="slider"] { + color: #005a9c; +} + +.slider-temperature [role="slider"]:focus, +.slider-seek [role="slider"]:focus { + outline: none; +} + +.slider-temperature [role="slider"]:focus .focus-ring, +.slider-seek [role="slider"]:focus .focus-ring { + display: block; + stroke-opacity: 1; +} + +.slider-seek [role="slider"]:focus .value { + display: block; +} diff --git a/examples/slider/css/text-slider.css b/examples/slider/css/text-slider.css deleted file mode 100644 index 7709acdb95..0000000000 --- a/examples/slider/css/text-slider.css +++ /dev/null @@ -1,48 +0,0 @@ -/* CSS Document */ - -div.aria-widget-text-slider { - margin-top: 0.5em; - margin-bottom: 0.5em; - height: 120px; -} - -div.aria-widget-text-slider .rail { - margin: 2px; - padding: 1px; - background-color: #eee; - border: 1px solid #888; - position: relative; - top: 2em; - height: 4px; -} - -div.aria-widget-text-slider .thumb { - border: 1px solid #888; - border-top-color: #666; - border-left-color: #666; - background-color: #ddd; - position: relative; -} - -div.aria-widget-text-slider .rail .thumb.focus, -div.aria-widget-text-slider .rail .thumb:hover { - outline: 2px solid #888; - background-color: #e0cb52; -} - -div.aria-widget-text-slider .rail.focus { - background-color: #aaa; -} - -div.aria-widget-text-slider div.value { - text-align: center; - display: block; - position: absolute; -} - -div.aria-widget-text-slider .label { - display: block; - margin-top: 0.5em; - margin-bottom: 0.5em; - font-weight: bold; -} diff --git a/examples/slider/css/vertical-slider.css b/examples/slider/css/vertical-slider.css deleted file mode 100644 index f7d0ab3ee5..0000000000 --- a/examples/slider/css/vertical-slider.css +++ /dev/null @@ -1,51 +0,0 @@ -/* CSS Document */ - -div.aria-widget-vertical-slider { - margin-top: 0.5em; - margin-bottom: 0.5em; -} - -div.aria-widget-vertical-slider .rail { - margin: 2px; - padding: 1px; - background-color: #eee; - border: 1px solid #888; - position: relative; - left: 3.8em; - width: 4px; -} - -div.aria-widget-vertical-slider .thumb { - border: 1px solid #888; - border-top-color: #666; - border-left-color: #666; - background-color: #ddd; - position: relative; -} - -div.aria-widget-vertical-slider .rail .thumb.focus, -div.aria-widget-vertical-slider .rail .thumb:hover { - outline: 2px solid #888; - background-color: #e0cb52; -} - -div.aria-widget-vertical-slider .rail.focus { - background-color: #aaa; -} - -div.aria-widget-vertical-slider .label, -div.aria-widget-vertical-slider .value { - margin-top: 0.5em; - margin-bottom: 0.5em; - text-align: center; - width: 8em; -} - -div.aria-widget-vertical-slider .label { - font-weight: bold; - font-size: 110%; -} - -#id_kbd { - clear: both; -} diff --git a/examples/slider/js/slider-temperature.js b/examples/slider/js/slider-temperature.js new file mode 100644 index 0000000000..9f4770c338 --- /dev/null +++ b/examples/slider/js/slider-temperature.js @@ -0,0 +1,256 @@ +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: slider-valuetext.js + * + * Desc: Slider widgets using aria-valuetext that implements ARIA Authoring Practices + */ + +'use strict'; + +class SliderTemperature { + constructor(domNode) { + this.labelCelsiusAbbrev = '°C'; + this.labelCelsius = ' degrees Celsius'; + this.changeValue = 0.1; + this.bigChangeValue = 20 * this.changeValue; + + this.domNode = domNode; + + this.isMoving = false; + + this.svgNode = domNode.querySelector('svg'); + this.svgPoint = this.svgNode.createSVGPoint(); + + this.borderWidth = 2; + this.borderWidth2 = 2 * this.borderWidth; + + this.railNode = domNode.querySelector('.rail'); + this.sliderNode = domNode.querySelector('[role=slider]'); + this.sliderValueNode = this.sliderNode.querySelector('.value'); + this.sliderFocusNode = this.sliderNode.querySelector('.focus-ring'); + this.sliderThumbNode = this.sliderNode.querySelector('.thumb'); + + // The input elements are optional to support mobile devices, + // when a keyboard is not present + this.valueNode = domNode.querySelector('.temp-value'); + + // Dimensions of the slider focus ring, thumb and rail + + this.valueX = parseInt(this.sliderValueNode.getAttribute('x')); + this.valueHeight = this.sliderValueNode.getBoundingClientRect().height; + + this.railHeight = parseInt(this.railNode.getAttribute('height')); + this.railWidth = parseInt(this.railNode.getAttribute('width')); + this.railY = parseInt(this.railNode.getAttribute('y')); + this.railX = parseInt(this.railNode.getAttribute('x')); + + this.thumbY = parseInt(this.sliderThumbNode.getAttribute('y')); + this.thumbWidth = parseInt(this.sliderThumbNode.getAttribute('width')); + this.thumbHeight = parseInt(this.sliderThumbNode.getAttribute('height')); + + this.focusX = parseInt(this.sliderFocusNode.getAttribute('x')); + this.focusWidth = parseInt(this.sliderFocusNode.getAttribute('width')); + this.focusHeight = parseInt(this.sliderFocusNode.getAttribute('height')); + + this.thumbX = this.railX + this.railWidth / 2 - this.thumbWidth / 2; + this.sliderThumbNode.setAttribute('x', this.thumbX); + this.sliderValueNode.setAttribute('x', this.valueX); + this.sliderFocusNode.setAttribute('x', this.focusX); + + this.svgNode.addEventListener('click', this.onRailClick.bind(this)); + this.sliderNode.addEventListener( + 'keydown', + this.onSliderKeydown.bind(this) + ); + this.sliderNode.addEventListener( + 'pointerdown', + this.onSliderPointerDown.bind(this) + ); + + // bind a pointermove event handler to move pointer + this.svgNode.addEventListener('pointermove', this.onPointerMove.bind(this)); + + // bind a pointerup event handler to stop tracking pointer movements + document.addEventListener('pointerup', this.onPointerUp.bind(this)); + + this.sliderNode.addEventListener('focus', this.onSliderFocus.bind(this)); + this.sliderNode.addEventListener('blur', this.onSliderBlur.bind(this)); + + this.moveSliderTo(this.getValue()); + } + + // Get point in global SVG space + getSVGPoint(event) { + this.svgPoint.x = event.clientX; + this.svgPoint.y = event.clientY; + return this.svgPoint.matrixTransform(this.svgNode.getScreenCTM().inverse()); + } + + getValue() { + return parseFloat(this.sliderNode.getAttribute('aria-valuenow')); + } + + getValueMin() { + return parseFloat(this.sliderNode.getAttribute('aria-valuemin')); + } + + getValueMax() { + return parseFloat(this.sliderNode.getAttribute('aria-valuemax')); + } + + isInRange(value) { + let valueMin = this.getValueMin(); + let valueMax = this.getValueMax(); + return value <= valueMax && value >= valueMin; + } + + moveSliderTo(value) { + var valueMax, valueMin, pos; + + valueMin = this.getValueMin(); + valueMax = this.getValueMax(); + + value = Math.min(Math.max(value, valueMin), valueMax); + + let valueOutput = value.toFixed(1) + this.labelCelsiusAbbrev; + + let valueText = value.toFixed(1) + this.labelCelsius; + + this.valueNode.textContent = valueOutput; + this.sliderNode.setAttribute('aria-valuenow', value.toFixed(1)); + this.sliderNode.setAttribute('aria-valuetext', valueText); + + let height = this.railHeight - this.thumbHeight + this.borderWidth2; + + pos = this.railY + height - 1; + pos -= Math.round(((value - valueMin) * height) / (valueMax - valueMin)); + this.sliderNode.setAttribute('y', pos); + + // update INPUT, label and ARIA attributes + this.sliderValueNode.textContent = valueOutput; + + // move the SVG focus ring and thumb elements + this.sliderFocusNode.setAttribute( + 'y', + pos - (this.focusHeight - this.thumbHeight) / 2 + ); + this.sliderThumbNode.setAttribute('y', pos); + + // Position value + this.sliderValueNode.setAttribute( + 'y', + pos - + this.borderWidth + + this.thumbHeight - + (this.valueHeight - this.thumbHeight) / 2 + ); + } + + onSliderKeydown(event) { + var flag = false; + var value = this.getValue(); + var valueMin = this.getValueMin(); + var valueMax = this.getValueMax(); + + switch (event.key) { + case 'ArrowLeft': + case 'ArrowDown': + this.moveSliderTo(value - this.changeValue); + flag = true; + break; + + case 'ArrowRight': + case 'ArrowUp': + this.moveSliderTo(value + this.changeValue); + flag = true; + break; + + case 'PageDown': + this.moveSliderTo(value - this.bigChangeValue); + flag = true; + break; + + case 'PageUp': + this.moveSliderTo(value + this.bigChangeValue); + flag = true; + break; + + case 'Home': + this.moveSliderTo(valueMin); + flag = true; + break; + + case 'End': + this.moveSliderTo(valueMax); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.preventDefault(); + event.stopPropagation(); + } + } + + onSliderFocus() { + this.domNode.classList.add('focus'); + } + + onSliderBlur() { + this.domNode.classList.remove('focus'); + } + + calcValue(y) { + let min = this.getValueMin(); + let max = this.getValueMax(); + let diffY = y - this.railY; + return max - (diffY * (max - min)) / this.railHeight; + } + + onRailClick(event) { + var value = this.calcValue(this.getSVGPoint(event).y); + this.moveSliderTo(value); + + event.preventDefault(); + event.stopPropagation(); + + // Set focus to the clicked handle + this.sliderNode.focus(); + } + + onSliderPointerDown(event) { + this.isMoving = true; + + event.preventDefault(); + event.stopPropagation(); + + // Set focus to the clicked handle + this.sliderNode.focus(); + } + + onPointerMove(event) { + if (this.isMoving) { + var value = this.calcValue(this.getSVGPoint(event).y); + this.moveSliderTo(value); + + event.preventDefault(); + event.stopPropagation(); + } + } + + onPointerUp() { + this.isMoving = false; + } +} + +window.addEventListener('load', function () { + var sliders = document.querySelectorAll('.slider-temperature'); + for (let i = 0; i < sliders.length; i++) { + new SliderTemperature(sliders[i]); + } +}); diff --git a/examples/slider/js/text-slider.js b/examples/slider/js/text-slider.js deleted file mode 100644 index 683e311f93..0000000000 --- a/examples/slider/js/text-slider.js +++ /dev/null @@ -1,210 +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: text-slider.js - * - * Desc: Text slider widget that implements ARIA Authoring Practices - */ - -'use strict'; - -// Create Text Slider that contains value, valuemin, valuemax, and valuenow - -var TSlider = function (domNode) { - this.domNode = domNode; - this.railDomNode = domNode.parentNode; - - this.valueDomNode = false; - - this.values = []; - this.valueNodes = this.railDomNode.getElementsByClassName('value'); - for (var i = 0; this.valueNodes[i]; i++) { - this.values.push(this.valueNodes[i].innerHTML); - } - this.valueMin = 0; - this.valueMax = this.values.length - 1; - this.valueNow = 0; - this.valueText = parseInt(this.values[this.valueNow]); - this.valueInc = 1; - - this.railWidth = 0; - - this.thumbWidth = 8; - this.thumbHeight = 28; - - this.keyCode = Object.freeze({ - left: 37, - up: 38, - right: 39, - down: 40, - end: 35, - home: 36, - }); -}; - -// Initialize text slider -TSlider.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)); - - 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'; - - var pos = 0; - var diff = this.railWidth / (this.valueNodes.length - 1); - for (var i = 0; this.valueNodes[i]; i++) { - this.valueNodes[i].style.left = - pos - this.valueNodes[i].offsetWidth / 2 + 'px'; - pos = pos + diff; - } - - 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.moveTSliderTo(this.valueNow); -}; - -TSlider.prototype.moveTSliderTo = 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); - this.domNode.setAttribute('aria-valuetext', this.values[this.valueNow]); - - var pos = - Math.round( - (this.valueNow * this.railWidth) / (this.valueMax - this.valueMin) - ) - - this.thumbWidth / 2; - - this.domNode.style.left = pos + 'px'; -}; - -TSlider.prototype.handleKeyDown = function (event) { - var flag = false; - - switch (event.keyCode) { - case this.keyCode.left: - case this.keyCode.down: - this.moveTSliderTo(this.valueNow - 1); - flag = true; - break; - - case this.keyCode.right: - case this.keyCode.up: - this.moveTSliderTo(this.valueNow + 1); - flag = true; - break; - - case this.keyCode.home: - this.moveTSliderTo(this.valueMin); - flag = true; - break; - - case this.keyCode.end: - this.moveTSliderTo(this.valueMax); - flag = true; - break; - - default: - break; - } - if (flag) { - event.preventDefault(); - event.stopPropagation(); - } -}; - -TSlider.prototype.handleFocus = function () { - this.domNode.classList.add('focus'); - this.railDomNode.classList.add('focus'); -}; - -TSlider.prototype.handleBlur = function () { - this.domNode.classList.remove('focus'); - this.railDomNode.classList.remove('focus'); -}; - -TSlider.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.moveTSliderTo(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 -TSlider.prototype.handleClick = function (event) { - var diffX = event.pageX - this.railDomNode.offsetLeft; - this.valueNow = parseInt( - ((this.valueMax - this.valueMin) * diffX) / this.railWidth - ); - this.moveTSliderTo(this.valueNow); - - event.preventDefault(); - event.stopPropagation(); -}; - -// Initialize TSliders on the page -window.addEventListener('load', function () { - var sliders = document.querySelectorAll( - '.aria-widget-text-slider [role=slider]' - ); - - for (var i = 0; i < sliders.length; i++) { - var s = new TSlider(sliders[i]); - s.init(); - } -}); diff --git a/examples/slider/js/vertical-slider.js b/examples/slider/js/vertical-slider.js deleted file mode 100644 index fedd86a763..0000000000 --- a/examples/slider/js/vertical-slider.js +++ /dev/null @@ -1,221 +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: vertical-slider.js - * - * Desc: Vertical slider widget that implements ARIA Authoring Practices - */ - -'use strict'; - -// Create Vertical Slider that contains value, valuemin, valuemax, and valuenow -var VSlider = function (domNode) { - this.domNode = domNode; - this.railDomNode = domNode.parentNode; - - this.valueDomNode = false; - - this.valueMin = 0; - this.valueMax = 100; - this.valueNow = 50; - - this.railHeight = 0; - - this.thumbWidth = 28; - this.thumbHeight = 8; - - this.keyCode = Object.freeze({ - left: 37, - up: 38, - right: 39, - down: 40, - pageUp: 33, - pageDown: 34, - end: 35, - home: 36, - }); -}; - -// Initialize vertical slider -VSlider.prototype.init = function () { - this.domNode.setAttribute('aria-orientation', 'vertical'); - - 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.railHeight = parseInt(this.railDomNode.style.height.slice(0, -2)); - - this.valueDomNode = this.railDomNode.previousElementSibling; - - if (this.valueDomNode) { - this.valueDomNode.style.position = 'relative'; - } - - 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.left = this.thumbWidth / -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.moveVSliderTo(this.valueNow); -}; - -VSlider.prototype.moveVSliderTo = 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); - this.domNode.setAttribute('aria-valuetext', this.valueNow + ' degrees'); - - var pos = - Math.round( - ((this.valueMax - this.valueNow) * this.railHeight) / - (this.valueMax - this.valueMin) - ) - - this.thumbHeight / 2; - - this.domNode.style.top = pos + 'px'; - - if (this.valueDomNode) { - this.valueDomNode.innerHTML = this.valueNow.toString(); - this.valueDomNode.style.left = this.railDomNode.offsetWidth / 2 - 2 + 'px'; - } -}; - -VSlider.prototype.handleKeyDown = function (event) { - var flag = false; - - switch (event.keyCode) { - case this.keyCode.left: - case this.keyCode.down: - this.moveVSliderTo(this.valueNow - 1); - flag = true; - break; - - case this.keyCode.right: - case this.keyCode.up: - this.moveVSliderTo(this.valueNow + 1); - flag = true; - break; - - case this.keyCode.pageDown: - this.moveVSliderTo(this.valueNow - 10); - flag = true; - break; - - case this.keyCode.pageUp: - this.moveVSliderTo(this.valueNow + 10); - flag = true; - break; - - case this.keyCode.home: - this.moveVSliderTo(this.valueMin); - flag = true; - break; - - case this.keyCode.end: - this.moveVSliderTo(this.valueMax); - flag = true; - break; - - default: - break; - } - - if (flag) { - event.preventDefault(); - event.stopPropagation(); - } -}; - -VSlider.prototype.handleFocus = function () { - this.domNode.classList.add('focus'); - this.railDomNode.classList.add('focus'); -}; - -VSlider.prototype.handleBlur = function () { - this.domNode.classList.remove('focus'); - this.railDomNode.classList.remove('focus'); -}; - -VSlider.prototype.handleMouseDown = function (event) { - var self = this; - - var handleMouseMove = function (event) { - var diffY = event.pageY - self.railDomNode.offsetTop; - self.valueNow = - self.valueMax - - parseInt(((self.valueMax - self.valueMin) * diffY) / self.railHeight); - self.moveVSliderTo(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 -VSlider.prototype.handleClick = function (event) { - var diffY = event.pageY - this.railDomNode.offsetTop; - this.valueNow = - this.valueMax - - parseInt(((this.valueMax - this.valueMin) * diffY) / this.railHeight); - this.moveVSliderTo(this.valueNow); - - event.preventDefault(); - event.stopPropagation(); -}; - -// Initialize VSliders on the page -window.addEventListener('load', function () { - var sliders = document.querySelectorAll( - '.aria-widget-vertical-slider [role=slider]' - ); - - for (var i = 0; i < sliders.length; i++) { - var s = new VSlider(sliders[i]); - s.init(); - } -}); diff --git a/examples/slider/slider-2.html b/examples/slider/slider-temperature.html similarity index 53% rename from examples/slider/slider-2.html rename to examples/slider/slider-temperature.html index 6785ddd487..e582b6cd9a 100644 --- a/examples/slider/slider-2.html +++ b/examples/slider/slider-temperature.html @@ -2,7 +2,9 @@ - Slider Examples with aria-orientation and aria-valuetext | WAI-ARIA Authoring Practices 1.2 + + + Slider Example with aria-orientation and aria-valuetext | WAI-ARIA Authoring Practices 1.2 @@ -11,10 +13,8 @@ - - - - + +

    Slider Examples with aria-orientation and aria-valuetext

    +
    +

    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. +

    +

    - The following example of a hypothetical online thermostat demonstrates the + The following examples are a temperature selector and seek control for a media player using the slider design pattern. - The desired temperature is set with the first slider, which is vertically oriented. - Two horizontal sliders set fan speed and mode. - The horizontal sliders illustrate how to use aria-valuetext to provide assistive technologies with meaningful names for numeric values. + The sliders illustrate how to use aria-valuetext to provide assistive technologies with meaningful names for numeric values. + The first slider is vertically oriented for setting a temperature with fixed point values and the aria-valuetext includes the Celsius temperature unit. + The second slider represents a slider to move or identify the current playing position in a audio or video media player and converts the seconds value in to minutes and seconds for aria-valuetext.

    Similar examples include:

    - +

    Example

    -
    -
    Temperature
    -
    0
    -
    -
    -
    -
    -
    -
    Fan
    -
    -
    -
    Off
    -
    Low
    -
    High
    -
    Auto
    -
    -
    -
    -
    Heat/Cool
    -
    -
    -
    Off
    -
    Heat
    -
    Cool
    -
    +
    +

    Temperature

    + + + 25°C + + + 25°C + + + +
    +
    +

    Accessibility Features

    +
      +
    • + For people with visual impairments the current value of the slider is visible next to the thumb when it has focus or is being dragged. +
    • +
    • + To ensure the borders of the slider rail, thumb and focus ring have sufficient contrast with the background when high contrast settings invert colors, the CSS currentColor value for the stroke property is used for the inline SVG rect elements to synchronize the border color with text content. If specific colors are used to specify the stroke property, these colors will remain the same in high contrast mode, which could lead to insufficient contrast between the rail and the thumb or even make them invisible if its color matches the high contrast mode background.
      + Note: The SVG element needs to have the CSS forced-color-adjust property set to the value auto for the currentColor value to be updated in high contrast modes, some browsers do not use auto for the default value. In addition, some browsers the SVG rect elements may need to adjust their stroke-opacity and fill-opacity values in place of using the transparent value for setting the fill and stroke colors in high contrast modes. +
    • +
    +
    +

    Keyboard Support

    @@ -121,13 +127,13 @@

    Keyboard Support

    - + - +
    Page Up Increases temperature slider value multiple steps. In this slider, jumps ten steps.Increases temperature slider value multiple steps. In this slider, jumps twenty steps (e.g. 2°C).
    Page Down Decreases temperature slider value multiple steps. In this slider, jumps ten steps.Decreases temperature slider value multiple steps. In this slider, jumps twenty steps (e.g. 2°C).
    @@ -143,11 +149,13 @@

    Keyboard Support

    +

    Role, Property, State, and Tabindex Attributes

    - + +
    @@ -157,28 +165,40 @@

    Role, Property, State, and Tabindex Attributes

    + + + + + + - + @@ -188,13 +208,13 @@

    Role, Property, State, and Tabindex Attributes

    aria-orientation @@ -204,7 +224,7 @@

    Role, Property, State, and Tabindex Attributes

    aria-valuemax=NUMBER @@ -214,7 +234,7 @@

    Role, Property, State, and Tabindex Attributes

    aria-valuemin=NUMBER @@ -224,7 +244,7 @@

    Role, Property, State, and Tabindex Attributes

    aria-valuenow=NUMBER @@ -234,13 +254,13 @@

    Role, Property, State, and Tabindex Attributes

    aria-valuetext=STRING @@ -250,9 +270,19 @@

    Role, Property, State, and Tabindex Attributes

    aria-labelledby=IDREF + + + + + + - +
    Role
    + none + + svg + + The use of the none role on the SVG element ensures assistive technologies do not interpret the SVG element as an image or some other role. +
    slider - div + g
    • 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.
    • +
    • Set on the g element that represents as the movable thumb because it is the operable element that represents the slider value.
    tabindex=0 - div + g Includes the slider thumb in the page tab sequence.
    - div + g
    • Indicates the orientation of the slider element.
    • Set to vertical for the temperature slider.
    • -
    • Set to horizontal for the fan and heat/cool controls.
    • +
    • Set to horizontal for the media seek slider.
    - div + g Specifies a numeric value that is the maximum value the slider can have.
    - div + g Specifies a numeric value that is the minimum value the slider can have.
    - div + g A numeric value that is the current value of the slider.
    - div + g
    • A string value that provides a user-friendly name for the current value of the slider.
    • For the temperature slider, is the value of the slider appended with the text degrees.
    • -
    • For fan and heat/cool, is the text representation of the current state, e.g., Off or Heat.
    • +
    • For media seek slider, is the text representation of the minutes and seconds.
    - div + g + Refers to the element containing the name (e.g. label) of the slider.
    + aria-hidden=true + + rect Refers to the element containing the name of the slider.Removes the SVG rect element from the accessibility tree, since some screen readers will describe it as an image separate from the slider.
    @@ -261,10 +291,8 @@

    Role, Property, State, and Tabindex Attributes

    Javascript and CSS Source Code

    diff --git a/test/tests/slider_slider-2.js b/test/tests/slider_slider-2.js deleted file mode 100644 index 498d66e3da..0000000000 --- a/test/tests/slider_slider-2.js +++ /dev/null @@ -1,795 +0,0 @@ -const { ariaTest } = require('..'); -const { By, Key } = require('selenium-webdriver'); -const assertAttributeValues = require('../util/assertAttributeValues'); -const assertAriaLabelledby = require('../util/assertAriaLabelledby'); -const assertAriaRoles = require('../util/assertAriaRoles'); - -const exampleFile = 'slider/slider-2.html'; - -const ex = { - sliderSelector: '#ex1 [role="slider"]', - tempSelector: '#ex1 #idTempThumb[role="slider"]', - fanSelector: '#ex1 #idFanThumb[role="slider"]', - heatSelector: '#ex1 #idHeatThumb[role="slider"]', - pageTempSelector: '#idTempValue', - tempMax: '100', - tempMin: '50', - tempDefault: '68', - tempPageInc: '10', - fanMax: '3', - fanMin: '0', - heatMax: '2', - heatMin: '0', - fanValues: ['Off', 'Low', 'High', 'Auto'], - heatValues: ['Off', 'Heat', 'Cool'], -}; - -const sendAllSlidersToEnd = async function (t) { - const sliders = await t.context.queryElements(t, ex.sliderSelector); - - for (let slider of sliders) { - await slider.sendKeys(Key.END); - } -}; - -const getValueAndText = async function (t, selector) { - const slider = await t.context.session.findElement(By.css(selector)); - const value = await slider.getAttribute('aria-valuenow'); - const text = await slider.getAttribute('aria-valuetext'); - return [value, text]; -}; - -// Attributes - -ariaTest( - 'role="slider" on div element', - exampleFile, - 'slider-role', - async (t) => { - await assertAriaRoles(t, 'ex1', 'slider', '3', 'div'); - } -); - -ariaTest( - '"tabindex" set to "0" on sliders', - exampleFile, - 'tabindex', - async (t) => { - await assertAttributeValues(t, ex.sliderSelector, 'tabindex', '0'); - } -); - -ariaTest( - '"aria-orientation" set on sliders', - exampleFile, - 'aria-orientation', - async (t) => { - await assertAttributeValues( - t, - ex.tempSelector, - 'aria-orientation', - 'vertical' - ); - await assertAttributeValues( - t, - ex.fanSelector, - 'aria-orientation', - 'horizontal' - ); - await assertAttributeValues( - t, - ex.heatSelector, - 'aria-orientation', - 'horizontal' - ); - } -); - -ariaTest( - '"aria-valuemax" set on sliders', - exampleFile, - 'aria-valuemax', - async (t) => { - await assertAttributeValues( - t, - ex.tempSelector, - 'aria-valuemax', - ex.tempMax - ); - await assertAttributeValues(t, ex.fanSelector, 'aria-valuemax', ex.fanMax); - await assertAttributeValues( - t, - ex.heatSelector, - 'aria-valuemax', - ex.heatMax - ); - } -); - -ariaTest( - '"aria-valuemin" set on sliders', - exampleFile, - 'aria-valuemin', - async (t) => { - await assertAttributeValues( - t, - ex.tempSelector, - 'aria-valuemin', - ex.tempMin - ); - await assertAttributeValues(t, ex.fanSelector, 'aria-valuemin', ex.fanMin); - await assertAttributeValues( - t, - ex.heatSelector, - 'aria-valuemin', - ex.heatMin - ); - } -); - -ariaTest( - '"aria-valuenow" reflects slider value', - exampleFile, - 'aria-valuenow', - async (t) => { - await assertAttributeValues( - t, - ex.tempSelector, - 'aria-valuenow', - ex.tempDefault - ); - await assertAttributeValues(t, ex.fanSelector, 'aria-valuenow', '0'); - await assertAttributeValues(t, ex.heatSelector, 'aria-valuenow', '0'); - } -); - -ariaTest( - '"aria-valuetext" reflects slider value', - exampleFile, - 'aria-valuetext', - async (t) => { - await assertAttributeValues( - t, - ex.fanSelector, - 'aria-valuetext', - ex.fanValues[0] - ); - await assertAttributeValues( - t, - ex.heatSelector, - 'aria-valuetext', - ex.heatValues[0] - ); - } -); - -ariaTest( - '"aria-labelledby" set on sliders', - exampleFile, - 'aria-labelledby', - async (t) => { - await assertAriaLabelledby(t, ex.sliderSelector); - } -); - -// Keys - -ariaTest( - 'Right arrow increases slider value by 1', - exampleFile, - 'key-right-arrow', - async (t) => { - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement( - By.css(ex.tempSelector) - ); - await tempSlider.sendKeys(Key.ARROW_RIGHT); - - let sliderVal = parseInt(ex.tempDefault) + 1; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - sliderVal.toString(), - 'After sending 1 arrow right key to the temp slider, "aria-valuenow": ' + - sliderVal - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - sliderVal.toString(), - 'Temp display should match value of slider: ' + sliderVal - ); - - // Send 51 more keys to temp slider - for (let i = 0; i < 51; i++) { - await tempSlider.sendKeys(Key.ARROW_RIGHT); - } - - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMax, - 'After sending 52 arrow right key, the value of the temp slider should be: ' + - ex.tempMax - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - ex.tempMax, - 'Temp display should match value of slider: ' + ex.tempMax - ); - - // Send 1 key to fan slider - const fanSlider = await t.context.session.findElement( - By.css(ex.fanSelector) - ); - await fanSlider.sendKeys(Key.ARROW_RIGHT); - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - ['1', ex.fanValues[1]], - 'After sending 1 arrow right key to the fan slider, aria-valuenow should be "1" and aria-value-text should be: ' + - ex.fanValues[1] - ); - - // Send more than 5 keys to fan slider - for (let i = 0; i < 5; i++) { - await fanSlider.sendKeys(Key.ARROW_RIGHT); - } - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [ex.fanMax, ex.fanValues[parseInt(ex.fanMax)]], - 'After sending 5 arrow right key to the fan slider, aria-valuenow should be "' + - ex.fanMax + - '" and aria-value-text should be: ' + - ex.fanValues[parseInt(ex.fanMax)] - ); - - // Send 1 key to heat slider - const heatSlider = await t.context.session.findElement( - By.css(ex.heatSelector) - ); - await heatSlider.sendKeys(Key.ARROW_RIGHT); - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - ['1', ex.heatValues[1]], - 'After sending 1 arrow right key to the heat slider, aria-valuenow should be "1" and aria-value-text should be: ' + - ex.heatValues[1] - ); - - // Send more than 5 keys to heat slider - for (let i = 0; i < 5; i++) { - await heatSlider.sendKeys(Key.ARROW_RIGHT); - } - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [ex.heatMax, ex.heatValues[parseInt(ex.heatMax)]], - 'After sending 5 arrow right key to the heat slider, aria-valuenow should be "' + - ex.heatMax + - '" and aria-value-text should be: ' + - ex.heatValues[parseInt(ex.heatMax)] - ); - } -); - -ariaTest( - 'up arrow increases slider value by 1', - exampleFile, - 'key-up-arrow', - async (t) => { - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement( - By.css(ex.tempSelector) - ); - await tempSlider.sendKeys(Key.ARROW_UP); - - let sliderVal = parseInt(ex.tempDefault) + 1; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - sliderVal.toString(), - 'After sending 1 arrow up key to the temp slider, "aria-valuenow": ' + - sliderVal - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - sliderVal.toString(), - 'Temp display should match value of slider: ' + sliderVal - ); - - // Send 51 more keys to temp slider - for (let i = 0; i < 51; i++) { - await tempSlider.sendKeys(Key.ARROW_UP); - } - - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMax, - 'After sending 52 arrow up key, the value of the temp slider should be: ' + - ex.tempMax - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - ex.tempMax, - 'Temp display should match value of slider: ' + ex.tempMax - ); - - // Send 1 key to fan slider - const fanSlider = await t.context.session.findElement( - By.css(ex.fanSelector) - ); - await fanSlider.sendKeys(Key.ARROW_UP); - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - ['1', ex.fanValues[1]], - 'After sending 1 arrow up key to the fan slider, aria-valuenow should be "1" and aria-value-text should be: ' + - ex.fanValues[1] - ); - - // Send more than 5 keys to fan slider - for (let i = 0; i < 5; i++) { - await fanSlider.sendKeys(Key.ARROW_UP); - } - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [ex.fanMax, ex.fanValues[parseInt(ex.fanMax)]], - 'After sending 6 arrow up key to the fan slider, aria-valuenow should be "' + - ex.fanMax + - '" and aria-value-text should be: ' + - ex.fanValues[parseInt(ex.fanMax)] - ); - - // Send 1 key to heat slider - const heatSlider = await t.context.session.findElement( - By.css(ex.heatSelector) - ); - await heatSlider.sendKeys(Key.ARROW_UP); - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - ['1', ex.heatValues[1]], - 'After sending 1 arrow up key to the heat slider, aria-valuenow should be "1" and aria-value-text should be: ' + - ex.heatValues[1] - ); - - // Send more than 5 keys to heat slider - for (let i = 0; i < 5; i++) { - await heatSlider.sendKeys(Key.ARROW_UP); - } - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [ex.heatMax, ex.heatValues[parseInt(ex.heatMax)]], - 'After sending 6 arrow up key to the heat slider, aria-valuenow should be "' + - ex.heatMax + - '" and aria-value-text should be: ' + - ex.heatValues[parseInt(ex.heatMax)] - ); - } -); - -ariaTest( - 'page up increases slider value by 10', - exampleFile, - 'key-page-up', - async (t) => { - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement( - By.css(ex.tempSelector) - ); - await tempSlider.sendKeys(Key.PAGE_UP); - - let sliderVal = parseInt(ex.tempDefault) + 10; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - sliderVal.toString(), - 'After sending 1 page up key to the temp slider, the value of the temp slider should be: ' + - sliderVal - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - sliderVal.toString(), - 'Temp display should match value of slider: ' + sliderVal - ); - - // Send more than 5 keys to temp slider - for (let i = 0; i < 5; i++) { - await tempSlider.sendKeys(Key.PAGE_UP); - } - - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMax, - 'After sending 5 page up key, the value of the temp slider should be: ' + - ex.tempMax - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - ex.tempMax, - 'Temp display should match value of slider: ' + ex.tempMax - ); - } -); - -ariaTest( - 'key end set slider at max value', - exampleFile, - 'key-end', - async (t) => { - // Send key end to temp slider - const tempSlider = await t.context.session.findElement( - By.css(ex.tempSelector) - ); - await tempSlider.sendKeys(Key.END); - - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMax, - 'After sending key END, the value of the temp slider should be: ' + - ex.tempMax - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - ex.tempMax, - 'Temp display should match value of slider: ' + ex.tempMax - ); - - // Send key end to fan slider - const fanSlider = await t.context.session.findElement( - By.css(ex.fanSelector) - ); - await fanSlider.sendKeys(Key.END); - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [ex.fanMax, ex.fanValues[parseInt(ex.fanMax)]], - 'After sending key end to the heat slider, aria-valuenow should be "' + - ex.fanMax + - '" and aria-value-text should be: ' + - ex.fanValues[parseInt(ex.fanMax)] - ); - - // Send key end to heat slider - const heatSlider = await t.context.session.findElement( - By.css(ex.heatSelector) - ); - await heatSlider.sendKeys(Key.END); - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [ex.heatMax, ex.heatValues[parseInt(ex.heatMax)]], - 'After sending key end to the heat slider, aria-valuenow should be "' + - ex.heatMax + - '" and aria-value-text should be: ' + - ex.heatValues[parseInt(ex.heatMax)] - ); - } -); - -ariaTest( - 'left arrow decreases slider value by 1', - exampleFile, - 'key-left-arrow', - async (t) => { - await sendAllSlidersToEnd(t); - - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement( - By.css(ex.tempSelector) - ); - await tempSlider.sendKeys(Key.ARROW_LEFT); - - let tempVal = parseInt(ex.tempMax) - 1; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - tempVal.toString(), - 'After sending 1 arrow left key, the value of the temp slider should be: ' + - tempVal - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - tempVal.toString(), - 'Temp display should match value of slider: ' + tempVal - ); - - // Send 51 more keys to temp slider - for (let i = 0; i < 51; i++) { - await tempSlider.sendKeys(Key.ARROW_LEFT); - } - - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMin.toString(), - 'After sending 53 arrow left key to the temp slider, "aria-valuenow": ' + - ex.tempMin - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - ex.tempMin.toString(), - 'Temp display should match value of slider: ' + ex.tempMin - ); - - // Send 1 key to fan slider - const fanSlider = await t.context.session.findElement( - By.css(ex.fanSelector) - ); - await fanSlider.sendKeys(Key.ARROW_LEFT); - - let fanVal = parseInt(ex.fanMax) - 1; - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [fanVal.toString(), ex.fanValues[fanVal]], - 'After sending 1 arrow left key to the fan slider, aria-valuenow should be "' + - fanVal + - '" and aria-value-text should be: ' + - ex.fanValues[fanVal] - ); - - // Send more than 5 keys to fan slider - for (let i = 0; i < 5; i++) { - await fanSlider.sendKeys(Key.ARROW_LEFT); - } - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - ['0', ex.fanValues[0]], - 'After sending 6 arrow left key to the fan slider, aria-valuenow should be "0" and aria-value-text should be: ' + - ex.fanValues[0] - ); - - // Send 1 key to heat slider - const heatSlider = await t.context.session.findElement( - By.css(ex.heatSelector) - ); - await heatSlider.sendKeys(Key.ARROW_LEFT); - - let heatVal = parseInt(ex.heatMax) - 1; - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [heatVal.toString(), ex.heatValues[heatVal]], - 'After sending 1 arrow left key to the heat slider, aria-valuenow should be "' + - heatVal + - '" and aria-value-text should be: ' + - ex.heatValues[heatVal] - ); - - // Send more than 5 keys to heat slider - for (let i = 0; i < 5; i++) { - await heatSlider.sendKeys(Key.ARROW_LEFT); - } - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - ['0', ex.heatValues[0]], - 'After sending 6 arrow left key to the heat slider, aria-valuenow should be "0" and aria-value-text should be: ' + - ex.heatValues[0] - ); - } -); - -ariaTest( - 'down arrow decreases slider value by 1', - exampleFile, - 'key-down-arrow', - async (t) => { - await sendAllSlidersToEnd(t); - - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement( - By.css(ex.tempSelector) - ); - await tempSlider.sendKeys(Key.ARROW_DOWN); - - let tempVal = parseInt(ex.tempMax) - 1; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - tempVal.toString(), - 'After sending 1 arrow down key, the value of the temp slider should be: ' + - tempVal - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - tempVal.toString(), - 'Temp display should match value of slider: ' + tempVal - ); - - // Send 51 more keys to temp slider - for (let i = 0; i < 51; i++) { - await tempSlider.sendKeys(Key.ARROW_DOWN); - } - - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMin.toString(), - 'After sending 53 arrow down key to the temp slider, "aria-valuenow": ' + - ex.tempMin - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - ex.tempMin.toString(), - 'Temp display should match value of slider: ' + ex.tempMin - ); - - // Send 1 key to fan slider - const fanSlider = await t.context.session.findElement( - By.css(ex.fanSelector) - ); - await fanSlider.sendKeys(Key.ARROW_DOWN); - - let fanVal = parseInt(ex.fanMax) - 1; - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [fanVal.toString(), ex.fanValues[fanVal]], - 'After sending 1 arrow down key to the fan slider, aria-valuenow should be "' + - fanVal + - '" and aria-value-text should be: ' + - ex.fanValues[fanVal] - ); - - // Send more than 5 keys to fan slider - for (let i = 0; i < 5; i++) { - await fanSlider.sendKeys(Key.ARROW_DOWN); - } - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - ['0', ex.fanValues[0]], - 'After sending 6 arrow down key to the fan slider, aria-valuenow should be "0" and aria-value-text should be: ' + - ex.fanValues[0] - ); - - // Send 1 key to heat slider - const heatSlider = await t.context.session.findElement( - By.css(ex.heatSelector) - ); - await heatSlider.sendKeys(Key.ARROW_DOWN); - - let heatVal = parseInt(ex.heatMax) - 1; - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [heatVal.toString(), ex.heatValues[heatVal]], - 'After sending 1 arrow down key to the heat slider, aria-valuenow should be "' + - heatVal + - '" and aria-value-text should be: ' + - ex.heatValues[heatVal] - ); - - // Send more than 5 keys to heat slider - for (let i = 0; i < 5; i++) { - await heatSlider.sendKeys(Key.ARROW_DOWN); - } - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - ['0', ex.heatValues[0]], - 'After sending 6 arrow down key to the heat slider, aria-valuenow should be "0" and aria-value-text should be: ' + - ex.heatValues[0] - ); - } -); - -ariaTest( - 'page down decreases slider value by 10', - exampleFile, - 'key-page-down', - async (t) => { - // Send 1 key to temp slider - const tempSlider = await t.context.session.findElement( - By.css(ex.tempSelector) - ); - await tempSlider.sendKeys(Key.PAGE_DOWN); - - let sliderVal = parseInt(ex.tempDefault) - 10; - t.is( - await tempSlider.getAttribute('aria-valuenow'), - sliderVal.toString(), - 'After sending 1 page down key to the temp slider, the value of the temp slider should be: ' + - sliderVal - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - sliderVal.toString(), - 'Temp display should match value of slider: ' + sliderVal - ); - - // Send more than 5 keys to temp slider - for (let i = 0; i < 5; i++) { - await tempSlider.sendKeys(Key.PAGE_DOWN); - } - - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMin, - 'After sending 5 page down key, the value of the temp slider should be: ' + - ex.tempMin - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - ex.tempMin, - 'Temp display should match value of slider: ' + ex.tempMin - ); - } -); - -ariaTest( - 'home set slider value to minimum', - exampleFile, - 'key-home', - async (t) => { - // Send key home to temp slider - const tempSlider = await t.context.session.findElement( - By.css(ex.tempSelector) - ); - await tempSlider.sendKeys(Key.HOME); - - t.is( - await tempSlider.getAttribute('aria-valuenow'), - ex.tempMin, - 'After sending key HOME, the value of the temp slider should be: ' + - ex.tempMin - ); - t.is( - await t.context.session - .findElement(By.css(ex.pageTempSelector)) - .getText(), - ex.tempMin, - 'Temp display should match value of slider: ' + ex.tempMin - ); - - // Send key home to fan slider - const fanSlider = await t.context.session.findElement( - By.css(ex.fanSelector) - ); - await fanSlider.sendKeys(Key.HOME); - - t.deepEqual( - await getValueAndText(t, ex.fanSelector), - [ex.fanMin, ex.fanValues[parseInt(ex.fanMin)]], - 'After sending key home to the heat slider, aria-valuenow should be "' + - ex.fanMin + - '" and aria-value-text should be: ' + - ex.fanValues[parseInt(ex.fanMin)] - ); - - // Send key home to heat slider - const heatSlider = await t.context.session.findElement( - By.css(ex.heatSelector) - ); - await heatSlider.sendKeys(Key.HOME); - - t.deepEqual( - await getValueAndText(t, ex.heatSelector), - [ex.heatMin, ex.heatValues[parseInt(ex.heatMin)]], - 'After sending key home to the heat slider, aria-valuenow should be "' + - ex.heatMin + - '" and aria-value-text should be: ' + - ex.heatValues[parseInt(ex.heatMin)] - ); - } -); diff --git a/test/tests/slider_slider-temperature.js b/test/tests/slider_slider-temperature.js new file mode 100644 index 0000000000..9cfe953e59 --- /dev/null +++ b/test/tests/slider_slider-temperature.js @@ -0,0 +1,473 @@ +const { ariaTest } = require('..'); +const { By, Key } = require('selenium-webdriver'); +const assertAttributeValues = require('../util/assertAttributeValues'); +const assertAriaLabelledby = require('../util/assertAriaLabelledby'); +const assertAriaRoles = require('../util/assertAriaRoles'); + +const exampleFile = 'slider/slider-temperature.html'; + +const ex = { + railRects: '#ex1 rect.rail', + labelG: '#ex1 g.value-label', + sliderSelector: '#ex1 [role="slider"]', + tempSelector: '#id-temp-slider', + tempValueSelector: '#id-temp-slider .value', + tempMax: '38.0', + tempMin: '10.0', + tempDefault: '25.0', + tempInc: '.1', + tempPageInc: '2.0', + tempSuffix: '°C', +}; + +const sendAllSlidersToEnd = async function (t) { + const sliders = await t.context.queryElements(t, ex.sliderSelector); + + for (let slider of sliders) { + await slider.sendKeys(Key.END); + } +}; + +// Attributes + +ariaTest('role="none" on SVG element', exampleFile, 'svg-none', async (t) => { + await assertAriaRoles(t, 'ex1', 'none', '1', 'svg'); +}); + +ariaTest( + 'SVG rects used for the rail have aria-hidden', + exampleFile, + 'aria-hidden-rect', + async (t) => { + await assertAttributeValues(t, ex.railRects, 'aria-hidden', 'true'); + } +); + +ariaTest( + 'role="slider" on SVG g element', + exampleFile, + 'slider-role', + async (t) => { + await assertAriaRoles(t, 'ex1', 'slider', '1', 'g'); + } +); + +ariaTest( + '"tabindex" set to "0" on sliders', + exampleFile, + 'slider-tabindex', + async (t) => { + await assertAttributeValues(t, ex.sliderSelector, 'tabindex', '0'); + } +); + +ariaTest( + '"aria-orientation" set on sliders', + exampleFile, + 'aria-orientation', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-orientation', + 'vertical' + ); + } +); + +ariaTest( + '"aria-valuemax" set on sliders', + exampleFile, + 'aria-valuemax', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-valuemax', + ex.tempMax + ); + } +); + +ariaTest( + '"aria-valuemin" set on sliders', + exampleFile, + 'aria-valuemin', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-valuemin', + ex.tempMin + ); + } +); + +ariaTest( + '"aria-valuenow" reflects slider value', + exampleFile, + 'aria-valuenow', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-valuenow', + ex.tempDefault + ); + } +); + +ariaTest( + '"aria-labelledby" set on sliders', + exampleFile, + 'aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.sliderSelector); + } +); + +// Keys + +ariaTest( + 'Right arrow increases slider value by 1', + exampleFile, + 'key-right-arrow', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_RIGHT); + + let sliderVal = parseFloat(ex.tempDefault) + ex.tempInc; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 arrow right key to the temp slider, "aria-valuenow": ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + sliderVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send 200 more keys to temp slider + for (let i = 0; i < 200; i++) { + await tempSlider.sendKeys(Key.ARROW_RIGHT); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending 200 arrow right key, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMax + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMax + ); + } +); + +ariaTest( + 'up arrow increases slider value by 1', + exampleFile, + 'key-up-arrow', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_UP); + + let sliderVal = parseFloat(ex.tempDefault) + ex.tempInc; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 arrow up key to the temp slider, "aria-valuenow": ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + sliderVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send 200 more keys to temp slider + for (let i = 0; i < 200; i++) { + await tempSlider.sendKeys(Key.ARROW_UP); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending 200 arrow up key, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMax + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMax + ); + } +); + +ariaTest( + 'page up increases slider value by big step', + exampleFile, + 'key-page-up', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.PAGE_UP); + + let sliderVal = ( + parseFloat(ex.tempDefault) + parseFloat(ex.tempPageInc) + ).toFixed(1); + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 page up key to the temp slider, the value of the temp slider should be: ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + sliderVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send more than 10 keys to temp slider + for (let i = 0; i < 10; i++) { + await tempSlider.sendKeys(Key.PAGE_UP); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending 10 page up key, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMax + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMax + ); + } +); + +ariaTest( + 'key end set slider at max value', + exampleFile, + 'key-end', + async (t) => { + // Send key end to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.END); + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending key END, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMax + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMax + ); + } +); + +ariaTest( + 'left arrow decreases slider value by 1', + exampleFile, + 'key-left-arrow', + async (t) => { + await sendAllSlidersToEnd(t); + + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_LEFT); + + let tempVal = (parseFloat(ex.tempMax) - parseFloat(ex.tempInc)).toFixed(1); + t.is( + await tempSlider.getAttribute('aria-valuenow'), + tempVal.toString(), + 'After sending 1 arrow left key, the value of the temp slider should be: ' + + tempVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + tempVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + tempVal + ); + + // Send 300 more keys to temp slider + for (let i = 0; i < 300; i++) { + await tempSlider.sendKeys(Key.ARROW_LEFT); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin.toString(), + 'After sending 300 arrow left key to the temp slider, "aria-valuenow": ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMin.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMin + ); + } +); + +ariaTest( + 'down arrow decreases slider value by 1', + exampleFile, + 'key-down-arrow', + async (t) => { + await sendAllSlidersToEnd(t); + + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_DOWN); + + let tempVal = (parseFloat(ex.tempMax) - parseFloat(ex.tempInc)).toFixed(1); + t.is( + await tempSlider.getAttribute('aria-valuenow'), + tempVal.toString(), + 'After sending 1 arrow down key, the value of the temp slider should be: ' + + tempVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + tempVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + tempVal + ); + + // Send 300 more keys to temp slider + for (let i = 0; i < 300; i++) { + await tempSlider.sendKeys(Key.ARROW_DOWN); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin.toString(), + 'After sending 300 arrow down key to the temp slider, "aria-valuenow": ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMin.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMin + ); + } +); + +ariaTest( + 'page down decreases slider value by big step', + exampleFile, + 'key-page-down', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.PAGE_DOWN); + + let sliderVal = ( + parseFloat(ex.tempDefault) - parseFloat(ex.tempPageInc) + ).toFixed(1); + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 page down key to the temp slider, the value of the temp slider should be: ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + sliderVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send more than 20 keys to temp slider + for (let i = 0; i < 20; i++) { + await tempSlider.sendKeys(Key.PAGE_DOWN); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin, + 'After sending 20 page down key, the value of the temp slider should be: ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMin + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMin + ); + } +); + +ariaTest( + 'home set slider value to minimum', + exampleFile, + 'key-home', + async (t) => { + // Send key home to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.HOME); + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin, + 'After sending key HOME, the value of the temp slider should be: ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMin + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMin + ); + } +); From 4b84b40766436032621d2dc11a07689818a8e938 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 21 Apr 2021 11:35:43 -0500 Subject: [PATCH 02/17] updated documentation and added validation exception --- .vnurc | 3 +++ aria-practices.html | 4 ++-- examples/index.html | 18 +++++++++--------- examples/slider/slider-temperature.html | 23 ++++++++++------------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.vnurc b/.vnurc index 460f3625dc..01d8027396 100644 --- a/.vnurc +++ b/.vnurc @@ -20,3 +20,6 @@ The “row” role is unnecessary for element “tr”. Attribute “aria-activedescendant” value should either refer to a descendant element, or should be accompanied by attribute “aria-owns”. # https://github.com/w3c/aria-practices/issues/1678 Section lacks heading. Consider using “h2”-“h6” elements to add identifying headings to all sections. +# https://github.com/validator/validator/issues/1096 +Bad value “none” for attribute “role” on element “svg”. +Bad value “presentation” for attribute “role” on element “svg”. diff --git a/aria-practices.html b/aria-practices.html index c0740a23ec..346cd6a6a0 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -2361,8 +2361,8 @@

    Slider

    Examples

    diff --git a/examples/index.html b/examples/index.html index 57c7518408..64c23b3994 100644 --- a/examples/index.html +++ b/examples/index.html @@ -237,7 +237,7 @@

    Examples by Role

    @@ -305,7 +305,7 @@

    Examples by Role

    @@ -589,7 +589,7 @@

    Examples By Properties and States

  • Radio Group Using aria-activedescendant
  • Radio Group Using Roving tabindex
  • Color Viewer Slider
  • -
  • Slider with aria-orientation and aria-valuetext
  • +
  • Temperature Selector Slider with aria-orientation and aria-valuetext
  • Date Picker Spin Button
  • Table
  • Tabs with Automatic Activation
  • @@ -627,7 +627,7 @@

    Examples By Properties and States

  • Radio Group Using aria-activedescendant
  • Radio Group Using Roving tabindex
  • Color Viewer Slider
  • -
  • Slider with aria-orientation and aria-valuetext
  • +
  • Temperature Selector Slider with aria-orientation and aria-valuetext
  • Date Picker Spin Button
  • Tabs with Automatic Activation
  • Tabs with Manual Activation
  • @@ -681,7 +681,7 @@

    Examples By Properties and States

    aria-orientation - Slider with aria-orientation and aria-valuetext + Temperature Selector Slider with aria-orientation and aria-valuetext aria-owns @@ -771,7 +771,7 @@

    Examples By Properties and States

  • Meter
  • Horizontal Multi-Thumb Slider
  • Color Viewer Slider
  • -
  • Slider with aria-orientation and aria-valuetext
  • +
  • Temperature Selector Slider with aria-orientation and aria-valuetext
  • Date Picker Spin Button
  • @@ -783,7 +783,7 @@

    Examples By Properties and States

  • Meter
  • Horizontal Multi-Thumb Slider
  • Color Viewer Slider
  • -
  • Slider with aria-orientation and aria-valuetext
  • +
  • Temperature Selector Slider with aria-orientation and aria-valuetext
  • Date Picker Spin Button
  • Toolbar
  • @@ -796,7 +796,7 @@

    Examples By Properties and States

  • Meter
  • Horizontal Multi-Thumb Slider
  • Color Viewer Slider
  • -
  • Slider with aria-orientation and aria-valuetext
  • +
  • Temperature Selector Slider with aria-orientation and aria-valuetext
  • Date Picker Spin Button
  • Toolbar
  • @@ -807,7 +807,7 @@

    Examples By Properties and States

    diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index e582b6cd9a..3150a635ae 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -4,7 +4,7 @@ - Slider Example with aria-orientation and aria-valuetext | WAI-ARIA Authoring Practices 1.2 + Temperature Selector Slider Example with aria-orientation and aria-valuetext | WAI-ARIA Authoring Practices 1.2 @@ -26,17 +26,16 @@
    -

    Slider Examples with aria-orientation and aria-valuetext

    +

    Temperature Selector Slider Example with aria-orientation and aria-valuetext

    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.

    - The following examples are a temperature selector and seek control for a media player using the + The following example is a vertically oriented temperature selector with fixed point numerical values using the slider design pattern. - The sliders illustrate how to use aria-valuetext to provide assistive technologies with meaningful names for numeric values. - The first slider is vertically oriented for setting a temperature with fixed point values and the aria-valuetext includes the Celsius temperature unit. - The second slider represents a slider to move or identify the current playing position in a audio or video media player and converts the seconds value in to minutes and seconds for aria-valuetext. + The slider illustrates how to use aria-valuetext to provide assistive technologies with meaningful names for numeric values. + The aria-orientation attribute identifies the slider is vertically oriented and aria-valuetext provides a more meaningful name by appending "degrees Celsius" temperature unit to the current value.

    Similar examples include:

    @@ -835,7 +835,7 @@

    Examples By Properties and States

  • Meter
  • Horizontal Multi-Thumb Slider
  • Color Viewer Slider (HC)
  • -
  • Temperature Selector Slider with aria-orientation and aria-valuetext (HC)
  • +
  • Vertical Temperature Slider (HC)
  • Date Picker Spin Button
  • Toolbar
  • @@ -846,7 +846,7 @@

    Examples By Properties and States

    diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index ac7b1a747d..8825fa12ed 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -4,7 +4,7 @@ - Temperature Selector Slider Example with aria-orientation and aria-valuetext | WAI-ARIA Authoring Practices 1.2 + Vertical Temperature Slider Example | WAI-ARIA Authoring Practices 1.2 @@ -26,10 +26,14 @@
    -

    Temperature Selector Slider Example with aria-orientation and aria-valuetext

    +

    Vertical Temperature Slider Example/h1>
    -

    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. -

    +

    + 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. +

    The following example is a vertically oriented temperature selector with fixed point numerical values using the From 3195dd16d346b6fe350fab912c50d220359e531f Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 11 May 2021 22:14:57 -0700 Subject: [PATCH 06/17] Streamline intro --- examples/slider/slider-temperature.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index 8825fa12ed..8206cf5816 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -36,14 +36,13 @@

    Vertical Temperature Slider Example/h1>

    - The following example is a vertically oriented temperature selector with fixed point numerical values using the + The following example is a vertically oriented temperature control that implements the slider design pattern. - The slider illustrates how to use aria-valuetext to provide assistive technologies with meaningful names for numeric values. - The aria-orientation attribute identifies the slider is vertically oriented and aria-valuetext provides a more meaningful name by appending "degrees Celsius" temperature unit to the current value. + The slider illustrates use of aria-orientation to specify vertical orientation and use of aria-valuetext to convey unit of measure for numeric values to assistive technology users by appending degrees Celsius to the current value.

    Similar examples include:

    From a15ffb242c216af0764451ffe2d638e8de367ad2 Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 11 May 2021 22:17:02 -0700 Subject: [PATCH 07/17] adjust indent of accessibility features section --- examples/slider/slider-temperature.html | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index 8206cf5816..8acbe03f76 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -78,18 +78,18 @@

    Example

    -
    -

    Accessibility Features

    -
      -
    • - The current value of the slider is visible next to the thumb, so when the thumb value is changed using a pointing device by a person with the visual impairment they can immediately see the change in value without have to move their visual attention to another part of the page. -
    • -
    • - To ensure the borders of the slider rail, thumb and focus ring have sufficient contrast with the background when high contrast settings invert colors, the CSS currentColor value for the stroke property is used for the inline SVG rect elements to synchronize the border color with text content. If specific colors are used to specify the stroke property, these colors will remain the same in high contrast mode, which could lead to insufficient contrast between the rail and the thumb or even make them invisible if its color matches the high contrast mode background.
      - Note: The SVG element needs to have the CSS forced-color-adjust property set to the value auto for the currentColor value to be updated in high contrast modes, some browsers do not use auto for the default value. In addition, some browsers the SVG rect elements may need to adjust their stroke-opacity and fill-opacity values in place of using the transparent value for setting the fill and stroke colors in high contrast modes. -
    • -
    -
    +
    +

    Accessibility Features

    +
      +
    • + The current value of the slider is visible next to the thumb, so when the thumb value is changed using a pointing device by a person with the visual impairment they can immediately see the change in value without have to move their visual attention to another part of the page. +
    • +
    • + To ensure the borders of the slider rail, thumb and focus ring have sufficient contrast with the background when high contrast settings invert colors, the CSS currentColor value for the stroke property is used for the inline SVG rect elements to synchronize the border color with text content. If specific colors are used to specify the stroke property, these colors will remain the same in high contrast mode, which could lead to insufficient contrast between the rail and the thumb or even make them invisible if its color matches the high contrast mode background.
      + Note: The SVG element needs to have the CSS forced-color-adjust property set to the value auto for the currentColor value to be updated in high contrast modes, some browsers do not use auto for the default value. In addition, some browsers the SVG rect elements may need to adjust their stroke-opacity and fill-opacity values in place of using the transparent value for setting the fill and stroke colors in high contrast modes. +
    • +
    +

    Keyboard Support

    From edb061e090e4d1dbfcc029e1c4d87a4edfa4b2a6 Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 11 May 2021 22:46:49 -0700 Subject: [PATCH 08/17] Fix h1 closing tag and editorial revisions to accessibility features --- examples/slider/slider-temperature.html | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index 8acbe03f76..77fa9e6df0 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -26,7 +26,7 @@
    -

    Vertical Temperature Slider Example/h1> +

    Vertical Temperature 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. @@ -81,12 +81,14 @@

    Example

    Accessibility Features

      +
    • The display of the slider's current value remains adjacent to the thumb as the thumb is moved, so people with a small field of view (e.g., due to magnification) can easily see the value while focusing on the thumb as they move it.
    • - The current value of the slider is visible next to the thumb, so when the thumb value is changed using a pointing device by a person with the visual impairment they can immediately see the change in value without have to move their visual attention to another part of the page. -
    • -
    • - To ensure the borders of the slider rail, thumb and focus ring have sufficient contrast with the background when high contrast settings invert colors, the CSS currentColor value for the stroke property is used for the inline SVG rect elements to synchronize the border color with text content. If specific colors are used to specify the stroke property, these colors will remain the same in high contrast mode, which could lead to insufficient contrast between the rail and the thumb or even make them invisible if its color matches the high contrast mode background.
      - Note: The SVG element needs to have the CSS forced-color-adjust property set to the value auto for the currentColor value to be updated in high contrast modes, some browsers do not use auto for the default value. In addition, some browsers the SVG rect elements may need to adjust their stroke-opacity and fill-opacity values in place of using the transparent value for setting the fill and stroke colors in high contrast modes. + To ensure the borders of the slider rail, thumb and focus ring have sufficient contrast with the background when high contrast settings invert colors, the color of the borders is synchronized with the color of the text content. + This is achieved by using the CSS currentColor value for the stroke property of the inline SVG rect elements. + If specific colors were instead used to specify the stroke property, those colors would remain the same in high contrast mode, which could lead to insufficient contrast between the rail and the thumb or even make them invisible if their color matched the high contrast mode background.
      + Note: The SVG element needs to have the CSS forced-color-adjust property set to auto for the currentColor value to be updated in high contrast mode. + Some browsers do not use auto for the default value. + In addition, some browsers the SVG rect elements may need to adjust their stroke-opacity and fill-opacity values in place of using the transparent value for setting the fill and stroke colors in high contrast modes.
    From 4b92e4881a3e3918e9f89ab6cc018c2c422a97a4 Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 11 May 2021 23:12:48 -0700 Subject: [PATCH 09/17] Editorial revisios to states and props documentation --- examples/slider/slider-temperature.html | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index 77fa9e6df0..b1debda531 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -156,9 +156,8 @@

    Keyboard Support

    -
    +

    Role, Property, State, and Tabindex Attributes

    - @@ -178,7 +177,7 @@

    Role, Property, State, and Tabindex Attributes

    svg @@ -192,7 +191,7 @@

    Role, Property, State, and Tabindex Attributes

    @@ -215,10 +214,7 @@

    Role, Property, State, and Tabindex Attributes

    g @@ -229,7 +225,7 @@

    Role, Property, State, and Tabindex Attributes

    - + @@ -239,7 +235,7 @@

    Role, Property, State, and Tabindex Attributes

    - + @@ -260,10 +256,7 @@

    Role, Property, State, and Tabindex Attributes

    g From 60f5ccae90f7bf4b334dfcec8047227868c95480 Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 11 May 2021 23:19:27 -0700 Subject: [PATCH 10/17] Replace q tags inside code tags with quote chars --- examples/slider/slider-temperature.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index b1debda531..dd6e156bac 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -198,7 +198,7 @@

    Role, Property, State, and Tabindex Attributes

    - +
    - The use of the none role on the SVG element ensures assistive technologies do not interpret the SVG element as an image or some other role. + ensures assistive technologies do not present the SVG element as an image or any other type of meaningful element.
    • Identifies the element as a slider.
    • -
    • Set on the g element that represents as the movable thumb because it is the operable element that represents the slider value.
    • +
    • Set on the g element that represents the movable thumb because it is the operable element that receives focus and conveys the value.
    -
      -
    • Indicates the orientation of the slider element.
    • -
    • Set to vertical for the temperature selector slider.
    • -
    + Indicates the slider is vertically oriented.
    g Specifies a numeric value that is the maximum value the slider can have.Specifies the numeric value that is the maximum allowed value of the slider.
    g Specifies a numeric value that is the minimum value the slider can have.Specifies the numeric value that is the minimum allowed value of the slider.
    -
      -
    • A string value that provides a user-friendly name for the current value of the slider.
    • -
    • For the temperature selector slider, is the value of the slider appended with the text degrees Celsius.
    • -
    + Provides a more user-friendly name for the current value of the slider by combining the current value with the string degrees Celsius.
    - tabindex=0 + tabindex="0" g @@ -220,7 +220,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-valuemax=NUMBER + aria-valuemax="NUMBER" g @@ -230,7 +230,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-valuemin=NUMBER + aria-valuemin="NUMBER" g @@ -240,7 +240,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-valuenow=NUMBER + aria-valuenow="NUMBER" g @@ -250,7 +250,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-valuetext=STRING + aria-valuetext="STRING" g @@ -262,7 +262,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-labelledby=IDREF + aria-labelledby="IDREF" g @@ -272,7 +272,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-hidden=true + aria-hidden="true" rect From 877cd26cd9d0fa09a33154641f3781c32582f4b4 Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 11 May 2021 23:25:25 -0700 Subject: [PATCH 11/17] Clarify aria-hidden documentation --- examples/slider/slider-temperature.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index dd6e156bac..f46903b7e5 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -277,7 +277,7 @@

    Role, Property, State, and Tabindex Attributes

    rect Removes the SVG rect element from the accessibility tree, since some screen readers will describe it as an image separate from the slider.Removes the SVG rect element from the accessibility tree to prevent screen readers from presenting it as a meaningful image.
    From 354ca6857f0fc0aa005ef2924543fdcfef25329e Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 12 May 2021 10:04:43 -0500 Subject: [PATCH 12/17] updated guidance for using fill-opacity --- examples/slider/slider-temperature.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index f46903b7e5..45f37935db 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -84,11 +84,10 @@

    Accessibility Features

  • The display of the slider's current value remains adjacent to the thumb as the thumb is moved, so people with a small field of view (e.g., due to magnification) can easily see the value while focusing on the thumb as they move it.
  • To ensure the borders of the slider rail, thumb and focus ring have sufficient contrast with the background when high contrast settings invert colors, the color of the borders is synchronized with the color of the text content. - This is achieved by using the CSS currentColor value for the stroke property of the inline SVG rect elements. - If specific colors were instead used to specify the stroke property, those colors would remain the same in high contrast mode, which could lead to insufficient contrast between the rail and the thumb or even make them invisible if their color matched the high contrast mode background.
    + Support for the high contrast foreground color is achieved by using the CSS currentColor value for the stroke property of inline SVG rect elements. To enable the high contrast background color to be the used as the contrasting color the fill-opacity attribute is set to zero. + If specific colors were instead used to specify the stroke and fill properties, those colors would remain the same in high contrast mode, which could lead to insufficient contrast between the rail and the thumb or even make them invisible if their color matched the high contrast mode background.
    Note: The SVG element needs to have the CSS forced-color-adjust property set to auto for the currentColor value to be updated in high contrast mode. Some browsers do not use auto for the default value. - In addition, some browsers the SVG rect elements may need to adjust their stroke-opacity and fill-opacity values in place of using the transparent value for setting the fill and stroke colors in high contrast modes.
  • From 9204360d90b428090aa8e81b2fb28a2e625f157f Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Wed, 12 May 2021 10:21:14 -0500 Subject: [PATCH 13/17] updated guidance for using fill-opacity --- examples/slider/slider-temperature.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index 45f37935db..3f9b8aa004 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -83,8 +83,8 @@

    Accessibility Features

    • The display of the slider's current value remains adjacent to the thumb as the thumb is moved, so people with a small field of view (e.g., due to magnification) can easily see the value while focusing on the thumb as they move it.
    • - To ensure the borders of the slider rail, thumb and focus ring have sufficient contrast with the background when high contrast settings invert colors, the color of the borders is synchronized with the color of the text content. - Support for the high contrast foreground color is achieved by using the CSS currentColor value for the stroke property of inline SVG rect elements. To enable the high contrast background color to be the used as the contrasting color the fill-opacity attribute is set to zero. + To ensure the borders of the slider rail, thumb and focus ring have sufficient contrast with the background when high contrast settings invert colors, the color of the borders are synchronized with the color of the text content. + For example, support for using the high contrast foreground color for the focus ring border is achieved by using the CSS currentColor value for the stroke property of an inline SVG rect element. To enable the high contrast background color to be the used as the contrasting color the fill-opacity attribute of the rect is set to zero. If specific colors were instead used to specify the stroke and fill properties, those colors would remain the same in high contrast mode, which could lead to insufficient contrast between the rail and the thumb or even make them invisible if their color matched the high contrast mode background.
      Note: The SVG element needs to have the CSS forced-color-adjust property set to auto for the currentColor value to be updated in high contrast mode. Some browsers do not use auto for the default value. From 4a739bef37a9a676cf94f4105fd7b05489f1329f Mon Sep 17 00:00:00 2001 From: Matt King Date: Wed, 12 May 2021 22:19:53 -0700 Subject: [PATCH 14/17] Editorial revision to HCNM documentation --- examples/slider/slider-temperature.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index 3f9b8aa004..75f011d382 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -84,7 +84,8 @@

      Accessibility Features

    • The display of the slider's current value remains adjacent to the thumb as the thumb is moved, so people with a small field of view (e.g., due to magnification) can easily see the value while focusing on the thumb as they move it.
    • To ensure the borders of the slider rail, thumb and focus ring have sufficient contrast with the background when high contrast settings invert colors, the color of the borders are synchronized with the color of the text content. - For example, support for using the high contrast foreground color for the focus ring border is achieved by using the CSS currentColor value for the stroke property of an inline SVG rect element. To enable the high contrast background color to be the used as the contrasting color the fill-opacity attribute of the rect is set to zero. + For example, the color of the focus ring border is set to match the foreground color of high contrast mode text by specifying the CSS currentColor value for the stroke property of the inline SVG rect element used to draw the focus ring border. + To make the background of the rect match the high contrast background color, the fill-opacity attribute of the rect is set to zero. If specific colors were instead used to specify the stroke and fill properties, those colors would remain the same in high contrast mode, which could lead to insufficient contrast between the rail and the thumb or even make them invisible if their color matched the high contrast mode background.
      Note: The SVG element needs to have the CSS forced-color-adjust property set to auto for the currentColor value to be updated in high contrast mode. Some browsers do not use auto for the default value. From 9d7954d40c916c0ad945ecda527d74242e3c874e Mon Sep 17 00:00:00 2001 From: Matt King Date: Wed, 12 May 2021 22:21:26 -0700 Subject: [PATCH 15/17] Remove unnecessary styling from kb documentation H2 --- examples/slider/slider-temperature.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index 75f011d382..c768c45442 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -94,7 +94,7 @@

      Accessibility Features

    -

    Keyboard Support

    +

    Keyboard Support

    From 265b9c412f49d28afdd63882b1a1d42fa8a01966 Mon Sep 17 00:00:00 2001 From: Matt King Date: Tue, 18 May 2021 10:50:50 -0700 Subject: [PATCH 16/17] increment and decrement to increase and decrease --- examples/slider/slider-temperature.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index c768c45442..ef966f8bb9 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -30,7 +30,7 @@

    Vertical Temperature 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. + To change the slider value, touch-based assistive technologies need to respond to user gestures for increasing and decreasing 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.

    From babb50cb241e47c6542d7f1d917dedfc4c18c1ee Mon Sep 17 00:00:00 2001 From: Matt King Date: Sun, 23 May 2021 15:10:21 -0700 Subject: [PATCH 17/17] Update links among examples --- aria-practices.html | 5 +++-- examples/slider/slider-color-viewer.html | 7 +++---- examples/slider/slider-rating.html | 4 +++- examples/slider/slider-temperature.html | 4 +++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/aria-practices.html b/aria-practices.html index 480976f78d..72ba1faafb 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -2361,8 +2361,9 @@

    Slider

    Examples

    diff --git a/examples/slider/slider-color-viewer.html b/examples/slider/slider-color-viewer.html index 17fd7ee5b3..b278585962 100644 --- a/examples/slider/slider-color-viewer.html +++ b/examples/slider/slider-color-viewer.html @@ -43,10 +43,9 @@

    Color Viewer Slider Example

    Similar examples include:

    diff --git a/examples/slider/slider-rating.html b/examples/slider/slider-rating.html index 959ab4a5e4..d716bdb415 100644 --- a/examples/slider/slider-rating.html +++ b/examples/slider/slider-rating.html @@ -45,7 +45,9 @@

    Rating Slider Example

    Similar examples include:

    diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html index ef966f8bb9..b83c1b0f50 100644 --- a/examples/slider/slider-temperature.html +++ b/examples/slider/slider-temperature.html @@ -42,7 +42,9 @@

    Vertical Temperature Slider Example

    Similar examples include: