Skip to content
This repository has been archived by the owner on May 24, 2024. It is now read-only.

Commit

Permalink
[Terra-Dropdown] VO does not announce Expanded state and Index of lis…
Browse files Browse the repository at this point in the history
…t items while navigating through arrow keys (#3805)
  • Loading branch information
AH106586Harika authored Jul 17, 2023
1 parent 70ccf4f commit c6b9a36
Show file tree
Hide file tree
Showing 104 changed files with 629 additions and 409 deletions.
4 changes: 2 additions & 2 deletions packages/terra-dropdown-button/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

## Unreleased

* Added
* Added focus styles for dropdown list items.
* Added
* Added focus styles and screenreader support to announce expand/collapse state and index for dropdown list items.

## 1.34.0 - (May 9, 2023)

Expand Down
29 changes: 7 additions & 22 deletions packages/terra-dropdown-button/src/DropdownButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,18 @@ class DropdownButton extends React.Component {
this.setListNode = this.setListNode.bind(this);
this.toggleDropDown = this.toggleDropDown.bind(this);
this.state = {
isOpen: false, isActive: false, openedViaKeyboard: false, selectText: '',
isOpen: false, isActive: false, selectText: '',
};
}

handleDropdownButtonClick(event) {
if (this.state.isOpen) {
this.setState({ openedViaKeyboard: false });
}
this.toggleDropDown(event);
this.setState({ selectText: '' });
}

handleDropdownRequestClose(callback) {
const onSelectCallback = typeof callback === 'function' ? callback : undefined;
this.setState({ isOpen: false, openedViaKeyboard: false, isActive: false }, onSelectCallback);
this.setState({ isOpen: false, isActive: false }, onSelectCallback);
}

handleKeyDown(event) {
Expand All @@ -91,25 +89,14 @@ class DropdownButton extends React.Component {
}
if (event.keyCode === KeyCode.KEY_SPACE || event.keyCode === KeyCode.KEY_RETURN) {
// In FireFox active styles don't get applied on space
this.setState({ isActive: true, openedViaKeyboard: true });
this.setState({ isActive: true });
/*
Prevent the callback from being called repeatedly if the RETURN or SPACE key is held down.
The keyDown event of native html button triggers Onclick() event on RETURN or SPACE key press.
where holding RETURN key for longer time will call dropdownClick() event repeatedly which would cause
the dropdown to open and close itself.
*/
event.preventDefault();
} else if (event.keyCode === KeyCode.KEY_DOWN && this.state.isOpen && !this.state.openedViaKeyboard) {
// set focus to first list element on down arrow key press only when dropdown is opened by mouse click.
const listOptions = this.dropdownList.querySelectorAll('[data-terra-dropdown-list-item]');
listOptions[0].focus();
// prevent handleFocus() callback of DropdownList.
event.preventDefault();
} else if (event.keyCode === KeyCode.KEY_UP && this.state.isOpen && !this.state.openedViaKeyboard) {
// set focus to last list element on up arrow key press only when dropdown is opened by mouse click
const listOptions = this.dropdownList.querySelectorAll('[data-terra-dropdown-list-item]');
listOptions[listOptions.length - 1].focus();
event.preventDefault();
} else if (event.keyCode === KeyCode.KEY_TAB) {
this.handleDropdownRequestClose();
}
Expand Down Expand Up @@ -166,7 +153,7 @@ class DropdownButton extends React.Component {
const theme = this.context;

const {
isOpen, isActive, openedViaKeyboard, selectText,
isOpen, isActive, selectText,
} = this.state;
const selectedLabel = intl.formatMessage({ id: 'Terra.dropdownButton.selected' });
const classnames = cx(
Expand All @@ -191,9 +178,8 @@ class DropdownButton extends React.Component {
isCompact={isCompact}
isDisabled={isDisabled}
requestClose={this.handleDropdownRequestClose}
openedViaKeyboard={openedViaKeyboard}
buttonRef={this.getButtonNode}
refCallback={this.setListNode}
buttonRef={this.getButtonNode}
getSelectedOptionText={this.getSelectedOptionText}
>
<button
Expand All @@ -205,9 +191,8 @@ class DropdownButton extends React.Component {
disabled={isDisabled}
tabIndex={isDisabled ? '-1' : undefined}
aria-disabled={isDisabled}
aria-expanded={isOpen}
aria-haspopup="menu"
ref={this.setButtonNode}
aria-expanded={isOpen}
aria-label={selectText ? `${selectText}, ${selectedLabel}` : ''}
onBlur={this.handleBlur}
>
Expand Down
16 changes: 6 additions & 10 deletions packages/terra-dropdown-button/src/Item.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,13 @@ const Item = ({
}}
tabIndex="0"
role="menuitem"
className={cx(
'item',
{ active: isActive },
theme.className,
)}
>
<div
role="none"
className={cx(
'item',
{ active: isActive },
theme.className,
)}
>
{label}
</div>
{label}
</li>
);
};
Expand Down
29 changes: 5 additions & 24 deletions packages/terra-dropdown-button/src/SplitButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,21 +80,19 @@ class SplitButton extends React.Component {
this.handlePrimaryKeyUp = this.handlePrimaryKeyUp.bind(this);
this.handleCaretKeyDown = this.handleCaretKeyDown.bind(this);
this.handleCaretKeyUp = this.handleCaretKeyUp.bind(this);
this.setButtonNode = this.setButtonNode.bind(this);
this.getButtonNode = this.getButtonNode.bind(this);
this.setButtonNode = this.setButtonNode.bind(this);
this.setListNode = this.setListNode.bind(this);
this.toggleDropDown = this.toggleDropDown.bind(this);

this.state = {
isOpen: false, caretIsActive: false, primaryIsActive: false, openedViaKeyboard: false, selectText: '',
isOpen: false, caretIsActive: false, primaryIsActive: false, selectText: '',
};
}

handleDropdownButtonClick(event) {
if (this.state.isOpen) {
this.setState({ openedViaKeyboard: false });
}
this.toggleDropDown(event);
this.setState({ selectText: '' });
}

handlePrimaryButtonClick(event) {
Expand All @@ -108,7 +106,7 @@ class SplitButton extends React.Component {

handleDropdownRequestClose(callback) {
const onSelectCallback = typeof callback === 'function' ? callback : undefined;
this.setState({ isOpen: false, openedViaKeyboard: false, caretIsActive: false }, onSelectCallback);
this.setState({ isOpen: false, caretIsActive: false }, onSelectCallback);
}

/*
Expand All @@ -132,25 +130,14 @@ class SplitButton extends React.Component {
}
if (event.keyCode === KeyCode.KEY_SPACE || event.keyCode === KeyCode.KEY_RETURN) {
// In FireFox active styles don't get applied onKeyDown
this.setState({ caretIsActive: true, openedViaKeyboard: true });
this.setState({ caretIsActive: true });
/*
Prevent the callback from being called repeatedly if the RETURN or SPACE key is held down.
The keyDown event of native html button triggers Onclick() event on RETURN or SPACE key press.
where holding RETURN key for longer time will call dropdownClick() event repeatedly which would cause
the dropdown to open and close itself.
*/
event.preventDefault();
} else if (event.keyCode === KeyCode.KEY_DOWN && this.state.isOpen && !this.state.openedViaKeyboard) {
// set focus to first list element on down arrow key press only when dropdown is opened by mouse click.
const listOptions = this.dropdownList.querySelectorAll('[data-terra-dropdown-list-item]');
listOptions[0].focus();
// prevent handleFocus() callback of DropdownList.
event.preventDefault();
} else if (event.keyCode === KeyCode.KEY_UP && this.state.isOpen && !this.state.openedViaKeyboard) {
// set focus to last list element on up arrow key press only when dropdown is opened by mouse click
const listOptions = this.dropdownList.querySelectorAll('[data-terra-dropdown-list-item]');
listOptions[listOptions.length - 1].focus();
event.preventDefault();
} else if (event.keyCode === KeyCode.KEY_TAB) {
this.handleDropdownRequestClose();
}
Expand Down Expand Up @@ -180,10 +167,6 @@ class SplitButton extends React.Component {
this.setState({ selectText: selectedOptionText });
}

handleFocus = () => {
this.setState({ selectText: '' });
};

handleBlur = () => {
this.setState({ selectText: '' });
};
Expand Down Expand Up @@ -215,7 +198,6 @@ class SplitButton extends React.Component {
isOpen,
primaryIsActive,
caretIsActive,
openedViaKeyboard,
selectText,
} = this.state;

Expand Down Expand Up @@ -253,7 +235,6 @@ class SplitButton extends React.Component {
isBlock={isBlock}
isCompact={isCompact}
isDisabled={isDisabled}
openedViaKeyboard={openedViaKeyboard}
buttonRef={this.getButtonNode}
refCallback={this.setListNode}
getSelectedOptionText={this.getSelectedOptionText}
Expand Down
79 changes: 45 additions & 34 deletions packages/terra-dropdown-button/src/_Dropdown.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import FocusTrap from 'focus-trap-react';
import Hookshot from 'terra-hookshot';
Expand Down Expand Up @@ -27,52 +27,63 @@ const propTypes = {
*/
width: PropTypes.string,
/**
* Whether or not dropdown is opened using keyboard.
* Ref callback for the dropdown list DOM element.
*/
openedViaKeyboard: PropTypes.bool,
refCallback: PropTypes.func,
/**
* Callback for reference of the dropdown button
*/
buttonRef: PropTypes.func,
/**
* Ref callback for the dropdown list DOM element.
*/
refCallback: PropTypes.func,
/**
* Callback for the dropdown list selected option.
*/
getSelectedOptionText: PropTypes.func,
};

const Dropdown = ({
requestClose, isOpen, targetRef, children, width, openedViaKeyboard, buttonRef, refCallback, getSelectedOptionText,
}) => (
<Hookshot
isOpen={isOpen}
isEnabled
targetRef={targetRef}
attachmentBehavior="flip"
contentAttachment={{ vertical: 'top', horizontal: 'start' }}
targetAttachment={{ vertical: 'bottom', horizontal: 'start' }}
>
<Hookshot.Content
onEsc={requestClose}
onOutsideClick={requestClose}
>
<FocusTrap focusTrapOptions={{ returnFocusOnDeactivate: true, initialFocus: openedViaKeyboard ? '' : buttonRef, clickOutsideDeactivates: true }}>
<DropdownList
requestClose={requestClose}
width={width}
refCallback={refCallback}
getSelectedOptionText={getSelectedOptionText}
>
{children}
</DropdownList>
</FocusTrap>
</Hookshot.Content>
</Hookshot>
);
requestClose, isOpen, targetRef, children, width, refCallback, buttonRef, getSelectedOptionText,
}) => {
const buttonFocused = useRef(false);
useEffect(() => {
// added this change to bring focus back to button when dropdown list is closed.
if (buttonFocused.current && buttonRef) {
buttonFocused.current = false;
buttonRef().focus();
}
}, [isOpen, buttonRef]);

const handleClose = () => {
buttonFocused.current = true;
requestClose();
};

return (
<Hookshot
isOpen={isOpen}
isEnabled
targetRef={targetRef}
attachmentBehavior="flip"
contentAttachment={{ vertical: 'top', horizontal: 'start' }}
targetAttachment={{ vertical: 'bottom', horizontal: 'start' }}
>
<Hookshot.Content
onEsc={requestClose}
onOutsideClick={handleClose}
>
<FocusTrap focusTrapOptions={{ returnFocusOnDeactivate: true, clickOutsideDeactivates: true }}>
<DropdownList
requestClose={requestClose}
width={width}
refCallback={refCallback}
getSelectedOptionText={getSelectedOptionText}
>
{children}
</DropdownList>
</FocusTrap>
</Hookshot.Content>
</Hookshot>
);
};
Dropdown.propTypes = propTypes;

export default Dropdown;
13 changes: 3 additions & 10 deletions packages/terra-dropdown-button/src/_DropdownButtonBase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,13 @@ const propTypes = {
*/
isDisabled: PropTypes.bool,
/**
* Whether or not dropdown is opened using keyboard.
* Ref callback for the dropdown list DOM element.
*/
openedViaKeyboard: PropTypes.bool,
refCallback: PropTypes.func,
/**
* Callback for reference of the dropdown button
*/
buttonRef: PropTypes.func,
/**
* Ref callback for the dropdown list DOM element.
*/
refCallback: PropTypes.func,
/**
* Callback for the dropdown list selected option.
*/
Expand All @@ -59,7 +55,6 @@ const defaultProps = {
isBlock: false,
isCompact: false,
isDisabled: false,
openedViaKeyboard: false,
};

class DropdownButtonBase extends React.Component {
Expand Down Expand Up @@ -89,9 +84,8 @@ class DropdownButtonBase extends React.Component {
isBlock,
isCompact,
isDisabled,
openedViaKeyboard,
buttonRef,
refCallback,
buttonRef,
getSelectedOptionText,
...customProps
} = this.props;
Expand Down Expand Up @@ -126,7 +120,6 @@ class DropdownButtonBase extends React.Component {
isOpen={isOpen}
requestClose={requestClose}
width={calcWidth}
openedViaKeyboard={openedViaKeyboard}
buttonRef={buttonRef}
refCallback={refCallback}
getSelectedOptionText={getSelectedOptionText}
Expand Down
Loading

0 comments on commit c6b9a36

Please sign in to comment.