Improve focus handling with dropdown menus (#11511)
- Focus first item when activated via keyboard - When the dropdown menu closes, give back the focus to the actual element which was focused prior to opening the menu
This commit is contained in:
		
							parent
							
								
									ac33f1aedd
								
							
						
					
					
						commit
						396b8cdd0f
					
				
					 2 changed files with 44 additions and 7 deletions
				
			
		| 
						 | 
				
			
			@ -45,7 +45,6 @@ class DropdownMenu extends React.PureComponent {
 | 
			
		|||
    document.addEventListener('click', this.handleDocumentClick, false);
 | 
			
		||||
    document.addEventListener('keydown', this.handleKeyDown, false);
 | 
			
		||||
    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
			
		||||
    this.activeElement = document.activeElement;
 | 
			
		||||
    if (this.focusedItem && this.props.openedViaKeyboard) {
 | 
			
		||||
      this.focusedItem.focus();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -56,9 +55,6 @@ class DropdownMenu extends React.PureComponent {
 | 
			
		|||
    document.removeEventListener('click', this.handleDocumentClick, false);
 | 
			
		||||
    document.removeEventListener('keydown', this.handleKeyDown, false);
 | 
			
		||||
    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
 | 
			
		||||
    if (this.activeElement) {
 | 
			
		||||
      this.activeElement.focus();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setRef = c => {
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +113,7 @@ class DropdownMenu extends React.PureComponent {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleItemKeyUp = e => {
 | 
			
		||||
  handleItemKeyPress = e => {
 | 
			
		||||
    if (e.key === 'Enter' || e.key === ' ') {
 | 
			
		||||
      this.handleClick(e);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -147,7 +143,7 @@ class DropdownMenu extends React.PureComponent {
 | 
			
		|||
 | 
			
		||||
    return (
 | 
			
		||||
      <li className='dropdown-menu__item' key={`${text}-${i}`}>
 | 
			
		||||
        <a href={href} target={target} data-method={method} rel='noopener' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyUp={this.handleItemKeyUp} data-index={i}>
 | 
			
		||||
        <a href={href} target={target} data-method={method} rel='noopener' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
 | 
			
		||||
          {text}
 | 
			
		||||
        </a>
 | 
			
		||||
      </li>
 | 
			
		||||
| 
						 | 
				
			
			@ -214,15 +210,44 @@ export default class Dropdown extends React.PureComponent {
 | 
			
		|||
    } else {
 | 
			
		||||
      const { top } = target.getBoundingClientRect();
 | 
			
		||||
      const placement = top * 2 < innerHeight ? 'bottom' : 'top';
 | 
			
		||||
 | 
			
		||||
      this.props.onOpen(this.state.id, this.handleItemClick, placement, type !== 'click');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleClose = () => {
 | 
			
		||||
    if (this.activeElement) {
 | 
			
		||||
      this.activeElement.focus();
 | 
			
		||||
      this.activeElement = null;
 | 
			
		||||
    }
 | 
			
		||||
    this.props.onClose(this.state.id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleMouseDown = () => {
 | 
			
		||||
    if (!this.state.open) {
 | 
			
		||||
      this.activeElement = document.activeElement;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleButtonKeyDown = (e) => {
 | 
			
		||||
    switch(e.key) {
 | 
			
		||||
    case ' ':
 | 
			
		||||
    case 'Enter':
 | 
			
		||||
      this.handleMouseDown();
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleKeyPress = (e) => {
 | 
			
		||||
    switch(e.key) {
 | 
			
		||||
    case ' ':
 | 
			
		||||
    case 'Enter':
 | 
			
		||||
      this.handleClick(e);
 | 
			
		||||
      e.stopPropagation();
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleItemClick = e => {
 | 
			
		||||
    const i = Number(e.currentTarget.getAttribute('data-index'));
 | 
			
		||||
    const { action, to } = this.props.items[i];
 | 
			
		||||
| 
						 | 
				
			
			@ -266,6 +291,9 @@ export default class Dropdown extends React.PureComponent {
 | 
			
		|||
          size={size}
 | 
			
		||||
          ref={this.setTargetRef}
 | 
			
		||||
          onClick={this.handleClick}
 | 
			
		||||
          onMouseDown={this.handleMouseDown}
 | 
			
		||||
          onKeyDown={this.handleButtonKeyDown}
 | 
			
		||||
          onKeyPress={this.handleKeyPress}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,7 @@ export default class IconButton extends React.PureComponent {
 | 
			
		|||
    onClick: PropTypes.func,
 | 
			
		||||
    onMouseDown: PropTypes.func,
 | 
			
		||||
    onKeyDown: PropTypes.func,
 | 
			
		||||
    onKeyPress: PropTypes.func,
 | 
			
		||||
    size: PropTypes.number,
 | 
			
		||||
    active: PropTypes.bool,
 | 
			
		||||
    pressed: PropTypes.bool,
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +45,12 @@ export default class IconButton extends React.PureComponent {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleKeyPress = (e) => {
 | 
			
		||||
    if (this.props.onKeyPress && !this.props.disabled) {
 | 
			
		||||
      this.props.onKeyPress(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleMouseDown = (e) => {
 | 
			
		||||
    if (!this.props.disabled && this.props.onMouseDown) {
 | 
			
		||||
      this.props.onMouseDown(e);
 | 
			
		||||
| 
						 | 
				
			
			@ -100,6 +107,7 @@ export default class IconButton extends React.PureComponent {
 | 
			
		|||
          onClick={this.handleClick}
 | 
			
		||||
          onMouseDown={this.handleMouseDown}
 | 
			
		||||
          onKeyDown={this.handleKeyDown}
 | 
			
		||||
          onKeyPress={this.handleKeyPress}
 | 
			
		||||
          style={style}
 | 
			
		||||
          tabIndex={tabIndex}
 | 
			
		||||
          disabled={disabled}
 | 
			
		||||
| 
						 | 
				
			
			@ -121,6 +129,7 @@ export default class IconButton extends React.PureComponent {
 | 
			
		|||
            onClick={this.handleClick}
 | 
			
		||||
            onMouseDown={this.handleMouseDown}
 | 
			
		||||
            onKeyDown={this.handleKeyDown}
 | 
			
		||||
            onKeyPress={this.handleKeyPress}
 | 
			
		||||
            style={style}
 | 
			
		||||
            tabIndex={tabIndex}
 | 
			
		||||
            disabled={disabled}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue