emoji-picker-react 4.0.0-rc.8 → 4.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 (117) hide show
  1. package/README.md +176 -43
  2. package/dist/DomUtils/classNames.d.ts +9 -1
  3. package/dist/DomUtils/elementPositionInRow.d.ts +10 -0
  4. package/dist/DomUtils/focusElement.d.ts +5 -0
  5. package/dist/DomUtils/getActiveElement.d.ts +2 -0
  6. package/dist/DomUtils/isDarkTheme.d.ts +1 -0
  7. package/dist/DomUtils/keyboardNavigation.d.ts +8 -0
  8. package/dist/DomUtils/scrollTo.d.ts +3 -1
  9. package/dist/DomUtils/selectors.d.ts +24 -11
  10. package/dist/EmojiPickerReact.d.ts +6 -0
  11. package/dist/components/ErrorBoundary.d.ts +15 -0
  12. package/dist/components/body/{RecentlyUsed.d.ts → Suggested.d.ts} +1 -1
  13. package/dist/components/context/ElementRefContext.d.ts +10 -13
  14. package/dist/components/context/PickerContext.d.ts +3 -3
  15. package/dist/components/emoji/Emoji.d.ts +11 -7
  16. package/dist/components/emoji/ExportedEmoji.d.ts +8 -0
  17. package/dist/components/footer/Preview.d.ts +1 -1
  18. package/dist/config/categoryConfig.d.ts +7 -3
  19. package/dist/config/config.d.ts +18 -15
  20. package/dist/config/useConfig.d.ts +11 -4
  21. package/dist/data/skinToneVariations.d.ts +2 -0
  22. package/dist/dataUtils/emojiSelectors.d.ts +3 -2
  23. package/dist/dataUtils/suggested.d.ts +11 -0
  24. package/dist/emoji-picker-react.cjs.development.css +9 -9
  25. package/dist/emoji-picker-react.cjs.development.js +3774 -3070
  26. package/dist/emoji-picker-react.cjs.development.js.map +1 -1
  27. package/dist/emoji-picker-react.cjs.production.min.js +1 -1
  28. package/dist/emoji-picker-react.cjs.production.min.js.map +1 -1
  29. package/dist/emoji-picker-react.esm.js +3784 -3080
  30. package/dist/emoji-picker-react.esm.js.map +1 -1
  31. package/dist/hooks/preloadEmoji.d.ts +1 -1
  32. package/dist/hooks/useActiveCategoryScrollDetection.d.ts +1 -2
  33. package/dist/hooks/useCloseAllOpenToggles.d.ts +2 -4
  34. package/dist/hooks/useDebouncedState.d.ts +1 -1
  35. package/dist/hooks/useDisallowMouseMove.d.ts +4 -0
  36. package/dist/hooks/useEmojiPreviewEvents.d.ts +3 -0
  37. package/dist/hooks/useFilter.d.ts +3 -3
  38. package/dist/hooks/useFocus.d.ts +3 -0
  39. package/dist/hooks/useInitialLoad.d.ts +1 -1
  40. package/dist/hooks/useIsEmojiHidden.d.ts +7 -1
  41. package/dist/hooks/useKeyboardNavigation.d.ts +1 -0
  42. package/dist/hooks/useMouseDownHandlers.d.ts +3 -2
  43. package/dist/hooks/useOnFocus.d.ts +1 -0
  44. package/dist/hooks/useScrollCategoryIntoView.d.ts +1 -0
  45. package/dist/hooks/useSetVariationPicker.d.ts +2 -0
  46. package/dist/index.d.ts +2 -2
  47. package/dist/types/exposedTypes.d.ts +5 -1
  48. package/package.json +13 -5
  49. package/src/DomUtils/classNames.ts +12 -1
  50. package/src/DomUtils/elementPositionInRow.ts +167 -0
  51. package/src/DomUtils/focusElement.ts +35 -0
  52. package/src/DomUtils/getActiveElement.ts +5 -0
  53. package/src/DomUtils/isDarkTheme.ts +7 -0
  54. package/src/DomUtils/keyboardNavigation.ts +161 -0
  55. package/src/DomUtils/scrollTo.ts +22 -1
  56. package/src/DomUtils/selectors.ts +200 -41
  57. package/src/EmojiPickerReact.css +99 -48
  58. package/src/EmojiPickerReact.tsx +26 -0
  59. package/src/components/ErrorBoundary.tsx +28 -0
  60. package/src/components/body/Body.tsx +3 -2
  61. package/src/components/body/EmojiCategory.css +14 -28
  62. package/src/components/body/EmojiCategory.tsx +5 -4
  63. package/src/components/body/EmojiList.tsx +13 -13
  64. package/src/components/body/EmojiVariationPicker.css +13 -5
  65. package/src/components/body/EmojiVariationPicker.tsx +25 -17
  66. package/src/components/body/Suggested.tsx +54 -0
  67. package/src/components/context/ElementRefContext.tsx +54 -19
  68. package/src/components/context/PickerContext.tsx +36 -33
  69. package/src/components/emoji/Emoji.css +42 -12
  70. package/src/components/emoji/Emoji.tsx +73 -34
  71. package/src/components/emoji/ExportedEmoji.tsx +30 -0
  72. package/src/components/footer/Preview.css +1 -1
  73. package/src/components/footer/Preview.tsx +16 -23
  74. package/src/components/header/Search.css +7 -7
  75. package/src/components/header/Search.tsx +31 -7
  76. package/src/components/header/SkinTonePicker.css +15 -8
  77. package/src/components/header/SkinTonePicker.tsx +23 -5
  78. package/src/components/main/PickerMain.css +0 -2
  79. package/src/components/main/PickerMain.tsx +14 -1
  80. package/src/components/navigation/CategoryNavigation.css +41 -10
  81. package/src/components/navigation/CategoryNavigation.tsx +22 -10
  82. package/src/components/navigation/svg/CategoryNav.svg +343 -379
  83. package/src/config/categoryConfig.ts +40 -14
  84. package/src/config/config.ts +59 -13
  85. package/src/config/useConfig.ts +35 -7
  86. package/src/data/emojis.json +1 -1
  87. package/src/data/skinToneVariations.ts +19 -0
  88. package/src/dataUtils/emojiSelectors.ts +25 -5
  89. package/src/dataUtils/{recentlyUsed.ts → suggested.ts} +20 -18
  90. package/src/hooks/preloadEmoji.ts +13 -2
  91. package/src/hooks/useActiveCategoryScrollDetection.ts +10 -8
  92. package/src/hooks/useCloseAllOpenToggles.ts +15 -4
  93. package/src/hooks/useDebouncedState.ts +10 -7
  94. package/src/hooks/useDisallowMouseMove.ts +47 -0
  95. package/src/hooks/useEmojiPreviewEvents.ts +103 -0
  96. package/src/hooks/useFilter.ts +46 -47
  97. package/src/hooks/useFocus.ts +40 -0
  98. package/src/hooks/useInitialLoad.ts +1 -0
  99. package/src/hooks/useIsEmojiHidden.ts +16 -3
  100. package/src/hooks/useKeyboardNavigation.ts +438 -0
  101. package/src/hooks/useMouseDownHandlers.ts +97 -89
  102. package/src/hooks/useOnFocus.ts +46 -0
  103. package/src/hooks/useOnScroll.ts +2 -2
  104. package/src/hooks/useScrollCategoryIntoView.ts +28 -0
  105. package/src/hooks/useSetVariationPicker.ts +28 -0
  106. package/src/index.tsx +9 -17
  107. package/src/types/exposedTypes.ts +6 -1
  108. package/dist/DomUtils/emojiElementRef.d.ts +0 -10
  109. package/dist/DomUtils/scrollCategoryIntoView.d.ts +0 -1
  110. package/dist/dataUtils/recentlyUsed.d.ts +0 -11
  111. package/dist/hooks/useEmojiElements.d.ts +0 -1
  112. package/dist/hooks/useEmojiMouseEnter.d.ts +0 -3
  113. package/src/DomUtils/emojiElementRef.ts +0 -42
  114. package/src/DomUtils/scrollCategoryIntoView.ts +0 -18
  115. package/src/components/body/RecentlyUsed.tsx +0 -50
  116. package/src/hooks/useEmojiElements.ts +0 -7
  117. package/src/hooks/useEmojiMouseEnter.ts +0 -59
