diff --git a/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/TransparentOverlay/TransparentOverlay.tsx b/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/TransparentOverlay/TransparentOverlay.tsx new file mode 100644 index 000000000000..c761faccad39 --- /dev/null +++ b/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/TransparentOverlay/TransparentOverlay.tsx @@ -0,0 +1,47 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import type {PointerEvent} from 'react-native'; +import type PressableProps from '@components/Pressable/GenericPressable/types'; +import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; + +type TransparentOverlayProps = { + resetSuggestions: () => void; +}; + +type OnPressHandler = PressableProps['onPress']; + +function TransparentOverlay({resetSuggestions}: TransparentOverlayProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const onResetSuggestions = useCallback>( + (event) => { + event?.preventDefault(); + resetSuggestions(); + }, + [resetSuggestions], + ); + + const handlePointerDown = useCallback((e: PointerEvent) => { + e?.preventDefault(); + }, []); + + return ( + + + + ); +} + +export default TransparentOverlay; diff --git a/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/index.native.tsx b/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/index.native.tsx index 9848d77e479e..9ac43c4d8830 100644 --- a/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/index.native.tsx +++ b/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/index.native.tsx @@ -4,9 +4,10 @@ import {View} from 'react-native'; import BaseAutoCompleteSuggestions from '@components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions'; import useStyleUtils from '@hooks/useStyleUtils'; import getBottomSuggestionPadding from './getBottomSuggestionPadding'; +import TransparentOverlay from './TransparentOverlay/TransparentOverlay'; import type {AutoCompleteSuggestionsPortalProps} from './types'; -function AutoCompleteSuggestionsPortal({left = 0, width = 0, bottom = 0, ...props}: AutoCompleteSuggestionsPortalProps) { +function AutoCompleteSuggestionsPortal({left = 0, width = 0, bottom = 0, resetSuggestions = () => {}, ...props}: AutoCompleteSuggestionsPortalProps) { const StyleUtils = useStyleUtils(); const styles = useMemo(() => StyleUtils.getBaseAutoCompleteSuggestionContainerStyle({left, width, bottom: bottom + getBottomSuggestionPadding()}), [StyleUtils, left, width, bottom]); @@ -16,6 +17,7 @@ function AutoCompleteSuggestionsPortal({left = 0, width = 0, bottom return ( + {/* eslint-disable-next-line react/jsx-props-no-spreading */} diff --git a/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/index.tsx b/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/index.tsx index 2d1d533c2859..d26dd0422368 100644 --- a/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/index.tsx +++ b/src/components/AutoCompleteSuggestions/AutoCompleteSuggestionsPortal/index.tsx @@ -5,6 +5,7 @@ import {View} from 'react-native'; import BaseAutoCompleteSuggestions from '@components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions'; import useStyleUtils from '@hooks/useStyleUtils'; import getBottomSuggestionPadding from './getBottomSuggestionPadding'; +import TransparentOverlay from './TransparentOverlay/TransparentOverlay'; import type {AutoCompleteSuggestionsPortalProps} from './types'; /** @@ -14,7 +15,13 @@ import type {AutoCompleteSuggestionsPortalProps} from './types'; * On the native platform, tapping on auto-complete suggestions will not blur the main input. */ -function AutoCompleteSuggestionsPortal({left = 0, width = 0, bottom = 0, ...props}: AutoCompleteSuggestionsPortalProps): ReactElement | null | false { +function AutoCompleteSuggestionsPortal({ + left = 0, + width = 0, + bottom = 0, + resetSuggestions = () => {}, + ...props +}: AutoCompleteSuggestionsPortalProps): ReactElement | null | false { const StyleUtils = useStyleUtils(); const bodyElement = document.querySelector('body'); @@ -31,7 +38,10 @@ function AutoCompleteSuggestionsPortal({left = 0, width = 0, bottom !!width && bodyElement && ReactDOM.createPortal( - {componentToRender}, + <> + + {componentToRender} + , bodyElement, ) ); diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index 1aa486eccd4d..41a01fa27c46 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -34,6 +34,13 @@ function isEnoughSpaceToRenderMenuAboveCursor({y, cursorCoordinates, scrollValue return y + (cursorCoordinates.y - scrollValue) > contentHeight + topInset + CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_BOX_MAX_SAFE_DISTANCE; } +const initialContainerState = { + width: 0, + left: 0, + bottom: 0, + cursorCoordinates: {x: 0, y: 0}, +}; + /** * On the mobile-web platform, when long-pressing on auto-complete suggestions, * we need to prevent focus shifting to avoid blurring the main input (which makes the suggestions picker close and fires the onSelect callback). @@ -48,12 +55,7 @@ function AutoCompleteSuggestions({measureParentContainerAndReportCu const prevLeftValue = React.useRef(0); const {windowHeight, windowWidth, isSmallScreenWidth} = useWindowDimensions(); const [suggestionHeight, setSuggestionHeight] = React.useState(0); - const [containerState, setContainerState] = React.useState({ - width: 0, - left: 0, - bottom: 0, - cursorCoordinates: {x: 0, y: 0}, - }); + const [containerState, setContainerState] = React.useState(initialContainerState); const StyleUtils = useStyleUtils(); const insets = useSafeAreaInsets(); const {keyboardHeight} = useKeyboardState(); @@ -80,6 +82,11 @@ function AutoCompleteSuggestions({measureParentContainerAndReportCu return; } + if (!windowHeight || !windowWidth || !suggestionsLength) { + setContainerState(initialContainerState); + return; + } + measureParentContainerAndReportCursor(({x, y, width, scrollValue, cursorCoordinates}: MeasureParentContainerAndCursor) => { const xCoordinatesOfCursor = x + cursorCoordinates.x; const bigScreenLeftOffset = diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index 48bb6b713032..57347cd65abe 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -42,6 +42,9 @@ type AutoCompleteSuggestionsProps = { /** Measures the parent container's position and dimensions. Also add a cursor coordinates */ measureParentContainerAndReportCursor?: (props: MeasureParentContainerAndCursorCallback) => void; + + /** Reset the emoji suggestions */ + resetSuggestions?: () => void; }; export type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps, MeasureParentContainerAndCursorCallback, MeasureParentContainerAndCursor}; diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx index 3781507b544c..a2996482be6a 100644 --- a/src/components/EmojiSuggestions.tsx +++ b/src/components/EmojiSuggestions.tsx @@ -34,6 +34,9 @@ type EmojiSuggestionsProps = { /** Measures the parent container's position and dimensions. Also add cursor coordinates */ measureParentContainerAndReportCursor: (callback: MeasureParentContainerAndCursorCallback) => void; + + /** Reset the emoji suggestions */ + resetSuggestions: () => void; }; /** @@ -49,6 +52,7 @@ function EmojiSuggestions({ preferredSkinToneIndex, highlightedEmojiIndex = 0, measureParentContainerAndReportCursor = () => {}, + resetSuggestions, }: EmojiSuggestionsProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -93,6 +97,7 @@ function EmojiSuggestions({ isSuggestionPickerLarge={isEmojiPickerLarge} accessibilityLabelExtractor={keyExtractor} measureParentContainerAndReportCursor={measureParentContainerAndReportCursor} + resetSuggestions={resetSuggestions} /> ); } diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx index 1142a90c87d1..bdc19316d491 100644 --- a/src/components/MentionSuggestions.tsx +++ b/src/components/MentionSuggestions.tsx @@ -55,6 +55,9 @@ type MentionSuggestionsProps = { /** Measures the parent container's position and dimensions. Also add cursor coordinates */ measureParentContainerAndReportCursor: (callback: MeasureParentContainerAndCursorCallback) => void; + + /** Reset the emoji suggestions */ + resetSuggestions: () => void; }; /** @@ -62,7 +65,15 @@ type MentionSuggestionsProps = { */ const keyExtractor = (item: Mention) => item.alternateText; -function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSelect, isMentionPickerLarge, measureParentContainerAndReportCursor = () => {}}: MentionSuggestionsProps) { +function MentionSuggestions({ + prefix, + mentions, + highlightedMentionIndex = 0, + onSelect, + isMentionPickerLarge, + measureParentContainerAndReportCursor = () => {}, + resetSuggestions, +}: MentionSuggestionsProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -149,6 +160,7 @@ function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSe isSuggestionPickerLarge={isMentionPickerLarge} accessibilityLabelExtractor={keyExtractor} measureParentContainerAndReportCursor={measureParentContainerAndReportCursor} + resetSuggestions={resetSuggestions} /> ); } diff --git a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx index 8d5a544afd42..c6a4738331b7 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx @@ -222,6 +222,7 @@ function SuggestionEmoji( preferredSkinToneIndex={preferredSkinTone} isEmojiPickerLarge={!!isAutoSuggestionPickerLarge} measureParentContainerAndReportCursor={measureParentContainerAndReportCursor} + resetSuggestions={resetSuggestions} /> ); } diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx index 86a05bad1994..740159694691 100644 --- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx +++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx @@ -435,6 +435,7 @@ function SuggestionMention( onSelect={insertSelectedMention} isMentionPickerLarge={!!isAutoSuggestionPickerLarge} measureParentContainerAndReportCursor={measureParentContainerAndReportCursor} + resetSuggestions={resetSuggestions} /> ); } diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index b34cd68730f0..726679351b79 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -194,7 +194,7 @@ function ReportActionItemMessageEdit( } // Show the main composer when the focused message is deleted from another client - // to prevent the main composer stays hidden until we swtich to another chat. + // to prevent the main composer stays hidden until we switch to another chat. setShouldShowComposeInputKeyboardAware(true); }; }, @@ -354,7 +354,7 @@ function ReportActionItemMessageEdit( const keyEvent = e as KeyboardEvent; const isSuggestionsMenuVisible = suggestionsRef.current?.getIsSuggestionsMenuVisible(); - if (isSuggestionsMenuVisible && keyEvent.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) { + if (isSuggestionsMenuVisible) { suggestionsRef.current?.triggerHotkeyActions(keyEvent); return; } @@ -374,16 +374,12 @@ function ReportActionItemMessageEdit( [deleteDraft, hideSuggestionMenu, isKeyboardShown, isSmallScreenWidth, publishDraft], ); - const measureContainer = useCallback( - (callback: MeasureInWindowOnSuccessCallback) => { - if (!containerRef.current) { - return; - } - containerRef.current.measureInWindow(callback); - }, - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - [isFocused], - ); + const measureContainer = useCallback((callback: MeasureInWindowOnSuccessCallback) => { + if (!containerRef.current) { + return; + } + containerRef.current.measureInWindow(callback); + }, []); const measureParentContainerAndReportCursor = useCallback( (callback: MeasureParentContainerAndCursorCallback) => { @@ -408,8 +404,7 @@ function ReportActionItemMessageEdit( // eslint-disable-next-line react-compiler/react-compiler tag.value = findNodeHandle(textInputRef.current) ?? -1; - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, []); + }, [tag]); useFocusedInputHandler( { onSelectionChange: (event) => {