Skip to content

Commit

Permalink
Merge pull request #35572 from ntdiary/fix-restore-focus-29011
Browse files Browse the repository at this point in the history
phase1: fix emoji picker refocus issue
  • Loading branch information
Julesssss committed Apr 5, 2024
2 parents abcb6e1 + 2a46033 commit 5da3daf
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 33 deletions.
5 changes: 5 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,11 @@ const CONST = {
RIGHT: 'right',
},
POPOVER_MENU_PADDING: 8,
RESTORE_FOCUS_TYPE: {
DEFAULT: 'default',
DELETE: 'delete',
PRESERVE: 'preserve',
},
},
TIMING: {
CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action',
Expand Down
2 changes: 2 additions & 0 deletions src/components/EmojiPicker/EmojiPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ const EmojiPicker = forwardRef((props, ref) => {
anchorDimensions={emojiAnchorDimension.current}
avoidKeyboard
shoudSwitchPositionIfOverflow
shouldEnableNewFocusManagement
restoreFocusType={CONST.MODAL.RESTORE_FOCUS_TYPE.DELETE}
>
<EmojiPickerMenu
onEmojiSelected={selectEmoji}
Expand Down
35 changes: 23 additions & 12 deletions src/components/Modal/BaseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import useNativeDriver from '@libs/useNativeDriver';
import variables from '@styles/variables';
import * as Modal from '@userActions/Modal';
import CONST from '@src/CONST';
import ModalContent from './ModalContent';
import type BaseModalProps from './types';

function BaseModal(
Expand Down Expand Up @@ -42,6 +43,8 @@ function BaseModal(
children,
shouldUseCustomBackdrop = false,
onBackdropPress,
shouldEnableNewFocusManagement = false,
restoreFocusType,
}: BaseModalProps,
ref: React.ForwardedRef<View>,
) {
Expand All @@ -56,6 +59,14 @@ function BaseModal(
const isVisibleRef = useRef(isVisible);
const wasVisible = usePrevious(isVisible);

const modalId = useMemo(() => ComposerFocusManager.getId(), []);
const saveFocusState = () => {
if (shouldEnableNewFocusManagement) {
ComposerFocusManager.saveFocusState(modalId);
}
ComposerFocusManager.resetReadyToFocus(modalId);
};

/**
* Hides modal
* @param callHideCallback - Should we call the onModalHide callback
Expand All @@ -70,11 +81,9 @@ function BaseModal(
onModalHide();
}
Modal.onModalDidClose();
if (!fullscreen) {
ComposerFocusManager.setReadyToFocus();
}
ComposerFocusManager.refocusAfterModalFullyClosed(modalId, restoreFocusType);
},
[shouldSetModalVisibility, onModalHide, fullscreen],
[shouldSetModalVisibility, onModalHide, restoreFocusType, modalId],
);

useEffect(() => {
Expand Down Expand Up @@ -126,7 +135,7 @@ function BaseModal(
};

const handleDismissModal = () => {
ComposerFocusManager.setReadyToFocus();
ComposerFocusManager.setReadyToFocus(modalId);
};

const {
Expand Down Expand Up @@ -190,7 +199,7 @@ function BaseModal(
onModalShow={handleShowModal}
propagateSwipe={propagateSwipe}
onModalHide={hideModal}
onModalWillShow={() => ComposerFocusManager.resetReadyToFocus()}
onModalWillShow={saveFocusState}
onDismiss={handleDismissModal}
onSwipeComplete={() => onClose?.()}
swipeDirection={swipeDirection}
Expand All @@ -214,12 +223,14 @@ function BaseModal(
avoidKeyboard={avoidKeyboard}
customBackdrop={shouldUseCustomBackdrop ? <Overlay onPress={handleBackdropPress} /> : undefined}
>
<View
style={[styles.defaultModalContainer, modalPaddingStyles, modalContainerStyle, !isVisible && styles.pointerEventsNone]}
ref={ref}
>
<ColorSchemeWrapper>{children}</ColorSchemeWrapper>
</View>
<ModalContent onDismiss={handleDismissModal}>
<View
style={[styles.defaultModalContainer, modalPaddingStyles, modalContainerStyle, !isVisible && styles.pointerEventsNone]}
ref={ref}
>
<ColorSchemeWrapper>{children}</ColorSchemeWrapper>
</View>
</ModalContent>
</ReactNativeModal>
);
}
Expand Down
23 changes: 23 additions & 0 deletions src/components/Modal/ModalContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type {ReactNode} from 'react';
import React from 'react';

type ModalContentProps = {
/** Modal contents */
children: ReactNode;

/**
* Callback method fired after modal content is unmounted.
* isVisible is not enough to cover all modal close cases,
* such as closing the attachment modal through the browser's back button.
* */
onDismiss: () => void;
};

function ModalContent({children, onDismiss = () => {}}: ModalContentProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(() => () => onDismiss?.(), []);
return children;
}
ModalContent.displayName = 'ModalContent';

export default ModalContent;
10 changes: 0 additions & 10 deletions src/components/Modal/index.android.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import React from 'react';
import {AppState} from 'react-native';
import ComposerFocusManager from '@libs/ComposerFocusManager';
import BaseModal from './BaseModal';
import type BaseModalProps from './types';

AppState.addEventListener('focus', () => {
ComposerFocusManager.setReadyToFocus();
});

AppState.addEventListener('blur', () => {
ComposerFocusManager.resetReadyToFocus();
});

// Only want to use useNativeDriver on Android. It has strange flashes issue on IOS
// https://github.com/react-native-modal/react-native-modal#the-modal-flashes-in-a-weird-way-when-animating
function Modal({useNativeDriver = true, ...rest}: BaseModalProps) {
Expand Down
9 changes: 9 additions & 0 deletions src/components/Modal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ type BaseModalProps = Partial<ModalProps> & {

/** Should we use a custom backdrop for the modal? (This prevents focus issues on desktop) */
shouldUseCustomBackdrop?: boolean;

/**
* Whether the modal should enable the new focus manager.
* We are attempting to migrate to a new refocus manager, adding this property for gradual migration.
* */
shouldEnableNewFocusManagement?: boolean;

/** How to re-focus after the modal is dismissed */
restoreFocusType?: ValueOf<typeof CONST.MODAL.RESTORE_FOCUS_TYPE>;
};

export default BaseModalProps;
Expand Down
Loading

0 comments on commit 5da3daf

Please sign in to comment.