emoji-picker-react 4.13.3 → 4.14.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 (42) hide show
  1. package/dist/DomUtils/elementPositionInRow.d.ts +1 -6
  2. package/dist/components/body/EmojiCategory.d.ts +2 -1
  3. package/dist/components/body/EmojiList.d.ts +3 -1
  4. package/dist/components/context/ElementRefContext.d.ts +1 -0
  5. package/dist/components/emoji/ClickableEmojiButton.d.ts +2 -1
  6. package/dist/components/emoji/Emoji.d.ts +3 -2
  7. package/dist/dataUtils/emojiSelectors.d.ts +1 -1
  8. package/dist/emoji-picker-react.cjs.development.js +615 -544
  9. package/dist/emoji-picker-react.cjs.development.js.map +1 -1
  10. package/dist/emoji-picker-react.cjs.production.min.js +1 -1
  11. package/dist/emoji-picker-react.cjs.production.min.js.map +1 -1
  12. package/dist/emoji-picker-react.esm.js +616 -545
  13. package/dist/emoji-picker-react.esm.js.map +1 -1
  14. package/dist/hooks/preloadEmoji.d.ts +4 -0
  15. package/dist/hooks/useCategoryHeight.d.ts +5 -0
  16. package/dist/hooks/useEmojiVirtualization.d.ts +16 -0
  17. package/dist/hooks/useOnScroll.d.ts +1 -1
  18. package/dist/virtualization/virtualizationHelpers.d.ts +18 -0
  19. package/package.json +2 -1
  20. package/src/DomUtils/elementPositionInRow.ts +8 -131
  21. package/src/DomUtils/keyboardNavigation.ts +62 -48
  22. package/src/components/body/Body.tsx +2 -2
  23. package/src/components/body/EmojiCategory.tsx +7 -5
  24. package/src/components/body/EmojiList.tsx +43 -78
  25. package/src/components/context/ElementRefContext.tsx +8 -0
  26. package/src/components/emoji/ClickableEmojiButton.tsx +4 -1
  27. package/src/components/emoji/Emoji.tsx +4 -1
  28. package/src/components/header/Search/Search.tsx +0 -5
  29. package/src/components/main/PickerMain.tsx +8 -5
  30. package/src/dataUtils/emojiSelectors.ts +29 -3
  31. package/src/hooks/preloadEmoji.ts +49 -7
  32. package/src/hooks/useCategoryHeight.ts +92 -0
  33. package/src/hooks/useEmojiVirtualization.tsx +112 -0
  34. package/src/hooks/useFilter.ts +17 -24
  35. package/src/hooks/useOnScroll.ts +5 -1
  36. package/src/virtualization/virtualizationHelpers.ts +45 -0
  37. package/dist/components/body/Suggested.d.ts +0 -7
  38. package/dist/components/header/Search/CssSearch.d.ts +0 -4
  39. package/dist/globals/doesBrowserSupportCssHas.d.ts +0 -2
  40. package/src/components/body/Suggested.tsx +0 -69
  41. package/src/components/header/Search/CssSearch.tsx +0 -45
  42. package/src/globals/doesBrowserSupportCssHas.ts +0 -16
@@ -1,6 +1,5 @@
1
1
  import { cx } from 'flairup';
2
2
  import * as React from 'react';
3
- import { useState } from 'react';
4
3
 
5
4
  import { darkMode, stylesheet } from '../../../Stylesheet/stylesheet';
