diff --git a/.prettierignore b/.prettierignore index 8214e01592..b8849114c3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -22,3 +22,6 @@ android/build/ android/.cxx/ tea.yaml + +coverage/ +e2e/kit/assets/ diff --git a/src/components/KeyboardAwareScrollView/__fixtures__/mocks.tsx b/src/components/KeyboardAwareScrollView/__fixtures__/mocks.tsx new file mode 100644 index 0000000000..1f1ca690ab --- /dev/null +++ b/src/components/KeyboardAwareScrollView/__fixtures__/mocks.tsx @@ -0,0 +1,113 @@ +/** + * Shared `jest.mock()` registrations for KeyboardAwareScrollView tests. + * + * Import this file in each test file. Because `jest.mock()` calls are hoisted + * by babel, they will always run before any other imports. + * + * @example import "../__fixtures__/mocks"; + */ + +// --------------------------------------------------------------------------- +// Inline constants & helpers — defined here to avoid a circular dependency +// chain (testUtils → useChatKeyboard/testUtils → reanimated → this factory). +// --------------------------------------------------------------------------- +const MOCK_SCREEN_H = 928; +const MOCK_SV = 1469; + +const mockInterpolateFn = ( + value: number, + input: number[], + output: number[], +): number => { + "worklet"; + + if (input[1] === 0) { + return 0; + } + + const progress = (value - input[0]) / (input[1] - input[0]); + + return output[0] + progress * (output[1] - output[0]); +}; + +const mockState = () => require("./testUtils"); + +// --------------------------------------------------------------------------- +// jest.mock registrations +// --------------------------------------------------------------------------- + +jest.mock("react-native-reanimated", () => ({ + ...require("react-native-reanimated/mock"), + scrollTo: (...args: unknown[]) => mockState().mockScrollTo(...args), + interpolate: mockInterpolateFn, + clamp: (value: number, min: number, max: number) => + Math.min(Math.max(value, min), max), +})); + +jest.mock("../useSmoothKeyboardHandler", () => ({ + useSmoothKeyboardHandler: jest.fn( + (h: { + onStart: (e: unknown) => void; + onMove: (e: unknown) => void; + onEnd: (e: unknown) => void; + }) => { + mockState().mockKeyboardHandlers.current = h; + }, + ), +})); + +jest.mock("../../../hooks", () => ({ + useFocusedInputHandler: jest.fn( + (h: { onSelectionChange: (...args: unknown[]) => void }) => { + mockState().mockSelectionHandler.current = h.onSelectionChange; + }, + ), + useReanimatedFocusedInput: jest.fn(() => ({ + input: mockState().mockInput, + update: jest.fn().mockResolvedValue(undefined), + })), + useWindowDimensions: jest.fn(() => ({ height: MOCK_SCREEN_H })), +})); + +jest.mock("../../hooks/useScrollState", () => ({ + __esModule: true, + default: jest.fn(() => ({ + offset: mockState().mockOffset, + layout: mockState().mockLayout, + size: mockState().mockSize, + })), +})); + +jest.mock("../../hooks/useCombinedRef", () => ({ + __esModule: true, + default: jest.fn(() => jest.fn()), +})); + +jest.mock("../../../utils/findNodeHandle", () => ({ + findNodeHandle: jest.fn(() => MOCK_SV), +})); + +jest.mock("../../../bindings", () => ({ + KeyboardControllerNative: { + viewPositionInWindow: jest.fn().mockResolvedValue({ y: 0 }), + }, +})); + +jest.mock("../../ScrollViewWithBottomPadding", () => { + const { forwardRef, createElement } = require("react"); + const { View: MockView } = require("react-native"); + + return { + __esModule: true, + default: forwardRef( + ( + props: { onLayout?: (e: never) => void; children?: unknown }, + ref: unknown, + ) => { + mockState().mockCapturedOnLayout.current = props.onLayout ?? null; + + return createElement(MockView, { ref }, props.children); + }, + ), + }; +}); diff --git a/src/components/KeyboardAwareScrollView/__fixtures__/testUtils.tsx b/src/components/KeyboardAwareScrollView/__fixtures__/testUtils.tsx new file mode 100644 index 0000000000..13e0dca5fb --- /dev/null +++ b/src/components/KeyboardAwareScrollView/__fixtures__/testUtils.tsx @@ -0,0 +1,151 @@ +import { act, render } from "@testing-library/react-native"; +import React from "react"; +import { View } from "react-native"; + +import type { LayoutChangeEvent } from "react-native"; +import type { + FocusedInputLayoutChangedEvent, + FocusedInputSelectionChangedEvent, + NativeEvent, +} from "react-native-keyboard-controller"; +import type { SharedValue } from "react-native-reanimated"; + +// --------------------------------------------------------------------------- +// Constants (derived from real device logs) +// --------------------------------------------------------------------------- +export const MOCK_SCREEN_HEIGHT = 928; +export const KEYBOARD_HEIGHT = 312; +export const BOTTOM_OFFSET = 62; +export const MOCK_SV_TARGET = 1469; +export const INPUT_TARGET_A = 1373; +export const INPUT_TARGET_B = 1395; + +export const INPUT_LAYOUT_A = { + absoluteY: 281.67, + height: 60.33, + y: 165.67, + x: 0, + absoluteX: 16, + width: 394.67, +}; + +export const INPUT_LAYOUT_B = { + absoluteY: 695.67, + height: 60.33, + y: 579.67, + x: 0, + absoluteX: 16, + width: 121, +}; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- +export type KeyboardHandlers = { + onStart: (e: NativeEvent) => void; + onMove: (e: NativeEvent) => void; + onEnd: (e: NativeEvent) => void; +}; + +export type SelectionHandler = (e: FocusedInputSelectionChangedEvent) => void; + +// --------------------------------------------------------------------------- +// Mock state +// --------------------------------------------------------------------------- +export const mockScrollTo = jest.fn(); +export const mockInput = { + value: null, +} as SharedValue; +export const mockOffset = { value: 0 } as SharedValue; +export const mockLayout = { + value: { width: 390, height: 812 }, +} as SharedValue<{ width: number; height: number }>; +export const mockSize = { + value: { width: 390, height: 2000 }, +} as SharedValue<{ width: number; height: number }>; + +// --------------------------------------------------------------------------- +// Captured handlers (populated on each render) +// --------------------------------------------------------------------------- +export const mockKeyboardHandlers: { current: KeyboardHandlers } = { + current: undefined as unknown as KeyboardHandlers, +}; +export const mockSelectionHandler: { current: SelectionHandler } = { + current: undefined as unknown as SelectionHandler, +}; +export const mockCapturedOnLayout: { + current: ((e: LayoutChangeEvent) => void) | null; +} = { current: null }; + +// --------------------------------------------------------------------------- +// Re-export shared utilities +// --------------------------------------------------------------------------- + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- +export const reset = () => { + mockScrollTo.mockClear(); + mockOffset.value = 0; + mockLayout.value = { width: 390, height: 812 }; + mockSize.value = { width: 390, height: 2000 }; + mockInput.value = null; + mockCapturedOnLayout.current = null; +}; + +export const inputEvent = ( + target: number, + layout: typeof INPUT_LAYOUT_A, +): FocusedInputLayoutChangedEvent => ({ + target, + parentScrollViewTarget: MOCK_SV_TARGET, + layout: { ...layout }, +}); + +export const selectionEvent = ( + target: number, + endY = 47, + endPosition = 0, +): FocusedInputSelectionChangedEvent => ({ + target, + selection: { + start: { x: 0, y: endY, position: endPosition }, + end: { x: 0, y: endY, position: endPosition }, + }, +}); + +export const kbEvent = (height: number, target: number): NativeEvent => ({ + height, + target, + duration: 285, + progress: KEYBOARD_HEIGHT > 0 ? height / KEYBOARD_HEIGHT : 0, +}); + +export const renderKeyboardAwareScrollView = async ( + bottomOffset = BOTTOM_OFFSET, +) => { + const KeyboardAwareScrollView = require("../index").default; + + render( + + + , + ); + + await act(async () => { + mockCapturedOnLayout.current?.({ + nativeEvent: { layout: { x: 0, y: 0, width: 390, height: 812 } }, + } as LayoutChangeEvent); + }); +}; + +export const lastScrollToY = (): number | undefined => { + const calls = mockScrollTo.mock.calls; + + if (calls.length === 0) { + return undefined; + } + + // scrollTo(ref, x, y, animated) + return calls[calls.length - 1][2] as number; +}; diff --git a/src/components/KeyboardAwareScrollView/__tests__/refocus.spec.tsx b/src/components/KeyboardAwareScrollView/__tests__/refocus.spec.tsx new file mode 100644 index 0000000000..b405161635 --- /dev/null +++ b/src/components/KeyboardAwareScrollView/__tests__/refocus.spec.tsx @@ -0,0 +1,114 @@ +import "../__fixtures__/mocks"; + +import { + INPUT_LAYOUT_B, + INPUT_TARGET_B, + KEYBOARD_HEIGHT, + inputEvent, + kbEvent, + lastScrollToY, + mockInput, + mockKeyboardHandlers, + mockOffset, + mockScrollTo, + mockSelectionHandler, + renderKeyboardAwareScrollView, + reset, + selectionEvent, +} from "../__fixtures__/testUtils"; + +beforeEach(() => { + reset(); +}); + +describe("KeyboardAwareScrollView — refocus same input", () => { + // Input B: point = 695.67 + 47 = 742.67 + // relativeScrollTo = 312 - (928 - 742.67) + 62 = 188.67 + + describe("iOS 15: refocus after keyboard hide", () => { + it("should use fresh selection data on refocus", async () => { + await renderKeyboardAwareScrollView(); + mockInput.value = inputEvent(INPUT_TARGET_B, INPUT_LAYOUT_B); + + // ---- First focus (cursor at y=47) ---- + mockKeyboardHandlers.current.onStart( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + mockSelectionHandler.current(selectionEvent(INPUT_TARGET_B, 47, 0)); + mockKeyboardHandlers.current.onMove( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + mockKeyboardHandlers.current.onEnd( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + mockOffset.value = 189; + + // ---- Keyboard hide ---- + mockKeyboardHandlers.current.onStart(kbEvent(0, INPUT_TARGET_B)); + mockKeyboardHandlers.current.onEnd(kbEvent(0, INPUT_TARGET_B)); + mockOffset.value = 0; + + // ---- Second focus (cursor moved to y=20) ---- + mockScrollTo.mockClear(); + + mockKeyboardHandlers.current.onStart( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + mockSelectionHandler.current(selectionEvent(INPUT_TARGET_B, 20, 5)); + mockKeyboardHandlers.current.onMove( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + + // point = 695.67 + 20 = 715.67 + // relativeScrollTo = 312 - (928 - 715.67) + 62 = 161.67 + expect(lastScrollToY()).toBeCloseTo(161.67, 0); + + mockKeyboardHandlers.current.onEnd( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + }); + }); + + describe("Android: selection not re-emitted on refocus", () => { + it("should use stale selection as fallback", async () => { + await renderKeyboardAwareScrollView(); + mockInput.value = inputEvent(INPUT_TARGET_B, INPUT_LAYOUT_B); + + // ---- First focus (selection arrives) ---- + mockSelectionHandler.current(selectionEvent(INPUT_TARGET_B, 47, 0)); + mockKeyboardHandlers.current.onStart( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + mockKeyboardHandlers.current.onMove( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + mockKeyboardHandlers.current.onEnd( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + mockOffset.value = 189; + + // ---- Keyboard hide ---- + mockKeyboardHandlers.current.onStart(kbEvent(0, INPUT_TARGET_B)); + mockKeyboardHandlers.current.onEnd(kbEvent(0, INPUT_TARGET_B)); + mockOffset.value = 0; + + // ---- Second focus (NO selection event — Android behavior) ---- + mockScrollTo.mockClear(); + + mockKeyboardHandlers.current.onStart( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + // No selectionHandler call — Android doesn't re-emit for same cursor + mockKeyboardHandlers.current.onMove( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + + // Must use stale selection (height=47), NOT full input height (60.33) + expect(lastScrollToY()).toBeCloseTo(188.67, 0); + + mockKeyboardHandlers.current.onEnd( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + }); + }); +}); diff --git a/src/components/KeyboardAwareScrollView/__tests__/selectionOrder.spec.tsx b/src/components/KeyboardAwareScrollView/__tests__/selectionOrder.spec.tsx new file mode 100644 index 0000000000..dff78b3250 --- /dev/null +++ b/src/components/KeyboardAwareScrollView/__tests__/selectionOrder.spec.tsx @@ -0,0 +1,84 @@ +import "../__fixtures__/mocks"; + +import { + INPUT_LAYOUT_A, + INPUT_LAYOUT_B, + INPUT_TARGET_A, + INPUT_TARGET_B, + KEYBOARD_HEIGHT, + inputEvent, + kbEvent, + lastScrollToY, + mockInput, + mockKeyboardHandlers, + mockScrollTo, + mockSelectionHandler, + renderKeyboardAwareScrollView, + reset, + selectionEvent, +} from "../__fixtures__/testUtils"; + +beforeEach(() => { + reset(); +}); + +describe("KeyboardAwareScrollView — selection order", () => { + // Input A: above the keyboard → no scroll needed + // Input B: below the keyboard → scroll needed + // + // visibleRect = 928 - 312 = 616 + // + // Input A: point = 281.67 + 47 = 328.67 + // visibleRect - point = 287.33 > bottomOffset(62) → NO scroll + // + // Input B: point = 695.67 + 47 = 742.67 + // visibleRect - point = -126.67 <= bottomOffset(62) → SCROLL + // relativeScrollTo = 312 - (928 - 742.67) + 62 = 188.67 + + describe("iOS 16+: onSelectionChange → onStart → onMove → onEnd", () => { + it("should not scroll when input is already visible", async () => { + await renderKeyboardAwareScrollView(); + mockInput.value = inputEvent(INPUT_TARGET_A, INPUT_LAYOUT_A); + + mockSelectionHandler.current(selectionEvent(INPUT_TARGET_A)); + mockKeyboardHandlers.current.onStart( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_A), + ); + + mockScrollTo.mockClear(); + mockKeyboardHandlers.current.onMove( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_A), + ); + + expect(mockScrollTo).not.toHaveBeenCalled(); + + mockKeyboardHandlers.current.onEnd( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_A), + ); + }); + }); + + describe("iOS 15: onStart → onSelectionChange → onMove → onEnd", () => { + it("should use deferred selection and scroll correctly", async () => { + await renderKeyboardAwareScrollView(); + mockInput.value = inputEvent(INPUT_TARGET_B, INPUT_LAYOUT_B); + + mockKeyboardHandlers.current.onStart( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + mockSelectionHandler.current(selectionEvent(INPUT_TARGET_B)); + + mockScrollTo.mockClear(); + mockKeyboardHandlers.current.onMove( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + + // Must use selection height (47), not full input (60.33) + expect(lastScrollToY()).toBeCloseTo(188.67, 0); + + mockKeyboardHandlers.current.onEnd( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + }); + }); +}); diff --git a/src/components/KeyboardAwareScrollView/__tests__/toolbar.spec.tsx b/src/components/KeyboardAwareScrollView/__tests__/toolbar.spec.tsx new file mode 100644 index 0000000000..52cbf19d66 --- /dev/null +++ b/src/components/KeyboardAwareScrollView/__tests__/toolbar.spec.tsx @@ -0,0 +1,63 @@ +import "../__fixtures__/mocks"; + +import { + INPUT_LAYOUT_A, + INPUT_LAYOUT_B, + INPUT_TARGET_A, + INPUT_TARGET_B, + KEYBOARD_HEIGHT, + inputEvent, + kbEvent, + lastScrollToY, + mockInput, + mockKeyboardHandlers, + mockScrollTo, + mockSelectionHandler, + renderKeyboardAwareScrollView, + reset, + selectionEvent, +} from "../__fixtures__/testUtils"; + +beforeEach(() => { + reset(); +}); + +describe("KeyboardAwareScrollView — toolbar focus switch", () => { + it("should scroll to newly focused input via deferred selection", async () => { + await renderKeyboardAwareScrollView(); + mockInput.value = inputEvent(INPUT_TARGET_A, INPUT_LAYOUT_A); + + // ---- Focus input A — keyboard opens, no scroll needed ---- + mockSelectionHandler.current(selectionEvent(INPUT_TARGET_A)); + mockKeyboardHandlers.current.onStart( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_A), + ); + mockKeyboardHandlers.current.onMove( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_A), + ); + mockKeyboardHandlers.current.onEnd( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_A), + ); + + // ---- Toolbar moves focus to input B (keyboard stays visible) ---- + // Native sends onStart → onEnd (no animation, duration=0) + // then onSelectionChange arrives + mockInput.value = inputEvent(INPUT_TARGET_B, INPUT_LAYOUT_B); + mockScrollTo.mockClear(); + + mockKeyboardHandlers.current.onStart( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + mockKeyboardHandlers.current.onEnd( + kbEvent(KEYBOARD_HEIGHT, INPUT_TARGET_B), + ); + + // Selection arrives AFTER onEnd — the pendingSelectionForFocus flag + // must survive onEnd so the deferred scroll runs here + mockSelectionHandler.current(selectionEvent(INPUT_TARGET_B, 47, 0)); + + // point = 695.67 + 47 = 742.67 + // relativeScrollTo = 312 - (928 - 742.67) + 62 = 188.67 + expect(lastScrollToY()).toBeCloseTo(188.67, 0); + }); +}); diff --git a/src/components/KeyboardAwareScrollView/index.tsx b/src/components/KeyboardAwareScrollView/index.tsx index 162e1fec57..da5bf242a4 100644 --- a/src/components/KeyboardAwareScrollView/index.tsx +++ b/src/components/KeyboardAwareScrollView/index.tsx @@ -30,34 +30,15 @@ import ScrollViewWithBottomPadding from "../ScrollViewWithBottomPadding"; import { useSmoothKeyboardHandler } from "./useSmoothKeyboardHandler"; import { debounce, scrollDistanceWithRespectToSnapPoints } from "./utils"; -import type { AnimatedScrollViewComponent } from "../ScrollViewWithBottomPadding"; -import type { - LayoutChangeEvent, - ScrollView, - ScrollViewProps, -} from "react-native"; +import type { LayoutChangeEvent, ScrollView } from "react-native"; import type { FocusedInputLayoutChangedEvent, FocusedInputSelectionChangedEvent, + KeyboardAwareScrollViewProps, + KeyboardAwareScrollViewRef, NativeEvent, } from "react-native-keyboard-controller"; -export type KeyboardAwareScrollViewProps = { - /** The distance between the keyboard and the caret inside a focused `TextInput` when the keyboard is shown. Default is `0`. */ - 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`. */ - enabled?: boolean; - /** Adjusting the bottom spacing of KeyboardAwareScrollView. Default is `0`. */ - extraKeyboardSpace?: number; - /** Custom component for `ScrollView`. Default is `ScrollView`. */ - ScrollViewComponent?: AnimatedScrollViewComponent; -} & ScrollViewProps; -export type KeyboardAwareScrollViewRef = { - assureFocusedInputVisible: () => void; -} & ScrollView; - // 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 @@ -151,6 +132,7 @@ const KeyboardAwareScrollView = forwardRef< useSharedValue(null); const ghostViewSpace = useSharedValue(-1); const pendingSelectionForFocus = useSharedValue(false); + const selectionUpdatedSinceHide = useSharedValue(false); const scrollViewPageY = useSharedValue(0); const { height } = useWindowDimensions(); @@ -339,8 +321,9 @@ const KeyboardAwareScrollView = forwardRef< const latestSelection = lastSelection.value?.selection; lastSelection.value = e; + selectionUpdatedSinceHide.value = true; - if (e.target !== lastTarget) { + if (e.target !== lastTarget || pendingSelectionForFocus.value) { if (pendingSelectionForFocus.value) { // selection arrived after onStart - complete the deferred setup pendingSelectionForFocus.value = false; @@ -428,14 +411,22 @@ const KeyboardAwareScrollView = forwardRef< if (focusWasChanged) { tag.value = e.target; - if (lastSelection.value?.target === e.target) { - // selection arrived before onStart - use it to update layout + if ( + lastSelection.value?.target === e.target && + selectionUpdatedSinceHide.value + ) { + // fresh selection arrived before onStart - use it to update layout updateLayoutFromSelection(); pendingSelectionForFocus.value = false; } else { - // selection hasn't arrived yet for the new target. - // use input layout as-is; will be refined when selection arrives. - if (input.value) { + // selection hasn't arrived yet for the new target (iOS 15), + // or it's stale from previous session (Android refocus same input). + // Use stale selection as best-effort fallback if available for same target, + // otherwise fall back to full input layout. + // Will be corrected if a fresh onSelectionChange arrives. + if (lastSelection.value?.target === e.target) { + updateLayoutFromSelection(); + } else if (input.value) { layout.value = input.value; } pendingSelectionForFocus.value = true; @@ -484,7 +475,14 @@ const KeyboardAwareScrollView = forwardRef< scrollPosition.value = position.value; if (e.height === 0) { - lastSelection.value = null; + selectionUpdatedSinceHide.value = false; + } else if (keyboardWillAppear.value) { + // keyboard fully shown after appearing from hidden state — clear + // pending flag to prevent leaking into next focus-change session. + // Only when the keyboard was actually appearing (not a focus switch + // with same keyboard height), otherwise we'd clear the flag before + // onSelectionChange has a chance to process it. + pendingSelectionForFocus.value = false; } syncKeyboardFrame(e); diff --git a/src/components/KeyboardAwareScrollView/types.ts b/src/components/KeyboardAwareScrollView/types.ts new file mode 100644 index 0000000000..7fe4faf950 --- /dev/null +++ b/src/components/KeyboardAwareScrollView/types.ts @@ -0,0 +1,18 @@ +import type { AnimatedScrollViewComponent } from "../ScrollViewWithBottomPadding"; +import type { ScrollView, ScrollViewProps } from "react-native"; + +export type KeyboardAwareScrollViewProps = { + /** The distance between the keyboard and the caret inside a focused `TextInput` when the keyboard is shown. Default is `0`. */ + 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`. */ + enabled?: boolean; + /** Adjusting the bottom spacing of KeyboardAwareScrollView. Default is `0`. */ + extraKeyboardSpace?: number; + /** Custom component for `ScrollView`. Default is `ScrollView`. */ + ScrollViewComponent?: AnimatedScrollViewComponent; +} & ScrollViewProps; +export type KeyboardAwareScrollViewRef = { + assureFocusedInputVisible: () => void; +} & ScrollView; diff --git a/src/components/index.ts b/src/components/index.ts index 3a05547bbe..8ba53b29c0 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -11,6 +11,6 @@ export type { KeyboardStickyViewProps } from "./KeyboardStickyView"; export type { KeyboardAwareScrollViewProps, KeyboardAwareScrollViewRef, -} from "./KeyboardAwareScrollView"; +} from "./KeyboardAwareScrollView/types"; export type { KeyboardToolbarProps } from "./KeyboardToolbar"; export type { KeyboardChatScrollViewProps } from "./KeyboardChatScrollView/types"; diff --git a/tsconfig.build.json b/tsconfig.build.json index b5b22b4aa5..050013e145 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,4 +1,11 @@ { "extends": "./tsconfig", - "exclude": ["example", "FabricExample", "docs", "e2e"] + "exclude": [ + "example", + "FabricExample", + "docs", + "e2e", + "**/__tests__", + "**/__fixtures__" + ] }