emoji-picker-react 4.15.0 → 4.15.2

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.15.0",
2
+ "version": "4.15.2",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "homepage": "https://ealush.com/emoji-picker-react",
@@ -52,6 +52,8 @@ export function EmojiCategory({
52
52
  const styles = stylesheet.create({
53
53
  category: {
54
54
  '.': ClassNames.category,
55
+ minHeight:
56
+ 'calc(var(--epr-emoji-fullsize) + var(--epr-category-label-height))',
55
57
  position: 'relative'
56
58
  },
57
59
  categoryContent: {
@@ -16,6 +16,7 @@ import { useEmojiListRef } from '../context/ElementRefContext';
16
16
  import { useVisibleCategoriesState } from '../context/PickerContext';
17
17
 
18
18
  import { EmojiCategory } from './EmojiCategory';
19
+ import { MeasureEmoji } from './MeasureEmoji';
19
20
 
20
21
  export function EmojiList({ scrollTop }: { scrollTop: number }) {
21
22
  const categories = useCategoriesConfig();
@@ -30,6 +31,7 @@ export function EmojiList({ scrollTop }: { scrollTop: number }) {
30
31
  let topOffset = 0;
31
32
  return (
32
33
  <ul className={cx(styles.emojiList)} ref={EmojiListRef}>
34
+ <MeasureEmoji />
33
35
  {categories.map(categoryConfig => {
34
36
  const category = categoryFromCategoryConfig(categoryConfig);
35
37
 
@@ -0,0 +1,74 @@
1
+ import * as React from 'react';
2
+
3
+ import {
4
+ categoryFromCategoryConfig
5
+ } from '../../config/categoryConfig';
6
+ import {
7
+ useCategoriesConfig,
8
+ useEmojiStyleConfig,
9
+ useGetEmojiUrlConfig,
10
+ useLazyLoadEmojisConfig
11
+ } from '../../config/useConfig';
12
+ import {
13
+ useGetEmojisByCategory,
14
+ emojiUnified
15
+ } from '../../dataUtils/emojiSelectors';
16
+ import { useActiveSkinToneState, useEmojiSizeState } from '../context/PickerContext';
17
+ import { ClickableEmoji } from '../emoji/Emoji';
18
+
19
+ export function MeasureEmoji() {
20
+ const categories = useCategoriesConfig();
21
+ const getEmojisByCategory = useGetEmojisByCategory();
22
+ const emojiStyle = useEmojiStyleConfig();
23
+ const getEmojiUrl = useGetEmojiUrlConfig();
24
+ const lazyLoadEmojis = useLazyLoadEmojisConfig();
25
+ const [activeSkinTone] = useActiveSkinToneState();
26
+ const [emojiSize, setEmojiSize] = useEmojiSizeState();
27
+ const ref = React.useRef<HTMLDivElement>(null);
28
+
29
+ React.useLayoutEffect(() => {
30
+ if (ref.current) {
31
+ setEmojiSize(ref.current.clientHeight);
32
+ }
33
+ });
34
+
35
+ if (emojiSize) {
36
+ return null;
37
+ }
38
+
39
+ const firstCategory = categories[0];
40
+ const dummyEmoji = getEmojisByCategory(
41
+ categoryFromCategoryConfig(firstCategory)
42
+ )[0];
43
+ const unified = dummyEmoji
44
+ ? emojiUnified(dummyEmoji, activeSkinTone)
45
+ : '';
46
+
47
+ if (!dummyEmoji) {
48
+ return null;
49
+ }
50
+
51
+ return (
52
+ <div ref={ref}>
53
+ <ClickableEmoji
54
+ emoji={dummyEmoji}
55
+ unified={unified}
56
+ emojiStyle={emojiStyle}
57
+ getEmojiUrl={getEmojiUrl}
58
+ lazyLoad={lazyLoadEmojis}
59
+ showVariations={false}
60
+ hidden={false}
61
+ style={{
62
+ opacity: 0,
63
+ pointerEvents: 'none',
64
+ position: 'absolute',
65
+ top: 0,
66
+ left: 0,
67
+ zIndex: -1,
68
+ height: 'var(--epr-emoji-fullsize)',
69
+ width: 'var(--epr-emoji-fullsize)'
70
+ }}
71
+ />
72
+ </div>
73
+ );
74
+ }
@@ -36,6 +36,7 @@ export function PickerContextProvider({ children }: Props) {
36
36
  const reactionsModeState = useState(reactionsDefaultOpen);
37
37
  const [isPastInitialLoad, setIsPastInitialLoad] = useState(false);
38
38
  const visibleCategoriesState = useState<string[]>([]);
39
+ const emojiSizeState = useState<number | null>(null);
39
40
 
40
41
  useMarkInitialLoad(setIsPastInitialLoad);
41
42
 
@@ -55,7 +56,8 @@ export function PickerContextProvider({ children }: Props) {
55
56
  skinToneFanOpenState,
56
57
  suggestedUpdateState,
57
58
  reactionsModeState,
58
- visibleCategoriesState
59
+ visibleCategoriesState,
60
+ emojiSizeState
59
61
  }}
60
62
  >
61
63
  {children}
@@ -80,6 +82,7 @@ const PickerContext = React.createContext<{
80
82
  disallowedEmojisRef: React.MutableRefObject<Record<string, boolean>>;
81
83
  reactionsModeState: ReactState<boolean>;
82
84
  visibleCategoriesState: ReactState<Array<string>>;
85
+ emojiSizeState: ReactState<number | null>;
83
86
  }>({
84
87
  activeCategoryState: [null, () => {}],
85
88
  activeSkinTone: [SkinTones.NEUTRAL, () => {}],
@@ -94,7 +97,8 @@ const PickerContext = React.createContext<{
94
97
  skinToneFanOpenState: [false, () => {}],
95
98
  suggestedUpdateState: [Date.now(), () => {}],
96
99
  reactionsModeState: [false, () => {}],
97
- visibleCategoriesState: [[], () => []]
100
+ visibleCategoriesState: [[], () => []],
101
+ emojiSizeState: [null, () => {}]
98
102
  });
99
103
 
100
104
  type Props = Readonly<{
@@ -164,6 +168,11 @@ export function useVisibleCategoriesState() {
164
168
  return visibleCategoriesState;
165
169
  }
166
170
 
171
+ export function useEmojiSizeState() {
172
+ const { emojiSizeState } = React.useContext(PickerContext);
173
+ return emojiSizeState;
174
+ }
175
+
167
176
  export function useUpdateSuggested(): [number, () => void] {
168
177
  const { suggestedUpdateState } = React.useContext(PickerContext);
169
178
 
@@ -15,6 +15,7 @@ export function useActiveCategoryScrollDetection({
15
15
 
16
16
  useEffect(() => {
17
17
  const visibleCategories = new Map<string, number>();
18
+ const intersectingCategories = new Map<string, boolean>();
18
19
  const bodyRef = BodyRef.current;
19
20
  const observer = new IntersectionObserver(
20
21
  entries => {
@@ -30,15 +31,18 @@ export function useActiveCategoryScrollDetection({
30
31
  }
31
32
 
32
33
  visibleCategories.set(id, entry.intersectionRatio);
34
+ intersectingCategories.set(id, entry.isIntersecting);
33
35
  }
34
36
 
35
37
  const ratios = Array.from(visibleCategories);
36
- setVisibleCategories(
37
- ratios.filter(([_, ratio]) => ratio > 0).map(([id]) => id)
38
- );
38
+ const visibleCats = ratios
39
+ .filter(([id, ratio]) => ratio > 0 || intersectingCategories.get(id))
40
+ .map(([id]) => id);
41
+
42
+ setVisibleCategories(visibleCats);
39
43
  const lastCategory = ratios[ratios.length - 1];
40
44
 
41
- if (lastCategory[1] == 1) {
45
+ if (lastCategory?.[1] == 1) {
42
46
  return setActiveCategory(lastCategory[0]);
43
47
  }
44
48
 
@@ -50,11 +54,16 @@ export function useActiveCategoryScrollDetection({
50
54
  }
51
55
  },
52
56
  {
57
+ root: bodyRef,
53
58
  threshold: [0, 1]
54
59
  }
55
60
  );
56
61
  bodyRef?.querySelectorAll(asSelectors(ClassNames.category)).forEach(el => {
57
62
  observer.observe(el);
58
63
  });
64
+
65
+ return () => {
66
+ observer.disconnect();
67
+ };
59
68
  }, [BodyRef, setActiveCategory, setVisibleCategories]);
60
69
  }
@@ -3,18 +3,17 @@ import * as React from 'react';
3
3
  import { EmojiButtonSelector } from '../DomUtils/selectors';
4
4
  import {
5
5
  useEmojiListRef,
6
- usePickerMainRef
6
+ usePickerMainRef,
7
7
  } from '../components/context/ElementRefContext';
8
8
  import {
9
9
  useReactionsModeState,
10
- useVisibleCategoriesState
10
+ useVisibleCategoriesState,
11
+ useEmojiSizeState,
11
12
  } from '../components/context/PickerContext';
12
13
 
13
- const EMOJI_SIZE_DEFAULT = 32;
14
+ const EMOJI_SIZE_DEFAULT = 40;
14
15
 
15
- export function useCategoryHeight(
16
- emojiCount: number
17
- ):
16
+ export function useCategoryHeight(emojiCount: number):
18
17
  | {
19
18
  categoryHeight: number;
20
19
  emojisPerRow: number;
@@ -26,6 +25,7 @@ export function useCategoryHeight(
26
25
  const PickerMainRef = usePickerMainRef();
27
26
  const emojiSizeRef = React.useRef<number | undefined>();
28
27
  const [visibleCategories] = useVisibleCategoriesState();
28
+ const [emojiSizeFromContext] = useEmojiSizeState();
29
29
  const [dimensions, setDimensions] = React.useState<{
30
30
  categoryHeight: number;
31
31
  emojisPerRow: number;
@@ -38,12 +38,18 @@ export function useCategoryHeight(
38
38
  if (!listEl) return;
39
39
 
40
40
  const emojiElement = listEl.querySelector(
41
- EmojiButtonSelector
41
+ EmojiButtonSelector,
42
42
  ) as HTMLElement | null;
43
43
 
44
44
  const measured = emojiElement?.clientHeight;
45
- const emojiSize = measured ?? emojiSizeRef.current ?? EMOJI_SIZE_DEFAULT;
46
- emojiSizeRef.current = emojiSize;
45
+ if (measured) {
46
+ emojiSizeRef.current = measured;
47
+ }
48
+ const emojiSize =
49
+ emojiSizeFromContext ||
50
+ measured ||
51
+ emojiSizeRef.current ||
52
+ EMOJI_SIZE_DEFAULT;
47
53
  const pickerWidth = listEl.clientWidth;
48
54
 
49
55
  if (pickerWidth === 0 || emojiSize === 0) return;
@@ -53,7 +59,7 @@ export function useCategoryHeight(
53
59
  const categoryHeight = rowCount * emojiSize;
54
60
 
55
61
  setDimensions({ categoryHeight, emojisPerRow, emojiSize });
56
- }, [EmojiListRef, emojiCount]);
62
+ }, [EmojiListRef, emojiCount, emojiSizeFromContext]);
57
63
 
58
64
  // Recompute on data-count changes and when reactions mode toggles
59
65
  React.useEffect(() => {
@@ -62,7 +68,7 @@ export function useCategoryHeight(
62
68
  emojiCount,
63
69
  isReactionsMode,
64
70
  computeAndSetDimensions,
65
- visibleCategories.length
71
+ visibleCategories.length,
66
72
  ]);
67
73
 
68
74
  // Listen to transitionend on the picker root (where height transition occurs)
@@ -90,7 +96,7 @@ export function useCategoryHeight(
90
96
  };
91
97
 
92
98
  rootEl.addEventListener('transitionend', handler, {
93
- passive: true
99
+ passive: true,
94
100
  });
95
101
  return () => {
96
102
  rootEl.removeEventListener('transitionend', handler);
@@ -1,4 +1,5 @@
1
- import { ReactNode, useEffect } from 'react';
1
+
2
+ import { ReactNode, useEffect } from 'react';
2
3
  import * as React from 'react';
3
4
 
4
5
  import { useBodyRef } from '../components/context/ElementRefContext';
@@ -61,20 +62,22 @@ export function useEmojiVirtualization({
61
62
  }
62
63
  }, [dimensions, onHeightReady, emojisToPush.length]);
63
64
 
65
+ const isVirtualized = (style: { top: number; left: number } | undefined) =>
66
+ dimensions &&
67
+ BodyRef.current &&
68
+ shouldVirtualize({
69
+ scrollTop,
70
+ clientHeight: BodyRef.current?.clientHeight ?? 0,
71
+ topOffset,
72
+ style,
73
+ dimensions
74
+ });
75
+
64
76
  const emojis = emojisToPush.reduce((accumulator, emoji, index) => {
65
77
  const unified = emojiUnified(emoji, activeSkinTone);
66
78
  const style = getEmojiPositionStyle(dimensions, index);
67
- if (
68
- dimensions &&
69
- BodyRef.current &&
70
- shouldVirtualize({
71
- scrollTop,
72
- clientHeight: BodyRef.current?.clientHeight ?? 0,
73
- topOffset,
74
- style,
75
- dimensions
76
- })
77
- ) {
79
+
80
+ if (isVirtualized(style)) {
78
81
  virtualizedCounter++;
79
82
  preloadEmojiIfNeeded(
80
83
  emoji,