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.
Files changed (135) hide show
  1. package/LICENSE +17 -20
  2. package/README.md +129 -168
  3. package/dist/DomUtils/categoryNameFromDom.d.ts +1 -0
  4. package/dist/DomUtils/scrollCategoryIntoView.d.ts +1 -0
  5. package/dist/DomUtils/scrollTo.d.ts +1 -0
  6. package/dist/DomUtils/selectors.d.ts +12 -0
  7. package/dist/components/Layout/Flex.d.ts +14 -0
  8. package/dist/components/Layout/Relative.d.ts +7 -0
  9. package/dist/components/body/Body.d.ts +3 -0
  10. package/dist/components/body/EmojiCategory.d.ts +10 -0
  11. package/dist/components/body/EmojiList.d.ts +3 -0
  12. package/dist/components/body/EmojiVariationPicker.d.ts +3 -0
  13. package/dist/components/body/RecentlyUsed.d.ts +7 -0
  14. package/dist/components/context/ElementRefContext.d.ts +17 -0
  15. package/dist/components/context/PickerConfigContext.d.ts +8 -0
  16. package/dist/components/context/PickerContext.d.ts +21 -0
  17. package/dist/components/emoji/Emoji.d.ts +14 -0
  18. package/dist/components/footer/Preview.d.ts +7 -0
  19. package/dist/components/header/Header.d.ts +3 -0
  20. package/dist/components/header/Search.d.ts +3 -0
  21. package/dist/components/header/SkinTonePicker.d.ts +3 -0
  22. package/dist/components/main/PickerMain.d.ts +7 -0
  23. package/dist/components/navigation/CategoryNavigation.d.ts +3 -0
  24. package/dist/config/categoryConfig.d.ts +21 -0
  25. package/dist/config/cdnUrls.d.ts +2 -0
  26. package/dist/config/config.d.ts +26 -0
  27. package/dist/config/useConfig.d.ts +11 -0
  28. package/dist/data/emojis.d.ts +40 -0
  29. package/dist/data/groups.d.ts +2 -0
  30. package/dist/data/skinToneVariations.d.ts +3 -0
  31. package/dist/dataUtils/DataTypes.d.ts +8 -0
  32. package/dist/dataUtils/alphaNumericEmojiIndex.d.ts +4 -0
  33. package/dist/dataUtils/asEmoji.d.ts +2 -0
  34. package/dist/dataUtils/emojiSelectors.d.ts +15 -0
  35. package/dist/dataUtils/parseNativeEmoji.d.ts +1 -0
  36. package/dist/dataUtils/recentlyUsed.d.ts +11 -0
  37. package/dist/emoji-picker-react.cjs.development.css +13 -0
  38. package/dist/emoji-picker-react.cjs.development.js +7419 -0
  39. package/dist/emoji-picker-react.cjs.development.js.map +1 -0
  40. package/dist/emoji-picker-react.cjs.production.min.js +2 -0
  41. package/dist/emoji-picker-react.cjs.production.min.js.map +1 -0
  42. package/dist/emoji-picker-react.esm.js +7485 -0
  43. package/dist/emoji-picker-react.esm.js.map +1 -0
  44. package/dist/hooks/preloadEmoji.d.ts +4 -0
  45. package/dist/hooks/useActiveCategoryScrollDetection.d.ts +2 -0
  46. package/dist/hooks/useCloseAllOpenToggles.d.ts +4 -0
  47. package/dist/hooks/useEmojiElements.d.ts +1 -0
  48. package/dist/hooks/useEmojiMouseEnter.d.ts +3 -0
  49. package/dist/hooks/useFilter.d.ts +8 -0
  50. package/dist/hooks/useInitialLoad.d.ts +2 -0
  51. package/dist/hooks/useIsEmojiHidden.d.ts +2 -0
  52. package/dist/hooks/useIsSearchMode.d.ts +1 -0
  53. package/dist/hooks/useMouseDownHandlers.d.ts +2 -0
  54. package/dist/hooks/useOnScroll.d.ts +2 -0
  55. package/dist/index.d.ts +7 -0
  56. package/dist/index.js +8 -3
  57. package/dist/types/exposedTypes.d.ts +28 -0
  58. package/package.json +72 -52
  59. package/src/DomUtils/categoryNameFromDom.ts +3 -0
  60. package/src/DomUtils/scrollCategoryIntoView.ts +18 -0
  61. package/src/DomUtils/scrollTo.ts +11 -0
  62. package/src/DomUtils/selectors.ts +93 -0
  63. package/src/EmojiPickerReact.css +85 -0
  64. package/src/components/Layout/Flex.css +11 -0
  65. package/src/components/Layout/Flex.tsx +28 -0
  66. package/src/components/Layout/Relative.tsx +14 -0
  67. package/src/components/body/Body.css +6 -0
  68. package/src/components/body/Body.tsx +24 -0
  69. package/src/components/body/EmojiCategory.css +38 -0
  70. package/src/components/body/EmojiCategory.tsx +27 -0
  71. package/src/components/body/EmojiList.css +5 -0
  72. package/src/components/body/EmojiList.tsx +101 -0
  73. package/src/components/body/EmojiVariationPicker.css +65 -0
  74. package/src/components/body/EmojiVariationPicker.tsx +154 -0
  75. package/src/components/body/RecentlyUsed.tsx +50 -0
  76. package/src/components/body/svg/triangle.svg +11 -0
  77. package/src/components/context/ElementRefContext.tsx +61 -0
  78. package/src/components/context/PickerConfigContext.tsx +29 -0
  79. package/src/components/context/PickerContext.tsx +139 -0
  80. package/src/components/emoji/Emoji.css +44 -0
  81. package/src/components/emoji/Emoji.tsx +104 -0
  82. package/src/components/footer/Preview.css +14 -0
  83. package/src/components/footer/Preview.tsx +80 -0
  84. package/src/components/header/Header.css +4 -0
  85. package/src/components/header/Header.tsx +21 -0
  86. package/src/components/header/Search.css +72 -0
  87. package/src/components/header/Search.tsx +36 -0
  88. package/src/components/header/SkinTonePicker.css +86 -0
  89. package/src/components/header/SkinTonePicker.tsx +55 -0
  90. package/src/components/header/svg/magnifier.svg +13 -0
  91. package/src/components/header/svg/times.svg +33 -0
  92. package/src/components/main/PickerMain.css +11 -0
  93. package/src/components/main/PickerMain.tsx +42 -0
  94. package/src/components/navigation/CategoryNavigation.css +82 -0
  95. package/src/components/navigation/CategoryNavigation.tsx +32 -0
  96. package/src/components/navigation/svg/CategoryNav.svg +508 -0
  97. package/src/config/categoryConfig.ts +107 -0
  98. package/src/config/cdnUrls.ts +24 -0
  99. package/src/config/config.ts +50 -0
  100. package/src/config/useConfig.ts +52 -0
  101. package/src/data/emojis.ts +5350 -0
  102. package/src/data/groups.ts +12 -0
  103. package/src/data/skinToneVariations.ts +12 -0
  104. package/src/dataUtils/DataTypes.ts +10 -0
  105. package/src/dataUtils/alphaNumericEmojiIndex.ts +23 -0
  106. package/src/dataUtils/asEmoji.ts +5 -0
  107. package/src/dataUtils/emojiSelectors.ts +94 -0
  108. package/src/dataUtils/parseNativeEmoji.ts +6 -0
  109. package/src/dataUtils/recentlyUsed.ts +61 -0
  110. package/src/hooks/preloadEmoji.ts +29 -0
  111. package/src/hooks/useActiveCategoryScrollDetection.ts +46 -0
  112. package/src/hooks/useCloseAllOpenToggles.ts +26 -0
  113. package/src/hooks/useEmojiElements.ts +7 -0
  114. package/src/hooks/useEmojiMouseEnter.ts +59 -0
  115. package/src/hooks/useFilter.ts +142 -0
  116. package/src/hooks/useInitialLoad.ts +9 -0
  117. package/src/hooks/useIsEmojiHidden.ts +16 -0
  118. package/src/hooks/useIsSearchMode.ts +7 -0
  119. package/src/hooks/useMouseDownHandlers.ts +160 -0
  120. package/src/hooks/useOnScroll.ts +28 -0
  121. package/src/index.tsx +27 -0
  122. package/src/types/exposedTypes.ts +31 -0
  123. package/.babelrc +0 -30
  124. package/.eslintrc.js +0 -37
  125. package/.github/workflows/npmpublish.yml +0 -30
  126. package/.prettierignore +0 -2
  127. package/.storybook/addons.js +0 -2
  128. package/.storybook/config.js +0 -9
  129. package/assets/captured.gif +0 -0
  130. package/dist/index.js.LICENSE.txt +0 -5
  131. package/dist/index.js.map +0 -1
  132. package/dist/main.css +0 -206
  133. package/dist/main.css.map +0 -1
  134. package/emoji-picker-react.d.ts +0 -45
  135. package/webpack.config.js +0 -52
