EmojiPicker: Enter adds emoji in keyboard mode, otherwise dismisses
This commit is contained in:
parent
28eaf1689f
commit
9533796c81
9 changed files with 72 additions and 15 deletions
|
@ -193,6 +193,7 @@ export function CustomizingPreferredReactionsModal({
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
deselectDraftEmoji();
|
deselectDraftEmoji();
|
||||||
}}
|
}}
|
||||||
|
wasInvokedFromKeyboard={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -91,7 +91,7 @@ export type PropsType = {
|
||||||
| 'platform'
|
| 'platform'
|
||||||
| 'sortedGroupMembers'
|
| 'sortedGroupMembers'
|
||||||
> &
|
> &
|
||||||
EmojiPickerProps;
|
Omit<EmojiPickerProps, 'wasInvokedFromKeyboard'>;
|
||||||
|
|
||||||
const INITIAL_IMAGE_STATE: ImageStateType = {
|
const INITIAL_IMAGE_STATE: ImageStateType = {
|
||||||
angle: 0,
|
angle: 0,
|
||||||
|
|
|
@ -26,6 +26,7 @@ const renderEmojiPicker: ReactionPickerProps['renderEmojiPicker'] = ({
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onPickEmoji={onPickEmoji}
|
onPickEmoji={onPickEmoji}
|
||||||
onSetSkinTone={onSetSkinTone}
|
onSetSkinTone={onSetSkinTone}
|
||||||
|
wasInvokedFromKeyboard={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ const renderEmojiPicker: TimelineItemProps['renderEmojiPicker'] = ({
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onPickEmoji={onPickEmoji}
|
onPickEmoji={onPickEmoji}
|
||||||
|
wasInvokedFromKeyboard={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,7 @@ const renderEmojiPicker: Props['renderEmojiPicker'] = ({
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onPickEmoji={onPickEmoji}
|
onPickEmoji={onPickEmoji}
|
||||||
|
wasInvokedFromKeyboard={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,8 @@ export const EmojiButton = React.memo(function EmojiButtonInner({
|
||||||
const isRTL = i18n.getLocaleDirection() === 'rtl';
|
const isRTL = i18n.getLocaleDirection() === 'rtl';
|
||||||
|
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [wasInvokedFromKeyboard, setWasInvokedFromKeyboard] =
|
||||||
|
React.useState(false);
|
||||||
const buttonRef = React.useRef<HTMLButtonElement | null>(null);
|
const buttonRef = React.useRef<HTMLButtonElement | null>(null);
|
||||||
const popperRef = React.useRef<HTMLDivElement | null>(null);
|
const popperRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
const refMerger = useRefMerger();
|
const refMerger = useRefMerger();
|
||||||
|
@ -69,25 +71,30 @@ export const EmojiButton = React.memo(function EmojiButtonInner({
|
||||||
}, [open, onOpen]);
|
}, [open, onOpen]);
|
||||||
|
|
||||||
const handleClickButton = React.useCallback(() => {
|
const handleClickButton = React.useCallback(() => {
|
||||||
|
setWasInvokedFromKeyboard(false);
|
||||||
if (open) {
|
if (open) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
} else {
|
} else {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
}
|
}
|
||||||
}, [open, setOpen]);
|
}, [open, setOpen, setWasInvokedFromKeyboard]);
|
||||||
|
|
||||||
const handleClose = React.useCallback(() => {
|
const handleClose = React.useCallback(() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
setWasInvokedFromKeyboard(false);
|
||||||
if (onClose) {
|
if (onClose) {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
}, [setOpen, onClose]);
|
}, [setOpen, setWasInvokedFromKeyboard, onClose]);
|
||||||
|
|
||||||
const api = React.useMemo(
|
const api = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
close: () => setOpen(false),
|
close: () => {
|
||||||
|
setOpen(false);
|
||||||
|
setWasInvokedFromKeyboard(false);
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[setOpen]
|
[setOpen, setWasInvokedFromKeyboard]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (emojiButtonApi) {
|
if (emojiButtonApi) {
|
||||||
|
@ -132,6 +139,7 @@ export const EmojiButton = React.memo(function EmojiButtonInner({
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
setWasInvokedFromKeyboard(true);
|
||||||
setOpen(!open);
|
setOpen(!open);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -180,6 +188,7 @@ export const EmojiButton = React.memo(function EmojiButtonInner({
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
skinTone={skinTone}
|
skinTone={skinTone}
|
||||||
onSetSkinTone={onSetSkinTone}
|
onSetSkinTone={onSetSkinTone}
|
||||||
|
wasInvokedFromKeyboard={wasInvokedFromKeyboard}
|
||||||
recentEmojis={recentEmojis}
|
recentEmojis={recentEmojis}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -57,6 +57,7 @@ export function Base(): JSX.Element {
|
||||||
'open_mouth',
|
'open_mouth',
|
||||||
'zipper_mouth_face',
|
'zipper_mouth_face',
|
||||||
]}
|
]}
|
||||||
|
wasInvokedFromKeyboard={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -70,6 +71,7 @@ export function NoRecents(): JSX.Element {
|
||||||
onClose={action('onClose')}
|
onClose={action('onClose')}
|
||||||
skinTone={0}
|
skinTone={0}
|
||||||
recentEmojis={[]}
|
recentEmojis={[]}
|
||||||
|
wasInvokedFromKeyboard={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -84,6 +86,7 @@ export function WithSettingsButton(): JSX.Element {
|
||||||
onClose={action('onClose')}
|
onClose={action('onClose')}
|
||||||
skinTone={0}
|
skinTone={0}
|
||||||
recentEmojis={[]}
|
recentEmojis={[]}
|
||||||
|
wasInvokedFromKeyboard={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,16 +33,28 @@ export type EmojiPickDataType = {
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
readonly i18n: LocalizerType;
|
readonly i18n: LocalizerType;
|
||||||
readonly onPickEmoji: (o: EmojiPickDataType) => unknown;
|
|
||||||
readonly skinTone?: number;
|
|
||||||
readonly onSetSkinTone?: (tone: number) => unknown;
|
|
||||||
readonly recentEmojis?: ReadonlyArray<string>;
|
readonly recentEmojis?: ReadonlyArray<string>;
|
||||||
|
readonly skinTone?: number;
|
||||||
readonly onClickSettings?: () => unknown;
|
readonly onClickSettings?: () => unknown;
|
||||||
readonly onClose?: () => unknown;
|
readonly onClose?: () => unknown;
|
||||||
|
readonly onPickEmoji: (o: EmojiPickDataType) => unknown;
|
||||||
|
readonly onSetSkinTone?: (tone: number) => unknown;
|
||||||
|
readonly wasInvokedFromKeyboard: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Props = OwnProps & Pick<React.HTMLProps<HTMLDivElement>, 'style'>;
|
export type Props = OwnProps & Pick<React.HTMLProps<HTMLDivElement>, 'style'>;
|
||||||
|
|
||||||
|
function isEventFromMouse(
|
||||||
|
event:
|
||||||
|
| React.MouseEvent<HTMLButtonElement>
|
||||||
|
| React.KeyboardEvent<HTMLButtonElement>
|
||||||
|
): boolean {
|
||||||
|
return (
|
||||||
|
('clientX' in event && event.clientX !== 0) ||
|
||||||
|
('clientY' in event && event.clientY !== 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function focusOnRender(el: HTMLElement | null) {
|
function focusOnRender(el: HTMLElement | null) {
|
||||||
if (el) {
|
if (el) {
|
||||||
el.focus();
|
el.focus();
|
||||||
|
@ -77,11 +89,16 @@ export const EmojiPicker = React.memo(
|
||||||
style,
|
style,
|
||||||
onClickSettings,
|
onClickSettings,
|
||||||
onClose,
|
onClose,
|
||||||
|
wasInvokedFromKeyboard,
|
||||||
}: Props,
|
}: Props,
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const isRTL = i18n.getLocaleDirection() === 'rtl';
|
const isRTL = i18n.getLocaleDirection() === 'rtl';
|
||||||
|
|
||||||
|
const [isUsingKeyboard, setIsUsingKeyboard] = React.useState(
|
||||||
|
wasInvokedFromKeyboard
|
||||||
|
);
|
||||||
|
|
||||||
const [firstRecent] = React.useState(recentEmojis);
|
const [firstRecent] = React.useState(recentEmojis);
|
||||||
const [selectedCategory, setSelectedCategory] = React.useState<Category>(
|
const [selectedCategory, setSelectedCategory] = React.useState<Category>(
|
||||||
categories[0]
|
categories[0]
|
||||||
|
@ -97,6 +114,9 @@ export const EmojiPicker = React.memo(
|
||||||
| React.MouseEvent<HTMLButtonElement>
|
| React.MouseEvent<HTMLButtonElement>
|
||||||
| React.KeyboardEvent<HTMLButtonElement>
|
| React.KeyboardEvent<HTMLButtonElement>
|
||||||
) => {
|
) => {
|
||||||
|
if (isEventFromMouse(e)) {
|
||||||
|
setIsUsingKeyboard(false);
|
||||||
|
}
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -129,6 +149,9 @@ export const EmojiPicker = React.memo(
|
||||||
| React.MouseEvent<HTMLButtonElement>
|
| React.MouseEvent<HTMLButtonElement>
|
||||||
| React.KeyboardEvent<HTMLButtonElement>
|
| React.KeyboardEvent<HTMLButtonElement>
|
||||||
) => {
|
) => {
|
||||||
|
if (isEventFromMouse(e)) {
|
||||||
|
setIsUsingKeyboard(false);
|
||||||
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
@ -151,24 +174,42 @@ export const EmojiPicker = React.memo(
|
||||||
const { shortName } = e.currentTarget.dataset;
|
const { shortName } = e.currentTarget.dataset;
|
||||||
if ('key' in e) {
|
if ('key' in e) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
if (shortName) {
|
if (shortName && isUsingKeyboard) {
|
||||||
onPickEmoji({ skinTone: selectedTone, shortName });
|
onPickEmoji({ skinTone: selectedTone, shortName });
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
} else if (onClose) {
|
||||||
|
onClose();
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (shortName) {
|
} else if (shortName) {
|
||||||
|
if (isEventFromMouse(e)) {
|
||||||
|
setIsUsingKeyboard(false);
|
||||||
|
}
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onPickEmoji({ skinTone: selectedTone, shortName });
|
onPickEmoji({ skinTone: selectedTone, shortName });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onPickEmoji, selectedTone]
|
[
|
||||||
|
onClose,
|
||||||
|
onPickEmoji,
|
||||||
|
isUsingKeyboard,
|
||||||
|
selectedTone,
|
||||||
|
setIsUsingKeyboard,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle key presses, particularly Escape
|
// Handle key presses, particularly Escape
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const handler = (event: KeyboardEvent) => {
|
const handler = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Tab') {
|
||||||
|
// We do NOT prevent default here to allow Tab to be used normally
|
||||||
|
setIsUsingKeyboard(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
if (searchMode) {
|
if (searchMode) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -190,7 +231,6 @@ export const EmojiPicker = React.memo(
|
||||||
'ArrowRight',
|
'ArrowRight',
|
||||||
'Enter',
|
'Enter',
|
||||||
'Shift',
|
'Shift',
|
||||||
'Tab',
|
|
||||||
' ', // Space
|
' ', // Space
|
||||||
].includes(event.key)
|
].includes(event.key)
|
||||||
) {
|
) {
|
||||||
|
@ -215,7 +255,7 @@ export const EmojiPicker = React.memo(
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', handler);
|
document.removeEventListener('keydown', handler);
|
||||||
};
|
};
|
||||||
}, [onClose, searchMode, setSearchMode]);
|
}, [onClose, setIsUsingKeyboard, searchMode, setSearchMode]);
|
||||||
|
|
||||||
const [, ...renderableCategories] = categories;
|
const [, ...renderableCategories] = categories;
|
||||||
|
|
||||||
|
|
|
@ -38,15 +38,16 @@ export const SmartEmojiPicker = memo(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
ref={ref}
|
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
skinTone={skinTone}
|
|
||||||
onClickSettings={onClickSettings}
|
onClickSettings={onClickSettings}
|
||||||
|
onClose={onClose}
|
||||||
onSetSkinTone={onSetSkinTone}
|
onSetSkinTone={onSetSkinTone}
|
||||||
onPickEmoji={handlePickEmoji}
|
onPickEmoji={handlePickEmoji}
|
||||||
recentEmojis={recentEmojis}
|
recentEmojis={recentEmojis}
|
||||||
onClose={onClose}
|
ref={ref}
|
||||||
|
skinTone={skinTone}
|
||||||
style={style}
|
style={style}
|
||||||
|
wasInvokedFromKeyboard={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue