emoji-picker-react 4.22.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (405) hide show
  1. package/README.md +335 -49
  2. package/dist/DomUtils/categoryNameFromDom.d.ts +1 -1
  3. package/dist/DomUtils/classNames.d.ts +24 -23
  4. package/dist/DomUtils/detectEmojyPartiallyBelowFold.d.ts +2 -2
  5. package/dist/DomUtils/elementPositionInRow.d.ts +5 -5
  6. package/dist/DomUtils/eventBelongsToPicker.d.ts +2 -0
  7. package/dist/DomUtils/focusElement.d.ts +5 -5
  8. package/dist/DomUtils/getActiveElement.d.ts +2 -2
  9. package/dist/DomUtils/isJsdom.d.ts +1 -0
  10. package/dist/DomUtils/isRtl.d.ts +1 -0
  11. package/dist/DomUtils/keyboardNavigation.d.ts +7 -8
  12. package/dist/DomUtils/scrollTo.d.ts +4 -5
  13. package/dist/DomUtils/selectors.d.ts +31 -35
  14. package/dist/EmojiPickerReact.d.ts +5 -5
  15. package/dist/Stylesheet/DedupedStyle.d.ts +13 -0
  16. package/dist/Stylesheet/stylesheet.d.ts +40 -37
  17. package/dist/components/ErrorBoundary.d.ts +15 -15
  18. package/dist/components/Layout/Absolute.d.ts +8 -8
  19. package/dist/components/Layout/Flex.d.ts +13 -13
  20. package/dist/components/Layout/Relative.d.ts +8 -8
  21. package/dist/components/Layout/Space.d.ts +7 -7
  22. package/dist/components/Reactions/BtnPlus.d.ts +2 -2
  23. package/dist/components/Reactions/DEFAULT_REACTIONS.d.ts +1 -1
  24. package/dist/components/Reactions/Reactions.d.ts +3 -2
  25. package/dist/components/atoms/Button.d.ts +11 -6
  26. package/dist/components/body/EmojiCategory.d.ts +13 -12
  27. package/dist/components/body/EmojiList.d.ts +7 -4
  28. package/dist/components/body/EmojiVariationPicker.d.ts +2 -2
  29. package/dist/components/body/MeasureEmoji.d.ts +2 -2
  30. package/dist/components/body/listComponents.d.ts +47 -0
  31. package/dist/components/context/ElementRefContext.d.ts +18 -16
  32. package/dist/components/context/PickerConfigContext.d.ts +14 -9
  33. package/dist/components/context/PickerContext.d.ts +57 -25
  34. package/dist/components/context/PickerDataContext.d.ts +23 -17
  35. package/dist/components/emoji/BaseEmojiProps.d.ts +14 -13
  36. package/dist/components/emoji/ClickableEmojiButton.d.ts +23 -15
  37. package/dist/components/emoji/Emoji.d.ts +18 -15
  38. package/dist/components/emoji/EmojiImg.d.ts +11 -11
  39. package/dist/components/emoji/ExportedEmoji.d.ts +11 -11
  40. package/dist/components/emoji/NativeEmoji.d.ts +6 -6
  41. package/dist/components/emoji/ViewOnlyEmoji.d.ts +3 -3
  42. package/dist/components/emoji/emojiStyles.d.ts +6 -1
  43. package/dist/components/footer/Preview.d.ts +7 -7
  44. package/dist/components/header/Header.d.ts +2 -2
  45. package/dist/components/header/Search/BtnClearSearch.d.ts +2 -2
  46. package/dist/components/header/Search/IcnSearch.d.ts +2 -2
  47. package/dist/components/header/Search/Search.d.ts +11 -3
  48. package/dist/components/header/SkinTonePicker/BtnSkinToneVariation.d.ts +12 -12
  49. package/dist/components/header/SkinTonePicker/SkinTonePicker.d.ts +11 -11
  50. package/dist/components/icons/svgIcons.d.ts +4 -0
  51. package/dist/components/main/PickerMainBehaviors.d.ts +4 -0
  52. package/dist/components/main/defaultAppearance.d.ts +12 -0
  53. package/dist/components/main/labelHeight.d.ts +1 -0
  54. package/dist/components/navigation/CategoryButton.d.ts +12 -12
  55. package/dist/components/navigation/CategoryNavIcon.d.ts +4 -4
  56. package/dist/components/navigation/CategoryNavigation.d.ts +19 -2
  57. package/dist/config/categoryConfig.d.ts +34 -34
  58. package/dist/config/cdnUrls.d.ts +2 -2
  59. package/dist/config/compareConfig.d.ts +10 -2
  60. package/dist/config/config.d.ts +166 -64
  61. package/dist/config/customEmojiConfig.d.ts +14 -14
  62. package/dist/config/defaultPickerConfiguration.d.ts +10 -0
  63. package/dist/config/mutableConfig.d.ts +32 -10
  64. package/dist/config/useConfig.d.ts +48 -36
  65. package/dist/data/defaultEmojiData.d.ts +9 -0
  66. package/dist/data/defaultEmojiData.js +40 -0
  67. package/dist/data/emojis-bn.d.mts +39 -0
  68. package/dist/data/emojis-bn.d.ts +38 -2
  69. package/dist/data/emojis-bn.js +25 -2
  70. package/dist/data/emojis-bn.mjs +2 -0
  71. package/dist/data/emojis-da.d.mts +39 -0
  72. package/dist/data/emojis-da.d.ts +38 -2
  73. package/dist/data/emojis-da.js +25 -2
  74. package/dist/data/emojis-da.mjs +2 -0
  75. package/dist/data/emojis-de.d.mts +39 -0
  76. package/dist/data/emojis-de.d.ts +38 -2
  77. package/dist/data/emojis-de.js +25 -2
  78. package/dist/data/emojis-de.mjs +2 -0
  79. package/dist/data/emojis-en-gb.d.mts +39 -0
  80. package/dist/data/emojis-en-gb.d.ts +38 -2
  81. package/dist/data/emojis-en-gb.js +25 -2
  82. package/dist/data/emojis-en-gb.mjs +2 -0
  83. package/dist/data/emojis-en.d.mts +39 -0
  84. package/dist/data/emojis-en.d.ts +38 -2
  85. package/dist/data/emojis-en.js +25 -2
  86. package/dist/data/emojis-en.mjs +2 -0
  87. package/dist/data/emojis-es-mx.d.mts +39 -0
  88. package/dist/data/emojis-es-mx.d.ts +38 -2
  89. package/dist/data/emojis-es-mx.js +25 -2
  90. package/dist/data/emojis-es-mx.mjs +2 -0
  91. package/dist/data/emojis-es.d.mts +39 -0
  92. package/dist/data/emojis-es.d.ts +38 -2
  93. package/dist/data/emojis-es.js +25 -2
  94. package/dist/data/emojis-es.mjs +2 -0
  95. package/dist/data/emojis-et.d.mts +39 -0
  96. package/dist/data/emojis-et.d.ts +38 -2
  97. package/dist/data/emojis-et.js +25 -2
  98. package/dist/data/emojis-et.mjs +2 -0
  99. package/dist/data/emojis-fi.d.mts +39 -0
  100. package/dist/data/emojis-fi.d.ts +38 -2
  101. package/dist/data/emojis-fi.js +25 -2
  102. package/dist/data/emojis-fi.mjs +2 -0
  103. package/dist/data/emojis-fr.d.mts +39 -0
  104. package/dist/data/emojis-fr.d.ts +38 -2
  105. package/dist/data/emojis-fr.js +25 -2
  106. package/dist/data/emojis-fr.mjs +2 -0
  107. package/dist/data/emojis-hi.d.mts +39 -0
  108. package/dist/data/emojis-hi.d.ts +38 -2
  109. package/dist/data/emojis-hi.js +25 -2
  110. package/dist/data/emojis-hi.mjs +2 -0
  111. package/dist/data/emojis-hu.d.mts +39 -0
  112. package/dist/data/emojis-hu.d.ts +38 -2
  113. package/dist/data/emojis-hu.js +25 -2
  114. package/dist/data/emojis-hu.mjs +2 -0
  115. package/dist/data/emojis-it.d.mts +39 -0
  116. package/dist/data/emojis-it.d.ts +38 -2
  117. package/dist/data/emojis-it.js +25 -2
  118. package/dist/data/emojis-it.mjs +2 -0
  119. package/dist/data/emojis-ja.d.mts +39 -0
  120. package/dist/data/emojis-ja.d.ts +38 -2
  121. package/dist/data/emojis-ja.js +25 -2
  122. package/dist/data/emojis-ja.mjs +2 -0
  123. package/dist/data/emojis-ko.d.mts +39 -0
  124. package/dist/data/emojis-ko.d.ts +38 -2
  125. package/dist/data/emojis-ko.js +25 -2
  126. package/dist/data/emojis-ko.mjs +2 -0
  127. package/dist/data/emojis-lt.d.mts +39 -0
  128. package/dist/data/emojis-lt.d.ts +38 -2
  129. package/dist/data/emojis-lt.js +25 -2
  130. package/dist/data/emojis-lt.mjs +2 -0
  131. package/dist/data/emojis-ms.d.mts +39 -0
  132. package/dist/data/emojis-ms.d.ts +38 -2
  133. package/dist/data/emojis-ms.js +25 -2
  134. package/dist/data/emojis-ms.mjs +2 -0
  135. package/dist/data/emojis-nb.d.mts +39 -0
  136. package/dist/data/emojis-nb.d.ts +38 -2
  137. package/dist/data/emojis-nb.js +25 -2
  138. package/dist/data/emojis-nb.mjs +2 -0
  139. package/dist/data/emojis-nl.d.mts +39 -0
  140. package/dist/data/emojis-nl.d.ts +38 -2
  141. package/dist/data/emojis-nl.js +25 -2
  142. package/dist/data/emojis-nl.mjs +2 -0
  143. package/dist/data/emojis-pl.d.mts +39 -0
  144. package/dist/data/emojis-pl.d.ts +38 -2
  145. package/dist/data/emojis-pl.js +25 -2
  146. package/dist/data/emojis-pl.mjs +2 -0
  147. package/dist/data/emojis-pt.d.mts +39 -0
  148. package/dist/data/emojis-pt.d.ts +38 -2
  149. package/dist/data/emojis-pt.js +25 -2
  150. package/dist/data/emojis-pt.mjs +2 -0
  151. package/dist/data/emojis-ru.d.mts +39 -0
  152. package/dist/data/emojis-ru.d.ts +38 -2
  153. package/dist/data/emojis-ru.js +25 -2
  154. package/dist/data/emojis-ru.mjs +2 -0
  155. package/dist/data/emojis-sv.d.mts +39 -0
  156. package/dist/data/emojis-sv.d.ts +38 -2
  157. package/dist/data/emojis-sv.js +25 -2
  158. package/dist/data/emojis-sv.mjs +2 -0
  159. package/dist/data/emojis-th.d.mts +39 -0
  160. package/dist/data/emojis-th.d.ts +38 -2
  161. package/dist/data/emojis-th.js +25 -2
  162. package/dist/data/emojis-th.mjs +2 -0
  163. package/dist/data/emojis-uk.d.mts +39 -0
  164. package/dist/data/emojis-uk.d.ts +38 -2
  165. package/dist/data/emojis-uk.js +25 -2
  166. package/dist/data/emojis-uk.mjs +2 -0
  167. package/dist/data/emojis-vi.d.mts +39 -0
  168. package/dist/data/emojis-vi.d.ts +38 -2
  169. package/dist/data/emojis-vi.js +25 -2
  170. package/dist/data/emojis-vi.mjs +2 -0
  171. package/dist/data/emojis-zh-hant.d.mts +39 -0
  172. package/dist/data/emojis-zh-hant.d.ts +38 -2
  173. package/dist/data/emojis-zh-hant.js +25 -2
  174. package/dist/data/emojis-zh-hant.mjs +2 -0
  175. package/dist/data/emojis-zh.d.mts +39 -0
  176. package/dist/data/emojis-zh.d.ts +38 -2
  177. package/dist/data/emojis-zh.js +25 -2
  178. package/dist/data/emojis-zh.mjs +2 -0
  179. package/dist/data/index.js +1 -0
  180. package/dist/data/registerDefaultEmojiData.d.ts +1 -0
  181. package/dist/data/registerDefaultEmojiData.js +3 -0
  182. package/dist/data-core/pickerData.d.ts +12 -0
  183. package/dist/data-core/prepare.d.ts +23 -0
  184. package/dist/data-core/search.d.ts +13 -0
  185. package/dist/data-core/types.d.ts +23 -0
  186. package/dist/data.d.mts +72 -0
  187. package/dist/data.d.ts +72 -0
  188. package/dist/dataUtils/DataTypes.d.ts +23 -19
  189. package/dist/dataUtils/emojiSelectors.d.ts +15 -16
  190. package/dist/dataUtils/emojiUtils.d.ts +1 -12
  191. package/dist/dataUtils/nativeEmojiSequenceSupport.d.ts +10 -0
  192. package/dist/dataUtils/nativeEmojiSupport.d.ts +40 -0
  193. package/dist/dataUtils/parseNativeEmoji.d.ts +1 -1
  194. package/dist/dataUtils/suggested.d.ts +11 -11
  195. package/dist/dataUtils/suggestedEmojis.d.ts +7 -0
  196. package/dist/esm/chunks/chunk-46BUDQLE.mjs +228 -0
  197. package/dist/esm/chunks/chunk-7ZGTUZFB.mjs +16 -0
  198. package/dist/esm/chunks/chunk-FQIJ4HBW.mjs +25 -0
  199. package/dist/esm/chunks/chunk-KQPOIRSC.mjs +9305 -0
  200. package/dist/esm/chunks/chunk-LO4TPPLH.mjs +43 -0
  201. package/dist/esm/chunks/emojis-6L3GRBAP.mjs +9 -0
  202. package/dist/esm/data.mjs +18 -0
  203. package/dist/esm/index.mjs +389 -0
  204. package/dist/esm/primitives/index.mjs +67 -0
  205. package/dist/hooks/preloadEmoji.d.ts +9 -9
  206. package/dist/hooks/useActiveCategoryScrollDetection.d.ts +11 -11
  207. package/dist/hooks/useCategoryHeight.d.ts +6 -5
  208. package/dist/hooks/useCloseAllOpenToggles.d.ts +2 -2
  209. package/dist/hooks/useDataIdentityStabilityWarning.d.ts +13 -0
  210. package/dist/hooks/useDebouncedState.d.ts +1 -1
  211. package/dist/hooks/useDisallowMouseMove.d.ts +4 -4
  212. package/dist/hooks/useDisallowedEmojis.d.ts +2 -3
  213. package/dist/hooks/useEmojiPreviewEvents.d.ts +3 -3
  214. package/dist/hooks/useEmojiVirtualization.d.ts +21 -17
  215. package/dist/hooks/useFilter.d.ts +10 -16
  216. package/dist/hooks/useFocus.d.ts +3 -3
  217. package/dist/hooks/useInitialLoad.d.ts +2 -2
  218. package/dist/hooks/useIsEmojiHidden.d.ts +8 -8
  219. package/dist/hooks/useIsMounted.d.ts +1 -1
  220. package/dist/hooks/useIsSearchMode.d.ts +1 -1
  221. package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -2
  222. package/dist/hooks/useKeyboardNavigation.d.ts +3 -1
  223. package/dist/hooks/useMouseDownHandlers.d.ts +8 -4
  224. package/dist/hooks/useOnFocus.d.ts +1 -1
  225. package/dist/hooks/useOnScroll.d.ts +2 -2
  226. package/dist/hooks/useReactionsFocus.d.ts +1 -0
  227. package/dist/hooks/useRegisterRegion.d.ts +24 -0
  228. package/dist/hooks/useResolvedEmojiData.d.ts +37 -0
  229. package/dist/hooks/useScrollCategoryIntoView.d.ts +1 -1
  230. package/dist/hooks/useSearchController.d.ts +17 -0
  231. package/dist/hooks/useSearchResults.d.ts +8 -0
  232. package/dist/hooks/useSetVariationPicker.d.ts +2 -2
  233. package/dist/hooks/useShouldHideCustomEmojis.d.ts +1 -1
  234. package/dist/hooks/useShouldShowSkinTonePicker.d.ts +4 -4
  235. package/dist/index.d.mts +376 -0
  236. package/dist/index.d.ts +376 -9
  237. package/dist/index.js +2 -8
  238. package/dist/primitives/CategoryNav.d.ts +4 -0
  239. package/dist/primitives/Empty.d.ts +6 -0
  240. package/dist/primitives/List.d.ts +4 -0
  241. package/dist/primitives/LoadError.d.ts +8 -0
  242. package/dist/primitives/Loading.d.ts +4 -0
  243. package/dist/primitives/Panel.d.ts +3 -0
  244. package/dist/primitives/Preview.d.ts +3 -0
  245. package/dist/primitives/Reactions.d.ts +2 -0
  246. package/dist/primitives/Root.d.ts +26 -0
  247. package/dist/primitives/Search.d.ts +5 -0
  248. package/dist/primitives/SearchInput.d.ts +3 -0
  249. package/dist/primitives/SkinTone.d.ts +4 -0
  250. package/dist/primitives/Viewport.d.ts +19 -0
  251. package/dist/primitives/appearance.d.ts +3 -0
  252. package/dist/primitives/components.d.ts +27 -0
  253. package/dist/primitives/hooks.d.ts +48 -0
  254. package/dist/primitives/index.d.mts +561 -0
  255. package/dist/primitives/index.d.ts +561 -0
  256. package/dist/primitives/index.js +2 -0
  257. package/dist/primitives/nativeProps.d.ts +15 -0
  258. package/dist/primitives/scope.d.ts +13 -0
  259. package/dist/primitives/structuralStyles.d.ts +16 -0
  260. package/dist/primitives/tokens.d.ts +18 -0
  261. package/dist/primitives/types.d.ts +85 -0
  262. package/dist/state/activeEmojiStore.d.ts +7 -0
  263. package/dist/state/navigationRegistry.d.ts +61 -0
  264. package/dist/state/regionTraversal.d.ts +20 -0
  265. package/dist/typeRefinements/typeRefinements.d.ts +6 -6
  266. package/dist/types/exposedTypes.d.ts +16 -6
  267. package/dist/types/exposedTypes.js +5 -0
  268. package/dist/virtualization/virtualizationHelpers.d.ts +20 -18
  269. package/llms-full.txt +3680 -0
  270. package/llms.txt +66 -476
  271. package/package.json +204 -66
  272. package/src/DomUtils/categoryNameFromDom.ts +1 -1
  273. package/src/DomUtils/classNames.ts +1 -0
  274. package/src/DomUtils/elementPositionInRow.ts +26 -4
  275. package/src/DomUtils/eventBelongsToPicker.ts +17 -0
  276. package/src/DomUtils/getActiveElement.ts +7 -1
  277. package/src/DomUtils/isJsdom.ts +12 -0
  278. package/src/DomUtils/isRtl.ts +5 -0
  279. package/src/DomUtils/keyboardNavigation.ts +105 -149
  280. package/src/DomUtils/scrollTo.ts +18 -16
  281. package/src/DomUtils/selectors.ts +20 -111
  282. package/src/EmojiPickerReact.tsx +155 -48
  283. package/src/Stylesheet/DedupedStyle.tsx +80 -0
  284. package/src/Stylesheet/stylesheet.tsx +128 -59
  285. package/src/components/Layout/Flex.tsx +12 -11
  286. package/src/components/Reactions/BtnPlus.tsx +61 -51
  287. package/src/components/Reactions/Reactions.tsx +113 -55
  288. package/src/components/atoms/Button.tsx +18 -15
  289. package/src/components/body/EmojiCategory.tsx +77 -40
  290. package/src/components/body/EmojiList.tsx +55 -23
  291. package/src/components/body/EmojiVariationPicker.tsx +113 -66
  292. package/src/components/body/MeasureEmoji.tsx +9 -2
  293. package/src/components/body/listComponents.tsx +79 -0
  294. package/src/components/context/ElementRefContext.tsx +46 -35
  295. package/src/components/context/PickerConfigContext.tsx +55 -40
  296. package/src/components/context/PickerContext.tsx +567 -92
  297. package/src/components/context/PickerDataContext.tsx +110 -99
  298. package/src/components/emoji/BaseEmojiProps.ts +5 -4
  299. package/src/components/emoji/ClickableEmojiButton.tsx +148 -63
  300. package/src/components/emoji/Emoji.tsx +24 -2
  301. package/src/components/emoji/EmojiImg.tsx +13 -12
  302. package/src/components/emoji/ExportedEmoji.tsx +5 -4
  303. package/src/components/emoji/NativeEmoji.tsx +19 -16
  304. package/src/components/emoji/ViewOnlyEmoji.tsx +55 -30
  305. package/src/components/emoji/emojiStyles.ts +12 -11
  306. package/src/components/footer/Preview.tsx +86 -91
  307. package/src/components/header/Header.tsx +21 -6
  308. package/src/components/header/Search/BtnClearSearch.tsx +69 -55
  309. package/src/components/header/Search/IcnSearch.tsx +21 -21
  310. package/src/components/header/Search/Search.tsx +77 -137
  311. package/src/components/header/SkinTonePicker/BtnSkinToneVariation.tsx +99 -62
  312. package/src/components/header/SkinTonePicker/SkinTonePicker.tsx +84 -46
  313. package/src/components/icons/svgIcons.ts +17 -0
  314. package/src/components/main/PickerMainBehaviors.tsx +163 -0
  315. package/src/components/main/defaultAppearance.tsx +188 -0
  316. package/src/components/main/labelHeight.ts +5 -0
  317. package/src/components/navigation/CategoryButton.tsx +83 -57
  318. package/src/components/navigation/CategoryNavIcon.tsx +73 -194
  319. package/src/components/navigation/CategoryNavigation.tsx +118 -46
  320. package/src/config/categoryConfig.ts +7 -3
  321. package/src/config/cdnUrls.ts +11 -21
  322. package/src/config/compareConfig.ts +128 -30
  323. package/src/config/config.ts +228 -24
  324. package/src/config/defaultPickerConfiguration.ts +7 -0
  325. package/src/config/mutableConfig.ts +44 -20
  326. package/src/config/useConfig.ts +130 -37
  327. package/src/data/defaultEmojiData.ts +49 -0
  328. package/src/data/registerDefaultEmojiData.ts +8 -0
  329. package/src/data-core/pickerData.ts +173 -0
  330. package/src/data-core/prepare.ts +178 -0
  331. package/src/data-core/search.ts +122 -0
  332. package/src/data-core/types.ts +36 -0
  333. package/src/data.ts +10 -0
  334. package/src/dataUtils/DataTypes.ts +4 -0
  335. package/src/dataUtils/emojiSelectors.ts +58 -63
  336. package/src/dataUtils/emojiUtils.ts +17 -74
  337. package/src/dataUtils/nativeEmojiSequenceSupport.ts +116 -0
  338. package/src/dataUtils/nativeEmojiSupport.ts +160 -0
  339. package/src/dataUtils/suggested.ts +8 -4
  340. package/src/dataUtils/suggestedEmojis.ts +92 -0
  341. package/src/env.d.ts +4 -0
  342. package/src/hooks/preloadEmoji.ts +3 -3
  343. package/src/hooks/useActiveCategoryScrollDetection.ts +10 -1
  344. package/src/hooks/useCategoryHeight.ts +82 -8
  345. package/src/hooks/useDataIdentityStabilityWarning.ts +86 -0
  346. package/src/hooks/useDebouncedState.ts +13 -1
  347. package/src/hooks/useDisallowedEmojis.ts +26 -42
  348. package/src/hooks/useEmojiPreviewEvents.ts +43 -10
  349. package/src/hooks/useEmojiVirtualization.tsx +38 -4
  350. package/src/hooks/useFilter.ts +85 -127
  351. package/src/hooks/useFocus.ts +14 -24
  352. package/src/hooks/useIsEmojiHidden.ts +10 -6
  353. package/src/hooks/useKeyboardNavigation.ts +322 -429
  354. package/src/hooks/useMouseDownHandlers.ts +91 -16
  355. package/src/hooks/useOnFocus.ts +12 -3
  356. package/src/hooks/useOnScroll.ts +18 -2
  357. package/src/hooks/useReactionsFocus.ts +118 -0
  358. package/src/hooks/useRegisterRegion.ts +92 -0
  359. package/src/hooks/useResolvedEmojiData.ts +200 -0
  360. package/src/hooks/useScrollCategoryIntoView.ts +74 -12
  361. package/src/hooks/useSearchController.ts +369 -0
  362. package/src/hooks/useSearchResults.ts +51 -0
  363. package/src/hooks/useSetVariationPicker.ts +4 -1
  364. package/src/index.tsx +67 -5
  365. package/src/primitives/CategoryNav.tsx +40 -0
  366. package/src/primitives/Empty.tsx +68 -0
  367. package/src/primitives/List.tsx +61 -0
  368. package/src/primitives/LoadError.tsx +38 -0
  369. package/src/primitives/Loading.tsx +60 -0
  370. package/src/primitives/Panel.tsx +47 -0
  371. package/src/primitives/Preview.tsx +38 -0
  372. package/src/primitives/Reactions.tsx +2 -0
  373. package/src/primitives/Root.tsx +398 -0
  374. package/src/primitives/Search.tsx +41 -0
  375. package/src/primitives/SearchInput.tsx +110 -0
  376. package/src/primitives/SkinTone.tsx +76 -0
  377. package/src/primitives/Viewport.tsx +204 -0
  378. package/src/primitives/appearance.tsx +7 -0
  379. package/src/primitives/components.tsx +30 -0
  380. package/src/primitives/hooks.ts +165 -0
  381. package/src/primitives/index.ts +94 -0
  382. package/src/primitives/nativeProps.ts +90 -0
  383. package/src/primitives/scope.tsx +109 -0
  384. package/src/primitives/structuralStyles.tsx +133 -0
  385. package/src/primitives/tokens.ts +149 -0
  386. package/src/primitives/types.ts +192 -0
  387. package/src/state/activeEmojiStore.ts +39 -0
  388. package/src/state/navigationRegistry.ts +209 -0
  389. package/src/state/regionTraversal.ts +116 -0
  390. package/src/svg.d.ts +2 -2
  391. package/src/types/exposedTypes.ts +28 -1
  392. package/src/virtualization/virtualizationHelpers.ts +20 -6
  393. package/dist/components/body/Body.d.ts +0 -2
  394. package/dist/components/main/PickerMain.d.ts +0 -7
  395. package/dist/dataUtils/alphaNumericEmojiIndex.d.ts +0 -5
  396. package/dist/emoji-picker-react.cjs.development.js +0 -31413
  397. package/dist/emoji-picker-react.cjs.development.js.map +0 -1
  398. package/dist/emoji-picker-react.cjs.production.min.js +0 -2
  399. package/dist/emoji-picker-react.cjs.production.min.js.map +0 -1
  400. package/dist/emoji-picker-react.esm.js +0 -31411
  401. package/dist/emoji-picker-react.esm.js.map +0 -1
  402. package/dist/hooks/useHideEmojisByUniocode.d.ts +0 -1
  403. package/src/components/body/Body.tsx +0 -43
  404. package/src/components/main/PickerMain.tsx +0 -249
  405. package/src/dataUtils/alphaNumericEmojiIndex.ts +0 -21
