UNPKG

6.79 kBJavaScriptView Raw
1'use client';
2
3import _extends from "@babel/runtime/helpers/esm/extends";
4import * as React from 'react';
5import { unstable_useForkRef as useForkRef, unstable_useId as useId, unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils';
6import { menuReducer } from './menuReducer';
7import { DropdownContext } from '../useDropdown/DropdownContext';
8import { ListActionTypes, useList } from '../useList';
9import { DropdownActionTypes } from '../useDropdown';
10import { useCompoundParent } from '../useCompound';
11import { combineHooksSlotProps } from '../utils/combineHooksSlotProps';
12import { extractEventHandlers } from '../utils/extractEventHandlers';
13const FALLBACK_MENU_CONTEXT = {
14 dispatch: () => {},
15 popupId: '',
16 registerPopup: () => {},
17 registerTrigger: () => {},
18 state: {
19 open: true,
20 changeReason: null
21 },
22 triggerElement: null
23};
24
25/**
26 *
27 * Demos:
28 *
29 * - [Menu](https://mui.com/base-ui/react-menu/#hooks)
30 *
31 * API:
32 *
33 * - [useMenu API](https://mui.com/base-ui/react-menu/hooks-api/#use-menu)
34 */
35export function useMenu(parameters = {}) {
36 var _useId, _React$useContext;
37 const {
38 listboxRef: listboxRefProp,
39 onItemsChange,
40 id: idParam,
41 disabledItemsFocusable = true,
42 disableListWrap = false,
43 autoFocus = true,
44 componentName = 'useMenu'
45 } = parameters;
46 const rootRef = React.useRef(null);
47 const handleRef = useForkRef(rootRef, listboxRefProp);
48 const listboxId = (_useId = useId(idParam)) != null ? _useId : '';
49 const {
50 state: {
51 open,
52 changeReason
53 },
54 dispatch: menuDispatch,
55 triggerElement,
56 registerPopup
57 } = (_React$useContext = React.useContext(DropdownContext)) != null ? _React$useContext : FALLBACK_MENU_CONTEXT;
58
59 // store the initial open state to prevent focus stealing
60 // (the first menu items gets focued only when the menu is opened by the user)
61 const isInitiallyOpen = React.useRef(open);
62 const {
63 subitems,
64 contextValue: compoundComponentContextValue
65 } = useCompoundParent();
66 const subitemKeys = React.useMemo(() => Array.from(subitems.keys()), [subitems]);
67 const getItemDomElement = React.useCallback(itemId => {
68 var _subitems$get$ref$cur, _subitems$get;
69 if (itemId == null) {
70 return null;
71 }
72 return (_subitems$get$ref$cur = (_subitems$get = subitems.get(itemId)) == null ? void 0 : _subitems$get.ref.current) != null ? _subitems$get$ref$cur : null;
73 }, [subitems]);
74 const isItemDisabled = React.useCallback(id => {
75 var _subitems$get2;
76 return (subitems == null || (_subitems$get2 = subitems.get(id)) == null ? void 0 : _subitems$get2.disabled) || false;
77 }, [subitems]);
78 const getItemAsString = React.useCallback(id => {
79 var _subitems$get3, _subitems$get4;
80 return ((_subitems$get3 = subitems.get(id)) == null ? void 0 : _subitems$get3.label) || ((_subitems$get4 = subitems.get(id)) == null || (_subitems$get4 = _subitems$get4.ref.current) == null ? void 0 : _subitems$get4.innerText);
81 }, [subitems]);
82 const reducerActionContext = React.useMemo(() => ({
83 listboxRef: rootRef
84 }), [rootRef]);
85 const {
86 dispatch: listDispatch,
87 getRootProps: getListRootProps,
88 contextValue: listContextValue,
89 state: {
90 highlightedValue
91 },
92 rootRef: mergedListRef
93 } = useList({
94 disabledItemsFocusable,
95 disableListWrap,
96 focusManagement: 'DOM',
97 getItemDomElement,
98 getInitialState: () => ({
99 selectedValues: [],
100 highlightedValue: null
101 }),
102 isItemDisabled,
103 items: subitemKeys,
104 getItemAsString,
105 rootRef: handleRef,
106 onItemsChange,
107 reducerActionContext,
108 selectionMode: 'none',
109 stateReducer: menuReducer,
110 componentName
111 });
112 useEnhancedEffect(() => {
113 registerPopup(listboxId);
114 }, [listboxId, registerPopup]);
115 useEnhancedEffect(() => {
116 if (open && (changeReason == null ? void 0 : changeReason.type) === 'keydown' && changeReason.key === 'ArrowUp') {
117 listDispatch({
118 type: ListActionTypes.highlightLast,
119 event: changeReason
120 });
121 }
122 }, [open, changeReason, listDispatch]);
123 React.useEffect(() => {
124 if (open && autoFocus && highlightedValue && !isInitiallyOpen.current) {
125 var _subitems$get5;
126 (_subitems$get5 = subitems.get(highlightedValue)) == null || (_subitems$get5 = _subitems$get5.ref) == null || (_subitems$get5 = _subitems$get5.current) == null || _subitems$get5.focus();
127 }
128 }, [open, autoFocus, highlightedValue, subitems, subitemKeys]);
129 React.useEffect(() => {
130 var _rootRef$current;
131 // set focus to the highlighted item (but prevent stealing focus from other elements on the page)
132 if ((_rootRef$current = rootRef.current) != null && _rootRef$current.contains(document.activeElement) && highlightedValue !== null) {
133 var _subitems$get6;
134 subitems == null || (_subitems$get6 = subitems.get(highlightedValue)) == null || (_subitems$get6 = _subitems$get6.ref.current) == null || _subitems$get6.focus();
135 }
136 }, [highlightedValue, subitems]);
137 const createHandleBlur = otherHandlers => event => {
138 var _otherHandlers$onBlur, _rootRef$current2;
139 (_otherHandlers$onBlur = otherHandlers.onBlur) == null || _otherHandlers$onBlur.call(otherHandlers, event);
140 if (event.defaultMuiPrevented) {
141 return;
142 }
143 if ((_rootRef$current2 = rootRef.current) != null && _rootRef$current2.contains(event.relatedTarget) || event.relatedTarget === triggerElement) {
144 return;
145 }
146 menuDispatch({
147 type: DropdownActionTypes.blur,
148 event
149 });
150 };
151 const createHandleKeyDown = otherHandlers => event => {
152 var _otherHandlers$onKeyD;
153 (_otherHandlers$onKeyD = otherHandlers.onKeyDown) == null || _otherHandlers$onKeyD.call(otherHandlers, event);
154 if (event.defaultMuiPrevented) {
155 return;
156 }
157 if (event.key === 'Escape') {
158 menuDispatch({
159 type: DropdownActionTypes.escapeKeyDown,
160 event
161 });
162 }
163 };
164 const getOwnListboxHandlers = (otherHandlers = {}) => ({
165 onBlur: createHandleBlur(otherHandlers),
166 onKeyDown: createHandleKeyDown(otherHandlers)
167 });
168 const getListboxProps = (externalProps = {}) => {
169 const getCombinedRootProps = combineHooksSlotProps(getOwnListboxHandlers, getListRootProps);
170 const externalEventHandlers = extractEventHandlers(externalProps);
171 return _extends({}, externalProps, externalEventHandlers, getCombinedRootProps(externalEventHandlers), {
172 id: listboxId,
173 role: 'menu'
174 });
175 };
176 React.useDebugValue({
177 subitems,
178 highlightedValue
179 });
180 return {
181 contextValue: _extends({}, compoundComponentContextValue, listContextValue),
182 dispatch: listDispatch,
183 getListboxProps,
184 highlightedValue,
185 listboxRef: mergedListRef,
186 menuItems: subitems,
187 open,
188 triggerElement
189 };
190}
\No newline at end of file