@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
@@ -40,10 +40,10 @@ export default class TokenManager {
40
40
  // still valid
41
41
  return Promise.resolve(mediaApiToken);
42
42
  }
43
- if (activeTokenRefresh) {
44
- // refresh token promise already active, return that
45
- return activeTokenRefresh;
46
- }
43
+ }
44
+ if (activeTokenRefresh) {
45
+ // refresh token promise already active, return that
46
+ return activeTokenRefresh;
47
47
  }
48
48
 
49
49
  // request a new token and track the promise for future requests until completed
@@ -19,7 +19,9 @@ export const CachingEmoji = props => {
19
19
  // Optimisation to only render CachingMediaEmoji if necessary
20
20
  // slight performance hit, which accumulates for a large number of emoji.
21
21
  const {
22
- emoji
22
+ emoji,
23
+ placeholderSize,
24
+ ...restProps
23
25
  } = props;
24
26
  // start emoji rendered experience, it may have already started earlier in `ResourcedEmoji`.
25
27
  useSampledUFOComponentExperience(ufoExperiences['emoji-rendered'].getInstance(emoji.id || emoji.shortName), SAMPLING_RATE_EMOJI_RENDERED_EXP, {
@@ -34,9 +36,14 @@ export const CachingEmoji = props => {
34
36
  }, []);
35
37
  const emojiNode = () => {
36
38
  if (isMediaEmoji(emoji)) {
37
- return /*#__PURE__*/React.createElement(CachingMediaEmoji, props);
39
+ return /*#__PURE__*/React.createElement(CachingMediaEmoji, _extends({
40
+ emoji: emoji,
41
+ placeholderSize: placeholderSize
42
+ }, restProps));
38
43
  }
39
- return /*#__PURE__*/React.createElement(Emoji, props);
44
+ return /*#__PURE__*/React.createElement(Emoji, _extends({
45
+ emoji: emoji
46
+ }, restProps));
40
47
  };
41
48
  return /*#__PURE__*/React.createElement(UfoErrorBoundary, {
42
49
  experiences: [ufoExperiences['emoji-rendered'].getInstance(props.emoji.id || props.emoji.shortName)]
@@ -50,6 +50,15 @@ const handleMouseMove = (props, event) => {
50
50
  onMouseMove(toEmojiId(emoji), emoji, event);
51
51
  }
52
52
  };
53
+ const handleFocus = (props, event) => {
54
+ const {
55
+ emoji,
56
+ onFocus
57
+ } = props;
58
+ if (onFocus) {
59
+ onFocus(toEmojiId(emoji), emoji, event);
60
+ }
61
+ };
53
62
  const handleDelete = (props, event) => {
54
63
  const {
55
64
  emoji,
@@ -86,7 +95,18 @@ export const SpriteEmoji = props => {
86
95
  selectOnHover,
87
96
  className,
88
97
  showTooltip,
89
- shouldBeInteractive
98
+ shouldBeInteractive = false,
99
+ tabIndex,
100
+ onSelected,
101
+ onMouseMove,
102
+ onFocus,
103
+ onDelete,
104
+ onLoadError,
105
+ onLoadSuccess,
106
+ showDelete,
107
+ disableLazyLoad,
108
+ autoWidth,
109
+ ...other
90
110
  } = props;
91
111
  const representation = emoji.representation;
92
112
  const sprite = representation.sprite;
@@ -108,11 +128,11 @@ export const SpriteEmoji = props => {
108
128
  backgroundSize: `${sprite.column * 100}% ${sprite.row * 100}%`,
109
129
  ...sizing
110
130
  };
111
- return jsx("span", {
131
+ return jsx("span", _extends({
112
132
  "data-testid": `sprite-emoji-${emoji.shortName}`,
113
133
  "data-emoji-type": "sprite",
114
- tabIndex: shouldBeInteractive ? 0 : undefined,
115
- role: shouldBeInteractive ? 'button' : undefined,
134
+ tabIndex: shouldBeInteractive ? tabIndex || 0 : undefined,
135
+ role: shouldBeInteractive ? 'button' : 'img',
116
136
  css: emojiContainer,
117
137
  className: classes,
118
138
  onKeyPress: event => handleKeyPress(props, event),
@@ -122,9 +142,12 @@ export const SpriteEmoji = props => {
122
142
  onMouseEnter: event => {
123
143
  handleMouseMove(props, event);
124
144
  },
145
+ onFocus: event => {
146
+ handleFocus(props, event);
147
+ },
125
148
  "aria-label": emoji.shortName,
126
149
  title: showTooltip ? emoji.shortName : ''
127
- }, jsx("span", {
150
+ }, other), jsx("span", {
128
151
  className: emojiSprite,
129
152
  style: style
130
153
  }, "\xA0"));
@@ -140,10 +163,17 @@ export const ImageEmoji = props => {
140
163
  className,
141
164
  showTooltip,
142
165
  showDelete,
143
- shouldBeInteractive,
166
+ shouldBeInteractive = false,
167
+ tabIndex,
168
+ onSelected,
169
+ onMouseMove,
170
+ onFocus,
171
+ onDelete,
172
+ onLoadError,
144
173
  onLoadSuccess,
145
174
  disableLazyLoad,
146
- autoWidth
175
+ autoWidth,
176
+ ...other
147
177
  } = props;
148
178
  const [ref, inView] = useInView({
149
179
  triggerOnce: true
@@ -233,12 +263,12 @@ export const ImageEmoji = props => {
233
263
  onError: onError,
234
264
  onLoad: onLoad
235
265
  }, sizing));
236
- return jsx("span", {
266
+ return jsx("span", _extends({
237
267
  "data-testid": `image-emoji-${emoji.shortName}`,
238
268
  "data-emoji-type": "image",
239
269
  css: emojiStyles,
240
- tabIndex: shouldBeInteractive ? 0 : undefined,
241
- role: shouldBeInteractive ? 'button' : undefined,
270
+ tabIndex: shouldBeInteractive ? tabIndex || 0 : undefined,
271
+ role: shouldBeInteractive ? 'button' : 'img',
242
272
  className: classes,
243
273
  onKeyPress: event => handleKeyPress(props, event),
244
274
  onMouseDown: event => {
@@ -247,10 +277,13 @@ export const ImageEmoji = props => {
247
277
  onMouseEnter: event => {
248
278
  handleMouseMove(props, event);
249
279
  },
280
+ onFocus: event => {
281
+ handleFocus(props, event);
282
+ },
250
283
  "aria-label": emoji.shortName,
251
284
  title: showTooltip ? emoji.shortName : '',
252
285
  ref: ref
253
- }, deleteButton, emojiNode);
286
+ }, other), deleteButton, emojiNode);
254
287
  };
255
288
  export const Emoji = props => {
256
289
  const {
@@ -1,19 +1,20 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
  /** @jsx jsx */
3
- import { Fragment, useState } from 'react';
3
+ import { Fragment, useState, useRef, useEffect, memo } from 'react';
4
4
  import { jsx } from '@emotion/react';
5
5
  import { FormattedMessage, injectIntl } from 'react-intl-next';
6
6
  import EmojiDeletePreview from '../common/EmojiDeletePreview';
7
7
  import EmojiUploadPicker from '../common/EmojiUploadPicker';
8
- import EmojiPickerListSearch from '../picker/EmojiPickerListSearch';
8
+ import { EmojiPickerListSearch } from '../picker/EmojiPickerListSearch';
9
9
  import ToneSelector from './ToneSelector';
10
- import EmojiButton from './EmojiButton';
10
+ import TonePreviewButton from './TonePreviewButton';
11
11
  import { messages } from '../i18n';
12
12
  import AkButton from '@atlaskit/button/standard-button';
13
13
  import AddIcon from '@atlaskit/icon/glyph/add';
14
14
  import { setSkinToneAriaLabelText } from './setSkinToneAriaLabelText';
15
15
  import { addCustomEmoji, addCustomEmojiButton, emojiActionsWrapper, emojiPickerAddEmoji, emojiToneSelectorContainer } from './styles';
16
16
  import { emojiActionsContainerWithBottomShadow, emojiPickerFooter } from '../picker/styles';
17
+ import { DEFAULT_TONE } from '../../util/constants';
17
18
  export const emojiActionsTestId = 'emoji-actions';
18
19
  export const uploadEmojiTestId = 'upload-emoji';
19
20
 
@@ -22,10 +23,7 @@ export const uploadEmojiTestId = 'upload-emoji';
22
23
  const AddOwnEmoji = props => {
23
24
  const {
24
25
  onOpenUpload,
25
- uploadEnabled,
26
- intl: {
27
- formatMessage
28
- }
26
+ uploadEnabled
29
27
  } = props;
30
28
  return jsx(Fragment, null, uploadEnabled && jsx("div", {
31
29
  css: addCustomEmoji,
@@ -33,47 +31,76 @@ const AddOwnEmoji = props => {
33
31
  }, jsx(FormattedMessage, messages.addCustomEmojiLabel, label => jsx(AkButton, {
34
32
  onClick: onOpenUpload,
35
33
  iconBefore: jsx(AddIcon, {
36
- label: formatMessage(messages.addCustomEmojiLabel),
34
+ label: "",
37
35
  size: "small"
38
36
  }),
39
37
  appearance: "subtle",
40
38
  css: addCustomEmojiButton,
41
- className: emojiPickerAddEmoji
39
+ className: emojiPickerAddEmoji,
40
+ tabIndex: 0,
41
+ id: "add-custom-emoji"
42
42
  }, label))));
43
43
  };
44
44
  const TonesWrapper = props => {
45
45
  const {
46
46
  toneEmoji,
47
- selectedTone,
47
+ selectedTone = DEFAULT_TONE,
48
48
  intl,
49
- onToneSelected,
50
49
  onToneOpen,
51
50
  showToneSelector
52
51
  } = props;
53
52
  const {
54
53
  formatMessage
55
54
  } = intl;
55
+ const tonePreviewButtonRef = useRef(null);
56
+ const [focusTonePreviewButton, setFocusTonePreviewButton] = useState(false);
57
+ useEffect(() => {
58
+ if (focusTonePreviewButton && tonePreviewButtonRef.current) {
59
+ tonePreviewButtonRef.current.focus();
60
+ }
61
+ return () => {
62
+ setFocusTonePreviewButton(false);
63
+ };
64
+ });
65
+ const onToneCloseHandler = () => {
66
+ const {
67
+ onToneClose
68
+ } = props;
69
+ onToneClose();
70
+ setFocusTonePreviewButton(true);
71
+ };
72
+ const onToneSelectedHandler = toneValue => {
73
+ const {
74
+ onToneSelected
75
+ } = props;
76
+ onToneSelected(toneValue);
77
+ setFocusTonePreviewButton(true);
78
+ };
56
79
  if (!toneEmoji) {
57
80
  return null;
58
81
  }
59
- let previewEmoji = toneEmoji;
60
- if (selectedTone && previewEmoji.skinVariations) {
61
- previewEmoji = previewEmoji.skinVariations[(selectedTone || 1) - 1];
82
+ let previewToneEmoji = toneEmoji;
83
+ if (selectedTone !== DEFAULT_TONE && previewToneEmoji.skinVariations) {
84
+ previewToneEmoji = previewToneEmoji.skinVariations[selectedTone - 1];
62
85
  }
63
86
  return jsx("div", {
64
87
  css: emojiToneSelectorContainer
65
- }, showToneSelector && jsx(ToneSelector, {
88
+ }, jsx(ToneSelector, {
66
89
  emoji: toneEmoji,
67
- onToneSelected: onToneSelected,
68
- previewEmojiId: previewEmoji.id
69
- }), jsx(EmojiButton, {
90
+ onToneSelected: onToneSelectedHandler,
91
+ onToneClose: onToneCloseHandler,
92
+ selectedTone: selectedTone,
93
+ isVisible: showToneSelector
94
+ }), jsx(TonePreviewButton, {
95
+ ref: tonePreviewButtonRef,
70
96
  ariaExpanded: showToneSelector,
71
- emoji: previewEmoji,
97
+ emoji: previewToneEmoji,
72
98
  selectOnHover: true,
73
99
  onSelected: onToneOpen,
74
100
  ariaLabelText: formatMessage(messages.emojiSelectSkinToneButtonAriaLabelText, {
75
- selectedTone: `${setSkinToneAriaLabelText(previewEmoji.name)} selected`
76
- })
101
+ selectedTone: `${setSkinToneAriaLabelText(previewToneEmoji.name)}`
102
+ }),
103
+ isVisible: !showToneSelector
77
104
  }));
78
105
  };
79
106
  export const EmojiActions = props => {
@@ -90,11 +117,13 @@ export const EmojiActions = props => {
90
117
  onFileChooserClicked,
91
118
  emojiToDelete,
92
119
  onChange,
93
- query
120
+ query,
121
+ resultsCount = 0
94
122
  } = props;
95
123
  const [showToneSelector, setShowToneSelector] = useState(false);
96
124
  const previewFooterClassnames = [emojiPickerFooter, emojiActionsContainerWithBottomShadow];
97
125
  const onToneOpenHandler = () => setShowToneSelector(true);
126
+ const onToneCloseHandler = () => setShowToneSelector(false);
98
127
  const onToneSelectedHandler = toneValue => {
99
128
  setShowToneSelector(false);
100
129
  if (onToneSelected) {
@@ -135,11 +164,13 @@ export const EmojiActions = props => {
135
164
  css: emojiActionsWrapper
136
165
  }, !showToneSelector && jsx(EmojiPickerListSearch, {
137
166
  onChange: onChange,
138
- query: query
167
+ query: query,
168
+ resultsCount: resultsCount
139
169
  }), jsx(TonesWrapper, _extends({}, props, {
140
170
  onToneOpen: onToneOpenHandler,
171
+ onToneClose: onToneCloseHandler,
141
172
  onToneSelected: onToneSelectedHandler,
142
173
  showToneSelector: showToneSelector
143
174
  }))), jsx(AddOwnEmoji, props));
144
175
  };
145
- export default injectIntl(EmojiActions);
176
+ export default injectIntl( /*#__PURE__*/memo(EmojiActions));
@@ -1,8 +1,9 @@
1
1
  /** @jsx jsx */
2
-
2
+ import { Fragment } from 'react';
3
3
  import { jsx } from '@emotion/react';
4
4
  import Tooltip from '@atlaskit/tooltip';
5
5
  import ErrorIcon from '@atlaskit/icon/glyph/error';
6
+ import VisuallyHidden from '@atlaskit/visually-hidden';
6
7
  export const emojiErrorMessageTestId = 'emoji-error-message';
7
8
  export const emojiErrorMessageTooltipTestId = 'emoji-error-message-tooltip';
8
9
  export const emojiErrorIconTestId = 'emoji-error-icon';
@@ -12,7 +13,7 @@ const EmojiErrorMessage = props => {
12
13
  message,
13
14
  tooltip
14
15
  } = props;
15
- return tooltip ? jsx("div", {
16
+ const visualContent = tooltip ? jsx("div", {
16
17
  css: messageStyles,
17
18
  "data-testid": emojiErrorMessageTestId
18
19
  }, jsx(Tooltip, {
@@ -29,6 +30,9 @@ const EmojiErrorMessage = props => {
29
30
  }, jsx(ErrorIcon, {
30
31
  label: "Error",
31
32
  size: "small"
32
- }), " ", message);
33
+ }), message);
34
+ return jsx(Fragment, null, jsx(VisuallyHidden, {
35
+ role: "alert"
36
+ }, message), visualContent);
33
37
  };
34
38
  export default EmojiErrorMessage;
@@ -32,6 +32,7 @@ const EmojiPlaceholder = props => {
32
32
  return jsx("span", {
33
33
  "data-testid": emojiPlaceholderTestId(shortName),
34
34
  "aria-busy": loading,
35
+ role: "status",
35
36
  "aria-label": shortName,
36
37
  className: placeholder,
37
38
  css: loading ? [placeholderContainer, placeholderContainerAnimated] : placeholderContainer,
@@ -0,0 +1,54 @@
1
+ /** @jsx jsx */
2
+ import React, { memo, forwardRef } from 'react';
3
+ import { jsx } from '@emotion/react';
4
+ import { leftClick } from '../../util/mouse';
5
+ import { emojiButton, emojiRadio } from './styles';
6
+ import Emoji from './Emoji';
7
+ import { TONESELECTOR_KEYBOARD_KEYS_SUPPORTED } from '../../util/constants';
8
+ import VisuallyHidden from '@atlaskit/visually-hidden';
9
+ const handleMouseDown = (props, event) => {
10
+ const {
11
+ onSelected
12
+ } = props;
13
+ event.preventDefault();
14
+ if (onSelected && leftClick(event)) {
15
+ onSelected();
16
+ }
17
+ };
18
+ const handleKeyPress = (props, event) => {
19
+ if (TONESELECTOR_KEYBOARD_KEYS_SUPPORTED.includes(event.key)) {
20
+ const {
21
+ onSelected
22
+ } = props;
23
+ event.preventDefault();
24
+ if (onSelected) {
25
+ onSelected();
26
+ }
27
+ }
28
+ };
29
+ export const EmojiRadioButton = /*#__PURE__*/forwardRef((props, ref) => {
30
+ const {
31
+ emoji,
32
+ selectOnHover,
33
+ ariaLabelText,
34
+ defaultChecked
35
+ } = props;
36
+ return jsx("label", {
37
+ css: emojiButton,
38
+ onMouseDown: event => handleMouseDown(props, event),
39
+ onKeyDown: event => handleKeyPress(props, event)
40
+ }, jsx(VisuallyHidden, null, ariaLabelText), jsx("input", {
41
+ ref: ref,
42
+ "data-testid": ariaLabelText,
43
+ type: "radio",
44
+ name: "skin-tone",
45
+ css: emojiRadio,
46
+ defaultChecked: defaultChecked
47
+ }), jsx(Emoji, {
48
+ emoji: emoji,
49
+ selectOnHover: selectOnHover,
50
+ shouldBeInteractive: false,
51
+ "aria-hidden": true
52
+ }));
53
+ });
54
+ export default /*#__PURE__*/memo(EmojiRadioButton);
@@ -1,10 +1,11 @@
1
1
  /** @jsx jsx */
2
- import React, { Fragment, useEffect, useState } from 'react';
2
+ import { Fragment, useEffect, useState, useRef, memo, useCallback, useMemo } from 'react';
3
3
  import { jsx } from '@emotion/react';
4
4
  import { FormattedMessage, injectIntl } from 'react-intl-next';
5
5
  import TextField from '@atlaskit/textfield';
6
6
  import CrossIcon from '@atlaskit/icon/glyph/cross';
7
7
  import AkButton from '@atlaskit/button/standard-button';
8
+ import FocusLock from 'react-focus-lock';
8
9
  import * as ImageUtil from '../../util/image';
9
10
  import debug from '../../util/logger';
10
11
  import { messages } from '../i18n';
@@ -14,6 +15,8 @@ import FileChooser from './FileChooser';
14
15
  import { UploadStatus } from './internal-types';
15
16
  import { closeEmojiUploadButton, emojiChooseFileErrorMessage, emojiUpload, emojiUploadBottom, emojiUploadTop, uploadChooseFileBrowse, uploadChooseFileEmojiName, uploadChooseFileMessage, uploadChooseFileRow } from './styles';
16
17
  export const uploadEmojiNameInputTestId = 'upload-emoji-name-input';
18
+ export const uploadEmojiComponentTestId = 'upload-emoji-component';
19
+ export const cancelEmojiUploadPickerTestId = 'cancel-emoji-upload-picker';
17
20
  const disallowedReplacementsMap = new Map([[':', ''], ['!', ''], ['@', ''], ['#', ''], ['%', ''], ['^', ''], ['&', ''], ['*', ''], ['(', ''], [')', ''], [' ', '_']]);
18
21
  const sanitizeName = name => {
19
22
  // prevent / replace certain characters, allow others
@@ -27,7 +30,7 @@ const toEmojiName = uploadName => {
27
30
  const name = uploadName.split('_').join(' ');
28
31
  return `${name.substr(0, 1).toLocaleUpperCase()}${name.substr(1)}`;
29
32
  };
30
- const ChooseEmojiFile = props => {
33
+ const ChooseEmojiFile = /*#__PURE__*/memo(props => {
31
34
  const {
32
35
  name = '',
33
36
  onChooseFile,
@@ -42,13 +45,31 @@ const ChooseEmojiFile = props => {
42
45
  } = intl;
43
46
  const disableChooser = !name;
44
47
  const fileChooserButtonDescriptionId = 'choose.emoji.file.button.screen.reader.description.id';
45
- const onKeyDownHandler = event => {
48
+ const inputRef = useRef(null);
49
+ const onKeyDownHandler = useCallback(event => {
46
50
  if (event.key === 'Escape') {
47
51
  onUploadCancelled();
48
52
  }
49
- };
53
+ }, [onUploadCancelled]);
54
+ const setInputFocus = useCallback(() => {
55
+ var _inputRef$current, _document$activeEleme, _inputRef$current2;
56
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
57
+ if (((_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 ? void 0 : _document$activeEleme.id) !== ((_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 ? void 0 : _inputRef$current2.id)) {
58
+ setInputFocus();
59
+ }
60
+ }, []);
61
+
62
+ // make sure input has focus after update
63
+ useEffect(() => {
64
+ window.requestAnimationFrame(setInputFocus);
65
+ }, [setInputFocus]);
66
+ const cancelLabel = formatMessage(messages.cancelLabel);
67
+ const emojiPlaceholder = formatMessage(messages.emojiPlaceholder);
68
+ const emojiNameAriaLabel = formatMessage(messages.emojiNameAriaLabel);
69
+ const emojiChooseFileTitle = formatMessage(messages.emojiChooseFileTitle);
50
70
  return jsx("div", {
51
- css: emojiUpload
71
+ css: emojiUpload,
72
+ "data-testid": uploadEmojiComponentTestId
52
73
  }, jsx("div", {
53
74
  css: emojiUploadTop
54
75
  }, jsx("span", {
@@ -57,34 +78,37 @@ const ChooseEmojiFile = props => {
57
78
  css: closeEmojiUploadButton
58
79
  }, jsx(AkButton, {
59
80
  onClick: onUploadCancelled,
60
- "aria-describedby": formatMessage(messages.cancelLabel),
81
+ "aria-label": cancelLabel,
61
82
  appearance: "subtle",
62
83
  spacing: "none",
63
- shouldFitContainer: true
84
+ shouldFitContainer: true,
85
+ testId: cancelEmojiUploadPickerTestId
64
86
  }, jsx(CrossIcon, {
65
87
  size: "small",
66
- label: formatMessage(messages.cancelLabel)
88
+ label: cancelLabel
67
89
  })))), jsx("div", {
68
90
  css: uploadChooseFileRow
69
91
  }, jsx("span", {
70
92
  css: uploadChooseFileEmojiName
71
93
  }, jsx(TextField, {
72
- placeholder: formatMessage(messages.emojiPlaceholder),
73
- "aria-label": formatMessage(messages.emojiNameAriaLabel),
94
+ placeholder: emojiPlaceholder,
95
+ "aria-label": emojiNameAriaLabel,
74
96
  maxLength: maxNameLength,
75
97
  onChange: onNameChange,
76
98
  onKeyDown: onKeyDownHandler,
77
99
  value: name,
78
100
  isCompact: true,
79
101
  autoFocus: true,
80
- testId: uploadEmojiNameInputTestId
102
+ testId: uploadEmojiNameInputTestId,
103
+ ref: inputRef,
104
+ id: "new-emoji-name-input"
81
105
  })), jsx("span", {
82
106
  css: uploadChooseFileBrowse
83
107
  }, jsx(FormattedMessage, messages.emojiChooseFileScreenReaderDescription, screenReaderDescription => jsx(Fragment, null, jsx("span", {
84
108
  hidden: true,
85
109
  id: fileChooserButtonDescriptionId
86
110
  }, screenReaderDescription), jsx(FileChooser, {
87
- label: formatMessage(messages.emojiChooseFileTitle),
111
+ label: emojiChooseFileTitle,
88
112
  onChange: onChooseFile,
89
113
  onClick: onClick,
90
114
  accept: "image/png,image/jpeg,image/gif",
@@ -96,8 +120,9 @@ const ChooseEmojiFile = props => {
96
120
  messageStyles: emojiChooseFileErrorMessage,
97
121
  message: errorMessage
98
122
  })));
99
- };
123
+ });
100
124
  const EmojiUploadPicker = props => {
125
+ var _document$activeEleme2;
101
126
  const {
102
127
  errorMessage,
103
128
  initialUploadName,
@@ -111,6 +136,8 @@ const EmojiUploadPicker = props => {
111
136
  const [name, setName] = useState(initialUploadName && sanitizeName(initialUploadName));
112
137
  const [filename, setFilename] = useState();
113
138
  const [previewImage, setPreviewImage] = useState();
139
+ // document is undefined during ssr rendering and throws an error
140
+ const lastFocusedElementId = useRef(typeof document !== 'undefined' ? (_document$activeEleme2 = document.activeElement) === null || _document$activeEleme2 === void 0 ? void 0 : _document$activeEleme2.id : '');
114
141
  useEffect(() => {
115
142
  if (errorMessage) {
116
143
  setUploadStatus(UploadStatus.Error);
@@ -126,13 +153,18 @@ const EmojiUploadPicker = props => {
126
153
  setName(sanitizeName(initialUploadName));
127
154
  }
128
155
  }, [initialUploadName]);
129
- const onNameChange = event => {
156
+ const clearUploadPicker = useCallback(() => {
157
+ setName(undefined);
158
+ setPreviewImage(undefined);
159
+ setUploadStatus(UploadStatus.Waiting);
160
+ }, []);
161
+ const onNameChange = useCallback(event => {
130
162
  let newName = sanitizeName(event.target.value);
131
163
  if (name !== newName) {
132
164
  setName(newName);
133
165
  }
134
- };
135
- const onAddEmoji = () => {
166
+ }, [name]);
167
+ const onAddEmoji = useCallback(() => {
136
168
  if (uploadStatus === UploadStatus.Uploading) {
137
169
  return;
138
170
  }
@@ -164,13 +196,16 @@ const EmojiUploadPicker = props => {
164
196
  });
165
197
  });
166
198
  }
167
- };
168
- const errorOnUpload = event => {
199
+ }, [clearUploadPicker, filename, name, onUploadEmoji, previewImage, uploadStatus]);
200
+ const cancelChooseFile = useCallback(() => {
201
+ setPreviewImage(undefined);
202
+ }, []);
203
+ const errorOnUpload = useCallback(event => {
169
204
  debug('File load error: ', event);
170
205
  setChooseEmojiErrorMessage(messages.emojiUploadFailed);
171
206
  cancelChooseFile();
172
- };
173
- const onFileLoad = file => async f => {
207
+ }, [cancelChooseFile]);
208
+ const onFileLoad = useCallback(file => async f => {
174
209
  try {
175
210
  setFilename(file.name);
176
211
  await ImageUtil.parseImage(f.target.result);
@@ -179,11 +214,8 @@ const EmojiUploadPicker = props => {
179
214
  setChooseEmojiErrorMessage(messages.emojiInvalidImage);
180
215
  cancelChooseFile();
181
216
  }
182
- };
183
- const cancelChooseFile = () => {
184
- setPreviewImage(undefined);
185
- };
186
- const onChooseFile = event => {
217
+ }, [cancelChooseFile]);
218
+ const onChooseFile = useCallback(event => {
187
219
  const files = event.target.files;
188
220
  if (files.length) {
189
221
  const reader = new FileReader();
@@ -200,17 +232,23 @@ const EmojiUploadPicker = props => {
200
232
  } else {
201
233
  cancelChooseFile();
202
234
  }
203
- };
204
- const clearUploadPicker = () => {
205
- setName(undefined);
206
- setPreviewImage(undefined);
207
- setUploadStatus(UploadStatus.Waiting);
208
- };
209
- const cancelUpload = () => {
235
+ }, [cancelChooseFile, errorOnUpload, onFileLoad]);
236
+ const cancelUpload = useCallback(() => {
210
237
  clearUploadPicker();
211
238
  onUploadCancelled();
212
- };
213
- return jsx(React.Fragment, null, name && previewImage ? jsx(EmojiUploadPreview, {
239
+
240
+ // using setTimeout here to allow the UI to update before setting focus
241
+ setTimeout(lastFocus => {
242
+ if (lastFocus) {
243
+ var _document$getElementB;
244
+ (_document$getElementB = document.getElementById(lastFocus)) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.focus();
245
+ }
246
+ }, 0, lastFocusedElementId.current);
247
+ }, [clearUploadPicker, onUploadCancelled]);
248
+ const chooseErrorMessage = useMemo(() => chooseEmojiErrorMessage ? jsx(FormattedMessage, chooseEmojiErrorMessage) : undefined, [chooseEmojiErrorMessage]);
249
+ return jsx(FocusLock, {
250
+ noFocusGuards: true
251
+ }, name && previewImage ? jsx(EmojiUploadPreview, {
214
252
  errorMessage: errorMessage,
215
253
  name: name,
216
254
  onAddEmoji: onAddEmoji,
@@ -223,8 +261,9 @@ const EmojiUploadPicker = props => {
223
261
  onClick: onFileChooserClicked,
224
262
  onNameChange: onNameChange,
225
263
  onUploadCancelled: cancelUpload,
226
- errorMessage: chooseEmojiErrorMessage ? jsx(FormattedMessage, chooseEmojiErrorMessage) : undefined,
264
+ errorMessage: chooseErrorMessage,
227
265
  intl: intl
228
266
  }));
229
267
  };
230
- export default injectIntl(EmojiUploadPicker);
268
+ const EmojiUploadPickerComponent = injectIntl( /*#__PURE__*/memo(EmojiUploadPicker));
269
+ export default EmojiUploadPickerComponent;
@@ -11,8 +11,10 @@ import EmojiErrorMessage from './EmojiErrorMessage';
11
11
  import { UploadStatus } from './internal-types';
12
12
  import RetryableButton from './RetryableButton';
13
13
  import { bigEmojiPreview, cancelButton, emojiPreviewErrorMessage, uploadAddRow, uploadPreview, uploadPreviewFooter, uploadPreviewText } from './styles';
14
+ import VisuallyHidden from '@atlaskit/visually-hidden';
14
15
  export const uploadPreviewTestId = 'upload-preview';
15
16
  export const cancelUploadButtonTestId = 'cancel-upload-button';
17
+ const addEmojiButtonDescriptionId = 'add.emoji.button.screen.reader.description.id';
16
18
  class EmojiUploadPreview extends PureComponent {
17
19
  render() {
18
20
  const {
@@ -64,12 +66,19 @@ class EmojiUploadPreview extends PureComponent {
64
66
  messageStyles: emojiPreviewErrorMessage,
65
67
  message: errorMessage,
66
68
  tooltip: true
67
- }) : null, jsx(RetryableButton, {
69
+ }) : null, !errorMessage && jsx(VisuallyHidden, {
70
+ id: addEmojiButtonDescriptionId
71
+ }, jsx(FormattedMessage, _extends({}, messages.emojiPreview, {
72
+ values: {
73
+ emoji: name
74
+ }
75
+ }))), jsx(RetryableButton, {
68
76
  label: formatMessage(messages.addEmojiLabel),
69
77
  onSubmit: onAddEmoji,
70
78
  appearance: "primary",
71
79
  loading: uploading,
72
- error: !!errorMessage
80
+ error: !!errorMessage,
81
+ ariaDescribedby: addEmojiButtonDescriptionId
73
82
  }), jsx(AkButton, {
74
83
  onClick: onUploadCancelled,
75
84
  appearance: "subtle",
@@ -1,5 +1,5 @@
1
1
  import React, { useRef } from 'react';
2
- import AkButton from '@atlaskit/button/custom-theme-button';
2
+ import AkButton from '@atlaskit/button/standard-button';
3
3
  export const chooseFileButtonTestId = 'choose-file-button';
4
4
  export const fileUploadInputTestId = 'file-upload';
5
5
  const FileChooser = props => {