emoji-picker-react
Version:
Emoji Picker component for React Applications on the web
204 lines (164 loc) • 4.87 kB
text/typescript
import { scrollTo } from '../DomUtils/scrollTo';
import {
usePickerMainRef,
useSearchInputRef
} from '../components/context/ElementRefContext';
import {
FilterState,
useFilterRef,
useSearchTermState
} from '../components/context/PickerContext';
import { useSearchResultsConfig } from '../config/useConfig';
import { DataEmoji } from '../dataUtils/DataTypes';
import { emojiNames } from '../dataUtils/emojiSelectors';
import { useFocusSearchInput } from './useFocus';
function useSetFilterRef() {
const filterRef = useFilterRef();
return function setFilter(
setter: FilterState | ((current: FilterState) => FilterState)
): void {
if (typeof setter === 'function') {
return setFilter(setter(filterRef.current));
}
filterRef.current = setter;
};
}
export function useClearSearch() {
const applySearch = useApplySearch();
const SearchInputRef = useSearchInputRef();
const focusSearchInput = useFocusSearchInput();
return function clearSearch() {
if (SearchInputRef.current) {
SearchInputRef.current.value = '';
}
applySearch('');
focusSearchInput();
};
}
export function useAppendSearch() {
const SearchInputRef = useSearchInputRef();
const applySearch = useApplySearch();
return function appendSearch(str: string) {
if (SearchInputRef.current) {
SearchInputRef.current.value = `${SearchInputRef.current.value}${str}`;
applySearch(getNormalizedSearchTerm(SearchInputRef.current.value));
} else {
applySearch(getNormalizedSearchTerm(str));
}
};
}
export function useFilter() {
const SearchInputRef = useSearchInputRef();
const filterRef = useFilterRef();
const setFilterRef = useSetFilterRef();
const applySearch = useApplySearch();
const [searchTerm] = useSearchTermState();
const statusSearchResults = getStatusSearchResults(
filterRef.current,
searchTerm
);
return {
onChange,
searchTerm,
SearchInputRef,
statusSearchResults
};
function onChange(inputValue: string) {
const filter = filterRef.current;
const nextValue = inputValue.toLowerCase();
if (filter?.[nextValue] || nextValue.length <= 1) {
return applySearch(nextValue);
}
const longestMatch = findLongestMatch(nextValue, filter);
if (!longestMatch) {
// Can we even get here?
// If so, we need to search among all emojis
return applySearch(nextValue);
}
setFilterRef(current =>
Object.assign(current, {
[nextValue]: filterEmojiObjectByKeyword(longestMatch, nextValue)
})
);
applySearch(nextValue);
}
}
function useApplySearch() {
const [, setSearchTerm] = useSearchTermState();
const PickerMainRef = usePickerMainRef();
return function applySearch(searchTerm: string) {
requestAnimationFrame(() => {
setSearchTerm(searchTerm ? searchTerm?.toLowerCase() : searchTerm).then(
() => {
scrollTo(PickerMainRef.current, 0);
}
);
});
};
}
function filterEmojiObjectByKeyword(
emojis: FilterDict,
keyword: string
): FilterDict {
const filtered: FilterDict = {};
for (const unified in emojis) {
const emoji = emojis[unified];
if (hasMatch(emoji, keyword)) {
filtered[unified] = emoji;
}
}
return filtered;
}
function hasMatch(emoji: DataEmoji, keyword: string): boolean {
return emojiNames(emoji).some(name => name.includes(keyword));
}
export function useIsEmojiFiltered(): (unified: string) => boolean {
const { current: filter } = useFilterRef();
const [searchTerm] = useSearchTermState();
return unified => isEmojiFilteredBySearchTerm(unified, filter, searchTerm);
}
function isEmojiFilteredBySearchTerm(
unified: string,
filter: FilterState,
searchTerm: string
): boolean {
if (!filter || !searchTerm) {
return false;
}
return !filter[searchTerm]?.[unified];
}
export type FilterDict = Record<string, DataEmoji>;
function findLongestMatch(
keyword: string,
dict: Record<string, FilterDict> | null
): FilterDict | null {
if (!dict) {
return null;
}
if (dict[keyword]) {
return dict[keyword];
}
const longestMatchingKey = Object.keys(dict)
.sort((a, b) => b.length - a.length)
.find(key => keyword.includes(key));
if (longestMatchingKey) {
return dict[longestMatchingKey];
}
return null;
}
export function getNormalizedSearchTerm(str: string): string {
if (!str || typeof str !== 'string') {
return '';
}
return str.trim().toLowerCase();
}
function getStatusSearchResults(
filterState: FilterState,
searchTerm: string
): string {
if (!filterState?.[searchTerm]) return '';
const searchResultsCount =
Object.entries(filterState?.[searchTerm])?.length || 0;
// eslint-disable-next-line react-hooks/rules-of-hooks
return useSearchResultsConfig(searchResultsCount);
}