Private
Public Access
1
0
Files

129 lines
5.9 KiB
JavaScript

'use client';
import { ArrowLeft, Tab, ArrowRight, Escape } from '@fluentui/keyboard-keys';
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
import { useMotionForwardedRef } from '@fluentui/react-motion';
import { useRestoreFocusSource } from '@fluentui/react-tabster';
import { getIntrinsicElementProps, useEventCallback, useMergedRefs, slot, useTimeout } from '@fluentui/react-utilities';
import * as React from 'react';
import { useMenuContext_unstable } from '../../contexts/menuContext';
import { useMenuListContext_unstable } from '../../contexts/menuListContext';
import { dispatchMenuEnterEvent, useIsSubmenu } from '../../utils/index';
/**
* Create the state required to render MenuPopover.
*
* The returned state can be modified with hooks such as useMenuPopoverStyles_unstable,
* before being passed to renderMenuPopover_unstable.
*
* @param props - props from this instance of MenuPopover
* @param ref - reference to root HTMLElement of MenuPopover
*/ export const useMenuPopover_unstable = (props, ref)=>{
'use no memo';
const safeZone = useMenuContext_unstable((context)=>context.safeZone);
const popoverRef = useMenuContext_unstable((context)=>context.menuPopoverRef);
const setOpen = useMenuContext_unstable((context)=>context.setOpen);
const open = useMenuContext_unstable((context)=>context.open);
const openOnHover = useMenuContext_unstable((context)=>context.openOnHover);
const triggerRef = useMenuContext_unstable((context)=>context.triggerRef);
const isSubmenu = useIsSubmenu();
const shouldCloseOnArrowLeft = useMenuListContext_unstable((ctx)=>{
var _ctx_shouldCloseOnArrowLeft;
return (_ctx_shouldCloseOnArrowLeft = ctx.shouldCloseOnArrowLeft) !== null && _ctx_shouldCloseOnArrowLeft !== void 0 ? _ctx_shouldCloseOnArrowLeft : true;
});
const canDispatchCustomEventRef = React.useRef(true);
const restoreFocusSourceAttributes = useRestoreFocusSource();
const [setThrottleTimeout, clearThrottleTimeout] = useTimeout();
const { dir } = useFluent();
const CloseArrowKey = dir === 'ltr' ? ArrowLeft : ArrowRight;
// use DOM listener since react events propagate up the react tree
// no need to do `contains` logic as menus are all positioned in different portals
const mouseOverListenerCallbackRef = React.useCallback((node)=>{
if (node) {
// Dispatches the custom menu mouse enter event with throttling
// Needs to trigger on mouseover to support keyboard + mouse together
// i.e. keyboard opens submenus while cursor is still on the parent
node.addEventListener('mouseover', (e)=>{
if (canDispatchCustomEventRef.current) {
canDispatchCustomEventRef.current = false;
dispatchMenuEnterEvent(popoverRef.current, e);
setThrottleTimeout(()=>{
canDispatchCustomEventRef.current = true;
}, 250);
}
});
}
}, [
popoverRef,
setThrottleTimeout
]);
React.useEffect(()=>{
return ()=>clearThrottleTimeout();
}, [
clearThrottleTimeout
]);
var _useMenuContext_unstable;
const inline = (_useMenuContext_unstable = useMenuContext_unstable((context)=>context.inline)) !== null && _useMenuContext_unstable !== void 0 ? _useMenuContext_unstable : false;
const mountNode = useMenuContext_unstable((context)=>context.mountNode);
const rootProps = slot.always(getIntrinsicElementProps('div', {
role: 'presentation',
...restoreFocusSourceAttributes,
...props,
// FIXME:
// `ref` is wrongly assigned to be `HTMLElement` instead of `HTMLDivElement`
// but since it would be a breaking change to fix it, we are casting ref to it's proper type
ref: useMergedRefs(ref, popoverRef, mouseOverListenerCallbackRef, useMotionForwardedRef())
}), {
elementType: 'div'
});
const { onMouseEnter: onMouseEnterOriginal, onKeyDown: onKeyDownOriginal } = rootProps;
rootProps.onMouseEnter = useEventCallback((event)=>{
if (openOnHover || isSubmenu) {
setOpen(event, {
open: true,
keyboard: false,
type: 'menuPopoverMouseEnter',
event
});
}
onMouseEnterOriginal === null || onMouseEnterOriginal === void 0 ? void 0 : onMouseEnterOriginal(event);
});
rootProps.onKeyDown = useEventCallback((event)=>{
const key = event.key;
if (key === Escape || isSubmenu && shouldCloseOnArrowLeft && key === CloseArrowKey) {
var _popoverRef_current;
if (open && ((_popoverRef_current = popoverRef.current) === null || _popoverRef_current === void 0 ? void 0 : _popoverRef_current.contains(event.target)) && !event.isDefaultPrevented()) {
setOpen(event, {
open: false,
keyboard: true,
type: 'menuPopoverKeyDown',
event
});
// stop propagation to avoid conflicting with other elements that listen for `Escape`
// e,g: Dialog, Popover, Menu and Tooltip
event.preventDefault();
}
}
if (key === Tab) {
setOpen(event, {
open: false,
keyboard: true,
type: 'menuPopoverKeyDown',
event
});
if (!isSubmenu) {
var _triggerRef_current;
(_triggerRef_current = triggerRef.current) === null || _triggerRef_current === void 0 ? void 0 : _triggerRef_current.focus();
}
}
onKeyDownOriginal === null || onKeyDownOriginal === void 0 ? void 0 : onKeyDownOriginal(event);
});
return {
inline,
mountNode,
safeZone,
components: {
root: 'div'
},
root: rootProps
};
};