@@ -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
12
  focusAndClickFirstVisibleEmoji,
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
17
  import { buttonFromTarget } from '../DomUtils/selectors';
20
18
  import {
19
+ ElementRef,
21
20
  useBodyRef,
22
21
  useCategoryNavigationRef,
23
22
  usePickerMainRef,
@@ -26,358 +25,285 @@ import {
26
25
  useSkinTonePickerRef,
27
26
  } from '../components/context/ElementRefContext';
28
27
  import {
29
- useReactionsModeState,
28
+ useNavigationRegistry,
29
+ useSearchComposingState,
30
30
  useSkinToneFanOpenState,
31
31
  } from '../components/context/PickerContext';
32
32
  import { useSearchDisabledConfig } from '../config/useConfig';
33
+ import {
34
+ NavigationRegionKind,
35
+ RegisteredRegion,
36
+ } from '../state/navigationRegistry';
37
+ import { getNextRegion, getPrevRegion } from '../state/regionTraversal';
33
38
 
34
39
  import {
35
40
  useCloseAllOpenToggles,
36
41
  useHasOpenToggles,
37
42
  } from './useCloseAllOpenToggles';
38
43
  import { useDisallowMouseMove } from './useDisallowMouseMove';
39
- import { useAppendSearch, useClearSearch } from './useFilter';
40
44
  import {
41
45
  useFocusCategoryNavigation,
42
46
  useFocusSearchInput,
43
47
  useFocusSkinTonePicker,
44
48
  } from './useFocus';
45
49
  import useIsSearchMode from './useIsSearchMode';
50
+ import { useClearSearchValue, useTypeToSearchKey } from './useSearchController';
46
51
  import useSetVariationPicker from './useSetVariationPicker';
47
- 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
- }
61
52
 
