emoji-picker-react 4.22.1 → 4.22.3

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.
@@ -1,11 +1,11 @@
1
- export declare function useActiveCategoryScrollDetection({ setActiveCategory, setVisibleCategories, categoryIdentitiesKey, }: {
1
+ import { CategoriesConfig } from '../config/categoryConfig';
2
+ export declare function useActiveCategoryScrollDetection({ setActiveCategory, setVisibleCategories, categories, }: {
2
3
  setActiveCategory: (category: string) => void;
3
4
  setVisibleCategories: (categories: string[]) => void;
4
5
  /**
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.
6
+ * Effective category list. The merged array reference only changes when
7
+ * the configuration is re-merged, so the observer re-subscribes exactly
8
+ * when sections are added or removed — no serialization involved.
9
9
  */
10
- categoryIdentitiesKey: string;
10
+ categories: CategoriesConfig;
11
11
  }): void;
package/llms.txt CHANGED
@@ -11,7 +11,7 @@ The most popular fully customizable emoji picker for React.
11
11
 
12
12
  [![npm downloads](https://img.shields.io/npm/dm/emoji-picker-react.svg)](https://www.npmjs.com/package/emoji-picker-react)
13
13
 
14
- **[Live Demo](https://ealush.com/emoji-picker-react)** | **[Report a Bug](https://github.com/ealush/emoji-picker-react/issues)**
14
+ **[Live Demo](https://ealush.com/emoji-picker-react)** | **[Report a Bug](https://github.com/ealush/emoji-picker-react/issues)** | **[Sponsor](https://github.com/sponsors/ealush)**
15
15
 
16
16
  ![image](https://github.com/ealush/emoji-picker-react/assets/11255103/48901306-e7fd-49cd-8f1e-9b214083a61d)
17
17
 
@@ -117,6 +117,10 @@ On older versions only, the workaround was adding this to your HTML:
117
117
  </script>
118
118
  ```
119
119
 
120
+ ## Support Emoji Picker React
121
+
122
+ Emoji Picker React is independently maintained. If your team relies on it, you can help fund ongoing maintenance and releases through [GitHub Sponsors](https://github.com/sponsors/ealush). Organizations can also support the project through [Tidelift](https://tidelift.com/subscription/pkg/npm-emoji-picker-react).
123
+
120
124
  ## More from the maintainer
121
125
 
122
126
  Building complex forms? Check out [**Vest**](https://vestjs.dev) — a validation framework for stateful, async, and dependent validation.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.22.1",
2
+ "version": "4.22.3",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -26,6 +26,30 @@
26
26
  "type": "git",
27
27
  "url": "https://github.com/ealush/emoji-picker-react.git"
28
28
  },
29
+ "funding": [
30
+ {
31
+ "type": "github",
32
+ "url": "https://github.com/sponsors/ealush"
33
+ },
34
+ {
35
+ "type": "tidelift",
36
+ "url": "https://tidelift.com/subscription/pkg/npm-emoji-picker-react"
37
+ }
38
+ ],
39
+ "overrides": {
40
+ "decode-uri-component": "0.2.2",
41
+ "deep-object-diff": "1.1.9",
42
+ "ua-parser-js": "0.7.41",
43
+ "minimist": "1.2.8",
44
+ "loader-utils": "2.0.4",
45
+ "form-data": "4.0.6",
46
+ "request": {
47
+ "form-data": "2.5.6"
48
+ },
49
+ "tsconfig-paths": {
50
+ "json5": "1.0.2"
51
+ }
52
+ },
29
53
  "scripts": {
30
54
  "start": "tsdx watch",
31
55
  "build": "npm run build:data:src && npm run type-check && tsdx build --transpileOnly && npm run build:data:dist && npm run docs:llms",
@@ -122,7 +146,7 @@
122
146
  "tsdx": "^0.14.1",
123
147
  "tslib": "^2.4.0",
124
148
  "typescript": "^4.7.4",
125
- "vitest": "^2.0.5"
149
+ "vitest": "^4.1.11"
126
150
  },
127
151
  "dependencies": {
128
152
  "shipstyles": "1.0.0"
@@ -36,16 +36,12 @@ export function CategoryNavigation() {
36
36
  const hideCustomCategory = useShouldHideCustomEmojis();
37
37
  const { customGroups, emojiData } = usePickerDataContext();
38
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('|');
39
+ // The observer re-subscribes when the merged categories reference
40
+ // changes (sections added/removed); the reference is stable otherwise.
45
41
  useActiveCategoryScrollDetection({
46
42
  setActiveCategory,
47
43
  setVisibleCategories,
48
- categoryIdentitiesKey,
44
+ categories: categoriesConfig,
49
45
  });
50
46
 
51
47
  const visibleCategories = categoriesConfig.filter(categoryConfig => {
@@ -1,3 +1,5 @@
1
+ import { Categories, CategoryConfig } from '../types/exposedTypes';
2
+
1
3
  import { UserCategoryConfig } from './categoryConfig';
2
4
  import { PickerConfig } from './config';
3
5
  import { CustomEmoji } from './customEmojiConfig';
@@ -27,35 +29,74 @@ export function compareConfig(prev: PickerConfig, next: PickerConfig) {
27
29
  prev.searchDisabled === next.searchDisabled &&
28
30
  prev.skinTonePickerLocation === next.skinTonePickerLocation &&
29
31
  prevCustomEmojis.length === nextCustomEmojis.length &&
30
- customEmojisKey(prevCustomEmojis) === customEmojisKey(nextCustomEmojis) &&
31
- categoriesKey(prev.categories) === categoriesKey(next.categories) &&
32
+ customEmojisEqual(prevCustomEmojis, nextCustomEmojis) &&
33
+ categoriesEqual(prev.categories, next.categories) &&
32
34
  prev.emojiData === next.emojiData
33
35
  );
34
36
  }
35
37
 
36
38
  /**
37
- * Content identity for custom emojis. Length alone misses regroups and
38
- * swaps, which would leave merged categories, search, and sections stale.
39
+ * Structural content equality for custom emojis. Length alone misses
40
+ * regroups and swaps, which would leave merged categories, search, and
41
+ * sections stale. Fields are compared element-wise (never serialized
42
+ * with delimiters) so user-controlled values cannot collide.
43
+ */
44
+ function customEmojisEqual(
45
+ prev: CustomEmoji[],
46
+ next: CustomEmoji[],
47
+ ): boolean {
48
+ if (prev.length !== next.length) {
49
+ return false;
50
+ }
51
+ return prev.every((emoji, index) => {
52
+ const other = next[index];
53
+ return (
54
+ emoji.id === other.id &&
55
+ (emoji.group ?? '') === (other.group ?? '') &&
56
+ emoji.imgUrl === other.imgUrl &&
57
+ emoji.names.length === other.names.length &&
58
+ emoji.names.every((name, nameIndex) => name === other.names[nameIndex])
59
+ );
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Structural content equality for the categories input. Order, labels,
65
+ * group selection, and icon identity all flow into the merged
66
+ * configuration. Icons compare by reference: swapping one icon element
67
+ * for another must rebuild, while a stable reference stays cheap.
39
68
  */
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('|');
69
+ function categoriesEqual(
70
+ prev: UserCategoryConfig | undefined,
71
+ next: UserCategoryConfig | undefined,
72
+ ): boolean {
73
+ const prevEntries = prev ?? [];
74
+ const nextEntries = next ?? [];
75
+ if (prevEntries.length !== nextEntries.length) {
76
+ return false;
77
+ }
78
+ return prevEntries.every((entry, index) =>
79
+ categoryEntriesEqual(entry, nextEntries[index]),
80
+ );
47
81
  }
48
82
 
49
83
  /**
50
- * Content identity for the categories input. Order, labels, icons, and
51
- * group selection all flow into the merged configuration.
84
+ * One categories entry compared structurally. Icons compare by reference:
85
+ * swapping one icon element for another rebuilds, a stable reference stays
86
+ * cheap. Delimited serialization is avoided so user-controlled values
87
+ * cannot collide.
52
88
  */
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('|');
89
+ function categoryEntriesEqual(
90
+ entry: Categories | CategoryConfig,
91
+ other: Categories | CategoryConfig,
92
+ ): boolean {
93
+ if (typeof entry === 'string' || typeof other === 'string') {
94
+ return entry === other;
95
+ }
96
+ return (
97
+ entry.category === other.category &&
98
+ entry.name === other.name &&
99
+ (entry.group ?? '') === (other.group ?? '') &&
100
+ entry.icon === other.icon
101
+ );
61
102
  }
@@ -3,21 +3,21 @@ import { useEffect } from 'react';
3
3
  import { categoryNameFromDom } from '../DomUtils/categoryNameFromDom';
4
4
  import { asSelectors, ClassNames } from '../DomUtils/classNames';
5
5
  import { useBodyRef } from '../components/context/ElementRefContext';
6
+ import { CategoriesConfig } from '../config/categoryConfig';
6
7
 
7
8
  export function useActiveCategoryScrollDetection({
8
9
  setActiveCategory,
9
10
  setVisibleCategories,
10
- categoryIdentitiesKey,
11
+ categories,
11
12
  }: {
12
13
  setActiveCategory: (category: string) => void;
13
14
  setVisibleCategories: (categories: string[]) => void;
14
15
  /**
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.
16
+ * Effective category list. The merged array reference only changes when
17
+ * the configuration is re-merged, so the observer re-subscribes exactly
18
+ * when sections are added or removed — no serialization involved.
19
19
  */
20
- categoryIdentitiesKey: string;
20
+ categories: CategoriesConfig;
21
21
  }) {
22
22
  const BodyRef = useBodyRef();
23
23
 
@@ -75,5 +75,5 @@ export function useActiveCategoryScrollDetection({
75
75
  return () => {
76
76
  observer.disconnect();
77
77
  };
78
- }, [BodyRef, categoryIdentitiesKey, setActiveCategory, setVisibleCategories]);
78
+ }, [BodyRef, categories, setActiveCategory, setVisibleCategories]);
79
79
  }