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 6 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
14 changes: 14 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,12 +37,19 @@ function BaseListItem<TItem extends ListItem>({
const theme = useTheme();
const styles = useThemeStyles();
const {hovered, bind} = useHover();
const {isMouseDownOnInput} = useMouseContext();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const {isMouseDownOnInput} = useMouseContext();
const {isMouseDownOnInput, setMouseUp} = useMouseContext();


const pressableRef = useRef<View>(null);

// Sync focus on an item
useSyncFocus(pressableRef, Boolean(isFocused), shouldSyncFocus);

const {setMouseUp} = useMouseContext();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const {setMouseUp} = useMouseContext();

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

const rightHandSideComponentRender = () => {
if (canSelectMultiple || !rightHandSideComponent) {
return null;
Expand All @@ -67,6 +75,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 +94,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
36 changes: 36 additions & 0 deletions src/hooks/useMouseContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type {ReactNode} from 'react';
import React, {createContext, useContext, useMemo, useState} from 'react';

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

// Create a context with default values and handlers
dragnoir marked this conversation as resolved.
Show resolved Hide resolved
const MouseContext = createContext<MouseContextProps>({
isMouseDownOnInput: false,
setMouseDown: () => {},
setMouseUp: () => {},
});

type MouseProviderProps = {
children: ReactNode;
};

// Context provider component
dragnoir marked this conversation as resolved.
Show resolved Hide resolved
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>;
}

// Custom hook to use the mouse context
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Custom hook to use the mouse context

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

export {MouseProvider, useMouseContext};
Loading