62
53
  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();
54
+ const ref = usePickerMainRef();
55
+ const hasOpenToggles = useHasOpenToggles();
56
+ const clearSearch = useClearSearchValue();
74
57
  const scrollTo = useScrollTo();
75
- const SearchInputRef = useSearchInputRef();
76
58
  const focusSearchInput = useFocusSearchInput();
77
- const hasOpenToggles = useHasOpenToggles();
78
59
  const disallowMouseMove = useDisallowMouseMove();
79
-
80
60
  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
61
  const focusSkinTonePicker = useFocusSkinTonePicker();
129
- const PickerMainRef = usePickerMainRef();
130
62
  const BodyRef = useBodyRef();
131
63
  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
-
169
- useEffect(() => {
170
- const current = SearchInputRef.current;
171
-
172
- if (!current) {
173
- return;
174
- }
175
-
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
64
  const [isOpen, setIsOpen] = useSkinToneFanOpenState();
190
- const isSkinToneInPreview = useIsSkinToneInPreview();
191
- const isSkinToneInSearch = useIsSkinToneInSearch();
65
+ const focusCategoryNavigation = useFocusCategoryNavigation();
66
+ const focusNextRegionFromSearch = useFocusNextRegionFrom('search');
67
+ const isSearchMode = useIsSearchMode();
68
+ const SkinTonePickerRef = useSkinTonePickerRef();
69
+ const isSkinToneInSearch = () =>
70
+ !!SkinTonePickerRef.current?.closest('[data-epr-part="search"]');
192
71
  const onType = useOnType();
72
+ const CategoryNavigationRef = useCategoryNavigationRef();
73
+ const focusPrevRegionFromCategories = useFocusPrevRegionFrom('categories');
74
+ const focusNextRegionFromCategories = useFocusNextRegionFrom('categories');
75
+ const ReactionsRef = useReactionsRef();
76
+ const focusPrevRegionFromGrid = useFocusPrevRegionFrom('grid');
77
+ const setVariationPicker = useSetVariationPicker();
78
+ const registry = useNavigationRegistry();
79
+ const goDownFromSearchInput = () => {
80
+ if (isSearchMode) return focusFirstVisibleEmoji(BodyRef.current);
81
+ focusCategoryNavigation();
82
+ };
83
+ const goUpFromBody = () => {
84
+ if (isSearchMode) return focusSearchInput();
85
+ focusPrevRegionFromGrid(focusCategoryNavigation);
86
+ };
87
+ const main = function onKeyDown(event: KeyboardEvent) {
88
+ const { key } = event;
193
89
 
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
- }
90
+ disallowMouseMove();
91
+ switch (key) {
92
+ case 'Escape':
93
+ event.preventDefault();
94
+ if (hasOpenToggles()) {
95
+ closeAllOpenToggles();
96
+ // A nested variation/tone menu consumes Escape before the
97
+ // host popover or autocomplete dismisses the whole picker.
98
+ event.stopPropagation();
99
+ return;
227
100
  }
101
+ clearSearch();
102
+ scrollTo(0);
103
+ focusSearchInput();
104
+ break;
105
+ }
106
+ };
107
+ const search = function onKeyDown(event: KeyboardEvent) {
108
+ if (!eventInRegion(event, SearchInputRef)) return;
109
+ const key = logicalArrowKey(event, SearchInputRef.current);
228
110
 
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
- }
111
+ switch (key) {
112
+ case 'ArrowRight':
113
+ if (!isSkinToneInSearch()) {
114
+ return;
249
115
  }
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;
116
+ event.preventDefault();
117
+ setIsOpen(true);
118
+ focusSkinTonePicker();
119
+ break;
120
+ case 'ArrowDown':
121
+ event.preventDefault();
122
+ // The active-search Search↔Grid exception
123
+ // wins over generic DOM-order traversal.
124
+ if (isSearchMode) {
125
+ goDownFromSearchInput();
126
+ } else {
127
+ focusNextRegionFromSearch(goDownFromSearchInput);
128
+ }
129
+ break;
130
+ case 'Enter':
131
+ event.preventDefault();
132
+ focusAndClickFirstVisibleEmoji(BodyRef.current);
133
+ break;
134
+ }
135
+ };
136
+ const tone = function onKeyDown(event: KeyboardEvent) {
137
+ if (!eventInRegion(event, SkinTonePickerRef)) return;
138
+ const key = logicalArrowKey(event, SkinTonePickerRef.current);
139
+ // The fan axis decides the arrow keys: the search placement and a
140
+ // horizontal SkinTone primitive move left/right, the preview
141
+ // placement and a vertical primitive move up/down.
142
+ const vertical =
143
+ SkinTonePickerRef.current?.getAttribute('data-epr-direction') ===
144
+ 'vertical';
145
+
146
+ switch (key) {
147
+ case vertical ? 'ArrowUp' : 'ArrowLeft':
148
+ event.preventDefault();
149
+ if (!isOpen) return focusSearchInput();
150
+ focusNextSkinTone(focusSearchInput);
151
+ break;
152
+ case vertical ? 'ArrowDown' : 'ArrowRight':
153
+ event.preventDefault();
154
+ if (!isOpen) return focusSearchInput();
155
+ focusPrevElementSibling(getActiveElement());
156
+ break;
157
+ case 'ArrowDown':
158
+ event.preventDefault();
159
+ setIsOpen(false);
160
+ goDownFromSearchInput();
161
+ break;
162
+ default:
163
+ onType(event);
164
+ }
165
+ };
166
+ const categories = function onKeyDown(event: KeyboardEvent) {
167
+ if (!eventInRegion(event, CategoryNavigationRef)) return;
168
+ // Arrows along the tab axis move between tabs; arrows across it
169
+ // leave for the previous/next region. A vertical tablist (e.g. a
170
+ // side rail) therefore uses Up/Down for tabs, per the ARIA tabs
171
+ // pattern, and Left/Right to leave.
172
+ const vertical =
173
+ CategoryNavigationRef.current?.getAttribute('aria-orientation') ===
174
+ 'vertical';
175
+ const key = logicalArrowKey(event, CategoryNavigationRef.current);
176
+ const direction = reactionFocusDelta(key);
177
+
178
+ if (!direction) {
179
+ onType(event);
180
+ return;
181
+ }
182
+ event.preventDefault();
183
+ if (vertical === (key === 'ArrowUp' || key === 'ArrowDown')) {
184
+ const focusSibling =
185
+ direction > 0 ? focusNextElementSibling : focusPrevElementSibling;
186
+ focusSibling(getActiveElement());
187
+ } else if (direction < 0) {
188
+ focusPrevRegionFromCategories(focusSearchInput);
189
+ } else {
190
+ focusNextRegionFromCategories(() =>
191
+ focusFirstVisibleEmoji(BodyRef.current),
192
+ );
193
+ }
194
+ };
195
+ const reactions = function onKeyDown(event: KeyboardEvent) {
196
+ if (!eventInRegion(event, ReactionsRef)) return;
197
+ const key = logicalArrowKey(event, ReactionsRef.current);
198
+ const current = ReactionsRef.current;
264
199
 
265
200
  if (!current) {
266
201
  return;
267
202
  }
268
203
 
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();
204
+ const delta = reactionFocusDelta(key);
282
205
 
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;
308
- }
309
- },
310
- [BodyRef, focusSearchInput, onType],
206
+ if (delta !== 0 && focusReactionSibling(current, delta)) {
207
+ event.preventDefault();
208
+ }
209
+ };
210
+ const body = function onKeyDown(event: KeyboardEvent) {
211
+ if (!eventInRegion(event, BodyRef)) return;
212
+ const key = logicalArrowKey(event, BodyRef.current);
213
+
214
+ const activeElement = buttonFromTarget(event.target as HTMLElement);
215
+ // Consumer controls own editing and activation; only managed emoji
216
+ // buttons issue grid commands.
217
+ if (!activeElement) return;
218
+
219
+ // Navigation-generation guard: a pending
220
+ // materialize/scroll/focus completion aborts when search, data,
221
+ // geometry, or reactions state changed underneath it.
222
+ const generation = registry.currentGeneration();
223
+ const focusGuard = () => registry.isCurrent(generation);
224
+
225
+ const direction = reactionFocusDelta(key);
226
+ if (direction) {
227
+ event.preventDefault();
228
+ const row = key === 'ArrowDown' || key === 'ArrowUp';
229
+ if (row && hasOpenToggles()) closeAllOpenToggles();
230
+ else
231
+ focusAdjacentEmoji(activeElement, direction, row, {
232
+ shouldFocus: focusGuard,
233
+ exitUp: row ? goUpFromBody : undefined,
234
+ });
235
+ return;
236
+ }
237
+ switch (key) {
238
+ case ' ':
239
+ event.preventDefault();
240
+ setVariationPicker(event.target as HTMLElement);
241
+ break;
242
+ default:
243
+ onType(event);
244
+ break;
245
+ }
246
+ };
247
+ const [composing] = useSearchComposingState();
248
+ const handler = useRef<(event: KeyboardEvent, menusOnly?: boolean) => void>(
249
+ () => {},
311
250
  );