@@ -0,0 +1,12 @@
1
+ const groups = [
2
+ 'smileys_people',
3
+ 'animals_nature',
4
+ 'food_drink',
5
+ 'travel_places',
6
+ 'activities',
7
+ 'objects',
8
+ 'symbols',
9
+ 'flags'
10
+ ];
11
+
12
+ export default groups;
@@ -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,10 @@
1
+ import emojis from '../data/emojis';
2
+
3
+ export type DataEmoji = {
4
+ n: string[];
5
+ u: string;
6
+ v?: string[];
7
+ };
8
+ export type DataEmojis = DataEmoji[];
9
+
10
+ export type DataGroups = keyof typeof emojis;
@@ -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,5 @@
1
+ import { DataEmoji } from './DataTypes';
2
+
3
+ export function asEmoji(emoji: DataEmoji | undefined | null): DataEmoji {
4
+ return emoji as DataEmoji;
5
+ }
@@ -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,6 @@
1
+ export function parseNativeEmoji(unified: string): string {
2
+ return unified
3
+ .split('-')
4
+ .map(hex => String.fromCodePoint(parseInt(hex, 16)))
5
+ .join('');
6
+ }
@@ -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,7 @@
1
+ import { usePickerMainRef } from '../components/context/ElementRefContext';
2
+
3
+ export function useEmojiElements() {
4
+ const PickerMainRef = usePickerMainRef();
5
+
6
+ return PickerMainRef.current?.querySelectorAll('button.epr-emoji');
7
+ }
@@ -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,9 @@
1
+ import { useEffect } from 'react';
2
+
3
+ export function useMarkInitialLoad(
4
+ dispatch: React.Dispatch<React.SetStateAction<boolean>>
5
+ ) {
6
+ useEffect(() => {
7
+ dispatch(true);
8
+ }, [dispatch]);
9
+ }
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ import { useSearchTermState } from '../components/context/PickerContext';
2
+
3
+ export default function useIsSearchMode(): boolean {
4
+ const [searchTerm] = useSearchTermState();
5
+
6
+ return !!searchTerm;
7
+ }