ammarahm-ed / react-native-actions-sheet

A Cross Platform(Android, iOS & Web) ActionSheet with a flexible api, native performance and zero dependency code for react native. Create anything you want inside ActionSheet.
https://rnas.vercel.app
MIT License
1.41k stars 119 forks source link

Keyboard handler causing full screen sheet to rapidly close and reopen, header scrolling behind dynamic island. #365

Open richardshaw700 opened 1 month ago

richardshaw700 commented 1 month ago

// Issue 1: While sheet is fullscren, the keyboard handler makes the sheet rapidly close and open again, onFocus, and onBlur. // Issue 2: There's no method to add margin to the top of the main container, so when you scroll on a list, the header sits behind the iPhone dynamic island. drawUnderStatusBar={false} and statusBarTranslucent={false} don't do anything.

See video here: https://github.com/ammarahm-ed/react-native-actions-sheet/assets/103525068/56036d80-363e-45fc-8c88-ee1c395bd95d

// Attempted solutions for Issue 1: // a. Set keyboardHandlerEnabled={false} and wrap the view, sheet, or children in react-native KeyboardAvoidingView. Didn't do anything // b. Attempted onChange position tracking to dynamically add padding to the bottom of the sheet. Massive jitter artifacts. Not a viable solution // c. 🛑 Only option for keyboard handling is to use the built-in keyboardHandlerEnabled prop.

