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
@@ -1,23 +1,22 @@
1
- import { useCallback, useEffect, useMemo } from 'react';
1
+ import { useCallback, useEffect, useRef } from 'react';
2
2
 
3
- import { hasNextElementSibling } from '../DomUtils/elementPositionInRow';
3
+ import { eventBelongsToPicker } from '../DomUtils/eventBelongsToPicker';
4
4
  import {
5
5
  focusElement,
6
6
  focusNextElementSibling,
7
7
  focusPrevElementSibling,
8
8
  } from '../DomUtils/focusElement';
9
9
  import { getActiveElement } from '../DomUtils/getActiveElement';
10
+ import { isRtl } from '../DomUtils/isRtl';
10
11
  import {
11
- focusAndClickFirstVisibleEmoji,
12
+ focusFirstEmoji,
12
13
  focusFirstVisibleEmoji,
13
- focusNextVisibleEmoji,
14
- focusPrevVisibleEmoji,
15
- focusVisibleEmojiOneRowDown,
16
- focusVisibleEmojiOneRowUp,
14
+ focusAdjacentEmoji,
17
15
  } from '../DomUtils/keyboardNavigation';
18
16
  import { useScrollTo } from '../DomUtils/scrollTo';
19
- import { buttonFromTarget } from '../DomUtils/selectors';
17
+ import { buttonFromTarget, firstVisibleEmoji } from '../DomUtils/selectors';
20
18
  import {
19
+ ElementRef,
21
20
  useBodyRef,
22
21
  useCategoryNavigationRef,
23
22
  usePickerMainRef,
@@ -26,358 +25,372 @@ import {
26
25
  useSkinTonePickerRef,
27
26
  } from '../components/context/ElementRefContext';
28
27
  import {
29
- useReactionsModeState,
28
+ useNavigationRegistry,
29
+ useSearchComposingState,
30
+ useSearchDisplayState,
31
+ useSearchTermState,
30
32
  useSkinToneFanOpenState,
31
33
  } from '../components/context/PickerContext';
32
- import { useSearchDisabledConfig } from '../config/useConfig';
34
+ import { usePickerDataContext } from '../components/context/PickerDataContext';
35
+ import {
36
+ useSearchDisabledConfig,
37
+ useSuggestedEmojisConfig,
38
+ } from '../config/useConfig';
39
+ import { normalizeQuery } from '../data-core/prepare';
40
+ import {
41
+ NavigationRegionKind,
42
+ RegisteredRegion,
43
+ } from '../state/navigationRegistry';
44
+ import { getNextRegion, getPrevRegion } from '../state/regionTraversal';
33
45
 
34
46
  import {
35
47
  useCloseAllOpenToggles,
36
48
  useHasOpenToggles,
37
49
  } from './useCloseAllOpenToggles';
38
50
  import { useDisallowMouseMove } from './useDisallowMouseMove';
39
- import { useAppendSearch, useClearSearch } from './useFilter';
40
51
  import {
41
52
  useFocusCategoryNavigation,
42
53
  useFocusSearchInput,
43
54
  useFocusSkinTonePicker,
44
55
  } from './useFocus';
45
56
  import useIsSearchMode from './useIsSearchMode';
46
- import useSetVariationPicker from './useSetVariationPicker';
47
57
  import {
48
- useIsSkinToneInPreview,
49
- useIsSkinToneInSearch,
50
- } from './useShouldShowSkinTonePicker';
51
-
52
- enum KeyboardEvents {
53
- ArrowDown = 'ArrowDown',
54
- ArrowUp = 'ArrowUp',
55
- ArrowLeft = 'ArrowLeft',
56
- ArrowRight = 'ArrowRight',
57
- Escape = 'Escape',
58
- Enter = 'Enter',
59
- Space = ' ',
60
- }
58
+ useAcceptedSearchValue,
59
+ useClearSearchValue,
60
+ useTypeToSearchKey,
61
+ } from './useSearchController';
62
+ import useSetVariationPicker from './useSetVariationPicker';
61
63
 
