diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 99cce64b2c..d9a4bc3159 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -10,6 +10,7 @@ module.exports = { "eslint-comments", "prettier", "react-perf", + "jsdoc", ], extends: [ "@react-native", @@ -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, diff --git a/package.json b/package.json index aca781512f..2b609a06bc 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/animated.tsx b/src/animated.tsx index 757e22b512..5aa199754d 100644 --- a/src/animated.tsx +++ b/src/animated.tsx @@ -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`. */ @@ -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 + * + * + * + * ``` + */ +export const KeyboardProvider = (props: KeyboardProviderProps) => { + const { + children, + statusBarTranslucent, + navigationBarTranslucent, + preserveEdgeToEdge, + enabled: initiallyEnabled = true, + } = props; // ref const viewTagRef = useRef>(null); // state diff --git a/src/bindings.ts b/src/bindings.ts index a82850206a..5ff13de3c2 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -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), }; @@ -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; +/** + * 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; export const RCTOverKeyboardView = diff --git a/src/components/KeyboardAvoidingView/index.tsx b/src/components/KeyboardAvoidingView/index.tsx index eb329be4af..926244e3c6 100644 --- a/src/components/KeyboardAvoidingView/index.tsx +++ b/src/components/KeyboardAvoidingView/index.tsx @@ -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 + * + * + * + * ``` */ const KeyboardAvoidingView = forwardRef< View, diff --git a/src/components/KeyboardAwareScrollView/index.tsx b/src/components/KeyboardAwareScrollView/index.tsx index 3ef6f66435..33a61ff626 100644 --- a/src/components/KeyboardAwareScrollView/index.tsx +++ b/src/components/KeyboardAwareScrollView/index.tsx @@ -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; -/* - * 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 + * + * + * + * + * ``` */ const KeyboardAwareScrollView = forwardRef< ScrollView, @@ -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) => { diff --git a/src/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.ts b/src/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.ts index 645835ed8c..97ea033be4 100644 --- a/src/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.ts +++ b/src/components/KeyboardAwareScrollView/useSmoothKeyboardHandler.ts @@ -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, diff --git a/src/components/KeyboardStickyView/index.tsx b/src/components/KeyboardStickyView/index.tsx index 0ad5b89058..06a3ef392b 100644 --- a/src/components/KeyboardStickyView/index.tsx +++ b/src/components/KeyboardStickyView/index.tsx @@ -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 + * + *