Skip to content

Commit 9684799

Browse files
committed
feat(menu): allow user to specify both window and document env
1 parent a79dac8 commit 9684799

File tree

2 files changed

+47
-26
lines changed

2 files changed

+47
-26
lines changed

packages/menu/src/types.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,19 @@ export interface IUseMenuProps<T = HTMLButtonElement, M = HTMLElement> {
8686
focusedValue?: string | null;
8787
selectedItems?: ISelectedItem[];
8888
}) => void;
89-
/** Sets the environment where the menu is rendered */
89+
/**
90+
* Sets the environment where the menu is rendered
91+
* @deprecated use `window` prop instead.
92+
* */
9093
environment?: Window;
94+
/**
95+
* Sets the window where the menu is rendered
96+
* */
97+
window?: Window;
98+
/**
99+
* Sets the document where the menu is rendered
100+
* */
101+
document?: Document | ShadowRoot;
91102
}
92103

93104
export interface IUseMenuReturnValue {

packages/menu/src/useMenu.ts

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,32 @@ import {
3939
} from './types';
4040

4141
export const useMenu = <T extends HTMLElement = HTMLElement, M extends HTMLElement = HTMLElement>({
42-
items: rawItems,
43-
idPrefix,
42+
defaultExpanded = false,
43+
defaultFocusedValue,
44+
document: documentProp,
4445
environment,
45-
menuRef,
46-
triggerRef,
47-
rtl = false,
48-
onChange = () => undefined,
46+
focusedValue,
47+
idPrefix,
4948
isExpanded,
50-
defaultExpanded = false,
49+
items: rawItems,
50+
menuRef,
5151
restoreFocus = true,
52+
rtl = false,
5253
selectedItems,
53-
focusedValue,
54-
defaultFocusedValue
54+
triggerRef,
55+
window: windowProp,
56+
onChange = () => undefined
5557
}: IUseMenuProps<T, M>): IUseMenuReturnValue => {
5658
const prefix = `${useId(idPrefix)}-`;
5759
const triggerId = `${prefix}menu-trigger`;
5860
const isExpandedControlled = isExpanded !== undefined;
5961
const isSelectedItemsControlled = selectedItems !== undefined;
6062
const isFocusedValueControlled = focusedValue !== undefined;
63+
64+
const _window = windowProp || environment;
65+
let _document: Document | ShadowRoot = _window ? _window.document : window.document;
66+
_document = documentProp ? documentProp : _document;
67+
6168
const menuItems = useMemo(
6269
() =>
6370
rawItems.reduce((items, item: MenuItem) => {
@@ -398,11 +405,9 @@ export const useMenu = <T extends HTMLElement = HTMLElement, M extends HTMLEleme
398405
*/
399406
const handleBlur = useCallback(
400407
(event: React.FocusEvent) => {
401-
const win = environment || window;
402-
403408
setTimeout(() => {
404409
// Timeout is required to ensure blur is handled after focus
405-
const activeElement = win.document.activeElement;
410+
const activeElement = _document.activeElement;
406411
const isMenuOrTriggerFocused =
407412
menuRef.current?.contains(activeElement) || triggerRef.current?.contains(activeElement);
408413

@@ -420,7 +425,14 @@ export const useMenu = <T extends HTMLElement = HTMLElement, M extends HTMLEleme
420425
}
421426
});
422427
},
423-
[closeMenu, controlledIsExpanded, environment, menuRef, returnFocusToTrigger, triggerRef]
428+
[
429+
_document.activeElement,
430+
closeMenu,
431+
controlledIsExpanded,
432+
menuRef,
433+
returnFocusToTrigger,
434+
triggerRef
435+
]
424436
);
425437

426438
const handleMenuMouseLeave = useCallback(() => {
@@ -515,7 +527,7 @@ export const useMenu = <T extends HTMLElement = HTMLElement, M extends HTMLEleme
515527
event.preventDefault();
516528

517529
if (item.href) {
518-
triggerLink(event.target as HTMLAnchorElement, environment || window);
530+
triggerLink(event.target as HTMLAnchorElement, _window || window);
519531
}
520532

521533
returnFocusToTrigger(isTransitionItem);
@@ -587,16 +599,16 @@ export const useMenu = <T extends HTMLElement = HTMLElement, M extends HTMLEleme
587599
}
588600
},
589601
[
602+
_window,
603+
getNextFocusedValue,
590604
getSelectedItems,
591605
isExpandedControlled,
606+
isFocusedValueControlled,
592607
isSelectedItemsControlled,
608+
onChange,
593609
returnFocusToTrigger,
594-
environment,
595610
rtl,
596-
getNextFocusedValue,
597-
isFocusedValueControlled,
598-
state.nestedPathIds,
599-
onChange
611+
state.nestedPathIds
600612
]
601613
);
602614

@@ -635,18 +647,16 @@ export const useMenu = <T extends HTMLElement = HTMLElement, M extends HTMLEleme
635647
* Respond to clicks outside the open menu
636648
*/
637649
useEffect(() => {
638-
const win = environment || window;
639-
640650
if (controlledIsExpanded) {
641-
win.document.addEventListener('keydown', handleMenuKeyDown, true);
651+
(_document as Document).addEventListener('keydown', handleMenuKeyDown, true);
642652
} else if (!controlledIsExpanded) {
643-
win.document.removeEventListener('keydown', handleMenuKeyDown, true);
653+
(_document as Document).removeEventListener('keydown', handleMenuKeyDown, true);
644654
}
645655

646656
return () => {
647-
win.document.removeEventListener('keydown', handleMenuKeyDown, true);
657+
(_document as Document).removeEventListener('keydown', handleMenuKeyDown, true);
648658
};
649-
}, [controlledIsExpanded, handleMenuKeyDown, environment]);
659+
}, [controlledIsExpanded, _document, handleMenuKeyDown]);
650660

651661
/**
652662
* When the menu is opened, this effect sets focus on the current menu item using `focusedValue`

0 commit comments

Comments
 (0)