emoji-picker-react 4.22.3 → 5.0.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.
Files changed (409) hide show
  1. package/README.md +331 -49
  2. package/dist/DomUtils/categoryNameFromDom.d.ts +1 -1
  3. package/dist/DomUtils/classNames.d.ts +23 -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 +15 -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/skinToneVariations.js +2 -2
  183. package/dist/data/skinToneVariations.ts +8 -7
  184. package/dist/data-core/pickerData.d.ts +12 -0
  185. package/dist/data-core/prepare.d.ts +23 -0
  186. package/dist/data-core/search.d.ts +13 -0
  187. package/dist/data-core/types.d.ts +23 -0
  188. package/dist/data.d.mts +72 -0
  189. package/dist/data.d.ts +72 -0
  190. package/dist/dataUtils/DataTypes.d.ts +23 -19
  191. package/dist/dataUtils/emojiSelectors.d.ts +15 -16
  192. package/dist/dataUtils/emojiUtils.d.ts +1 -12
  193. package/dist/dataUtils/nativeEmojiSequenceSupport.d.ts +10 -0
  194. package/dist/dataUtils/nativeEmojiSupport.d.ts +40 -0
  195. package/dist/dataUtils/parseNativeEmoji.d.ts +1 -1
  196. package/dist/dataUtils/suggested.d.ts +11 -11
  197. package/dist/dataUtils/suggestedEmojis.d.ts +7 -0
  198. package/dist/esm/chunks/chunk-7ZGTUZFB.mjs +8025 -0
  199. package/dist/esm/chunks/chunk-ACXAQEKG.mjs +149 -0
  200. package/dist/esm/chunks/chunk-F2KS7DCA.mjs +6735 -0
  201. package/dist/esm/chunks/chunk-L5W4PFRJ.mjs +14 -0
  202. package/dist/esm/chunks/chunk-LO4TPPLH.mjs +33 -0
  203. package/dist/esm/chunks/emojis-6L3GRBAP.mjs +7 -0
  204. package/dist/esm/data.mjs +11 -0
  205. package/dist/esm/index.mjs +279 -0
  206. package/dist/esm/primitives/index.mjs +8 -0
  207. package/dist/hooks/preloadEmoji.d.ts +5 -9
  208. package/dist/hooks/useActiveCategoryScrollDetection.d.ts +11 -11
  209. package/dist/hooks/useCategoryHeight.d.ts +7 -5
  210. package/dist/hooks/useCloseAllOpenToggles.d.ts +2 -2
  211. package/dist/hooks/useDataIdentityStabilityWarning.d.ts +13 -0
  212. package/dist/hooks/useDebouncedState.d.ts +1 -1
  213. package/dist/hooks/useDisallowMouseMove.d.ts +4 -4
  214. package/dist/hooks/useDisallowedEmojis.d.ts +2 -3
  215. package/dist/hooks/useEmojiPreviewEvents.d.ts +3 -3
  216. package/dist/hooks/useEmojiVirtualization.d.ts +22 -17
  217. package/dist/hooks/useFilter.d.ts +10 -16
  218. package/dist/hooks/useFocus.d.ts +3 -3
  219. package/dist/hooks/useInitialLoad.d.ts +2 -2
  220. package/dist/hooks/useIsEmojiHidden.d.ts +8 -8
  221. package/dist/hooks/useIsMounted.d.ts +1 -1
  222. package/dist/hooks/useIsSearchMode.d.ts +1 -1
  223. package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -2
  224. package/dist/hooks/useKeyboardNavigation.d.ts +3 -1
  225. package/dist/hooks/useMouseDownHandlers.d.ts +8 -4
  226. package/dist/hooks/useOnFocus.d.ts +1 -1
  227. package/dist/hooks/useOnScroll.d.ts +2 -2
  228. package/dist/hooks/useReactionsFocus.d.ts +1 -0
  229. package/dist/hooks/useRegisterRegion.d.ts +24 -0
  230. package/dist/hooks/useResolvedEmojiData.d.ts +37 -0
  231. package/dist/hooks/useScrollCategoryIntoView.d.ts +1 -1
  232. package/dist/hooks/useSearchController.d.ts +17 -0
  233. package/dist/hooks/useSearchResults.d.ts +8 -0
  234. package/dist/hooks/useSetVariationPicker.d.ts +2 -2
  235. package/dist/hooks/useShouldHideCustomEmojis.d.ts +1 -1
  236. package/dist/hooks/useShouldShowSkinTonePicker.d.ts +4 -4
  237. package/dist/index.d.mts +376 -0
  238. package/dist/index.d.ts +376 -9
  239. package/dist/index.js +2 -8
  240. package/dist/primitives/CategoryNav.d.ts +4 -0
  241. package/dist/primitives/Empty.d.ts +6 -0
  242. package/dist/primitives/List.d.ts +4 -0
  243. package/dist/primitives/LoadError.d.ts +8 -0
  244. package/dist/primitives/Loading.d.ts +4 -0
  245. package/dist/primitives/Panel.d.ts +3 -0
  246. package/dist/primitives/Preview.d.ts +3 -0
  247. package/dist/primitives/Reactions.d.ts +2 -0
  248. package/dist/primitives/Root.d.ts +26 -0
  249. package/dist/primitives/Search.d.ts +5 -0
  250. package/dist/primitives/SearchInput.d.ts +3 -0
  251. package/dist/primitives/SkinTone.d.ts +4 -0
  252. package/dist/primitives/Viewport.d.ts +19 -0
  253. package/dist/primitives/appearance.d.ts +3 -0
  254. package/dist/primitives/components.d.ts +27 -0
  255. package/dist/primitives/hooks.d.ts +48 -0
  256. package/dist/primitives/index.d.mts +561 -0
  257. package/dist/primitives/index.d.ts +561 -0
  258. package/dist/primitives/index.js +2 -0
  259. package/dist/primitives/nativeProps.d.ts +15 -0
  260. package/dist/primitives/scope.d.ts +13 -0
  261. package/dist/primitives/structuralStyles.d.ts +16 -0
  262. package/dist/primitives/tokens.d.ts +18 -0
  263. package/dist/primitives/types.d.ts +85 -0
  264. package/dist/state/activeEmojiStore.d.ts +7 -0
  265. package/dist/state/navigationRegistry.d.ts +61 -0
  266. package/dist/state/regionTraversal.d.ts +20 -0
  267. package/dist/typeRefinements/typeRefinements.d.ts +6 -6
  268. package/dist/types/exposedTypes.d.ts +16 -6
  269. package/dist/types/exposedTypes.js +5 -0
  270. package/dist/virtualization/virtualizationHelpers.d.ts +22 -18
  271. package/llms-full.txt +3683 -0
  272. package/llms.txt +66 -480
  273. package/package.json +180 -66
  274. package/src/DomUtils/categoryNameFromDom.ts +1 -1
  275. package/src/DomUtils/classNames.ts +1 -4
  276. package/src/DomUtils/elementPositionInRow.ts +28 -6
  277. package/src/DomUtils/eventBelongsToPicker.ts +17 -0
  278. package/src/DomUtils/getActiveElement.ts +7 -1
  279. package/src/DomUtils/isJsdom.ts +12 -0
  280. package/src/DomUtils/isRtl.ts +5 -0
  281. package/src/DomUtils/keyboardNavigation.ts +164 -144
  282. package/src/DomUtils/scrollTo.ts +20 -18
  283. package/src/DomUtils/selectors.ts +30 -127
  284. package/src/EmojiPickerReact.tsx +155 -48
  285. package/src/Stylesheet/DedupedStyle.tsx +80 -0
  286. package/src/Stylesheet/stylesheet.tsx +128 -59
  287. package/src/components/Layout/Flex.tsx +12 -11
  288. package/src/components/Reactions/BtnPlus.tsx +61 -51
  289. package/src/components/Reactions/Reactions.tsx +113 -55
  290. package/src/components/atoms/Button.tsx +18 -15
  291. package/src/components/body/EmojiCategory.tsx +77 -40
  292. package/src/components/body/EmojiList.tsx +107 -44
  293. package/src/components/body/EmojiVariationPicker.tsx +113 -66
  294. package/src/components/body/MeasureEmoji.tsx +9 -2
  295. package/src/components/body/listComponents.tsx +79 -0
  296. package/src/components/context/ElementRefContext.tsx +46 -35
  297. package/src/components/context/PickerConfigContext.tsx +55 -40
  298. package/src/components/context/PickerContext.tsx +567 -92
  299. package/src/components/context/PickerDataContext.tsx +114 -99
  300. package/src/components/emoji/BaseEmojiProps.ts +5 -4
  301. package/src/components/emoji/ClickableEmojiButton.tsx +148 -63
  302. package/src/components/emoji/Emoji.tsx +24 -2
  303. package/src/components/emoji/EmojiImg.tsx +13 -12
  304. package/src/components/emoji/ExportedEmoji.tsx +5 -4
  305. package/src/components/emoji/NativeEmoji.tsx +19 -16
  306. package/src/components/emoji/ViewOnlyEmoji.tsx +55 -30
  307. package/src/components/emoji/emojiStyles.ts +12 -11
  308. package/src/components/footer/Preview.tsx +86 -91
  309. package/src/components/header/Header.tsx +21 -6
  310. package/src/components/header/Search/BtnClearSearch.tsx +69 -55
  311. package/src/components/header/Search/IcnSearch.tsx +21 -21
  312. package/src/components/header/Search/Search.tsx +79 -137
  313. package/src/components/header/SkinTonePicker/BtnSkinToneVariation.tsx +99 -62
  314. package/src/components/header/SkinTonePicker/SkinTonePicker.tsx +84 -46
  315. package/src/components/icons/svgIcons.ts +17 -0
  316. package/src/components/main/PickerMainBehaviors.tsx +163 -0
  317. package/src/components/main/defaultAppearance.tsx +189 -0
  318. package/src/components/main/labelHeight.ts +5 -0
  319. package/src/components/navigation/CategoryButton.tsx +83 -57
  320. package/src/components/navigation/CategoryNavIcon.tsx +73 -194
  321. package/src/components/navigation/CategoryNavigation.tsx +118 -46
  322. package/src/config/categoryConfig.ts +7 -3
  323. package/src/config/cdnUrls.ts +11 -21
  324. package/src/config/compareConfig.ts +128 -30
  325. package/src/config/config.ts +228 -24
  326. package/src/config/defaultPickerConfiguration.ts +7 -0
  327. package/src/config/mutableConfig.ts +44 -20
  328. package/src/config/useConfig.ts +130 -37
  329. package/src/data/defaultEmojiData.ts +49 -0
  330. package/src/data/registerDefaultEmojiData.ts +8 -0
  331. package/src/data/skinToneVariations.ts +8 -7
  332. package/src/data-core/pickerData.ts +171 -0
  333. package/src/data-core/prepare.ts +163 -0
  334. package/src/data-core/search.ts +122 -0
  335. package/src/data-core/types.ts +36 -0
  336. package/src/data.ts +10 -0
  337. package/src/dataUtils/DataTypes.ts +4 -0
  338. package/src/dataUtils/emojiSelectors.ts +59 -63
  339. package/src/dataUtils/emojiUtils.ts +17 -74
  340. package/src/dataUtils/nativeEmojiSequenceSupport.ts +116 -0
  341. package/src/dataUtils/nativeEmojiSupport.ts +160 -0
  342. package/src/dataUtils/parseNativeEmoji.ts +3 -4
  343. package/src/dataUtils/suggested.ts +8 -4
  344. package/src/dataUtils/suggestedEmojis.ts +92 -0
  345. package/src/env.d.ts +4 -0
  346. package/src/hooks/preloadEmoji.ts +2 -47
  347. package/src/hooks/useActiveCategoryScrollDetection.ts +28 -16
  348. package/src/hooks/useCategoryHeight.ts +116 -10
  349. package/src/hooks/useDataIdentityStabilityWarning.ts +86 -0
  350. package/src/hooks/useDebouncedState.ts +13 -1
  351. package/src/hooks/useDisallowedEmojis.ts +31 -42
  352. package/src/hooks/useEmojiPreviewEvents.ts +43 -10
  353. package/src/hooks/useEmojiVirtualization.tsx +112 -52
  354. package/src/hooks/useFilter.ts +92 -132
  355. package/src/hooks/useFocus.ts +14 -24
  356. package/src/hooks/useIsEmojiHidden.ts +22 -14
  357. package/src/hooks/useKeyboardNavigation.ts +412 -432
  358. package/src/hooks/useMouseDownHandlers.ts +91 -16
  359. package/src/hooks/useOnFocus.ts +12 -3
  360. package/src/hooks/useOnScroll.ts +18 -2
  361. package/src/hooks/useReactionsFocus.ts +118 -0
  362. package/src/hooks/useRegisterRegion.ts +92 -0
  363. package/src/hooks/useResolvedEmojiData.ts +200 -0
  364. package/src/hooks/useScrollCategoryIntoView.ts +74 -12
  365. package/src/hooks/useSearchController.ts +369 -0
  366. package/src/hooks/useSearchResults.ts +51 -0
  367. package/src/hooks/useSetVariationPicker.ts +4 -1
  368. package/src/index.tsx +67 -5
  369. package/src/primitives/CategoryNav.tsx +40 -0
  370. package/src/primitives/Empty.tsx +68 -0
  371. package/src/primitives/List.tsx +61 -0
  372. package/src/primitives/LoadError.tsx +38 -0
  373. package/src/primitives/Loading.tsx +60 -0
  374. package/src/primitives/Panel.tsx +47 -0
  375. package/src/primitives/Preview.tsx +38 -0
  376. package/src/primitives/Reactions.tsx +2 -0
  377. package/src/primitives/Root.tsx +398 -0
  378. package/src/primitives/Search.tsx +41 -0
  379. package/src/primitives/SearchInput.tsx +110 -0
  380. package/src/primitives/SkinTone.tsx +76 -0
  381. package/src/primitives/Viewport.tsx +204 -0
  382. package/src/primitives/appearance.tsx +7 -0
  383. package/src/primitives/components.tsx +30 -0
  384. package/src/primitives/hooks.ts +165 -0
  385. package/src/primitives/index.ts +94 -0
  386. package/src/primitives/nativeProps.ts +90 -0
  387. package/src/primitives/scope.tsx +109 -0
  388. package/src/primitives/structuralStyles.tsx +136 -0
  389. package/src/primitives/tokens.ts +150 -0
  390. package/src/primitives/types.ts +192 -0
  391. package/src/state/activeEmojiStore.ts +39 -0
  392. package/src/state/navigationRegistry.ts +209 -0
  393. package/src/state/regionTraversal.ts +116 -0
  394. package/src/svg.d.ts +2 -2
  395. package/src/types/exposedTypes.ts +28 -1
  396. package/src/virtualization/virtualizationHelpers.ts +29 -8
  397. package/dist/components/body/Body.d.ts +0 -2
  398. package/dist/components/main/PickerMain.d.ts +0 -7
  399. package/dist/dataUtils/alphaNumericEmojiIndex.d.ts +0 -5
  400. package/dist/emoji-picker-react.cjs.development.js +0 -31413
  401. package/dist/emoji-picker-react.cjs.development.js.map +0 -1
  402. package/dist/emoji-picker-react.cjs.production.min.js +0 -2
  403. package/dist/emoji-picker-react.cjs.production.min.js.map +0 -1
  404. package/dist/emoji-picker-react.esm.js +0 -31411
  405. package/dist/emoji-picker-react.esm.js.map +0 -1
  406. package/dist/hooks/useHideEmojisByUniocode.d.ts +0 -1
  407. package/src/components/body/Body.tsx +0 -43
  408. package/src/components/main/PickerMain.tsx +0 -249
  409. 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,6 +1,5 @@
