Search code examples
javascripthtmlmdc-components

MDC menu - keep menu open when focus is on input


I am building an autocomplete input using mdc web components. I have an input textfield and a menu. You can see the codesandox here. I am showing the menu on focusin event, but if I start writing in the input field the menu is closed immediately.

const input = new MDCTextField(document.querySelector(".mdc-text-field"));
const menu = new MDCMenu(document.querySelector(".mdc-menu"));

input.listen("focusin", () => (menu.open = true));

I assume that is because of the menu default behaviour to close when click outside of the menu is triggered. The problem here is also that focus is taken by the first menu item from the textfield. What would be the way to prevent that, and have menu open until the focus is lost on the input field?


Solution

  • The issue here has to do with accessibility, this is what's happening in order:

    1. The input is getting the client "focus" when you click into the input.

    2. This triggers the "focusin" event which is opening the menu.

    3. Then, according to the accessibility documentation defined here, the MDCMenu will automatically focus the first menu item.

    To stop this focus from happening automatically, we can set

    menu.setDefaultFocusState(DefaultFocusState.NONE);
    

    DefaultFocusState.NONE

    Does not change the focus. Set this if you do not want the menu to grab focus on open. (Autocomplete dropdown menu, for example).

    This would be great if it was that simple, but now the issue is that if the MDCMenu closes automatically when it doesn't have focus.

    [edit]: this next section is incorrect, the anchor doesn't achor it open just anchor it around an element

    To solve this next problem, we anchor the MDCMenu to the input element (has to be the native element):

    menu.setAnchorElement(document.querySelector(".mdc-text-field"));
    

    Finally we need to tell the MDCMenu to close after focus is taken out of the input:

    input.listen("focusout", () => {
      menu.open = false;
    });
    

    There might be some other issues that you can tweak but I think that should get you close. There is a shared example of code which also uses MDCMenu to make an 'autocomplete dropdown menu" which you can reference here: https://gist.dreamtobe.cn/gpulido/4bae80a5be4fd5c7ed61f1f1667da039

    Here's the code with my changes that you can use in your codesandbox:

    import { MDCTextField } from "@material/textfield";
    import { MDCMenu, DefaultFocusState, Corner } from "@material/menu";
    import "./styles.scss";
    
    const input = new MDCTextField(document.querySelector(".mdc-text-field"));
    const menu = new MDCMenu(document.querySelector(".mdc-menu"));
    menu.setDefaultFocusState(DefaultFocusState.NONE);
    menu.setAnchorCorner(Corner.BOTTOM_START);
    menu.setAnchorElement(input.component);
    
    input.listen("focusin", () => {
      menu.open = true;
    });
    input.listen("click", () => {
      menu.open = true;
    });
    input.listen("focusout", () => {
      menu.open = false;
    });