From b5c3bd3b029922567a67bd9caf67f619c6073cbc Mon Sep 17 00:00:00 2001 From: Matt King Date: Mon, 6 Oct 2025 15:45:05 -0700 Subject: [PATCH 1/9] Create initial draft of HTML for example page. --- .../coverage-and-quality-report.html | 42 +- .../coverage-and-quality/prop-coverage.csv | 8 +- .../coverage-and-quality/role-coverage.csv | 4 +- content/index/index.html | 1 + .../examples/listbox-scrollable-actions.html | 401 ++++++++++++++++++ 5 files changed, 445 insertions(+), 11 deletions(-) create mode 100644 content/patterns/listbox/examples/listbox-scrollable-actions.html 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 519a0dabe2..4a27fbac1a 100644 --- a/content/about/coverage-and-quality/coverage-and-quality-report.html +++ b/content/about/coverage-and-quality/coverage-and-quality-report.html @@ -391,6 +391,7 @@

Roles with More than One Guidance or Exa
  • (Deprecated) Collapsible Dropdown Listbox
  • Listbox with Grouped Options
  • Listboxes with Rearrangeable Options
  • +
  • Experimental Scrollable Listbox with Actions on Options
  • Scrollable Listbox
  • @@ -540,6 +541,7 @@

    Roles with More than One Guidance or Exa
  • (Deprecated) Collapsible Dropdown Listbox
  • Listbox with Grouped Options
  • Listboxes with Rearrangeable Options
  • +
  • Experimental Scrollable Listbox with Actions on Options
  • Scrollable Listbox
  • @@ -845,6 +847,7 @@

    Properties and States with More than One
  • (Deprecated) Collapsible Dropdown Listbox
  • Listbox with Grouped Options
  • Listboxes with Rearrangeable Options
  • +
  • Experimental Scrollable Listbox with Actions on Options
  • Scrollable Listbox
  • Actions Menu Button Using aria-activedescendant (HC)
  • Radio Group Using aria-activedescendant (HC)
  • @@ -1010,6 +1013,7 @@

    Properties and States with More than One
  • Button (IDL Version)
  • Listbox with Grouped Options
  • Listboxes with Rearrangeable Options
  • +
  • Experimental Scrollable Listbox with Actions on Options
  • Scrollable Listbox
  • Editor Menubar (HC)
  • Horizontal Multi-Thumb Slider (HC)
  • @@ -1072,6 +1076,7 @@

    Properties and States with More than One
  • (Deprecated) Collapsible Dropdown Listbox
  • Listbox with Grouped Options
  • Listboxes with Rearrangeable Options
  • +
  • Experimental Scrollable Listbox with Actions on Options
  • Scrollable Listbox
  • Actions Menu Button Using aria-activedescendant (HC)
  • Actions Menu Button Using element.focus() (HC)
  • @@ -1198,6 +1203,7 @@

    Properties and States with More than One
  • (Deprecated) Collapsible Dropdown Listbox
  • Listbox with Grouped Options
  • Listboxes with Rearrangeable Options
  • +
  • Experimental Scrollable Listbox with Actions on Options
  • Scrollable Listbox
  • Experimental Tabs with Action Buttons (HC)
  • Tabs with Automatic Activation (HC)
  • @@ -1301,7 +1307,7 @@

    Coding Summary

    Total Examples - 63 + 64 High Contrast Documentation @@ -1317,7 +1323,7 @@

    Coding Summary

    Uses currentColor value - 29 + 30 + + + + + + + + + + + + + + +
    +

    Experimental Example of Scrollable Listbox with Actions on Options

    + +
    +

    About This Experimental Example

    + +

    + This is an experimental implementation of the draft specification of the aria-actions attribute. + The aria-actions property enables an element to reference one or more interactive elements that can be activated to perform an action on the referencing element. + In this example, each option element in the listbox references several buttons that perform actions on the option. + The relationship provided by aria-actions enables an assistive technology to both communicate the availability of the action button and provide a command for activating the button while focus is on the tab. +

    +

    + The following example implementation of the Listbox Pattern demonstrates a scrollable single-select listbox widget. + This widget is functionally similar to an HTML select input where the size attribute has a value greater than one. +

    +

    + This example also demonstrates how to provide buttons that provide contextual actions for each item in the list. + Each option has an associated set of four actions that enable users to move an option up or down, favorite it, or remove it from the list. + The contextual actions are provided by buttons that appear on hover or focus. + The buttons are referenced by aria-actions specified on the option element, which enables them to be discovered and activated by an assistive technology user while focus is on the option. +

    +

    Similar examples include:

    + +
    + +
    +
    +

    Example

    +
    + +
    +

    Choose your favorite transuranic element (actinide or transactinide).

    +
    +
    + Transuranium elements: +
      +
    • + + None +
    • +
    • + + Neptunium +
    • +
    • + + Plutonium +
    • +
    • + + Americium +
    • +
    • + + Curium +
    • +
    • + + Berkelium +
    • +
    • + + Californium +
    • +
    • + + Einsteinium +
    • +
    • + + Fermium +
    • +
    • + + Mendelevium +
    • +
    • + + Nobelium +
    • +
    • + + Lawrencium +
    • +
    • + + Rutherfordium +
    • +
    • + + Dubnium +
    • +
    • + + Seaborgium +
    • +
    • + + Bohrium +
    • +
    • + + Hassium +
    • +
    • + + Meitnerium +
    • +
    • + + Darmstadtium +
    • +
    • + + Roentgenium +
    • +
    • + + Copernicium +
    • +
    • + + Nihonium +
    • +
    • + + Flerovium +
    • +
    • + + Moscovium +
    • +
    • + + Livermorium +
    • +
    • + + Tennessine +
    • +
    • + + Oganesson +
    • +
    +
    +
    +
    + +
    + +
    +

    Accessibility Features

    +
      +
    • + 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.
      • +
      +
    • +
    +
    + +
    +

    Keyboard Support

    +

    + 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. +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyFunction
    TabMoves focus into and out of the listbox.
    Down ArrowMoves focus to and selects the next option.
    Up ArrowMoves focus to and selects the previous option.
    Right Arrow +
      +
    • If an option has focus, moves focus to the first action button.
    • +
    • If an action button has focus, moves focus to the next action button for the current option.
    • +
    • If the last action button has focus, does nothing.
    • +
    +
    Left Arrow +
      +
    • If any action button except for the first action button has focus, moves focus to the previous action button for the current option.
    • +
    • If the first action button has focus, moves focus to the current option.
    • +
    • If an option has focus, does nothing.
    • +
    +
    HomeMoves focus to and selects the first option.
    EndMoves 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.
    • +
    +
    +
    + +
    +

    Role, Property, State, and Tabindex Attributes

    +

    + 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. +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    listboxulIdentifies the focusable element that has listbox behaviors and contains the listbox options.
    aria-labelledby="ID_REF"ulRefers to the element containing the listbox label.
    tabindex="0"ulIncludes the listbox in the page tab sequence.
    aria-activedescendant="ID_REF"ul +
      +
    • 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.
    • +
    • + For more information about this focus management technique, see + Managing Focus in Composites Using aria-activedescendant. +
    • +
    +
    optionliIdentifies each selectable element containing the name of an option.
    aria-selected="true"li +
      +
    • Indicates that the option is selected.
    • +
    • Applied to the element with role option that is visually styled as selected.
    • +
    • The option with this attribute is always the same as the option that is referenced by aria-activedescendant because it is a single-select listbox where selection follows focus.
    • +
    +
    aria-actions=" "li +
      +
    • Indicates to assistive technologies that the option will have actions available on focus or hover, enabling the assistive technology to focus the element to gain access to the actions.
    • +
    • Applied to all elements with role option when they are not visually styled as selected and are not referenced by aria-activedescendant.
    • +
    +
    aria-actions="ID_REFS"li +
      +
    • Specifies the ID attributes of the action buttons that are associated with the option.
    • +
    • Applied to the element with role option that is visually styled as selected and is referenced by aria-activedescendant.
    • +
    +
    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. +
    +
    + +
    +

    JavaScript and CSS Source Code

    + +
    + +
    +

    HTML Source Code

    +

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

    + +
    + + +
    +
    + + From d6bada33e4569c517bd0a79a8c3d4fffeabe5a19 Mon Sep 17 00:00:00 2001 From: Matt King Date: Mon, 6 Oct 2025 15:53:24 -0700 Subject: [PATCH 2/9] Addmissing closing `td` tags --- .../patterns/listbox/examples/listbox-scrollable-actions.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/patterns/listbox/examples/listbox-scrollable-actions.html b/content/patterns/listbox/examples/listbox-scrollable-actions.html index 822effd0f5..eb637bf058 100644 --- a/content/patterns/listbox/examples/listbox-scrollable-actions.html +++ b/content/patterns/listbox/examples/listbox-scrollable-actions.html @@ -238,6 +238,7 @@

    Keyboard Support

  • If an action button has focus, moves focus to the next action button for the current option.
  • If the last action button has focus, does nothing.
  • + Left Arrow @@ -247,6 +248,7 @@

    Keyboard Support

  • If the first action button has focus, moves focus to the current option.
  • If an option has focus, does nothing.
  • + Home From d2fe5c3a6452f0c27db9986ceba6887f5d40dae1 Mon Sep 17 00:00:00 2001 From: Matt King Date: Mon, 6 Oct 2025 20:26:59 -0700 Subject: [PATCH 3/9] Remove space from empty aria-actions attribute in roles, states, and props table. --- .../patterns/listbox/examples/listbox-scrollable-actions.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/patterns/listbox/examples/listbox-scrollable-actions.html b/content/patterns/listbox/examples/listbox-scrollable-actions.html index eb637bf058..74706e81af 100644 --- a/content/patterns/listbox/examples/listbox-scrollable-actions.html +++ b/content/patterns/listbox/examples/listbox-scrollable-actions.html @@ -341,7 +341,7 @@

    Role, Property, State, and Tabindex Attributes

    - aria-actions=" " + aria-actions="" li
      From 81a58156410e88ef0c7743f4d8c0d612eeb428a5 Mon Sep 17 00:00:00 2001 From: Curt Bellew Date: Thu, 16 Oct 2025 13:39:07 -0600 Subject: [PATCH 4/9] Initial addition of actions items --- .../listbox/examples/css/listbox-actions.css | 78 +++++ .../listbox/examples/js/listbox-actions.js | 277 ++++++++++++++++++ .../examples/listbox-scrollable-actions.html | 212 ++++++++++++-- 3 files changed, 540 insertions(+), 27 deletions(-) create mode 100644 content/patterns/listbox/examples/css/listbox-actions.css create mode 100644 content/patterns/listbox/examples/js/listbox-actions.js diff --git a/content/patterns/listbox/examples/css/listbox-actions.css b/content/patterns/listbox/examples/css/listbox-actions.css new file mode 100644 index 0000000000..56ef0d8c32 --- /dev/null +++ b/content/patterns/listbox/examples/css/listbox-actions.css @@ -0,0 +1,78 @@ +[role="option"] span.actions { + display: none; +} +[role="listbox"]:focus [role="option"].focused span.actions, [role="option"]:hover span.actions { + position: absolute; + right: 0.5em; + display: inline; +} +[role="option"] span[role="option"]:hover span.actions { + position: absolute; + right: 0.5em; + display: inline; +} +span.uparrow::before { + content: "↑"; + min-width: 24px; + min-height: 24px; + display: inline-block; + text-align: center; + padding: 2px; +} +span.downarrow::before { + content: "↓"; + min-width: 24px; + min-height: 24px; + display: inline-block; + text-align: center; + padding: 2px; +} +span.favorite[aria-pressed="true"]::before { + content: "★"; + min-width: 24px; + min-height: 24px; + display: inline-block; + text-align: center; + padding: 2px; +} +span.favorite:not([aria-pressed])::before, span.favorite[aria-pressed="false"]::before { + content: "☆"; + min-width: 24px; + min-height: 24px; + display: inline-block; + text-align: center; + padding: 2px; +} +span.delete::before { + content: "🗑"; + min-width: 24px; + min-height: 24px; + display: inline-block; + text-align: center; + padding: 2px; +} +[role="option"] [role="button"]:hover, .focusedActionButton { + background-color: rgb(226, 239, 225); + color: black; + outline-offset: 2px; + white-space: pre; + /* outline: #036 solid 4px; */ + padding-top: 10px; + padding-bottom: 7px; + border: 3px solid #036; + margin-top: 2px; +} +.listbox-area { + display: block; + padding: 20px; + border-width: 1px; + border-style: solid; + border-color: rgb(170, 170, 170); + border-image: initial; + border-radius: 4px; + background: rgb(238, 238, 238); + max-width: 20em; +} +.hide-actions-button { + display: none; +} \ No newline at end of file diff --git a/content/patterns/listbox/examples/js/listbox-actions.js b/content/patterns/listbox/examples/js/listbox-actions.js new file mode 100644 index 0000000000..708b1844df --- /dev/null +++ b/content/patterns/listbox/examples/js/listbox-actions.js @@ -0,0 +1,277 @@ +/* + * 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 || {}; + +/** + * @class + * @description + * ListboxActions object representing the state and interactions for a listbox widget + * @param listboxActionsNode + * The DOM node pointing to the listbox + */ + +aria.ListboxActions = class ListboxActions { + constructor(listboxActionsNode) { + this.listboxActionsNode = listboxActionsNode; + this.activeDescendant = this.listboxActionsNode.getAttribute( + 'aria-activedescendant' + ); + this.registerActionsEvents(); + this.listboxOptionArray = Array.from(this.listboxActionsNode.querySelectorAll('[role="option"]')); + this.handleItemChange = function () {}; + this.listboxItemCurrent = null; + this.activeDescendant = null; + this.listboxActiveOption = null; + this.listboxCurrentItemActionsButtons = []; + this.listboxCurrentOptionIndex = -1; + this.listboxItemsWithAriaActionsArray = []; + + } + + registerActionsEvents() { + this.listboxActionsNode.addEventListener('keydown', this.checkKeyPressActions.bind(this)); + let buttons = this.listboxActionsNode.querySelectorAll('[role="button"]:not(.hide-actions-button)'); + for (let i = 0;i < buttons.length;i++) { + buttons[i].addEventListener('click', this.checkClickItemActions.bind(this)); + } + } + /** + * Check if the selected option is in view, and scroll if not + */ + updateScroll() { + const selectedOption = document.getElementById(this.activeDescendant); + if (selectedOption) { + const scrollBottom = + this.listboxActionsNode.clientHeight + this.listboxActionsNode.scrollTop; + const elementBottom = + selectedOption.offsetTop + selectedOption.offsetHeight; + if (elementBottom > scrollBottom) { + this.listboxActionsNode.scrollTop = + elementBottom - this.listboxActionsNode.clientHeight; + } else if (selectedOption.offsetTop < this.listboxActionsNode.scrollTop) { + this.listboxActionsNode.scrollTop = selectedOption.offsetTop; + } + selectedOption.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + } + } + /** + * @description + * Defocus the specified item + * @param element + * The element to defocus + */ + defocusActionsItem(element) { + if (!element) { + return; + } + element.classList.remove('focusedActionButton'); + } + /** + * @description + * set aria-activedescendant attribute and at listbox + * @param element + * The element to defocus + */ + setActiveDescendant(element) { + if (!element) { + return; + } + this.listboxActionsNode.setAttribute('aria-activedescendant', element.id); + this.activeDescendant = element.id; + } + + /** + * @description + * Focus on the specified item + * @param element + * The element to focus + */ + focusActionsItem(element) { + let li = element.closest('[role="option"]'); + //let ad = li.querySelector('#'+this.activeDescendant); + //let ad = document.getElementById(this.activeDescendant); + //this.defocusActionsItem(ad); + element.classList.add('focusedActionButton'); + this.setActiveDescendant(element); + } + /** + * @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; + } + const currentItem = document.getElementById(this.activeDescendant).closest('[role="option"]'); + const previousItem = currentItem.previousElementSibling; + this.listboxOptionArray = Array.from(this.listboxActionsNode.querySelectorAll('[role="option"]')); + if (previousItem) { + this.listboxActionsNode.insertBefore(currentItem, previousItem); + this.handleItemChange('moved_up', [currentItem]); + /** Hides the down arrow for item moved to the end of list and unhides item moved up from bottom */ + if (this.listboxOptionArray.indexOf(previousItem) == this.listboxOptionArray.length-2) { + currentItem.querySelector('.downarrow').classList.remove('hide-actions-button'); + currentItem.querySelector('.downarrow').classList.remove('focusedActionButton'); + previousItem.querySelector('.downarrow').classList.add('hide-actions-button'); + this.activeDescendant = currentItem.id; + } + /** Hides the up arrow for item moved to the top of list and unhides item moved down from top */ + if (this.listboxOptionArray.indexOf(previousItem) == 1) { + currentItem.querySelector('.uparrow').classList.add('hide-actions-button'); + currentItem.querySelector('.uparrow').classList.remove('focusedActionButton'); + previousItem.querySelector('.uparrow').classList.remove('hide-actions-button'); + this.setActiveDescendant(currentItem); + } + } + } + /** + * @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; + } + var currentItem = document.getElementById(this.activeDescendant).closest('[role="option"]'); + var nextItem = currentItem.nextElementSibling; + this.listboxOptionArray = Array.from(this.listboxActionsNode.querySelectorAll('[role="option"]')); + if (nextItem) { + this.listboxActionsNode.insertBefore(nextItem, currentItem); + this.handleItemChange('moved_down', [currentItem]); + /** Hides the down arrow for item moved to the end of list and unhides item moved up from bottom */ + if (this.listboxOptionArray.indexOf(nextItem) == this.listboxOptionArray.length-1) { + currentItem.querySelector('.downarrow').classList.add('hide-actions-button'); + currentItem.querySelector('.downarrow').classList.remove('focusedActionButton'); + nextItem.querySelector('.downarrow').classList.remove('hide-actions-button'); + this.setActiveDescendant(currentItem); + } + /** Hides the up arrow for item moved to the top of list and unhides item moved down from top */ + if (this.listboxOptionArray.indexOf(nextItem) == 2) { + currentItem.querySelector('.uparrow').classList.remove('hide-actions-button'); + currentItem.querySelector('.uparrow').classList.remove('focusedActionButton'); + nextItem.querySelector('.uparrow').classList.add('hide-actions-button'); + this.activeDescendant = currentItem.id; + } + } + } + + /** + * @description + * Delete, Move or Favorite the currently selected items. + */ + doActionButtonEvents (evt, activeButton) { + let activeButtonClasslist = Array.from(activeButton.classList); + const index = activeButtonClasslist.indexOf('focusedActionButton'); + if (index > -1) { + activeButtonClasslist.splice(index, 1); + } + switch(activeButtonClasslist[0]) { + case 'delete': + this.listboxItemCurrent.remove(); + for (let i = 0;i < this.listboxCurrentItemActionsButtons.length;i++) { + this.defocusActionsItem(this.listboxCurrentItemActionsButtons[i]); + } + for (let i = 0;i < this.listboxItemsWithAriaActionsArray.length; i++) { + this.listboxItemsWithAriaActionsArray[i].setAttribute('aria-actions',''); + } + break; + case 'favorite': + let isPressed = activeButton.ariaPressed && activeButton.ariaPressed == 'true'; + activeButton.setAttribute('aria-pressed',(!isPressed).toString()); + break; + case 'uparrow': + this.moveUpItems(); + break; + case 'downarrow': + this.moveDownItems(); + break; + } + this.updateScroll(); + } + checkKeyPressActions(evt) { + let listitemCurrentItemActionsButtonPosition, listboxCurrentItemActionsButton; + this.listboxItemCurrent = this.listboxActionsNode.querySelector('.focused'); + this.activeDescendant = this.listboxActionsNode.getAttribute('aria-activedescendant'); + this.listboxActiveOption = this.listboxActionsNode.querySelector('.focused'); + this.listboxCurrentItemActionsButtons = Array.from(this.listboxItemCurrent.querySelectorAll('[role="button"]:not(.hide-actions-button)')); + this.listboxItemsWithAriaActionsArray = evt.currentTarget.querySelectorAll('[aria-actions]'); + switch (evt.key) { + case 'ArrowUp': + case 'ArrowDown': + for (let i = 0;i < this.listboxCurrentItemActionsButtons.length;i++) { + this.defocusActionsItem(this.listboxCurrentItemActionsButtons[i]); + } + for (let i = 0;i < this.listboxItemsWithAriaActionsArray.length; i++) { + this.listboxItemsWithAriaActionsArray[i].setAttribute('aria-actions',''); + } + this.listboxItemCurrent.setAttribute('aria-actions',this.listboxCurrentItemActionsButtons.map(node => node.id).join(' ')); + break; + case 'ArrowLeft': + case 'ArrowRight': + for (let i = 0;i < this.listboxCurrentItemActionsButtons.length;i++) { + this.defocusActionsItem(this.listboxCurrentItemActionsButtons[i]); + } + listboxCurrentItemActionsButton = this.listboxActionsNode.querySelector('#'+this.activeDescendant); + if (listboxCurrentItemActionsButton && this.listboxCurrentItemActionsButtons.find(elem => elem.id === this.activeDescendant)) { + listitemCurrentItemActionsButtonPosition = this.listboxCurrentItemActionsButtons.findIndex(elem => elem.id === this.activeDescendant); + } else { + listitemCurrentItemActionsButtonPosition = -1; + } + if (evt.key == 'ArrowLeft') { + if (listitemCurrentItemActionsButtonPosition > 0) { + this.focusActionsItem(this.listboxCurrentItemActionsButtons[listitemCurrentItemActionsButtonPosition-1]); + } + } else if (evt.key == 'ArrowRight') { + if (listitemCurrentItemActionsButtonPosition < 0) { + this.focusActionsItem(this.listboxCurrentItemActionsButtons[0]); + } else if (listitemCurrentItemActionsButtonPosition < (this.listboxCurrentItemActionsButtons.length-1)) { + this.focusActionsItem(this.listboxCurrentItemActionsButtons[listitemCurrentItemActionsButtonPosition+1]); + } + } + break; + case 'Enter': + let activeButton = evt.currentTarget.ariaActiveDescendantElement?evt.currentTarget.ariaActiveDescendantElement:evt.currentTarget; + this.doActionButtonEvents (evt, activeButton); + break; + default: + break; + } + } + checkClickItemActions(evt) { + evt.preventDefault(); + let button = evt.currentTarget; + if (button.role == 'button') { + evt.key = 'Enter'; + this.setActiveDescendant(evt.srcElement); + let previousFocus = this.listboxActionsNode.querySelectorAll('.focused'); + let prev; + for (let i = 0;i < previousFocus.length; i++){ + prev = previousFocus[i]; + prev.classList.remove('focused'); + } + let newFocus = evt.srcElement.closest('[role="option"]'); + newFocus.classList.add('focused'); + this.checkKeyPressActions(evt); + } else { + let listboxNode = evt.srcElement.closest('[role="option"]'); + listboxNode.click(); + } + + } +}; +window.addEventListener('load', function () { + new aria.ListboxActions(document.getElementById('ss_elem_list')); +}); \ No newline at end of file diff --git a/content/patterns/listbox/examples/listbox-scrollable-actions.html b/content/patterns/listbox/examples/listbox-scrollable-actions.html index 74706e81af..ff059b6f80 100644 --- a/content/patterns/listbox/examples/listbox-scrollable-actions.html +++ b/content/patterns/listbox/examples/listbox-scrollable-actions.html @@ -15,8 +15,10 @@ + +
    From f14799e2cca286fe2a59ea2da9f973b890d85a39 Mon Sep 17 00:00:00 2001 From: Curt Bellew Date: Tue, 2 Dec 2025 16:43:27 -0700 Subject: [PATCH 8/9] Updated button look and feel --- .../listbox/examples/css/listbox-actions.css | 19 ++++++++++++++++--- .../listbox/examples/js/listbox-actions.js | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/content/patterns/listbox/examples/css/listbox-actions.css b/content/patterns/listbox/examples/css/listbox-actions.css index 2369dbcd16..4e1415979f 100644 --- a/content/patterns/listbox/examples/css/listbox-actions.css +++ b/content/patterns/listbox/examples/css/listbox-actions.css @@ -18,7 +18,18 @@ span[role="option"]:hover span.actions { right: 0.5em; display: inline; } - +button.actionbutton { + display: inline-block; + border-radius: 5px; + text-align: center; + color: #222428; + font-size: 14px; + line-height: 1.5em; + margin-right: 0.25em; + background-color: buttonface; + padding: 0px 5px 0px 5px; + margin-top: -3px; +} button.actionbutton::before { display: flex; justify-content: center; @@ -51,10 +62,12 @@ button.delete::before { [role="option"] button:hover, .focusedActionButton { background-color: rgb(226, 239, 225); - color: black; + color: black!important; outline-offset: 0px; white-space: pre; - outline: #036 solid 3px; + outline-offset: 0px; + outline: #036 solid 3px!important; + border-color: rgb(0, 90, 156); } .listbox-area { diff --git a/content/patterns/listbox/examples/js/listbox-actions.js b/content/patterns/listbox/examples/js/listbox-actions.js index 2f473d5250..65bc3960b4 100644 --- a/content/patterns/listbox/examples/js/listbox-actions.js +++ b/content/patterns/listbox/examples/js/listbox-actions.js @@ -28,7 +28,7 @@ aria.ListboxActions = class ListboxActions { var updateText = ''; switch (event) { case 'removed': - updateText = 'Removed ' + items[0].innerText; + updateText = 'Deleted ' + items[0].innerText; break; case 'moved_up': case 'moved_down': From 45905bbe5817701e521a78f31a2c588cc3d92eea Mon Sep 17 00:00:00 2001 From: Curt Bellew Date: Tue, 2 Dec 2025 17:38:33 -0700 Subject: [PATCH 9/9] Linting fixes --- .../patterns/listbox/examples/css/listbox-actions.css | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/content/patterns/listbox/examples/css/listbox-actions.css b/content/patterns/listbox/examples/css/listbox-actions.css index 4e1415979f..97915b8cbc 100644 --- a/content/patterns/listbox/examples/css/listbox-actions.css +++ b/content/patterns/listbox/examples/css/listbox-actions.css @@ -13,11 +13,6 @@ top: -3px; } -span[role="option"]:hover span.actions { - position: absolute; - right: 0.5em; - display: inline; -} button.actionbutton { display: inline-block; border-radius: 5px; @@ -27,9 +22,10 @@ button.actionbutton { line-height: 1.5em; margin-right: 0.25em; background-color: buttonface; - padding: 0px 5px 0px 5px; + padding: 0px 5px; margin-top: -3px; } + button.actionbutton::before { display: flex; justify-content: center; @@ -63,7 +59,6 @@ button.delete::before { .focusedActionButton { background-color: rgb(226, 239, 225); color: black!important; - outline-offset: 0px; white-space: pre; outline-offset: 0px; outline: #036 solid 3px!important;