@atlaskit/emoji 67.1.0 → 67.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/cjs/api/EmojiResource.js +29 -24
  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 +60 -24
  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/ResourcedEmoji.js +5 -3
  14. package/dist/cjs/components/common/ResourcedEmojiComponent.js +4 -0
  15. package/dist/cjs/components/common/RetryableButton.js +7 -3
  16. package/dist/cjs/components/common/TonePreviewButton.js +44 -0
  17. package/dist/cjs/components/common/ToneSelector.js +53 -25
  18. package/dist/cjs/components/common/styles.js +45 -16
  19. package/dist/cjs/components/i18n.js +44 -4
  20. package/dist/cjs/components/picker/CategorySelector.js +112 -90
  21. package/dist/cjs/components/picker/CategoryTracker.js +0 -28
  22. package/dist/cjs/components/picker/EmojiPickerCategoryHeading.js +2 -1
  23. package/dist/cjs/components/picker/EmojiPickerComponent.js +13 -7
  24. package/dist/cjs/components/picker/EmojiPickerEmojiRow.js +32 -4
  25. package/dist/cjs/components/picker/EmojiPickerList.js +140 -51
  26. package/dist/cjs/components/picker/EmojiPickerListSearch.js +16 -3
  27. package/dist/cjs/components/picker/EmojiPickerVirtualItems.js +5 -2
  28. package/dist/cjs/components/picker/VirtualList.js +196 -14
  29. package/dist/cjs/components/picker/styles.js +43 -51
  30. package/dist/cjs/context/EmojiPickerListContext.js +33 -0
  31. package/dist/cjs/hooks/useEmojiPickerListContext.js +12 -0
  32. package/dist/cjs/util/constants.js +40 -1
  33. package/dist/cjs/util/shared-styles.js +3 -4
  34. package/dist/cjs/version.json +1 -1
  35. package/dist/es2019/api/EmojiResource.js +29 -24
  36. package/dist/es2019/api/media/TokenManager.js +4 -4
  37. package/dist/es2019/components/common/CachingEmoji.js +10 -3
  38. package/dist/es2019/components/common/Emoji.js +44 -11
  39. package/dist/es2019/components/common/EmojiActions.js +54 -23
  40. package/dist/es2019/components/common/EmojiErrorMessage.js +7 -3
  41. package/dist/es2019/components/common/EmojiPlaceholder.js +1 -0
  42. package/dist/es2019/components/common/EmojiRadioButton.js +54 -0
  43. package/dist/es2019/components/common/EmojiUploadPicker.js +75 -36
  44. package/dist/es2019/components/common/EmojiUploadPreview.js +11 -2
  45. package/dist/es2019/components/common/FileChooser.js +1 -1
  46. package/dist/es2019/components/common/ResourcedEmoji.js +5 -3
  47. package/dist/es2019/components/common/ResourcedEmojiComponent.js +4 -0
  48. package/dist/es2019/components/common/RetryableButton.js +7 -3
  49. package/dist/es2019/components/common/TonePreviewButton.js +34 -0
  50. package/dist/es2019/components/common/ToneSelector.js +55 -21
  51. package/dist/es2019/components/common/styles.js +41 -10
  52. package/dist/es2019/components/i18n.js +44 -4
  53. package/dist/es2019/components/picker/CategorySelector.js +114 -89
  54. package/dist/es2019/components/picker/CategoryTracker.js +0 -24
  55. package/dist/es2019/components/picker/EmojiPickerCategoryHeading.js +2 -2
  56. package/dist/es2019/components/picker/EmojiPickerComponent.js +14 -7
  57. package/dist/es2019/components/picker/EmojiPickerEmojiRow.js +51 -21
  58. package/dist/es2019/components/picker/EmojiPickerList.js +102 -21
  59. package/dist/es2019/components/picker/EmojiPickerListSearch.js +14 -4
  60. package/dist/es2019/components/picker/EmojiPickerVirtualItems.js +4 -1
  61. package/dist/es2019/components/picker/VirtualList.js +193 -12
  62. package/dist/es2019/components/picker/styles.js +20 -28
  63. package/dist/es2019/context/EmojiPickerListContext.js +17 -0
  64. package/dist/es2019/hooks/useEmojiPickerListContext.js +3 -0
  65. package/dist/es2019/util/constants.js +31 -0
  66. package/dist/es2019/util/shared-styles.js +1 -2
  67. package/dist/es2019/version.json +1 -1
  68. package/dist/esm/api/EmojiResource.js +29 -24
  69. package/dist/esm/api/media/TokenManager.js +4 -4
  70. package/dist/esm/components/common/CachingEmoji.js +14 -6
  71. package/dist/esm/components/common/Emoji.js +48 -12
  72. package/dist/esm/components/common/EmojiActions.js +61 -25
  73. package/dist/esm/components/common/EmojiErrorMessage.js +7 -3
  74. package/dist/esm/components/common/EmojiPlaceholder.js +1 -0
  75. package/dist/esm/components/common/EmojiRadioButton.js +52 -0
  76. package/dist/esm/components/common/EmojiUploadPicker.js +77 -36
  77. package/dist/esm/components/common/EmojiUploadPreview.js +11 -2
  78. package/dist/esm/components/common/FileChooser.js +1 -1
  79. package/dist/esm/components/common/ResourcedEmoji.js +5 -3
  80. package/dist/esm/components/common/ResourcedEmojiComponent.js +4 -0
  81. package/dist/esm/components/common/RetryableButton.js +7 -3
  82. package/dist/esm/components/common/TonePreviewButton.js +33 -0
  83. package/dist/esm/components/common/ToneSelector.js +49 -18
  84. package/dist/esm/components/common/styles.js +40 -12
  85. package/dist/esm/components/i18n.js +44 -4
  86. package/dist/esm/components/picker/CategorySelector.js +114 -95
  87. package/dist/esm/components/picker/CategoryTracker.js +0 -28
  88. package/dist/esm/components/picker/EmojiPickerCategoryHeading.js +2 -2
  89. package/dist/esm/components/picker/EmojiPickerComponent.js +13 -7
  90. package/dist/esm/components/picker/EmojiPickerEmojiRow.js +32 -4
  91. package/dist/esm/components/picker/EmojiPickerList.js +141 -52
  92. package/dist/esm/components/picker/EmojiPickerListSearch.js +17 -4
  93. package/dist/esm/components/picker/EmojiPickerVirtualItems.js +5 -2
  94. package/dist/esm/components/picker/VirtualList.js +195 -12
  95. package/dist/esm/components/picker/styles.js +37 -45
  96. package/dist/esm/context/EmojiPickerListContext.js +21 -0
  97. package/dist/esm/hooks/useEmojiPickerListContext.js +5 -0
  98. package/dist/esm/util/constants.js +31 -0
  99. package/dist/esm/util/shared-styles.js +1 -2
  100. package/dist/esm/version.json +1 -1
  101. package/dist/types/api/EmojiResource.d.ts +2 -0
  102. package/dist/types/components/common/Emoji.d.ts +7 -1
  103. package/dist/types/components/common/EmojiActions.d.ts +3 -2
  104. package/dist/types/components/common/{EmojiButton.d.ts → EmojiRadioButton.d.ts} +3 -4
  105. package/dist/types/components/common/EmojiUploadPicker.d.ts +6 -4
  106. package/dist/types/components/common/RetryableButton.d.ts +1 -0
  107. package/dist/types/components/common/TonePreviewButton.d.ts +14 -0
  108. package/dist/types/components/common/ToneSelector.d.ts +8 -5
  109. package/dist/types/components/common/internal-types.d.ts +9 -0
  110. package/dist/types/components/common/styles.d.ts +2 -1
  111. package/dist/types/components/i18n.d.ts +40 -0
  112. package/dist/types/components/picker/CategorySelector.d.ts +3 -10
  113. package/dist/types/components/picker/CategoryTracker.d.ts +0 -2
  114. package/dist/types/components/picker/EmojiPickerCategoryHeading.d.ts +2 -1
  115. package/dist/types/components/picker/EmojiPickerEmojiRow.d.ts +5 -0
  116. package/dist/types/components/picker/EmojiPickerList.d.ts +10 -5
  117. package/dist/types/components/picker/EmojiPickerListSearch.d.ts +1 -0
  118. package/dist/types/components/picker/EmojiPickerVirtualItems.d.ts +1 -1
  119. package/dist/types/components/picker/VirtualList.d.ts +2 -0
  120. package/dist/types/components/picker/styles.d.ts +1 -1
  121. package/dist/types/context/EmojiPickerListContext.d.ts +10 -0
  122. package/dist/types/hooks/useEmojiPickerListContext.d.ts +1 -0
  123. package/dist/types/util/constants.d.ts +27 -0
  124. package/dist/types/util/shared-styles.d.ts +1 -1
  125. package/dist/types/util/type-helpers.d.ts +1 -1
  126. package/package.json +9 -6
  127. package/report.api.md +52 -1
  128. package/README.md +0 -3
  129. package/dist/es2019/components/common/EmojiButton.js +0 -49
  130. package/dist/esm/components/common/EmojiButton.js +0 -43
  131. /package/dist/cjs/{components/hooks.js → hooks/useIsMounted.js} +0 -0
  132. /package/dist/es2019/{components/hooks.js → hooks/useIsMounted.js} +0 -0
  133. /package/dist/esm/{components/hooks.js → hooks/useIsMounted.js} +0 -0
  134. /package/dist/types/{components/hooks.d.ts → hooks/useIsMounted.d.ts} +0 -0
