Skip to content

Commit

Permalink
feat(JMX-3428): [SubMenu] support touch interaction on mobile
Browse files Browse the repository at this point in the history
  • Loading branch information
rc-aaron authored and 233mawile-rc committed Jun 2, 2023
1 parent 0cf4a91 commit 641c74c
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 101 deletions.
208 changes: 108 additions & 100 deletions packages/juno-core/src/components/Menu/SubMenu/SubMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
useId,
useRcPortalWindowContext,
useThemeProps,
isTap,
} from '../../../foundation';
import { ClickAwayListener } from '../../ClickAwayListener';
import { RcIcon } from '../../Icon';
Expand Down Expand Up @@ -83,6 +84,8 @@ const _RcSubMenu = forwardRef<any, RcSubMenuProps>(
title: titleProp,
disabled,
onKeyDown,
onTouchStart,
onTouchEnd,
onMouseEnter,
onMouseLeave,
MenuListProps,
Expand All @@ -102,55 +105,64 @@ const _RcSubMenu = forwardRef<any, RcSubMenuProps>(
const menuListContext = useContext(RcMenuListContext);
const menuContext = useContext(RcMenuContext);
const subMenuContext = useContext(RcSubMenuContext);
const ignoreMouseEventRef = useRef(false);

const { externalWindow } = useRcPortalWindowContext();

const handleClose = useEventCallback(
(e: {}, reason: RcSubMenuOnCloseReasonsType) => {
onClose?.(e, reason);
menuListContext?.onClose?.(e, reason);
},
);
const handleClose = (e: {}, reason: RcSubMenuOnCloseReasonsType) => {
onClose?.(e, reason);
menuListContext?.onClose?.(e, reason);
};

const handleItemKeyDown = useEventCallback(
(e: React.KeyboardEvent<HTMLLIElement>) => {
const { key } = e;
const { ArrowRight, Space, Enter } = a11yKeyboard;
const handleItemKeyDown = (e: React.KeyboardEvent<HTMLLIElement>) => {
const { key } = e;
const { ArrowRight, Space, Enter } = a11yKeyboard;

if ([ArrowRight, Space, Enter].includes(key)) {
openPopper(e);
}
if ([ArrowRight, Space, Enter].includes(key)) {
openPopper(e);
}

onKeyDown?.(e);
},
);
onKeyDown?.(e);
};

const handleItemTouchStart = (e: React.TouchEvent<HTMLLIElement>) => {
ignoreMouseEventRef.current = true;
onTouchStart?.(e);
};

const handleItemMouseEnter = useEventCallback(
(e: React.MouseEvent<HTMLLIElement>) => {
const handleItemTouchEnd = (e: React.TouchEvent<HTMLLIElement>) => {
if (isTap(e)) {
openPopper(e);
onMouseEnter?.(e);
},
);
}
onTouchEnd?.(e);
};

const handleItemMouseLeave = useEventCallback(
(e: React.MouseEvent<HTMLLIElement>) => {
onMouseLeave?.(e);

if (!_popperRef.current || !e.currentTarget) {
return;
}
const { clientX } = e;
const { left, width } = _popperRef.current.getBoundingClientRect();
const isMoveToPopper =
left < clientX + POPPER_OFFSET &&
clientX - POPPER_OFFSET < left + width;

if (!isMoveToPopper) {
closePopper();
handleClose(e, 'subMenuItemAnchorMouseLeave');
}
},
);
const handleItemMouseEnter = (e: React.MouseEvent<HTMLLIElement>) => {
if (!ignoreMouseEventRef.current) {
openPopper(e);
} else {
ignoreMouseEventRef.current = false;
}
onMouseEnter?.(e);
};

const handleItemMouseLeave = (e: React.MouseEvent<HTMLLIElement>) => {
onMouseLeave?.(e);

if (!_popperRef.current || !e.currentTarget) {
return;
}
const { clientX } = e;
const { left, width } = _popperRef.current.getBoundingClientRect();
const isMoveToPopper =
left < clientX + POPPER_OFFSET &&
clientX - POPPER_OFFSET < left + width;

if (!isMoveToPopper) {
closePopper();
handleClose(e, 'subMenuItemAnchorMouseLeave');
}
};

const classes = useMemo(
() => combineClasses(RcSubMenuClasses, classesProp),
Expand All @@ -173,80 +185,74 @@ const _RcSubMenu = forwardRef<any, RcSubMenuProps>(
...restPopperProps
} = PopperProps;

const openPopper = useEventCallback(
(event: React.MouseEvent | React.KeyboardEvent) => {
if (!disabled && event.currentTarget) {
setAnchorEl(event.currentTarget as HTMLElement);
setOpen(true);
}
},
);
const openPopper = (
event: React.MouseEvent | React.KeyboardEvent | React.TouchEvent,
) => {
if (!disabled && event.currentTarget) {
setAnchorEl(event.currentTarget as HTMLElement);
setOpen(true);
}
};

const closePopper = useEventCallback(() => {
const closePopper = () => {
setOpen(false);
setAnchorEl(null);
});
};

const handleCloseSubMenu = useEventCallback(() => {
anchorEl?.focus();
closePopper();
});

const handlePopperKeyDown = useEventCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
const { key } = e;
const { ArrowLeft, Escape, Tab } = a11yKeyboard;
const handlePopperKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
const { key } = e;
const { ArrowLeft, Escape, Tab } = a11yKeyboard;

e.stopPropagation();
e.stopPropagation();

if (key === ArrowLeft) {
handleCloseSubMenu();
handleClose(e, 'subMenuItemArrowLeftKeyDown');
} else if ([Escape, Tab].includes(key)) {
const reason = key === Escape ? 'escapeKeyDown' : 'tabKeyDown';
handleCloseSubMenu();
menuContext.closeMenu(e, reason);
subMenuContext?.closeSubMenu?.(e, reason);
handleClose(e, reason);
}

onPopperKeyDown?.(e);
},
);
if (key === ArrowLeft) {
handleCloseSubMenu();
handleClose(e, 'subMenuItemArrowLeftKeyDown');
} else if ([Escape, Tab].includes(key)) {
const reason = key === Escape ? 'escapeKeyDown' : 'tabKeyDown';
handleCloseSubMenu();
menuContext.closeMenu(e, reason);
subMenuContext?.closeSubMenu?.(e, reason);
handleClose(e, reason);
}

const handlePopperClickAway = useEventCallback(
(e: React.MouseEvent<Document, MouseEvent>) => {
if (
anchorEl &&
e.target &&
!anchorEl.contains(e.target as HTMLElement)
) {
closePopper();
handleClose(e, 'backdropClick');
}
},
);
onPopperKeyDown?.(e);
};

