emoji-picker-react 4.22.3 → 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 +331 -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 -480
  271. package/package.json +180 -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,6 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { useEffect, useRef } from 'react';
3
3
 
4
+ import { eventBelongsToPicker } from '../DomUtils/eventBelongsToPicker';
4
5
  import {
5
6
  allUnifiedFromEmojiElement,
6
7
  isEmojiElement,
@@ -22,15 +23,21 @@ import {
22
23
  } from '../config/useConfig';
23
24
  import { DataEmoji } from '../dataUtils/DataTypes';
24
25
  import {
25
- activeVariationFromUnified,
26
+ skinToneFromEmoji,
26
27
  emojiHasVariations,
27
28
  emojiNames,
28
29
  emojiUnified,
30
+ emojiCanonicalUnified,
29
31
  } from '../dataUtils/emojiUtils';
30
32
  import { parseNativeEmoji } from '../dataUtils/parseNativeEmoji';
31
33
  import { setSuggested } from '../dataUtils/suggested';
32
34
  import { isCustomEmoji } from '../typeRefinements/typeRefinements';
33
- import { EmojiClickData, SkinTones, EmojiStyle } from '../types/exposedTypes';
35
+ import {
36
+ EmojiClickData,
37
+ EmojiStyle,
38
+ EmojiStyleValue,
39
+ SkinTones,
40
+ } from '../types/exposedTypes';
34
41
 
35
42
  import { useCloseAllOpenToggles } from './useCloseAllOpenToggles';
36
43
  import useSetVariationPicker from './useSetVariationPicker';
@@ -39,7 +46,9 @@ export function useMouseDownHandlers(
39
46
  ContainerRef: React.MutableRefObject<NullableElement>,
40
47
  mouseEventSource: MOUSE_EVENT_SOURCE,
41
48
  ) {
42
- const mouseDownTimerRef = useRef<ReturnType<typeof setTimeout>>();
49
+ const mouseDownTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(
50
+ undefined,
51
+ );
43
52
  const setVariationPicker = useSetVariationPicker();
44
53
  const disallowClickRef = useDisallowClickRef();
45
54
  const [, setEmojiVariationPicker] = useEmojiVariationPickerState();
@@ -53,20 +62,23 @@ export function useMouseDownHandlers(
53
62
 
54
63
  const onClick = React.useCallback(
55
64
  function onClick(event: MouseEvent) {
56
- if (disallowClickRef.current) {
65
+ if (
66
+ !eventBelongsToPicker(event, ContainerRef.current) ||
67
+ disallowClickRef.current
68
+ ) {
57
69
  return;
58
70
  }
59
71
 
60
72
  closeAllOpenToggles();
61
73
 
62
- const [emoji, unified] = emojiFromEvent(event, emojiByUnified);
74
+ const [record, unified] = emojiFromEvent(event, emojiByUnified);
63
75
 
64
- if (!emoji || !unified) {
76
+ if (!record || !unified) {
65
77
  return;
66
78
  }
67
79
 
68
- const skinToneToUse =
69
- activeVariationFromUnified(unified) || activeSkinTone;
80
+ const emoji = { ...record, renderUnified: unified };
81
+ const skinToneToUse = skinToneFromEmoji(emoji, unified, activeSkinTone);
70
82
 
71
83
  updateSuggested();
72
84
  setSuggested(emoji, skinToneToUse);
@@ -76,6 +88,7 @@ export function useMouseDownHandlers(
76
88
  );
77
89
  },
78
90
  [
91
+ ContainerRef,
79
92
  activeSkinTone,
80
93
  closeAllOpenToggles,
81
94
  disallowClickRef,
@@ -89,6 +102,7 @@ export function useMouseDownHandlers(
89
102
 
90
103
  const onMouseDown = React.useCallback(
91
104
  function onMouseDown(event: MouseEvent) {
105
+ if (!eventBelongsToPicker(event, ContainerRef.current)) return;
92
106
  if (mouseDownTimerRef.current) {
93
107
  clearTimeout(mouseDownTimerRef.current);
94
108
  }
@@ -99,15 +113,19 @@ export function useMouseDownHandlers(
99
113
  return;
100
114
  }
101
115
 
116
+ // Preserve the native target before dispatch ends. Shadow DOM events
117
+ // are retargeted to the host afterward, before the long-press timer.
118
+ const target = event.target as HTMLElement;
102
119
  mouseDownTimerRef.current = setTimeout(() => {
103
120
  disallowClickRef.current = true;
104
121
  mouseDownTimerRef.current = undefined;
105
122
  closeAllOpenToggles();
106
- setVariationPicker(event.target as HTMLElement);
123
+ setVariationPicker(target);
107
124
  setEmojiVariationPicker(emoji);
108
125
  }, 500);
109
126
  },
110
127
  [
128
+ ContainerRef,
111
129
  disallowClickRef,
112
130
  emojiByUnified,
113
131
  closeAllOpenToggles,
@@ -140,6 +158,40 @@ export function useMouseDownHandlers(
140
158
  return;
141
159
  }
142
160
  const confainerRef = ContainerRef.current;
161
+ let pointerStart: { x: number; y: number } | null = null;
162
+ const cancelPress = () => {
163
+ if (mouseDownTimerRef.current) clearTimeout(mouseDownTimerRef.current);
164
+ mouseDownTimerRef.current = undefined;
165
+ pointerStart = null;
166
+ };
167
+ const onPointerDown = (event: PointerEvent) => {
168
+ if (event.pointerType === 'mouse' || !event.isPrimary) return;
169
+ pointerStart = { x: event.clientX, y: event.clientY };
170
+ onMouseDown(event);
171
+ };
172
+ const onPointerMove = (event: PointerEvent) => {
173
+ if (
174
+ pointerStart &&
175
+ Math.hypot(
176
+ event.clientX - pointerStart.x,
177
+ event.clientY - pointerStart.y,
178
+ ) > 8
179
+ ) {
180
+ cancelPress();
181
+ }
182
+ };
183
+ const onPointerUp = (event: PointerEvent) => {
184
+ if (event.pointerType === 'mouse') return;
185
+ onMouseUp();
186
+ pointerStart = null;
187
+ };
188
+ const onPointerCancel = () => {
189
+ onMouseUp();
190
+ cancelPress();
191
+ };
192
+ const onContextMenu = (event: Event) => {
193
+ if (disallowClickRef.current) event.preventDefault();
194
+ };
143
195
  confainerRef.addEventListener('click', onClick, {
144
196
  passive: true,
145
197
  });
@@ -150,13 +202,32 @@ export function useMouseDownHandlers(
150
202
  confainerRef.addEventListener('mouseup', onMouseUp, {
151
203
  passive: true,
152
204
  });
205
+ confainerRef.addEventListener('pointerdown', onPointerDown, {
206
+ passive: true,
207
+ });
208
+ confainerRef.addEventListener('pointermove', onPointerMove, {
209
+ passive: true,
210
+ });
211
+ window.addEventListener('pointerup', onPointerUp, { passive: true });
212
+ window.addEventListener('pointercancel', onPointerCancel, {
213
+ passive: true,
214
+ });
215
+ confainerRef.addEventListener('scroll', cancelPress, { passive: true });
216
+ confainerRef.addEventListener('contextmenu', onContextMenu);
153
217
 
154
218
  return () => {
219
+ cancelPress();
155
220
  confainerRef?.removeEventListener('click', onClick);
156
221
  confainerRef?.removeEventListener('mousedown', onMouseDown);
157
222
  confainerRef?.removeEventListener('mouseup', onMouseUp);
223
+ confainerRef.removeEventListener('pointerdown', onPointerDown);
224
+ confainerRef.removeEventListener('pointermove', onPointerMove);
225
+ window.removeEventListener('pointerup', onPointerUp);
226
+ window.removeEventListener('pointercancel', onPointerCancel);
227
+ confainerRef.removeEventListener('scroll', cancelPress);
228
+ confainerRef.removeEventListener('contextmenu', onContextMenu);
158
229
  };
159
- }, [ContainerRef, onClick, onMouseDown, onMouseUp]);
230
+ }, [ContainerRef, onClick, onMouseDown, onMouseUp, disallowClickRef]);
160
231
  }
161
232
 
162
233
  function emojiFromEvent(
@@ -179,13 +250,13 @@ function emojiFromEvent(
179
250
  return [];
180
251
  }
181
252
 
182
- return [emoji, unified ?? resolvedUnified];
253
+ return [emoji, resolvedUnified];
183
254
  }
184
255
 
185
- function emojiClickOutput(
256
+ export function emojiClickOutput(
186
257
  emoji: DataEmoji,
187
258
  activeSkinTone: SkinTones,
188
- activeEmojiStyle: EmojiStyle,
259
+ activeEmojiStyle: EmojiStyleValue,
189
260
  getEmojiUrl: GetEmojiUrl,
190
261
  ): EmojiClickData {
191
262
  const names = emojiNames(emoji);
@@ -206,17 +277,21 @@ function emojiClickOutput(
206
277
  };
207
278
  }
208
279
  const unified = emojiUnified(emoji, activeSkinTone);
280
+ const imageStyle =
281
+ activeEmojiStyle === EmojiStyle.NATIVE
282
+ ? EmojiStyle.APPLE
283
+ : activeEmojiStyle;
209
284
 
210
285
  return {
211
286
  activeSkinTone,
212
287
  emoji: parseNativeEmoji(unified),
213
- getImageUrl(emojiStyle: EmojiStyle = activeEmojiStyle ?? EmojiStyle.APPLE) {
288
+ getImageUrl(emojiStyle: EmojiStyleValue = imageStyle) {
214
289
  return getEmojiUrl(unified, emojiStyle);
215
290
  },
216
- imageUrl: getEmojiUrl(unified, activeEmojiStyle ?? EmojiStyle.APPLE),
291
+ imageUrl: getEmojiUrl(unified, imageStyle),
217
292
  isCustom: false,
218
293
  names,
219
294
  unified,
220
- unifiedWithoutSkinTone: emojiUnified(emoji),
295
+ unifiedWithoutSkinTone: emojiCanonicalUnified(emoji),
221
296
  };
222
297
  }
@@ -1,7 +1,12 @@
1
1
  import { useEffect } from 'react';
2
2
 
3
- import { buttonFromTarget, emojiFromElement } from '../DomUtils/selectors';
3
+ import { eventBelongsToPicker } from '../DomUtils/eventBelongsToPicker';
4
+ import {
5
+ allUnifiedFromEmojiElement,
6
+ buttonFromTarget,
7
+ } from '../DomUtils/selectors';
4
8
  import { useBodyRef } from '../components/context/ElementRefContext';
9
+ import { usePickerDataContext } from '../components/context/PickerDataContext';
5
10
  import { useEmojiStyleConfig, useGetEmojiUrlConfig } from '../config/useConfig';
6
11
  import { emojiHasVariations } from '../dataUtils/emojiUtils';
7
12
  import { EmojiStyle } from '../types/exposedTypes';
@@ -12,6 +17,8 @@ export function useOnFocus() {
12
17
  const BodyRef = useBodyRef();
13
18
  const emojiStyle = useEmojiStyleConfig();
14
19
  const getEmojiUrl = useGetEmojiUrlConfig();
20
+ // Root-scoped lookup: honors localized emojiData and custom emojis.
21
+ const { emojiByUnified } = usePickerDataContext();
15
22
 
16
23
  useEffect(() => {
17
24
  if (emojiStyle === EmojiStyle.NATIVE) {
@@ -27,13 +34,15 @@ export function useOnFocus() {
27
34
  };
28
35
 
29
36
  function onFocus(event: FocusEvent) {
37
+ if (!eventBelongsToPicker(event, bodyRef)) return;
30
38
  const button = buttonFromTarget(event.target as HTMLElement);
31
39
 
32
40
  if (!button) {
33
41
  return;
34
42
  }
35
43
 
36
- const [emoji] = emojiFromElement(button);
44
+ const { unified, originalUnified } = allUnifiedFromEmojiElement(button);
45
+ const emoji = emojiByUnified(unified ?? originalUnified ?? undefined);
37
46
 
38
47
  if (!emoji) {
39
48
  return;
@@ -43,5 +52,5 @@ export function useOnFocus() {
43
52
  preloadEmoji(getEmojiUrl, emoji, emojiStyle);
44
53
  }
45
54
  }
46
- }, [BodyRef, emojiStyle, getEmojiUrl]);
55
+ }, [BodyRef, emojiStyle, getEmojiUrl, emojiByUnified]);
47
56
  }
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
2
 
3
3
  import { ElementRef } from '../components/context/ElementRefContext';
4
4
 
@@ -7,6 +7,9 @@ import { useCloseAllOpenToggles } from './useCloseAllOpenToggles';
7
7
  export function useOnScroll(BodyRef: ElementRef) {
8
8
  const closeAllOpenToggles = useCloseAllOpenToggles();
9
9
  const [scrollTop, setScrollTop] = useState(0);
10
+ const latestRef = useRef(0);
11
+ const scheduledRef = useRef(false);
12
+ const frameRef = useRef(0);
10
13
 
11
14
  useEffect(() => {
12
15
  const bodyRef = BodyRef.current;
@@ -18,13 +21,26 @@ export function useOnScroll(BodyRef: ElementRef) {
18
21
  passive: true,
19
22
  });
20
23
 
24
+ // High-frequency geometry work coalesces to at most one scheduled
25
+ // virtualization update per animation frame.
26
+ // Toggle dismissal stays immediate: those setters bail out when
27
+ // nothing is open, so unrelated regions never rerender for it.
21
28
  function onScroll() {
22
- setScrollTop(bodyRef?.scrollTop ?? 0);
29
+ latestRef.current = bodyRef?.scrollTop ?? 0;
30
+ if (!scheduledRef.current) {
31
+ scheduledRef.current = true;
32
+ frameRef.current = requestAnimationFrame(() => {
33
+ scheduledRef.current = false;
34
+ setScrollTop(latestRef.current);
35
+ });
36
+ }
23
37
  closeAllOpenToggles();
24
38
  }
25
39
 
26
40
  return () => {
27
41
  bodyRef?.removeEventListener('scroll', onScroll);
42
+ cancelAnimationFrame(frameRef.current);
43
+ scheduledRef.current = false;
28
44
  };
29
45
  }, [BodyRef, closeAllOpenToggles]);
30
46
 
@@ -0,0 +1,118 @@
1
+ import * as React from 'react';
2
+
3
+ import { focusElement } from '../DomUtils/focusElement';
4
+ import {
5
+ usePickerMainRef,
6
+ useReactionsRef,
7
+ useSearchInputRef,
8
+ } from '../components/context/ElementRefContext';
9
+ import {
10
+ useNavigationRegistry,
11
+ useReactionsModeState,
12
+ } from '../components/context/PickerContext';
13
+ import { useAutoFocusSearchConfig } from '../config/useConfig';
14
+ import { getActiveRegionsInDomOrder } from '../state/regionTraversal';
15
+
16
+ import { useFocusSearchInput } from './useFocus';
17
+ import { useFocusRegion } from './useKeyboardNavigation';
18
+
19
+ // Reactions ↔ full-picker focus management.
20
+ //
21
+ // - On expansion (compact → full): the managed panel activates; after its
22
+ // destination exists, Search is preferred when present and autofocus is
23
+ // enabled, otherwise the next valid focusable region takes focus. No
24
+ // focus transfer waits on an arbitrary animation timeout — effects run
25
+ // after commit, when destinations exist.
26
+ // - On collapse (full → compact): the panel deactivates and focus restores
27
+ // to the initiating control when it is still connected and outside the
28
+ // now-inert panel; otherwise the first valid Reactions control.
29
+ // - Initial mount never steals focus in either mode (autofocus, when
30
+ // enabled, remains the browser/input's own behavior).
31
+ export function useReactionsFocusManager() {
32
+ const PickerMainRef = usePickerMainRef();
33
+ const ReactionsRef = useReactionsRef();
34
+ const SearchInputRef = useSearchInputRef();
35
+ const [reactionsOpen] = useReactionsModeState();
36
+ const autoFocusSearch = useAutoFocusSearchConfig();
37
+ const focusSearchInput = useFocusSearchInput();
38
+ const focusRegion = useFocusRegion();
39
+ const registry = useNavigationRegistry();
40
+ const lastControlRef = React.useRef<HTMLElement | null>(null);
41
+ // Previous-value ref (not a first-run flag): StrictMode double-invokes
42
+ // this effect on mount, and a flag would treat the second run as a
43
+ // transition and steal focus. Comparing values keeps both runs silent.
44
+ const prevReactionsOpen = React.useRef(reactionsOpen);
45
+
46
+ // Remember the initiating control: the last focused element inside Root.
47
+ React.useEffect(() => {
48
+ const root = PickerMainRef.current;
49
+ if (!root) {
50
+ return;
51
+ }
52
+ function onFocusIn(event: FocusEvent) {
53
+ if (event.target instanceof HTMLElement) {
54
+ lastControlRef.current = event.target;
55
+ }
56
+ }
57
+ root.addEventListener('focusin', onFocusIn);
58
+ return () => {
59
+ root.removeEventListener('focusin', onFocusIn);
60
+ };
61
+ }, [PickerMainRef]);
62
+
63
+ React.useEffect(() => {
64
+ if (prevReactionsOpen.current === reactionsOpen) {
65
+ return;
66
+ }
67
+ prevReactionsOpen.current = reactionsOpen;
68
+ if (!reactionsOpen) {
69
+ focusAfterExpansion();
70
+ } else {
71
+ focusAfterCollapse();
72
+ }
73
+ // Runs on reactions-mode transitions only.
74
+ // eslint-disable-next-line react-hooks/exhaustive-deps
75
+ }, [reactionsOpen]);
76
+
77
+ function focusAfterExpansion() {
78
+ if (autoFocusSearch && SearchInputRef.current) {
79
+ focusSearchInput();
80
+ return;
81
+ }
82
+ // Next valid focusable region in DOM order (Search deliberately
83
+ // skipped here: without autofocus it is not the preferred entry).
84
+ const root = PickerMainRef.current;
85
+ const active = getActiveRegionsInDomOrder(registry, root).filter(
86
+ (region) => region.kind !== 'search' && region.kind !== 'reactions',
87
+ );
88
+ focusRegion(active[0]);
89
+ }
90
+
91
+ function focusAfterCollapse() {
92
+ const last = lastControlRef.current;
93
+ const panel = PickerMainRef.current?.querySelector(
94
+ '[data-epr-part="panel"]',
95
+ );
96
+ if (
97
+ last &&
98
+ last.isConnected &&
99
+ !(panel && panel.contains(last)) &&
100
+ isFocusable(last)
101
+ ) {
102
+ focusElement(last);
103
+ return;
104
+ }
105
+ const firstReaction = ReactionsRef.current?.querySelector('button');
106
+ focusElement(firstReaction ?? null);
107
+ }
108
+
109
+ function isFocusable(element: HTMLElement): boolean {
110
+ if (element.hidden || element.getAttribute('aria-hidden') === 'true') {
111
+ return false;
112
+ }
113
+ if (element instanceof HTMLButtonElement && element.disabled) {
114
+ return false;
115
+ }
116
+ return true;
117
+ }
118
+ }
@@ -0,0 +1,92 @@
1
+ import * as React from 'react';
2
+
3
+ import { usePickerMainRef } from '../components/context/ElementRefContext';
4
+ import { useNavigationRegistry } from '../components/context/PickerContext';
5
+ import {
6
+ NavigationRegionKind,
7
+ reportDuplicateRegion,
8
+ reportPortalRegion,
9
+ } from '../state/navigationRegistry';
10
+
11
+ /**
12
+ * Register a semantic focus region root with the Root-scoped navigation
13
+ * registry.
14
+ *
15
+ * - Registers after mount and unregisters on unmount; omitted regions
16
+ * (null element) stay absent from the graph — there are no placeholder
17
+ * or dead graph nodes.
18
+ * - `presenceDeps` reruns registration when presence-affecting state
19
+ * changes (e.g. a tab bar that unmounts below two categories).
20
+ * - Portal roots (outside the Root DOM element) are unsupported: dev
21
+ * warns and the region is excluded from traversal.
22
+ * - A second singleton registration throws in development and is ignored
23
+ * (first wins) in production.
24
+ */
25
+ export function useRegisterRegion(
26
+ kind: NavigationRegionKind,
27
+ ref: React.RefObject<Element | null> & {
28
+ subscribe?: (listener: () => void) => () => void;
29
+ },
30
+ presenceDeps: readonly unknown[] = [],
31
+ ): void {
32
+ const registry = useNavigationRegistry();
33
+ const PickerMainRef = usePickerMainRef();
34
+
35
+ React.useEffect(() => {
36
+ if (!registry.claimSingleton(kind)) {
37
+ // Development throws inside; production warns once and falls
38
+ // through to the return below, keeping the first registration
39
+ // authoritative.
40
+ reportDuplicateRegion(kind);
41
+ return;
42
+ }
43
+
44
+ let unregister: (() => void) | undefined;
45
+ const attach = () => {
46
+ unregister?.();
47
+ unregister = undefined;
48
+ const element = ref.current;
49
+ if (!element) return;
50
+ const rootElement = PickerMainRef.current;
51
+ if (rootElement && !rootElement.contains(element)) {
52
+ reportPortalRegion(kind);
53
+ return;
54
+ }
55
+ // A tone control composed inside Search belongs to that region's
56
+ // local keyboard flow, rather than an extra region on ArrowDown.
57
+ if (
58
+ kind === 'preview-skin-tone' &&
59
+ element.closest('[data-epr-part="search"]')
60
+ )
61
+ return;
62
+ unregister = registry.register(kind, element);
63
+ };
64
+ attach();
65
+ const unsubscribe = ref.subscribe?.(attach);
66
+ return () => {
67
+ unsubscribe?.();
68
+ unregister?.();
69
+ registry.releaseSingleton(kind);
70
+ };
71
+ // presenceDeps reruns the effect when region presence may have changed.
72
+ // eslint-disable-next-line react-hooks/exhaustive-deps
73
+ }, [registry, kind, ref, PickerMainRef, ...presenceDeps]);
74
+ }
75
+
76
+ /**
77
+ * Claim a structural singleton slot (Viewport/Preview)
78
+ * without registering a focus region. Same duplicate policy as regions.
79
+ */
80
+ export function useSingletonClaim(kind: string): void {
81
+ const registry = useNavigationRegistry();
82
+
83
+ React.useEffect(() => {
84
+ if (!registry.claimSingleton(kind)) {
85
+ reportDuplicateRegion(kind);
86
+ return;
87
+ }
88
+ return () => {
89
+ registry.releaseSingleton(kind);
90
+ };
91
+ }, [registry, kind]);
92
+ }