@@ -1,4 +1,4 @@
1
1
  import { DataEmoji } from '../dataUtils/DataTypes';
2
2
  import { EmojiStyle } from '../types/exposedTypes';
3
- export declare function preloadEmoji(emoji: DataEmoji, emojiStyle: EmojiStyle): void;
3
+ export declare function preloadEmoji(emoji: undefined | DataEmoji, emojiStyle: EmojiStyle): void;
4
4
  export declare const preloadedEmojs: Set<string>;
@@ -1,2 +1 @@
1
- import { ElementRef } from '../components/context/ElementRefContext';
2
- export declare function useActiveCategoryScrollDetection(bodyRef: ElementRef): void;
1
+ export declare function useActiveCategoryScrollDetection(setActiveCategory: (category: string) => void): void;
@@ -1,4 +1,2 @@
1
- export declare function useCloseAllOpenToggles(): {
2
- closeAllOpenToggles: () => void;
3
- dependencyArray: (boolean | import("../dataUtils/DataTypes").DataEmoji | null)[];
4
- };
1
+ export declare function useCloseAllOpenToggles(): () => void;
2
+ export declare function useHasOpenToggles(): () => boolean;
@@ -1 +1 @@
1
- export declare function useDebouncedState<T>(initialValue: T, delay?: number): [T, (value: T) => void];
1
+ export declare function useDebouncedState<T>(initialValue: T, delay?: number): [T, (value: T) => Promise<T>];
@@ -0,0 +1,4 @@
1
+ export declare function useDisallowMouseMove(): () => void;
2
+ export declare function useAllowMouseMove(): () => void;
3
+ export declare function useIsMouseDisallowed(): () => boolean;
4
+ export declare function useOnMouseMove(): void;
@@ -0,0 +1,3 @@
1
+ import * as React from 'react';
2
+ import { PreviewEmoji } from '../components/footer/Preview';
3
+ export declare function useEmojiPreviewEvents(allow: boolean, setPreviewEmoji: React.Dispatch<React.SetStateAction<PreviewEmoji>>): void;
@@ -1,10 +1,10 @@
1
- /// <reference types="react" />
2
1
  import { DataEmoji } from '../dataUtils/DataTypes';
