From 1d44c70393efea68ce6295d8a99229d82ba4e4d9 Mon Sep 17 00:00:00 2001 From: trevor-signal <131492920+trevor-signal@users.noreply.github.com> Date: Wed, 19 Feb 2025 12:22:48 -0500 Subject: [PATCH] Avoid errant scroll on context menu hide --- patches/react-contextmenu+2.14.0.patch | 18 ++++++++++++++++-- .../CallingNotification.stories.tsx | 1 + .../conversation/CallingNotification.tsx | 3 +++ .../conversation/MessageContextMenu.tsx | 13 +++++++++++-- ts/components/conversation/TimelineItem.tsx | 1 + ts/components/conversation/TimelineMessage.tsx | 1 + 6 files changed, 33 insertions(+), 4 deletions(-) diff --git a/patches/react-contextmenu+2.14.0.patch b/patches/react-contextmenu+2.14.0.patch index b8588dcee..9ccfb3385 100644 --- a/patches/react-contextmenu+2.14.0.patch +++ b/patches/react-contextmenu+2.14.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-contextmenu/modules/ContextMenu.js b/node_modules/react-contextmenu/modules/ContextMenu.js -index 2f88213..4cf584a 100644 +index 2f88213..41e47ea 100644 --- a/node_modules/react-contextmenu/modules/ContextMenu.js +++ b/node_modules/react-contextmenu/modules/ContextMenu.js @@ -81,6 +81,11 @@ var ContextMenu = function (_AbstractMenu) { @@ -35,13 +35,15 @@ index 2f88213..4cf584a 100644 }); }); } else { -@@ -248,6 +259,14 @@ var ContextMenu = function (_AbstractMenu) { +@@ -248,6 +259,16 @@ var ContextMenu = function (_AbstractMenu) { if (!_this2.menu) return; _this2.menu.style.opacity = 0; _this2.menu.style.pointerEvents = 'none'; + + // Return to the previous focus state when dismissing the menu, unless the + // menu option focused another element. This is important for keyboard mode. ++ if (_this2.props.avoidFocusRestoreOnBlur) return; ++ + var isFocusWithinMenu = _this2.menu.contains(document.activeElement); + if (isFocusWithinMenu && _this2.previousFocus && _this2.previousFocus.focus) { + _this2.previousFocus.focus(); @@ -139,3 +141,15 @@ index ad1dc70..c919be8 100644 this.subMenu.classList.remove(_helpers.cssClasses.menuVisible); } } +diff --git a/node_modules/react-contextmenu/src/index.d.ts b/node_modules/react-contextmenu/src/index.d.ts +index 753ce90..c5971a4 100644 +--- a/node_modules/react-contextmenu/src/index.d.ts ++++ b/node_modules/react-contextmenu/src/index.d.ts +@@ -14,6 +14,7 @@ declare module "react-contextmenu" { + preventHideOnResize?: boolean, + preventHideOnScroll?: boolean, + style?: React.CSSProperties, ++ avoidFocusRestoreOnBlur?: boolean; + } + + export interface ContextMenuTriggerProps { diff --git a/ts/components/conversation/CallingNotification.stories.tsx b/ts/components/conversation/CallingNotification.stories.tsx index 0f2dbb3d0..12db60f50 100644 --- a/ts/components/conversation/CallingNotification.stories.tsx +++ b/ts/components/conversation/CallingNotification.stories.tsx @@ -62,6 +62,7 @@ const getCommonProps = (options: { id: 'message-id', conversationId: conversation.id, i18n, + interactionMode: 'mouse', isNextItemCallingNotification: false, onOutgoingAudioCallInConversation: action( 'onOutgoingAudioCallInConversation' diff --git a/ts/components/conversation/CallingNotification.tsx b/ts/components/conversation/CallingNotification.tsx index 423667d86..c8cf7155f 100644 --- a/ts/components/conversation/CallingNotification.tsx +++ b/ts/components/conversation/CallingNotification.tsx @@ -38,6 +38,7 @@ import { import { MINUTE } from '../../util/durations'; import { isMoreRecentThan } from '../../util/timestamp'; import { InAnotherCallTooltip } from './InAnotherCallTooltip'; +import type { InteractionModeType } from '../../state/ducks/conversations'; export type PropsActionsType = { onOutgoingAudioCallInConversation: (conversationId: string) => void; @@ -50,6 +51,7 @@ type PropsHousekeeping = { i18n: LocalizerType; id: string; conversationId: string; + interactionMode: InteractionModeType; isNextItemCallingNotification: boolean; }; @@ -120,6 +122,7 @@ export const CallingNotification: React.FC = React.memo( { props.toggleDeleteMessagesModal({ conversationId: props.conversationId, diff --git a/ts/components/conversation/MessageContextMenu.tsx b/ts/components/conversation/MessageContextMenu.tsx index 9d7c07386..688c1cbe9 100644 --- a/ts/components/conversation/MessageContextMenu.tsx +++ b/ts/components/conversation/MessageContextMenu.tsx @@ -5,6 +5,7 @@ import React, { type RefObject } from 'react'; import { ContextMenu, MenuItem } from 'react-contextmenu'; import ReactDOM from 'react-dom'; import type { LocalizerType } from '../../types/I18N'; +import type { InteractionModeType } from '../../state/ducks/conversations'; export type ContextMenuTriggerType = { handleContextClick: ( @@ -16,7 +17,7 @@ type MessageContextProps = { i18n: LocalizerType; triggerId: string; shouldShowAdditional: boolean; - + interactionMode: InteractionModeType; onDownload: (() => void) | undefined; onEdit: (() => void) | undefined; onReplyToMessage: (() => void) | undefined; @@ -33,6 +34,7 @@ export const MessageContextMenu = ({ i18n, triggerId, shouldShowAdditional, + interactionMode, onDownload, onEdit, onReplyToMessage, @@ -46,7 +48,14 @@ export const MessageContextMenu = ({ onDeleteMessage, }: MessageContextProps): JSX.Element => { const menu = ( - + // We avoid restoring focus on this context menu because it is not intended for + // keyboard use and restoring focus to the message could cause an unwanted scroll + {shouldShowAdditional && ( <> {onDownload && ( diff --git a/ts/components/conversation/TimelineItem.tsx b/ts/components/conversation/TimelineItem.tsx index 235c4c88c..32a794013 100644 --- a/ts/components/conversation/TimelineItem.tsx +++ b/ts/components/conversation/TimelineItem.tsx @@ -286,6 +286,7 @@ export const TimelineItem = memo(function TimelineItem({