emoji-picker-react 5.0.1 → 5.0.2

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/llms-full.txt CHANGED
@@ -1633,6 +1633,7 @@ Existing `reactionsDefaultOpen`, `allowExpandReactions`, `reactions`, `onReactio
1633
1633
 
1634
1634
  For standard emoji IDs:
1635
1635
  - matching is case-insensitive;
1636
+ - matching ignores where U+FE0F appears: `'2764'` and `'2764-fe0f'` both resolve to the dataset's own spelling, the same rule `reactions` and `hiddenEmojis` use;
1636
1637
  - a valid variation remains that exact variation for rendering rather than collapsing to the neutral base emoji;
1637
1638
  - duplicates are removed by canonical normalized ID, first occurrence wins.
1638
1639
 
@@ -2370,6 +2371,7 @@ The following variables are already documented public customization surface and
2370
2371
  ### Skin tone
2371
2372
  - `--epr-skin-tone-picker-menu-color`
2372
2373
  - `--epr-skin-tone-size`
2374
+ - `--epr-skin-tone-neutral-color`, `--epr-skin-tone-light-color`, `--epr-skin-tone-medium-light-color`, `--epr-skin-tone-medium-color`, `--epr-skin-tone-medium-dark-color`, `--epr-skin-tone-dark-color` — the tone swatch colors (defaults `#ffd225`, `#ffdfbd`, `#e9c197`, `#c88e62`, `#a86637`, `#60463a`).
2373
2375
 
2374
2376
  ### Dark mode
2375
2377
  - `--epr-dark-bg-color`
@@ -3028,6 +3030,7 @@ No other data helpers are part of v5.
3028
3030
  - trims surrounding whitespace;
3029
3031
  - normalizes hexadecimal unified code to lowercase;
3030
3032
  - accepts base or variation unified code;
3033
+ - accepts either U+FE0F spelling (`2764` or `2764-fe0f`); an exact match wins;
3031
3034
  - variation lookup returns the canonical/base EmojiInfo whose `variations` includes the requested variation;
3032
3035
  - returns `undefined` for unknown/empty input;
3033
3036
  - does not mutate the dataset;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.0.1",
2
+ "version": "5.0.2",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -183,7 +183,7 @@
183
183
  },
184
184
  {
185
185
  "path": "dist/esm/index.mjs",
186
- "limit": "75 KB"
186
+ "limit": "76 KB"
187
187
  },
188
188
  {
189
189
  "path": "dist/esm/primitives/index.mjs",
@@ -5,7 +5,7 @@ import {
5
5
  useSuggestedEmojisModeConfig,
6
6
  } from '../../config/useConfig';
7
7
  import { getPickerDataSnapshot } from '../../data-core/pickerData';
8
- import { normalizeQuery } from '../../data-core/prepare';
8
+ import { normalizeQuery, withoutFe0f } from '../../data-core/prepare';
9
9
  import { searchEmojis } from '../../data-core/search';
10
10
  import {
11
11
  DataEmoji,
@@ -79,6 +79,7 @@ export function PickerDataProvider({
79
79
 
80
80
  const result =
81
81
  data.allEmojisByUnified[unified] ??
82
+ data.allEmojisByUnified[withoutFe0f(unified)] ??
82
83
  data.allEmojisByUnified[unifiedWithoutSkinTone(unified)];
83
84
  return result;
84
85
  },
@@ -108,23 +108,24 @@ const styles = /* @__PURE__ */ (() =>
108
108
  ':focus': {
109
109
  boxShadow: '0 0 0 3px var(--epr-focus-bg-color)',
110
110
  },
111
+ // Swatch colors are themeable; the fallbacks are the defaults.
111
112
  '&.epr-tone-neutral': {
112
- backgroundColor: '#ffd225',
113
+ backgroundColor: 'var(--epr-skin-tone-neutral-color, #ffd225)',
113
114
  },
114
115
  '&.epr-tone-1f3fb': {
115
- backgroundColor: '#ffdfbd',
116
+ backgroundColor: 'var(--epr-skin-tone-light-color, #ffdfbd)',
116
117
  },
117
118
  '&.epr-tone-1f3fc': {
118
- backgroundColor: '#e9c197',
119
+ backgroundColor: 'var(--epr-skin-tone-medium-light-color, #e9c197)',
119
120
  },
120
121
  '&.epr-tone-1f3fd': {
121
- backgroundColor: '#c88e62',
122
+ backgroundColor: 'var(--epr-skin-tone-medium-color, #c88e62)',
122
123
  },
123
124
  '&.epr-tone-1f3fe': {
124
- backgroundColor: '#a86637',
125
+ backgroundColor: 'var(--epr-skin-tone-medium-dark-color, #a86637)',
125
126
  },
126
127
  '&.epr-tone-1f3ff': {
127
- backgroundColor: '#60463a',
128
+ backgroundColor: 'var(--epr-skin-tone-dark-color, #60463a)',
128
129
  },
129
130
  },
130
131
  }))();