@@ -1,20 +1,21 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  /** @jsx jsx */
4
- import { Fragment, useState } from 'react';
4
+ import { Fragment, useState, useRef, useEffect, memo } from 'react';
5
5
  import { jsx } from '@emotion/react';
6
6
  import { FormattedMessage, injectIntl } from 'react-intl-next';
7
7
  import EmojiDeletePreview from '../common/EmojiDeletePreview';
8
8
  import EmojiUploadPicker from '../common/EmojiUploadPicker';
9
9
  import { EmojiPickerListSearch } from '../picker/EmojiPickerListSearch';
10
10
  import ToneSelector from './ToneSelector';
11
- import EmojiButton from './EmojiButton';
11
+ import TonePreviewButton from './TonePreviewButton';
12
12
  import { messages } from '../i18n';
13
13
  import AkButton from '@atlaskit/button/standard-button';
14
14
  import AddIcon from '@atlaskit/icon/glyph/add';
15
15
  import { setSkinToneAriaLabelText } from './setSkinToneAriaLabelText';
16
16
  import { addCustomEmoji, addCustomEmojiButton, emojiActionsWrapper, emojiPickerAddEmoji, emojiToneSelectorContainer } from './styles';
17
17
  import { emojiActionsContainerWithBottomShadow, emojiPickerFooter } from '../picker/styles';
