Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
"eslint-comments",
"prettier",
"react-perf",
"jsdoc",
],
extends: [
"@react-native",
Expand Down Expand Up @@ -163,6 +164,63 @@ module.exports = {
],
},
},
{
files: ["src/**"],
excludedFiles: ["src/specs/**"],
rules: {
"jsdoc/check-access": "error",
"jsdoc/check-alignment": "error",
"jsdoc/check-indentation": 1,
"jsdoc/check-line-alignment": 1,
"jsdoc/check-param-names": "error",
"jsdoc/check-template-names": 1,
"jsdoc/check-property-names": "error",
"jsdoc/check-syntax": 1,
"jsdoc/check-tag-names": ["error", { definedTags: ["platform"] }],
"jsdoc/check-types": "error",
"jsdoc/check-values": "error",
"jsdoc/empty-tags": "error",
"jsdoc/implements-on-classes": "error",
"jsdoc/informative-docs": 1,
"jsdoc/match-description": 1,
"jsdoc/multiline-blocks": "error",
"jsdoc/no-bad-blocks": 1,
"jsdoc/no-blank-block-descriptions": 1,
"jsdoc/no-defaults": 1,
"jsdoc/no-multi-asterisks": "error",
"jsdoc/no-types": 1,
"jsdoc/require-asterisk-prefix": 1,
"jsdoc/require-description": 1,
"jsdoc/require-description-complete-sentence": 1,
"jsdoc/require-example": 1,
"jsdoc/require-hyphen-before-param-description": 1,
"jsdoc/require-jsdoc": "error",
"jsdoc/require-param": "error",
"jsdoc/require-param-description": "error",
"jsdoc/require-param-name": "error",
"jsdoc/require-property": "error",
"jsdoc/require-property-description": "error",
"jsdoc/require-property-name": "error",
"jsdoc/require-returns": "error",
"jsdoc/require-returns-check": "error",
"jsdoc/require-returns-description": "error",
"jsdoc/require-template": 1,
"jsdoc/require-throws": 1,
"jsdoc/require-yields": "error",
"jsdoc/require-yields-check": "error",
"jsdoc/sort-tags": 1,
"jsdoc/tag-lines": ["error", "never", { startLines: 1 }],
"jsdoc/valid-types": "error",
"jsdoc/no-restricted-syntax": "off",
"jsdoc/require-file-overview": "off",
"jsdoc/no-missing-syntax": "off",
"jsdoc/check-examples": "off",
"jsdoc/no-undefined-types": "off",
"jsdoc/require-param-type": "off",
"jsdoc/require-property-type": "off",
"jsdoc/require-returns-type": "off",
},
},
],
env: {
"react-native/react-native": true,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"eslint-plugin-ft-flow": "^3.0.11",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jest": "^26.5.3",
"eslint-plugin-jsdoc": "^50.6.17",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react-compiler": "0.0.0-experimental-9ed098e-20240725",
"eslint-plugin-react-perf": "^3.3.2",
Expand Down
39 changes: 27 additions & 12 deletions src/animated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,29 +56,29 @@ type KeyboardProviderProps = {
* or `StatusBar` component from `react-native`, you can ignore it.
* Defaults to `false`.
*
* @see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/14
* @platform android
* @see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/14
*/
statusBarTranslucent?: boolean;
/**
* Set the value to `true`, if you use translucent navigation bar on Android.
* Defaults to `false`.
*
* @see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/119
* @platform android
* @see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/119
*/
navigationBarTranslucent?: boolean;
/**
* A boolean property indicating whether to keep edge-to-edge mode always enabled (even when you disable the module).
* Defaults to `false`.
*
* @see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/592
* @platform android
* @see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/592
*/
preserveEdgeToEdge?: boolean;
/**
* A boolean prop indicating whether the module is enabled. It indicate only initial state,
* i. e. if you try to change this prop after component mount it will not have any effect.
* A boolean prop indicating whether the module is enabled. It indicate only initial state
* (if you try to change this prop after component mount it will not have any effect).
* To change the property in runtime use `useKeyboardController` hook and `setEnabled` method.
* Defaults to `true`.
*/
Expand All @@ -89,13 +89,28 @@ type KeyboardProviderProps = {
// see https://github.com/kirillzyusko/react-native-keyboard-controller/issues/393 and https://github.com/kirillzyusko/react-native-keyboard-controller/issues/294 for more details
const OS = Platform.OS;

export const KeyboardProvider = ({
children,
statusBarTranslucent,
navigationBarTranslucent,
preserveEdgeToEdge,
enabled: initiallyEnabled = true,
}: KeyboardProviderProps) => {
/**
* A component that wrap your app. Under the hood it works with {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller-view|KeyboardControllerView} to receive events during keyboard movements,
* maps these events to `Animated`/`Reanimated` values and store them in context.
*
* @param props - Provider props, such as `statusBarTranslucent`, `navigationBarTranslucent`, etc.
* @returns A component that should be mounted in root of your App layout.
* @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-provider|Documentation} page for more details.
* @example
* ```tsx
* <KeyboardProvider>
* <NavigationContainer />
* </KeyboardProvider>
* ```
*/
export const KeyboardProvider = (props: KeyboardProviderProps) => {
const {
children,
statusBarTranslucent,
navigationBarTranslucent,
preserveEdgeToEdge,
enabled: initiallyEnabled = true,
} = props;
// ref
const viewTagRef = useRef<React.Component<KeyboardControllerProps>>(null);
// state
Expand Down
19 changes: 19 additions & 0 deletions src/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ export const KeyboardControllerNative: KeyboardControllerNativeModule = {
addListener: NOOP,
removeListeners: NOOP,
};
/**
* An event emitter that provides a way to subscribe to next keyboard events:
* - `keyboardWillShow`;
* - `keyboardDidShow`;
* - `keyboardWillHide`;
* - `keyboardDidHide`.
*
* Use `addListener` function to add your event listener for a specific keyboard event.
*/
export const KeyboardEvents: KeyboardEventsModule = {
addListener: () => ({ remove: NOOP } as EmitterSubscription),
};
Expand All @@ -34,8 +43,18 @@ export const FocusedInputEvents: FocusedInputEventsModule = {
export const WindowDimensionsEvents: WindowDimensionsEventsModule = {
addListener: () => ({ remove: NOOP } as EmitterSubscription),
};
/**
* A view that sends events whenever keyboard or focused events are happening.
*
* @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-controller-view|Documentation} page for more details.
*/
export const KeyboardControllerView =
View as unknown as React.FC<KeyboardControllerProps>;
/**
* A view that defines a region on the screen, where gestures will control the keyboard position.
*
* @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/keyboard-gesture-area|Documentation} page for more details.
*/
export const KeyboardGestureArea =
View as unknown as React.FC<KeyboardGestureAreaProps>;
export const RCTOverKeyboardView =
Expand Down
13 changes: 11 additions & 2 deletions src/components/KeyboardAvoidingView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,17 @@ const defaultLayout: LayoutRectangle = {
};

/**
* View that moves out of the way when the keyboard appears by automatically
* adjusting its height, position, or bottom padding.
* A View component that automatically adjusts its height, position, or bottom padding
* when the keyboard appears to ensure that the content remains visible.
*
* @returns A View component that adjusts to keyboard visibility.
* @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-avoiding-view|Documentation} page for more details.
* @example
* ```tsx
* <KeyboardAvoidingView behavior="padding">
* <TextInput />
* </KeyboardAvoidingView>
* ```
*/
const KeyboardAvoidingView = forwardRef<
View,
Expand Down
93 changes: 54 additions & 39 deletions src/components/KeyboardAwareScrollView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,51 +35,66 @@ export type KeyboardAwareScrollViewProps = {
bottomOffset?: number;
/** Prevents automatic scrolling of the `ScrollView` when the keyboard gets hidden, maintaining the current screen position. Default is `false`. */
disableScrollOnKeyboardHide?: boolean;
/** Controls whether this `KeyboardAwareScrollView` instance should take effect. Default is `true` */
/** Controls whether this `KeyboardAwareScrollView` instance should take effect. Default is `true`. */
enabled?: boolean;
/** Adjusting the bottom spacing of KeyboardAwareScrollView. Default is `0` */
/** Adjusting the bottom spacing of KeyboardAwareScrollView. Default is `0`. */
extraKeyboardSpace?: number;
/** Custom component for `ScrollView`. Default is `ScrollView` */
/** Custom component for `ScrollView`. Default is `ScrollView`. */
ScrollViewComponent?: React.ComponentType<ScrollViewProps>;
} & ScrollViewProps;

/*
* Everything begins from `onStart` handler. This handler is called every time,
* when keyboard changes its size or when focused `TextInput` was changed. In
* this handler we are calculating/memoizing values which later will be used
* during layout movement. For that we calculate:
* - layout of focused field (`layout`) - to understand whether there will be overlap
* - initial keyboard size (`initialKeyboardSize`) - used in scroll interpolation
* - future keyboard height (`keyboardHeight`) - used in scroll interpolation
* - current scroll position (`scrollPosition`) - used to scroll from this point
// Everything begins from `onStart` handler. This handler is called every time,
// when keyboard changes its size or when focused `TextInput` was changed. In
// this handler we are calculating/memoizing values which later will be used
// during layout movement. For that we calculate:
// - layout of focused field (`layout`) - to understand whether there will be overlap
// - initial keyboard size (`initialKeyboardSize`) - used in scroll interpolation
// - future keyboard height (`keyboardHeight`) - used in scroll interpolation
// - current scroll position (`scrollPosition`) - used to scroll from this point
//
// Once we've calculated all necessary variables - we can actually start to use them.
// It happens in `onMove` handler - this function simply calls `maybeScroll` with
// current keyboard frame height. This functions makes the smooth transition.
//
// When the transition has finished we go to `onEnd` handler. In this handler
// we verify, that the current field is not overlapped within a keyboard frame.
// For full `onStart`/`onMove`/`onEnd` flow it may look like a redundant thing,
// however there could be some cases, when `onMove` is not called:
// - on iOS when TextInput was changed - keyboard transition is instant
// - on Android when TextInput was changed and keyboard size wasn't changed
// So `onEnd` handler handle the case, when `onMove` wasn't triggered.
//
// ====================================================================================================================+
// -----------------------------------------------------Flow chart-----------------------------------------------------+
// ====================================================================================================================+
//
// +============================+ +============================+ +==================================+
// + User Press on TextInput + => + Keyboard starts showing + => + As keyboard moves frame by frame + =>
// + + + (run `onStart`) + + `onMove` is getting called +
// +============================+ +============================+ +==================================+
//
// +============================+ +============================+ +=====================================+
// + Keyboard is shown and we + => + User moved focus to + => + Only `onStart`/`onEnd` maybe called +
// + call `onEnd` handler + + another `TextInput` + + (without involving `onMove`) +
// +============================+ +============================+ +=====================================+
//

/**
* A ScrollView component that automatically handles keyboard appearance and disappearance
* by adjusting its content position to ensure the focused input remains visible.
*
* Once we've calculated all necessary variables - we can actually start to use them.
* It happens in `onMove` handler - this function simply calls `maybeScroll` with
* current keyboard frame height. This functions makes the smooth transition.
*
* When the transition has finished we go to `onEnd` handler. In this handler
* we verify, that the current field is not overlapped within a keyboard frame.
* For full `onStart`/`onMove`/`onEnd` flow it may look like a redundant thing,
* however there could be some cases, when `onMove` is not called:
* - on iOS when TextInput was changed - keyboard transition is instant
* - on Android when TextInput was changed and keyboard size wasn't changed
* So `onEnd` handler handle the case, when `onMove` wasn't triggered.
*
* ====================================================================================================================+
* -----------------------------------------------------Flow chart-----------------------------------------------------+
* ====================================================================================================================+
*
* +============================+ +============================+ +==================================+
* + User Press on TextInput + => + Keyboard starts showing + => + As keyboard moves frame by frame + =>
* + + + (run `onStart`) + + `onMove` is getting called +
* +============================+ +============================+ +==================================+
*
*
* +============================+ +============================+ +=====================================+
* + Keyboard is shown and we + => + User moved focus to + => + Only `onStart`/`onEnd` maybe called +
* + call `onEnd` handler + + another `TextInput` + + (without involving `onMove`) +
* +============================+ +============================+ +=====================================+
* The component uses a sophisticated animation system to smoothly handle keyboard transitions
* and maintain proper scroll position during keyboard interactions.
*
* @returns A ScrollView component that handles keyboard interactions.
* @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-aware-scroll-view|Documentation} page for more details.
* @example
* ```tsx
* <KeyboardAwareScrollView bottomOffset={20}>
* <TextInput placeholder="Enter text" />
* <TextInput placeholder="Another input" />
* </KeyboardAwareScrollView>
* ```
*/
const KeyboardAwareScrollView = forwardRef<
ScrollView,
Expand Down Expand Up @@ -133,7 +148,7 @@ const KeyboardAwareScrollView = forwardRef<
);

/**
* Function that will scroll a ScrollView as keyboard gets moving
* Function that will scroll a ScrollView as keyboard gets moving.
*/
const maybeScroll = useCallback(
(e: number, animated: boolean = false) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,23 @@ const TELEGRAM_ANDROID_TIMING_CONFIG = {

/**
* Hook that uses default transitions for iOS and Android > 11, and uses
* custom interpolation on Android < 11 to achieve more smooth animation
* custom interpolation on Android < 11 to achieve more smooth animation.
*
* @param handler - Object containing keyboard event handlers.
* @param [deps] - Dependencies array for the effect.
* @example
* ```ts
* useSmoothKeyboardHandler(
* {
* onStart: (e) => {
* "worklet";
*
* // your handler for keyboard start
* },
* },
* [],
* );
* ```
*/
export const useSmoothKeyboardHandler: typeof useKeyboardHandler = (
handler,
Expand Down
15 changes: 14 additions & 1 deletion src/components/KeyboardStickyView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,23 @@ export type KeyboardStickyViewProps = {
opened?: number;
};

/** Controls whether this `KeyboardStickyView` instance should take effect. Default is `true` */
/** Controls whether this `KeyboardStickyView` instance should take effect. Default is `true`. */
enabled?: boolean;
} & ViewProps;

/**
* A View component that sticks to the keyboard and moves with it when it appears or disappears.
* The view can be configured with custom offsets for both closed and open keyboard states.
*
* @returns An animated View component that sticks to the keyboard.
* @see {@link https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-sticky-view|Documentation} page for more details.
* @example
* ```tsx
* <KeyboardStickyView offset={{ closed: 0, opened: 20 }}>
* <Button title="Submit" />
* </KeyboardStickyView>
* ```
*/
const KeyboardStickyView = forwardRef<
View,
React.PropsWithChildren<KeyboardStickyViewProps>
Expand Down
Loading
Loading