emoji-picker-react 4.21.1 → 4.22.0

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.
@@ -321,6 +321,34 @@ var SuggestedRecent = {
321
321
  name: 'Recently Used',
322
322
  category: Categories.SUGGESTED
323
323
  };
324
+ /**
325
+ * Stable identity for a category entry. Custom groups are namespaced so
326
+ * duplicate `{ category: CUSTOM }` configs no longer collide in React
327
+ * keys, scroll targets, and active-tab detection.
328
+ */
329
+ function categoryIdFromCategoryConfig(categoryConfig) {
330
+ var category = categoryFromCategoryConfig(categoryConfig);
331
+ if (isCustomGroupConfig(categoryConfig)) {
332
+ return category + ":" + categoryConfig.group;
333
+ }
334
+ return category;
335
+ }
336
+ function isCustomGroupConfig(categoryConfig) {
337
+ if (categoryConfig.category !== Categories.CUSTOM) {
338
+ return false;
339
+ }
340
+ var group = categoryConfig.group;
341
+ return typeof group === 'string' && group.length > 0;
342
+ }
343
+ /**
344
+ * The `CustomEmoji` group rendered by a category entry, if any.
345
+ */
346
+ function customGroupFromCategoryConfig(categoryConfig) {
347
+ if (isCustomGroupConfig(categoryConfig)) {
348
+ return categoryConfig.group;
349
+ }
350
+ return undefined;
351
+ }
324
352
  var configByCategory = (_configByCategory = {}, _configByCategory[Categories.SUGGESTED] = {
325
353
  category: Categories.SUGGESTED,
326
354
  name: 'Frequently Used'
@@ -363,14 +391,17 @@ function categoryFromCategoryConfig(category) {
363
391
  function categoryNameFromCategoryConfig(category) {
364
392
  return category.name;
365
393
  }
366
- function mergeCategoriesConfig(userCategoriesConfig, modifiers, emojiData) {
367
- var _userCategoriesConfig;
394
+ function mergeCategoriesConfig(userCategoriesConfig, modifiers, emojiData, customEmojis) {
395
+ var _userCategoriesConfig, _userCategoriesConfig2;
368
396
  if (userCategoriesConfig === void 0) {
369
397
  userCategoriesConfig = [];
370
398
  }
371
399
  if (modifiers === void 0) {
372
400
  modifiers = {};
373
401
  }
402
+ if (customEmojis === void 0) {
403
+ customEmojis = [];
404
+ }
374
405
  var extra = function () {
375
406
  // 1. Start with localized categories from emojiData
376
407
  var fromData = emojiData != null && emojiData.categories ? Object.fromEntries(Object.entries(emojiData.categories).filter(function (_ref) {
@@ -389,15 +420,50 @@ function mergeCategoriesConfig(userCategoriesConfig, modifiers, emojiData) {
389
420
  return fromData;
390
421
  }();
391
422
  var base = baseCategoriesConfig(extra);
392
- if (!((_userCategoriesConfig = userCategoriesConfig) != null && _userCategoriesConfig.length)) {
393
- return base;
394
- }
395
- return userCategoriesConfig.map(function (category) {
423
+ var explicit = (_userCategoriesConfig = userCategoriesConfig) != null && _userCategoriesConfig.length ? userCategoriesConfig.map(mapUserEntry) : base;
424
+ // Groups without a `categories` entry get an appended section so their
425
+ // emojis always render somewhere — unless the user opted out of customs
426
+ // entirely by listing `categories` without CUSTOM. `categories` remains
427
+ // the override for order, display name, and icon.
428
+ // https://github.com/ealush/emoji-picker-react/issues/510
429
+ var customInPlay = !((_userCategoriesConfig2 = userCategoriesConfig) != null && _userCategoriesConfig2.length) || userCategoriesConfig.some(function (entry) {
430
+ return entry === Categories.CUSTOM || typeof entry !== 'string' && entry.category === Categories.CUSTOM;
431
+ });
432
+ var listedGroups = new Set(explicit.map(function (entry) {
433
+ return customGroupFromCategoryConfig(entry);
434
+ }).filter(function (group) {
435
+ return !!group;
436
+ }));
437
+ var appended = !customInPlay ? [] : Array.from(new Set(customEmojis.map(function (emoji) {
438
+ return emoji.group;
439
+ }).filter(function (group) {
440
+ return !!group;
441
+ }))).filter(function (group) {
442
+ return !listedGroups.has(group);
443
+ }).map(function (group) {
444
+ return {
445
+ category: Categories.CUSTOM,
446
+ name: group,
447
+ group: group
448
+ };
449
+ });
450
+ // Collapse duplicate entries (same identity twice renders the same
451
+ // section twice under one React key); first occurrence wins.
452
+ var seen = new Set();
453
+ return explicit.concat(appended).filter(function (entry) {
454
+ var id = categoryIdFromCategoryConfig(entry);
455
+ if (seen.has(id)) {
456
+ return false;
457
+ }
458
+ seen.add(id);
459
+ return true;
460
+ });
461
+ function mapUserEntry(category) {
396
462
  if (typeof category === 'string') {
397
463
  return getBaseConfigByCategory(category, extra[category]);
398
464
  }
399
465
  return _extends({}, getBaseConfigByCategory(category.category, extra[category.category]), category);
400
- });
466
+ }
401
467
  }
402
468
  function getBaseConfigByCategory(category, modifier) {
403
469
  if (modifier === void 0) {
@@ -428,7 +494,7 @@ function mergeConfig(userConfig) {
428
494
  var config = Object.assign(base, userConfig);
429
495
  var categories = mergeCategoriesConfig(userConfig.categories, {
430
496
  suggestionMode: config.suggestedEmojisMode
431
- }, userConfig.emojiData);
497
+ }, userConfig.emojiData, userConfig.customEmojis);
432
498
  config.hiddenEmojis.forEach(function (emoji) {
433
499
  config.unicodeToHide.add(emoji);
434
500
  });
@@ -26862,6 +26928,7 @@ var PickerDataContext = /*#__PURE__*/React__default.createContext({
26862
26928
  allEmojis: [],
26863
26929
  allEmojisByUnified: {},
26864
26930
  searchIndex: {},
26931
+ customGroups: {},
26865
26932
  emojiByUnified: emojiByUnified,
26866
26933
  activeVariationFromUnified: function activeVariationFromUnified() {
26867
26934
  return null;
@@ -26876,11 +26943,23 @@ function PickerDataProvider(_ref) {
26876
26943
  var emojiData = genericEmojiData || defaultEmojiData;
26877
26944
  // Clone to avoid mutation of shared source
26878
26945
  var newData = JSON.parse(JSON.stringify(emojiData));
26946
+ var customGroups = {};
26879
26947
  if (customEmojis && customEmojis.length > 0) {
26880
- newData.emojis[Categories.CUSTOM] = customEmojis.map(customToRegularEmoji);
26948
+ for (var _iterator = _createForOfIteratorHelperLoose(customEmojis), _step; !(_step = _iterator()).done;) {
26949
+ var _customGroups$emoji$g;
26950
+ var emoji = _step.value;
26951
+ if (!emoji.group) {
26952
+ continue;
26953
+ }
26954
+ customGroups[emoji.group] = (_customGroups$emoji$g = customGroups[emoji.group]) != null ? _customGroups$emoji$g : [];
26955
+ customGroups[emoji.group].push(customToRegularEmoji(emoji));
26956
+ }
26957
+ newData.emojis[Categories.CUSTOM] = customEmojis.filter(function (emoji) {
26958
+ return !emoji.group;
26959
+ }).map(customToRegularEmoji);
26881
26960
  }
26882
26961
  var emojis = newData.emojis || {};
26883
- var allEmojis = Object.values(emojis).flat();
26962
+ var allEmojis = Object.values(emojis).concat(Object.values(customGroups)).flat();
26884
26963
  var allEmojisByUnified = {};
26885
26964
  var searchIndex = {};
26886
26965
  allEmojis.forEach(function (emoji) {
@@ -26905,7 +26984,8 @@ function PickerDataProvider(_ref) {
26905
26984
  emojiData: newData,
26906
26985
  allEmojis: allEmojis,
26907
26986
  allEmojisByUnified: allEmojisByUnified,
26908
- searchIndex: searchIndex
26987
+ searchIndex: searchIndex,
26988
+ customGroups: customGroups
26909
26989
  };
26910
26990
  }, [genericEmojiData, customEmojis]);
26911
26991
  var emojiByUnified = React__default.useCallback(function (unified) {
@@ -26927,7 +27007,8 @@ function usePickerDataContext() {
26927
27007
  function useGetEmojisByCategory() {
26928
27008
  var _usePickerDataContext = usePickerDataContext(),
26929
27009
  emojiData = _usePickerDataContext.emojiData,
26930
- emojiByUnified = _usePickerDataContext.emojiByUnified;
27010
+ emojiByUnified = _usePickerDataContext.emojiByUnified,
27011
+ customGroups = _usePickerDataContext.customGroups;
26931
27012
  var suggestedEmojisModeConfig = useSuggestedEmojisModeConfig();
26932
27013
  var _useUpdateSuggested = useUpdateSuggested(),
26933
27014
  suggestedUpdated = _useUpdateSuggested[0];
@@ -26948,11 +27029,15 @@ function useGetEmojisByCategory() {
26948
27029
  }).filter(Boolean);
26949
27030
  // eslint-disable-next-line react-hooks/exhaustive-deps
26950
27031
  }, [isMounted, suggestedUpdated, suggestedEmojisModeConfig, emojiByUnified]);
26951
- return function getEmojisByCategory(category) {
27032
+ return function getEmojisByCategory(category, group) {
26952
27033
  var _emojiData$emojis$cat, _emojiData$emojis;
26953
27034
  if (category === Categories.SUGGESTED) {
26954
27035
  return suggested;
26955
27036
  }
27037
+ if (category === Categories.CUSTOM && group) {
27038
+ var _customGroups$group;
27039
+ return (_customGroups$group = customGroups[group]) != null ? _customGroups$group : [];
27040
+ }
26956
27041
  return (_emojiData$emojis$cat = (_emojiData$emojis = emojiData.emojis) == null ? void 0 : _emojiData$emojis[category]) != null ? _emojiData$emojis$cat : [];
26957
27042
  };
26958
27043
  }
@@ -29586,11 +29671,10 @@ function EmojiCategory(_ref) {
29586
29671
  hiddenOnSearch = _ref.hiddenOnSearch,
29587
29672
  height = _ref.height,
29588
29673
  emojisPerRow = _ref.emojisPerRow;
29589
- var category = categoryFromCategoryConfig(categoryConfig);
29590
29674
  var categoryName = categoryNameFromCategoryConfig(categoryConfig);
29591
29675
  return createElement("li", {
29592
29676
  className: cx(styles$7.category, hidden && commonStyles.hidden, hiddenOnSearch && commonInteractionStyles.hiddenOnSearch),
29593
- "data-name": category,
29677
+ "data-name": categoryIdFromCategoryConfig(categoryConfig),
29594
29678
  role: "rowgroup",
29595
29679
  "aria-label": categoryName
29596
29680
  }, createElement("h2", {
@@ -29665,7 +29749,7 @@ function MeasureEmoji() {
29665
29749
  return null;
29666
29750
  }
29667
29751
  var firstCategory = categories[0];
29668
- var dummyEmoji = getEmojisByCategory(categoryFromCategoryConfig(firstCategory))[0];
29752
+ var dummyEmoji = firstCategory ? getEmojisByCategory(categoryFromCategoryConfig(firstCategory), customGroupFromCategoryConfig(firstCategory))[0] : undefined;
29669
29753
  var unified = dummyEmoji ? emojiUnified(dummyEmoji, activeSkinTone) : '';
29670
29754
  if (!dummyEmoji) {
29671
29755
  return null;
@@ -29719,22 +29803,24 @@ function EmojiList(_ref) {
29719
29803
  role: "grid"
29720
29804
  }, createElement(MeasureEmoji, null), categories.map(function (categoryConfig, index) {
29721
29805
  var category = categoryFromCategoryConfig(categoryConfig);
29806
+ var categoryId = categoryIdFromCategoryConfig(categoryConfig);
29807
+ var group = customGroupFromCategoryConfig(categoryConfig);
29722
29808
  var currentOffset = topOffset;
29723
- var categoryHeight = categoryHeights[category];
29809
+ var categoryHeight = categoryHeights[categoryId];
29724
29810
  if (categoryHeight) {
29725
29811
  topOffset += categoryHeight + labelHeight;
29726
29812
  }
29727
29813
  return createElement(RenderCategory, {
29728
- key: category,
29729
- categoryEmojis: getEmojisByCategory(category),
29814
+ key: categoryId,
29815
+ categoryEmojis: getEmojisByCategory(category, group),
29730
29816
  categoryConfig: categoryConfig,
29731
29817
  topOffset: currentOffset,
29732
29818
  isFirstCategory: index === 0,
29733
29819
  onHeightReady: function onHeightReady(height) {
29734
- if (categoryHeights[category] !== height) {
29820
+ if (categoryHeights[categoryId] !== height) {
29735
29821
  setCategoryHeights(function (prev) {
29736
29822
  var _extends2;
29737
- return _extends({}, prev, (_extends2 = {}, _extends2[category] = height, _extends2));
29823
+ return _extends({}, prev, (_extends2 = {}, _extends2[categoryId] = height, _extends2));
29738
29824
  });
29739
29825
  }
29740
29826
  },
@@ -29758,7 +29844,7 @@ function RenderCategory(_ref2) {
29758
29844
  // the deadlock; the observer takes over from there.
29759
29845
  // https://github.com/ealush/emoji-picker-react/issues/469
29760
29846
  // https://github.com/ealush/emoji-picker-react/issues/475
29761
- var isCategoryVisible = isFirstCategory || visibleCategories.includes(categoryConfig.category);
29847
+ var isCategoryVisible = isFirstCategory || visibleCategories.includes(categoryIdFromCategoryConfig(categoryConfig));
29762
29848
  var _useEmojiVirtualizati = useEmojiVirtualization({
29763
29849
  categoryEmojis: categoryEmojis,
29764
29850
  topOffset: topOffset,
@@ -30483,7 +30569,8 @@ function useScrollCategoryIntoView() {
30483
30569
  if (!BodyRef.current) {
30484
30570
  return;
30485
30571
  }
30486
- var $category = (_BodyRef$current = BodyRef.current) == null ? void 0 : _BodyRef$current.querySelector("[data-name=\"" + category + "\"]");
30572
+ // Group names are user-controlled; escape for the attribute selector.
30573
+ var $category = (_BodyRef$current = BodyRef.current) == null ? void 0 : _BodyRef$current.querySelector("[data-name=\"" + CSS.escape(category) + "\"]");
30487
30574
  if (!$category) {
30488
30575
  return;
30489
30576
  }
@@ -30866,8 +30953,24 @@ function CategoryNavigation() {
30866
30953
  var categoryIcons = useCategoryIconsConfig();
30867
30954
  var CategoryNavigationRef = useCategoryNavigationRef();
30868
30955
  var hideCustomCategory = useShouldHideCustomEmojis();
30956
+ var _usePickerDataContext = usePickerDataContext(),
30957
+ customGroups = _usePickerDataContext.customGroups,
30958
+ emojiData = _usePickerDataContext.emojiData;
30869
30959
  var visibleCategories = categoriesConfig.filter(function (categoryConfig) {
30870
- return !(isCustomCategory(categoryConfig) && hideCustomCategory);
30960
+ if (isCustomCategory(categoryConfig) && hideCustomCategory) {
30961
+ return false;
30962
+ }
30963
+ // Tabs navigating to an empty (hidden) section are dead weight.
30964
+ var group = customGroupFromCategoryConfig(categoryConfig);
30965
+ if (group) {
30966
+ var _customGroups$group$l, _customGroups$group;
30967
+ return ((_customGroups$group$l = (_customGroups$group = customGroups[group]) == null ? void 0 : _customGroups$group.length) != null ? _customGroups$group$l : 0) > 0;
30968
+ }
30969
+ if (isCustomCategory(categoryConfig)) {
30970
+ var _emojiData$emojis$Cat, _emojiData$emojis, _emojiData$emojis$Cat2;
30971
+ return ((_emojiData$emojis$Cat = (_emojiData$emojis = emojiData.emojis) == null ? void 0 : (_emojiData$emojis$Cat2 = _emojiData$emojis[Categories.CUSTOM]) == null ? void 0 : _emojiData$emojis$Cat2.length) != null ? _emojiData$emojis$Cat : 0) > 0;
30972
+ }
30973
+ return true;
30871
30974
  });
30872
30975
  // A single tab navigates nowhere — hide the bar to reclaim its space.
30873
30976
  // https://github.com/ealush/emoji-picker-react/issues/396
@@ -30882,19 +30985,20 @@ function CategoryNavigation() {
30882
30985
  ref: CategoryNavigationRef
30883
30986
  }, visibleCategories.map(function (categoryConfig) {
30884
30987
  var category = categoryFromCategoryConfig(categoryConfig);
30885
- var isActiveCategory = category === activeCategory;
30988
+ var categoryId = categoryIdFromCategoryConfig(categoryConfig);
30989
+ var isActiveCategory = categoryId === activeCategory;
30886
30990
  var allowNavigation = !isSearchMode && !isActiveCategory;
30887
30991
  return createElement(CategoryButton, {
30888
- key: category,
30992
+ key: categoryId,
30889
30993
  category: category,
30890
30994
  isActiveCategory: isActiveCategory,
30891
30995
  allowNavigation: allowNavigation,
30892
30996
  categoryConfig: categoryConfig,
30893
30997
  customIcon: categoryIcons[category],
30894
30998
  onClick: function onClick() {
30895
- scrollCategoryIntoView(category);
30999
+ scrollCategoryIntoView(categoryId);
30896
31000
  setTimeout(function () {
30897
- setActiveCategory(category);
31001
+ setActiveCategory(categoryId);
30898
31002
  }, 10);
30899
31003
  }
30900
31004
  });