18
+ import { DEFAULT_TONE } from '../../util/constants';
18
19
  export var emojiActionsTestId = 'emoji-actions';
19
20
  export var uploadEmojiTestId = 'upload-emoji';
20
21
 
@@ -22,8 +23,7 @@ export var uploadEmojiTestId = 'upload-emoji';
22
23
 
23
24
  var AddOwnEmoji = function AddOwnEmoji(props) {
24
25
  var onOpenUpload = props.onOpenUpload,
25
- uploadEnabled = props.uploadEnabled,
26
- formatMessage = props.intl.formatMessage;
26
+ uploadEnabled = props.uploadEnabled;
27
27
  return jsx(Fragment, null, uploadEnabled && jsx("div", {
28
28
  css: addCustomEmoji,
29
29
  "data-testid": uploadEmojiTestId
@@ -31,44 +31,73 @@ var AddOwnEmoji = function AddOwnEmoji(props) {
31
31
  return jsx(AkButton, {
32
32
  onClick: onOpenUpload,
33
33
  iconBefore: jsx(AddIcon, {
34
- label: formatMessage(messages.addCustomEmojiLabel),
34
+ label: "",
35
35
  size: "small"
36
36
  }),
37
37
  appearance: "subtle",
38
38
  css: addCustomEmojiButton,
39
- className: emojiPickerAddEmoji
39
+ className: emojiPickerAddEmoji,
40
+ tabIndex: 0,
41
+ id: "add-custom-emoji"
40
42
  }, label);
41
43
  })));
42
44
  };
43
45
  var TonesWrapper = function TonesWrapper(props) {
44
46
  var toneEmoji = props.toneEmoji,
45
- selectedTone = props.selectedTone,
47
+ _props$selectedTone = props.selectedTone,
48
+ selectedTone = _props$selectedTone === void 0 ? DEFAULT_TONE : _props$selectedTone,
46
49
  intl = props.intl,
47
- onToneSelected = props.onToneSelected,
48
50
  onToneOpen = props.onToneOpen,
49
51
  showToneSelector = props.showToneSelector;
50
52
  var formatMessage = intl.formatMessage;
53
+ var tonePreviewButtonRef = useRef(null);
54
+ var _useState = useState(false),
55
+ _useState2 = _slicedToArray(_useState, 2),
56
+ focusTonePreviewButton = _useState2[0],
57
+ setFocusTonePreviewButton = _useState2[1];
58
+ useEffect(function () {
59
+ if (focusTonePreviewButton && tonePreviewButtonRef.current) {
60
+ tonePreviewButtonRef.current.focus();
61
+ }
62
+ return function () {
63
+ setFocusTonePreviewButton(false);
64
+ };
65
+ });
66
+ var onToneCloseHandler = function onToneCloseHandler() {
67
+ var onToneClose = props.onToneClose;
68
+ onToneClose();
69
+ setFocusTonePreviewButton(true);
70
+ };
71
+ var onToneSelectedHandler = function onToneSelectedHandler(toneValue) {
72
+ var onToneSelected = props.onToneSelected;
73
+ onToneSelected(toneValue);
74
+ setFocusTonePreviewButton(true);
75
+ };
51
76
  if (!toneEmoji) {
52
77
  return null;
53
78
  }
54
- var previewEmoji = toneEmoji;
55
- if (selectedTone && previewEmoji.skinVariations) {
56
- previewEmoji = previewEmoji.skinVariations[(selectedTone || 1) - 1];
79
+ var previewToneEmoji = toneEmoji;
80
+ if (selectedTone !== DEFAULT_TONE && previewToneEmoji.skinVariations) {
81
+ previewToneEmoji = previewToneEmoji.skinVariations[selectedTone - 1];
57
82
  }
58
83
  return jsx("div", {
59
84
  css: emojiToneSelectorContainer
60
- }, showToneSelector && jsx(ToneSelector, {
85
+ }, jsx(ToneSelector, {
61
86
  emoji: toneEmoji,
62
- onToneSelected: onToneSelected,
63
- previewEmojiId: previewEmoji.id
64
- }), jsx(EmojiButton, {
87
+ onToneSelected: onToneSelectedHandler,
88
+ onToneClose: onToneCloseHandler,
89
+ selectedTone: selectedTone,
90
+ isVisible: showToneSelector
91
+ }), jsx(TonePreviewButton, {
92
+ ref: tonePreviewButtonRef,
65
93
  ariaExpanded: showToneSelector,
66
- emoji: previewEmoji,
94
+ emoji: previewToneEmoji,
67
95
  selectOnHover: true,
68
96
  onSelected: onToneOpen,
69
97
  ariaLabelText: formatMessage(messages.emojiSelectSkinToneButtonAriaLabelText, {
70
- selectedTone: "".concat(setSkinToneAriaLabelText(previewEmoji.name), " selected")
71
- })
98
+ selectedTone: "".concat(setSkinToneAriaLabelText(previewToneEmoji.name))
99
+ }),
100
+ isVisible: !showToneSelector
72
101
  }));
73
102
  };
