emoji-picker-react 4.20.7 → 4.20.8

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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.20.7",
2
+ "version": "4.20.8",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -1,11 +1,19 @@
1
1
  import { NullableElement } from './selectors';
2
2
 
3
- export function focusElement(element: NullableElement) {
3
+ export function focusElement(
4
+ element: NullableElement,
5
+ shouldFocus?: () => boolean,
6
+ ) {
4
7
  if (!element) {
5
8
  return;
6
9
  }
7
10
 
8
11
  requestAnimationFrame(() => {
12
+ // Rechecked here rather than at call time: focus may have moved in the
13
+ // meantime (e.g. hover scheduled focus, then the user tabbed out).
14
+ if (shouldFocus && !shouldFocus()) {
15
+ return;
16
+ }
9
17
  element.focus();
10
18
  });
11
19
  }
@@ -1,13 +1,15 @@
1
1
  import * as React from 'react';
2
2
  import { useEffect } from 'react';
3
3
 
4
- import { detectEmojyPartiallyBelowFold } from '../DomUtils/detectEmojyPartiallyBelowFold';
5
4
  import { focusElement } from '../DomUtils/focusElement';
6
5
  import {
7
6
  allUnifiedFromEmojiElement,
8
7
  buttonFromTarget,
9
8
  } from '../DomUtils/selectors';
10
- import { useBodyRef } from '../components/context/ElementRefContext';
9
+ import {
10
+ useBodyRef,
11
+ usePickerMainRef,
12
+ } from '../components/context/ElementRefContext';
11
13
  import { PreviewEmoji } from '../components/footer/Preview';
12
14
 
13
15
  import {
@@ -20,6 +22,7 @@ export function useEmojiPreviewEvents(
20
22
  setPreviewEmoji: React.Dispatch<React.SetStateAction<PreviewEmoji>>,
21
23
  ) {
22
24
  const BodyRef = useBodyRef();
25
+ const PickerMainRef = usePickerMainRef();
23
26
  const isMouseDisallowed = useIsMouseDisallowed();
24
27
  const allowMouseMove = useAllowMouseMove();
25
28
 
@@ -84,17 +87,41 @@ export function useEmojiPreviewEvents(
84
87
 
85
88
  const button = buttonFromTarget(e.target as HTMLElement);
86
89
 
87
- if (button) {
88
- const belowFoldByPx = detectEmojyPartiallyBelowFold(button, bodyRef);
89
- const buttonHeight = button.getBoundingClientRect().height;
90
- if (belowFoldByPx < buttonHeight) {
91
- return handlePartiallyVisibleElementFocus(button, setPreviewEmoji);
92
- }
90
+ if (!button) {
91
+ return;
92
+ }
93
+
94
+ const { unified, originalUnified } = allUnifiedFromEmojiElement(button);
95
+
96
+ if (!unified || !originalUnified) {
97
+ return;
98
+ }
99
+
100
+ setPreviewEmoji({
101
+ unified,
102
+ originalUnified,
103
+ });
93
104
 
94
- focusElement(button);
105
+ // Let arrow-key navigation continue from the hovered emoji, but only
106
+ // when focus is already inside the picker (or nowhere meaningful).
107
+ // Pulling focus out of an external element steals the caret from
108
+ // host-app inputs (issue #320). The guard runs again inside the
109
+ // deferred callback in case focus moved out after this event.
110
+ if (!isExternalElementFocused()) {
111
+ focusElement(button, () => !isExternalElementFocused());
95
112
  }
96
113
  }
97
114
 
115
+ function isExternalElementFocused(): boolean {
116
+ const active = document.activeElement as HTMLElement | null;
117
+
118
+ if (!active || active === document.body || active === document.documentElement) {
119
+ return false;
120
+ }
121
+
122
+ return !PickerMainRef.current?.contains(active);
123
+ }
124
+
98
125
  return () => {
99
126
  bodyRef?.removeEventListener('mouseover', onMouseOver);
100
127
  bodyRef?.removeEventListener('mouseout', onLeave);
@@ -104,21 +131,3 @@ export function useEmojiPreviewEvents(
104
131
  };
105
132
  }, [BodyRef, allow, setPreviewEmoji, isMouseDisallowed, allowMouseMove]);
106
133
  }
107
-
108
- function handlePartiallyVisibleElementFocus(
109
- button: HTMLElement,
110
- setPreviewEmoji: React.Dispatch<React.SetStateAction<PreviewEmoji>>,
111
- ) {
112
- const { unified, originalUnified } = allUnifiedFromEmojiElement(button);
113
-
114
- if (!unified || !originalUnified) {
115
- return;
116
- }
117
-
118
- (document.activeElement as HTMLElement)?.blur?.();
119
-
120
- setPreviewEmoji({
121
- unified,
122
- originalUnified,
123
- });
124
- }