Files
react-native/Libraries/Components/ScrollView/ScrollViewViewConfig.js
T
Marc Rousavy 6e903b07fa Feature: ScrollView automaticallyAdjustKeyboardInsets (#31402)
Summary:
Currently, ScrollViews provide the prop `keyboardDismissMode` which lets you choose `"interactive"`. However when the keyboard is shown, it will be rendered above the ScrollView, potentially blocking content.

With the `automaticallyAdjustKeyboardInsets` prop the ScrollView will automatically adjust it's `contentInset`, `scrollIndicatorInsets` and `contentOffset` (scroll Y) props to push the content up so nothing gets blocked.

* The animation curve and duration of the Keyboard is exactly matched.
* The absolute position of the ScrollView is respected, so if the Keyboard only overlaps 10 pixels of the ScrollView, it will only get inset by 10 pixels.
* By respecting the absolute position on screen, this automatically makes it fully compatible with phones with notches (custom safe areas)
* By using the keyboard frame, this also works for different sized keyboards and even `<InputAccessoryView>`s
* This also supports `maintainVisibleContentPosition` and `autoscrollToTopThreshold`.
* I also fixed an issue with the `maintainVisibleContentPosition` (`autoscrollToTopThreshold`) prop(s), so they behave more reliably when `contentInset`s are applied. (This makes automatically scrolling to new items fully compatible with `automaticallyAdjustKeyboardInsets`)

## Changelog

* [iOS] [Added] - ScrollView: `automaticallyAdjustKeyboardInsets` prop: Automatically animate `contentInset`, `scrollIndicatorInsets` and `contentOffset` (scroll Y) to avoid the Keyboard. (respecting absolute position on screen and safe-areas)
* [iOS] [Fixed] - ScrollView: Respect `contentInset` when animating new items with `autoscrollToTopThreshold`, make `automaticallyAdjustKeyboardInsets` work with `autoscrollToTopThreshold` (includes vertical, vertical-inverted, horizontal and horizontal-inverted ScrollViews)

Pull Request resolved: https://github.com/facebook/react-native/pull/31402

Test Plan:
<table>
<tr>
<th>Before</th>
<th>After</th>
</tr>
<tr>
<td>

https://user-images.githubusercontent.com/15199031/115708680-9700aa80-a370-11eb-8016-e75d81a92cd7.MP4

</td>

<td>

https://user-images.githubusercontent.com/15199031/115708699-9b2cc800-a370-11eb-976f-c4010cd96d55.MP4

</td>
</table>

### "Why not just use `<KeyboardAvoidingView>`?"

<table>
<tr>
<th>Before (with <code>&lt;KeyboardAvoidingView&gt;</code>)</th>
<th>After (with <code>automaticallyAdjustKeyboardInsets</code>)</th>
</tr>
<tr>
<td>

https://user-images.githubusercontent.com/15199031/115708749-abdd3e00-a370-11eb-8e09-a27ffaef12b8.MP4

</td>

<td>

https://user-images.githubusercontent.com/15199031/115708777-b3044c00-a370-11eb-9b7a-e040ccb3ef8c.MP4

</td>
</table>

> Also notice how the `<KeyboardAvoidingView>` does not match the animation curve of the Keyboard

### Usage

```jsx
export const ChatPage = ({
  flatListProps,
  textInputProps
}: Props): React.ReactElement => (
  <>
    <FlatList
      {...flatListProps}
      keyboardDismissMode="interactive"
      automaticallyAdjustContentInsets={false}
      contentInsetAdjustmentBehavior="never"
      maintainVisibleContentPosition={{ minIndexForVisible: 0, autoscrollToTopThreshold: 100 }}
      automaticallyAdjustKeyboardInsets={true}
    />
    <InputAccessoryView backgroundColor={colors.white}>
      <ChatInput {...textInputProps} />
    </InputAccessoryView>
  </>
);
```

## Related Issues

* Fixes https://github.com/facebook/react-native/issues/31394
* Fixes https://github.com/facebook/react-native/issues/13073

Reviewed By: yungsters

Differential Revision: D30015799

Pulled By: sota000

fbshipit-source-id: 9ccfb4b6d477da192a96db4cfa07c31a2d2cefcb
2021-09-24 18:46:00 -07:00

85 lines
2.3 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
import type {PartialViewConfig} from '../../Renderer/shims/ReactNativeTypes';
const ScrollViewViewConfig = {
uiViewClassName: 'RCTScrollView',
bubblingEventTypes: {},
directEventTypes: {
topScrollToTop: {
registrationName: 'onScrollToTop',
},
},
validAttributes: {
alwaysBounceHorizontal: true,
alwaysBounceVertical: true,
automaticallyAdjustContentInsets: true,
automaticallyAdjustKeyboardInsets: true,
automaticallyAdjustsScrollIndicatorInsets: true,
bounces: true,
bouncesZoom: true,
canCancelContentTouches: true,
centerContent: true,
contentInset: {
diff: require('../../Utilities/differ/pointsDiffer'),
},
contentOffset: {
diff: require('../../Utilities/differ/pointsDiffer'),
},
contentInsetAdjustmentBehavior: true,
decelerationRate: true,
directionalLockEnabled: true,
disableIntervalMomentum: true,
endFillColor: {
process: require('../../StyleSheet/processColor'),
},
fadingEdgeLength: true,
indicatorStyle: true,
inverted: true,
keyboardDismissMode: true,
maintainVisibleContentPosition: true,
maximumZoomScale: true,
minimumZoomScale: true,
nestedScrollEnabled: true,
onMomentumScrollBegin: true,
onMomentumScrollEnd: true,
onScroll: true,
onScrollBeginDrag: true,
onScrollEndDrag: true,
onScrollToTop: true,
overScrollMode: true,
pagingEnabled: true,
persistentScrollbar: true,
pinchGestureEnabled: true,
scrollEnabled: true,
scrollEventThrottle: true,
scrollIndicatorInsets: {
diff: require('../../Utilities/differ/pointsDiffer'),
},
scrollPerfTag: true,
scrollToOverflowEnabled: true,
scrollsToTop: true,
sendMomentumEvents: true,
showsHorizontalScrollIndicator: true,
showsVerticalScrollIndicator: true,
snapToAlignment: true,
snapToEnd: true,
snapToInterval: true,
snapToOffsets: true,
snapToStart: true,
zoomScale: true,
},
};
module.exports = (ScrollViewViewConfig: PartialViewConfig);