Skip to content

Commit f8f54cf

Browse files
authored
Merge pull request #55494 from huult/53814-fix-confirmation-button-jump
53814 fix confirmation button jump
2 parents e716659 + 7784586 commit f8f54cf

File tree

4 files changed

+84
-17
lines changed

4 files changed

+84
-17
lines changed

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import CustomStatusBarAndBackgroundContextProvider from './components/CustomStat
1414
import ErrorBoundary from './components/ErrorBoundary';
1515
import HTMLEngineProvider from './components/HTMLEngineProvider';
1616
import InitialURLContextProvider from './components/InitialURLContextProvider';
17+
import {InputBlurContextProvider} from './components/InputBlurContext';
1718
import KeyboardProvider from './components/KeyboardProvider';
1819
import {LocaleContextProvider} from './components/LocaleContextProvider';
1920
import OnyxProvider from './components/OnyxProvider';
@@ -98,6 +99,7 @@ function App({url}: AppProps) {
9899
KeyboardProvider,
99100
SearchRouterContextProvider,
100101
ProductTrainingContextProvider,
102+
InputBlurContextProvider,
101103
]}
102104
>
103105
<CustomStatusBarAndBackground />

src/components/Form/FormProvider.tsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import {useFocusEffect} from '@react-navigation/native';
22
import lodashIsEqual from 'lodash/isEqual';
33
import type {ForwardedRef, MutableRefObject, ReactNode, RefAttributes} from 'react';
44
import React, {createRef, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
5+
import {InteractionManager} from 'react-native';
56
import type {NativeSyntheticEvent, StyleProp, TextInputSubmitEditingEventData, ViewStyle} from 'react-native';
67
import {useOnyx} from 'react-native-onyx';
8+
import {useInputBlurContext} from '@components/InputBlurContext';
79
import useDebounceNonReactive from '@hooks/useDebounceNonReactive';
810
import useLocalize from '@hooks/useLocalize';
9-
import * as ValidationUtils from '@libs/ValidationUtils';
11+
import {isSafari} from '@libs/Browser';
12+
import {prepareValues} from '@libs/ValidationUtils';
1013
import Visibility from '@libs/Visibility';
11-
import * as FormActions from '@userActions/FormActions';
14+
import {clearErrorFields, clearErrors, setDraftValues, setErrors as setFormErrors} from '@userActions/FormActions';
1215
import CONST from '@src/CONST';
1316
import type {OnyxFormDraftKey, OnyxFormKey} from '@src/ONYXKEYS';
1417
import ONYXKEYS from '@src/ONYXKEYS';
@@ -95,15 +98,16 @@ function FormProvider(
9598
const [inputValues, setInputValues] = useState<Form>(() => ({...draftValues}));
9699
const [errors, setErrors] = useState<GenericFormInputErrors>({});
97100
const hasServerError = useMemo(() => !!formState && !isEmptyObject(formState?.errors), [formState]);
101+
const {setIsBlurred} = useInputBlurContext();
98102

99103
const onValidate = useCallback(
100104
(values: FormOnyxValues, shouldClearServerError = true) => {
101-
const trimmedStringValues = shouldTrimValues ? ValidationUtils.prepareValues(values) : values;
105+
const trimmedStringValues = shouldTrimValues ? prepareValues(values) : values;
102106

103107
if (shouldClearServerError) {
104-
FormActions.clearErrors(formID);
108+
clearErrors(formID);
105109
}
106-
FormActions.clearErrorFields(formID);
110+
clearErrorFields(formID);
107111

108112
const validateErrors: GenericFormInputErrors = validate?.(trimmedStringValues) ?? {};
109113

@@ -168,7 +172,7 @@ function FormProvider(
168172
}
169173

170174
// Prepare validation values
171-
const trimmedStringValues = shouldTrimValues ? ValidationUtils.prepareValues(inputValues) : inputValues;
175+
const trimmedStringValues = shouldTrimValues ? prepareValues(inputValues) : inputValues;
172176

173177
// Validate in order to make sure the correct error translations are displayed,
174178
// making sure to not clear server errors if they exist
@@ -194,7 +198,7 @@ function FormProvider(
194198
}
195199

196200
// Prepare values before submitting
197-
const trimmedStringValues = shouldTrimValues ? ValidationUtils.prepareValues(inputValues) : inputValues;
201+
const trimmedStringValues = shouldTrimValues ? prepareValues(inputValues) : inputValues;
198202

199203
// Touches all form inputs, so we can validate the entire form
200204
Object.keys(inputRefs.current).forEach((inputID) => (touchedInputs.current[inputID] = true));
@@ -246,16 +250,16 @@ function FormProvider(
246250
);
247251

248252
const resetErrors = useCallback(() => {
249-
FormActions.clearErrors(formID);
250-
FormActions.clearErrorFields(formID);
253+
clearErrors(formID);
254+
clearErrorFields(formID);
251255
setErrors({});
252256
}, [formID]);
253257

254258
const resetFormFieldError = useCallback(
255259
(inputID: keyof Form) => {
256260
const newErrors = {...errors};
257261
delete newErrors[inputID];
258-
FormActions.setErrors(formID, newErrors as Errors);
262+
setFormErrors(formID, newErrors as Errors);
259263
setErrors(newErrors);
260264
},
261265
[errors, formID],
@@ -371,6 +375,11 @@ function FormProvider(
371375
}, VALIDATE_DELAY);
372376
}
373377
inputProps.onBlur?.(event);
378+
if (isSafari()) {
379+
InteractionManager.runAfterInteractions(() => {
380+
setIsBlurred(true);
381+
});
382+
}
374383
},
375384
onInputChange: (value, key) => {
376385
const inputKey = key ?? inputID;
@@ -387,13 +396,13 @@ function FormProvider(
387396
});
388397

389398
if (inputProps.shouldSaveDraft && !formID.includes('Draft')) {
390-
FormActions.setDraftValues(formID, {[inputKey]: value});
399+
setDraftValues(formID, {[inputKey]: value});
391400
}
392401
inputProps.onValueChange?.(value, inputKey);
393402
},
394403
};
395404
},
396-
[draftValues, inputValues, formState?.errorFields, errors, submit, setTouchedInput, shouldValidateOnBlur, onValidate, hasServerError, formID, shouldValidateOnChange],
405+
[draftValues, inputValues, formState?.errorFields, errors, submit, setTouchedInput, shouldValidateOnBlur, onValidate, hasServerError, setIsBlurred, formID, shouldValidateOnChange],
397406
);
398407
const value = useMemo(() => ({registerInput}), [registerInput]);
399408

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React, {useContext, useMemo, useState} from 'react';
2+
import type ChildrenProps from '@src/types/utils/ChildrenProps';
3+
4+
type InputBlurContextType = {
5+
isBlurred: boolean; // Boolean state to track blur
6+
setIsBlurred: React.Dispatch<React.SetStateAction<boolean>>; // Function to update the state
7+
};
8+
9+
const InputBlurContext = React.createContext<InputBlurContextType>({
10+
isBlurred: true,
11+
setIsBlurred: () => {},
12+
});
13+
14+
function InputBlurContextProvider({children}: ChildrenProps) {
15+
const [isBlurred, setIsBlurred] = useState<boolean>(false);
16+
17+
const contextValue = useMemo(
18+
() => ({
19+
isBlurred,
20+
setIsBlurred,
21+
}),
22+
[isBlurred],
23+
);
24+
25+
return <InputBlurContext.Provider value={contextValue}>{children}</InputBlurContext.Provider>;
26+
}
27+
28+
function useInputBlurContext() {
29+
return useContext(InputBlurContext);
30+
}
31+
32+
export {InputBlurContext, useInputBlurContext, InputBlurContextProvider};

