emoji-picker-react 3.6.1 → 4.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +17 -20
- package/README.md +129 -168
- package/dist/DomUtils/categoryNameFromDom.d.ts +1 -0
- package/dist/DomUtils/scrollCategoryIntoView.d.ts +1 -0
- package/dist/DomUtils/scrollTo.d.ts +1 -0
- package/dist/DomUtils/selectors.d.ts +12 -0
- package/dist/components/Layout/Flex.d.ts +14 -0
- package/dist/components/Layout/Relative.d.ts +7 -0
- package/dist/components/body/Body.d.ts +3 -0
- package/dist/components/body/EmojiCategory.d.ts +10 -0
- package/dist/components/body/EmojiList.d.ts +3 -0
- package/dist/components/body/EmojiVariationPicker.d.ts +3 -0
- package/dist/components/body/RecentlyUsed.d.ts +7 -0
- package/dist/components/context/ElementRefContext.d.ts +17 -0
- package/dist/components/context/PickerConfigContext.d.ts +8 -0
- package/dist/components/context/PickerContext.d.ts +21 -0
- package/dist/components/emoji/Emoji.d.ts +14 -0
- package/dist/components/footer/Preview.d.ts +7 -0
- package/dist/components/header/Header.d.ts +3 -0
- package/dist/components/header/Search.d.ts +3 -0
- package/dist/components/header/SkinTonePicker.d.ts +3 -0
- package/dist/components/main/PickerMain.d.ts +7 -0
- package/dist/components/navigation/CategoryNavigation.d.ts +3 -0
- package/dist/config/categoryConfig.d.ts +21 -0
- package/dist/config/cdnUrls.d.ts +2 -0
- package/dist/config/config.d.ts +26 -0
- package/dist/config/useConfig.d.ts +11 -0
- package/dist/data/emojis.d.ts +40 -0
- package/dist/data/groups.d.ts +2 -0
- package/dist/data/skinToneVariations.d.ts +3 -0
- package/dist/dataUtils/DataTypes.d.ts +8 -0
- package/dist/dataUtils/alphaNumericEmojiIndex.d.ts +4 -0
- package/dist/dataUtils/asEmoji.d.ts +2 -0
- package/dist/dataUtils/emojiSelectors.d.ts +15 -0
- package/dist/dataUtils/parseNativeEmoji.d.ts +1 -0
- package/dist/dataUtils/recentlyUsed.d.ts +11 -0
- package/dist/emoji-picker-react.cjs.development.css +13 -0
- package/dist/emoji-picker-react.cjs.development.js +7419 -0
- package/dist/emoji-picker-react.cjs.development.js.map +1 -0
- package/dist/emoji-picker-react.cjs.production.min.js +2 -0
- package/dist/emoji-picker-react.cjs.production.min.js.map +1 -0
- package/dist/emoji-picker-react.esm.js +7485 -0
- package/dist/emoji-picker-react.esm.js.map +1 -0
- package/dist/hooks/preloadEmoji.d.ts +4 -0
- package/dist/hooks/useActiveCategoryScrollDetection.d.ts +2 -0
- package/dist/hooks/useCloseAllOpenToggles.d.ts +4 -0
- package/dist/hooks/useEmojiElements.d.ts +1 -0
- package/dist/hooks/useEmojiMouseEnter.d.ts +3 -0
- package/dist/hooks/useFilter.d.ts +8 -0
- package/dist/hooks/useInitialLoad.d.ts +2 -0
- package/dist/hooks/useIsEmojiHidden.d.ts +2 -0
- package/dist/hooks/useIsSearchMode.d.ts +1 -0
- package/dist/hooks/useMouseDownHandlers.d.ts +2 -0
- package/dist/hooks/useOnScroll.d.ts +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -3
- package/dist/types/exposedTypes.d.ts +28 -0
- package/package.json +72 -52
- package/src/DomUtils/categoryNameFromDom.ts +3 -0
- package/src/DomUtils/scrollCategoryIntoView.ts +18 -0
- package/src/DomUtils/scrollTo.ts +11 -0
- package/src/DomUtils/selectors.ts +93 -0
- package/src/EmojiPickerReact.css +85 -0
- package/src/components/Layout/Flex.css +11 -0
- package/src/components/Layout/Flex.tsx +28 -0
- package/src/components/Layout/Relative.tsx +14 -0
- package/src/components/body/Body.css +6 -0
- package/src/components/body/Body.tsx +24 -0
- package/src/components/body/EmojiCategory.css +38 -0
- package/src/components/body/EmojiCategory.tsx +27 -0
- package/src/components/body/EmojiList.css +5 -0
- package/src/components/body/EmojiList.tsx +101 -0
- package/src/components/body/EmojiVariationPicker.css +65 -0
- package/src/components/body/EmojiVariationPicker.tsx +154 -0
- package/src/components/body/RecentlyUsed.tsx +50 -0
- package/src/components/body/svg/triangle.svg +11 -0
- package/src/components/context/ElementRefContext.tsx +61 -0
- package/src/components/context/PickerConfigContext.tsx +29 -0
- package/src/components/context/PickerContext.tsx +139 -0
- package/src/components/emoji/Emoji.css +44 -0
- package/src/components/emoji/Emoji.tsx +104 -0
- package/src/components/footer/Preview.css +14 -0
- package/src/components/footer/Preview.tsx +80 -0
- package/src/components/header/Header.css +4 -0
- package/src/components/header/Header.tsx +21 -0
- package/src/components/header/Search.css +72 -0
- package/src/components/header/Search.tsx +36 -0
- package/src/components/header/SkinTonePicker.css +86 -0
- package/src/components/header/SkinTonePicker.tsx +55 -0
- package/src/components/header/svg/magnifier.svg +13 -0
- package/src/components/header/svg/times.svg +33 -0
- package/src/components/main/PickerMain.css +11 -0
- package/src/components/main/PickerMain.tsx +42 -0
- package/src/components/navigation/CategoryNavigation.css +82 -0
- package/src/components/navigation/CategoryNavigation.tsx +32 -0
- package/src/components/navigation/svg/CategoryNav.svg +508 -0
- package/src/config/categoryConfig.ts +107 -0
- package/src/config/cdnUrls.ts +24 -0
- package/src/config/config.ts +50 -0
- package/src/config/useConfig.ts +52 -0
- package/src/data/emojis.ts +5350 -0
- package/src/data/groups.ts +12 -0
- package/src/data/skinToneVariations.ts +12 -0
- package/src/dataUtils/DataTypes.ts +10 -0
- package/src/dataUtils/alphaNumericEmojiIndex.ts +23 -0
- package/src/dataUtils/asEmoji.ts +5 -0
- package/src/dataUtils/emojiSelectors.ts +94 -0
- package/src/dataUtils/parseNativeEmoji.ts +6 -0
- package/src/dataUtils/recentlyUsed.ts +61 -0
- package/src/hooks/preloadEmoji.ts +29 -0
- package/src/hooks/useActiveCategoryScrollDetection.ts +46 -0
- package/src/hooks/useCloseAllOpenToggles.ts +26 -0
- package/src/hooks/useEmojiElements.ts +7 -0
- package/src/hooks/useEmojiMouseEnter.ts +59 -0
- package/src/hooks/useFilter.ts +142 -0
- package/src/hooks/useInitialLoad.ts +9 -0
- package/src/hooks/useIsEmojiHidden.ts +16 -0
- package/src/hooks/useIsSearchMode.ts +7 -0
- package/src/hooks/useMouseDownHandlers.ts +160 -0
- package/src/hooks/useOnScroll.ts +28 -0
- package/src/index.tsx +27 -0
- package/src/types/exposedTypes.ts +31 -0
- package/.babelrc +0 -30
- package/.eslintrc.js +0 -37
- package/.github/workflows/npmpublish.yml +0 -30
- package/.prettierignore +0 -2
- package/.storybook/addons.js +0 -2
- package/.storybook/config.js +0 -9
- package/assets/captured.gif +0 -0
- package/dist/index.js.LICENSE.txt +0 -5
- package/dist/index.js.map +0 -1
- package/dist/main.css +0 -206
- package/dist/main.css.map +0 -1
- package/emoji-picker-react.d.ts +0 -45
- package/webpack.config.js +0 -52
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SkinTones } from '../types/exposedTypes';
|
|
2
|
+
|
|
3
|
+
const skinToneVariations = [
|
|
4
|
+
SkinTones.NEUTRAL,
|
|
5
|
+
SkinTones.LIGHT,
|
|
6
|
+
SkinTones.MEDIUM_LIGHT,
|
|
7
|
+
SkinTones.MEDIUM,
|
|
8
|
+
SkinTones.MEDIUM_DARK,
|
|
9
|
+
SkinTones.DARK
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export default skinToneVariations;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DataEmoji } from './DataTypes';
|
|
2
|
+
import { allEmojis, emojiNames, emojiUnified } from './emojiSelectors';
|
|
3
|
+
|
|
4
|
+
export const alphaNumericEmojiIndex: BaseIndex = {};
|
|
5
|
+
|
|
6
|
+
setTimeout(() => {
|
|
7
|
+
allEmojis.reduce((searchIndex, emoji) => {
|
|
8
|
+
const joinedNameString = emojiNames(emoji)
|
|
9
|
+
.flat()
|
|
10
|
+
.join('')
|
|
11
|
+
.replace(/[^a-zA-Z\d]/g, '')
|
|
12
|
+
.split('');
|
|
13
|
+
|
|
14
|
+
joinedNameString.forEach(char => {
|
|
15
|
+
searchIndex[char] = searchIndex[char] ?? {};
|
|
16
|
+
|
|
17
|
+
searchIndex[char][emojiUnified(emoji)] = emoji;
|
|
18
|
+
});
|
|
19
|
+
return searchIndex;
|
|
20
|
+
}, alphaNumericEmojiIndex as BaseIndex);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
type BaseIndex = Record<string, Record<string, DataEmoji>>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Categories } from './../config/categoryConfig';
|
|
2
|
+
import { cdnUrl } from '../config/cdnUrls';
|
|
3
|
+
import emojis from '../data/emojis';
|
|
4
|
+
import { DataEmoji, DataEmojis } from './DataTypes';
|
|
5
|
+
import skinToneVariations from '../data/skinToneVariations';
|
|
6
|
+
import { EmojiStyle, SkinTones } from '../types/exposedTypes';
|
|
7
|
+
|
|
8
|
+
enum EmojiProperties {
|
|
9
|
+
name = 'n',
|
|
10
|
+
unified = 'u',
|
|
11
|
+
variations = 'v'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function emojiNames(emoji: DataEmoji): string[] {
|
|
15
|
+
return emoji[EmojiProperties.name] ?? [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function emojiName(emoji: DataEmoji): string {
|
|
19
|
+
return emojiNames(emoji)[0];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function emojiUrl(
|
|
23
|
+
emojiStyle: EmojiStyle,
|
|
24
|
+
emoji: DataEmoji,
|
|
25
|
+
skinTone?: string
|
|
26
|
+
): string {
|
|
27
|
+
return emojiUrlByUnified(emojiStyle, emojiUnified(emoji, skinTone));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function emojiUnified(emoji: DataEmoji, skinTone?: string): string {
|
|
31
|
+
const unified = emoji[EmojiProperties.unified];
|
|
32
|
+
|
|
33
|
+
if (!skinTone || !emojiHasVariations(emoji)) {
|
|
34
|
+
return unified;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return emojiVariationUnified(emoji, skinTone) ?? unified;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function emojisByCategory(category: Categories): DataEmojis {
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
return emojis?.[category] ?? [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function emojiUrlByUnified(
|
|
46
|
+
emojiStyle: EmojiStyle,
|
|
47
|
+
unified: string
|
|
48
|
+
): string {
|
|
49
|
+
return `${cdnUrl(emojiStyle)}${unified}.png`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function emojiVariations(emoji: DataEmoji): string[] {
|
|
53
|
+
return emoji[EmojiProperties.variations] ?? [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function emojiHasVariations(emoji: DataEmoji): boolean {
|
|
57
|
+
return emojiVariations(emoji).length > 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function emojiVariationUnified(
|
|
61
|
+
emoji: DataEmoji,
|
|
62
|
+
skinTone?: string
|
|
63
|
+
): string | undefined {
|
|
64
|
+
return skinTone
|
|
65
|
+
? emojiVariations(emoji).find(variation => variation.includes(skinTone))
|
|
66
|
+
: emojiUnified(emoji);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function emojiByUnified(unified?: string): DataEmoji | undefined {
|
|
70
|
+
if (!unified) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
return allEmojisByUnified[unified];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const allEmojis: DataEmojis = Object.values(emojis).flat();
|
|
77
|
+
|
|
78
|
+
const allEmojisByUnified: {
|
|
79
|
+
[unified: string]: DataEmoji;
|
|
80
|
+
} = {};
|
|
81
|
+
|
|
82
|
+
window?.setTimeout(() => {
|
|
83
|
+
allEmojis.reduce((allEmojis, Emoji) => {
|
|
84
|
+
allEmojis[emojiUnified(Emoji)] = Emoji;
|
|
85
|
+
return allEmojis;
|
|
86
|
+
}, allEmojisByUnified);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export function activeVariationFromUnified(unified: string): SkinTones | null {
|
|
90
|
+
const [, suspectedSkinTone] = unified.split('-') as [string, SkinTones];
|
|
91
|
+
return skinToneVariations.includes(suspectedSkinTone)
|
|
92
|
+
? suspectedSkinTone
|
|
93
|
+
: null;
|
|
94
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { SkinTones } from '../types/exposedTypes';
|
|
2
|
+
import { DataEmoji } from './DataTypes';
|
|
3
|
+
import { emojiUnified } from './emojiSelectors';
|
|
4
|
+
|
|
5
|
+
const RECENTLY_USED_LS_KEY = 'epr_recentlyUsed';
|
|
6
|
+
|
|
7
|
+
type RecentlyUsedItem = {
|
|
8
|
+
unified: string;
|
|
9
|
+
original: string;
|
|
10
|
+
count: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type RecentlyUsed = RecentlyUsedItem[];
|
|
14
|
+
|
|
15
|
+
export function getRecentlyUsed(): RecentlyUsed {
|
|
16
|
+
if (!window?.localStorage) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(
|
|
21
|
+
window?.localStorage.getItem(RECENTLY_USED_LS_KEY) ?? '[]'
|
|
22
|
+
);
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function setRecentlyUsed(emoji: DataEmoji, skinTone: SkinTones) {
|
|
29
|
+
const recent = getRecentlyUsed();
|
|
30
|
+
|
|
31
|
+
const unified = emojiUnified(emoji, skinTone);
|
|
32
|
+
const originalUnified = emojiUnified(emoji);
|
|
33
|
+
|
|
34
|
+
let existing = recent.find(({ unified: u }) => u === unified);
|
|
35
|
+
|
|
36
|
+
let nextList;
|
|
37
|
+
|
|
38
|
+
if (existing) {
|
|
39
|
+
nextList = [existing].concat(recent.filter(i => i !== existing));
|
|
40
|
+
} else {
|
|
41
|
+
existing = {
|
|
42
|
+
unified,
|
|
43
|
+
original: originalUnified,
|
|
44
|
+
count: 0
|
|
45
|
+
};
|
|
46
|
+
nextList = [existing, ...recent];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
existing.count++;
|
|
50
|
+
|
|
51
|
+
nextList.length = Math.min(nextList.length, 14);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
window?.localStorage.setItem(
|
|
55
|
+
RECENTLY_USED_LS_KEY,
|
|
56
|
+
JSON.stringify(nextList)
|
|
57
|
+
);
|
|
58
|
+
} catch {
|
|
59
|
+
// ignore
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
emojiUnified,
|
|
3
|
+
emojiUrlByUnified,
|
|
4
|
+
emojiVariations
|
|
5
|
+
} from '../dataUtils/emojiSelectors';
|
|
6
|
+
import { DataEmoji } from '../dataUtils/DataTypes';
|
|
7
|
+
import { EmojiStyle } from '../types/exposedTypes';
|
|
8
|
+
|
|
9
|
+
export function preloadEmoji(emoji: DataEmoji, emojiStyle: EmojiStyle): void {
|
|
10
|
+
const unified = emojiUnified(emoji);
|
|
11
|
+
|
|
12
|
+
if (preloadedEmojs.has(unified)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
emojiVariations(emoji).forEach(variation => {
|
|
17
|
+
const emojiUrl = emojiUrlByUnified(emojiStyle, variation);
|
|
18
|
+
preloadImage(emojiUrl);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
preloadedEmojs.add(unified);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const preloadedEmojs: Set<string> = new Set();
|
|
25
|
+
|
|
26
|
+
function preloadImage(url: string): void {
|
|
27
|
+
const image = new Image();
|
|
28
|
+
image.src = url;
|
|
29
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import { categoryNameFromDom } from '../DomUtils/categoryNameFromDom';
|
|
4
|
+
import { ElementRef } from '../components/context/ElementRefContext';
|
|
5
|
+
import { useActiveCategoryState } from '../components/context/PickerContext';
|
|
6
|
+
|
|
7
|
+
export function useActiveCategoryScrollDetection(bodyRef: ElementRef) {
|
|
8
|
+
const [, setActiveCategory] = useActiveCategoryState();
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const visibleCategories = new Map();
|
|
12
|
+
const observer = new IntersectionObserver(
|
|
13
|
+
entries => {
|
|
14
|
+
const refCurrent = bodyRef.current;
|
|
15
|
+
if (!refCurrent) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const id = categoryNameFromDom(entry.target);
|
|
21
|
+
visibleCategories.set(id, entry.intersectionRatio);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ratios = Array.from(visibleCategories);
|
|
25
|
+
const lastCategory = ratios[ratios.length - 1];
|
|
26
|
+
|
|
27
|
+
if (lastCategory[1] == 1) {
|
|
28
|
+
return setActiveCategory(lastCategory[0]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const [id, ratio] of ratios) {
|
|
32
|
+
if (ratio) {
|
|
33
|
+
setActiveCategory(id);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
threshold: [0, 1]
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
bodyRef.current?.querySelectorAll('.epr-emoji-category').forEach(el => {
|
|
43
|
+
observer.observe(el);
|
|
44
|
+
});
|
|
45
|
+
}, [bodyRef]);
|
|
46
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useEmojiVariationPickerState,
|
|
5
|
+
useSkinToneFanOpenState
|
|
6
|
+
} from '../components/context/PickerContext';
|
|
7
|
+
|
|
8
|
+
export function useCloseAllOpenToggles() {
|
|
9
|
+
const [variationPicker, setVariationPicker] = useEmojiVariationPickerState();
|
|
10
|
+
const [skinToneFanOpen, setSkinToneFanOpen] = useSkinToneFanOpenState();
|
|
11
|
+
|
|
12
|
+
const closeAllOpenToggles = useCallback(() => {
|
|
13
|
+
if (variationPicker) {
|
|
14
|
+
setVariationPicker(null);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (skinToneFanOpen) {
|
|
18
|
+
setSkinToneFanOpen(false);
|
|
19
|
+
}
|
|
20
|
+
}, [[variationPicker, skinToneFanOpen]]);
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
closeAllOpenToggles,
|
|
24
|
+
dependencyArray: [variationPicker, skinToneFanOpen]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buttonFromEmoji,
|
|
5
|
+
originalUnifiedFromEmojiElement,
|
|
6
|
+
unifiedFromEmojiElement
|
|
7
|
+
} from '../DomUtils/selectors';
|
|
8
|
+
import { useBodyRef } from '../components/context/ElementRefContext';
|
|
9
|
+
import { HoveredEmoji } from '../components/footer/Preview';
|
|
10
|
+
|
|
11
|
+
export function useEmojiMouseEnter(
|
|
12
|
+
allow: boolean,
|
|
13
|
+
setHoveredEmoji: React.Dispatch<React.SetStateAction<HoveredEmoji>>
|
|
14
|
+
) {
|
|
15
|
+
const BodyRef = useBodyRef();
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!allow) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const bodyRef = BodyRef.current;
|
|
22
|
+
|
|
23
|
+
bodyRef?.addEventListener('mouseover', onMouseOver, {
|
|
24
|
+
passive: true
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
bodyRef?.addEventListener('mouseout', onMouseOut, {
|
|
28
|
+
passive: true
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return () => {
|
|
32
|
+
bodyRef?.removeEventListener('mouseover', onMouseOver);
|
|
33
|
+
bodyRef?.removeEventListener('mouseout', onMouseOut);
|
|
34
|
+
};
|
|
35
|
+
}, [BodyRef.current]);
|
|
36
|
+
|
|
37
|
+
function onMouseOver(e: MouseEvent) {
|
|
38
|
+
const button = buttonFromEmoji(e.target as HTMLElement);
|
|
39
|
+
|
|
40
|
+
if (!button) {
|
|
41
|
+
return onMouseOut();
|
|
42
|
+
}
|
|
43
|
+
const unified = unifiedFromEmojiElement(button);
|
|
44
|
+
const originalUnified = originalUnifiedFromEmojiElement(button);
|
|
45
|
+
|
|
46
|
+
if (!unified || !originalUnified) {
|
|
47
|
+
return onMouseOut();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setHoveredEmoji({
|
|
51
|
+
unified,
|
|
52
|
+
originalUnified
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function onMouseOut() {
|
|
57
|
+
setHoveredEmoji(null);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { scrollTo } from '../DomUtils/scrollTo';
|
|
2
|
+
import { usePickerMainRef } from '../components/context/ElementRefContext';
|
|
3
|
+
import {
|
|
4
|
+
FilterState,
|
|
5
|
+
useFilterRef,
|
|
6
|
+
useSearchTermState
|
|
7
|
+
} from '../components/context/PickerContext';
|
|
8
|
+
import { DataEmoji } from '../dataUtils/DataTypes';
|
|
9
|
+
import { alphaNumericEmojiIndex } from '../dataUtils/alphaNumericEmojiIndex';
|
|
10
|
+
import { emojiNames } from '../dataUtils/emojiSelectors';
|
|
11
|
+
|
|
12
|
+
function useSetFilterRef() {
|
|
13
|
+
const filterRef = useFilterRef();
|
|
14
|
+
|
|
15
|
+
return function setFilter(
|
|
16
|
+
setter: FilterState | ((current: FilterState) => FilterState)
|
|
17
|
+
): void {
|
|
18
|
+
if (typeof setter === 'function') {
|
|
19
|
+
return setFilter(setter(filterRef.current));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
filterRef.current = setter;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useFilter() {
|
|
27
|
+
const filterRef = useFilterRef();
|
|
28
|
+
const setFilterRef = useSetFilterRef();
|
|
29
|
+
|
|
30
|
+
const [searchTerm, setSearchTerm] = useSearchTermState();
|
|
31
|
+
const PickerMainRef = usePickerMainRef();
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
onChange,
|
|
35
|
+
searchTerm,
|
|
36
|
+
clearSearch
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function clearSearch() {
|
|
40
|
+
setSearchTerm('');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function onChange(nextValue: string) {
|
|
44
|
+
scrollTo(PickerMainRef.current, 0);
|
|
45
|
+
setSearchTerm(nextValue);
|
|
46
|
+
const filter = filterRef.current;
|
|
47
|
+
|
|
48
|
+
if (nextValue.length === 0) {
|
|
49
|
+
// setFilter(null);
|
|
50
|
+
return;
|
|
51
|
+
} else if (nextValue.length === 1) {
|
|
52
|
+
const index = alphaNumericEmojiIndex;
|
|
53
|
+
setFilterRef(current => ({
|
|
54
|
+
...current,
|
|
55
|
+
[nextValue]: index[nextValue]
|
|
56
|
+
}));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (filter?.[nextValue]) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const longestMatch = findLongestMatch(nextValue, filter);
|
|
65
|
+
|
|
66
|
+
if (!longestMatch) {
|
|
67
|
+
// Can we even get here?
|
|
68
|
+
// If so, we need to search among all emojis
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setFilterRef(current => ({
|
|
73
|
+
...current,
|
|
74
|
+
[nextValue]: filterEmojiObjectByKeyword(longestMatch, nextValue)
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function filterEmojiObjectByKeyword(
|
|
80
|
+
emojis: FilterDict,
|
|
81
|
+
keyword: string
|
|
82
|
+
): FilterDict {
|
|
83
|
+
const filtered: FilterDict = {};
|
|
84
|
+
|
|
85
|
+
for (const unified in emojis) {
|
|
86
|
+
const emoji = emojis[unified];
|
|
87
|
+
|
|
88
|
+
if (hasMatch(emoji, keyword)) {
|
|
89
|
+
filtered[unified] = emoji;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return filtered;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function hasMatch(emoji: DataEmoji, keyword: string): boolean {
|
|
97
|
+
return emojiNames(emoji).some(name => name.includes(keyword));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function useIsEmojiFiltered(): (unified: string) => boolean {
|
|
101
|
+
const { current: filter } = useFilterRef();
|
|
102
|
+
const [searchTerm] = useSearchTermState();
|
|
103
|
+
|
|
104
|
+
return unified => isEmojiFilteredBySearchTerm(unified, filter, searchTerm);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isEmojiFilteredBySearchTerm(
|
|
108
|
+
unified: string,
|
|
109
|
+
filter: FilterState,
|
|
110
|
+
searchTerm: string
|
|
111
|
+
): boolean {
|
|
112
|
+
if (!filter || !searchTerm) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return !filter[searchTerm]?.[unified];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type FilterDict = Record<string, DataEmoji>;
|
|
120
|
+
|
|
121
|
+
function findLongestMatch(
|
|
122
|
+
keyword: string,
|
|
123
|
+
dict: Record<string, FilterDict> | null
|
|
124
|
+
): FilterDict | null {
|
|
125
|
+
if (!dict) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (dict[keyword]) {
|
|
130
|
+
return dict[keyword];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const longestMatchingKey = Object.keys(dict)
|
|
134
|
+
.sort((a, b) => b.length - a.length)
|
|
135
|
+
.find(key => keyword.includes(key));
|
|
136
|
+
|
|
137
|
+
if (longestMatchingKey) {
|
|
138
|
+
return dict[longestMatchingKey];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEmojisThatFailedToLoadState } from '../components/context/PickerContext';
|
|
2
|
+
import { DataEmoji } from '../dataUtils/DataTypes';
|
|
3
|
+
import { emojiUnified } from '../dataUtils/emojiSelectors';
|
|
4
|
+
|
|
5
|
+
import { useIsEmojiFiltered } from './useFilter';
|
|
6
|
+
|
|
7
|
+
export function useIsEmojiHidden(): (emoji: DataEmoji) => boolean {
|
|
8
|
+
const [emojisThatFailedToLoad] = useEmojisThatFailedToLoadState();
|
|
9
|
+
const isEmojiFiltered = useIsEmojiFiltered();
|
|
10
|
+
|
|
11
|
+
return (emoji: DataEmoji): boolean => {
|
|
12
|
+
const unified = emojiUnified(emoji);
|
|
13
|
+
|
|
14
|
+
return emojisThatFailedToLoad.has(unified) || isEmojiFiltered(unified);
|
|
15
|
+
};
|
|
16
|
+
}
|