emoji-picker-react 4.0.0-rc.8 → 4.0.0

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 (117) hide show
  1. package/README.md +176 -43
  2. package/dist/DomUtils/classNames.d.ts +9 -1
  3. package/dist/DomUtils/elementPositionInRow.d.ts +10 -0
  4. package/dist/DomUtils/focusElement.d.ts +5 -0
  5. package/dist/DomUtils/getActiveElement.d.ts +2 -0
  6. package/dist/DomUtils/isDarkTheme.d.ts +1 -0
  7. package/dist/DomUtils/keyboardNavigation.d.ts +8 -0
  8. package/dist/DomUtils/scrollTo.d.ts +3 -1
  9. package/dist/DomUtils/selectors.d.ts +24 -11
  10. package/dist/EmojiPickerReact.d.ts +6 -0
  11. package/dist/components/ErrorBoundary.d.ts +15 -0
  12. package/dist/components/body/{RecentlyUsed.d.ts → Suggested.d.ts} +1 -1
  13. package/dist/components/context/ElementRefContext.d.ts +10 -13
  14. package/dist/components/context/PickerContext.d.ts +3 -3
  15. package/dist/components/emoji/Emoji.d.ts +11 -7
  16. package/dist/components/emoji/ExportedEmoji.d.ts +8 -0
  17. package/dist/components/footer/Preview.d.ts +1 -1
  18. package/dist/config/categoryConfig.d.ts +7 -3
  19. package/dist/config/config.d.ts +18 -15
  20. package/dist/config/useConfig.d.ts +11 -4
  21. package/dist/data/skinToneVariations.d.ts +2 -0
  22. package/dist/dataUtils/emojiSelectors.d.ts +3 -2
  23. package/dist/dataUtils/suggested.d.ts +11 -0
  24. package/dist/emoji-picker-react.cjs.development.css +9 -9
  25. package/dist/emoji-picker-react.cjs.development.js +3774 -3070
  26. package/dist/emoji-picker-react.cjs.development.js.map +1 -1
  27. package/dist/emoji-picker-react.cjs.production.min.js +1 -1
  28. package/dist/emoji-picker-react.cjs.production.min.js.map +1 -1
  29. package/dist/emoji-picker-react.esm.js +3784 -3080
  30. package/dist/emoji-picker-react.esm.js.map +1 -1
  31. package/dist/hooks/preloadEmoji.d.ts +1 -1
  32. package/dist/hooks/useActiveCategoryScrollDetection.d.ts +1 -2
  33. package/dist/hooks/useCloseAllOpenToggles.d.ts +2 -4
  34. package/dist/hooks/useDebouncedState.d.ts +1 -1
  35. package/dist/hooks/useDisallowMouseMove.d.ts +4 -0
  36. package/dist/hooks/useEmojiPreviewEvents.d.ts +3 -0
  37. package/dist/hooks/useFilter.d.ts +3 -3
  38. package/dist/hooks/useFocus.d.ts +3 -0
  39. package/dist/hooks/useInitialLoad.d.ts +1 -1
  40. package/dist/hooks/useIsEmojiHidden.d.ts +7 -1
  41. package/dist/hooks/useKeyboardNavigation.d.ts +1 -0
  42. package/dist/hooks/useMouseDownHandlers.d.ts +3 -2
  43. package/dist/hooks/useOnFocus.d.ts +1 -0
  44. package/dist/hooks/useScrollCategoryIntoView.d.ts +1 -0
  45. package/dist/hooks/useSetVariationPicker.d.ts +2 -0
  46. package/dist/index.d.ts +2 -2
  47. package/dist/types/exposedTypes.d.ts +5 -1
  48. package/package.json +13 -5
  49. package/src/DomUtils/classNames.ts +12 -1
  50. package/src/DomUtils/elementPositionInRow.ts +167 -0
  51. package/src/DomUtils/focusElement.ts +35 -0
  52. package/src/DomUtils/getActiveElement.ts +5 -0
  53. package/src/DomUtils/isDarkTheme.ts +7 -0
  54. package/src/DomUtils/keyboardNavigation.ts +161 -0
  55. package/src/DomUtils/scrollTo.ts +22 -1
  56. package/src/DomUtils/selectors.ts +200 -41
  57. package/src/EmojiPickerReact.css +99 -48
  58. package/src/EmojiPickerReact.tsx +26 -0
  59. package/src/components/ErrorBoundary.tsx +28 -0
  60. package/src/components/body/Body.tsx +3 -2
  61. package/src/components/body/EmojiCategory.css +14 -28
  62. package/src/components/body/EmojiCategory.tsx +5 -4
  63. package/src/components/body/EmojiList.tsx +13 -13
  64. package/src/components/body/EmojiVariationPicker.css +13 -5
  65. package/src/components/body/EmojiVariationPicker.tsx +25 -17
  66. package/src/components/body/Suggested.tsx +54 -0
  67. package/src/components/context/ElementRefContext.tsx +54 -19
  68. package/src/components/context/PickerContext.tsx +36 -33
  69. package/src/components/emoji/Emoji.css +42 -12
  70. package/src/components/emoji/Emoji.tsx +73 -34
  71. package/src/components/emoji/ExportedEmoji.tsx +30 -0
  72. package/src/components/footer/Preview.css +1 -1
  73. package/src/components/footer/Preview.tsx +16 -23
  74. package/src/components/header/Search.css +7 -7
  75. package/src/components/header/Search.tsx +31 -7
  76. package/src/components/header/SkinTonePicker.css +15 -8
  77. package/src/components/header/SkinTonePicker.tsx +23 -5
  78. package/src/components/main/PickerMain.css +0 -2
  79. package/src/components/main/PickerMain.tsx +14 -1
  80. package/src/components/navigation/CategoryNavigation.css +41 -10
  81. package/src/components/navigation/CategoryNavigation.tsx +22 -10
  82. package/src/components/navigation/svg/CategoryNav.svg +343 -379
  83. package/src/config/categoryConfig.ts +40 -14
  84. package/src/config/config.ts +59 -13
  85. package/src/config/useConfig.ts +35 -7
  86. package/src/data/emojis.json +1 -1
  87. package/src/data/skinToneVariations.ts +19 -0
  88. package/src/dataUtils/emojiSelectors.ts +25 -5
  89. package/src/dataUtils/{recentlyUsed.ts → suggested.ts} +20 -18
  90. package/src/hooks/preloadEmoji.ts +13 -2
  91. package/src/hooks/useActiveCategoryScrollDetection.ts +10 -8
  92. package/src/hooks/useCloseAllOpenToggles.ts +15 -4
  93. package/src/hooks/useDebouncedState.ts +10 -7
  94. package/src/hooks/useDisallowMouseMove.ts +47 -0
  95. package/src/hooks/useEmojiPreviewEvents.ts +103 -0
  96. package/src/hooks/useFilter.ts +46 -47
  97. package/src/hooks/useFocus.ts +40 -0
  98. package/src/hooks/useInitialLoad.ts +1 -0
  99. package/src/hooks/useIsEmojiHidden.ts +16 -3
  100. package/src/hooks/useKeyboardNavigation.ts +438 -0
  101. package/src/hooks/useMouseDownHandlers.ts +97 -89
  102. package/src/hooks/useOnFocus.ts +46 -0
  103. package/src/hooks/useOnScroll.ts +2 -2
  104. package/src/hooks/useScrollCategoryIntoView.ts +28 -0
  105. package/src/hooks/useSetVariationPicker.ts +28 -0
  106. package/src/index.tsx +9 -17
  107. package/src/types/exposedTypes.ts +6 -1
  108. package/dist/DomUtils/emojiElementRef.d.ts +0 -10
  109. package/dist/DomUtils/scrollCategoryIntoView.d.ts +0 -1
  110. package/dist/dataUtils/recentlyUsed.d.ts +0 -11
  111. package/dist/hooks/useEmojiElements.d.ts +0 -1
  112. package/dist/hooks/useEmojiMouseEnter.d.ts +0 -3
  113. package/src/DomUtils/emojiElementRef.ts +0 -42
  114. package/src/DomUtils/scrollCategoryIntoView.ts +0 -18
  115. package/src/components/body/RecentlyUsed.tsx +0 -50
  116. package/src/hooks/useEmojiElements.ts +0 -7
  117. package/src/hooks/useEmojiMouseEnter.ts +0 -59
