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.
- package/README.md +331 -49
- package/dist/DomUtils/categoryNameFromDom.d.ts +1 -1
- package/dist/DomUtils/classNames.d.ts +23 -23
- package/dist/DomUtils/detectEmojyPartiallyBelowFold.d.ts +2 -2
- package/dist/DomUtils/elementPositionInRow.d.ts +5 -5
- package/dist/DomUtils/eventBelongsToPicker.d.ts +2 -0
- package/dist/DomUtils/focusElement.d.ts +5 -5
- package/dist/DomUtils/getActiveElement.d.ts +2 -2
- package/dist/DomUtils/isJsdom.d.ts +1 -0
- package/dist/DomUtils/isRtl.d.ts +1 -0
- package/dist/DomUtils/keyboardNavigation.d.ts +15 -8
- package/dist/DomUtils/scrollTo.d.ts +4 -5
- package/dist/DomUtils/selectors.d.ts +31 -35
- package/dist/EmojiPickerReact.d.ts +5 -5
- package/dist/Stylesheet/DedupedStyle.d.ts +13 -0
- package/dist/Stylesheet/stylesheet.d.ts +40 -37
- package/dist/components/ErrorBoundary.d.ts +15 -15
- package/dist/components/Layout/Absolute.d.ts +8 -8
- package/dist/components/Layout/Flex.d.ts +13 -13
- package/dist/components/Layout/Relative.d.ts +8 -8
- package/dist/components/Layout/Space.d.ts +7 -7
- package/dist/components/Reactions/BtnPlus.d.ts +2 -2
- package/dist/components/Reactions/DEFAULT_REACTIONS.d.ts +1 -1
- package/dist/components/Reactions/Reactions.d.ts +3 -2
- package/dist/components/atoms/Button.d.ts +11 -6
- package/dist/components/body/EmojiCategory.d.ts +13 -12
- package/dist/components/body/EmojiList.d.ts +7 -4
- package/dist/components/body/EmojiVariationPicker.d.ts +2 -2
- package/dist/components/body/MeasureEmoji.d.ts +2 -2
- package/dist/components/body/listComponents.d.ts +47 -0
- package/dist/components/context/ElementRefContext.d.ts +18 -16
- package/dist/components/context/PickerConfigContext.d.ts +14 -9
- package/dist/components/context/PickerContext.d.ts +57 -25
- package/dist/components/context/PickerDataContext.d.ts +23 -17
- package/dist/components/emoji/BaseEmojiProps.d.ts +14 -13
- package/dist/components/emoji/ClickableEmojiButton.d.ts +23 -15
- package/dist/components/emoji/Emoji.d.ts +18 -15
- package/dist/components/emoji/EmojiImg.d.ts +11 -11
- package/dist/components/emoji/ExportedEmoji.d.ts +11 -11
- package/dist/components/emoji/NativeEmoji.d.ts +6 -6
- package/dist/components/emoji/ViewOnlyEmoji.d.ts +3 -3
- package/dist/components/emoji/emojiStyles.d.ts +6 -1
- package/dist/components/footer/Preview.d.ts +7 -7
- package/dist/components/header/Header.d.ts +2 -2
- package/dist/components/header/Search/BtnClearSearch.d.ts +2 -2
- package/dist/components/header/Search/IcnSearch.d.ts +2 -2
- package/dist/components/header/Search/Search.d.ts +11 -3
- package/dist/components/header/SkinTonePicker/BtnSkinToneVariation.d.ts +12 -12
- package/dist/components/header/SkinTonePicker/SkinTonePicker.d.ts +11 -11
- package/dist/components/icons/svgIcons.d.ts +4 -0
- package/dist/components/main/PickerMainBehaviors.d.ts +4 -0
- package/dist/components/main/defaultAppearance.d.ts +12 -0
- package/dist/components/main/labelHeight.d.ts +1 -0
- package/dist/components/navigation/CategoryButton.d.ts +12 -12
- package/dist/components/navigation/CategoryNavIcon.d.ts +4 -4
- package/dist/components/navigation/CategoryNavigation.d.ts +19 -2
- package/dist/config/categoryConfig.d.ts +34 -34
- package/dist/config/cdnUrls.d.ts +2 -2
- package/dist/config/compareConfig.d.ts +10 -2
- package/dist/config/config.d.ts +166 -64
- package/dist/config/customEmojiConfig.d.ts +14 -14
- package/dist/config/defaultPickerConfiguration.d.ts +10 -0
- package/dist/config/mutableConfig.d.ts +32 -10
- package/dist/config/useConfig.d.ts +48 -36
- package/dist/data/defaultEmojiData.d.ts +9 -0
- package/dist/data/defaultEmojiData.js +40 -0
- package/dist/data/emojis-bn.d.mts +39 -0
- package/dist/data/emojis-bn.d.ts +38 -2
- package/dist/data/emojis-bn.js +25 -2
- package/dist/data/emojis-bn.mjs +2 -0
- package/dist/data/emojis-da.d.mts +39 -0
- package/dist/data/emojis-da.d.ts +38 -2
- package/dist/data/emojis-da.js +25 -2
- package/dist/data/emojis-da.mjs +2 -0
- package/dist/data/emojis-de.d.mts +39 -0
- package/dist/data/emojis-de.d.ts +38 -2
- package/dist/data/emojis-de.js +25 -2
- package/dist/data/emojis-de.mjs +2 -0
- package/dist/data/emojis-en-gb.d.mts +39 -0
- package/dist/data/emojis-en-gb.d.ts +38 -2
- package/dist/data/emojis-en-gb.js +25 -2
- package/dist/data/emojis-en-gb.mjs +2 -0
- package/dist/data/emojis-en.d.mts +39 -0
- package/dist/data/emojis-en.d.ts +38 -2
- package/dist/data/emojis-en.js +25 -2
- package/dist/data/emojis-en.mjs +2 -0
- package/dist/data/emojis-es-mx.d.mts +39 -0
- package/dist/data/emojis-es-mx.d.ts +38 -2
- package/dist/data/emojis-es-mx.js +25 -2
- package/dist/data/emojis-es-mx.mjs +2 -0
- package/dist/data/emojis-es.d.mts +39 -0
- package/dist/data/emojis-es.d.ts +38 -2
- package/dist/data/emojis-es.js +25 -2
- package/dist/data/emojis-es.mjs +2 -0
- package/dist/data/emojis-et.d.mts +39 -0
- package/dist/data/emojis-et.d.ts +38 -2
- package/dist/data/emojis-et.js +25 -2
- package/dist/data/emojis-et.mjs +2 -0
- package/dist/data/emojis-fi.d.mts +39 -0
- package/dist/data/emojis-fi.d.ts +38 -2
- package/dist/data/emojis-fi.js +25 -2
- package/dist/data/emojis-fi.mjs +2 -0
- package/dist/data/emojis-fr.d.mts +39 -0
- package/dist/data/emojis-fr.d.ts +38 -2
- package/dist/data/emojis-fr.js +25 -2
- package/dist/data/emojis-fr.mjs +2 -0
- package/dist/data/emojis-hi.d.mts +39 -0
- package/dist/data/emojis-hi.d.ts +38 -2
- package/dist/data/emojis-hi.js +25 -2
- package/dist/data/emojis-hi.mjs +2 -0
- package/dist/data/emojis-hu.d.mts +39 -0
- package/dist/data/emojis-hu.d.ts +38 -2
- package/dist/data/emojis-hu.js +25 -2
- package/dist/data/emojis-hu.mjs +2 -0
- package/dist/data/emojis-it.d.mts +39 -0
- package/dist/data/emojis-it.d.ts +38 -2
- package/dist/data/emojis-it.js +25 -2
- package/dist/data/emojis-it.mjs +2 -0
- package/dist/data/emojis-ja.d.mts +39 -0
- package/dist/data/emojis-ja.d.ts +38 -2
- package/dist/data/emojis-ja.js +25 -2
- package/dist/data/emojis-ja.mjs +2 -0
- package/dist/data/emojis-ko.d.mts +39 -0
- package/dist/data/emojis-ko.d.ts +38 -2
- package/dist/data/emojis-ko.js +25 -2
- package/dist/data/emojis-ko.mjs +2 -0
- package/dist/data/emojis-lt.d.mts +39 -0
- package/dist/data/emojis-lt.d.ts +38 -2
- package/dist/data/emojis-lt.js +25 -2
- package/dist/data/emojis-lt.mjs +2 -0
- package/dist/data/emojis-ms.d.mts +39 -0
- package/dist/data/emojis-ms.d.ts +38 -2
- package/dist/data/emojis-ms.js +25 -2
- package/dist/data/emojis-ms.mjs +2 -0
- package/dist/data/emojis-nb.d.mts +39 -0
- package/dist/data/emojis-nb.d.ts +38 -2
- package/dist/data/emojis-nb.js +25 -2
- package/dist/data/emojis-nb.mjs +2 -0
- package/dist/data/emojis-nl.d.mts +39 -0
- package/dist/data/emojis-nl.d.ts +38 -2
- package/dist/data/emojis-nl.js +25 -2
- package/dist/data/emojis-nl.mjs +2 -0
- package/dist/data/emojis-pl.d.mts +39 -0
- package/dist/data/emojis-pl.d.ts +38 -2
- package/dist/data/emojis-pl.js +25 -2
- package/dist/data/emojis-pl.mjs +2 -0
- package/dist/data/emojis-pt.d.mts +39 -0
- package/dist/data/emojis-pt.d.ts +38 -2
- package/dist/data/emojis-pt.js +25 -2
- package/dist/data/emojis-pt.mjs +2 -0
- package/dist/data/emojis-ru.d.mts +39 -0
- package/dist/data/emojis-ru.d.ts +38 -2
- package/dist/data/emojis-ru.js +25 -2
- package/dist/data/emojis-ru.mjs +2 -0
- package/dist/data/emojis-sv.d.mts +39 -0
- package/dist/data/emojis-sv.d.ts +38 -2
- package/dist/data/emojis-sv.js +25 -2
- package/dist/data/emojis-sv.mjs +2 -0
- package/dist/data/emojis-th.d.mts +39 -0
- package/dist/data/emojis-th.d.ts +38 -2
- package/dist/data/emojis-th.js +25 -2
- package/dist/data/emojis-th.mjs +2 -0
- package/dist/data/emojis-uk.d.mts +39 -0
- package/dist/data/emojis-uk.d.ts +38 -2
- package/dist/data/emojis-uk.js +25 -2
- package/dist/data/emojis-uk.mjs +2 -0
- package/dist/data/emojis-vi.d.mts +39 -0
- package/dist/data/emojis-vi.d.ts +38 -2
- package/dist/data/emojis-vi.js +25 -2
- package/dist/data/emojis-vi.mjs +2 -0
- package/dist/data/emojis-zh-hant.d.mts +39 -0
- package/dist/data/emojis-zh-hant.d.ts +38 -2
- package/dist/data/emojis-zh-hant.js +25 -2
- package/dist/data/emojis-zh-hant.mjs +2 -0
- package/dist/data/emojis-zh.d.mts +39 -0
- package/dist/data/emojis-zh.d.ts +38 -2
- package/dist/data/emojis-zh.js +25 -2
- package/dist/data/emojis-zh.mjs +2 -0
- package/dist/data/index.js +1 -0
- package/dist/data/registerDefaultEmojiData.d.ts +1 -0
- package/dist/data/registerDefaultEmojiData.js +3 -0
- package/dist/data/skinToneVariations.js +2 -2
- package/dist/data/skinToneVariations.ts +8 -7
- package/dist/data-core/pickerData.d.ts +12 -0
- package/dist/data-core/prepare.d.ts +23 -0
- package/dist/data-core/search.d.ts +13 -0
- package/dist/data-core/types.d.ts +23 -0
- package/dist/data.d.mts +72 -0
- package/dist/data.d.ts +72 -0
- package/dist/dataUtils/DataTypes.d.ts +23 -19
- package/dist/dataUtils/emojiSelectors.d.ts +15 -16
- package/dist/dataUtils/emojiUtils.d.ts +1 -12
- package/dist/dataUtils/nativeEmojiSequenceSupport.d.ts +10 -0
- package/dist/dataUtils/nativeEmojiSupport.d.ts +40 -0
- package/dist/dataUtils/parseNativeEmoji.d.ts +1 -1
- package/dist/dataUtils/suggested.d.ts +11 -11
- package/dist/dataUtils/suggestedEmojis.d.ts +7 -0
- package/dist/esm/chunks/chunk-7ZGTUZFB.mjs +8025 -0
- package/dist/esm/chunks/chunk-ACXAQEKG.mjs +149 -0
- package/dist/esm/chunks/chunk-F2KS7DCA.mjs +6735 -0
- package/dist/esm/chunks/chunk-L5W4PFRJ.mjs +14 -0
- package/dist/esm/chunks/chunk-LO4TPPLH.mjs +33 -0
- package/dist/esm/chunks/emojis-6L3GRBAP.mjs +7 -0
- package/dist/esm/data.mjs +11 -0
- package/dist/esm/index.mjs +279 -0
- package/dist/esm/primitives/index.mjs +8 -0
- package/dist/hooks/preloadEmoji.d.ts +5 -9
- package/dist/hooks/useActiveCategoryScrollDetection.d.ts +11 -11
- package/dist/hooks/useCategoryHeight.d.ts +7 -5
- package/dist/hooks/useCloseAllOpenToggles.d.ts +2 -2
- package/dist/hooks/useDataIdentityStabilityWarning.d.ts +13 -0
- package/dist/hooks/useDebouncedState.d.ts +1 -1
- package/dist/hooks/useDisallowMouseMove.d.ts +4 -4
- package/dist/hooks/useDisallowedEmojis.d.ts +2 -3
- package/dist/hooks/useEmojiPreviewEvents.d.ts +3 -3
- package/dist/hooks/useEmojiVirtualization.d.ts +22 -17
- package/dist/hooks/useFilter.d.ts +10 -16
- package/dist/hooks/useFocus.d.ts +3 -3
- package/dist/hooks/useInitialLoad.d.ts +2 -2
- package/dist/hooks/useIsEmojiHidden.d.ts +8 -8
- package/dist/hooks/useIsMounted.d.ts +1 -1
- package/dist/hooks/useIsSearchMode.d.ts +1 -1
- package/dist/hooks/useIsomorphicLayoutEffect.d.ts +2 -2
- package/dist/hooks/useKeyboardNavigation.d.ts +3 -1
- package/dist/hooks/useMouseDownHandlers.d.ts +8 -4
- package/dist/hooks/useOnFocus.d.ts +1 -1
- package/dist/hooks/useOnScroll.d.ts +2 -2
- package/dist/hooks/useReactionsFocus.d.ts +1 -0
- package/dist/hooks/useRegisterRegion.d.ts +24 -0
- package/dist/hooks/useResolvedEmojiData.d.ts +37 -0
- package/dist/hooks/useScrollCategoryIntoView.d.ts +1 -1
- package/dist/hooks/useSearchController.d.ts +17 -0
- package/dist/hooks/useSearchResults.d.ts +8 -0
- package/dist/hooks/useSetVariationPicker.d.ts +2 -2
- package/dist/hooks/useShouldHideCustomEmojis.d.ts +1 -1
- package/dist/hooks/useShouldShowSkinTonePicker.d.ts +4 -4
- package/dist/index.d.mts +376 -0
- package/dist/index.d.ts +376 -9
- package/dist/index.js +2 -8
- package/dist/primitives/CategoryNav.d.ts +4 -0
- package/dist/primitives/Empty.d.ts +6 -0
- package/dist/primitives/List.d.ts +4 -0
- package/dist/primitives/LoadError.d.ts +8 -0
- package/dist/primitives/Loading.d.ts +4 -0
- package/dist/primitives/Panel.d.ts +3 -0
- package/dist/primitives/Preview.d.ts +3 -0
- package/dist/primitives/Reactions.d.ts +2 -0
- package/dist/primitives/Root.d.ts +26 -0
- package/dist/primitives/Search.d.ts +5 -0
- package/dist/primitives/SearchInput.d.ts +3 -0
- package/dist/primitives/SkinTone.d.ts +4 -0
- package/dist/primitives/Viewport.d.ts +19 -0
- package/dist/primitives/appearance.d.ts +3 -0
- package/dist/primitives/components.d.ts +27 -0
- package/dist/primitives/hooks.d.ts +48 -0
- package/dist/primitives/index.d.mts +561 -0
- package/dist/primitives/index.d.ts +561 -0
- package/dist/primitives/index.js +2 -0
- package/dist/primitives/nativeProps.d.ts +15 -0
- package/dist/primitives/scope.d.ts +13 -0
- package/dist/primitives/structuralStyles.d.ts +16 -0
- package/dist/primitives/tokens.d.ts +18 -0
- package/dist/primitives/types.d.ts +85 -0
- package/dist/state/activeEmojiStore.d.ts +7 -0
- package/dist/state/navigationRegistry.d.ts +61 -0
- package/dist/state/regionTraversal.d.ts +20 -0
- package/dist/typeRefinements/typeRefinements.d.ts +6 -6
- package/dist/types/exposedTypes.d.ts +16 -6
- package/dist/types/exposedTypes.js +5 -0
- package/dist/virtualization/virtualizationHelpers.d.ts +22 -18
- package/llms-full.txt +3683 -0
- package/llms.txt +66 -480
- package/package.json +180 -66
- package/src/DomUtils/categoryNameFromDom.ts +1 -1
- package/src/DomUtils/classNames.ts +1 -4
- package/src/DomUtils/elementPositionInRow.ts +28 -6
- package/src/DomUtils/eventBelongsToPicker.ts +17 -0
- package/src/DomUtils/getActiveElement.ts +7 -1
- package/src/DomUtils/isJsdom.ts +12 -0
- package/src/DomUtils/isRtl.ts +5 -0
- package/src/DomUtils/keyboardNavigation.ts +164 -144
- package/src/DomUtils/scrollTo.ts +20 -18
- package/src/DomUtils/selectors.ts +30 -127
- package/src/EmojiPickerReact.tsx +155 -48
- package/src/Stylesheet/DedupedStyle.tsx +80 -0
- package/src/Stylesheet/stylesheet.tsx +128 -59
- package/src/components/Layout/Flex.tsx +12 -11
- package/src/components/Reactions/BtnPlus.tsx +61 -51
- package/src/components/Reactions/Reactions.tsx +113 -55
- package/src/components/atoms/Button.tsx +18 -15
- package/src/components/body/EmojiCategory.tsx +77 -40
- package/src/components/body/EmojiList.tsx +107 -44
- package/src/components/body/EmojiVariationPicker.tsx +113 -66
- package/src/components/body/MeasureEmoji.tsx +9 -2
- package/src/components/body/listComponents.tsx +79 -0
- package/src/components/context/ElementRefContext.tsx +46 -35
- package/src/components/context/PickerConfigContext.tsx +55 -40
- package/src/components/context/PickerContext.tsx +567 -92
- package/src/components/context/PickerDataContext.tsx +114 -99
- package/src/components/emoji/BaseEmojiProps.ts +5 -4
- package/src/components/emoji/ClickableEmojiButton.tsx +148 -63
- package/src/components/emoji/Emoji.tsx +24 -2
- package/src/components/emoji/EmojiImg.tsx +13 -12
- package/src/components/emoji/ExportedEmoji.tsx +5 -4
- package/src/components/emoji/NativeEmoji.tsx +19 -16
- package/src/components/emoji/ViewOnlyEmoji.tsx +55 -30
- package/src/components/emoji/emojiStyles.ts +12 -11
- package/src/components/footer/Preview.tsx +86 -91
- package/src/components/header/Header.tsx +21 -6
- package/src/components/header/Search/BtnClearSearch.tsx +69 -55
- package/src/components/header/Search/IcnSearch.tsx +21 -21
- package/src/components/header/Search/Search.tsx +79 -137
- package/src/components/header/SkinTonePicker/BtnSkinToneVariation.tsx +99 -62
- package/src/components/header/SkinTonePicker/SkinTonePicker.tsx +84 -46
- package/src/components/icons/svgIcons.ts +17 -0
- package/src/components/main/PickerMainBehaviors.tsx +163 -0
- package/src/components/main/defaultAppearance.tsx +189 -0
- package/src/components/main/labelHeight.ts +5 -0
- package/src/components/navigation/CategoryButton.tsx +83 -57
- package/src/components/navigation/CategoryNavIcon.tsx +73 -194
- package/src/components/navigation/CategoryNavigation.tsx +118 -46
- package/src/config/categoryConfig.ts +7 -3
- package/src/config/cdnUrls.ts +11 -21
- package/src/config/compareConfig.ts +128 -30
- package/src/config/config.ts +228 -24
- package/src/config/defaultPickerConfiguration.ts +7 -0
- package/src/config/mutableConfig.ts +44 -20
- package/src/config/useConfig.ts +130 -37
- package/src/data/defaultEmojiData.ts +49 -0
- package/src/data/registerDefaultEmojiData.ts +8 -0
- package/src/data/skinToneVariations.ts +8 -7
- package/src/data-core/pickerData.ts +171 -0
- package/src/data-core/prepare.ts +163 -0
- package/src/data-core/search.ts +122 -0
- package/src/data-core/types.ts +36 -0
- package/src/data.ts +10 -0
- package/src/dataUtils/DataTypes.ts +4 -0
- package/src/dataUtils/emojiSelectors.ts +59 -63
- package/src/dataUtils/emojiUtils.ts +17 -74
- package/src/dataUtils/nativeEmojiSequenceSupport.ts +116 -0
- package/src/dataUtils/nativeEmojiSupport.ts +160 -0
- package/src/dataUtils/parseNativeEmoji.ts +3 -4
- package/src/dataUtils/suggested.ts +8 -4
- package/src/dataUtils/suggestedEmojis.ts +92 -0
- package/src/env.d.ts +4 -0
- package/src/hooks/preloadEmoji.ts +2 -47
- package/src/hooks/useActiveCategoryScrollDetection.ts +28 -16
- package/src/hooks/useCategoryHeight.ts +116 -10
- package/src/hooks/useDataIdentityStabilityWarning.ts +86 -0
- package/src/hooks/useDebouncedState.ts +13 -1
- package/src/hooks/useDisallowedEmojis.ts +31 -42
- package/src/hooks/useEmojiPreviewEvents.ts +43 -10
- package/src/hooks/useEmojiVirtualization.tsx +112 -52
- package/src/hooks/useFilter.ts +92 -132
- package/src/hooks/useFocus.ts +14 -24
- package/src/hooks/useIsEmojiHidden.ts +22 -14
- package/src/hooks/useKeyboardNavigation.ts +412 -432
- package/src/hooks/useMouseDownHandlers.ts +91 -16
- package/src/hooks/useOnFocus.ts +12 -3
- package/src/hooks/useOnScroll.ts +18 -2
- package/src/hooks/useReactionsFocus.ts +118 -0
- package/src/hooks/useRegisterRegion.ts +92 -0
- package/src/hooks/useResolvedEmojiData.ts +200 -0
- package/src/hooks/useScrollCategoryIntoView.ts +74 -12
- package/src/hooks/useSearchController.ts +369 -0
- package/src/hooks/useSearchResults.ts +51 -0
- package/src/hooks/useSetVariationPicker.ts +4 -1
- package/src/index.tsx +67 -5
- package/src/primitives/CategoryNav.tsx +40 -0
- package/src/primitives/Empty.tsx +68 -0
- package/src/primitives/List.tsx +61 -0
- package/src/primitives/LoadError.tsx +38 -0
- package/src/primitives/Loading.tsx +60 -0
- package/src/primitives/Panel.tsx +47 -0
- package/src/primitives/Preview.tsx +38 -0
- package/src/primitives/Reactions.tsx +2 -0
- package/src/primitives/Root.tsx +398 -0
- package/src/primitives/Search.tsx +41 -0
- package/src/primitives/SearchInput.tsx +110 -0
- package/src/primitives/SkinTone.tsx +76 -0
- package/src/primitives/Viewport.tsx +204 -0
- package/src/primitives/appearance.tsx +7 -0
- package/src/primitives/components.tsx +30 -0
- package/src/primitives/hooks.ts +165 -0
- package/src/primitives/index.ts +94 -0
- package/src/primitives/nativeProps.ts +90 -0
- package/src/primitives/scope.tsx +109 -0
- package/src/primitives/structuralStyles.tsx +136 -0
- package/src/primitives/tokens.ts +150 -0
- package/src/primitives/types.ts +192 -0
- package/src/state/activeEmojiStore.ts +39 -0
- package/src/state/navigationRegistry.ts +209 -0
- package/src/state/regionTraversal.ts +116 -0
- package/src/svg.d.ts +2 -2
- package/src/types/exposedTypes.ts +28 -1
- package/src/virtualization/virtualizationHelpers.ts +29 -8
- package/dist/components/body/Body.d.ts +0 -2
- package/dist/components/main/PickerMain.d.ts +0 -7
- package/dist/dataUtils/alphaNumericEmojiIndex.d.ts +0 -5
- package/dist/emoji-picker-react.cjs.development.js +0 -31413
- package/dist/emoji-picker-react.cjs.development.js.map +0 -1
- package/dist/emoji-picker-react.cjs.production.min.js +0 -2
- package/dist/emoji-picker-react.cjs.production.min.js.map +0 -1
- package/dist/emoji-picker-react.esm.js +0 -31411
- package/dist/emoji-picker-react.esm.js.map +0 -1
- package/dist/hooks/useHideEmojisByUniocode.d.ts +0 -1
- package/src/components/body/Body.tsx +0 -43
- package/src/components/main/PickerMain.tsx +0 -249
- package/src/dataUtils/alphaNumericEmojiIndex.ts +0 -21
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import { useCallback, useEffect,
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
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
|
+
focusFirstEmoji,
|
|
12
13
|
focusFirstVisibleEmoji,
|
|
13
|
-
|
|
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
|
-
|
|
28
|
+
useNavigationRegistry,
|
|
29
|
+
useSearchComposingState,
|
|
30
|
+
useSearchDisplayState,
|
|
31
|
+
useSearchTermState,
|
|
30
32
|
useSkinToneFanOpenState,
|
|
31
33
|
} from '../components/context/PickerContext';
|
|
32
|
-
import {
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 [,
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
|
171
|
-
|
|
172
|
-
if (
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
253
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
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('
|
|
370
|
+
current.removeEventListener('input', cancelPendingPick);
|
|
371
|
+
current.removeEventListener('focusout', cancelPendingPick);
|
|
372
|
+
current.removeEventListener('keydown', scoped);
|
|
373
|
+
window.removeEventListener('keydown', capture, true);
|
|
324
374
|
};
|
|
325
|
-
}, [
|
|
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
|
|
377
|
-
case
|
|
389
|
+
case 'ArrowRight':
|
|
390
|
+
case 'ArrowDown':
|
|
378
391
|
return 1;
|
|
379
|
-
case
|
|
380
|
-
case
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const
|
|
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
|
|
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
|
|
484
|
-
if (
|
|
485
|
-
return
|
|
426
|
+
function focusRegion(region: RegisteredRegion | undefined): boolean {
|
|
427
|
+
if (!region) {
|
|
428
|
+
return false;
|
|
486
429
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
[
|
|
454
|
+
[
|
|
455
|
+
focusSearchInput,
|
|
456
|
+
focusCategoryNavigation,
|
|
457
|
+
focusSkinTonePicker,
|
|
458
|
+
BodyRef,
|
|
459
|
+
ReactionsRef,
|
|
460
|
+
registry,
|
|
461
|
+
],
|
|
506
462
|
);
|
|
507
463
|
}
|
|
508
464
|
|
|
509
|
-
function
|
|
510
|
-
|
|
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
|
|
524
|
-
|
|
469
|
+
function useFocusPrevRegionFrom(from: NavigationRegionKind) {
|
|
470
|
+
return useFocusAdjacentRegion(from, -1);
|
|
471
|
+
}
|
|
525
472
|
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|