react-data-grid icon indicating copy to clipboard operation
react-data-grid copied to clipboard

Cannot tab into cell contents

Open kycutler opened this issue 3 years ago • 2 comments

Describe the bug

RDG captures Tab key presses and manually implements navigation, suppressing the default browser behavior. This is problematic because cell contents such as links or buttons are not accessible by keyboard.

To Reproduce

  1. Render a link in column formatter
  2. Notice that you cannot access the link in the grid via keyboard

Link to code example: https://codesandbox.io/s/purple-glade-ipvy62

Expected behavior

Tabbing should use the browser behavior as much as possible so that cell contents can be tabbed into normally

Environment

  • react-data-grid version: 7.0.0-beta.18
  • react/react-dom version: 18.2.0

Additional context

kycutler avatar Sep 19 '22 19:09 kycutler

This is by design so the grid can control cell navigation but it allows custom formatters to define focus behavior. Here is the updated example https://codesandbox.io/s/runtime-water-noucsp?file=/src/App.js

function LinkFormatter({ isCellSelected, row }) {
  const { ref, tabIndex } = useFocusRef(isCellSelected);
  return (
    <a ref={ref} tabIndex={tabIndex} href={row.href}>
      {row.name}
    </a>
  );
}

amanmahajan7 avatar Sep 19 '22 20:09 amanmahajan7

@amanmahajan7 thanks for the example! I didn't know about the useFocusRef hook.

I was able to get something working -- It's a bit more complicated than the example I posted since we actually have a complex component inside the cell, so there are multiple focusable elements, and we can't modify everything since it's from a library.

My workaround was to use the useFocusRef hook as in your example and then add an onKeyDown listener to selectively prevent tab events from bubbling up to RDG:

if (ev.key === "Tab" && ref.current) {
  const focusableElements = ref.current.querySelectorAll(
    'a:not([disabled]), area:not([disabled]), button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex="0"]:not([disabled])'
  );
  const focusedElement = document.activeElement;

  // If we are focused on the root and will tab out, let the event bubble up.
  if (focusedElement === ref.current && (ev.shiftKey || focusableElements.length === 0)) {
    return;
  }

  // If we are tabbing forwards and are at the last focusable child, let the event bubble up.
  if (
    focusableElements.length > 0 &&
    focusedElement === focusableElements[focusableElements.length - 1] &&
    !ev.shiftKey
  ) {
    return;
  }

  // Otherwise, prevent the event from bubbling to RDG so that the browser tabbing behavior is applied.
  ev.stopPropagation();
}

It's not perfect (for example, tabbing backwards into a cell then requires you to tab forwards into the contents), but it will do for now.

If you have any other ideas for how to improve this, I would love to hear them. Otherwise I think this issue can be closed!

kycutler avatar Sep 19 '22 22:09 kycutler

This is the best solution as RDG cannot handle all the cases. Closing it for now .

amanmahajan7 avatar Dec 23 '22 16:12 amanmahajan7