diff --git a/packages/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/packages/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
index d2fff116cc32..0f848c61be6d 100644
--- a/packages/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
+++ b/packages/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
@@ -16,9 +16,13 @@ import type {
} from '../View/ViewPropTypes';
import type {KeyboardEvent, KeyboardMetrics} from './Keyboard';
+import KeyboardAvoidingViewNativeComponent from '../../../src/private/components/keyboardavoidingview/specs/KeyboardAvoidingViewNativeComponent';
+import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
import LayoutAnimation from '../../LayoutAnimation/LayoutAnimation';
import StyleSheet from '../../StyleSheet/StyleSheet';
+import NativeDeviceInfo from '../../Utilities/NativeDeviceInfo';
import Platform from '../../Utilities/Platform';
+import warnOnce from '../../Utilities/warnOnce';
import {type EventSubscription} from '../../vendor/emitter/EventEmitter';
import AccessibilityInfo from '../AccessibilityInfo/AccessibilityInfo';
import View from '../View/View';
@@ -44,6 +48,15 @@ type KeyboardAvoidingViewPropsCore = Readonly<{
*/
enabled?: ?boolean,
+ /**
+ * Follow the keyboard animation natively, frame by frame.
+ * Requires the `useSharedAnimatedBackend` feature flag and an edge-to-edge
+ * app; otherwise, this prop has no effect.
+ *
+ * @default `false`
+ */
+ experimental_native?: ?boolean,
+
/**
* Distance between the top of the user screen and the React Native view,
* may be non-zero in some use cases.
@@ -63,6 +76,46 @@ type KeyboardAvoidingViewState = {
bottom: number,
};
+function getIsNativeSupported(): boolean {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ if (!ReactNativeFeatureFlags.useSharedAnimatedBackend()) {
+ if (__DEV__) {
+ warnOnce(
+ 'KeyboardAvoidingView-experimental_native-flag',
+ 'KeyboardAvoidingView: `experimental_native` has no effect because ' +
+ 'the `useSharedAnimatedBackend` feature flag is disabled.',
+ );
+ }
+ return false;
+ }
+ if (
+ Platform.OS === 'android' &&
+ NativeDeviceInfo.getConstants().isEdgeToEdge !== true
+ ) {
+ if (__DEV__) {
+ warnOnce(
+ 'KeyboardAvoidingView-experimental_native-edge-to-edge',
+ 'KeyboardAvoidingView: `experimental_native` has no effect because ' +
+ 'the app is not edge-to-edge.',
+ );
+ }
+ return false;
+ }
+ return true;
+}
+
+let isNativeSupported: ?boolean = null;
+
+function shouldUseNative(props: KeyboardAvoidingViewProps): boolean {
+ if (props.experimental_native !== true || props.behavior == null) {
+ return false;
+ }
+ if (isNativeSupported == null) {
+ isNativeSupported = getIsNativeSupported();
+ }
+ return isNativeSupported;
+}
+
/**
* Automatically adjusts its height, position, or bottom padding based on the
* keyboard height to remain visible while the virtual keyboard is displayed.
@@ -161,6 +214,10 @@ class KeyboardAvoidingView extends React.Component<
};
_updateBottomIfNecessary = async () => {
+ if (shouldUseNative(this.props)) {
+ return;
+ }
+
if (this._keyboardEvent == null) {
this._setBottom(0);
return;
@@ -196,6 +253,7 @@ class KeyboardAvoidingView extends React.Component<
if (enabled && this._bottom !== prevState.bottom) {
this.setState({bottom: this._bottom});
}
+ this._updateSubscriptions();
}
componentDidMount(): void {
@@ -203,6 +261,29 @@ class KeyboardAvoidingView extends React.Component<
this._keyboardEvent = null;
this._setBottom(0);
}
+ this._updateSubscriptions();
+ }
+
+ // Only the JavaScript implementation needs keyboard events.
+ _updateSubscriptions(): void {
+ const shouldSubscribe = !shouldUseNative(this.props);
+
+ if (shouldSubscribe === this._subscriptions.length > 0) {
+ return;
+ }
+
+ if (!shouldSubscribe) {
+ this._subscriptions.forEach(subscription => {
+ subscription.remove();
+ });
+ this._subscriptions = [];
+ // The last event goes stale while unsubscribed.
+ this._keyboardEvent = null;
+ if (this._bottom !== 0) {
+ this._setBottom(0);
+ }
+ return;
+ }
if (Platform.OS === 'ios') {
this._subscriptions = [
@@ -235,12 +316,47 @@ class KeyboardAvoidingView extends React.Component<
children,
contentContainerStyle,
enabled = true,
- // eslint-disable-next-line no-unused-vars
+ experimental_native,
keyboardVerticalOffset = 0,
style,
onLayout,
...props
} = this.props;
+
+ if (shouldUseNative(this.props)) {
+ if (behavior === 'position') {
+ return (
+
+
+ {children}
+
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+ }
+
const bottomHeight = enabled === true ? this.state.bottom : 0;
switch (behavior) {
case 'height':
diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift
index ac1611c59c07..b052da345682 100644
--- a/packages/react-native/Package.swift
+++ b/packages/react-native/Package.swift
@@ -490,6 +490,7 @@ let reactFabric = RNTarget(
"consistency",
"uimanager/consistency/tests",
"components/inputaccessory",
+ "components/keyboardavoidingview",
"components/modal",
"components/rncore",
"components/safeareaview",
@@ -513,6 +514,12 @@ let reactFabricInputAccessory = RNTarget(
dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging]
)
+let reactFabricKeyboardAvoidingView = RNTarget(
+ name: .reactFabricKeyboardAvoidingView,
+ path: "ReactCommon/react/renderer/components/keyboardavoidingview",
+ dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging]
+)
+
let reactFabricModal = RNTarget(
name: .reactFabricModal,
path: "ReactCommon/react/renderer/components/modal",
@@ -588,7 +595,7 @@ let reactRCTFabric = RNTarget(
name: .reactRCTFabric,
path: "React/Fabric",
searchPaths: [virtualViewPath],
- dependencies: [.reactNativeDependencies, .reactCore, .reactRCTImage, .yoga, .reactRCTText, .jsi, .reactFabricInputAccessory, .reactFabricModal, .reactFabricSafeAreaView, .reactFabricSwitch, .reactFabricText, .reactFabricTextInput, .reactFabricUnimplementedView, .reactFabricTextLayoutManager, .reactGraphics, .reactImageManager, .reactDebug, .reactUtils, .reactPerformanceTimeline, .reactRendererDebug, .reactRendererConsistency, .reactRuntimeScheduler, .reactRCTAnimation, .reactJsInspector, .reactJsInspectorNetwork, .reactJsInspectorTracing, .reactFabric, .reactFabricImage, .rctSwiftUIWrapper]
+ dependencies: [.reactNativeDependencies, .reactCore, .reactRCTImage, .yoga, .reactRCTText, .jsi, .reactFabricInputAccessory, .reactFabricKeyboardAvoidingView, .reactFabricModal, .reactFabricSafeAreaView, .reactFabricSwitch, .reactFabricText, .reactFabricTextInput, .reactFabricUnimplementedView, .reactFabricTextLayoutManager, .reactGraphics, .reactImageManager, .reactDebug, .reactUtils, .reactPerformanceTimeline, .reactRendererDebug, .reactRendererConsistency, .reactRuntimeScheduler, .reactRCTAnimation, .reactJsInspector, .reactJsInspectorNetwork, .reactJsInspectorTracing, .reactFabric, .reactFabricImage, .rctSwiftUIWrapper]
)
/// React-ImageManagerApple.podspec
@@ -710,6 +717,7 @@ let targets = [
reactRCTFabric,
reactFabricImage,
reactFabricInputAccessory,
+ reactFabricKeyboardAvoidingView,
reactFabricModal,
reactFabricSafeAreaView,
reactFabricSwitch,
@@ -897,6 +905,7 @@ extension String {
static let reactFabricImage = "React-FabricImage"
static let reactFabricInputAccessory = "React-FabricInputAccessory"
+ static let reactFabricKeyboardAvoidingView = "React-FabricKeyboardAvoidingView"
static let reactFabricModal = "React-FabricModal"
static let reactFabricSafeAreaView = "React-FabricSafeAreaView"
static let reactFabricSwitch = "React-FabricSwitch"
diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/KeyboardAvoidingView/RCTKeyboardAvoidingViewComponentView.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/KeyboardAvoidingView/RCTKeyboardAvoidingViewComponentView.h
new file mode 100644
index 000000000000..c537302bde37
--- /dev/null
+++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/KeyboardAvoidingView/RCTKeyboardAvoidingViewComponentView.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#import
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * UIView class for component.
+ *
+ * Follows the keyboard with `UIKeyboardLayoutGuide` and sends the keyboard
+ * inset to the shared animation backend on each frame.
+ */
+@interface RCTKeyboardAvoidingViewComponentView : RCTViewComponentView
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/KeyboardAvoidingView/RCTKeyboardAvoidingViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/KeyboardAvoidingView/RCTKeyboardAvoidingViewComponentView.mm
new file mode 100644
index 000000000000..db4f5b7bd623
--- /dev/null
+++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/KeyboardAvoidingView/RCTKeyboardAvoidingViewComponentView.mm
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#import "RCTKeyboardAvoidingViewComponentView.h"
+
+#import
+#import
+
+#import "RCTFabricComponentsPlugins.h"
+
+using namespace facebook::react;
+
+/*
+ * Hidden view between the keyboard layout guide and the bottom of the guide
+ * owner. Kept out of the guide owner, whose subviews Fabric can manage.
+ */
+@interface RCTKeyboardTrackingView : UIView
+
+@property (nonatomic, copy, nullable) void (^onLayout)(void);
+
+@end
+
+@implementation RCTKeyboardTrackingView
+
+- (void)layoutSubviews
+{
+ [super layoutSubviews];
+ if (self.onLayout != nil) {
+ self.onLayout();
+ }
+}
+
+@end
+
+@interface RCTKeyboardAvoidingViewDisplayLinkTarget : NSObject
+
+@property (nonatomic, weak) RCTKeyboardAvoidingViewComponentView *view;
+
+@end
+
+@interface RCTKeyboardAvoidingViewComponentView ()
+
+- (void)_displayLinkDidFire;
+
+@end
+
+@implementation RCTKeyboardAvoidingViewDisplayLinkTarget
+
+- (void)displayLinkDidFire:(CADisplayLink *)displayLink
+{
+ [self.view _displayLinkDidFire];
+}
+
+@end
+
+@implementation RCTKeyboardAvoidingViewComponentView {
+ KeyboardAvoidingViewShadowNode::ConcreteState::Shared _state;
+ RCTKeyboardTrackingView *_trackingView;
+ // Holds the tracking view. A direct subview of the window, so no other code
+ // manages its subviews.
+ UIView *_trackingContainerView;
+ // Owns the keyboard layout guide. The guide of the window itself is not
+ // updated, and the guide of this view moves when the view resizes.
+ __weak UIView *_guideOwnerView;
+ CADisplayLink *_displayLink;
+ BOOL _isUpdateScheduled;
+
+ // Height of the tracking view when the keyboard is hidden.
+ CGFloat _keyboardBaseHeight;
+ // Smallest height of the tracking view since the guide owner got its size.
+ CGFloat _minTrackingHeight;
+ CGSize _guideOwnerSize;
+ // The tracking view has no size until its first layout.
+ BOOL _isTrackingViewLaidOut;
+ CGFloat _targetKeyboardHeight;
+ // Keyboard height and inset when the keyboard started to move to its target.
+ CGFloat _startKeyboardHeight;
+ CGFloat _startInset;
+ CGFloat _restingHeight;
+ CGFloat _appliedInset;
+ CGFloat _appliedRestingHeight;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+ if (self = [super initWithFrame:frame]) {
+ _props = KeyboardAvoidingViewShadowNode::defaultSharedProps();
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(_keyboardWillChangeFrame:)
+ name:UIKeyboardWillChangeFrameNotification
+ object:nil];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [_displayLink invalidate];
+ [_trackingContainerView removeFromSuperview];
+}
+
+- (void)didMoveToWindow
+{
+ [super didMoveToWindow];
+
+ [self _stopDisplayLink];
+ [_trackingContainerView removeFromSuperview];
+ _trackingContainerView = nil;
+ _trackingView = nil;
+
+ UIWindow *window = self.window;
+ if (window == nil) {
+ return;
+ }
+
+ UIView *guideOwnerView = window.rootViewController.view;
+ for (UIResponder *responder = self.nextResponder; responder != nil; responder = responder.nextResponder) {
+ if ([responder isKindOfClass:[UIViewController class]]) {
+ guideOwnerView = ((UIViewController *)responder).view;
+ break;
+ }
+ }
+ if (guideOwnerView == nil) {
+ return;
+ }
+ _guideOwnerView = guideOwnerView;
+ _guideOwnerSize = CGSizeZero;
+ _isTrackingViewLaidOut = NO;
+
+ _trackingContainerView = [[UIView alloc] initWithFrame:window.bounds];
+ _trackingContainerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+ _trackingContainerView.hidden = YES;
+ _trackingContainerView.userInteractionEnabled = NO;
+ [window addSubview:_trackingContainerView];
+
+ _trackingView = [RCTKeyboardTrackingView new];
+ _trackingView.translatesAutoresizingMaskIntoConstraints = NO;
+ __weak __typeof(self) weakSelf = self;
+ _trackingView.onLayout = ^{
+ __typeof(self) strongSelf = weakSelf;
+ if (strongSelf == nil) {
+ return;
+ }
+ strongSelf->_isTrackingViewLaidOut = YES;
+ [strongSelf _startDisplayLink];
+ };
+ [_trackingContainerView addSubview:_trackingView];
+
+ [NSLayoutConstraint activateConstraints:@[
+ [_trackingView.leadingAnchor constraintEqualToAnchor:_trackingContainerView.leadingAnchor],
+ [_trackingView.trailingAnchor constraintEqualToAnchor:_trackingContainerView.trailingAnchor],
+ [_trackingView.topAnchor constraintEqualToAnchor:guideOwnerView.keyboardLayoutGuide.topAnchor],
+ [_trackingView.bottomAnchor constraintEqualToAnchor:guideOwnerView.bottomAnchor],
+ ]];
+
+ [self _scheduleUpdate];
+}
+
+- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
+ oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
+{
+ [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
+ // A commit can not start inside a mount, so the update runs on the next loop.
+ [self _scheduleUpdate];
+}
+
+#pragma mark - Keyboard tracking
+
+/*
+ * UIKit animates the guide only while it lays out the guide owner. The tracking
+ * view is outside of it, so it is laid out here with the keyboard animation.
+ * The guide already has its final position when this notification is posted.
+ */
+- (void)_keyboardWillChangeFrame:(NSNotification *)notification
+{
+ UIView *trackingContainerView = _trackingContainerView;
+ if (trackingContainerView == nil) {
+ return;
+ }
+
+ NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
+ UIViewAnimationCurve curve =
+ (UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
+ [UIView animateWithDuration:duration
+ delay:0
+ options:(UIViewAnimationOptions)curve << 16 | UIViewAnimationOptionBeginFromCurrentState
+ animations:^{
+ [trackingContainerView layoutIfNeeded];
+ }
+ completion:nil];
+}
+
+- (CGFloat)_currentKeyboardBaseHeight
+{
+ // The tracking view is at its smallest height when the keyboard is hidden.
+ // Capped at the bottom safe area, in case the keyboard was never hidden.
+ UIView *guideOwnerView = _guideOwnerView;
+ if (guideOwnerView == nil) {
+ return 0;
+ }
+ if (!CGSizeEqualToSize(guideOwnerView.bounds.size, _guideOwnerSize)) {
+ _guideOwnerSize = guideOwnerView.bounds.size;
+ _minTrackingHeight = CGFLOAT_MAX;
+ }
+ _minTrackingHeight = MIN(_minTrackingHeight, _trackingView.layer.bounds.size.height);
+ CGFloat bottomSafeArea =
+ MAX(CGRectGetMaxY(guideOwnerView.bounds) - CGRectGetMaxY(guideOwnerView.safeAreaLayoutGuide.layoutFrame), 0);
+ return MIN(bottomSafeArea, _minTrackingHeight);
+}
+
+- (void)_startDisplayLink
+{
+ if (_displayLink != nil) {
+ return;
+ }
+ auto target = [RCTKeyboardAvoidingViewDisplayLinkTarget new];
+ target.view = self;
+ _displayLink = [CADisplayLink displayLinkWithTarget:target selector:@selector(displayLinkDidFire:)];
+ [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
+}
+
+- (void)_stopDisplayLink
+{
+ [_displayLink invalidate];
+ _displayLink = nil;
+}
+
+- (void)_displayLinkDidFire
+{
+ if (_trackingView == nil || !_isTrackingViewLaidOut) {
+ [self _stopDisplayLink];
+ return;
+ }
+
+ CALayer *layer = _trackingView.layer;
+ CALayer *presentationLayer = layer.presentationLayer ?: layer;
+ BOOL isAnimating = layer.animationKeys.count > 0;
+
+ _keyboardBaseHeight = [self _currentKeyboardBaseHeight];
+ CGFloat keyboardHeight = MAX(presentationLayer.bounds.size.height - _keyboardBaseHeight, 0);
+ CGFloat targetHeight = MAX(layer.bounds.size.height - _keyboardBaseHeight, 0);
+
+ [self _keyboardFrameDidChange:keyboardHeight targetHeight:targetHeight];
+
+ if (!isAnimating) {
+ [self _stopDisplayLink];
+ }
+}
+
+- (void)_scheduleUpdate
+{
+ if (_isUpdateScheduled) {
+ return;
+ }
+ _isUpdateScheduled = YES;
+ __weak __typeof(self) weakSelf = self;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ __typeof(self) strongSelf = weakSelf;
+ if (strongSelf == nil) {
+ return;
+ }
+ strongSelf->_isUpdateScheduled = NO;
+ // A running display link sends the next update itself.
+ if (strongSelf->_displayLink == nil) {
+ [strongSelf _startDisplayLink];
+ }
+ });
+}
+
+/*
+ * Overlap between the view at rest and a keyboard of the given height,
+ * measured like the JavaScript implementation.
+ */
+- (CGFloat)_relativeKeyboardHeight:(CGFloat)keyboardHeight
+{
+ if (keyboardHeight <= 0 || self.window == nil) {
+ return 0;
+ }
+
+ const auto &props = static_cast(*_props);
+
+ CGFloat frameY;
+ CGFloat frameHeight;
+ if (props.behavior == KeyboardAvoidingViewBehavior::Position) {
+ // `position` moves a content container: measure the view that holds it.
+ UIView *container = self.superview;
+ if (container == nil) {
+ return 0;
+ }
+ frameHeight = container.bounds.size.height;
+ frameY = container.center.y - frameHeight * container.layer.anchorPoint.y;
+ } else {
+ frameY = _layoutMetrics.frame.origin.y;
+ frameHeight =
+ props.behavior == KeyboardAvoidingViewBehavior::Height ? _restingHeight : _layoutMetrics.frame.size.height;
+ }
+
+ CGFloat keyboardY = self.window.bounds.size.height - keyboardHeight - props.keyboardVerticalOffset;
+ return MAX(frameY + frameHeight - keyboardY, 0);
+}
+
+- (void)_keyboardFrameDidChange:(CGFloat)keyboardHeight targetHeight:(CGFloat)targetHeight
+{
+ if (targetHeight != _targetKeyboardHeight) {
+ _targetKeyboardHeight = targetHeight;
+ _startKeyboardHeight = keyboardHeight;
+ _startInset = _appliedInset;
+ }
+
+ if (!_state) {
+ return;
+ }
+
+ const auto &props = static_cast(*_props);
+ if (props.behavior == KeyboardAvoidingViewBehavior::None && _appliedInset == 0) {
+ return;
+ }
+
+ if (_appliedInset == 0) {
+ _restingHeight = _layoutMetrics.frame.size.height;
+ }
+
+ // The tracking view stops at the bottom of the guide owner, which can be
+ // above the bottom of the window.
+ CGFloat ownerBottomInset = 0;
+ if (UIView *guideOwnerView = _guideOwnerView) {
+ ownerBottomInset = MAX(
+ self.window.bounds.size.height - CGRectGetMaxY([guideOwnerView convertRect:guideOwnerView.bounds toView:nil]),
+ 0);
+ }
+ CGFloat targetInset =
+ targetHeight > 0 ? [self _relativeKeyboardHeight:targetHeight + _keyboardBaseHeight + ownerBottomInset] : 0;
+ CGFloat distance = targetHeight - _startKeyboardHeight;
+ CGFloat progress = distance != 0 ? MIN(MAX((keyboardHeight - _startKeyboardHeight) / distance, 0), 1) : 1;
+ BOOL isActive = props.enabled && props.behavior != KeyboardAvoidingViewBehavior::None;
+ CGFloat inset = isActive ? _startInset + (targetInset - _startInset) * progress : 0;
+
+ if (inset == _appliedInset && (inset == 0 || _restingHeight == _appliedRestingHeight)) {
+ return;
+ }
+
+ _appliedInset = inset;
+ _appliedRestingHeight = _restingHeight;
+ _state->getData().update(inset, _restingHeight);
+}
+
+#pragma mark - RCTComponentViewProtocol
+
++ (ComponentDescriptorProvider)componentDescriptorProvider
+{
+ return concreteComponentDescriptorProvider();
+}
+
+- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
+{
+ const auto &oldKeyboardAvoidingViewProps = static_cast(*_props);
+ const auto &newKeyboardAvoidingViewProps = static_cast(*props);
+
+ BOOL needsUpdate = oldKeyboardAvoidingViewProps.behavior != newKeyboardAvoidingViewProps.behavior ||
+ oldKeyboardAvoidingViewProps.enabled != newKeyboardAvoidingViewProps.enabled ||
+ oldKeyboardAvoidingViewProps.keyboardVerticalOffset != newKeyboardAvoidingViewProps.keyboardVerticalOffset;
+
+ [super updateProps:props oldProps:oldProps];
+
+ if (needsUpdate) {
+ [self _scheduleUpdate];
+ }
+}
+
+- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState
+{
+ _state = std::static_pointer_cast(state);
+ [self _scheduleUpdate];
+}
+
+- (void)prepareForRecycle
+{
+ [super prepareForRecycle];
+ [self _stopDisplayLink];
+ _state.reset();
+ _targetKeyboardHeight = 0;
+ _startKeyboardHeight = 0;
+ _startInset = 0;
+ _restingHeight = 0;
+ _appliedInset = 0;
+ _appliedRestingHeight = 0;
+}
+
+@end
+
+Class RCTKeyboardAvoidingViewCls(void)
+{
+ return RCTKeyboardAvoidingViewComponentView.class;
+}
diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.h b/packages/react-native/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.h
index 6b8a6ff6d6dc..4d5beec3b0a1 100644
--- a/packages/react-native/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.h
+++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.h
@@ -32,6 +32,7 @@ Class RCTFabricComponentsProvider(const char *name);
Class RCTActivityIndicatorViewCls(void) __attribute__((used));
Class RCTDebuggingOverlayCls(void) __attribute__((used));
Class RCTInputAccessoryCls(void) __attribute__((used));
+Class RCTKeyboardAvoidingViewCls(void) __attribute__((used));
Class RCTParagraphCls(void) __attribute__((used));
Class RCTPullToRefreshViewCls(void) __attribute__((used));
Class RCTSafeAreaViewCls(void) __attribute__((used));
diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.mm
index 442c2e6e3962..b6f46c1e4250 100644
--- a/packages/react-native/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.mm
+++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/RCTFabricComponentsPlugins.mm
@@ -21,6 +21,7 @@
{"ActivityIndicatorView", RCTActivityIndicatorViewCls},
{"DebuggingOverlay", RCTDebuggingOverlayCls},
{"InputAccessoryView", RCTInputAccessoryCls},
+ {"KeyboardAvoidingView", RCTKeyboardAvoidingViewCls},
{"Paragraph", RCTParagraphCls},
{"PullToRefreshView", RCTPullToRefreshViewCls},
{"SafeAreaView", RCTSafeAreaViewCls},
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt
index 14b053b4196f..6311b9a5506b 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt
@@ -48,6 +48,7 @@ import com.facebook.react.modules.websocket.WebSocketModule
import com.facebook.react.uimanager.ViewManager
import com.facebook.react.views.drawer.ReactDrawerLayoutManager
import com.facebook.react.views.image.ReactImageManager
+import com.facebook.react.views.keyboardavoidingview.ReactKeyboardAvoidingViewManager
import com.facebook.react.views.modal.ReactModalHostManager
import com.facebook.react.views.progressbar.ReactProgressBarViewManager
import com.facebook.react.views.safeareaview.ReactSafeAreaViewManager
@@ -144,6 +145,7 @@ constructor(private val config: MainPackageConfig? = null) :
ReactDrawerLayoutManager(),
ReactHorizontalScrollViewManager(),
ReactHorizontalScrollContainerViewManager(),
+ ReactKeyboardAvoidingViewManager(),
ReactProgressBarViewManager(),
if (ReactNativeFeatureFlags.useNestedScrollViewAndroid()) ReactNestedScrollViewManager()
else ReactScrollViewManager(),
@@ -176,6 +178,8 @@ constructor(private val config: MainPackageConfig? = null) :
ModuleSpec.viewManagerSpec { ReactHorizontalScrollViewManager() },
ReactHorizontalScrollContainerViewManager.REACT_CLASS to
ModuleSpec.viewManagerSpec { ReactHorizontalScrollContainerViewManager() },
+ ReactKeyboardAvoidingViewManager.REACT_CLASS to
+ ModuleSpec.viewManagerSpec { ReactKeyboardAvoidingViewManager() },
ReactProgressBarViewManager.REACT_CLASS to
ModuleSpec.viewManagerSpec { ReactProgressBarViewManager() },
ReactSafeAreaViewManager.REACT_CLASS to
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/keyboardavoidingview/KeyboardAvoidingViewBinding.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/keyboardavoidingview/KeyboardAvoidingViewBinding.kt
new file mode 100644
index 000000000000..130a073af55a
--- /dev/null
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/keyboardavoidingview/KeyboardAvoidingViewBinding.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package com.facebook.react.views.keyboardavoidingview
+
+import com.facebook.proguard.annotations.DoNotStripAny
+import com.facebook.react.fabric.FabricSoLoader
+import com.facebook.react.fabric.StateWrapperImpl
+
+/**
+ * Sends keyboard avoidance values to the shared animation backend, through the C++ state of a
+ * `KeyboardAvoidingView` shadow node. Must be called on the UI thread.
+ */
+@DoNotStripAny
+internal object KeyboardAvoidingViewBinding {
+ init {
+ FabricSoLoader.staticInit()
+ }
+
+ @JvmStatic
+ external fun update(
+ stateWrapper: StateWrapperImpl,
+ keyboardInset: Float,
+ keyboardRestingHeight: Float,
+ )
+}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/keyboardavoidingview/ReactKeyboardAvoidingView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/keyboardavoidingview/ReactKeyboardAvoidingView.kt
new file mode 100644
index 000000000000..1409056caac9
--- /dev/null
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/keyboardavoidingview/ReactKeyboardAvoidingView.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package com.facebook.react.views.keyboardavoidingview
+
+import android.content.Context
+import android.view.View
+import androidx.annotation.UiThread
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsAnimationCompat
+import androidx.core.view.WindowInsetsCompat
+import com.facebook.react.fabric.StateWrapperImpl
+import com.facebook.react.uimanager.PixelUtil.pxToDp
+import com.facebook.react.uimanager.StateWrapper
+import com.facebook.react.views.view.ReactViewGroup
+
+/**
+ * Follows the keyboard with [WindowInsetsAnimationCompat] and sends the keyboard inset to the
+ * shared animation backend on each frame. The shadow node applies the inset to its layout.
+ */
+internal class ReactKeyboardAvoidingView(context: Context) : ReactViewGroup(context) {
+
+ internal enum class Behavior {
+ NONE,
+ HEIGHT,
+ PADDING,
+ POSITION,
+ }
+
+ internal var stateWrapper: StateWrapper? = null
+ set(value) {
+ field = value
+ scheduleUpdate()
+ }
+
+ internal var behavior: Behavior = Behavior.NONE
+ set(value) {
+ if (field != value) {
+ field = value
+ scheduleUpdate()
+ }
+ }
+
+ internal var isAvoidanceEnabled: Boolean = true
+ set(value) {
+ if (field != value) {
+ field = value
+ scheduleUpdate()
+ }
+ }
+
+ /** In dp. */
+ internal var keyboardVerticalOffset: Float = 0f
+ set(value) {
+ if (field != value) {
+ field = value
+ scheduleUpdate()
+ }
+ }
+
+ private var isAnimating = false
+ private var isUpdateScheduled = false
+
+ // All values below are in dp.
+ private var lastKeyboardHeight = 0f
+ private var targetKeyboardHeight = 0f
+ private var lastTargetHeight = 0f
+ // Keyboard height and inset when the keyboard started to move to its target.
+ private var startKeyboardHeight = 0f
+ private var startInset = 0f
+ private var restingHeight = 0f
+ private var appliedInset = 0f
+ private var appliedRestingHeight = 0f
+
+ private val updateRunnable = Runnable {
+ isUpdateScheduled = false
+ if (!isAnimating) {
+ val keyboardHeight = currentKeyboardHeight()
+ onKeyboardFrame(keyboardHeight, keyboardHeight)
+ }
+ }
+
+ private val insetsAnimationCallback =
+ object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
+ override fun onPrepare(animation: WindowInsetsAnimationCompat) {
+ if (animation.isImeAnimation()) {
+ isAnimating = true
+ }
+ }
+
+ override fun onStart(
+ animation: WindowInsetsAnimationCompat,
+ bounds: WindowInsetsAnimationCompat.BoundsCompat,
+ ): WindowInsetsAnimationCompat.BoundsCompat {
+ if (animation.isImeAnimation()) {
+ // Insets are already in their end state when the animation starts.
+ targetKeyboardHeight = currentKeyboardHeight()
+ onKeyboardFrame(lastKeyboardHeight, targetKeyboardHeight)
+ }
+ return bounds
+ }
+
+ override fun onProgress(
+ insets: WindowInsetsCompat,
+ runningAnimations: MutableList,
+ ): WindowInsetsCompat {
+ if (runningAnimations.any { it.isImeAnimation() }) {
+ onKeyboardFrame(
+ insets.getInsets(WindowInsetsCompat.Type.ime()).bottom.toFloat().pxToDp(),
+ targetKeyboardHeight,
+ )
+ }
+ return insets
+ }
+
+ override fun onEnd(animation: WindowInsetsAnimationCompat) {
+ if (animation.isImeAnimation()) {
+ isAnimating = false
+ val keyboardHeight = currentKeyboardHeight()
+ onKeyboardFrame(keyboardHeight, keyboardHeight)
+ }
+ }
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ ViewCompat.setWindowInsetsAnimationCallback(this, insetsAnimationCallback)
+ ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
+ scheduleUpdate()
+ insets
+ }
+ scheduleUpdate()
+ }
+
+ override fun onDetachedFromWindow() {
+ ViewCompat.setWindowInsetsAnimationCallback(this, null)
+ ViewCompat.setOnApplyWindowInsetsListener(this, null)
+ removeCallbacks(updateRunnable)
+ isUpdateScheduled = false
+ isAnimating = false
+ super.onDetachedFromWindow()
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ // A commit can not start inside a mount, so the update runs on the next loop.
+ if (changed) {
+ scheduleUpdate()
+ }
+ }
+
+ private fun scheduleUpdate() {
+ if (!isUpdateScheduled && isAttachedToWindow) {
+ isUpdateScheduled = true
+ post(updateRunnable)
+ }
+ }
+
+ private fun WindowInsetsAnimationCompat.isImeAnimation(): Boolean =
+ (typeMask and WindowInsetsCompat.Type.ime()) != 0
+
+ private fun currentKeyboardHeight(): Float {
+ val insets = ViewCompat.getRootWindowInsets(this) ?: return 0f
+ return insets.getInsets(WindowInsetsCompat.Type.ime()).bottom.toFloat().pxToDp()
+ }
+
+ /** Overlap between the view (at rest) and a keyboard of the given height, in dp. */
+ private fun relativeKeyboardHeight(keyboardHeight: Float): Float {
+ if (keyboardHeight <= 0f) {
+ return 0f
+ }
+ // `position` moves a content container: measure the view that holds it.
+ val frameView: View = if (behavior == Behavior.POSITION) parent as? View ?: return 0f else this
+ val frameY = frameView.top.toFloat().pxToDp()
+ val frameHeight =
+ if (behavior == Behavior.HEIGHT) restingHeight else frameView.height.toFloat().pxToDp()
+ val keyboardY = rootView.height.toFloat().pxToDp() - keyboardHeight - keyboardVerticalOffset
+ return maxOf(frameY + frameHeight - keyboardY, 0f)
+ }
+
+ @UiThread
+ private fun onKeyboardFrame(keyboardHeight: Float, targetHeight: Float) {
+ lastKeyboardHeight = keyboardHeight
+ if (targetHeight != lastTargetHeight) {
+ lastTargetHeight = targetHeight
+ startKeyboardHeight = keyboardHeight
+ startInset = appliedInset
+ }
+
+ // The mounting manager destroys the state when it deletes the view.
+ val stateWrapper = (stateWrapper as? StateWrapperImpl)?.takeIf { it.isValid } ?: return
+ if (behavior == Behavior.NONE && appliedInset == 0f) {
+ return
+ }
+
+ if (appliedInset == 0f) {
+ restingHeight = height.toFloat().pxToDp()
+ }
+
+ val targetInset = relativeKeyboardHeight(targetHeight)
+ val distance = targetHeight - startKeyboardHeight
+ val progress =
+ if (distance != 0f) ((keyboardHeight - startKeyboardHeight) / distance).coerceIn(0f, 1f)
+ else 1f
+ val isActive = isAvoidanceEnabled && behavior != Behavior.NONE
+ val inset = if (isActive) startInset + (targetInset - startInset) * progress else 0f
+
+ if (inset == appliedInset && (inset == 0f || restingHeight == appliedRestingHeight)) {
+ return
+ }
+
+ appliedInset = inset
+ appliedRestingHeight = restingHeight
+ KeyboardAvoidingViewBinding.update(stateWrapper, inset, restingHeight)
+ }
+}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/keyboardavoidingview/ReactKeyboardAvoidingViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/keyboardavoidingview/ReactKeyboardAvoidingViewManager.kt
new file mode 100644
index 000000000000..7a2f77bbbc4b
--- /dev/null
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/keyboardavoidingview/ReactKeyboardAvoidingViewManager.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package com.facebook.react.views.keyboardavoidingview
+
+import com.facebook.react.module.annotations.ReactModule
+import com.facebook.react.uimanager.ReactStylesDiffMap
+import com.facebook.react.uimanager.StateWrapper
+import com.facebook.react.uimanager.ThemedReactContext
+import com.facebook.react.uimanager.annotations.ReactProp
+import com.facebook.react.views.view.ReactViewGroup
+import com.facebook.react.views.view.ReactViewManager
+
+/** View manager for [ReactKeyboardAvoidingView] components. */
+@ReactModule(name = ReactKeyboardAvoidingViewManager.REACT_CLASS)
+internal class ReactKeyboardAvoidingViewManager : ReactViewManager() {
+
+ override fun getName(): String = REACT_CLASS
+
+ override fun createViewInstance(context: ThemedReactContext): ReactViewGroup =
+ ReactKeyboardAvoidingView(context)
+
+ override fun updateState(
+ view: ReactViewGroup,
+ props: ReactStylesDiffMap,
+ stateWrapper: StateWrapper,
+ ): Any? {
+ (view as ReactKeyboardAvoidingView).stateWrapper = stateWrapper
+ return null
+ }
+
+ @ReactProp(name = "behavior")
+ fun setBehavior(view: ReactViewGroup, behavior: String?) {
+ (view as ReactKeyboardAvoidingView).behavior =
+ when (behavior) {
+ "height" -> ReactKeyboardAvoidingView.Behavior.HEIGHT
+ "padding" -> ReactKeyboardAvoidingView.Behavior.PADDING
+ "position" -> ReactKeyboardAvoidingView.Behavior.POSITION
+ else -> ReactKeyboardAvoidingView.Behavior.NONE
+ }
+ }
+
+ @ReactProp(name = "enabled", defaultBoolean = true)
+ fun setEnabled(view: ReactViewGroup, enabled: Boolean) {
+ (view as ReactKeyboardAvoidingView).isAvoidanceEnabled = enabled
+ }
+
+ @ReactProp(name = "keyboardVerticalOffset", defaultFloat = 0f)
+ fun setKeyboardVerticalOffset(view: ReactViewGroup, offset: Float) {
+ (view as ReactKeyboardAvoidingView).keyboardVerticalOffset = offset
+ }
+
+ internal companion object {
+ const val REACT_CLASS: String = "KeyboardAvoidingView"
+ }
+}
diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt
index 4c5be342c0bd..ac64a9bc22b1 100644
--- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt
+++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt
@@ -119,6 +119,7 @@ add_react_common_subdir(react/renderer/components/legacyviewmanagerinterop)
add_react_common_subdir(react/renderer/componentregistry/native)
add_react_common_subdir(react/renderer/components/text)
add_react_common_subdir(react/renderer/components/unimplementedview)
+add_react_common_subdir(react/renderer/components/keyboardavoidingview)
add_react_common_subdir(react/renderer/components/modal)
add_react_common_subdir(react/renderer/components/scrollview)
add_react_common_subdir(react/renderer/components/safeareaview)
@@ -253,6 +254,7 @@ add_library(reactnative
$
$
$
+ $
$
$
$
@@ -357,6 +359,7 @@ target_include_directories(reactnative
$
$
$
+ $
$
$
$
diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt
index 557b7a14b23a..aa037171bbe9 100644
--- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt
+++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt
@@ -48,6 +48,7 @@ target_link_libraries(
react_renderer_textlayoutmanager
react_renderer_uimanager
react_renderer_uimanager_consistency
+ rrc_keyboardavoidingview
rrc_legacyviewmanagerinterop
react_utils
reactnativejni
diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/KeyboardAvoidingViewBinding.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/KeyboardAvoidingViewBinding.cpp
new file mode 100644
index 000000000000..cd2dfcac078e
--- /dev/null
+++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/KeyboardAvoidingViewBinding.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#include "KeyboardAvoidingViewBinding.h"
+
+#include
+
+namespace facebook::react {
+
+namespace {
+
+std::shared_ptr getState(
+ jni::alias_ref stateWrapper) {
+ if (!stateWrapper) {
+ return nullptr;
+ }
+ return std::dynamic_pointer_cast<
+ const KeyboardAvoidingViewShadowNode::ConcreteState>(
+ stateWrapper->cthis()->getState());
+}
+
+} // namespace
+
+void KeyboardAvoidingViewBinding::registerNatives() {
+ javaClassStatic()->registerNatives({
+ makeNativeMethod("update", KeyboardAvoidingViewBinding::update),
+ });
+}
+
+void KeyboardAvoidingViewBinding::update(
+ jni::alias_ref /*unused*/,
+ jni::alias_ref stateWrapper,
+ jfloat keyboardInset,
+ jfloat keyboardRestingHeight) {
+ auto state = getState(stateWrapper);
+ if (state == nullptr) {
+ return;
+ }
+
+ state->getData().update(keyboardInset, keyboardRestingHeight);
+}
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/KeyboardAvoidingViewBinding.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/KeyboardAvoidingViewBinding.h
new file mode 100644
index 000000000000..2068ea288c8c
--- /dev/null
+++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/KeyboardAvoidingViewBinding.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#pragma once
+
+#include
+
+#include "StateWrapperImpl.h"
+
+namespace facebook::react {
+
+/*
+ * Gives `ReactKeyboardAvoidingView` access to the shared animation backend
+ * through the C++ state of its shadow node.
+ */
+class KeyboardAvoidingViewBinding : public jni::JavaClass {
+ public:
+ constexpr static const char *const kJavaDescriptor =
+ "Lcom/facebook/react/views/keyboardavoidingview/KeyboardAvoidingViewBinding;";
+
+ static void registerNatives();
+
+ private:
+ static void update(
+ jni::alias_ref /*unused*/,
+ jni::alias_ref stateWrapper,
+ jfloat keyboardInset,
+ jfloat keyboardRestingHeight);
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/OnLoad.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/OnLoad.cpp
index 2d411d4dc7af..47caaf3d93e6 100644
--- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/OnLoad.cpp
+++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/OnLoad.cpp
@@ -11,6 +11,7 @@
#include "EventBeatManager.h"
#include "EventEmitterWrapper.h"
#include "FabricUIManagerBinding.h"
+#include "KeyboardAvoidingViewBinding.h"
#include "StateWrapperImpl.h"
#include "SurfaceHandlerBinding.h"
@@ -19,6 +20,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*unused*/) {
facebook::react::EventBeatManager::registerNatives();
facebook::react::EventEmitterWrapper::registerNatives();
facebook::react::FabricUIManagerBinding::registerNatives();
+ facebook::react::KeyboardAvoidingViewBinding::registerNatives();
facebook::react::StateWrapperImpl::registerNatives();
facebook::react::ComponentFactory::registerNatives();
facebook::react::SurfaceHandlerBinding::registerNatives();
diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CMakeLists.txt
index 62b76deff23f..fd9418769a61 100644
--- a/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CMakeLists.txt
+++ b/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CMakeLists.txt
@@ -33,6 +33,7 @@ target_link_libraries(react_newarchdefaults
react_renderer_componentregistry
react_renderer_textlayoutmanager
rrc_image
+ rrc_keyboardavoidingview
rrc_modal
rrc_progressbar
rrc_safeareaview
diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CoreComponentsRegistry.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CoreComponentsRegistry.cpp
index 5eacbff0b81d..18647c8cffb7 100644
--- a/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CoreComponentsRegistry.cpp
+++ b/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CoreComponentsRegistry.cpp
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -58,6 +59,9 @@ void addCoreComponents(
concreteComponentDescriptorProvider());
providerRegistry->add(
concreteComponentDescriptorProvider());
+ providerRegistry->add(
+ concreteComponentDescriptorProvider<
+ KeyboardAvoidingViewComponentDescriptor>());
providerRegistry->add(
concreteComponentDescriptorProvider());
providerRegistry->add(
diff --git a/packages/react-native/ReactCommon/React-FabricComponents.podspec b/packages/react-native/ReactCommon/React-FabricComponents.podspec
index 95d9c1c4548f..facd6079820a 100644
--- a/packages/react-native/ReactCommon/React-FabricComponents.podspec
+++ b/packages/react-native/ReactCommon/React-FabricComponents.podspec
@@ -101,6 +101,12 @@ Pod::Spec.new do |s|
sss.header_dir = "react/renderer/components/inputaccessory"
end
+ ss.subspec "keyboardavoidingview" do |sss|
+ sss.source_files = podspec_sources("react/renderer/components/keyboardavoidingview/**/*.{m,mm,cpp,h}", "react/renderer/components/keyboardavoidingview/**/*.h")
+ sss.exclude_files = "react/renderer/components/keyboardavoidingview/tests"
+ sss.header_dir = "react/renderer/components/keyboardavoidingview"
+ end
+
ss.subspec "modal" do |sss|
sss.source_files = podspec_sources("react/renderer/components/modal/*.{m,mm,cpp,h}", "react/renderer/components/modal/*.h")
sss.exclude_files = "react/renderer/components/modal/tests"
diff --git a/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/CMakeLists.txt
new file mode 100644
index 000000000000..057a00fa22b9
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/CMakeLists.txt
@@ -0,0 +1,41 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+#
+# This source code is licensed under the MIT license found in the
+# LICENSE file in the root directory of this source tree.
+
+cmake_minimum_required(VERSION 3.13)
+set(CMAKE_VERBOSE_MAKEFILE on)
+
+include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
+
+file(GLOB rrc_keyboardavoidingview_SRCS CONFIGURE_DEPENDS *.cpp)
+
+add_library(
+ rrc_keyboardavoidingview
+ STATIC
+ ${rrc_keyboardavoidingview_SRCS}
+)
+
+target_include_directories(rrc_keyboardavoidingview PUBLIC .)
+
+target_link_libraries(
+ rrc_keyboardavoidingview
+ glog
+ fbjni
+ folly_runtime
+ glog_init
+ react_codegen_rncore
+ react_cxxstableapi
+ react_debug
+ react_renderer_animationbackend
+ react_renderer_componentregistry
+ react_renderer_core
+ react_renderer_debug
+ react_renderer_graphics
+ react_renderer_uimanager
+ reactnativejni
+ rrc_view
+ yoga
+)
+
+target_compile_reactnative_options(rrc_keyboardavoidingview PRIVATE)
diff --git a/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewComponentDescriptor.h
new file mode 100644
index 000000000000..8086c27b2361
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewComponentDescriptor.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#pragma once
+
+#include
+
+#include
+#include
+
+namespace facebook::react {
+
+/*
+ * Descriptor for component.
+ */
+class KeyboardAvoidingViewComponentDescriptor final
+ : public ConcreteComponentDescriptor {
+ using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
+ void adopt(ShadowNode &shadowNode) const override
+ {
+ static_cast(shadowNode).applyKeyboardInset();
+
+ ConcreteComponentDescriptor::adopt(shadowNode);
+ }
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewShadowNode.cpp
new file mode 100644
index 000000000000..f9322b8e5d66
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewShadowNode.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#include "KeyboardAvoidingViewShadowNode.h"
+
+#include
+
+namespace facebook::react {
+
+// NOLINTNEXTLINE(modernize-avoid-c-arrays)
+const char KeyboardAvoidingViewComponentName[] = "KeyboardAvoidingView";
+
+KeyboardAvoidingViewState KeyboardAvoidingViewShadowNode::initialStateData(
+ const Props::Shared& /*props*/,
+ const ShadowNodeFamily::Shared& family,
+ const ComponentDescriptor& componentDescriptor) {
+ auto animationBackend =
+ componentDescriptor.getContextContainer()
+ ->find>(AnimationBackendKey)
+ .value_or(std::weak_ptr{});
+ return {family, animationBackend};
+}
+
+void KeyboardAvoidingViewShadowNode::applyKeyboardInset() const {
+ ensureUnsealed();
+
+ const auto& props = getConcreteProps();
+ if (!props.enabled || props.keyboardInset <= 0) {
+ return;
+ }
+
+ auto inset =
+ yoga::StyleLength::points(static_cast(props.keyboardInset));
+ auto style = yogaNode_.style();
+
+ switch (props.behavior) {
+ case KeyboardAvoidingViewBehavior::None:
+ return;
+ case KeyboardAvoidingViewBehavior::Height:
+ style.setDimension(
+ yoga::Dimension::Height,
+ yoga::StyleSizeLength::points(
+ std::max(
+ static_cast(
+ props.keyboardRestingHeight - props.keyboardInset),
+ 0.0f)));
+ style.setFlex(yoga::FloatOptional(0));
+ break;
+ case KeyboardAvoidingViewBehavior::Padding:
+ style.setPadding(yoga::Edge::Bottom, inset);
+ break;
+ case KeyboardAvoidingViewBehavior::Position:
+ style.setPosition(yoga::Edge::Bottom, inset);
+ break;
+ }
+
+ if (style != yogaNode_.style()) {
+ yogaNode_.setStyle(style);
+ yogaNode_.setDirty(true);
+ }
+}
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewShadowNode.h
new file mode 100644
index 000000000000..7713e5d4b1bf
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewShadowNode.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+
+namespace facebook::react {
+
+// NOLINTNEXTLINE(modernize-avoid-c-arrays)
+extern const char KeyboardAvoidingViewComponentName[];
+
+/*
+ * `ShadowNode` for component.
+ */
+class KeyboardAvoidingViewShadowNode final : public ConcreteViewShadowNode<
+ KeyboardAvoidingViewComponentName,
+ KeyboardAvoidingViewProps,
+ ViewEventEmitter,
+ KeyboardAvoidingViewState> {
+ public:
+ using ConcreteViewShadowNode::ConcreteViewShadowNode;
+
+ static KeyboardAvoidingViewState initialStateData(
+ const Props::Shared &props,
+ const ShadowNodeFamily::Shared &family,
+ const ComponentDescriptor &componentDescriptor);
+
+ /*
+ * Applies the `keyboardInset` prop to the Yoga style, on top of the style
+ * set by React. Called on each new node and on each clone.
+ */
+ void applyKeyboardInset() const;
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewState.cpp b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewState.cpp
new file mode 100644
index 000000000000..c18e4f77e34b
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewState.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#include "KeyboardAvoidingViewState.h"
+
+#include
+#include
+
+#include
+
+namespace facebook::react {
+
+KeyboardAvoidingViewState::KeyboardAvoidingViewState(
+ ShadowNodeFamily::Weak family,
+ std::weak_ptr animationBackend)
+ : family_(std::move(family)),
+ animationBackend_(std::move(animationBackend)) {}
+
+#ifdef ANDROID
+folly::dynamic KeyboardAvoidingViewState::getDynamic() const {
+ return folly::dynamic::object();
+}
+#endif
+
+void KeyboardAvoidingViewState::update(
+ Float keyboardInset,
+ Float keyboardRestingHeight) const {
+ auto family = family_.lock();
+ auto animationBackend = animationBackend_.lock();
+ if (!family || !animationBackend) {
+ return;
+ }
+
+ animationBackend->pushAnimationMutations(
+ [&](AnimationTimestamp /*timestamp*/) {
+ folly::dynamic rawProps = folly::dynamic::object(
+ "keyboardInset", static_cast(keyboardInset))(
+ "keyboardRestingHeight",
+ static_cast(keyboardRestingHeight));
+ AnimatedPropsBuilder builder;
+ builder.storeDynamic(rawProps);
+
+ AnimationMutations mutations;
+ mutations.batch.push_back(
+ AnimationMutation{
+ .tag = family->getTag(),
+ .family = family,
+ .props = builder.get(),
+ .hasLayoutUpdates = true});
+ return mutations;
+ });
+}
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewState.h b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewState.h
new file mode 100644
index 000000000000..3e2ecf4d25d8
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/components/keyboardavoidingview/KeyboardAvoidingViewState.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+
+#include
+
+#ifdef ANDROID
+#include
+#endif
+
+namespace facebook::react {
+
+/*
+ * State for component.
+ */
+class KeyboardAvoidingViewState final {
+ public:
+ KeyboardAvoidingViewState() = default;
+
+ KeyboardAvoidingViewState(ShadowNodeFamily::Weak family, std::weak_ptr animationBackend);
+
+#ifdef ANDROID
+ KeyboardAvoidingViewState(const KeyboardAvoidingViewState &previousState, const folly::dynamic & /*data*/)
+ : KeyboardAvoidingViewState(previousState) {};
+
+ folly::dynamic getDynamic() const;
+#endif
+
+ /*
+ * Sends the keyboard inset to the shared animation backend, which applies
+ * it with a commit. Must be called on the main thread.
+ */
+ void update(Float keyboardInset, Float keyboardRestingHeight) const;
+
+ private:
+ ShadowNodeFamily::Weak family_;
+ std::weak_ptr animationBackend_;
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp
index a278fb2b3400..0f4777843067 100644
--- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp
@@ -130,6 +130,7 @@ Scheduler::Scheduler(
// Must come after `setDelegate`: the backend's constructor registers a
// surface-start callback, and `UIManager` silently drops those while it has
// no delegate.
+ contextContainer_->erase(AnimationBackendKey);
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
auto animationBackend = std::make_shared(
schedulerToolbox.animationChoreographer, uiManager);
@@ -138,6 +139,10 @@ Scheduler::Scheduler(
animationBackend);
uiManager->unstable_setAnimationBackend(animationBackend);
+
+ contextContainer_->insert(
+ AnimationBackendKey,
+ std::weak_ptr(animationBackend));
}
auto bindingsExecutor =
diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h
index d57f1e772ca9..b05c6fc88417 100644
--- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h
+++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h
@@ -24,6 +24,13 @@ struct AnimationMutations;
using AnimationTimestamp = std::chrono::duration;
using CallbackId = uint64_t;
+/*
+ * `ContextContainer` key of the `std::weak_ptr`
+ * that native components use to animate their own props.
+ * Only present when the backend is enabled.
+ */
+constexpr const char *const AnimationBackendKey = "AnimationBackend";
+
class UIManagerAnimationBackend {
public:
using Callback = std::function;
diff --git a/packages/react-native/scripts/ios-prebuild/headers-config.js b/packages/react-native/scripts/ios-prebuild/headers-config.js
index 61addbc7a4c4..bd7f01e322ec 100644
--- a/packages/react-native/scripts/ios-prebuild/headers-config.js
+++ b/packages/react-native/scripts/ios-prebuild/headers-config.js
@@ -374,6 +374,17 @@ const PodspecExceptions /*: {[key: string]: PodSpecConfiguration} */ = {
headerDir: 'react/renderer/components/inputaccessory',
},
+ {
+ name: 'keyboardavoidingview',
+ headerPatterns: [
+ 'react/renderer/components/keyboardavoidingview/**/*.h',
+ ],
+ excludePatterns: [
+ 'react/renderer/components/keyboardavoidingview/tests',
+ ],
+ headerDir: 'react/renderer/components/keyboardavoidingview',
+ },
+
{
name: 'modal',
headerPatterns: ['react/renderer/components/modal/*.h'],
diff --git a/packages/react-native/src/private/components/keyboardavoidingview/specs/KeyboardAvoidingViewNativeComponent.js b/packages/react-native/src/private/components/keyboardavoidingview/specs/KeyboardAvoidingViewNativeComponent.js
new file mode 100644
index 000000000000..d3213c10a3c3
--- /dev/null
+++ b/packages/react-native/src/private/components/keyboardavoidingview/specs/KeyboardAvoidingViewNativeComponent.js
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and 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
+ */
+
+import type {ViewProps} from '../../../../../Libraries/Components/View/ViewPropTypes';
+import type {
+ Float,
+ WithDefault,
+} from '../../../../../Libraries/Types/CodegenTypes';
+import type {HostComponent} from '../../../types/HostComponent';
+
+import codegenNativeComponent from '../../../../../Libraries/Utilities/codegenNativeComponent';
+
+type KeyboardAvoidingViewNativeProps = Readonly<{
+ ...ViewProps,
+
+ behavior?: WithDefault<'none' | 'height' | 'padding' | 'position', 'none'>,
+ enabled?: WithDefault,
+ keyboardVerticalOffset?: WithDefault,
+
+ /**
+ * Written by native code through the shared animation backend on each
+ * keyboard animation frame. Do not set from JavaScript.
+ */
+ keyboardInset?: WithDefault,
+ keyboardRestingHeight?: WithDefault,
+}>;
+
+export default codegenNativeComponent(
+ 'KeyboardAvoidingView',
+ {
+ interfaceOnly: true,
+ },
+) as HostComponent;
diff --git a/packages/rn-tester/js/examples/KeyboardAvoidingView/KeyboardAvoidingViewExample.js b/packages/rn-tester/js/examples/KeyboardAvoidingView/KeyboardAvoidingViewExample.js
index 750496f9a369..b77759beb092 100644
--- a/packages/rn-tester/js/examples/KeyboardAvoidingView/KeyboardAvoidingViewExample.js
+++ b/packages/rn-tester/js/examples/KeyboardAvoidingView/KeyboardAvoidingViewExample.js
@@ -22,6 +22,7 @@ import {
Pressable,
ScrollView,
StyleSheet,
+ Switch,
Text,
TextInput,
TouchableOpacity,
@@ -72,6 +73,19 @@ const BEHAVIOR_DESCRIPTIONS: {[KeyboardAvoidingBehavior]: string} = {
height: 'Shrinks the view height by the keyboard overlap.',
};
+const NativeToggle = (props: {native: boolean, setNative: boolean => void}) => {
+ return (
+
+ experimental_native
+
+
+ );
+};
+
const BehaviorPicker = (props: {
behavior: KeyboardAvoidingBehavior,
setBehavior: KeyboardAvoidingBehavior => void,
@@ -114,11 +128,16 @@ const BehaviorPicker = (props: {
const KeyboardAvoidingViewBehaviour = () => {
const [modalOpen, setModalOpen] = useState(false);
const [behavior, setBehavior] = useState('padding');
+ const [native, setNative] = useState(false);
return (
-
+
+
@@ -305,6 +324,13 @@ const styles = StyleSheet.create({
fontWeight: '500',
color: 'blue',
},
+ nativeToggle: {
+ alignItems: 'center',
+ flexDirection: 'row',
+ gap: 8,
+ justifyContent: 'center',
+ marginBottom: 8,
+ },
behaviorDescription: {
textAlign: 'center',
alignSelf: 'center',
diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api
index 6d9ac1caa787..99fb7f575eb0 100644
--- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api
@@ -1705,6 +1705,26 @@ struct facebook::react::JavaTurboModule::InitParams {
public std::string moduleName;
}
+class facebook::react::KeyboardAvoidingViewBinding : public facebook::jni::JavaClass {
+ public static constexpr char* const kJavaDescriptor;
+ public static void registerNatives();
+}
+
+class facebook::react::KeyboardAvoidingViewEventEmitter : public facebook::react::BaseViewEventEmitter {
+}
+
+class facebook::react::KeyboardAvoidingViewProps : public facebook::react::HostPlatformViewProps {
+ public KeyboardAvoidingViewProps() = default;
+ public KeyboardAvoidingViewProps(const facebook::react::PropsParserContext& context, const facebook::react::KeyboardAvoidingViewProps& sourceProps, const facebook::react::RawProps& rawProps);
+ public bool enabled;
+ public facebook::react::Float keyboardInset;
+ public facebook::react::Float keyboardRestingHeight;
+ public facebook::react::Float keyboardVerticalOffset;
+ public facebook::react::KeyboardAvoidingViewBehavior behavior;
+ public virtual facebook::react::ComponentName getDiffPropsImplementationTarget() const override;
+ public virtual folly::dynamic getDiffProps(const facebook::react::Props* prevProps) const override;
+}
+
class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode {
public LayoutableShadowNode(const facebook::react::ShadowNode& sourceShadowNode, const facebook::react::ShadowNodeFragment& fragment);
public LayoutableShadowNode(const facebook::react::ShadowNodeFragment& fragment, const facebook::react::ShadowNodeFamily::Shared& family, facebook::react::ShadowNodeTraits traits);
@@ -2857,6 +2877,13 @@ enum facebook::react::Isolation {
Isolate,
}
+enum facebook::react::KeyboardAvoidingViewBehavior {
+ Height,
+ None,
+ Padding,
+ Position,
+}
+
enum facebook::react::LayoutConformance : uint8_t {
Compatibility,
Strict,
diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api
index d2b41ea569c8..70e24701d4c3 100644
--- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api
@@ -387,6 +387,7 @@ const char facebook::react::ViewComponentName[];
const facebook::react::EventTag facebook::react::EMPTY_EVENT_TAG;
constexpr auto facebook::react::kSimpleThreadSafeCacheSizeCap;
constexpr char facebook::react::LayoutConformanceShadowNodeComponentName[];
+constexpr char* const facebook::react::AnimationBackendKey;
constexpr char* const facebook::react::TextLayoutManagerKey;
constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion;
constexpr facebook::react::Tag facebook::react::kNoTag;
@@ -2538,6 +2539,26 @@ struct facebook::react::JsErrorHandler::ProcessedError::StackFrame {
public std::string methodName;
}
+class facebook::react::KeyboardAvoidingViewBinding : public facebook::jni::JavaClass {
+ public static constexpr char* const kJavaDescriptor;
+ public static void registerNatives();
+}
+
+class facebook::react::KeyboardAvoidingViewEventEmitter : public facebook::react::BaseViewEventEmitter {
+}
+
+class facebook::react::KeyboardAvoidingViewProps : public facebook::react::HostPlatformViewProps {
+ public KeyboardAvoidingViewProps() = default;
+ public KeyboardAvoidingViewProps(const facebook::react::PropsParserContext& context, const facebook::react::KeyboardAvoidingViewProps& sourceProps, const facebook::react::RawProps& rawProps);
+ public bool enabled;
+ public facebook::react::Float keyboardInset;
+ public facebook::react::Float keyboardRestingHeight;
+ public facebook::react::Float keyboardVerticalOffset;
+ public facebook::react::KeyboardAvoidingViewBehavior behavior;
+ public virtual facebook::react::ComponentName getDiffPropsImplementationTarget() const override;
+ public virtual folly::dynamic getDiffProps(const facebook::react::Props* prevProps) const override;
+}
+
class facebook::react::LayoutAnimationCallbackWrapper {
public LayoutAnimationCallbackWrapper();
public LayoutAnimationCallbackWrapper(facebook::jsi::Function&& callback);
@@ -5115,6 +5136,13 @@ enum facebook::react::Isolation {
Isolate,
}
+enum facebook::react::KeyboardAvoidingViewBehavior {
+ Height,
+ None,
+ Padding,
+ Position,
+}
+
enum facebook::react::LayoutConformance : uint8_t {
Compatibility,
Strict,
diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api
index 57f7bc098311..7fb278abcb3e 100644
--- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api
@@ -1704,6 +1704,26 @@ struct facebook::react::JavaTurboModule::InitParams {
public std::string moduleName;
}
+class facebook::react::KeyboardAvoidingViewBinding : public facebook::jni::JavaClass {
+ public static constexpr char* const kJavaDescriptor;
+ public static void registerNatives();
+}
+
+class facebook::react::KeyboardAvoidingViewEventEmitter : public facebook::react::BaseViewEventEmitter {
+}
+
+class facebook::react::KeyboardAvoidingViewProps : public facebook::react::HostPlatformViewProps {
+ public KeyboardAvoidingViewProps() = default;
+ public KeyboardAvoidingViewProps(const facebook::react::PropsParserContext& context, const facebook::react::KeyboardAvoidingViewProps& sourceProps, const facebook::react::RawProps& rawProps);
+ public bool enabled;
+ public facebook::react::Float keyboardInset;
+ public facebook::react::Float keyboardRestingHeight;
+ public facebook::react::Float keyboardVerticalOffset;
+ public facebook::react::KeyboardAvoidingViewBehavior behavior;
+ public virtual facebook::react::ComponentName getDiffPropsImplementationTarget() const override;
+ public virtual folly::dynamic getDiffProps(const facebook::react::Props* prevProps) const override;
+}
+
class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode {
public LayoutableShadowNode(const facebook::react::ShadowNode& sourceShadowNode, const facebook::react::ShadowNodeFragment& fragment);
public LayoutableShadowNode(const facebook::react::ShadowNodeFragment& fragment, const facebook::react::ShadowNodeFamily::Shared& family, facebook::react::ShadowNodeTraits traits);
@@ -2850,6 +2870,13 @@ enum facebook::react::Isolation {
Isolate,
}
+enum facebook::react::KeyboardAvoidingViewBehavior {
+ Height,
+ None,
+ Padding,
+ Position,
+}
+
enum facebook::react::LayoutConformance : uint8_t {
Compatibility,
Strict,
diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api
index 57f7bc098311..7fb278abcb3e 100644
--- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api
@@ -1704,6 +1704,26 @@ struct facebook::react::JavaTurboModule::InitParams {
public std::string moduleName;
}
+class facebook::react::KeyboardAvoidingViewBinding : public facebook::jni::JavaClass {
+ public static constexpr char* const kJavaDescriptor;
+ public static void registerNatives();
+}
+
+class facebook::react::KeyboardAvoidingViewEventEmitter : public facebook::react::BaseViewEventEmitter {
+}
+
+class facebook::react::KeyboardAvoidingViewProps : public facebook::react::HostPlatformViewProps {
+ public KeyboardAvoidingViewProps() = default;
+ public KeyboardAvoidingViewProps(const facebook::react::PropsParserContext& context, const facebook::react::KeyboardAvoidingViewProps& sourceProps, const facebook::react::RawProps& rawProps);
+ public bool enabled;
+ public facebook::react::Float keyboardInset;
+ public facebook::react::Float keyboardRestingHeight;
+ public facebook::react::Float keyboardVerticalOffset;
+ public facebook::react::KeyboardAvoidingViewBehavior behavior;
+ public virtual facebook::react::ComponentName getDiffPropsImplementationTarget() const override;
+ public virtual folly::dynamic getDiffProps(const facebook::react::Props* prevProps) const override;
+}
+
class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode {
public LayoutableShadowNode(const facebook::react::ShadowNode& sourceShadowNode, const facebook::react::ShadowNodeFragment& fragment);
public LayoutableShadowNode(const facebook::react::ShadowNodeFragment& fragment, const facebook::react::ShadowNodeFamily::Shared& family, facebook::react::ShadowNodeTraits traits);
@@ -2850,6 +2870,13 @@ enum facebook::react::Isolation {
Isolate,
}
+enum facebook::react::KeyboardAvoidingViewBehavior {
+ Height,
+ None,
+ Padding,
+ Position,
+}
+
enum facebook::react::LayoutConformance : uint8_t {
Compatibility,
Strict,
diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api
index 8de2c49759a6..3e4684ade23f 100644
--- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api
@@ -1257,6 +1257,9 @@ interface RCTKeyCommands : public NSObject {
public virtual void unregisterKeyCommandWithInput:modifierFlags:(NSString* input, UIKeyModifierFlags flags);
}
+interface RCTKeyboardAvoidingViewComponentView : public RCTViewComponentView {
+}
+
interface RCTKeyboardObserver : public RCTEventEmitter {
}
@@ -2786,6 +2789,9 @@ protocol RCTJSRuntimeConfiguratorProtocol {
public virtual JSRuntimeFactoryRef createJSRuntimeFactory();
}
+protocol RCTKeyboardAvoidingViewViewProtocol : public NSObject {
+}
+
protocol RCTLocalizationProtocol : public NSObject {
public virtual NSString* localizedString:withDescription:(NSString* oldString, NSString* description);
}
@@ -3986,6 +3992,19 @@ class facebook::react::JSBigString : public facebook::jsi::Buffer {
public ~JSBigString() override = default;
}
+class facebook::react::KeyboardAvoidingViewEventEmitter : public facebook::react::BaseViewEventEmitter {
+}
+
+class facebook::react::KeyboardAvoidingViewProps : public facebook::react::HostPlatformViewProps {
+ public KeyboardAvoidingViewProps() = default;
+ public KeyboardAvoidingViewProps(const facebook::react::PropsParserContext& context, const facebook::react::KeyboardAvoidingViewProps& sourceProps, const facebook::react::RawProps& rawProps);
+ public bool enabled;
+ public facebook::react::Float keyboardInset;
+ public facebook::react::Float keyboardRestingHeight;
+ public facebook::react::Float keyboardVerticalOffset;
+ public facebook::react::KeyboardAvoidingViewBehavior behavior;
+}
+
class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode {
public LayoutableShadowNode(const facebook::react::ShadowNode& sourceShadowNode, const facebook::react::ShadowNodeFragment& fragment);
public LayoutableShadowNode(const facebook::react::ShadowNodeFragment& fragment, const facebook::react::ShadowNodeFamily::Shared& family, facebook::react::ShadowNodeTraits traits);
@@ -5010,6 +5029,13 @@ enum facebook::react::Isolation {
Isolate,
}
+enum facebook::react::KeyboardAvoidingViewBehavior {
+ Height,
+ None,
+ Padding,
+ Position,
+}
+
enum facebook::react::LayoutConformance : uint8_t {
Compatibility,
Strict,
diff --git a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api
index 6b5e45141f07..8415a67cb2b6 100644
--- a/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api
@@ -1300,6 +1300,9 @@ interface RCTKeyCommands : public NSObject {
public virtual void unregisterKeyCommandWithInput:modifierFlags:(NSString* input, UIKeyModifierFlags flags);
}
+interface RCTKeyboardAvoidingViewComponentView : public RCTViewComponentView {
+}
+
interface RCTKeyboardObserver : public RCTEventEmitter {
}
@@ -2867,6 +2870,9 @@ protocol RCTJSRuntimeConfiguratorProtocol {
public virtual JSRuntimeFactoryRef createJSRuntimeFactory();
}
+protocol RCTKeyboardAvoidingViewViewProtocol : public NSObject {
+}
+
protocol RCTLocalizationProtocol : public NSObject {
public virtual NSString* localizedString:withDescription:(NSString* oldString, NSString* description);
}
@@ -3250,6 +3256,7 @@ const char facebook::react::ViewComponentName[];
const facebook::react::EventTag facebook::react::EMPTY_EVENT_TAG;
constexpr auto facebook::react::kSimpleThreadSafeCacheSizeCap;
constexpr char facebook::react::LayoutConformanceShadowNodeComponentName[];
+constexpr char* const facebook::react::AnimationBackendKey;
constexpr char* const facebook::react::TextLayoutManagerKey;
constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion;
constexpr facebook::react::Tag facebook::react::kNoTag;
@@ -4752,6 +4759,19 @@ struct facebook::react::JsErrorHandler::ProcessedError::StackFrame {
public std::string methodName;
}
+class facebook::react::KeyboardAvoidingViewEventEmitter : public facebook::react::BaseViewEventEmitter {
+}
+
+class facebook::react::KeyboardAvoidingViewProps : public facebook::react::HostPlatformViewProps {
+ public KeyboardAvoidingViewProps() = default;
+ public KeyboardAvoidingViewProps(const facebook::react::PropsParserContext& context, const facebook::react::KeyboardAvoidingViewProps& sourceProps, const facebook::react::RawProps& rawProps);
+ public bool enabled;
+ public facebook::react::Float keyboardInset;
+ public facebook::react::Float keyboardRestingHeight;
+ public facebook::react::Float keyboardVerticalOffset;
+ public facebook::react::KeyboardAvoidingViewBehavior behavior;
+}
+
class facebook::react::LayoutAnimationCallbackWrapper {
public LayoutAnimationCallbackWrapper();
public LayoutAnimationCallbackWrapper(facebook::jsi::Function&& callback);
@@ -7254,6 +7274,13 @@ enum facebook::react::KeyboardAppearance {
Light,
}
+enum facebook::react::KeyboardAvoidingViewBehavior {
+ Height,
+ None,
+ Padding,
+ Position,
+}
+
enum facebook::react::KeyboardType {
ASCIICapable,
ASCIICapableNumberPad,
diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api
index e8201541b2e1..865585cf8a34 100644
--- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api
@@ -1257,6 +1257,9 @@ interface RCTKeyCommands : public NSObject {
public virtual void unregisterKeyCommandWithInput:modifierFlags:(NSString* input, UIKeyModifierFlags flags);
}
+interface RCTKeyboardAvoidingViewComponentView : public RCTViewComponentView {
+}
+
interface RCTKeyboardObserver : public RCTEventEmitter {
}
@@ -2778,6 +2781,9 @@ protocol RCTJSRuntimeConfiguratorProtocol {
public virtual JSRuntimeFactoryRef createJSRuntimeFactory();
}
+protocol RCTKeyboardAvoidingViewViewProtocol : public NSObject {
+}
+
protocol RCTLocalizationProtocol : public NSObject {
public virtual NSString* localizedString:withDescription:(NSString* oldString, NSString* description);
}
@@ -3977,6 +3983,19 @@ class facebook::react::JSBigString : public facebook::jsi::Buffer {
public ~JSBigString() override = default;
}
+class facebook::react::KeyboardAvoidingViewEventEmitter : public facebook::react::BaseViewEventEmitter {
+}
+
+class facebook::react::KeyboardAvoidingViewProps : public facebook::react::HostPlatformViewProps {
+ public KeyboardAvoidingViewProps() = default;
+ public KeyboardAvoidingViewProps(const facebook::react::PropsParserContext& context, const facebook::react::KeyboardAvoidingViewProps& sourceProps, const facebook::react::RawProps& rawProps);
+ public bool enabled;
+ public facebook::react::Float keyboardInset;
+ public facebook::react::Float keyboardRestingHeight;
+ public facebook::react::Float keyboardVerticalOffset;
+ public facebook::react::KeyboardAvoidingViewBehavior behavior;
+}
+
class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode {
public LayoutableShadowNode(const facebook::react::ShadowNode& sourceShadowNode, const facebook::react::ShadowNodeFragment& fragment);
public LayoutableShadowNode(const facebook::react::ShadowNodeFragment& fragment, const facebook::react::ShadowNodeFamily::Shared& family, facebook::react::ShadowNodeTraits traits);
@@ -4995,6 +5014,13 @@ enum facebook::react::Isolation {
Isolate,
}
+enum facebook::react::KeyboardAvoidingViewBehavior {
+ Height,
+ None,
+ Padding,
+ Position,
+}
+
enum facebook::react::LayoutConformance : uint8_t {
Compatibility,
Strict,
diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api
index d3ff97cc59c6..3a4f9775e594 100644
--- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api
@@ -1257,6 +1257,9 @@ interface RCTKeyCommands : public NSObject {
public virtual void unregisterKeyCommandWithInput:modifierFlags:(NSString* input, UIKeyModifierFlags flags);
}
+interface RCTKeyboardAvoidingViewComponentView : public RCTViewComponentView {
+}
+
interface RCTKeyboardObserver : public RCTEventEmitter {
}
@@ -2786,6 +2789,9 @@ protocol RCTJSRuntimeConfiguratorProtocol {
public virtual JSRuntimeFactoryRef createJSRuntimeFactory();
}
+protocol RCTKeyboardAvoidingViewViewProtocol : public NSObject {
+}
+
protocol RCTLocalizationProtocol : public NSObject {
public virtual NSString* localizedString:withDescription:(NSString* oldString, NSString* description);
}
@@ -3985,6 +3991,19 @@ class facebook::react::JSBigString : public facebook::jsi::Buffer {
public ~JSBigString() override = default;
}
+class facebook::react::KeyboardAvoidingViewEventEmitter : public facebook::react::BaseViewEventEmitter {
+}
+
+class facebook::react::KeyboardAvoidingViewProps : public facebook::react::HostPlatformViewProps {
+ public KeyboardAvoidingViewProps() = default;
+ public KeyboardAvoidingViewProps(const facebook::react::PropsParserContext& context, const facebook::react::KeyboardAvoidingViewProps& sourceProps, const facebook::react::RawProps& rawProps);
+ public bool enabled;
+ public facebook::react::Float keyboardInset;
+ public facebook::react::Float keyboardRestingHeight;
+ public facebook::react::Float keyboardVerticalOffset;
+ public facebook::react::KeyboardAvoidingViewBehavior behavior;
+}
+
class facebook::react::LayoutableShadowNode : public facebook::react::ShadowNode {
public LayoutableShadowNode(const facebook::react::ShadowNode& sourceShadowNode, const facebook::react::ShadowNodeFragment& fragment);
public LayoutableShadowNode(const facebook::react::ShadowNodeFragment& fragment, const facebook::react::ShadowNodeFamily::Shared& family, facebook::react::ShadowNodeTraits traits);
@@ -5003,6 +5022,13 @@ enum facebook::react::Isolation {
Isolate,
}
+enum facebook::react::KeyboardAvoidingViewBehavior {
+ Height,
+ None,
+ Padding,
+ Position,
+}
+
enum facebook::react::LayoutConformance : uint8_t {
Compatibility,
Strict,
diff --git a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api
index c06d3fc44d2a..fb3c464eaa64 100644
--- a/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api
@@ -84,6 +84,7 @@ const char facebook::react::ViewComponentName[];
const facebook::react::EventTag facebook::react::EMPTY_EVENT_TAG;
constexpr auto facebook::react::kSimpleThreadSafeCacheSizeCap;
constexpr char facebook::react::LayoutConformanceShadowNodeComponentName[];
+constexpr char* const facebook::react::AnimationBackendKey;
constexpr char* const facebook::react::TextLayoutManagerKey;
constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion;
constexpr facebook::react::Tag facebook::react::kNoTag;