312
-
313
- useEffect(() => {
314
- const current = CategoryNavigationRef.current;
315
-
316
- if (!current) {
251
+ // One listener owns the native lifecycle; current callbacks supply state
252
+ // and live region refs without reattaching on every picker render.
253
+ handler.current = (event, menusOnly) => {
254
+ if (
255
+ composing ||
256
+ event.isComposing ||
257
+ event.keyCode === 229 ||
258
+ (menusOnly && !hasOpenToggles())
259
+ )
317
260
  return;
261
+ if (!menusOnly) {
262
+ search(event);
263
+ tone(event);
264
+ categories(event);
265
+ reactions(event);
266
+ body(event);
318
267
  }
319
-
320
- current.addEventListener('keydown', onKeyDown);
321
-
268
+ main(event);
269
+ };
270
+ useEffect(() => {
271
+ // RootAside owns this native aside for its entire mounted lifetime.
272
+ // Descendant parts are read through live refs by the delegated handler.
273
+ const current = ref.current;
274
+ if (!current) return;
275
+ const scoped = (event: KeyboardEvent) => {
276
+ if (eventBelongsToPicker(event, current)) handler.current(event);
277
+ };
278
+ const capture = (event: KeyboardEvent) => {
279
+ if (event.key === 'Escape' && eventBelongsToPicker(event, current))
280
+ handler.current(event, true);
281
+ };
282
+ current.addEventListener('keydown', scoped);
283
+ window.addEventListener('keydown', capture, true);
322
284
  return () => {
323
- current.removeEventListener('keydown', onKeyDown);
285
+ current.removeEventListener('keydown', scoped);
286
+ window.removeEventListener('keydown', capture, true);
324
287
  };
325
- }, [CategoryNavigationRef, BodyRef, onKeyDown]);
288
+ }, [ref]);
289
+ }
290
+
291
+ function eventInRegion(event: KeyboardEvent, ref: ElementRef): boolean {
292
+ return !!ref.current?.contains(event.target as Node);
326
293
  }
