@atlaskit/emoji 64.6.0 → 64.7.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 (93) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/cjs/api/EmojiRepository.js +4 -0
  3. package/dist/cjs/api/EmojiResource.js +13 -1
  4. package/dist/cjs/api/EmojiUtils.js +4 -0
  5. package/dist/cjs/api/internal/UsageFrequencyTracker.js +4 -1
  6. package/dist/cjs/components/common/CachingEmoji.js +33 -12
  7. package/dist/cjs/components/common/DeleteButton.js +2 -1
  8. package/dist/cjs/components/common/Emoji.js +98 -21
  9. package/dist/cjs/components/common/EmojiPlaceholder.js +1 -0
  10. package/dist/cjs/components/common/Popup.js +21 -1
  11. package/dist/cjs/components/common/RecordSelectionDefault.js +13 -1
  12. package/dist/cjs/components/common/ResourcedEmoji.js +13 -3
  13. package/dist/cjs/components/common/ResourcedEmojiComponent.js +32 -3
  14. package/dist/cjs/components/common/UfoErrorBoundary.js +30 -4
  15. package/dist/cjs/components/common/UploadEmoji.js +17 -1
  16. package/dist/cjs/components/picker/EmojiPickerComponent.js +16 -4
  17. package/dist/cjs/components/picker/EmojiPickerListSearch.js +5 -0
  18. package/dist/cjs/components/picker/styles.js +2 -0
  19. package/dist/cjs/components/typeahead/EmojiTypeAheadComponent.js +18 -2
  20. package/dist/cjs/components/uploader/EmojiUploadComponent.js +6 -1
  21. package/dist/cjs/types.js +27 -2
  22. package/dist/cjs/util/analytics/ufoExperiences.js +46 -3
  23. package/dist/cjs/util/analytics/useSampledUFOComponentExperience.js +2 -1
  24. package/dist/cjs/util/browser-support.js +8 -0
  25. package/dist/cjs/util/constants.js +4 -5
  26. package/dist/cjs/util/storage-available.js +4 -0
  27. package/dist/cjs/util/useInView.js +23 -0
  28. package/dist/cjs/version.json +1 -1
  29. package/dist/es2019/api/EmojiRepository.js +4 -0
  30. package/dist/es2019/api/EmojiResource.js +13 -1
  31. package/dist/es2019/api/EmojiUtils.js +4 -0
  32. package/dist/es2019/api/internal/UsageFrequencyTracker.js +4 -1
  33. package/dist/es2019/components/common/CachingEmoji.js +28 -9
  34. package/dist/es2019/components/common/DeleteButton.js +2 -1
  35. package/dist/es2019/components/common/Emoji.js +76 -24
  36. package/dist/es2019/components/common/EmojiPlaceholder.js +1 -0
  37. package/dist/es2019/components/common/Popup.js +21 -1
  38. package/dist/es2019/components/common/RecordSelectionDefault.js +13 -1
  39. package/dist/es2019/components/common/ResourcedEmoji.js +14 -4
  40. package/dist/es2019/components/common/ResourcedEmojiComponent.js +28 -3
  41. package/dist/es2019/components/common/UfoErrorBoundary.js +14 -2
  42. package/dist/es2019/components/common/UploadEmoji.js +10 -1
  43. package/dist/es2019/components/picker/EmojiPickerComponent.js +16 -3
  44. package/dist/es2019/components/picker/EmojiPickerListSearch.js +5 -0
  45. package/dist/es2019/components/picker/styles.js +3 -0
  46. package/dist/es2019/components/typeahead/EmojiTypeAheadComponent.js +18 -2
  47. package/dist/es2019/components/uploader/EmojiUploadComponent.js +6 -1
  48. package/dist/es2019/types.js +24 -1
  49. package/dist/es2019/util/analytics/ufoExperiences.js +37 -2
  50. package/dist/es2019/util/analytics/useSampledUFOComponentExperience.js +2 -1
  51. package/dist/es2019/util/browser-support.js +1 -0
  52. package/dist/es2019/util/constants.js +2 -2
  53. package/dist/es2019/util/storage-available.js +4 -0
  54. package/dist/es2019/util/useInView.js +12 -0
  55. package/dist/es2019/version.json +1 -1
  56. package/dist/esm/api/EmojiRepository.js +4 -0
  57. package/dist/esm/api/EmojiResource.js +13 -1
  58. package/dist/esm/api/EmojiUtils.js +4 -0
  59. package/dist/esm/api/internal/UsageFrequencyTracker.js +4 -1
  60. package/dist/esm/components/common/CachingEmoji.js +32 -13
  61. package/dist/esm/components/common/DeleteButton.js +2 -1
  62. package/dist/esm/components/common/Emoji.js +84 -24
  63. package/dist/esm/components/common/EmojiPlaceholder.js +1 -0
  64. package/dist/esm/components/common/Popup.js +21 -1
  65. package/dist/esm/components/common/RecordSelectionDefault.js +13 -1
  66. package/dist/esm/components/common/ResourcedEmoji.js +14 -4
  67. package/dist/esm/components/common/ResourcedEmojiComponent.js +30 -3
  68. package/dist/esm/components/common/UfoErrorBoundary.js +30 -4
  69. package/dist/esm/components/common/UploadEmoji.js +15 -1
  70. package/dist/esm/components/picker/EmojiPickerComponent.js +16 -3
  71. package/dist/esm/components/picker/EmojiPickerListSearch.js +5 -0
  72. package/dist/esm/components/picker/styles.js +2 -0
  73. package/dist/esm/components/typeahead/EmojiTypeAheadComponent.js +18 -2
  74. package/dist/esm/components/uploader/EmojiUploadComponent.js +6 -1
  75. package/dist/esm/types.js +24 -1
  76. package/dist/esm/util/analytics/ufoExperiences.js +39 -2
  77. package/dist/esm/util/analytics/useSampledUFOComponentExperience.js +2 -1
  78. package/dist/esm/util/browser-support.js +1 -0
  79. package/dist/esm/util/constants.js +2 -2
  80. package/dist/esm/util/storage-available.js +4 -0
  81. package/dist/esm/util/useInView.js +12 -0
  82. package/dist/esm/version.json +1 -1
  83. package/dist/types/components/common/CachingEmoji.d.ts +0 -1
  84. package/dist/types/components/common/Emoji.d.ts +2 -0
  85. package/dist/types/components/common/ResourcedEmojiComponent.d.ts +1 -0
  86. package/dist/types/components/common/UfoErrorBoundary.d.ts +2 -2
  87. package/dist/types/types.d.ts +17 -0
  88. package/dist/types/util/analytics/samplingUfo.d.ts +7 -0
  89. package/dist/types/util/analytics/ufoExperiences.d.ts +4 -1
  90. package/dist/types/util/browser-support.d.ts +1 -0
  91. package/dist/types/util/constants.d.ts +1 -1
  92. package/dist/types/util/useInView.d.ts +4 -0
  93. package/package.json +7 -6
@@ -288,6 +288,10 @@ var EmojiRepository = /*#__PURE__*/function () {
288
288
  // frequent category being shown in the picker).
289
289
 
290
290
  if (this.dynamicCategoryList.indexOf(frequentCategory) === -1) {
291
+ if (typeof window === 'undefined') {
292
+ return;
293
+ }
294
+
291
295
  window.setTimeout(function () {
292
296
  _this2.dynamicCategoryList.push(frequentCategory);
293
297
  });
@@ -96,7 +96,11 @@ export var EmojiResource = /*#__PURE__*/function (_AbstractResource) {
96
96
 
97
97
  ufoExperiences['emoji-resource-fetched'].getInstance(providerType).failure({
98
98
  metadata: {
99
- reason: reason
99
+ reason: reason,
100
+ source: 'EmojiProvider',
101
+ data: {
102
+ providerUrl: provider.url
103
+ }
100
104
  }
101
105
  });
102
106
  });
@@ -185,6 +189,10 @@ export var EmojiResource = /*#__PURE__*/function (_AbstractResource) {
185
189
  }, {
186
190
  key: "loadStoredTone",
187
191
  value: function loadStoredTone() {
192
+ if (typeof window === 'undefined') {
193
+ return undefined;
194
+ }
195
+
188
196
  var storedToneString = window.localStorage.getItem(selectedToneStorageKey);
189
197
 
190
198
  if (storedToneString) {
@@ -498,6 +506,10 @@ export var EmojiResource = /*#__PURE__*/function (_AbstractResource) {
498
506
  value: function setSelectedTone(tone) {
499
507
  this.selectedTone = tone;
500
508
 
509
+ if (typeof window === 'undefined') {
510
+ return;
511
+ }
512
+
501
513
  if (storageAvailable('localStorage')) {
502
514
  try {
503
515
  window.localStorage.setItem(selectedToneStorageKey, tone ? tone.toString() : '');
@@ -49,6 +49,10 @@ var calculateScale = function calculateScale(getRatio) {
49
49
  };
50
50
 
51
51
  export var getPixelRatio = function getPixelRatio() {
52
+ if (typeof window === 'undefined') {
53
+ return 0;
54
+ }
55
+
52
56
  return window.devicePixelRatio;
53
57
  };
54
58
  export var getAltRepresentation = function getAltRepresentation(reps) {
@@ -137,7 +137,10 @@ export var Gateway = /*#__PURE__*/function () {
137
137
  }
138
138
  };
139
139
 
140
- window.setTimeout(wrappedFunc);
140
+ if (typeof window !== 'undefined') {
141
+ window.setTimeout(wrappedFunc);
142
+ }
143
+
141
144
  return true;
142
145
  }
143
146
  }, {
@@ -8,15 +8,16 @@ import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
8
8
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
9
9
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
10
10
  var _excluded = ["placeholderSize"],
11
- _excluded2 = ["children", "placeholderSize"];
11
+ _excluded2 = ["children"];
12
12
 
13
13
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
14
14
 
15
15
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
16
16
 
17
- import React from 'react';
17
+ import React, { useEffect } from 'react';
18
18
  import { PureComponent } from 'react';
19
19
  import { isMediaEmoji } from '../../util/type-helpers';
20
+ import { UfoEmojiTimings } from '../../types';
20
21
  import debug from '../../util/logger';
21
22
  import Emoji from './Emoji';
22
23
  import EmojiPlaceholder from './EmojiPlaceholder';
@@ -24,6 +25,7 @@ import { UfoErrorBoundary } from './UfoErrorBoundary';
24
25
  import { sampledUfoRenderedEmoji, ufoExperiences, useSampledUFOComponentExperience } from '../../util/analytics';
25
26
  import { SAMPLING_RATE_EMOJI_RENDERED_EXP } from '../../util/constants';
26
27
  import { EmojiContext } from '../../context/EmojiContext';
28
+ import { hasUfoMarked } from '../../util/analytics/ufoExperiences';
27
29
 
28
30
  /**
29
31
  * Renders an emoji from a cached image, if required.
@@ -36,8 +38,15 @@ export var CachingEmoji = function CachingEmoji(props) {
36
38
 
37
39
 
38
40
  useSampledUFOComponentExperience(ufoExperiences['emoji-rendered'].getInstance(emojiProps.emoji.id || emojiProps.emoji.shortName), SAMPLING_RATE_EMOJI_RENDERED_EXP, {
39
- source: 'caching-emoji'
41
+ source: 'caching-emoji',
42
+ emoji: emojiProps.emoji.shortName
40
43
  });
44
+ useEffect(function () {
45
+ if (!hasUfoMarked(sampledUfoRenderedEmoji(emojiProps.emoji), 'fmp')) {
46
+ sampledUfoRenderedEmoji(emojiProps.emoji).markFMP();
47
+ } // eslint-disable-next-line react-hooks/exhaustive-deps
48
+
49
+ }, []);
41
50
 
42
51
  var emojiNode = function emojiNode() {
43
52
  if (isMediaEmoji(props.emoji)) {
@@ -71,7 +80,12 @@ export var CachingMediaEmoji = /*#__PURE__*/function (_PureComponent) {
71
80
  _defineProperty(_assertThisInitialized(_this), "handleLoadError", function (_emojiId) {
72
81
  sampledUfoRenderedEmoji(_emojiId).failure({
73
82
  metadata: {
74
- reason: 'load error'
83
+ reason: 'load error',
84
+ source: 'CachingMediaEmoji',
85
+ emoji: {
86
+ id: _emojiId.id,
87
+ shortName: _emojiId.shortName
88
+ }
75
89
  }
76
90
  });
77
91
 
@@ -90,11 +104,6 @@ export var CachingMediaEmoji = /*#__PURE__*/function (_PureComponent) {
90
104
  }
91
105
 
92
106
  _createClass(CachingMediaEmoji, [{
93
- key: "componentDidMount",
94
- value: function componentDidMount() {
95
- sampledUfoRenderedEmoji(this.props.emoji).markFMP();
96
- }
97
- }, {
98
107
  key: "componentDidUpdate",
99
108
  value: function componentDidUpdate() {
100
109
  var _this$state$cachedEmo;
@@ -123,11 +132,14 @@ export var CachingMediaEmoji = /*#__PURE__*/function (_PureComponent) {
123
132
  }
124
133
 
125
134
  debug('Loading image via media cache', emoji.shortName);
135
+ sampledUfoRenderedEmoji(emoji).mark(UfoEmojiTimings.MEDIA_START);
126
136
  emojiProvider.getMediaEmojiDescriptionURLWithInlineToken(emoji).then(function (cachedEmoji) {
127
137
  _this2.setState({
128
138
  cachedEmoji: cachedEmoji,
129
139
  invalidImage: false
130
140
  });
141
+
142
+ sampledUfoRenderedEmoji(emoji).mark(UfoEmojiTimings.MEDIA_END);
131
143
  }).catch(function () {
132
144
  _this2.setState({
133
145
  cachedEmoji: undefined,
@@ -136,7 +148,15 @@ export var CachingMediaEmoji = /*#__PURE__*/function (_PureComponent) {
136
148
 
137
149
  sampledUfoRenderedEmoji(emoji).failure({
138
150
  metadata: {
139
- reason: 'failed to load media emoji'
151
+ reason: 'failed to load media emoji',
152
+ source: 'CachingMediaEmoji',
153
+ data: {
154
+ emoji: {
155
+ id: emoji.id,
156
+ shortName: emoji.shortName,
157
+ name: emoji.name
158
+ }
159
+ }
140
160
  }
141
161
  });
142
162
  });
@@ -150,7 +170,6 @@ export var CachingMediaEmoji = /*#__PURE__*/function (_PureComponent) {
150
170
 
151
171
  var _this$props = this.props,
152
172
  children = _this$props.children,
153
- placeholderSize = _this$props.placeholderSize,
154
173
  otherProps = _objectWithoutProperties(_this$props, _excluded2);
155
174
 
156
175
  var emojiComponent;
@@ -163,13 +182,13 @@ export var CachingMediaEmoji = /*#__PURE__*/function (_PureComponent) {
163
182
  } else {
164
183
  var _this$props2 = this.props,
165
184
  emoji = _this$props2.emoji,
166
- _placeholderSize = _this$props2.placeholderSize,
185
+ placeholderSize = _this$props2.placeholderSize,
167
186
  showTooltip = _this$props2.showTooltip,
168
187
  fitToHeight = _this$props2.fitToHeight;
169
188
  var shortName = emoji.shortName,
170
189
  representation = emoji.representation;
171
190
  emojiComponent = /*#__PURE__*/React.createElement(EmojiPlaceholder, {
172
- size: fitToHeight || _placeholderSize,
191
+ size: fitToHeight || placeholderSize,
173
192
  shortName: shortName,
174
193
  showTooltip: showTooltip,
175
194
  representation: representation
@@ -10,7 +10,8 @@ import { emojiDeleteButton, deleteButton } from './styles';
10
10
  var DeleteButton = function DeleteButton(props) {
11
11
  return jsx("span", {
12
12
  css: deleteButton,
13
- className: emojiDeleteButton
13
+ className: emojiDeleteButton,
14
+ "data-testid": "emoji-delete-button"
14
15
  }, jsx(Button, {
15
16
  iconBefore: jsx(CrossCircleIcon, {
16
17
  label: deleteEmojiLabel,
@@ -1,4 +1,5 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
3
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
4
 
4
5
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
@@ -7,14 +8,18 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
7
8
 
8
9
  /** @jsx jsx */
9
10
  import { jsx } from '@emotion/core';
10
- import React from 'react';
11
+ import React, { useEffect, useCallback } from 'react';
11
12
  import { shouldUseAltRepresentation } from '../../api/EmojiUtils';
12
- import { deleteEmojiLabel } from '../../util/constants';
13
+ import { deleteEmojiLabel, SAMPLING_RATE_EMOJI_RENDERED_EXP } from '../../util/constants';
13
14
  import { isImageRepresentation, isMediaRepresentation, isSpriteRepresentation, toEmojiId } from '../../util/type-helpers';
15
+ import { UfoEmojiTimings } from '../../types';
14
16
  import { leftClick } from '../../util/mouse';
15
17
  import DeleteButton from './DeleteButton';
16
18
  import { emojiContainer, emojiNodeStyles, commonSelectedStyles, selectOnHoverStyles, emojiSprite, emojiMainStyle, emojiStyles, emojiImage } from './styles';
17
- import { sampledUfoRenderedEmoji } from '../../util/analytics';
19
+ import { sampledUfoRenderedEmoji, ufoExperiences, useSampledUFOComponentExperience } from '../../util/analytics';
20
+ import { isIntersectionObserverSupported } from '../../util/browser-support';
21
+ import { useInView } from '../../util/useInView';
22
+ import { hasUfoMarked } from '../../util/analytics/ufoExperiences';
18
23
 
19
24
  var handleMouseDown = function handleMouseDown(props, event) {
20
25
  // Clicked emoji delete button
@@ -78,9 +83,10 @@ var handleImageError = function handleImageError(props, event) {
78
83
  }
79
84
  }; // Pure functional components are used in favour of class based components, due to the performance!
80
85
  // When rendering 1500+ emoji using class based components had a significant impact.
86
+ // TODO: add UFO tracking for sprite emoji
81
87
 
82
88
 
83
- var renderAsSprite = function renderAsSprite(props) {
89
+ export var SpriteEmoji = function SpriteEmoji(props) {
84
90
  var emoji = props.emoji,
85
91
  fitToHeight = props.fitToHeight,
86
92
  selected = props.selected,
@@ -111,11 +117,9 @@ var renderAsSprite = function renderAsSprite(props) {
111
117
  backgroundSize: "".concat(sprite.column * 100, "% ").concat(sprite.row * 100, "%")
112
118
  }, sizing);
113
119
 
114
- var emojiNode = jsx("span", {
115
- className: emojiSprite,
116
- style: style
117
- }, "\xA0");
118
120
  return jsx("span", {
121
+ "data-testid": "sprite-emoji-".concat(emoji.shortName),
122
+ "data-emoji-type": "sprite",
119
123
  tabIndex: shouldBeInteractive ? 0 : undefined,
120
124
  role: shouldBeInteractive ? 'button' : undefined,
121
125
  css: emojiContainer,
@@ -131,11 +135,13 @@ var renderAsSprite = function renderAsSprite(props) {
131
135
  },
132
136
  "aria-label": emoji.shortName,
133
137
  title: showTooltip ? emoji.shortName : ''
134
- }, emojiNode);
138
+ }, jsx("span", {
139
+ className: emojiSprite,
140
+ style: style
141
+ }, "\xA0"));
135
142
  }; // Keep as pure functional component, see renderAsSprite.
136
143
 
137
-
138
- var renderAsImage = function renderAsImage(props) {
144
+ export var ImageEmoji = function ImageEmoji(props) {
139
145
  var emoji = props.emoji,
140
146
  fitToHeight = props.fitToHeight,
141
147
  selected = props.selected,
@@ -144,6 +150,15 @@ var renderAsImage = function renderAsImage(props) {
144
150
  showTooltip = props.showTooltip,
145
151
  showDelete = props.showDelete,
146
152
  shouldBeInteractive = props.shouldBeInteractive;
153
+
154
+ var _useInView = useInView({
155
+ triggerOnce: true
156
+ }),
157
+ _useInView2 = _slicedToArray(_useInView, 2),
158
+ ref = _useInView2[0],
159
+ inView = _useInView2[1];
160
+
161
+ var ufoExp = sampledUfoRenderedEmoji(emoji);
147
162
  var classes = "".concat(emojiMainStyle, " ").concat(emojiNodeStyles, " ").concat(selected ? commonSelectedStyles : '', " ").concat(selectOnHover ? selectOnHoverStyles : '', " ").concat(emojiImage, " ").concat(className ? className : '');
148
163
  var width;
149
164
  var height;
@@ -184,17 +199,43 @@ var renderAsImage = function renderAsImage(props) {
184
199
  handleImageError(props, event);
185
200
  };
186
201
 
187
- var onLoad = function onLoad() {
188
- sampledUfoRenderedEmoji(emoji).success();
189
- }; // Pass src attribute as key to force React to rerender img node since browser does not
190
- // change preview image until loaded
191
- // We currently have the following error: property 'loading' does not exist on type 'DetailedHTMLProps<ImgHTMLAttributes, HTMLImageElement>'
192
- // The fix for this was added as a part of @types/react@16.9.20 - https://github.com/facebook/react/issues/16942
193
- // TODO: remove @ts-ignore for the <img> below when the @types/react will be bumped from v16.8.0 to v16.9.20 or higher.
202
+ var markStartLoadFromMountedTime = useCallback(function () {
203
+ var mountedMark = ufoExp.metrics.marks.find(function (mark) {
204
+ return mark.name === UfoEmojiTimings.MOUNTED_END;
205
+ });
206
+ ufoExp.mark(UfoEmojiTimings.ONLOAD_START, mountedMark === null || mountedMark === void 0 ? void 0 : mountedMark.time);
207
+ }, [ufoExp]);
194
208
 
209
+ var onLoad = function onLoad() {
210
+ // onload could trigger before onBeforeLoad when emojis in viewport at start, so we need to mark onload start manually.
211
+ if (!hasUfoMarked(ufoExp, UfoEmojiTimings.ONLOAD_START)) {
212
+ markStartLoadFromMountedTime();
213
+ }
214
+
215
+ if (!hasUfoMarked(ufoExp, UfoEmojiTimings.ONLOAD_END)) {
216
+ ufoExp.mark(UfoEmojiTimings.ONLOAD_END);
217
+ }
218
+
219
+ ufoExp.success({
220
+ metadata: {
221
+ IBSupported: isIntersectionObserverSupported
222
+ }
223
+ });
224
+ };
195
225
 
226
+ var onBeforeLoad = useCallback(function () {
227
+ if (!hasUfoMarked(ufoExp, UfoEmojiTimings.ONLOAD_START)) {
228
+ ufoExp.mark(UfoEmojiTimings.ONLOAD_START);
229
+ }
230
+ }, [ufoExp]); // because of the lack of browser support of on before load natively, used IntersectionObserver helper hook to mimic the before load time mark for UFO.
231
+
232
+ useEffect(function () {
233
+ if (inView) {
234
+ onBeforeLoad();
235
+ }
236
+ }, [inView, onBeforeLoad]);
196
237
  var emojiNode = jsx("img", _extends({
197
- // @ts-ignore
238
+ //@ts-ignore
198
239
  loading: "lazy",
199
240
  src: src,
200
241
  key: src,
@@ -210,6 +251,8 @@ var renderAsImage = function renderAsImage(props) {
210
251
  onLoad: onLoad
211
252
  }, sizing));
212
253
  return jsx("span", {
254
+ "data-testid": "image-emoji-".concat(emoji.shortName),
255
+ "data-emoji-type": "image",
213
256
  css: emojiStyles,
214
257
  tabIndex: shouldBeInteractive ? 0 : undefined,
215
258
  role: shouldBeInteractive ? 'button' : undefined,
@@ -224,17 +267,34 @@ var renderAsImage = function renderAsImage(props) {
224
267
  handleMouseMove(props, event);
225
268
  },
226
269
  "aria-label": emoji.shortName,
227
- title: showTooltip ? emoji.shortName : ''
270
+ title: showTooltip ? emoji.shortName : '',
271
+ ref: ref
228
272
  }, deleteButton, emojiNode);
229
273
  };
230
-
231
274
  export var Emoji = function Emoji(props) {
232
- var emoji = props.emoji; // TODO: We always prefer render as image as having accessibility issues with sprite representation
275
+ var emoji = props.emoji; // start emoji rendered experience, it may have already started earlier in ResourcedEmoji or CachingEmoji
276
+
277
+ useSampledUFOComponentExperience(ufoExperiences['emoji-rendered'].getInstance(emoji.id || emoji.shortName), SAMPLING_RATE_EMOJI_RENDERED_EXP, {
278
+ source: 'emoji',
279
+ emoji: emoji.shortName
280
+ });
281
+ useEffect(function () {
282
+ var ufoExp = sampledUfoRenderedEmoji(emoji);
283
+
284
+ if (!hasUfoMarked(ufoExp, 'fmp')) {
285
+ ufoExp.markFMP();
286
+ }
287
+
288
+ if (!hasUfoMarked(ufoExp, UfoEmojiTimings.MOUNTED_END)) {
289
+ ufoExp.mark(UfoEmojiTimings.MOUNTED_END);
290
+ } // eslint-disable-next-line react-hooks/exhaustive-deps
291
+
292
+ }, []); // TODO: We always prefer render as image as having accessibility issues with sprite representation
233
293
 
234
294
  if (isSpriteRepresentation(emoji.representation)) {
235
- return renderAsSprite(props);
295
+ return jsx(SpriteEmoji, props);
236
296
  }
237
297
 
238
- return renderAsImage(props);
298
+ return jsx(ImageEmoji, props);
239
299
  };
240
300
  export default Emoji;
@@ -31,6 +31,7 @@ var EmojiPlaceholder = function EmojiPlaceholder(props) {
31
31
  height: "".concat(height, "px")
32
32
  };
33
33
  return jsx("span", {
34
+ "data-testid": "emoji-placeholder-".concat(shortName),
34
35
  "aria-label": shortName,
35
36
  className: placeholder,
36
37
  css: placeholderContainer,
@@ -48,6 +48,10 @@ var Popup = /*#__PURE__*/function (_PureComponent) {
48
48
  if (_this.debounced) {
49
49
  clearTimeout(_this.debounced);
50
50
  _this.debounced = null;
51
+ }
52
+
53
+ if (typeof window === 'undefined') {
54
+ return;
51
55
  } // Timeout set to 30ms as to not throttle IE11
52
56
 
53
57
 
@@ -67,7 +71,11 @@ var Popup = /*#__PURE__*/function (_PureComponent) {
67
71
  this.popup = document.createElement('div');
68
72
  document.body.appendChild(this.popup);
69
73
  this.popup.style.position = 'absolute';
70
- window.addEventListener('resize', this.handleResize);
74
+
75
+ if (typeof window !== 'undefined') {
76
+ window.addEventListener('resize', this.handleResize);
77
+ }
78
+
71
79
  this.applyAbsolutePosition();
72
80
  this.renderContent();
73
81
  }
@@ -80,6 +88,10 @@ var Popup = /*#__PURE__*/function (_PureComponent) {
80
88
  }, {
81
89
  key: "componentWillUnmount",
82
90
  value: function componentWillUnmount() {
91
+ if (typeof window === 'undefined') {
92
+ return;
93
+ }
94
+
83
95
  window.removeEventListener('resize', this.handleResize);
84
96
 
85
97
  if (this.popup) {
@@ -105,6 +117,10 @@ var Popup = /*#__PURE__*/function (_PureComponent) {
105
117
  }, {
106
118
  key: "applyAbovePosition",
107
119
  value: function applyAbovePosition() {
120
+ if (typeof window === 'undefined') {
121
+ return;
122
+ }
123
+
108
124
  var targetNode = getTargetNode(this.props.target);
109
125
 
110
126
  if (targetNode && this.popup) {
@@ -119,6 +135,10 @@ var Popup = /*#__PURE__*/function (_PureComponent) {
119
135
  }, {
120
136
  key: "applyAbsolutePosition",
121
137
  value: function applyAbsolutePosition() {
138
+ if (typeof window === 'undefined') {
139
+ return;
140
+ }
141
+
122
142
  if (this.props.relativePosition === 'above') {
123
143
  this.applyAbovePosition();
124
144
  } else if (this.props.relativePosition === 'below') {
@@ -18,7 +18,19 @@ export var createRecordSelectionDefault = function createRecordSelectionDefault(
18
18
  ufoExperiences['emoji-selection-recorded'].success();
19
19
  }).catch(function () {
20
20
  fireAnalytics && fireAnalytics(recordFailed);
21
- ufoExperiences['emoji-selection-recorded'].failure();
21
+ ufoExperiences['emoji-selection-recorded'].failure({
22
+ metadata: {
23
+ reason: 'recordSelection error',
24
+ source: 'RecordSelectionDefault',
25
+ data: {
26
+ emoji: {
27
+ id: emoji.id,
28
+ name: emoji.name,
29
+ shortName: emoji.shortName
30
+ }
31
+ }
32
+ }
33
+ });
22
34
  });
23
35
  }
24
36
  } finally {
@@ -14,7 +14,7 @@ function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflec
14
14
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
15
15
 
16
16
  import React from 'react';
17
- import { defaultEmojiHeight, SAMPLING_RATE_EMOJI_RENDERED_EXP_RESOURCEEMOJI } from '../../util/constants';
17
+ import { defaultEmojiHeight, SAMPLING_RATE_EMOJI_RENDERED_EXP } from '../../util/constants';
18
18
  import EmojiPlaceholder from './EmojiPlaceholder';
19
19
  import LoadingEmojiComponent from './LoadingEmojiComponent';
20
20
  import { sampledUfoRenderedEmoji, ufoExperiences } from '../../util/analytics';
@@ -51,10 +51,11 @@ var ResourcedEmoji = /*#__PURE__*/function (_LoadingEmojiComponen) {
51
51
  });
52
52
 
53
53
  sampledUfoRenderedEmoji(props.emojiId).start({
54
- samplingRate: SAMPLING_RATE_EMOJI_RENDERED_EXP_RESOURCEEMOJI
54
+ samplingRate: SAMPLING_RATE_EMOJI_RENDERED_EXP
55
55
  });
56
56
  ufoExperiences['emoji-rendered'].getInstance(props.emojiId.id || props.emojiId.shortName).addMetadata({
57
- source: 'resourced-emoji'
57
+ source: 'resourced-emoji',
58
+ emoji: props.emojiId.shortName
58
59
  });
59
60
  return _this;
60
61
  }
@@ -62,7 +63,16 @@ var ResourcedEmoji = /*#__PURE__*/function (_LoadingEmojiComponen) {
62
63
  _createClass(ResourcedEmoji, [{
63
64
  key: "componentWillUnmount",
64
65
  value: function componentWillUnmount() {
65
- sampledUfoRenderedEmoji(this.props.emojiId).abort();
66
+ sampledUfoRenderedEmoji(this.props.emojiId).abort({
67
+ metadata: {
68
+ source: 'ResourcedEmoji',
69
+ reason: 'unmount',
70
+ data: {
71
+ emojiId: this.props.emojiId.id,
72
+ shortName: this.props.emojiId.shortName
73
+ }
74
+ }
75
+ });
66
76
  }
67
77
  }, {
68
78
  key: "asyncLoadComponent",
@@ -14,10 +14,12 @@ import React from 'react';
14
14
  import { Component } from 'react';
15
15
  import { defaultEmojiHeight } from '../../util/constants';
16
16
  import { isPromise } from '../../util/type-helpers';
17
+ import { UfoEmojiTimings } from '../../types';
17
18
  import CachingEmoji from './CachingEmoji';
18
19
  import EmojiPlaceholder from './EmojiPlaceholder';
19
20
  import { sampledUfoRenderedEmoji } from '../../util/analytics';
20
21
  import LegacyEmojiContextProvider from '../../context/LegacyEmojiContextProvider';
22
+ import { hasUfoMarked } from '../../util/analytics/ufoExperiences';
21
23
 
22
24
  var ResourcedEmojiComponent = /*#__PURE__*/function (_Component) {
23
25
  _inherits(ResourcedEmojiComponent, _Component);
@@ -46,6 +48,7 @@ var ResourcedEmojiComponent = /*#__PURE__*/function (_Component) {
46
48
  var _this2 = this;
47
49
 
48
50
  var foundEmoji = emojiProvider.findByEmojiId(emojiId);
51
+ sampledUfoRenderedEmoji(emojiId).mark(UfoEmojiTimings.METADATA_START);
49
52
 
50
53
  if (isPromise(foundEmoji)) {
51
54
  this.setState({
@@ -59,11 +62,20 @@ var ResourcedEmojiComponent = /*#__PURE__*/function (_Component) {
59
62
  loaded: true
60
63
  });
61
64
 
65
+ sampledUfoRenderedEmoji(emojiId).mark(UfoEmojiTimings.METADATA_END);
66
+
62
67
  if (!emoji) {
63
68
  // emoji is undefined
64
69
  sampledUfoRenderedEmoji(emojiId).failure({
65
70
  metadata: {
66
- reason: 'failed to find'
71
+ reason: 'failed to find',
72
+ source: 'ResourcedEmojiComponent',
73
+ data: {
74
+ emoji: {
75
+ id: emojiId.id,
76
+ shortName: emojiId.shortName
77
+ }
78
+ }
67
79
  }
68
80
  });
69
81
  }
@@ -71,12 +83,20 @@ var ResourcedEmojiComponent = /*#__PURE__*/function (_Component) {
71
83
  }).catch(function () {
72
84
  sampledUfoRenderedEmoji(emojiId).failure({
73
85
  metadata: {
74
- reason: 'failed to load'
86
+ reason: 'failed to load',
87
+ source: 'ResourcedEmojiComponent',
88
+ data: {
89
+ emoji: {
90
+ id: emojiId.id,
91
+ shortName: emojiId.shortName
92
+ }
93
+ }
75
94
  }
76
95
  });
77
96
  });
78
97
  } else {
79
- // loaded
98
+ sampledUfoRenderedEmoji(emojiId).mark(UfoEmojiTimings.METADATA_END); // loaded
99
+
80
100
  this.setState({
81
101
  emoji: foundEmoji,
82
102
  loaded: true
@@ -99,6 +119,13 @@ var ResourcedEmojiComponent = /*#__PURE__*/function (_Component) {
99
119
  value: function componentWillUnmount() {
100
120
  this.ready = false;
101
121
  }
122
+ }, {
123
+ key: "componentDidMount",
124
+ value: function componentDidMount() {
125
+ if (!hasUfoMarked(sampledUfoRenderedEmoji(this.props.emojiId), UfoEmojiTimings.FMP_END)) {
126
+ sampledUfoRenderedEmoji(this.props.emojiId).markFMP();
127
+ }
128
+ }
102
129
  }, {
103
130
  key: "UNSAFE_componentWillReceiveProps",
104
131
  value: function UNSAFE_componentWillReceiveProps(nextProps) {
@@ -4,6 +4,12 @@ import _inherits from "@babel/runtime/helpers/inherits";
4
4
  import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
5
5
  import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
6
6
 
7
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
8
+
9
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
10
+
11
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
12
+
7
13
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
8
14
 
9
15
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
@@ -22,10 +28,30 @@ export var UfoErrorBoundary = /*#__PURE__*/function (_React$Component) {
22
28
 
23
29
  _createClass(UfoErrorBoundary, [{
24
30
  key: "componentDidCatch",
25
- value: function componentDidCatch() {
26
- this.props.experiences.forEach(function (e) {
27
- return e.failure();
28
- });
31
+ value: function componentDidCatch(error, errorInfo) {
32
+ var _iterator = _createForOfIteratorHelper(this.props.experiences),
33
+ _step;
34
+
35
+ try {
36
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
37
+ var exp = _step.value;
38
+ exp.failure({
39
+ metadata: {
40
+ source: 'UfoErrorBoundary',
41
+ reason: 'error',
42
+ error: {
43
+ name: error.name,
44
+ message: error.message,
45
+ infoStack: errorInfo.componentStack
46
+ }
47
+ }
48
+ });
49
+ }
50
+ } catch (err) {
51
+ _iterator.e(err);
52
+ } finally {
53
+ _iterator.f();
54
+ }
29
55
  }
30
56
  }, {
31
57
  key: "render",