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
1 change: 1 addition & 0 deletions src/components/DatePicker/DatePickerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function DatePickerModal({
shouldSwitchPositionIfOverflow
shouldEnableNewFocusManagement
shouldMeasureAnchorPositionFromTop={shouldPositionFromTop}
shouldSkipRemeasurement
>
<CalendarPicker
minDate={minDate}
Expand Down
1 change: 1 addition & 0 deletions src/components/EmojiPicker/EmojiPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ function EmojiPicker({viewportOffsetTop}: EmojiPickerProps, ref: ForwardedRef<Em
shouldSwitchPositionIfOverflow
shouldEnableNewFocusManagement
restoreFocusType={CONST.MODAL.RESTORE_FOCUS_TYPE.DELETE}
shouldSkipRemeasurement
>
<FocusTrapForModal active={isEmojiPickerVisible}>
<View>
Expand Down
20 changes: 12 additions & 8 deletions src/components/EmojiPicker/EmojiPickerMenu/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import useSingleExecution from '@hooks/useSingleExecution';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as EmojiUtils from '@libs/EmojiUtils';
import type {EmojiPickerList, EmojiPickerListItem} from '@libs/EmojiUtils';
import {getRemovedSkinToneEmoji} from '@libs/EmojiUtils';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import BaseEmojiPickerMenu from './BaseEmojiPickerMenu';
Expand Down Expand Up @@ -44,7 +45,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r
} = useEmojiPickerMenu();
const StyleUtils = useStyleUtils();

const updateEmojiList = (emojiData: EmojiUtils.EmojiPickerList | Emoji[], headerData: number[] = []) => {
const updateEmojiList = (emojiData: EmojiPickerList | Emoji[], headerData: number[] = []) => {
setFilteredEmojis(emojiData);
setHeaderIndices(headerData);

Expand All @@ -68,17 +69,20 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r
}
}, 300);

const scrollToHeader = (headerIndex: number) => {
const calculatedOffset = Math.floor(headerIndex / CONST.EMOJI_NUM_PER_ROW) * CONST.EMOJI_PICKER_HEADER_HEIGHT;
emojiListRef.current?.scrollToOffset({offset: calculatedOffset, animated: true});
};
const scrollToHeader = useCallback(
(headerIndex: number) => {
const calculatedOffset = Math.floor(headerIndex / CONST.EMOJI_NUM_PER_ROW) * CONST.EMOJI_PICKER_HEADER_HEIGHT;
emojiListRef.current?.scrollToOffset({offset: calculatedOffset, animated: true});
},
[emojiListRef],
);

/**
* Given an emoji item object, render a component based on its type.
* Items with the code "SPACER" return nothing and are used to fill rows up to 8
* so that the sticky headers function properly.
*/
const renderItem: ListRenderItem<EmojiUtils.EmojiPickerListItem> = useCallback(
const renderItem: ListRenderItem<EmojiPickerListItem> = useCallback(
({item, target}) => {
const code = item.code;
const types = 'types' in item ? item.types : undefined;
Expand All @@ -96,7 +100,7 @@ function EmojiPickerMenu({onEmojiSelected, activeEmoji}: EmojiPickerMenuProps, r
}

const emojiCode = typeof preferredSkinTone === 'number' && preferredSkinTone !== -1 && types?.at(preferredSkinTone) ? types.at(preferredSkinTone) : code;
const shouldEmojiBeHighlighted = !!activeEmoji && EmojiUtils.getRemovedSkinToneEmoji(emojiCode) === EmojiUtils.getRemovedSkinToneEmoji(activeEmoji);
const shouldEmojiBeHighlighted = !!activeEmoji && getRemovedSkinToneEmoji(emojiCode) === getRemovedSkinToneEmoji(activeEmoji);

return (
<EmojiPickerMenuItem
Expand Down
72 changes: 50 additions & 22 deletions src/components/PopoverWithMeasuredContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ type PopoverWithMeasuredContentProps = Omit<PopoverProps, 'anchorPosition'> & {

/** Whether we should should use top side for the anchor positioning */
shouldMeasureAnchorPositionFromTop?: boolean;

/** Whether to skip re-measurement when becoming visible (for components with static dimensions) */
shouldSkipRemeasurement?: boolean;
};

/**
Expand Down Expand Up @@ -66,6 +69,7 @@ function PopoverWithMeasuredContent({
shouldHandleNavigationBack = false,
shouldEnableNewFocusManagement,
shouldMeasureAnchorPositionFromTop = false,
shouldSkipRemeasurement = false,
...props
}: PopoverWithMeasuredContentProps) {
const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext);
Expand All @@ -75,15 +79,28 @@ function PopoverWithMeasuredContent({
const [popoverHeight, setPopoverHeight] = useState(popoverDimensions.height);
const [isContentMeasured, setIsContentMeasured] = useState(popoverWidth > 0 && popoverHeight > 0);
const prevIsVisible = usePrevious(isVisible);
const prevAnchorPosition = usePrevious(anchorPosition);
const prevWindowDimensions = usePrevious({windowWidth, windowHeight});

const modalId = useMemo(() => ComposerFocusManager.getId(), []);

if (!prevIsVisible && isVisible && shouldEnableNewFocusManagement) {
ComposerFocusManager.saveFocusState(modalId);
}

if (!prevIsVisible && isVisible && isContentMeasured) {
setIsContentMeasured(false);
if (!prevIsVisible && isVisible && isContentMeasured && !shouldSkipRemeasurement) {
// Check if anything significant changed that would require re-measurement
const hasAnchorPositionChanged = !isEqual(prevAnchorPosition, anchorPosition);
const hasWindowSizeChanged = !isEqual(prevWindowDimensions, {windowWidth, windowHeight});
const hasStaticDimensions = popoverDimensions.width > 0 && popoverDimensions.height > 0;

// Only reset if:
// 1. We don't have static dimensions, OR
// 2. The anchor position changed significantly, OR
// 3. The window size changed significantly
if (!hasStaticDimensions || hasAnchorPositionChanged || hasWindowSizeChanged) {
setIsContentMeasured(false);
}
}

/**
Expand Down Expand Up @@ -144,28 +161,39 @@ function PopoverWithMeasuredContent({
};
}, [anchorPosition, anchorAlignment, popoverWidth, popoverHeight]);

const horizontalShift = PopoverWithMeasuredContentUtils.computeHorizontalShift(adjustedAnchorPosition.left, popoverWidth, windowWidth);
const verticalShift = PopoverWithMeasuredContentUtils.computeVerticalShift(
adjustedAnchorPosition.top,
popoverHeight,
windowHeight,
anchorDimensions.height,
shouldSwitchPositionIfOverflow,
);
const shiftedAnchorPosition: PopoverAnchorPosition = {
left: adjustedAnchorPosition.left + horizontalShift,
...(shouldMeasureAnchorPositionFromTop ? {top: adjustedAnchorPosition.top + verticalShift} : {}),
};
const positionCalculations = useMemo(() => {
const horizontalShift = PopoverWithMeasuredContentUtils.computeHorizontalShift(adjustedAnchorPosition.left, popoverWidth, windowWidth);
const verticalShift = PopoverWithMeasuredContentUtils.computeVerticalShift(
adjustedAnchorPosition.top,
popoverHeight,
windowHeight,
anchorDimensions.height,
shouldSwitchPositionIfOverflow,
);
return {horizontalShift, verticalShift};
}, [adjustedAnchorPosition.left, adjustedAnchorPosition.top, popoverWidth, popoverHeight, windowWidth, windowHeight, anchorDimensions.height, shouldSwitchPositionIfOverflow]);

const shiftedAnchorPosition: PopoverAnchorPosition = useMemo(() => {
const result: PopoverAnchorPosition = {
left: adjustedAnchorPosition.left + positionCalculations.horizontalShift,
};

if (anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP) {
const top = adjustedAnchorPosition.top + verticalShift;
const maxTop = windowHeight - popoverHeight - verticalShift;
shiftedAnchorPosition.top = Math.min(Math.max(verticalShift, top), maxTop);
}
if (shouldMeasureAnchorPositionFromTop) {
result.top = adjustedAnchorPosition.top + positionCalculations.verticalShift;
}

if (anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM) {
shiftedAnchorPosition.bottom = windowHeight - (adjustedAnchorPosition.top + popoverHeight) - verticalShift;
}
if (anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP) {
const top = adjustedAnchorPosition.top + positionCalculations.verticalShift;
const maxTop = windowHeight - popoverHeight - positionCalculations.verticalShift;
result.top = Math.min(Math.max(positionCalculations.verticalShift, top), maxTop);
}

if (anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM) {
result.bottom = windowHeight - (adjustedAnchorPosition.top + popoverHeight) - positionCalculations.verticalShift;
}

return result;
}, [adjustedAnchorPosition, positionCalculations, anchorAlignment.vertical, windowHeight, popoverHeight, shouldMeasureAnchorPositionFromTop]);

return isContentMeasured ? (
<Popover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,12 +373,29 @@ function ReportActionCompose({
// eslint-disable-next-line react-compiler/react-compiler
onSubmitAction = handleSendMessage;

const emojiPositionValues = useMemo(
() => ({
secondaryRowHeight: styles.chatItemComposeSecondaryRow.height,
secondaryRowMarginTop: styles.chatItemComposeSecondaryRow.marginTop,
secondaryRowMarginBottom: styles.chatItemComposeSecondaryRow.marginBottom,
composeBoxMinHeight: styles.chatItemComposeBox.minHeight,
emojiButtonHeight: styles.chatItemEmojiButton.height,
}),
[
styles.chatItemComposeSecondaryRow.height,
styles.chatItemComposeSecondaryRow.marginTop,
styles.chatItemComposeSecondaryRow.marginBottom,
styles.chatItemComposeBox.minHeight,
styles.chatItemEmojiButton.height,
],
);

const emojiShiftVertical = useMemo(() => {
const chatItemComposeSecondaryRowHeight = styles.chatItemComposeSecondaryRow.height + styles.chatItemComposeSecondaryRow.marginTop + styles.chatItemComposeSecondaryRow.marginBottom;
const reportActionComposeHeight = styles.chatItemComposeBox.minHeight + chatItemComposeSecondaryRowHeight;
const emojiOffsetWithComposeBox = (styles.chatItemComposeBox.minHeight - styles.chatItemEmojiButton.height) / 2;
const chatItemComposeSecondaryRowHeight = emojiPositionValues.secondaryRowHeight + emojiPositionValues.secondaryRowMarginTop + emojiPositionValues.secondaryRowMarginBottom;
const reportActionComposeHeight = emojiPositionValues.composeBoxMinHeight + chatItemComposeSecondaryRowHeight;
const emojiOffsetWithComposeBox = (emojiPositionValues.composeBoxMinHeight - emojiPositionValues.emojiButtonHeight) / 2;
return reportActionComposeHeight - emojiOffsetWithComposeBox - CONST.MENU_POSITION_REPORT_ACTION_COMPOSE_BOTTOM;
}, [styles]);
}, [emojiPositionValues]);

const validateMaxLength = useCallback(
(value: string) => {
Expand Down