diff --git a/content/about/coverage-and-quality/coverage-and-quality-report.html b/content/about/coverage-and-quality/coverage-and-quality-report.html index dd4c7ecf84..60e342bfe6 100644 --- a/content/about/coverage-and-quality/coverage-and-quality-report.html +++ b/content/about/coverage-and-quality/coverage-and-quality-report.html @@ -368,7 +368,7 @@

Roles with More than One Guidance or Exa
  • Listbox with Grouped Options
  • Editor Menubar (HC)
  • Color Viewer Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • Switch Using HTML Button (HC)
  • File Directory Treeview Using Computed Properties
  • File Directory Treeview Using Declared Properties
  • @@ -628,7 +628,8 @@

    Roles with More than One Guidance or Exa Spinbutton Pattern @@ -900,6 +901,7 @@

    Properties and States with More than One
  • Actions Menu Button Using aria-activedescendant (HC)
  • Actions Menu Button Using element.focus() (HC)
  • Navigation Menu Button (HC)
  • +
  • Quantity Spin Button
  • Experimental Tabs with Action Buttons (HC)
  • Tabs with Automatic Activation (HC)
  • Tabs with Manual Activation (HC)
  • @@ -939,6 +941,7 @@

    Properties and States with More than One @@ -1003,7 +1006,8 @@

    Properties and States with More than One
  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • +
  • Quantity Spin Button
  • Switch Using HTML Button (HC)
  • Switch Using HTML Checkbox Input (HC)
  • Switch (HC)
  • @@ -1030,7 +1034,7 @@

    Properties and States with More than One
  • Navigation Menubar (HC)
  • Rating Radio Group (HC)
  • Horizontal Multi-Thumb Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • Table
  • Toolbar
  • Treegrid Email Inbox
  • @@ -1070,7 +1074,7 @@

    Properties and States with More than One
  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • Switch Using HTML Button (HC)
  • Experimental Tabs with Action Buttons (HC)
  • Tabs with Automatic Activation (HC)
  • @@ -1223,7 +1227,8 @@

    Properties and States with More than One
  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • +
  • Quantity Spin Button
  • Toolbar
  • @@ -1238,7 +1243,8 @@

    Properties and States with More than One
  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • +
  • Quantity Spin Button
  • Toolbar
  • @@ -1254,7 +1260,8 @@

    Properties and States with More than One
  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • +
  • Quantity Spin Button
  • Toolbar
  • @@ -1267,7 +1274,7 @@

    Properties and States with More than One
  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • Toolbar
  • @@ -1283,7 +1290,7 @@

    Coding Summary

    Total Examples - 62 + 63 High Contrast Documentation @@ -1295,7 +1302,7 @@

    Coding Summary

    Uses forced-colors media query - 3 + 4 Uses currentColor value @@ -1317,7 +1324,7 @@

    Coding Summary

    Use Class - 37 + 38 Use Prototype @@ -1964,7 +1971,7 @@

    Coding Practices

    - Date Picker Spin Button + (Deprecated) Date Picker Spin Button prototype Yes @@ -1976,6 +1983,19 @@

    Coding Practices

    7 + + Quantity Spin Button + class + + + + example + 1 + 1 + 7 + 6 + aria-describedby + Switch Using HTML Button class @@ -2462,13 +2482,21 @@

    SVG and High Contrast Techniques

    Yes - Date Picker Spin Button + (Deprecated) Date Picker Spin Button Yes Yes + + Quantity Spin Button + + + + Yes + + Switch Using HTML Button Yes diff --git a/content/about/coverage-and-quality/prop-coverage.csv b/content/about/coverage-and-quality/prop-coverage.csv index 64c42b359d..19db885b0f 100644 --- a/content/about/coverage-and-quality/prop-coverage.csv +++ b/content/about/coverage-and-quality/prop-coverage.csv @@ -7,22 +7,22 @@ "aria-colcount","1","1","Guidance: Using aria-colcount and aria-colindex","Example: Data Grid" "aria-colindex","3","1","Guidance: Using aria-colcount and aria-colindex","Guidance: Using aria-colindex When Column Indices Are Contiguous","Guidance: Using aria-colindex When Column Indices Are Not Contiguous","Example: Data Grid" "aria-colspan","1","0","Guidance: Defining cell spans using aria-colspan and aria-rowspan" -"aria-controls","0","22","Example: Accordion","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Checkbox (Mixed-State)","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Disclosure (Show/Hide) Card","Example: Disclosure (Show/Hide) for Answers to Frequently Asked Questions","Example: Disclosure (Show/Hide) for Image Description","Example: Disclosure Navigation Menu with Top-Level Links","Example: Disclosure Navigation Menu","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation","Example: Toolbar" +"aria-controls","0","23","Example: Accordion","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Checkbox (Mixed-State)","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Disclosure (Show/Hide) Card","Example: Disclosure (Show/Hide) for Answers to Frequently Asked Questions","Example: Disclosure (Show/Hide) for Image Description","Example: Disclosure Navigation Menu with Top-Level Links","Example: Disclosure Navigation Menu","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Quantity Spin Button","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation","Example: Toolbar" "aria-current","0","5","Example: Breadcrumb","Example: Disclosure Navigation Menu with Top-Level Links","Example: Disclosure Navigation Menu","Example: Navigation Menubar","Example: Navigation Treeview" "aria-describedby","1","6","Guidance: Describing by referencing content with aria-describedby","Example: Alert Dialog","Example: Date Picker Combobox","Example: Date Picker Dialog","Example: Modal Dialog","Example: Infinite Scrolling Feed","Example: Table" "aria-details","0","0" -"aria-disabled","0","3","Example: Alert Dialog","Example: Editor Menubar","Example: Toolbar" +"aria-disabled","0","4","Example: Alert Dialog","Example: Editor Menubar","Example: Quantity Spin Button","Example: Toolbar" "aria-dropeffect","0","0" "aria-errormessage","0","0" "aria-expanded","0","23","Example: Accordion","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Disclosure (Show/Hide) Card","Example: Disclosure (Show/Hide) for Answers to Frequently Asked Questions","Example: Disclosure (Show/Hide) for Image Description","Example: Disclosure Navigation Menu with Top-Level Links","Example: Disclosure Navigation Menu","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Editor Menubar","Example: Navigation Menubar","Example: Toolbar","Example: Treegrid Email Inbox","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties","Example: Navigation Treeview" "aria-flowto","0","0" "aria-grabbed","0","0" "aria-haspopup","0","9","Example: Date Picker Combobox","Example: Editable Combobox with Grid Popup","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Editor Menubar","Example: Navigation Menubar","Example: Toolbar" -"aria-hidden","0","16","Example: Button (IDL Version)","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox","Example: Editor Menubar","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Switch Using HTML Button","Example: Switch Using HTML Checkbox Input","Example: Switch","Example: Sortable Table","Example: Toolbar" +"aria-hidden","0","17","Example: Button (IDL Version)","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox","Example: Editor Menubar","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Quantity Spin Button","Example: Switch Using HTML Button","Example: Switch Using HTML Checkbox Input","Example: Switch","Example: Sortable Table","Example: Toolbar" "aria-invalid","0","0" "aria-keyshortcuts","0","0" -"aria-label","1","18","Guidance: Naming with a String Attribute Via aria-label","Example: Breadcrumb","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Date Picker Dialog","Example: Link","Example: Editor Menubar","Example: Navigation Menubar","Example: Rating Radio Group","Example: Horizontal Multi-Thumb Slider","Example: Date Picker Spin Button","Example: Table","Example: Toolbar","Example: Treegrid Email Inbox","Example: Navigation Treeview" -"aria-labelledby","1","41","Guidance: Naming with Referenced Content Via aria-labelledby","Example: Accordion","Example: Alert Dialog","Example: Checkbox (Two State)","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Date Picker Dialog","Example: Modal Dialog","Example: Infinite Scrolling Feed","Example: Data Grid","Example: Layout Grid","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Navigation Menubar","Example: Meter","Example: Radio Group Using aria-activedescendant","Example: Rating Radio Group","Example: Radio Group Using Roving tabindex","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Switch Using HTML Button","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties","Example: Navigation Treeview","Example: Complementary Landmark","Example: Form Landmark","Example: Main Landmark","Example: Navigation Landmark","Example: Region Landmark","Example: Search Landmark" +"aria-label","1","18","Guidance: Naming with a String Attribute Via aria-label","Example: Breadcrumb","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Date Picker Dialog","Example: Link","Example: Editor Menubar","Example: Navigation Menubar","Example: Rating Radio Group","Example: Horizontal Multi-Thumb Slider","Example: (Deprecated) Date Picker Spin Button","Example: Table","Example: Toolbar","Example: Treegrid Email Inbox","Example: Navigation Treeview" +"aria-labelledby","1","41","Guidance: Naming with Referenced Content Via aria-labelledby","Example: Accordion","Example: Alert Dialog","Example: Checkbox (Two State)","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Date Picker Dialog","Example: Modal Dialog","Example: Infinite Scrolling Feed","Example: Data Grid","Example: Layout Grid","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox","Example: Actions Menu Button Using aria-activedescendant","Example: Actions Menu Button Using element.focus()","Example: Navigation Menu Button","Example: Navigation Menubar","Example: Meter","Example: Radio Group Using aria-activedescendant","Example: Rating Radio Group","Example: Radio Group Using Roving tabindex","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Switch Using HTML Button","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties","Example: Navigation Treeview","Example: Complementary Landmark","Example: Form Landmark","Example: Main Landmark","Example: Navigation Landmark","Example: Region Landmark","Example: Search Landmark" "aria-level","0","2","Example: Treegrid Email Inbox","Example: File Directory Treeview Using Declared Properties" "aria-live","0","5","Example: Alert","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Date Picker Combobox","Example: Date Picker Dialog" "aria-modal","0","4","Example: Alert Dialog","Example: Date Picker Combobox","Example: Date Picker Dialog","Example: Modal Dialog" @@ -43,7 +43,7 @@ "aria-selected","0","17","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Editable Combobox With Both List and Inline Autocomplete","Example: Editable Combobox With List Autocomplete","Example: Editable Combobox without Autocomplete","Example: Date Picker Combobox","Example: Select-Only Combobox","Example: Editable Combobox with Grid Popup","Example: Date Picker Dialog","Example: (Deprecated) Collapsible Dropdown Listbox","Example: Listbox with Grouped Options","Example: Listboxes with Rearrangeable Options","Example: Scrollable Listbox","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties" "aria-setsize","0","3","Example: Infinite Scrolling Feed","Example: Treegrid Email Inbox","Example: File Directory Treeview Using Declared Properties" "aria-sort","1","2","Guidance: Indicating sort order with aria-sort","Example: Data Grid","Example: Sortable Table" -"aria-valuemax","1","8","Guidance: Using aria-valuemin, aria-valuemax and aria-valuenow","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Toolbar" -"aria-valuemin","0","8","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Toolbar" -"aria-valuenow","1","8","Guidance: Using aria-valuemin, aria-valuemax and aria-valuenow","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Toolbar" -"aria-valuetext","1","5","Guidance: Using aria-valuetext","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: Date Picker Spin Button","Example: Toolbar" +"aria-valuemax","1","9","Guidance: Using aria-valuemin, aria-valuemax and aria-valuenow","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Quantity Spin Button","Example: Toolbar" +"aria-valuemin","0","9","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Quantity Spin Button","Example: Toolbar" +"aria-valuenow","1","9","Guidance: Using aria-valuemin, aria-valuemax and aria-valuenow","Example: Meter","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Quantity Spin Button","Example: Toolbar" +"aria-valuetext","1","5","Guidance: Using aria-valuetext","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider","Example: (Deprecated) Date Picker Spin Button","Example: Toolbar" diff --git a/content/about/coverage-and-quality/role-coverage.csv b/content/about/coverage-and-quality/role-coverage.csv index e760c3cb20..8b06bb226d 100644 --- a/content/about/coverage-and-quality/role-coverage.csv +++ b/content/about/coverage-and-quality/role-coverage.csv @@ -25,7 +25,7 @@ "generic","0","0" "grid","3","5","Guidance: Grid Popup Keyboard Interaction","Guidance: Grid (Interactive Tabular Data and Layout Containers) Pattern","Guidance: Grid and Table Properties","Example: Date Picker Combobox","Example: Editable Combobox with Grid Popup","Example: Date Picker Dialog","Example: Data Grid","Example: Layout Grid" "gridcell","0","3","Example: Editable Combobox with Grid Popup","Example: Layout Grid","Example: Treegrid Email Inbox" -"group","2","10","Guidance: Radio Group Pattern","Guidance: For Radio Group Contained in a Toolbar","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Checkbox (Two State)","Example: Listbox with Grouped Options","Example: Editor Menubar","Example: Color Viewer Slider","Example: Date Picker Spin Button","Example: Switch Using HTML Button","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties","Example: Navigation Treeview" +"group","2","10","Guidance: Radio Group Pattern","Guidance: For Radio Group Contained in a Toolbar","Example: Auto-Rotating Image Carousel with Buttons for Slide Control","Example: Checkbox (Two State)","Example: Listbox with Grouped Options","Example: Editor Menubar","Example: Color Viewer Slider","Example: (Deprecated) Date Picker Spin Button","Example: Switch Using HTML Button","Example: File Directory Treeview Using Computed Properties","Example: File Directory Treeview Using Declared Properties","Example: Navigation Treeview" "heading","0","0" "img","0","0" "input","0","0" @@ -62,7 +62,7 @@ "searchbox","0","0" "separator","0","1","Example: Editor Menubar" "slider","2","5","Guidance: Slider (Multi-Thumb) Pattern","Guidance: Slider Pattern","Example: Horizontal Multi-Thumb Slider","Example: Color Viewer Slider","Example: Rating Slider","Example: Media Seek Slider","Example: Vertical Temperature Slider" -"spinbutton","1","2","Guidance: Spinbutton Pattern","Example: Date Picker Spin Button","Example: Toolbar" +"spinbutton","1","3","Guidance: Spinbutton Pattern","Example: (Deprecated) Date Picker Spin Button","Example: Quantity Spin Button","Example: Toolbar" "status","0","0" "switch","1","3","Guidance: Switch Pattern","Example: Switch Using HTML Button","Example: Switch Using HTML Checkbox Input","Example: Switch" "tab","1","4","Guidance: Keyboard Navigation Between Components (The Tab Sequence)","Example: Auto-Rotating Image Carousel with Tabs for Slide Control","Example: Experimental Tabs with Action Buttons","Example: Tabs with Automatic Activation","Example: Tabs with Manual Activation" diff --git a/content/index/index.html b/content/index/index.html index 6972acf2d9..4bda7d3a8f 100644 --- a/content/index/index.html +++ b/content/index/index.html @@ -168,7 +168,7 @@

    Examples by Role

  • Listbox with Grouped Options
  • Editor Menubar (HC)
  • Color Viewer Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • Switch Using HTML Button (HC)
  • File Directory Treeview Using Computed Properties
  • File Directory Treeview Using Declared Properties
  • @@ -363,7 +363,8 @@

    Examples by Role

    spinbutton @@ -537,6 +538,7 @@

    Examples By Properties and States

  • Actions Menu Button Using aria-activedescendant (HC)
  • Actions Menu Button Using element.focus() (HC)
  • Navigation Menu Button (HC)
  • +
  • Quantity Spin Button
  • Tabs with Automatic Activation (HC)
  • Tabs with Manual Activation (HC)
  • Toolbar
  • @@ -574,6 +576,7 @@

    Examples By Properties and States

    @@ -638,7 +641,8 @@

    Examples By Properties and States

  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • +
  • Quantity Spin Button
  • Switch Using HTML Button (HC)
  • Switch Using HTML Checkbox Input (HC)
  • Switch (HC)
  • @@ -664,7 +668,7 @@

    Examples By Properties and States

  • Navigation Menubar (HC)
  • Rating Radio Group (HC)
  • Horizontal Multi-Thumb Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • Table
  • Toolbar
  • Treegrid Email Inbox
  • @@ -703,7 +707,7 @@

    Examples By Properties and States

  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • Switch Using HTML Button (HC)
  • Tabs with Automatic Activation (HC)
  • Tabs with Manual Activation (HC)
  • @@ -862,7 +866,8 @@

    Examples By Properties and States

  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • +
  • Quantity Spin Button
  • Toolbar
  • @@ -877,7 +882,8 @@

    Examples By Properties and States

  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • +
  • Quantity Spin Button
  • Toolbar
  • @@ -892,7 +898,8 @@

    Examples By Properties and States

  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • +
  • Quantity Spin Button
  • Toolbar
  • @@ -904,7 +911,7 @@

    Examples By Properties and States

  • Rating Slider (HC)
  • Media Seek Slider (HC)
  • Vertical Temperature Slider (HC)
  • -
  • Date Picker Spin Button
  • +
  • (Deprecated) Date Picker Spin Button
  • Toolbar
  • diff --git a/content/patterns/spinbutton/examples/css/quantity-spinbutton.css b/content/patterns/spinbutton/examples/css/quantity-spinbutton.css new file mode 100644 index 0000000000..1ad2e330ef --- /dev/null +++ b/content/patterns/spinbutton/examples/css/quantity-spinbutton.css @@ -0,0 +1,145 @@ +.spinners { + --length-s: 0.25rem; + --length-m: 0.5rem; + --color-field-background: white; + --color-button-background-idle: color-mix(in srgb, ghostwhite, darkblue 10%); + --color-button-background-hover: color-mix(in srgb, ghostwhite, darkblue 20%); + --color-interactive-focus: var(--wai-green, #005a6a); + --transition-duration-snappy: 0; + --transition-duration-leisurely: 0; + + @media (prefers-reduced-motion: no-preference) and (forced-colors: none) { + --transition-duration-snappy: 0.15s; + --transition-duration-leisurely: 0.5s; + } + + @media (forced-colors: active) { + --color-interactive-focus: Highlight; + } + + display: inline-flex; + font-family: system-ui, sans-serif; + line-height: 1.4; + padding: 1rem; + background-color: color-mix(in srgb, ghostwhite, darkblue 1%); + border: 1px solid color-mix(in srgb, ghostwhite, darkblue 10%); + border-radius: 0.5rem; + + *, + *::before, + *::after { + box-sizing: border-box; + margin: 0; + padding: 0; + } + + .visually-hidden { + border: 0; + clip: rect(0 0 0 0); + height: auto; + margin: 0; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + white-space: nowrap; + } + + fieldset { + padding: 0.5rem; + border: 1px solid transparent; + } + + legend { + font-size: 1.2rem; + font-weight: bold; + margin-block-end: 1rem; + } + + .spinner-fields { + display: flex; + flex-wrap: wrap; + gap: 2ch; + } + + .spinner-field { + display: flex; + flex-direction: column; + gap: 0.5rem; + + label { + font-size: 1.2rem; + } + + small { + font-size: 1rem; + } + } + + .spinner { + display: flex; + flex-wrap: wrap; + gap: 0; + max-inline-size: calc(100vw - 6.4rem); + font-size: 1.4rem; + border: 1px solid color-mix(in srgb, ghostwhite, darkblue 60%); + border-radius: 0.25rem; + padding: 0.125em; + background-color: var(--color-field-background); + outline: 0 solid transparent; + outline-offset: 0; + transition: + outline-offset var(--transition-duration-snappy) ease, + outline-width var(--transition-duration-snappy) ease, + outline-color var(--transition-duration-snappy) ease, + border-color var(--transition-duration-snappy) ease; + + &:focus-within { + outline: var(--length-s) solid var(--color-interactive-focus); + outline-offset: var(--length-s); + } + + input, + button { + appearance: none; + font: inherit; + font-weight: bold; + color: inherit; + border: none; + background: transparent; + padding: 0.25em 0.5em; + margin: 0; + outline: none; + border-radius: 0; + } + + [role="spinbutton"] { + text-align: center; + min-inline-size: 4ch; + max-inline-size: fit-content; + field-sizing: content; + font-variant-numeric: tabular-nums; + + &, + &:hover, + &:focus { + border: none; + } + } + + button { + min-inline-size: 3ch; + background-color: var(--color-button-background-idle); + + &:hover { + background-color: var(--color-button-background-hover); + } + + &[aria-disabled="true"] { + opacity: 0.25; + background-color: transparent; + cursor: not-allowed; + } + } + } +} diff --git a/content/patterns/spinbutton/examples/datepicker-spinbuttons.html b/content/patterns/spinbutton/examples/datepicker-spinbuttons.html index 906b15aed4..86e3af0d9b 100644 --- a/content/patterns/spinbutton/examples/datepicker-spinbuttons.html +++ b/content/patterns/spinbutton/examples/datepicker-spinbuttons.html @@ -3,7 +3,7 @@ - Date Picker Spin Button Example + (Deprecated) Date Picker Spin Button Example @@ -27,10 +27,17 @@
    -

    Date Picker Spin Button Example

    +

    (Deprecated) Date Picker Spin Button Example

    About This Example

    +
    +

    Deprecation Warning

    +

    + This pattern has been deprecated, and will be removed in a future version of the ARIA Authoring Practices. + The Quantity Spin Button should be used as an alternative to this pattern. +

    +

    The following example uses the Spin Button Pattern to implement a date picker. diff --git a/content/patterns/spinbutton/examples/js/quantity-spinbutton.js b/content/patterns/spinbutton/examples/js/quantity-spinbutton.js new file mode 100644 index 0000000000..85c54354eb --- /dev/null +++ b/content/patterns/spinbutton/examples/js/quantity-spinbutton.js @@ -0,0 +1,110 @@ +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: quantity-spinbutton.js + */ + +'use strict'; + +class SpinButton { + constructor(el) { + this.el = el; + this.id = el.id; + this.controls = Array.from( + document.querySelectorAll(`button[aria-controls="${this.id}"]`) + ); + this.output = document.querySelector(`output[for="${this.id}"]`); + this.timer = null; + this.setBounds(); + el.addEventListener('input', () => this.setValue(el.value)); + el.addEventListener('blur', () => this.setValue(el.value, true)); + el.addEventListener('keydown', (e) => this.handleKey(e)); + this.controls.forEach((btn) => + btn.addEventListener('click', () => this.handleClick(btn)) + ); + this.setValue(el.value); + } + + clamp(n) { + return Math.min(Math.max(n, this.min), this.max); + } + + parseValue(raw) { + const s = String(raw).trim(); + if (!s) return null; + const n = parseInt(s.replace(/[^\d-]/g, ''), 10); + return isNaN(n) ? null : n; + } + + setBounds() { + const el = this.el; + this.hasMin = el.hasAttribute('aria-valuemin'); + this.hasMax = el.hasAttribute('aria-valuemax'); + this.min = this.hasMin + ? +el.getAttribute('aria-valuemin') + : Number.MIN_SAFE_INTEGER; + this.max = this.hasMax + ? +el.getAttribute('aria-valuemax') + : Number.MAX_SAFE_INTEGER; + } + + setValue(raw, onBlur = false) { + let val = typeof raw === 'number' ? raw : this.parseValue(raw); + val = + val === null ? (onBlur && this.hasMin ? this.min : '') : this.clamp(val); + this.el.value = val; + this.el.setAttribute('aria-valuenow', val); + this.updateButtonStates(); + } + + updateButtonStates() { + const val = +this.el.value; + this.controls.forEach((btn) => { + const op = btn.getAttribute('data-spinbutton-operation'); + btn.setAttribute( + 'aria-disabled', + (op === 'decrement' ? val <= this.min : val >= this.max) + ? 'true' + : 'false' + ); + }); + } + + announce() { + if (!this.output) return; + this.output.textContent = this.el.value; + clearTimeout(this.timer); + this.timer = setTimeout(() => { + this.output.textContent = ''; + this.timer = null; + }, this.output.dataset.selfDestruct || 1000); + } + + handleKey(e) { + let v = +this.el.value || 0; + if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { + e.preventDefault(); + this.setValue(v + (e.key === 'ArrowUp' ? 1 : -1)); + } else if (e.key === 'Home') { + e.preventDefault(); + this.setValue(this.min); + } else if (e.key === 'End') { + e.preventDefault(); + this.setValue(this.max); + } + } + + handleClick(btn) { + const dir = + btn.getAttribute('data-spinbutton-operation') === 'decrement' ? -1 : 1; + this.setValue((+this.el.value || 0) + dir); + this.announce(); + } +} + +window.addEventListener('load', () => + document + .querySelectorAll('[role="spinbutton"]') + .forEach((el) => new SpinButton(el)) +); diff --git a/content/patterns/spinbutton/examples/quantity-spinbutton.html b/content/patterns/spinbutton/examples/quantity-spinbutton.html new file mode 100644 index 0000000000..0fb92b5b36 --- /dev/null +++ b/content/patterns/spinbutton/examples/quantity-spinbutton.html @@ -0,0 +1,421 @@ + + + + + + Quantity Spin Button Example + + + + + + + + + + + + + + + +

    +
    +

    Quantity Spin Button Example

    + +
    +

    About This Example

    + +

    + The following example uses the Spin Button Pattern to implement three quantity inputs. +

    +

    Similar examples include:

    +
      +
    • Toolbar Example: A toolbar that contains a spin button for setting font size.
    • +
    +
    + +
    +
    +

    Example

    +
    + +
    +
    +
    + Guests +
    +
    + +
    + + + +
    + 1 to 8 + +
    +
    + +
    + + + +
    + 0 to 8 + +
    +
    + +
    + + + +
    + 0 to 12 + +
    +
    +
    +
    +
    + +
    + +
    +

    Accessibility Features

    +
      +
    • + The element with role spinbutton allows text input, for + users who prefer to enter a value directly. +
    • +
    • + The spin button input and its adjacent buttons are visually + presented as a singular form field containing an editable value, an + increment button, and a + decrement button. +
    • +
    • + When either the spin button input or its adjacent buttons have + received focus, a single visual focus indicator encompasses all + three, reinforcing the relationship between then. +
    • +
    • + For users who have not set a preference for reduced motion, the + focus indicator appears with subtle animation to draw attention. +
    • +
    • + The increment and decrement buttons: +
        +
      • + Are generously sized for ease of use. +
      • +
      • + Are adjacent to the spin button input so they can be accessed by + users of touch-based and voice-based assistive technologies. +
      • +
      • + Are labeled with the title attribute, providing a + human-friendly representation of the plus and minus characters + for users of voice control and touch screen. The + title attribute also presents a tooltip on hover, + clarifying the meaning of each button’s icon. +
      • +
      • + Use an invisible live region to announce the updated value to + when pressed. The live region empties its contents after 3 + seconds to avoid leaving stale content in the document. +
      • +
      • + Are excluded from the page Tab sequence with + tabindex="-1" because they are redundant with the + arrow key support provided to keyboard users. +
      • +
      • + Can be activated with voice control by speaking a command such + as Click add adult. +
      • +
      +
    • +
    • + When forced colors are enabled, system + color keywords are used to ensure visibility and sufficient + contrast for the spin button’s content and interactive states. +
    • +
    +
    + +
    +

    Keyboard Support

    +

    The spin buttons provide the following keyboard support described in the Spin Button Pattern.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyFunction
    Down ArrowDecreases value by 1.
    Up ArrowIncreases value by 1.
    HomeDecreases to minimum value.
    EndIncreases to maximum value.
    Standard single line text editing keys +
      +
    • Keys used for cursor movement and text manipulation, such as Delete and Shift + Right Arrow.
    • +
    • An HTML input with type="text" is used for the spin button so the browser will provide platform-specific editing keys.
    • +
    +
    +
    + +
    +

    Role, Property, State, and Tabindex Attributes

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    spinbuttoninput[type="text"]Identifies the input[type="text"] element as a spin button.
    aria-valuenow="NUMBER"input[type="text"] +
      +
    • Indicates the current numeric value of the spin button.
    • +
    • Updated by JavaScript as users change the value of the spin button.
    • +
    +
    aria-valuemin="NUMBER"input[type="text"]Indicates the minimum allowed value for the spin button.
    aria-valuemax="NUMBER"input[type="text"]Indicates the maximum allowed value for the spin button.
    title="NAME_STRING"buttonDefines the accessible name for each increment and decrement button (Remove adult, Add adult, Remove kid, Add kid, Remove animal, and Add animal).
    + aria-controls="ID_REF" + buttonIdentifies the element whose value will be modified when the button is activated.
    tabindex="-1"buttonRemoves the increment and decrement buttons from the page Tab sequence while keeping them focusable so they can be accessed with touch-based and voice-based assistive technologies.
    aria-disabled="true"buttonSet when the minimum or maximum value has been reached to inform assistive technologies that the button has been disabled.
    aria-disabled="false"buttonSet when the value is greater than the minimum value or lesser than the maximum value.
    aria-hidden="true"spanFor assistive technology users, hides the visible minus and plus characters in the increment and decrement buttons since they are symbolic of the superior button labels provided by the title attribute.
    output +
      +
    • An element with an implicit role of status and an implicit aria-live value of polite.
    • +
    • Triggers a screen reader announcement of the output element’s updated content at the next graceful opportunity.
    • +
    • When either the increment or decrement button is pressed, the current value of the spin button is injected into the output element.
    • +
    • Its contents are emptied 2000 milliseconds after injection.
    • +
    +
    +
    + +
    +

    JavaScript and CSS Source Code

    + +
    + +
    +

    HTML Source Code

    +

    To copy the following HTML code, please open it in CodePen.

    + +
    + + +
    +
    + + diff --git a/content/patterns/spinbutton/spinbutton-pattern.html b/content/patterns/spinbutton/spinbutton-pattern.html index 58f1f3e052..564cb74a8f 100644 --- a/content/patterns/spinbutton/spinbutton-pattern.html +++ b/content/patterns/spinbutton/spinbutton-pattern.html @@ -36,7 +36,7 @@

    About This Pattern

    Example

    -

    Date Picker Spin Button Example: Illustrates a date picker made from three spin buttons for day, month, and year.

    +

    Quantity Spin Button Example: A set of three spin buttons to collect the quantities of adults, children, and animals for a hotel reservation.

    diff --git a/cspell.json b/cspell.json index 4e3e63c5b2..aa7bfc6548 100644 --- a/cspell.json +++ b/cspell.json @@ -4,6 +4,7 @@ "words": [ "accesskey", "Accesskey", + "accname", "activedescendants", "affordance", "Ahlefeldt", @@ -172,6 +173,7 @@ "Nihonium", "nodir", "norotate", + "nums", "Nurthen", "NVDA", "Obel", diff --git a/test/tests/spinbutton_quantity.js b/test/tests/spinbutton_quantity.js new file mode 100644 index 0000000000..8f9c68ee01 --- /dev/null +++ b/test/tests/spinbutton_quantity.js @@ -0,0 +1,442 @@ +const { ariaTest } = require('..'); +const { By, Key, until } = require('selenium-webdriver'); +const assertAttributeValues = require('../util/assertAttributeValues'); +const assertAriaRoles = require('../util/assertAriaRoles'); +const translatePlatformKey = require('../util/translatePlatformKeys'); + +const exampleFile = + 'content/patterns/spinbutton/examples/quantity-spinbutton.html'; + +const BUFFER_VAL = 500; // until() buffer, in milliseconds + +const ex = { + spin: { + id: 'adults', + sel: '#adults', + min: '1', + max: '8', + now: '1', + }, + + inc: { + sel: '[aria-controls="adults"][data-spinbutton-operation="increment"]', + accname: 'Add adult', + }, + + dec: { + sel: '[aria-controls="adults"][data-spinbutton-operation="decrement"]', + accname: 'Remove adult', + }, + + output: { + sel: 'output[for="adults"]', + selfDestruct: '2000', + }, +}; + +ex.inc.symbolSel = ex.inc.sel + ' > span'; +ex.dec.symbolSel = ex.dec.sel + ' > span'; +ex.inputScenarios = { + 0: ex.spin.min, + 1: ex.spin.min, + 4: '4', + 8: ex.spin.max, + 13: ex.spin.max, + abc: ex.spin.min, + '-7': '7', + ' ': ex.spin.min, +}; + +// Attributes + +ariaTest( + 'role="spinbutton" on input element', + exampleFile, + 'spinbutton-role', + async (t) => { + await assertAriaRoles(t, 'example', 'spinbutton', '3', 'input'); + } +); + +ariaTest( + '"aria-valuemin" represents the minimum value on spinbuttons', + exampleFile, + 'spinbutton-aria-valuemin', + async (t) => { + await assertAttributeValues(t, ex.spin.sel, 'aria-valuemin', ex.spin.min); + } +); + +ariaTest( + '"aria-valuemax" represents the maximum value on spinbuttons', + exampleFile, + 'spinbutton-aria-valuemax', + async (t) => { + await assertAttributeValues(t, ex.spin.sel, 'aria-valuemax', ex.spin.max); + } +); + +ariaTest( + '"aria-valuenow" reflects spinbutton value as a number', + exampleFile, + 'spinbutton-aria-valuenow', + async (t) => { + await assertAttributeValues(t, ex.spin.sel, 'aria-valuenow', ex.spin.now); + } +); + +ariaTest( + '"tabindex=-1" removes increment and decrement buttons from page tab order', + exampleFile, + 'button-tabindex', + async (t) => { + await assertAttributeValues(t, ex.inc.sel, 'tabindex', '-1'); + await assertAttributeValues(t, ex.dec.sel, 'tabindex', '-1'); + } +); + +ariaTest( + 'increment and decrement buttons use aria-controls to reference spinner', + exampleFile, + 'button-aria-controls', + async (t) => { + await assertAttributeValues(t, ex.inc.sel, 'aria-controls', ex.spin.id); + await assertAttributeValues(t, ex.dec.sel, 'aria-controls', ex.spin.id); + } +); + +ariaTest( + '"title" provides accessible name for the increment and decrement buttons', + exampleFile, + 'button-title', + async (t) => { + await assertAttributeValues(t, ex.inc.sel, 'title', ex.inc.accname); + await assertAttributeValues(t, ex.dec.sel, 'title', ex.dec.accname); + } +); + +ariaTest( + '"aria-hidden" hides symbolic accname equivalent from screen reader users', + exampleFile, + 'span-aria-hidden', + async (t) => { + await assertAttributeValues(t, ex.inc.symbolSel, 'aria-hidden', 'true'); + await assertAttributeValues(t, ex.dec.symbolSel, 'aria-hidden', 'true'); + } +); + +ariaTest('output element exists', exampleFile, 'output', async (t) => { + let output = await t.context.queryElements(t, ex.output.sel); + + t.is( + output.length, + 1, + 'One output element should be found by selector: ' + ex.output.sel + ); +}); + +// keys + +ariaTest('end', exampleFile, 'spinbutton-end', async (t) => { + const spinner = await t.context.session.findElement(By.css(ex.spin.sel)); + const max = parseInt(ex.spin.max); + + // Send end key + await spinner.sendKeys(Key.END); + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + max, + `After sending end key, aria-valuenow should be the maximum value: ${max}` + ); + + // Check that the decrement button is not disabled. + await assertAttributeValues(t, ex.dec.sel, 'aria-disabled', 'false'); + + // Check that the increment button is disabled. + await assertAttributeValues(t, ex.inc.sel, 'aria-disabled', 'true'); +}); + +ariaTest('home', exampleFile, 'spinbutton-home', async (t) => { + const spinner = await t.context.session.findElement(By.css(ex.spin.sel)); + const min = parseInt(ex.spin.min); + + // Send home key + await spinner.sendKeys(Key.HOME); + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + min, + `After sending home key, aria-valuenow should be the minimum value: ${min}` + ); + + // Check that the decrement button is disabled. + await assertAttributeValues(t, ex.dec.sel, 'aria-disabled', 'true'); + + // Check that the increment button is not disabled. + await assertAttributeValues(t, ex.inc.sel, 'aria-disabled', 'false'); +}); + +ariaTest('up arrow', exampleFile, 'spinbutton-up-arrow', async (t) => { + const spinner = await t.context.session.findElement(By.css(ex.spin.sel)); + const min = parseInt(ex.spin.min); + const max = parseInt(ex.spin.max); + let val = min; + + // Send home key + await spinner.sendKeys(Key.HOME); + + // Send arrow keys up to and including the maximum value. + while (++val <= max) { + await spinner.sendKeys(Key.ARROW_UP); + + // Check that aria-valuenow is updated correctly. + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + val, + `After sending ${val - 1} up arrows, aria-valuenow should be ${val}` + ); + } + + // Check that the decrement button is no longer disabled. + await assertAttributeValues(t, ex.dec.sel, 'aria-disabled', 'false'); + + // Check that the increment button is now disabled. + await assertAttributeValues(t, ex.inc.sel, 'aria-disabled', 'true'); + + // Send one more and check that aria-valuenow remains at the maximum value. + await spinner.sendKeys(Key.ARROW_UP); + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + max, + `After sending one more up arrow, aria-valuenow should still be ${max}` + ); + + // Check that the decrement button is still not disabled + await assertAttributeValues(t, ex.dec.sel, 'aria-disabled', 'false'); + + // Check that the increment button is still disabled + await assertAttributeValues(t, ex.inc.sel, 'aria-disabled', 'true'); +}); + +ariaTest('down arrow', exampleFile, 'spinbutton-down-arrow', async (t) => { + const spinner = await t.context.session.findElement(By.css(ex.spin.sel)); + const min = parseInt(ex.spin.min); + const max = parseInt(ex.spin.max); + let val = max; + + // Send end key + await spinner.sendKeys(Key.END); + + // Send arrow keys down to and including the minimum value. + while (--val >= min) { + await spinner.sendKeys(Key.ARROW_DOWN); + + // Check that aria-valuenow is updated correctly. + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + val, + `After sending ${val + 1} down arrows, aria-valuenow should be ${val}` + ); + } + + // Check that the decrement button is now disabled. + await assertAttributeValues(t, ex.dec.sel, 'aria-disabled', 'true'); + + // Check that the increment button is no longer disabled. + await assertAttributeValues(t, ex.inc.sel, 'aria-disabled', 'false'); + + // Send one more and check that aria-valuenow remains at the maximum value. + await spinner.sendKeys(Key.ARROW_DOWN); + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + min, + `After sending one more up arrow, aria-valuenow should still be ${min}` + ); + + // Check that the decrement button is still disabled. + await assertAttributeValues(t, ex.dec.sel, 'aria-disabled', 'true'); + + // Check that the increment button is still not disabled. + await assertAttributeValues(t, ex.inc.sel, 'aria-disabled', 'false'); +}); + +// text input + +ariaTest( + 'Expected behavior for direct input of numeric values', + exampleFile, + 'standard-single-line-editing-keys', + async (t) => { + const spinner = await t.context.session.findElement(By.css(ex.spin.sel)); + const min = parseInt(ex.spin.min); + const max = parseInt(ex.spin.max); + const selectAllKeys = translatePlatformKey([Key.CONTROL, 'a']); + const selectAllChord = Key.chord(...selectAllKeys); + + for (const inputScenario of Object.entries(ex.inputScenarios)) { + const [input, expected] = inputScenario; + + // Input the value + await spinner.clear(); + await spinner.sendKeys(selectAllChord); + await spinner.sendKeys(input); + + // Force blur after input + await t.context.session.executeScript((el) => el.blur(), spinner); + + // Check that aria-valuenow is updated correctly. + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + parseInt(expected), + `After inputting “${input}”, aria-valuenow should be ${expected}` + ); + + // Check that the decrement button has the expected aria-disabled state. + await assertAttributeValues( + t, + ex.dec.sel, + 'aria-disabled', + parseInt(expected) <= parseInt(min) ? 'true' : 'false' + ); + + // Check that the increment button has the expected aria-disabled state. + await assertAttributeValues( + t, + ex.inc.sel, + 'aria-disabled', + parseInt(expected) >= parseInt(max) ? 'true' : 'false' + ); + } + } +); + +// increment and decrement buttons + +ariaTest('increment button', exampleFile, 'increment-button', async (t) => { + const spinner = await t.context.session.findElement(By.css(ex.spin.sel)); + const output = await t.context.session.findElement(By.css(ex.output.sel)); + const button = await t.context.session.findElement(By.css(ex.inc.sel)); + const min = parseInt(ex.spin.min); + const max = parseInt(ex.spin.max); + let val = min; + + // Send home key + await spinner.sendKeys(Key.HOME); + + // Click increment button up to and including the maximum value. + while (++val <= max) { + await button.click(); + + // Check that aria-valuenow is updated correctly. + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + val, + `After clicking ${val - 1} times, aria-valuenow should be ${val}` + ); + + // Check that the output element has the expected value. + t.is( + await output.getText(), + String(val), + `After clicking ${val - 1} times, output should be ${val}` + ); + } + + // Check that the decrement button is no longer disabled. + await assertAttributeValues(t, ex.dec.sel, 'aria-disabled', 'false'); + + // Check that the increment button is now disabled. + await assertAttributeValues(t, ex.inc.sel, 'aria-disabled', 'true'); + + // Send one more and check that aria-valuenow remains at the maximum value. + await button.click(); + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + max, + `After clicking once more, aria-valuenow should still be ${max}` + ); + + // Check that the output element has the expected value. + t.is( + await output.getText(), + String(max), + `After clicking once more, output should still be ${max}` + ); + + // Check that the decrement button is still not disabled + await assertAttributeValues(t, ex.dec.sel, 'aria-disabled', 'false'); + + // Check that the increment button is still disabled + await assertAttributeValues(t, ex.inc.sel, 'aria-disabled', 'true'); + + // Wait for the output to self-destruct + await t.context.session.wait( + until.elementTextIs(output, ''), + output.selfDestruct + BUFFER_VAL + ); + t.pass('After waiting for the output self-destruct timer, output is empty'); +}); + +ariaTest('decrement button', exampleFile, 'decrement-button', async (t) => { + const spinner = await t.context.session.findElement(By.css(ex.spin.sel)); + const output = await t.context.session.findElement(By.css(ex.output.sel)); + const button = await t.context.session.findElement(By.css(ex.dec.sel)); + const min = parseInt(ex.spin.min); + const max = parseInt(ex.spin.max); + let val = max; + + // Send end key + await spinner.sendKeys(Key.END); + + // Click decrement button down to and including the minimum value. + while (--val >= min) { + await button.click(); + + // Check that aria-valuenow is updated correctly. + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + val, + `After clicking ${val + 1} times, aria-valuenow should be ${val}` + ); + + // Check that the output element has the expected value. + t.is( + await output.getText(), + String(val), + `After clicking ${val + 1} times, output should be ${val}` + ); + } + + // Check that the decrement button is now disabled. + await assertAttributeValues(t, ex.dec.sel, 'aria-disabled', 'true'); + + // Check that the increment button is no longer disabled. + await assertAttributeValues(t, ex.inc.sel, 'aria-disabled', 'false'); + + // Send one more and check that aria-valuenow remains at the maximum value. + await button.click(); + t.is( + parseInt(await spinner.getAttribute('aria-valuenow')), + min, + `After clicking once more, aria-valuenow should still be ${min}` + ); + + // Check that the output element has the expected value. + t.is( + await output.getText(), + String(min), + `After clicking once more, output should still be ${min}` + ); + + // Check that the decrement button is still disabled + await assertAttributeValues(t, ex.dec.sel, 'aria-disabled', 'true'); + + // Check that the increment button is still not disabled + await assertAttributeValues(t, ex.inc.sel, 'aria-disabled', 'false'); + + // Wait for the output to self-destruct + await t.context.session.wait( + until.elementTextIs(output, ''), + output.selfDestruct + BUFFER_VAL + ); + t.pass('After waiting for the output self-destruct timer, output is empty'); +});