1
1
  export function parseNativeEmoji(unified: string): string {
2
- return unified
3
- .split('-')
4
- .map((hex) => String.fromCodePoint(parseInt(hex, 16)))
5
- .join('');
2
+ return String.fromCodePoint(
3
+ ...unified.split('-').map((hex) => parseInt(hex, 16)),
4
+ );
6
5
  }
@@ -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,57 +1,12 @@
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';
5
- import { Dimensions } from '../virtualization/virtualizationHelpers';
6
-
7
- // eslint-disable-next-line max-params
8
- export function preloadEmojiIfNeeded(
9
- emoji: undefined | DataEmoji,
10
- emojiStyles: EmojiStyle,
11
- scrollTop: number,
12
- clientHeight: number,
13
- topOffset: number,
14
- style: { top: number } | undefined,
15
- dimensions: Dimensions,
16
- getEmojiUrl: GetEmojiUrl,
17
- ): void {
18
- if (!emoji) {
19
- return;
20
- }
21
-
22
- if (emojiStyles === EmojiStyle.NATIVE) {
23
- return;
24
- }
25
-
26
- const unified = emojiUnified(emoji);
27
-
28
- if (preloadedEmojs.has(unified)) {
29
- return;
30
- }
31
-
32
- // preload only if the emoji is a few rows below the viewport
33
- if (!style || !dimensions) {
34
- return;
35
- }
36
-
37
- setTimeout(() => {
38
- const emojiTop = topOffset + style.top;
39
- const viewportBottom = scrollTop + clientHeight;
40
-
41
- const isJustBelowViewport =
42
- emojiTop >= viewportBottom &&
43
- emojiTop < viewportBottom + dimensions.emojiSize * 2;
44
-
45
- if (isJustBelowViewport) {
46
- preloadEmoji(getEmojiUrl, emoji, emojiStyles);
47
- }
48
- });
49
- }
4
+ import { EmojiStyleValue } from '../types/exposedTypes';
50
5
 