src/components/ScreenWrapper.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@ import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets';
1212
import useTackInputFocus from '@hooks/useTackInputFocus';
1313
import useThemeStyles from '@hooks/useThemeStyles';
1414
import useWindowDimensions from '@hooks/useWindowDimensions';
15-
import * as Browser from '@libs/Browser';
15+
import {isMobile, isMobileWebKit, isSafari} from '@libs/Browser';
1616
import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types';
1717
import type {AuthScreensParamList, RootStackParamList} from '@libs/Navigation/types';
18+
import addViewportResizeListener from '@libs/VisualViewport';
1819
import toggleTestToolsModal from '@userActions/TestTool';
1920
import CONST from '@src/CONST';
2021
import CustomDevMenu from './CustomDevMenu';
2122
import FocusTrapForScreens from './FocusTrap/FocusTrapForScreen';
2223
import type FocusTrapForScreenProps from './FocusTrap/FocusTrapForScreen/FocusTrapProps';
2324
import HeaderGap from './HeaderGap';
2425
import ImportedStateIndicator from './ImportedStateIndicator';
26+
import {useInputBlurContext} from './InputBlurContext';
2527
import KeyboardAvoidingView from './KeyboardAvoidingView';
2628
import ModalContext from './Modal/ModalContext';
2729
import OfflineIndicator from './OfflineIndicator';
@@ -159,12 +161,13 @@ function ScreenWrapper(
159161
const {isDevelopment} = useEnvironment();
160162
const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false);
161163
const maxHeight = shouldEnableMaxHeight ? windowHeight : undefined;
162-
const minHeight = shouldEnableMinHeight && !Browser.isSafari() ? initialHeight : undefined;
164+
const minHeight = shouldEnableMinHeight && !isSafari() ? initialHeight : undefined;
163165

