From d063b4b89b766050b13b2660ebf5e3d72ff46074 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Thu, 22 Apr 2021 20:53:22 -0500 Subject: [PATCH 01/25] an initial draft of using radio buttons for a rating widget --- examples/index.html | 5 + examples/radio/css/radio-rating.css | 72 ++++++ examples/radio/js/radio-rating.js | 139 +++++++++++ examples/radio/radio-rating.html | 288 +++++++++++++++++++++++ test/tests/radio_radio-rating.js | 346 ++++++++++++++++++++++++++++ 5 files changed, 850 insertions(+) create mode 100644 examples/radio/css/radio-rating.css create mode 100644 examples/radio/js/radio-rating.js create mode 100644 examples/radio/radio-rating.html create mode 100644 test/tests/radio_radio-rating.js diff --git a/examples/index.html b/examples/index.html index 13eb4991ed..462d6c4020 100644 --- a/examples/index.html +++ b/examples/index.html @@ -256,6 +256,7 @@

Examples by Role

@@ -266,6 +267,7 @@

Examples by Role

@@ -433,6 +435,7 @@

Examples By Properties and States

  • Checkbox (Mixed-State)
  • Editor Menubar
  • Radio Group Using aria-activedescendant
  • +
  • Rating Radio Group
  • Radio Group Using Roving tabindex
  • Toolbar
  • @@ -586,6 +589,7 @@

    Examples By Properties and States

  • Editor Menubar
  • Navigation Menubar
  • Radio Group Using aria-activedescendant
  • +
  • Rating Radio Group
  • Radio Group Using Roving tabindex
  • Color Viewer Slider
  • Date Picker Spin Button
  • @@ -623,6 +627,7 @@

    Examples By Properties and States

  • Navigation Menu Button
  • Navigation Menubar
  • Radio Group Using aria-activedescendant
  • +
  • Rating Radio Group
  • Radio Group Using Roving tabindex
  • Color Viewer Slider
  • Date Picker Spin Button
  • diff --git a/examples/radio/css/radio-rating.css b/examples/radio/css/radio-rating.css new file mode 100644 index 0000000000..5014925986 --- /dev/null +++ b/examples/radio/css/radio-rating.css @@ -0,0 +1,72 @@ +/* CSS Document */ + +.rating-radio label { + display: block; +} + +.rating-radio { + color: #005a9c; +} + +.rating-radio svg { + forced-color-adjust: auto; +} + +.rating-radio svg .focus-ring { + fill: #eee; + stroke-width: 0; + fill-opacity: 0; +} + +.rating-radio svg .star { + stroke-width: 2px; + stroke: currentColor; + fill-opacity: 0; +} + +.rating-radio svg .fill-left, +.rating-radio svg .fill-right { + stroke-width: 0; + fill-opacity: 0; +} + +.rating-radio[data-rating-value="5"] svg .star { + fill: currentColor; + fill-opacity: 1; +} + +.rating-radio svg .star-none { + stroke-opacity: 0; + fill-opacity: 0; +} + +.rating-radio[data-rating-value="1"] svg .star-1 .star { + fill: currentColor; + fill-opacity: 1; +} + +.rating-radio[data-rating-value="2"] svg .star-2 .star { + fill: currentColor; + fill-opacity: 1; +} + +.rating-radio[data-rating-value="3"] svg .star-3 .star { + fill: currentColor; + fill-opacity: 1; +} + +.rating-radio[data-rating-value="4"] svg .star-4 .star { + fill: currentColor; + fill-opacity: 1; +} + +/* focus styling */ + +.rating-radio:focus { + outline: none; +} + +.rating-radio svg g:focus .focus-ring { + stroke-width: 2px; + stroke: currentColor; +} diff --git a/examples/radio/js/radio-rating.js b/examples/radio/js/radio-rating.js new file mode 100644 index 0000000000..b379453d82 --- /dev/null +++ b/examples/radio/js/radio-rating.js @@ -0,0 +1,139 @@ +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: radio-rating.js + * + * Desc: Radio group widget that implements ARIA Authoring Practices + */ + +'use strict'; + +class RatingRadioGroup { + constructor(groupNode) { + this.groupNode = groupNode; + + this.radioButtons = []; + + this.firstRadioButton = null; + this.lastRadioButton = null; + + var rbs = this.groupNode.querySelectorAll('[role=radio]'); + + for (var i = 0; i < rbs.length; i++) { + var rb = rbs[i]; + + 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)); + + this.radioButtons.push(rb); + + if (!this.firstRadioButton) { + this.firstRadioButton = rb; + } + this.lastRadioButton = rb; + } + this.firstRadioButton.tabIndex = 0; + } + + setChecked(currentItem) { + for (var i = 0; i < this.radioButtons.length; i++) { + var rb = this.radioButtons[i]; + rb.setAttribute('aria-checked', 'false'); + rb.tabIndex = -1; + } + var value = currentItem.getAttribute('data-rating'); + this.groupNode.setAttribute('data-rating-value', value); + + currentItem.setAttribute('aria-checked', 'true'); + currentItem.tabIndex = 0; + currentItem.focus(); + } + + setCheckedToPreviousItem(currentItem) { + var index; + + if (currentItem === this.firstRadioButton) { + this.setChecked(this.lastRadioButton); + } 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]); + } + } + + /* 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(); + } + } + + handleClick(event) { + this.setChecked(event.currentTarget); + } + + handleFocus(event) { + event.currentTarget.classList.add('focus'); + } + + handleBlur(event) { + event.currentTarget.classList.remove('focus'); + } +} + +// Initialize radio button group + +window.addEventListener('load', function () { + var rrgs = document.querySelectorAll('.rating-radio'); + for (var i = 0; i < rrgs.length; i++) { + new RatingRadioGroup(rrgs[i]); + } +}); diff --git a/examples/radio/radio-rating.html b/examples/radio/radio-rating.html new file mode 100644 index 0000000000..9b370cebfb --- /dev/null +++ b/examples/radio/radio-rating.html @@ -0,0 +1,288 @@ + + + + + + Rating Radio Group Example | WAI-ARIA Authoring Practices 1.2 + + + + + + + + + + + + + + +
    +

    Rating Radio Group Example

    + +

    + Following is an example of a rating input that demonstrates the + Radio Group Design Pattern. + Rating level is indicated by the number of stars selected by the user. +

    +

    Similar examples include:

    + +
    +
    +

    Example

    +
    + +
    +
    +
    Rating
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    +

    Accessibility Features

    +
      +
    • + To highlight the interactive nature of the rating stars a focus ring appears around the star that has focus. If no star is selected the focus ring is around all the stars. +
    • +
    • + To ensure the stars have sufficient contrast with the background when high contrast settings invert colors, the CSS currentColor value for the stroke property is used for the SVG rect elements to synchronize the border color with text content. + If specific colors were used to specify the stroke property, the color of these elements would remain the same in high contrast mode, which could lead to insufficient contrast between them and their background or even make them invisible if their color were to match 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 and polygon 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

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyFunction
    Tab +
      +
    • Moves focus to the checked radio button in the radiogroup.
    • +
    • If a radio button is not checked, focus moves to the first radio button in the group.
    • +
    +
    Space +
      +
    • If the radio button with focus is not checked, changes the state to checked.
    • +
    • Otherwise, does nothing.
    • +
    • Note: The state where a radio is not checked only occurs on page load.
    • +
    +
    Down arrow
    Right arrow
    +
      +
    • Moves focus to and checks the next radio button in the group.
    • +
    • If focus is on the last radio button, moves focus to the first radio button.
    • +
    • The state of the previously checked radio button is changed to unchecked.
    • +
    +
    Up arrow
    Left arrow
    +
      +
    • Moves focus to and checks the previous radio button in the group.
    • +
    • If focus is on the first radio button, moves focus to and checks the last radio button.
    • +
    • The state of the previously checked radio button is changed to unchecked.
    • +
    +
    +
    + +
    +

    Role, Property, State, and Tabindex Attributes

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributesElementUsage
    radiogroupdiv +
      +
    • Identifies the div element as a container for a group of radio buttons.
    • +
    • Is not focusable because focus is managed using a roving tabindex strategy as described below.
    • +
    +
    aria-labelledby="[IDREF]"divRefers to the element that contains the label of the radio group.
    radiodiv +
      +
    • Identifies the div element as an ARIA radio button.
    • +
    • The accessible name is computed from the child text content of the div element.
    • +
    +
    tabindex="-1"div +
      +
    • Makes the element focusable but not part of the page Tab sequence.
    • +
    • Applied to all radio buttons contained in the radio group except for one that is included in the page Tab sequence.
    • +
    • This approach to managing focus is described in the section on roving tabindex.
    • +
    +
    tabindex="0"div +
      +
    • Makes the radio button focusable and includes it in the page Tab sequence.
    • +
    • Set on only one radio in the radio group.
    • +
    • On page load, is set on the first radio button in the radio group.
    • +
    • Moves with focus inside the radio group so the most recently focused radio button is included in the page Tab sequence.
    • +
    • This approach to managing focus is described in the section on roving tabindex.
    • +
    +
    aria-checked="false"div +
      +
    • Identifies radio buttons which are not checked.
    • +
    • CSS attribute selectors (e.g. [aria-checked="false"]) are used to synchronize the visual states with the value of the aria-checked attribute.
    • +
    • The CSS ::before pseudo-element is used to indicate visual state of unchecked radio buttons to support high contrast settings in operating systems and browsers.
    • +
    +
    aria-checked="true"div +
      +
    • Identifies the radio button which is checked.
    • +
    • CSS attribute selectors (e.g. [aria-checked="true"]) are used to synchronize the visual states with the value of the aria-checked attribute.
    • +
    • The CSS ::before pseudo-element is used to indicate visual state of checked radio buttons to support high contrast settings in operating systems and browsers.
    • +
    +
    +
    + + +
    +

    Javascript and CSS Source Code

    + +
    + +
    +

    HTML Source Code

    + +
    + + +
    + +
    + + + diff --git a/test/tests/radio_radio-rating.js b/test/tests/radio_radio-rating.js new file mode 100644 index 0000000000..1c4eb1d303 --- /dev/null +++ b/test/tests/radio_radio-rating.js @@ -0,0 +1,346 @@ +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 assertRovingTabindex = require('../util/assertRovingTabindex'); +const assertTabOrder = require('../util/assertTabOrder'); + +const exampleFile = 'radio/radio.html'; + +const ex = { + radiogroupSelector: '#ex1 [role="radiogroup"]', + radioSelector: '#ex1 [role="radio"]', + innerRadioSelector: '[role="radio"]', + crustRadioSelector: '#ex1 [role="radiogroup"]:nth-of-type(1) [role="radio"]', + deliveryRadioSelector: + '#ex1 [role="radiogroup"]:nth-of-type(2) [role="radio"]', +}; + +const checkFocus = async function (t, selector, index) { + return t.context.session.executeScript( + function () { + const [selector, index] = arguments; + const items = document.querySelectorAll(selector); + return items[index] === document.activeElement; + }, + selector, + index + ); +}; + +// Attributes + +ariaTest( + 'role="radiogroup" on div element', + exampleFile, + 'radiogroup-role', + async (t) => { + await assertAriaRoles(t, 'ex1', 'radiogroup', '2', 'div'); + } +); + +ariaTest( + '"aria-labelledby" attribute on radiogroup', + exampleFile, + 'radiogroup-aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.radiogroupSelector); + } +); + +ariaTest( + 'role="radio" on div elements', + exampleFile, + 'radio-role', + async (t) => { + await assertAriaRoles(t, 'ex1', 'radio', '6', 'div'); + } +); + +ariaTest( + 'roving tabindex on radio elements', + exampleFile, + 'radio-tabindex', + async (t) => { + // Test first radio group + let radios = + ex.radiogroupSelector + ':nth-of-type(1) ' + ex.innerRadioSelector; + await assertRovingTabindex(t, radios, Key.ARROW_RIGHT); + + // Test second radio group + radios = ex.radiogroupSelector + ':nth-of-type(2) ' + ex.innerRadioSelector; + await assertRovingTabindex(t, radios, Key.ARROW_RIGHT); + } +); + +ariaTest( + '"aria-checked" set on role="radio"', + exampleFile, + 'radio-aria-checked', + async (t) => { + // The radio groups will be all unchecked on page load + await assertAttributeValues(t, ex.radioSelector, 'aria-checked', 'false'); + + const radiogroups = await t.context.queryElements(t, ex.radiogroupSelector); + for (let radiogroup of radiogroups) { + let radios = await t.context.queryElements( + t, + ex.innerRadioSelector, + radiogroup + ); + + for (let checked = 0; checked < radios.length; checked++) { + await radios[checked].click(); + for (let el = 0; el < radios.length; el++) { + // test only one element has aria-checked="true" + let isChecked = el === checked ? 'true' : 'false'; + t.is( + await radios[el].getAttribute('aria-checked'), + isChecked, + 'Tab at index ' + + checked + + ' is checked, therefore, tab at index ' + + el + + ' should have aria-checked="' + + checked + + '"' + ); + } + } + } + } +); + +// Keys + +ariaTest( + 'Moves focus to first or checked item', + exampleFile, + 'key-tab', + async (t) => { + // On page load, the first item in the radio list should be in tab index + await assertTabOrder(t, [ + ex.crustRadioSelector + ':nth-of-type(1)', + ex.deliveryRadioSelector + ':nth-of-type(1)', + ]); + + // Click the second item in each radio list, to set roving tab index + await t.context.session + .findElement(By.css(ex.crustRadioSelector + ':nth-of-type(2)')) + .click(); + await t.context.session + .findElement(By.css(ex.deliveryRadioSelector + ':nth-of-type(2)')) + .click(); + + // Now the second radio item in each list should be selected + await assertTabOrder(t, [ + ex.crustRadioSelector + ':nth-of-type(2)', + ex.deliveryRadioSelector + ':nth-of-type(2)', + ]); + } +); + +ariaTest('Selects radio item', exampleFile, 'key-space', async (t) => { + const firstCrustSelector = ex.crustRadioSelector + ':nth-of-type(1)'; + await t.context.session.findElement(By.css(firstCrustSelector)).sendKeys(' '); + await assertAttributeValues(t, firstCrustSelector, 'aria-checked', 'true'); + + const firstDeliverySelector = ex.deliveryRadioSelector + ':nth-of-type(1)'; + await t.context.session + .findElement(By.css(firstDeliverySelector)) + .sendKeys(' '); + await assertAttributeValues(t, firstDeliverySelector, 'aria-checked', 'true'); +}); + +ariaTest( + 'RIGHT ARROW changes focus and checks radio', + exampleFile, + 'key-down-right-arrow', + async (t) => { + let radios = await t.context.queryElements(t, ex.crustRadioSelector); + + // Right arrow moves focus to right + for (let index = 0; index < radios.length - 1; index++) { + await radios[index].sendKeys(Key.ARROW_RIGHT); + const newIndex = index + 1; + + t.true( + await checkFocus(t, ex.crustRadioSelector, newIndex), + 'Focus should be on radio at index ' + + newIndex + + ' after ARROW_RIGHT to radio' + + ' at index ' + + index + ); + t.is( + await radios[newIndex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newIndex + + ' should be checked after ARROW_RIGHT to radio' + + ' at index ' + + index + ); + } + + // Right arrow should move focus from last item to first + await radios[radios.length - 1].sendKeys(Key.ARROW_RIGHT); + t.true( + await checkFocus(t, ex.crustRadioSelector, 0), + 'Focus should be on radio at index 0 after ARROW_RIGHT to radio at index ' + + (radios.length - 1) + ); + t.is( + await radios[0].getAttribute('aria-checked'), + 'true', + 'Radio at index 0 should be checked after ARROW_RIGHT to radio at index ' + + (radios.length - 1) + ); + } +); + +ariaTest( + 'DOWN ARROW changes focus and checks radio', + exampleFile, + 'key-down-right-arrow', + async (t) => { + let radios = await t.context.queryElements(t, ex.crustRadioSelector); + + // Down arrow moves focus to down + for (let index = 0; index < radios.length - 1; index++) { + await radios[index].sendKeys(Key.ARROW_DOWN); + const newIndex = index + 1; + + t.true( + await checkFocus(t, ex.crustRadioSelector, newIndex), + 'Focus should be on radio at index ' + + newIndex + + ' after ARROW_DOWN to radio' + + ' at index ' + + index + ); + t.is( + await radios[newIndex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newIndex + + ' should be checked after ARROW_DOWN to radio' + + ' at index ' + + index + ); + } + + // Down arrow should move focus from last item to first + await radios[radios.length - 1].sendKeys(Key.ARROW_DOWN); + t.true( + await checkFocus(t, ex.crustRadioSelector, 0), + 'Focus should be on radio at index 0 after ARROW_DOWN to radio at index ' + + (radios.length - 1) + ); + t.is( + await radios[0].getAttribute('aria-checked'), + 'true', + 'Radio at index 0 should be checked after ARROW_DOWN to radio at index ' + + (radios.length - 1) + ); + } +); + +ariaTest( + 'LEFT ARROW changes focus and checks radio', + exampleFile, + 'key-up-left-arrow', + async (t) => { + let radios = await t.context.queryElements(t, ex.crustRadioSelector); + + // Left arrow should move focus from first item to last, then progressively left + await radios[0].sendKeys(Key.ARROW_LEFT); + + t.true( + await checkFocus(t, ex.crustRadioSelector, radios.length - 1), + 'Focus should be on radio at index ' + + (radios.length - 1) + + ' after ARROW_LEFT to radio at index 0' + ); + t.is( + await radios[radios.length - 1].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + (radios.length - 1) + + ' should be checked after ARROW_LEFT to radio at index 0' + ); + + for (let index = radios.length - 1; index > 0; index--) { + await radios[index].sendKeys(Key.ARROW_LEFT); + const newIndex = index - 1; + + t.true( + await checkFocus(t, ex.crustRadioSelector, newIndex), + 'Focus should be on radio at index ' + + newIndex + + ' after ARROW_LEFT to radio' + + ' at index ' + + index + ); + t.is( + await radios[newIndex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newIndex + + ' should be checked after ARROW_LEFT to radio' + + ' at index ' + + index + ); + } + } +); + +ariaTest( + 'UP ARROW changes focus and checks radio', + exampleFile, + 'key-up-left-arrow', + async (t) => { + let radios = await t.context.queryElements(t, ex.crustRadioSelector); + + // Up arrow should move focus from first item to last, then progressively up + await radios[0].sendKeys(Key.ARROW_UP); + + t.true( + await checkFocus(t, ex.crustRadioSelector, radios.length - 1), + 'Focus should be on radio at index ' + + (radios.length - 1) + + ' after ARROW_UP to radio at index 0' + ); + t.is( + await radios[radios.length - 1].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + (radios.length - 1) + + ' should be checked after ARROW_UP to radio at index 0' + ); + + for (let index = radios.length - 1; index > 0; index--) { + await radios[index].sendKeys(Key.ARROW_UP); + const newIndex = index - 1; + + t.true( + await checkFocus(t, ex.crustRadioSelector, newIndex), + 'Focus should be on radio at index ' + + newIndex + + ' after ARROW_UP to radio' + + ' at index ' + + index + ); + t.is( + await radios[newIndex].getAttribute('aria-checked'), + 'true', + 'Radio at index ' + + newIndex + + ' should be checked after ARROW_UP to radio' + + ' at index ' + + index + ); + } + } +); From 0dbfcbad44ba5a9d8a3e2c5dfb9476043109e256 Mon Sep 17 00:00:00 2001 From: Jon Gunderson Date: Fri, 23 Apr 2021 14:46:02 -0500 Subject: [PATCH 02/25] added draft of radio group used for a rating --- examples/radio/css/radio-rating.css | 26 +++++++--- examples/radio/js/radio-rating.js | 22 ++++---- examples/radio/radio-rating.html | 78 ++++++++++++++++++++--------- test/tests/radio_radio-rating.js | 57 +++++++++++---------- 4 files changed, 111 insertions(+), 72 deletions(-) diff --git a/examples/radio/css/radio-rating.css b/examples/radio/css/radio-rating.css index 5014925986..611b14d264 100644 --- a/examples/radio/css/radio-rating.css +++ b/examples/radio/css/radio-rating.css @@ -4,7 +4,7 @@ display: block; } -.rating-radio { +.rating-radio svg g[role="radio"] { color: #005a9c; } @@ -12,8 +12,8 @@ forced-color-adjust: auto; } -.rating-radio svg .focus-ring { - fill: #eee; +.rating-radio svg .focus-ring, +.rating-radio svg .focus-ring-none { stroke-width: 0; fill-opacity: 0; } @@ -24,6 +24,12 @@ fill-opacity: 0; } +.rating-radio svg .star-none { + stroke-width: 3px; + stroke: currentColor; + fill-opacity: 0; +} + .rating-radio svg .fill-left, .rating-radio svg .fill-right { stroke-width: 0; @@ -35,11 +41,6 @@ fill-opacity: 1; } -.rating-radio svg .star-none { - stroke-opacity: 0; - fill-opacity: 0; -} - .rating-radio[data-rating-value="1"] svg .star-1 .star { fill: currentColor; fill-opacity: 1; @@ -66,7 +67,16 @@ outline: none; } +.rating-radio svg g:focus { + outline: none; +} + .rating-radio svg g:focus .focus-ring { stroke-width: 2px; stroke: currentColor; } + +.rating-radio:focus svg .focus-ring-none { + stroke-width: 2px; + stroke: currentColor; +} diff --git a/examples/radio/js/radio-rating.js b/examples/radio/js/radio-rating.js index b379453d82..bb4ce700a5 100644 --- a/examples/radio/js/radio-rating.js +++ b/examples/radio/js/radio-rating.js @@ -28,8 +28,6 @@ class RatingRadioGroup { 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); @@ -38,10 +36,20 @@ class RatingRadioGroup { } this.lastRadioButton = rb; } - this.firstRadioButton.tabIndex = 0; + var value = groupNode.getAttribute('data-rating-value'); + var index = parseInt(value); + + if (value && index >= 0 && index < this.radioButtons.length) { + this.radioButtons[index].tabIndex = 0; + } else { + value = this.firstRadioButton.getAttribute('data-rating-value'); + groupNode.getAttribute('data-rating-value', value); + this.firstRadioButton.tabIndex = 0; + } } setChecked(currentItem) { + this.groupNode.tabIndex = -1; for (var i = 0; i < this.radioButtons.length; i++) { var rb = this.radioButtons[i]; rb.setAttribute('aria-checked', 'false'); @@ -119,14 +127,6 @@ class RatingRadioGroup { handleClick(event) { this.setChecked(event.currentTarget); } - - handleFocus(event) { - event.currentTarget.classList.add('focus'); - } - - handleBlur(event) { - event.currentTarget.classList.remove('focus'); - } } // Initialize radio button group diff --git a/examples/radio/radio-rating.html b/examples/radio/radio-rating.html index 9b370cebfb..c76d4bc7ee 100644 --- a/examples/radio/radio-rating.html +++ b/examples/radio/radio-rating.html @@ -35,7 +35,6 @@

    Rating Radio Group Example

    Similar examples include: