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,5 +1,9 @@
1
1
  import { GetEmojiUrl } from '../components/emoji/BaseEmojiProps';
2
2
  import { DataEmoji } from '../dataUtils/DataTypes';
3
3
  import { EmojiStyle } from '../types/exposedTypes';
4
+ import { Dimensions } from '../virtualization/virtualizationHelpers';
5
+ export declare function preloaEmojidIfNeeded(emoji: undefined | DataEmoji, emojiStyles: EmojiStyle, scrollTop: number, clientHeight: number, topOffset: number, style: {
6
+ top: number;
7
+ } | undefined, dimensions: Dimensions, getEmojiUrl: GetEmojiUrl): void;
4
8
  export declare function preloadEmoji(getEmojiUrl: GetEmojiUrl, emoji: undefined | DataEmoji, emojiStyle: EmojiStyle): void;
5
9
  export declare const preloadedEmojs: Set<string>;
@@ -0,0 +1,5 @@
1
+ export declare function useCategoryHeight(emojiCount: number): {
2
+ categoryHeight: number;
3
+ emojisPerRow: number;
4
+ emojiSize: number;
5
+ } | undefined;
@@ -0,0 +1,16 @@
1
+ import { ReactNode } from 'react';
2
+ import { DataEmojis } from '../dataUtils/DataTypes';
3
+ export declare function useEmojiVirtualization({ categoryEmojis, topOffset, onHeightReady, scrollTop }: {
4
+ categoryEmojis: DataEmojis;
5
+ topOffset: number;
6
+ onHeightReady: (height: number) => void;
7
+ scrollTop: number;
8
+ }): {
9
+ virtualizedCounter: number;
10
+ emojis: ReactNode[];
11
+ dimensions: {
12
+ categoryHeight: number;
13
+ emojisPerRow: number;
14
+ emojiSize: number;
15
+ } | undefined;
16
+ };
@@ -1,2 +1,2 @@
1
1
  import { ElementRef } from '../components/context/ElementRefContext';
2
- export declare function useOnScroll(BodyRef: ElementRef): void;
2
+ export declare function useOnScroll(BodyRef: ElementRef): number;
@@ -0,0 +1,18 @@
1
+ export declare function shouldVirtualize({ scrollTop, clientHeight, topOffset, style, dimensions }: {
2
+ scrollTop: number;
3
+ clientHeight: number;
4
+ topOffset: number;
5
+ style: {
6
+ top: number;
7
+ } | undefined;
8
+ dimensions: Dimensions;
9
+ }): boolean;
10
+ export declare function getEmojiPositionStyle(dimensions: Dimensions, index: number): {
11
+ top: number;
12
+ left: number;
13
+ } | undefined;
14
+ export declare type Dimensions = {
15
+ emojiSize: number;
16
+ emojisPerRow: number;
17
+ categoryHeight: number;
18
+ } | undefined;
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
- "version": "4.13.3",
2
+ "version": "4.14.0",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
+ "homepage": "https://ealush.com/emoji-picker-react",
5
6
  "typings": "dist/index.d.ts",
