emoji-picker-react 4.22.2 → 5.0.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.
Files changed (405) hide show
  1. package/README.md +335 -49
  2. package/dist/DomUtils/categoryNameFromDom.d.ts +1 -1
  3. package/dist/DomUtils/classNames.d.ts +24 -23
  4. package/dist/DomUtils/detectEmojyPartiallyBelowFold.d.ts +2 -2
  5. package/dist/DomUtils/elementPositionInRow.d.ts +5 -5
  6. package/dist/DomUtils/eventBelongsToPicker.d.ts +2 -0
  7. package/dist/DomUtils/focusElement.d.ts +5 -5
  8. package/dist/DomUtils/getActiveElement.d.ts +2 -2
  9. package/dist/DomUtils/isJsdom.d.ts +1 -0
  10. package/dist/DomUtils/isRtl.d.ts +1 -0
  11. package/dist/DomUtils/keyboardNavigation.d.ts +7 -8
  12. package/dist/DomUtils/scrollTo.d.ts +4 -5
  13. package/dist/DomUtils/selectors.d.ts +31 -35
  14. package/dist/EmojiPickerReact.d.ts +5 -5
  15. package/dist/Stylesheet/DedupedStyle.d.ts +13 -0
  16. package/dist/Stylesheet/stylesheet.d.ts +40 -37
  17. package/dist/components/ErrorBoundary.d.ts +15 -15
  18. package/dist/components/Layout/Absolute.d.ts +8 -8
  19. package/dist/components/Layout/Flex.d.ts +13 -13
  20. package/dist/components/Layout/Relative.d.ts +8 -8
  21. package/dist/components/Layout/Space.d.ts +7 -7
  22. package/dist/components/Reactions/BtnPlus.d.ts +2 -2
  23. package/dist/components/Reactions/DEFAULT_REACTIONS.d.ts +1 -1
  24. package/dist/components/Reactions/Reactions.d.ts +3 -2
  25. package/dist/components/atoms/Button.d.ts +11 -6
  26. package/dist/components/body/EmojiCategory.d.ts +13 -12
  27. package/dist/components/body/EmojiList.d.ts +7 -4
  28. package/dist/components/body/EmojiVariationPicker.d.ts +2 -2
  29. package/dist/components/body/MeasureEmoji.d.ts +2 -2
  30. package/dist/components/body/listComponents.d.ts +47 -0
  31. package/dist/components/context/ElementRefContext.d.ts +18 -16
  32. package/dist/components/context/PickerConfigContext.d.ts +14 -9
  33. package/dist/components/context/PickerContext.d.ts +57 -25
  34. package/dist/components/context/PickerDataContext.d.ts +23 -17
  35. package/dist/components/emoji/BaseEmojiProps.d.ts +14 -13
  36. package/dist/components/emoji/ClickableEmojiButton.d.ts +23 -15
  37. package/dist/components/emoji/Emoji.d.ts +18 -15
  38. package/dist/components/emoji/EmojiImg.d.ts +11 -11
  39. package/dist/components/emoji/ExportedEmoji.d.ts +11 -11
  40. package/dist/components/emoji/NativeEmoji.d.ts +6 -6
  41. package/dist/components/emoji/ViewOnlyEmoji.d.ts +3 -3
  42. package/dist/components/emoji/emojiStyles.d.ts +6 -1
  43. package/dist/components/footer/Preview.d.ts +7 -7
  44. package/dist/components/header/Header.d.ts +2 -2
  45. package/dist/components/header/Search/BtnClearSearch.d.ts +2 -2
  46. package/dist/components/header/Search/IcnSearch.d.ts +2 -2
  47. package/dist/components/header/Search/Search.d.ts +11 -3
  48. package/dist/components/header/SkinTonePicker/BtnSkinToneVariation.d.ts +12 -12
  49. package/dist/components/header/SkinTonePicker/SkinTonePicker.d.ts +11 -11
  50. package/dist/components/icons/svgIcons.d.ts +4 -0
  51. package/dist/components/main/PickerMainBehaviors.d.ts +4 -0
  52. package/dist/components/main/defaultAppearance.d.ts +12 -0
  53. package/dist/components/main/labelHeight.d.ts +1 -0
  54. package/dist/components/navigation/CategoryButton.d.ts +12 -12
  55. package/dist/components/navigation/CategoryNavIcon.d.ts +4 -4
  56. package/dist/components/navigation/CategoryNavigation.d.ts +19 -2
  57. package/dist/config/categoryConfig.d.ts +34 -34
  58. package/dist/config/cdnUrls.d.ts +2 -2
  59. package/dist/config/compareConfig.d.ts +10 -2
  60. package/dist/config/config.d.ts +166 -64
  61. package/dist/config/customEmojiConfig.d.ts +14 -14
  62. package/dist/config/defaultPickerConfiguration.d.ts +10 -0
  63. package/dist/config/mutableConfig.d.ts +32 -10
  64. package/dist/config/useConfig.d.ts +48 -36
  65. package/dist/data/defaultEmojiData.d.ts +9 -0
  66. package/dist/data/defaultEmojiData.js +40 -0
  67. package/dist/data/emojis-bn.d.mts +39 -0
  68. package/dist/data/emojis-bn.d.ts +38 -2
  69. package/dist/data/emojis-bn.js +25 -2
  70. package/dist/data/emojis-bn.mjs +2 -0
  71. package/dist/data/emojis-da.d.mts +39 -0
  72. package/dist/data/emojis-da.d.ts +38 -2
  73. package/dist/data/emojis-da.js +25 -2
  74. package/dist/data/emojis-da.mjs +2 -0
  75. package/dist/data/emojis-de.d.mts +39 -0
  76. package/dist/data/emojis-de.d.ts +38 -2
  77. package/dist/data/emojis-de.js +25 -2
  78. package/dist/data/emojis-de.mjs +2 -0
  79. package/dist/data/emojis-en-gb.d.mts +39 -0
  80. package/dist/data/emojis-en-gb.d.ts +38 -2
  81. package/dist/data/emojis-en-gb.js +25 -2
  82. package/dist/data/emojis-en-gb.mjs +2 -0
  83. package/dist/data/emojis-en.d.mts +39 -0
  84. package/dist/data/emojis-en.d.ts +38 -2
  85. package/dist/data/emojis-en.js +25 -2
  86. package/dist/data/emojis-en.mjs +2 -0
  87. package/dist/data/emojis-es-mx.d.mts +39 -0
  88. package/dist/data/emojis-es-mx.d.ts +38 -2
  89. package/dist/data/emojis-es-mx.js +25 -2
  90. package/dist/data/emojis-es-mx.mjs +2 -0
  91. package/dist/data/emojis-es.d.mts +39 -0
  92. package/dist/data/emojis-es.d.ts +38 -2
  93. package/dist/data/emojis-es.js +25 -2
  94. package/dist/data/emojis-es.mjs +2 -0
  95. package/dist/data/emojis-et.d.mts +39 -0
  96. package/dist/data/emojis-et.d.ts +38 -2
  97. package/dist/data/emojis-et.js +25 -2
  98. package/dist/data/emojis-et.mjs +2 -0
  99. package/dist/data/emojis-fi.d.mts +39 -0
  100. package/dist/data/emojis-fi.d.ts +38 -2
  101. package/dist/data/emojis-fi.js +25 -2
  102. package/dist/data/emojis-fi.mjs +2 -0
  103. package/dist/data/emojis-fr.d.mts +39 -0
  104. package/dist/data/emojis-fr.d.ts +38 -2
  105. package/dist/data/emojis-fr.js +25 -2
  106. package/dist/data/emojis-fr.mjs +2 -0
  107. package/dist/data/emojis-hi.d.mts +39 -0
  108. package/dist/data/emojis-hi.d.ts +38 -2
  109. package/dist/data/emojis-hi.js +25 -2
  110. package/dist/data/emojis-hi.mjs +2 -0
  111. package/dist/data/emojis-hu.d.mts +39 -0
  112. package/dist/data/emojis-hu.d.ts +38 -2
  113. package/dist/data/emojis-hu.js +25 -2
  114. package/dist/data/emojis-hu.mjs +2 -0
  115. package/dist/data/emojis-it.d.mts +39 -0
  116. package/dist/data/emojis-it.d.ts +38 -2
  117. package/dist/data/emojis-it.js +25 -2
  118. package/dist/data/emojis-it.mjs +2 -0
  119. package/dist/data/emojis-ja.d.mts +39 -0
  120. package/dist/data/emojis-ja.d.ts +38 -2
  121. package/dist/data/emojis-ja.js +25 -2
  122. package/dist/data/emojis-ja.mjs +2 -0
  123. package/dist/data/emojis-ko.d.mts +39 -0
  124. package/dist/data/emojis-ko.d.ts +38 -2
  125. package/dist/data/emojis-ko.js +25 -2
  126. package/dist/data/emojis-ko.mjs +2 -0
  127. package/dist/data/emojis-lt.d.mts +39 -0
  128. package/dist/data/emojis-lt.d.ts +38 -2
  129. package/dist/data/emojis-lt.js +25 -2
  130. package/dist/data/emojis-lt.mjs +2 -0
  131. package/dist/data/emojis-ms.d.mts +39 -0
  132. package/dist/data/emojis-ms.d.ts +38 -2
  133. package/dist/data/emojis-ms.js +25 -2
  134. package/dist/data/emojis-ms.mjs +2 -0
  135. package/dist/data/emojis-nb.d.mts +39 -0
  136. package/dist/data/emojis-nb.d.ts +38 -2
  137. package/dist/data/emojis-nb.js +25 -2
  138. package/dist/data/emojis-nb.mjs +2 -0
  139. package/dist/data/emojis-nl.d.mts +39 -0
  140. package/dist/data/emojis-nl.d.ts +38 -2
  141. package/dist/data/emojis-nl.js +25 -2
  142. package/dist/data/emojis-nl.mjs +2 -0
  143. package/dist/data/emojis-pl.d.mts +39 -0
  144. package/dist/data/emojis-pl.d.ts +38 -2
  145. package/dist/data/emojis-pl.js +25 -2
  146. package/dist/data/emojis-pl.mjs +2 -0
  147. package/dist/data/emojis-pt.d.mts +39 -0
  148. package/dist/data/emojis-pt.d.ts +38 -2
  149. package/dist/data/emojis-pt.js +25 -2
  150. package/dist/data/emojis-pt.mjs +2 -0
  151. package/dist/data/emojis-ru.d.mts +39 -0
  152. package/dist/data/emojis-ru.d.ts +38 -2
  153. package/dist/data/emojis-ru.js +25 -2
  154. package/dist/data/emojis-ru.mjs +2 -0
  155. package/dist/data/emojis-sv.d.mts +39 -0
  156. package/dist/data/emojis-sv.d.ts +38 -2
  157. package/dist/data/emojis-sv.js +25 -2
  158. package/dist/data/emojis-sv.mjs +2 -0
  159. package/dist/data/emojis-th.d.mts +39 -0
  160. package/dist/data/emojis-th.d.ts +38 -2
  161. package/dist/data/emojis-th.js +25 -2
  162. package/dist/data/emojis-th.mjs +2 -0
  163. package/dist/data/emojis-uk.d.mts +39 -0
  164. package/dist/data/emojis-uk.d.ts +38 -2
  165. package/dist/data/emojis-uk.js +25 -2
  166. package/dist/data/emojis-uk.mjs +2 -0
  167. package/dist/data/emojis-vi.d.mts +39 -0
  168. package/dist/data/emojis-vi.d.ts +38 -2
  169. package/dist/data/emojis-vi.js +25 -2
  170. package/dist/data/emojis-vi.mjs +2 -0
  171. package/dist/data/emojis-zh-hant.d.mts +39 -0
  172. package/dist/data/emojis-zh-hant.d.ts +38 -2
  173. package/dist/data/emojis-zh-hant.js +25 -2
  174. package/dist/data/emojis-zh-hant.mjs +2 -0
  175. package/dist/data/emojis-zh.d.mts +39 -0
  176. package/dist/data/emojis-zh.d.ts +38 -2
  177. package/dist/data/emojis-zh.js +25 -2
  178. package/dist/data/emojis-zh.mjs +2 -0
  179. package/dist/data/index.js +1 -0
  180. package/dist/data/registerDefaultEmojiData.d.ts +1 -0
  181. package/dist/data/registerDefaultEmojiData.js +3 -0
  182. package/dist/data-core/pickerData.d.ts +12 -0
  183. package/dist/data-core/prepare.d.ts +23 -0
  184. package/dist/data-core/search.d.ts +13 -0
  185. package/dist/data-core/types.d.ts +23 -0
  186. package/dist/data.d.mts +72 -0
  187. package/dist/data.d.ts +72 -0
  188. package/dist/dataUtils/DataTypes.d.ts +23 -19
  189. package/dist/dataUtils/emojiSelectors.d.ts +15 -16
  190. package/dist/dataUtils/emojiUtils.d.ts +1 -12
  191. package/dist/dataUtils/nativeEmojiSequenceSupport.d.ts +10 -0
  192. package/dist/dataUtils/nativeEmojiSupport.d.ts +40 -0
  193. package/dist/dataUtils/parseNativeEmoji.d.ts +1 -1
  194. package/dist/dataUtils/suggested.d.ts +11 -11
  195. package/dist/dataUtils/suggestedEmojis.d.ts +7 -0
  196. package/dist/esm/chunks/chunk-46BUDQLE.mjs +228 -0
  197. package/dist/esm/chunks/chunk-7ZGTUZFB.mjs +16 -0
  198. package/dist/esm/chunks/chunk-FQIJ4HBW.mjs +25 -0
  199. package/dist/esm/chunks/chunk-KQPOIRSC.mjs +9305 -0
  200. package/dist/esm/chunks/chunk-LO4TPPLH.mjs +43 -0
  201. package/dist/esm/chunks/emojis-6L3GRBAP.mjs +9 -0
  202. package/dist/esm/data.mjs +18 -0
  203. package/dist/esm/index.mjs +389 -0
  204. package/dist/esm/primitives/index.mjs +67 -0
  205. package/dist/hooks/preloadEmoji.d.ts +9 -9
  206. package/dist/hooks/useActiveCategoryScrollDetection.d.ts +11 -11
  207. package/dist/hooks/useCategoryHeight.d.ts +6 -5
  208. package/dist/hooks/useCloseAllOpenToggles.d.ts +2 -2
  209. package/dist/hooks/useDataIdentityStabilityWarning.d.ts +13 -0
  210. package/dist/hooks/useDebouncedState.d.ts +1 -1
  211. package/dist/hooks/useDisallowMouseMove.d.ts +4 -4
  212. package/dist/hooks/useDisallowedEmojis.d.ts +2 -3
  213. package/dist/hooks/useEmojiPreviewEvents.d.ts +3 -3
  214. package/dist/hooks/useEmojiVirtualization.d.ts +21 -17
  215. package/dist/hooks/useFilter.d.ts +10 -16
  216. package/dist/hooks/useFocus.d.ts +3 -3
  217. package/dist/hooks/useInitialLoad.d.ts +2 -2
  218. package/dist/hooks/useIsEmojiHidden.d.ts +8 -8
  219. package/dist/hooks/useIsMounted.d.ts +1 -1
  220. package/dist/hooks/useIsSearchMode.d.ts +1 -1
  221. package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -2
  222. package/dist/hooks/useKeyboardNavigation.d.ts +3 -1
  223. package/dist/hooks/useMouseDownHandlers.d.ts +8 -4
  224. package/dist/hooks/useOnFocus.d.ts +1 -1
  225. package/dist/hooks/useOnScroll.d.ts +2 -2
  226. package/dist/hooks/useReactionsFocus.d.ts +1 -0
  227. package/dist/hooks/useRegisterRegion.d.ts +24 -0
  228. package/dist/hooks/useResolvedEmojiData.d.ts +37 -0
  229. package/dist/hooks/useScrollCategoryIntoView.d.ts +1 -1
  230. package/dist/hooks/useSearchController.d.ts +17 -0
  231. package/dist/hooks/useSearchResults.d.ts +8 -0
  232. package/dist/hooks/useSetVariationPicker.d.ts +2 -2
  233. package/dist/hooks/useShouldHideCustomEmojis.d.ts +1 -1
  234. package/dist/hooks/useShouldShowSkinTonePicker.d.ts +4 -4
  235. package/dist/index.d.mts +376 -0
  236. package/dist/index.d.ts +376 -9
  237. package/dist/index.js +2 -8
  238. package/dist/primitives/CategoryNav.d.ts +4 -0
  239. package/dist/primitives/Empty.d.ts +6 -0
  240. package/dist/primitives/List.d.ts +4 -0
  241. package/dist/primitives/LoadError.d.ts +8 -0
  242. package/dist/primitives/Loading.d.ts +4 -0
  243. package/dist/primitives/Panel.d.ts +3 -0
  244. package/dist/primitives/Preview.d.ts +3 -0
  245. package/dist/primitives/Reactions.d.ts +2 -0
  246. package/dist/primitives/Root.d.ts +26 -0
  247. package/dist/primitives/Search.d.ts +5 -0
  248. package/dist/primitives/SearchInput.d.ts +3 -0
  249. package/dist/primitives/SkinTone.d.ts +4 -0
  250. package/dist/primitives/Viewport.d.ts +19 -0
  251. package/dist/primitives/appearance.d.ts +3 -0
  252. package/dist/primitives/components.d.ts +27 -0
  253. package/dist/primitives/hooks.d.ts +48 -0
  254. package/dist/primitives/index.d.mts +561 -0
  255. package/dist/primitives/index.d.ts +561 -0
  256. package/dist/primitives/index.js +2 -0
  257. package/dist/primitives/nativeProps.d.ts +15 -0
  258. package/dist/primitives/scope.d.ts +13 -0
  259. package/dist/primitives/structuralStyles.d.ts +16 -0
  260. package/dist/primitives/tokens.d.ts +18 -0
  261. package/dist/primitives/types.d.ts +85 -0
  262. package/dist/state/activeEmojiStore.d.ts +7 -0
  263. package/dist/state/navigationRegistry.d.ts +61 -0
  264. package/dist/state/regionTraversal.d.ts +20 -0
  265. package/dist/typeRefinements/typeRefinements.d.ts +6 -6
  266. package/dist/types/exposedTypes.d.ts +16 -6
  267. package/dist/types/exposedTypes.js +5 -0
  268. package/dist/virtualization/virtualizationHelpers.d.ts +20 -18
  269. package/llms-full.txt +3680 -0
  270. package/llms.txt +66 -476
  271. package/package.json +204 -66
  272. package/src/DomUtils/categoryNameFromDom.ts +1 -1
  273. package/src/DomUtils/classNames.ts +1 -0
  274. package/src/DomUtils/elementPositionInRow.ts +26 -4
  275. package/src/DomUtils/eventBelongsToPicker.ts +17 -0
  276. package/src/DomUtils/getActiveElement.ts +7 -1
  277. package/src/DomUtils/isJsdom.ts +12 -0
  278. package/src/DomUtils/isRtl.ts +5 -0
  279. package/src/DomUtils/keyboardNavigation.ts +105 -149
  280. package/src/DomUtils/scrollTo.ts +18 -16
  281. package/src/DomUtils/selectors.ts +20 -111
  282. package/src/EmojiPickerReact.tsx +155 -48
  283. package/src/Stylesheet/DedupedStyle.tsx +80 -0
  284. package/src/Stylesheet/stylesheet.tsx +128 -59
  285. package/src/components/Layout/Flex.tsx +12 -11
  286. package/src/components/Reactions/BtnPlus.tsx +61 -51
  287. package/src/components/Reactions/Reactions.tsx +113 -55
  288. package/src/components/atoms/Button.tsx +18 -15
  289. package/src/components/body/EmojiCategory.tsx +77 -40
  290. package/src/components/body/EmojiList.tsx +55 -23
  291. package/src/components/body/EmojiVariationPicker.tsx +113 -66
  292. package/src/components/body/MeasureEmoji.tsx +9 -2
  293. package/src/components/body/listComponents.tsx +79 -0
  294. package/src/components/context/ElementRefContext.tsx +46 -35
  295. package/src/components/context/PickerConfigContext.tsx +55 -40
  296. package/src/components/context/PickerContext.tsx +567 -92
  297. package/src/components/context/PickerDataContext.tsx +110 -99
  298. package/src/components/emoji/BaseEmojiProps.ts +5 -4
  299. package/src/components/emoji/ClickableEmojiButton.tsx +148 -63
  300. package/src/components/emoji/Emoji.tsx +24 -2
  301. package/src/components/emoji/EmojiImg.tsx +13 -12
  302. package/src/components/emoji/ExportedEmoji.tsx +5 -4
  303. package/src/components/emoji/NativeEmoji.tsx +19 -16
  304. package/src/components/emoji/ViewOnlyEmoji.tsx +55 -30
  305. package/src/components/emoji/emojiStyles.ts +12 -11
  306. package/src/components/footer/Preview.tsx +86 -91
  307. package/src/components/header/Header.tsx +21 -6
  308. package/src/components/header/Search/BtnClearSearch.tsx +69 -55
  309. package/src/components/header/Search/IcnSearch.tsx +21 -21
  310. package/src/components/header/Search/Search.tsx +77 -137
  311. package/src/components/header/SkinTonePicker/BtnSkinToneVariation.tsx +99 -62
  312. package/src/components/header/SkinTonePicker/SkinTonePicker.tsx +84 -46
  313. package/src/components/icons/svgIcons.ts +17 -0
  314. package/src/components/main/PickerMainBehaviors.tsx +163 -0
  315. package/src/components/main/defaultAppearance.tsx +188 -0
  316. package/src/components/main/labelHeight.ts +5 -0
  317. package/src/components/navigation/CategoryButton.tsx +83 -57
  318. package/src/components/navigation/CategoryNavIcon.tsx +73 -194
  319. package/src/components/navigation/CategoryNavigation.tsx +118 -46
  320. package/src/config/categoryConfig.ts +7 -3
  321. package/src/config/cdnUrls.ts +11 -21
  322. package/src/config/compareConfig.ts +128 -30
  323. package/src/config/config.ts +228 -24
  324. package/src/config/defaultPickerConfiguration.ts +7 -0
  325. package/src/config/mutableConfig.ts +44 -20
  326. package/src/config/useConfig.ts +130 -37
  327. package/src/data/defaultEmojiData.ts +49 -0
  328. package/src/data/registerDefaultEmojiData.ts +8 -0
  329. package/src/data-core/pickerData.ts +173 -0
  330. package/src/data-core/prepare.ts +178 -0
  331. package/src/data-core/search.ts +122 -0
  332. package/src/data-core/types.ts +36 -0
  333. package/src/data.ts +10 -0
  334. package/src/dataUtils/DataTypes.ts +4 -0
  335. package/src/dataUtils/emojiSelectors.ts +58 -63
  336. package/src/dataUtils/emojiUtils.ts +17 -74
  337. package/src/dataUtils/nativeEmojiSequenceSupport.ts +116 -0
  338. package/src/dataUtils/nativeEmojiSupport.ts +160 -0
  339. package/src/dataUtils/suggested.ts +8 -4
  340. package/src/dataUtils/suggestedEmojis.ts +92 -0
  341. package/src/env.d.ts +4 -0
  342. package/src/hooks/preloadEmoji.ts +3 -3
  343. package/src/hooks/useActiveCategoryScrollDetection.ts +10 -1
  344. package/src/hooks/useCategoryHeight.ts +82 -8
  345. package/src/hooks/useDataIdentityStabilityWarning.ts +86 -0
  346. package/src/hooks/useDebouncedState.ts +13 -1
  347. package/src/hooks/useDisallowedEmojis.ts +26 -42
  348. package/src/hooks/useEmojiPreviewEvents.ts +43 -10
  349. package/src/hooks/useEmojiVirtualization.tsx +38 -4
  350. package/src/hooks/useFilter.ts +85 -127
  351. package/src/hooks/useFocus.ts +14 -24
  352. package/src/hooks/useIsEmojiHidden.ts +10 -6
  353. package/src/hooks/useKeyboardNavigation.ts +322 -429
  354. package/src/hooks/useMouseDownHandlers.ts +91 -16
  355. package/src/hooks/useOnFocus.ts +12 -3
  356. package/src/hooks/useOnScroll.ts +18 -2
  357. package/src/hooks/useReactionsFocus.ts +118 -0
  358. package/src/hooks/useRegisterRegion.ts +92 -0
  359. package/src/hooks/useResolvedEmojiData.ts +200 -0
  360. package/src/hooks/useScrollCategoryIntoView.ts +74 -12
  361. package/src/hooks/useSearchController.ts +369 -0
  362. package/src/hooks/useSearchResults.ts +51 -0
  363. package/src/hooks/useSetVariationPicker.ts +4 -1
  364. package/src/index.tsx +67 -5
  365. package/src/primitives/CategoryNav.tsx +40 -0
  366. package/src/primitives/Empty.tsx +68 -0
  367. package/src/primitives/List.tsx +61 -0
  368. package/src/primitives/LoadError.tsx +38 -0
  369. package/src/primitives/Loading.tsx +60 -0
  370. package/src/primitives/Panel.tsx +47 -0
  371. package/src/primitives/Preview.tsx +38 -0
  372. package/src/primitives/Reactions.tsx +2 -0
  373. package/src/primitives/Root.tsx +398 -0
  374. package/src/primitives/Search.tsx +41 -0
  375. package/src/primitives/SearchInput.tsx +110 -0
  376. package/src/primitives/SkinTone.tsx +76 -0
  377. package/src/primitives/Viewport.tsx +204 -0
  378. package/src/primitives/appearance.tsx +7 -0
  379. package/src/primitives/components.tsx +30 -0
  380. package/src/primitives/hooks.ts +165 -0
  381. package/src/primitives/index.ts +94 -0
  382. package/src/primitives/nativeProps.ts +90 -0
  383. package/src/primitives/scope.tsx +109 -0
  384. package/src/primitives/structuralStyles.tsx +133 -0
  385. package/src/primitives/tokens.ts +149 -0
  386. package/src/primitives/types.ts +192 -0
  387. package/src/state/activeEmojiStore.ts +39 -0
  388. package/src/state/navigationRegistry.ts +209 -0
  389. package/src/state/regionTraversal.ts +116 -0
  390. package/src/svg.d.ts +2 -2
  391. package/src/types/exposedTypes.ts +28 -1
  392. package/src/virtualization/virtualizationHelpers.ts +20 -6
  393. package/dist/components/body/Body.d.ts +0 -2
  394. package/dist/components/main/PickerMain.d.ts +0 -7
  395. package/dist/dataUtils/alphaNumericEmojiIndex.d.ts +0 -5
  396. package/dist/emoji-picker-react.cjs.development.js +0 -31413
  397. package/dist/emoji-picker-react.cjs.development.js.map +0 -1
  398. package/dist/emoji-picker-react.cjs.production.min.js +0 -2
  399. package/dist/emoji-picker-react.cjs.production.min.js.map +0 -1
  400. package/dist/emoji-picker-react.esm.js +0 -31411
  401. package/dist/emoji-picker-react.esm.js.map +0 -1
  402. package/dist/hooks/useHideEmojisByUniocode.d.ts +0 -1
  403. package/src/components/body/Body.tsx +0 -43
  404. package/src/components/main/PickerMain.tsx +0 -249
  405. package/src/dataUtils/alphaNumericEmojiIndex.ts +0 -21
