From 17ae4f2670ce0c39788301db6c418acc6b1e93d4 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Tue, 14 Sep 2021 12:28:42 -0500 Subject: [PATCH] Radio Group Examples: Implement latest APG coding practices (pull #1878) Update radio group examples: 1. Implement latest APG coding practices. 2. Simplify CSS by using inline SVG images in the content property. 3. Updated accessibility documentation to include using currentColor and forced-color-adjust with SVG to support high contrast modes of operating systems. Co-authored-by: Matt King --- examples/radio/css/radio.css | 78 ++----- examples/radio/js/radio-activedescendant.js | 243 ++++++++++---------- examples/radio/js/radio.js | 197 ++++++++-------- examples/radio/radio-activedescendant.html | 24 +- examples/radio/radio.html | 24 +- 5 files changed, 264 insertions(+), 302 deletions(-) diff --git a/examples/radio/css/radio.css b/examples/radio/css/radio.css index c8292223e3..00d3eaaa05 100644 --- a/examples/radio/css/radio.css +++ b/examples/radio/css/radio.css @@ -9,88 +9,36 @@ } [role="radio"] { - padding: 4px; - padding-left: 30px; - padding-right: 8px; + padding: 4px 8px 4px 8px; border: 0 solid transparent; border-radius: 5px; display: inline-block; position: relative; cursor: default; outline: none; + color: black; } [role="radio"] + [role="radio"] { margin-left: 1em; } -[role="radio"]::before, -[role="radio"]::after { - position: absolute; - top: 50%; - left: 11px; - transform: translate(-20%, -50%); - content: ""; -} - [role="radio"]::before { - width: 14px; - height: 14px; - border: 2px solid hsl(0, 0%, 40%); - border-radius: 100%; + position: relative; + top: 1px; + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='14' width='14' style='forced-color-adjust: auto;'%3E%3Ccircle cx='7' cy='7' r='6' stroke='rgb(0, 90, 156)' stroke-width='2' fill-opacity='0' /%3E%3C/svg%3E"); } [role="radio"][aria-checked="true"]::before { - border-color: hsl(216, 80%, 50%); - background: hsl(217, 95%, 68%); - background-image: linear-gradient( - to bottom, - hsl(217, 95%, 68%), - hsl(216, 80%, 57%) - ); -} - -[role="radio"][aria-checked="true"]::after { - display: block; - border: 4px solid #fff; - border-radius: 100%; - transform: translate(20%, -50%); -} - -[role="radio"][aria-checked="true"]:active::before { - background-image: linear-gradient( - to bottom, - hsl(216, 80%, 57%), - hsl(217, 95%, 68%) 60% - ); -} - -[role="radio"]:hover::before { - border-color: hsl(216, 94%, 65%); -} - -[role="radio"].focus { - padding: 2px; - padding-left: 28px; - padding-right: 6px; - border: 2px solid hsl(216, 94%, 73%); - background-color: hsl(216, 80%, 97%); + position: relative; + top: 1px; + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='14' width='14' style='forced-color-adjust: auto;'%3E%3Ccircle cx='7' cy='7' r='6' stroke='rgb(0, 90, 156)' stroke-width='2' fill-opacity='0' /%3E%3Ccircle cx='7' cy='7' r='3' fill='rgb(0, 90, 156)' stroke-opacity='0' /%3E%3C/svg%3E"); } +[role="radio"].focus, [role="radio"]:hover { - padding: 2px; - padding-left: 28px; - padding-right: 6px; - border: 2px solid hsl(216, 94%, 73%); - background-color: hsl(216, 80%, 92%); -} - -[role="radio"].focus::before, -[role="radio"]:hover::before { - left: 9px; -} - -[role="radio"][aria-checked="true"].focus::after, -[role="radio"][aria-checked="true"]:hover::after { - transform: translate(-6%, -50%); + padding: 2px 6px 2px 6px; + border: 2px solid #005a9c; + background-color: #def; + cursor: pointer; } diff --git a/examples/radio/js/radio-activedescendant.js b/examples/radio/js/radio-activedescendant.js index 0ba8c997c9..18dd31e489 100644 --- a/examples/radio/js/radio-activedescendant.js +++ b/examples/radio/js/radio-activedescendant.js @@ -9,155 +9,152 @@ 'use strict'; -var RadioGroupActiveDescendant = function (groupNode) { - this.groupNode = groupNode; +class RadioGroupActiveDescendant { + constructor(groupNode) { + this.groupNode = groupNode; - this.radioButtons = []; + this.radioButtons = []; - this.firstRadioButton = null; - this.lastRadioButton = null; + this.firstRadioButton = null; + this.lastRadioButton = null; - this.groupNode.addEventListener('keydown', this.handleKeydown.bind(this)); - this.groupNode.addEventListener('focus', this.handleFocus.bind(this)); - this.groupNode.addEventListener('blur', this.handleBlur.bind(this)); + this.groupNode.addEventListener('keydown', this.handleKeydown.bind(this)); + this.groupNode.addEventListener('focus', this.handleFocus.bind(this)); + this.groupNode.addEventListener('blur', this.handleBlur.bind(this)); - // initialize - if (!this.groupNode.getAttribute('role')) { - this.groupNode.setAttribute('role', 'radiogroup'); - } + // initialize + if (!this.groupNode.getAttribute('role')) { + this.groupNode.setAttribute('role', 'radiogroup'); + } - var rbs = this.groupNode.querySelectorAll('[role=radio]'); + var rbs = this.groupNode.querySelectorAll('[role=radio]'); - for (var i = 0; i < rbs.length; i++) { - var rb = rbs[i]; - rb.addEventListener('click', this.handleClick.bind(this)); - this.radioButtons.push(rb); - if (!this.firstRadioButton) { - this.firstRadioButton = rb; + for (var i = 0; i < rbs.length; i++) { + var rb = rbs[i]; + rb.addEventListener('click', this.handleClick.bind(this)); + this.radioButtons.push(rb); + if (!this.firstRadioButton) { + this.firstRadioButton = rb; + } + this.lastRadioButton = rb; } - this.lastRadioButton = rb; + this.groupNode.tabIndex = 0; } - this.groupNode.tabIndex = 0; -}; - -RadioGroupActiveDescendant.prototype.setChecked = function (currentItem) { - for (var i = 0; i < this.radioButtons.length; i++) { - var rb = this.radioButtons[i]; - rb.setAttribute('aria-checked', 'false'); - rb.classList.remove('focus'); + + setChecked(currentItem) { + for (var i = 0; i < this.radioButtons.length; i++) { + var rb = this.radioButtons[i]; + rb.setAttribute('aria-checked', 'false'); + rb.classList.remove('focus'); + } + currentItem.setAttribute('aria-checked', 'true'); + currentItem.classList.add('focus'); + this.groupNode.setAttribute('aria-activedescendant', currentItem.id); + this.groupNode.focus(); } - currentItem.setAttribute('aria-checked', 'true'); - currentItem.classList.add('focus'); - this.groupNode.setAttribute('aria-activedescendant', currentItem.id); - this.groupNode.focus(); -}; - -RadioGroupActiveDescendant.prototype.setCheckedToPreviousItem = function ( - currentItem -) { - var index; - - if (currentItem === this.firstRadioButton) { - this.setChecked(this.lastRadioButton); - } else { - index = this.radioButtons.indexOf(currentItem); - this.setChecked(this.radioButtons[index - 1]); + + setCheckedToPreviousItem(currentItem) { + var index; + + if (currentItem === this.firstRadioButton) { + this.setChecked(this.lastRadioButton); + } else { + index = this.radioButtons.indexOf(currentItem); + this.setChecked(this.radioButtons[index - 1]); + } } -}; - -RadioGroupActiveDescendant.prototype.setCheckedToNextItem = function ( - currentItem -) { - var index; - - if (currentItem === this.lastRadioButton) { - this.setChecked(this.firstRadioButton); - } else { - index = this.radioButtons.indexOf(currentItem); - this.setChecked(this.radioButtons[index + 1]); + + setCheckedToNextItem(currentItem) { + var index; + + if (currentItem === this.lastRadioButton) { + this.setChecked(this.firstRadioButton); + } else { + index = this.radioButtons.indexOf(currentItem); + this.setChecked(this.radioButtons[index + 1]); + } } -}; -RadioGroupActiveDescendant.prototype.getCurrentRadioButton = function () { - var id = this.groupNode.getAttribute('aria-activedescendant'); - if (!id) { + getCurrentRadioButton() { + var id = this.groupNode.getAttribute('aria-activedescendant'); + if (!id) { + this.groupNode.setAttribute( + 'aria-activedescendant', + this.firstRadioButton.id + ); + return this.firstRadioButton; + } + for (var i = 0; i < this.radioButtons.length; i++) { + var rb = this.radioButtons[i]; + if (rb.id === id) { + return rb; + } + } this.groupNode.setAttribute( 'aria-activedescendant', this.firstRadioButton.id ); return this.firstRadioButton; } - for (var i = 0; i < this.radioButtons.length; i++) { - var rb = this.radioButtons[i]; - if (rb.id === id) { - return rb; + + // Event Handlers + + handleKeydown(event) { + var flag = false; + + var currentItem = this.getCurrentRadioButton(); + switch (event.key) { + case ' ': + case 'Enter': + this.setChecked(currentItem); + flag = true; + break; + + case 'Up': + case 'ArrowUp': + case 'Left': + case 'ArrowLeft': + this.setCheckedToPreviousItem(currentItem); + flag = true; + break; + + case 'Down': + case 'ArrowDown': + case 'Right': + case 'ArrowRight': + this.setCheckedToNextItem(currentItem); + flag = true; + break; + + default: + break; } - } - this.groupNode.setAttribute( - 'aria-activedescendant', - this.firstRadioButton.id - ); - return this.firstRadioButton; -}; - -// Event Handlers - -RadioGroupActiveDescendant.prototype.handleKeydown = function (event) { - var flag = false; - - var currentItem = this.getCurrentRadioButton(); - switch (event.key) { - case ' ': - case 'Enter': - this.setChecked(currentItem); - flag = true; - break; - - case 'Up': - case 'ArrowUp': - case 'Left': - case 'ArrowLeft': - this.setCheckedToPreviousItem(currentItem); - flag = true; - break; - - case 'Down': - case 'ArrowDown': - case 'Right': - case 'ArrowRight': - this.setCheckedToNextItem(currentItem); - flag = true; - break; - - default: - break; - } - if (flag) { - event.stopPropagation(); - event.preventDefault(); + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } } -}; -RadioGroupActiveDescendant.prototype.handleClick = function (event) { - this.setChecked(event.currentTarget); -}; + handleClick(event) { + this.setChecked(event.currentTarget); + } -RadioGroupActiveDescendant.prototype.handleFocus = function () { - var currentItem = this.getCurrentRadioButton(); - currentItem.classList.add('focus'); -}; + handleFocus() { + var currentItem = this.getCurrentRadioButton(); + currentItem.classList.add('focus'); + } -RadioGroupActiveDescendant.prototype.handleBlur = function () { - var currentItem = this.getCurrentRadioButton(); - currentItem.classList.remove('focus'); -}; + handleBlur() { + var currentItem = this.getCurrentRadioButton(); + currentItem.classList.remove('focus'); + } +} // Initialize radio button group using aria-activedescendant - window.addEventListener('load', function () { - var rgs = document.querySelectorAll('.radiogroup-activedescendant'); - for (var i = 0; i < rgs.length; i++) { - new RadioGroupActiveDescendant(rgs[i]); + var radios = document.querySelectorAll('.radiogroup-activedescendant'); + for (var i = 0; i < radios.length; i++) { + new RadioGroupActiveDescendant(radios[i]); } }); diff --git a/examples/radio/js/radio.js b/examples/radio/js/radio.js index ddf887df69..c096a48126 100644 --- a/examples/radio/js/radio.js +++ b/examples/radio/js/radio.js @@ -9,126 +9,127 @@ 'use strict'; -var RadioGroup = function (groupNode) { - this.groupNode = groupNode; +class RadioGroup { + constructor(groupNode) { + this.groupNode = groupNode; - this.radioButtons = []; + this.radioButtons = []; - this.firstRadioButton = null; - this.lastRadioButton = null; + this.firstRadioButton = null; + this.lastRadioButton = null; - var rbs = this.groupNode.querySelectorAll('[role=radio]'); + var rbs = this.groupNode.querySelectorAll('[role=radio]'); - for (var i = 0; i < rbs.length; i++) { - var rb = rbs[i]; + for (var i = 0; i < rbs.length; i++) { + var rb = rbs[i]; - rb.tabIndex = -1; - rb.setAttribute('aria-checked', 'false'); + rb.tabIndex = -1; + rb.setAttribute('aria-checked', 'false'); - rb.addEventListener('keydown', this.handleKeydown.bind(this)); - rb.addEventListener('click', this.handleClick.bind(this)); - rb.addEventListener('focus', this.handleFocus.bind(this)); - rb.addEventListener('blur', this.handleBlur.bind(this)); + rb.addEventListener('keydown', this.handleKeydown.bind(this)); + rb.addEventListener('click', this.handleClick.bind(this)); + rb.addEventListener('focus', this.handleFocus.bind(this)); + rb.addEventListener('blur', this.handleBlur.bind(this)); - this.radioButtons.push(rb); + this.radioButtons.push(rb); - if (!this.firstRadioButton) { - this.firstRadioButton = rb; + if (!this.firstRadioButton) { + this.firstRadioButton = rb; + } + this.lastRadioButton = rb; } - this.lastRadioButton = rb; + this.firstRadioButton.tabIndex = 0; } - this.firstRadioButton.tabIndex = 0; -}; - -RadioGroup.prototype.setChecked = function (currentItem) { - for (var i = 0; i < this.radioButtons.length; i++) { - var rb = this.radioButtons[i]; - rb.setAttribute('aria-checked', 'false'); - rb.tabIndex = -1; - } - currentItem.setAttribute('aria-checked', 'true'); - currentItem.tabIndex = 0; - currentItem.focus(); -}; - -RadioGroup.prototype.setCheckedToPreviousItem = function (currentItem) { - var index; - - if (currentItem === this.firstRadioButton) { - this.setChecked(this.lastRadioButton); - } else { - index = this.radioButtons.indexOf(currentItem); - this.setChecked(this.radioButtons[index - 1]); + + setChecked(currentItem) { + for (var i = 0; i < this.radioButtons.length; i++) { + var rb = this.radioButtons[i]; + rb.setAttribute('aria-checked', 'false'); + rb.tabIndex = -1; + } + currentItem.setAttribute('aria-checked', 'true'); + currentItem.tabIndex = 0; + currentItem.focus(); } -}; -RadioGroup.prototype.setCheckedToNextItem = function (currentItem) { - var index; + setCheckedToPreviousItem(currentItem) { + var index; - if (currentItem === this.lastRadioButton) { - this.setChecked(this.firstRadioButton); - } else { - index = this.radioButtons.indexOf(currentItem); - this.setChecked(this.radioButtons[index + 1]); + if (currentItem === this.firstRadioButton) { + this.setChecked(this.lastRadioButton); + } else { + index = this.radioButtons.indexOf(currentItem); + this.setChecked(this.radioButtons[index - 1]); + } } -}; - -/* EVENT HANDLERS */ - -RadioGroup.prototype.handleKeydown = function (event) { - var tgt = event.currentTarget, - flag = false; - - switch (event.key) { - case ' ': - case 'Enter': - this.setChecked(tgt); - flag = true; - break; - - case 'Up': - case 'ArrowUp': - case 'Left': - case 'ArrowLeft': - this.setCheckedToPreviousItem(tgt); - flag = true; - break; - - case 'Down': - case 'ArrowDown': - case 'Right': - case 'ArrowRight': - this.setCheckedToNextItem(tgt); - flag = true; - break; - - default: - break; + + setCheckedToNextItem(currentItem) { + var index; + + if (currentItem === this.lastRadioButton) { + this.setChecked(this.firstRadioButton); + } else { + index = this.radioButtons.indexOf(currentItem); + this.setChecked(this.radioButtons[index + 1]); + } } - if (flag) { - event.stopPropagation(); - event.preventDefault(); + /* EVENT HANDLERS */ + + handleKeydown(event) { + var tgt = event.currentTarget, + flag = false; + + switch (event.key) { + case ' ': + case 'Enter': + this.setChecked(tgt); + flag = true; + break; + + case 'Up': + case 'ArrowUp': + case 'Left': + case 'ArrowLeft': + this.setCheckedToPreviousItem(tgt); + flag = true; + break; + + case 'Down': + case 'ArrowDown': + case 'Right': + case 'ArrowRight': + this.setCheckedToNextItem(tgt); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.stopPropagation(); + event.preventDefault(); + } } -}; -RadioGroup.prototype.handleClick = function (event) { - this.setChecked(event.currentTarget); -}; + handleClick(event) { + this.setChecked(event.currentTarget); + } -RadioGroup.prototype.handleFocus = function (event) { - event.currentTarget.classList.add('focus'); -}; + handleFocus(event) { + event.currentTarget.classList.add('focus'); + } -RadioGroup.prototype.handleBlur = function (event) { - event.currentTarget.classList.remove('focus'); -}; + handleBlur(event) { + event.currentTarget.classList.remove('focus'); + } +} // Initialize radio button group - window.addEventListener('load', function () { - var rgs = document.querySelectorAll('[role="radiogroup"]'); - for (var i = 0; i < rgs.length; i++) { - new RadioGroup(rgs[i]); + var radios = document.querySelectorAll('[role="radiogroup"]'); + for (var i = 0; i < radios.length; i++) { + new RadioGroup(radios[i]); } }); diff --git a/examples/radio/radio-activedescendant.html b/examples/radio/radio-activedescendant.html index 7afb599412..e5a5f0ed88 100644 --- a/examples/radio/radio-activedescendant.html +++ b/examples/radio/radio-activedescendant.html @@ -97,17 +97,25 @@

Pizza Delivery

Accessibility Features

diff --git a/examples/radio/radio.html b/examples/radio/radio.html index 34bb5c0668..7caa7a6d2b 100644 --- a/examples/radio/radio.html +++ b/examples/radio/radio.html @@ -78,17 +78,25 @@

Pizza Delivery

Accessibility Features