@@ -0,0 +1,438 @@
1
+ import { useCallback, useEffect, useMemo } from 'react';
2
+
3
+ import { hasNextElementSibling } from '../DomUtils/elementPositionInRow';
4
+ import {
5
+ focusNextElementSibling,
6
+ focusPrevElementSibling
7
+ } from '../DomUtils/focusElement';
8
+ import { getActiveElement } from '../DomUtils/getActiveElement';
9
+ import {
10
+ focusAndClickFirstVisibleEmoji,
11
+ focusFirstVisibleEmoji,
12
+ focusNextVisibleEmoji,
13
+ focusPrevVisibleEmoji,
14
+ focusVisibleEmojiOneRowDown,
15
+ focusVisibleEmojiOneRowUp
16
+ } from '../DomUtils/keyboardNavigation';
17
+ import { useScrollTo } from '../DomUtils/scrollTo';
18
+ import { buttonFromTarget } from '../DomUtils/selectors';
19
+ import {
20
+ useBodyRef,
21
+ useCategoryNavigationRef,
22
+ usePickerMainRef,
23
+ useSearchInputRef,
24
+ useSkinTonePickerRef
25
+ } from '../components/context/ElementRefContext';
26
+ import { useSkinToneFanOpenState } from '../components/context/PickerContext';
27
+
28
+ import {
29
+ useCloseAllOpenToggles,
30
+ useHasOpenToggles
31
+ } from './useCloseAllOpenToggles';
32
+ import { useDisallowMouseMove } from './useDisallowMouseMove';
33
+ import { useAppendSearch, useClearSearch } from './useFilter';
34
+ import {
35
+ useFocusCategoryNavigation,
36
+ useFocusSearchInput,
37
+ useFocusSkinTonePicker
38
+ } from './useFocus';
39
+ import useIsSearchMode from './useIsSearchMode';
40
+ import useSetVariationPicker from './useSetVariationPicker';
41
+
42
+ enum KeyboardEvents {
43
+ ArrowDown = 'ArrowDown',
44
+ ArrowUp = 'ArrowUp',
45
+ ArrowLeft = 'ArrowLeft',
46
+ ArrowRight = 'ArrowRight',
47
+ Escape = 'Escape',
48
+ Enter = 'Enter',
49
+ Space = ' '
50
+ }
51
+
52
+ export function useKeyboardNavigation() {
53
+ usePickerMainKeyboardEvents();
54
+ useSearchInputKeyboardEvents();
55
+ useSkinTonePickerKeyboardEvents();
56
+ useCategoryNavigationKeyboardEvents();
57
+ useBodyKeyboardEvents();
58
+ }
59
+
60
+ function usePickerMainKeyboardEvents() {
61
+ const PickerMainRef = usePickerMainRef();
62
+ const clearSearch = useClearSearch();
63
+ const scrollTo = useScrollTo();
64
+ const SearchInputRef = useSearchInputRef();
65
+ const focusSearchInput = useFocusSearchInput();
66
+ const hasOpenToggles = useHasOpenToggles();
67
+ const disallowMouseMove = useDisallowMouseMove();
68
+
69
+ const closeAllOpenToggles = useCloseAllOpenToggles();
70
+
71
+ const onKeyDown = useMemo(
72
+ () =>
73
+ function onKeyDown(event: KeyboardEvent) {
74
+ const { key } = event;
75
+
76
+ switch (key) {
77
+ case KeyboardEvents.ArrowDown:
78
+ case KeyboardEvents.ArrowUp:
79
+ case KeyboardEvents.ArrowLeft:
80
+ case KeyboardEvents.ArrowRight:
81
+ disallowMouseMove();
82
+ break;
83
+ // eslint-disable-next-line no-fallthrough
84
+ case KeyboardEvents.Escape:
85
+ event.preventDefault();
86
+ if (hasOpenToggles()) {
87
+ closeAllOpenToggles();
88
+ return;
89
+ }
90
+ clearSearch();
91
+ scrollTo(0);
92
+ focusSearchInput();
93
+ break;
94
+ }
95
+ },
96
+ [
97
+ scrollTo,
98
+ clearSearch,
99
+ closeAllOpenToggles,
100
+ focusSearchInput,
101
+ hasOpenToggles,
102
+ disallowMouseMove
103
+ ]
104
+ );
105
+
106
+ useEffect(() => {
107
+ const current = PickerMainRef.current;
108
+
109
+ if (!current) {
110
+ return;
111
+ }
112
+
113
+ current.addEventListener('keydown', onKeyDown);
114
+
115
+ return () => {
116
+ current.removeEventListener('keydown', onKeyDown);
117
+ };
118
+ }, [PickerMainRef, SearchInputRef, scrollTo, onKeyDown]);
119
+ }
120
+
121
+ function useSearchInputKeyboardEvents() {
122
+ const focusSkinTonePicker = useFocusSkinTonePicker();
123
+ const PickerMainRef = usePickerMainRef();
124
+ const BodyRef = useBodyRef();
125
+ const SearchInputRef = useSearchInputRef();
126
+ const [, setSkinToneFanOpenState] = useSkinToneFanOpenState();
127
+ const goDownFromSearchInput = useGoDownFromSearchInput();
128
+
129
+ const onKeyDown = useMemo(
130
+ () =>
131
+ function onKeyDown(event: KeyboardEvent) {
132
+ const { key } = event;
133
+
134
+ switch (key) {
135
+ case KeyboardEvents.ArrowRight:
136
+ event.preventDefault();
137
+ setSkinToneFanOpenState(true);
138
+ focusSkinTonePicker();
139
+ break;
140
+ case KeyboardEvents.ArrowDown:
141
+ event.preventDefault();
142
+ goDownFromSearchInput();
143
+ break;
144
+ case KeyboardEvents.Enter:
145
+ event.preventDefault();
146
+ focusAndClickFirstVisibleEmoji(BodyRef.current);
147
+ break;
148
+ }
149
+ },
150
+ [
151
+ focusSkinTonePicker,
152
+ goDownFromSearchInput,
153
+ setSkinToneFanOpenState,
154
+ BodyRef
155
+ ]
156
+ );
157
+
158
+ useEffect(() => {
159
+ const current = SearchInputRef.current;
160
+
161
+ if (!current) {
162
+ return;
163
+ }
164
+
165
+ current.addEventListener('keydown', onKeyDown);
166
+
167
+ return () => {
168
+ current.removeEventListener('keydown', onKeyDown);
169
+ };
170
+ }, [PickerMainRef, SearchInputRef, onKeyDown]);
171
+ }
172
+
173
+ function useSkinTonePickerKeyboardEvents() {
174
+ const SkinTonePickerRef = useSkinTonePickerRef();
175
+ const focusSearchInput = useFocusSearchInput();
176
+ const SearchInputRef = useSearchInputRef();
177
+ const goDownFromSearchInput = useGoDownFromSearchInput();
178
+ const [isOpen, setIsOpen] = useSkinToneFanOpenState();
179
+ const onType = useOnType();
180
+
181
+ const onKeyDown = useMemo(
182
+ () =>
183
+ function onKeyDown(event: KeyboardEvent) {
184
+ const { key } = event;
185
+
186
+ switch (key) {
187
+ case KeyboardEvents.ArrowLeft:
188
+ event.preventDefault();
189
+ if (!isOpen) {
190
+ return focusSearchInput();
191
+ }
192
+ focusNextSkinTone(focusSearchInput);
193
+ break;
194
+ case KeyboardEvents.ArrowRight:
195
+ event.preventDefault();
196
+ if (!isOpen) {
197
+ return focusSearchInput();
198
+ }
199
+ focusPrevSkinTone();
200
+ break;
201
+ case KeyboardEvents.ArrowDown:
202
+ event.preventDefault();
203
+ if (isOpen) {
204
+ setIsOpen(false);
205
+ }
206
+ goDownFromSearchInput();
207
+ break;
208
+ default:
209
+ onType(event);
210
+ break;
211
+ }
212
+ },
213
+ [isOpen, focusSearchInput, setIsOpen, goDownFromSearchInput, onType]
214
+ );
215
+
216
+ useEffect(() => {
217
+ const current = SkinTonePickerRef.current;
218
+
219
+ if (!current) {
220
+ return;
221
+ }
222
+
223
+ current.addEventListener('keydown', onKeyDown);
224
+
225
+ return () => {
226
+ current.removeEventListener('keydown', onKeyDown);
227
+ };
228
+ }, [SkinTonePickerRef, SearchInputRef, isOpen, onKeyDown]);
229
+ }
230
+
231
+ function useCategoryNavigationKeyboardEvents() {
232
+ const focusSearchInput = useFocusSearchInput();
233
+ const CategoryNavigationRef = useCategoryNavigationRef();
234
+ const BodyRef = useBodyRef();
235
+ const onType = useOnType();
236
+
237
+ const onKeyDown = useMemo(
238
+ () =>
239
+ function onKeyDown(event: KeyboardEvent) {
240
+ const { key } = event;
241
+
242
+ switch (key) {
243
+ case KeyboardEvents.ArrowUp:
244
+ event.preventDefault();
245
+ focusSearchInput();
246
+ break;
247
+ case KeyboardEvents.ArrowRight:
248
+ event.preventDefault();
249
+ focusNextElementSibling(getActiveElement());
250
+ break;
251
+ case KeyboardEvents.ArrowLeft:
252
+ event.preventDefault();
253
+ focusPrevElementSibling(getActiveElement());
254
+ break;
255
+ case KeyboardEvents.ArrowDown:
256
+ event.preventDefault();
257
+ focusFirstVisibleEmoji(BodyRef.current);
258
+ break;
259
+ default:
260
+ onType(event);
261
+ break;
262
+ }
263
+ },
264
+ [BodyRef, focusSearchInput, onType]
265
+ );
266
+
267
+ useEffect(() => {
268
+ const current = CategoryNavigationRef.current;
269
+
270
+ if (!current) {
271
+ return;
272
+ }
273
+
274
+ current.addEventListener('keydown', onKeyDown);
275
+
276
+ return () => {
277
+ current.removeEventListener('keydown', onKeyDown);
278
+ };
279
+ }, [CategoryNavigationRef, BodyRef, onKeyDown]);
280
+ }
281
+
282
+ function useBodyKeyboardEvents() {
283
+ const BodyRef = useBodyRef();
284
+ const goUpFromBody = useGoUpFromBody();
285
+ const setVariationPicker = useSetVariationPicker();
286
+ const hasOpenToggles = useHasOpenToggles();
287
+ const closeAllOpenToggles = useCloseAllOpenToggles();
288
+
289
+ const onType = useOnType();
290
+
291
+ const onKeyDown = useMemo(
292
+ () =>
293
+ // eslint-disable-next-line complexity
294
+ function onKeyDown(event: KeyboardEvent) {
295
+ const { key } = event;
296
+
297
+ const activeElement = buttonFromTarget(getActiveElement());
298
+
299
+ switch (key) {
300
+ case KeyboardEvents.ArrowRight:
301
+ event.preventDefault();
302
+ focusNextVisibleEmoji(activeElement);
303
+ break;
304
+ case KeyboardEvents.ArrowLeft:
305
+ event.preventDefault();
306
+ focusPrevVisibleEmoji(activeElement);
307
+ break;
308
+ case KeyboardEvents.ArrowDown:
309
+ event.preventDefault();
310
+ if (hasOpenToggles()) {
311
+ closeAllOpenToggles();
312
+ break;
313
+ }
314
+ focusVisibleEmojiOneRowDown(activeElement);
315
+ break;
316
+ case KeyboardEvents.ArrowUp:
317
+ event.preventDefault();
318
+ if (hasOpenToggles()) {
319
+ closeAllOpenToggles();
320
+ break;
321
+ }
322
+ focusVisibleEmojiOneRowUp(activeElement, goUpFromBody);
323
+ break;
324
+ case KeyboardEvents.Space:
325
+ event.preventDefault();
326
+ setVariationPicker(event.target as HTMLElement);
327
+ break;
328
+ default:
329
+ onType(event);
330
+ break;
331
+ }
332
+ },
333
+ [
334
+ goUpFromBody,
335
+ onType,
336
+ setVariationPicker,
337
+ hasOpenToggles,
338
+ closeAllOpenToggles
339
+ ]
340
+ );
341
+
342
+ useEffect(() => {
343
+ const current = BodyRef.current;
344
+
345
+ if (!current) {
346
+ return;
347
+ }
348
+
349
+ current.addEventListener('keydown', onKeyDown);
350
+
351
+ return () => {
352
+ current.removeEventListener('keydown', onKeyDown);
353
+ };
354
+ }, [BodyRef, onKeyDown]);
355
+ }
356
+
357
+ function useGoDownFromSearchInput() {
358
+ const focusCategoryNavigation = useFocusCategoryNavigation();
359
+ const isSearchMode = useIsSearchMode();
360
+ const BodyRef = useBodyRef();
361
+
362
+ return useCallback(
363
+ function goDownFromSearchInput() {
364
+ if (isSearchMode) {
365
+ return focusFirstVisibleEmoji(BodyRef.current);
366
+ }
367
+ return focusCategoryNavigation();
368
+ },
369
+ [BodyRef, focusCategoryNavigation, isSearchMode]
370
+ );
371
+ }
372
+
373
+ function useGoUpFromBody() {
374
+ const focusSearchInput = useFocusSearchInput();
375
+ const focusCategoryNavigation = useFocusCategoryNavigation();
376
+ const isSearchMode = useIsSearchMode();
377
+
378
+ return useCallback(
379
+ function goUpFromEmoji() {
380
+ if (isSearchMode) {
381
+ return focusSearchInput();
382
+ }
383
+ return focusCategoryNavigation();
384
+ },
385
+ [focusSearchInput, isSearchMode, focusCategoryNavigation]
386
+ );
387
+ }
388
+
389
+ function focusNextSkinTone(exitLeft: () => void) {
390
+ const currentSkinTone = getActiveElement();
391
+
392
+ if (!currentSkinTone) {
393
+ return;
394
+ }
395
+
396
+ if (!hasNextElementSibling(currentSkinTone)) {
397
+ exitLeft();
398
+ }
399
+
400
+ focusNextElementSibling(currentSkinTone);
401
+ }
402
+
403
+ function focusPrevSkinTone() {
404
+ const currentSkinTone = getActiveElement();
405
+
406
+ if (!currentSkinTone) {
407
+ return;
408
+ }
409
+
410
+ focusPrevElementSibling(currentSkinTone);
411
+ }
412
+
413
+ function useOnType() {
414
+ const appendSearch = useAppendSearch();
415
+ const focusSearchInput = useFocusSearchInput();
416
+ const closeAllOpenToggles = useCloseAllOpenToggles();
417
+
418
+ return function onType(event: KeyboardEvent) {
419
+ const { key } = event;
420
+
421
+ if (hasModifier(event)) {
422
+ return;
423
+ }
424
+
425
+ if (key.match(/(^[a-zA-Z0-9]$){1}/)) {
426
+ event.preventDefault();
427
+ closeAllOpenToggles();
428
+ focusSearchInput();
429
+ appendSearch(key);
430
+ }
431
+ };
432
+ }
433
+
434
+ function hasModifier(event: KeyboardEvent): boolean {
435
+ const { metaKey, ctrlKey, altKey } = event;
436
+
437
+ return metaKey || ctrlKey || altKey;
438
+ }
@@ -1,17 +1,18 @@
1
- import { SkinTones } from './../types/exposedTypes';
1
+ import * as React from 'react';
2
2
  import { useEffect, useRef } from 'react';
