emoji-picker-react 4.22.0 → 4.22.1
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/dist/emoji-picker-react.cjs.development.js +50 -16
- package/dist/emoji-picker-react.cjs.development.js.map +1 -1
- package/dist/emoji-picker-react.cjs.production.min.js +1 -1
- package/dist/emoji-picker-react.cjs.production.min.js.map +1 -1
- package/dist/emoji-picker-react.esm.js +50 -16
- package/dist/emoji-picker-react.esm.js.map +1 -1
- package/dist/hooks/useActiveCategoryScrollDetection.d.ts +8 -1
- package/llms.txt +7 -0
- package/package.json +1 -1
- package/src/components/context/PickerConfigContext.tsx +5 -1
- package/src/components/context/PickerDataContext.tsx +12 -7
- package/src/components/navigation/CategoryNavigation.tsx +12 -1
- package/src/config/compareConfig.ts +31 -0
- package/src/hooks/useActiveCategoryScrollDetection.ts +9 -1
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
export declare function useActiveCategoryScrollDetection({ setActiveCategory, setVisibleCategories, }: {
|
|
1
|
+
export declare function useActiveCategoryScrollDetection({ setActiveCategory, setVisibleCategories, categoryIdentitiesKey, }: {
|
|
2
2
|
setActiveCategory: (category: string) => void;
|
|
3
3
|
setVisibleCategories: (categories: string[]) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Stable string derived from the rendered category identities
|
|
6
|
+
* (e.g. ids joined). Re-subscribes the observer when sections are
|
|
7
|
+
* added or removed so new elements are observed and detached ones
|
|
8
|
+
* stop contributing; unrelated renders keep the same key.
|
|
9
|
+
*/
|
|
10
|
+
categoryIdentitiesKey: string;
|
|
4
11
|
}): void;
|
package/llms.txt
CHANGED
|
@@ -236,6 +236,13 @@ automatically (after the standard categories), so grouped emojis always
|
|
|
236
236
|
render somewhere. Omit `CUSTOM` from `categories` entirely to hide all
|
|
237
237
|
customs — explicit `categories` stay an allowlist:
|
|
238
238
|
|
|
239
|
+
### Updating groups at runtime
|
|
240
|
+
|
|
241
|
+
`categories` and `customEmojis` are immutable inputs: replacing either
|
|
242
|
+
array (even with a same-length array) rebuilds sections, tabs, search,
|
|
243
|
+
and lookup. Mutating an array in place is not detected — always provide
|
|
244
|
+
a new reference. Group strings are exact, case-sensitive identifiers.
|
|
245
|
+
|
|
239
246
|
```tsx
|
|
240
247
|
import EmojiPicker, { Categories } from 'emoji-picker-react';
|
|
241
248
|
|
package/package.json
CHANGED
|
@@ -39,7 +39,11 @@ export function useSetConfig(config: PickerConfig) {
|
|
|
39
39
|
// not gonna...
|
|
40
40
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
41
41
|
}, [
|
|
42
|
-
|
|
42
|
+
// New references rebuild merged categories and data even at equal
|
|
43
|
+
// length; compareConfig above still gates no-op updates so inline
|
|
44
|
+
// literals don't loop.
|
|
45
|
+
config.categories,
|
|
46
|
+
config.customEmojis,
|
|
43
47
|
config.open,
|
|
44
48
|
config.emojiVersion,
|
|
45
49
|
config.reactionsDefaultOpen,
|
|
@@ -34,9 +34,9 @@ export interface PickerDataContextValue {
|
|
|
34
34
|
const PickerDataContext = React.createContext<PickerDataContextValue>({
|
|
35
35
|
emojiData: {} as EmojiData,
|
|
36
36
|
allEmojis: [],
|
|
37
|
-
allEmojisByUnified:
|
|
38
|
-
searchIndex:
|
|
39
|
-
customGroups:
|
|
37
|
+
allEmojisByUnified: Object.create(null),
|
|
38
|
+
searchIndex: Object.create(null),
|
|
39
|
+
customGroups: Object.create(null),
|
|
40
40
|
emojiByUnified,
|
|
41
41
|
activeVariationFromUnified: () => null,
|
|
42
42
|
});
|
|
@@ -54,7 +54,10 @@ export function PickerDataProvider({
|
|
|
54
54
|
// Clone to avoid mutation of shared source
|
|
55
55
|
const newData: EmojiData = JSON.parse(JSON.stringify(emojiData));
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
// Group names and emoji ids are user-controlled strings: use
|
|
58
|
+
// null-prototype dictionaries so names like `__proto__` behave as
|
|
59
|
+
// ordinary data instead of resolving Object.prototype members.
|
|
60
|
+
const customGroups: Record<string, DataEmojis> = Object.create(null);
|
|
58
61
|
|
|
59
62
|
if (customEmojis && customEmojis.length > 0) {
|
|
60
63
|
for (const emoji of customEmojis) {
|
|
@@ -75,8 +78,10 @@ export function PickerDataProvider({
|
|
|
75
78
|
const allEmojis: DataEmojis = Object.values(emojis)
|
|
76
79
|
.concat(Object.values(customGroups))
|
|
77
80
|
.flat();
|
|
78
|
-
const allEmojisByUnified: Record<string, DataEmoji> =
|
|
79
|
-
|
|
81
|
+
const allEmojisByUnified: Record<string, DataEmoji> =
|
|
82
|
+
Object.create(null);
|
|
83
|
+
const searchIndex: Record<string, Record<string, DataEmoji>> =
|
|
84
|
+
Object.create(null);
|
|
80
85
|
|
|
81
86
|
allEmojis.forEach((emoji) => {
|
|
82
87
|
const unified = emoji[Keys.unified];
|
|
@@ -96,7 +101,7 @@ export function PickerDataProvider({
|
|
|
96
101
|
.split('');
|
|
97
102
|
|
|
98
103
|
joinedNameString.forEach((char: string) => {
|
|
99
|
-
searchIndex[char] = searchIndex[char] ??
|
|
104
|
+
searchIndex[char] = searchIndex[char] ?? Object.create(null);
|
|
100
105
|
searchIndex[char][unified] = emoji;
|
|
101
106
|
});
|
|
102
107
|
});
|
|
@@ -28,7 +28,6 @@ export function CategoryNavigation() {
|
|
|
28
28
|
const [activeCategory, setActiveCategory] = useState<string | null>(null);
|
|
29
29
|
const [, setVisibleCategories] = useVisibleCategoriesState();
|
|
30
30
|
const scrollCategoryIntoView = useScrollCategoryIntoView();
|
|
31
|
-
useActiveCategoryScrollDetection({ setActiveCategory, setVisibleCategories });
|
|
32
31
|
const isSearchMode = useIsSearchMode();
|
|
33
32
|
|
|
34
33
|
const categoriesConfig = useCategoriesConfig();
|
|
@@ -37,6 +36,18 @@ export function CategoryNavigation() {
|
|
|
37
36
|
const hideCustomCategory = useShouldHideCustomEmojis();
|
|
38
37
|
const { customGroups, emojiData } = usePickerDataContext();
|
|
39
38
|
|
|
39
|
+
// Rendered section identities. Passed to the scroll-detection hook so
|
|
40
|
+
// added/removed sections are (re-)subscribed; stable across renders
|
|
41
|
+
// unless the effective category list changes.
|
|
42
|
+
const categoryIdentitiesKey = categoriesConfig
|
|
43
|
+
.map(entry => categoryIdFromCategoryConfig(entry))
|
|
44
|
+
.join('|');
|
|
45
|
+
useActiveCategoryScrollDetection({
|
|
46
|
+
setActiveCategory,
|
|
47
|
+
setVisibleCategories,
|
|
48
|
+
categoryIdentitiesKey,
|
|
49
|
+
});
|
|
50
|
+
|
|
40
51
|
const visibleCategories = categoriesConfig.filter(categoryConfig => {
|
|
41
52
|
if (isCustomCategory(categoryConfig) && hideCustomCategory) {
|
|
42
53
|
return false;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { UserCategoryConfig } from './categoryConfig';
|
|
1
2
|
import { PickerConfig } from './config';
|
|
3
|
+
import { CustomEmoji } from './customEmojiConfig';
|
|
2
4
|
|
|
3
5
|
// eslint-disable-next-line complexity
|
|
4
6
|
export function compareConfig(prev: PickerConfig, next: PickerConfig) {
|
|
@@ -25,6 +27,35 @@ export function compareConfig(prev: PickerConfig, next: PickerConfig) {
|
|
|
25
27
|
prev.searchDisabled === next.searchDisabled &&
|
|
26
28
|
prev.skinTonePickerLocation === next.skinTonePickerLocation &&
|
|
27
29
|
prevCustomEmojis.length === nextCustomEmojis.length &&
|
|
30
|
+
customEmojisKey(prevCustomEmojis) === customEmojisKey(nextCustomEmojis) &&
|
|
31
|
+
categoriesKey(prev.categories) === categoriesKey(next.categories) &&
|
|
28
32
|
prev.emojiData === next.emojiData
|
|
29
33
|
);
|
|
30
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Content identity for custom emojis. Length alone misses regroups and
|
|
38
|
+
* swaps, which would leave merged categories, search, and sections stale.
|
|
39
|
+
*/
|
|
40
|
+
function customEmojisKey(customEmojis: CustomEmoji[]): string {
|
|
41
|
+
return customEmojis
|
|
42
|
+
.map(
|
|
43
|
+
emoji =>
|
|
44
|
+
`${emoji.id}:${emoji.group ?? ''}:${emoji.names.join(',')}:${emoji.imgUrl}`,
|
|
45
|
+
)
|
|
46
|
+
.join('|');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Content identity for the categories input. Order, labels, icons, and
|
|
51
|
+
* group selection all flow into the merged configuration.
|
|
52
|
+
*/
|
|
53
|
+
function categoriesKey(categories: UserCategoryConfig | undefined): string {
|
|
54
|
+
return (categories ?? [])
|
|
55
|
+
.map(entry =>
|
|
56
|
+
typeof entry === 'string'
|
|
57
|
+
? entry
|
|
58
|
+
: `${entry.category}:${entry.name}:${entry.group ?? ''}:${entry.icon ? '1' : ''}`,
|
|
59
|
+
)
|
|
60
|
+
.join('|');
|
|
61
|
+
}
|
|
@@ -7,9 +7,17 @@ import { useBodyRef } from '../components/context/ElementRefContext';
|
|
|
7
7
|
export function useActiveCategoryScrollDetection({
|
|
8
8
|
setActiveCategory,
|
|
9
9
|
setVisibleCategories,
|
|
10
|
+
categoryIdentitiesKey,
|
|
10
11
|
}: {
|
|
11
12
|
setActiveCategory: (category: string) => void;
|
|
12
13
|
setVisibleCategories: (categories: string[]) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Stable string derived from the rendered category identities
|
|
16
|
+
* (e.g. ids joined). Re-subscribes the observer when sections are
|
|
17
|
+
* added or removed so new elements are observed and detached ones
|
|
18
|
+
* stop contributing; unrelated renders keep the same key.
|
|
19
|
+
*/
|
|
20
|
+
categoryIdentitiesKey: string;
|
|
13
21
|
}) {
|
|
14
22
|
const BodyRef = useBodyRef();
|
|
15
23
|
|
|
@@ -67,5 +75,5 @@ export function useActiveCategoryScrollDetection({
|
|
|
67
75
|
return () => {
|
|
68
76
|
observer.disconnect();
|
|
69
77
|
};
|
|
70
|
-
}, [BodyRef, setActiveCategory, setVisibleCategories]);
|
|
78
|
+
}, [BodyRef, categoryIdentitiesKey, setActiveCategory, setVisibleCategories]);
|
|
71
79
|
}
|