admin管理员组

文章数量:1405170

I am building a context menu with support for escape and navigation keys. However, I've noticed that if I open the context menu for the first time, the arrow navigation keys work perfectly; however, if I open it by the second time, the arrow navigation keys work in reverse order (a bug in my logic?). If I open it more times, it bugs totally and may not work anymore.

Playground

Here are related code parts:

(navigation on outermost context menu)

for (let i = 0; i < div.children.length; i++)
{
    // Child (item or submenu)
    const child = div.children[i] as HTMLElement;

    // If focused
    if (document.activeElement === child)
    {
        // navigate up
        if (Input.input.justPressed("navigateUp"))
        {
            focusPrevSibling(child);
        }
        // navigate down
        else if (Input.input.justPressed("navigateDown"))
        {
            focusNextSibling(child);
        }
        // open submenu
        else if (Input.input.justPressed(localeDir == "ltr" ? "navigateRight" : "navigateLeft") && child.classList.contains(submenuItemClassName))
        {
            (child as HTMLButtonElement).click();
        }
        
        return;
    }
}

(focusability utils)

/**
 * Focus the previous focusable sibling of an element.
 */
export function focusPrevSibling(element: HTMLElement): void
{
    const parent = element.parentElement;
    const children = Array.from(parent.children) as HTMLElement[];
    const i = children.indexOf(element);
    const list = children.slice(0, i).reverse().concat(children.slice(i + 1).reverse());
    const firstFocusable = list.find(e => canFocus(e));
    if (firstFocusable)
    {
        (firstFocusable as HTMLElement).focus();
    }
}

/**
 * Focus the next focusable sibling of an element.
 */
export function focusNextSibling(element: HTMLElement): void
{
    const parent = element.parentElement;
    const children = Array.from(parent.children) as HTMLElement[];
    const i = children.indexOf(element);
    const list = children.slice(i + 1).concat(children.slice(0, i + 1));
    const firstFocusable = list.find(e => canFocus(e));
    if (firstFocusable)
    {
        (firstFocusable as HTMLElement).focus();
    }
}

The problem here is basically that these functions like focusNextSibling are messed up next open up times. The firstFocusable that comes out is wrong in past opens.


It does seem to have to do with the Input.input event listeners that get accumulated, because since when I press for example down it outputs multiple console.logs gradually more each time I re-open the context menu; however, I have now done removeEventListener before addEventListener and it doesn't seem to work still (it keeps accumulating).

I am building a context menu with support for escape and navigation keys. However, I've noticed that if I open the context menu for the first time, the arrow navigation keys work perfectly; however, if I open it by the second time, the arrow navigation keys work in reverse order (a bug in my logic?). If I open it more times, it bugs totally and may not work anymore.

Playground

Here are related code parts:

(navigation on outermost context menu)

for (let i = 0; i < div.children.length; i++)
{
    // Child (item or submenu)
    const child = div.children[i] as HTMLElement;

    // If focused
    if (document.activeElement === child)
    {
        // navigate up
        if (Input.input.justPressed("navigateUp"))
        {
            focusPrevSibling(child);
        }
        // navigate down
        else if (Input.input.justPressed("navigateDown"))
        {
            focusNextSibling(child);
        }
        // open submenu
        else if (Input.input.justPressed(localeDir == "ltr" ? "navigateRight" : "navigateLeft") && child.classList.contains(submenuItemClassName))
        {
            (child as HTMLButtonElement).click();
        }
        
        return;
    }
}

(focusability utils)

/**
 * Focus the previous focusable sibling of an element.
 */
export function focusPrevSibling(element: HTMLElement): void
{
    const parent = element.parentElement;
    const children = Array.from(parent.children) as HTMLElement[];
    const i = children.indexOf(element);
    const list = children.slice(0, i).reverse().concat(children.slice(i + 1).reverse());
    const firstFocusable = list.find(e => canFocus(e));
    if (firstFocusable)
    {
        (firstFocusable as HTMLElement).focus();
    }
}

/**
 * Focus the next focusable sibling of an element.
 */
export function focusNextSibling(element: HTMLElement): void
{
    const parent = element.parentElement;
    const children = Array.from(parent.children) as HTMLElement[];
    const i = children.indexOf(element);
    const list = children.slice(i + 1).concat(children.slice(0, i + 1));
    const firstFocusable = list.find(e => canFocus(e));
    if (firstFocusable)
    {
        (firstFocusable as HTMLElement).focus();
    }
}

The problem here is basically that these functions like focusNextSibling are messed up next open up times. The firstFocusable that comes out is wrong in past opens.


It does seem to have to do with the Input.input event listeners that get accumulated, because since when I press for example down it outputs multiple console.logs gradually more each time I re-open the context menu; however, I have now done removeEventListener before addEventListener and it doesn't seem to work still (it keeps accumulating).

Share Improve this question edited Mar 9 at 9:50 Hydroper asked Mar 8 at 20:05 HydroperHydroper 217 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

The only solution I have found is to have a global Input.input.addEventListener() call (rather than adding an event listener per context menu) and through it call a global callback that is updated by the context menus.

// Weak map mapping to Input listeners of submenus reliably.
// The keys are the submenu lists themselves, not the submenu representing items.
const submenuInputPressedListeners = new WeakMap<HTMLDivElement, Function>();

// Globalized input action listener
Input.input.addEventListener("inputPressed", function(): void
{
    currentInputPressedListener?.();
});

(Updated with either currentInputPressedListener = input_onInputPressed; or currentInputPressedListener = null;, instead of Input.input.addEventListener or removeEventListener)

Now am I doing this to test navigation input on submenus from outer context menus:

// Check input on submenu
submenuInputPressedListeners.get(submenus[submenus.length - 1])();

本文标签: reactjsCustom context menu is messing up children in navigationStack Overflow