// Attempted solutions for Issue 2: // a. Add paddingTop: 80 to containerStyle. Problem: doesn't solve issue 1 // b. Set containerStyle={{backgroundColor: 'transparent', height: '90%'}} and payload value View style={{backgroundColor: 'white', height: '110%'}}. ⭐️ Best solution yet, but causes several render artifacts, especially when opening a TextInput that sits behind the keyboard.

 return (
    <ActionSheet
      id={props.sheetId}
      snapPoints={[100]}
      headerAlwaysVisible={true}
      containerStyle={{backgroundColor: 'transparent', height: '90%'}}
      CustomHeaderComponent={customHeader()}
      gestureEnabled={true}
      keyboardHandlerEnabled={keyboardHandler}
      onClose={props.onClose}>
      <BlurView style={{borderRadius: 20, height: '110%'}} blurType={backgroundBlur} blurAmount={13} reducedTransparencyFallbackColor="white">
        {value}
      </BlurView>
    </ActionSheet>
richardshaw700 commented 1 month ago

• Same effect with BlurView or View btw -- already ruled that out. • CategoryBudgetsEditList uses a map. Tried FlashList, didn't help.

Here's some more background code:

  const handleCategoryBudgetsPress = () => {
    hapticFeedback();
    SheetManager.show('cheddar-sheet', {
      payload: {
        value: <CategoryBudgetsSheet appContext={appContext} cheddarStyles={cheddarStyles} styles={styles} />,
        keyboardHandler: true,
      },
    });
  }; 
export const CategoryBudgetsSheet = ({appContext, cheddarStyles, styles}) => {
  const categoriesController = appContext.categoriesController;
  const currencyCode = appContext.currencyCode;

  return (
    <View style={cheddarStyles.m24}>
      <View style={styles.bottomSheetListHeader}>
        <Text style={styles.bottomSheetHeaderText}>Category Budgets</Text>
      </View>

      <ScrollView showsVerticalScrollIndicator={false}>
        <View style={cheddarStyles.safeMarginBottom}>
          <CategoryBudgetsEditList
            isExpense={true}
            iconBackgroundColor={colors.icon}
            onEndEditing={(text, categoryId) => handleCategoryBudgetChange(text, categoryId, categoriesController, currencyCode, appContext)}
          />
        </View>
      </ScrollView>
    </View>
  );
};
ryanengland commented 1 month ago

Hi all,

To confirm, I'm also seeing this exact same behaviour, on an actions sheet with a set height:

https://github.com/ammarahm-ed/react-native-actions-sheet/assets/7584828/7c21b7ae-9e29-46ae-b656-199a0ad81542

The code for the action sheet is as follows, and I have confirmed that this behaviour exists regardless of safeArea modifications or drawUnderStatus bar. It seems like the minimal reproducible is this:

<ActionSheet
            gestureEnabled
            drawUnderStatusBar={false}
            containerStyle={{ height: 700 }}>
            <ScrollView>
             ....contents
            </ScrollView>
</ActionSheet>

I absolutely love this library - would be happy to dig into the code and try to resolve if pull requests are accepted. Any pointers appreciated!

Thanks,

Ryan

imtheaman commented 1 month ago

I've faced it as well, but only in emulators, not on real devices. dunno the cause yet btw

alireza-k74 commented 1 month ago

use this. its work for me

gestureEnabled
closable={false}
backgroundInteractionEnabled={true}
isModal={false}
animated={true}
Ritik5Prasad commented 1 month ago

@alireza-k74 Bro if you keep closable false then it won't close if you open keyboard or not We want to close through backdrop or gesture 😞

BenjaminTournois commented 1 month ago

Facing the same issue at the moment, setting animated={false} solving the problem by not showing the animation on close/reopen, but would still love to be able to show the animation without showing this weird behavior.

I realize that it happen when the position is below 0. You can check with

onChange={(position, height) => {
                if(position < 0){
                    console.log('position', position);
                }
            }}

I will try digging in the code to find a fix.

severinferard commented 4 weeks ago

I think I have a similar issue where the keyboard would briefly reopen when dismissed, shifting the content up.

https://github.com/ammarahm-ed/react-native-actions-sheet/assets/51379148/0834b371-172e-4077-84a8-d62c4c4fd5e9

After looking a bit into the code for the keyboard handling, I found that the keyboard event listeners were firing twice, probably because of rerenders during the keyboard animations.

Some simple changes fixed the issue for me, here is the patch to use with patch package, maybe someone else will find it useful.

diff --git a/node_modules/react-native-actions-sheet/dist/src/hooks/useKeyboard.js b/node_modules/react-native-actions-sheet/dist/src/hooks/useKeyboard.js
index ec2da4b..672a119 100644
--- a/node_modules/react-native-actions-sheet/dist/src/hooks/useKeyboard.js
+++ b/node_modules/react-native-actions-sheet/dist/src/hooks/useKeyboard.js
@@ -15,7 +15,12 @@ export function useKeyboard(enabled) {
     var _a = useState(false), shown = _a[0], setShown = _a[1];
     var _b = useState(initialValue), coordinates = _b[0], setCoordinates = _b[1];
     var _c = useState(0), keyboardHeight = _c[0], setKeyboardHeight = _c[1];
+    var keyboardHasReachedBottom = useRef(true);
+    
     var handleKeyboardDidShow = React.useCallback(function (e) {
+        if (!keyboardHasReachedBottom.current) return;
+        keyboardHasReachedBottom.current = false;
+
         if (pauseKeyboardHandler.current)
             return;
         setCoordinates({ start: e.startCoordinates, end: e.endCoordinates });
@@ -23,6 +28,17 @@ export function useKeyboard(enabled) {
         setShown(true);
     }, []);
     var handleKeyboardDidHide = React.useCallback(function (e) {
+        keyboardHasReachedBottom.current = true;
+        setShown(false);
+        if (e) {
+            setCoordinates({ start: e.startCoordinates, end: e.endCoordinates });
+        }
+        else {
+            setCoordinates(initialValue);
+            setKeyboardHeight(0);
+        }
+    }, []);
+    var handleKeyboardWillHide = React.useCallback(function (e) {
         setShown(false);
         if (e) {
             setCoordinates({ start: e.startCoordinates, end: e.endCoordinates });
@@ -43,13 +59,15 @@ export function useKeyboard(enabled) {
                 subscriptions.push(Keyboard.addListener('keyboardDidShow', handleKeyboardDidShow));
             }
             else {
-                subscriptions.push(Keyboard.addListener('keyboardWillShow', handleKeyboardDidShow), Keyboard.addListener('keyboardWillHide', handleKeyboardDidHide));
+                subscriptions.push(Keyboard.addListener('keyboardWillShow', handleKeyboardDidShow), Keyboard.addListener('keyboardWillHide', handleKeyboardWillHide));
             }
         }
         return function () {
             subscriptions.forEach(function (subscription) { return subscription.remove(); });
         };
-    }, [enabled, handleKeyboardDidHide, handleKeyboardDidShow]);
+    }, [enabled, handleKeyboardWillHide, handleKeyboardDidHide, handleKeyboardDidShow]);
+
+
     return {
         keyboardShown: !enabled ? false : shown,
         coordinates: !enabled || !shown ? emptyCoordinates : coordinates,
konooc commented 1 week ago

I think I have a similar issue where the keyboard would briefly reopen when dismissed, shifting the content up.

Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-06-11.at.12.23.20.mp4 After looking a bit into the code for the keyboard handling, I found that the keyboard event listeners were firing twice, probably because of rerenders during the keyboard animations.

Some simple changes fixed the issue for me, here is the patch to use with patch package, maybe someone else will find it useful.

diff --git a/node_modules/react-native-actions-sheet/dist/src/hooks/useKeyboard.js b/node_modules/react-native-actions-sheet/dist/src/hooks/useKeyboard.js
index ec2da4b..672a119 100644
--- a/node_modules/react-native-actions-sheet/dist/src/hooks/useKeyboard.js
+++ b/node_modules/react-native-actions-sheet/dist/src/hooks/useKeyboard.js
@@ -15,7 +15,12 @@ export function useKeyboard(enabled) {
     var _a = useState(false), shown = _a[0], setShown = _a[1];
     var _b = useState(initialValue), coordinates = _b[0], setCoordinates = _b[1];
     var _c = useState(0), keyboardHeight = _c[0], setKeyboardHeight = _c[1];
+    var keyboardHasReachedBottom = useRef(true);
+    
     var handleKeyboardDidShow = React.useCallback(function (e) {
+        if (!keyboardHasReachedBottom.current) return;
+        keyboardHasReachedBottom.current = false;
+
         if (pauseKeyboardHandler.current)
             return;
         setCoordinates({ start: e.startCoordinates, end: e.endCoordinates });
@@ -23,6 +28,17 @@ export function useKeyboard(enabled) {
         setShown(true);
     }, []);
     var handleKeyboardDidHide = React.useCallback(function (e) {
+        keyboardHasReachedBottom.current = true;
+        setShown(false);
+        if (e) {
+            setCoordinates({ start: e.startCoordinates, end: e.endCoordinates });
+        }
+        else {
+            setCoordinates(initialValue);
+            setKeyboardHeight(0);
+        }
+    }, []);
+    var handleKeyboardWillHide = React.useCallback(function (e) {
         setShown(false);
         if (e) {
             setCoordinates({ start: e.startCoordinates, end: e.endCoordinates });
@@ -43,13 +59,15 @@ export function useKeyboard(enabled) {
                 subscriptions.push(Keyboard.addListener('keyboardDidShow', handleKeyboardDidShow));
             }
             else {
-                subscriptions.push(Keyboard.addListener('keyboardWillShow', handleKeyboardDidShow), Keyboard.addListener('keyboardWillHide', handleKeyboardDidHide));
+                subscriptions.push(Keyboard.addListener('keyboardWillShow', handleKeyboardDidShow), Keyboard.addListener('keyboardWillHide', handleKeyboardWillHide));
             }
         }
         return function () {
             subscriptions.forEach(function (subscription) { return subscription.remove(); });
         };
-    }, [enabled, handleKeyboardDidHide, handleKeyboardDidShow]);
+    }, [enabled, handleKeyboardWillHide, handleKeyboardDidHide, handleKeyboardDidShow]);
+
+
     return {
         keyboardShown: !enabled ? false : shown,
         coordinates: !enabled || !shown ? emptyCoordinates : coordinates,

is good! this works for me