164166
const route = useRoute();
165167
const shouldReturnToOldDot = useMemo(() => {
166168
return !!route?.params && 'singleNewDotEntry' in route.params && route.params.singleNewDotEntry === 'true';
167169
}, [route?.params]);
170+
const {isBlurred, setIsBlurred} = useInputBlurContext();
168171

169172
UNSTABLE_usePreventRemove(shouldReturnToOldDot, () => {
170173
NativeModules.HybridAppModule?.closeReactNativeApp(false, false);
@@ -181,14 +184,35 @@ function ScreenWrapper(
181184
PanResponder.create({
182185
onMoveShouldSetPanResponderCapture: (_e, gestureState) => {
183186
const isHorizontalSwipe = Math.abs(gestureState.dx) > Math.abs(gestureState.dy);
184-
const shouldDismissKeyboard = shouldDismissKeyboardBeforeClose && Keyboard.isVisible() && Browser.isMobile();
187+
const shouldDismissKeyboard = shouldDismissKeyboardBeforeClose && Keyboard.isVisible() && isMobile();
185188

186189
return isHorizontalSwipe && shouldDismissKeyboard;
187190
},
188191
onPanResponderGrant: Keyboard.dismiss,
189192
}),
190193
).current;
191194

195+
useEffect(() => {
196+
/**
197+
* Handler to manage viewport resize events specific to Safari.
198+
* Disables the blur state when Safari is detected.
199+
*/
200+
const handleViewportResize = () => {
201+
if (!isSafari()) {
202+
return; // Exit early if not Safari
203+
}
204+
setIsBlurred(false); // Disable blur state for Safari
205+
};
206+
207+
// Add the viewport resize listener
208+
const removeResizeListener = addViewportResizeListener(handleViewportResize);
209+
210+
// Cleanup function to remove the listener
211+
return () => {
212+
removeResizeListener();
213+
};
214+
}, [setIsBlurred]);
215+
192216
useEffect(() => {
193217
// On iOS, the transitionEnd event doesn't trigger some times. As such, we need to set a timeout
194218
const timeout = setTimeout(() => {
@@ -249,7 +273,7 @@ function ScreenWrapper(
249273
paddingStyle.paddingBottom = unmodifiedPaddings.bottom;
250274
}
251275

252-
const isAvoidingViewportScroll = useTackInputFocus(isFocused && shouldEnableMaxHeight && shouldAvoidScrollOnVirtualViewport && Browser.isMobileWebKit());
276+
const isAvoidingViewportScroll = useTackInputFocus(isFocused && shouldEnableMaxHeight && shouldAvoidScrollOnVirtualViewport && isMobileWebKit());
253277
const contextValue = useMemo(
254278
() => ({didScreenTransitionEnd, isSafeAreaTopPaddingApplied, isSafeAreaBottomPaddingApplied: includeSafeAreaPaddingBottom}),
255279
[didScreenTransitionEnd, includeSafeAreaPaddingBottom, isSafeAreaTopPaddingApplied],
@@ -271,7 +295,7 @@ function ScreenWrapper(
271295
{...keyboardDismissPanResponder.panHandlers}
272296
>
273297
<KeyboardAvoidingView
274-
style={[styles.w100, styles.h100, {maxHeight}, isAvoidingViewportScroll ? [styles.overflowAuto, styles.overscrollBehaviorContain] : {}]}
298+
style={[styles.w100, styles.h100, !isBlurred ? {maxHeight} : undefined, isAvoidingViewportScroll ? [styles.overflowAuto, styles.overscrollBehaviorContain] : {}]}
275299
behavior={keyboardAvoidingViewBehavior}
276300
enabled={shouldEnableKeyboardAvoidingView}
277301
>

0 commit comments

Comments
 (0)