@atlaskit/emoji 67.0.7 → 67.2.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 (238) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/cjs/api/EmojiResource.js +41 -25
  3. package/dist/cjs/api/media/TokenManager.js +4 -4
  4. package/dist/cjs/components/common/CachingEmoji.js +14 -6
  5. package/dist/cjs/components/common/Emoji.js +48 -12
  6. package/dist/cjs/components/common/EmojiActions.js +62 -26
  7. package/dist/cjs/components/common/EmojiErrorMessage.js +12 -7
  8. package/dist/cjs/components/common/EmojiPlaceholder.js +1 -0
  9. package/dist/cjs/components/common/{EmojiButton.js → EmojiRadioButton.js} +28 -19
  10. package/dist/cjs/components/common/EmojiUploadPicker.js +80 -37
  11. package/dist/cjs/components/common/EmojiUploadPreview.js +11 -2
  12. package/dist/cjs/components/common/FileChooser.js +2 -2
  13. package/dist/cjs/components/common/ResourcedEmojiComponent.js +4 -0
  14. package/dist/cjs/components/common/RetryableButton.js +7 -3
  15. package/dist/cjs/components/common/TonePreviewButton.js +44 -0
  16. package/dist/cjs/components/common/ToneSelector.js +53 -25
  17. package/dist/cjs/components/common/styles.js +45 -16
  18. package/dist/cjs/components/i18n.js +44 -4
  19. package/dist/cjs/components/picker/CategorySelector.js +112 -90
  20. package/dist/cjs/components/picker/CategoryTracker.js +0 -28
  21. package/dist/cjs/components/picker/EmojiPickerCategoryHeading.js +2 -1
  22. package/dist/cjs/components/picker/EmojiPickerComponent.js +33 -44
  23. package/dist/cjs/components/picker/EmojiPickerEmojiRow.js +32 -4
  24. package/dist/cjs/components/picker/EmojiPickerList.js +154 -88
  25. package/dist/cjs/components/picker/EmojiPickerListSearch.js +66 -117
  26. package/dist/cjs/components/picker/EmojiPickerVirtualItems.js +5 -2
  27. package/dist/cjs/components/picker/VirtualList.js +273 -171
  28. package/dist/cjs/components/picker/styles.js +43 -51
  29. package/dist/cjs/components/typeahead/EmojiTypeAheadComponent.js +0 -10
  30. package/dist/cjs/context/EmojiPickerListContext.js +33 -0
  31. package/dist/cjs/hooks/useEmojiPickerListContext.js +12 -0
  32. package/dist/cjs/hooks/useIsMounted.js +17 -0
  33. package/dist/cjs/i18n/cs.js +35 -34
  34. package/dist/cjs/i18n/da.js +35 -34
  35. package/dist/cjs/i18n/de.js +35 -34
  36. package/dist/cjs/i18n/en.js +35 -34
  37. package/dist/cjs/i18n/en_GB.js +35 -34
  38. package/dist/cjs/i18n/es.js +35 -34
  39. package/dist/cjs/i18n/fi.js +35 -34
  40. package/dist/cjs/i18n/fr.js +35 -34
  41. package/dist/cjs/i18n/hu.js +35 -34
  42. package/dist/cjs/i18n/it.js +35 -34
  43. package/dist/cjs/i18n/ja.js +35 -34
  44. package/dist/cjs/i18n/ko.js +35 -34
  45. package/dist/cjs/i18n/nb.js +35 -34
  46. package/dist/cjs/i18n/nl.js +35 -34
  47. package/dist/cjs/i18n/pl.js +35 -34
  48. package/dist/cjs/i18n/pt_BR.js +35 -34
  49. package/dist/cjs/i18n/ru.js +35 -34
  50. package/dist/cjs/i18n/sv.js +35 -34
  51. package/dist/cjs/i18n/th.js +35 -34
  52. package/dist/cjs/i18n/tr.js +35 -34
  53. package/dist/cjs/i18n/uk.js +35 -34
  54. package/dist/cjs/i18n/vi.js +35 -34
  55. package/dist/cjs/i18n/zh.js +35 -34
  56. package/dist/cjs/i18n/zh_TW.js +35 -34
  57. package/dist/cjs/types.js +7 -1
  58. package/dist/cjs/util/constants.js +43 -2
  59. package/dist/cjs/util/shared-styles.js +3 -4
  60. package/dist/cjs/version.json +1 -1
  61. package/dist/es2019/api/EmojiResource.js +42 -26
  62. package/dist/es2019/api/media/TokenManager.js +4 -4
  63. package/dist/es2019/components/common/CachingEmoji.js +10 -3
  64. package/dist/es2019/components/common/Emoji.js +44 -11
  65. package/dist/es2019/components/common/EmojiActions.js +55 -24
  66. package/dist/es2019/components/common/EmojiErrorMessage.js +7 -3
  67. package/dist/es2019/components/common/EmojiPlaceholder.js +1 -0
  68. package/dist/es2019/components/common/EmojiRadioButton.js +54 -0
  69. package/dist/es2019/components/common/EmojiUploadPicker.js +75 -36
  70. package/dist/es2019/components/common/EmojiUploadPreview.js +11 -2
  71. package/dist/es2019/components/common/FileChooser.js +1 -1
  72. package/dist/es2019/components/common/ResourcedEmojiComponent.js +4 -0
  73. package/dist/es2019/components/common/RetryableButton.js +7 -3
  74. package/dist/es2019/components/common/TonePreviewButton.js +34 -0
  75. package/dist/es2019/components/common/ToneSelector.js +55 -21
  76. package/dist/es2019/components/common/styles.js +41 -10
  77. package/dist/es2019/components/i18n.js +44 -4
  78. package/dist/es2019/components/picker/CategorySelector.js +114 -89
  79. package/dist/es2019/components/picker/CategoryTracker.js +0 -24
  80. package/dist/es2019/components/picker/EmojiPickerCategoryHeading.js +2 -2
  81. package/dist/es2019/components/picker/EmojiPickerComponent.js +36 -46
  82. package/dist/es2019/components/picker/EmojiPickerEmojiRow.js +51 -21
  83. package/dist/es2019/components/picker/EmojiPickerList.js +113 -55
  84. package/dist/es2019/components/picker/EmojiPickerListSearch.js +61 -98
  85. package/dist/es2019/components/picker/EmojiPickerVirtualItems.js +4 -1
  86. package/dist/es2019/components/picker/VirtualList.js +247 -108
  87. package/dist/es2019/components/picker/styles.js +20 -28
  88. package/dist/es2019/components/typeahead/EmojiTypeAheadComponent.js +0 -10
  89. package/dist/es2019/context/EmojiPickerListContext.js +17 -0
  90. package/dist/es2019/hooks/useEmojiPickerListContext.js +3 -0
  91. package/dist/es2019/hooks/useIsMounted.js +11 -0
  92. package/dist/es2019/i18n/cs.js +35 -34
  93. package/dist/es2019/i18n/da.js +35 -34
  94. package/dist/es2019/i18n/de.js +35 -34
  95. package/dist/es2019/i18n/en.js +35 -34
  96. package/dist/es2019/i18n/en_GB.js +35 -34
  97. package/dist/es2019/i18n/es.js +35 -34
  98. package/dist/es2019/i18n/fi.js +35 -34
  99. package/dist/es2019/i18n/fr.js +35 -34
  100. package/dist/es2019/i18n/hu.js +35 -34
  101. package/dist/es2019/i18n/it.js +35 -34
  102. package/dist/es2019/i18n/ja.js +35 -34
  103. package/dist/es2019/i18n/ko.js +35 -34
  104. package/dist/es2019/i18n/nb.js +35 -34
  105. package/dist/es2019/i18n/nl.js +35 -34
  106. package/dist/es2019/i18n/pl.js +35 -34
  107. package/dist/es2019/i18n/pt_BR.js +35 -34
  108. package/dist/es2019/i18n/ru.js +35 -34
  109. package/dist/es2019/i18n/sv.js +35 -34
  110. package/dist/es2019/i18n/th.js +35 -34
  111. package/dist/es2019/i18n/tr.js +35 -34
  112. package/dist/es2019/i18n/uk.js +35 -34
  113. package/dist/es2019/i18n/vi.js +35 -34
  114. package/dist/es2019/i18n/zh.js +35 -34
  115. package/dist/es2019/i18n/zh_TW.js +35 -34
  116. package/dist/es2019/types.js +5 -0
  117. package/dist/es2019/util/constants.js +32 -0
  118. package/dist/es2019/util/shared-styles.js +1 -2
  119. package/dist/es2019/version.json +1 -1
  120. package/dist/esm/api/EmojiResource.js +42 -26
  121. package/dist/esm/api/media/TokenManager.js +4 -4
  122. package/dist/esm/components/common/CachingEmoji.js +14 -6
  123. package/dist/esm/components/common/Emoji.js +48 -12
  124. package/dist/esm/components/common/EmojiActions.js +62 -26
  125. package/dist/esm/components/common/EmojiErrorMessage.js +7 -3
  126. package/dist/esm/components/common/EmojiPlaceholder.js +1 -0
  127. package/dist/esm/components/common/EmojiRadioButton.js +52 -0
  128. package/dist/esm/components/common/EmojiUploadPicker.js +77 -36
  129. package/dist/esm/components/common/EmojiUploadPreview.js +11 -2
  130. package/dist/esm/components/common/FileChooser.js +1 -1
  131. package/dist/esm/components/common/ResourcedEmojiComponent.js +4 -0
  132. package/dist/esm/components/common/RetryableButton.js +7 -3
  133. package/dist/esm/components/common/TonePreviewButton.js +33 -0
  134. package/dist/esm/components/common/ToneSelector.js +49 -18
  135. package/dist/esm/components/common/styles.js +40 -12
  136. package/dist/esm/components/i18n.js +44 -4
  137. package/dist/esm/components/picker/CategorySelector.js +114 -95
  138. package/dist/esm/components/picker/CategoryTracker.js +0 -28
  139. package/dist/esm/components/picker/EmojiPickerCategoryHeading.js +2 -2
  140. package/dist/esm/components/picker/EmojiPickerComponent.js +35 -46
  141. package/dist/esm/components/picker/EmojiPickerEmojiRow.js +32 -4
  142. package/dist/esm/components/picker/EmojiPickerList.js +156 -86
  143. package/dist/esm/components/picker/EmojiPickerListSearch.js +64 -117
  144. package/dist/esm/components/picker/EmojiPickerVirtualItems.js +5 -2
  145. package/dist/esm/components/picker/VirtualList.js +274 -172
  146. package/dist/esm/components/picker/styles.js +37 -45
  147. package/dist/esm/components/typeahead/EmojiTypeAheadComponent.js +0 -10
  148. package/dist/esm/context/EmojiPickerListContext.js +21 -0
  149. package/dist/esm/hooks/useEmojiPickerListContext.js +5 -0
  150. package/dist/esm/hooks/useIsMounted.js +11 -0
  151. package/dist/esm/i18n/cs.js +35 -34
  152. package/dist/esm/i18n/da.js +35 -34
  153. package/dist/esm/i18n/de.js +35 -34
  154. package/dist/esm/i18n/en.js +35 -34
  155. package/dist/esm/i18n/en_GB.js +35 -34
  156. package/dist/esm/i18n/es.js +35 -34
  157. package/dist/esm/i18n/fi.js +35 -34
  158. package/dist/esm/i18n/fr.js +35 -34
  159. package/dist/esm/i18n/hu.js +35 -34
  160. package/dist/esm/i18n/it.js +35 -34
  161. package/dist/esm/i18n/ja.js +35 -34
  162. package/dist/esm/i18n/ko.js +35 -34
  163. package/dist/esm/i18n/nb.js +35 -34
  164. package/dist/esm/i18n/nl.js +35 -34
  165. package/dist/esm/i18n/pl.js +35 -34
  166. package/dist/esm/i18n/pt_BR.js +35 -34
  167. package/dist/esm/i18n/ru.js +35 -34
  168. package/dist/esm/i18n/sv.js +35 -34
  169. package/dist/esm/i18n/th.js +35 -34
  170. package/dist/esm/i18n/tr.js +35 -34
  171. package/dist/esm/i18n/uk.js +35 -34
  172. package/dist/esm/i18n/vi.js +35 -34
  173. package/dist/esm/i18n/zh.js +35 -34
  174. package/dist/esm/i18n/zh_TW.js +35 -34
  175. package/dist/esm/types.js +5 -0
  176. package/dist/esm/util/constants.js +32 -0
  177. package/dist/esm/util/shared-styles.js +1 -2
  178. package/dist/esm/version.json +1 -1
  179. package/dist/types/api/EmojiResource.d.ts +2 -0
  180. package/dist/types/components/common/Emoji.d.ts +7 -1
  181. package/dist/types/components/common/EmojiActions.d.ts +4 -3
  182. package/dist/types/components/common/{EmojiButton.d.ts → EmojiRadioButton.d.ts} +3 -4
  183. package/dist/types/components/common/EmojiUploadPicker.d.ts +6 -4
  184. package/dist/types/components/common/RetryableButton.d.ts +1 -0
  185. package/dist/types/components/common/TonePreviewButton.d.ts +14 -0
  186. package/dist/types/components/common/ToneSelector.d.ts +8 -5
  187. package/dist/types/components/common/internal-types.d.ts +9 -0
  188. package/dist/types/components/common/styles.d.ts +2 -1
  189. package/dist/types/components/i18n.d.ts +40 -0
  190. package/dist/types/components/picker/CategorySelector.d.ts +3 -10
  191. package/dist/types/components/picker/CategoryTracker.d.ts +0 -2
  192. package/dist/types/components/picker/EmojiPickerCategoryHeading.d.ts +2 -1
  193. package/dist/types/components/picker/EmojiPickerEmojiRow.d.ts +5 -0
  194. package/dist/types/components/picker/EmojiPickerList.d.ts +14 -7
  195. package/dist/types/components/picker/EmojiPickerListSearch.d.ts +4 -8
  196. package/dist/types/components/picker/EmojiPickerVirtualItems.d.ts +1 -1
  197. package/dist/types/components/picker/VirtualList.d.ts +7 -24
  198. package/dist/types/components/picker/styles.d.ts +1 -1
  199. package/dist/types/context/EmojiPickerListContext.d.ts +10 -0
  200. package/dist/types/hooks/useEmojiPickerListContext.d.ts +1 -0
  201. package/dist/types/hooks/useIsMounted.d.ts +1 -0
  202. package/dist/types/i18n/cs.d.ts +34 -34
  203. package/dist/types/i18n/da.d.ts +34 -34
  204. package/dist/types/i18n/de.d.ts +34 -34
  205. package/dist/types/i18n/en.d.ts +34 -34
  206. package/dist/types/i18n/en_GB.d.ts +34 -34
  207. package/dist/types/i18n/es.d.ts +34 -34
  208. package/dist/types/i18n/fi.d.ts +34 -34
  209. package/dist/types/i18n/fr.d.ts +34 -34
  210. package/dist/types/i18n/hu.d.ts +34 -34
  211. package/dist/types/i18n/it.d.ts +34 -34
  212. package/dist/types/i18n/ja.d.ts +34 -34
  213. package/dist/types/i18n/ko.d.ts +34 -34
  214. package/dist/types/i18n/nb.d.ts +34 -34
  215. package/dist/types/i18n/nl.d.ts +34 -34
  216. package/dist/types/i18n/pl.d.ts +34 -34
  217. package/dist/types/i18n/pt_BR.d.ts +34 -34
  218. package/dist/types/i18n/ru.d.ts +34 -34
  219. package/dist/types/i18n/sv.d.ts +34 -34
  220. package/dist/types/i18n/th.d.ts +34 -34
  221. package/dist/types/i18n/tr.d.ts +34 -34
  222. package/dist/types/i18n/uk.d.ts +34 -34
  223. package/dist/types/i18n/vi.d.ts +34 -34
  224. package/dist/types/i18n/zh.d.ts +34 -34
  225. package/dist/types/i18n/zh_TW.d.ts +34 -34
  226. package/dist/types/types.d.ts +5 -0
  227. package/dist/types/util/constants.d.ts +28 -0
  228. package/dist/types/util/shared-styles.d.ts +1 -1
  229. package/dist/types/util/type-helpers.d.ts +1 -1
  230. package/package.json +12 -8
  231. package/report.api.md +62 -1
  232. package/README.md +0 -3
  233. package/dist/cjs/components/hooks.js +0 -14
  234. package/dist/es2019/components/common/EmojiButton.js +0 -49
  235. package/dist/es2019/components/hooks.js +0 -8
  236. package/dist/esm/components/common/EmojiButton.js +0 -43
  237. package/dist/esm/components/hooks.js +0 -8
  238. package/dist/types/components/hooks.d.ts +0 -1
