Search code examples
contenteditablekeyboard-eventsvertical-scrollingtabindexreact-virtualized

Are there any issues using Arrow Key Stepper to scroll when the rows have tabIndex or contentEditable enabled?


We've implemented React-Vitualized using single column Grid with rows that are enabled for keyboard event (onKeyUp/onKeyDown/onKeyPress). We are using Arrow-Key-Stepper to enable ArrowUp/ArrowDown scrolling of the rows.

All works quite well even with PgUp/PgDn, Home/End, Space and Shift-Space. However, when we add either tabIndex and/or contenteditable attributes to the rows (required for keyboard events), scrolling freezes when the focused rows scrolls out-of-view and removed from the DOM. We can regain control by using Tab key and/or the mouse.

QUESTION: Why is tabIndex/contenteditable attributes causing scrolling failure?

I am not allowed to reproduce the code publicly. Not asking for solution nor code but more an opinion from a more experienced source. This is the last issue for our implementation of this widget library, which has been very good thus far.

Appreciate any suggest/opinion.


Solution

  • However, when we add either tabIndex and/or contenteditable attributes to the rows (required for keyboard events), scrolling freezes when the focused rows scrolls out-of-view and removed from the DOM.

    I'm curious why the rows themselves need this attribute? The outer container rendered by ArrowKeyStepper has a tab-index, in order to be focusable. This is how ArrowKeyStepper listens to keyboard events.

    If the row is focused, then when it goes out of the viewport, ArrowKeyStepper is left listening to ... nothing. The way to fix this is to use a class component to render your cell and conditionally self-focusing:

    class Cell extends Component {
      componentDidMount = () => this._maybeFocus();
      componentDidUpdate = () => this._maybeFocus();
    
      render() {
        const {
          columnIndex,
          rowIndex,
          scrollToColumn,
          scrollToRow,
          style,
        } = this.props;
    
        return (
          <div
            style={style}
            ref={this._setRef}
            tabIndex={1}
          >
            {/* Your content here... */}
          </div>
        );
      };
    
      _maybeFocus = () => {
        const {
          columnIndex,
          rowIndex,
          scrollToColumn,
          scrollToRow,
        } = this.props;
    
        if (
          columnIndex === scrollToColumn &&
          rowIndex === scrollToRow
        ) {
          this._ref.focus();
        }
      }
    
      _setRef = ref => {
        this._ref = ref;
      };
    }