emoji-picker-react 4.0.0-rc.19 → 4.0.0-rc.21

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 (82) hide show
  1. package/README.md +44 -4
  2. package/dist/DomUtils/classNames.d.ts +6 -1
  3. package/dist/DomUtils/elementPositionInRow.d.ts +10 -0
  4. package/dist/DomUtils/emojiElementRef.d.ts +4 -3
  5. package/dist/DomUtils/focusElement.d.ts +5 -0
  6. package/dist/DomUtils/getActiveElement.d.ts +2 -0
  7. package/dist/DomUtils/keyboardNavigation.d.ts +8 -0
  8. package/dist/DomUtils/scrollCategoryIntoView.d.ts +2 -1
  9. package/dist/DomUtils/scrollTo.d.ts +3 -1
  10. package/dist/DomUtils/selectors.d.ts +23 -11
  11. package/dist/components/context/ElementRefContext.d.ts +9 -13
  12. package/dist/components/context/PickerContext.d.ts +1 -1
  13. package/dist/components/emoji/Emoji.d.ts +2 -2
  14. package/dist/components/footer/Preview.d.ts +1 -1
  15. package/dist/config/config.d.ts +12 -15
  16. package/dist/config/useConfig.d.ts +2 -1
  17. package/dist/dataUtils/emojiSelectors.d.ts +2 -2
  18. package/dist/emoji-picker-react.cjs.development.css +7 -7
  19. package/dist/emoji-picker-react.cjs.development.js +1119 -302
  20. package/dist/emoji-picker-react.cjs.development.js.map +1 -1
  21. package/dist/emoji-picker-react.cjs.production.min.js +1 -1
  22. package/dist/emoji-picker-react.cjs.production.min.js.map +1 -1
  23. package/dist/emoji-picker-react.esm.js +1129 -312
  24. package/dist/emoji-picker-react.esm.js.map +1 -1
  25. package/dist/hooks/useCloseAllOpenToggles.d.ts +1 -4
  26. package/dist/hooks/useDebouncedState.d.ts +1 -1
  27. package/dist/hooks/useEmojiPreviewEvents.d.ts +3 -0
  28. package/dist/hooks/useFilter.d.ts +3 -3
  29. package/dist/hooks/useFocus.d.ts +3 -0
  30. package/dist/hooks/useInitialLoad.d.ts +1 -1
  31. package/dist/hooks/useKeyboardNavigation.d.ts +1 -0
  32. package/dist/hooks/useMouseDownHandlers.d.ts +3 -2
  33. package/package.json +7 -4
  34. package/src/DomUtils/classNames.ts +9 -1
  35. package/src/DomUtils/elementPositionInRow.ts +169 -0
  36. package/src/DomUtils/emojiElementRef.ts +4 -3
  37. package/src/DomUtils/focusElement.ts +35 -0
  38. package/src/DomUtils/getActiveElement.ts +5 -0
  39. package/src/DomUtils/keyboardNavigation.ts +159 -0
  40. package/src/DomUtils/scrollCategoryIntoView.ts +2 -1
  41. package/src/DomUtils/scrollTo.ts +22 -1
  42. package/src/DomUtils/selectors.ts +157 -32
  43. package/src/EmojiPickerReact.css +57 -29
  44. package/src/components/ErrorBoundary.tsx +1 -0
  45. package/src/components/body/EmojiCategory.css +14 -20
  46. package/src/components/body/EmojiCategory.tsx +3 -3
  47. package/src/components/body/EmojiList.tsx +8 -7
  48. package/src/components/body/EmojiVariationPicker.tsx +3 -3
  49. package/src/components/body/Suggested.tsx +3 -1
  50. package/src/components/context/ElementRefContext.tsx +46 -19
  51. package/src/components/context/PickerContext.tsx +16 -14
  52. package/src/components/emoji/Emoji.css +7 -2
  53. package/src/components/emoji/Emoji.tsx +10 -10
  54. package/src/components/footer/Preview.css +1 -1
  55. package/src/components/footer/Preview.tsx +13 -16
  56. package/src/components/header/Search.css +7 -7
  57. package/src/components/header/Search.tsx +6 -4
  58. package/src/components/header/SkinTonePicker.css +12 -5
  59. package/src/components/header/SkinTonePicker.tsx +11 -3
  60. package/src/components/main/PickerMain.tsx +6 -2
  61. package/src/components/navigation/CategoryNavigation.css +11 -0
  62. package/src/components/navigation/CategoryNavigation.tsx +5 -2
  63. package/src/config/config.ts +39 -10
  64. package/src/config/useConfig.ts +6 -5
  65. package/src/dataUtils/emojiSelectors.ts +3 -2
  66. package/src/dataUtils/suggested.ts +1 -0
  67. package/src/hooks/preloadEmoji.ts +1 -1
  68. package/src/hooks/useActiveCategoryScrollDetection.ts +7 -4
  69. package/src/hooks/useCloseAllOpenToggles.ts +7 -5
  70. package/src/hooks/useDebouncedState.ts +10 -7
  71. package/src/hooks/useEmojiPreviewEvents.ts +90 -0
  72. package/src/hooks/useFilter.ts +41 -21
  73. package/src/hooks/useFocus.ts +40 -0
  74. package/src/hooks/useInitialLoad.ts +1 -0
  75. package/src/hooks/useKeyboardNavigation.ts +389 -0
  76. package/src/hooks/useMouseDownHandlers.ts +100 -74
  77. package/src/hooks/useOnScroll.ts +2 -2
  78. package/src/index.tsx +3 -1
  79. package/dist/hooks/useEmojiElements.d.ts +0 -1
  80. package/dist/hooks/useEmojiMouseEnter.d.ts +0 -3
  81. package/src/hooks/useEmojiElements.ts +0 -7
  82. package/src/hooks/useEmojiMouseEnter.ts +0 -59