@@ -1,107 +1,70 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
1
  /** @jsx jsx */
3
- import React, { PureComponent } from 'react';
2
+ import React, { useLayoutEffect, useRef, useState } from 'react';
4
3
  import { jsx } from '@emotion/react';
5
4
  import TextField from '@atlaskit/textfield';
6
5
  import SearchIcon from '@atlaskit/icon/glyph/search';
7
- import { injectIntl } from 'react-intl-next';
6
+ import VisuallyHidden from '@atlaskit/visually-hidden';
7
+ import { useIntl } from 'react-intl-next';
8
8
  import { messages } from '../i18n';
9
9
  import { input, pickerSearch, searchIcon } from './styles';
10
+ import { EMOJI_SEARCH_DEBOUNCE } from '../../util/constants';
11
+ import { useDebouncedCallback } from 'use-debounce';
10
12
  export const emojiPickerSearchTestId = 'emoji-picker-serach';
11
- class EmojiPickerListSearch extends PureComponent {
12
- constructor(...args) {
13
- super(...args);
14
- _defineProperty(this, "onBlur", () => {
15
- const activeElement = document.activeElement;
16
- // Input lost focus to emoji picker container (happens in IE11 when updating search results)
17
- // See FS-2111
18
- if (activeElement instanceof HTMLElement && activeElement.getAttribute('data-emoji-picker-container')) {
19
- this.restoreInputFocus();
13
+ export const EmojiPickerListSearch = props => {
14
+ const {
15
+ style,
16
+ query,
17
+ resultsCount,
18
+ onChange
19
+ } = props;
20
+ const textRef = useRef(null);
21
+ const [dirty, setDirty] = useState(false);
22
+ const {
23
+ formatMessage
24
+ } = useIntl();
25
+
26
+ // Debounce callback
27
+ const [debouncedSearch] = useDebouncedCallback(value => {
28
+ onChange(value);
29
+ setDirty(true);
30
+ },
31
+ // delay in ms
32
+ EMOJI_SEARCH_DEBOUNCE);
33
+ const handleOnChange = e => {
34
+ debouncedSearch(e.target.value);
35
+ };
36
+ useLayoutEffect(() => {
37
+ requestAnimationFrame(() => {
38
+ if (textRef) {
39
+ var _textRef$current;
40
+ (_textRef$current = textRef.current) === null || _textRef$current === void 0 ? void 0 : _textRef$current.focus();
20
41
  }
21
42
  });
22
- _defineProperty(this, "onChange", e => {
23
- this.saveInputSelection();
24
- this.props.onChange(e);
25
- });
26
- _defineProperty(this, "focusInput", () => {
27
- if (this.inputRef) {
28
- this.inputRef.focus();
29
- }
30
- });
31
- _defineProperty(this, "handleInputRef", input => {
32
- if (input) {
33
- // Defer focus so it give some time to position the popup before
34
- // setting the focus to search input.
35
- // see FS-2056
36
- this.inputRef = input;
37
- if (typeof window === 'undefined') {
38
- return;
39
- }
40
- window.requestAnimationFrame(this.focusInput);
41
- }
42
- });
43
- }
44
- saveInputSelection() {
45
- this.inputSelection = undefined;
46
- if (this.inputRef) {
47
- const {
48
- selectionStart,
49
- selectionEnd,
50
- selectionDirection
51
- } = this.inputRef;
52
- if (selectionStart && selectionEnd && selectionDirection) {
53
- this.inputSelection = {
54
- selectionStart,
55
- selectionEnd,
56
- selectionDirection: selectionDirection
57
- };
58
- }
59
- }
60
- }
61
- restoreInputFocus() {
62
- this.focusInput();
63
- if (this.inputSelection && this.inputRef && this.inputRef.setSelectionRange) {
64
- const {
65
- selectionStart,
66
- selectionEnd,
67
- selectionDirection
68
- } = this.inputSelection;
69
- this.inputRef.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
70
- }
71
- }
72
- render() {
73
- const {
74
- style,
75
- query,
76
- intl
77
- } = this.props;
78
- const {
79
- formatMessage
80
- } = intl;
81
- return jsx("div", {
82
- css: pickerSearch,
83
- style: style
84
- }, jsx(TextField, {
85
- "aria-label": formatMessage(messages.searchLabel),
86
- css: input,
87
- autoComplete: "off",
88
- name: "search",
89
- placeholder: `${formatMessage(messages.searchPlaceholder)}...`,
90
- onChange: this.onChange,
91
- value: query || '',
92
- ref: this.handleInputRef,
93
- isCompact: true,
94
- onBlur: this.onBlur,
95
- elemBeforeInput: jsx("span", {
96
- css: searchIcon
97
- }, jsx(SearchIcon, {
98
- label: ""
99
- })),
100
- testId: emojiPickerSearchTestId
101
- }));
102
- }
103
- }
104
- _defineProperty(EmojiPickerListSearch, "defaultProps", {
105
- style: {}
106
- });
107
- export default injectIntl(EmojiPickerListSearch);
43
+ }, []);
44
+ return jsx("div", {
45
+ css: pickerSearch,
46
+ style: style
47
+ }, jsx(VisuallyHidden, {
48
+ id: "emoji-search-results-status",
49
+ role: "status"
50
+ }, dirty && query === '' && formatMessage(messages.searchResultsStatusSeeAll), query !== '' && formatMessage(messages.searchResultsStatus, {
51
+ count: resultsCount
52
+ })), jsx(TextField, {
53
+ role: "searchbox",
54
+ "aria-label": formatMessage(messages.searchLabel),
55
+ css: input,
56
+ autoComplete: "off",
57
+ name: "search",
58
+ placeholder: `${formatMessage(messages.searchPlaceholder)}...`,
59
+ defaultValue: query || '',
60
+ onChange: handleOnChange,
61
+ elemBeforeInput: jsx("span", {
62
+ css: searchIcon
63
+ }, jsx(SearchIcon, {
64
+ label: ""
65
+ })),
66
+ testId: emojiPickerSearchTestId,
67
+ ref: textRef,
68
+ isCompact: true
69
+ }));
70
+ };
@@ -1,3 +1,4 @@
1
+ import _extends from "@babel/runtime/helpers/extends";
1
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
3
  /** @jsx jsx */
3
4
 
@@ -16,7 +17,9 @@ export class AbstractItem {
16
17
  export class EmojisRowItem extends AbstractItem {
17
18
  constructor(props) {
18
19
  super(props, sizes.emojiRowHeight);
19
- _defineProperty(this, "renderItem", () => jsx(EmojiPickerEmojiRow, this.props));
20
+ _defineProperty(this, "renderItem", context => jsx(EmojiPickerEmojiRow, _extends({}, this.props, {
21
+ virtualItemContext: context
22
+ })));
20
23
  }
21
24
  }
22
25
  export class LoadingItem extends AbstractItem {
@@ -1,42 +1,50 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
1
  /** @jsx jsx */
3
2
  import { jsx } from '@emotion/react';
4
- import { Virtualizer, observeElementRect, observeElementOffset, elementScroll } from '@tanstack/react-virtual';
5
- import React, { PureComponent, createRef } from 'react';
3
+ import React, { useCallback, useImperativeHandle } from 'react';
6
4
  import { virtualList } from './styles';
5
+ import { useVirtualizer } from '@tanstack/react-virtual';
6
+ import { useEmojiPickerListContext } from '../../hooks/useEmojiPickerListContext';
7
+ import { EMOJIPICKERLIST_KEYBOARD_KEYS_SUPPORTED, EMOJI_LIST_COLUMNS, EMOJI_LIST_PAGE_COUNT, KeyboardNavigationDirection, KeyboardKeys } from '../../util/constants';
7
8
  export const virtualListScrollContainerTestId = 'virtual-list-scroll-container';
8
- export class VirtualList extends PureComponent {
9
- /**
10
- * Determine if the component is mounted to the DOM or not
11
- */
12
-
13
- constructor(props) {
14
- super(props);
15
- _defineProperty(this, "_isMounted", false);
16
- this.parentRef = /*#__PURE__*/createRef();
17
- this.rowVirtualizer = new Virtualizer(this.getVirtualizerOptions(props));
18
- }
19
- getVirtualizerOptions(props) {
9
+ export const VirtualList = /*#__PURE__*/React.forwardRef((props, ref) => {
10
+ const parentRef = React.useRef(null);
11
+ const virtualistItemsRef = React.useRef(null);
12
+ const {
13
+ rowRenderer,
14
+ onRowsRendered,
15
+ scrollToAlignment,
16
+ width,
17
+ height,
18
+ rowCount
19
+ } = props;
20
+ const {
21
+ currentEmojisFocus,
22
+ setEmojisFocus
23
+ } = useEmojiPickerListContext();
24
+ const getVirtualizerOptions = () => {
20
25
  const {
21
26
  rowCount,
22
27
  rowHeight,
23
28
  overscanRowCount
24
29
  } = props;
25
30
  return {
26
- observeElementRect: observeElementRect,
27
- observeElementOffset: observeElementOffset,
28
- scrollToFn: elementScroll,
29
31
  count: rowCount,
30
- getScrollElement: () => this.parentRef.current,
32
+ getScrollElement: () => parentRef.current,
31
33
  estimateSize: rowHeight,
32
34
  overscan: overscanRowCount,
33
35
  onChange: () => {
34
- this.forceUpdateGrid();
35
- }
36
+ const startIndex = getFirstVisibleListElementIndex();
37
+ onRowsRendered({
38
+ startIndex
39
+ });
40
+ },
41
+ scrollPaddingStart: 28,
42
+ scrollPaddingEnd: 28
36
43
  };
37
- }
38
- isElementVisible(element) {
39
- const parent = this.parentRef.current;
44
+ };
45
+ const rowVirtualizer = useVirtualizer(getVirtualizerOptions());
46
+ const isElementVisible = element => {
47
+ const parent = parentRef.current;
40
48
  const elementRect = element.getBoundingClientRect();
41
49
  const parentRect = parent.getBoundingClientRect();
42
50
  const elemTop = elementRect.top;
@@ -47,105 +55,236 @@ export class VirtualList extends PureComponent {
47
55
  // Only completely visible elements return true:
48
56
  const isVisible = elemTop >= parentTop && elemBottom <= parentBottom;
49
57
  return isVisible;
50
- }
51
- getFirstVisibleListElementIndex() {
52
- var _this$parentRef$curre, _this$parentRef$curre2;
53
- const virtualList = this.rowVirtualizer.getVirtualItems();
54
- const renderedElements = (_this$parentRef$curre = this.parentRef.current) === null || _this$parentRef$curre === void 0 ? void 0 : (_this$parentRef$curre2 = _this$parentRef$curre.firstChild) === null || _this$parentRef$curre2 === void 0 ? void 0 : _this$parentRef$curre2.childNodes;
58
+ };
59
+ const getFirstVisibleListElementIndex = useCallback(() => {
60
+ var _parentRef$current, _parentRef$current$fi;
61
+ const virtualList = rowVirtualizer.getVirtualItems();
62
+ const renderedElements = (_parentRef$current = parentRef.current) === null || _parentRef$current === void 0 ? void 0 : (_parentRef$current$fi = _parentRef$current.firstChild) === null || _parentRef$current$fi === void 0 ? void 0 : _parentRef$current$fi.childNodes;
55
63
  if (virtualList.length === 0 || !renderedElements || renderedElements.length === 0) {
56
64
  return 0;
57
65
  }
58
-
59
66
  // Convert NodeListOf<ChildNodes> to ChildNodes[]
60
67
  const renderedElementsToArray = Array.from(renderedElements);
61
- const firstVisibleIndex = renderedElementsToArray.findIndex(elem => this.isElementVisible(elem));
68
+ const firstVisibleIndex = renderedElementsToArray.findIndex(elem => isElementVisible(elem));
62
69
  if (firstVisibleIndex !== -1) {
63
70
  var _virtualList$firstVis;
64
71
  return ((_virtualList$firstVis = virtualList[firstVisibleIndex]) === null || _virtualList$firstVis === void 0 ? void 0 : _virtualList$firstVis.index) || 0;
65
72
  }
66
73
  return 0;
67
- }
68
- onRendered() {
69
- const {
70
- onRowsRendered
71
- } = this.props;
72
- this.rowVirtualizer.setOptions(this.getVirtualizerOptions(this.props));
73
- const startIndex = this.getFirstVisibleListElementIndex();
74
- onRowsRendered({
75
- startIndex
76
- });
77
- }
78
- componentDidUpdate() {
79
- this.onRendered();
80
- }
81
- componentDidMount() {
82
- if (this.rowVirtualizer) {
83
- this.rowVirtualizer._didMount();
84
- this.rowVirtualizer._willUpdate();
85
- this._isMounted = true;
74
+ }, [rowVirtualizer]);
75
+
76
+ /**
77
+ * Recurisive function to find next available emoji and it's focus indexes in the grid
78
+ *
79
+ * current focus element is at rowIndex.columnIndex
80
+ * if found element then return the element and focus indexes
81
+ * otherwise change row/column till find the element
82
+ * if can't find the element till reach the edge of grid, we keep current focus states
83
+ *
84
+ * @param rowIndex search from row index (0 based)
85
+ * @param columnIndex search from column index (0 based)
86
+ * @param direction search direction
87
+ */
88
+ const findNextEmoji = useCallback((rowIndex, columnIndex, direction) => {
89
+ var _virtualistItemsRef$c;
90
+ const emojiToFocus = (_virtualistItemsRef$c = virtualistItemsRef.current) === null || _virtualistItemsRef$c === void 0 ? void 0 : _virtualistItemsRef$c.querySelector(`[data-focus-index="${rowIndex}-${columnIndex}"]`);
91
+ const lastRowIndex = rowCount - 1;
92
+ const lastColumnIndex = EMOJI_LIST_COLUMNS - 1;
93
+ if (emojiToFocus) {
94
+ return {
95
+ element: emojiToFocus,
96
+ rowIndex,
97
+ columnIndex
98
+ };
86
99
  }
87
- }
88
- componentWillUnmount() {
89
- this._isMounted = false;
90
- }
91
- scrollToRow(index) {
92
- const {
93
- scrollToAlignment
94
- } = this.props;
95
- if (index !== undefined) {
96
- var _this$rowVirtualizer;
97
- (_this$rowVirtualizer = this.rowVirtualizer) === null || _this$rowVirtualizer === void 0 ? void 0 : _this$rowVirtualizer.scrollToIndex(index, {
98
- align: scrollToAlignment,
100
+ switch (direction) {
101
+ case KeyboardNavigationDirection.Down:
102
+ if (rowIndex >= lastRowIndex) {
103
+ return null;
104
+ }
105
+ // find emoji in same column but lower row
106
+ return findNextEmoji(rowIndex + 1, columnIndex, KeyboardNavigationDirection.Down);
107
+ case KeyboardNavigationDirection.Up:
108
+ if (rowIndex <= 0) {
109
+ return null;
110
+ }
111
+ // find emoji in same column but upper row
112
+ return findNextEmoji(rowIndex - 1, columnIndex, KeyboardNavigationDirection.Up);
113
+ case KeyboardNavigationDirection.Left:
114
+ if (rowIndex <= 0) {
115
+ return null;
116
+ }
117
+ if (columnIndex < 0) {
118
+ // find emoji in upper row
119
+ return findNextEmoji(rowIndex - 1, lastColumnIndex, KeyboardNavigationDirection.Left);
120
+ }
121
+ // find emoji on left in the current row
122
+ return findNextEmoji(rowIndex, columnIndex - 1, KeyboardNavigationDirection.Left);
123
+ case KeyboardNavigationDirection.Right:
124
+ if (rowIndex >= lastRowIndex) {
125
+ return null;
126
+ }
127
+ // if no emoji on right, we try first emoji in next row
128
+ return findNextEmoji(rowIndex + 1, 0, KeyboardNavigationDirection.Right);
129
+ default:
130
+ return null;
131
+ }
132
+ }, [rowCount]);
133
+
134
+ /**
135
+ * Find the valid emoji to scroll and focus
136
+ */
137
+ const scrollToRowAndFocusEmoji = useCallback(emojiToFocus => {
138
+ if (emojiToFocus) {
139
+ var _emojiToFocus$element;
140
+ rowVirtualizer.scrollToIndex(emojiToFocus.rowIndex, {
141
+ align: 'auto',
99
142
  smoothScroll: false
100
143
  });
101
- this.forceUpdateGrid();
144
+ (_emojiToFocus$element = emojiToFocus.element) === null || _emojiToFocus$element === void 0 ? void 0 : _emojiToFocus$element.focus({
145
+ preventScroll: true
146
+ });
147
+ setEmojisFocus({
148
+ rowIndex: emojiToFocus.rowIndex,
149
+ columnIndex: emojiToFocus.columnIndex
150
+ });
151
+ }
152
+ }, [rowVirtualizer, setEmojisFocus]);
153
+ const focusEmoji = useCallback((rIndex, cIndex, direction, waitForScrollFinish = false) => {
154
+ if (waitForScrollFinish) {
155
+ // scroll to target rowIndex first to ensure the row is rendered in list.
156
+ // used in page up/down, ctrl+Home, ctrl+End
157
+ rowVirtualizer.scrollToIndex(rIndex, {
158
+ align: 'auto',
159
+ smoothScroll: false
160
+ });
161
+ setTimeout(() => {
162
+ const emojiToFocus = findNextEmoji(rIndex, cIndex, direction);
163
+ scrollToRowAndFocusEmoji(emojiToFocus);
164
+ }, 100); // 100ms is virtual list scrolling time
165
+ } else {
166
+ const emojiToFocus = findNextEmoji(rIndex, cIndex, direction);
167
+ scrollToRowAndFocusEmoji(emojiToFocus);
102
168
  }
103
- }
104
- forceUpdateGrid() {
105
- if (this._isMounted) {
106
- this.forceUpdate();
169
+ }, [scrollToRowAndFocusEmoji, findNextEmoji, rowVirtualizer]);
170
+
171
+ // following the guide from https://www.w3.org/WAI/ARIA/apg/patterns/grid/
172
+ const handleKeyDown = e => {
173
+ if (!EMOJIPICKERLIST_KEYBOARD_KEYS_SUPPORTED.includes(e.key)) {
174
+ return;
107
175
  }
108
- }
109
- recomputeRowHeights() {
110
- var _this$rowVirtualizer2;
111
- (_this$rowVirtualizer2 = this.rowVirtualizer) === null || _this$rowVirtualizer2 === void 0 ? void 0 : _this$rowVirtualizer2.measure();
112
- }
113
- handleScroll(e) {
114
176
  e.preventDefault();
115
- }
116
- render() {
117
- var _this$rowVirtualizer3, _this$rowVirtualizer4;
118
- const {
119
- rowRenderer,
120
- width,
121
- height
122
- } = this.props;
123
- return jsx("div", {
124
- ref: this.parentRef,
125
- role: "grid",
126
- style: {
127
- height: `${height}px`,
128
- width: `${width}px`
177
+ const lastRowIndex = rowCount - 1;
178
+ const lastColumnIndex = EMOJI_LIST_COLUMNS - 1;
179
+
180
+ // focus first emoji on first row
181
+ if (e.key === KeyboardKeys.Home && e.ctrlKey) {
182
+ focusEmoji(1, 0, KeyboardNavigationDirection.Up, true);
183
+ return;
184
+ } else if (e.key === KeyboardKeys.End && e.ctrlKey) {
185
+ // focus last available emoji on last row
186
+ focusEmoji(lastRowIndex, lastColumnIndex, KeyboardNavigationDirection.Left, true);
187
+ return;
188
+ }
189
+ switch (e.key) {
190
+ // navigate to the right column
191
+ case KeyboardKeys.ArrowRight:
192
+ focusEmoji(currentEmojisFocus.rowIndex, currentEmojisFocus.columnIndex + 1, KeyboardNavigationDirection.Right);
193
+ break;
194
+ // navigate to the left column
195
+ case KeyboardKeys.ArrowLeft:
196
+ focusEmoji(currentEmojisFocus.rowIndex, currentEmojisFocus.columnIndex - 1, KeyboardNavigationDirection.Left);
197
+ break;
198
+ // navigate to the down row
199
+ case KeyboardKeys.ArrowDown:
200
+ focusEmoji(currentEmojisFocus.rowIndex === lastRowIndex ? lastRowIndex : currentEmojisFocus.rowIndex + 1, currentEmojisFocus.columnIndex, KeyboardNavigationDirection.Down);
201
+ break;
202
+ // navigate to the row after {EMOJI_LIST_PAGE_COUNT} rows
203
+ case KeyboardKeys.PageDown:
204
+ focusEmoji(currentEmojisFocus.rowIndex + EMOJI_LIST_PAGE_COUNT, currentEmojisFocus.columnIndex, KeyboardNavigationDirection.Down, true);
205
+ break;
206
+ // navigate to the up row
207
+ case KeyboardKeys.ArrowUp:
208
+ focusEmoji(currentEmojisFocus.rowIndex <= 1 ? 1 : currentEmojisFocus.rowIndex - 1, currentEmojisFocus.columnIndex, KeyboardNavigationDirection.Up);
209
+ break;
210
+ // navigate to the row before {EMOJI_LIST_PAGE_COUNT} rows
211
+ case KeyboardKeys.PageUp:
212
+ focusEmoji(currentEmojisFocus.rowIndex - EMOJI_LIST_PAGE_COUNT, currentEmojisFocus.columnIndex, KeyboardNavigationDirection.Up, true);
213
+ break;
214
+ // navigate to the first cell of current row
215
+ case KeyboardKeys.Home:
216
+ focusEmoji(currentEmojisFocus.rowIndex, 0, KeyboardNavigationDirection.Left);
217
+ break;
218
+ // navigate to the last cell of current row
219
+ case KeyboardKeys.End:
220
+ focusEmoji(currentEmojisFocus.rowIndex, lastColumnIndex, KeyboardNavigationDirection.Left);
221
+ break;
222
+ }
223
+ };
224
+
225
+ // Exposing a custom ref handle to the parent component EmojiPickerList to trigger scrollToRow via the listRef
226
+ // https://beta.reactjs.org/reference/react/useImperativeHandle
227
+ useImperativeHandle(ref, () => {
228
+ return {
229
+ scrollToRow(index) {
230
+ if (index !== undefined) {
231
+ rowVirtualizer.setOptions({
232
+ ...rowVirtualizer.options,
233
+ scrollPaddingStart: 0
234
+ });
235
+ rowVirtualizer.scrollToIndex(index, {
236
+ align: scrollToAlignment,
237
+ smoothScroll: false
238
+ });
239
+ }
129
240
  },
130
- css: virtualList,
131
- "data-testid": virtualListScrollContainerTestId,
132
- onScroll: this.handleScroll
133
- }, jsx("div", {
134
- style: {
135
- height: `${(_this$rowVirtualizer3 = this.rowVirtualizer) === null || _this$rowVirtualizer3 === void 0 ? void 0 : _this$rowVirtualizer3.getTotalSize()}px`,
136
- width: '100%',
137
- position: 'relative'
138
- }
139
- }, (_this$rowVirtualizer4 = this.rowVirtualizer) === null || _this$rowVirtualizer4 === void 0 ? void 0 : _this$rowVirtualizer4.getVirtualItems().map(virtualRow => jsx("div", {
140
- key: virtualRow.key,
141
- style: {
142
- position: 'absolute',
143
- top: 0,
144
- left: 0,
145
- width: '100%',
146
- height: `${virtualRow.size}px`,
147
- transform: `translateY(${virtualRow.start}px)`
241
+ scrollToRowAndFocusLastEmoji(index) {
242
+ if (index !== undefined) {
243
+ focusEmoji(index, EMOJI_LIST_COLUMNS, KeyboardNavigationDirection.Left, true);
244
+ }
245
+ },
246
+ updateFocusIndex(index) {
247
+ var _virtualistItemsRef$c2;
248
+ // row could be removed from virtual list after scrolling, we'll update emoji cell tabIndex after losing focus
249
+ if (!((_virtualistItemsRef$c2 = virtualistItemsRef.current) !== null && _virtualistItemsRef$c2 !== void 0 && _virtualistItemsRef$c2.contains(document.activeElement))) {
250
+ setEmojisFocus({
251
+ rowIndex: index,
252
+ columnIndex: 0
253
+ });
254
+ }
148
255
  }
149
- }, rowRenderer(virtualRow)))));
150
- }
151
- }
256
+ };
257
+ }, [setEmojisFocus, focusEmoji, rowVirtualizer, scrollToAlignment]);
258
+ return jsx("div", {
259
+ ref: parentRef,
260
+ style: {
261
+ height: `${height}px`,
262
+ width: `${width}px`
263
+ },
264
+ css: virtualList,
265
+ "data-testid": virtualListScrollContainerTestId,
266
+ "aria-labelledby": "emoji-picker-table-description",
267
+ role: "grid"
268
+ }, jsx("div", {
269
+ style: {
270
+ height: `${rowVirtualizer.getTotalSize()}px`,
271
+ width: '100%',
272
+ position: 'relative'
273
+ },
274
+ ref: virtualistItemsRef,
275
+ onKeyDown: handleKeyDown,
276
+ role: "presentation"
277
+ }, rowVirtualizer.getVirtualItems().map((virtualRow, index) => jsx("div", {
278
+ key: virtualRow.key,
279
+ style: {
280
+ position: 'absolute',
281
+ top: 0,
282
+ left: 0,
283
+ width: '100%',
284
+ height: `${virtualRow.size}px`,
285
+ transform: `translateY(${virtualRow.start}px)`
286
+ },
287
+ role: "row",
288
+ "aria-rowindex": index + 1
289
+ }, rowRenderer(virtualRow)))));
290
+ });