Revert "Revert "Message Input: Allow typing even when not focused (#2135)""
This reverts commit 0e1e5513295281600890cb2bcabca7f8fa069587.
This commit is contained in:
parent
199670997d
commit
e7d3de60bd
@ -31,6 +31,7 @@ import { IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
|
|||||||
import { ANIMATION_END_DELAY } from '../../config';
|
import { ANIMATION_END_DELAY } from '../../config';
|
||||||
import { MEDIA_VIEWER_MEDIA_QUERY } from '../common/helpers/mediaDimensions';
|
import { MEDIA_VIEWER_MEDIA_QUERY } from '../common/helpers/mediaDimensions';
|
||||||
import windowSize from '../../util/windowSize';
|
import windowSize from '../../util/windowSize';
|
||||||
|
import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager';
|
||||||
import { animateClosing, animateOpening } from './helpers/ghostAnimation';
|
import { animateClosing, animateOpening } from './helpers/ghostAnimation';
|
||||||
import { renderMessageText } from '../common/helpers/renderMessageText';
|
import { renderMessageText } from '../common/helpers/renderMessageText';
|
||||||
|
|
||||||
@ -146,6 +147,16 @@ const MediaViewer: FC<StateProps> = ({
|
|||||||
animationKey.current = selectedMediaIndex;
|
animationKey.current = selectedMediaIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
disableDirectTextInput();
|
||||||
|
|
||||||
|
return enableDirectTextInput;
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
exitPictureInPictureIfNeeded();
|
exitPictureInPictureIfNeeded();
|
||||||
|
|||||||
@ -1237,6 +1237,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
<MessageInput
|
<MessageInput
|
||||||
id="message-input-text"
|
id="message-input-text"
|
||||||
|
editableInputId={EDITABLE_INPUT_ID}
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
html={!attachments.length ? html : ''}
|
html={!attachments.length ? html : ''}
|
||||||
@ -1247,6 +1248,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
forcedPlaceholder={inlineBotHelp}
|
forcedPlaceholder={inlineBotHelp}
|
||||||
canAutoFocus={isReady && !attachments.length}
|
canAutoFocus={isReady && !attachments.length}
|
||||||
|
noFocusInterception={attachments.length > 0}
|
||||||
shouldSuppressFocus={IS_SINGLE_COLUMN_LAYOUT && isSymbolMenuOpen}
|
shouldSuppressFocus={IS_SINGLE_COLUMN_LAYOUT && isSymbolMenuOpen}
|
||||||
shouldSuppressTextFormatter={isEmojiTooltipOpen || isMentionTooltipOpen || isInlineBotTooltipOpen}
|
shouldSuppressTextFormatter={isEmojiTooltipOpen || isMentionTooltipOpen || isInlineBotTooltipOpen}
|
||||||
onUpdate={setHtml}
|
onUpdate={setHtml}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
import type { IAnchorPosition, ISettings } from '../../../types';
|
import type { IAnchorPosition, ISettings } from '../../../types';
|
||||||
|
|
||||||
import { EDITABLE_INPUT_ID } from '../../../config';
|
import { EDITABLE_INPUT_ID } from '../../../config';
|
||||||
import { selectReplyingToId } from '../../../global/selectors';
|
import { selectIsInSelectMode, selectReplyingToId } from '../../../global/selectors';
|
||||||
import { debounce } from '../../../util/schedulers';
|
import { debounce } from '../../../util/schedulers';
|
||||||
import focusEditableElement from '../../../util/focusEditableElement';
|
import focusEditableElement from '../../../util/focusEditableElement';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV,
|
IS_ANDROID, IS_EMOJI_SUPPORTED, IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV,
|
||||||
} from '../../../util/environment';
|
} from '../../../util/environment';
|
||||||
import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
|
import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
|
||||||
|
import { getIsDirectTextInputDisabled } from '../../../util/directInputManager';
|
||||||
import useLayoutEffectWithPrevDeps from '../../../hooks/useLayoutEffectWithPrevDeps';
|
import useLayoutEffectWithPrevDeps from '../../../hooks/useLayoutEffectWithPrevDeps';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import { isHeavyAnimating } from '../../../hooks/useHeavyAnimationCheck';
|
import { isHeavyAnimating } from '../../../hooks/useHeavyAnimationCheck';
|
||||||
@ -40,6 +41,7 @@ type OwnProps = {
|
|||||||
html: string;
|
html: string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
forcedPlaceholder?: string;
|
forcedPlaceholder?: string;
|
||||||
|
noFocusInterception?: boolean;
|
||||||
canAutoFocus: boolean;
|
canAutoFocus: boolean;
|
||||||
shouldSuppressFocus?: boolean;
|
shouldSuppressFocus?: boolean;
|
||||||
shouldSuppressTextFormatter?: boolean;
|
shouldSuppressTextFormatter?: boolean;
|
||||||
@ -51,7 +53,7 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
replyingToId?: number;
|
replyingToId?: number;
|
||||||
noTabCapture?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
messageSendKeyCombo?: ISettings['messageSendKeyCombo'];
|
messageSendKeyCombo?: ISettings['messageSendKeyCombo'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -62,6 +64,7 @@ const SELECTION_RECALCULATE_DELAY_MS = 260;
|
|||||||
const TEXT_FORMATTER_SAFE_AREA_PX = 90;
|
const TEXT_FORMATTER_SAFE_AREA_PX = 90;
|
||||||
// For some reason Safari inserts `<br>` after user removes text from input
|
// For some reason Safari inserts `<br>` after user removes text from input
|
||||||
const SAFARI_BR = '<br>';
|
const SAFARI_BR = '<br>';
|
||||||
|
const IGNORE_KEYS = ['Enter', 'PageUp', 'PageDown', 'Meta', 'Alt', 'Ctrl', 'ArrowDown', 'ArrowUp'];
|
||||||
|
|
||||||
function clearSelection() {
|
function clearSelection() {
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
@ -86,10 +89,11 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
placeholder,
|
placeholder,
|
||||||
forcedPlaceholder,
|
forcedPlaceholder,
|
||||||
canAutoFocus,
|
canAutoFocus,
|
||||||
|
noFocusInterception,
|
||||||
shouldSuppressFocus,
|
shouldSuppressFocus,
|
||||||
shouldSuppressTextFormatter,
|
shouldSuppressTextFormatter,
|
||||||
replyingToId,
|
replyingToId,
|
||||||
noTabCapture,
|
isSelectModeActive,
|
||||||
messageSendKeyCombo,
|
messageSendKeyCombo,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
onSuppressedFocus,
|
onSuppressedFocus,
|
||||||
@ -363,19 +367,68 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
}, [chatId, focusInput, replyingToId, canAutoFocus]);
|
}, [chatId, focusInput, replyingToId, canAutoFocus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (noTabCapture) {
|
if (
|
||||||
|
!chatId
|
||||||
|
|| editableInputId !== EDITABLE_INPUT_ID
|
||||||
|
|| noFocusInterception
|
||||||
|
|| (IS_TOUCH_ENV && IS_SINGLE_COLUMN_LAYOUT)
|
||||||
|
|| isSelectModeActive
|
||||||
|
) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDocumentKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (getIsDirectTextInputDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { key } = e;
|
||||||
|
const target = e.target as HTMLElement | undefined;
|
||||||
|
|
||||||
|
if (!target || IGNORE_KEYS.includes(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = inputRef.current!;
|
||||||
|
const isSelectionCollapsed = document.getSelection()?.isCollapsed;
|
||||||
|
|
||||||
|
if (
|
||||||
|
((key.startsWith('Arrow') || (e.shiftKey && key === 'Shift')) && !isSelectionCollapsed)
|
||||||
|
|| (e.code === 'KeyC' && (e.ctrlKey || e.metaKey) && target.tagName !== 'INPUT')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
input
|
||||||
|
&& target !== input
|
||||||
|
&& target.tagName !== 'INPUT'
|
||||||
|
&& !target.isContentEditable
|
||||||
|
) {
|
||||||
|
focusEditableElement(input, true, true);
|
||||||
|
|
||||||
|
const newEvent = new KeyboardEvent(e.type, e as any);
|
||||||
|
input.dispatchEvent(newEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keydown', handleDocumentKeyDown, true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', handleDocumentKeyDown, true);
|
||||||
|
};
|
||||||
|
}, [chatId, editableInputId, isSelectModeActive, noFocusInterception]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
const captureFirstTab = debounce((e: KeyboardEvent) => {
|
const captureFirstTab = debounce((e: KeyboardEvent) => {
|
||||||
if (e.key === 'Tab') {
|
if (e.key === 'Tab' && !getIsDirectTextInputDisabled()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
requestAnimationFrame(focusInput);
|
requestAnimationFrame(focusInput);
|
||||||
}
|
}
|
||||||
}, TAB_INDEX_PRIORITY_TIMEOUT, true, false);
|
}, TAB_INDEX_PRIORITY_TIMEOUT, true, false);
|
||||||
|
|
||||||
return captureKeyboardListeners({ onTab: captureFirstTab });
|
return captureKeyboardListeners({ onTab: captureFirstTab });
|
||||||
}, [focusInput, noTabCapture]);
|
}, [focusInput]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const input = inputRef.current!;
|
const input = inputRef.current!;
|
||||||
@ -444,7 +497,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {
|
return {
|
||||||
messageSendKeyCombo,
|
messageSendKeyCombo,
|
||||||
replyingToId: chatId && threadId ? selectReplyingToId(global, chatId, threadId) : undefined,
|
replyingToId: chatId && threadId ? selectReplyingToId(global, chatId, threadId) : undefined,
|
||||||
noTabCapture: global.pollModal.isOpen || global.payment.isPaymentModalOpen,
|
isSelectModeActive: selectIsInSelectMode(global),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(MessageInput));
|
)(MessageInput));
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import type { TextPart } from '../../types';
|
|||||||
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
||||||
import trapFocus from '../../util/trapFocus';
|
import trapFocus from '../../util/trapFocus';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
import { enableDirectTextInput, disableDirectTextInput } from '../../util/directInputManager';
|
||||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||||
import useShowTransition from '../../hooks/useShowTransition';
|
import useShowTransition from '../../hooks/useShowTransition';
|
||||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||||
@ -63,6 +64,16 @@ const Modal: FC<OwnProps & StateProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
disableDirectTextInput();
|
||||||
|
|
||||||
|
return enableDirectTextInput;
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
useEffect(() => (isOpen
|
useEffect(() => (isOpen
|
||||||
? captureKeyboardListeners({ onEsc: onClose, onEnter })
|
? captureKeyboardListeners({ onEsc: onClose, onEnter })
|
||||||
: undefined), [isOpen, onClose, onEnter]);
|
: undefined), [isOpen, onClose, onEnter]);
|
||||||
|
|||||||
13
src/util/directInputManager.ts
Normal file
13
src/util/directInputManager.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
export function disableDirectTextInput() {
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enableDirectTextInput() {
|
||||||
|
counter -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIsDirectTextInputDisabled() {
|
||||||
|
return counter > 0;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user