@@ -2,6 +2,7 @@ import * as React from 'react';
2
2
 
3
3
  import { DEFAULT_REACTIONS } from '../components/Reactions/DEFAULT_REACTIONS';
4
4
  import { GetEmojiUrl } from '../components/emoji/BaseEmojiProps';
5
+ import { withoutFe0f } from '../data-core/prepare';
5
6
  import { emojiUrlByUnified } from '../dataUtils/emojiUtils';
6
7
  import type { EmojiDataInput } from '../hooks/useResolvedEmojiData';
7
8
  import {
@@ -27,7 +28,8 @@ import {
27
28
  } from './categoryConfig';
28
29
  import { CustomEmoji } from './customEmojiConfig';
29
30
 
30
- const KNOWN_FAILING_EMOJIS = ['2640-fe0f', '2642-fe0f', '2695-fe0f'];
31
+ // Stored without U+FE0F, like every hiddenEmojis entry.
32
+ const KNOWN_FAILING_EMOJIS = ['2640', '2642', '2695'];
31
33
 
32
34
  export const DEFAULT_SEARCH_PLACEHOLDER = 'Search';
33
35
  export const DEFAULT_PICKER_WIDTH = 350;
@@ -171,9 +173,10 @@ export function mergeConfig(
171
173
 
172
174
  // Dataset ids are lowercase; accept the uppercase spelling consumers
173
175
  // copy from docs and tools (and stray whitespace), like `reactions` and
174
- // `suggestedEmojis` already do.
176
+ // `suggestedEmojis` already do. Kept without U+FE0F, so either spelling
177
+ // of an ID hides the emoji (see useIsEmojiDisallowed).
175
178
  config.hiddenEmojis.forEach((emoji) => {
176
- const id = String(emoji).trim().toLowerCase();
179
+ const id = withoutFe0f(String(emoji).trim().toLowerCase());
177
180
  if (id) config.unicodeToHide.add(id);
178
181
  });
179
182
 
@@ -21,7 +21,7 @@ import {
21
21
  } from '../dataUtils/DataTypes';
22
22
  import { Categories, type EmojiData } from '../types/exposedTypes';
23
23
 
24
- import { getPreparedCore } from './prepare';
24
+ import { getPreparedCore, withoutFe0f } from './prepare';
25
25
 
26
26
  export interface PickerDataSnapshot {
27
27
  emojiData: EmojiData;
@@ -134,15 +134,13 @@ export function getPickerDataSnapshot(
134
134
 
135
135
  const allEmojisByUnified: Record<string, DataEmoji> = Object.create(null);
136
136
 
137
+ // The base unified and each variation, and either U+FE0F spelling of
138
+ // them; an exact ID always wins over another record's spelling.
137
139
  allEmojis.forEach((emoji) => {
138
- const unified = emoji[Keys.unified];
139
- allEmojisByUnified[unified] = emoji;
140
-
141
- if (emoji[Keys.variations]) {
142
- emoji[Keys.variations]?.forEach((variation) => {
143
- allEmojisByUnified[variation] = emoji;
144
- });
145
- }
140
+ [emoji[Keys.unified], ...(emoji[Keys.variations] || [])].forEach((id) => {
141
+ allEmojisByUnified[id] = emoji;
142
+ allEmojisByUnified[withoutFe0f(id)] ||= emoji;
143
+ });
146
144
  });
147
145
 
148
146
  const snapshot: PickerDataSnapshot = {
@@ -55,6 +55,15 @@ export function normalizeUnified(input: string): string {
55
55
  return input.trim().toLowerCase();
56
56
  }
57
57
 
58
+ /**
59
+ * Datasets disagree on where U+FE0F (emoji presentation) appears in an ID:
60
+ * `2764` and `2764-fe0f` are the same heart. IDs are also indexed under
61
+ * this form, so either spelling finds the dataset's own record.
62
+ */
63
+ export function withoutFe0f(unified: string): string {
64
+ return unified.replace(/-fe0f/g, '');
65
+ }
66
+
58
67
  export function normalizeQuery(input: string): string {
59
68
  if (!input || typeof input !== 'string') {
60
69
  return '';
@@ -102,10 +111,9 @@ function appendEntry(
102
111
  });
103
112
  records.push(info);
104
113
  byUnified.set(unified, info);
105
- for (const variation of info.variations) {
106
- if (!byUnified.has(variation)) {
107
- byUnified.set(variation, info);
108
- }
114
+ for (const id of [unified, ...info.variations]) {
115
+ if (!byUnified.has(id)) byUnified.set(id, info);
116
+ if (!byUnified.has(withoutFe0f(id))) byUnified.set(withoutFe0f(id), info);
109
117
  }
110
118
  indexCharBucket(byCharMutable, names, info);
111
119
  }
@@ -1,7 +1,12 @@
1
1
  // Pure v5 data operations shared by the picker and `emoji-picker-react/data`.
2
2
  // No React / ShipStyles imports allowed in this module graph.
3
3
 
4
- import { getPreparedCore, normalizeQuery, normalizeUnified } from './prepare';
4
+ import {
5
+ getPreparedCore,
6
+ normalizeQuery,
7
+ normalizeUnified,
8
+ withoutFe0f,
9
+ } from './prepare';
5
10
  import type { EmojiDataOptions, EmojiInfo } from './types';
6
11
 
7
12
  export type { EmojiDataOptions, EmojiInfo };
@@ -74,7 +79,10 @@ export function getEmojiByUnified(
74
79
  return undefined;
75
80
  }
76
81
  const core = getPreparedCore(options?.emojiData);
77
- return core.byUnified.get(normalized);
82
+ return (
83
+ core.byUnified.get(normalized) ??
84
+ core.byUnified.get(withoutFe0f(normalized))
85
+ );
78
86
  }
79
87
 
80
88
  /**
@@ -1,3 +1,5 @@
1
+ import { withoutFe0f } from '../data-core/prepare';
2
+
1
3
  import { DataEmoji, EmojiProperties } from './DataTypes';
2
4
 
3
5
  // Caller-defined Suggested contents.
@@ -47,46 +49,30 @@ export function resolveEntryId(
47
49
  if (!id) {
48
50
  return undefined;
49
51
  }
50
- if (isKnownRenderId(id, lookup)) {
51
- return id;
52
- }
53
- // Unified IDs and custom IDs are ASCII; anything else may be the
54
- // emoji character itself.
55
- if (Array.from(id).every((char) => (char.codePointAt(0) as number) < 0x80)) {
56
- return undefined;
52
+ let unified = id;
53
+ let record = lookup(id);
54
+ // Unified IDs and custom IDs are ASCII; anything else may be the emoji
55
+ // character itself, read as its code points.
56
+ if (!record && /[^\0-\x7f]/.test(id)) {
57
+ unified = Array.from(id)
58
+ .map((char) =>
59
+ (char.codePointAt(0) as number).toString(16).padStart(4, '0'),
60
+ )
61
+ .join('-');
62
+ record = lookup(unified);
57
63
  }
58
- return nativeToUnifiedCandidates(id).find((candidate) =>
59
- isKnownRenderId(candidate, lookup),
60
- );
61
- }
62
-
63
- // Dataset IDs are zero-padded code points joined by "-", and carry U+FE0F
64
- // where the emoji presentation needs it; typed or stored characters often
65
- // omit or add it. Try the exact sequence, then without FE0F, then with
66
- // FE0F after the first code point (e.g. "©" -> "00a9-fe0f").
67
- function nativeToUnifiedCandidates(native: string): string[] {
68
- const codePoints = Array.from(native).map((char) =>
69
- (char.codePointAt(0) as number).toString(16).padStart(4, '0'),
70
- );
71
- const exact = codePoints.join('-');
72
- const bare = codePoints.filter((codePoint) => codePoint !== 'fe0f');
73
- const withPresentation = [bare[0], 'fe0f', ...bare.slice(1)];
74
- return [exact, bare.join('-'), withPresentation.join('-')];
75
- }
76
-
77
- // A render identity is valid only as the base unified or a listed
78
- // variation. Anything else — e.g. a nonexistent variation that fell back
79
- // to the base record — is unknown and ignored.
80
- function isKnownRenderId(
81
- id: string,
82
- lookup: (normalizedId: string) => DataEmoji | undefined,
83
- ): boolean {
84
- const record = lookup(id);
85
64
  if (!record) {
86
- return false;
65
+ return undefined;
87
66
  }
88
- return (
89
- record[EmojiProperties.unified] === id ||
90
- (record[EmojiProperties.variations] ?? []).includes(id)
91
- );
67
+ // A render identity is the base unified or a listed variation; a
68
+ // nonexistent variation that fell back to the base record is unknown.
69
+ // Typed, stored or other datasets' IDs often omit or add U+FE0F: resolve
70
+ // to the record's own spelling of that identity.
71
+ const known = [
72
+ record[EmojiProperties.unified],
73
+ ...(record[EmojiProperties.variations] ?? []),
74
+ ];
75
+ return known.includes(unified)
76
+ ? unified
77
+ : known.find((each) => withoutFe0f(each) === withoutFe0f(unified));
92
78
  }
@@ -7,24 +7,30 @@ export function preloadEmoji(
7
7
  getEmojiUrl: GetEmojiUrl,
8
8
  emoji: undefined | DataEmoji,
9
9
  emojiStyle: EmojiStyleValue,
10
+ rendered?: string,
10
11
  ): void {
11
12
  if (!emoji) {
12
13
  return;
13
14
  }
14
15
 
15
16
  const unified = emojiUnified(emoji);
17
+ // With `rendered`, only the image the cell renders (the active tone's, or
18
+ // the base) is fetched. It is marked apart from a full preload, so a
19
+ // later full preload (on focus) still fetches every tone; a full one
20
+ // covers it.
21
+ const key = rendered ? rendered + '/only' : unified;
16
22
 
17
- if (preloadedEmojs.has(unified)) {
23
+ if (preloadedEmojs.has(unified) || preloadedEmojs.has(key)) {
18
24
  return;
19
25
  }
20
- preloadedEmojs.add(unified);
26
+ preloadedEmojs.add(key);
21
27
 
22
- emojiVariations(emoji)
23
- .concat(unified)
24
- .forEach((variation) => {
28
+ (rendered ? [rendered] : emojiVariations(emoji).concat(unified)).forEach(
29
+ (variation) => {
25
30
  const emojiUrl = getEmojiUrl(variation, emojiStyle);
26
31
  preloadImage(emojiUrl);
27
- });
32
+ },
33
+ );
28
34
  }
29
35
 
30
36
  export const preloadedEmojs: Set<string> = new Set();
@@ -5,6 +5,7 @@ import {
5
5
  useNativeEmojiSupport,
6
6
  } from '../components/context/PickerContext';
7
7
  import { useEmojiVersionConfig, useUnicodeToHide } from '../config/useConfig';
8
+ import { withoutFe0f } from '../data-core/prepare';
8
9
  import { DataEmoji } from '../dataUtils/DataTypes';
9
10
  import {
10
11
  addedIn,
@@ -27,7 +28,11 @@ export function useIsEmojiDisallowed() {
27
28
  emoji: DataEmoji,
28
29
  renderedUnified = emojiUnified(emoji, activeSkinTone),
29
30
  ) {
30
- if (unicodeToHide.has(unifiedWithoutSkinTone(emojiUnified(emoji))))
31
+ if (
32
+ unicodeToHide.has(
33
+ withoutFe0f(unifiedWithoutSkinTone(emojiUnified(emoji))),
34
+ )
35
+ )
31
36
  return true;
32
37
  if (isCustomEmoji(emoji)) return false;
33
38
  const version = addedIn(emoji);
@@ -169,8 +169,18 @@ export function useEmojiVirtualization({
169
169
  useEffect(() => requestNativeProbe(probe));
170
170
  // Image requests belong to committed work, never an abandoned render.
171
171
  // The proximity and cache checks run synchronously, without per-cell timers.
172
+ // Only the image a nearby cell will render is fetched: every skin tone of
173
+ // every nearby cell is hundreds of requests per scroll and long frames on
174
+ // slow devices. Focusing a cell (useOnFocus) fetches its variations.
172
175
  useEffect(() => {
173
- preloads.forEach((emoji) => preloadEmoji(getEmojiUrl, emoji, emojiStyle));
176
+ preloads.forEach((emoji) =>
177
+ preloadEmoji(
178
+ getEmojiUrl,
179
+ emoji,
180
+ emojiStyle,
181
+ emojiUnified(emoji, activeSkinTone),
182
+ ),
183
+ );
174
184
  });
175
185
 
176
186
  return {