const handlePopperMouseLeave = useEventCallback(
(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const handlePopperClickAway = (
e: React.MouseEvent<Document, MouseEvent>,
) => {
if (anchorEl && e.target && !anchorEl.contains(e.target as HTMLElement)) {
closePopper();
onPopperMouseLeave?.(e);

if (!anchorEl || !e.currentTarget) {
return;
}

const { clientX } = e;
const { left, width } = anchorEl.getBoundingClientRect();
const isMoveToAnchor =
left < clientX + POPPER_OFFSET &&
clientX - POPPER_OFFSET < left + width;

if (!isMoveToAnchor) {
handleClose(e, 'popperMouseLeave');
}
},
);
handleClose(e, 'backdropClick');
}
};

const handlePopperMouseLeave = (
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
) => {
closePopper();
onPopperMouseLeave?.(e);

if (!anchorEl || !e.currentTarget) {
return;
}

const { clientX } = e;
const { left, width } = anchorEl.getBoundingClientRect();
const isMoveToAnchor =
left < clientX + POPPER_OFFSET &&
clientX - POPPER_OFFSET < left + width;

if (!isMoveToAnchor) {
handleClose(e, 'popperMouseLeave');
}
};

useLayoutEffect(() => {
if (
Expand Down Expand Up @@ -324,6 +330,8 @@ const _RcSubMenu = forwardRef<any, RcSubMenuProps>(
disabled={disabled}
classes={classes}
onKeyDown={handleItemKeyDown}
onTouchStart={handleItemTouchStart}
onTouchEnd={handleItemTouchEnd}
onMouseEnter={handleItemMouseEnter}
onMouseLeave={handleItemMouseLeave}
idRef={menuItemIdRef}
Expand Down
1 change: 1 addition & 0 deletions packages/juno-core/src/foundation/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ export * from './shallowEqual';
export * from './styleParser';
export * from './swapArrayLocs';
export * from './withDeprecatedCheck';
export * from './isTap';
19 changes: 19 additions & 0 deletions packages/juno-core/src/foundation/utils/isTap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { TouchEvent } from 'react';

/**
* @param event TouchEndEvent
*/
export const isTap = (event: TouchEvent) => {
if (event.touches.length < 2 && event.changedTouches.length < 2) {
const touch = event.touches[0] || event.changedTouches[0];

const elm = document.elementFromPoint(
touch.clientX,
touch.clientY,
) as HTMLElement | null;

return event.currentTarget === elm || event.currentTarget.contains(elm);
}

return false;
};
2 changes: 1 addition & 1 deletion sync-github.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"latestCommitSHA": "d63b648605ed42ca105877421c378cbe6e4e1917"
"latestCommitSHA": "5f7dffde66d522257b0f3c7e2564914d104f0646"
}

0 comments on commit 641c74c

Please sign in to comment.