|
|
@ -1,20 +1,17 @@
|
|
|
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
|
|
|
|
|
|
|
import { Map as ImmutableMap } from 'immutable';
|
|
|
|
import { Map as ImmutableMap } from 'immutable';
|
|
|
|
import React, { useEffect, useState, useLayoutEffect } from 'react';
|
|
|
|
import React, { useEffect, useState, useLayoutEffect } from 'react';
|
|
|
|
import { createPortal } from 'react-dom';
|
|
|
|
|
|
|
|
import { defineMessages, useIntl } from 'react-intl';
|
|
|
|
import { defineMessages, useIntl } from 'react-intl';
|
|
|
|
import { usePopper } from 'react-popper';
|
|
|
|
|
|
|
|
import { createSelector } from 'reselect';
|
|
|
|
import { createSelector } from 'reselect';
|
|
|
|
|
|
|
|
|
|
|
|
import { useEmoji } from 'soapbox/actions/emojis';
|
|
|
|
import { useEmoji } from 'soapbox/actions/emojis';
|
|
|
|
import { changeSetting } from 'soapbox/actions/settings';
|
|
|
|
import { changeSetting } from 'soapbox/actions/settings';
|
|
|
|
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
|
|
|
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
|
|
|
import { isMobile } from 'soapbox/is-mobile';
|
|
|
|
|
|
|
|
import { RootState } from 'soapbox/store';
|
|
|
|
import { RootState } from 'soapbox/store';
|
|
|
|
|
|
|
|
|
|
|
|
import { buildCustomEmojis } from '../../emoji';
|
|
|
|
import { buildCustomEmojis } from '../../emoji';
|
|
|
|
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
|
|
|
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import type { State as PopperState } from '@popperjs/core';
|
|
|
|
import type { Emoji, CustomEmoji, NativeEmoji } from 'soapbox/features/emoji';
|
|
|
|
import type { Emoji, CustomEmoji, NativeEmoji } from 'soapbox/features/emoji';
|
|
|
|
|
|
|
|
|
|
|
|
let EmojiPicker: any; // load asynchronously
|
|
|
|
let EmojiPicker: any; // load asynchronously
|
|
|
@ -49,13 +46,9 @@ export const messages = defineMessages({
|
|
|
|
export interface IEmojiPickerDropdown {
|
|
|
|
export interface IEmojiPickerDropdown {
|
|
|
|
onPickEmoji?: (emoji: Emoji) => void
|
|
|
|
onPickEmoji?: (emoji: Emoji) => void
|
|
|
|
condensed?: boolean
|
|
|
|
condensed?: boolean
|
|
|
|
render: React.FC<{
|
|
|
|
visible: boolean
|
|
|
|
setPopperReference: React.Ref<HTMLButtonElement>
|
|
|
|
setVisible: (value: boolean) => void
|
|
|
|
title?: string
|
|
|
|
update: (() => Promise<Partial<PopperState>>) | null
|
|
|
|
visible?: boolean
|
|
|
|
|
|
|
|
loading?: boolean
|
|
|
|
|
|
|
|
handleToggle: (e: Event) => void
|
|
|
|
|
|
|
|
}>
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const perLine = 8;
|
|
|
|
const perLine = 8;
|
|
|
@ -132,9 +125,9 @@ const RenderAfter = ({ children, update }: any) => {
|
|
|
|
return nextTick ? children : null;
|
|
|
|
return nextTick ? children : null;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
|
|
|
const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({
|
|
|
|
|
|
|
|
onPickEmoji, condensed, visible, setVisible, update,
|
|
|
|
const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, condensed, render: Render }) => {
|
|
|
|
}) => {
|
|
|
|
const intl = useIntl();
|
|
|
|
const intl = useIntl();
|
|
|
|
const dispatch = useAppDispatch();
|
|
|
|
const dispatch = useAppDispatch();
|
|
|
|
const settings = useSettings();
|
|
|
|
const settings = useSettings();
|
|
|
@ -145,29 +138,8 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, cond
|
|
|
|
const customEmojis = useAppSelector((state) => getCustomEmojis(state));
|
|
|
|
const customEmojis = useAppSelector((state) => getCustomEmojis(state));
|
|
|
|
const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state));
|
|
|
|
const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state));
|
|
|
|
|
|
|
|
|
|
|
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
|
|
|
|
|
|
|
const [popperReference, setPopperReference] = useState<HTMLButtonElement | null>(null);
|
|
|
|
|
|
|
|
const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [visible, setVisible] = useState(false);
|
|
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
const placement = condensed ? 'bottom-start' : 'top-start';
|
|
|
|
|
|
|
|
const { styles, attributes, update } = usePopper(popperReference, popperElement, {
|
|
|
|
|
|
|
|
placement: isMobile(window.innerWidth) ? 'auto' : placement,
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleToggle = (e: Event) => {
|
|
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
setVisible(!visible);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleDocClick = (e: any) => {
|
|
|
|
|
|
|
|
if (!containerElement?.contains(e.target) && !popperElement?.contains(e.target)) {
|
|
|
|
|
|
|
|
setVisible(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handlePick = (emoji: any) => {
|
|
|
|
const handlePick = (emoji: any) => {
|
|
|
|
setVisible(false);
|
|
|
|
setVisible(false);
|
|
|
|
|
|
|
|
|
|
|
@ -233,17 +205,6 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, cond
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
|
|
document.addEventListener('click', handleDocClick, false);
|
|
|
|
|
|
|
|
document.addEventListener('touchend', handleDocClick, listenerOptions);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return function cleanup() {
|
|
|
|
|
|
|
|
document.removeEventListener('click', handleDocClick, false);
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
|
|
|
document.removeEventListener('touchend', handleDocClick, listenerOptions);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
useEffect(() => {
|
|
|
|
// fix scrolling focus issue
|
|
|
|
// fix scrolling focus issue
|
|
|
|
if (visible) {
|
|
|
|
if (visible) {
|
|
|
@ -265,29 +226,8 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, cond
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [visible]);
|
|
|
|
}, [visible]);
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: move to class
|
|
|
|
|
|
|
|
const style: React.CSSProperties = !isMobile(window.innerWidth) ? styles.popper : {
|
|
|
|
|
|
|
|
...styles.popper, width: '100%',
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<div className='relative' ref={setContainerElement}>
|
|
|
|
visible ? (
|
|
|
|
<Render
|
|
|
|
|
|
|
|
handleToggle={handleToggle}
|
|
|
|
|
|
|
|
visible={visible}
|
|
|
|
|
|
|
|
loading={loading}
|
|
|
|
|
|
|
|
title={title}
|
|
|
|
|
|
|
|
setPopperReference={setPopperReference}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{createPortal(
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
className='z-[101]'
|
|
|
|
|
|
|
|
ref={setPopperElement}
|
|
|
|
|
|
|
|
style={style}
|
|
|
|
|
|
|
|
{...attributes.popper}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{visible && (
|
|
|
|
|
|
|
|
<RenderAfter update={update}>
|
|
|
|
<RenderAfter update={update}>
|
|
|
|
{!loading && (
|
|
|
|
{!loading && (
|
|
|
|
<EmojiPicker
|
|
|
|
<EmojiPicker
|
|
|
@ -305,11 +245,7 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, cond
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
</RenderAfter>
|
|
|
|
</RenderAfter>
|
|
|
|
)}
|
|
|
|
) : null
|
|
|
|
</div>,
|
|
|
|
|
|
|
|
document.body,
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
);
|
|
|
|
);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|