From cf8bf318d6430e8cd2d689ff59bb99d5632d5d4b Mon Sep 17 00:00:00 2001 From: Vit Horacek <36083550+mountiny@users.noreply.github.com> Date: Sat, 5 Jul 2025 07:10:12 +0200 Subject: [PATCH] Revert "fix: Keyboard opens briefly between ReportActionContextMenu and EmojiPicker modals" --- patches/react-native/details.md | 10 - ...put-prevent-focus-on-first-responder.patch | 237 ------------------ .../ActionSheetAwareScrollViewContext.tsx | 6 +- .../ActionSheetKeyboardSpace.tsx | 4 +- src/components/EmojiPicker/EmojiPicker.tsx | 75 ++---- .../EmojiPicker/EmojiPickerButton.tsx | 13 +- .../EmojiPicker/EmojiPickerButtonDropdown.tsx | 16 +- src/components/RNMarkdownTextInput.tsx | 2 +- .../Reactions/AddReactionBubble.tsx | 16 +- .../Reactions/MiniQuickEmojiReactions.tsx | 14 +- src/libs/ReportActionComposeFocusManager.ts | 29 +-- src/libs/actions/EmojiPickerAction.ts | 45 ++-- src/libs/actions/Modal.ts | 26 +- .../index.ios.ts | 11 - .../index.ts | 5 - .../types.ts | 7 - ...focusComposerAfterPreventFirstResponder.ts | 15 -- .../PopoverReportActionContextMenu.tsx | 35 +-- .../ContextMenu/ReportActionContextMenu.ts | 28 +-- .../ComposerWithSuggestions.tsx | 12 +- 20 files changed, 116 insertions(+), 490 deletions(-) delete mode 100644 patches/react-native/details.md delete mode 100644 patches/react-native/react-native+0.79.2+025+textinput-prevent-focus-on-first-responder.patch delete mode 100644 src/libs/preventTextInputFocusOnFirstResponderOnce/index.ios.ts delete mode 100644 src/libs/preventTextInputFocusOnFirstResponderOnce/index.ts delete mode 100644 src/libs/preventTextInputFocusOnFirstResponderOnce/types.ts delete mode 100644 src/libs/refocusComposerAfterPreventFirstResponder.ts diff --git a/patches/react-native/details.md b/patches/react-native/details.md deleted file mode 100644 index de41bb180876c..0000000000000 --- a/patches/react-native/details.md +++ /dev/null @@ -1,10 +0,0 @@ -# `react-native` patches - -### [react-native+0.79.2+025+textinput-prevent-focus-on-first-responder.patch](react-native+0.79.2+025+textinput-prevent-focus-on-first-responder.patch) - -- Reason: On iOS, a text input automatically becomes the "first responder" in UIKit's "UIResponder" chain. Once a text input becomes the first responder, it will be automatically focused. (This also causes the keyboard ot open) - - This is not handled by React or React Native, but is rather an native iOS/UIKit behaviour. This patch adds additional an additional `TextInput` prop (`preventFocusOnFirstResponder`) and a ref method (`preventFocusOnFirstResponderOnce`) to bypass the focus on first responder. - - In E/App this causes issues with e.g. the keyboard briefly opening after a modal has been dismissed before another modal is opened (`ReportActionContextMenu` -> `EmojiPicker`) -- Upstream PR/issue: None, because this is not a real bug fix but a hotfix specific to Expensify -- E/App issue: [#54813](https://github.com/Expensify/App/issues/54813) -- PR Introducing Patch: [#61492](https://github.com/Expensify/App/pull/61492) diff --git a/patches/react-native/react-native+0.79.2+025+textinput-prevent-focus-on-first-responder.patch b/patches/react-native/react-native+0.79.2+025+textinput-prevent-focus-on-first-responder.patch deleted file mode 100644 index 97c630b1b429d..0000000000000 --- a/patches/react-native/react-native+0.79.2+025+textinput-prevent-focus-on-first-responder.patch +++ /dev/null @@ -1,237 +0,0 @@ -diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTMultilineTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/RCTMultilineTextInputNativeComponent.js -index 8c40af6..2a32cd4 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/RCTMultilineTextInputNativeComponent.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTMultilineTextInputNativeComponent.js -@@ -21,7 +21,7 @@ type NativeType = HostComponent<{...}>; - type NativeCommands = TextInputNativeCommands; - - export const Commands: NativeCommands = codegenNativeCommands({ -- supportedCommands: ['focus', 'blur', 'setTextAndSelection'], -+ supportedCommands: ['focus', 'blur', 'setTextAndSelection', 'preventFocusOnFirstResponderOnce'], - }); - - export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = { -diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTSingelineTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/RCTSingelineTextInputNativeComponent.js -index a52be63..820153a 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/RCTSingelineTextInputNativeComponent.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTSingelineTextInputNativeComponent.js -@@ -21,7 +21,7 @@ type NativeType = HostComponent<{...}>; - type NativeCommands = TextInputNativeCommands; - - export const Commands: NativeCommands = codegenNativeCommands({ -- supportedCommands: ['focus', 'blur', 'setTextAndSelection'], -+ supportedCommands: ['focus', 'blur', 'setTextAndSelection', 'preventFocusOnFirstResponderOnce'], - }); - - export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = { -diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -index 94b7d31..549ffab 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -@@ -163,6 +163,7 @@ const RCTTextInputViewConfig = { - lineBreakStrategyIOS: true, - lineBreakModeIOS: true, - smartInsertDelete: true, -+ preventFocusOnFirstResponder: true, - ...ConditionallyIgnoredEventHandlers({ - onClear: true, - onChange: true, -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -index 2112772..4efd46e 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -@@ -770,6 +770,11 @@ export interface TextInputProps - */ - multiline?: boolean | undefined; - -+ /** -+ * If true, the text input will not restore focus when the input becomes first responder. -+ */ -+ preventFocusOnFirstResponder?: boolean | undefined; -+ - /** - * Callback that is called when the text input is blurred - */ -@@ -1033,4 +1038,9 @@ export class TextInput extends TextInputBase { - * Sets the start and end positions of text selection. - */ - setSelection: (start: number, end: number) => void; -+ -+ /** -+ * Prevents the text input once from restoring focus when the input becomes first responder. -+ */ -+ preventFocusOnFirstResponderOnce: () => void; - } -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -index 50f7794..d5585f0 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -@@ -1012,6 +1012,7 @@ export interface TextInputInstance extends HostInstance { - +isFocused: () => boolean; - +getNativeRef: () => ?HostInstance; - +setSelection: (start: number, end: number) => void; -+ +preventFocusOnFirstResponderOnce: () => void; - } - - /** -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -index 1fb07fb..1fed107 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -@@ -1430,6 +1430,11 @@ function InternalTextInput(props: TextInputProps): React.Node { - ); - } - }, -+ preventFocusOnFirstResponderOnce: () => { -+ if (inputRef.current != null && Platform.OS !== 'android') { -+ viewCommands.preventFocusOnFirstResponderOnce(inputRef.current); -+ } -+ }, - }); - } - }, -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInputNativeCommands.js b/node_modules/react-native/Libraries/Components/TextInput/TextInputNativeCommands.js -index 9da8899..bf87ffd 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInputNativeCommands.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInputNativeCommands.js -@@ -22,8 +22,9 @@ export interface TextInputNativeCommands { - start: Int32, - end: Int32, - ) => void; -+ +preventFocusOnFirstResponderOnce: (viewRef: React.ElementRef) => void; - } - --const supportedCommands = ['focus', 'blur', 'setTextAndSelection'] as string[]; -+const supportedCommands = ['focus', 'blur', 'setTextAndSelection', 'preventFocusOnFirstResponderOnce'] as string[]; - - export default supportedCommands; -diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm -index fef9c63..0a887d5 100644 ---- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm -+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm -@@ -70,6 +70,16 @@ static NSSet *returnKeyTypesSet; - NSDictionary *_originalTypingAttributes; - - BOOL _hasInputAccessoryView; -+ -+ /** -+ * Prevents automatic focus of the text input once it becomes the first responder (e.g. on modal dismissal). -+ */ -+ BOOL _preventFocusOnFirstResponder; -+ -+ /** -+ * Same as above but is only used to prevent focus once -+ */ -+ BOOL _preventFocusOnFirstResponderOnce; - } - - #pragma mark - UIView overrides -@@ -295,6 +305,10 @@ static NSSet *returnKeyTypesSet; - _backedTextInputView.disableKeyboardShortcuts = newTextInputProps.disableKeyboardShortcuts; - } - -+ if (newTextInputProps.preventFocusOnFirstResponder != oldTextInputProps.preventFocusOnFirstResponder) { -+ _preventFocusOnFirstResponder = newTextInputProps.preventFocusOnFirstResponder; -+ } -+ - [super updateProps:props oldProps:oldProps]; - - [self setDefaultInputAccessoryView]; -@@ -357,6 +371,15 @@ static NSSet *returnKeyTypesSet; - - - (BOOL)textInputShouldBeginEditing - { -+ if (_preventFocusOnFirstResponderOnce) { -+ _preventFocusOnFirstResponderOnce = NO; -+ return NO; -+ } -+ -+ if (_preventFocusOnFirstResponder) { -+ return NO; -+ } -+ - return YES; - } - -@@ -576,6 +599,11 @@ static NSSet *returnKeyTypesSet; - _comingFromJS = NO; - } - -+- (void)preventFocusOnFirstResponderOnce -+{ -+ _preventFocusOnFirstResponderOnce = YES; -+} -+ - #pragma mark - Default input accessory view - - - (NSString *)returnKeyTypeToString:(UIReturnKeyType)returnKeyType -diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h -index f674d98..6ad10ec 100644 ---- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h -+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputNativeCommands.h -@@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN - value:(NSString *__nullable)value - start:(NSInteger)start - end:(NSInteger)end; -+- (void)preventFocusOnFirstResponderOnce; - @end - - RCT_EXTERN inline void -@@ -109,6 +110,19 @@ RCTTextInputHandleCommand(id componentView, const NSSt - return; - } - -+ if ([commandName isEqualToString:@"preventFocusOnFirstResponderOnce"]) { -+#if RCT_DEBUG -+ if ([args count] != 0) { -+ RCTLogError( -+ @"%@ command %@ received %d arguments, expected %d.", @"TextInput", commandName, (int)[args count], 0); -+ return; -+ } -+#endif -+ -+ [componentView preventFocusOnFirstResponderOnce]; -+ return; -+ } -+ - #if RCT_DEBUG - RCTLogError(@"%@ received command %@, which is not a supported command.", @"TextInput", commandName); - #endif -diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.cpp -index 47787a5..f671682 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.cpp -+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.cpp -@@ -127,6 +127,12 @@ BaseTextInputProps::BaseTextInputProps( - "multiline", - sourceProps.multiline, - {false})), -+ preventFocusOnFirstResponder(convertRawProp( -+ context, -+ rawProps, -+ "preventFocusOnFirstResponder", -+ sourceProps.preventFocusOnFirstResponder, -+ {false})), - disableKeyboardShortcuts(convertRawProp( - context, - rawProps, -@@ -215,6 +221,7 @@ void BaseTextInputProps::setProp( - RAW_SET_PROP_SWITCH_CASE_BASIC(submitBehavior); - RAW_SET_PROP_SWITCH_CASE_BASIC(multiline); - RAW_SET_PROP_SWITCH_CASE_BASIC(disableKeyboardShortcuts); -+ RAW_SET_PROP_SWITCH_CASE_BASIC(preventFocusOnFirstResponder); - } - } - -diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.h b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.h -index 3e93402..092dbf0 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.h -+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.h -@@ -79,6 +79,8 @@ class BaseTextInputProps : public ViewProps, public BaseTextProps { - - bool multiline{false}; - -+ bool preventFocusOnFirstResponder{false}; -+ - bool disableKeyboardShortcuts{false}; - }; - diff --git a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx index 56419ea39af0f..cd5b20bbf10e1 100644 --- a/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx +++ b/src/components/ActionSheetAwareScrollView/ActionSheetAwareScrollViewContext.tsx @@ -67,7 +67,7 @@ const States = { POPOVER_CLOSED: 'popoverClosed', KEYBOARD_POPOVER_CLOSED: 'keyboardPopoverClosed', KEYBOARD_POPOVER_OPEN: 'keyboardPopoverOpen', - KEYBOARD_CLOSING_POPOVER: 'keyboardClosingPopover', + KEYBOARD_CLOSED_POPOVER: 'keyboardClosingPopover', POPOVER_MEASURED: 'popoverMeasured', MODAL_WITH_KEYBOARD_OPEN_DELETED: 'modalWithKeyboardOpenDeleted', } as const; @@ -97,13 +97,13 @@ const STATE_MACHINE: StateMachine, ValueOf) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollViewContext); - const [isEmojiPickerVisible, setIsEmojiPickerVisible] = useState(false); const [emojiPopoverAnchorPosition, setEmojiPopoverAnchorPosition] = useState({ horizontal: 0, @@ -49,11 +43,10 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef(() => {}); + const onModalHide = useRef(() => {}); const onEmojiSelected = useRef(() => {}); const activeEmoji = useRef(undefined); const emojiSearchInput = useRef(null); - const composerToRefocusOnClose = useRef(undefined); const {windowHeight} = useWindowDimensions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -78,24 +71,16 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef { - composerToRefocusOnClose.current = composerToRefocusOnCloseValue; - if (composerToRefocusOnCloseValue === 'main') { - ReportActionComposeFocusManager.preventComposerFocusOnFirstResponderOnce(); - } else if (composerToRefocusOnCloseValue === 'edit') { - ReportActionComposeFocusManager.preventEditComposerFocusOnFirstResponderOnce(); - } - + ) => { onModalHide.current = onModalHideValue; onEmojiSelected.current = onEmojiSelectedValue; activeEmoji.current = activeEmojiValue; @@ -130,30 +115,16 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef { - const currOnModalHide = onModalHide.current; - onModalHide.current = () => { - if (currOnModalHide) { - currOnModalHide(!!isNavigating); - } - - emojiPopoverAnchorRef.current = null; - }; - setIsEmojiPickerVisible(false); - actionSheetAwareScrollViewContext.transitionActionSheetState({ - type: Actions.CLOSE_POPOVER, - }); - }, - [onModalHide, actionSheetAwareScrollViewContext], - ); - - const handleModalHide = () => { - onModalHide.current(); + const hideEmojiPicker = (isNavigating?: boolean) => { + const currOnModalHide = onModalHide.current; + onModalHide.current = () => { + if (currOnModalHide) { + currOnModalHide(!!isNavigating); + } - refocusComposerAfterPreventFirstResponder(composerToRefocusOnClose.current).then(() => { - composerToRefocusOnClose.current = undefined; - }); + emojiPopoverAnchorRef.current = null; + }; + setIsEmojiPickerVisible(false); }; /** @@ -220,7 +191,7 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef {}, + emojiPickerID, + ); } else { emojiPickerRef.current.hideEmojiPicker(); } diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx index c71bb3a15d29b..8725b179ba288 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.tsx @@ -11,8 +11,8 @@ import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {EmojiPickerOnModalHide} from '@libs/actions/EmojiPickerAction'; import {hideEmojiPicker, isEmojiPickerVisible, resetEmojiPopoverAnchor, showEmojiPicker} from '@libs/actions/EmojiPickerAction'; +import type {OnModalHideValue} from '@libs/actions/EmojiPickerAction'; import getButtonState from '@libs/getButtonState'; import CONST from '@src/CONST'; @@ -21,7 +21,7 @@ type EmojiPickerButtonDropdownProps = { isDisabled?: boolean; accessibilityLabel?: string; role?: string; - onModalHide: EmojiPickerOnModalHide; + onModalHide: OnModalHideValue; onInputChange: (emoji: string) => void; value?: string; disabled?: boolean; @@ -47,18 +47,20 @@ function EmojiPickerButtonDropdown( return; } - showEmojiPicker({ + showEmojiPicker( onModalHide, - onEmojiSelected: (emoji) => onInputChange(emoji), + (emoji) => onInputChange(emoji), emojiPopoverAnchor, - anchorOrigin: { + { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, shiftVertical: 4, }, - activeEmoji: value, + () => {}, + undefined, + value, withoutOverlay, - }); + ); }; return ( diff --git a/src/components/RNMarkdownTextInput.tsx b/src/components/RNMarkdownTextInput.tsx index 19179d4332b2d..49c651ae01bcc 100644 --- a/src/components/RNMarkdownTextInput.tsx +++ b/src/components/RNMarkdownTextInput.tsx @@ -85,7 +85,7 @@ function RNMarkdownTextInputWithRef({maxLength, parser, ...props}: RNMarkdownTex ); } -RNMarkdownTextInputWithRef.displayName = 'RNMarkdownTextInputWithRef'; +RNMarkdownTextInputWithRef.displayName = 'RNTextInputWithRef'; export default forwardRef(RNMarkdownTextInputWithRef); export type {AnimatedMarkdownTextInputRef}; diff --git a/src/components/Reactions/AddReactionBubble.tsx b/src/components/Reactions/AddReactionBubble.tsx index 5bfa95ede5d42..f755f2a6a4af3 100644 --- a/src/components/Reactions/AddReactionBubble.tsx +++ b/src/components/Reactions/AddReactionBubble.tsx @@ -10,7 +10,6 @@ import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; -import {contextMenuRef} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import variables from '@styles/variables'; import {emojiPickerRef, resetEmojiPopoverAnchor, showEmojiPicker} from '@userActions/EmojiPickerAction'; import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; @@ -59,19 +58,18 @@ function AddReactionBubble({onSelectEmoji, reportAction, onPressOpenPicker, onWi const onPress = () => { const openPicker = (refParam?: PickerRefElement, anchorOrigin?: AnchorOrigin) => { - showEmojiPicker({ - onModalHide: () => { + showEmojiPicker( + () => { setIsEmojiPickerActive?.(false); }, - onEmojiSelected: (emojiCode, emojiObject) => { + (emojiCode, emojiObject) => { onSelectEmoji(emojiObject); }, - emojiPopoverAnchor: refParam ?? ref, + refParam ?? ref, anchorOrigin, - onWillShow: onWillShowPicker, - id: reportAction.reportActionID, - composerToRefocusOnClose: contextMenuRef.current?.composerToRefocusOnCloseEmojiPicker, - }); + onWillShowPicker, + reportAction.reportActionID, + ); }; if (!emojiPickerRef.current?.isEmojiPickerVisible) { diff --git a/src/components/Reactions/MiniQuickEmojiReactions.tsx b/src/components/Reactions/MiniQuickEmojiReactions.tsx index 63d5cd585662b..71d9354e351cd 100644 --- a/src/components/Reactions/MiniQuickEmojiReactions.tsx +++ b/src/components/Reactions/MiniQuickEmojiReactions.tsx @@ -42,14 +42,16 @@ function MiniQuickEmojiReactions({reportAction, reportActionID, onEmojiSelected, const openEmojiPicker = () => { onPressOpenPicker(); - showEmojiPicker({ - onModalHide: onEmojiPickerClosed, - onEmojiSelected: (_emojiCode, emojiObject) => { + showEmojiPicker( + onEmojiPickerClosed, + (emojiCode, emojiObject) => { onEmojiSelected(emojiObject, emojiReactions); }, - emojiPopoverAnchor: ref, - id: reportAction.reportActionID, - }); + ref, + undefined, + () => {}, + reportAction.reportActionID, + ); }; return ( diff --git a/src/libs/ReportActionComposeFocusManager.ts b/src/libs/ReportActionComposeFocusManager.ts index 435468cb8a0e4..b79249cd027fe 100644 --- a/src/libs/ReportActionComposeFocusManager.ts +++ b/src/libs/ReportActionComposeFocusManager.ts @@ -1,21 +1,18 @@ import {findFocusedRoute} from '@react-navigation/native'; -import type {RefObject} from 'react'; import React from 'react'; +import type {MutableRefObject} from 'react'; import type {TextInput} from 'react-native'; import SCREENS from '@src/SCREENS'; import isReportOpenInRHP from './Navigation/helpers/isReportOpenInRHP'; import navigationRef from './Navigation/navigationRef'; -import preventTextInputFocusOnFirstResponderOnce from './preventTextInputFocusOnFirstResponderOnce'; - -type ComposerType = 'main' | 'edit'; type FocusCallback = (shouldFocusForNonBlurInputOnTapOutside?: boolean) => void; -const composerRef: RefObject = React.createRef(); +const composerRef: MutableRefObject = React.createRef(); // There are two types of composer: general composer (edit composer) and main composer. // The general composer callback will take priority if it exists. -const editComposerRef: RefObject = React.createRef(); +const editComposerRef: MutableRefObject = React.createRef(); // There are two types of focus callbacks: priority and general // Priority callback would take priority if it existed let priorityFocusCallback: FocusCallback | null = null; @@ -86,22 +83,6 @@ function isEditFocused(): boolean { return !!editComposerRef.current?.isFocused(); } -/** - * This will prevent the composer's text input from focusing the next time it becomes the - * first responder in the UIResponder chain. (iOS only, no-op on Android) - */ -function preventComposerFocusOnFirstResponderOnce() { - preventTextInputFocusOnFirstResponderOnce(composerRef); -} - -/** - * This will prevent the edit composer's text input from focusing the next time it becomes the - * first responder in the UIResponder chain. (iOS only, no-op on Android) - */ -function preventEditComposerFocusOnFirstResponderOnce() { - preventTextInputFocusOnFirstResponderOnce(editComposerRef); -} - export default { composerRef, onComposerFocus, @@ -110,8 +91,4 @@ export default { isFocused, editComposerRef, isEditFocused, - preventComposerFocusOnFirstResponderOnce, - preventEditComposerFocusOnFirstResponderOnce, }; - -export type {ComposerType}; diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index e7a65a436d17c..adb0c9cb702d5 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -1,10 +1,9 @@ -import type {RefObject} from 'react'; import React from 'react'; +import type {RefObject} from 'react'; import type {TextInput, View} from 'react-native'; import type {ValueOf} from 'type-fest'; import type {Emoji} from '@assets/emojis/types'; import type {CloseContextMenuCallback} from '@components/Reactions/QuickEmojiReactions/types'; -import type {ComposerType} from '@libs/ReportActionComposeFocusManager'; import type CONST from '@src/CONST'; type AnchorOrigin = { @@ -15,24 +14,21 @@ type AnchorOrigin = { type EmojiPopoverAnchor = RefObject; -type EmojiPickerOnWillShow = (callback?: CloseContextMenuCallback) => void; +type OnWillShowPicker = (callback?: CloseContextMenuCallback) => void; -type EmojiPickerOnModalHide = (isNavigating?: boolean) => void; - -type ShowEmojiPickerOptions = { - onModalHide: EmojiPickerOnModalHide; - onEmojiSelected: OnEmojiSelected; - emojiPopoverAnchor: EmojiPopoverAnchor; - anchorOrigin?: AnchorOrigin; - onWillShow?: EmojiPickerOnWillShow; - id?: string; - activeEmoji?: string; - withoutOverlay?: boolean; - composerToRefocusOnClose?: ComposerType; -}; +type OnModalHideValue = (isNavigating?: boolean) => void; type EmojiPickerRef = { - showEmojiPicker: (options: ShowEmojiPickerOptions) => void; + showEmojiPicker: ( + onModalHideValue: OnModalHideValue, + onEmojiSelectedValue: OnEmojiSelected, + emojiPopoverAnchor: EmojiPopoverAnchor, + anchorOrigin?: AnchorOrigin, + onWillShow?: OnWillShowPicker, + id?: string, + activeEmoji?: string, + withoutOverlay?: boolean, + ) => void; isActive: (id: string) => boolean; clearActive: () => void; hideEmojiPicker: (isNavigating?: boolean) => void; @@ -54,12 +50,21 @@ const emojiPickerRef = React.createRef(); * @param onWillShow - Run a callback when Popover will show * @param id - Unique id for EmojiPicker */ -function showEmojiPicker(options: ShowEmojiPickerOptions) { +function showEmojiPicker( + onModalHide: OnModalHideValue, + onEmojiSelected: OnEmojiSelected, + emojiPopoverAnchor: EmojiPopoverAnchor, + anchorOrigin?: AnchorOrigin, + onWillShow: OnWillShowPicker = () => {}, + id?: string, + activeEmoji?: string, + withoutOverlay?: boolean, +) { if (!emojiPickerRef.current) { return; } - emojiPickerRef.current.showEmojiPicker(options); + emojiPickerRef.current.showEmojiPicker(onModalHide, onEmojiSelected, emojiPopoverAnchor, anchorOrigin, onWillShow, id, activeEmoji, withoutOverlay); } /** @@ -109,4 +114,4 @@ function resetEmojiPopoverAnchor() { } export {emojiPickerRef, showEmojiPicker, hideEmojiPicker, isActive, clearActive, isEmojiPickerVisible, resetEmojiPopoverAnchor}; -export type {AnchorOrigin, EmojiPickerOnModalHide, OnEmojiSelected, EmojiPopoverAnchor, EmojiPickerOnWillShow, ShowEmojiPickerOptions, EmojiPickerRef}; +export type {AnchorOrigin, OnModalHideValue, OnEmojiSelected, EmojiPopoverAnchor, OnWillShowPicker, EmojiPickerRef}; diff --git a/src/libs/actions/Modal.ts b/src/libs/actions/Modal.ts index 3bd96c98e925c..4c766e11f638c 100644 --- a/src/libs/actions/Modal.ts +++ b/src/libs/actions/Modal.ts @@ -25,39 +25,33 @@ function setCloseModal(onClose: () => void) { } /** - * Close the topmost or a specific modal based on an offset from the top + * Close topmost modal */ -function closeTop(topModalOffset?: number) { +function closeTop() { if (closeModals.length === 0) { return; } - - // Add bounds to the offset to ensure it's not out of bounds and use topmost modal by default. - const startFromTopMostModal = topModalOffset === undefined ? -1 : Math.max(Math.min(-topModalOffset - 1, -1), -closeModals.length); - - const modalCloseCallback = closeModals.splice(startFromTopMostModal, 1).at(0); - if (onModalClose) { - modalCloseCallback?.(isNavigate); + closeModals[closeModals.length - 1](isNavigate); + closeModals.pop(); return; } - modalCloseCallback?.(); + closeModals[closeModals.length - 1](); + closeModals.pop(); } /** * Close modal in other parts of the app */ -function close(onModalCloseCallback?: () => void, isNavigating = true, shouldCloseAllModals = false, topModalOffset = 0) { +function close(onModalCloseCallback: () => void, isNavigating = true, shouldCloseAllModals = false) { if (closeModals.length === 0) { - onModalCloseCallback?.(); + onModalCloseCallback(); return; } - if (onModalCloseCallback) { - onModalClose = onModalCloseCallback; - } + onModalClose = onModalCloseCallback; shouldCloseAll = shouldCloseAllModals; isNavigate = isNavigating; - closeTop(topModalOffset); + closeTop(); } function onModalDidClose() { diff --git a/src/libs/preventTextInputFocusOnFirstResponderOnce/index.ios.ts b/src/libs/preventTextInputFocusOnFirstResponderOnce/index.ios.ts deleted file mode 100644 index 702bcb283ed75..0000000000000 --- a/src/libs/preventTextInputFocusOnFirstResponderOnce/index.ios.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type {PreventTextInputFocusOnFirstResponderOnce} from './types'; - -/** - * This will prevent the composer's text input from focusing the next time it becomes the - * first responder in the UIResponder chain. (iOS only, no-op on Android and web) - */ -const preventTextInputFocusOnFirstResponderOnce: PreventTextInputFocusOnFirstResponderOnce = (composerRef) => { - composerRef.current?.preventFocusOnFirstResponderOnce(); -}; - -export default preventTextInputFocusOnFirstResponderOnce; diff --git a/src/libs/preventTextInputFocusOnFirstResponderOnce/index.ts b/src/libs/preventTextInputFocusOnFirstResponderOnce/index.ts deleted file mode 100644 index 05c4a5de971a0..0000000000000 --- a/src/libs/preventTextInputFocusOnFirstResponderOnce/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type {PreventTextInputFocusOnFirstResponderOnce} from './types'; - -const preventTextInputFocusOnFirstResponderOnce: PreventTextInputFocusOnFirstResponderOnce = () => {}; - -export default preventTextInputFocusOnFirstResponderOnce; diff --git a/src/libs/preventTextInputFocusOnFirstResponderOnce/types.ts b/src/libs/preventTextInputFocusOnFirstResponderOnce/types.ts deleted file mode 100644 index 0c2e1233c5571..0000000000000 --- a/src/libs/preventTextInputFocusOnFirstResponderOnce/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type {RefObject} from 'react'; -import type {TextInput} from 'react-native'; - -type PreventTextInputFocusOnFirstResponderOnce = (composerRef: RefObject) => void; - -// eslint-disable-next-line import/prefer-default-export -export type {PreventTextInputFocusOnFirstResponderOnce}; diff --git a/src/libs/refocusComposerAfterPreventFirstResponder.ts b/src/libs/refocusComposerAfterPreventFirstResponder.ts deleted file mode 100644 index 6ac3fca6dcbc4..0000000000000 --- a/src/libs/refocusComposerAfterPreventFirstResponder.ts +++ /dev/null @@ -1,15 +0,0 @@ -import isWindowReadyToFocus from './isWindowReadyToFocus'; -import type {ComposerType} from './ReportActionComposeFocusManager'; -import ReportActionComposeFocusManager from './ReportActionComposeFocusManager'; - -function refocusComposerAfterPreventFirstResponder(composerToRefocusOnClose: ComposerType | undefined) { - return isWindowReadyToFocus().then(() => { - if (composerToRefocusOnClose === 'main') { - ReportActionComposeFocusManager.composerRef.current?.focus(); - } else if (composerToRefocusOnClose === 'edit') { - ReportActionComposeFocusManager.editComposerRef.current?.focus(); - } - }); -} - -export default refocusComposerAfterPreventFirstResponder; diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index ee73611b6d335..e13032099ab24 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -13,9 +13,6 @@ import useLocalize from '@hooks/useLocalize'; import {deleteMoneyRequest, deleteTrackExpense} from '@libs/actions/IOU'; import {deleteReportComment} from '@libs/actions/Report'; import calculateAnchorPosition from '@libs/calculateAnchorPosition'; -import refocusComposerAfterPreventFirstResponder from '@libs/refocusComposerAfterPreventFirstResponder'; -import type {ComposerType} from '@libs/ReportActionComposeFocusManager'; -import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import {getOriginalMessage, isMoneyRequestAction, isTrackExpenseAction} from '@libs/ReportActionsUtils'; import CONST from '@src/CONST'; import type {AnchorDimensions} from '@src/styles'; @@ -83,8 +80,6 @@ function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef(); - const onPopoverShow = useRef(() => {}); const [isContextMenuOpening, setIsContextMenuOpening] = useState(false); const onPopoverHide = useRef(() => {}); @@ -178,12 +173,6 @@ function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef {}, onHide = () => {}, setIsEmojiPickerActive = () => {}} = callbacks; @@ -277,24 +266,17 @@ function PopoverReportActionContextMenu(_props: unknown, ref: ForwardedRef