Skip to content

Commit

Permalink
Radio Group Examples: Implement latest APG coding practices (pull #1878)
Browse files Browse the repository at this point in the history
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 <a11yThinker@gmail.com>
  • Loading branch information
jongund and mcking65 authored Sep 14, 2021
1 parent 3d5edf6 commit 17ae4f2
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 302 deletions.
78 changes: 13 additions & 65 deletions examples/radio/css/radio.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
243 changes: 120 additions & 123 deletions examples/radio/js/radio-activedescendant.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
});
Loading

0 comments on commit 17ae4f2

Please sign in to comment.