From 6b5657c161a64e9bcd9504809101e856313de08d Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 22 Oct 2025 12:04:08 +0200 Subject: [PATCH 1/2] feat: `KeyboardLayout` --- package.json | 1 + src/components/KeyboardLayout/index.tsx | 111 ++++++++++++++++++++++++ src/components/index.ts | 1 + src/index.ts | 1 + yarn.lock | 5 ++ 5 files changed, 119 insertions(+) create mode 100644 src/components/KeyboardLayout/index.tsx diff --git a/package.json b/package.json index d6575ff572..c76394c24d 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "registry": "https://registry.npmjs.org/" }, "dependencies": { + "react-freeze": "^1.0.4", "react-native-is-edge-to-edge": "^1.2.1" }, "devDependencies": { diff --git a/src/components/KeyboardLayout/index.tsx b/src/components/KeyboardLayout/index.tsx new file mode 100644 index 0000000000..b47a2eb233 --- /dev/null +++ b/src/components/KeyboardLayout/index.tsx @@ -0,0 +1,111 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { Freeze } from "react-freeze"; +import { StyleSheet, View } from "react-native"; + +import { KeyboardEvents } from "../../bindings"; + +// Incomplete type, all accessible properties available at: +// react-native/Libraries/Components/View/фReactNativeViewViewConfig.js +interface ViewConfig extends View { + viewConfig: { + validAttributes: { + style: { + display: boolean | null; + }; + }; + }; + _viewConfig: { + validAttributes: { + style: { + display: boolean | null; + }; + }; + }; +} + +/** + * A component that skips rendering its children when the keyboard is animating. + * Skipping these updates can help to deliver smoother animations. + * + * @param props - Properties of the component. + * @param props.children - Children of the component. + * @returns Children that skips rendering while keyboard is animating. + * @example + * ``` + * + * + * + * + * + * ``` + */ +const KeyboardLayout = (props: { children: React.ReactNode }) => { + const { children } = props; + const [isFrozen, setFrozen] = useState(false); + + const handleRef = useCallback((ref: ViewConfig) => { + // Workaround is necessary to prevent React Native from hiding frozen screens. + // See this PR: https://github.com/grahammendick/navigation/pull/860 + if (ref?.viewConfig?.validAttributes?.style) { + ref.viewConfig.validAttributes.style = { + ...ref.viewConfig.validAttributes.style, + display: null, + }; + } else if (ref?._viewConfig?.validAttributes?.style) { + ref._viewConfig.validAttributes.style = { + ...ref._viewConfig.validAttributes.style, + display: null, + }; + } + }, []); + + useEffect(() => { + const willShowListener = KeyboardEvents.addListener( + "keyboardWillShow", + () => { + setFrozen(true); + }, + ); + const didHideListener = KeyboardEvents.addListener( + "keyboardDidHide", + () => { + setFrozen(false); + }, + ); + const willHideListener = KeyboardEvents.addListener( + "keyboardWillHide", + () => { + setFrozen(true); + }, + ); + const didShowListener = KeyboardEvents.addListener( + "keyboardDidShow", + () => { + setFrozen(false); + }, + ); + + return () => { + willShowListener.remove(); + didHideListener.remove(); + willHideListener.remove(); + didShowListener.remove(); + }; + }, []); + + return ( + + + {children} + + + ); +}; + +const styles = StyleSheet.create({ + flex: { + flex: 1, + }, +}); + +export default KeyboardLayout; diff --git a/src/components/index.ts b/src/components/index.ts index e868519b32..de9ad75d52 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -5,6 +5,7 @@ export { default as KeyboardToolbar, DefaultKeyboardToolbarTheme, } from "./KeyboardToolbar"; +export { default as KeyboardLayout } from "./KeyboardLayout"; export type { KeyboardAvoidingViewProps } from "./KeyboardAvoidingView"; export type { KeyboardStickyViewProps } from "./KeyboardStickyView"; export type { KeyboardAwareScrollViewProps } from "./KeyboardAwareScrollView"; diff --git a/src/index.ts b/src/index.ts index 9093708b5e..8798cdce3e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ export * from "./module"; export * from "./types"; export { + KeyboardLayout, KeyboardAvoidingView, KeyboardStickyView, KeyboardAwareScrollView, diff --git a/yarn.lock b/yarn.lock index 43a2c7dbcb..99544c90c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7330,6 +7330,11 @@ react-error-boundary@^3.1.0: dependencies: "@babel/runtime" "^7.12.5" +react-freeze@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.4.tgz#cbbea2762b0368b05cbe407ddc9d518c57c6f3ad" + integrity sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" From f244dedc0d892676c796ff14895c64a368f242fe Mon Sep 17 00:00:00 2001 From: kirillzyusko Date: Wed, 22 Oct 2025 12:08:23 +0200 Subject: [PATCH 2/2] feat: show use case --- .../Examples/ReanimatedChatFlatList/index.tsx | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/FabricExample/src/screens/Examples/ReanimatedChatFlatList/index.tsx b/FabricExample/src/screens/Examples/ReanimatedChatFlatList/index.tsx index 64e801064b..f58e291326 100644 --- a/FabricExample/src/screens/Examples/ReanimatedChatFlatList/index.tsx +++ b/FabricExample/src/screens/Examples/ReanimatedChatFlatList/index.tsx @@ -7,7 +7,10 @@ import { TouchableOpacity, View, } from "react-native"; -import { KeyboardAvoidingView } from "react-native-keyboard-controller"; +import { + KeyboardAvoidingView, + KeyboardLayout, +} from "react-native-keyboard-controller"; import Message from "../../../components/Message"; import { history } from "../../../components/Message/data"; @@ -30,31 +33,33 @@ function ReanimatedChatFlatList() { return ( <> - - - ref.current?.scrollToEnd()} - > - - - - + + + + + ref.current?.scrollToEnd()} + > + + + + );