2
+ export declare function useClearSearch(): () => void;
3
+ export declare function useAppendSearch(): (str: string) => void;
3
4
  export declare function useFilter(): {
4
5
  onChange: (nextValue: string) => void;
5
6
  searchTerm: string;
6
- clearSearch: () => void;
7
- SearchInputRef: import("react").RefObject<HTMLInputElement>;
7
+ SearchInputRef: import("../components/context/ElementRefContext").ElementRef<HTMLInputElement>;
8
8
  };
9
9
  export declare function useIsEmojiFiltered(): (unified: string) => boolean;
10
10
  export declare type FilterDict = Record<string, DataEmoji>;
@@ -0,0 +1,3 @@
1
+ export declare function useFocusSearchInput(): () => void;
2
+ export declare function useFocusSkinTonePicker(): () => void;
3
+ export declare function useFocusCategoryNavigation(): () => void;
@@ -1,2 +1,2 @@
1
- /// <reference types="react" />
1
+ import * as React from 'react';
2
2
  export declare function useMarkInitialLoad(dispatch: React.Dispatch<React.SetStateAction<boolean>>): void;
@@ -1,2 +1,8 @@
1
1
  import { DataEmoji } from '../dataUtils/DataTypes';
2
- export declare function useIsEmojiHidden(): (emoji: DataEmoji) => boolean;
2
+ export declare function useIsEmojiHidden(): (emoji: DataEmoji) => IsHiddenReturn;
3
+ declare type IsHiddenReturn = {
4
+ failedToLoad: boolean;
5
+ filteredOut: boolean;
6
+ hidden: boolean;
7
+ };
8
+ export {};
@@ -0,0 +1 @@
1
+ export declare function useKeyboardNavigation(): void;
@@ -1,2 +1,3 @@
1
- /// <reference types="react" />
2
- export declare function useMouseDownHandlers(BodyRef: React.MutableRefObject<HTMLElement | null>): void;
1
+ import * as React from 'react';
2
+ import { NullableElement } from '../DomUtils/selectors';
3
+ export declare function useMouseDownHandlers(BodyRef: React.MutableRefObject<NullableElement>): void;
@@ -0,0 +1 @@
1
+ export declare function useOnFocus(): void;
@@ -0,0 +1 @@
1
+ export declare function useScrollCategoryIntoView(): (category: string) => void;
@@ -0,0 +1,2 @@
1
+ import { NullableElement } from '../DomUtils/selectors';
2
+ export default function useSetVariationPicker(): (element: NullableElement) => void;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /// <reference types="react" />
2
2
  import { PickerConfig } from './config/config';
