[Perf] [Refactoring] Move useMenuPosition into Menu component
This commit is contained in:
parent
8aa7eb2fcb
commit
03de021b82
@ -15,7 +15,6 @@ import renderText from '../../common/helpers/renderText';
|
|||||||
import formatGroupCallVolume from './helpers/formatGroupCallVolume';
|
import formatGroupCallVolume from './helpers/formatGroupCallVolume';
|
||||||
|
|
||||||
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
import useMenuPosition from '../../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import Avatar from '../../common/Avatar';
|
import Avatar from '../../common/Avatar';
|
||||||
@ -52,7 +51,7 @@ const GroupCallParticipant: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen,
|
isContextMenuOpen,
|
||||||
contextMenuPosition,
|
contextMenuAnchor,
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
handleBeforeContextMenu,
|
handleBeforeContextMenu,
|
||||||
handleContextMenuClose,
|
handleContextMenuClose,
|
||||||
@ -76,16 +75,6 @@ const GroupCallParticipant: FC<OwnProps & StateProps> = ({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
getLayout,
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasCustomVolume = Boolean(
|
const hasCustomVolume = Boolean(
|
||||||
!isMuted && isSpeaking && participant.volume && participant.volume !== GROUP_CALL_DEFAULT_VOLUME,
|
!isMuted && isSpeaking && participant.volume && participant.volume !== GROUP_CALL_DEFAULT_VOLUME,
|
||||||
);
|
);
|
||||||
@ -147,11 +136,11 @@ const GroupCallParticipant: FC<OwnProps & StateProps> = ({
|
|||||||
<GroupCallParticipantMenu
|
<GroupCallParticipantMenu
|
||||||
participant={participant}
|
participant={participant}
|
||||||
isDropdownOpen={isContextMenuOpen}
|
isDropdownOpen={isContextMenuOpen}
|
||||||
positionX={positionX}
|
anchor={contextMenuAnchor}
|
||||||
positionY={positionY}
|
getTriggerElement={getTriggerElement}
|
||||||
transformOriginX={transformOriginX}
|
getRootElement={getRootElement}
|
||||||
transformOriginY={transformOriginY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
getLayout={getLayout}
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
onCloseAnimationEnd={handleContextMenuHide}
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
menuRef={menuRef}
|
menuRef={menuRef}
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, { memo, useEffect, useState } from '../../../lib/teact/teact';
|
||||||
memo, useEffect, useState,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { GroupCallParticipant } from '../../../lib/secret-sauce';
|
import type { GroupCallParticipant } from '../../../lib/secret-sauce';
|
||||||
|
import type { MenuPositionOptions } from '../../ui/Menu';
|
||||||
|
|
||||||
import { GROUP_CALL_DEFAULT_VOLUME, GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config';
|
import { GROUP_CALL_DEFAULT_VOLUME, GROUP_CALL_VOLUME_MULTIPLIER } from '../../../config';
|
||||||
import { selectIsAdminInActiveGroupCall } from '../../../global/selectors/calls';
|
import { selectIsAdminInActiveGroupCall } from '../../../global/selectors/calls';
|
||||||
@ -26,18 +25,15 @@ import './GroupCallParticipantMenu.scss';
|
|||||||
const SPEAKER_ICON_DISABLED_SEGMENT: [number, number] = [0, 17];
|
const SPEAKER_ICON_DISABLED_SEGMENT: [number, number] = [0, 17];
|
||||||
const SPEAKER_ICON_ENABLED_SEGMENT: [number, number] = [17, 34];
|
const SPEAKER_ICON_ENABLED_SEGMENT: [number, number] = [17, 34];
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps =
|
||||||
participant?: GroupCallParticipant;
|
{
|
||||||
onCloseAnimationEnd: VoidFunction;
|
participant?: GroupCallParticipant;
|
||||||
onClose: VoidFunction;
|
onCloseAnimationEnd: VoidFunction;
|
||||||
isDropdownOpen: boolean;
|
onClose: VoidFunction;
|
||||||
positionX?: 'left' | 'right';
|
isDropdownOpen: boolean;
|
||||||
positionY?: 'top' | 'bottom';
|
menuRef?: React.RefObject<HTMLDivElement>;
|
||||||
transformOriginX?: number;
|
}
|
||||||
transformOriginY?: number;
|
& MenuPositionOptions;
|
||||||
style?: string;
|
|
||||||
menuRef?: React.RefObject<HTMLDivElement>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
@ -58,12 +54,8 @@ const GroupCallParticipantMenu: FC<OwnProps & StateProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
isDropdownOpen,
|
isDropdownOpen,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
positionY,
|
|
||||||
menuRef,
|
menuRef,
|
||||||
positionX,
|
...menuPositionOptions
|
||||||
style,
|
|
||||||
transformOriginY,
|
|
||||||
transformOriginX,
|
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
toggleGroupCallMute,
|
toggleGroupCallMute,
|
||||||
@ -175,16 +167,13 @@ const GroupCallParticipantMenu: FC<OwnProps & StateProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isDropdownOpen}
|
isOpen={isDropdownOpen}
|
||||||
positionX={positionX}
|
|
||||||
positionY={positionY}
|
|
||||||
transformOriginX={transformOriginX}
|
|
||||||
transformOriginY={transformOriginY}
|
|
||||||
style={style}
|
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
withPortal
|
withPortal
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||||
className="participant-menu with-menu-transitions"
|
className="participant-menu with-menu-transitions"
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...menuPositionOptions}
|
||||||
>
|
>
|
||||||
{!isSelf && !shouldRaiseHand && (
|
{!isSelf && !shouldRaiseHand && (
|
||||||
<div className="group">
|
<div className="group">
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import formatGroupCallVolume from './helpers/formatGroupCallVolume';
|
|||||||
import useInterval from '../../../hooks/schedulers/useInterval';
|
import useInterval from '../../../hooks/schedulers/useInterval';
|
||||||
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import FullNameTitle from '../../common/FullNameTitle';
|
import FullNameTitle from '../../common/FullNameTitle';
|
||||||
@ -209,7 +208,7 @@ const GroupCallParticipantVideo: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen,
|
isContextMenuOpen,
|
||||||
contextMenuPosition,
|
contextMenuAnchor,
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
handleContextMenuClose,
|
handleContextMenuClose,
|
||||||
handleContextMenuHide,
|
handleContextMenuHide,
|
||||||
@ -232,16 +231,6 @@ const GroupCallParticipantVideo: FC<OwnProps & StateProps> = ({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
getLayout,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClickPin = useCallback(() => {
|
const handleClickPin = useCallback(() => {
|
||||||
setPinned(!isPinned ? {
|
setPinned(!isPinned ? {
|
||||||
id: user?.id || chat!.id,
|
id: user?.id || chat!.id,
|
||||||
@ -318,11 +307,11 @@ const GroupCallParticipantVideo: FC<OwnProps & StateProps> = ({
|
|||||||
<GroupCallParticipantMenu
|
<GroupCallParticipantMenu
|
||||||
participant={participant}
|
participant={participant}
|
||||||
isDropdownOpen={isContextMenuOpen}
|
isDropdownOpen={isContextMenuOpen}
|
||||||
positionX={positionX}
|
anchor={contextMenuAnchor}
|
||||||
positionY={positionY}
|
getTriggerElement={getTriggerElement}
|
||||||
transformOriginX={transformOriginX}
|
getRootElement={getRootElement}
|
||||||
transformOriginY={transformOriginY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
getLayout={getLayout}
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
onCloseAnimationEnd={handleContextMenuHide}
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
menuRef={menuRef}
|
menuRef={menuRef}
|
||||||
|
|||||||
@ -29,8 +29,7 @@ import type {
|
|||||||
ApiWebPage,
|
ApiWebPage,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type {
|
import type {
|
||||||
ApiDraft, GlobalState, MessageList,
|
ApiDraft, GlobalState, MessageList, MessageListType, TabState,
|
||||||
MessageListType, TabState,
|
|
||||||
} from '../../global/types';
|
} from '../../global/types';
|
||||||
import type {
|
import type {
|
||||||
IAnchorPosition, InlineBotSettings, ISettings, ThreadId,
|
IAnchorPosition, InlineBotSettings, ISettings, ThreadId,
|
||||||
@ -830,7 +829,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
} = useContextMenuHandlers(mainButtonRef, !(mainButtonState === MainButtonState.Send && canShowCustomSendMenu));
|
} = useContextMenuHandlers(mainButtonRef, !(mainButtonState === MainButtonState.Send && canShowCustomSendMenu));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
contextMenuPosition: storyReactionPickerPosition,
|
contextMenuAnchor: storyReactionPickerAnchor,
|
||||||
handleContextMenu: handleStoryPickerContextMenu,
|
handleContextMenu: handleStoryPickerContextMenu,
|
||||||
handleBeforeContextMenu: handleBeforeStoryPickerContextMenu,
|
handleBeforeContextMenu: handleBeforeStoryPickerContextMenu,
|
||||||
handleContextMenuHide: handleStoryPickerContextMenuHide,
|
handleContextMenuHide: handleStoryPickerContextMenuHide,
|
||||||
@ -839,15 +838,15 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isReactionPickerOpen) return;
|
if (isReactionPickerOpen) return;
|
||||||
|
|
||||||
if (storyReactionPickerPosition) {
|
if (storyReactionPickerAnchor) {
|
||||||
openStoryReactionPicker({
|
openStoryReactionPicker({
|
||||||
peerId: chatId,
|
peerId: chatId,
|
||||||
storyId: storyId!,
|
storyId: storyId!,
|
||||||
position: storyReactionPickerPosition,
|
position: storyReactionPickerAnchor,
|
||||||
});
|
});
|
||||||
handleStoryPickerContextMenuHide();
|
handleStoryPickerContextMenuHide();
|
||||||
}
|
}
|
||||||
}, [chatId, handleStoryPickerContextMenuHide, isReactionPickerOpen, storyId, storyReactionPickerPosition]);
|
}, [chatId, handleStoryPickerContextMenuHide, isReactionPickerOpen, storyId, storyReactionPickerAnchor]);
|
||||||
|
|
||||||
useClipboardPaste(
|
useClipboardPaste(
|
||||||
isForCurrentMessageList || isInStoryViewer,
|
isForCurrentMessageList || isInStoryViewer,
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
|||||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
import useMenuPosition from '../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
@ -69,7 +68,7 @@ const GifButton: FC<OwnProps> = ({
|
|||||||
const isVideoReady = loadAndPlay && isBuffered;
|
const isVideoReady = loadAndPlay && isBuffered;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition,
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
handleBeforeContextMenu, handleContextMenu,
|
handleBeforeContextMenu, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref);
|
} = useContextMenuHandlers(ref);
|
||||||
@ -78,15 +77,6 @@ const GifButton: FC<OwnProps> = ({
|
|||||||
const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar'));
|
const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar'));
|
||||||
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.gif-context-menu .bubble'));
|
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.gif-context-menu .bubble'));
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (isContextMenuOpen || !onClick) return;
|
if (isContextMenuOpen || !onClick) return;
|
||||||
onClick({
|
onClick({
|
||||||
@ -185,14 +175,13 @@ const GifButton: FC<OwnProps> = ({
|
|||||||
{shouldRenderSpinner && (
|
{shouldRenderSpinner && (
|
||||||
<Spinner color={previewBlobUrl || withThumb ? 'white' : 'black'} />
|
<Spinner color={previewBlobUrl || withThumb ? 'white' : 'black'} />
|
||||||
)}
|
)}
|
||||||
{onClick && contextMenuPosition !== undefined && (
|
{onClick && contextMenuAnchor !== undefined && (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
transformOriginX={transformOriginX}
|
anchor={contextMenuAnchor}
|
||||||
transformOriginY={transformOriginY}
|
getTriggerElement={getTriggerElement}
|
||||||
positionX={positionX}
|
getRootElement={getRootElement}
|
||||||
positionY={positionY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
|
||||||
className="gif-context-menu"
|
className="gif-context-menu"
|
||||||
autoClose
|
autoClose
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import useDynamicColorListener from '../../hooks/stickers/useDynamicColorListene
|
|||||||
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
@ -118,29 +117,18 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
|||||||
const isIntesectingForShowing = useIsIntersecting(ref, observeIntersectionForShowing);
|
const isIntesectingForShowing = useIsIntersecting(ref, observeIntersectionForShowing);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition,
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
handleBeforeContextMenu, handleContextMenu,
|
handleBeforeContextMenu, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref);
|
} = useContextMenuHandlers(ref);
|
||||||
const shouldRenderContextMenu = Boolean(!noContextMenu && contextMenuPosition);
|
const shouldRenderContextMenu = Boolean(!noContextMenu && contextMenuAnchor);
|
||||||
|
|
||||||
const getTriggerElement = useLastCallback(() => ref.current);
|
const getTriggerElement = useLastCallback(() => ref.current);
|
||||||
const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar'));
|
const getRootElement = useLastCallback(() => ref.current!.closest('.custom-scroll, .no-scrollbar'));
|
||||||
const getMenuElement = useLastCallback(() => {
|
const getMenuElement = useLastCallback(() => {
|
||||||
return isStatusPicker ? menuRef.current : ref.current!.querySelector('.sticker-context-menu .bubble');
|
return isStatusPicker ? menuRef.current : ref.current!.querySelector('.sticker-context-menu .bubble');
|
||||||
});
|
});
|
||||||
|
const getLayout = useLastCallback(() => ({ withPortal: isStatusPicker, shouldAvoidNegativePosition: true }));
|
||||||
const getLayout = () => ({ withPortal: isStatusPicker, shouldAvoidNegativePosition: true });
|
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
getLayout,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isContextMenuOpen) {
|
if (isContextMenuOpen) {
|
||||||
@ -345,11 +333,11 @@ const StickerButton = <T extends number | ApiSticker | ApiBotInlineMediaResult |
|
|||||||
<Menu
|
<Menu
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
transformOriginX={transformOriginX}
|
anchor={contextMenuAnchor}
|
||||||
transformOriginY={transformOriginY}
|
getTriggerElement={getTriggerElement}
|
||||||
positionX={positionX}
|
getRootElement={getRootElement}
|
||||||
positionY={positionY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
getLayout={getLayout}
|
||||||
className="sticker-context-menu"
|
className="sticker-context-menu"
|
||||||
autoClose
|
autoClose
|
||||||
withPortal={isStatusPicker}
|
withPortal={isStatusPicker}
|
||||||
|
|||||||
@ -12,9 +12,7 @@ import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
|||||||
import type { FocusDirection, ThreadId } from '../../types';
|
import type { FocusDirection, ThreadId } from '../../types';
|
||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
||||||
|
|
||||||
import {
|
import { getChatTitle, getMessageHtmlId, isJoinedChannelMessage } from '../../global/helpers';
|
||||||
getChatTitle, getMessageHtmlId, isJoinedChannelMessage,
|
|
||||||
} from '../../global/helpers';
|
|
||||||
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
||||||
import {
|
import {
|
||||||
selectCanPlayAnimatedEmojis,
|
selectCanPlayAnimatedEmojis,
|
||||||
@ -191,11 +189,11 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition,
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
handleBeforeContextMenu, handleContextMenu,
|
handleBeforeContextMenu, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref);
|
} = useContextMenuHandlers(ref);
|
||||||
const isContextMenuShown = contextMenuPosition !== undefined;
|
const isContextMenuShown = contextMenuAnchor !== undefined;
|
||||||
|
|
||||||
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||||
preventMessageInputBlur(e);
|
preventMessageInputBlur(e);
|
||||||
@ -372,10 +370,10 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
<ActionMessageSuggestedAvatar message={message} renderContent={renderContent} />
|
<ActionMessageSuggestedAvatar message={message} renderContent={renderContent} />
|
||||||
)}
|
)}
|
||||||
{isJoinedMessage && <SimilarChannels chatId={targetChatId!} />}
|
{isJoinedMessage && <SimilarChannels chatId={targetChatId!} />}
|
||||||
{contextMenuPosition && (
|
{contextMenuAnchor && (
|
||||||
<ContextMenuContainer
|
<ContextMenuContainer
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
anchor={contextMenuPosition}
|
anchor={contextMenuAnchor}
|
||||||
message={message}
|
message={message}
|
||||||
messageListType="thread"
|
messageListType="thread"
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
|
|||||||
@ -142,12 +142,12 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
const menuButtonRef = useRef<HTMLButtonElement>(null);
|
const menuButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const [menuPosition, setMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
|
const [menuAnchor, setMenuAnchor] = useState<IAnchorPosition | undefined>(undefined);
|
||||||
|
|
||||||
const handleHeaderMenuOpen = useLastCallback(() => {
|
const handleHeaderMenuOpen = useLastCallback(() => {
|
||||||
setIsMenuOpen(true);
|
setIsMenuOpen(true);
|
||||||
const rect = menuButtonRef.current!.getBoundingClientRect();
|
const rect = menuButtonRef.current!.getBoundingClientRect();
|
||||||
setMenuPosition({ x: rect.right, y: rect.bottom });
|
setMenuAnchor({ x: rect.right, y: rect.bottom });
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleHeaderMenuClose = useLastCallback(() => {
|
const handleHeaderMenuClose = useLastCallback(() => {
|
||||||
@ -155,7 +155,7 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleHeaderMenuHide = useLastCallback(() => {
|
const handleHeaderMenuHide = useLastCallback(() => {
|
||||||
setMenuPosition(undefined);
|
setMenuAnchor(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubscribeClick = useLastCallback(() => {
|
const handleSubscribeClick = useLastCallback(() => {
|
||||||
@ -420,12 +420,12 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
<i className="icon icon-more" aria-hidden />
|
<i className="icon icon-more" aria-hidden />
|
||||||
</Button>
|
</Button>
|
||||||
{menuPosition && (
|
{menuAnchor && (
|
||||||
<HeaderMenuContainer
|
<HeaderMenuContainer
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
isOpen={isMenuOpen}
|
isOpen={isMenuOpen}
|
||||||
anchor={menuPosition}
|
anchor={menuAnchor}
|
||||||
withExtraActions={isMobile || !canExpandActions}
|
withExtraActions={isMobile || !canExpandActions}
|
||||||
isChannel={isChannel}
|
isChannel={isChannel}
|
||||||
canStartBot={canStartBot}
|
canStartBot={canStartBot}
|
||||||
|
|||||||
@ -34,7 +34,6 @@ import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
|||||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated';
|
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated';
|
||||||
|
|
||||||
@ -160,7 +159,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
|||||||
useEffect(() => (isShown ? captureEscKeyListener(clearEmbedded) : undefined), [isShown, clearEmbedded]);
|
useEffect(() => (isShown ? captureEscKeyListener(clearEmbedded) : undefined), [isShown, clearEmbedded]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition, handleContextMenu,
|
isContextMenuOpen, contextMenuAnchor, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref);
|
} = useContextMenuHandlers(ref);
|
||||||
|
|
||||||
@ -199,15 +198,6 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
|||||||
const getRootElement = useLastCallback(() => ref.current!);
|
const getRootElement = useLastCallback(() => ref.current!);
|
||||||
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.forward-context-menu .bubble'));
|
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.forward-context-menu .bubble'));
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!shouldRender) {
|
if (!shouldRender) {
|
||||||
handleContextMenuClose();
|
handleContextMenuClose();
|
||||||
@ -296,11 +286,10 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
|||||||
{(isShowingReply || isForwarding) && !isContextMenuDisabled && (
|
{(isShowingReply || isForwarding) && !isContextMenuDisabled && (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
transformOriginX={transformOriginX}
|
anchor={contextMenuAnchor}
|
||||||
transformOriginY={transformOriginY}
|
getTriggerElement={getTriggerElement}
|
||||||
positionX={positionX}
|
getRootElement={getRootElement}
|
||||||
positionY={positionY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
|
||||||
className="forward-context-menu"
|
className="forward-context-menu"
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
onCloseAnimationEnd={handleContextMenuHide}
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { withGlobal } from '../../../global';
|
|||||||
import type { ApiSticker, ApiVideo } from '../../../api/types';
|
import type { ApiSticker, ApiVideo } from '../../../api/types';
|
||||||
import type { GlobalActions } from '../../../global';
|
import type { GlobalActions } from '../../../global';
|
||||||
import type { ThreadId } from '../../../types';
|
import type { ThreadId } from '../../../types';
|
||||||
|
import type { MenuPositionOptions } from '../../ui/Menu';
|
||||||
|
|
||||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { selectIsContextMenuTranslucent, selectTabState } from '../../../global/selectors';
|
import { selectIsContextMenuTranslucent, selectTabState } from '../../../global/selectors';
|
||||||
@ -61,12 +62,8 @@ export type OwnProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
isAttachmentModal?: boolean;
|
isAttachmentModal?: boolean;
|
||||||
canSendPlainText?: boolean;
|
canSendPlainText?: boolean;
|
||||||
positionX?: 'left' | 'right';
|
}
|
||||||
positionY?: 'top' | 'bottom';
|
& MenuPositionOptions;
|
||||||
transformOriginX?: number;
|
|
||||||
transformOriginY?: number;
|
|
||||||
style?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
isLeftColumnShown: boolean;
|
isLeftColumnShown: boolean;
|
||||||
@ -87,11 +84,6 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
isAttachmentModal,
|
isAttachmentModal,
|
||||||
canSendPlainText,
|
canSendPlainText,
|
||||||
className,
|
className,
|
||||||
positionX,
|
|
||||||
positionY,
|
|
||||||
transformOriginX,
|
|
||||||
transformOriginY,
|
|
||||||
style,
|
|
||||||
isBackgroundTranslucent,
|
isBackgroundTranslucent,
|
||||||
onLoad,
|
onLoad,
|
||||||
onClose,
|
onClose,
|
||||||
@ -103,6 +95,7 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
onSearchOpen,
|
onSearchOpen,
|
||||||
addRecentEmoji,
|
addRecentEmoji,
|
||||||
addRecentCustomEmoji,
|
addRecentCustomEmoji,
|
||||||
|
...menuPositionOptions
|
||||||
}) => {
|
}) => {
|
||||||
const [activeTab, setActiveTab] = useState<number>(0);
|
const [activeTab, setActiveTab] = useState<number>(0);
|
||||||
const [recentEmojis, setRecentEmojis] = useState<string[]>([]);
|
const [recentEmojis, setRecentEmojis] = useState<string[]>([]);
|
||||||
@ -325,8 +318,6 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<UnfreezableMenu
|
<UnfreezableMenu
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
positionX={isAttachmentModal ? positionX : 'left'}
|
|
||||||
positionY={isAttachmentModal ? positionY : 'bottom'}
|
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
withPortal={isAttachmentModal}
|
withPortal={isAttachmentModal}
|
||||||
className={buildClassName('SymbolMenu', className)}
|
className={buildClassName('SymbolMenu', className)}
|
||||||
@ -335,9 +326,11 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
|
|||||||
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
||||||
noCloseOnBackdrop={!IS_TOUCH_ENV}
|
noCloseOnBackdrop={!IS_TOUCH_ENV}
|
||||||
noCompact
|
noCompact
|
||||||
transformOriginX={transformOriginX}
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
transformOriginY={transformOriginY}
|
{...(isAttachmentModal ? menuPositionOptions : {
|
||||||
style={style}
|
positionX: 'left',
|
||||||
|
positionY: 'bottom',
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</UnfreezableMenu>
|
</UnfreezableMenu>
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import buildClassName from '../../../util/buildClassName';
|
|||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../../hooks/useMenuPosition';
|
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
|
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
|
||||||
@ -89,7 +88,7 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
|||||||
const triggerRef = useRef<HTMLDivElement>(null);
|
const triggerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [isSymbolMenuLoaded, onSymbolMenuLoadingComplete] = useFlag();
|
const [isSymbolMenuLoaded, onSymbolMenuLoadingComplete] = useFlag();
|
||||||
const [contextMenuPosition, setContextMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
|
const [contextMenuAnchor, setContextMenuAnchor] = useState<IAnchorPosition | undefined>(undefined);
|
||||||
|
|
||||||
const symbolMenuButtonClassName = buildClassName(
|
const symbolMenuButtonClassName = buildClassName(
|
||||||
'mobile-symbol-menu-button',
|
'mobile-symbol-menu-button',
|
||||||
@ -106,7 +105,7 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
|||||||
const triggerEl = triggerRef.current;
|
const triggerEl = triggerRef.current;
|
||||||
if (!triggerEl) return;
|
if (!triggerEl) return;
|
||||||
const { x, y } = triggerEl.getBoundingClientRect();
|
const { x, y } = triggerEl.getBoundingClientRect();
|
||||||
setContextMenuPosition({ x, y });
|
setContextMenuAnchor({ x, y });
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSearchOpen = useLastCallback((type: 'stickers' | 'gifs') => {
|
const handleSearchOpen = useLastCallback((type: 'stickers' | 'gifs') => {
|
||||||
@ -141,16 +140,6 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
|||||||
const getMenuElement = useLastCallback(() => document.querySelector('#portals .SymbolMenu .bubble'));
|
const getMenuElement = useLastCallback(() => document.querySelector('#portals .SymbolMenu .bubble'));
|
||||||
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
getLayout,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
@ -199,11 +188,11 @@ const SymbolMenuButton: FC<OwnProps> = ({
|
|||||||
isAttachmentModal={isAttachmentModal}
|
isAttachmentModal={isAttachmentModal}
|
||||||
canSendPlainText={canSendPlainText}
|
canSendPlainText={canSendPlainText}
|
||||||
className={buildClassName(className, forceDarkTheme && 'component-theme-dark')}
|
className={buildClassName(className, forceDarkTheme && 'component-theme-dark')}
|
||||||
positionX={isAttachmentModal ? positionX : undefined}
|
anchor={isAttachmentModal ? contextMenuAnchor : undefined}
|
||||||
positionY={isAttachmentModal ? positionY : undefined}
|
getTriggerElement={isAttachmentModal ? getTriggerElement : undefined}
|
||||||
transformOriginX={isAttachmentModal ? transformOriginX : undefined}
|
getRootElement={isAttachmentModal ? getRootElement : undefined}
|
||||||
transformOriginY={isAttachmentModal ? transformOriginY : undefined}
|
getMenuElement={isAttachmentModal ? getMenuElement : undefined}
|
||||||
style={isAttachmentModal ? menuStyle : undefined}
|
getLayout={isAttachmentModal ? getLayout : undefined}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||||
memo, useEffect, useRef,
|
|
||||||
} from '../../../lib/teact/teact';
|
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -23,7 +21,6 @@ import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
|||||||
import useDerivedSignal from '../../../hooks/useDerivedSignal';
|
import useDerivedSignal from '../../../hooks/useDerivedSignal';
|
||||||
import useDerivedState from '../../../hooks/useDerivedState';
|
import useDerivedState from '../../../hooks/useDerivedState';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated';
|
import useShowTransitionDeprecated from '../../../hooks/useShowTransitionDeprecated';
|
||||||
import useSyncEffect from '../../../hooks/useSyncEffect';
|
import useSyncEffect from '../../../hooks/useSyncEffect';
|
||||||
@ -122,7 +119,7 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition, handleContextMenu,
|
isContextMenuOpen, contextMenuAnchor, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref, isEditing, true);
|
} = useContextMenuHandlers(ref, isEditing, true);
|
||||||
|
|
||||||
@ -132,15 +129,6 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
|
|||||||
() => ref.current!.querySelector('.web-page-preview-context-menu .bubble'),
|
() => ref.current!.querySelector('.web-page-preview-context-menu .bubble'),
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePreviewClick = useLastCallback((e: React.MouseEvent): void => {
|
const handlePreviewClick = useLastCallback((e: React.MouseEvent): void => {
|
||||||
handleContextMenu(e);
|
handleContextMenu(e);
|
||||||
});
|
});
|
||||||
@ -172,11 +160,10 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
transformOriginX={transformOriginX}
|
anchor={contextMenuAnchor}
|
||||||
transformOriginY={transformOriginY}
|
getTriggerElement={getTriggerElement}
|
||||||
positionX={positionX}
|
getRootElement={getRootElement}
|
||||||
positionY={positionY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
|
||||||
className="web-page-preview-context-menu"
|
className="web-page-preview-context-menu"
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
onCloseAnimationEnd={handleContextMenuHide}
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
@ -185,7 +172,7 @@ const WebPagePreview: FC<OwnProps & StateProps> = ({
|
|||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
isInvertedMedia ? (
|
isInvertedMedia ? (
|
||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
<MenuItem icon="move-caption-up" onClick={() => updateIsInvertedMedia(undefined)}>
|
<MenuItem icon="move-caption-up" onClick={() => updateIsInvertedMedia(undefined)}>
|
||||||
{lang('PreviewSender.MoveTextUp')}
|
{lang('PreviewSender.MoveTextUp')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
@ -20,15 +25,10 @@ import type {
|
|||||||
ApiTypeStory,
|
ApiTypeStory,
|
||||||
ApiUser,
|
ApiUser,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type {
|
import type { ActiveEmojiInteraction, ChatTranslatedMessages, MessageListType } from '../../../global/types';
|
||||||
ActiveEmojiInteraction,
|
|
||||||
ChatTranslatedMessages,
|
|
||||||
MessageListType,
|
|
||||||
} from '../../../global/types';
|
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
import type {
|
import type {
|
||||||
FocusDirection, IAlbum, ISettings, ScrollTargetPosition,
|
FocusDirection, IAlbum, ISettings, ScrollTargetPosition, ThreadId,
|
||||||
ThreadId,
|
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
import type { PinnedIntersectionChangedCallback } from '../hooks/usePinnedMessage';
|
import type { PinnedIntersectionChangedCallback } from '../hooks/usePinnedMessage';
|
||||||
@ -66,7 +66,8 @@ import {
|
|||||||
selectActiveDownloads,
|
selectActiveDownloads,
|
||||||
selectAnimatedEmoji,
|
selectAnimatedEmoji,
|
||||||
selectCanAutoLoadMedia,
|
selectCanAutoLoadMedia,
|
||||||
selectCanAutoPlayMedia, selectCanReplyToMessage,
|
selectCanAutoPlayMedia,
|
||||||
|
selectCanReplyToMessage,
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatFullInfo,
|
selectChatFullInfo,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
@ -443,7 +444,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen,
|
isContextMenuOpen,
|
||||||
contextMenuPosition,
|
contextMenuAnchor,
|
||||||
contextMenuTarget,
|
contextMenuTarget,
|
||||||
handleBeforeContextMenu,
|
handleBeforeContextMenu,
|
||||||
handleContextMenu: onContextMenu,
|
handleContextMenu: onContextMenu,
|
||||||
@ -535,7 +536,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
) && !album.messages.some((msg) => Object.keys(msg.content).length === 0);
|
) && !album.messages.some((msg) => Object.keys(msg.content).length === 0);
|
||||||
const isInDocumentGroupNotFirst = isInDocumentGroup && !isFirstInDocumentGroup;
|
const isInDocumentGroupNotFirst = isInDocumentGroup && !isFirstInDocumentGroup;
|
||||||
const isInDocumentGroupNotLast = isInDocumentGroup && !isLastInDocumentGroup;
|
const isInDocumentGroupNotLast = isInDocumentGroup && !isLastInDocumentGroup;
|
||||||
const isContextMenuShown = contextMenuPosition !== undefined;
|
const isContextMenuShown = contextMenuAnchor !== undefined;
|
||||||
const canShowActionButton = (
|
const canShowActionButton = (
|
||||||
!(isContextMenuShown || isInSelectMode || isForwarding)
|
!(isContextMenuShown || isInSelectMode || isForwarding)
|
||||||
&& !isInDocumentGroupNotLast
|
&& !isInDocumentGroupNotLast
|
||||||
@ -1650,10 +1651,10 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{contextMenuPosition && (
|
{contextMenuAnchor && (
|
||||||
<ContextMenuContainer
|
<ContextMenuContainer
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
anchor={contextMenuPosition}
|
anchor={contextMenuAnchor}
|
||||||
targetHref={contextMenuTarget?.matches('a[href]') ? (contextMenuTarget as HTMLAnchorElement).href : undefined}
|
targetHref={contextMenuTarget?.matches('a[href]') ? (contextMenuTarget as HTMLAnchorElement).href : undefined}
|
||||||
message={message}
|
message={message}
|
||||||
album={album}
|
album={album}
|
||||||
|
|||||||
@ -28,7 +28,6 @@ import { getMessageCopyOptions } from './helpers/copyOptions';
|
|||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import AvatarList from '../../common/AvatarList';
|
import AvatarList from '../../common/AvatarList';
|
||||||
@ -323,15 +322,11 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
}, ANIMATION_DURATION);
|
}, ANIMATION_DURATION);
|
||||||
}, [isOpen, markIsReady, unmarkIsReady]);
|
}, [isOpen, markIsReady, unmarkIsReady]);
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style, menuStyle, withScroll,
|
|
||||||
} = useMenuPosition(anchor, getTriggerElement, getRootElement, getMenuElement, getLayout);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
disableScrolling(withScroll ? scrollableRef.current : undefined, '.ReactionPicker');
|
disableScrolling(scrollableRef.current, '.ReactionPicker');
|
||||||
|
|
||||||
return enableScrolling;
|
return enableScrolling;
|
||||||
}, [withScroll]);
|
}, [isOpen]);
|
||||||
|
|
||||||
const handleOpenMessageReactionPicker = useLastCallback((position: IAnchorPosition) => {
|
const handleOpenMessageReactionPicker = useLastCallback((position: IAnchorPosition) => {
|
||||||
onReactionPickerOpen!(position);
|
onReactionPickerOpen!(position);
|
||||||
@ -342,12 +337,11 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
<Menu
|
<Menu
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
transformOriginX={transformOriginX}
|
anchor={anchor}
|
||||||
transformOriginY={transformOriginY}
|
getTriggerElement={getTriggerElement}
|
||||||
positionX={positionX}
|
getRootElement={getRootElement}
|
||||||
positionY={positionY}
|
getMenuElement={getMenuElement}
|
||||||
style={style}
|
getLayout={getLayout}
|
||||||
bubbleStyle={menuStyle}
|
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
'MessageContextMenu', 'fluid', withReactions && 'with-reactions',
|
'MessageContextMenu', 'fluid', withReactions && 'with-reactions',
|
||||||
)}
|
)}
|
||||||
@ -376,13 +370,12 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
ref={scrollableRef}
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
'MessageContextMenu_items scrollable-content custom-scroll',
|
'MessageContextMenu_items scrollable-content custom-scroll',
|
||||||
areItemsHidden && 'MessageContextMenu_items-hidden',
|
areItemsHidden && 'MessageContextMenu_items-hidden',
|
||||||
)}
|
)}
|
||||||
style={menuStyle}
|
|
||||||
dir={lang.isRtl ? 'rtl' : undefined}
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
ref={scrollableRef}
|
|
||||||
>
|
>
|
||||||
{canSendNow && <MenuItem icon="send-outline" onClick={onSend}>{lang('MessageScheduleSend')}</MenuItem>}
|
{canSendNow && <MenuItem icon="send-outline" onClick={onSend}>{lang('MessageScheduleSend')}</MenuItem>}
|
||||||
{canReschedule && (
|
{canReschedule && (
|
||||||
|
|||||||
@ -65,7 +65,7 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
|||||||
threshold: 1,
|
threshold: 1,
|
||||||
});
|
});
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition,
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
handleBeforeContextMenu, handleContextMenu,
|
handleBeforeContextMenu, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref, undefined, true, IS_ANDROID);
|
} = useContextMenuHandlers(ref, undefined, true, IS_ANDROID);
|
||||||
@ -183,10 +183,10 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{contextMenuPosition && (
|
{contextMenuAnchor && (
|
||||||
<SponsoredMessageContextMenuContainer
|
<SponsoredMessageContextMenuContainer
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
anchor={contextMenuPosition}
|
anchor={contextMenuAnchor}
|
||||||
message={message!}
|
message={message!}
|
||||||
onAboutAdsClick={openAboutAdsModal}
|
onAboutAdsClick={openAboutAdsModal}
|
||||||
onReportAd={handleReportSponsoredMessage}
|
onReportAd={handleReportSponsoredMessage}
|
||||||
|
|||||||
@ -5,15 +5,25 @@ import { getActions, getGlobal, withGlobal } from '../../../../global';
|
|||||||
import type { IAnchorPosition } from '../../../../types';
|
import type { IAnchorPosition } from '../../../../types';
|
||||||
import {
|
import {
|
||||||
type ApiAvailableEffect,
|
type ApiAvailableEffect,
|
||||||
type ApiMessage, type ApiMessageEntity,
|
type ApiMessage,
|
||||||
type ApiReaction, type ApiReactionCustomEmoji, type ApiSticker, type ApiStory, type ApiStorySkipped,
|
type ApiMessageEntity,
|
||||||
|
type ApiReaction,
|
||||||
|
type ApiReactionCustomEmoji,
|
||||||
|
type ApiSticker,
|
||||||
|
type ApiStory,
|
||||||
|
type ApiStorySkipped,
|
||||||
MAIN_THREAD_ID,
|
MAIN_THREAD_ID,
|
||||||
} from '../../../../api/types';
|
} from '../../../../api/types';
|
||||||
|
|
||||||
import { getReactionKey, getStoryKey, isUserId } from '../../../../global/helpers';
|
import { getReactionKey, getStoryKey, isUserId } from '../../../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectChat, selectChatFullInfo, selectChatMessage, selectIsContextMenuTranslucent, selectIsCurrentUserPremium,
|
selectChat,
|
||||||
selectPeerStory, selectTabState,
|
selectChatFullInfo,
|
||||||
|
selectChatMessage,
|
||||||
|
selectIsContextMenuTranslucent,
|
||||||
|
selectIsCurrentUserPremium,
|
||||||
|
selectPeerStory,
|
||||||
|
selectTabState,
|
||||||
} from '../../../../global/selectors';
|
} from '../../../../global/selectors';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
import parseHtmlAsFormattedText from '../../../../util/parseHtmlAsFormattedText';
|
import parseHtmlAsFormattedText from '../../../../util/parseHtmlAsFormattedText';
|
||||||
@ -23,7 +33,6 @@ import { buildCustomEmojiHtml } from '../../composer/helpers/customEmoji';
|
|||||||
import { getIsMobile } from '../../../../hooks/useAppLayout';
|
import { getIsMobile } from '../../../../hooks/useAppLayout';
|
||||||
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../../hooks/useCurrentOrPrev';
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../../../hooks/useOldLang';
|
import useOldLang from '../../../../hooks/useOldLang';
|
||||||
|
|
||||||
import CustomEmojiPicker from '../../../common/CustomEmojiPicker';
|
import CustomEmojiPicker from '../../../common/CustomEmojiPicker';
|
||||||
@ -104,9 +113,6 @@ const ReactionPicker: FC<OwnProps & StateProps> = ({
|
|||||||
? -(menuRef.current.offsetWidth - REACTION_SELECTOR_WIDTH) / 2 - FULL_PICKER_SHIFT_DELTA.x / 2
|
? -(menuRef.current.offsetWidth - REACTION_SELECTOR_WIDTH) / 2 - FULL_PICKER_SHIFT_DELTA.x / 2
|
||||||
: 0,
|
: 0,
|
||||||
}));
|
}));
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style,
|
|
||||||
} = useMenuPosition(renderingPosition, getTriggerElement, getRootElement, getMenuElement, getLayout);
|
|
||||||
|
|
||||||
const handleToggleCustomReaction = useLastCallback((sticker: ApiSticker) => {
|
const handleToggleCustomReaction = useLastCallback((sticker: ApiSticker) => {
|
||||||
if (!renderedChatId || !renderedMessageId) {
|
if (!renderedChatId || !renderedMessageId) {
|
||||||
@ -213,11 +219,11 @@ const ReactionPicker: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
withPortal
|
withPortal
|
||||||
noCompact
|
noCompact
|
||||||
positionX={positionX}
|
anchor={renderingPosition}
|
||||||
positionY={positionY}
|
getTriggerElement={getTriggerElement}
|
||||||
transformOriginX={transformOriginX}
|
getRootElement={getRootElement}
|
||||||
transformOriginY={transformOriginY}
|
getMenuElement={getMenuElement}
|
||||||
style={style}
|
getLayout={getLayout}
|
||||||
backdropExcludedSelector=".Modal.confirm"
|
backdropExcludedSelector=".Modal.confirm"
|
||||||
onClose={closeReactionPicker}
|
onClose={closeReactionPicker}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import React, { memo, useRef } from '../../../../lib/teact/teact';
|
import React, { memo, useRef } from '../../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../../global';
|
import { getActions } from '../../../../global';
|
||||||
|
|
||||||
import type {
|
import type { ApiReaction, ApiSavedReactionTag } from '../../../../api/types';
|
||||||
ApiReaction, ApiSavedReactionTag,
|
|
||||||
} from '../../../../api/types';
|
|
||||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
@ -12,7 +10,6 @@ import { REM } from '../../../common/helpers/mediaDimensions';
|
|||||||
import useContextMenuHandlers from '../../../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../../../hooks/useContextMenuHandlers';
|
||||||
import useFlag from '../../../../hooks/useFlag';
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../../../hooks/useOldLang';
|
import useOldLang from '../../../../hooks/useOldLang';
|
||||||
|
|
||||||
import ReactionAnimatedEmoji from '../../../common/reactions/ReactionAnimatedEmoji';
|
import ReactionAnimatedEmoji from '../../../common/reactions/ReactionAnimatedEmoji';
|
||||||
@ -88,7 +85,7 @@ const SavedTagButton = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen,
|
isContextMenuOpen,
|
||||||
contextMenuPosition,
|
contextMenuAnchor,
|
||||||
handleBeforeContextMenu,
|
handleBeforeContextMenu,
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
handleContextMenuClose,
|
handleContextMenuClose,
|
||||||
@ -98,18 +95,7 @@ const SavedTagButton = ({
|
|||||||
const getTriggerElement = useLastCallback(() => ref.current);
|
const getTriggerElement = useLastCallback(() => ref.current);
|
||||||
const getRootElement = useLastCallback(() => document.body);
|
const getRootElement = useLastCallback(() => document.body);
|
||||||
const getMenuElement = useLastCallback(() => menuRef.current);
|
const getMenuElement = useLastCallback(() => menuRef.current);
|
||||||
|
const getLayout = useLastCallback(() => ({ withPortal: true, shouldAvoidNegativePosition: true }));
|
||||||
const getLayout = () => ({ withPortal: true, shouldAvoidNegativePosition: true });
|
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
getLayout,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (withCount && count === 0) {
|
if (withCount && count === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -166,15 +152,15 @@ const SavedTagButton = ({
|
|||||||
onSubmit={handleRenameTag}
|
onSubmit={handleRenameTag}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{withContextMenu && contextMenuPosition && (
|
{withContextMenu && contextMenuAnchor && (
|
||||||
<Menu
|
<Menu
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
transformOriginX={transformOriginX}
|
anchor={contextMenuAnchor}
|
||||||
transformOriginY={transformOriginY}
|
getTriggerElement={getTriggerElement}
|
||||||
positionX={positionX}
|
getRootElement={getRootElement}
|
||||||
positionY={positionY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
getLayout={getLayout}
|
||||||
autoClose
|
autoClose
|
||||||
withPortal
|
withPortal
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMe
|
|||||||
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../hooks/useMedia';
|
||||||
import useMenuPosition from '../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import Icon from '../common/icons/Icon';
|
import Icon from '../common/icons/Icon';
|
||||||
@ -73,19 +72,10 @@ function MediaStory({
|
|||||||
}, [isDeleted, isFullyLoaded, story]);
|
}, [isDeleted, isFullyLoaded, story]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition,
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
handleBeforeContextMenu, handleContextMenu,
|
handleBeforeContextMenu, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(containerRef, !isOwn);
|
} = useContextMenuHandlers(containerRef, !isOwn);
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
getLayout,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
openStoryViewer({
|
openStoryViewer({
|
||||||
@ -156,14 +146,14 @@ function MediaStory({
|
|||||||
{isFullyLoaded && <MediaAreaOverlay story={story} />}
|
{isFullyLoaded && <MediaAreaOverlay story={story} />}
|
||||||
{isProtected && <span className="protector" />}
|
{isProtected && <span className="protector" />}
|
||||||
</div>
|
</div>
|
||||||
{contextMenuPosition !== undefined && (
|
{contextMenuAnchor !== undefined && (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
transformOriginX={transformOriginX}
|
anchor={contextMenuAnchor}
|
||||||
transformOriginY={transformOriginY}
|
getTriggerElement={getTriggerElement}
|
||||||
positionX={positionX}
|
getRootElement={getRootElement}
|
||||||
positionY={positionY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
getLayout={getLayout}
|
||||||
className={buildClassName(styles.contextMenu, 'story-context-menu')}
|
className={buildClassName(styles.contextMenu, 'story-context-menu')}
|
||||||
autoClose
|
autoClose
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { preventMessageInputBlurWithBubbling } from '../middle/helpers/preventMe
|
|||||||
|
|
||||||
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
import useStoryPreloader from './hooks/useStoryPreloader';
|
import useStoryPreloader from './hooks/useStoryPreloader';
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ function StoryRibbonButton({ peer, isArchived }: OwnProps) {
|
|||||||
useStoryPreloader(peer.id);
|
useStoryPreloader(peer.id);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition,
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
handleBeforeContextMenu, handleContextMenu,
|
handleBeforeContextMenu, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(ref);
|
} = useContextMenuHandlers(ref);
|
||||||
@ -53,16 +52,6 @@ function StoryRibbonButton({ peer, isArchived }: OwnProps) {
|
|||||||
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.story-peer-context-menu .bubble'));
|
const getMenuElement = useLastCallback(() => ref.current!.querySelector('.story-peer-context-menu .bubble'));
|
||||||
const getLayout = useLastCallback(() => ({ withPortal: true, isDense: true }));
|
const getLayout = useLastCallback(() => ({ withPortal: true, isDense: true }));
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
getLayout,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
if (isContextMenuOpen) return;
|
if (isContextMenuOpen) return;
|
||||||
|
|
||||||
@ -114,14 +103,14 @@ function StoryRibbonButton({ peer, isArchived }: OwnProps) {
|
|||||||
<div className={buildClassName(styles.name, peer.hasUnreadStories && styles.name_hasUnreadStory)}>
|
<div className={buildClassName(styles.name, peer.hasUnreadStories && styles.name_hasUnreadStory)}>
|
||||||
{isSelf ? lang('MyStory') : getSenderTitle(lang, peer)}
|
{isSelf ? lang('MyStory') : getSenderTitle(lang, peer)}
|
||||||
</div>
|
</div>
|
||||||
{contextMenuPosition !== undefined && (
|
{contextMenuAnchor !== undefined && (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
transformOriginX={transformOriginX}
|
anchor={contextMenuAnchor}
|
||||||
transformOriginY={transformOriginY}
|
getTriggerElement={getTriggerElement}
|
||||||
positionX={positionX}
|
getRootElement={getRootElement}
|
||||||
positionY={positionY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
getLayout={getLayout}
|
||||||
className={buildClassName('story-peer-context-menu', styles.contextMenu)}
|
className={buildClassName('story-peer-context-menu', styles.contextMenu)}
|
||||||
autoClose
|
autoClose
|
||||||
withPortal
|
withPortal
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback, useMemo,
|
useCallback, useMemo, useRef, useState,
|
||||||
useRef, useState,
|
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
@ -44,12 +43,11 @@ const DropdownMenu: FC<OwnProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
// eslint-disable-next-line no-null/no-null
|
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const toggleIsOpen = () => {
|
const toggleIsOpen = () => {
|
||||||
setIsOpen(!isOpen);
|
setIsOpen(!isOpen);
|
||||||
|
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
onClose?.();
|
onClose?.();
|
||||||
} else {
|
} else {
|
||||||
@ -96,7 +94,6 @@ const DropdownMenu: FC<OwnProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={dropdownRef}
|
|
||||||
className={`DropdownMenu ${className || ''}`}
|
className={`DropdownMenu ${className || ''}`}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onTransitionEnd={onTransitionEnd}
|
onTransitionEnd={onTransitionEnd}
|
||||||
@ -105,7 +102,6 @@ const DropdownMenu: FC<OwnProps> = ({
|
|||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
containerRef={dropdownRef}
|
|
||||||
isOpen={isOpen || Boolean(forceOpen)}
|
isOpen={isOpen || Boolean(forceOpen)}
|
||||||
className={className || ''}
|
className={className || ''}
|
||||||
transformOriginX={transformOriginX}
|
transformOriginX={transformOriginX}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
|||||||
import { useFastClick } from '../../hooks/useFastClick';
|
import { useFastClick } from '../../hooks/useFastClick';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../hooks/useMenuPosition';
|
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import Icon from '../common/icons/Icon';
|
import Icon from '../common/icons/Icon';
|
||||||
@ -120,7 +119,7 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
const [isTouched, markIsTouched, unmarkIsTouched] = useFlag();
|
const [isTouched, markIsTouched, unmarkIsTouched] = useFlag();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isContextMenuOpen, contextMenuPosition,
|
isContextMenuOpen, contextMenuAnchor,
|
||||||
handleBeforeContextMenu, handleContextMenu,
|
handleBeforeContextMenu, handleContextMenu,
|
||||||
handleContextMenuClose, handleContextMenuHide,
|
handleContextMenuClose, handleContextMenuHide,
|
||||||
} = useContextMenuHandlers(containerRef, !contextActions);
|
} = useContextMenuHandlers(containerRef, !contextActions);
|
||||||
@ -133,16 +132,6 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
const getLayout = useLastCallback(() => ({ withPortal: withPortalForMenu }));
|
const getLayout = useLastCallback(() => ({ withPortal: withPortalForMenu }));
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
getLayout,
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClickEvent = useLastCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
const handleClickEvent = useLastCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
const hasModifierKey = e.ctrlKey || e.metaKey || e.shiftKey;
|
const hasModifierKey = e.ctrlKey || e.metaKey || e.shiftKey;
|
||||||
if (!hasModifierKey && e.button === MouseButton.Main) {
|
if (!hasModifierKey && e.button === MouseButton.Main) {
|
||||||
@ -215,7 +204,7 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
disabled && 'disabled',
|
disabled && 'disabled',
|
||||||
allowDisabledClick && 'click-allowed',
|
allowDisabledClick && 'click-allowed',
|
||||||
inactive && 'inactive',
|
inactive && 'inactive',
|
||||||
contextMenuPosition && 'has-menu-open',
|
contextMenuAnchor && 'has-menu-open',
|
||||||
focus && 'focus',
|
focus && 'focus',
|
||||||
destructive && 'destructive',
|
destructive && 'destructive',
|
||||||
multiline && 'multiline',
|
multiline && 'multiline',
|
||||||
@ -267,14 +256,14 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
{rightElement}
|
{rightElement}
|
||||||
</ButtonElementTag>
|
</ButtonElementTag>
|
||||||
{contextActions && contextMenuPosition !== undefined && (
|
{contextActions && contextMenuAnchor !== undefined && (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
transformOriginX={transformOriginX}
|
anchor={contextMenuAnchor}
|
||||||
transformOriginY={transformOriginY}
|
getTriggerElement={getTriggerElement}
|
||||||
positionX={positionX}
|
getRootElement={getRootElement}
|
||||||
positionY={positionY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
getLayout={getLayout}
|
||||||
className="ListItem-context-menu with-menu-transitions"
|
className="ListItem-context-menu with-menu-transitions"
|
||||||
autoClose
|
autoClose
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import React, { type FC, memo, useEffect } from '../../lib/teact/teact';
|
import React, {
|
||||||
|
type FC, memo, useEffect, useRef,
|
||||||
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { MenuPositionOptions } from '../../hooks/useMenuPosition';
|
||||||
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import buildStyle from '../../util/buildStyle';
|
|
||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
import freezeWhenClosed from '../../util/hoc/freezeWhenClosed';
|
import freezeWhenClosed from '../../util/hoc/freezeWhenClosed';
|
||||||
import { IS_BACKDROP_BLUR_SUPPORTED } from '../../util/windowEnvironment';
|
import { IS_BACKDROP_BLUR_SUPPORTED } from '../../util/windowEnvironment';
|
||||||
@ -13,6 +16,7 @@ import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'
|
|||||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||||
import useKeyboardListNavigation from '../../hooks/useKeyboardListNavigation';
|
import useKeyboardListNavigation from '../../hooks/useKeyboardListNavigation';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
import useMenuPosition from '../../hooks/useMenuPosition';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
import useVirtualBackdrop from '../../hooks/useVirtualBackdrop';
|
import useVirtualBackdrop from '../../hooks/useVirtualBackdrop';
|
||||||
|
|
||||||
@ -20,54 +24,44 @@ import Portal from './Portal';
|
|||||||
|
|
||||||
import './Menu.scss';
|
import './Menu.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
export type { MenuPositionOptions } from '../../hooks/useMenuPosition';
|
||||||
ref?: React.RefObject<HTMLDivElement>;
|
|
||||||
containerRef?: React.RefObject<HTMLElement>;
|
type OwnProps =
|
||||||
isOpen: boolean;
|
{
|
||||||
shouldCloseFast?: boolean;
|
ref?: React.RefObject<HTMLDivElement>;
|
||||||
id?: string;
|
isOpen: boolean;
|
||||||
className?: string;
|
shouldCloseFast?: boolean;
|
||||||
bubbleClassName?: string;
|
id?: string;
|
||||||
style?: string;
|
className?: string;
|
||||||
bubbleStyle?: string;
|
bubbleClassName?: string;
|
||||||
ariaLabelledBy?: string;
|
ariaLabelledBy?: string;
|
||||||
transformOriginX?: number;
|
autoClose?: boolean;
|
||||||
transformOriginY?: number;
|
footer?: string;
|
||||||
positionX?: 'left' | 'right';
|
noCloseOnBackdrop?: boolean;
|
||||||
positionY?: 'top' | 'bottom';
|
backdropExcludedSelector?: string;
|
||||||
autoClose?: boolean;
|
noCompact?: boolean;
|
||||||
footer?: string;
|
onKeyDown?: (e: React.KeyboardEvent<any>) => void;
|
||||||
noCloseOnBackdrop?: boolean;
|
onCloseAnimationEnd?: () => void;
|
||||||
backdropExcludedSelector?: string;
|
onClose: () => void;
|
||||||
noCompact?: boolean;
|
onMouseEnter?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||||
onKeyDown?: (e: React.KeyboardEvent<any>) => void;
|
onMouseEnterBackdrop?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||||
onCloseAnimationEnd?: () => void;
|
onMouseLeave?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||||
onClose: () => void;
|
withPortal?: boolean;
|
||||||
onMouseEnter?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
children?: React.ReactNode;
|
||||||
onMouseEnterBackdrop?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
}
|
||||||
onMouseLeave?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
& MenuPositionOptions;
|
||||||
withPortal?: boolean;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ANIMATION_DURATION = 200;
|
const ANIMATION_DURATION = 200;
|
||||||
|
|
||||||
const Menu: FC<OwnProps> = ({
|
const Menu: FC<OwnProps> = ({
|
||||||
ref: externalRef,
|
ref: externalRef,
|
||||||
containerRef,
|
|
||||||
shouldCloseFast,
|
shouldCloseFast,
|
||||||
isOpen,
|
isOpen,
|
||||||
id,
|
id,
|
||||||
className,
|
className,
|
||||||
bubbleClassName,
|
bubbleClassName,
|
||||||
style,
|
|
||||||
bubbleStyle,
|
|
||||||
ariaLabelledBy,
|
ariaLabelledBy,
|
||||||
children,
|
children,
|
||||||
transformOriginX,
|
|
||||||
transformOriginY,
|
|
||||||
positionX = 'left',
|
|
||||||
positionY = 'top',
|
|
||||||
autoClose = false,
|
autoClose = false,
|
||||||
footer,
|
footer,
|
||||||
noCloseOnBackdrop = false,
|
noCloseOnBackdrop = false,
|
||||||
@ -79,16 +73,20 @@ const Menu: FC<OwnProps> = ({
|
|||||||
onMouseLeave,
|
onMouseLeave,
|
||||||
withPortal,
|
withPortal,
|
||||||
onMouseEnterBackdrop,
|
onMouseEnterBackdrop,
|
||||||
|
...positionOptions
|
||||||
}) => {
|
}) => {
|
||||||
const { isTouchScreen } = useAppLayout();
|
const { isTouchScreen } = useAppLayout();
|
||||||
|
|
||||||
const { ref: menuRef } = useShowTransition({
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const { ref: bubbleRef } = useShowTransition({
|
||||||
isOpen,
|
isOpen,
|
||||||
ref: externalRef,
|
ref: externalRef,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
});
|
});
|
||||||
|
|
||||||
const backdropContainerRef = containerRef || menuRef;
|
useMenuPosition(isOpen, containerRef, bubbleRef, positionOptions);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => (isOpen ? captureEscKeyListener(onClose) : undefined),
|
() => (isOpen ? captureEscKeyListener(onClose) : undefined),
|
||||||
@ -107,11 +105,11 @@ const Menu: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
const handleKeyDown = useKeyboardListNavigation(menuRef, isOpen, autoClose ? onClose : undefined, undefined, true);
|
const handleKeyDown = useKeyboardListNavigation(bubbleRef, isOpen, autoClose ? onClose : undefined, undefined, true);
|
||||||
|
|
||||||
useVirtualBackdrop(
|
useVirtualBackdrop(
|
||||||
isOpen,
|
isOpen,
|
||||||
backdropContainerRef,
|
containerRef,
|
||||||
noCloseOnBackdrop ? undefined : onClose,
|
noCloseOnBackdrop ? undefined : onClose,
|
||||||
undefined,
|
undefined,
|
||||||
backdropExcludedSelector,
|
backdropExcludedSelector,
|
||||||
@ -119,16 +117,11 @@ const Menu: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const bubbleFullClassName = buildClassName(
|
const bubbleFullClassName = buildClassName(
|
||||||
'bubble menu-container custom-scroll',
|
'bubble menu-container custom-scroll',
|
||||||
positionY,
|
|
||||||
positionX,
|
|
||||||
footer && 'with-footer',
|
footer && 'with-footer',
|
||||||
bubbleClassName,
|
bubbleClassName,
|
||||||
shouldCloseFast && 'close-fast',
|
shouldCloseFast && 'close-fast',
|
||||||
);
|
);
|
||||||
|
|
||||||
const transformOriginYStyle = transformOriginY !== undefined ? `${transformOriginY}px` : undefined;
|
|
||||||
const transformOriginXStyle = transformOriginX !== undefined ? `${transformOriginX}px` : undefined;
|
|
||||||
|
|
||||||
const handleClick = useLastCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
const handleClick = useLastCallback((e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (autoClose) {
|
if (autoClose) {
|
||||||
@ -138,6 +131,7 @@ const Menu: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<div
|
<div
|
||||||
|
ref={containerRef}
|
||||||
id={id}
|
id={id}
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
'Menu',
|
'Menu',
|
||||||
@ -146,7 +140,6 @@ const Menu: FC<OwnProps> = ({
|
|||||||
withPortal && 'in-portal',
|
withPortal && 'in-portal',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
style={style}
|
|
||||||
aria-labelledby={ariaLabelledBy}
|
aria-labelledby={ariaLabelledBy}
|
||||||
role={ariaLabelledBy ? 'menu' : undefined}
|
role={ariaLabelledBy ? 'menu' : undefined}
|
||||||
onKeyDown={isOpen ? handleKeyDown : undefined}
|
onKeyDown={isOpen ? handleKeyDown : undefined}
|
||||||
@ -163,12 +156,8 @@ const Menu: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
role="presentation"
|
role="presentation"
|
||||||
ref={menuRef}
|
ref={bubbleRef}
|
||||||
className={bubbleFullClassName}
|
className={bubbleFullClassName}
|
||||||
style={buildStyle(
|
|
||||||
`transform-origin: ${transformOriginXStyle || positionX} ${transformOriginYStyle || positionY}`,
|
|
||||||
bubbleStyle,
|
|
||||||
)}
|
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import renderText from '../common/helpers/renderText';
|
|||||||
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
import { useFastClick } from '../../hooks/useFastClick';
|
import { useFastClick } from '../../hooks/useFastClick';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useMenuPosition from '../../hooks/useMenuPosition';
|
|
||||||
|
|
||||||
import Menu from './Menu';
|
import Menu from './Menu';
|
||||||
import MenuItem from './MenuItem';
|
import MenuItem from './MenuItem';
|
||||||
@ -106,7 +105,7 @@ const Tab: FC<OwnProps> = ({
|
|||||||
}, [isActive, previousActiveTab]);
|
}, [isActive, previousActiveTab]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
contextMenuPosition, handleContextMenu, handleBeforeContextMenu, handleContextMenuClose,
|
contextMenuAnchor, handleContextMenu, handleBeforeContextMenu, handleContextMenuClose,
|
||||||
handleContextMenuHide, isContextMenuOpen,
|
handleContextMenuHide, isContextMenuOpen,
|
||||||
} = useContextMenuHandlers(tabRef, !contextActions);
|
} = useContextMenuHandlers(tabRef, !contextActions);
|
||||||
|
|
||||||
@ -131,16 +130,6 @@ const Tab: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
const getLayout = useLastCallback(() => ({ withPortal: true }));
|
||||||
|
|
||||||
const {
|
|
||||||
positionX, positionY, transformOriginX, transformOriginY, style: menuStyle,
|
|
||||||
} = useMenuPosition(
|
|
||||||
contextMenuPosition,
|
|
||||||
getTriggerElement,
|
|
||||||
getRootElement,
|
|
||||||
getMenuElement,
|
|
||||||
getLayout,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={buildClassName('Tab', onClick && 'Tab--interactive', className)}
|
className={buildClassName('Tab', onClick && 'Tab--interactive', className)}
|
||||||
@ -158,14 +147,14 @@ const Tab: FC<OwnProps> = ({
|
|||||||
<i className="platform" />
|
<i className="platform" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{contextActions && contextMenuPosition !== undefined && (
|
{contextActions && contextMenuAnchor !== undefined && (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
transformOriginX={transformOriginX}
|
anchor={contextMenuAnchor}
|
||||||
transformOriginY={transformOriginY}
|
getTriggerElement={getTriggerElement}
|
||||||
positionX={positionX}
|
getRootElement={getRootElement}
|
||||||
positionY={positionY}
|
getMenuElement={getMenuElement}
|
||||||
style={menuStyle}
|
getLayout={getLayout}
|
||||||
className="Tab-context-menu"
|
className="Tab-context-menu"
|
||||||
autoClose
|
autoClose
|
||||||
onClose={handleContextMenuClose}
|
onClose={handleContextMenuClose}
|
||||||
|
|||||||
@ -6,10 +6,7 @@ import type { IAnchorPosition } from '../types';
|
|||||||
import type { Signal } from '../util/signals';
|
import type { Signal } from '../util/signals';
|
||||||
|
|
||||||
import { requestMutation } from '../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../lib/fasterdom/fasterdom';
|
||||||
import {
|
import { IS_IOS, IS_PWA, IS_TOUCH_ENV } from '../util/windowEnvironment';
|
||||||
IS_IOS,
|
|
||||||
IS_PWA, IS_TOUCH_ENV,
|
|
||||||
} from '../util/windowEnvironment';
|
|
||||||
import useLastCallback from './useLastCallback';
|
import useLastCallback from './useLastCallback';
|
||||||
|
|
||||||
const LONG_TAP_DURATION_MS = 200;
|
const LONG_TAP_DURATION_MS = 200;
|
||||||
@ -29,7 +26,7 @@ const useContextMenuHandlers = (
|
|||||||
getIsReady?: Signal<boolean>,
|
getIsReady?: Signal<boolean>,
|
||||||
) => {
|
) => {
|
||||||
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
|
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
|
||||||
const [contextMenuPosition, setContextMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
|
const [contextMenuAnchor, setContextMenuAnchor] = useState<IAnchorPosition | undefined>(undefined);
|
||||||
const [contextMenuTarget, setContextMenuTarget] = useState<HTMLElement | undefined>(undefined);
|
const [contextMenuTarget, setContextMenuTarget] = useState<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
const handleBeforeContextMenu = useLastCallback((e: React.MouseEvent) => {
|
const handleBeforeContextMenu = useLastCallback((e: React.MouseEvent) => {
|
||||||
@ -51,12 +48,12 @@ const useContextMenuHandlers = (
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (contextMenuPosition) {
|
if (contextMenuAnchor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsContextMenuOpen(true);
|
setIsContextMenuOpen(true);
|
||||||
setContextMenuPosition({ x: e.clientX, y: e.clientY });
|
setContextMenuAnchor({ x: e.clientX, y: e.clientY });
|
||||||
setContextMenuTarget(e.target as HTMLElement);
|
setContextMenuTarget(e.target as HTMLElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -65,7 +62,7 @@ const useContextMenuHandlers = (
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleContextMenuHide = useLastCallback(() => {
|
const handleContextMenuHide = useLastCallback(() => {
|
||||||
setContextMenuPosition(undefined);
|
setContextMenuAnchor(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Support context menu on touch devices
|
// Support context menu on touch devices
|
||||||
@ -93,7 +90,7 @@ const useContextMenuHandlers = (
|
|||||||
|
|
||||||
const { clientX, clientY, target } = originalEvent.touches[0];
|
const { clientX, clientY, target } = originalEvent.touches[0];
|
||||||
|
|
||||||
if (contextMenuPosition || (shouldDisableOnLink && (target as HTMLElement).matches('a[href]'))) {
|
if (contextMenuAnchor || (shouldDisableOnLink && (target as HTMLElement).matches('a[href]'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +126,7 @@ const useContextMenuHandlers = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsContextMenuOpen(true);
|
setIsContextMenuOpen(true);
|
||||||
setContextMenuPosition({ x: clientX, y: clientY });
|
setContextMenuAnchor({ x: clientX, y: clientY });
|
||||||
};
|
};
|
||||||
|
|
||||||
const startLongPressTimer = (e: TouchEvent) => {
|
const startLongPressTimer = (e: TouchEvent) => {
|
||||||
@ -156,12 +153,12 @@ const useContextMenuHandlers = (
|
|||||||
element.removeEventListener('touchmove', clearLongPressTimer);
|
element.removeEventListener('touchmove', clearLongPressTimer);
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
contextMenuPosition, isMenuDisabled, shouldDisableOnLongTap, elementRef, shouldDisableOnLink, getIsReady,
|
contextMenuAnchor, isMenuDisabled, shouldDisableOnLongTap, elementRef, shouldDisableOnLink, getIsReady,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isContextMenuOpen,
|
isContextMenuOpen,
|
||||||
contextMenuPosition,
|
contextMenuAnchor,
|
||||||
contextMenuTarget,
|
contextMenuTarget,
|
||||||
handleBeforeContextMenu,
|
handleBeforeContextMenu,
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
|
|||||||
@ -1,8 +1,35 @@
|
|||||||
import { useEffect, useState } from '../lib/teact/teact';
|
import type React from '../lib/teact/teact';
|
||||||
|
import { useLayoutEffect } from '../lib/teact/teact';
|
||||||
|
import { addExtraClass, setExtraStyles } from '../lib/teact/teact-dom';
|
||||||
|
|
||||||
import type { IAnchorPosition } from '../types';
|
import type { IAnchorPosition } from '../types';
|
||||||
|
|
||||||
interface Layout {
|
import { requestForcedReflow } from '../lib/fasterdom/fasterdom';
|
||||||
|
import { useStateRef } from './useStateRef';
|
||||||
|
|
||||||
|
interface StaticPositionOptions {
|
||||||
|
anchor?: IAnchorPosition;
|
||||||
|
positionX?: 'left' | 'right';
|
||||||
|
positionY?: 'top' | 'bottom';
|
||||||
|
transformOriginX?: number;
|
||||||
|
transformOriginY?: number;
|
||||||
|
style?: string;
|
||||||
|
bubbleStyle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DynamicPositionOptions {
|
||||||
|
anchor: IAnchorPosition;
|
||||||
|
getTriggerElement: () => HTMLElement | null;
|
||||||
|
getRootElement: () => HTMLElement | null;
|
||||||
|
getMenuElement: () => HTMLElement | null;
|
||||||
|
getLayout?: () => Layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MenuPositionOptions =
|
||||||
|
StaticPositionOptions
|
||||||
|
| DynamicPositionOptions;
|
||||||
|
|
||||||
|
export interface Layout {
|
||||||
extraPaddingX?: number;
|
extraPaddingX?: number;
|
||||||
extraTopPadding?: number;
|
extraTopPadding?: number;
|
||||||
extraMarginTop?: number;
|
extraMarginTop?: number;
|
||||||
@ -21,124 +48,175 @@ const EMPTY_RECT = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function useMenuPosition(
|
export default function useMenuPosition(
|
||||||
anchor: IAnchorPosition | undefined,
|
isOpen: boolean,
|
||||||
getTriggerElement: () => HTMLElement | null,
|
containerRef: React.RefObject<HTMLDivElement>,
|
||||||
getRootElement: () => HTMLElement | null,
|
bubbleRef: React.RefObject<HTMLDivElement>,
|
||||||
getMenuElement: () => HTMLElement | null,
|
options: MenuPositionOptions,
|
||||||
getLayout?: () => Layout,
|
|
||||||
) {
|
) {
|
||||||
const [positionX, setPositionX] = useState<'right' | 'left'>('right');
|
const optionsRef = useStateRef(options);
|
||||||
const [positionY, setPositionY] = useState<'top' | 'bottom'>('bottom');
|
|
||||||
const [transformOriginX, setTransformOriginX] = useState<number>();
|
|
||||||
const [transformOriginY, setTransformOriginY] = useState<number>();
|
|
||||||
const [withScroll, setWithScroll] = useState(false);
|
|
||||||
const [style, setStyle] = useState('');
|
|
||||||
const [menuStyle, setMenuStyle] = useState('opacity: 0;');
|
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const triggerEl = getTriggerElement();
|
if (!isOpen) return;
|
||||||
if (!anchor || !triggerEl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { x, y } = anchor;
|
const options2 = optionsRef.current;
|
||||||
const anchorX = x;
|
|
||||||
const anchorY = y;
|
|
||||||
|
|
||||||
const menuEl = getMenuElement();
|
if (!('getTriggerElement' in options2)) {
|
||||||
const rootEl = getRootElement();
|
applyStaticOptions(containerRef, bubbleRef, options2);
|
||||||
|
|
||||||
const {
|
|
||||||
extraPaddingX = 0,
|
|
||||||
extraTopPadding = 0,
|
|
||||||
extraMarginTop = 0,
|
|
||||||
topShiftY = 0,
|
|
||||||
menuElMinWidth = 0,
|
|
||||||
deltaX = 0,
|
|
||||||
shouldAvoidNegativePosition = false,
|
|
||||||
withPortal = false,
|
|
||||||
isDense = false,
|
|
||||||
} = getLayout?.() || {};
|
|
||||||
|
|
||||||
const marginTop = menuEl ? parseInt(getComputedStyle(menuEl).marginTop, 10) + extraMarginTop : undefined;
|
|
||||||
const { offsetWidth: menuElWidth, offsetHeight: menuElHeight } = menuEl || { offsetWidth: 0, offsetHeight: 0 };
|
|
||||||
const menuRect = menuEl ? {
|
|
||||||
width: Math.max(menuElWidth, menuElMinWidth),
|
|
||||||
height: menuElHeight + marginTop!,
|
|
||||||
} : EMPTY_RECT;
|
|
||||||
|
|
||||||
const rootRect = rootEl ? rootEl.getBoundingClientRect() : EMPTY_RECT;
|
|
||||||
|
|
||||||
let horizontalPosition: 'left' | 'right';
|
|
||||||
let verticalPosition: 'top' | 'bottom';
|
|
||||||
if (isDense || (x + menuRect.width + extraPaddingX < rootRect.width + rootRect.left)) {
|
|
||||||
x += 3;
|
|
||||||
horizontalPosition = 'left';
|
|
||||||
} else if (x - menuRect.width - rootRect.left > 0) {
|
|
||||||
horizontalPosition = 'right';
|
|
||||||
x -= 3;
|
|
||||||
} else {
|
} else {
|
||||||
horizontalPosition = 'left';
|
requestForcedReflow(() => {
|
||||||
x = 16;
|
const staticOptions = processDynamically(containerRef, bubbleRef, options2);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
applyStaticOptions(containerRef, bubbleRef, staticOptions);
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setPositionX(horizontalPosition);
|
}, [isOpen, containerRef, bubbleRef, optionsRef]);
|
||||||
|
}
|
||||||
|
|
||||||
x += deltaX;
|
function applyStaticOptions(
|
||||||
|
containerRef: React.RefObject<HTMLDivElement>,
|
||||||
|
bubbleRef: React.RefObject<HTMLDivElement>,
|
||||||
|
{
|
||||||
|
positionX = 'left',
|
||||||
|
positionY = 'top',
|
||||||
|
transformOriginX,
|
||||||
|
transformOriginY,
|
||||||
|
style,
|
||||||
|
bubbleStyle,
|
||||||
|
}: StaticPositionOptions,
|
||||||
|
) {
|
||||||
|
const containerEl = containerRef.current!;
|
||||||
|
const bubbleEl = bubbleRef.current!;
|
||||||
|
|
||||||
const yWithTopShift = y + topShiftY;
|
if (style) {
|
||||||
|
containerEl.style.cssText = style;
|
||||||
|
}
|
||||||
|
|
||||||
if (isDense || (yWithTopShift + menuRect.height < rootRect.height + rootRect.top)) {
|
if (bubbleStyle) {
|
||||||
verticalPosition = 'top';
|
bubbleEl.style.cssText = bubbleStyle;
|
||||||
y = yWithTopShift;
|
}
|
||||||
} else {
|
|
||||||
verticalPosition = 'bottom';
|
|
||||||
|
|
||||||
if (y - menuRect.height < rootRect.top + extraTopPadding) {
|
if (positionX) {
|
||||||
y = rootRect.top + rootRect.height;
|
addExtraClass(bubbleEl, positionX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (positionY) {
|
||||||
|
addExtraClass(bubbleEl, positionY);
|
||||||
|
}
|
||||||
|
|
||||||
|
setExtraStyles(bubbleEl, {
|
||||||
|
transformOrigin: [
|
||||||
|
transformOriginX ? `${transformOriginX}px` : positionX,
|
||||||
|
transformOriginY ? `${transformOriginY}px` : positionY,
|
||||||
|
].join(' '),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function processDynamically(
|
||||||
|
containerRef: React.RefObject<HTMLDivElement>,
|
||||||
|
bubbleRef: React.RefObject<HTMLDivElement>,
|
||||||
|
{
|
||||||
|
anchor,
|
||||||
|
getRootElement,
|
||||||
|
getMenuElement,
|
||||||
|
getTriggerElement,
|
||||||
|
getLayout,
|
||||||
|
}: DynamicPositionOptions,
|
||||||
|
) {
|
||||||
|
const triggerEl = getTriggerElement()!;
|
||||||
|
|
||||||
|
let { x, y } = anchor;
|
||||||
|
const anchorX = x;
|
||||||
|
const anchorY = y;
|
||||||
|
|
||||||
|
const menuEl = getMenuElement();
|
||||||
|
const rootEl = getRootElement();
|
||||||
|
|
||||||
|
const {
|
||||||
|
extraPaddingX = 0,
|
||||||
|
extraTopPadding = 0,
|
||||||
|
extraMarginTop = 0,
|
||||||
|
topShiftY = 0,
|
||||||
|
menuElMinWidth = 0,
|
||||||
|
deltaX = 0,
|
||||||
|
shouldAvoidNegativePosition = false,
|
||||||
|
withPortal = false,
|
||||||
|
isDense = false,
|
||||||
|
} = getLayout?.() || {};
|
||||||
|
|
||||||
|
const marginTop = menuEl ? parseInt(getComputedStyle(menuEl).marginTop, 10) + extraMarginTop : undefined;
|
||||||
|
const { offsetWidth: menuElWidth, offsetHeight: menuElHeight } = menuEl || { offsetWidth: 0, offsetHeight: 0 };
|
||||||
|
const menuRect = menuEl ? {
|
||||||
|
width: Math.max(menuElWidth, menuElMinWidth),
|
||||||
|
height: menuElHeight + marginTop!,
|
||||||
|
} : EMPTY_RECT;
|
||||||
|
|
||||||
|
const rootRect = rootEl ? rootEl.getBoundingClientRect() : EMPTY_RECT;
|
||||||
|
|
||||||
|
let positionX: 'left' | 'right';
|
||||||
|
let positionY: 'top' | 'bottom';
|
||||||
|
if (isDense || (x + menuRect.width + extraPaddingX < rootRect.width + rootRect.left)) {
|
||||||
|
x += 3;
|
||||||
|
positionX = 'left';
|
||||||
|
} else if (x - menuRect.width - rootRect.left > 0) {
|
||||||
|
positionX = 'right';
|
||||||
|
x -= 3;
|
||||||
|
} else {
|
||||||
|
positionX = 'left';
|
||||||
|
x = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
x += deltaX;
|
||||||
|
|
||||||
|
const yWithTopShift = y + topShiftY;
|
||||||
|
|
||||||
|
if (isDense || (yWithTopShift + menuRect.height < rootRect.height + rootRect.top)) {
|
||||||
|
positionY = 'top';
|
||||||
|
y = yWithTopShift;
|
||||||
|
} else {
|
||||||
|
positionY = 'bottom';
|
||||||
|
|
||||||
|
if (y - menuRect.height < rootRect.top + extraTopPadding) {
|
||||||
|
y = rootRect.top + rootRect.height;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setPositionY(verticalPosition);
|
const triggerRect = triggerEl.getBoundingClientRect();
|
||||||
|
|
||||||
const triggerRect = triggerEl.getBoundingClientRect();
|
const addedYForPortalPositioning = (withPortal ? triggerRect.top : 0);
|
||||||
|
const addedXForPortalPositioning = (withPortal ? triggerRect.left : 0);
|
||||||
|
|
||||||
const addedYForPortalPositioning = (withPortal ? triggerRect.top : 0);
|
const leftWithPossibleNegative = Math.min(
|
||||||
const addedXForPortalPositioning = (withPortal ? triggerRect.left : 0);
|
x - triggerRect.left,
|
||||||
|
rootRect.width - menuRect.width - MENU_POSITION_VISUAL_COMFORT_SPACE_PX,
|
||||||
|
);
|
||||||
|
let left = (positionX === 'left'
|
||||||
|
? (withPortal || shouldAvoidNegativePosition
|
||||||
|
? Math.max(MENU_POSITION_VISUAL_COMFORT_SPACE_PX, leftWithPossibleNegative)
|
||||||
|
: leftWithPossibleNegative)
|
||||||
|
: (x - triggerRect.left)) + addedXForPortalPositioning;
|
||||||
|
let top = y - triggerRect.top + addedYForPortalPositioning;
|
||||||
|
|
||||||
const leftWithPossibleNegative = Math.min(
|
if (isDense) {
|
||||||
x - triggerRect.left,
|
left = Math.min(left, rootRect.width - menuRect.width - MENU_POSITION_VISUAL_COMFORT_SPACE_PX);
|
||||||
rootRect.width - menuRect.width - MENU_POSITION_VISUAL_COMFORT_SPACE_PX,
|
top = Math.min(top, rootRect.height - menuRect.height - MENU_POSITION_VISUAL_COMFORT_SPACE_PX);
|
||||||
);
|
}
|
||||||
let left = (horizontalPosition === 'left'
|
|
||||||
? (withPortal || shouldAvoidNegativePosition
|
|
||||||
? Math.max(MENU_POSITION_VISUAL_COMFORT_SPACE_PX, leftWithPossibleNegative)
|
|
||||||
: leftWithPossibleNegative)
|
|
||||||
: (x - triggerRect.left)) + addedXForPortalPositioning;
|
|
||||||
let top = y - triggerRect.top + addedYForPortalPositioning;
|
|
||||||
|
|
||||||
if (isDense) {
|
// Avoid hiding external parts of menus on mobile devices behind the edges of the screen (ReactionSelector for example)
|
||||||
left = Math.min(left, rootRect.width - menuRect.width - MENU_POSITION_VISUAL_COMFORT_SPACE_PX);
|
const addedXForMenuPositioning = menuElMinWidth ? Math.max(0, (menuElMinWidth - menuElWidth) / 2) : 0;
|
||||||
top = Math.min(top, rootRect.height - menuRect.height - MENU_POSITION_VISUAL_COMFORT_SPACE_PX);
|
if (left - addedXForMenuPositioning < 0 && shouldAvoidNegativePosition) {
|
||||||
}
|
left = addedXForMenuPositioning + MENU_POSITION_VISUAL_COMFORT_SPACE_PX;
|
||||||
|
}
|
||||||
|
|
||||||
// Avoid hiding external parts of menus on mobile devices behind the edges of the screen (ReactionSelector for example)
|
const offsetX = (anchorX + addedXForPortalPositioning - triggerRect.left) - left;
|
||||||
const addedXForMenuPositioning = menuElMinWidth ? Math.max(0, (menuElMinWidth - menuElWidth) / 2) : 0;
|
const offsetY = (anchorY + addedYForPortalPositioning - triggerRect.top) - top - (marginTop || 0);
|
||||||
if (left - addedXForMenuPositioning < 0 && shouldAvoidNegativePosition) {
|
const transformOriginX = positionX === 'left' ? offsetX : menuRect.width + offsetX;
|
||||||
left = addedXForMenuPositioning + MENU_POSITION_VISUAL_COMFORT_SPACE_PX;
|
const transformOriginY = positionY === 'bottom' ? menuRect.height + offsetY : offsetY;
|
||||||
}
|
|
||||||
|
|
||||||
const menuMaxHeight = rootRect.height - MENU_POSITION_BOTTOM_MARGIN - (marginTop || 0);
|
const menuMaxHeight = rootRect.height - MENU_POSITION_BOTTOM_MARGIN - (marginTop || 0);
|
||||||
|
const bubbleStyle = `max-height: ${menuMaxHeight}px;`;
|
||||||
setWithScroll(menuMaxHeight < menuRect.height);
|
const style = `left: ${left}px; top: ${top}px`;
|
||||||
setMenuStyle(`max-height: ${menuMaxHeight}px;`);
|
|
||||||
setStyle(`left: ${left}px; top: ${top}px`);
|
|
||||||
const offsetX = (anchorX + addedXForPortalPositioning - triggerRect.left) - left;
|
|
||||||
const offsetY = (anchorY + addedYForPortalPositioning - triggerRect.top) - top - (marginTop || 0);
|
|
||||||
setTransformOriginX(horizontalPosition === 'left' ? offsetX : menuRect.width + offsetX);
|
|
||||||
setTransformOriginY(verticalPosition === 'bottom' ? menuRect.height + offsetY : offsetY);
|
|
||||||
}, [
|
|
||||||
anchor, getMenuElement, getRootElement, getTriggerElement, getLayout,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
positionX,
|
positionX,
|
||||||
@ -146,7 +224,6 @@ export default function useMenuPosition(
|
|||||||
transformOriginX,
|
transformOriginX,
|
||||||
transformOriginY,
|
transformOriginY,
|
||||||
style,
|
style,
|
||||||
menuStyle,
|
bubbleStyle,
|
||||||
withScroll,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ const BACKDROP_CLASSNAME = 'backdrop';
|
|||||||
// without adding extra elements to the DOM
|
// without adding extra elements to the DOM
|
||||||
export default function useVirtualBackdrop(
|
export default function useVirtualBackdrop(
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
menuRef: RefObject<HTMLElement>,
|
containerRef: RefObject<HTMLElement>,
|
||||||
onClose?: () => void | undefined,
|
onClose?: () => void | undefined,
|
||||||
ignoreRightClick?: boolean,
|
ignoreRightClick?: boolean,
|
||||||
excludedClosestSelector?: string,
|
excludedClosestSelector?: string,
|
||||||
@ -18,14 +18,14 @@ export default function useVirtualBackdrop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleEvent = (e: MouseEvent) => {
|
const handleEvent = (e: MouseEvent) => {
|
||||||
const menu = menuRef.current;
|
const container = containerRef.current;
|
||||||
const target = e.target as HTMLElement | null;
|
const target = e.target as HTMLElement | null;
|
||||||
if (!menu || !target || (ignoreRightClick && e.button === 2)) {
|
if (!container || !target || (ignoreRightClick && e.button === 2)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((
|
if ((
|
||||||
!menu.contains(e.target as Node | null)
|
!container.contains(e.target as Node | null)
|
||||||
|| target.classList.contains(BACKDROP_CLASSNAME)
|
|| target.classList.contains(BACKDROP_CLASSNAME)
|
||||||
) && !(excludedClosestSelector && (
|
) && !(excludedClosestSelector && (
|
||||||
target.matches(excludedClosestSelector) || target.closest(excludedClosestSelector)
|
target.matches(excludedClosestSelector) || target.closest(excludedClosestSelector)
|
||||||
@ -41,5 +41,5 @@ export default function useVirtualBackdrop(
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('mousedown', handleEvent);
|
document.removeEventListener('mousedown', handleEvent);
|
||||||
};
|
};
|
||||||
}, [excludedClosestSelector, ignoreRightClick, isOpen, menuRef, onClose]);
|
}, [excludedClosestSelector, ignoreRightClick, isOpen, containerRef, onClose]);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user