@atlaskit/dropdown-menu
Version:
A dropdown menu displays a list of actions or options to a user.
67 lines (66 loc) • 2.2 kB
JavaScript
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { bind } from 'bind-event-listener';
import { useLayering } from '@atlaskit/layering/use-layering';
import handleFocus from '../utils/handle-focus';
import { FocusManagerContext } from './focus-manager-context';
/**
* Focus manager logic.
*/
const FocusManager = ({
children,
onClose
}) => {
const menuItemRefs = useRef([]);
// Used to force a re-render only
const [refresh, setRefresh] = useState(0);
const registerMode = useRef('ordered');
registerMode.current = 'ordered';
const registerRef = useCallback(ref => {
if (menuItemRefs.current.includes(ref)) {
return;
}
switch (registerMode.current) {
case 'ordered':
menuItemRefs.current.push(ref);
break;
case 'unordered':
// Reset and force a rerender
registerMode.current = 'regenerate';
menuItemRefs.current = [];
setRefresh(refresh + 1);
break;
case 'regenerate':
// Ignore registrations until the next render cycle
break;
default:
throw new Error(`Unexpected case of ${registerMode.current}`);
}
},
// Updating register ref on force reload will cause `useRegisterItemWithFocusManager` to re-register
[refresh]);
const {
isLayerDisabled,
currentLevel
} = useLayering();
// Root menu (from a button) is at level 1; first submenu is at level 2. ARIA: Left only closes submenus.
const isNestedMenu = currentLevel > 1;
// Intentionally rebinding on each render
useEffect(() => {
if (registerMode.current === 'ordered') {
// Use effect is called after rendering is complete and useEffects of the children a called first
registerMode.current = 'unordered';
}
});
useEffect(() => bind(window, {
type: 'keydown',
listener: handleFocus(menuItemRefs, isLayerDisabled, onClose, isNestedMenu)
}), [isLayerDisabled, onClose, isNestedMenu]);
const contextValue = {
menuItemRefs: menuItemRefs.current,
registerRef
};
return /*#__PURE__*/React.createElement(FocusManagerContext.Provider, {
value: contextValue
}, children);
};
export default FocusManager;