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
2 changes: 2 additions & 0 deletions docs/docs/api/components/keyboard-chat-scroll-view.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { ScrollView } from "react-native-gesture-handler";

When `true`, freezes all keyboard-driven layout changes. This is useful when dismissing the keyboard to show a custom input view (such as an emoji picker or bottom sheet) — it prevents the chat content from shifting while the transition happens.

Accepts either a plain `boolean` or a [Reanimated `SharedValue<boolean>`](https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/). Using a `SharedValue` allows you to toggle freezing from the UI thread (e.g., inside a worklet or gesture handler) without triggering a React re-render.

### `inverted`

Set to `true` if your list uses the `inverted` prop (the standard pattern for chat-style lists where the newest messages appear at the bottom).
Expand Down
22 changes: 22 additions & 0 deletions docs/docs/guides/building-chat-app.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,28 @@ const onKeyboardPress = () => {

When `freeze` is `true`, all keyboard-driven layout changes (padding, content offset, scroll position) are paused.

:::tip SharedValue support
`freeze` also accepts a [Reanimated `SharedValue<boolean>`](https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/). This is useful when you need to toggle freezing synchronously from a worklet — for example, inside a gesture handler:

```tsx
import { useSharedValue } from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";

const freeze = useSharedValue(false);

const gesture = Gesture.Pan().onStart(() => {
"worklet";
// freeze synchronously on the UI thread before the keyboard starts dismissing
freeze.value = true;
});

<KeyboardChatScrollView freeze={freeze}>
{/* ...messages... */}
</KeyboardChatScrollView>;
```

:::

### Handling a growing multiline input

If your composer has a `multiline` `TextInput` that grows as the user types, you need to tell `KeyboardChatScrollView` about the extra space it takes up — otherwise the component doesn't know the scrollable range has changed and the bottom messages can get clipped under the input.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { ScrollView } from "react-native-gesture-handler";

When `true`, freezes all keyboard-driven layout changes. This is useful when dismissing the keyboard to show a custom input view (such as an emoji picker or bottom sheet) — it prevents the chat content from shifting while the transition happens.

Accepts either a plain `boolean` or a [Reanimated `SharedValue<boolean>`](https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/). Using a `SharedValue` allows you to toggle freezing from the UI thread (e.g., inside a worklet or gesture handler) without triggering a React re-render.

### `inverted`

Set to `true` if your list uses the `inverted` prop (the standard pattern for chat-style lists where the newest messages appear at the bottom).
Expand Down
22 changes: 22 additions & 0 deletions docs/versioned_docs/version-1.21.0/guides/building-chat-app.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,28 @@ const onKeyboardPress = () => {

When `freeze` is `true`, all keyboard-driven layout changes (padding, content offset, scroll position) are paused.

:::tip SharedValue support
`freeze` also accepts a [Reanimated `SharedValue<boolean>`](https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/). This is useful when you need to toggle freezing synchronously from a worklet — for example, inside a gesture handler:

```tsx
import { useSharedValue } from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";

const freeze = useSharedValue(false);

const gesture = Gesture.Pan().onStart(() => {
"worklet";
// freeze synchronously on the UI thread before the keyboard starts dismissing
freeze.value = true;
});

<KeyboardChatScrollView freeze={freeze}>
{/* ...messages... */}
</KeyboardChatScrollView>;
```

:::

### Handling a growing multiline input

If your composer has a `multiline` `TextInput` that grows as the user types, you need to tell `KeyboardChatScrollView` about the extra space it takes up — otherwise the component doesn't know the scrollable range has changed and the bottom messages can get clipped under the input.
Expand Down
7 changes: 5 additions & 2 deletions src/components/KeyboardChatScrollView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const KeyboardChatScrollView = forwardRef<
) => {
const scrollViewRef = useAnimatedRef<Reanimated.ScrollView>();
const onRef = useCombinedRef(ref, scrollViewRef);
const freezeSV = useDerivedValue(() =>
typeof freeze === "boolean" ? freeze : freeze.value,
);
const {
padding,
currentHeight,
Expand All @@ -55,7 +58,7 @@ const KeyboardChatScrollView = forwardRef<
} = useChatKeyboard(scrollViewRef, {
inverted,
keyboardLiftBehavior,
freeze,
freeze: freezeSV,
offset,
blankSpace,
extraContentPadding,
Expand All @@ -72,7 +75,7 @@ const KeyboardChatScrollView = forwardRef<
contentOffsetY,
inverted,
keyboardLiftBehavior,
freeze,
freeze: freezeSV,
});

const totalPadding = useDerivedValue(() =>
Expand Down
2 changes: 1 addition & 1 deletion src/components/KeyboardChatScrollView/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export type KeyboardChatScrollViewProps = {
*
* Default is `false`.
*/
freeze?: boolean;
freeze?: boolean | SharedValue<boolean>;
/**
* A shared value representing additional padding from external elements
* (e.g., a growing multiline `TextInput` in a `KeyboardStickyView`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ type RenderOptions = Omit<
Parameters<typeof useChatKeyboard>[1],
"freeze" | "offset" | "blankSpace" | "extraContentPadding"
> & {
freeze?: boolean;
freeze?: boolean | SharedValue<boolean>;
offset?: number;
blankSpace?: SharedValue<number>;
extraContentPadding?: SharedValue<number>;
Expand All @@ -99,9 +99,11 @@ export function createRender(modulePath: string) {
return renderHook(() => {
const ref = useAnimatedRef<Reanimated.ScrollView>();

const freeze = options.freeze ?? false;

return mod.useChatKeyboard(ref, {
...options,
freeze: options.freeze ?? false,
freeze: typeof freeze === "boolean" ? sv(freeze) : freeze,
offset: options.offset ?? 0,
blankSpace: options.blankSpace ?? sv(0),
extraContentPadding: options.extraContentPadding ?? sv(0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function useChatKeyboard(
onStart: (e) => {
"worklet";

if (freeze) {
if (freeze.value) {
return;
}

Expand Down Expand Up @@ -229,7 +229,7 @@ function useChatKeyboard(
onEnd: (e) => {
"worklet";

if (freeze) {
if (freeze.value) {
return;
}

Expand All @@ -242,7 +242,7 @@ function useChatKeyboard(
padding.value = effective;
},
},
[inverted, keyboardLiftBehavior, freeze, offset, extraContentPadding],
[inverted, keyboardLiftBehavior, offset, extraContentPadding],
);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function useChatKeyboard(
onStart: (e) => {
"worklet";

if (freeze) {
if (freeze.value) {
return;
}

Expand Down Expand Up @@ -166,7 +166,7 @@ function useChatKeyboard(
onMove: (e) => {
"worklet";

if (freeze) {
if (freeze.value) {
return;
}

Expand Down Expand Up @@ -340,7 +340,7 @@ function useChatKeyboard(
onEnd: (e) => {
"worklet";

if (freeze) {
if (freeze.value) {
return;
}

Expand All @@ -358,7 +358,7 @@ function useChatKeyboard(
}
},
},
[inverted, keyboardLiftBehavior, freeze, offset],
[inverted, keyboardLiftBehavior, offset],
);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type KeyboardLiftBehavior = "always" | "whenAtEnd" | "persistent" | "never";
type UseChatKeyboardOptions = {
inverted: boolean;
keyboardLiftBehavior: KeyboardLiftBehavior;
freeze: boolean;
freeze: SharedValue<boolean>;
offset: number;
blankSpace: SharedValue<number>;
/** Extra content padding shared value — needed on iOS to correctly clamp contentOffset. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,23 @@ jest.mock("react-native-reanimated", () => ({

type RenderOptions = Omit<
Parameters<typeof useExtraContentPadding>[0],
"scrollViewRef" | "blankSpace"
"scrollViewRef" | "blankSpace" | "freeze"
> & {
blankSpace?: SharedValue<number>;
freeze: boolean | SharedValue<boolean>;
};

export const createRender = () => {
return function render(options: RenderOptions) {
return renderHook(() => {
const ref = useAnimatedRef<Reanimated.ScrollView>();
const { freeze, ...rest } = options;

useExtraContentPadding({
scrollViewRef: ref,
blankSpace: options.blankSpace ?? sv(0),
...options,
freeze: typeof freeze === "boolean" ? sv(freeze) : freeze,
...rest,
});
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type UseExtraContentPaddingOptions = {
contentOffsetY?: SharedValue<number>;
inverted: boolean;
keyboardLiftBehavior: KeyboardLiftBehavior;
freeze: boolean;
freeze: SharedValue<boolean>;
};

/**
Expand Down Expand Up @@ -81,7 +81,7 @@ function useExtraContentPadding(options: UseExtraContentPaddingOptions): void {
useAnimatedReaction(
() => extraContentPadding.value,
(current, previous) => {
if (freeze || previous === null) {
if (freeze.value || previous === null) {
return;
}

Expand Down Expand Up @@ -141,7 +141,7 @@ function useExtraContentPadding(options: UseExtraContentPaddingOptions): void {
scrollToTarget(target);
}
},
[inverted, keyboardLiftBehavior, freeze],
[inverted, keyboardLiftBehavior],
);
}

Expand Down
Loading