Skip to content

Commit 1b31109

Browse files
authored
Fix two unavailable menu dialogs from being open (#4469)
Fix two unavailable menu dialogs from being open at the same time
1 parent 1cd1833 commit 1b31109

File tree

2 files changed

+16
-10
lines changed

2 files changed

+16
-10
lines changed

packages/@react-aria/menu/src/useMenuItem.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
119119

120120
let onSubmenuOpen = useEffectEvent(() => {
121121
cancelOpenTimeout();
122+
for (let expandedKey of state.expandedKeys) {
123+
state.toggleKey(expandedKey);
124+
}
122125
if (!state.expandedKeys.has(key)) {
123126
state.toggleKey(key);
124127
}
@@ -198,27 +201,22 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
198201
allowsDifferentPressOrigin: true
199202
});
200203

201-
let {pressProps, isPressed} = usePress({onPressStart, onPressUp, isDisabled});
204+
let {pressProps, isPressed} = usePress({onPressStart, onPressUp, isDisabled: isDisabled || (isMenuDialogTrigger && state.expandedKeys.has(key))});
202205
let {hoverProps} = useHover({
203206
isDisabled,
204207
onHoverStart() {
205-
if (!isFocusVisible()) {
208+
if (!isFocusVisible() && !(isMenuDialogTrigger && state.expandedKeys.has(key))) {
206209
state.selectionManager.setFocused(true);
207210
state.selectionManager.setFocusedKey(key);
208211
// focus immediately so that a focus scope opened on hover has the correct restore node
209212
let isFocused = key === state.selectionManager.focusedKey;
210213
if (isFocused && state.selectionManager.isFocused && document.activeElement !== ref.current) {
211-
if (state.expandedKeys.size > 0 && !state.expandedKeys.has(key)) {
212-
for (let expandedKey of state.expandedKeys) {
213-
state.toggleKey(expandedKey);
214-
}
215-
}
216214
focusSafely(ref.current);
217215
}
218216
}
219217
},
220218
onHoverChange: isHovered => {
221-
if (isHovered && isMenuDialogTrigger) {
219+
if (isHovered && isMenuDialogTrigger && !state.expandedKeys.has(key)) {
222220
if (!openTimeout.current) {
223221
openTimeout.current = setTimeout(() => {
224222
onSubmenuOpen();

packages/@react-stately/tree/src/useTreeState.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
*/
1212

1313
import {Collection, CollectionStateBase, Expandable, MultipleSelection, Node} from '@react-types/shared';
14-
import {Key, useCallback, useEffect, useMemo} from 'react';
14+
import {Key, useCallback, useEffect, useMemo, useRef} from 'react';
1515
import {SelectionManager, useMultipleSelectionState} from '@react-stately/selection';
1616
import {TreeCollection} from './TreeCollection';
1717
import {useCollection} from '@react-stately/collections';
@@ -45,6 +45,12 @@ export function useTreeState<T extends object>(props: TreeProps<T>): TreeState<T
4545
props.defaultExpandedKeys ? new Set(props.defaultExpandedKeys) : new Set(),
4646
props.onExpandedChange
4747
);
48+
let currentExpandedKeys = useRef(expandedKeys);
49+
// I don't know a way to avoid this, we have to sync to the controlled state in case the prop `expandedKeys` changed.
50+
// And we need to track the 'previous state' otherwise two back to back calls to `toggleKey` will only register the last call.
51+
if (currentExpandedKeys.current !== expandedKeys) {
52+
currentExpandedKeys.current = expandedKeys;
53+
}
4854

4955
let selectionState = useMultipleSelectionState(props);
5056
let disabledKeys = useMemo(() =>
@@ -62,7 +68,9 @@ export function useTreeState<T extends object>(props: TreeProps<T>): TreeState<T
6268
}, [tree, selectionState.focusedKey]);
6369

6470
let onToggle = (key: Key) => {
65-
setExpandedKeys(toggleKey(expandedKeys, key));
71+
let newSet = toggleKey(currentExpandedKeys.current, key);
72+
currentExpandedKeys.current = newSet;
73+
setExpandedKeys(newSet);
6674
};
6775

6876
return {

0 commit comments

Comments
 (0)