51
6
  export function preloadEmoji(
52
7
  getEmojiUrl: GetEmojiUrl,
53
8
  emoji: undefined | DataEmoji,
54
- emojiStyle: EmojiStyle,
9
+ emojiStyle: EmojiStyleValue,
55
10
  ): void {
56
11
  if (!emoji) {
57
12
  return;
@@ -1,9 +1,12 @@
1
1
  import { useEffect } from 'react';
2
2
 
3
3
  import { categoryNameFromDom } from '../DomUtils/categoryNameFromDom';
4
- import { asSelectors, ClassNames } from '../DomUtils/classNames';
4
+ import { ClassNames } from '../DomUtils/classNames';
5
5
  import { useBodyRef } from '../components/context/ElementRefContext';
6
- import { CategoriesConfig } from '../config/categoryConfig';
6
+ import {
7
+ CategoriesConfig,
8
+ categoryIdFromCategoryConfig,
9
+ } from '../config/categoryConfig';
7
10
 
8
11
  export function useActiveCategoryScrollDetection({
9
12
  setActiveCategory,
@@ -22,8 +25,11 @@ export function useActiveCategoryScrollDetection({
22
25
  const BodyRef = useBodyRef();
23
26
 
24
27
  useEffect(() => {
25
- const visibleCategories = new Map<string, number>();
26
- const intersectingCategories = new Map<string, boolean>();
28
+ if (typeof IntersectionObserver === 'undefined') {
29
+ setVisibleCategories(categories.map(categoryIdFromCategoryConfig));
30
+ return;
31
+ }
32
+ const observed = new Map<string, IntersectionObserverEntry>();
27
33
  const bodyRef = BodyRef.current;
28
34
  const observer = new IntersectionObserver(
29
35
  (entries) => {
@@ -38,24 +44,32 @@ export function useActiveCategoryScrollDetection({
38
44
  continue;
39
45
  }
40
46
 
41
- visibleCategories.set(id, entry.intersectionRatio);
42
- intersectingCategories.set(id, entry.isIntersecting);
47
+ observed.set(id, entry);
43
48
  }
44
49
 
45
- const ratios = Array.from(visibleCategories);
50
+ const ratios = Array.from(observed);
46
51
  const visibleCats = ratios
47
- .filter(([id, ratio]) => ratio > 0 || intersectingCategories.get(id))
52
+ .filter(
53
+ ([, entry]) => entry.intersectionRatio > 0 || entry.isIntersecting,
54
+ )
48
55
  .map(([id]) => id);
49
56
 
50
57
  setVisibleCategories(visibleCats);
51
58
  const lastCategory = ratios[ratios.length - 1];
52
59
 
53
- if (lastCategory?.[1] == 1) {
60
+ if (lastCategory?.[1].intersectionRatio === 1) {
54
61
  return setActiveCategory(lastCategory[0]);
55
62
  }
56
63
 
57
- for (const [id, ratio] of ratios) {
58
- if (ratio) {
64
+ const viewportTop = bodyRef.getBoundingClientRect().top;
65
+ for (const [id, entry] of ratios) {
66
+ // Fractional offsets can leave less than a pixel of the preceding
67
+ // section intersecting after a jump. It must not steal the tab
68
+ // highlight from the section aligned with the viewport top.
69
+ if (
70
+ entry.intersectionRatio &&
71
+ entry.target.getBoundingClientRect().bottom > viewportTop + 1
72
+ ) {
59
73
  setActiveCategory(id);
60
74
  break;
61
75
  }
@@ -66,11 +80,9 @@ export function useActiveCategoryScrollDetection({
66
80
  threshold: [0, 1],
67
81
  },
68
82
  );
69
- bodyRef
70
- ?.querySelectorAll(asSelectors(ClassNames.category))
71
- .forEach((el) => {
72
- observer.observe(el);
73
- });
83
+ bodyRef?.querySelectorAll(`.${ClassNames.category}`).forEach((el) => {
84
+ observer.observe(el);
85
+ });
74
86
 
75
87
  return () => {
76
88
  observer.disconnect();
@@ -1,35 +1,94 @@
1
1
  import * as React from 'react';
2
2
 
3
- import { EmojiButtonSelector } from '../DomUtils/selectors';
3
+ import { ClassNames } from '../DomUtils/classNames';
4
4
  import {
5
+ elementBorderBoxHeight,
6
+ EmojiButtonSelector,
7
+ } from '../DomUtils/selectors';
8
+ import {
9
+ useBodyRef,
5
10
  useEmojiListRef,
6
11
  usePickerMainRef,
7
12
  } from '../components/context/ElementRefContext';
8
13
  import {
14
+ useNavigationRegistry,
9
15
  useReactionsModeState,
10
16
  useVisibleCategoriesState,
11
17
  useEmojiSizeState,
12
18
  } from '../components/context/PickerContext';
19
+ import { useColumnsConfig } from '../config/useConfig';
20
+
21
+ import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
13
22
 
14
23
  const EMOJI_SIZE_DEFAULT = 40;
15
24
 
16
- export function useCategoryHeight(emojiCount: number):
25
+ // Rows live inside the category content box, not the full list:
26
+ // category margins/padding shrink the usable width, and dividing
27
+ // the raw list width would overcount columns so the last emoji in
28
+ // every row spills past the content edge (into the scrollbar).
29
+ // Measure a rendered, visible content box when one exists; fall back
30
+ // to the list width (identical when categories carry no horizontal
31
+ // inset). Hidden categories report zero width and are skipped.
32
+ function firstVisibleContentWidth(
33
+ listEl: HTMLElement,
34
+ fallbackWidth: number,
35
+ ): number {
36
+ const contentBoxes = listEl.querySelectorAll(
37
+ `.${ClassNames.categoryContent}`,
38
+ );
39
+ for (const box of Array.from(contentBoxes)) {
40
+ const width = (box as HTMLElement).clientWidth;
41
+ if (width > 0) {
42
+ return width;
43
+ }
44
+ }
45
+ return fallbackWidth;
46
+ }
47
+
48
+ // A column-count change obsoletes rows computed by pending keyboard
49
+ // navigation.
50
+ function trackColumnCount(
51
+ previousRef: React.MutableRefObject<number | undefined>,
52
+ emojisPerRow: number,
53
+ registry: { invalidate(): void },
54
+ ): void {
55
+ if (
56
+ previousRef.current !== undefined &&
57
+ previousRef.current !== emojisPerRow
58
+ ) {
59
+ registry.invalidate();
60
+ }
61
+ previousRef.current = emojisPerRow;
62
+ }
63
+
64
+ export function useCategoryHeight(
65
+ emojiCount: number,
66
+ ready = true,
67
+ ):
17
68
  | {
18
69
  categoryHeight: number;
19
70
  emojisPerRow: number;
20
71
  emojiSize: number;
72
+ rowWidth: number;
73
+ viewportHeight?: number;
21
74
  }
22
75
  | undefined {
23
76
  const EmojiListRef = useEmojiListRef();
77
+ const BodyRef = useBodyRef();
24
78
  const [isReactionsMode] = useReactionsModeState();
25
79
  const PickerMainRef = usePickerMainRef();
26
- const emojiSizeRef = React.useRef<number | undefined>();
80
+ const emojiSizeRef = React.useRef<number | undefined>(undefined);
81
+ const emojisPerRowRef = React.useRef<number | undefined>(undefined);
82
+ const registry = useNavigationRegistry();
27
83
  const [visibleCategories] = useVisibleCategoriesState();
28
84
  const [emojiSizeFromContext] = useEmojiSizeState();
85
+ const columns = useColumnsConfig();
29
86
  const [dimensions, setDimensions] = React.useState<{
30
87
  categoryHeight: number;
31
88
  emojisPerRow: number;
32
89
  emojiSize: number;
90
+ rowWidth: number;
91
+ viewportHeight?: number;
33
92
  }>();
34
93
 
35
94
  // Helper to compute and store dimensions based on current DOM
@@ -41,36 +100,83 @@ export function useCategoryHeight(emojiCount: number):
41
100
  EmojiButtonSelector,
42
101
  ) as HTMLElement | null;
43
102
 
44
- const measured = emojiElement?.clientHeight;
103
+ // Border-box geometry includes design-library button borders.
104
+ const measured = elementBorderBoxHeight(emojiElement);
45
105
  if (measured) {
46
106
  emojiSizeRef.current = measured;
47
107
  }
48
108
  const emojiSize =
49
- emojiSizeFromContext ||
50
109
  measured ||
110
+ emojiSizeFromContext ||
51
111
  emojiSizeRef.current ||
52
112
  EMOJI_SIZE_DEFAULT;
53
113
  const pickerWidth = listEl.clientWidth;
54
114
 
55
115
  if (pickerWidth === 0 || emojiSize === 0) return;
56
116
 
57
- const emojisPerRow = Math.max(1, Math.floor(pickerWidth / emojiSize));
117
+ const rowWidth = firstVisibleContentWidth(listEl, pickerWidth);
118
+ const fit = Math.max(1, Math.floor(rowWidth / emojiSize));
119
+ const emojisPerRow = columns ? Math.min(columns, fit) : fit;
58
120
  const rowCount = Math.ceil(emojiCount / emojisPerRow);
59
121
  const categoryHeight = rowCount * emojiSize;
60
122
 
61
- setDimensions({ categoryHeight, emojisPerRow, emojiSize });
62
- }, [EmojiListRef, emojiCount, emojiSizeFromContext]);
123
+ const next = {
124
+ categoryHeight,
125
+ emojisPerRow,
126
+ emojiSize,
127
+ rowWidth,
128
+ // Virtualization reads the viewport height during render, so a picker
129
+ // that mounts collapsed (reactions first) must re-render once it opens.
130
+ viewportHeight: BodyRef.current?.clientHeight,
131
+ };
63
132
 
64
- // Recompute on data-count changes and when reactions mode toggles
65
- React.useEffect(() => {
133
+ trackColumnCount(emojisPerRowRef, emojisPerRow, registry);
134
+ // Returning `previous` when nothing changed is what stops the layout
135
+ // effect below, which depends on `dimensions`, from looping forever.
136
+ // Both objects come from the literal above, so their keys share an order.
137
+ setDimensions((previous) =>
138
+ JSON.stringify(previous) === JSON.stringify(next) ? previous : next,
139
+ );
140
+ }, [
141
+ EmojiListRef,
142
+ BodyRef,
143
+ emojiCount,
144
+ emojiSizeFromContext,
145
+ registry,
146
+ columns,
147
+ ]);
148
+
149
+ // Restore measured row heights in the same commit as the filtered
150
+ // contents. A passive effect leaves one frame of stale offsets that
151
+ // category navigation can observe immediately after clearing search.
152
+ // Recheck an estimated size when positioned/visible cells first mount.
153
+ useIsomorphicLayoutEffect(() => {
66
154
  computeAndSetDimensions();
67
155
  }, [
68
156
  emojiCount,
69
157
  isReactionsMode,
70
158
  computeAndSetDimensions,
71
159
  visibleCategories.length,
160
+ dimensions,
161
+ ready,
72
162
  ]);
73
163
 
164
+ // Fluid layouts (width: 100%, sheets, docks) resize without a CSS
165
+ // transition: recompute columns when the list's width changes.
166
+ React.useEffect(() => {
167
+ const listEl = EmojiListRef.current;
168
+ if (!listEl || typeof ResizeObserver === 'undefined') return;
169
+ let lastWidth = listEl.clientWidth;
170
+ const observer = new ResizeObserver(() => {
171
+ const width = listEl.clientWidth;
172
+ if (width === lastWidth) return;
173
+ lastWidth = width;
174
+ computeAndSetDimensions();
175
+ });
176
+ observer.observe(listEl);
177
+ return () => observer.disconnect();
178
+ }, [EmojiListRef, computeAndSetDimensions]);
179
+
74
180
  // Listen to transitionend on the picker root (where height transition occurs)
75
181
  React.useEffect(() => {
76
182
  const rootEl = PickerMainRef.current;