3
- import './EmojiPickerReact.css';
3
+ export { ExportedEmoji as Emoji } from './components/emoji/ExportedEmoji';
4
4
  export { EmojiStyle, SkinTones, Theme, Categories, EmojiClickData } from './types/exposedTypes';
5
5
  export interface Props extends PickerConfig {
6
6
  }
7
- export declare function EmojiPicker(props: Props): JSX.Element;
7
+ export default function EmojiPicker(props: Props): JSX.Element;
@@ -6,6 +6,10 @@ export declare type EmojiClickData = {
6
6
  names: string[];
7
7
  getImageUrl: (emojiStyle: EmojiStyle) => string;
8
8
  };
9
+ export declare enum SuggestionMode {
10
+ RECENT = "recent",
11
+ FREQUENT = "frequent"
12
+ }
9
13
  export declare enum EmojiStyle {
10
14
  NATIVE = "native",
11
15
  APPLE = "apple",
@@ -27,7 +31,7 @@ export declare enum SkinTones {
27
31
  DARK = "1f3ff"
28
32
  }
29
33
  export declare enum Categories {
30
- RECENTLY_USED = "recently_used",
34
+ SUGGESTED = "suggested",
31
35
  SMILEYS_PEOPLE = "smileys_people",
32
36
  ANIMALS_NATURE = "animals_nature",
33
37
  FOOD_DRINK = "food_drink",
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.0.0-rc.8",
2
+ "version": "4.0.0",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -10,11 +10,15 @@
10
10
  "engines": {
11
11
  "node": ">=10"
12
12
  },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/ealush/emoji-picker-react.git"
16
+ },
13
17
  "scripts": {
14
18
  "start": "tsdx watch",
15
19
  "build": "node ./scripts/prepare.js && tsdx build",
16
20
  "test": "tsdx test --passWithNoTests",
17
- "lint": "tsdx lint",
21
+ "lint": "eslint ./src --ext .ts,.tsx",
18
22
  "prepare": "tsdx build",
19
23
  "size": "size-limit",
20
24
  "analyze": "size-limit --why",
@@ -26,7 +30,7 @@
26
30
  },
27
31
  "husky": {
28
32
  "hooks": {
29
- "pre-commit": "tsdx lint"
33
+ "pre-commit": "yarn lint"
30
34
  }
31
35
  },
32
36
  "prettier": {
@@ -58,6 +62,8 @@
58
62
  "@storybook/react": "^6.5.10",
59
63
  "@types/react": "^18.0.17",
60
64
  "@types/react-dom": "^18.0.6",
65
+ "@typescript-eslint/eslint-plugin": "^5.36.2",
66
+ "@typescript-eslint/parser": "^5.36.2",
61
67
  "autoprefixer": "^10.4.8",
62
68
  "babel-loader": "^8.2.5",
63
69
  "cssnano": "^5.1.13",
@@ -77,12 +83,14 @@
77
83
  "rollup-plugin-postcss": "^4.0.2",
78
84
  "size-limit": "^8.0.1",
79
85
  "tiny-invariant": "^1.2.0",
80
- "tinykeys": "^1.4.0",
81
86
  "tsdx": "^0.14.1",
82
87
  "tslib": "^2.4.0",
83
88
  "typescript": "^4.7.4"
84
89
  },
85
90
  "dependencies": {
86
- "clsx": "^1.2.1"
91
+ "clsx": "^1.2.1",
92
+ "eslint-plugin-jsx-a11y": "^6.6.1",
93
+ "eslint-plugin-react": "^7.31.8",
94
+ "eslint-plugin-react-hooks": "^4.6.0"
87
95
  }
88
96
  }