3
3
 
4
- import { emojiFromElement, isEmojiElement } from '../DomUtils/selectors';
5
- import { useSetAnchoredEmojiRef } from '../components/context/ElementRefContext';
4
+ import {
5
+ emojiFromElement,
6
+ isEmojiElement,
7
+ NullableElement
8
+ } from '../DomUtils/selectors';
6
9
  import {
7
10
  useActiveSkinToneState,
8
11
  useDisallowClickRef,
9
- useEmojiVariationPickerState
12
+ useEmojiVariationPickerState,
13
+ useUpdateSuggested
10
14
  } from '../components/context/PickerContext';
11
- import {
12
- useEmojiStyleConfig,
13
- useOnEmojiClickConfig
14
- } from '../config/useConfig';
15
+ import { useOnEmojiClickConfig } from '../config/useConfig';
15
16
  import { DataEmoji } from '../dataUtils/DataTypes';
16
17
  import {
17
18
  activeVariationFromUnified,
@@ -21,25 +22,100 @@ import {
21
22
  emojiUrlByUnified
22
23
  } from '../dataUtils/emojiSelectors';
23
24
  import { parseNativeEmoji } from '../dataUtils/parseNativeEmoji';
24
- import { setRecentlyUsed } from '../dataUtils/recentlyUsed';
25
- import { EmojiClickData, EmojiStyle } from '../types/exposedTypes';
25
+ import { setsuggested } from '../dataUtils/suggested';
26
+ import { EmojiClickData, EmojiStyle, SkinTones } from '../types/exposedTypes';
26
27
 
27
28
  import { useCloseAllOpenToggles } from './useCloseAllOpenToggles';
28
- import { preloadEmoji } from './preloadEmoji';
29
-
30
- let mouseDownTimer: undefined | number;
29
+ import useSetVariationPicker from './useSetVariationPicker';
31
30
 
32
31
  export function useMouseDownHandlers(
33
- BodyRef: React.MutableRefObject<HTMLElement | null>
32
+ BodyRef: React.MutableRefObject<NullableElement>
34
33
  ) {
35
- const preloading = useRef(false);
36
- const setAnchoredEmojiRef = useSetAnchoredEmojiRef();
34
+ const mouseDownTimerRef = useRef<undefined | number>();
35
+ const setVariationPicker = useSetVariationPicker();
37
36
  const disallowClickRef = useDisallowClickRef();
38
37
  const [, setEmojiVariationPicker] = useEmojiVariationPickerState();
39
- const { closeAllOpenToggles, dependencyArray } = useCloseAllOpenToggles();
38
+ const closeAllOpenToggles = useCloseAllOpenToggles();
40
39
  const [activeSkinTone] = useActiveSkinToneState();
41
40
  const onEmojiClick = useOnEmojiClickConfig();
42
- const emojiStyle = useEmojiStyleConfig();
41
+ const [, updateSuggested] = useUpdateSuggested();
42
+
43
+ const onClick = React.useCallback(
44
+ function onClick(event: MouseEvent) {
45
+ if (disallowClickRef.current) {
46
+ return;
47
+ }
48
+
49
+ closeAllOpenToggles();
50
+
51
+ const [emoji, unified] = emojiFromEvent(event);
52
+
53
+ if (!emoji || !unified) {
54
+ return;
55
+ }
56
+
57
+ const skinToneToUse =
58
+ activeVariationFromUnified(unified) || activeSkinTone;
59
+
60
+ updateSuggested();
61
+ setsuggested(emoji, skinToneToUse);
62
+ onEmojiClick(emojiClickOutput(emoji, skinToneToUse), event);
63
+ },
64
+ [
65
+ activeSkinTone,
66
+ closeAllOpenToggles,
67
+ disallowClickRef,
68
+ onEmojiClick,
69
+ updateSuggested
70
+ ]
71
+ );
72
+
73
+ const onMouseDown = React.useCallback(
74
+ function onMouseDown(event: MouseEvent) {
75
+ if (mouseDownTimerRef.current) {
76
+ clearTimeout(mouseDownTimerRef.current);
77
+ }
78
+
79
+ const [emoji] = emojiFromEvent(event);
80
+
81
+ if (!emoji || !emojiHasVariations(emoji)) {
82
+ return;
83
+ }
84
+
85
+ mouseDownTimerRef.current = window?.setTimeout(() => {
86
+ disallowClickRef.current = true;
87
+ mouseDownTimerRef.current = undefined;
88
+ closeAllOpenToggles();
89
+ setVariationPicker(event.target as HTMLElement);
90
+ setEmojiVariationPicker(emoji);
91
+ }, 500);
92
+ },
93
+ [
94
+ disallowClickRef,
95
+ closeAllOpenToggles,
96
+ setVariationPicker,
97
+ setEmojiVariationPicker
98
+ ]
99
+ );
100
+ const onMouseUp = React.useCallback(
101
+ function onMouseUp() {
102
+ if (mouseDownTimerRef.current) {
103
+ clearTimeout(mouseDownTimerRef.current);
104
+ mouseDownTimerRef.current = undefined;
105
+ } else if (disallowClickRef.current) {
106
+ // The problem we're trying to overcome here
107
+ // is that the emoji has both mouseup and click events
108
+ // and when releasing a mouseup event
109
+ // the click gets triggered too
110
+ // So we're disallowing the click event for a short time
111
+
112
+ requestAnimationFrame(() => {
113
+ disallowClickRef.current = false;
114
+ });
115
+ }
116
+ },
117
+ [disallowClickRef]
118
+ );
43
119
 
44
120
  useEffect(() => {
45
121
  if (!BodyRef.current) {
@@ -62,81 +138,13 @@ export function useMouseDownHandlers(
62
138
  bodyRef?.removeEventListener('mousedown', onMouseDown);
63
139
  bodyRef?.removeEventListener('mouseup', onMouseUp);
64
140
  };
65
- }, [BodyRef, ...dependencyArray]);
66
-
67
- function onClick(event: MouseEvent) {
68
- if (disallowClickRef.current) {
69
- return;
70
- }
71
-
72
- closeAllOpenToggles();
73
-
74
- const emoji = emojiFromEvent(event);
75
-
76
- if (!emoji) {
77
- return;
78
- }
79
-
80
- const unified = emojiUnified(emoji);
81
-
82
- const skinToneToUse = activeVariationFromUnified(unified) || activeSkinTone;
83
-
84
- setRecentlyUsed(emoji, skinToneToUse);
85
- onEmojiClick(event, emojiClickOutput(emoji, skinToneToUse));
86
- }
87
-
88
- function onMouseDown(event: MouseEvent) {
89
- if (mouseDownTimer) {
90
- clearTimeout(mouseDownTimer);
91
- }
92
-
93
- const emoji = emojiFromEvent(event);
94
-
95
- if (!emoji || !emojiHasVariations(emoji)) {
96
- return;
97
- }
98
-
99
- preloading.current = true;
100
-
101
- window?.setTimeout(() => {
102
- if (preloading.current) {
103
- preloadEmoji(emoji, emojiStyle);
104
- preloading.current = false;
105
- }
106
- }, 50);
107
-
108
- mouseDownTimer = window?.setTimeout(() => {
109
- disallowClickRef.current = true;
110
- mouseDownTimer = undefined;
111
- closeAllOpenToggles();
112
- setAnchoredEmojiRef(event.target as HTMLElement);
113
- setEmojiVariationPicker(emoji);
114
- }, 250);
115
- }
116
- function onMouseUp() {
117
- preloading.current = false;
118
-
119
- if (mouseDownTimer) {
120
- clearTimeout(mouseDownTimer);
121
- mouseDownTimer = undefined;
122
- } else if (disallowClickRef.current) {
123
- // The problem we're trying to overcome here
124
- // is that the emoji has both mouseup and click events
125
- // and when releasing a mouseup event
126
- // the click gets triggered too
127
- // So we're disallowing the click event for a short time
128
-
129
- requestAnimationFrame(() => {
130
- disallowClickRef.current = false;
131
- });
132
- }
133
- }
141
+ }, [BodyRef, onClick, onMouseDown, onMouseUp]);
134
142
  }
