Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: blur the composer focus when open context menu #47621

Merged
merged 14 commits into from
Oct 11, 2024
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
15 changes: 14 additions & 1 deletion src/libs/ReportActionComposeFocusManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import navigationRef from './Navigation/navigationRef';
type FocusCallback = (shouldFocusForNonBlurInputOnTapOutside?: boolean) => void;

const composerRef: MutableRefObject<TextInput | null> = React.createRef<TextInput>();
const editComposerRef = React.createRef<TextInput>();

// There are two types of composer: general composer (edit composer) and main composer.
// The general composer callback will take priority if it exists.
const editComposerRef: MutableRefObject<TextInput | null> = React.createRef<TextInput>();
// There are two types of focus callbacks: priority and general
// Priority callback would take priority if it existed
let priorityFocusCallback: FocusCallback | null = null;
Expand Down Expand Up @@ -58,6 +61,7 @@ function focus(shouldFocusForNonBlurInputOnTapOutside?: boolean) {
*/
function clear(isPriorityCallback = false) {
if (isPriorityCallback) {
editComposerRef.current = null;
priorityFocusCallback = null;
} else {
focusCallback = null;
Expand All @@ -78,6 +82,14 @@ function isEditFocused(): boolean {
return !!editComposerRef.current?.isFocused();
}

/**
* Utility function to blur both main composer and edit composer.
*/
function blurAll(): void {
composerRef.current?.blur();
editComposerRef.current?.blur();
}

export default {
composerRef,
onComposerFocus,
Expand All @@ -86,4 +98,5 @@ export default {
isFocused,
editComposerRef,
isEditFocused,
blurAll,
};
2 changes: 2 additions & 0 deletions src/pages/home/report/ReportActionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import Navigation from '@libs/Navigation/Navigation';
import Permissions from '@libs/Permissions';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import SelectionScraper from '@libs/SelectionScraper';
Expand Down Expand Up @@ -335,6 +336,7 @@ function ReportActionItem({
return;
}

ReportActionComposeFocusManager.blurAll();
setIsContextMenuActive(true);
const selection = SelectionScraper.getCurrentSelection();
ReportActionContextMenu.showContextMenu(
Expand Down
27 changes: 19 additions & 8 deletions src/pages/home/report/ReportActionItemMessageEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ function ReportActionItemMessageEdit(
};
}, []);

useEffect(
// Remove focus callback on unmount to avoid stale callbacks
() => {
if (textInputRef.current) {
ReportActionComposeFocusManager.editComposerRef.current = textInputRef.current;
}
return () => {
if (ReportActionComposeFocusManager.editComposerRef.current !== textInputRef.current) {
return;
}
ReportActionComposeFocusManager.clear(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. There is already a code used for handling unmount as mentioned here. Let us use that code to avoid duplicate coding.
  2. Also, shouldn't it be ReportActionComposeFocusManager.clear(); here as we do not want to reset the editcomposer if focus already exists on another edit item. Here is a test video demonstrating the problem:
47621-clear-issue-1.mp4

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here's the result after refactoring

Screen.Recording.2024-10-01.at.11.21.52.mov

};
},
[],
);

// We consider the report action active if it's focused, its emoji picker is open or its context menu is open
const isActive = useCallback(
() => isFocusedRef.current || EmojiPickerAction.isActive(action.reportActionID) || ReportActionContextMenu.isActiveReportAction(action.reportActionID),
Expand All @@ -188,14 +204,6 @@ function ReportActionItemMessageEdit(
}, true);
}, [focus]);

useEffect(
// Remove focus callback on unmount to avoid stale callbacks
() => () => {
ReportActionComposeFocusManager.clear(true);
},
[],
);

useEffect(
() => {
if (isInitialMount.current) {
Expand Down Expand Up @@ -516,6 +524,9 @@ function ReportActionItemMessageEdit(
style={[styles.textInputCompose, styles.flex1, styles.bgTransparent]}
onFocus={() => {
setIsFocused(true);
if (textInputRef.current) {
ReportActionComposeFocusManager.editComposerRef.current = textInputRef.current;
Copy link
Contributor

Choose a reason for hiding this comment

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

Let us reset the editComposerRef when the focus is lost to avoid dangling references. One way to do this is by resetting it inside ReportActionComposeFocusManager.clear.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we shouldn't reset editComposerRef since we use it to restore focus in case users open edit emoji picker then dismiss it

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe I am missing something here but why would we need editComposerRef after ReportActionComposeFocusManager.clear is called here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, when ReportActionComposeFocusManager.clear we can reset editComposerRef, but not when the focus is lost. So you mean to reset it when the edit composer disappears (not when it's blurred), right?

Copy link
Contributor

Choose a reason for hiding this comment

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

when ReportActionComposeFocusManager.clear we can reset editComposerRef

Yes, that's what I meant

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please check if we also have to set the editComposerRef on component mount of ReportActionItemMessageEdit because I ran into issues where the edit composer did not blur as editComposerRef was not set at all? Here is a video that demonstrates this. Please have a look.

47621-mweb-safari-blurissue.mp4

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me check this and get back to you.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rojiphil after testing, there's a small change needed to fix this issue. As ReportActionComposeFocusManager.clear(); is triggered in both main and edit composer, when users open new edit composer, editComposerRef is set, then the logic clear

in main composer would reset editComposerRef

-> It’s wrong since the edit composer is still open

I think we should set/reset editComposerRef in ReportActionItemMessageEdit. I have pushed the latest commit with the change.

}
InteractionManager.runAfterInteractions(() => {
requestAnimationFrame(() => {
reportScrollManager.scrollToIndex(index, true);
Expand Down
Loading