327
294
 
328
295
  // Arrow keys move between reaction buttons (and the expand button)
329
296
  // while the reactions bar has focus. The bar lives outside the scroll
330
297
  // body, so the body handler never sees these events.
331
298
  // 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
299
 
374
300
  function reactionFocusDelta(key: string): number {
375
301
  switch (key) {
376
- case KeyboardEvents.ArrowRight:
377
- case KeyboardEvents.ArrowDown:
302
+ case 'ArrowRight':
303
+ case 'ArrowDown':
378
304
  return 1;
379
- case KeyboardEvents.ArrowLeft:
380
- case KeyboardEvents.ArrowUp:
305
+ case 'ArrowLeft':
306
+ case 'ArrowUp':
381
307
  return -1;
382
308
  default:
383
309
  return 0;
@@ -386,9 +312,7 @@ function reactionFocusDelta(key: string): number {
386
312
 
387
313
  function focusReactionSibling(root: HTMLElement, delta: number): boolean {
388
314
  const buttons = Array.from(root.querySelectorAll('button'));
389
- const activeIndex = buttons.indexOf(
390
- getActiveElement() as HTMLButtonElement,
391
- );
315
+ const activeIndex = buttons.indexOf(getActiveElement() as HTMLButtonElement);
392
316
  const sibling = activeIndex === -1 ? null : buttons[activeIndex + delta];
393
317
 
394
318
  if (!sibling) {
@@ -399,141 +323,87 @@ function focusReactionSibling(root: HTMLElement, delta: number): boolean {
399
323
  return true;
400
324
  }
401
325
 
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() {
326
+ // Cross-region focus through the registered semantic graph. Returns true
327
+ // when a region handled the move; callers keep their legacy direct target
328
+ // as fallback so unregistered trees behave exactly as before. Exported for
329
+ // focus-restoration flows (reactions transitions) that target regions.
330
+ export function useFocusRegion() {
331
+ const focusSearchInput = useFocusSearchInput();
478
332
  const focusCategoryNavigation = useFocusCategoryNavigation();
479
- const isSearchMode = useIsSearchMode();
333
+ const focusSkinTonePicker = useFocusSkinTonePicker();
480
334
  const BodyRef = useBodyRef();
335
+ const ReactionsRef = useReactionsRef();
336
+ const registry = useNavigationRegistry();
481
337
 
482
338
  return useCallback(
483
- function goDownFromSearchInput() {
484
- if (isSearchMode) {
485
- return focusFirstVisibleEmoji(BodyRef.current);
339
+ function focusRegion(region: RegisteredRegion | undefined): boolean {
340
+ if (!region) {
341
+ return false;
486
342
  }
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();
343
+ // Grid entry is a materialize/scroll/focus operation: capture the
344
+ // navigation generation so a stale completion cannot steal focus.
345
+ const generation = registry.currentGeneration();
346
+ const focusGuard = () => registry.isCurrent(generation);
347
+ switch (region.kind as NavigationRegionKind) {
348
+ case 'search':
349
+ focusSearchInput();
350
+ return true;
351
+ case 'categories':
352
+ focusCategoryNavigation();
353
+ return true;
354
+ case 'grid':
355
+ focusFirstVisibleEmoji(BodyRef.current, focusGuard);
356
+ return true;
357
+ case 'reactions': {
358
+ const firstButton = ReactionsRef.current?.querySelector('button');
359
+ focusElement(firstButton ?? null, focusGuard);
360
+ return true;
361
+ }
362
+ case 'preview-skin-tone':
363
+ focusSkinTonePicker();
364
+ return true;
502
365
  }
503
- return focusCategoryNavigation();
504
366
  },
505
- [focusSearchInput, isSearchMode, focusCategoryNavigation],
367
+ [
368
+ focusSearchInput,
369
+ focusCategoryNavigation,
370
+ focusSkinTonePicker,
371
+ BodyRef,
372
+ ReactionsRef,
373
+ registry,
374
+ ],
506
375
  );
507
376
  }
508
377
 
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);
378
+ function useFocusNextRegionFrom(from: NavigationRegionKind) {
379
+ return useFocusAdjacentRegion(from, 1);
521
380
  }
522
381
 
523
- function focusPrevSkinTone() {
524
- const currentSkinTone = getActiveElement();
382
+ function useFocusPrevRegionFrom(from: NavigationRegionKind) {
383
+ return useFocusAdjacentRegion(from, -1);
384
+ }
525
385
 
526
- if (!currentSkinTone) {
527
- return;
528
- }
386
+ function useFocusAdjacentRegion(from: NavigationRegionKind, direction: number) {
387
+ const registry = useNavigationRegistry();
388
+ const PickerMainRef = usePickerMainRef();
389
+ const focusRegion = useFocusRegion();
390
+ return (fallback?: () => void) => {
391
+ const getRegion = direction > 0 ? getNextRegion : getPrevRegion;
392
+ if (!focusRegion(getRegion(registry, PickerMainRef.current, from)))
393
+ fallback?.();
394
+ };
395
+ }
529
396
 
530
- focusPrevElementSibling(currentSkinTone);
397
+ function focusNextSkinTone(exitLeft: () => void) {
398
+ const current = getActiveElement();
399
+ if (current && !current.nextElementSibling) exitLeft();
400
+ focusNextElementSibling(current);
531
401
  }
532
402
 
533
403
  function useOnType() {
534
- const appendSearch = useAppendSearch();
535
- const focusSearchInput = useFocusSearchInput();
404
+ const typeToSearch = useTypeToSearchKey();
536
405
  const searchDisabled = useSearchDisabledConfig();
406
+ const SearchInputRef = useSearchInputRef();
537
407
  const closeAllOpenToggles = useCloseAllOpenToggles();
538
408
 
539
409
  return function onType(event: KeyboardEvent) {
@@ -543,15 +413,38 @@ function useOnType() {
543
413
  return;
544
414
  }
545
415
 
546
- if (key.match(/(^[a-zA-Z0-9]$){1}/)) {
416
+ // No search input mounted (a bare Root without Search): the
417
+ // transition would no-op, so the key is left unclaimed — no
418
+ // preventDefault, no toggle closing — exactly like searchDisabled.
419
+ if (!SearchInputRef.current) {
420
+ return;
421
+ }
422
+
423
+ // One printable letter or digit in any script (not only ASCII): the
424
+ // localized datasets are searched in their own languages, so a Hebrew,
425
+ // Cyrillic or Japanese keystroke on the grid starts a search like a
426
+ // Latin one. Named keys ("Enter", "Tab") are longer than one code
427
+ // point; space and punctuation stay unclaimed for the grid's own keys.
428
+ if (/^[\p{L}\p{N}]$/u.test(key)) {
547
429
  event.preventDefault();
548
430
  closeAllOpenToggles();
549
- focusSearchInput();
550
- appendSearch(key);
431
+ typeToSearch(key);
551
432
  }
552
433
  };
553
434
  }
554
435
 
436
+ // Left/right arrows follow the visual direction: under `dir="rtl"` the
437
+ // grid, tabs, reactions and tone fan are mirrored, so ArrowLeft moves
438
+ // forward. The live region element supplies the computed direction.
439
+ function logicalArrowKey(event: KeyboardEvent, region: Element | null): string {
440
+ const { key } = event;
441
+ return (key === 'ArrowLeft' || key === 'ArrowRight') && isRtl(region)
442
+ ? key === 'ArrowLeft'
443
+ ? 'ArrowRight'
444
+ : 'ArrowLeft'
445
+ : key;
446
+ }
447
+
555
448
  function hasModifier(event: KeyboardEvent): boolean {
556
449
  const { metaKey, ctrlKey, altKey } = event;
557
450