From a662e58e78f7b729ff4820e47c2577a16b7ec0ff Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Tue, 20 Jan 2026 12:01:22 +0100 Subject: [PATCH] Composer Embedded Message: Fix closing animation (#6634) --- .../composer/ComposerEmbeddedMessage.tsx | 169 +++++++++++------- src/hooks/useFrozenProps.ts | 24 +++ 2 files changed, 124 insertions(+), 69 deletions(-) create mode 100644 src/hooks/useFrozenProps.ts diff --git a/src/components/middle/composer/ComposerEmbeddedMessage.tsx b/src/components/middle/composer/ComposerEmbeddedMessage.tsx index 92c5336ed..17acf633c 100644 --- a/src/components/middle/composer/ComposerEmbeddedMessage.tsx +++ b/src/components/middle/composer/ComposerEmbeddedMessage.tsx @@ -31,6 +31,7 @@ import { unique } from '../../../util/iteratees'; import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; +import useFrozenProps from '../../../hooks/useFrozenProps'; import useLang from '../../../hooks/useLang'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; @@ -81,33 +82,21 @@ type OwnProps = { const CLOSE_DURATION = 350; -const ComposerEmbeddedMessage = ({ - replyInfo, - suggestedPostInfo, - editingId, - message, - sender, - shouldAnimate, - forwardedMessagesCount, - noAuthors, - noCaptions, - forwardsHaveCaptions, - shouldForceShowEditing, - isCurrentUserPremium, - isContextMenuDisabled, - isReplyToDiscussion, - isInChangingRecipientMode, - shouldPreventComposerAnimation, - senderChat, - chatId, - currentUserId, - isSenderChannel, - forwardMessageIds, - fromChatId, - isMediaNsfw, - theme, - onClear, -}: OwnProps & StateProps) => { +const ComposerEmbeddedMessage = (props: OwnProps & StateProps) => { + const { + shouldAnimate, + isReplyToDiscussion, + isInChangingRecipientMode, + forwardMessageIds, + fromChatId, + replyInfo, + editingId, + suggestedPostInfo, + shouldForceShowEditing, + message, + forwardedMessagesCount, + } = props; + const { resetDraftReplyInfo, resetDraftSuggestedPostInfo, @@ -127,19 +116,16 @@ const ComposerEmbeddedMessage = ({ const lang = useLang(); const isReplyToTopicStart = message?.content.action?.type === 'topicCreate'; - const isShowingReply = replyInfo && !shouldForceShowEditing; - const isReplyWithQuote = Boolean(replyInfo?.quoteText); const isShowingSuggestedPost = Boolean(suggestedPostInfo) && !shouldForceShowEditing; - const isForwarding = Boolean(forwardedMessagesCount); const selectSenderFromForwardedMessage = useLastCallback((forwardedMessage: ApiMessage) => { const global = getGlobal(); - sender = selectForwardedSender(global, forwardedMessage); - if (!sender) { - sender = selectSender(global, forwardedMessage); + let localSender = selectForwardedSender(global, forwardedMessage); + if (!localSender) { + localSender = selectSender(global, forwardedMessage); } - return sender; + return localSender; }); const forwardSenders = useMemo(() => { @@ -159,7 +145,7 @@ const ComposerEmbeddedMessage = ({ })(); const { - shouldRender, transitionClassNames, + shouldRender, transitionClassNames, isClosing, } = useShowTransitionDeprecated( isShown && !isReplyToTopicStart && !isReplyToDiscussion, undefined, @@ -169,6 +155,35 @@ const ComposerEmbeddedMessage = ({ CLOSE_DURATION, !shouldAnimate, ); + + const { + chatId, + currentUserId, + theme, + onClear, + isCurrentUserPremium, + isContextMenuDisabled, + shouldPreventComposerAnimation, + sender, + senderChat, + isMediaNsfw, + noAuthors, + noCaptions, + forwardsHaveCaptions, + forwardedMessagesCount: frozenForwardedMessagesCount, + message: frozenMessage, + shouldForceShowEditing: frozenShouldForceShowEditing, + suggestedPostInfo: frozenSuggestedPostInfo, + replyInfo: frozenReplyInfo, + editingId: frozenEditingId, + isSenderChannel, + } = useFrozenProps(props, isClosing); + + const isForwardingRendering = Boolean(frozenForwardedMessagesCount); + const isShowingReplyRendering = Boolean(frozenReplyInfo) && !frozenShouldForceShowEditing; + const isReplyWithQuoteRendering = Boolean(frozenReplyInfo?.quoteText); + const isShowingSuggestedPostRendering = Boolean(frozenSuggestedPostInfo) && !frozenShouldForceShowEditing; + useEffect(() => { if (shouldPreventComposerAnimation) { setShouldPreventComposerAnimation({ shouldPreventComposerAnimation: false }); @@ -176,14 +191,14 @@ const ComposerEmbeddedMessage = ({ }); const clearEmbedded = useLastCallback(() => { - if (editingId) { + if (frozenEditingId) { setEditingId({ messageId: undefined }); - } else if (forwardedMessagesCount) { + } else if (frozenForwardedMessagesCount) { exitForwardMode(); - } else if (isShowingSuggestedPost) { + } else if (isShowingSuggestedPostRendering) { resetDraftSuggestedPostInfo(); resetDraftReplyInfo(); - } else if (replyInfo && !shouldForceShowEditing) { + } else if (frozenReplyInfo && !frozenShouldForceShowEditing) { resetDraftReplyInfo(); } onClear?.(); @@ -197,10 +212,10 @@ const ComposerEmbeddedMessage = ({ } = useContextMenuHandlers(ref); const focusMessageFromDraft = () => { - focusMessage({ chatId: message!.chatId, messageId: message!.id, noForumTopicPanel: true }); + focusMessage({ chatId: frozenMessage!.chatId, messageId: frozenMessage!.id, noForumTopicPanel: true }); }; const handleMessageClick = useLastCallback((e: React.MouseEvent): void => { - if (suggestedPostInfo) { + if (frozenSuggestedPostInfo) { openSuggestMessageModal({ chatId }); return; } @@ -231,6 +246,13 @@ const ComposerEmbeddedMessage = ({ }); const handleDoNotReplyClick = useLastCallback(buildAutoCloseMenuItemHandler(clearEmbedded)); + const handleIconKeyDown = useLastCallback((e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleContextMenu(e as unknown as React.MouseEvent); + } + }); + const getTriggerElement = useLastCallback(() => ref.current); const getRootElement = useLastCallback(() => ref.current!); const getMenuElement = useLastCallback(() => ref.current!.querySelector('.forward-context-menu .bubble')); @@ -243,47 +265,47 @@ const ComposerEmbeddedMessage = ({ }, [handleContextMenuClose, handleContextMenuHide, shouldRender]); const className = buildClassName('ComposerEmbeddedMessage', transitionClassNames); - const renderingSender = useCurrentOrPrev(sender, true); const { className: peerColorClass, style: peerColorStyle } = usePeerColor({ - peer: renderingSender, + peer: sender, theme, }); const innerClassName = buildClassName('ComposerEmbeddedMessage_inner', peerColorClass); const leftIcon = useMemo(() => { - if (editingId) { + if (frozenEditingId) { return 'edit'; } - if (isShowingSuggestedPost) { + if (isShowingSuggestedPostRendering) { return 'cash-circle'; } - if (isForwarding) { + if (isForwardingRendering) { return 'forward'; } - if (isShowingReply) { + if (isShowingReplyRendering) { return 'reply'; } return undefined; - }, [editingId, isForwarding, isShowingReply, isShowingSuggestedPost]); + }, [frozenEditingId, isForwardingRendering, isShowingReplyRendering, isShowingSuggestedPostRendering]); - const customText = forwardedMessagesCount && forwardedMessagesCount > 1 - ? oldLang('ForwardedMessageCount', forwardedMessagesCount) + const customText = frozenForwardedMessagesCount && frozenForwardedMessagesCount > 1 + ? oldLang('ForwardedMessageCount', frozenForwardedMessagesCount) : undefined; const strippedMessage = useMemo(() => { - if (!message || !isForwarding || !message.content.text || !noAuthors || isCurrentUserPremium) return message; + if (!frozenMessage || !isForwardingRendering || !frozenMessage.content.text + || !noAuthors || isCurrentUserPremium) return frozenMessage; - const strippedText = stripCustomEmoji(message.content.text); + const strippedText = stripCustomEmoji(frozenMessage.content.text); return { - ...message, + ...frozenMessage, content: { - ...message.content, + ...frozenMessage.content, text: strippedText, }, }; - }, [isCurrentUserPremium, isForwarding, message, noAuthors]); + }, [isCurrentUserPremium, isForwardingRendering, frozenMessage, noAuthors]); const renderingLeftIcon = useCurrentOrPrev(leftIcon, true); @@ -291,22 +313,29 @@ const ComposerEmbeddedMessage = ({ return undefined; } - const canReplyInSenderChat = sender && !isSenderChannel && chatId !== sender.id && sender.id !== currentUserId; + const canReplyInSenderChat = sender && !isSenderChannel + && chatId !== sender.id && sender.id !== currentUserId; return (
-
+
{renderingLeftIcon && } - {Boolean(replyInfo?.quoteText) && ( + {Boolean(frozenReplyInfo?.quoteText) && ( )}
- {(isShowingReply || isForwarding) && !isContextMenuDisabled && ( + {(isShowingReplyRendering || isForwardingRendering) && !isContextMenuDisabled && ( - {isForwarding && ( + {isForwardingRendering && ( <> - {oldLang(forwardedMessagesCount > 1 ? 'ShowSenderNames' : 'ShowSendersName')} + {oldLang(frozenForwardedMessagesCount > 1 ? 'ShowSenderNames' : 'ShowSendersName')} - {oldLang(forwardedMessagesCount > 1 ? 'HideSenderNames' : 'HideSendersName')} + {oldLang(frozenForwardedMessagesCount > 1 ? 'HideSenderNames' : 'HideSendersName')} {forwardsHaveCaptions && ( <> @@ -372,7 +401,8 @@ const ComposerEmbeddedMessage = ({ noCaptions: false, })} > - {oldLang(forwardedMessagesCount > 1 ? 'Conversation.ForwardOptions.ShowCaption' : 'ShowCaption')} + {oldLang(frozenForwardedMessagesCount > 1 + ? 'Conversation.ForwardOptions.ShowCaption' : 'ShowCaption')} - {oldLang(forwardedMessagesCount > 1 ? 'Conversation.ForwardOptions.HideCaption' : 'HideCaption')} + {oldLang(frozenForwardedMessagesCount > 1 + ? 'Conversation.ForwardOptions.HideCaption' : 'HideCaption')} )} @@ -392,7 +423,7 @@ const ComposerEmbeddedMessage = ({ )} - {isShowingReply && ( + {isShowingReplyRendering && ( <> {oldLang('Message.Context.Goto')} - {isReplyWithQuote && ( + {isReplyWithQuoteRendering && ( >( + props: T, + shouldFreeze: boolean, + alwaysFreshKeys?: readonly (keyof T)[], +): T { + const frozenRef = useRef(props); + + if (!shouldFreeze) { + frozenRef.current = props; + } else if (alwaysFreshKeys?.length) { + const updates: Partial = {}; + for (const key of alwaysFreshKeys) { + updates[key] = props[key]; + } + frozenRef.current = { + ...frozenRef.current, + ...updates, + }; + } + + return frozenRef.current; +}