Message Input: Fix focus delay in attachment modal (#6273)
This commit is contained in:
parent
d62cd024a9
commit
729ed3dd7d
@ -8,6 +8,7 @@ import {
|
||||
|
||||
import { requestMeasure } from '../../../lib/fasterdom/fasterdom';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import focusNoScroll from '../../../util/focusNoScroll';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
|
||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||
@ -69,9 +70,6 @@ type OwnProps = {
|
||||
onLoadMore?: () => void;
|
||||
} & (SingleModeProps | MultipleModeProps);
|
||||
|
||||
// Focus slows down animation, also it breaks transition layout in Chrome
|
||||
const FOCUS_DELAY_MS = 500;
|
||||
|
||||
const ITEM_CLASS_NAME = 'ItemPickerItem';
|
||||
|
||||
const ItemPicker = ({
|
||||
@ -103,15 +101,9 @@ const ItemPicker = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSearchable) return undefined;
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
requestMeasure(() => {
|
||||
inputRef.current?.focus();
|
||||
});
|
||||
}, FOCUS_DELAY_MS);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
requestMeasure(() => {
|
||||
focusNoScroll(inputRef.current);
|
||||
});
|
||||
}, [isSearchable]);
|
||||
|
||||
const selectedValues = useMemo(() => {
|
||||
|
||||
@ -12,6 +12,7 @@ import { getGroupStatus, getMainUsername, getUserStatus, isUserOnline } from '..
|
||||
import { getPeerTypeKey, isApiPeerChat } from '../../../global/helpers/peers';
|
||||
import { selectPeer, selectUserStatus } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import focusNoScroll from '../../../util/focusNoScroll';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
|
||||
@ -81,9 +82,6 @@ type OwnProps<CategoryType extends string> = {
|
||||
onLoadMore?: () => void;
|
||||
} & (SingleModeProps<CategoryType> | MultipleModeProps<CategoryType>);
|
||||
|
||||
// Focus slows down animation, also it breaks transition layout in Chrome
|
||||
const FOCUS_DELAY_MS = 500;
|
||||
|
||||
const MAX_FULL_ITEMS = 10;
|
||||
const ALWAYS_FULL_ITEMS_COUNT = 5;
|
||||
|
||||
@ -141,15 +139,9 @@ const PeerPicker = <CategoryType extends string = CustomPeerType>({
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSearchable) return undefined;
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
requestMeasure(() => {
|
||||
inputRef.current?.focus();
|
||||
});
|
||||
}, FOCUS_DELAY_MS);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId);
|
||||
};
|
||||
requestMeasure(() => {
|
||||
focusNoScroll(inputRef.current);
|
||||
});
|
||||
}, [isSearchable]);
|
||||
|
||||
const lockedSelectedIdsSet = useMemo(() => new Set(lockedSelectedIds), [lockedSelectedIds]);
|
||||
|
||||
@ -35,6 +35,7 @@ import {
|
||||
} from '../../global/selectors';
|
||||
import { ARE_CALLS_SUPPORTED, IS_APP } from '../../util/browser/windowEnvironment';
|
||||
import { isUserId } from '../../util/entities/ids';
|
||||
import focusNoScroll from '../../util/focusNoScroll';
|
||||
|
||||
import { useHotkeys } from '../../hooks/useHotkeys';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
@ -89,9 +90,6 @@ interface StateProps {
|
||||
isAccountFrozen?: boolean;
|
||||
}
|
||||
|
||||
// Chrome breaks layout when focusing input during transition
|
||||
const SEARCH_FOCUS_DELAY_MS = 320;
|
||||
|
||||
const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
threadId,
|
||||
@ -207,16 +205,13 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
|
||||
openMiddleSearch();
|
||||
|
||||
if (isMobile) {
|
||||
// iOS requires synchronous focus on user event.
|
||||
setFocusInSearchInput();
|
||||
} else if (noAnimation) {
|
||||
if (noAnimation) {
|
||||
// The second RAF is necessary because Teact must update the state and render the async component
|
||||
requestMeasure(() => {
|
||||
requestNextMutation(setFocusInSearchInput);
|
||||
});
|
||||
} else {
|
||||
setTimeout(setFocusInSearchInput, SEARCH_FOCUS_DELAY_MS);
|
||||
setFocusInSearchInput();
|
||||
}
|
||||
});
|
||||
|
||||
@ -566,5 +561,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
function setFocusInSearchInput() {
|
||||
const searchInput = document.querySelector<HTMLInputElement>('#MiddleSearch input');
|
||||
searchInput?.focus();
|
||||
if (searchInput) {
|
||||
focusNoScroll(searchInput);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ import type { ChangeEvent } from 'react';
|
||||
import type { ElementRef, FC, TeactNode } from '../../../lib/teact/teact';
|
||||
import type React from '../../../lib/teact/teact';
|
||||
import {
|
||||
getIsHeavyAnimating,
|
||||
memo, useEffect, useLayoutEffect,
|
||||
useRef, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
@ -45,8 +44,6 @@ import TextTimer from '../../ui/TextTimer';
|
||||
import TextFormatter from './TextFormatter.async';
|
||||
|
||||
const CONTEXT_MENU_CLOSE_DELAY_MS = 100;
|
||||
// Focus slows down animation, also it breaks transition layout in Chrome
|
||||
const FOCUS_DELAY_MS = 350;
|
||||
const TRANSITION_DURATION_FACTOR = 50;
|
||||
|
||||
const SCROLLER_CLASS = 'input-scroller';
|
||||
@ -248,6 +245,10 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
useLayoutEffect(() => {
|
||||
const html = isActive ? getHtml() : '';
|
||||
|
||||
if (!isActive && inputRef.current) {
|
||||
inputRef.current.blur();
|
||||
}
|
||||
|
||||
if (html !== inputRef.current!.innerHTML) {
|
||||
inputRef.current!.innerHTML = html;
|
||||
}
|
||||
@ -270,11 +271,6 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (getIsHeavyAnimating()) {
|
||||
setTimeout(focusInput, FOCUS_DELAY_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
focusEditableElement(inputRef.current);
|
||||
});
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ import buildClassName from '../../../util/buildClassName';
|
||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||
import { getDayStartAt } from '../../../util/dates/dateFormat';
|
||||
import focusEditableElement from '../../../util/focusEditableElement';
|
||||
import focusNoScroll from '../../../util/focusNoScroll';
|
||||
import { getSearchResultKey, parseSearchResultKey, type SearchResultKey } from '../../../util/keys/searchResultKey';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import { debounce, fastRaf } from '../../../util/schedulers';
|
||||
@ -174,7 +175,7 @@ const MiddleSearch: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const focusInput = useLastCallback(() => {
|
||||
requestMeasure(() => {
|
||||
inputRef.current?.focus();
|
||||
focusNoScroll(inputRef.current);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -4,10 +4,12 @@ import {
|
||||
useRef, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
||||
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
|
||||
import focusNoScroll from '../../util/focusNoScroll';
|
||||
import { CardType, detectCardType } from '../common/helpers/detectCardType';
|
||||
import { formatCardNumber } from '../middle/helpers/inputFormatters';
|
||||
|
||||
import useFocusAfterAnimation from '../../hooks/useFocusAfterAnimation';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
import InputText from '../ui/InputText';
|
||||
@ -24,13 +26,22 @@ export type OwnProps = {
|
||||
value: string;
|
||||
error?: string;
|
||||
onChange: (value: string) => void;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
const CardInput: FC<OwnProps> = ({ value, error, onChange }) => {
|
||||
const CardInput: FC<OwnProps> = ({ value, error, onChange, isActive }) => {
|
||||
const lang = useOldLang();
|
||||
const cardNumberRef = useRef<HTMLInputElement>();
|
||||
|
||||
useFocusAfterAnimation(cardNumberRef);
|
||||
useEffect(() => {
|
||||
if (!isActive || IS_TOUCH_ENV) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestMeasure(() => {
|
||||
focusNoScroll(cardNumberRef.current);
|
||||
});
|
||||
}, [isActive]);
|
||||
|
||||
const [cardType, setCardType] = useState<number>(CardType.Default);
|
||||
useEffect(() => {
|
||||
|
||||
@ -26,6 +26,7 @@ export type OwnProps = {
|
||||
needZip?: boolean;
|
||||
countryList: ApiCountry[];
|
||||
dispatch: FormEditDispatch;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
const PaymentInfo: FC<OwnProps> = ({
|
||||
@ -36,6 +37,7 @@ const PaymentInfo: FC<OwnProps> = ({
|
||||
needZip,
|
||||
countryList,
|
||||
dispatch,
|
||||
isActive,
|
||||
}) => {
|
||||
const selectCountryRef = useRef<HTMLSelectElement>();
|
||||
|
||||
@ -88,6 +90,7 @@ const PaymentInfo: FC<OwnProps> = ({
|
||||
onChange={handleCardNumberChange}
|
||||
value={state.cardNumber}
|
||||
error={formErrors.cardNumber && lang.withRegular(formErrors.cardNumber)}
|
||||
isActive={isActive}
|
||||
/>
|
||||
{needCardholderName && (
|
||||
<InputText
|
||||
|
||||
@ -275,7 +275,7 @@ const PaymentModal: FC<OwnProps & StateProps> = ({
|
||||
sendForm();
|
||||
}, [sendForm]);
|
||||
|
||||
function renderModalContent(currentStep: PaymentStep) {
|
||||
function renderModalContent(currentStep: PaymentStep, isActive?: boolean) {
|
||||
switch (currentStep) {
|
||||
case PaymentStep.Checkout:
|
||||
return (
|
||||
@ -328,6 +328,7 @@ const PaymentModal: FC<OwnProps & StateProps> = ({
|
||||
needCountry={needCountry}
|
||||
needZip={needZip}
|
||||
countryList={countryList}
|
||||
isActive={isActive}
|
||||
/>
|
||||
);
|
||||
case PaymentStep.ShippingInfo:
|
||||
@ -580,9 +581,11 @@ const PaymentModal: FC<OwnProps & StateProps> = ({
|
||||
shouldCleanup
|
||||
cleanupOnlyKey={PaymentStep.ConfirmPayment}
|
||||
>
|
||||
<div className="content custom-scroll">
|
||||
{renderModalContent(step)}
|
||||
</div>
|
||||
{(isActive) => (
|
||||
<div className="content custom-scroll">
|
||||
{renderModalContent(step, isActive)}
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
) : (
|
||||
<div className="empty-content">
|
||||
|
||||
@ -7,7 +7,10 @@ import {
|
||||
import type { ApiCountry } from '../../api/types';
|
||||
import type { FormEditDispatch, FormState } from '../../hooks/reducers/usePaymentReducer';
|
||||
|
||||
import useFocusAfterAnimation from '../../hooks/useFocusAfterAnimation';
|
||||
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
|
||||
import { IS_TOUCH_ENV } from '../../util/browser/windowEnvironment';
|
||||
import focusNoScroll from '../../util/focusNoScroll';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
@ -50,7 +53,15 @@ const ShippingInfo: FC<OwnProps> = ({
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
|
||||
useFocusAfterAnimation(inputRef);
|
||||
useEffect(() => {
|
||||
if (IS_TOUCH_ENV) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestMeasure(() => {
|
||||
focusNoScroll(inputRef.current);
|
||||
});
|
||||
}, [inputRef]);
|
||||
|
||||
const handleAddress1Change = useCallback((e) => {
|
||||
dispatch({ type: 'changeAddress1', payload: e.target.value });
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import type { ElementRef } from '../lib/teact/teact';
|
||||
import { useEffect } from '../lib/teact/teact';
|
||||
|
||||
import { requestMutation } from '../lib/fasterdom/fasterdom';
|
||||
import useAppLayout from './useAppLayout';
|
||||
import { requestMeasure } from '../lib/fasterdom/fasterdom';
|
||||
import { IS_TOUCH_ENV } from '../util/browser/windowEnvironment';
|
||||
import focusNoScroll from '../util/focusNoScroll';
|
||||
|
||||
// Focus slows down animation, also it breaks transition layout in Chrome
|
||||
const FOCUS_DELAY_MS = 500;
|
||||
const MODAL_HIDE_DELAY_MS = 300;
|
||||
|
||||
export default function useInputFocusOnOpen(
|
||||
@ -13,18 +12,12 @@ export default function useInputFocusOnOpen(
|
||||
isOpen?: boolean,
|
||||
onClose?: NoneToVoidFunction,
|
||||
) {
|
||||
const { isMobile } = useAppLayout();
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
if (!isMobile) {
|
||||
setTimeout(() => {
|
||||
requestMutation(() => {
|
||||
if (inputRef.current?.isConnected) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
});
|
||||
}, FOCUS_DELAY_MS);
|
||||
if (!IS_TOUCH_ENV && inputRef.current?.isConnected) {
|
||||
requestMeasure(() => {
|
||||
focusNoScroll(inputRef.current);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (inputRef.current?.isConnected) {
|
||||
@ -35,5 +28,5 @@ export default function useInputFocusOnOpen(
|
||||
setTimeout(onClose, MODAL_HIDE_DELAY_MS);
|
||||
}
|
||||
}
|
||||
}, [inputRef, isMobile, isOpen, onClose]);
|
||||
}, [inputRef, isOpen, onClose]);
|
||||
}
|
||||
|
||||
5
src/util/focusNoScroll.ts
Normal file
5
src/util/focusNoScroll.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export default function focusNoScroll(element?: HTMLElement) {
|
||||
if (!element) return;
|
||||
|
||||
element.focus({ preventScroll: true });
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user