@@ -2,5 +2,16 @@ export enum ClassNames {
2
2
  hiddenOnSearch = 'epr-hidden-on-search',
3
3
  searchActive = 'epr-search-active',
4
4
  hidden = 'epr-hidden',
5
- visible = 'epr-visible'
5
+ visible = 'epr-visible',
6
+ active = 'epr-active',
7
+ emoji = 'epr-emoji',
8
+ category = 'epr-emoji-category',
9
+ label = 'epr-emoji-category-label',
10
+ categoryContent = 'epr-emoji-category-content',
11
+ emojiHasVariatios = 'epr-emoji-has-variations',
12
+ external = '__EmojiPicker__'
13
+ }
14
+
15
+ export function asSelectors(...classNames: ClassNames[]): string {
16
+ return classNames.map(c => `.${c}`).join('');
6
17
  }
@@ -0,0 +1,167 @@
1
+ import { NullableElement } from './selectors';
2
+
3
+ export function elementCountInRow(
4
+ parent: NullableElement,
5
+ element: NullableElement
6
+ ): number {
7
+ if (!parent || !element) {
8
+ return 0;
9
+ }
10
+
11
+ const parentWidth = parent.getBoundingClientRect().width;
12
+ const elementWidth = element.getBoundingClientRect().width;
13
+ return Math.floor(parentWidth / elementWidth);
14
+ }
15
+
16
+ export function elementIndexInRow(
17
+ parent: NullableElement,
18
+ element: NullableElement
19
+ ): number {
20
+ if (!parent || !element) {
21
+ return 0;
22
+ }
23
+
24
+ const elementWidth = element.getBoundingClientRect().width;
25
+ const elementLeft = element.getBoundingClientRect().left;
26
+ const parentLeft = parent.getBoundingClientRect().left;
27
+
28
+ return Math.floor((elementLeft - parentLeft) / elementWidth);
29
+ }
30
+
31
+ export function rowNumber(
32
+ parent: NullableElement,
33
+ element: NullableElement
34
+ ): number {
35
+ if (!parent || !element) {
36
+ return 0;
37
+ }
38
+
39
+ const elementHeight = element.getBoundingClientRect().height;
40
+ const elementTop = element.getBoundingClientRect().top;
41
+ const parentTop = parent.getBoundingClientRect().top;
42
+ return Math.round((elementTop - parentTop) / elementHeight);
43
+ }
44
+
45
+ export function hasNextRow(
46
+ parent: NullableElement,
47
+ element: NullableElement
48
+ ): boolean {
49
+ if (!parent || !element) {
50
+ return false;
51
+ }
52
+
53
+ const elementHeight = element.getBoundingClientRect().height;
54
+ const elementTop = element.getBoundingClientRect().top;
55
+ const parentTop = parent.getBoundingClientRect().top;
56
+ const parentHeight = parent.getBoundingClientRect().height;
57
+
58
+ return Math.round(elementTop - parentTop + elementHeight) < parentHeight;
59
+ }
60
+
61
+ function getRowElements(
62
+ elements: HTMLElement[],
63
+ row: number,
64
+ elementsInRow: number
65
+ ): HTMLElement[] {
66
+ if (row === -1) {
67
+ const lastRow = Math.floor((elements.length - 1) / elementsInRow);
68
+ const firstElementIndex = lastRow * elementsInRow;
69
+ const lastElementIndex = elements.length - 1;
70
+ return elements.slice(firstElementIndex, lastElementIndex + 1);
71
+ }
72
+
73
+ return elements.slice(row * elementsInRow, (row + 1) * elementsInRow);
74
+ }
75
+
76
+ function getNextRowElements(
77
+ allElements: HTMLElement[],
78
+ currentRow: number,
79
+ elementsInRow: number
80
+ ): HTMLElement[] {
81
+ const nextRow = currentRow + 1;
82
+
83
+ if (nextRow * elementsInRow > allElements.length) {
84
+ return [];
85
+ }
86
+
87
+ return getRowElements(allElements, nextRow, elementsInRow);
88
+ }
89
+
90
+ export function getElementInRow(
91
+ elements: HTMLElement[],
92
+ row: number,
93
+ elementsInRow: number,
94
+ indexInRow: number
95
+ ): NullableElement {
96
+ const rowElements = getRowElements(elements, row, elementsInRow);
97
+ // get element, default to last
98
+ return rowElements[indexInRow] || rowElements[rowElements.length - 1] || null;
99
+ }
100
+
101
+ export function getElementInNextRow(
102
+ allElements: HTMLElement[],
103
+ currentRow: number,
104
+ elementsInRow: number,
105
+ index: number
106
+ ): NullableElement {
107
+ const nextRowElements = getNextRowElements(
108
+ allElements,
109
+ currentRow,
110
+ elementsInRow
111
+ );
112
+
113
+ // return item in index, or last item in row
114
+ return (
115
+ nextRowElements[index] ||
116
+ nextRowElements[nextRowElements.length - 1] ||
117
+ null
118
+ );
119
+ }
120
+
121
+ export function getElementInPrevRow(
122
+ allElements: HTMLElement[],
123
+ currentRow: number,
124
+ elementsInRow: number,
125
+ index: number
126
+ ): NullableElement {
127
+ const prevRowElements = getRowElements(
128
+ allElements,
129
+ currentRow - 1,
130
+ elementsInRow
131
+ );
132
+
133
+ // default to last
134
+ return (
135
+ prevRowElements[index] ||
136
+ prevRowElements[prevRowElements.length - 1] ||
137
+ null
138
+ );
139
+ }
140
+
141
+ export function firstVisibleElementInContainer(
142
+ parent: NullableElement,
143
+ elements: HTMLElement[]
144
+ ): NullableElement {
145
+ if (!parent || !elements.length) {
146
+ return null;
147
+ }
148
+
149
+ const parentTop = parent.getBoundingClientRect().top;
150
+ const parentBottom = parent.getBoundingClientRect().bottom;
151
+
152
+ const visibleElements = elements.find(element => {
153
+ const elementTop = element.getBoundingClientRect().top;
154
+ const elementBottom = element.getBoundingClientRect().bottom;
155
+
156
+ return (
157
+ (elementTop >= parentTop && elementTop <= parentBottom) ||
158
+ (elementBottom >= parentTop && elementBottom <= parentBottom)
159
+ );
160
+ });
161
+
162
+ return visibleElements || null;
163
+ }
164
+
165
+ export function hasNextElementSibling(element: HTMLElement) {
166
+ return !!element.nextElementSibling;
167
+ }
@@ -0,0 +1,35 @@
1
+ import { NullableElement } from './selectors';
2
+
3
+ export function focusElement(element: NullableElement) {
4
+ if (!element) {
5
+ return;
6
+ }
7
+
8
+ requestAnimationFrame(() => {
9
+ element.focus();
10
+ });
11
+ }
12
+
13
+ export function focusPrevElementSibling(element: NullableElement) {
14
+ if (!element) return;
15
+
16
+ const prev = element.previousElementSibling as HTMLElement;
17
+
18
+ focusElement(prev);
19
+ }
20
+
21
+ export function focusNextElementSibling(element: NullableElement) {
22
+ if (!element) return;
23
+
24
+ const next = element.nextElementSibling as HTMLElement;
25
+
26
+ focusElement(next);
27
+ }
28
+
29
+ export function focusFirstElementChild(element: NullableElement) {
30
+ if (!element) return;
31
+
32
+ const first = element.firstElementChild as HTMLElement;
33
+
34
+ focusElement(first);
35
+ }
@@ -0,0 +1,5 @@
1
+ import { NullableElement } from './selectors';
2
+
3
+ export function getActiveElement() {
4
+ return document.activeElement as NullableElement;
5
+ }
@@ -0,0 +1,7 @@
1
+ export function isSystemDarkTheme(): boolean {
2
+ if (typeof window === 'undefined') {
3
+ return false;
4
+ }
5
+
6
+ return !!window.matchMedia('(prefers-color-scheme: dark)').matches;
7
+ }
@@ -0,0 +1,161 @@
1
+ import {
2
+ elementCountInRow,
3
+ elementIndexInRow,
4
+ getElementInNextRow,
5
+ getElementInPrevRow,
6
+ getElementInRow,
7
+ hasNextRow,
8
+ rowNumber
9
+ } from './elementPositionInRow';
10
+ import { focusElement } from './focusElement';
11
+ import {
12
+ allVisibleEmojis,
13
+ closestCategory,
14
+ firstVisibleEmoji,
15
+ lastVisibleEmoji,
16
+ nextCategory,
17
+ nextVisibleEmoji,
18
+ NullableElement,
19
+ prevCategory,
20
+ prevVisibleEmoji,
21
+ closestCategoryContent
22
+ } from './selectors';
23
+
24
+ export function focusFirstVisibleEmoji(parent: NullableElement) {
25
+ focusElement(firstVisibleEmoji(parent));
26
+ }
27
+
28
+ export function focusAndClickFirstVisibleEmoji(parent: NullableElement) {
29
+ const firstEmoji = firstVisibleEmoji(parent);
30
+
31
+ focusElement(firstEmoji);
32
+ firstEmoji?.click();
33
+ }
34
+
35
+ export function focusLastVisibleEmoji(parent: NullableElement) {
36
+ focusElement(lastVisibleEmoji(parent));
37
+ }
38
+
39
+ export function focusNextVisibleEmoji(element: NullableElement) {
40
+ if (!element) {
41
+ return;
42
+ }
43
+
44
+ const next = nextVisibleEmoji(element);
45
+
46
+ if (!next) {
47
+ return focusFirstVisibleEmoji(nextCategory(element));
48
+ }
49
+
50
+ focusElement(next);
51
+ }
52
+
53
+ export function focusPrevVisibleEmoji(element: NullableElement) {
54
+ if (!element) {
55
+ return;
56
+ }
57
+
58
+ const prev = prevVisibleEmoji(element);
59
+
60
+ if (!prev) {
61
+ return focusLastVisibleEmoji(prevCategory(element));
62
+ }
63
+
64
+ focusElement(prev);
65
+ }
66
+
67
+ export function focusVisibleEmojiOneRowUp(
68
+ element: NullableElement,
69
+ exitUp: () => void
70
+ ) {
71
+ if (!element) {
72
+ return;
73
+ }
74
+
75
+ const prev = visibleEmojiOneRowUp(element);
76
+
77
+ if (!prev) {
78
+ return exitUp();
79
+ }
80
+
81
+ focusElement(prev);
82
+ }
83
+
84
+ export function focusVisibleEmojiOneRowDown(element: NullableElement) {
85
+ if (!element) {
86
+ return;
87
+ }
88
+
89
+ const next = visibleEmojiOneRowDown(element);
90
+
91
+ return focusElement(next);
92
+ }
93
+
94
+ function visibleEmojiOneRowUp(element: HTMLElement) {
95
+ if (!element) {
96
+ return null;
97
+ }
98
+
99
+ const categoryContent = closestCategoryContent(element);
100
+ const category = closestCategory(categoryContent);
101
+ const indexInRow = elementIndexInRow(categoryContent, element);
102
+ const row = rowNumber(categoryContent, element);
103
+ const countInRow = elementCountInRow(categoryContent, element);
104
+
105
+ if (row === 0) {
106
+ const prevVisibleCategory = prevCategory(category);
107
+
108
+ if (!prevVisibleCategory) {
109
+ return null;
110
+ }
111
+
112
+ return getElementInRow(
113
+ allVisibleEmojis(prevVisibleCategory),
114
+ -1, // last row
115
+ countInRow,
116
+ indexInRow
117
+ );
118
+ }
119
+
120
+ return getElementInPrevRow(
121
+ allVisibleEmojis(categoryContent),
122
+ row,
123
+ countInRow,
124
+ indexInRow
125
+ );
126
+ }
127
+
128
+ function visibleEmojiOneRowDown(element: HTMLElement) {
129
+ if (!element) {
130
+ return null;
131
+ }
132
+
133
+ const categoryContent = closestCategoryContent(element);
134
+ const category = closestCategory(categoryContent);
135
+ const indexInRow = elementIndexInRow(categoryContent, element);
136
+ const row = rowNumber(categoryContent, element);
137
+ const countInRow = elementCountInRow(categoryContent, element);
138
+ if (!hasNextRow(categoryContent, element)) {
139
+ const nextVisibleCategory = nextCategory(category);
140
+
141
+ if (!nextVisibleCategory) {
142
+ return null;
143
+ }
144
+
145
+ return getElementInRow(
146
+ allVisibleEmojis(nextVisibleCategory),
147
+ 0,
148
+ countInRow,
149
+ indexInRow
150
+ );
151
+ }
152
+
153
+ const itemInNextRow = getElementInNextRow(
154
+ allVisibleEmojis(categoryContent),
155
+ row,
156
+ countInRow,
157
+ indexInRow
158
+ );
159
+
160
+ return itemInNextRow;
161
+ }
@@ -1,4 +1,10 @@
1
- export function scrollTo(root: HTMLElement | null, top: number = 0) {
1
+ import { useCallback } from 'react';
2
+
3
+ import { useBodyRef } from '../components/context/ElementRefContext';
4
+
5
+ import { NullableElement } from './selectors';
6
+
7
+ export function scrollTo(root: NullableElement, top: number = 0) {
2
8
  const $eprBody = root ? root.querySelector('.epr-body') : null;
3
9
 
4
10
  if (!root || !$eprBody) {
@@ -9,3 +15,18 @@ export function scrollTo(root: HTMLElement | null, top: number = 0) {
9
15
  $eprBody.scrollTop = top + 1;
10
16
  });
11
17
  }
18
+
19
+ export function useScrollTo() {
20
+ const BodyRef = useBodyRef();
21
+
22
+ return useCallback(
23
+ (top: number) => {
24
+ requestAnimationFrame(() => {
25
+ if (BodyRef.current) {
26
+ BodyRef.current.scrollTop = top;
27
+ }
28
+ });
29
+ },
30
+ [BodyRef]
31
+ );
32
+ }