diff --git a/ARIA/apg/about/contributing/contributing.md b/ARIA/apg/about/contributing/contributing.md
new file mode 100644
index 000000000..274226480
--- /dev/null
+++ b/ARIA/apg/about/contributing/contributing.md
@@ -0,0 +1,176 @@
+---
+# This file was generated by scripts/pre-build/library/formatForJekyll.js
+title: "Contributing to the APG"
+ref: /ARIA/apg/about/contributing/
+
+github:
+ repository: w3c/aria-practices
+ branch: main
+ path: content/about/contributing/contributing.html
+feedbackmail: public-aria-practices@w3.org
+permalink: /ARIA/apg/about/contributing/
+
+sidebar: true
+
+
+
+# Context here: https://github.com/w3c/wai-aria-practices/issues/31
+type_of_guidance: APG
+
+lang: en
+---
+
+
+
Contributing to the APG
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Introduction
+
+ Continuous improvement to the utility and quality of the ARIA Authoring Practices Guide (APG) depends on broad community input and participation.
+ Anyone may contribute feedback, suggestions, or code via the channels described below.
+ However, if you would like to contribute regularly, please consider
+ joining the Authoring Practices Task Force.
+ As a task force participant, you and your organization have greater influence over APG development, and are listed as contributors where appropriate.
+
+
+
+
+
Providing feedback
+
+ The APG provides two public feedback channels -- GitHub and email.
+ Links to both are provided at the bottom of every page in the APG.
+
+
+ GitHub issues are the best way to ask a question, make a suggestion, or report a problem.
+ Before creating a new issue, it is helpful if you first review existing issues to determine whether there is an issue addressing the same concern.
+ If so, please comment on that issue rather than raising another.
+ If the concern you would like addressed is related to an example implementation of an APG pattern, at the bottom of the page, next to the revision date, there is a link named "Related Issues".
+ That link opens a GitHub project containing all issues associated with the relevant pattern and its examples.
+
+ The task force also uses the aria-practices public mailing list for email discussion.
+ Meeting announcements, agendas, and links to minutes are sent to the mailing list.
+ While GitHub issues are the preferred place to discuss APG content, the mailing list is available to anyone who would prefer to communicate by
+ sending email to the ARIA Authoring Practices public mailing list.
+
+ The APG is developed by the
+ ARIA Authoring Practices Task Force,
+ which is a sub-group of the
+ ARIA Working Group.
+ The task force meets weekly to coordinate work on development of the APG.
+ Task force participants are expected to regularly attend meetings and to actively contribute.
+ Examples of contributions include:
+
+
+
Testing the functionality and accessibility of example implementations of the APG patterns
+
Fixing bugs and implementing enhancements in example implementations of the APG patterns
+
Helping write responses to community feedback
+
Reviewing and editing documentation
+
Designing or engineering pattern implementation examples
+
Creating and editing patterns and practices for ARIA usage
+
+
+ To participate in the Authoring Practices Task Force, you first need to
+ become a participant in the ARIA Working Group.
+ If you are already a participant in the ARIA Working Group, contact
+ Daniel Montalvo
+ and request membership in the Authoring Practices Task Force.
+
To help people with visual impairments identify the combobox as an interactive element, the cursor is changed to a pointer when hovering over the combobox or list.
To make it easier to distinguish the selected listbox option from other options, selection creates a 2 pixel border above and below the option.
- Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from zero to two pixels and padding is reduced by two pixels.
When an element loses focus, its border changes from two pixels to two and padding is increased by two pixels.
diff --git a/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list.md b/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list.md
index acae1d1a4..c0872733f 100644
--- a/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list.md
+++ b/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/combobox/examples/combobox-autocomplete-list/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -212,7 +212,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To help people with visual impairments identify the combobox as an interactive element, the cursor is changed to a pointer when hovering over the combobox or list.
To make it easier to distinguish the selected listbox option from other options, selection creates a 2 pixel border above and below the option.
- Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from zero to two pixels and padding is reduced by two pixels.
When an element loses focus, its border changes from two pixels to two and padding is increased by two pixels.
diff --git a/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-none.md b/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-none.md
index 53dd89354..47409e19d 100644
--- a/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-none.md
+++ b/ARIA/apg/patterns/combobox/examples/combobox-autocomplete-none.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/combobox/examples/combobox-autocomplete-none/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -165,7 +165,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To help people with visual impairments identify the combobox as an interactive element, the cursor is changed to a pointer when hovering over the combobox or list.
To make it easier to distinguish the selected listbox option from other options, selection creates a 2 pixel border above and below the option.
- Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from zero to two pixels and padding is reduced by two pixels.
When an element loses focus, its border changes from two pixels to two and padding is increased by two pixels.
diff --git a/ARIA/apg/patterns/combobox/examples/combobox-datepicker.md b/ARIA/apg/patterns/combobox/examples/combobox-datepicker.md
index fd6bc846d..491116275 100644
--- a/ARIA/apg/patterns/combobox/examples/combobox-datepicker.md
+++ b/ARIA/apg/patterns/combobox/examples/combobox-datepicker.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/combobox/examples/combobox-datepicker/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/combobox/examples/combobox-select-only.md b/ARIA/apg/patterns/combobox/examples/combobox-select-only.md
index 28bbc0b3b..f3213e345 100644
--- a/ARIA/apg/patterns/combobox/examples/combobox-select-only.md
+++ b/ARIA/apg/patterns/combobox/examples/combobox-select-only.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/combobox/examples/combobox-select-only/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/combobox/examples/grid-combo.md b/ARIA/apg/patterns/combobox/examples/grid-combo.md
index b861175a3..7e6377520 100644
--- a/ARIA/apg/patterns/combobox/examples/grid-combo.md
+++ b/ARIA/apg/patterns/combobox/examples/grid-combo.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/combobox/examples/grid-combo/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog.md b/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog.md
index 7342f5ae6..7c38f70f9 100644
--- a/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog.md
+++ b/ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/dialog-modal/examples/datepicker-dialog/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/dialog-modal/examples/dialog.md b/ARIA/apg/patterns/dialog-modal/examples/dialog.md
index be7ebecbd..aee446fcc 100644
--- a/ARIA/apg/patterns/dialog-modal/examples/dialog.md
+++ b/ARIA/apg/patterns/dialog-modal/examples/dialog.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/dialog-modal/examples/dialog/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/disclosure/examples/disclosure-faq.md b/ARIA/apg/patterns/disclosure/examples/disclosure-faq.md
index 28350c46d..5f201761d 100644
--- a/ARIA/apg/patterns/disclosure/examples/disclosure-faq.md
+++ b/ARIA/apg/patterns/disclosure/examples/disclosure-faq.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/disclosure/examples/disclosure-faq/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -177,7 +177,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To help people with visual impairments identify the disclosure as interactive and make it easier to perceive that clicking either the disclosure button or its label changes the expanded state, when a pointer hovers over the button or its label, the background color changes, a border appears, and the cursor changes to a pointer.
- Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from 0 to 2 pixels and padding is reduced by 2 pixels.
When an element loses focus, its border changes from 2 pixels to 0 and padding is increased by 2 pixels.
diff --git a/ARIA/apg/patterns/disclosure/examples/disclosure-image-description.md b/ARIA/apg/patterns/disclosure/examples/disclosure-image-description.md
index a68a522a1..fee070ceb 100644
--- a/ARIA/apg/patterns/disclosure/examples/disclosure-image-description.md
+++ b/ARIA/apg/patterns/disclosure/examples/disclosure-image-description.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/disclosure/examples/disclosure-image-description/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -315,7 +315,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To help people with visual impairments identify the disclosure as interactive and make it easier to perceive that clicking either the disclosure button or its label changes the expanded state, when a pointer hovers over the button or its label, the background color changes, a border appears, and the cursor changes to a pointer.
- Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from 0 to 2 pixels and padding is reduced by 2 pixels.
When an element loses focus, its border changes from 2 pixels to 0 and padding is increased by 2 pixels.
diff --git a/ARIA/apg/patterns/disclosure/examples/disclosure-navigation-hybrid.md b/ARIA/apg/patterns/disclosure/examples/disclosure-navigation-hybrid.md
index 368f6c1d0..41ce85325 100644
--- a/ARIA/apg/patterns/disclosure/examples/disclosure-navigation-hybrid.md
+++ b/ARIA/apg/patterns/disclosure/examples/disclosure-navigation-hybrid.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/disclosure/examples/disclosure-navigation-hybrid/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/disclosure/examples/disclosure-navigation.md b/ARIA/apg/patterns/disclosure/examples/disclosure-navigation.md
index e5494dc94..64283cbc7 100644
--- a/ARIA/apg/patterns/disclosure/examples/disclosure-navigation.md
+++ b/ARIA/apg/patterns/disclosure/examples/disclosure-navigation.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/disclosure/examples/disclosure-navigation/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/feed/examples/feed.md b/ARIA/apg/patterns/feed/examples/feed.md
index 99acd6e8d..82a8776d1 100644
--- a/ARIA/apg/patterns/feed/examples/feed.md
+++ b/ARIA/apg/patterns/feed/examples/feed.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/feed/examples/feed/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/grid/examples/advanced-data-grid.md b/ARIA/apg/patterns/grid/examples/advanced-data-grid.md
index d775b94cb..6b282ddaf 100644
--- a/ARIA/apg/patterns/grid/examples/advanced-data-grid.md
+++ b/ARIA/apg/patterns/grid/examples/advanced-data-grid.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/grid/examples/advanced-data-grid/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/grid/examples/data-grids.md b/ARIA/apg/patterns/grid/examples/data-grids.md
index 93fee6d5f..3dc9a8ab9 100644
--- a/ARIA/apg/patterns/grid/examples/data-grids.md
+++ b/ARIA/apg/patterns/grid/examples/data-grids.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/grid/examples/data-grids/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/grid/examples/layout-grids.md b/ARIA/apg/patterns/grid/examples/layout-grids.md
index 0c92270a5..94bbbc738 100644
--- a/ARIA/apg/patterns/grid/examples/layout-grids.md
+++ b/ARIA/apg/patterns/grid/examples/layout-grids.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/grid/examples/layout-grids/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/link/examples/link.md b/ARIA/apg/patterns/link/examples/link.md
index 3ba88b735..5a55c20a1 100644
--- a/ARIA/apg/patterns/link/examples/link.md
+++ b/ARIA/apg/patterns/link/examples/link.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/link/examples/link/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/listbox/examples/listbox-collapsible.md b/ARIA/apg/patterns/listbox/examples/listbox-collapsible.md
index 91982ea1f..1df9cb988 100644
--- a/ARIA/apg/patterns/listbox/examples/listbox-collapsible.md
+++ b/ARIA/apg/patterns/listbox/examples/listbox-collapsible.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/listbox/examples/listbox-collapsible/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -32,7 +32,6 @@ lang: en
href="../../../../../../content-assets/wai-aria-practices/patterns/listbox/examples/css/listbox.css"
rel="stylesheet"
/>
-
@@ -108,7 +107,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
class="example-page-example-icon"
>
- The following example implementation of the Listbox Pattern demonstrates a collapsible single-select listbox widget that is functionally similar to an HTML select input with the attribute size="1".
+ The following example implementation of the Listbox Pattern demonstrates a collapsible single-select listbox widget that is functionally similar to an HTML select input with the attribute size="1".
The widget consists of a button that triggers the display of a listbox.
In its default state, the widget is collapsed (the listbox is not visible) and the button label shows the currently selected option from the listbox.
When the button is activated, the listbox is displayed and the current option is focused and selected.
@@ -167,24 +166,23 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
Notes
-
This listbox is scrollable; it has more options than its height can accommodate.
-
+
+
+
+
Accessibility Features
+
- Scrolling only works as expected if the listbox is the options' offsetParent.
- The example uses position: relative on the listbox to that effect.
+ Because this listbox implementation is scrollable and manages which option is focused by using aria-activedescendant, the JavaScript must ensure the focused option is visible.
+ So, when a keyboard or pointer event changes the option referenced by aria-activedescendant, if the referenced option is not fully visible, the JavaScript scrolls the listbox to position the option in view.
- When an option is focused that isn't (fully) visible, the listbox's scroll position is updated:
-
-
If Up Arrow or Down Arrow is pressed, the previous or next option is scrolled into view.
-
If Home or End is pressed, the listbox scrolls all the way to the top or to the bottom.
-
If focusItem is called, the focused option will be scrolled to the top of the view if it was located above it or to the bottom if it was below it.
-
If the mouse is clicked on a partially visible option, it will be scrolled fully into view.
-
+ To enhance perceivability when operating the listbox, visual keyboard focus and hover are styled using the CSS :hover and :focus pseudo-classes:
+
+
To help people with visual impairments identify the listbox as an interactive element, the cursor is changed to a pointer when hovering over the list.
+
To make it easier to distinguish the selected listbox option from other options, selection creates a 2 pixel border above and below the option.
+
-
When a fully visible option is focused in any way, no scrolling occurs.
-
+
@@ -193,6 +191,12 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
The example listbox on this page implements the following keyboard interface.
Other variations and options for the keyboard interface are described in the Keyboard Interaction section of the Listbox Pattern.
+
+ NOTE: When visual focus is on an option in this listbox implementation, DOM focus remains on the listbox element and the value of aria-activedescendant on the listbox refers to the descendant option that is visually indicated as focused.
+ Where the following descriptions of keyboard commands mention focus, they are referring to the visual focus indicator, not DOM focus.
+ For more information about this focus management technique, see
+ Managing Focus in Composites Using aria-activedescendant.
+
@@ -201,6 +205,15 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
+
+
Tab
+
+
+
Moves focus into and out of the listbox.
+
If the listbox is expanded, selects the focused option, collapses the listbox, and moves focus out of the listbox.
+
+
+
Enter
@@ -271,7 +284,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-labelledby="ID_REF1 ID_REF2"
+
aria-labelledby="ID_REF1 ID_REF2"
button
@@ -283,13 +296,13 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-haspopup="listbox"
+
aria-haspopup="listbox"
button
Indicates that activating the button displays a listbox.
-
aria-expanded="true"
+
aria-expanded="true"
button
@@ -306,13 +319,13 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-labelledby="ID_REF"
+
aria-labelledby="ID_REF"
ul
Refers to the element containing the listbox label.
-
tabindex="-1"
+
tabindex="-1"
ul
@@ -323,14 +336,13 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-activedescendant="ID_REF"
+
aria-activedescendant="ID_REF"
ul
-
Set by the JavaScript when it displays and sets focus on the listbox; otherwise is not present.
-
Refers to the option in the listbox that is visually indicated as having keyboard focus.
+
When an option in the listbox is visually indicated as having keyboard focus, refers to that option.
+
Enables assistive technologies to know which element the application regards as focused while DOM focus remains on the listbox element.
When navigation keys, such as Down Arrow, are pressed, the JavaScript changes the value.
-
Enables assistive technologies to know which element the application regards as focused while DOM focus remains on the ul element.
This listbox is scrollable; it has more options than its height can accommodate.
-
-
- Scrolling only works as expected if the listbox is the options' offsetParent.
- The example uses position: relative on the listbox to that effect.
-
+
+
+
+
+
Accessibility Features
+
- When an option is focused that isn't (fully) visible, the listbox's scroll position is updated:
-
-
If Up Arrow or Down Arrow is pressed, the previous or next option is scrolled into view.
-
If Home or End is pressed, the listbox scrolls all the way to the top or to the bottom.
-
If focusItem is called, the focused option will be scrolled to the top of the view if it was located above it or to the bottom if it was below it.
-
If the mouse is clicked on a partially visible option, it will be scrolled fully into view.
-
+ Because this listbox implementation is scrollable and manages which option is focused by using aria-activedescendant, the JavaScript must ensure the focused option is visible.
+ So, when a keyboard or pointer event changes the option referenced by aria-activedescendant, if the referenced option is not fully visible, the JavaScript scrolls the listbox to position the option in view.
-
When a fully visible option is focused in any way, no scrolling occurs.
- Normal scrolling through any scrolling mechanism (including Page Up and Page Down) works as expected.
- The scroll position will jump as described for focusItem if a means other than a mouse click is used to change focus after scrolling.
+ To enhance perceivability when operating the listbox, visual keyboard focus and hover are styled using the CSS :hover and :focus pseudo-classes:
+
+
To help people with visual impairments identify the listbox as an interactive element, the cursor is changed to a pointer when hovering over the list.
+
To make it easier to distinguish the selected listbox option from other options, selection creates a 2 pixel border above and below the option.
+
-
+
Keyboard Support
- The example listboxes on this page implement the following keyboard interface.
+ The example listbox on this page implements the following keyboard interface.
Other variations and options for the keyboard interface are described in the Keyboard Interaction section of the Listbox Pattern.
+
+ NOTE: When visual focus is on an option in this listbox implementation, DOM focus remains on the listbox element and the value of aria-activedescendant on the listbox refers to the descendant option that is visually indicated as focused.
+ Where the following descriptions of keyboard commands mention focus, they are referring to the visual focus indicator, not DOM focus.
+ For more information about this focus management technique, see
+ Managing Focus in Composites Using aria-activedescendant.
+
@@ -184,6 +221,10 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
+
+
Tab
+
Moves focus into and out of the listbox.
+
Down Arrow
Moves focus to and selects the next option.
@@ -200,6 +241,15 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
End
Moves focus to and selects the last option.
+
+
Printable Characters
+
+
+
Type a character: focus moves to the next item with a name that starts with the typed character.
+
Type multiple characters in rapid succession: focus moves to the next item with a name that starts with the string of characters typed.
+
+
+
@@ -228,25 +278,29 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-labelledby="ID_REF"
+
aria-labelledby="ID_REF"
div
Refers to the element containing the listbox label.
-
tabindex="0"
+
tabindex="0"
div
Includes the listbox in the page tab sequence.
-
aria-activedescendant="ID_REF"
+
aria-activedescendant="ID_REF"
div
-
Tells assistive technologies which of the options, if any, is visually indicated as having keyboard focus.
-
DOM focus remains on the ul element and the idref specified for aria-activedescendant refers to the li element that is visually styled as focused.
+
When an option in the listbox is visually indicated as having keyboard focus, refers to that option.
+
Enables assistive technologies to know which element the application regards as focused while DOM focus remains on the listbox element.
When navigation keys, such as Down Arrow, are pressed, the JavaScript changes the value.
@@ -258,7 +312,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-labelledby="ID_REF"
+
aria-labelledby="ID_REF"
ul
Refers to the element containing the option group label.
@@ -270,7 +324,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-selected="true"
+
aria-selected="true"
li
@@ -280,6 +334,14 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
+
+
+
aria-hidden="true"
+
span
+
+ Removes the character entity used for the check mark icon from the accessibility tree to prevent it from being included in the accessible name of the option.
+
+
@@ -293,7 +355,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
- Assistive technologies are told which option in the list is visually focused by the value of aria-activedescendant:
-
-
DOM focus remains on the listbox element.
-
When a key that moves focus is pressed or an option is clicked, JavaScript changes the value of aria-activedescendant on the listbox element.
-
If the listbox element does not contain any options, aria-activedescendant does not have a value.
-
-
-
- When Tab moves focus into either listbox:
-
-
If none of the options are selected, the first option receives focus.
-
If an option is selected, the selected option receives focus.
-
-
-
Only one option may be selected at a time (have aria-selected="true").
-
- As the user moves focus in the list, selection also moves.
- That is, both the value of aria-activedescendant and the element that has aria-selected="true" change.
-
-
@@ -190,73 +222,99 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
Available upgrades:
-
-
Leather seats
-
Front seat warmers
-
Rear bucket seats
-
Rear seat warmers
-
Front sun roof
-
Rear sun roof
-
Cloaking capability
-
Food synthesizer
-
Advanced waste recycling system
-
Turbo vertical take-off capability
+
+
+
+ Leather seats
+
+
+
+ Front seat warmers
+
+
+
+ Rear bucket seats
+
+
+
+ Rear seat warmers
+
+
+
+ Front sun roof
+
+
+
+ Rear sun roof
+
+
+
+ Cloaking capability
+
+
+
+ Food synthesizer
+
+
+
+ Advanced waste recycling system
+
+
+
+ Turbo vertical take-off capability
+
-
+
Upgrades you have chosen:
-
-
+
+
+
Last change:
-
Notes
-
-
- Like in example 1, assistive technologies are told which option in the list is visually focused by the value of aria-activedescendant:
-
-
DOM focus remains on the listbox element.
-
When a key that moves focus is pressed or an option is clicked, JavaScript changes the value of aria-activedescendant on the listbox element.
-
If the listbox element does not contain any options, aria-activedescendant does not have a value.
-
-
-
- When Tab moves focus into either listbox:
-
-
If none of the options are selected, focus is set on the first option.
-
If one or more options are selected, focus is set on the first selected option.
-
-
-
- Unlike example 1, more than one option may be selected at a time (have aria-selected="true").
-
-
The multi-select capability is communicated to assistive technologies by setting aria-multiselectable="true" on the listbox element.
-
All option elements have a value set for aria-selected.
-
Selected options have aria-selected set to true and all others have it set to false.
-
Keys that move focus do not change the selected state of an option.
-
-
-
Users can toggle the selected state of the focused option with Space or click.
-
Accessibility Features
-
+
Keyboard shortcuts for action buttons:
-
+
Action buttons have the following shortcuts:
-
"Up": Alt + Up Arrow
-
"Down": Alt + Down Arrow
-
"Add": Enter
-
"Not Important", "Important", and "Remove": Delete
+
"Up": Alt + Up Arrow
+
"Down": Alt + Down Arrow
+
"Add": Enter
+
"Not Important", "Important", and "Remove": Delete
Availability of the shortcuts is communicated to assistive technologies via the aria-keyshortcuts property on the button elements.
@@ -266,15 +324,26 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
Using a shortcut key intentionally places focus to optimize both screen reader and keyboard usability.
- For example, pressing Alt + Up Arrow in the "Important Features" list keeps focus on the option that is moved up, enabling all keyboard users to easily perform consecutive move operations for an option and screen reader users to hear the position of an option after it is moved.
+ For example, pressing Alt + Up Arrow in the "Important Features" list keeps focus on the option that is moved up, enabling all keyboard users to easily perform consecutive move operations for an option and screen reader users to hear the position of an option after it is moved.
Similarly, pressing Enter in the available options list leaves focus in the available options list.
If the option that had focus before the add operation is no longer present in the list, focus lands on the first of the subsequent options that is still present.
-
+
In example 1, since there are four action buttons, a toolbar widget is used to group all the action buttons into a single tab stop.
Live regions provide confirmation of completed actions.
-
+
+ Because this listbox implementation is scrollable and manages which option is focused by using aria-activedescendant, the JavaScript must ensure the focused option is visible.
+ So, when a keyboard or pointer event changes the option referenced by aria-activedescendant, if the referenced option is not fully visible, the JavaScript scrolls the listbox to position the option in view.
+
+
+ To enhance perceivability when operating the listbox, visual keyboard focus and hover are styled using the CSS :hover and :focus pseudo-classes:
+
+
To help people with visual impairments identify the listbox as an interactive element, the cursor is changed to a pointer when hovering over the list.
+
To make it easier to distinguish the selected listbox option from other options, selection creates a 2 pixel border above and below the option.
+
+
+
@@ -283,6 +352,12 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
The example listboxes on this page implement the following keyboard interface.
Other variations and options for the keyboard interface are described in the Keyboard Interaction section of the Listbox Pattern.
+
+ NOTE: When visual focus is on an option in these implementations of listbox, DOM focus remains on the listbox element and the value of aria-activedescendant on the listbox refers to the descendant option that is visually indicated as focused.
+ Where the following descriptions of keyboard commands mention focus, they are referring to the visual focus indicator, not DOM focus.
+ For more information about this focus management technique, see
+ Managing Focus in Composites Using aria-activedescendant.
+
@@ -291,6 +366,18 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
+
+
Tab
+
+
+
Moves focus into and out of the listbox.
+
+ When the listbox receives focus, if none of the options are selected, the first option receives focus.
+ Otherwise, the first selected option receives focus.
+
+
+
+
Down Arrow
@@ -327,15 +414,23 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
+
+
Printable Characters
+
+
+
Type a character: focus moves to the next item with a name that starts with the typed character.
+
Type multiple characters in rapid succession: focus moves to the next item with a name that starts with the string of characters typed.
+
+
+
Multiple selection keys supported in example 2
-
-
Note
-
The selection behavior demonstrated differs from the behavior provided by browsers for native HTML <select multiple> elements.
+
+ NOTE: The selection behavior demonstrated differs from the behavior provided by browsers for native HTML <select multiple> elements.
The HTML select element behavior is to alter selection with unmodified up/down arrow keys, requiring the use of modifier keys to select multiple options.
- This example demonstrates the multiple selection interaction model recommended in the Keyboard Interaction section of the Listbox Pattern, which does not require the use of modifier keys.
@@ -402,19 +497,19 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-labelledby="ID_REF"
+
aria-labelledby="ID_REF"
ul
Applied to the element with the listbox role, it refers to the span containing its label.
-
tabindex="0"
+
tabindex="0"
ul
Applied to the element with the listbox role, it puts the listbox in the tab sequence.
-
aria-multiselectable="true"
+
aria-multiselectable="true"
ul
@@ -426,14 +521,18 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-activedescendant="ID_REF"
+
aria-activedescendant="ID_REF"
ul
-
Applied to the element with the listbox role, tells assistive technologies which of the options, if any, is visually indicated as having keyboard focus.
-
DOM focus remains on the ul element and the idref specified for aria-activedescendant refers to the li element that is visually styled as focused.
+
When an option in the listbox is visually indicated as having keyboard focus, refers to that option.
+
Enables assistive technologies to know which element the application regards as focused while DOM focus remains on the listbox element.
When navigation keys, such as Down Arrow, are pressed, the JavaScript changes the value.
-
When the listbox is empty, aria-activedescendant="".
+
When the listbox is empty, aria-activedescendant="".
@@ -445,7 +544,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-selected="true"
+
aria-selected="true"
li
@@ -458,7 +557,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-selected="false"
+
aria-selected="false"
li
@@ -468,6 +567,14 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
+
+
+
aria-hidden="true"
+
span
+
+ Removes the character entities used for the check mark, left arrow and right arrow from the accessibility tree to prevent them from being included in the accessible name of an option or button.
+
+
@@ -481,7 +588,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
This listbox is scrollable; it has more options than its height can accommodate.
-
-
- Scrolling only works as expected if the listbox is the options' offsetParent.
- The example uses position: relative on the listbox to that effect.
-
+
+
+
+
Accessibility Features
+
- When an option is focused that isn't (fully) visible, the listbox's scroll position is updated:
-
-
If Up Arrow or Down Arrow is pressed, the previous or next option is scrolled into view.
-
If Home or End is pressed, the listbox scrolls all the way to the top or to the bottom.
-
If focusItem is called, the focused option will be scrolled to the top of the view if it was located above it or to the bottom if it was below it.
-
If the mouse is clicked on a partially visible option, it will be scrolled fully into view.
-
+ Because this listbox implementation is scrollable and manages which option is focused by using aria-activedescendant, the JavaScript must ensure the focused option is visible.
+ So, when a keyboard or pointer event changes the option referenced by aria-activedescendant, if the referenced option is not fully visible, the JavaScript scrolls the listbox to position the option in view.
-
When a fully visible option is focused in any way, no scrolling occurs.
- Normal scrolling through any scrolling mechanism (including Page Up and Page Down) works as expected.
- The scroll position will jump as described for focusItem if a means other than a mouse click is used to change focus after scrolling.
+ To enhance perceivability when operating the listbox, visual keyboard focus and hover are styled using the CSS :hover and :focus pseudo-classes:
+
+
To help people with visual impairments identify the listbox as an interactive element, the cursor is changed to a pointer when hovering over the list.
+
To make it easier to distinguish the selected listbox option from other options, selection creates a 2 pixel border above and below the option.
+
-
+
Keyboard Support
- The example listboxes on this page implement the following keyboard interface.
+ The example listbox on this page implements the following keyboard interface.
Other variations and options for the keyboard interface are described in the Keyboard Interaction section of the Listbox Pattern.
+
+ NOTE: When visual focus is on an option in this listbox implementation, DOM focus remains on the listbox element and the value of aria-activedescendant on the listbox refers to the descendant option that is visually indicated as focused.
+ Where the following descriptions of keyboard commands mention focus, they are referring to the visual focus indicator, not DOM focus.
+ For more information about this focus management technique, see
+ Managing Focus in Composites Using aria-activedescendant.
+
@@ -191,6 +269,10 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
+
+
Tab
+
Moves focus into and out of the listbox.
+
Down Arrow
Moves focus to and selects the next option.
@@ -207,6 +289,15 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
End
Moves focus to and selects the last option.
+
+
Printable Characters
+
+
+
Type a character: focus moves to the next item with a name that starts with the typed character.
+
Type multiple characters in rapid succession: focus moves to the next item with a name that starts with the string of characters typed.
+
+
+
@@ -214,7 +305,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
Role, Property, State, and Tabindex Attributes
- The example listboxes on this page implement the following ARIA roles, states, and properties.
+ The example listbox on this page implements the following ARIA roles, states, and properties.
Information about other ways of applying ARIA roles, states, and properties is available in the Roles, States, and Properties section of the Listbox Pattern.
@@ -235,25 +326,29 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-labelledby="ID_REF"
+
aria-labelledby="ID_REF"
ul
Refers to the element containing the listbox label.
-
tabindex="0"
+
tabindex="0"
ul
Includes the listbox in the page tab sequence.
-
aria-activedescendant="ID_REF"
+
aria-activedescendant="ID_REF"
ul
-
Tells assistive technologies which of the options, if any, is visually indicated as having keyboard focus.
-
DOM focus remains on the ul element and the idref specified for aria-activedescendant refers to the li element that is visually styled as focused.
+
When an option in the listbox is visually indicated as having keyboard focus, refers to that option.
+
Enables assistive technologies to know which element the application regards as focused while DOM focus remains on the listbox element.
When navigation keys, such as Down Arrow, are pressed, the JavaScript changes the value.
@@ -265,7 +360,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
-
aria-selected="true"
+
aria-selected="true"
li
@@ -275,6 +370,14 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
+
+
+
aria-hidden="true"
+
span
+
+ Removes the character entity used for the check mark icon from the accessibility tree to prevent it from being included in the accessible name of the option.
+
+
@@ -288,7 +391,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
diff --git a/ARIA/apg/patterns/menu-button/examples/menu-button-actions-active-descendant.md b/ARIA/apg/patterns/menu-button/examples/menu-button-actions-active-descendant.md
index 97a0e3453..91331573a 100644
--- a/ARIA/apg/patterns/menu-button/examples/menu-button-actions-active-descendant.md
+++ b/ARIA/apg/patterns/menu-button/examples/menu-button-actions-active-descendant.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/menu-button/examples/menu-button-actions-active-de
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -148,7 +148,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To support operating system high contrast settings:
- Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from 1 to 3 pixels and padding is reduced by 2 pixels.
When an element loses focus, its border changes from 3 pixels to 1 and padding is increased by 2 pixels.
diff --git a/ARIA/apg/patterns/menu-button/examples/menu-button-actions.md b/ARIA/apg/patterns/menu-button/examples/menu-button-actions.md
index 6664cf163..efc2bd7b0 100644
--- a/ARIA/apg/patterns/menu-button/examples/menu-button-actions.md
+++ b/ARIA/apg/patterns/menu-button/examples/menu-button-actions.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/menu-button/examples/menu-button-actions/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -149,7 +149,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To support operating system high contrast settings:
- Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from 1 to 3 pixels and padding is reduced by 2 pixels.
When an element loses focus, its border changes from 3 pixels to 1 and padding is increased by 2 pixels.
diff --git a/ARIA/apg/patterns/menu-button/examples/menu-button-links.md b/ARIA/apg/patterns/menu-button/examples/menu-button-links.md
index 978fd0edf..d926553f2 100644
--- a/ARIA/apg/patterns/menu-button/examples/menu-button-links.md
+++ b/ARIA/apg/patterns/menu-button/examples/menu-button-links.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/menu-button/examples/menu-button-links/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -165,7 +165,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To support operating system high contrast settings:
- Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from 1 to 3 pixels and padding is reduced by 2 pixels.
When an element loses focus, its border changes from 3 pixels to 1 and padding is increased by 2 pixels.
diff --git a/ARIA/apg/patterns/menubar/examples/menubar-editor.md b/ARIA/apg/patterns/menubar/examples/menubar-editor.md
index f336061b5..6c16eaec5 100644
--- a/ARIA/apg/patterns/menubar/examples/menubar-editor.md
+++ b/ARIA/apg/patterns/menubar/examples/menubar-editor.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/menubar/examples/menubar-editor/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/menubar/examples/menubar-navigation.md b/ARIA/apg/patterns/menubar/examples/menubar-navigation.md
index 9eb391cb2..60084d2e3 100644
--- a/ARIA/apg/patterns/menubar/examples/menubar-navigation.md
+++ b/ARIA/apg/patterns/menubar/examples/menubar-navigation.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/menubar/examples/menubar-navigation/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -323,7 +323,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To support operating system high contrast settings:
- Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from 0 to 2 pixels and padding is reduced by 2 pixels.
When an element loses focus, its border changes from 2 to 0 pixels and padding is increased by 2 pixels.
diff --git a/ARIA/apg/patterns/meter/examples/meter.md b/ARIA/apg/patterns/meter/examples/meter.md
index 6a1766b8a..aabc8a12f 100644
--- a/ARIA/apg/patterns/meter/examples/meter.md
+++ b/ARIA/apg/patterns/meter/examples/meter.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/meter/examples/meter/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/radio/examples/radio-activedescendant.md b/ARIA/apg/patterns/radio/examples/radio-activedescendant.md
index a7ef26f40..7db074684 100644
--- a/ARIA/apg/patterns/radio/examples/radio-activedescendant.md
+++ b/ARIA/apg/patterns/radio/examples/radio-activedescendant.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/radio/examples/radio-activedescendant/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/radio/examples/radio-rating.md b/ARIA/apg/patterns/radio/examples/radio-rating.md
index 8c7c51086..fbdbb6401 100644
--- a/ARIA/apg/patterns/radio/examples/radio-rating.md
+++ b/ARIA/apg/patterns/radio/examples/radio-rating.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/radio/examples/radio-rating/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/radio/examples/radio.md b/ARIA/apg/patterns/radio/examples/radio.md
index e711f4bcc..490ee72ff 100644
--- a/ARIA/apg/patterns/radio/examples/radio.md
+++ b/ARIA/apg/patterns/radio/examples/radio.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/radio/examples/radio/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/slider-multithumb/examples/slider-multithumb.md b/ARIA/apg/patterns/slider-multithumb/examples/slider-multithumb.md
index 013e152b6..de36a5482 100644
--- a/ARIA/apg/patterns/slider-multithumb/examples/slider-multithumb.md
+++ b/ARIA/apg/patterns/slider-multithumb/examples/slider-multithumb.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/slider-multithumb/examples/slider-multithumb/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/slider/examples/slider-color-viewer.md b/ARIA/apg/patterns/slider/examples/slider-color-viewer.md
index 461d80c3a..86604f6a9 100644
--- a/ARIA/apg/patterns/slider/examples/slider-color-viewer.md
+++ b/ARIA/apg/patterns/slider/examples/slider-color-viewer.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/slider/examples/slider-color-viewer/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/slider/examples/slider-rating.md b/ARIA/apg/patterns/slider/examples/slider-rating.md
index e3ac3cc34..a9cfa7fc4 100644
--- a/ARIA/apg/patterns/slider/examples/slider-rating.md
+++ b/ARIA/apg/patterns/slider/examples/slider-rating.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/slider/examples/slider-rating/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/slider/examples/slider-seek.md b/ARIA/apg/patterns/slider/examples/slider-seek.md
index 75401c970..3adb552a5 100644
--- a/ARIA/apg/patterns/slider/examples/slider-seek.md
+++ b/ARIA/apg/patterns/slider/examples/slider-seek.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/slider/examples/slider-seek/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/slider/examples/slider-temperature.md b/ARIA/apg/patterns/slider/examples/slider-temperature.md
index eb68790f9..47126250b 100644
--- a/ARIA/apg/patterns/slider/examples/slider-temperature.md
+++ b/ARIA/apg/patterns/slider/examples/slider-temperature.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/slider/examples/slider-temperature/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons.md b/ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons.md
index c383578e8..a96857dd3 100644
--- a/ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons.md
+++ b/ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/spinbutton/examples/datepicker-spinbuttons/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/switch/examples/switch-button.md b/ARIA/apg/patterns/switch/examples/switch-button.md
index b2f8d6f7f..9adbd68b9 100644
--- a/ARIA/apg/patterns/switch/examples/switch-button.md
+++ b/ARIA/apg/patterns/switch/examples/switch-button.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/switch/examples/switch-button/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/switch/examples/switch-checkbox.md b/ARIA/apg/patterns/switch/examples/switch-checkbox.md
index dde839333..66539b97e 100644
--- a/ARIA/apg/patterns/switch/examples/switch-checkbox.md
+++ b/ARIA/apg/patterns/switch/examples/switch-checkbox.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/switch/examples/switch-checkbox/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -168,7 +168,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To make it easier to perceive that clicking either the label or switch will activate the switch, the hover indicator is the same as the focus indicator.
To help people with visual impairments identify the switch as an interactive element, the cursor is changed to a pointer when hovering over the switch.
- Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from zero to two pixels and padding is reduced by two pixels.
When an element loses focus, its border changes from two pixels to zero and padding is increased by two pixels.
diff --git a/ARIA/apg/patterns/switch/examples/switch.md b/ARIA/apg/patterns/switch/examples/switch.md
index 9eb725e78..2ba338b1e 100644
--- a/ARIA/apg/patterns/switch/examples/switch.md
+++ b/ARIA/apg/patterns/switch/examples/switch.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/switch/examples/switch/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -149,7 +149,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To make it easier to perceive that clicking either the label or switch will activate the switch, the hover indicator is the same as the focus indicator.
To help people with visual impairments identify the switch as an interactive element, the cursor is changed to a pointer when hovering over the switch.
- Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Note: Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from zero to two pixels and padding is reduced by two pixels.
When an element loses focus, its border changes from two pixels to two and padding is increased by two pixels.
diff --git a/ARIA/apg/patterns/table/examples/sortable-table.md b/ARIA/apg/patterns/table/examples/sortable-table.md
index 2f1ddd295..8d9439509 100644
--- a/ARIA/apg/patterns/table/examples/sortable-table.md
+++ b/ARIA/apg/patterns/table/examples/sortable-table.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/table/examples/sortable-table/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/table/examples/table.md b/ARIA/apg/patterns/table/examples/table.md
index 3dacf5da1..845291ee2 100644
--- a/ARIA/apg/patterns/table/examples/table.md
+++ b/ARIA/apg/patterns/table/examples/table.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/table/examples/table/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/tabs/examples/tabs-automatic.md b/ARIA/apg/patterns/tabs/examples/tabs-automatic.md
index 692ccc2c1..93dbeb76e 100644
--- a/ARIA/apg/patterns/tabs/examples/tabs-automatic.md
+++ b/ARIA/apg/patterns/tabs/examples/tabs-automatic.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/tabs/examples/tabs-automatic/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/tabs/examples/tabs-manual.md b/ARIA/apg/patterns/tabs/examples/tabs-manual.md
index 0a9b9dfff..a0bfe3e7e 100644
--- a/ARIA/apg/patterns/tabs/examples/tabs-manual.md
+++ b/ARIA/apg/patterns/tabs/examples/tabs-manual.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/tabs/examples/tabs-manual/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/toolbar/examples/toolbar.md b/ARIA/apg/patterns/toolbar/examples/toolbar.md
index 051521c66..c0f716546 100644
--- a/ARIA/apg/patterns/toolbar/examples/toolbar.md
+++ b/ARIA/apg/patterns/toolbar/examples/toolbar.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/toolbar/examples/toolbar/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/treegrid/examples/treegrid-1.md b/ARIA/apg/patterns/treegrid/examples/treegrid-1.md
index 6566db4fe..f901e0979 100644
--- a/ARIA/apg/patterns/treegrid/examples/treegrid-1.md
+++ b/ARIA/apg/patterns/treegrid/examples/treegrid-1.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/treegrid/examples/treegrid-1/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/treegrid/treegrid-pattern.md b/ARIA/apg/patterns/treegrid/treegrid-pattern.md
index 01a633a59..1c21f2f93 100644
--- a/ARIA/apg/patterns/treegrid/treegrid-pattern.md
+++ b/ARIA/apg/patterns/treegrid/treegrid-pattern.md
@@ -211,7 +211,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
If focus is in the first row, focus does not move.
- If focus is on a cell, moves focus to the first cell in the column.
+ If focus is on a cell, moves focus to the cell in the first row in the same column as the cell that had focus.
If focus is in the first row, focus does not move.
@@ -224,7 +224,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
If focus is in the last row, focus does not move.
- If focus is on a cell, moves focus to the last cell in the column.
+ If focus is on a cell, moves focus to the cell in the last row in the same column as the cell that had focus.
If focus is in the last row, focus does not move.
diff --git a/ARIA/apg/patterns/treeview/examples/treeview-1a.md b/ARIA/apg/patterns/treeview/examples/treeview-1a.md
index 82cc0a4ea..ce2334664 100644
--- a/ARIA/apg/patterns/treeview/examples/treeview-1a.md
+++ b/ARIA/apg/patterns/treeview/examples/treeview-1a.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/treeview/examples/treeview-1a/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/treeview/examples/treeview-1b.md b/ARIA/apg/patterns/treeview/examples/treeview-1b.md
index 0149eb01d..f94cf4475 100644
--- a/ARIA/apg/patterns/treeview/examples/treeview-1b.md
+++ b/ARIA/apg/patterns/treeview/examples/treeview-1b.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/treeview/examples/treeview-1b/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
diff --git a/ARIA/apg/patterns/treeview/examples/treeview-navigation.md b/ARIA/apg/patterns/treeview/examples/treeview-navigation.md
index b7eb8ef24..e9672c598 100644
--- a/ARIA/apg/patterns/treeview/examples/treeview-navigation.md
+++ b/ARIA/apg/patterns/treeview/examples/treeview-navigation.md
@@ -12,7 +12,7 @@ permalink: /ARIA/apg/patterns/treeview/examples/treeview-navigation/
sidebar: true
-footer: " "
+footer: " "
# Context here: https://github.com/w3c/wai-aria-practices/issues/31
type_of_guidance: APG
@@ -419,7 +419,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
To support operating system high contrast settings:
- Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused an other elements.
+ Because transparent borders are visible on some systems with operating system high contrast settings enabled, transparency cannot be used to create a visual difference between the element that is focused and other elements.
Instead of using transparency, the focused element has a thicker border and less padding.
When an element receives focus, its border changes from 1 to 3 pixels and padding is reduced by 2 pixels.
When an element loses focus, its border changes from 3 pixels to 1 and padding is increased by 2 pixels.
diff --git a/ARIA/apg/practices/keyboard-interface/keyboard-interface-practice.md b/ARIA/apg/practices/keyboard-interface/keyboard-interface-practice.md
index 075dd6567..3aafd6f06 100644
--- a/ARIA/apg/practices/keyboard-interface/keyboard-interface-practice.md
+++ b/ARIA/apg/practices/keyboard-interface/keyboard-interface-practice.md
@@ -524,7 +524,7 @@ if (enableSidebar) document.body.classList.add('has-sidebar');
Redo action
Control + Y
-
Command + Shift + Z
+
Command + Shift + Z
diff --git a/_external/aria-practices b/_external/aria-practices
index 164188c47..7fb7c8e28 160000
--- a/_external/aria-practices
+++ b/_external/aria-practices
@@ -1 +1 @@
-Subproject commit 164188c47c4164ee4b8801fcb33f576d872e0861
+Subproject commit 7fb7c8e28b55a2804bda682c36945a1154d1096a
diff --git a/_external/data b/_external/data
index 3e2c9fde3..966876f60 160000
--- a/_external/data
+++ b/_external/data
@@ -1 +1 @@
-Subproject commit 3e2c9fde38da2f50b1613a45a76bb0c03e463020
+Subproject commit 966876f60a55bc3df0f19af41f30f62841da3aa3
diff --git a/content-assets/wai-aria-practices/patterns/combobox/examples/css/combobox-autocomplete.css b/content-assets/wai-aria-practices/patterns/combobox/examples/css/combobox-autocomplete.css
index 72f0f113d..cef393bd3 100644
--- a/content-assets/wai-aria-practices/patterns/combobox/examples/css/combobox-autocomplete.css
+++ b/content-assets/wai-aria-practices/patterns/combobox/examples/css/combobox-autocomplete.css
@@ -13,7 +13,7 @@
background-color: white;
color: black;
box-sizing: border-box;
- height: 24px;
+ height: 30px;
padding: 0;
margin: 0;
vertical-align: bottom;
@@ -46,7 +46,7 @@ ul[role="listbox"] {
padding: 0;
position: absolute;
left: 4px;
- top: 28px;
+ top: 34px;
list-style: none;
background-color: white;
display: none;
diff --git a/content-assets/wai-aria-practices/patterns/combobox/examples/css/grid-combo.css b/content-assets/wai-aria-practices/patterns/combobox/examples/css/grid-combo.css
index 5484f8f7b..8c86a4f3b 100644
--- a/content-assets/wai-aria-practices/patterns/combobox/examples/css/grid-combo.css
+++ b/content-assets/wai-aria-practices/patterns/combobox/examples/css/grid-combo.css
@@ -23,7 +23,7 @@
margin: 0;
padding: 0;
position: absolute;
- top: 1.7em;
+ top: auto;
z-index: 1;
}
diff --git a/content-assets/wai-aria-practices/patterns/listbox/examples/css/listbox.css b/content-assets/wai-aria-practices/patterns/listbox/examples/css/listbox.css
index 7fc920c58..5ecb8d129 100644
--- a/content-assets/wai-aria-practices/patterns/listbox/examples/css/listbox.css
+++ b/content-assets/wai-aria-practices/patterns/listbox/examples/css/listbox.css
@@ -44,15 +44,30 @@
[role="option"] {
position: relative;
display: block;
- padding: 0 1em 0 1.5em;
+ margin: 2px;
+ padding: 2px 1em 2px 1.5em;
line-height: 1.8em;
+ cursor: pointer;
}
-[role="option"].focused {
+[role="listbox"]:focus [role="option"].focused {
background: #bde4ff;
}
-[role="option"][aria-selected="true"]::before {
+[role="listbox"]:focus [role="option"].focused,
+[role="option"]:hover {
+ outline: 2px solid currentcolor;
+}
+
+.move-right-btn span.checkmark::after {
+ content: " →";
+}
+
+.move-left-btn span.checkmark::before {
+ content: "← ";
+}
+
+[role="option"][aria-selected="true"] span.checkmark::before {
position: absolute;
left: 0.5em;
content: "✓";
@@ -120,14 +135,6 @@ button[aria-disabled="true"] {
opacity: 0.5;
}
-.move-right-btn::after {
- content: " →";
-}
-
-.move-left-btn::before {
- content: "← ";
-}
-
.annotate {
color: #366ed4;
font-style: italic;
diff --git a/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-collapsible.js b/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-collapsible.js
index 4c5733de9..4beaae9f5 100644
--- a/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-collapsible.js
+++ b/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-collapsible.js
@@ -1,4 +1,18 @@
+/*
+ * This content is licensed according to the W3C Software License at
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
+ */
+
'use strict';
+
+/**
+ * @namespace aria
+ * @description
+ * The aria namespace is used to support sharing class definitions between example files
+ * without causing eslint errors for undefined classes
+ */
+var aria = aria || {};
+
/**
* ARIA Collapsible Dropdown Listbox Example
*
@@ -7,70 +21,66 @@
*/
window.addEventListener('load', function () {
- var button = document.getElementById('exp_button');
- var exListbox = new aria.Listbox(document.getElementById('exp_elem_list'));
- new aria.ListboxButton(button, exListbox);
+ const button = document.getElementById('exp_button');
+ const exListbox = new aria.Listbox(document.getElementById('exp_elem_list'));
+ new ListboxButton(button, exListbox);
});
-var aria = aria || {};
-
-aria.ListboxButton = function (button, listbox) {
- this.button = button;
- this.listbox = listbox;
- this.registerEvents();
-};
-
-aria.ListboxButton.prototype.registerEvents = function () {
- this.button.addEventListener('click', this.showListbox.bind(this));
- this.button.addEventListener('keyup', this.checkShow.bind(this));
- this.listbox.listboxNode.addEventListener(
- 'blur',
- this.hideListbox.bind(this)
- );
- this.listbox.listboxNode.addEventListener(
- 'keydown',
- this.checkHide.bind(this)
- );
- this.listbox.setHandleFocusChange(this.onFocusChange.bind(this));
-};
-
-aria.ListboxButton.prototype.checkShow = function (evt) {
- var key = evt.which || evt.keyCode;
+class ListboxButton {
+ constructor(button, listbox) {
+ this.button = button;
+ this.listbox = listbox;
+ this.registerEvents();
+ }
- switch (key) {
- case aria.KeyCode.UP:
- case aria.KeyCode.DOWN:
- evt.preventDefault();
- this.showListbox();
- this.listbox.checkKeyPress(evt);
- break;
+ registerEvents() {
+ this.button.addEventListener('click', this.showListbox.bind(this));
+ this.button.addEventListener('keyup', this.checkShow.bind(this));
+ this.listbox.listboxNode.addEventListener(
+ 'blur',
+ this.hideListbox.bind(this)
+ );
+ this.listbox.listboxNode.addEventListener(
+ 'keydown',
+ this.checkHide.bind(this)
+ );
+ this.listbox.setHandleFocusChange(this.onFocusChange.bind(this));
}
-};
-aria.ListboxButton.prototype.checkHide = function (evt) {
- var key = evt.which || evt.keyCode;
+ checkShow(evt) {
+ switch (evt.key) {
+ case 'ArrowUp':
+ case 'ArrowDown':
+ evt.preventDefault();
+ this.showListbox();
+ this.listbox.checkKeyPress(evt);
+ break;
+ }
+ }
- switch (key) {
- case aria.KeyCode.RETURN:
- case aria.KeyCode.ESC:
- evt.preventDefault();
- this.hideListbox();
- this.button.focus();
- break;
+ checkHide(evt) {
+ switch (evt.key) {
+ case 'Enter':
+ case 'Escape':
+ evt.preventDefault();
+ this.hideListbox();
+ this.button.focus();
+ break;
+ }
}
-};
-aria.ListboxButton.prototype.showListbox = function () {
- aria.Utils.removeClass(this.listbox.listboxNode, 'hidden');
- this.button.setAttribute('aria-expanded', 'true');
- this.listbox.listboxNode.focus();
-};
+ showListbox() {
+ this.listbox.listboxNode.classList.remove('hidden');
+ this.button.setAttribute('aria-expanded', 'true');
+ this.listbox.listboxNode.focus();
+ }
-aria.ListboxButton.prototype.hideListbox = function () {
- aria.Utils.addClass(this.listbox.listboxNode, 'hidden');
- this.button.removeAttribute('aria-expanded');
-};
+ hideListbox() {
+ this.listbox.listboxNode.classList.add('hidden');
+ this.button.removeAttribute('aria-expanded');
+ }
-aria.ListboxButton.prototype.onFocusChange = function (focusedItem) {
- this.button.innerText = focusedItem.innerText;
-};
+ onFocusChange(focusedItem) {
+ this.button.innerText = focusedItem.innerText;
+ }
+}
diff --git a/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-rearrangeable.js b/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-rearrangeable.js
index 561b5d519..b5ba8ee80 100644
--- a/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-rearrangeable.js
+++ b/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-rearrangeable.js
@@ -3,10 +3,16 @@
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*/
-/* global aria */
-
'use strict';
+/**
+ * @namespace aria
+ * @description
+ * The aria namespace is used to support sharing class definitions between example files
+ * without causing eslint errors for undefined classes
+ */
+var aria = aria || {};
+
/**
* ARIA Listbox Examples
*
diff --git a/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-scrollable.js b/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-scrollable.js
index 402040604..fe7b2658f 100644
--- a/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-scrollable.js
+++ b/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox-scrollable.js
@@ -3,10 +3,16 @@
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*/
-/* global aria */
-
'use strict';
+/**
+ * @namespace aria
+ * @description
+ * The aria namespace is used to support sharing class definitions between example files
+ * without causing eslint errors for undefined classes
+ */
+var aria = aria || {};
+
/**
* ARIA Scrollable Listbox Example
*
diff --git a/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox.js b/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox.js
index 8a24176bb..632063f25 100644
--- a/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox.js
+++ b/content-assets/wai-aria-practices/patterns/listbox/examples/js/listbox.js
@@ -7,6 +7,9 @@
/**
* @namespace aria
+ * @description
+ * The aria namespace is used to support sharing class definitions between example files
+ * without causing eslint errors for undefined classes
*/
var aria = aria || {};
@@ -17,681 +20,668 @@ var aria = aria || {};
* @param listboxNode
* The DOM node pointing to the listbox
*/
-aria.Listbox = function (listboxNode) {
- this.listboxNode = listboxNode;
- this.activeDescendant = this.listboxNode.getAttribute(
- 'aria-activedescendant'
- );
- this.multiselectable = this.listboxNode.hasAttribute('aria-multiselectable');
- this.moveUpDownEnabled = false;
- this.siblingList = null;
- this.startRangeIndex = 0;
- this.upButton = null;
- this.downButton = null;
- this.moveButton = null;
- this.keysSoFar = '';
- this.handleFocusChange = function () {};
- this.handleItemChange = function () {};
- this.registerEvents();
-};
-/**
- * @description
- * Register events for the listbox interactions
- */
-aria.Listbox.prototype.registerEvents = function () {
- this.listboxNode.addEventListener('focus', this.setupFocus.bind(this));
- this.listboxNode.addEventListener('keydown', this.checkKeyPress.bind(this));
- this.listboxNode.addEventListener('click', this.checkClickItem.bind(this));
-
- if (this.multiselectable) {
- this.listboxNode.addEventListener(
- 'mousedown',
- this.checkMouseDown.bind(this)
+aria.Listbox = class Listbox {
+ constructor(listboxNode) {
+ this.listboxNode = listboxNode;
+ this.activeDescendant = this.listboxNode.getAttribute(
+ 'aria-activedescendant'
+ );
+ this.multiselectable = this.listboxNode.hasAttribute(
+ 'aria-multiselectable'
);
+ this.moveUpDownEnabled = false;
+ this.siblingList = null;
+ this.startRangeIndex = 0;
+ this.upButton = null;
+ this.downButton = null;
+ this.moveButton = null;
+ this.keysSoFar = '';
+ this.handleFocusChange = function () {};
+ this.handleItemChange = function () {};
+ this.registerEvents();
+ }
+
+ registerEvents() {
+ this.listboxNode.addEventListener('focus', this.setupFocus.bind(this));
+ this.listboxNode.addEventListener('keydown', this.checkKeyPress.bind(this));
+ this.listboxNode.addEventListener('click', this.checkClickItem.bind(this));
+
+ if (this.multiselectable) {
+ this.listboxNode.addEventListener(
+ 'mousedown',
+ this.checkMouseDown.bind(this)
+ );
+ }
}
-};
-/**
- * @description
- * If there is no activeDescendant, focus on the first option
- */
-aria.Listbox.prototype.setupFocus = function () {
- if (this.activeDescendant) {
- return;
+ setupFocus() {
+ if (this.activeDescendant) {
+ const listitem = document.getElementById(this.activeDescendant);
+ listitem.scrollIntoView({ block: 'nearest', inline: 'nearest' });
+ }
}
-};
-/**
- * @description
- * Focus on the first option
- */
-aria.Listbox.prototype.focusFirstItem = function () {
- var firstItem = this.listboxNode.querySelector('[role="option"]');
+ focusFirstItem() {
+ var firstItem = this.listboxNode.querySelector('[role="option"]');
- if (firstItem) {
- this.focusItem(firstItem);
+ if (firstItem) {
+ this.focusItem(firstItem);
+ }
}
-};
-/**
- * @description
- * Focus on the last option
- */
-aria.Listbox.prototype.focusLastItem = function () {
- var itemList = this.listboxNode.querySelectorAll('[role="option"]');
+ focusLastItem() {
+ const itemList = this.listboxNode.querySelectorAll('[role="option"]');
- if (itemList.length) {
- this.focusItem(itemList[itemList.length - 1]);
+ if (itemList.length) {
+ this.focusItem(itemList[itemList.length - 1]);
+ }
}
-};
-/**
- * @description
- * Handle various keyboard controls; UP/DOWN will shift focus; SPACE selects
- * an item.
- * @param evt
- * The keydown event object
- */
-aria.Listbox.prototype.checkKeyPress = function (evt) {
- var key = evt.which || evt.keyCode;
- var lastActiveId = this.activeDescendant;
- var allOptions = this.listboxNode.querySelectorAll('[role="option"]');
- var currentItem =
- document.getElementById(this.activeDescendant) || allOptions[0];
- var nextItem = currentItem;
-
- if (!currentItem) {
- return;
- }
-
- switch (key) {
- case aria.KeyCode.PAGE_UP:
- case aria.KeyCode.PAGE_DOWN:
- if (this.moveUpDownEnabled) {
- evt.preventDefault();
+ checkKeyPress(evt) {
+ const lastActiveId = this.activeDescendant;
+ const allOptions = this.listboxNode.querySelectorAll('[role="option"]');
+ const currentItem =
+ document.getElementById(this.activeDescendant) || allOptions[0];
+ let nextItem = currentItem;
- if (key === aria.KeyCode.PAGE_UP) {
- this.moveUpItems();
- } else {
- this.moveDownItems();
+ if (!currentItem) {
+ return;
+ }
+
+ switch (evt.key) {
+ case 'PageUp':
+ case 'PageDown':
+ evt.preventDefault();
+ if (this.moveUpDownEnabled) {
+ if (evt.key === 'PageUp') {
+ this.moveUpItems();
+ } else {
+ this.moveDownItems();
+ }
}
- }
- break;
- case aria.KeyCode.UP:
- case aria.KeyCode.DOWN:
- if (!this.activeDescendant) {
- // focus first option if no option was previously focused, and perform no other actions
- this.focusItem(currentItem);
break;
- }
-
- if (this.moveUpDownEnabled && evt.altKey) {
+ case 'ArrowUp':
+ case 'ArrowDown':
evt.preventDefault();
- if (key === aria.KeyCode.UP) {
- this.moveUpItems();
+ if (!this.activeDescendant) {
+ // focus first option if no option was previously focused, and perform no other actions
+ this.focusItem(currentItem);
+ break;
+ }
+
+ if (this.moveUpDownEnabled && evt.altKey) {
+ evt.preventDefault();
+ if (evt.key === 'ArrowUp') {
+ this.moveUpItems();
+ } else {
+ this.moveDownItems();
+ }
+ this.updateScroll();
+ return;
+ }
+
+ if (evt.key === 'ArrowUp') {
+ nextItem = this.findPreviousOption(currentItem);
} else {
- this.moveDownItems();
+ nextItem = this.findNextOption(currentItem);
}
- return;
- }
- if (key === aria.KeyCode.UP) {
- nextItem = this.findPreviousOption(currentItem);
- } else {
- nextItem = this.findNextOption(currentItem);
- }
+ if (nextItem && this.multiselectable && event.shiftKey) {
+ this.selectRange(this.startRangeIndex, nextItem);
+ }
- if (nextItem && this.multiselectable && event.shiftKey) {
- this.selectRange(this.startRangeIndex, nextItem);
- }
+ if (nextItem) {
+ this.focusItem(nextItem);
+ }
- if (nextItem) {
- this.focusItem(nextItem);
+ break;
+
+ case 'Home':
evt.preventDefault();
- }
+ this.focusFirstItem();
- break;
- case aria.KeyCode.HOME:
- evt.preventDefault();
- this.focusFirstItem();
+ if (this.multiselectable && evt.shiftKey && evt.ctrlKey) {
+ this.selectRange(this.startRangeIndex, 0);
+ }
+ break;
- if (this.multiselectable && evt.shiftKey && evt.ctrlKey) {
- this.selectRange(this.startRangeIndex, 0);
- }
- break;
- case aria.KeyCode.END:
- evt.preventDefault();
- this.focusLastItem();
+ case 'End':
+ evt.preventDefault();
+ this.focusLastItem();
- if (this.multiselectable && evt.shiftKey && evt.ctrlKey) {
- this.selectRange(this.startRangeIndex, allOptions.length - 1);
- }
- break;
- case aria.KeyCode.SHIFT:
- this.startRangeIndex = this.getElementIndex(currentItem, allOptions);
- break;
- case aria.KeyCode.SPACE:
- evt.preventDefault();
- this.toggleSelectItem(nextItem);
- break;
- case aria.KeyCode.BACKSPACE:
- case aria.KeyCode.DELETE:
- case aria.KeyCode.RETURN:
- if (!this.moveButton) {
- return;
- }
+ if (this.multiselectable && evt.shiftKey && evt.ctrlKey) {
+ this.selectRange(this.startRangeIndex, allOptions.length - 1);
+ }
+ break;
- var keyshortcuts = this.moveButton.getAttribute('aria-keyshortcuts');
- if (key === aria.KeyCode.RETURN && keyshortcuts.indexOf('Enter') === -1) {
- return;
- }
- if (
- (key === aria.KeyCode.BACKSPACE || key === aria.KeyCode.DELETE) &&
- keyshortcuts.indexOf('Delete') === -1
- ) {
- return;
- }
+ case 'Shift':
+ this.startRangeIndex = this.getElementIndex(currentItem, allOptions);
+ break;
- evt.preventDefault();
+ case ' ':
+ evt.preventDefault();
+ this.toggleSelectItem(nextItem);
+ break;
- var nextUnselected = nextItem.nextElementSibling;
- while (nextUnselected) {
- if (nextUnselected.getAttribute('aria-selected') != 'true') {
- break;
+ case 'Backspace':
+ case 'Delete':
+ case 'Enter':
+ if (!this.moveButton) {
+ return;
}
- nextUnselected = nextUnselected.nextElementSibling;
- }
- if (!nextUnselected) {
- nextUnselected = nextItem.previousElementSibling;
+
+ var keyshortcuts = this.moveButton.getAttribute('aria-keyshortcuts');
+ if (evt.key === 'Enter' && keyshortcuts.indexOf('Enter') === -1) {
+ return;
+ }
+ if (
+ (evt.key === 'Backspace' || evt.key === 'Delete') &&
+ keyshortcuts.indexOf('Delete') === -1
+ ) {
+ return;
+ }
+
+ evt.preventDefault();
+
+ var nextUnselected = nextItem.nextElementSibling;
while (nextUnselected) {
if (nextUnselected.getAttribute('aria-selected') != 'true') {
break;
}
- nextUnselected = nextUnselected.previousElementSibling;
+ nextUnselected = nextUnselected.nextElementSibling;
+ }
+ if (!nextUnselected) {
+ nextUnselected = nextItem.previousElementSibling;
+ while (nextUnselected) {
+ if (nextUnselected.getAttribute('aria-selected') != 'true') {
+ break;
+ }
+ nextUnselected = nextUnselected.previousElementSibling;
+ }
}
- }
- this.moveItems();
+ this.moveItems();
- if (!this.activeDescendant && nextUnselected) {
- this.focusItem(nextUnselected);
- }
- break;
- case 65:
- // handle control + A
- if (this.multiselectable && (evt.ctrlKey || evt.metaKey)) {
- evt.preventDefault();
- this.selectRange(0, allOptions.length - 1);
+ if (!this.activeDescendant && nextUnselected) {
+ this.focusItem(nextUnselected);
+ }
break;
- }
- // fall through
- default:
- var itemToFocus = this.findItemToFocus(key);
- if (itemToFocus) {
- this.focusItem(itemToFocus);
- }
- break;
- }
- if (this.activeDescendant !== lastActiveId) {
- this.updateScroll();
+ case 'A':
+ case 'a':
+ // handle control + A
+ if (evt.ctrlKey || evt.metaKey) {
+ if (this.multiselectable) {
+ this.selectRange(0, allOptions.length - 1);
+ }
+ evt.preventDefault();
+ break;
+ }
+ // fall through
+ default:
+ if (evt.key.length === 1) {
+ const itemToFocus = this.findItemToFocus(evt.key.toLowerCase());
+ if (itemToFocus) {
+ this.focusItem(itemToFocus);
+ }
+ }
+ break;
+ }
+
+ if (this.activeDescendant !== lastActiveId) {
+ this.updateScroll();
+ }
}
-};
-aria.Listbox.prototype.findItemToFocus = function (key) {
- var itemList = this.listboxNode.querySelectorAll('[role="option"]');
- var character = String.fromCharCode(key);
- var searchIndex = 0;
+ findItemToFocus(character) {
+ const itemList = this.listboxNode.querySelectorAll('[role="option"]');
+ let searchIndex = 0;
- if (!this.keysSoFar) {
- for (var i = 0; i < itemList.length; i++) {
- if (itemList[i].getAttribute('id') == this.activeDescendant) {
- searchIndex = i;
+ if (!this.keysSoFar) {
+ for (let i = 0; i < itemList.length; i++) {
+ if (itemList[i].getAttribute('id') == this.activeDescendant) {
+ searchIndex = i;
+ }
}
}
- }
- this.keysSoFar += character;
- this.clearKeysSoFarAfterDelay();
- var nextMatch = this.findMatchInRange(
- itemList,
- searchIndex + 1,
- itemList.length
- );
- if (!nextMatch) {
- nextMatch = this.findMatchInRange(itemList, 0, searchIndex);
- }
- return nextMatch;
-};
+ this.keysSoFar += character;
+ this.clearKeysSoFarAfterDelay();
-/* Return the index of the passed element within the passed array, or null if not found */
-aria.Listbox.prototype.getElementIndex = function (option, options) {
- var allOptions = Array.prototype.slice.call(options); // convert to array
- var optionIndex = allOptions.indexOf(option);
+ let nextMatch = this.findMatchInRange(
+ itemList,
+ searchIndex + 1,
+ itemList.length
+ );
- return typeof optionIndex === 'number' ? optionIndex : null;
-};
+ if (!nextMatch) {
+ nextMatch = this.findMatchInRange(itemList, 0, searchIndex);
+ }
+ return nextMatch;
+ }
-/* Return the next listbox option, if it exists; otherwise, returns null */
-aria.Listbox.prototype.findNextOption = function (currentOption) {
- var allOptions = Array.prototype.slice.call(
- this.listboxNode.querySelectorAll('[role="option"]')
- ); // get options array
- var currentOptionIndex = allOptions.indexOf(currentOption);
- var nextOption = null;
+ /* Return the index of the passed element within the passed array, or null if not found */
+ getElementIndex(option, options) {
+ const allOptions = Array.prototype.slice.call(options); // convert to array
+ const optionIndex = allOptions.indexOf(option);
- if (currentOptionIndex > -1 && currentOptionIndex < allOptions.length - 1) {
- nextOption = allOptions[currentOptionIndex + 1];
+ return typeof optionIndex === 'number' ? optionIndex : null;
}
- return nextOption;
-};
+ /* Return the next listbox option, if it exists; otherwise, returns null */
+ findNextOption(currentOption) {
+ const allOptions = Array.prototype.slice.call(
+ this.listboxNode.querySelectorAll('[role="option"]')
+ ); // get options array
+ const currentOptionIndex = allOptions.indexOf(currentOption);
+ let nextOption = null;
-/* Return the previous listbox option, if it exists; otherwise, returns null */
-aria.Listbox.prototype.findPreviousOption = function (currentOption) {
- var allOptions = Array.prototype.slice.call(
- this.listboxNode.querySelectorAll('[role="option"]')
- ); // get options array
- var currentOptionIndex = allOptions.indexOf(currentOption);
- var previousOption = null;
+ if (currentOptionIndex > -1 && currentOptionIndex < allOptions.length - 1) {
+ nextOption = allOptions[currentOptionIndex + 1];
+ }
- if (currentOptionIndex > -1 && currentOptionIndex > 0) {
- previousOption = allOptions[currentOptionIndex - 1];
+ return nextOption;
}
- return previousOption;
-};
+ /* Return the previous listbox option, if it exists; otherwise, returns null */
+ findPreviousOption(currentOption) {
+ const allOptions = Array.prototype.slice.call(
+ this.listboxNode.querySelectorAll('[role="option"]')
+ ); // get options array
+ const currentOptionIndex = allOptions.indexOf(currentOption);
+ let previousOption = null;
-aria.Listbox.prototype.clearKeysSoFarAfterDelay = function () {
- if (this.keyClear) {
- clearTimeout(this.keyClear);
- this.keyClear = null;
- }
- this.keyClear = setTimeout(
- function () {
- this.keysSoFar = '';
- this.keyClear = null;
- }.bind(this),
- 500
- );
-};
-
-aria.Listbox.prototype.findMatchInRange = function (
- list,
- startIndex,
- endIndex
-) {
- // Find the first item starting with the keysSoFar substring, searching in
- // the specified range of items
- for (var n = startIndex; n < endIndex; n++) {
- var label = list[n].innerText;
- if (label && label.toUpperCase().indexOf(this.keysSoFar) === 0) {
- return list[n];
+ if (currentOptionIndex > -1 && currentOptionIndex > 0) {
+ previousOption = allOptions[currentOptionIndex - 1];
}
- }
- return null;
-};
-/**
- * @description
- * Check if an item is clicked on. If so, focus on it and select it.
- * @param evt
- * The click event object
- */
-aria.Listbox.prototype.checkClickItem = function (evt) {
- if (evt.target.getAttribute('role') !== 'option') {
- return;
+ return previousOption;
}
- this.focusItem(evt.target);
- this.toggleSelectItem(evt.target);
- this.updateScroll();
-
- if (this.multiselectable && evt.shiftKey) {
- this.selectRange(this.startRangeIndex, evt.target);
+ clearKeysSoFarAfterDelay() {
+ if (this.keyClear) {
+ clearTimeout(this.keyClear);
+ this.keyClear = null;
+ }
+ this.keyClear = setTimeout(
+ function () {
+ this.keysSoFar = '';
+ this.keyClear = null;
+ }.bind(this),
+ 500
+ );
}
-};
-/**
- * Prevent text selection on shift + click for multi-select listboxes
- *
- * @param evt
- */
-aria.Listbox.prototype.checkMouseDown = function (evt) {
- if (
- this.multiselectable &&
- evt.shiftKey &&
- evt.target.getAttribute('role') === 'option'
- ) {
- evt.preventDefault();
+ findMatchInRange(list, startIndex, endIndex) {
+ // Find the first item starting with the keysSoFar substring, searching in
+ // the specified range of items
+ for (let n = startIndex; n < endIndex; n++) {
+ const label = list[n].innerText;
+ if (label && label.toLowerCase().indexOf(this.keysSoFar) === 0) {
+ return list[n];
+ }
+ }
+ return null;
}
-};
-/**
- * @description
- * Toggle the aria-selected value
- * @param element
- * The element to select
- */
-aria.Listbox.prototype.toggleSelectItem = function (element) {
- if (this.multiselectable) {
- element.setAttribute(
- 'aria-selected',
- element.getAttribute('aria-selected') === 'true' ? 'false' : 'true'
- );
+ checkClickItem(evt) {
+ if (evt.target.getAttribute('role') !== 'option') {
+ return;
+ }
- this.updateMoveButton();
- }
-};
+ this.focusItem(evt.target);
+ this.toggleSelectItem(evt.target);
+ this.updateScroll();
-/**
- * @description
- * Defocus the specified item
- * @param element
- * The element to defocus
- */
-aria.Listbox.prototype.defocusItem = function (element) {
- if (!element) {
- return;
- }
- if (!this.multiselectable) {
- element.removeAttribute('aria-selected');
+ if (this.multiselectable && evt.shiftKey) {
+ this.selectRange(this.startRangeIndex, evt.target);
+ }
}
- element.classList.remove('focused');
-};
-/**
- * @description
- * Focus on the specified item
- * @param element
- * The element to focus
- */
-aria.Listbox.prototype.focusItem = function (element) {
- this.defocusItem(document.getElementById(this.activeDescendant));
- if (!this.multiselectable) {
- element.setAttribute('aria-selected', 'true');
+ /**
+ * Prevent text selection on shift + click for multi-select listboxes
+ *
+ * @param evt
+ */
+ checkMouseDown(evt) {
+ if (
+ this.multiselectable &&
+ evt.shiftKey &&
+ evt.target.getAttribute('role') === 'option'
+ ) {
+ evt.preventDefault();
+ }
}
- element.classList.add('focused');
- this.listboxNode.setAttribute('aria-activedescendant', element.id);
- this.activeDescendant = element.id;
- if (!this.multiselectable) {
- this.updateMoveButton();
+ /**
+ * @description
+ * Toggle the aria-selected value
+ * @param element
+ * The element to select
+ */
+ toggleSelectItem(element) {
+ if (this.multiselectable) {
+ element.setAttribute(
+ 'aria-selected',
+ element.getAttribute('aria-selected') === 'true' ? 'false' : 'true'
+ );
+
+ this.updateMoveButton();
+ }
}
- this.checkUpDownButtons();
- this.handleFocusChange(element);
-};
-
-/**
- * Helper function to check if a number is within a range; no side effects.
- *
- * @param index
- * @param start
- * @param end
- * @returns {boolean}
- */
-aria.Listbox.prototype.checkInRange = function (index, start, end) {
- var rangeStart = start < end ? start : end;
- var rangeEnd = start < end ? end : start;
+ /**
+ * @description
+ * Defocus the specified item
+ * @param element
+ * The element to defocus
+ */
+ defocusItem(element) {
+ if (!element) {
+ return;
+ }
+ if (!this.multiselectable) {
+ element.removeAttribute('aria-selected');
+ }
+ element.classList.remove('focused');
+ }
+
+ /**
+ * @description
+ * Focus on the specified item
+ * @param element
+ * The element to focus
+ */
+ focusItem(element) {
+ this.defocusItem(document.getElementById(this.activeDescendant));
+ if (!this.multiselectable) {
+ element.setAttribute('aria-selected', 'true');
+ }
+ element.classList.add('focused');
+ this.listboxNode.setAttribute('aria-activedescendant', element.id);
+ this.activeDescendant = element.id;
- return index >= rangeStart && index <= rangeEnd;
-};
+ if (!this.multiselectable) {
+ this.updateMoveButton();
+ }
-/**
- * Select a range of options
- *
- * @param start
- * @param end
- */
-aria.Listbox.prototype.selectRange = function (start, end) {
- // get start/end indices
- var allOptions = this.listboxNode.querySelectorAll('[role="option"]');
- var startIndex =
- typeof start === 'number' ? start : this.getElementIndex(start, allOptions);
- var endIndex =
- typeof end === 'number' ? end : this.getElementIndex(end, allOptions);
+ this.checkUpDownButtons();
+ this.handleFocusChange(element);
+ }
+
+ /**
+ * Helper function to check if a number is within a range; no side effects.
+ *
+ * @param index
+ * @param start
+ * @param end
+ * @returns {boolean}
+ */
+ checkInRange(index, start, end) {
+ const rangeStart = start < end ? start : end;
+ const rangeEnd = start < end ? end : start;
+
+ return index >= rangeStart && index <= rangeEnd;
+ }
+
+ /**
+ * Select a range of options
+ *
+ * @param start
+ * @param end
+ */
+ selectRange(start, end) {
+ // get start/end indices
+ const allOptions = this.listboxNode.querySelectorAll('[role="option"]');
+ const startIndex =
+ typeof start === 'number'
+ ? start
+ : this.getElementIndex(start, allOptions);
+ const endIndex =
+ typeof end === 'number' ? end : this.getElementIndex(end, allOptions);
+
+ for (let index = 0; index < allOptions.length; index++) {
+ const selected = this.checkInRange(index, startIndex, endIndex);
+ allOptions[index].setAttribute('aria-selected', selected + '');
+ }
- for (var index = 0; index < allOptions.length; index++) {
- var selected = this.checkInRange(index, startIndex, endIndex);
- allOptions[index].setAttribute('aria-selected', selected + '');
+ this.updateMoveButton();
}
- this.updateMoveButton();
-};
-
-/**
- * Check for selected options and update moveButton, if applicable
- */
-aria.Listbox.prototype.updateMoveButton = function () {
- if (!this.moveButton) {
- return;
- }
+ /**
+ * Check for selected options and update moveButton, if applicable
+ */
+ updateMoveButton() {
+ if (!this.moveButton) {
+ return;
+ }
- if (this.listboxNode.querySelector('[aria-selected="true"]')) {
- this.moveButton.setAttribute('aria-disabled', 'false');
- } else {
- this.moveButton.setAttribute('aria-disabled', 'true');
+ if (this.listboxNode.querySelector('[aria-selected="true"]')) {
+ this.moveButton.setAttribute('aria-disabled', 'false');
+ } else {
+ this.moveButton.setAttribute('aria-disabled', 'true');
+ }
}
-};
-/**
- * Check if the selected option is in view, and scroll if not
- */
-aria.Listbox.prototype.updateScroll = function () {
- var selectedOption = document.getElementById(this.activeDescendant);
- if (
- selectedOption &&
- this.listboxNode.scrollHeight > this.listboxNode.clientHeight
- ) {
- var scrollBottom =
- this.listboxNode.clientHeight + this.listboxNode.scrollTop;
- var elementBottom = selectedOption.offsetTop + selectedOption.offsetHeight;
- if (elementBottom > scrollBottom) {
- this.listboxNode.scrollTop =
- elementBottom - this.listboxNode.clientHeight;
- } else if (selectedOption.offsetTop < this.listboxNode.scrollTop) {
- this.listboxNode.scrollTop = selectedOption.offsetTop;
+ /**
+ * Check if the selected option is in view, and scroll if not
+ */
+ updateScroll() {
+ const selectedOption = document.getElementById(this.activeDescendant);
+ if (selectedOption) {
+ const scrollBottom =
+ this.listboxNode.clientHeight + this.listboxNode.scrollTop;
+ const elementBottom =
+ selectedOption.offsetTop + selectedOption.offsetHeight;
+ if (elementBottom > scrollBottom) {
+ this.listboxNode.scrollTop =
+ elementBottom - this.listboxNode.clientHeight;
+ } else if (selectedOption.offsetTop < this.listboxNode.scrollTop) {
+ this.listboxNode.scrollTop = selectedOption.offsetTop;
+ }
+ selectedOption.scrollIntoView({ block: 'nearest', inline: 'nearest' });
}
}
-};
-
-/**
- * @description
- * Enable/disable the up/down arrows based on the activeDescendant.
- */
-aria.Listbox.prototype.checkUpDownButtons = function () {
- var activeElement = document.getElementById(this.activeDescendant);
- if (!this.moveUpDownEnabled) {
- return;
- }
+ /**
+ * @description
+ * Enable/disable the up/down arrows based on the activeDescendant.
+ */
+ checkUpDownButtons() {
+ const activeElement = document.getElementById(this.activeDescendant);
- if (!activeElement) {
- this.upButton.setAttribute('aria-disabled', 'true');
- this.downButton.setAttribute('aria-disabled', 'true');
- return;
- }
+ if (!this.moveUpDownEnabled) {
+ return;
+ }
- if (this.upButton) {
- if (activeElement.previousElementSibling) {
- this.upButton.setAttribute('aria-disabled', false);
- } else {
+ if (!activeElement) {
this.upButton.setAttribute('aria-disabled', 'true');
+ this.downButton.setAttribute('aria-disabled', 'true');
+ return;
}
- }
- if (this.downButton) {
- if (activeElement.nextElementSibling) {
- this.downButton.setAttribute('aria-disabled', false);
- } else {
- this.downButton.setAttribute('aria-disabled', 'true');
+ if (this.upButton) {
+ if (activeElement.previousElementSibling) {
+ this.upButton.setAttribute('aria-disabled', false);
+ } else {
+ this.upButton.setAttribute('aria-disabled', 'true');
+ }
}
- }
-};
-/**
- * @description
- * Add the specified items to the listbox. Assumes items are valid options.
- * @param items
- * An array of items to add to the listbox
- */
-aria.Listbox.prototype.addItems = function (items) {
- if (!items || !items.length) {
- return;
+ if (this.downButton) {
+ if (activeElement.nextElementSibling) {
+ this.downButton.setAttribute('aria-disabled', false);
+ } else {
+ this.downButton.setAttribute('aria-disabled', 'true');
+ }
+ }
}
- items.forEach(
- function (item) {
- this.defocusItem(item);
- this.toggleSelectItem(item);
- this.listboxNode.append(item);
- }.bind(this)
- );
+ /**
+ * @description
+ * Add the specified items to the listbox. Assumes items are valid options.
+ * @param items
+ * An array of items to add to the listbox
+ */
+ addItems(items) {
+ if (!items || !items.length) {
+ return;
+ }
- if (!this.activeDescendant) {
- this.focusItem(items[0]);
- }
+ items.forEach(
+ function (item) {
+ this.defocusItem(item);
+ this.toggleSelectItem(item);
+ this.listboxNode.append(item);
+ }.bind(this)
+ );
- this.handleItemChange('added', items);
-};
+ if (!this.activeDescendant) {
+ this.focusItem(items[0]);
+ }
-/**
- * @description
- * Remove all of the selected items from the listbox; Removes the focused items
- * in a single select listbox and the items with aria-selected in a multi
- * select listbox.
- * @returns {Array}
- * An array of items that were removed from the listbox
- */
-aria.Listbox.prototype.deleteItems = function () {
- var itemsToDelete;
+ this.handleItemChange('added', items);
+ }
+
+ /**
+ * @description
+ * Remove all of the selected items from the listbox; Removes the focused items
+ * in a single select listbox and the items with aria-selected in a multi
+ * select listbox.
+ * @returns {Array}
+ * An array of items that were removed from the listbox
+ */
+ deleteItems() {
+ let itemsToDelete;
+
+ if (this.multiselectable) {
+ itemsToDelete = this.listboxNode.querySelectorAll(
+ '[aria-selected="true"]'
+ );
+ } else if (this.activeDescendant) {
+ itemsToDelete = [document.getElementById(this.activeDescendant)];
+ }
- if (this.multiselectable) {
- itemsToDelete = this.listboxNode.querySelectorAll('[aria-selected="true"]');
- } else if (this.activeDescendant) {
- itemsToDelete = [document.getElementById(this.activeDescendant)];
- }
+ if (!itemsToDelete || !itemsToDelete.length) {
+ return [];
+ }
- if (!itemsToDelete || !itemsToDelete.length) {
- return [];
- }
+ itemsToDelete.forEach(
+ function (item) {
+ item.remove();
- itemsToDelete.forEach(
- function (item) {
- item.remove();
+ if (item.id === this.activeDescendant) {
+ this.clearActiveDescendant();
+ }
+ }.bind(this)
+ );
- if (item.id === this.activeDescendant) {
- this.clearActiveDescendant();
- }
- }.bind(this)
- );
+ this.handleItemChange('removed', itemsToDelete);
- this.handleItemChange('removed', itemsToDelete);
+ return itemsToDelete;
+ }
- return itemsToDelete;
-};
+ clearActiveDescendant() {
+ this.activeDescendant = null;
+ this.listboxNode.setAttribute('aria-activedescendant', null);
-aria.Listbox.prototype.clearActiveDescendant = function () {
- this.activeDescendant = null;
- this.listboxNode.setAttribute('aria-activedescendant', null);
+ this.updateMoveButton();
+ this.checkUpDownButtons();
+ }
- this.updateMoveButton();
- this.checkUpDownButtons();
-};
+ /**
+ * @description
+ * Shifts the currently focused item up on the list. No shifting occurs if the
+ * item is already at the top of the list.
+ */
+ moveUpItems() {
+ if (!this.activeDescendant) {
+ return;
+ }
-/**
- * @description
- * Shifts the currently focused item up on the list. No shifting occurs if the
- * item is already at the top of the list.
- */
-aria.Listbox.prototype.moveUpItems = function () {
- if (!this.activeDescendant) {
- return;
- }
+ const currentItem = document.getElementById(this.activeDescendant);
+ const previousItem = currentItem.previousElementSibling;
- var currentItem = document.getElementById(this.activeDescendant);
- var previousItem = currentItem.previousElementSibling;
+ if (previousItem) {
+ this.listboxNode.insertBefore(currentItem, previousItem);
+ this.handleItemChange('moved_up', [currentItem]);
+ }
- if (previousItem) {
- this.listboxNode.insertBefore(currentItem, previousItem);
- this.handleItemChange('moved_up', [currentItem]);
+ this.checkUpDownButtons();
}
- this.checkUpDownButtons();
-};
+ /**
+ * @description
+ * Shifts the currently focused item down on the list. No shifting occurs if
+ * the item is already at the end of the list.
+ */
+ moveDownItems() {
+ if (!this.activeDescendant) {
+ return;
+ }
-/**
- * @description
- * Shifts the currently focused item down on the list. No shifting occurs if
- * the item is already at the end of the list.
- */
-aria.Listbox.prototype.moveDownItems = function () {
- if (!this.activeDescendant) {
- return;
- }
+ var currentItem = document.getElementById(this.activeDescendant);
+ var nextItem = currentItem.nextElementSibling;
- var currentItem = document.getElementById(this.activeDescendant);
- var nextItem = currentItem.nextElementSibling;
+ if (nextItem) {
+ this.listboxNode.insertBefore(nextItem, currentItem);
+ this.handleItemChange('moved_down', [currentItem]);
+ }
- if (nextItem) {
- this.listboxNode.insertBefore(nextItem, currentItem);
- this.handleItemChange('moved_down', [currentItem]);
+ this.checkUpDownButtons();
}
- this.checkUpDownButtons();
-};
+ /**
+ * @description
+ * Delete the currently selected items and add them to the sibling list.
+ */
+ moveItems() {
+ if (!this.siblingList) {
+ return;
+ }
-/**
- * @description
- * Delete the currently selected items and add them to the sibling list.
- */
-aria.Listbox.prototype.moveItems = function () {
- if (!this.siblingList) {
- return;
+ var itemsToMove = this.deleteItems();
+ this.siblingList.addItems(itemsToMove);
}
- var itemsToMove = this.deleteItems();
- this.siblingList.addItems(itemsToMove);
-};
-
-/**
- * @description
- * Enable Up/Down controls to shift items up and down.
- * @param upButton
- * Up button to trigger up shift
- * @param downButton
- * Down button to trigger down shift
- */
-aria.Listbox.prototype.enableMoveUpDown = function (upButton, downButton) {
- this.moveUpDownEnabled = true;
- this.upButton = upButton;
- this.downButton = downButton;
- upButton.addEventListener('click', this.moveUpItems.bind(this));
- downButton.addEventListener('click', this.moveDownItems.bind(this));
-};
+ /**
+ * @description
+ * Enable Up/Down controls to shift items up and down.
+ * @param upButton
+ * Up button to trigger up shift
+ * @param downButton
+ * Down button to trigger down shift
+ */
+ enableMoveUpDown(upButton, downButton) {
+ this.moveUpDownEnabled = true;
+ this.upButton = upButton;
+ this.downButton = downButton;
+ upButton.addEventListener('click', this.moveUpItems.bind(this));
+ downButton.addEventListener('click', this.moveDownItems.bind(this));
+ }
-/**
- * @description
- * Enable Move controls. Moving removes selected items from the current
- * list and adds them to the sibling list.
- * @param button
- * Move button to trigger delete
- * @param siblingList
- * Listbox to move items to
- */
-aria.Listbox.prototype.setupMove = function (button, siblingList) {
- this.siblingList = siblingList;
- this.moveButton = button;
- button.addEventListener('click', this.moveItems.bind(this));
-};
+ /**
+ * @description
+ * Enable Move controls. Moving removes selected items from the current
+ * list and adds them to the sibling list.
+ * @param button
+ * Move button to trigger delete
+ * @param siblingList
+ * Listbox to move items to
+ */
+ setupMove(button, siblingList) {
+ this.siblingList = siblingList;
+ this.moveButton = button;
+ button.addEventListener('click', this.moveItems.bind(this));
+ }
-aria.Listbox.prototype.setHandleItemChange = function (handlerFn) {
- this.handleItemChange = handlerFn;
-};
+ setHandleItemChange(handlerFn) {
+ this.handleItemChange = handlerFn;
+ }
-aria.Listbox.prototype.setHandleFocusChange = function (focusChangeHandler) {
- this.handleFocusChange = focusChangeHandler;
+ setHandleFocusChange(focusChangeHandler) {
+ this.handleFocusChange = focusChangeHandler;
+ }
};
diff --git a/content-assets/wai-aria-practices/patterns/listbox/examples/js/toolbar.js b/content-assets/wai-aria-practices/patterns/listbox/examples/js/toolbar.js
index 6def976c0..a1fb9f891 100644
--- a/content-assets/wai-aria-practices/patterns/listbox/examples/js/toolbar.js
+++ b/content-assets/wai-aria-practices/patterns/listbox/examples/js/toolbar.js
@@ -7,6 +7,9 @@
/**
* @namespace aria
+ * @description
+ * The aria namespace is used to support sharing class definitions between example files
+ * without causing eslint errors for undefined classes
*/
var aria = aria || {};
@@ -17,101 +20,118 @@ var aria = aria || {};
* @param toolbarNode
* The DOM node pointing to the toolbar
*/
-aria.Toolbar = function (toolbarNode) {
- this.toolbarNode = toolbarNode;
- this.items = this.toolbarNode.querySelectorAll('.toolbar-item');
- this.selectedItem = this.toolbarNode.querySelector('.selected');
- this.registerEvents();
-};
-/**
- * @description
- * Register events for the toolbar interactions
- */
-aria.Toolbar.prototype.registerEvents = function () {
- this.toolbarNode.addEventListener(
- 'keydown',
- this.checkFocusChange.bind(this)
- );
- this.toolbarNode.addEventListener('click', this.checkClickItem.bind(this));
-};
+aria.Toolbar = class Toolbar {
+ constructor(toolbarNode) {
+ this.toolbarNode = toolbarNode;
+ this.items = this.toolbarNode.querySelectorAll('.toolbar-item');
+ this.selectedItem = this.toolbarNode.querySelector('.selected');
+ this.registerEvents();
+ }
-/**
- * @description
- * Handle various keyboard controls; LEFT/RIGHT will shift focus; DOWN
- * activates a menu button if it is the focused item.
- * @param evt
- * The keydown event object
- */
-aria.Toolbar.prototype.checkFocusChange = function (evt) {
- var key = evt.which || evt.keyCode;
- var nextIndex, nextItem;
+ /**
+ * @description
+ * Register events for the toolbar interactions
+ */
+ registerEvents() {
+ this.toolbarNode.addEventListener(
+ 'keydown',
+ this.checkFocusChange.bind(this)
+ );
+ this.toolbarNode.addEventListener('click', this.checkClickItem.bind(this));
+ }
+
+ /**
+ * @description
+ * Handle various keyboard commands to move focus:
+ * LEFT: Previous button
+ * RIGHT: Next button
+ * HOME: First button
+ * END: Last button
+ * @param evt
+ * The keydown event object
+ */
+ checkFocusChange(evt) {
+ let nextIndex, nextItem;
+
+ // Do not move focus if any modifier keys pressed
+ if (!evt.shiftKey && !evt.metaKey && !evt.altKey && !evt.ctrlKey) {
+ switch (evt.key) {
+ case 'ArrowLeft':
+ case 'ArrowRight':
+ nextIndex = Array.prototype.indexOf.call(
+ this.items,
+ this.selectedItem
+ );
+ nextIndex = evt.key === 'ArrowLeft' ? nextIndex - 1 : nextIndex + 1;
+ nextIndex = Math.max(Math.min(nextIndex, this.items.length - 1), 0);
+
+ nextItem = this.items[nextIndex];
+ break;
- switch (key) {
- case aria.KeyCode.LEFT:
- case aria.KeyCode.RIGHT:
- nextIndex = Array.prototype.indexOf.call(this.items, this.selectedItem);
- nextIndex = key === aria.KeyCode.LEFT ? nextIndex - 1 : nextIndex + 1;
- nextIndex = Math.max(Math.min(nextIndex, this.items.length - 1), 0);
+ case 'End':
+ nextItem = this.items[this.items.length - 1];
+ break;
- nextItem = this.items[nextIndex];
- this.selectItem(nextItem);
- this.focusItem(nextItem);
- break;
- case aria.KeyCode.DOWN:
- // if selected item is menu button, pressing DOWN should act like a click
- if (aria.Utils.hasClass(this.selectedItem, 'menu-button')) {
+ case 'Home':
+ nextItem = this.items[0];
+ break;
+ }
+
+ if (nextItem) {
+ this.selectItem(nextItem);
+ this.focusItem(nextItem);
+ evt.stopPropagation();
evt.preventDefault();
- this.selectedItem.click();
}
- break;
+ }
}
-};
-/**
- * @description
- * Selects a toolbar item if it is clicked
- * @param evt
- * The click event object
- */
-aria.Toolbar.prototype.checkClickItem = function (evt) {
- if (aria.Utils.hasClass(evt.target, 'toolbar-item')) {
- this.selectItem(evt.target);
+ /**
+ * @description
+ * Selects a toolbar item if it is clicked
+ * @param evt
+ * The click event object
+ */
+ checkClickItem(evt) {
+ if (evt.target.classList.contains('toolbar-item')) {
+ this.selectItem(evt.target);
+ }
}
-};
-/**
- * @description
- * Deselect the specified item
- * @param element
- * The item to deselect
- */
-aria.Toolbar.prototype.deselectItem = function (element) {
- aria.Utils.removeClass(element, 'selected');
- element.setAttribute('aria-selected', 'false');
- element.setAttribute('tabindex', '-1');
-};
+ /**
+ * @description
+ * Deselect the specified item
+ * @param element
+ * The item to deselect
+ */
+ deselectItem(element) {
+ element.classList.remove('selected');
+ element.setAttribute('aria-selected', 'false');
+ element.setAttribute('tabindex', '-1');
+ }
-/**
- * @description
- * Deselect the currently selected item and select the specified item
- * @param element
- * The item to select
- */
-aria.Toolbar.prototype.selectItem = function (element) {
- this.deselectItem(this.selectedItem);
- aria.Utils.addClass(element, 'selected');
- element.setAttribute('aria-selected', 'true');
- element.setAttribute('tabindex', '0');
- this.selectedItem = element;
-};
+ /**
+ * @description
+ * Deselect the currently selected item and select the specified item
+ * @param element
+ * The item to select
+ */
+ selectItem(element) {
+ this.deselectItem(this.selectedItem);
+ element.classList.add('selected');
+ element.setAttribute('aria-selected', 'true');
+ element.setAttribute('tabindex', '0');
+ this.selectedItem = element;
+ }
-/**
- * @description
- * Focus on the specified item
- * @param element
- * The item to focus on
- */
-aria.Toolbar.prototype.focusItem = function (element) {
- element.focus();
+ /**
+ * @description
+ * Focus on the specified item
+ * @param element
+ * The item to focus on
+ */
+ focusItem(element) {
+ element.focus();
+ }
};
diff --git a/scripts/pre-build/library/rewriteElementPaths.js b/scripts/pre-build/library/rewriteElementPaths.js
index 33a08b528..b04178804 100644
--- a/scripts/pre-build/library/rewriteElementPaths.js
+++ b/scripts/pre-build/library/rewriteElementPaths.js
@@ -10,8 +10,9 @@ const rewriteElementPaths = async (html, { onSourcePath }) => {
const linkTags = html.querySelectorAll("link[href]");
const scriptTags = html.querySelectorAll("script[src]");
const imgTags = html.querySelectorAll("img[src]");
+ const iframeTags = html.querySelectorAll("iframe[src]");
- [...aTags, ...linkTags, ...scriptTags, ...imgTags].forEach((element) => {
+ [...aTags, ...linkTags, ...scriptTags, ...imgTags, ...iframeTags].forEach((element) => {
fixSpecLink(element);
const href = element.getAttribute("href");