@@ -4,14 +4,26 @@ import {
4
4
  unifiedWithoutSkinTone
5
5
  } from '../dataUtils/emojiSelectors';
6
6
 
7
- export function buttonFromEmoji(
8
- emojiElement: HTMLElement | null
7
+ import { asSelectors, ClassNames } from './classNames';
8
+ import { firstVisibleElementInContainer } from './elementPositionInRow';
9
+
10
+ export type NullableElement = HTMLElement | null;
11
+
12
+ export const EmojiButtonSelector = `button${asSelectors(ClassNames.emoji)}`;
13
+ export const VisibleEmojiSelector = [
14
+ EmojiButtonSelector,
15
+ asSelectors(ClassNames.visible),
16
+ `:not(${asSelectors(ClassNames.hidden)})`
17
+ ].join('');
18
+
19
+ export function buttonFromTarget(
20
+ emojiElement: NullableElement
9
21
  ): HTMLButtonElement | null {
10
- return emojiElement?.closest('button.epr-emoji') ?? null;
22
+ return emojiElement?.closest(EmojiButtonSelector) ?? null;
11
23
  }
12
24
 
13
25
  export function emojiFromElement(
14
- element: HTMLElement | null
26
+ element: NullableElement
15
27
  ): [DataEmoji, string] | [] {
16
28
  const originalUnified = originalUnifiedFromEmojiElement(element);
17
29
  const unified = unifiedFromEmojiElement(element);
@@ -29,65 +41,57 @@ export function emojiFromElement(
29
41
  return [emoji, unified as string];
30
42
  }
31
43
 
32
- export function isEmojiElement(element: HTMLElement | null): boolean {
44
+ export function isEmojiElement(element: NullableElement): boolean {
33
45
  return Boolean(
34
- element?.matches('button.epr-emoji') ||
35
- element?.parentElement?.matches('button.epr-emoji')
46
+ element?.matches(EmojiButtonSelector) ||
47
+ element?.parentElement?.matches(EmojiButtonSelector)
36
48
  );
37
49
  }
38
50
 
39
- export function closestCategory(
40
- element: HTMLElement | null
41
- ): HTMLElement | null {
42
- return element?.closest('.epr-emoji-category') ?? null;
43
- }
44
-
45
51
  export function categoryLabelFromCategory(
46
- category: HTMLElement | null
47
- ): HTMLElement | null {
48
- return category?.querySelector('.epr-emoji-category-label') ?? null;
52
+ category: NullableElement
53
+ ): NullableElement {
54
+ return category?.querySelector(asSelectors(ClassNames.label)) ?? null;
49
55
  }
50
56
 
51
57
  export function closestCategoryLabel(
52
- element: null | HTMLElement
53
- ): HTMLElement | null {
58
+ element: NullableElement
59
+ ): NullableElement {
54
60
  const category = closestCategory(element);
55
61
  return categoryLabelFromCategory(category);
56
62
  }
57
63
 
58
- export function elementHeight(element: HTMLElement | null): number {
64
+ export function elementHeight(element: NullableElement): number {
59
65
  return element?.clientHeight ?? 0;
60
66
  }
61
67
 
62
- export function emojiTrueOffsetTop(element: HTMLElement | null): number {
63
- const button = buttonFromEmoji(element);
68
+ export function emojiTrueOffsetTop(element: NullableElement): number {
69
+ const button = buttonFromTarget(element);
64
70
  const category = closestCategory(button);
65
71
 
66
72
  return elementOffsetTop(button) + elementOffsetTop(category);
67
73
  }
68
- export function emojiTruOffsetLeft(element: HTMLElement | null): number {
69
- const button = buttonFromEmoji(element);
74
+ export function emojiTruOffsetLeft(element: NullableElement): number {
75
+ const button = buttonFromTarget(element);
70
76
  const category = closestCategory(button);
71
77
 
72
78
  return elementOffsetLeft(button) + elementOffsetLeft(category);
73
79
  }
74
80
 
75
- function elementOffsetTop(element: HTMLElement | null): number {
81
+ function elementOffsetTop(element: NullableElement): number {
76
82
  return element?.offsetTop ?? 0;
77
83
  }
78
84
 
79
- function elementOffsetLeft(element: HTMLElement | null): number {
85
+ function elementOffsetLeft(element: NullableElement): number {
80
86
  return element?.offsetLeft ?? 0;
81
87
  }
82
88
 
83
- export function unifiedFromEmojiElement(
84
- emoji: HTMLElement | null
85
- ): string | null {
86
- return elementDataSetKey(buttonFromEmoji(emoji), 'unified') ?? null;
89
+ export function unifiedFromEmojiElement(emoji: NullableElement): string | null {
90
+ return elementDataSetKey(buttonFromTarget(emoji), 'unified') ?? null;
87
91
  }
88
92
 
89
93
  export function originalUnifiedFromEmojiElement(
90
- emoji: HTMLElement | null
94
+ emoji: NullableElement
91
95
  ): string | null {
92
96
  const unified = unifiedFromEmojiElement(emoji);
93
97
 
@@ -98,12 +102,133 @@ export function originalUnifiedFromEmojiElement(
98
102
  }
99
103
 
100
104
  function elementDataSetKey(
101
- element: HTMLElement | null,
105
+ element: NullableElement,
102
106
  key: string
103
107
  ): string | null {
104
108
  return elementDataSet(element)[key] ?? null;
105
109
  }
106
110
 
107
- function elementDataSet(element: HTMLElement | null): DOMStringMap {
111
+ function elementDataSet(element: NullableElement): DOMStringMap {
108
112
  return element?.dataset ?? {};
109
113
  }
114
+
115
+ export function isVisibleEmoji(element: HTMLElement) {
116
+ return element.classList.contains(ClassNames.visible);
117
+ }
118
+
119
+ export function isHidden(element: NullableElement) {
120
+ if (!element) return true;
121
+
122
+ return element.classList.contains(ClassNames.hidden);
123
+ }
124
+
125
+ export function allVisibleEmojis(parent: NullableElement) {
126
+ if (!parent) {
127
+ return [];
128
+ }
129
+
130
+ return Array.from(
131
+ parent.querySelectorAll(VisibleEmojiSelector)
132
+ ) as HTMLElement[];
133
+ }
134
+
135
+ export function lastVisibleEmoji(element: NullableElement): NullableElement {
136
+ if (!element) return null;
137
+
138
+ const allEmojis = allVisibleEmojis(element);
139
+ const [last] = allEmojis.slice(-1);
140
+ if (!last) {
141
+ return null;
142
+ }
143
+
144
+ if (!isVisibleEmoji(last)) {
145
+ return prevVisibleEmoji(last);
146
+ }
147
+
148
+ return last;
149
+ }
150
+
151
+ export function nextVisibleEmoji(element: HTMLElement): NullableElement {
152
+ const next = element.nextElementSibling as HTMLElement;
153
+
154
+ if (!next) {
155
+ return firstVisibleEmoji(nextCategory(element));
156
+ }
157
+
158
+ if (!isVisibleEmoji(next)) {
159
+ return nextVisibleEmoji(next);
160
+ }
161
+
162
+ return next;
163
+ }
164
+
165
+ export function prevVisibleEmoji(element: HTMLElement): NullableElement {
166
+ const prev = element.previousElementSibling as HTMLElement;
167
+
168
+ if (!prev) {
169
+ return lastVisibleEmoji(prevCategory(element));
170
+ }
171
+
172
+ if (!isVisibleEmoji(prev)) {
173
+ return prevVisibleEmoji(prev);
174
+ }
175
+
176
+ return prev;
177
+ }
178
+
179
+ export function firstVisibleEmoji(parent: NullableElement) {
180
+ if (!parent) {
181
+ return null;
182
+ }
183
+
184
+ const allEmojis = allVisibleEmojis(parent);
185
+
186
+ return firstVisibleElementInContainer(parent, allEmojis);
187
+ }
188
+
189
+ export function prevCategory(element: NullableElement): NullableElement {
190
+ const category = closestCategory(element);
191
+
192
+ if (!category) {
193
+ return null;
194
+ }
195
+
196
+ const prev = category.previousElementSibling as HTMLElement;
197
+
198
+ if (!prev) {
199
+ return null;
200
+ }
201
+
202
+ if (isHidden(prev)) {
203
+ return prevCategory(prev);
204
+ }
205
+
206
+ return prev;
207
+ }
208
+
209
+ export function nextCategory(element: NullableElement): NullableElement {
210
+ const category = closestCategory(element);
211
+
212
+ if (!category) {
213
+ return null;
214
+ }
215
+
216
+ const next = category.nextElementSibling as HTMLElement;
217
+
218
+ if (!next) {
219
+ return null;
220
+ }
221
+
222
+ if (isHidden(next)) {
223
+ return nextCategory(next);
224
+ }
225
+
226
+ return next;
227
+ }
228
+
229
+ export function closestCategory(element: NullableElement) {
230
+ if (!element) {
231
+ return null;
232
+ }
233
+ return element.closest(asSelectors(ClassNames.category)) as HTMLElement;
234
+ }
@@ -1,50 +1,75 @@
1
1
  .EmojiPickerReact {
2
2
  --epr-highlight-color: #007aeb;
3
3
  --epr-hover-bg-color: #f1f8ff;
4
+ --epr-focus-bg-color: #e0f0ff;
4
5
  --epr-text-color: #858585;
5
- --epr-search-bar-bg-color: #f6f6f6;
6
- --epr-category-label-bg-color: #ffffffe6;
6
+ --epr-search-input-bg-color: #f6f6f6;
7
7
  --epr-picker-border-color: #e7e7e7;
8
8
  --epr-bg-color: #fff;
9
- --epr-emoji-variation-indicator-color: var(--epr-picker-border-color);
10
- --epr-emoji-variation-indicator-color-hover: var(--epr-text-color);
9
+ --epr-category-icon-active-color: #6aa8de;
11
10
 
11
+ --epr-horizontal-padding: 10px;
12
+
13
+ /* Picker Dimensions */
12
14
  --epr-picker-width: 350px;
13
15
  --epr-picker-height: 450px;
14
- --epr-emoji-size: 30px;
15
- --epr-category-navigation-button-size: 30px;
16
- --epr-emoji-variation-picker-bg-color: var(--epr-bg-color);
17
- --epr-emoji-variation-picker-height: 55px;
18
- --epr-preview-height: 70px;
19
- --epr-preview-text-color: var(--epr-text-color);
20
- --epr-preview-text-size: 14px;
21
- --epr-preview-text-padding: 0 10px;
22
- --epr-active-category-indicator-color: var(--epr-highlight-color);
23
- --epr-active-skin-tone-indicator-border-color: var(--epr-highlight-color);
16
+ --epr-picker-border-radius: 8px;
17
+
18
+ /* Header */
24
19
  --epr-search-border-color: var(--epr-highlight-color);
20
+ --epr-header-padding: 15px var(--epr-horizontal-padding);
21
+
22
+ /* Skin Tone Picker */
23
+ --epr-active-skin-tone-indicator-border-color: var(--epr-highlight-color);
25
24
  --epr-active-skin-hover-color: var(--epr-hover-bg-color);
26
- --epr-emoji-hover-color: var(--epr-hover-bg-color);
27
- --epr-category-label-text-color: var(--epr-text-color);
28
- --epr-emoji-padding: 5px;
29
- --epr-header-padding: 15px 10px;
30
- --epr-emoji-gap: 2px;
31
- --epr-category-padding: 0 10px;
25
+
26
+ /* Search */
27
+ --epr-search-input-bg-color-active: var(--epr-search-input-bg-color);
32
28
  --epr-search-input-padding: 0 30px;
33
29
  --epr-search-input-border-radius: 8px;
34
30
  --epr-search-input-height: 40px;
35
- --epr-category-label-padding: 0 10px;
31
+ --epr-search-input-text-color: var(--epr-text-color);
32
+ --epr-search-input-placeholder-color: var(--epr-text-color);
33
+ --epr-search-bar-inner-padding: var(--epr-horizontal-padding);
34
+
35
+ /* Category Navigation */
36
+ --epr-category-navigation-button-size: 30px;
37
+
38
+ /* Variation Picker */
39
+ --epr-emoji-variation-picker-height: 55px;
40
+ --epr-emoji-variation-picker-bg-color: var(--epr-bg-color);
41
+
42
+ /* Preview */
43
+ --epr-preview-height: 70px;
44
+ --epr-preview-text-size: 14px;
45
+ --epr-preview-text-padding: 0 var(--epr-horizontal-padding);
46
+ --epr-preview-border-color: var(--epr-picker-border-color);
47
+ --epr-preview-text-color: var(--epr-text-color);
48
+
49
+ /* Category */
50
+ --epr-category-padding: 0 var(--epr-horizontal-padding);
51
+
52
+ /* Category Label */
53
+ --epr-category-label-bg-color: #ffffffe6;
54
+ --epr-category-label-text-color: var(--epr-text-color);
55
+ --epr-category-label-padding: 0 var(--epr-horizontal-padding);
36
56
  --epr-category-label-height: 40px;
37
57
 
58
+ /* Emoji */
59
+ --epr-emoji-size: 30px;
60
+ --epr-emoji-padding: 5px;
61
+ --epr-emoji-fullsize: calc(
62
+ var(--epr-emoji-size) + var(--epr-emoji-padding) * 2
63
+ );
64
+ --epr-emoji-hover-color: var(--epr-hover-bg-color);
65
+ --epr-emoji-variation-indicator-color: var(--epr-picker-border-color);
66
+ --epr-emoji-variation-indicator-color-hover: var(--epr-text-color);
67
+
68
+ /* Z-Index */
38
69
  --epr-header-overlay-z-index: 3;
39
70
  --epr-emoji-variations-indictator-z-index: 1;
40
71
  --epr-category-label-z-index: 2;
41
72
  --epr-skin-variation-picker-z-index: 5;
42
-
43
- --epr-picker-border-radius: 8px;
44
- --epr-search-bar-text-color: var(--epr-text-color);
45
- --epr-search-bar-placeholder-color: var(--epr-text-color);
46
- --epr-preview-border-color: var(--epr-picker-border-color);
47
- --epr-search-bar-bg-color-active: var(--epr-search-bar-bg-color);
48
73
  }
49
74
 
50
75
  .EmojiPickerReact.epr-dark-theme {
@@ -53,12 +78,14 @@
53
78
  --epr-highlight-color: #c0c0c0;
54
79
  --epr-text-color: var(--epr-highlight-color);
55
80
  --epr-hover-bg-color: #363636f6;
56
- --epr-search-bar-bg-color: #333333;
81
+ --epr-focus-bg-color: #474747;
82
+ --epr-search-input-bg-color: #333333;
57
83
  --epr-category-label-bg-color: #222222e6;
58
84
  --epr-picker-border-color: #151617;
59
85
  --epr-bg-color: #222222;
60
- --epr-search-bar-bg-color-active: var(--epr-dark);
86
+ --epr-search-input-bg-color-active: var(--epr-dark);
61
87
  --epr-emoji-variation-indicator-color: #444;
88
+ --epr-category-icon-active-color: #3271b7;
62
89
  }
63
90
 
64
91
  .EmojiPickerReact {
@@ -89,4 +116,5 @@
89
116
  cursor: pointer;
90
117
  border: 0;
91
118
  background: none;
119
+ outline: none;
92
120
  }
@@ -14,6 +14,7 @@ export default class ErrorBoundary extends React.Component<
14
14
  }
15
15
 
16
16
  componentDidCatch(error: Error, errorInfo: any) {
17
+ // eslint-disable-next-line no-console
17
18
  console.error('Emoji Picker React failed to render:', error, errorInfo);
18
19
  }
19
20
 
@@ -1,3 +1,17 @@
1
+ .EmojiPickerReact li.epr-emoji-category > .epr-emoji-category-content {
2
+ position: relative;
3
+ padding: var(--epr-category-padding);
4
+
5
+ display: grid;
6
+ grid-template-columns: repeat(auto-fill, var(--epr-emoji-fullsize));
7
+ grid-gap: 0;
8
+ justify-content: space-between;
9
+ }
10
+
11
+ .EmojiPickerReact li.epr-emoji-category:not(:has(.epr-visible)) {
12
+ display: none;
13
+ }
14
+
1
15
  .EmojiPickerReact li.epr-emoji-category > .epr-emoji-category-label {
2
16
  position: sticky;
3
17
  top: 0;
@@ -20,23 +34,3 @@
20
34
  position: relative;
21
35
  opacity: 0.4;
22
36
  }
23
-
24
- .EmojiPickerReact li.epr-emoji-category {
25
- position: relative;
26
- display: flex;
27
- flex-direction: row;
28
- flex-wrap: wrap;
29
- justify-content: space-around;
30
- padding: var(--epr-category-padding);
31
- gap: var(--epr-emoji-gap);
32
- }
33
-
34
- .EmojiPickerReact li.epr-emoji-category:after {
35
- content: '';
36
- display: inline-block;
37
- flex: 1;
38
- }
39
-
40
- .EmojiPickerReact li.epr-emoji-category:not(:has(.epr-visible)) {
41
- display: none;
42
- }
@@ -1,12 +1,12 @@
1
1
  import clsx from 'clsx';
2
2
  import * as React from 'react';
3
3
 
4
+ import { ClassNames } from '../../DomUtils/classNames';
4
5
  import {
5
6
  CategoryConfig,
6
7
  categoryFromCategoryConfig,
7
8
  categoryNameFromCategoryConfig
8
9
  } from '../../config/categoryConfig';
9
- import { ClassNames } from '../../DomUtils/classNames';
10
10
  import './EmojiCategory.css';
11
11
 
12
12
  type Props = Readonly<{
@@ -27,14 +27,14 @@ export function EmojiCategory({
27
27
 
28
28
  return (
29
29
  <li
30
- className={clsx('epr-emoji-category', {
30
+ className={clsx(ClassNames.category, {
31
31
  [ClassNames.hidden]: hidden,
32
32
  [ClassNames.hiddenOnSearch]: hiddenOnSearch
33
33
  })}
34
34
  data-name={category}
35
35
  >
36
36
  <div className="epr-emoji-category-label">{categoryName}</div>
37
- {children}
37
+ <div className="epr-emoji-category-content">{children}</div>
38
38
  </li>
39
39
  );
40
40
  }
@@ -1,27 +1,28 @@
1
1
  import * as React from 'react';
2
2
 
3
+ import { getEmojiRef } from '../../DomUtils/emojiElementRef';
3
4
  import {
4
5
  Categories,
5
6
  CategoryConfig,
6
- categoryFromCategoryConfig,
7
+ categoryFromCategoryConfig
7
8
  } from '../../config/categoryConfig';
8
9
  import {
9
10
  useCategoriesConfig,
10
11
  useEmojiStyleConfig,
11
- useLazyLoadEmojisConfig,
12
+ useLazyLoadEmojisConfig
12
13
  } from '../../config/useConfig';
13
14
  import { emojisByCategory, emojiUnified } from '../../dataUtils/emojiSelectors';
14
15
  import { useIsEmojiHidden } from '../../hooks/useIsEmojiHidden';
15
16
  import {
16
17
  useActiveSkinToneState,
17
- useIsPastInitialLoad,
18
+ useIsPastInitialLoad
18
19
  } from '../context/PickerContext';
20
+ import { Emoji } from '../emoji/Emoji';
19
21
 
20
22
  import { EmojiCategory } from './EmojiCategory';
21
23
  import { Suggested } from './Suggested';
24
+
22
25
  import './EmojiList.css';
23
- import { getEmojiRef } from '../../DomUtils/emojiElementRef';
24
- import { Emoji } from '../emoji/Emoji';
25
26
 
26
27
  export function EmojiList() {
27
28
  const categories = useCategoriesConfig();
@@ -51,7 +52,7 @@ export function EmojiList() {
51
52
  function RenderCategory({
52
53
  index,
53
54
  category,
54
- categoryConfig,
55
+ categoryConfig
55
56
  }: {
56
57
  index: number;
57
58
  category: Categories;
@@ -70,7 +71,7 @@ function RenderCategory({
70
71
 
71
72
  let hiddenCounter = 0;
72
73
 
73
- const emojis = emojisToPush.map((emoji) => {
74
+ const emojis = emojisToPush.map(emoji => {
74
75
  const unified = emojiUnified(emoji, activeSkinTone);
75
76
  const hidden = isEmojiHidden(emoji);
76
77
  const emojiRef = getEmojiRef(emojiUnified(emoji));
@@ -2,7 +2,7 @@ import clsx from 'clsx';
2
2
  import * as React from 'react';
3
3
 
4
4
  import {
5
- buttonFromEmoji,
5
+ buttonFromTarget,
6
6
  elementHeight,
7
7
  emojiTrueOffsetTop,
8
8
  emojiTruOffsetLeft
@@ -91,7 +91,7 @@ function usePointerStyle(VariationPickerRef: React.RefObject<HTMLElement>) {
91
91
  }
92
92
 
93
93
  if (AnchoredEmojiRef.current) {
94
- const button = buttonFromEmoji(AnchoredEmojiRef.current);
94
+ const button = buttonFromTarget(AnchoredEmojiRef.current);
95
95
 
96
96
  const offsetLeft = emojiTruOffsetLeft(button);
97
97
 
@@ -135,7 +135,7 @@ function useVariationPickerTop(
135
135
 
136
136
  if (AnchoredEmojiRef.current) {
137
137
  const bodyRef = BodyRef.current;
138
- const button = buttonFromEmoji(AnchoredEmojiRef.current);
138
+ const button = buttonFromTarget(AnchoredEmojiRef.current);
139
139
 
140
140
  const buttonHeight = elementHeight(button);
141
141
 
@@ -25,7 +25,8 @@ export function Suggested({ categoryConfig }: Props) {
25
25
  const suggestedEmojisModeConfig = useSuggestedEmojisModeConfig();
26
26
  const suggested = React.useMemo(
27
27
  () => getsuggested(suggestedEmojisModeConfig) ?? [],
28
- [suggestedUpdated]
28
+ // eslint-disable-next-line react-hooks/exhaustive-deps
29
+ [suggestedUpdated, suggestedEmojisModeConfig]
29
30
  );
30
31
  const emojiStyle = useEmojiStyleConfig();
31
32
 
@@ -51,6 +52,7 @@ export function Suggested({ categoryConfig }: Props) {
51
52
  emojiStyle={emojiStyle}
52
53
  emoji={emoji}
53
54
  key={suggestedItem.unified}
55
+ hidden={hidden}
54
56
  />
55
57
  );
56
58
  })}
@@ -1,5 +1,7 @@
1
1
  import * as React from 'react';
2
2
 
3
+ import { NullableElement } from '../../DomUtils/selectors';
4
+
3
5
  export function ElementRefContextProvider({
4
6
  children
5
7
  }: {
@@ -7,55 +9,80 @@ export function ElementRefContextProvider({
7
9
  }) {
8
10
  const PickerMainRef = React.useRef<HTMLElement>(null);
9
11
  const AnchoredEmojiRef = React.useRef<HTMLElement>(null);
10
- const BodyRef = React.useRef<HTMLElement>(null);
12
+ const BodyRef = React.useRef<HTMLDivElement>(null);
13
+ const SearchInputRef = React.useRef<HTMLInputElement>(null);
14
+ const SkinTonePickerRef = React.useRef<HTMLDivElement>(null);
15
+ const CategoryNavigationRef = React.useRef<HTMLDivElement>(null);
11
16
 
12
17
  return (
13
18
  <ElementRefContext.Provider
14
- value={{ PickerMainRef, AnchoredEmojiRef, BodyRef }}
19
+ value={{
20
+ AnchoredEmojiRef,
21
+ BodyRef,
22
+ CategoryNavigationRef,
23
+ PickerMainRef,
24
+ SearchInputRef,
25
+ SkinTonePickerRef
26
+ }}
15
27
  >
16
28
  {children}
17
29
  </ElementRefContext.Provider>
18
30
  );
19
31
  }
20
32
 
21
- export type ElementRef = React.MutableRefObject<HTMLElement | null>;
33
+ export type ElementRef<
34
+ E extends HTMLElement = HTMLElement
35
+ > = React.MutableRefObject<E | null>;
22
36
 
23
37
  type ElementRefs = {
24
38
  PickerMainRef: ElementRef;
25
39
  AnchoredEmojiRef: ElementRef;
26
- BodyRef: React.MutableRefObject<HTMLElement | null>;
40
+ SkinTonePickerRef: ElementRef<HTMLDivElement>;
41
+ SearchInputRef: ElementRef<HTMLInputElement>;
42
+ BodyRef: ElementRef<HTMLDivElement>;
43
+ CategoryNavigationRef: ElementRef<HTMLDivElement>;
27
44
  };
28
45
 
29
46
  const ElementRefContext = React.createContext<ElementRefs>({
30
- PickerMainRef: React.createRef(),
31
47
  AnchoredEmojiRef: React.createRef(),
32
- BodyRef: React.createRef()
48
+ BodyRef: React.createRef(),
49
+ CategoryNavigationRef: React.createRef(),
50
+ PickerMainRef: React.createRef(),
51
+ SearchInputRef: React.createRef(),
52
+ SkinTonePickerRef: React.createRef()
33
53
  });
34
54
 
35
- type ElementRefKeys = keyof ElementRefs;
36
-
37
- export function useElementRef(key: ElementRefKeys) {
38
- const { [key]: elementRef } = React.useContext(ElementRefContext);
39
- return elementRef;
55
+ function useElementRef() {
56
+ return React.useContext(ElementRefContext);
40
57
  }
41
58
 
42
59
  export function usePickerMainRef() {
43
- return useElementRef('PickerMainRef');
60
+ return useElementRef()['PickerMainRef'];
44
61
  }
45
62
 
46
63
  export function useAnchoredEmojiRef() {
47
- return useElementRef('AnchoredEmojiRef');
64
+ return useElementRef()['AnchoredEmojiRef'];
48
65
  }
49
66
 
50
- export function useSetAnchoredEmojiRef(): (target: null | HTMLElement) => void {
67
+ export function useSetAnchoredEmojiRef(): (target: NullableElement) => void {
51
68
  const AnchoredEmojiRef = useAnchoredEmojiRef();
52
- return (target: null | HTMLElement) => {
69
+ return (target: NullableElement) => {
53
70
  AnchoredEmojiRef.current = target;
54
71
  };
55
72
  }
56
73
 
57
- export function useBodyRef(): React.MutableRefObject<HTMLDivElement | null> {
58
- return useElementRef(
59
- 'BodyRef'
60
- ) as React.MutableRefObject<HTMLDivElement | null>;
74
+ export function useBodyRef() {
75
+ return useElementRef()['BodyRef'];
76
+ }
77
+
78
+ export function useSearchInputRef() {
79
+ return useElementRef()['SearchInputRef'];
80
+ }
81
+
82
+ export function useSkinTonePickerRef() {
83
+ return useElementRef()['SkinTonePickerRef'];
84
+ }
85
+
86
+ export function useCategoryNavigationRef() {
87
+ return useElementRef()['CategoryNavigationRef'];
61
88
  }