74
103
  export var EmojiActions = function EmojiActions(props) {
@@ -84,15 +113,20 @@ export var EmojiActions = function EmojiActions(props) {
84
113
  onFileChooserClicked = props.onFileChooserClicked,
85
114
  emojiToDelete = props.emojiToDelete,
86
115
  onChange = props.onChange,
87
- query = props.query;
88
- var _useState = useState(false),
89
- _useState2 = _slicedToArray(_useState, 2),
90
- showToneSelector = _useState2[0],
91
- setShowToneSelector = _useState2[1];
116
+ query = props.query,
117
+ _props$resultsCount = props.resultsCount,
118
+ resultsCount = _props$resultsCount === void 0 ? 0 : _props$resultsCount;
119
+ var _useState3 = useState(false),
120
+ _useState4 = _slicedToArray(_useState3, 2),
121
+ showToneSelector = _useState4[0],
122
+ setShowToneSelector = _useState4[1];
92
123
  var previewFooterClassnames = [emojiPickerFooter, emojiActionsContainerWithBottomShadow];
93
124
  var onToneOpenHandler = function onToneOpenHandler() {
94
125
  return setShowToneSelector(true);
95
126
  };
127
+ var onToneCloseHandler = function onToneCloseHandler() {
128
+ return setShowToneSelector(false);
129
+ };
96
130
  var onToneSelectedHandler = function onToneSelectedHandler(toneValue) {
97
131
  setShowToneSelector(false);
98
132
  if (onToneSelected) {
@@ -133,11 +167,13 @@ export var EmojiActions = function EmojiActions(props) {
133
167
  css: emojiActionsWrapper
134
168
  }, !showToneSelector && jsx(EmojiPickerListSearch, {
135
169
  onChange: onChange,
136
- query: query
170
+ query: query,
171
+ resultsCount: resultsCount
137
172
  }), jsx(TonesWrapper, _extends({}, props, {
138
173
  onToneOpen: onToneOpenHandler,
174
+ onToneClose: onToneCloseHandler,
139
175
  onToneSelected: onToneSelectedHandler,
140
176
  showToneSelector: showToneSelector
141
177
  }))), jsx(AddOwnEmoji, props));
142
178
  };
143
- export default injectIntl(EmojiActions);
179
+ 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 var emojiErrorMessageTestId = 'emoji-error-message';
7
8
  export var emojiErrorMessageTooltipTestId = 'emoji-error-message-tooltip';
8
9
  export var emojiErrorIconTestId = 'emoji-error-icon';
@@ -10,7 +11,7 @@ var EmojiErrorMessage = function EmojiErrorMessage(props) {
10
11
  var messageStyles = props.messageStyles,
11
12
  message = props.message,
12
13
  tooltip = props.tooltip;
13
- return tooltip ? jsx("div", {
14
+ var visualContent = tooltip ? jsx("div", {
14
15
  css: messageStyles,
15
16
  "data-testid": emojiErrorMessageTestId
16
17
  }, jsx(Tooltip, {
@@ -27,6 +28,9 @@ var EmojiErrorMessage = function EmojiErrorMessage(props) {
27
28
  }, jsx(ErrorIcon, {
28
29
  label: "Error",
29
30
  size: "small"
30
- }), " ", message);
31
+ }), message);
32
+ return jsx(Fragment, null, jsx(VisuallyHidden, {
33
+ role: "alert"
34
+ }, message), visualContent);
31
35
  };
32
36
  export default EmojiErrorMessage;
@@ -34,6 +34,7 @@ var EmojiPlaceholder = function EmojiPlaceholder(props) {
34
34
  return jsx("span", {
35
35
  "data-testid": emojiPlaceholderTestId(shortName),
36
36
  "aria-busy": loading,
37
+ role: "status",
37
38
  "aria-label": shortName,
38
39
  className: placeholder,
39
40
  css: loading ? [placeholderContainer, placeholderContainerAnimated] : placeholderContainer,
@@ -0,0 +1,52 @@
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
+ var handleMouseDown = function handleMouseDown(props, event) {
10
+ var onSelected = props.onSelected;
11
+ event.preventDefault();
12
+ if (onSelected && leftClick(event)) {
13
+ onSelected();
14
+ }
15
+ };
16
+ var handleKeyPress = function handleKeyPress(props, event) {
17
+ if (TONESELECTOR_KEYBOARD_KEYS_SUPPORTED.includes(event.key)) {
18
+ var onSelected = props.onSelected;
19
+ event.preventDefault();
20
+ if (onSelected) {
21
+ onSelected();
22
+ }
23
+ }
24
+ };
25
+ export var EmojiRadioButton = /*#__PURE__*/forwardRef(function (props, ref) {
26
+ var emoji = props.emoji,
27
+ selectOnHover = props.selectOnHover,
28
+ ariaLabelText = props.ariaLabelText,
29
+ defaultChecked = props.defaultChecked;
30
+ return jsx("label", {
31
+ css: emojiButton,
32
+ onMouseDown: function onMouseDown(event) {
33
+ return handleMouseDown(props, event);
34
+ },
35
+ onKeyDown: function onKeyDown(event) {
36
+ return handleKeyPress(props, event);
37
+ }
38
+ }, jsx(VisuallyHidden, null, ariaLabelText), jsx("input", {
39
+ ref: ref,
40
+ "data-testid": ariaLabelText,
41
+ type: "radio",
42
+ name: "skin-tone",
43
+ css: emojiRadio,
44
+ defaultChecked: defaultChecked
45
+ }), jsx(Emoji, {
46
+ emoji: emoji,
47
+ selectOnHover: selectOnHover,
48
+ shouldBeInteractive: false,
49
+ "aria-hidden": true
50
+ }));
51
+ });
52
+ export default /*#__PURE__*/memo(EmojiRadioButton);
@@ -2,12 +2,13 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
3
3
  import _regeneratorRuntime from "@babel/runtime/regenerator";
4
4
  /** @jsx jsx */
5
- import React, { Fragment, useEffect, useState } from 'react';
5
+ import { Fragment, useEffect, useState, useRef, memo, useCallback, useMemo } from 'react';
6
6
  import { jsx } from '@emotion/react';
7
7
  import { FormattedMessage, injectIntl } from 'react-intl-next';
8
8
  import TextField from '@atlaskit/textfield';
9
9
  import CrossIcon from '@atlaskit/icon/glyph/cross';
10
10
  import AkButton from '@atlaskit/button/standard-button';
11
+ import FocusLock from 'react-focus-lock';
11
12
  import * as ImageUtil from '../../util/image';
12
13
  import debug from '../../util/logger';
13
14
  import { messages } from '../i18n';
@@ -17,6 +18,8 @@ import FileChooser from './FileChooser';
17
18
  import { UploadStatus } from './internal-types';
18
19
  import { closeEmojiUploadButton, emojiChooseFileErrorMessage, emojiUpload, emojiUploadBottom, emojiUploadTop, uploadChooseFileBrowse, uploadChooseFileEmojiName, uploadChooseFileMessage, uploadChooseFileRow } from './styles';
19
20
  export var uploadEmojiNameInputTestId = 'upload-emoji-name-input';
21
+ export var uploadEmojiComponentTestId = 'upload-emoji-component';
22
+ export var cancelEmojiUploadPickerTestId = 'cancel-emoji-upload-picker';
20
23
  var disallowedReplacementsMap = new Map([[':', ''], ['!', ''], ['@', ''], ['#', ''], ['%', ''], ['^', ''], ['&', ''], ['*', ''], ['(', ''], [')', ''], [' ', '_']]);
21
24
  var sanitizeName = function sanitizeName(name) {
22
25
  // prevent / replace certain characters, allow others
@@ -30,7 +33,7 @@ var toEmojiName = function toEmojiName(uploadName) {
30
33
  var name = uploadName.split('_').join(' ');
31
34
  return "".concat(name.substr(0, 1).toLocaleUpperCase()).concat(name.substr(1));
32
35
  };
33
- var ChooseEmojiFile = function ChooseEmojiFile(props) {
36
+ var ChooseEmojiFile = /*#__PURE__*/memo(function (props) {
34
37
  var _props$name = props.name,
35
38
  name = _props$name === void 0 ? '' : _props$name,
36
39
  onChooseFile = props.onChooseFile,
@@ -42,13 +45,31 @@ var ChooseEmojiFile = function ChooseEmojiFile(props) {
42
45
  var formatMessage = intl.formatMessage;
43
46
  var disableChooser = !name;
44
47
  var fileChooserButtonDescriptionId = 'choose.emoji.file.button.screen.reader.description.id';
45
- var onKeyDownHandler = function onKeyDownHandler(event) {
48
+ var inputRef = useRef(null);
49
+ var onKeyDownHandler = useCallback(function (event) {
46
50
  if (event.key === 'Escape') {
47
51
  onUploadCancelled();
48
52
  }
49
- };
53
+ }, [onUploadCancelled]);
54
+ var setInputFocus = useCallback(function () {
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(function () {
64
+ window.requestAnimationFrame(setInputFocus);
65
+ }, [setInputFocus]);
66
+ var cancelLabel = formatMessage(messages.cancelLabel);
67
+ var emojiPlaceholder = formatMessage(messages.emojiPlaceholder);
68
+ var emojiNameAriaLabel = formatMessage(messages.emojiNameAriaLabel);
69
+ var 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", {
@@ -59,27 +80,30 @@ var ChooseEmojiFile = function ChooseEmojiFile(props) {
59
80
  css: closeEmojiUploadButton
60
81
  }, jsx(AkButton, {
61
82
  onClick: onUploadCancelled,
62
- "aria-describedby": formatMessage(messages.cancelLabel),
83
+ "aria-label": cancelLabel,
63
84
  appearance: "subtle",
64
85
  spacing: "none",
65
- shouldFitContainer: true
86
+ shouldFitContainer: true,
87
+ testId: cancelEmojiUploadPickerTestId
66
88
  }, jsx(CrossIcon, {
67
89
  size: "small",
68
- label: formatMessage(messages.cancelLabel)
90
+ label: cancelLabel
69
91
  })))), jsx("div", {
70
92
  css: uploadChooseFileRow
71
93
  }, jsx("span", {
72
94
  css: uploadChooseFileEmojiName
73
95
  }, jsx(TextField, {
74
- placeholder: formatMessage(messages.emojiPlaceholder),
75
- "aria-label": formatMessage(messages.emojiNameAriaLabel),
96
+ placeholder: emojiPlaceholder,
97
+ "aria-label": emojiNameAriaLabel,
76
98
  maxLength: maxNameLength,
77
99
  onChange: onNameChange,
78
100
  onKeyDown: onKeyDownHandler,
79
101
  value: name,
80
102
  isCompact: true,
81
103
  autoFocus: true,
82
- testId: uploadEmojiNameInputTestId
104
+ testId: uploadEmojiNameInputTestId,
105
+ ref: inputRef,
106
+ id: "new-emoji-name-input"
83
107
  })), jsx("span", {
84
108
  css: uploadChooseFileBrowse
85
109
  }, jsx(FormattedMessage, messages.emojiChooseFileScreenReaderDescription, function (screenReaderDescription) {
@@ -87,7 +111,7 @@ var ChooseEmojiFile = function ChooseEmojiFile(props) {
87
111
  hidden: true,
88
112
  id: fileChooserButtonDescriptionId
89
113
  }, screenReaderDescription), jsx(FileChooser, {
90
- label: formatMessage(messages.emojiChooseFileTitle),
114
+ label: emojiChooseFileTitle,
91
115
  onChange: onChooseFile,
92
116
  onClick: onClick,
93
117
  accept: "image/png,image/jpeg,image/gif",
@@ -100,8 +124,9 @@ var ChooseEmojiFile = function ChooseEmojiFile(props) {
100
124
  messageStyles: emojiChooseFileErrorMessage,
101
125
  message: errorMessage
102
126
  })));
103
- };
127
+ });
104
128
  var EmojiUploadPicker = function EmojiUploadPicker(props) {
129
+ var _document$activeEleme2;
105
130
  var errorMessage = props.errorMessage,
106
131
  initialUploadName = props.initialUploadName,
107
132
  onUploadEmoji = props.onUploadEmoji,
@@ -128,6 +153,8 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
128
153
  _useState10 = _slicedToArray(_useState9, 2),
129
154
  previewImage = _useState10[0],
130
155
  setPreviewImage = _useState10[1];
156
+ // document is undefined during ssr rendering and throws an error
157
+ var lastFocusedElementId = useRef(typeof document !== 'undefined' ? (_document$activeEleme2 = document.activeElement) === null || _document$activeEleme2 === void 0 ? void 0 : _document$activeEleme2.id : '');
131
158
  useEffect(function () {
132
159
  if (errorMessage) {
133
160
  setUploadStatus(UploadStatus.Error);
@@ -143,13 +170,18 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
143
170
  setName(sanitizeName(initialUploadName));
144
171
  }
145
172
  }, [initialUploadName]);
146
- var onNameChange = function onNameChange(event) {
173
+ var clearUploadPicker = useCallback(function () {
174
+ setName(undefined);
175
+ setPreviewImage(undefined);
176
+ setUploadStatus(UploadStatus.Waiting);
177
+ }, []);
178
+ var onNameChange = useCallback(function (event) {
147
179
  var newName = sanitizeName(event.target.value);
148
180
  if (name !== newName) {
149
181
  setName(newName);
150
182
  }
151
- };
152
- var onAddEmoji = function onAddEmoji() {
183
+ }, [name]);
184
+ var onAddEmoji = useCallback(function () {
153
185
  if (uploadStatus === UploadStatus.Uploading) {
154
186
  return;
155
187
  }
@@ -179,13 +211,16 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
179
211
  });
180
212
  });
181
213
  }
182
- };
183
- var errorOnUpload = function errorOnUpload(event) {
214
+ }, [clearUploadPicker, filename, name, onUploadEmoji, previewImage, uploadStatus]);
215
+ var cancelChooseFile = useCallback(function () {
216
+ setPreviewImage(undefined);
217
+ }, []);
218
+ var errorOnUpload = useCallback(function (event) {
184
219
  debug('File load error: ', event);
185
220
  setChooseEmojiErrorMessage(messages.emojiUploadFailed);
186
221
  cancelChooseFile();
187
- };
188
- var onFileLoad = function onFileLoad(file) {
222
+ }, [cancelChooseFile]);
223
+ var onFileLoad = useCallback(function (file) {
189
224
  return /*#__PURE__*/function () {
190
225
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(f) {
191
226
  return _regeneratorRuntime.wrap(function _callee$(_context) {
@@ -216,11 +251,8 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
216
251
  return _ref.apply(this, arguments);
217
252
  };
218
253
  }();
219
- };
220
- var cancelChooseFile = function cancelChooseFile() {
221
- setPreviewImage(undefined);
222
- };
223
- var onChooseFile = function onChooseFile(event) {
254
+ }, [cancelChooseFile]);
255
+ var onChooseFile = useCallback(function (event) {
224
256
  var files = event.target.files;
225
257
  if (files.length) {
226
258
  var reader = new FileReader();
@@ -237,17 +269,25 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
237
269
  } else {
238
270
  cancelChooseFile();
239
271
  }
240
- };
241
- var clearUploadPicker = function clearUploadPicker() {
242
- setName(undefined);
243
- setPreviewImage(undefined);
244
- setUploadStatus(UploadStatus.Waiting);
245
- };
246
- var cancelUpload = function cancelUpload() {
272
+ }, [cancelChooseFile, errorOnUpload, onFileLoad]);
273
+ var cancelUpload = useCallback(function () {
247
274
  clearUploadPicker();
248
275
  onUploadCancelled();
249
- };
250
- return jsx(React.Fragment, null, name && previewImage ? jsx(EmojiUploadPreview, {
276
+
277
+ // using setTimeout here to allow the UI to update before setting focus
278
+ setTimeout(function (lastFocus) {
279
+ if (lastFocus) {
280
+ var _document$getElementB;
281
+ (_document$getElementB = document.getElementById(lastFocus)) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.focus();
282
+ }
283
+ }, 0, lastFocusedElementId.current);
284
+ }, [clearUploadPicker, onUploadCancelled]);
285
+ var chooseErrorMessage = useMemo(function () {
286
+ return chooseEmojiErrorMessage ? jsx(FormattedMessage, chooseEmojiErrorMessage) : undefined;
287
+ }, [chooseEmojiErrorMessage]);
288
+ return jsx(FocusLock, {
289
+ noFocusGuards: true
290
+ }, name && previewImage ? jsx(EmojiUploadPreview, {
251
291
  errorMessage: errorMessage,
252
292
  name: name,
253
293
  onAddEmoji: onAddEmoji,
@@ -260,8 +300,9 @@ var EmojiUploadPicker = function EmojiUploadPicker(props) {
260
300
  onClick: onFileChooserClicked,
261
301
  onNameChange: onNameChange,
262
302
  onUploadCancelled: cancelUpload,
263
- errorMessage: chooseEmojiErrorMessage ? jsx(FormattedMessage, chooseEmojiErrorMessage) : undefined,
303
+ errorMessage: chooseErrorMessage,
264
304
  intl: intl
265
305
  }));
266
306
  };
267
- export default injectIntl(EmojiUploadPicker);
307
+ var EmojiUploadPickerComponent = injectIntl( /*#__PURE__*/memo(EmojiUploadPicker));
308
+ export default EmojiUploadPickerComponent;
@@ -18,8 +18,10 @@ import EmojiErrorMessage from './EmojiErrorMessage';
18
18
  import { UploadStatus } from './internal-types';
19
19
  import RetryableButton from './RetryableButton';
20
20
  import { bigEmojiPreview, cancelButton, emojiPreviewErrorMessage, uploadAddRow, uploadPreview, uploadPreviewFooter, uploadPreviewText } from './styles';
21
+ import VisuallyHidden from '@atlaskit/visually-hidden';
21
22
  export var uploadPreviewTestId = 'upload-preview';
22
23
  export var cancelUploadButtonTestId = 'cancel-upload-button';
24
+ var addEmojiButtonDescriptionId = 'add.emoji.button.screen.reader.description.id';
23
25
  var EmojiUploadPreview = /*#__PURE__*/function (_PureComponent) {
24
26
  _inherits(EmojiUploadPreview, _PureComponent);
25
27
  var _super = _createSuper(EmojiUploadPreview);
@@ -76,12 +78,19 @@ var EmojiUploadPreview = /*#__PURE__*/function (_PureComponent) {
76
78
  messageStyles: emojiPreviewErrorMessage,
77
79
  message: errorMessage,
78
80
  tooltip: true
79
- }) : null, jsx(RetryableButton, {
81
+ }) : null, !errorMessage && jsx(VisuallyHidden, {
82
+ id: addEmojiButtonDescriptionId
83
+ }, jsx(FormattedMessage, _extends({}, messages.emojiPreview, {
84
+ values: {
85
+ emoji: name
86
+ }
87
+ }))), jsx(RetryableButton, {
80
88
  label: formatMessage(messages.addEmojiLabel),
81
89
  onSubmit: onAddEmoji,
82
90
  appearance: "primary",
83
91
  loading: uploading,
84
- error: !!errorMessage
92
+ error: !!errorMessage,
93
+ ariaDescribedby: addEmojiButtonDescriptionId
85
94
  }), jsx(AkButton, {
86
95
  onClick: onUploadCancelled,
87
96
  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 var chooseFileButtonTestId = 'choose-file-button';
4
4
  export var fileUploadInputTestId = 'file-upload';
5
5
  var FileChooser = function FileChooser(props) {
@@ -14,7 +14,8 @@ var ResourcedEmojiComponent = Loadable({
14
14
  }
15
15
  });
16
16
  var ResourcedEmoji = function ResourcedEmoji(props) {
17
- var emojiId = props.emojiId;
17
+ var emojiId = props.emojiId,
18
+ optimisticImageURL = props.optimisticImageURL;
18
19
  useEffect(function () {
19
20
  if (!emojiId) {
20
21
  return;
@@ -24,7 +25,8 @@ var ResourcedEmoji = function ResourcedEmoji(props) {
24
25
  });
25
26
  ufoExperiences['emoji-rendered'].getInstance(emojiId.id || emojiId.shortName).addMetadata({
26
27
  source: 'ResourcedEmoji',
27
- emojiId: emojiId.id
28
+ emojiId: emojiId.id,
29
+ isOptimisticImageURL: !!optimisticImageURL
28
30
  });
29
31
  return function () {
30
32
  sampledUfoRenderedEmoji(emojiId).abort({
@@ -34,7 +36,7 @@ var ResourcedEmoji = function ResourcedEmoji(props) {
34
36
  }
35
37
  });
36
38
  };
37
- }, [emojiId]);
39
+ }, [emojiId, optimisticImageURL]);
38
40
  return /*#__PURE__*/React.createElement(UfoErrorBoundary, {
39
41
  experiences: [ufoExperiences['emoji-rendered'].getInstance(props.emojiId.id || props.emojiId.shortName)]
40
42
  }, /*#__PURE__*/React.createElement(ResourcedEmojiComponent, props));
@@ -139,6 +139,10 @@ export var ResourcedEmojiComponent = function ResourcedEmojiComponent(props) {
139
139
  }
140
140
  fetchOrGetEmoji(resolvedEmojiProvider, emojiId, optimistic);
141
141
  }, [resolvedEmojiProvider, emojiId, optimistic, fetchOrGetEmoji]);
142
+
143
+ /**
144
+ * Setting resolved emoji provider for optimistic rendering
145
+ */
142
146
  useEffect(function () {
143
147
  Promise.resolve(emojiProvider).then(function (emojiProvider) {
144
148
  setResolvedEmojiProvider(emojiProvider);
@@ -20,19 +20,23 @@ var RetryButton = function RetryButton(props) {
20
20
  css: uploadRetryButton,
21
21
  appearance: "warning",
22
22
  onClick: onSubmit,
23
- testId: retryUploadButtonTestId
23
+ testId: retryUploadButtonTestId,
24
+ autoFocus: true
24
25
  }, retryLabel);
25
26
  });
26
27
  };
27
28
  var UploadButton = function UploadButton(props) {
28
29
  var appearance = props.appearance,
29
30
  onSubmit = props.onSubmit,
30
- label = props.label;
31
+ label = props.label,
32
+ ariaDescribedby = props.ariaDescribedby;
31
33
  return jsx(AkButton, {
32
34
  css: uploadEmojiButton,
33
35
  appearance: appearance,
34
36
  onClick: onSubmit,
35
- testId: uploadEmojiButtonTestId
37
+ testId: uploadEmojiButtonTestId,
38
+ "aria-describedby": ariaDescribedby,
39
+ autoFocus: true
36
40
  }, label);
37
41
  };
38
42
  var RetryableButton = function RetryableButton(props) {
@@ -0,0 +1,33 @@
1
+ /** @jsx jsx */
2
+ import { forwardRef, memo } from 'react';
3
+ import { jsx } from '@emotion/react';
4
+ import { emojiButton, hidden } from './styles';
5
+ import Emoji from './Emoji';
6
+ export var tonePreviewTestId = 'tone-preview';
7
+ export var TonePreviewButton = /*#__PURE__*/forwardRef(function (props, ref) {
8
+ var emoji = props.emoji,
9
+ selectOnHover = props.selectOnHover,
10
+ ariaLabelText = props.ariaLabelText,
11
+ ariaExpanded = props.ariaExpanded,
12
+ onSelected = props.onSelected,
13
+ _props$isVisible = props.isVisible,
14
+ isVisible = _props$isVisible === void 0 ? true : _props$isVisible;
15
+ return jsx("button", {
16
+ ref: ref,
17
+ css: [emojiButton, !isVisible && hidden],
18
+ onClick: onSelected,
19
+ "aria-label": ariaLabelText,
20
+ "aria-expanded": ariaExpanded,
21
+ "aria-controls": "emoji-picker-tone-selector",
22
+ style: {
23
+ overflow: 'hidden'
24
+ },
25
+ "data-testid": tonePreviewTestId
26
+ }, jsx(Emoji, {
27
+ emoji: emoji,
28
+ selectOnHover: selectOnHover,
29
+ shouldBeInteractive: false,
30
+ "aria-hidden": true
31
+ }));
32
+ });
33
+ export default /*#__PURE__*/memo(TonePreviewButton);