135
143
 
136
- function emojiFromEvent(event: MouseEvent): DataEmoji | undefined {
144
+ function emojiFromEvent(event: MouseEvent): [DataEmoji, string] | [] {
137
145
  const target = event?.target as HTMLElement;
138
146
  if (!isEmojiElement(target)) {
139
- return;
147
+ return [];
140
148
  }
141
149
 
142
150
  return emojiFromElement(target);
@@ -0,0 +1,46 @@
1
+ import { useEffect } from 'react';
2
+
3
+ import { buttonFromTarget, emojiFromElement } from '../DomUtils/selectors';
4
+ import { useBodyRef } from '../components/context/ElementRefContext';
5
+ import { useEmojiStyleConfig } from '../config/useConfig';
6
+ import { emojiHasVariations } from '../dataUtils/emojiSelectors';
7
+ import { EmojiStyle } from '../types/exposedTypes';
8
+
9
+ import { preloadEmoji } from './preloadEmoji';
10
+
11
+ export function useOnFocus() {
12
+ const BodyRef = useBodyRef();
13
+ const emojiStyle = useEmojiStyleConfig();
14
+
15
+ useEffect(() => {
16
+ if (emojiStyle === EmojiStyle.NATIVE) {
17
+ return;
18
+ }
19
+
20
+ const bodyRef = BodyRef.current;
21
+
22
+ bodyRef?.addEventListener('focusin', onFocus);
23
+
24
+ return () => {
25
+ bodyRef?.removeEventListener('focusin', onFocus);
26
+ };
27
+
28
+ function onFocus(event: FocusEvent) {
29
+ const button = buttonFromTarget(event.target as HTMLElement);
30
+
31
+ if (!button) {
32
+ return;
33
+ }
34
+
35
+ const [emoji] = emojiFromElement(button);
36
+
37
+ if (!emoji) {
38
+ return;
39
+ }
40
+
41
+ if (emojiHasVariations(emoji)) {
42
+ preloadEmoji(emoji, emojiStyle);
43
+ }
44
+ }
45
+ }, [BodyRef, emojiStyle]);
46
+ }