Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: intercept mouse selection when out of input field #42823

Merged
merged 11 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/components/MoneyRequestAmountInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {ForwardedRef} from 'react';
import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
import type {NativeSyntheticEvent, StyleProp, TextInputSelectionChangeEventData, TextStyle, ViewStyle} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import {useMouseContext} from '@hooks/useMouseContext';
import * as Browser from '@libs/Browser';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import getOperatingSystem from '@libs/getOperatingSystem';
Expand Down Expand Up @@ -258,6 +259,16 @@ function MoneyRequestAmountInput(

const formattedAmount = MoneyRequestUtils.replaceAllDigits(currentAmount, toLocaleDigit);

const {setMouseDown, setMouseUp} = useMouseContext();
const handleMouseDown = (e: React.MouseEvent<Element, MouseEvent>) => {
e.stopPropagation();
setMouseDown();
};
const handleMouseUp = (e: React.MouseEvent<Element, MouseEvent>) => {
e.stopPropagation();
setMouseUp();
};

return (
<TextInputWithCurrencySymbol
autoGrow={autoGrow}
Expand Down Expand Up @@ -298,7 +309,8 @@ function MoneyRequestAmountInput(
touchableInputWrapperStyle={props.touchableInputWrapperStyle}
maxLength={maxLength}
hideFocusedState={hideFocusedState}
onMouseDown={(event) => event.stopPropagation()}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
/>
);
}
Expand Down
25 changes: 14 additions & 11 deletions src/components/MoneyRequestConfirmationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import {MouseProvider} from '@hooks/useMouseContext';
import useNetwork from '@hooks/useNetwork';
import usePermissions from '@hooks/usePermissions';
import usePrevious from '@hooks/usePrevious';
Expand Down Expand Up @@ -1228,17 +1229,19 @@ function MoneyRequestConfirmationList({
);

return (
<SelectionList<MoneyRequestConfirmationListItem>
sections={sections}
ListItem={UserListItem}
onSelectRow={navigateToReportOrUserDetail}
shouldDebounceRowSelect
canSelectMultiple={false}
shouldPreventDefaultFocusOnSelectRow
footerContent={footerContent}
listFooterContent={listFooterContent}
containerStyle={[styles.flexBasisAuto]}
/>
<MouseProvider>
<SelectionList<MoneyRequestConfirmationListItem>
sections={sections}
ListItem={UserListItem}
onSelectRow={navigateToReportOrUserDetail}
shouldDebounceRowSelect
canSelectMultiple={false}
shouldPreventDefaultFocusOnSelectRow
footerContent={footerContent}
listFooterContent={listFooterContent}
containerStyle={[styles.flexBasisAuto]}
/>
</MouseProvider>
);
}

Expand Down
12 changes: 12 additions & 0 deletions src/components/SelectionList/BaseListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as Expensicons from '@components/Icon/Expensicons';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import useHover from '@hooks/useHover';
import {useMouseContext} from '@hooks/useMouseContext';
import useSyncFocus from '@hooks/useSyncFocus';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
Expand Down Expand Up @@ -36,11 +37,16 @@ function BaseListItem<TItem extends ListItem>({
const theme = useTheme();
const styles = useThemeStyles();
const {hovered, bind} = useHover();
const {isMouseDownOnInput, setMouseUp} = useMouseContext();

const pressableRef = useRef<View>(null);

// Sync focus on an item
useSyncFocus(pressableRef, Boolean(isFocused), shouldSyncFocus);
const handleMouseUp = (e: React.MouseEvent<Element, MouseEvent>) => {
e.stopPropagation();
setMouseUp();
};

const rightHandSideComponentRender = () => {
if (canSelectMultiple || !rightHandSideComponent) {
Expand All @@ -67,6 +73,10 @@ function BaseListItem<TItem extends ListItem>({
{...bind}
ref={pressableRef}
onPress={(e) => {
if (isMouseDownOnInput) {
e?.stopPropagation(); // Preventing the click action
return;
}
if (shouldPreventEnterKeySubmit && e && 'key' in e && e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) {
return;
}
Expand All @@ -82,6 +92,8 @@ function BaseListItem<TItem extends ListItem>({
id={keyForList ?? ''}
style={pressableStyle}
onFocus={onFocus}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
tabIndex={item.tabIndex}
>
<View style={wrapperStyle}>
Expand Down
5 changes: 5 additions & 0 deletions src/components/TextInputWithCurrencySymbol/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ type TextInputWithCurrencySymbolProps = {
*/
onMouseDown?: ((e: React.MouseEvent) => void) | undefined;

/**
* Callback that is called when the text input is pressed up
*/
onMouseUp?: ((e: React.MouseEvent) => void) | undefined;

/** Whether the currency symbol is pressable */
isCurrencyPressable: boolean;

Expand Down
33 changes: 33 additions & 0 deletions src/hooks/useMouseContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type {ReactNode} from 'react';
import React, {createContext, useContext, useMemo, useState} from 'react';

type MouseContextProps = {
isMouseDownOnInput: boolean;
setMouseDown: () => void;
setMouseUp: () => void;
};

const MouseContext = createContext<MouseContextProps>({
isMouseDownOnInput: false,
setMouseDown: () => {},
setMouseUp: () => {},
});

type MouseProviderProps = {
children: ReactNode;
};

function MouseProvider({children}: MouseProviderProps) {
const [isMouseDownOnInput, setIsMouseDownOnInput] = useState(false);

const setMouseDown = () => setIsMouseDownOnInput(true);
const setMouseUp = () => setIsMouseDownOnInput(false);

const value = useMemo(() => ({isMouseDownOnInput, setMouseDown, setMouseUp}), [isMouseDownOnInput]);

return <MouseContext.Provider value={value}>{children}</MouseContext.Provider>;
}

const useMouseContext = () => useContext(MouseContext);

export {MouseProvider, useMouseContext};
Loading