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.
- package/README.md +176 -43
- package/dist/DomUtils/classNames.d.ts +9 -1
- package/dist/DomUtils/elementPositionInRow.d.ts +10 -0
- package/dist/DomUtils/focusElement.d.ts +5 -0
- package/dist/DomUtils/getActiveElement.d.ts +2 -0
- package/dist/DomUtils/isDarkTheme.d.ts +1 -0
- package/dist/DomUtils/keyboardNavigation.d.ts +8 -0
- package/dist/DomUtils/scrollTo.d.ts +3 -1
- package/dist/DomUtils/selectors.d.ts +24 -11
- package/dist/EmojiPickerReact.d.ts +6 -0
- package/dist/components/ErrorBoundary.d.ts +15 -0
- package/dist/components/body/{RecentlyUsed.d.ts → Suggested.d.ts} +1 -1
- package/dist/components/context/ElementRefContext.d.ts +10 -13
- package/dist/components/context/PickerContext.d.ts +3 -3
- package/dist/components/emoji/Emoji.d.ts +11 -7
- package/dist/components/emoji/ExportedEmoji.d.ts +8 -0
- package/dist/components/footer/Preview.d.ts +1 -1
- package/dist/config/categoryConfig.d.ts +7 -3
- package/dist/config/config.d.ts +18 -15
- package/dist/config/useConfig.d.ts +11 -4
- package/dist/data/skinToneVariations.d.ts +2 -0
- package/dist/dataUtils/emojiSelectors.d.ts +3 -2
- package/dist/dataUtils/suggested.d.ts +11 -0
- package/dist/emoji-picker-react.cjs.development.css +9 -9
- package/dist/emoji-picker-react.cjs.development.js +3774 -3070
- package/dist/emoji-picker-react.cjs.development.js.map +1 -1
- package/dist/emoji-picker-react.cjs.production.min.js +1 -1
- package/dist/emoji-picker-react.cjs.production.min.js.map +1 -1
- package/dist/emoji-picker-react.esm.js +3784 -3080
- package/dist/emoji-picker-react.esm.js.map +1 -1
- package/dist/hooks/preloadEmoji.d.ts +1 -1
- package/dist/hooks/useActiveCategoryScrollDetection.d.ts +1 -2
- package/dist/hooks/useCloseAllOpenToggles.d.ts +2 -4
- package/dist/hooks/useDebouncedState.d.ts +1 -1
- package/dist/hooks/useDisallowMouseMove.d.ts +4 -0
- package/dist/hooks/useEmojiPreviewEvents.d.ts +3 -0
- package/dist/hooks/useFilter.d.ts +3 -3
- package/dist/hooks/useFocus.d.ts +3 -0
- package/dist/hooks/useInitialLoad.d.ts +1 -1
- package/dist/hooks/useIsEmojiHidden.d.ts +7 -1
- package/dist/hooks/useKeyboardNavigation.d.ts +1 -0
- package/dist/hooks/useMouseDownHandlers.d.ts +3 -2
- package/dist/hooks/useOnFocus.d.ts +1 -0
- package/dist/hooks/useScrollCategoryIntoView.d.ts +1 -0
- package/dist/hooks/useSetVariationPicker.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/types/exposedTypes.d.ts +5 -1
- package/package.json +13 -5
- package/src/DomUtils/classNames.ts +12 -1
- package/src/DomUtils/elementPositionInRow.ts +167 -0
- package/src/DomUtils/focusElement.ts +35 -0
- package/src/DomUtils/getActiveElement.ts +5 -0
- package/src/DomUtils/isDarkTheme.ts +7 -0
- package/src/DomUtils/keyboardNavigation.ts +161 -0
- package/src/DomUtils/scrollTo.ts +22 -1
- package/src/DomUtils/selectors.ts +200 -41
- package/src/EmojiPickerReact.css +99 -48
- package/src/EmojiPickerReact.tsx +26 -0
- package/src/components/ErrorBoundary.tsx +28 -0
- package/src/components/body/Body.tsx +3 -2
- package/src/components/body/EmojiCategory.css +14 -28
- package/src/components/body/EmojiCategory.tsx +5 -4
- package/src/components/body/EmojiList.tsx +13 -13
- package/src/components/body/EmojiVariationPicker.css +13 -5
- package/src/components/body/EmojiVariationPicker.tsx +25 -17
- package/src/components/body/Suggested.tsx +54 -0
- package/src/components/context/ElementRefContext.tsx +54 -19
- package/src/components/context/PickerContext.tsx +36 -33
- package/src/components/emoji/Emoji.css +42 -12
- package/src/components/emoji/Emoji.tsx +73 -34
- package/src/components/emoji/ExportedEmoji.tsx +30 -0
- package/src/components/footer/Preview.css +1 -1
- package/src/components/footer/Preview.tsx +16 -23
- package/src/components/header/Search.css +7 -7
- package/src/components/header/Search.tsx +31 -7
- package/src/components/header/SkinTonePicker.css +15 -8
- package/src/components/header/SkinTonePicker.tsx +23 -5
- package/src/components/main/PickerMain.css +0 -2
- package/src/components/main/PickerMain.tsx +14 -1
- package/src/components/navigation/CategoryNavigation.css +41 -10
- package/src/components/navigation/CategoryNavigation.tsx +22 -10
- package/src/components/navigation/svg/CategoryNav.svg +343 -379
- package/src/config/categoryConfig.ts +40 -14
- package/src/config/config.ts +59 -13
- package/src/config/useConfig.ts +35 -7
- package/src/data/emojis.json +1 -1
- package/src/data/skinToneVariations.ts +19 -0
- package/src/dataUtils/emojiSelectors.ts +25 -5
- package/src/dataUtils/{recentlyUsed.ts → suggested.ts} +20 -18
- package/src/hooks/preloadEmoji.ts +13 -2
- package/src/hooks/useActiveCategoryScrollDetection.ts +10 -8
- package/src/hooks/useCloseAllOpenToggles.ts +15 -4
- package/src/hooks/useDebouncedState.ts +10 -7
- package/src/hooks/useDisallowMouseMove.ts +47 -0
- package/src/hooks/useEmojiPreviewEvents.ts +103 -0
- package/src/hooks/useFilter.ts +46 -47
- package/src/hooks/useFocus.ts +40 -0
- package/src/hooks/useInitialLoad.ts +1 -0
- package/src/hooks/useIsEmojiHidden.ts +16 -3
- package/src/hooks/useKeyboardNavigation.ts +438 -0
- package/src/hooks/useMouseDownHandlers.ts +97 -89
- package/src/hooks/useOnFocus.ts +46 -0
- package/src/hooks/useOnScroll.ts +2 -2
- package/src/hooks/useScrollCategoryIntoView.ts +28 -0
- package/src/hooks/useSetVariationPicker.ts +28 -0
- package/src/index.tsx +9 -17
- package/src/types/exposedTypes.ts +6 -1
- package/dist/DomUtils/emojiElementRef.d.ts +0 -10
- package/dist/DomUtils/scrollCategoryIntoView.d.ts +0 -1
- package/dist/dataUtils/recentlyUsed.d.ts +0 -11
- package/dist/hooks/useEmojiElements.d.ts +0 -1
- package/dist/hooks/useEmojiMouseEnter.d.ts +0 -3
- package/src/DomUtils/emojiElementRef.ts +0 -42
- package/src/DomUtils/scrollCategoryIntoView.ts +0 -18
- package/src/components/body/RecentlyUsed.tsx +0 -50
- package/src/hooks/useEmojiElements.ts +0 -7
- 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
|
|
1
|
+
import * as React from 'react';
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
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 {
|
|
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
|
|
29
|
-
|
|
30
|
-
let mouseDownTimer: undefined | number;
|
|
29
|
+
import useSetVariationPicker from './useSetVariationPicker';
|
|
31
30
|
|
|
32
31
|
export function useMouseDownHandlers(
|
|
33
|
-
BodyRef: React.MutableRefObject<
|
|
32
|
+
BodyRef: React.MutableRefObject<NullableElement>
|
|
34
33
|
) {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
34
|
+
const mouseDownTimerRef = useRef<undefined | number>();
|
|
35
|
+
const setVariationPicker = useSetVariationPicker();
|
|
37
36
|
const disallowClickRef = useDisallowClickRef();
|
|
38
37
|
const [, setEmojiVariationPicker] = useEmojiVariationPickerState();
|
|
39
|
-
const
|
|
38
|
+
const closeAllOpenToggles = useCloseAllOpenToggles();
|
|
40
39
|
const [activeSkinTone] = useActiveSkinToneState();
|
|
41
40
|
const onEmojiClick = useOnEmojiClickConfig();
|
|
42
|
-
const
|
|
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,
|
|
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 |
|
|
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
|
+
}
|