Search code examples
angularaccessibilityag-grid

tabbing into custom cell renderers in ag-grid


I have an angular implementation of ag-grid where I have some custom cell renderers. They render things from buttons to toggle statuses to links to other parts of the application.

I recently ran into a problem where keyboard only users are not able to tab into the contents of these cell renderers to "click" the element. I have tried putting a tabindex="0" and/or an href="#" onto these elements so that they appear in the tab order for the page, but the only way they can receive focus is by clicking with a mouse.

It seems that ag-grid overrides the default behavior of the keyboard, perhaps to add addtl functionality with arrow keys, etc. But it doesn't seem to play well with these custom renderers...

Below is a sample of a simplified version of one of these renderers.

@Component({
  selector: 'fave-renderer',
  template: `
    <i class="icon fave"
       tabindex="0"
       (click)="toggleFave()"></i>
  `,
  styles: [``]
})
export class FaveRendererComponent implements ICellRendererAngularComp {
  public params: any;

  agInit(params: any): void {
    this.params = params;
  }

  constructor(private resultsService: ResultsService) {}

  refresh(): boolean {return false;}

  toggleFave() {/*implementation not important... */}
}


Solution

  • So I ended up doing a sort of hacky workaround to fix this issue. What is happening is that ag-grid implements custom keyboard events. For example, Enter will start editing a cell, the Arrow keys will navigate through the grid, Tab navigates to the next cell, and so on.

    Because ag-grid overrides the default keyboard events, any nested cell renderers with focusable elements will never receive focus while tabbing because the Tab key is being handled by ag-grid. To prevent this, I added a callback for the Suppress Keyboard Events handler.

    In the callback, I only allowed ag-grid to handle the tab navigation when the currently focused element being tabbed away from is the last focusable child of the cell (or is the cell itself in the case of shift-tab).

    suppressKeyboardEvent(params: SuppressKeyboardEventParams) {
      const e = params.event;
      if (e.code == 'Tab' || e.key == 'Tab') {
        //get focusable children of parent cell
        let focusableChildrenOfParent = e.srcElement.closest(".ag-cell")
          .querySelectorAll('button, [href], :not(.ag-hidden) > input, select, textarea, [tabindex]:not([tabindex="-1"])');
    
        if (focusableChildrenOfParent.length == 0 ||
          (e.shiftKey == false && e.srcElement == focusableChildrenOfParent[focusableChildrenOfParent.length - 1]) ||
          (e.shiftKey == true && e.srcElement == focusableChildrenOfParent[0]) ||
          (e.shiftKey == true && e.srcElement.classList.contains("ag-cell")))
          return false; //do not suppress
        return true; //suppress
      }
      return false; //do not suppress by default
    }