@atlaskit/dropdown-menu
Version:
A dropdown menu displays a list of actions or options to a user.
77 lines (75 loc) • 2.28 kB
JavaScript
import React, { createContext, useCallback, useEffect, useRef, useState } from 'react';
import { bind } from 'bind-event-listener';
import __noop from '@atlaskit/ds-lib/noop';
import { useLayering } from '@atlaskit/layering';
import handleFocus from '../utils/handle-focus';
/**
*
*
* Context provider which maintains the list of focusable elements and a method to
* register new menu items.
* This list drives the keyboard navigation of the menu.
*
*/
export const FocusManagerContext = /*#__PURE__*/createContext({
menuItemRefs: [],
registerRef: __noop
});
/**
* 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
} = useLayering();
// 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)
}), [isLayerDisabled, onClose]);
const contextValue = {
menuItemRefs: menuItemRefs.current,
registerRef
};
return /*#__PURE__*/React.createElement(FocusManagerContext.Provider, {
value: contextValue
}, children);
};
export default FocusManager;