@@ -0,0 +1,160 @@
1
+ import { isJsdom } from '../DomUtils/isJsdom';
2
+
3
+ import { createNativeEmojiSequenceSupport } from './nativeEmojiSequenceSupport';
4
+
5
+ // Native emoji support detection.
6
+ //
7
+ // The native emoji style renders with the platform's emoji font, which lags
8
+ // behind the dataset: an emoji newer than the OS font draws as a blank box
9
+ // ("tofu") or decomposes into several glyphs, and Windows has no country
10
+ // flag glyphs at all. The picker probes the platform on the client and
11
+ // refreshes after emoji font changes or loading. SSR output is unaffected;
12
+ // detected unsupported glyphs are hidden.
13
+ //
14
+ // Detection runs in two phases so it never stalls the first paint.
15
+ // Drawing every sequence costs 0.2-0.8 s of main-thread time, depending
16
+ // on the browser and font, so before paint the picker samples one glyph
17
+ // per emoji version and a country flag, and predicts from that. Sequences
18
+ // about to be shown (cells near the viewport, their skin tones and the
19
+ // reactions) are then checked exactly in cached pixel batches between
20
+ // tasks. Exact results replace predictions: one version sample cannot
21
+ // prove font coverage.
22
+
23
+ export const DEFAULT_NATIVE_EMOJI_FONT =
24
+ '"Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "EmojiOne Color", "Android Emoji"';
25
+
26
+ const BASELINE_EMOJI = '\u{1F600}'; // grinning face (Emoji 1.0)
27
+
28
+ const FLAG_SAMPLE = '1f1fa-1f1f8'; // regional indicators U + S
29
+
30
+ export type NativeEmojiProbe = ReturnType<
31
+ typeof createNativeEmojiSequenceSupport
32
+ >;
33
+
34
+ /**
35
+ * Platform support as filtering reads it. `supports` returns a probed
36
+ * identity's exact result (null when inconclusive, undefined when not
37
+ * probed yet); the version and flag fields predict the rest.
38
+ */
39
+ export type NativeEmojiSupport = {
40
+ supports: (unified: string) => boolean | null | undefined;
41
+ /** Emoji versions whose sample the font cannot draw. */
42
+ failedVersions: Set<number>;
43
+ countryFlags: boolean;
44
+ };
45
+
46
+ export const UNKNOWN_SUPPORT = {};
47
+
48
+ let cache = new WeakMap<
49
+ Document,
50
+ Map<string, NativeEmojiProbe | typeof UNKNOWN_SUPPORT>
51
+ >();
52
+
53
+ /**
54
+ * Probes the font synchronously, sampling versions and flags only.
55
+ * Exact sequence results accumulate on the returned probe through
56
+ * `prime`, which pickers call in background batches.
57
+ */
58
+ export function detectNativeEmojiSupport(
59
+ fontFamily: string = DEFAULT_NATIVE_EMOJI_FONT,
60
+ refresh = false,
61
+ ownerDocument: Document | undefined = typeof document === 'undefined'
62
+ ? undefined
63
+ : document,
64
+ ): NativeEmojiProbe | typeof UNKNOWN_SUPPORT {
65
+ if (!ownerDocument) return UNKNOWN_SUPPORT;
66
+ const fonts =
67
+ cache.get(ownerDocument) ||
68
+ new Map<string, NativeEmojiProbe | typeof UNKNOWN_SUPPORT>();
69
+ cache.set(ownerDocument, fonts);
70
+ if (refresh) fonts.delete(fontFamily);
71
+ const cached = fonts.get(fontFamily);
72
+ if (cached) {
73
+ return cached;
74
+ }
75
+ const result = probe(fontFamily, ownerDocument);
76
+ // Bound retained font caches even when an application switches themes
77
+ // through many dynamically named families. Mounted Roots retain theirs.
78
+ if (fonts.size >= 16) fonts.clear();
79
+ fonts.set(fontFamily, result);
80
+ return result;
81
+ }
82
+
83
+ /**
84
+ * Whether a rendered identity should show. An exact result wins; until
85
+ * one exists, flags and emojis of a version whose sample failed are
86
+ * predicted unsupported. `addedIn` is the emoji's version, when known.
87
+ */
88
+ export function isNativeEmojiSupported(
89
+ support: NativeEmojiSupport | null,
90
+ unified: string,
91
+ addedIn = 0,
92
+ ): boolean {
93
+ const exact = support && support.supports(unified);
94
+ return typeof exact === 'boolean'
95
+ ? exact
96
+ : !support ||
97
+ !(
98
+ support.failedVersions.has(addedIn) ||
99
+ (!support.countryFlags && isCountryFlagUnified(unified))
100
+ );
101
+ }
102
+
103
+ /** The picker's view of a probe: frozen results, never drawing. */
104
+ /**
105
+ * The picker's view of a probe: frozen results, never drawing, plus a
106
+ * prediction for identities not checked yet. `samples` maps each emoji
107
+ * version to one emoji introduced in it. Versions are judged one by one:
108
+ * fonts often draw a newer version's single glyphs but not an older
109
+ * version's sequences.
110
+ */
111
+ export function nativeSupportSnapshot(
112
+ probe: NativeEmojiProbe,
113
+ samples: Map<number, string>,
114
+ ): NativeEmojiSupport {
115
+ probe.prime([FLAG_SAMPLE, ...samples.values()]);
116
+ const failedVersions = new Set<number>();
117
+ samples.forEach((unified, version) => {
118
+ if (probe.supports(unified) === false) failedVersions.add(version);
119
+ });
120
+ return {
121
+ supports: probe.snapshot(),
122
+ failedVersions,
123
+ // Only a definite failure predicts missing flags.
124
+ countryFlags: probe.supports(FLAG_SAMPLE) !== false,
125
+ };
126
+ }
127
+
128
+ /** Test-only: forget cached probe results. */
129
+ export function __resetNativeEmojiSupportForTest(): void {
130
+ cache = new WeakMap();
131
+ }
132
+
133
+ function probe(
134
+ fontFamily: string,
135
+ ownerDocument: Document,
136
+ ): NativeEmojiProbe | typeof UNKNOWN_SUPPORT {
137
+ if (isJsdom()) return UNKNOWN_SUPPORT;
138
+ try {
139
+ const canvas = ownerDocument.createElement('canvas');
140
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
141
+ if (!ctx) return UNKNOWN_SUPPORT;
142
+ ctx.font = `24px ${fontFamily}`;
143
+ ctx.textBaseline = 'top';
144
+ const baselineWidth = ctx.measureText(BASELINE_EMOJI).width;
145
+ const sequences = createNativeEmojiSequenceSupport(ctx, baselineWidth);
146
+ // A stable baseline rejects text fallback, noise and blocked readbacks.
147
+ if (!baselineWidth || sequences.supports('1f600') !== true)
148
+ return UNKNOWN_SUPPORT;
149
+ return sequences;
150
+ } catch {
151
+ return UNKNOWN_SUPPORT;
152
+ }
153
+ }
154
+
155
+ /** Whether a unified code is a regional-indicator country flag. */
156
+ export function isCountryFlagUnified(unified: string): boolean {
157
+ // Regional-indicator pairs, plus tag-sequence subdivision flags: fonts
158
+ // without the former lack the latter too.
159
+ return /^(1f1[ef][\da-f]-1f1[ef][\da-f]$|1f3f4-e00)/i.test(unified);
160
+ }
@@ -1,7 +1,11 @@
1
- import { SkinTones, SuggestionMode } from '../types/exposedTypes';
1
+ import {
2
+ SkinTones,
3
+ SuggestionMode,
4
+ SuggestionModeValue,
5
+ } from '../types/exposedTypes';
2
6
 