62
64
  export function useKeyboardNavigation() {
63
- usePickerMainKeyboardEvents();
64
- useSearchInputKeyboardEvents();
65
- useSkinTonePickerKeyboardEvents();
66
- useCategoryNavigationKeyboardEvents();
67
- useReactionsKeyboardEvents();
68
- useBodyKeyboardEvents();
69
- }
70
-
71
- function usePickerMainKeyboardEvents() {
72
- const PickerMainRef = usePickerMainRef();
73
- const clearSearch = useClearSearch();
65
+ const ref = usePickerMainRef();
66
+ const hasOpenToggles = useHasOpenToggles();
67
+ const clearSearch = useClearSearchValue();
74
68
  const scrollTo = useScrollTo();
75
- const SearchInputRef = useSearchInputRef();
76
69
  const focusSearchInput = useFocusSearchInput();
77
- const hasOpenToggles = useHasOpenToggles();
78
70
  const disallowMouseMove = useDisallowMouseMove();
79
-
80
71
  const closeAllOpenToggles = useCloseAllOpenToggles();
81
-
82
- const onKeyDown = useMemo(
83
- () =>
84
- function onKeyDown(event: KeyboardEvent) {
85
- const { key } = event;
86
-
87
- disallowMouseMove();
88
- switch (key) {
89
- // eslint-disable-next-line no-fallthrough
90
- case KeyboardEvents.Escape:
91
- event.preventDefault();
92
- if (hasOpenToggles()) {
93
- closeAllOpenToggles();
94
- return;
95
- }
96
- clearSearch();
97
- scrollTo(0);
98
- focusSearchInput();
99
- break;
100
- }
101
- },
102
- [
103
- scrollTo,
104
- clearSearch,
105
- closeAllOpenToggles,
106
- focusSearchInput,
107
- hasOpenToggles,
108
- disallowMouseMove,
109
- ],
110
- );
111
-
112
- useEffect(() => {
113
- const current = PickerMainRef.current;
114
-
115
- if (!current) {
116
- return;
117
- }
118
-
119
- current.addEventListener('keydown', onKeyDown);
120
-
121
- return () => {
122
- current.removeEventListener('keydown', onKeyDown);
123
- };
124
- }, [PickerMainRef, SearchInputRef, scrollTo, onKeyDown]);
125
- }
126
-
127
- function useSearchInputKeyboardEvents() {
128
72
  const focusSkinTonePicker = useFocusSkinTonePicker();
129
- const PickerMainRef = usePickerMainRef();
130
73
  const BodyRef = useBodyRef();
131
74
  const SearchInputRef = useSearchInputRef();
132
- const [, setSkinToneFanOpenState] = useSkinToneFanOpenState();
133
- const goDownFromSearchInput = useGoDownFromSearchInput();
134
- const isSkinToneInSearch = useIsSkinToneInSearch();
135
-
136
- const onKeyDown = useMemo(
137
- () =>
138
- function onKeyDown(event: KeyboardEvent) {
139
- const { key } = event;
140
-
141
- switch (key) {
142
- case KeyboardEvents.ArrowRight:
143
- if (!isSkinToneInSearch) {
144
- return;
145
- }
146
- event.preventDefault();
147
- setSkinToneFanOpenState(true);
148
- focusSkinTonePicker();
149
- break;
150
- case KeyboardEvents.ArrowDown:
151
- event.preventDefault();
152
- goDownFromSearchInput();
153
- break;
154
- case KeyboardEvents.Enter:
155
- event.preventDefault();
156
- focusAndClickFirstVisibleEmoji(BodyRef.current);
157
- break;
158
- }
159
- },
160
- [
161
- focusSkinTonePicker,
162
- goDownFromSearchInput,
163
- setSkinToneFanOpenState,
164
- BodyRef,
165
- isSkinToneInSearch,
166
- ],
167
- );
168
-
75
+ const [isOpen, setIsOpen] = useSkinToneFanOpenState();
76
+ const focusCategoryNavigation = useFocusCategoryNavigation();
77
+ const focusNextRegionFromSearch = useFocusNextRegionFrom('search');
78
+ const isSearchMode = useIsSearchMode();
79
+ const SkinTonePickerRef = useSkinTonePickerRef();
80
+ const isSkinToneInSearch = () =>
81
+ !!SkinTonePickerRef.current?.closest('[data-epr-part="search"]');
82
+ const onType = useOnType();
83
+ const CategoryNavigationRef = useCategoryNavigationRef();
84
+ const focusPrevRegionFromCategories = useFocusPrevRegionFrom('categories');
85
+ const focusNextRegionFromCategories = useFocusNextRegionFrom('categories');
86
+ const ReactionsRef = useReactionsRef();
87
+ const focusPrevRegionFromGrid = useFocusPrevRegionFrom('grid');
88
+ const setVariationPicker = useSetVariationPicker();
89
+ const registry = useNavigationRegistry();
90
+ const [searchTerm] = useSearchTermState();
91
+ const suggestions = JSON.stringify(useSuggestedEmojisConfig());
92
+ const { emojiData } = usePickerDataContext();
93
+ const searchValue = useAcceptedSearchValue();
94
+ // A controlled parent may not have accepted what the input shows yet.
95
+ const [displayed] = useSearchDisplayState();
96
+ // Enter pressed while the input is ahead of the debounced results: the
97
+ // query it was pressed for, selected once those results commit.
98
+ const pendingPick = useRef<string | null>(null);
99
+ const pickEmoji = (emoji: HTMLElement | null) => {
100
+ pendingPick.current = null;
101
+ focusElement(emoji);
102
+ emoji?.click();
103
+ };
104
+ const selectFirstResult = () => {
105
+ const query = pendingPick.current;
106
+ const generation = registry.currentGeneration();
107
+ if (
108
+ !focusFirstEmoji(BodyRef.current, {
109
+ shouldFocus: () =>
110
+ pendingPick.current === query &&
111
+ SearchInputRef.current?.value === query &&
112
+ registry.isCurrent(generation),
113
+ moveTo: pickEmoji,
114
+ })
115
+ )
116
+ pendingPick.current = null;
117
+ };
118
+ // Each committed result set settles a queued Enter for its query.
169
119
  useEffect(() => {
170
- const current = SearchInputRef.current;
171
-
172
- if (!current) {
120
+ const pending = pendingPick.current;
121
+ pendingPick.current = null;
122
+ if (
123
+ pending !== null &&
124
+ pending === displayed &&
125
+ normalizeQuery(pending) === searchTerm
126
+ ) {
127
+ pendingPick.current = pending;
128
+ selectFirstResult();
129
+ }
130
+ // Per committed result set; selection reads live refs.
131
+ // eslint-disable-next-line react-hooks/exhaustive-deps
132
+ }, [searchTerm, suggestions, emojiData]);
133
+ // Enter in search selects the first result, even one scrolled out of view.
134
+ const selectFromSearch = () => {
135
+ // Remember fast Enter while the input is ahead of the debounced grid.
136
+ if (
137
+ ![searchValue, displayed].every(
138
+ (value) => normalizeQuery(value) === searchTerm,
139
+ )
140
+ ) {
141
+ pendingPick.current = displayed;
173
142
  return;
174
143
  }
144
+ if (isSearchMode) {
145
+ pendingPick.current = displayed;
146
+ selectFirstResult();
147
+ } else pickEmoji(firstVisibleEmoji(BodyRef.current));
148
+ };
149
+ const goDownFromSearchInput = () => {
150
+ if (isSearchMode) return focusFirstVisibleEmoji(BodyRef.current);
151
+ focusCategoryNavigation();
152
+ };
153
+ const goUpFromBody = () => {
154
+ if (isSearchMode) return focusSearchInput();
155
+ focusPrevRegionFromGrid(focusCategoryNavigation);
156
+ };
157
+ const main = function onKeyDown(event: KeyboardEvent) {
158
+ const { key } = event;
175
159
 
176
- current.addEventListener('keydown', onKeyDown);
177
-
178
- return () => {
179
- current.removeEventListener('keydown', onKeyDown);
180
- };
181
- }, [PickerMainRef, SearchInputRef, onKeyDown]);
182
- }
183
-
184
- function useSkinTonePickerKeyboardEvents() {
185
- const SkinTonePickerRef = useSkinTonePickerRef();
186
- const focusSearchInput = useFocusSearchInput();
187
- const SearchInputRef = useSearchInputRef();
188
- const goDownFromSearchInput = useGoDownFromSearchInput();
189
- const [isOpen, setIsOpen] = useSkinToneFanOpenState();
190
- const isSkinToneInPreview = useIsSkinToneInPreview();
191
- const isSkinToneInSearch = useIsSkinToneInSearch();
192
- const onType = useOnType();
193
-
194
- const onKeyDown = useMemo(
195
- () =>
196
- // eslint-disable-next-line complexity
197
- function onKeyDown(event: KeyboardEvent) {
198
- const { key } = event;
199
-
200
- if (isSkinToneInSearch) {
201
- switch (key) {
202
- case KeyboardEvents.ArrowLeft:
203
- event.preventDefault();
204
- if (!isOpen) {
205
- return focusSearchInput();
206
- }
207
- focusNextSkinTone(focusSearchInput);
208
- break;
209
- case KeyboardEvents.ArrowRight:
210
- event.preventDefault();
211
- if (!isOpen) {
212
- return focusSearchInput();
213
- }
214
- focusPrevSkinTone();
215
- break;
216
- case KeyboardEvents.ArrowDown:
217
- event.preventDefault();
218
- if (isOpen) {
219
- setIsOpen(false);
220
- }
221
- goDownFromSearchInput();
222
- break;
223
- default:
224
- onType(event);
225
- break;
226
- }
160
+ disallowMouseMove();
161
+ switch (key) {
162
+ case 'Escape':
163
+ if (hasOpenToggles()) {
164
+ event.preventDefault();
165
+ closeAllOpenToggles();
166
+ // A nested variation/tone menu consumes Escape before the
167
+ // host popover or autocomplete dismisses the whole picker.
168
+ event.stopPropagation();
169
+ return;
227
170
  }
228
-
229
- if (isSkinToneInPreview) {
230
- switch (key) {
231
- case KeyboardEvents.ArrowUp:
232
- event.preventDefault();
233
- if (!isOpen) {
234
- return focusSearchInput();
235
- }
236
- focusNextSkinTone(focusSearchInput);
237
- break;
238
- case KeyboardEvents.ArrowDown:
239
- event.preventDefault();
240
- if (!isOpen) {
241
- return focusSearchInput();
242
- }
243
- focusPrevSkinTone();
244
- break;
245
- default:
246
- onType(event);
247
- break;
248
- }
171
+ // Clearing a search consumes the key. With nothing to clear, the
172
+ // event stays untouched (this runs in window capture, before host
173
+ // listeners that skip default-prevented keys, such as Radix).
174
+ if (searchValue || displayed) {
175
+ event.preventDefault();
176
+ event.stopPropagation();
249
177
  }
250
- },
251
- [
252
- isOpen,
253
- focusSearchInput,
254
- setIsOpen,
255
- goDownFromSearchInput,
256
- onType,
257
- isSkinToneInPreview,
258
- isSkinToneInSearch,
259
- ],
260
- );
261
-
262
- useEffect(() => {
263
- const current = SkinTonePickerRef.current;
264
-
265
- if (!current) {
266
- return;
178
+ clearSearch();
179
+ scrollTo(0);
180
+ focusSearchInput();
181
+ break;
267
182
  }
183
+ };
184
+ const search = function onKeyDown(event: KeyboardEvent) {
185
+ if (!eventInRegion(event, SearchInputRef)) return;
186
+ const key = logicalArrowKey(event, SearchInputRef.current);
268
187
 
269
- current.addEventListener('keydown', onKeyDown);
270
-
271
- return () => {
272
- current.removeEventListener('keydown', onKeyDown);
273
- };
274
- }, [SkinTonePickerRef, SearchInputRef, isOpen, onKeyDown]);
275
- }
276
-
277
- function useCategoryNavigationKeyboardEvents() {
278
- const focusSearchInput = useFocusSearchInput();
279
- const CategoryNavigationRef = useCategoryNavigationRef();
280
- const BodyRef = useBodyRef();
281
- const onType = useOnType();
282
-
283
- const onKeyDown = useMemo(
284
- () =>
285
- function onKeyDown(event: KeyboardEvent) {
286
- const { key } = event;
287
-
288
- switch (key) {
289
- case KeyboardEvents.ArrowUp:
290
- event.preventDefault();
291
- focusSearchInput();
292
- break;
293
- case KeyboardEvents.ArrowRight:
294
- event.preventDefault();
295
- focusNextElementSibling(getActiveElement());
296
- break;
297
- case KeyboardEvents.ArrowLeft:
298
- event.preventDefault();
299
- focusPrevElementSibling(getActiveElement());
300
- break;
301
- case KeyboardEvents.ArrowDown:
302
- event.preventDefault();
303
- focusFirstVisibleEmoji(BodyRef.current);
304
- break;
305
- default:
306
- onType(event);
307
- break;
188
+ switch (key) {
189
+ case 'ArrowRight':
190
+ if (!isSkinToneInSearch()) {
191
+ return;
308
192
  }
309
- },
310
- [BodyRef, focusSearchInput, onType],
311
- );
312
-
313
- useEffect(() => {
314
- const current = CategoryNavigationRef.current;
193
+ event.preventDefault();
194
+ setIsOpen(true);
195
+ focusSkinTonePicker();
196
+ break;
197
+ case 'ArrowDown':
198
+ event.preventDefault();
199
+ // The active-search Search↔Grid exception
200
+ // wins over generic DOM-order traversal.
201
+ if (isSearchMode) {
202
+ goDownFromSearchInput();
203
+ } else {
204
+ focusNextRegionFromSearch(goDownFromSearchInput);
205
+ }
206
+ break;
207
+ case 'Enter':
208
+ event.preventDefault();
209
+ selectFromSearch();
210
+ break;
211
+ }
212
+ };
213
+ const tone = function onKeyDown(event: KeyboardEvent) {
214
+ if (!eventInRegion(event, SkinTonePickerRef)) return;
215
+ const key = logicalArrowKey(event, SkinTonePickerRef.current);
216
+ // The fan axis decides the arrow keys: the search placement and a
217
+ // horizontal SkinTone primitive move left/right, the preview
218
+ // placement and a vertical primitive move up/down.
219
+ const vertical =
220
+ SkinTonePickerRef.current?.getAttribute('data-epr-direction') ===
221
+ 'vertical';
222
+
223
+ switch (key) {
224
+ case vertical ? 'ArrowUp' : 'ArrowLeft':
225
+ event.preventDefault();
226
+ if (!isOpen) return focusSearchInput();
227
+ focusNextSkinTone(focusSearchInput);
228
+ break;
229
+ case vertical ? 'ArrowDown' : 'ArrowRight':
230
+ event.preventDefault();
231
+ if (!isOpen) return focusSearchInput();
232
+ focusPrevElementSibling(getActiveElement());
233
+ break;
234
+ case 'ArrowDown':
235
+ event.preventDefault();
236
+ setIsOpen(false);
237
+ goDownFromSearchInput();
238
+ break;
239
+ default:
240
+ onType(event);
241
+ }
242
+ };
243
+ const categories = function onKeyDown(event: KeyboardEvent) {
244
+ if (!eventInRegion(event, CategoryNavigationRef)) return;
245
+ // Arrows along the tab axis move between tabs; arrows across it
246
+ // leave for the previous/next region. A vertical tablist (e.g. a
247
+ // side rail) therefore uses Up/Down for tabs, per the ARIA tabs
248
+ // pattern, and Left/Right to leave.
249
+ const vertical =
250
+ CategoryNavigationRef.current?.getAttribute('aria-orientation') ===
251
+ 'vertical';
252
+ const key = logicalArrowKey(event, CategoryNavigationRef.current);
253
+ const direction = reactionFocusDelta(key);
254
+
255
+ if (!direction) {
256
+ onType(event);
257
+ return;
258
+ }
259
+ event.preventDefault();
260
+ if (vertical === (key === 'ArrowUp' || key === 'ArrowDown')) {
261
+ const focusSibling =
262
+ direction > 0 ? focusNextElementSibling : focusPrevElementSibling;
263
+ focusSibling(getActiveElement());
264
+ } else if (direction < 0) {
265
+ focusPrevRegionFromCategories(focusSearchInput);
266
+ } else {
267
+ focusNextRegionFromCategories(() =>
268
+ focusFirstVisibleEmoji(BodyRef.current),
269
+ );
270
+ }
271
+ };
272
+ const reactions = function onKeyDown(event: KeyboardEvent) {
273
+ if (!eventInRegion(event, ReactionsRef)) return;
274
+ const key = logicalArrowKey(event, ReactionsRef.current);
275
+ const current = ReactionsRef.current;
315
276
 
316
277
  if (!current) {
317
278
  return;
318
279
  }
319
280
 
320
- current.addEventListener('keydown', onKeyDown);
281
+ const delta = reactionFocusDelta(key);
321
282
 
283
+ if (delta !== 0 && focusReactionSibling(current, delta)) {
284
+ event.preventDefault();
285
+ }
286
+ };
287
+ const body = function onKeyDown(event: KeyboardEvent) {
288
+ if (!eventInRegion(event, BodyRef)) return;
289
+ const key = logicalArrowKey(event, BodyRef.current);
290
+
291
+ const activeElement = buttonFromTarget(event.target as HTMLElement);
292
+ // Consumer controls own editing and activation; only managed emoji
293
+ // buttons issue grid commands.
294
+ if (!activeElement) return;
295
+
296
+ // Navigation-generation guard: a pending
297
+ // materialize/scroll/focus completion aborts when search, data,
298
+ // geometry, or reactions state changed underneath it.
299
+ const generation = registry.currentGeneration();
300
+ const focusGuard = () => registry.isCurrent(generation);
301
+
302
+ const direction = reactionFocusDelta(key);
303
+ if (direction) {
304
+ event.preventDefault();
305
+ const row = key === 'ArrowDown' || key === 'ArrowUp';
306
+ if (row && hasOpenToggles()) closeAllOpenToggles();
307
+ else
308
+ focusAdjacentEmoji(activeElement, direction, row, {
309
+ shouldFocus: focusGuard,
310
+ exitUp: row ? goUpFromBody : undefined,
311
+ });
312
+ return;
313
+ }
314
+ switch (key) {
315
+ case ' ':
316
+ event.preventDefault();
317
+ setVariationPicker(event.target as HTMLElement);
318
+ break;
319
+ default:
320
+ onType(event);
321
+ break;
322
+ }
323
+ };
324
+ const [composing] = useSearchComposingState();
325
+ const handler = useRef<(event: KeyboardEvent, escapeOnly?: boolean) => void>(
326
+ () => {},
327
+ );
328
+ // One listener owns the native lifecycle; current callbacks supply state
329
+ // and live region refs without reattaching on every picker render.
330
+ handler.current = (event, escapeOnly) => {
331
+ if (composing || event.isComposing || event.keyCode === 229) return;
332
+ if (event.key !== 'Enter') pendingPick.current = null;
333
+ if (!escapeOnly) {
334
+ search(event);
335
+ tone(event);
336
+ categories(event);
337
+ reactions(event);
338
+ body(event);
339
+ }
340
+ main(event);
341
+ };
342
+ useEffect(() => {
343
+ // RootAside owns this native aside for its entire mounted lifetime.
344
+ // Descendant parts are read through live refs by the delegated handler.
345
+ const current = ref.current;
346
+ if (!current) return;
347
+ // Escape runs once, in the window capture phase, so a consumed key is
348
+ // stopped before host dismissal listeners (Radix captures on document).
349
+ let captured: KeyboardEvent | undefined;
350
+ const scoped = (event: KeyboardEvent) => {
351
+ if (event === captured) return;
352
+ if (eventBelongsToPicker(event, current)) handler.current(event);
353
+ };
354
+ const capture = (event: KeyboardEvent) => {
355
+ if (event.key === 'Escape' && eventBelongsToPicker(event, current)) {
356
+ captured = event;
357
+ handler.current(event, true);
358
+ }
359
+ };
360
+ // Editing the query (paste, autocorrect) or leaving the picker cancels
361
+ // a queued Enter.
362
+ const cancelPendingPick = () => {
363
+ pendingPick.current = null;
364
+ };
365
+ current.addEventListener('input', cancelPendingPick);
366
+ current.addEventListener('focusout', cancelPendingPick);
367
+ current.addEventListener('keydown', scoped);
368
+ window.addEventListener('keydown', capture, true);
322
369
  return () => {
323
- current.removeEventListener('keydown', onKeyDown);
370
+ current.removeEventListener('input', cancelPendingPick);
371
+ current.removeEventListener('focusout', cancelPendingPick);
372
+ current.removeEventListener('keydown', scoped);
373
+ window.removeEventListener('keydown', capture, true);
324
374
  };
325
- }, [CategoryNavigationRef, BodyRef, onKeyDown]);
375
+ }, [ref]);
376
+ }
377
+
378
+ function eventInRegion(event: KeyboardEvent, ref: ElementRef): boolean {
379
+ return !!ref.current?.contains(event.target as Node);
326
380
  }
327
381
 
328
382
  // Arrow keys move between reaction buttons (and the expand button)
329
383
  // while the reactions bar has focus. The bar lives outside the scroll
330
384
  // body, so the body handler never sees these events.
331
385
  // https://github.com/ealush/emoji-picker-react/issues/411
332
- function useReactionsKeyboardEvents() {
333
- const ReactionsRef = useReactionsRef();
334
- // The reactions bar mounts late when the user collapses the full
335
- // picker, so the listener effect must rerun when it opens.
336
- const [reactionsOpen] = useReactionsModeState();
337
-
338
- const onKeyDown = useMemo(
339
- () =>
340
- function onKeyDown(event: KeyboardEvent) {
341
- const { key } = event;
342
- const current = ReactionsRef.current;
343
-
344
- if (!current) {
345
- return;
346
- }
347
-
348
- const delta = reactionFocusDelta(key);
349
-
350
- if (delta !== 0 && focusReactionSibling(current, delta)) {
351
- event.preventDefault();
352
- }
353
- },
354
- // reactionsOpen: the bar mounts late on collapse, and the new
355
- // callback identity reinstalls the listener effect below.
356
- [ReactionsRef, reactionsOpen],
357
- );
358
-
359
- useEffect(() => {
360
- const current = ReactionsRef.current;
361
-
362
- if (!current) {
363
- return;
364
- }
365
-
366
- current.addEventListener('keydown', onKeyDown);
367
-
368
- return () => {
369
- current.removeEventListener('keydown', onKeyDown);
370
- };
371
- }, [ReactionsRef, onKeyDown]);
372
- }
373
386
 
374
387
  function reactionFocusDelta(key: string): number {
375
388
  switch (key) {
376
- case KeyboardEvents.ArrowRight:
377
- case KeyboardEvents.ArrowDown:
389
+ case 'ArrowRight':
390
+ case 'ArrowDown':
378
391
  return 1;
379
- case KeyboardEvents.ArrowLeft:
380
- case KeyboardEvents.ArrowUp:
392
+ case 'ArrowLeft':
393
+ case 'ArrowUp':
381
394
  return -1;
382
395
  default:
383
396
  return 0;
@@ -386,9 +399,7 @@ function reactionFocusDelta(key: string): number {
386
399
 
387
400
  function focusReactionSibling(root: HTMLElement, delta: number): boolean {
388
401
  const buttons = Array.from(root.querySelectorAll('button'));
389
- const activeIndex = buttons.indexOf(
390
- getActiveElement() as HTMLButtonElement,
391
- );
402
+ const activeIndex = buttons.indexOf(getActiveElement() as HTMLButtonElement);
392
403
  const sibling = activeIndex === -1 ? null : buttons[activeIndex + delta];
393
404
 
394
405
  if (!sibling) {
@@ -399,141 +410,87 @@ function focusReactionSibling(root: HTMLElement, delta: number): boolean {
399
410
  return true;
400
411
  }
401
412
 
402
- function useBodyKeyboardEvents() {
403
- const BodyRef = useBodyRef();
404
- const goUpFromBody = useGoUpFromBody();
405
- const setVariationPicker = useSetVariationPicker();
406
- const hasOpenToggles = useHasOpenToggles();
407
- const closeAllOpenToggles = useCloseAllOpenToggles();
408
-
409
- const onType = useOnType();
410
-
411
- const onKeyDown = useMemo(
412
- () =>
413
- // eslint-disable-next-line complexity
414
- function onKeyDown(event: KeyboardEvent) {
415
- const { key } = event;
416
-
417
- const activeElement = buttonFromTarget(getActiveElement());
418
-
419
- switch (key) {
420
- case KeyboardEvents.ArrowRight:
421
- event.preventDefault();
422
- focusNextVisibleEmoji(activeElement);
423
- break;
424
- case KeyboardEvents.ArrowLeft:
425
- event.preventDefault();
426
- focusPrevVisibleEmoji(activeElement);
427
- break;
428
- case KeyboardEvents.ArrowDown:
429
- event.preventDefault();
430
- if (hasOpenToggles()) {
431
- closeAllOpenToggles();
432
- break;
433
- }
434
- focusVisibleEmojiOneRowDown(activeElement);
435
- break;
436
- case KeyboardEvents.ArrowUp:
437
- event.preventDefault();
438
- if (hasOpenToggles()) {
439
- closeAllOpenToggles();
440
- break;
441
- }
442
- focusVisibleEmojiOneRowUp(activeElement, goUpFromBody);
443
- break;
444
- case KeyboardEvents.Space:
445
- event.preventDefault();
446
- setVariationPicker(event.target as HTMLElement);
447
- break;
448
- default:
449
- onType(event);
450
- break;
451
- }
452
- },
453
- [
454
- goUpFromBody,
455
- onType,
456
- setVariationPicker,
457
- hasOpenToggles,
458
- closeAllOpenToggles,
459
- ],
460
- );
461
-
462
- useEffect(() => {
463
- const current = BodyRef.current;
464
-
465
- if (!current) {
466
- return;
467
- }
468
-
469
- current.addEventListener('keydown', onKeyDown);
470
-
471
- return () => {
472
- current.removeEventListener('keydown', onKeyDown);
473
- };
474
- }, [BodyRef, onKeyDown]);
475
- }
476
-
477
- function useGoDownFromSearchInput() {
413
+ // Cross-region focus through the registered semantic graph. Returns true
414
+ // when a region handled the move; callers keep their legacy direct target
415
+ // as fallback so unregistered trees behave exactly as before. Exported for
416
+ // focus-restoration flows (reactions transitions) that target regions.
417
+ export function useFocusRegion() {
418
+ const focusSearchInput = useFocusSearchInput();
478
419
  const focusCategoryNavigation = useFocusCategoryNavigation();
479
- const isSearchMode = useIsSearchMode();
420
+ const focusSkinTonePicker = useFocusSkinTonePicker();
480
421
  const BodyRef = useBodyRef();
422
+ const ReactionsRef = useReactionsRef();
423
+ const registry = useNavigationRegistry();
481
424
 
482
425
  return useCallback(
483
- function goDownFromSearchInput() {
484
- if (isSearchMode) {
485
- return focusFirstVisibleEmoji(BodyRef.current);
426
+ function focusRegion(region: RegisteredRegion | undefined): boolean {
427
+ if (!region) {
428
+ return false;
486
429
  }
487
- return focusCategoryNavigation();
488
- },
489
- [BodyRef, focusCategoryNavigation, isSearchMode],
490
- );
491
- }
492
-
493
- function useGoUpFromBody() {
494
- const focusSearchInput = useFocusSearchInput();
495
- const focusCategoryNavigation = useFocusCategoryNavigation();
496
- const isSearchMode = useIsSearchMode();
497
-
498
- return useCallback(
499
- function goUpFromEmoji() {
500
- if (isSearchMode) {
501
- return focusSearchInput();
430
+ // Grid entry is a materialize/scroll/focus operation: capture the
431
+ // navigation generation so a stale completion cannot steal focus.
432
+ const generation = registry.currentGeneration();
433
+ const focusGuard = () => registry.isCurrent(generation);
434
+ switch (region.kind as NavigationRegionKind) {
435
+ case 'search':
436
+ focusSearchInput();
437
+ return true;
438
+ case 'categories':
439
+ focusCategoryNavigation();
440
+ return true;
441
+ case 'grid':
442
+ focusFirstVisibleEmoji(BodyRef.current, focusGuard);
443
+ return true;
444
+ case 'reactions': {
445
+ const firstButton = ReactionsRef.current?.querySelector('button');
446
+ focusElement(firstButton ?? null, focusGuard);
447
+ return true;
448
+ }
449
+ case 'preview-skin-tone':
450
+ focusSkinTonePicker();
451
+ return true;
502
452
  }
503
- return focusCategoryNavigation();
504
453
  },
505
- [focusSearchInput, isSearchMode, focusCategoryNavigation],
454
+ [
455
+ focusSearchInput,
456
+ focusCategoryNavigation,
457
+ focusSkinTonePicker,
458
+ BodyRef,
459
+ ReactionsRef,
460
+ registry,
461
+ ],
506
462
  );
507
463
  }
508
464
 
509
- function focusNextSkinTone(exitLeft: () => void) {
510
- const currentSkinTone = getActiveElement();
511
-
512
- if (!currentSkinTone) {
513
- return;
514
- }
515
-
516
- if (!hasNextElementSibling(currentSkinTone)) {
517
- exitLeft();
518
- }
519
-
520
- focusNextElementSibling(currentSkinTone);
465
+ function useFocusNextRegionFrom(from: NavigationRegionKind) {
466
+ return useFocusAdjacentRegion(from, 1);
521
467
  }
522
468
 
523
- function focusPrevSkinTone() {
524
- const currentSkinTone = getActiveElement();
469
+ function useFocusPrevRegionFrom(from: NavigationRegionKind) {
470
+ return useFocusAdjacentRegion(from, -1);
471
+ }
525
472
 
526
- if (!currentSkinTone) {
527
- return;
528
- }
473
+ function useFocusAdjacentRegion(from: NavigationRegionKind, direction: number) {
474
+ const registry = useNavigationRegistry();
475
+ const PickerMainRef = usePickerMainRef();
476
+ const focusRegion = useFocusRegion();
477
+ return (fallback?: () => void) => {
478
+ const getRegion = direction > 0 ? getNextRegion : getPrevRegion;
479
+ if (!focusRegion(getRegion(registry, PickerMainRef.current, from)))
480
+ fallback?.();
481
+ };
482
+ }
529
483
 
530
- focusPrevElementSibling(currentSkinTone);
484
+ function focusNextSkinTone(exitLeft: () => void) {
485
+ const current = getActiveElement();
486
+ if (current && !current.nextElementSibling) exitLeft();
487
+ focusNextElementSibling(current);
531
488
  }
532
489
 
533
490
  function useOnType() {
534
- const appendSearch = useAppendSearch();
535
- const focusSearchInput = useFocusSearchInput();
491
+ const typeToSearch = useTypeToSearchKey();
536
492
  const searchDisabled = useSearchDisabledConfig();
493
+ const SearchInputRef = useSearchInputRef();
537
494
  const closeAllOpenToggles = useCloseAllOpenToggles();
538
495
 
539
496
  return function onType(event: KeyboardEvent) {
@@ -543,15 +500,38 @@ function useOnType() {
543
500
  return;
544
501
  }
545
502
 
546
- if (key.match(/(^[a-zA-Z0-9]$){1}/)) {
503
+ // No search input mounted (a bare Root without Search): the
504
+ // transition would no-op, so the key is left unclaimed — no
505
+ // preventDefault, no toggle closing — exactly like searchDisabled.
506
+ if (!SearchInputRef.current) {
507
+ return;
508
+ }
509
+
510
+ // One printable letter or digit in any script (not only ASCII): the
511
+ // localized datasets are searched in their own languages, so a Hebrew,
512
+ // Cyrillic or Japanese keystroke on the grid starts a search like a
513
+ // Latin one. Named keys ("Enter", "Tab") are longer than one code
514
+ // point; space and punctuation stay unclaimed for the grid's own keys.
515
+ if (/^[\p{L}\p{N}]$/u.test(key)) {
547
516
  event.preventDefault();
548
517
  closeAllOpenToggles();
549
- focusSearchInput();
550
- appendSearch(key);
518
+ typeToSearch(key);
551
519
  }
552
520
  };
553
521
  }
554
522
 
523
+ // Left/right arrows follow the visual direction: under `dir="rtl"` the
524
+ // grid, tabs, reactions and tone fan are mirrored, so ArrowLeft moves
525
+ // forward. The live region element supplies the computed direction.
526
+ function logicalArrowKey(event: KeyboardEvent, region: Element | null): string {
527
+ const { key } = event;
528
+ return (key === 'ArrowLeft' || key === 'ArrowRight') && isRtl(region)
529
+ ? key === 'ArrowLeft'
530
+ ? 'ArrowRight'
531
+ : 'ArrowLeft'
532
+ : key;
533
+ }
534
+
555
535
  function hasModifier(event: KeyboardEvent): boolean {
556
536
  const { metaKey, ctrlKey, altKey } = event;
557
537