Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions src/components/RNMarkdownTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {ForwardedRef} from 'react';
import React, {useCallback, useEffect, useRef} from 'react';
import {View} from 'react-native';
import Animated, {useSharedValue} from 'react-native-reanimated';
import useLandscapeOnBlurProxy from '@hooks/useLandscapeOnBlurProxy';
import useShortMentionsList from '@hooks/useShortMentionsList';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
Expand Down Expand Up @@ -36,6 +37,8 @@ function RNMarkdownTextInputWithRef({maxLength, parser, ref, forwardedFSClass =
// Expose the ref to the parent component
React.useImperativeHandle<AnimatedMarkdownTextInputRef | null, AnimatedMarkdownTextInputRef | null>(ref, () => inputRef.current);

const handleBlur = useLandscapeOnBlurProxy(inputRef, props.onBlur);

// Check if the cursor is at the end of the text
const isCursorAtEnd = props.selection && props.value && props.selection.start === props.value.length;

Expand Down Expand Up @@ -96,6 +99,7 @@ function RNMarkdownTextInputWithRef({maxLength, parser, ref, forwardedFSClass =
* If maxLength is not set, we should set it to CONST.MAX_COMMENT_LENGTH + 1, to avoid parsing markdown for large text
*/
maxLength={maxLength ?? CONST.MAX_COMMENT_LENGTH + 1}
onBlur={handleBlur}
/>
</View>
);
Expand Down
23 changes: 23 additions & 0 deletions src/hooks/useLandscapeOnBlurProxy/index.android.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import useIsInLandscapeMode from '@hooks/useIsInLandscapeMode';
import usePrevious from '@hooks/usePrevious';
import type {UseLandscapeOnBlurProxy} from './types';

// During a portrait → landscape rotation the input briefly ends up behind the keyboard
// while KeyboardAvoidingView catches up, and native blurs it as a result. When that blur
// fires we re-focus the input after a short delay — long enough for KAV to reposition so
// the input is on-screen again, otherwise the re-focus gets clobbered by the same issue.
const ROTATION_REFOCUS_DELAY_MS = 100;

const useLandscapeOnBlurProxy: UseLandscapeOnBlurProxy = (inputRef, onBlur) => {
const isInLandscapeMode = useIsInLandscapeMode();
const prevIsInLandscapeMode = usePrevious(isInLandscapeMode);

return (e) => {
if (prevIsInLandscapeMode !== isInLandscapeMode && isInLandscapeMode) {
setTimeout(() => inputRef.current?.focus?.(), ROTATION_REFOCUS_DELAY_MS);
Comment on lines +16 to +17
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate refocus to rotation-triggered blur only

The current condition refocuses on any blur while prevIsInLandscapeMode !== isInLandscapeMode && isInLandscapeMode, not specifically the blur caused by rotation. That means after rotating to landscape, a normal user blur (e.g., tapping outside to dismiss the keyboard) can be immediately undone by the timeout focus, causing the keyboard to pop back up and making blur behavior unreliable. This is especially visible when the OS does not emit an automatic blur during rotation, because the first intentional blur gets treated as a rotation blur.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While technically true, this is designed to address a specific case because that blur occurs on android. Couldn't reproduce scenario described above while testing.

}
onBlur?.(e);
};
};

export default useLandscapeOnBlurProxy;
7 changes: 7 additions & 0 deletions src/hooks/useLandscapeOnBlurProxy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type {UseLandscapeOnBlurProxy} from './types';

// The rotation-refocus workaround is only needed on Android — iOS and web don't lose focus
// when the orientation flips, so we pass the caller's onBlur through unchanged.
const useLandscapeOnBlurProxy: UseLandscapeOnBlurProxy = (_inputRef, onBlur) => onBlur;

export default useLandscapeOnBlurProxy;
10 changes: 10 additions & 0 deletions src/hooks/useLandscapeOnBlurProxy/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {RefObject} from 'react';
import type {FocusEvent} from 'react-native';

type FocusableRef = RefObject<{focus?: () => void} | null>;

type OnBlurHandler = (e: FocusEvent) => void;

type UseLandscapeOnBlurProxy = (inputRef: FocusableRef, onBlur?: OnBlurHandler) => OnBlurHandler | undefined;

export type {FocusableRef, OnBlurHandler, UseLandscapeOnBlurProxy};
Loading