3
7
  import { DataEmoji } from './DataTypes';
4
- import { emojiUnified } from './emojiUtils';
8
+ import { emojiUnified, emojiCanonicalUnified } from './emojiUtils';
5
9
 
6
10
  const SUGGESTED_LS_KEY = 'epr_suggested';
7
11
 
@@ -13,7 +17,7 @@ type SuggestedItem = {
13
17
 
14
18
  type Suggested = SuggestedItem[];
15
19
 
16
- export function getSuggested(mode?: SuggestionMode): Suggested {
20
+ export function getSuggested(mode?: SuggestionModeValue): Suggested {
17
21
  try {
18
22
  if (typeof window === 'undefined' || !window.localStorage) {
19
23
  return [];
@@ -36,7 +40,7 @@ export function setSuggested(emoji: DataEmoji, skinTone: SkinTones) {
36
40
  const recent = getSuggested();
37
41
 
38
42
  const unified = emojiUnified(emoji, skinTone);
39
- const originalUnified = emojiUnified(emoji);
43
+ const originalUnified = emojiCanonicalUnified(emoji);
40
44
 
41
45
  let existing = recent.find(({ unified: u }) => u === unified);
42
46
 
@@ -0,0 +1,92 @@
1
+ import { DataEmoji, EmojiProperties } from './DataTypes';
2
+
3
+ // Caller-defined Suggested contents.
4
+ //
5
+ // While `suggestedEmojis` is present it fully determines the Suggested
6
+ // category contents/order and `suggestedEmojisMode` is ignored for
7
+ // selection/order. One case-insensitive rule covers both Unicode and
8
+ // custom IDs, matching how `customEmojis` are indexed (lowercased on
9
+ // entry). A valid skin-tone variation keeps that exact variation for
10
+ // rendering rather than collapsing to the neutral base. Unknown entries
11
+ // are ignored; duplicates collapse by resolved render identity with first
12
+ // occurrence winning; caller order is otherwise preserved. The input
13
+ // array is never mutated. Native emoji characters ("🧠", "©️") are accepted
14
+ // too and resolved to their unified ID, so apps that store recents as the
15
+ // characters they insert can pass them through unchanged.
16
+
17
+ export function resolveSuggestedRenderIds(
18
+ entries: readonly string[],
19
+ lookup: (normalizedId: string) => DataEmoji | undefined,
20
+ ): string[] {
21
+ const seen = new Set<string>();
22
+ const result: string[] = [];
23
+
24
+ for (const entry of entries) {
25
+ if (typeof entry !== 'string') {
26
+ continue;
27
+ }
28
+ const id = resolveEntryId(entry.trim().toLowerCase(), lookup);
29
+ if (!id || seen.has(id)) {
30
+ continue;
31
+ }
32
+ seen.add(id);
33
+ result.push(id);
34
+ }
35
+
36
+ return result;
37
+ }
38
+
39
+ /**
40
+ * A lowercased unified ID, custom ID or emoji character, resolved to a
41
+ * known render identity (see resolveSuggestedRenderIds).
42
+ */
43
+ export function resolveEntryId(
44
+ id: string,
45
+ lookup: (normalizedId: string) => DataEmoji | undefined,
46
+ ): string | undefined {
47
+ if (!id) {
48
+ return undefined;
49
+ }
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;
57
+ }
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
+ if (!record) {
86
+ return false;
87
+ }
88
+ return (
89
+ record[EmojiProperties.unified] === id ||
90
+ (record[EmojiProperties.variations] ?? []).includes(id)
91
+ );
92
+ }
package/src/env.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ // Build-time environment flag, replaced or provided by the consumer's
2
+ // bundler/runtime. Declared minimally so the library does not depend on
3
+ // Node's type definitions.
4
+ declare const process: { env: { NODE_ENV?: string } };
@@ -1,13 +1,13 @@
1
1
  import { GetEmojiUrl } from '../components/emoji/BaseEmojiProps';
2
2
  import { DataEmoji } from '../dataUtils/DataTypes';
3
3
  import { emojiUnified, emojiVariations } from '../dataUtils/emojiUtils';
4
- import { EmojiStyle } from '../types/exposedTypes';
4
+ import { EmojiStyle, EmojiStyleValue } from '../types/exposedTypes';
5
5
  import { Dimensions } from '../virtualization/virtualizationHelpers';
6
6
 
7
7
  // eslint-disable-next-line max-params
8
8
  export function preloadEmojiIfNeeded(
9
9
  emoji: undefined | DataEmoji,
10
- emojiStyles: EmojiStyle,
10
+ emojiStyles: EmojiStyleValue,
11
11
  scrollTop: number,
12
12
  clientHeight: number,
13
13
  topOffset: number,
@@ -51,7 +51,7 @@ export function preloadEmojiIfNeeded(
51
51
  export function preloadEmoji(
52
52
  getEmojiUrl: GetEmojiUrl,
53
53
  emoji: undefined | DataEmoji,
54
- emojiStyle: EmojiStyle,
54
+ emojiStyle: EmojiStyleValue,
55
55
  ): void {
56
56
  if (!emoji) {
57
57
  return;
@@ -24,6 +24,7 @@ export function useActiveCategoryScrollDetection({
24
24
  useEffect(() => {
25
25
  const visibleCategories = new Map<string, number>();
26
26
  const intersectingCategories = new Map<string, boolean>();
27
+ const targets = new Map<string, Element>();
27
28
  const bodyRef = BodyRef.current;
28
29
  const observer = new IntersectionObserver(
29
30
  (entries) => {
@@ -40,6 +41,7 @@ export function useActiveCategoryScrollDetection({
40
41
 
41
42
  visibleCategories.set(id, entry.intersectionRatio);
42
43
  intersectingCategories.set(id, entry.isIntersecting);
44
+ targets.set(id, entry.target);
43
45
  }
44
46
 
45
47
  const ratios = Array.from(visibleCategories);
@@ -54,8 +56,15 @@ export function useActiveCategoryScrollDetection({
54
56
  return setActiveCategory(lastCategory[0]);
55
57
  }
56
58
 
59
+ const viewportTop = bodyRef.getBoundingClientRect().top;
57
60
  for (const [id, ratio] of ratios) {
58
- if (ratio) {
61
+ // Fractional offsets can leave less than a pixel of the preceding
62
+ // section intersecting after a jump. It must not steal the tab
63
+ // highlight from the section aligned with the viewport top.
64
+ if (
65
+ ratio &&
66
+ targets.get(id)!.getBoundingClientRect().bottom > viewportTop + 1
67
+ ) {
59
68
  setActiveCategory(id);
60
69
  break;
61
70
  }
@@ -1,35 +1,87 @@
1
1
  import * as React from 'react';
2
2
 
3
- import { EmojiButtonSelector } from '../DomUtils/selectors';
3
+ import { asSelectors, ClassNames } from '../DomUtils/classNames';
4
+ import {
5
+ elementBorderBoxHeight,
6
+ EmojiButtonSelector,
7
+ } from '../DomUtils/selectors';
4
8
  import {
5
9
  useEmojiListRef,
6
10
  usePickerMainRef,
7
11
  } from '../components/context/ElementRefContext';
8
12
  import {
13
+ useNavigationRegistry,
9
14
  useReactionsModeState,
10
15
  useVisibleCategoriesState,
11
16
  useEmojiSizeState,
12
17
  } from '../components/context/PickerContext';
18
+ import { useColumnsConfig } from '../config/useConfig';
19
+
20
+ import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
13
21
 
14
22
  const EMOJI_SIZE_DEFAULT = 40;
15
23
 
24
+ // Rows live inside the category content box, not the full list:
25
+ // category margins/padding shrink the usable width, and dividing
26
+ // the raw list width would overcount columns so the last emoji in
27
+ // every row spills past the content edge (into the scrollbar).
28
+ // Measure a rendered, visible content box when one exists; fall back
29
+ // to the list width (identical when categories carry no horizontal
30
+ // inset). Hidden categories report zero width and are skipped.
31
+ function firstVisibleContentWidth(
32
+ listEl: HTMLElement,
33
+ fallbackWidth: number,
34
+ ): number {
35
+ const contentBoxes = listEl.querySelectorAll(
36
+ asSelectors(ClassNames.categoryContent),
37
+ );
38
+ for (const box of Array.from(contentBoxes)) {
39
+ const width = (box as HTMLElement).clientWidth;
40
+ if (width > 0) {
41
+ return width;
42
+ }
43
+ }
44
+ return fallbackWidth;
45
+ }
46
+
47
+ // A column-count change obsoletes rows computed by pending keyboard
48
+ // navigation.
49
+ function trackColumnCount(
50
+ previousRef: React.MutableRefObject<number | undefined>,
51
+ emojisPerRow: number,
52
+ registry: { invalidate(): void },
53
+ ): void {
54
+ if (
55
+ previousRef.current !== undefined &&
56
+ previousRef.current !== emojisPerRow
57
+ ) {
58
+ registry.invalidate();
59
+ }
60
+ previousRef.current = emojisPerRow;
61
+ }
62
+
16
63
  export function useCategoryHeight(emojiCount: number):
17
64
  | {
18
65
  categoryHeight: number;
19
66
  emojisPerRow: number;
20
67
  emojiSize: number;
68
+ rowWidth: number;
21
69
  }
22
70
  | undefined {
23
71
  const EmojiListRef = useEmojiListRef();
24
72
  const [isReactionsMode] = useReactionsModeState();
25
73
  const PickerMainRef = usePickerMainRef();
26
- const emojiSizeRef = React.useRef<number | undefined>();
74
+ const emojiSizeRef = React.useRef<number | undefined>(undefined);
75
+ const emojisPerRowRef = React.useRef<number | undefined>(undefined);
76
+ const registry = useNavigationRegistry();
27
77
  const [visibleCategories] = useVisibleCategoriesState();
28
78
  const [emojiSizeFromContext] = useEmojiSizeState();
79
+ const columns = useColumnsConfig();
29
80
  const [dimensions, setDimensions] = React.useState<{
30
81
  categoryHeight: number;
31
82
  emojisPerRow: number;
32
83
  emojiSize: number;
84
+ rowWidth: number;
33
85
  }>();
34
86
 
35
87
  // Helper to compute and store dimensions based on current DOM
@@ -41,7 +93,8 @@ export function useCategoryHeight(emojiCount: number):
41
93
  EmojiButtonSelector,
42
94
  ) as HTMLElement | null;
43
95
 
44
- const measured = emojiElement?.clientHeight;
96
+ // Border-box geometry includes design-library button borders.
97
+ const measured = elementBorderBoxHeight(emojiElement);
45
98
  if (measured) {
46
99
  emojiSizeRef.current = measured;
47
100
  }
@@ -54,15 +107,20 @@ export function useCategoryHeight(emojiCount: number):
54
107
 
55
108
  if (pickerWidth === 0 || emojiSize === 0) return;
56
109
 
57
- const emojisPerRow = Math.max(1, Math.floor(pickerWidth / emojiSize));
110
+ const rowWidth = firstVisibleContentWidth(listEl, pickerWidth);
111
+ const fit = Math.max(1, Math.floor(rowWidth / emojiSize));
112
+ const emojisPerRow = columns ? Math.min(columns, fit) : fit;
58
113
  const rowCount = Math.ceil(emojiCount / emojisPerRow);
59
114
  const categoryHeight = rowCount * emojiSize;
60
115
 
61
- setDimensions({ categoryHeight, emojisPerRow, emojiSize });
62
- }, [EmojiListRef, emojiCount, emojiSizeFromContext]);
116
+ trackColumnCount(emojisPerRowRef, emojisPerRow, registry);
117
+ setDimensions({ categoryHeight, emojisPerRow, emojiSize, rowWidth });
118
+ }, [EmojiListRef, emojiCount, emojiSizeFromContext, registry, columns]);
63
119
 
64
- // Recompute on data-count changes and when reactions mode toggles
65
- React.useEffect(() => {
120
+ // Restore measured row heights in the same commit as the filtered
121
+ // contents. A passive effect leaves one frame of stale offsets that
122
+ // category navigation can observe immediately after clearing search.
123
+ useIsomorphicLayoutEffect(() => {
66
124
  computeAndSetDimensions();
67
125
  }, [
68
126
  emojiCount,
@@ -71,6 +129,22 @@ export function useCategoryHeight(emojiCount: number):
71
129
  visibleCategories.length,
72
130
  ]);
73
131
 
132
+ // Fluid layouts (width: 100%, sheets, docks) resize without a CSS
133
+ // transition: recompute columns when the list's width changes.
134
+ React.useEffect(() => {
135
+ const listEl = EmojiListRef.current;
136
+ if (!listEl || typeof ResizeObserver === 'undefined') return;
137
+ let lastWidth = listEl.clientWidth;
138
+ const observer = new ResizeObserver(() => {
139
+ const width = listEl.clientWidth;
140
+ if (width === lastWidth) return;
141
+ lastWidth = width;
142
+ computeAndSetDimensions();
143
+ });
144
+ observer.observe(listEl);
145
+ return () => observer.disconnect();
146
+ }, [EmojiListRef, computeAndSetDimensions]);
147
+
74
148
  // Listen to transitionend on the picker root (where height transition occurs)
75
149
  React.useEffect(() => {
76
150
  const rootEl = PickerMainRef.current;
@@ -0,0 +1,86 @@
1
+ /* global process: readonly */
2
+ import * as React from 'react';
3
+
4
+ import type { CustomEmoji } from '../config/customEmojiConfig';
5
+ import { defaultEmojiDataOrEmpty } from '../data/defaultEmojiData';
6
+ import type { EmojiData } from '../types/exposedTypes';
7
+
8
+ interface IdentityTracker {
9
+ prev: unknown;
10
+ consecutiveChanges: number;
11
+ warned: boolean;
12
+ }
13
+
14
+ function trackIdentity(
15
+ tracker: IdentityTracker,
16
+ value: unknown,
17
+ isDefault: boolean,
18
+ message: string,
19
+ ): void {
20
+ if (isDefault) {
21
+ tracker.prev = value;
22
+ tracker.consecutiveChanges = 0;
23
+ return;
24
+ }
25
+ if (tracker.prev !== undefined && tracker.prev !== value) {
26
+ tracker.consecutiveChanges += 1;
27
+ } else if (tracker.prev === value) {
28
+ tracker.consecutiveChanges = 0;
29
+ }
30
+ tracker.prev = value;
31
+ if (tracker.consecutiveChanges >= 3 && !tracker.warned) {
32
+ tracker.warned = true;
33
+ // eslint-disable-next-line no-console
34
+ console.warn(message);
35
+ }
36
+ }
37
+
38
+ function freshTracker(): IdentityTracker {
39
+ return { prev: undefined, consecutiveChanges: 0, warned: false };
40
+ }
41
+
42
+ /**
43
+ * Dev-only diagnostic.
44
+ *
45
+ * Identity caching only helps when callers keep data props referentially
46
+ * stable. If a non-default `emojiData` (or `customEmojis`) changes identity
47
+ * on three consecutive committed renders, warn once that repeated identity
48
+ * churn defeats prepared-data caching. Behavior stays correct; this is
49
+ * diagnostic only. A single/occasional replacement (e.g. locale change)
50
+ * must not warn. No deep-compare or clone is performed.
51
+ */
52
+ export const useDataIdentityStabilityWarning: (
53
+ emojiData: EmojiData | undefined,
54
+ customEmojis: CustomEmoji[] | undefined,
55
+ ) => void =
56
+ // Development only: production bundles drop the trackers entirely.
57
+ process.env.NODE_ENV === 'production'
58
+ ? () => undefined
59
+ : useIdentityChurnWarning;
60
+
61
+ function useIdentityChurnWarning(
62
+ emojiData: EmojiData | undefined,
63
+ customEmojis: CustomEmoji[] | undefined,
64
+ ): void {
65
+ const emojiState = React.useRef<IdentityTracker>(freshTracker());
66
+ const customState = React.useRef<IdentityTracker>(freshTracker());
67
+
68
+ React.useEffect(() => {
69
+ trackIdentity(
70
+ emojiState.current,
71
+ emojiData,
72
+ !emojiData || emojiData === defaultEmojiDataOrEmpty(),
73
+ '[emoji-picker-react] `emojiData` changed identity on three consecutive renders. ' +
74
+ 'Memoize/reuse the dataset object so the prepared-data cache can be shared. ' +
75
+ 'Behavior remains correct; caching is only defeated by identity churn.',
76
+ );
77
+ trackIdentity(
78
+ customState.current,
79
+ customEmojis,
80
+ !customEmojis || customEmojis.length === 0,
81
+ '[emoji-picker-react] `customEmojis` changed identity on three consecutive renders. ' +
82
+ 'Memoize/reuse the array so custom-emoji derivation can be cached. ' +
83
+ 'Behavior remains correct; caching is only defeated by identity churn.',
84
+ );
85
+ });
86
+ }
@@ -1,4 +1,4 @@
1
- import { useRef, useState } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
2
 
3
3
  export function useDebouncedState<T>(
4
4
  initialValue: T,
@@ -7,6 +7,18 @@ export function useDebouncedState<T>(
7
7
  const [state, setState] = useState<T>(initialValue);
8
8
  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
9
9
 
10
+ // A pending trailing edge must never fire after unmount (React warns
11
+ // about the post-unmount setState and the work is wasted).
12
+ useEffect(
13
+ () => () => {
14
+ if (timer.current) {
15
+ clearTimeout(timer.current);
16
+ timer.current = null;
17
+ }
18
+ },
19
+ [],
20
+ );
21
+
10
22
  function debouncedSetState(value: T) {
11
23
  return new Promise<T>((resolve) => {
12
24
  if (timer.current) {
@@ -1,52 +1,36 @@
1
- import { useRef, useMemo } from 'react';
2
-
3
- import { usePickerDataContext } from '../components/context/PickerDataContext';
4
- import { useEmojiVersionConfig } from '../config/useConfig';
1
+ import {
2
+ useActiveSkinToneState,
3
+ useNativeEmojiSupport,
4
+ } from '../components/context/PickerContext';
5
+ import { useEmojiVersionConfig, useUnicodeToHide } from '../config/useConfig';
5
6
  import { DataEmoji } from '../dataUtils/DataTypes';
6
7
  import {
7
8
  addedIn,
8
9
  emojiUnified,
9
10
  unifiedWithoutSkinTone,
10
11
  } from '../dataUtils/emojiUtils';
11
-
12
- import { useIsUnicodeHidden } from './useHideEmojisByUniocode';
13
-
14
- export function useDisallowedEmojis() {
15
- const DisallowedEmojisRef = useRef<Record<string, boolean>>({});
16
- const emojiVersionConfig = useEmojiVersionConfig();
17
- const { allEmojis } = usePickerDataContext();
18
-
19
- return useMemo(() => {
20
- const emojiVersion = parseFloat(`${emojiVersionConfig}`);
21
-
22
- if (!emojiVersionConfig || Number.isNaN(emojiVersion)) {
23
- return DisallowedEmojisRef.current;
24
- }
25
-
26
- return allEmojis.reduce((disallowedEmojis, emoji) => {
27
- if (addedInNewerVersion(emoji, emojiVersion)) {
28
- disallowedEmojis[emojiUnified(emoji)] = true;
29
- }
30
-
31
- return disallowedEmojis;
32
- }, DisallowedEmojisRef.current);
33
- }, [emojiVersionConfig, allEmojis]);
34
- }
12
+ import { isNativeEmojiSupported } from '../dataUtils/nativeEmojiSupport';
13
+ import { isCustomEmoji } from '../typeRefinements/typeRefinements';
35
14
 
36
15
  export function useIsEmojiDisallowed() {
37
- const disallowedEmojis = useDisallowedEmojis();
38
- const isUnicodeHidden = useIsUnicodeHidden();
39
-
40
- return function isEmojiDisallowed(emoji: DataEmoji) {
41
- const unified = unifiedWithoutSkinTone(emojiUnified(emoji));
42
-
43
- return Boolean(disallowedEmojis[unified] || isUnicodeHidden(unified));
16
+ const configured = parseFloat(useEmojiVersionConfig() || '');
17
+ const nativeSupport = useNativeEmojiSupport();
18
+ // A configured cap restricts native support; it never overrides it.
19
+ const cap = Number.isNaN(configured) ? Infinity : configured;
20
+ const unicodeToHide = useUnicodeToHide();
21
+ const [activeSkinTone] = useActiveSkinToneState();
22
+
23
+ return function isEmojiDisallowed(
24
+ emoji: DataEmoji,
25
+ renderedUnified = emojiUnified(emoji, activeSkinTone),
26
+ ) {
27
+ if (unicodeToHide.has(unifiedWithoutSkinTone(emojiUnified(emoji))))
28
+ return true;
29
+ if (isCustomEmoji(emoji)) return false;
30
+ const version = addedIn(emoji);
31
+ return (
32
+ version > cap ||
33
+ !isNativeEmojiSupported(nativeSupport, renderedUnified, version)
34
+ );
44
35
  };
45
36
  }
46
-
47
- function addedInNewerVersion(
48
- emoji: DataEmoji,
49
- supportedLevel: number,
50
- ): boolean {
51
- return addedIn(emoji) > supportedLevel;
52
- }