diff --git a/src/components/middle/message/MessageContextMenu.tsx b/src/components/middle/message/MessageContextMenu.tsx index a2f4c3f57..6163b8017 100644 --- a/src/components/middle/message/MessageContextMenu.tsx +++ b/src/components/middle/message/MessageContextMenu.tsx @@ -145,6 +145,22 @@ const MessageContextMenu: FC = ({ [], ); + const getLayout = useCallback(() => { + const extraHeightAudioPlayer = (IS_SINGLE_COLUMN_LAYOUT + && (document.querySelector('.AudioPlayer-content'))?.offsetHeight) || 0; + const pinnedElement = document.querySelector('.HeaderPinnedMessage-wrapper'); + const extraHeightPinned = (((IS_SINGLE_COLUMN_LAYOUT && !extraHeightAudioPlayer) + || (!IS_SINGLE_COLUMN_LAYOUT && pinnedElement?.classList.contains('full-width'))) + && pinnedElement?.offsetHeight) || 0; + + return { + extraPaddingX: SCROLLBAR_WIDTH, + extraTopPadding: (document.querySelector('.MiddleHeader')!).offsetHeight, + marginSides: withReactions ? REACTION_BUBBLE_EXTRA_WIDTH : undefined, + extraMarginTop: extraHeightPinned + extraHeightAudioPlayer, + }; + }, [withReactions]); + const handleRemoveReaction = useCallback(() => { onSendReaction(undefined, 0, 0); }, [onSendReaction]); @@ -160,25 +176,9 @@ const MessageContextMenu: FC = ({ }, ANIMATION_DURATION); }, [isOpen, markIsReady, unmarkIsReady]); - const extraHeightAudioPlayer = (IS_SINGLE_COLUMN_LAYOUT - && (document.querySelector('.AudioPlayer-content'))?.offsetHeight) || 0; - const pinnedElement = document.querySelector('.HeaderPinnedMessage-wrapper'); - const extraHeightPinned = (((IS_SINGLE_COLUMN_LAYOUT && !extraHeightAudioPlayer) - || (!IS_SINGLE_COLUMN_LAYOUT && pinnedElement?.classList.contains('full-width'))) - && pinnedElement?.offsetHeight) || 0; - const { positionX, positionY, style, menuStyle, withScroll, - } = useContextMenuPosition( - anchor, - getTriggerElement, - getRootElement, - getMenuElement, - SCROLLBAR_WIDTH, - (document.querySelector('.MiddleHeader')!).offsetHeight, - withReactions ? REACTION_BUBBLE_EXTRA_WIDTH : undefined, - extraHeightPinned + extraHeightAudioPlayer, - ); + } = useContextMenuPosition(anchor, getTriggerElement, getRootElement, getMenuElement, getLayout); useEffect(() => { disableScrolling(withScroll ? scrollableRef.current : undefined, '.ReactionSelector'); @@ -195,7 +195,7 @@ const MessageContextMenu: FC = ({ positionX={positionX} positionY={positionY} style={style} - menuStyle={menuStyle} + bubbleStyle={menuStyle} className={buildClassName( 'MessageContextMenu', 'fluid', withReactions && 'with-reactions', )} @@ -253,10 +253,12 @@ const MessageContextMenu: FC = ({ {canShowReactionsCount && message.reactors?.count ? ( canShowSeenBy && message.seenByUserIds?.length ? lang('Chat.OutgoingContextMixedReactionCount', [message.reactors.count, message.seenByUserIds.length]) - : lang('Chat.ContextReactionCount', message.reactors.count, 'i')) - : (message.seenByUserIds?.length + : lang('Chat.ContextReactionCount', message.reactors.count, 'i') + ) : ( + message.seenByUserIds?.length ? lang('Conversation.ContextMenuSeen', message.seenByUserIds.length, 'i') - : lang('Conversation.ContextMenuNoViews'))} + : lang('Conversation.ContextMenuNoViews') + )}
{seenByRecentUsers?.map((user) => ( = ({ isOpen, className, style, - menuStyle, + bubbleStyle, children, positionX = 'left', positionY = 'top', @@ -118,7 +118,7 @@ const Menu: FC = ({ ref={menuRef} className={bubbleClassName} // @ts-ignore teact feature - style={`transform-origin: ${positionY} ${positionX};${menuStyle || ''}`} + style={`transform-origin: ${positionY} ${positionX};${bubbleStyle || ''}`} onClick={autoClose ? onClose : undefined} > {children} diff --git a/src/hooks/useContextMenuPosition.ts b/src/hooks/useContextMenuPosition.ts index 92ce89ecf..7cc28c1c6 100644 --- a/src/hooks/useContextMenuPosition.ts +++ b/src/hooks/useContextMenuPosition.ts @@ -1,69 +1,78 @@ -import { useState, useLayoutEffect } from '../lib/teact/teact'; +import { useState, useEffect } from '../lib/teact/teact'; import { IAnchorPosition } from '../types'; +interface Layout { + extraPaddingX?: number; + extraTopPadding?: number; + marginSides?: number; + extraMarginTop?: number; +} + const MENU_POSITION_VISUAL_COMFORT_SPACE_PX = 16; const MENU_POSITION_BOTTOM_MARGIN = 12; +const EMPTY_RECT = { + width: 0, left: 0, height: 0, top: 0, +}; -export default ( +export default function useContextMenuPosition( anchor: IAnchorPosition | undefined, getTriggerElement: () => HTMLElement | null, getRootElement: () => HTMLElement | null, getMenuElement: () => HTMLElement | null, - extraPaddingX = 0, - extraTopPadding = 0, - marginSides = 0, - extraMarginTop = 0, -) => { + getLayout?: () => Layout, +) { const [positionX, setPositionX] = useState<'right' | 'left'>('right'); const [positionY, setPositionY] = useState<'top' | 'bottom'>('bottom'); const [withScroll, setWithScroll] = useState(false); const [style, setStyle] = useState(''); - const [menuStyle, setMenuStyle] = useState(''); + const [menuStyle, setMenuStyle] = useState('opacity: 0;'); - useLayoutEffect(() => { + useEffect(() => { const triggerEl = getTriggerElement(); if (!anchor || !triggerEl) { return; } let { x, y } = anchor; - const emptyRect = { - width: 0, left: 0, height: 0, top: 0, - }; const menuEl = getMenuElement(); const rootEl = getRootElement(); - const triggerRect = triggerEl.getBoundingClientRect(); + const { + extraPaddingX = 0, + extraTopPadding = 0, + marginSides = 0, + extraMarginTop = 0, + } = getLayout?.() || {}; const marginTop = menuEl ? parseInt(getComputedStyle(menuEl).marginTop, 10) + extraMarginTop : undefined; const menuRect = menuEl ? { width: menuEl.offsetWidth, height: menuEl.offsetHeight + marginTop!, - } : emptyRect; + } : EMPTY_RECT; - const rootRect = rootEl ? rootEl.getBoundingClientRect() : emptyRect; + const rootRect = rootEl ? rootEl.getBoundingClientRect() : EMPTY_RECT; - let horizontalPostition: 'left' | 'right'; + let horizontalPosition: 'left' | 'right'; if (x + menuRect.width + extraPaddingX < rootRect.width + rootRect.left) { x += 3; - horizontalPostition = 'left'; + horizontalPosition = 'left'; } else if (x - menuRect.width > 0) { - horizontalPostition = 'right'; + horizontalPosition = 'right'; x -= 3; } else { - horizontalPostition = 'left'; + horizontalPosition = 'left'; x = 16; } - setPositionX(horizontalPostition); + setPositionX(horizontalPosition); if (marginSides - && horizontalPostition === 'right' && (x + extraPaddingX + marginSides >= rootRect.width + rootRect.left)) { + && horizontalPosition === 'right' && (x + extraPaddingX + marginSides >= rootRect.width + rootRect.left)) { x -= marginSides; } - if (marginSides && horizontalPostition === 'left') { + if (marginSides && horizontalPosition === 'left') { if (x + extraPaddingX + marginSides + menuRect.width >= rootRect.width + rootRect.left) { x -= marginSides; } else if (x - marginSides <= 0) { @@ -81,7 +90,8 @@ export default ( } } - const left = horizontalPostition === 'left' + const triggerRect = triggerEl.getBoundingClientRect(); + const left = horizontalPosition === 'left' ? Math.min(x - triggerRect.left, rootRect.width - menuRect.width - MENU_POSITION_VISUAL_COMFORT_SPACE_PX) : Math.max((x - triggerRect.left), menuRect.width + MENU_POSITION_VISUAL_COMFORT_SPACE_PX); const top = Math.min( @@ -94,8 +104,7 @@ export default ( setMenuStyle(`max-height: ${menuMaxHeight}px;`); setStyle(`left: ${left}px; top: ${top}px`); }, [ - anchor, extraPaddingX, extraTopPadding, extraMarginTop, - getMenuElement, getRootElement, getTriggerElement, marginSides, + anchor, getMenuElement, getRootElement, getTriggerElement, getLayout, ]); return { @@ -105,4 +114,4 @@ export default ( menuStyle, withScroll, }; -}; +}