elastic / eui

Elastic UI Framework 🙌
https://eui.elastic.co/
Other
6.1k stars 840 forks source link

How to set my own focus within EuiContextMenu #8094

Open vera opened 5 days ago

vera commented 5 days ago

EUI version: v88.5.4

I am using an EuiContextMenu with nested panels, similarly to the first example here: https://eui.elastic.co/#/navigation/context-menu

At the lowest nesting level, the panels contain input fields (e.g. text input). I'd like the input field to be autofocused when I navigate to one of those panels. However, currently, when I try to autofocus the input field (using autoFocus or inputRef.current.focus()), the focus is always stolen by the header link.

Image

I found this comment describing that the autofocus may be disabled by setting initialFocusedItemIndex={-1} https://github.com/elastic/eui/issues/5942#issuecomment-1147750414 but it seems that was not merged/does not work.

Is there any way to achieve my desired behavior? Thanks.

cee-chen commented 2 days ago

Excellent find on that GitHub thread! It provides a lot of solid context around why we likely want to keep the current focus default.

However, currently, when I try to autofocus the input field (using autoFocus or inputRef.current.focus()), the focus is always stolen by the header link.

Since this is an edge case for most consumers/keyboard users, could I suggest wrapping your manual .focus() in a setTimeout() to beat the component's initial focus? Something like:

<EuiFieldText
  inputRef={(el) => {
    // Wrap in a setTimeout to beat EuiContextMenu's initial focus logic
    if (el) setTimeout(() => el.focus(), 300);
  }}
/>

Example CodeSandbox: https://codesandbox.io/p/sandbox/bold-water-p6rq9m?file=%2Fdemo.js%3A100%2C17

vera commented 2 days ago

Thanks! I've done a similar solution:

useEffect(() => {
    const handleFocusIn = () => {
      // The EuiContextMenu always wants to autofocus its own panel title and will steal the focus from the input field
      // So we need to wait for the panel title focus to happen, and then steal the focus ourselves
      if (document.activeElement?.className == "euiContextMenuPanelTitle") {
        inputRef.current?.focus();
        // Don't steal the focus again, so the panel title remains focusable (e.g. by tabbing)
        document.removeEventListener("focusin", handleFocusIn);
      }
    };

    document.addEventListener("focusin", handleFocusIn);
  }, []);

This works but it feels a bit like a hack. So I still think it would be nice to bring back the initialFocusedItemIndex={-1} for special cases where the developer would like to set the focus themselves.

cee-chen commented 2 days ago

That's a fair request. I'll resurrect the abandoned commit with this use case in mind!