6
5
  import {
@@ -17,7 +16,6 @@ import { useSearchInputRef } from '../../context/ElementRefContext';
17
16
  import { SkinTonePicker } from '../SkinTonePicker/SkinTonePicker';
18
17
 
19
18
  import { BtnClearSearch } from './BtnClearSearch';
20
- import { CssSearch } from './CssSearch';
21
19
  import { IcnSearch } from './IcnSearch';
22
20
  import SVGTimes from './svg/times.svg';
23
21
 
@@ -40,7 +38,6 @@ export function SearchContainer() {
40
38
  }
41
39
 
42
40
  export function Search() {
43
- const [inc, setInc] = useState(0);
44
41
  const closeAllOpenToggles = useCloseAllOpenToggles();
45
42
  const SearchInputRef = useSearchInputRef();
46
43
  const placeholder = useSearchPlaceHolderConfig();
@@ -52,7 +49,6 @@ export function Search() {
52
49
 
53
50
  return (
54
51
  <Relative className={cx(styles.searchContainer)}>
55
- <CssSearch value={value} />
56
52
  <input
57
53
  // eslint-disable-next-line jsx-a11y/no-autofocus
58
54
  autoFocus={autoFocus}
@@ -63,7 +59,6 @@ export function Search() {
63
59
  aria-controls="epr-search-id"
64
60
  placeholder={placeholder}
65
61
  onChange={event => {
66
- setInc(inc + 1);
67
62
  onChange(event?.target?.value ?? value);
68
63
  }}
69
64
  ref={SearchInputRef}
@@ -97,8 +97,10 @@ const DarkTheme = {
97
97
  'var(--epr-dark-category-icon-active-color)',
98
98
  '--epr-skin-tone-picker-menu-color':
99
99
  'var(--epr-dark-skin-tone-picker-menu-color)',
100
- '--epr-skin-tone-outer-border-color': 'var(--epr-dark-skin-tone-outer-border-color)',
101
- '--epr-skin-tone-inner-border-color': 'var(--epr-dark-skin-tone-inner-border-color)'
100
+ '--epr-skin-tone-outer-border-color':
101
+ 'var(--epr-dark-skin-tone-outer-border-color)',
102
+ '--epr-skin-tone-inner-border-color':
103
+ 'var(--epr-dark-skin-tone-inner-border-color)'
102
104
  };
103
105
 
104
106
  const styles = stylesheet.create({
@@ -113,7 +115,7 @@ const styles = stylesheet.create({
113
115
  borderColor: 'var(--epr-picker-border-color)',
114
116
  backgroundColor: 'var(--epr-bg-color)',
115
117
  overflow: 'hidden',
116
- transition: 'all 0.3s ease-in-out, background-color 0.1s ease-in-out',
118
+ transition: 'height 0.3s ease-in-out, background-color 0.1s ease-in-out',
117
119
  '*': {
118
120
  boxSizing: 'border-box',
119
121
  fontFamily: 'sans-serif'
@@ -213,8 +215,9 @@ const styles = stylesheet.create({
213
215
  '--epr-dark-emoji-variation-indicator-color': '#444',
214
216
  '--epr-dark-category-icon-active-color': '#3271b7',
215
217
  '--epr-dark-skin-tone-picker-menu-color': '#22222295',
216
- '--epr-dark-skin-tone-outer-border-color': 'var(--epr-dark-picker-border-color)',
217
- '--epr-dark-skin-tone-inner-border-color': '#00000000',
218
+ '--epr-dark-skin-tone-outer-border-color':
219
+ 'var(--epr-dark-picker-border-color)',
220
+ '--epr-dark-skin-tone-inner-border-color': '#00000000'
218
221
  }
219
222
  },
220
223
  autoThemeDark: {
@@ -1,6 +1,10 @@
1
+ import React from 'react';
2
+
3
+ import { useUpdateSuggested } from '../components/context/PickerContext';
1
4
  import { Categories } from '../config/categoryConfig';
2
5
  import { cdnUrl } from '../config/cdnUrls';
3
6
  import { CustomEmoji } from '../config/customEmojiConfig';
7
+ import { useSuggestedEmojisModeConfig } from '../config/useConfig';
4
8
  import emojis from '../data/emojis';
5
9
  import skinToneVariations, {
6
10
  skinTonesMapped
@@ -9,6 +13,7 @@ import { EmojiStyle, SkinTones } from '../types/exposedTypes';
9
13
 
10
14
  import { DataEmoji, DataEmojis, EmojiProperties, WithName } from './DataTypes';
11
15
  import { indexEmoji } from './alphaNumericEmojiIndex';
16
+ import { getSuggested } from './suggested';
12
17
 
13
18
  export function emojiNames(emoji: WithName): string[] {
14
19
  return emoji[EmojiProperties.name] ?? [];
@@ -47,9 +52,30 @@ export function emojiUnified(emoji: DataEmoji, skinTone?: string): string {
47
52
  return emojiVariationUnified(emoji, skinTone) ?? unified;
48
53
  }
49
54
 
50
- export function emojisByCategory(category: Categories): DataEmojis {
51
- // @ts-ignore
52
- return emojis?.[category] ?? [];
55
+ export function useGetEmojisByCategory(): (Category: Categories) => DataEmojis {
56
+ const suggestedEmojisModeConfig = useSuggestedEmojisModeConfig();
57
+ const [suggestedUpdated] = useUpdateSuggested();
58
+
59
+ const suggested = React.useMemo(
60
+ () => {
61
+ const suggested = getSuggested(suggestedEmojisModeConfig) ?? [];
62
+
63
+ return suggested
64
+ .map(s => emojiByUnified(s.unified))
65
+ .filter(Boolean) as DataEmojis;
66
+ },
67
+ // eslint-disable-next-line react-hooks/exhaustive-deps
68
+ [suggestedUpdated, suggestedEmojisModeConfig]
69
+ );
70
+
71
+ return function getEmojisByCategory(category: Categories): DataEmojis {
72
+ if (category === Categories.SUGGESTED) {
73
+ return suggested;
74
+ }
75
+
76
+ // @ts-ignore
77
+ return emojis[category] ?? [];
78
+ };
53
79
  }
54
80
 
55
81
  // WARNING: DO NOT USE DIRECTLY
@@ -2,17 +2,24 @@ import { GetEmojiUrl } from '../components/emoji/BaseEmojiProps';
2
2
  import { DataEmoji } from '../dataUtils/DataTypes';
3
3
  import { emojiUnified, emojiVariations } from '../dataUtils/emojiSelectors';
4
4
  import { EmojiStyle } from '../types/exposedTypes';
5
+ import { Dimensions } from '../virtualization/virtualizationHelpers';
5
6
 
6
- export function preloadEmoji(
7
- getEmojiUrl: GetEmojiUrl,
7
+ // eslint-disable-next-line max-params
8
+ export function preloaEmojidIfNeeded(
8
9
  emoji: undefined | DataEmoji,
9
- emojiStyle: EmojiStyle
10
+ emojiStyles: EmojiStyle,
11
+ scrollTop: number,
12
+ clientHeight: number,
13
+ topOffset: number,
14
+ style: { top: number } | undefined,
15
+ dimensions: Dimensions,
16
+ getEmojiUrl: GetEmojiUrl
10
17
  ): void {
11
18
  if (!emoji) {
12
19
  return;
13
20
  }
14
21
 
15
- if (emojiStyle === EmojiStyle.NATIVE) {
22
+ if (emojiStyles === EmojiStyle.NATIVE) {
16
23
  return;
17
24
  }
18
25
 
@@ -22,12 +29,47 @@ export function preloadEmoji(
22
29
  return;
23
30
  }
24
31
 
25
- emojiVariations(emoji).forEach((variation) => {
26
- const emojiUrl = getEmojiUrl(variation, emojiStyle);
27
- preloadImage(emojiUrl);
32
+ // preload only if the emoji is a few rows below the viewport
33
+ if (!style || !dimensions) {
34
+ return;
35
+ }
36
+
37
+ setTimeout(() => {
38
+ const emojiTop = topOffset + style.top;
39
+ const viewportBottom = scrollTop + clientHeight;
40
+
41
+ const isJustBelowViewport =
42
+ emojiTop >= viewportBottom &&
43
+ emojiTop < viewportBottom + dimensions.emojiSize * 2;
44
+
45
+ if (isJustBelowViewport) {
46
+ preloadEmoji(getEmojiUrl, emoji, emojiStyles);
47
+ }
28
48
  });
49
+ }
50
+
51
+ export function preloadEmoji(
52
+ getEmojiUrl: GetEmojiUrl,
53
+ emoji: undefined | DataEmoji,
54
+ emojiStyle: EmojiStyle
55
+ ): void {
56
+ if (!emoji) {
57
+ return;
58
+ }
29
59
 
60
+ const unified = emojiUnified(emoji);
61
+
62
+ if (preloadedEmojs.has(unified)) {
63
+ return;
64
+ }
30
65
  preloadedEmojs.add(unified);
66
+
67
+ emojiVariations(emoji)
68
+ .concat(unified)
69
+ .forEach(variation => {
70
+ const emojiUrl = getEmojiUrl(variation, emojiStyle);
71
+ preloadImage(emojiUrl);
72
+ });
31
73
  }
32
74
 
33
75
  export const preloadedEmojs: Set<string> = new Set();
@@ -0,0 +1,92 @@
1
+ import * as React from 'react';
2
+
3
+ import { EmojiButtonSelector } from '../DomUtils/selectors';
4
+ import {
5
+ useEmojiListRef,
6
+ usePickerMainRef
7
+ } from '../components/context/ElementRefContext';
8
+ import { useReactionsModeState } from '../components/context/PickerContext';
9
+
10
+ const EMOJI_SIZE_DEFAULT = 32;
11
+
12
+ export function useCategoryHeight(
13
+ emojiCount: number
14
+ ):
15
+ | {
16
+ categoryHeight: number;
17
+ emojisPerRow: number;
18
+ emojiSize: number;
19
+ }
20
+ | undefined {
21
+ const EmojiListRef = useEmojiListRef();
22
+ const [isReactionsMode] = useReactionsModeState();
23
+ const PickerMainRef = usePickerMainRef();
24
+ const emojiSizeRef = React.useRef<number | undefined>();
25
+ const [dimensions, setDimensions] = React.useState<{
26
+ categoryHeight: number;
27
+ emojisPerRow: number;
28
+ emojiSize: number;
29
+ }>();
30
+
31
+ // Helper to compute and store dimensions based on current DOM
32
+ const computeAndSetDimensions = React.useCallback(() => {
33
+ const listEl = EmojiListRef.current;
34
+ if (!listEl) return;
35
+
36
+ const emojiElement = listEl.querySelector(
37
+ EmojiButtonSelector
38
+ ) as HTMLElement | null;
39
+
40
+ const measured = emojiElement?.clientHeight;
41
+ const emojiSize = measured ?? emojiSizeRef.current ?? EMOJI_SIZE_DEFAULT;
42
+ emojiSizeRef.current = emojiSize;
43
+ const pickerWidth = listEl.clientWidth;
44
+
45
+ if (pickerWidth === 0 || emojiSize === 0) return;
46
+
47
+ const emojisPerRow = Math.max(1, Math.floor(pickerWidth / emojiSize));
48
+ const rowCount = Math.ceil(emojiCount / emojisPerRow);
49
+ const categoryHeight = rowCount * emojiSize;
50
+
51
+ setDimensions({ categoryHeight, emojisPerRow, emojiSize });
52
+ }, [EmojiListRef, emojiCount]);
53
+
54
+ // Recompute on data-count changes and when reactions mode toggles
55
+ React.useEffect(() => {
56
+ computeAndSetDimensions();
57
+ }, [emojiCount, isReactionsMode, computeAndSetDimensions]);
58
+
59
+ // Listen to transitionend on the picker root (where height transition occurs)
60
+ React.useEffect(() => {
61
+ const rootEl = PickerMainRef.current;
62
+ if (!rootEl) return;
63
+
64
+ const handler = (e: Event) => {
65
+ const te = e as TransitionEvent;
66
+ const prop = te.propertyName;
67
+ if (
68
+ prop === 'width' ||
69
+ prop === 'max-width' ||
70
+ prop === 'min-width' ||
71
+ prop === 'height' ||
72
+ prop === 'max-height' ||
73
+ prop === 'min-height'
74
+ ) {
75
+ if (typeof queueMicrotask === 'function') {
76
+ queueMicrotask(() => computeAndSetDimensions());
77
+ } else {
78
+ requestAnimationFrame(() => computeAndSetDimensions());
79
+ }
80
+ }
81
+ };
82
+
83
+ rootEl.addEventListener('transitionend', handler, {
84
+ passive: true
85
+ });
86
+ return () => {
87
+ rootEl.removeEventListener('transitionend', handler);
88
+ };
89
+ }, [PickerMainRef, computeAndSetDimensions]);
90
+
91
+ return dimensions;
92
+ }
@@ -0,0 +1,112 @@
1
+ import { ReactNode, useEffect } from 'react';
2
+ import * as React from 'react';
3
+
4
+ import { useBodyRef } from '../components/context/ElementRefContext';
5
+ import { useActiveSkinToneState } from '../components/context/PickerContext';
6
+ import { ClickableEmoji } from '../components/emoji/Emoji';
7
+ import {
8
+ useEmojiStyleConfig,
9
+ useGetEmojiUrlConfig,
10
+ useLazyLoadEmojisConfig,
11
+ useSkinTonesDisabledConfig
12
+ } from '../config/useConfig';
13
+ import { DataEmojis } from '../dataUtils/DataTypes';
14
+ import { emojiUnified } from '../dataUtils/emojiSelectors';
15
+ import {
16
+ getEmojiPositionStyle,
17
+ shouldVirtualize
18
+ } from '../virtualization/virtualizationHelpers';
19
+
20
+ import { preloaEmojidIfNeeded } from './preloadEmoji';
21
+ import { useCategoryHeight } from './useCategoryHeight';
22
+ import { useIsEmojiDisallowed } from './useDisallowedEmojis';
23
+ import { useIsEmojiHidden } from './useIsEmojiHidden';
24
+
25
+ export function useEmojiVirtualization({
26
+ categoryEmojis,
27
+ topOffset,
28
+ onHeightReady,
29
+ scrollTop
30
+ }: {
31
+ categoryEmojis: DataEmojis;
32
+ topOffset: number;
33
+ onHeightReady: (height: number) => void;
34
+ scrollTop: number;
35
+ }) {
36
+ const isEmojiHidden = useIsEmojiHidden();
37
+ const lazyLoadEmojis = useLazyLoadEmojisConfig();
38
+ const emojiStyle = useEmojiStyleConfig();
39
+ const [activeSkinTone] = useActiveSkinToneState();
40
+ const isEmojiDisallowed = useIsEmojiDisallowed();
41
+ const getEmojiUrl = useGetEmojiUrlConfig();
42
+ const showVariations = !useSkinTonesDisabledConfig();
43
+ const BodyRef = useBodyRef();
44
+
45
+ let virtualizedCounter = 0;
46
+
47
+ const emojisToPush = categoryEmojis.filter(emoji => {
48
+ const isDisallowed = isEmojiDisallowed(emoji);
49
+ const { failedToLoad, filteredOut, hidden } = isEmojiHidden(emoji);
50
+
51
+ return !failedToLoad && !filteredOut && !hidden && !isDisallowed;
52
+ });
53
+
54
+ const dimensions = useCategoryHeight(emojisToPush.length);
55
+
56
+ useEffect(() => {
57
+ if (dimensions) {
58
+ onHeightReady(dimensions.categoryHeight);
59
+ }
60
+ }, [dimensions, onHeightReady, emojisToPush.length]);
61
+
62
+ const emojis = emojisToPush.reduce((accumulator, emoji, index) => {
63
+ const unified = emojiUnified(emoji, activeSkinTone);
64
+ const style = getEmojiPositionStyle(dimensions, index);
65
+ if (
66
+ dimensions &&
67
+ BodyRef.current &&
68
+ shouldVirtualize({
69
+ scrollTop,
70
+ clientHeight: BodyRef.current?.clientHeight ?? 0,
71
+ topOffset,
72
+ style,
73
+ dimensions
74
+ })
75
+ ) {
76
+ virtualizedCounter++;
77
+ preloaEmojidIfNeeded(
78
+ emoji,
79
+ emojiStyle,
80
+ scrollTop,
81
+ BodyRef.current?.clientHeight ?? 0,
82
+ topOffset,
83
+ style,
84
+ dimensions,
85
+ getEmojiUrl
86
+ );
87
+ return accumulator;
88
+ }
89
+ accumulator.push(
90
+ <ClickableEmoji
91
+ showVariations={showVariations}
92
+ key={unified}
93
+ emoji={emoji}
94
+ unified={unified}
95
+ emojiStyle={emojiStyle}
96
+ lazyLoad={lazyLoadEmojis}
97
+ getEmojiUrl={getEmojiUrl}
98
+ style={{
99
+ ...style,
100
+ position: 'absolute'
101
+ }}
102
+ />
103
+ );
104
+ return accumulator;
105
+ }, [] as ReactNode[]);
106
+
107
+ return {
108
+ virtualizedCounter,
109
+ emojis,
110
+ dimensions
111
+ };
112
+ }
@@ -11,7 +11,6 @@ import {
11
11
  import { useSearchResultsConfig } from '../config/useConfig';
12
12
  import { DataEmoji } from '../dataUtils/DataTypes';
13
13
  import { emojiNames } from '../dataUtils/emojiSelectors';
14
- import doesBrowserSupportCssHas from '../globals/doesBrowserSupportCssHas';
15
14
 
16
15
  import { useFocusSearchInput } from './useFocus';
17
16
 
@@ -78,34 +77,28 @@ export function useFilter() {
78
77
  };
79
78
 
80
79
  function onChange(inputValue: string) {
81
- if (doesBrowserSupportCssHas) {
82
- return;
83
- }
84
-
85
- setTimeout(() => {
86
- const filter = filterRef.current;
80
+ const filter = filterRef.current;
87
81
 
88
- const nextValue = inputValue.toLowerCase();
82
+ const nextValue = inputValue.toLowerCase();
89
83
 
90
- if (filter?.[nextValue] || nextValue.length <= 1) {
91
- return applySearch(nextValue);
92
- }
84
+ if (filter?.[nextValue] || nextValue.length <= 1) {
85
+ return applySearch(nextValue);
86
+ }
93
87
 
94
- const longestMatch = findLongestMatch(nextValue, filter);
88
+ const longestMatch = findLongestMatch(nextValue, filter);
95
89
 
96
- if (!longestMatch) {
97
- // Can we even get here?
98
- // If so, we need to search among all emojis
99
- return applySearch(nextValue);
100
- }
90
+ if (!longestMatch) {
91
+ // Can we even get here?
92
+ // If so, we need to search among all emojis
93
+ return applySearch(nextValue);
94
+ }
101
95
 
102
- setFilterRef(current =>
103
- Object.assign(current, {
104
- [nextValue]: filterEmojiObjectByKeyword(longestMatch, nextValue)
105
- })
106
- );
107
- applySearch(nextValue);
108
- });
96
+ setFilterRef(current =>
97
+ Object.assign(current, {
98
+ [nextValue]: filterEmojiObjectByKeyword(longestMatch, nextValue)
99
+ })
100
+ );
101
+ applySearch(nextValue);
109
102
  }
110
103
  }
111
104
 
@@ -1,4 +1,4 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useState } from 'react';
2
2
 
3
3
  import { ElementRef } from '../components/context/ElementRefContext';
4
4
 
@@ -6,6 +6,7 @@ import { useCloseAllOpenToggles } from './useCloseAllOpenToggles';
6
6
 
7
7
  export function useOnScroll(BodyRef: ElementRef) {
8
8
  const closeAllOpenToggles = useCloseAllOpenToggles();
9
+ const [scrollTop, setScrollTop] = useState(0);
9
10
 
10
11
  useEffect(() => {
11
12
  const bodyRef = BodyRef.current;
@@ -18,6 +19,7 @@ export function useOnScroll(BodyRef: ElementRef) {
18
19
  });
19
20
 
20
21
  function onScroll() {
22
+ setScrollTop(bodyRef?.scrollTop ?? 0);
21
23
  closeAllOpenToggles();
22
24
  }
23
25
 
@@ -25,4 +27,6 @@ export function useOnScroll(BodyRef: ElementRef) {
25
27
  bodyRef?.removeEventListener('scroll', onScroll);
26
28
  };
27
29
  }, [BodyRef, closeAllOpenToggles]);
30
+
31
+ return scrollTop;
28
32
  }
@@ -0,0 +1,45 @@
1
+ export function shouldVirtualize({
2
+ scrollTop,
3
+ clientHeight,
4
+ topOffset,
5
+ style,
6
+ dimensions
7
+ }: {
8
+ scrollTop: number;
9
+ clientHeight: number;
10
+ topOffset: number;
11
+ style: { top: number } | undefined;
12
+ dimensions: Dimensions;
13
+ }): boolean {
14
+ if (!style || !dimensions) {
15
+ return false;
16
+ }
17
+
18
+ const emojiTop = topOffset + style.top;
19
+ const emojiBottom = emojiTop + dimensions.emojiSize;
20
+
21
+ const isVisible =
22
+ emojiBottom + dimensions.emojiSize * 2 >= scrollTop &&
23
+ emojiTop <= scrollTop + clientHeight + dimensions.emojiSize;
24
+
25
+ return !isVisible;
26
+ }
27
+
28
+ export function getEmojiPositionStyle(dimensions: Dimensions, index: number) {
29
+ return dimensions
30
+ ? {
31
+ top: Math.floor(index / dimensions.emojisPerRow) * dimensions.emojiSize,
32
+ left: (index % dimensions.emojisPerRow) * dimensions.emojiSize
33
+ }
34
+ : undefined;
35
+ }
36
+
37
+ // preload emoji if it is one row below viewport
38
+
39
+ export type Dimensions =
40
+ | {
41
+ emojiSize: number;
42
+ emojisPerRow: number;
43
+ categoryHeight: number;
44
+ }
45
+ | undefined;
@@ -1,7 +0,0 @@
1
- /// <reference types="react" />
2
- import { CategoryConfig } from '../../config/categoryConfig';
3
- declare type Props = Readonly<{
4
- categoryConfig: CategoryConfig;
5
- }>;
6
- export declare function Suggested({ categoryConfig }: Props): JSX.Element | null;
7
- export {};
@@ -1,4 +0,0 @@
1
- /// <reference types="react" />
2
- export declare function CssSearch({ value }: {
3
- value: undefined | string;
4
- }): JSX.Element | null;
@@ -1,2 +0,0 @@
1
- declare const browserSupportsCssHas: boolean;
2
- export default browserSupportsCssHas;
@@ -1,69 +0,0 @@
1
- import * as React from 'react';
2
-
3
- import { CategoryConfig } from '../../config/categoryConfig';
4
- import {
5
- useEmojiStyleConfig,
6
- useGetEmojiUrlConfig,
7
- useSuggestedEmojisModeConfig
8
- } from '../../config/useConfig';
9
- import { emojiByUnified } from '../../dataUtils/emojiSelectors';
10
- import { getSuggested } from '../../dataUtils/suggested';
11
- import { useIsEmojiDisallowed } from '../../hooks/useDisallowedEmojis';
12
- import { useIsEverMounted } from '../../hooks/useIsEverMounted';
13
- import { useUpdateSuggested } from '../context/PickerContext';
14
- import { ClickableEmoji } from '../emoji/Emoji';
15
-
16
- import { EmojiCategory } from './EmojiCategory';
17
-
18
- type Props = Readonly<{
19
- categoryConfig: CategoryConfig;
20
- }>;
21
-
22
- export function Suggested({ categoryConfig }: Props) {
23
- const [suggestedUpdated] = useUpdateSuggested();
24
- const isMounted = useIsEverMounted();
25
- const suggestedEmojisModeConfig = useSuggestedEmojisModeConfig();
26
- const getEmojiUrl = useGetEmojiUrlConfig();
27
- const suggested = React.useMemo(
28
- () => getSuggested(suggestedEmojisModeConfig) ?? [],
29
- // eslint-disable-next-line react-hooks/exhaustive-deps
30
- [suggestedUpdated, suggestedEmojisModeConfig]
31
- );
32
- const emojiStyle = useEmojiStyleConfig();
33
- const isEmojiDisallowed = useIsEmojiDisallowed();
34
-
35
- if (!isMounted) {
36
- return null;
37
- }
38
-
39
- return (
40
- <EmojiCategory
41
- categoryConfig={categoryConfig}
42
- hiddenOnSearch
43
- hidden={suggested.length === 0}
44
- >
45
- {suggested.map(suggestedItem => {
46
- const emoji = emojiByUnified(suggestedItem.original);
47
-
48
- if (!emoji) {
49
- return null;
50
- }
51
-
52
- if (isEmojiDisallowed(emoji)) {
53
- return null;
54
- }
55
-
56
- return (
57
- <ClickableEmoji
58
- showVariations={false}
59
- unified={suggestedItem.unified}
60
- emojiStyle={emojiStyle}
61
- emoji={emoji}
62
- key={suggestedItem.unified}
63
- getEmojiUrl={getEmojiUrl}
64
- />
65
- );
66
- })}
67
- </EmojiCategory>
68
- );
69
- }
@@ -1,45 +0,0 @@
1
- import * as React from 'react';
2
-
3
- import { ClassNames, asSelectors } from '../../../DomUtils/classNames';
4
- import { getNormalizedSearchTerm } from '../../../hooks/useFilter';
5
-
6
- const SCOPE = `${asSelectors(ClassNames.emojiPicker)} ${asSelectors(
7
- ClassNames.emojiList
8
- )}`;
9
-
10
- const EMOJI_BUTTON = ['button', asSelectors(ClassNames.emoji)].join('');
11
- const CATEGORY = asSelectors(ClassNames.category);
12
-
13
- export function CssSearch({ value }: { value: undefined | string }) {
14
- if (!value) {
15
- return null;
16
- }
17
-
18
- const q = genQuery(value);
19
-
20
- return (
21
- <style>{`
22
- ${SCOPE} ${EMOJI_BUTTON} {
23
- display: none;
24
- }
25
-
26
-
27
- ${SCOPE} ${q} {
28
- display: flex;
29
- }
30
-
31
- ${SCOPE} ${CATEGORY}:not(:has(${q})) {
32
- display: none;
33
- }
34
- `}</style>
35
- );
36
- }
37
-
38
- function genQuery(value: string): string {
39
- return [
40
- EMOJI_BUTTON,
41
- '[data-full-name*="',
42
- getNormalizedSearchTerm(value),
43
- '"]'
44
- ].join('');
45
- }