UNPKG

@atlaskit/dropdown-menu

Version:

A dropdown menu displays a list of actions or options to a user.

77 lines (75 loc) 2.28 kB
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;