Skip to content

Commit f28cfca

Browse files
committed
fix: resolved comments
1 parent 129a75a commit f28cfca

File tree

10 files changed

+147
-19
lines changed

10 files changed

+147
-19
lines changed

src/DomUtils/keyboardNavigation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,11 @@ function visibleEmojiOneRowDown(element: HTMLElement) {
141141
const indexInRow = elementIndexInRow(categoryContent, element);
142142
const row = rowNumber(categoryContent, element);
143143
const countInRow = elementCountInRow(categoryContent, element);
144+
144145
if (!hasNextRow(categoryContent, element)) {
145146
const nextVisibleCategory = nextCategory(category);
146147

148+
147149
if (!nextVisibleCategory) {
148150
return null;
149151
}

src/DomUtils/selectors.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { VIRTUALIZE_CLASS_NAMES } from '../components/Layout/Virtualise';
12
import { DataEmoji } from '../dataUtils/DataTypes';
23
import {
34
emojiByUnified,
@@ -267,7 +268,37 @@ export function firstVisibleEmoji(parent: NullableElement) {
267268
return firstVisibleElementInContainer(parent, allEmojis, 0.1);
268269
}
269270

271+
272+
const getNameBasedPrevCategory = (element: HTMLElement): HTMLElement | null => {
273+
const emojiList = document.querySelector<HTMLElement>(asSelectors(ClassNames.emojiList));
274+
if (!emojiList) return null;
275+
276+
const currentName = element?.getAttribute('data-name');
277+
const categories = Array.from(emojiList.children);
278+
const currentIndex = categories.findIndex(
279+
child => child.firstElementChild?.getAttribute('data-name') === currentName
280+
);
281+
282+
const prevIndex = Math.max(currentIndex - 1, 0);
283+
const prevName = categories[prevIndex]?.firstElementChild?.getAttribute('data-name');
284+
285+
return prevName
286+
? emojiList.querySelector(`[data-name="${prevName}"]`) as HTMLElement
287+
: null;
288+
};
289+
290+
270291
export function prevCategory(element: NullableElement): NullableElement {
292+
if (!element) {
293+
return null;
294+
}
295+
296+
const currentPrevCategory = getNameBasedPrevCategory(element);
297+
298+
if(currentPrevCategory){
299+
return currentPrevCategory;
300+
}
301+
271302
const category = closestCategory(element);
272303

273304
if (!category) {
@@ -287,7 +318,36 @@ export function prevCategory(element: NullableElement): NullableElement {
287318
return prev;
288319
}
289320

321+
const getNameBasedNextCategory = (element: HTMLElement): HTMLElement | null => {
322+
const emojiList = document.querySelector<HTMLElement>(asSelectors(ClassNames.emojiList));
323+
if (!emojiList) return null;
324+
325+
const currentName = element?.getAttribute('data-name');
326+
const categories = Array.from(emojiList.children);
327+
const currentIndex = categories.findIndex(
328+
child => child.firstElementChild?.getAttribute('data-name') === currentName
329+
);
330+
331+
const nextIndex = Math.min(currentIndex + 1, categories.length - 1);
332+
const nextName = categories[nextIndex]?.firstElementChild?.getAttribute('data-name');
333+
334+
return nextName
335+
? emojiList.querySelector(`[data-name="${nextName}"]`)
336+
: null;
337+
};
338+
339+
290340
export function nextCategory(element: NullableElement): NullableElement {
341+
if (!element) {
342+
return null;
343+
}
344+
345+
const currentNextCategory = getNameBasedNextCategory(element);
346+
347+
if(currentNextCategory){
348+
return currentNextCategory;
349+
}
350+
291351
const category = closestCategory(element);
292352

293353
if (!category) {

src/components/Layout/Virtualise.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
import { cx } from 'flairup';
2-
import React, { useState, useCallback, useMemo } from 'react';
2+
import React, { useState, useCallback, useMemo, useEffect } from 'react';
33

44
import { stylesheet } from '../../Stylesheet/stylesheet';
55
import useMeasure from '../../hooks/useMeasure';
6+
import { useActiveCategoryState } from '../context/PickerContext';
67

78
const sum = (arr: number[]) => arr.reduce((a, b) => a + b, 0);
89

910
const Virtualise = React.memo(({ children, itemHeights, overscan = 0, className }: {
10-
children: React.ReactNode[];
11+
children: React.ReactElement[];
1112
itemHeights: number[];
1213
overscan?: number;
1314
className?: string;
1415
}) => {
15-
const [containerMeasure, { height: containerHeight }] = useMeasure<HTMLDivElement>();
16+
const [_,setActiveCategory] = useActiveCategoryState();
17+
const [containerMeasure, { height: containerHeight, width }] = useMeasure<HTMLDivElement>();
1618
const [scrollOffset, setScrollOffset] = useState(0);
1719
const totalHeight = useMemo(() => sum(itemHeights), [itemHeights]);
1820

@@ -64,6 +66,15 @@ const Virtualise = React.memo(({ children, itemHeights, overscan = 0, className
6466
setScrollOffset(e.currentTarget.scrollTop);
6567
}, []);
6668

69+
useEffect(() => {
70+
if(!visibleItems.length) return;
71+
const lastItem = visibleItems[visibleItems.length - 1];
72+
const key = lastItem.element.key;
73+
if(typeof key === 'string') {
74+
setActiveCategory(key);
75+
}
76+
}, [setActiveCategory, visibleItems]);
77+
6778
return (
6879
<div
6980
ref={containerMeasure}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
let cachedScrollWidth: number | null = null;
2+
3+
export function getScrollbarWidth() {
4+
if (cachedScrollWidth !== null) {
5+
return cachedScrollWidth;
6+
}
7+
8+
// Create a temporary div to measure
9+
const outer = document.createElement('div');
10+
outer.style.visibility = 'hidden';
11+
outer.style.overflow = 'scroll';
12+
document.body.appendChild(outer);
13+
14+
// Create inner div
15+
const inner = document.createElement('div');
16+
outer.appendChild(inner);
17+
18+
// Calculate the width difference
19+
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
20+
21+
// Clean up
22+
outer.parentNode?.removeChild(outer);
23+
24+
cachedScrollWidth = scrollbarWidth;
25+
return scrollbarWidth;
26+
}

src/components/body/Body.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const styles = stylesheet.create({
3636
body: {
3737
'.': ClassNames.scrollBody,
3838
flex: '1',
39-
overflowY: 'scroll',
39+
overflowY: 'hidden',
4040
overflowX: 'hidden',
4141
position: 'relative'
4242
}

src/components/body/EmojiList.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import { DataEmoji } from '../../dataUtils/DataTypes';
1919
import { emojisByCategory, emojiUnified } from '../../dataUtils/emojiSelectors';
2020
import { useIsEmojiDisallowed } from '../../hooks/useDisallowedEmojis';
2121
import { useIsEmojiHidden } from '../../hooks/useIsEmojiHidden';
22+
import { useEmojiPreload } from '../../hooks/usePreloadEmoji';
2223
import Virtualise from '../Layout/Virtualise';
2324
import { getCategoriesHeight } from '../Layout/helpers';
25+
import { getScrollbarWidth } from '../Layout/helpers/get-scrollbar-width';
2426
import { useBodyRef } from '../context/ElementRefContext';
2527
import {
2628
useActiveSkinToneState,
@@ -43,6 +45,8 @@ export function EmojiList() {
4345
const categoriesHeightRef = useCategoriesHeightRef();
4446
const [searchTerm] = useSearchTermState();
4547

48+
const scrollbarWidth = useMemo(() => getScrollbarWidth(), []);
49+
4650

4751
const categoriesMap = useMemo(() => {
4852
return categories.reduce((acc, category) => {
@@ -59,15 +63,17 @@ export function EmojiList() {
5963

6064
if (totalEmojis.length > 0) {
6165
acc.push({
62-
height: getCategoriesHeight(totalEmojis.length, bodyWidth),
66+
height: getCategoriesHeight(totalEmojis.length, bodyWidth - scrollbarWidth),
6367
emojis: totalEmojis,
6468
category
6569
});
6670
}
6771

6872
return acc;
6973
}, [] as {height: number; emojis: DataEmoji[]; category: CategoryConfig }[]);
70-
}, [bodyWidth, categories, isEmojiDisallowed, isEmojiHidden]);
74+
}, [bodyWidth, categories, isEmojiDisallowed, isEmojiHidden, scrollbarWidth]);
75+
76+
useEmojiPreload(categoriesMap);
7177

7278
useEffect(() => {
7379
if(!bodyWidth || searchTerm) return;

src/components/context/PickerContext.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,14 @@ export function useUpdateSuggested(): [number, () => void] {
189189
];
190190
}
191191

192+
export function useActiveCategoryState(): [
193+
string | null,
194+
(category: string) => void
195+
] {
196+
const { activeCategoryState } = React.useContext(PickerContext);
197+
return activeCategoryState;
198+
}
199+
192200
export type FilterState = Record<string, FilterDict>;
193201

194202
type ActiveCategoryState = null | string;

src/components/navigation/CategoryNavigation.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { cx } from 'flairup';
22
import * as React from 'react';
3-
import { useState } from 'react';
43

54
import { stylesheet } from '../../Stylesheet/stylesheet';
65
import { categoryFromCategoryConfig } from '../../config/categoryConfig';
@@ -11,11 +10,12 @@ import { useScrollCategoryIntoView } from '../../hooks/useScrollCategoryIntoView
1110
import { useShouldHideCustomEmojis } from '../../hooks/useShouldHideCustomEmojis';
1211
import { isCustomCategory } from '../../typeRefinements/typeRefinements';
1312
import { useCategoryNavigationRef } from '../context/ElementRefContext';
13+
import { useActiveCategoryState } from '../context/PickerContext';
1414

1515
import { CategoryButton } from './CategoryButton';
1616

1717
export function CategoryNavigation() {
18-
const [activeCategory, setActiveCategory] = useState<string | null>(null);
18+
const [activeCategory, setActiveCategory] = useActiveCategoryState();
1919
const scrollCategoryIntoView = useScrollCategoryIntoView();
2020
useActiveCategoryScrollDetection(setActiveCategory);
2121
const isSearchMode = useIsSearchMode();

src/hooks/useEmojiSearch.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { emojiClickOutput } from "./useMouseDownHandlers";
1414

1515
const categoryArray = Object.values(baseCategoriesConfig());
1616

17-
export const useEmojiSearch = ({ activeSkinTone = SkinTones.NEUTRAL, activeEmojiStyle= EmojiStyle.NATIVE, limit = 10, getEmojiUrl = emojiUrlByUnified }: {activeSkinTone?: SkinTones, activeEmojiStyle?: EmojiStyle,limit?:number, getEmojiUrl?: GetEmojiUrl}) => {
17+
export const useEmojiSearch = ({ activeSkinTone = SkinTones.NEUTRAL, activeEmojiStyle= EmojiStyle.NATIVE, getEmojiUrl = emojiUrlByUnified }: {activeSkinTone?: SkinTones, activeEmojiStyle?: EmojiStyle,limit?:number, getEmojiUrl?: GetEmojiUrl}) => {
1818
const filterRef = useRef<FilterState>(alphaNumericEmojiIndex);
1919

2020
return useCallback((searchTerm: string) => {
@@ -30,21 +30,12 @@ export const useEmojiSearch = ({ activeSkinTone = SkinTones.NEUTRAL, activeEmoji
3030
const categoryEmojis = emojisByCategory(category.category).reduce((emojiAcc, emoji) => {
3131
const filteredOut = isEmojiFilteredBySearchTerm(emoji[EmojiProperties.unified], filterRef.current, searchTerm);
3232
if (!filteredOut) {
33-
if(emojiAcc.length >= limit) {
34-
return emojiAcc;
35-
}
3633
emojiAcc.push(emojiClickOutput(emoji,activeSkinTone,activeEmojiStyle,getEmojiUrl));
3734
}
38-
3935
return emojiAcc;
4036
}, [] as EmojiClickData[]);
4137

42-
if(emojisArray.length >= limit) {
43-
return emojisArray;
44-
}
45-
46-
4738
return [...emojisArray, ...categoryEmojis];
4839
}, [] as EmojiClickData[]);
49-
},[activeEmojiStyle, activeSkinTone, getEmojiUrl, limit]);
40+
},[activeEmojiStyle, activeSkinTone, getEmojiUrl]);
5041
}

src/hooks/usePreloadEmoji.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { useEffect } from 'react';
2+
3+
import { CategoryConfig } from '../config/categoryConfig';
4+
import { useEmojiStyleConfig, useGetEmojiUrlConfig, useLazyLoadEmojisConfig } from '../config/useConfig';
5+
import { DataEmoji } from '../dataUtils/DataTypes';
6+
7+
import { preloadEmoji } from './preloadEmoji';
8+
9+
export function useEmojiPreload(categoriesMap: { emojis: DataEmoji[]; category: CategoryConfig }[], preloadRange: number = 20) {
10+
const emojiStyle = useEmojiStyleConfig();
11+
const getEmojiUrl = useGetEmojiUrlConfig();
12+
const lazyLoadEmojis = useLazyLoadEmojisConfig();
13+
14+
useEffect(() => {
15+
if(!lazyLoadEmojis) return;
16+
17+
// Preload next batch of images
18+
categoriesMap.forEach(category => {
19+
category.emojis.forEach(emoji => {
20+
preloadEmoji(getEmojiUrl, emoji, emojiStyle);
21+
});
22+
});
23+
}, [emojiStyle, getEmojiUrl, preloadRange, lazyLoadEmojis, categoriesMap]);
24+
}

0 commit comments

Comments
 (0)