6
7
  "files": [
7
8
  "dist",
@@ -16,131 +16,6 @@ export function elementCountInRow(
16
16
  return Math.floor(parentWidth / elementWidth);
17
17
  }
18
18
 
19
- export function elementIndexInRow(
20
- parent: NullableElement,
21
- element: NullableElement
22
- ): number {
23
- if (!parent || !element) {
24
- return 0;
25
- }
26
-
27
- const elementWidth = element.getBoundingClientRect().width;
28
- const elementLeft = element.getBoundingClientRect().left;
29
- const parentLeft = parent.getBoundingClientRect().left;
30
-
31
- return Math.floor((elementLeft - parentLeft) / elementWidth);
32
- }
33
-
34
- export function rowNumber(
35
- parent: NullableElement,
36
- element: NullableElement
37
- ): number {
38
- if (!parent || !element) {
39
- return 0;
40
- }
41
-
42
- const elementHeight = element.getBoundingClientRect().height;
43
- const elementTop = element.getBoundingClientRect().top;
44
- const parentTop = parent.getBoundingClientRect().top;
45
- return Math.round((elementTop - parentTop) / elementHeight);
46
- }
47
-
48
- export function hasNextRow(
49
- parent: NullableElement,
50
- element: NullableElement
51
- ): boolean {
52
- if (!parent || !element) {
53
- return false;
54
- }
55
-
56
- const elementHeight = element.getBoundingClientRect().height;
57
- const elementTop = element.getBoundingClientRect().top;
58
- const parentTop = parent.getBoundingClientRect().top;
59
- const parentHeight = parent.getBoundingClientRect().height;
60
-
61
- return Math.round(elementTop - parentTop + elementHeight) < parentHeight;
62
- }
63
-
64
- function getRowElements(
65
- elements: HTMLElement[],
66
- row: number,
67
- elementsInRow: number
68
- ): HTMLElement[] {
69
- if (row === -1) {
70
- const lastRow = Math.floor((elements.length - 1) / elementsInRow);
71
- const firstElementIndex = lastRow * elementsInRow;
72
- const lastElementIndex = elements.length - 1;
73
- return elements.slice(firstElementIndex, lastElementIndex + 1);
74
- }
75
-
76
- return elements.slice(row * elementsInRow, (row + 1) * elementsInRow);
77
- }
78
-
79
- function getNextRowElements(
80
- allElements: HTMLElement[],
81
- currentRow: number,
82
- elementsInRow: number
83
- ): HTMLElement[] {
84
- const nextRow = currentRow + 1;
85
-
86
- if (nextRow * elementsInRow > allElements.length) {
87
- return [];
88
- }
89
-
90
- return getRowElements(allElements, nextRow, elementsInRow);
91
- }
92
-
93
- export function getElementInRow(
94
- elements: HTMLElement[],
95
- row: number,
96
- elementsInRow: number,
97
- indexInRow: number
98
- ): NullableElement {
99
- const rowElements = getRowElements(elements, row, elementsInRow);
100
- // get element, default to last
101
- return rowElements[indexInRow] || rowElements[rowElements.length - 1] || null;
102
- }
103
-
104
- export function getElementInNextRow(
105
- allElements: HTMLElement[],
106
- currentRow: number,
107
- elementsInRow: number,
108
- index: number
109
- ): NullableElement {
110
- const nextRowElements = getNextRowElements(
111
- allElements,
112
- currentRow,
113
- elementsInRow
114
- );
115
-
116
- // return item in index, or last item in row
117
- return (
118
- nextRowElements[index] ||
119
- nextRowElements[nextRowElements.length - 1] ||
120
- null
121
- );
122
- }
123
-
124
- export function getElementInPrevRow(
125
- allElements: HTMLElement[],
126
- currentRow: number,
127
- elementsInRow: number,
128
- index: number
129
- ): NullableElement {
130
- const prevRowElements = getRowElements(
131
- allElements,
132
- currentRow - 1,
133
- elementsInRow
134
- );
135
-
136
- // default to last
137
- return (
138
- prevRowElements[index] ||
139
- prevRowElements[prevRowElements.length - 1] ||
140
- null
141
- );
142
- }
143
-
144
19
  export function firstVisibleElementInContainer(
145
20
  parent: NullableElement,
146
21
  elements: HTMLElement[],
@@ -183,18 +58,20 @@ export function hasNextElementSibling(element: HTMLElement) {
183
58
  return !!element.nextElementSibling;
184
59
  }
185
60
 
186
- function getLabelHeight(parentNode: HTMLElement) {
187
- const labels = Array.from(
188
- parentNode.querySelectorAll(asSelectors(ClassNames.label))
189
- );
61
+ export function getLabelHeight(parentNode: NullableElement) {
62
+ if (!parentNode) {
63
+ return DEFAULT_LABEL_HEIGHT;
64
+ }
65
+
66
+ const label = parentNode.querySelector(asSelectors(ClassNames.label));
190
67
 
191
- for (const label of labels) {
68
+ if (label) {
192
69
  const height = label.getBoundingClientRect().height;
193
- // return height if label is not hidden
194
70
  if (height > 0) {
195
71
  return height;
196
72
  }
197
73
  }
198
74
 
75
+ // fallback to default
199
76
  return DEFAULT_LABEL_HEIGHT;
200
77
  }
@@ -1,12 +1,4 @@
1
- import {
2
- elementCountInRow,
3
- elementIndexInRow,
4
- getElementInNextRow,
5
- getElementInPrevRow,
6
- getElementInRow,
7
- hasNextRow,
8
- rowNumber
9
- } from './elementPositionInRow';
1
+ import { elementCountInRow } from './elementPositionInRow';
10
2
  import { focusElement } from './focusElement';
11
3
  import { scrollEmojiAboveLabel } from './scrollTo';
12
4
  import {
@@ -104,31 +96,47 @@ function visibleEmojiOneRowUp(element: HTMLElement) {
104
96
 
105
97
  const categoryContent = closestCategoryContent(element);
106
98
  const category = closestCategory(categoryContent);
107
- const indexInRow = elementIndexInRow(categoryContent, element);
108
- const row = rowNumber(categoryContent, element);
109
99
  const countInRow = elementCountInRow(categoryContent, element);
110
100
 
111
- if (row === 0) {
112
- const prevVisibleCategory = prevCategory(category);
101
+ const emojisInCurrentCategory = allVisibleEmojis(category);
102
+ const currentEmojiIndex = emojisInCurrentCategory.indexOf(element);
103
+ const indexInRow = currentEmojiIndex % countInRow;
113
104
 
114
- if (!prevVisibleCategory) {
115
- return null;
116
- }
105
+ if (currentEmojiIndex === -1) {
106
+ return null;
107
+ }
108
+
109
+ if (emojisInCurrentCategory[currentEmojiIndex - countInRow]) {
110
+ return emojisInCurrentCategory[currentEmojiIndex - countInRow];
111
+ }
112
+
113
+ const prevVisibleCategory = prevCategory(category);
114
+
115
+ if (!prevVisibleCategory) {
116
+ return null;
117
+ }
118
+
119
+ const allPrevEmojis = allVisibleEmojis(prevVisibleCategory);
120
+
121
+ // if there is an emoji in the same index of `indexInRow` in the previous category, return it
122
+ // for this we need to find the last emoji in the previous category that shares the same indexInRow
123
+ // this is by using the % operator to find the last emoji that matches
124
+
125
+ const lastIndexInRow = (allPrevEmojis.length % countInRow) - 1;
117
126
 
118
- return getElementInRow(
119
- allVisibleEmojis(prevVisibleCategory),
120
- -1, // last row
121
- countInRow,
122
- indexInRow
123
- );
127
+ if (indexInRow > lastIndexInRow) {
128
+ return allPrevEmojis.at(-1);
124
129
  }
125
130
 
126
- return getElementInPrevRow(
127
- allVisibleEmojis(categoryContent),
128
- row,
129
- countInRow,
130
- indexInRow
131
- );
131
+ // otherwise, return the last emoji that shares the same indexInRow
132
+
133
+ for (let i = allPrevEmojis.length - 1; i >= 0; i--) {
134
+ if (i % countInRow === indexInRow) {
135
+ return allPrevEmojis[i];
136
+ }
137
+ }
138
+
139
+ return allPrevEmojis.at(-1);
132
140
  }
133
141
 
134
142
  function visibleEmojiOneRowDown(element: HTMLElement) {
@@ -138,30 +146,36 @@ function visibleEmojiOneRowDown(element: HTMLElement) {
138
146
 
139
147
  const categoryContent = closestCategoryContent(element);
140
148
  const category = closestCategory(categoryContent);
141
- const indexInRow = elementIndexInRow(categoryContent, element);
142
- const row = rowNumber(categoryContent, element);
143
149
  const countInRow = elementCountInRow(categoryContent, element);
144
- if (!hasNextRow(categoryContent, element)) {
145
- const nextVisibleCategory = nextCategory(category);
146
150
 
147
- if (!nextVisibleCategory) {
148
- return null;
149
- }
151
+ const emojisInCurrentCategory = allVisibleEmojis(category);
152
+ const currentEmojiIndex = emojisInCurrentCategory.indexOf(element);
150
153
 
151
- return getElementInRow(
152
- allVisibleEmojis(nextVisibleCategory),
153
- 0,
154
- countInRow,
155
- indexInRow
156
- );
154
+ if (currentEmojiIndex === -1) {
155
+ return null;
157
156
  }
158
157
 
159
- const itemInNextRow = getElementInNextRow(
160
- allVisibleEmojis(categoryContent),
161
- row,
162
- countInRow,
163
- indexInRow
164
- );
158
+ // the remainder until the end of the row
159
+ const remainder = countInRow - (currentEmojiIndex % countInRow) - 1;
160
+ const firstInNextRow = currentEmojiIndex + remainder + 1;
161
+
162
+ if (emojisInCurrentCategory[firstInNextRow]) {
163
+ // if we have a next row, search in the next row for the last available emoji
164
+ for (let p = currentEmojiIndex + countInRow; p % countInRow >= 0; p--) {
165
+ if (emojisInCurrentCategory[p]) {
166
+ return emojisInCurrentCategory[p];
167
+ }
168
+ }
169
+ }
170
+
171
+ const indexInRow = currentEmojiIndex % countInRow;
172
+
173
+ const nextVisibleCategory = nextCategory(category);
174
+ const emojisInNextCategory = allVisibleEmojis(nextVisibleCategory);
175
+
176
+ if (emojisInNextCategory[indexInRow]) {
177
+ return emojisInNextCategory[indexInRow];
178
+ }
165
179
 
166
- return itemInNextRow;
180
+ return emojisInNextCategory.at(0) ?? null;
167
181
  }
@@ -17,7 +17,7 @@ import { EmojiVariationPicker } from './EmojiVariationPicker';
17
17
 
18
18
  export function Body() {
19
19
  const BodyRef = useBodyRef();
20
- useOnScroll(BodyRef);
20
+ const scrollTop = useOnScroll(BodyRef);
21
21
  useMouseDownHandlers(BodyRef, MOUSE_EVENT_SOURCE.PICKER);
22
22
  useOnMouseMove();
23
23
 
@@ -27,7 +27,7 @@ export function Body() {
27
27
  ref={BodyRef}
28
28
  >
29
29
  <EmojiVariationPicker />
30
- <EmojiList />
30
+ <EmojiList scrollTop={scrollTop} />
31
31
  </div>
32
32
  );
33
33
  }
@@ -18,13 +18,15 @@ type Props = Readonly<{
18
18
  children?: React.ReactNode;
19
19
  hidden?: boolean;
20
20
  hiddenOnSearch?: boolean;
21
+ height?: number;
21
22
  }>;
22
23
 
23
24
  export function EmojiCategory({
24
25
  categoryConfig,
25
26
  children,
26
27
  hidden,
27
- hiddenOnSearch
28
+ hiddenOnSearch,
29
+ height
28
30
  }: Props) {
29
31
  const category = categoryFromCategoryConfig(categoryConfig);
30
32
  const categoryName = categoryNameFromCategoryConfig(categoryConfig);
@@ -40,7 +42,9 @@ export function EmojiCategory({
40
42
  aria-label={categoryName}
41
43
  >
42
44
  <h2 className={cx(styles.label)}>{categoryName}</h2>
43
- <div className={cx(styles.categoryContent)}>{children}</div>
45
+ <div className={cx(styles.categoryContent)} style={{ height }}>
46
+ {children}
47
+ </div>
44
48
  </li>
45
49
  );
46
50
  }
@@ -48,9 +52,7 @@ export function EmojiCategory({
48
52
  const styles = stylesheet.create({
49
53
  category: {
50
54
  '.': ClassNames.category,
51
- ':not(:has(.epr-visible))': {
52
- display: 'none'
53
- }
55
+ position: 'relative'
54
56
  },
55
57
  categoryContent: {
56
58
  '.': ClassNames.categoryContent,
@@ -2,50 +2,54 @@ import { cx } from 'flairup';
2
2
  import * as React from 'react';
3
3
 
4
4
  import { ClassNames } from '../../DomUtils/classNames';
5
+ import { getLabelHeight } from '../../DomUtils/elementPositionInRow';
5
6
  import { stylesheet } from '../../Stylesheet/stylesheet';
6
7
  import {
7
- Categories,
8
8
  CategoryConfig,
9
9
  categoryFromCategoryConfig
10
10
  } from '../../config/categoryConfig';
11
- import {
12
- useCategoriesConfig,
13
- useEmojiStyleConfig,
14
- useGetEmojiUrlConfig,
15
- useLazyLoadEmojisConfig,
16
- useSkinTonesDisabledConfig
17
- } from '../../config/useConfig';
18
- import { emojisByCategory, emojiUnified } from '../../dataUtils/emojiSelectors';
19
- import { useIsEmojiDisallowed } from '../../hooks/useDisallowedEmojis';
20
- import { useIsEmojiHidden } from '../../hooks/useIsEmojiHidden';
21
- import {
22
- useActiveSkinToneState,
23
- useIsPastInitialLoad
24
- } from '../context/PickerContext';
25
- import { ClickableEmoji } from '../emoji/Emoji';
11
+ import { useCategoriesConfig } from '../../config/useConfig';
12
+ import { DataEmojis } from '../../dataUtils/DataTypes';
13
+ import { useGetEmojisByCategory } from '../../dataUtils/emojiSelectors';
14
+ import { useEmojiVirtualization } from '../../hooks/useEmojiVirtualization';
15
+ import { useEmojiListRef } from '../context/ElementRefContext';
26
16
 
27
17
  import { EmojiCategory } from './EmojiCategory';
28
- import { Suggested } from './Suggested';
29
18
 
30
- export function EmojiList() {
19
+ export function EmojiList({ scrollTop }: { scrollTop: number }) {
31
20
  const categories = useCategoriesConfig();
32
- const renderdCategoriesCountRef = React.useRef(0);
21
+ const [categoryHeights, setCategoryHeights] = React.useState<{
22
+ [key: string]: number;
23
+ }>({});
24
+ const EmojiListRef = useEmojiListRef();
25
+ const getEmojisByCategory = useGetEmojisByCategory();
26
+
27
+ const labelHeight = getLabelHeight(EmojiListRef.current);
33
28
 
29
+ let topOffset = 0;
34
30
  return (
35
- <ul className={cx(styles.emojiList)}>
31
+ <ul className={cx(styles.emojiList)} ref={EmojiListRef}>
36
32
  {categories.map(categoryConfig => {
37
33
  const category = categoryFromCategoryConfig(categoryConfig);
38
34
 
39
- if (category === Categories.SUGGESTED) {
40
- return <Suggested key={category} categoryConfig={categoryConfig} />;
35
+ const currentOffset = topOffset;
36
+ const categoryHeight = categoryHeights[category];
37
+ if (categoryHeight) {
38
+ topOffset += categoryHeight + labelHeight;
41
39
  }
42
40
 
43
41
  return (
44
42
  <React.Suspense key={category}>
45
43
  <RenderCategory
46
- category={category}
44
+ categoryEmojis={getEmojisByCategory(category)}
47
45
  categoryConfig={categoryConfig}
48
- renderdCategoriesCountRef={renderdCategoriesCountRef}
46
+ topOffset={currentOffset}
47
+ onHeightReady={height => {
48
+ if (categoryHeights[category] !== height) {
49
+ setCategoryHeights(prev => ({ ...prev, [category]: height }));
50
+ }
51
+ }}
52
+ scrollTop={scrollTop}
49
53
  />
50
54
  </React.Suspense>
51
55
  );
@@ -55,71 +59,32 @@ export function EmojiList() {
55
59
  }
56
60
 
57
61
  function RenderCategory({
58
- category,
62
+ categoryEmojis,
59
63
  categoryConfig,
60
- renderdCategoriesCountRef
64
+ topOffset,
65
+ onHeightReady,
66
+ scrollTop
61
67
  }: {
62
- category: Categories;
68
+ categoryEmojis: DataEmojis;
63
69
  categoryConfig: CategoryConfig;
64
- renderdCategoriesCountRef: React.MutableRefObject<number>;
70
+ topOffset: number;
71
+ onHeightReady: (height: number) => void;
72
+ scrollTop: number;
65
73
  }) {
66
- const isEmojiHidden = useIsEmojiHidden();
67
- const lazyLoadEmojis = useLazyLoadEmojisConfig();
68
- const emojiStyle = useEmojiStyleConfig();
69
- const isPastInitialLoad = useIsPastInitialLoad();
70
- const [activeSkinTone] = useActiveSkinToneState();
71
- const isEmojiDisallowed = useIsEmojiDisallowed();
72
- const getEmojiUrl = useGetEmojiUrlConfig();
73
- const showVariations = !useSkinTonesDisabledConfig();
74
-
75
- // Small trick to defer the rendering of all emoji categories until the first category is visible
76
- // This way the user gets to actually see something and not wait for the whole picker to render.
77
- const emojisToPush =
78
- !isPastInitialLoad && renderdCategoriesCountRef.current > 0
79
- ? []
80
- : emojisByCategory(category);
81
-
82
- if (emojisToPush.length > 0) {
83
- renderdCategoriesCountRef.current++;
84
- }
85
-
86
- let hiddenCounter = 0;
87
-
88
- const emojis = emojisToPush.map(emoji => {
89
- const unified = emojiUnified(emoji, activeSkinTone);
90
- const { failedToLoad, filteredOut, hidden } = isEmojiHidden(emoji);
91
-
92
- const isDisallowed = isEmojiDisallowed(emoji);
93
-
94
- if (hidden || isDisallowed) {
95
- hiddenCounter++;
96
- }
97
-
98
- if (isDisallowed) {
99
- return null;
100
- }
101
-
102
- return (
103
- <ClickableEmoji
104
- showVariations={showVariations}
105
- key={unified}
106
- emoji={emoji}
107
- unified={unified}
108
- hidden={failedToLoad}
109
- hiddenOnSearch={filteredOut}
110
- emojiStyle={emojiStyle}
111
- lazyLoad={lazyLoadEmojis}
112
- getEmojiUrl={getEmojiUrl}
113
- />
114
- );
74
+ const { virtualizedCounter, emojis, dimensions } = useEmojiVirtualization({
75
+ categoryEmojis,
76
+ topOffset,
77
+ onHeightReady,
78
+ scrollTop
115
79
  });
116
80
 
117
81
  return (
118
82
  <EmojiCategory
119
83
  categoryConfig={categoryConfig}
84
+ height={dimensions?.categoryHeight}
120
85
  // Indicates that there are no visible emojis
121
86
  // Hence, the category should be hidden
122
- hidden={hiddenCounter === emojis.length}
87
+ hidden={!emojis.length && virtualizedCounter === 0}
123
88
  >
124
89
  {emojis}
125
90
  </EmojiCategory>
@@ -11,6 +11,7 @@ export function ElementRefContextProvider({
11
11
  const PickerMainRef = React.useRef<HTMLElement>(null);
12
12
  const AnchoredEmojiRef = React.useRef<HTMLElement>(null);
13
13
  const BodyRef = React.useRef<HTMLDivElement>(null);
14
+ const EmojiListRef = React.useRef<HTMLUListElement>(null);
14
15
  const SearchInputRef = React.useRef<HTMLInputElement>(null);
15
16
  const SkinTonePickerRef = React.useRef<HTMLDivElement>(null);
16
17
  const CategoryNavigationRef = React.useRef<HTMLDivElement>(null);
@@ -22,6 +23,7 @@ export function ElementRefContextProvider({
22
23
  value={{
23
24
  AnchoredEmojiRef,
24
25
  BodyRef,
26
+ EmojiListRef,
25
27
  CategoryNavigationRef,
26
28
  PickerMainRef,
27
29
  SearchInputRef,
@@ -42,6 +44,7 @@ export type ElementRef<
42
44
  type ElementRefs = {
43
45
  PickerMainRef: ElementRef;
44
46
  AnchoredEmojiRef: ElementRef;
47
+ EmojiListRef: ElementRef<HTMLUListElement>;
45
48
  SkinTonePickerRef: ElementRef<HTMLDivElement>;
46
49
  SearchInputRef: ElementRef<HTMLInputElement>;
47
50
  BodyRef: ElementRef<HTMLDivElement>;
@@ -54,6 +57,7 @@ const ElementRefContext = React.createContext<ElementRefs>({
54
57
  AnchoredEmojiRef: React.createRef(),
55
58
  BodyRef: React.createRef(),
56
59
  CategoryNavigationRef: React.createRef(),
60
+ EmojiListRef: React.createRef(),
57
61
  PickerMainRef: React.createRef(),
58
62
  SearchInputRef: React.createRef(),
59
63
  SkinTonePickerRef: React.createRef(),
@@ -65,6 +69,10 @@ function useElementRef() {
65
69
  return React.useContext(ElementRefContext);
66
70
  }
67
71
 
72
+ export function useEmojiListRef() {
73
+ return useElementRef()['EmojiListRef'];
74
+ }
75
+
68
76
  export function usePickerMainRef() {
69
77
  return useElementRef()['PickerMainRef'];
70
78
  }
@@ -19,6 +19,7 @@ type ClickableEmojiButtonProps = Readonly<{
19
19
  unified?: string;
20
20
  noBackground?: boolean;
21
21
  className?: string;
22
+ style?: React.CSSProperties;
22
23
  }>;
23
24
 
24
25
  export function ClickableEmojiButton({
@@ -30,7 +31,8 @@ export function ClickableEmojiButton({
30
31
  hasVariations,
31
32
  children,
32
33
  className,
33
- noBackground = false
34
+ noBackground = false,
35
+ style
34
36
  }: ClickableEmojiButtonProps) {
35
37
  return (
36
38
  <Button
@@ -48,6 +50,7 @@ export function ClickableEmojiButton({
48
50
  data-unified={unified}
49
51
  aria-label={getAriaLabel(emojiNames)}
50
52
  data-full-name={emojiNames}
53
+ style={style}
51
54
  >
52
55
  {children}
53
56
  </Button>
@@ -15,6 +15,7 @@ type ClickableEmojiProps = Readonly<
15
15
  emoji: DataEmoji;
16
16
  className?: string;
17
17
  noBackground?: boolean;
18
+ style?: React.CSSProperties;
18
19
  }
19
20
  >;
20
21
 
@@ -29,7 +30,8 @@ export function ClickableEmoji({
29
30
  lazyLoad,
30
31
  getEmojiUrl,
31
32
  className,
32
- noBackground = false
33
+ noBackground = false,
34
+ style
33
35
  }: ClickableEmojiProps) {
34
36
  const hasVariations = emojiHasVariations(emoji);
35
37
 
@@ -42,6 +44,7 @@ export function ClickableEmoji({
42
44
  emojiNames={emojiNames(emoji)}
43
45
  unified={unified}
44
46
  noBackground={noBackground}
47
+ style={style}
45
48
  >
46
49
  <ViewOnlyEmoji
47
50
  unified={unified}