@atlaskit/reactions 22.2.5 → 22.2.6

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 (46) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cjs/analytics/analytics.js +20 -1
  3. package/dist/cjs/components/ReactionPicker/ReactionPicker.js +66 -35
  4. package/dist/cjs/components/ReactionPicker/styles.js +7 -1
  5. package/dist/cjs/components/Reactions/Reactions.js +7 -3
  6. package/dist/cjs/components/Trigger/Trigger.js +4 -1
  7. package/dist/cjs/components/Trigger/styles.js +5 -0
  8. package/dist/cjs/hooks/useCloseManager.js +2 -2
  9. package/dist/cjs/hooks/useFocusTrap.js +46 -0
  10. package/dist/cjs/shared/constants.js +6 -2
  11. package/dist/cjs/store/MemoryReactionsStore.js +1 -1
  12. package/dist/cjs/version.json +1 -1
  13. package/dist/es2019/analytics/analytics.js +18 -0
  14. package/dist/es2019/components/ReactionPicker/ReactionPicker.js +50 -26
  15. package/dist/es2019/components/ReactionPicker/styles.js +5 -0
  16. package/dist/es2019/components/Reactions/Reactions.js +7 -3
  17. package/dist/es2019/components/Trigger/Trigger.js +2 -0
  18. package/dist/es2019/components/Trigger/styles.js +6 -1
  19. package/dist/es2019/hooks/useCloseManager.js +2 -3
  20. package/dist/es2019/hooks/useFocusTrap.js +38 -0
  21. package/dist/es2019/shared/constants.js +4 -1
  22. package/dist/es2019/store/MemoryReactionsStore.js +1 -1
  23. package/dist/es2019/version.json +1 -1
  24. package/dist/esm/analytics/analytics.js +18 -0
  25. package/dist/esm/components/ReactionPicker/ReactionPicker.js +67 -36
  26. package/dist/esm/components/ReactionPicker/styles.js +5 -0
  27. package/dist/esm/components/Reactions/Reactions.js +7 -3
  28. package/dist/esm/components/Trigger/Trigger.js +2 -0
  29. package/dist/esm/components/Trigger/styles.js +6 -1
  30. package/dist/esm/hooks/useCloseManager.js +2 -3
  31. package/dist/esm/hooks/useFocusTrap.js +37 -0
  32. package/dist/esm/shared/constants.js +4 -1
  33. package/dist/esm/store/MemoryReactionsStore.js +1 -1
  34. package/dist/esm/version.json +1 -1
  35. package/dist/types/analytics/analytics.d.ts +9 -0
  36. package/dist/types/components/ReactionPicker/styles.d.ts +1 -0
  37. package/dist/types/components/Trigger/Trigger.d.ts +1 -0
  38. package/dist/types/hooks/useCloseManager.d.ts +3 -1
  39. package/dist/types/hooks/useFocusTrap.d.ts +5 -0
  40. package/dist/types/shared/constants.d.ts +1 -0
  41. package/package.json +9 -7
  42. package/README.md +0 -3
  43. package/dist/cjs/analytics/constants.js +0 -9
  44. package/dist/es2019/analytics/constants.js +0 -2
  45. package/dist/esm/analytics/constants.js +0 -2
  46. package/dist/types/analytics/constants.d.ts +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @atlaskit/reactions
2
2
 
3
+ ## 22.2.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [`6b5bf5505b6`](https://bitbucket.org/atlassian/atlassian-frontend/commits/6b5bf5505b6) - revert atlaskit popup refactor in reaction picker
8
+ - [`db658265a45`](https://bitbucket.org/atlassian/atlassian-frontend/commits/db658265a45) - add sampling for reaction view analytics
9
+ - [`c84afc8fbd8`](https://bitbucket.org/atlassian/atlassian-frontend/commits/c84afc8fbd8) - [ux] add focus trap to reaction picker
10
+ - [`ed219dee1bd`](https://bitbucket.org/atlassian/atlassian-frontend/commits/ed219dee1bd) - refactor reactions picker with @atlaskit/popup
11
+ - Updated dependencies
12
+
3
13
  ## 22.2.5
4
14
 
5
15
  ### Patch Changes
@@ -4,13 +4,32 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.extractErrorInfo = exports.createRestSucceededEvent = exports.createRestFailedEvent = exports.createReactionsRenderedEvent = exports.createReactionSelectionEvent = exports.createReactionHoveredEvent = exports.createReactionFocusedEvent = exports.createReactionClickedEvent = exports.createPickerMoreClickedEvent = exports.createPickerCancelledEvent = exports.createPickerButtonClickedEvent = exports.createAndFireSafe = exports.createAndFireEventInElementsChannel = void 0;
7
+ exports.isSampled = exports.extractErrorInfo = exports.createRestSucceededEvent = exports.createRestFailedEvent = exports.createReactionsRenderedEvent = exports.createReactionSelectionEvent = exports.createReactionHoveredEvent = exports.createReactionFocusedEvent = exports.createReactionClickedEvent = exports.createPickerMoreClickedEvent = exports.createPickerCancelledEvent = exports.createPickerButtonClickedEvent = exports.createAndFireSafe = exports.createAndFireEventInElementsChannel = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _analyticsNext = require("@atlaskit/analytics-next");
10
10
  var _analyticsGasTypes = require("@atlaskit/analytics-gas-types");
11
11
  var _version = require("../version.json");
12
12
  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; }
13
13
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
14
+ /**
15
+ * TODO: move to utility package?
16
+ * A random sampling function
17
+ * sampling algorithm is from @atlassian/jira-coinflip at https://stash.atlassian.com/projects/JIRACLOUD/repos/jira-frontend/browse/src/packages/platform/app-framework/coinflip/src/index.tsx
18
+ * E.g. isSampled(2) will pass 50% of the time
19
+ * @param rate The chance that it will pass (1 in <rate> times)
20
+ * @returns bool, if it passes or not
21
+ */
22
+ // default sampling function to determine which one to be sampled
23
+ var isSampled = function isSampled(rate) {
24
+ if (rate === 1) {
25
+ return true;
26
+ }
27
+ if (rate === 0) {
28
+ return false;
29
+ }
30
+ return Math.random() * rate <= 1;
31
+ };
32
+ exports.isSampled = isSampled;
14
33
  var createAndFireEventInElementsChannel = (0, _analyticsNext.createAndFireEvent)('fabric-elements');
15
34
  exports.createAndFireEventInElementsChannel = createAndFireEventInElementsChannel;
16
35
  var createAndFireSafe = function createAndFireSafe(createAnalyticsEvent, creator) {
@@ -20,6 +20,7 @@ var _analytics = require("../../analytics");
20
20
  var _shared = require("../../shared");
21
21
  var _hooks = require("../../hooks");
22
22
  var styles = _interopRequireWildcard(require("./styles"));
23
+ var _useFocusTrap = require("../../hooks/useFocusTrap");
23
24
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
24
25
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
25
26
  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; }
@@ -87,15 +88,20 @@ var ReactionPicker = /*#__PURE__*/_react.default.memo(function (props) {
87
88
  _props$tooltipContent = props.tooltipContent,
88
89
  tooltipContent = _props$tooltipContent === void 0 ? (0, _react2.jsx)(_reactIntlNext.FormattedMessage, _shared.i18n.messages.addReaction) : _props$tooltipContent,
89
90
  emojiPickerSize = props.emojiPickerSize;
91
+ var _useState = (0, _react.useState)(null),
92
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
93
+ triggerRef = _useState2[0],
94
+ setTriggerRef = _useState2[1];
95
+ var _useState3 = (0, _react.useState)(null),
96
+ _useState4 = (0, _slicedToArray2.default)(_useState3, 2),
97
+ popupRef = _useState4[0],
98
+ setPopupRef = _useState4[1];
90
99
  /**
91
100
  * Container <div /> reference (used by custom hook to detect click outside)
92
101
  */
93
102
  var wrapperRef = (0, _react.useRef)(null);
94
- /**
95
- * a function you can ask Popper to recompute your tooltip's position. It will directly call the Popper#update method
96
- */
97
103
  var updatePopper = (0, _react.useRef)();
98
- var _useState = (0, _react.useState)({
104
+ var _useState5 = (0, _react.useState)({
99
105
  /**
100
106
  * Show the picker floating panel
101
107
  */
@@ -105,27 +111,39 @@ var ReactionPicker = /*#__PURE__*/_react.default.memo(function (props) {
105
111
  */
106
112
  showFullPicker: !!allowAllEmojis && Array.isArray(pickerQuickReactionEmojiIds) && pickerQuickReactionEmojiIds.length === 0
107
113
  }),
108
- _useState2 = (0, _slicedToArray2.default)(_useState, 2),
109
- settings = _useState2[0],
110
- setSettings = _useState2[1];
114
+ _useState6 = (0, _slicedToArray2.default)(_useState5, 2),
115
+ settings = _useState6[0],
116
+ setSettings = _useState6[1];
111
117
 
112
118
  /**
113
119
  * Custom hook triggers when user clicks outside the reactions picker
114
120
  */
115
- (0, _hooks.useCloseManager)(wrapperRef, function () {
116
- onCancel();
121
+ (0, _hooks.useCloseManager)(wrapperRef, function (callbackType) {
117
122
  close();
123
+ onCancel();
124
+ if (triggerRef && callbackType === 'ESCAPE') {
125
+ requestAnimationFrame(function () {
126
+ return triggerRef.focus();
127
+ });
128
+ }
118
129
  }, true, settings.isOpen);
119
130
 
131
+ /**
132
+ * add focus lock to popup
133
+ */
134
+ (0, _useFocusTrap.useFocusTrap)({
135
+ initialFocusRef: null,
136
+ targetRef: popupRef
137
+ });
138
+
120
139
  /**
121
140
  * Event callback when the picker is closed
122
141
  * @param _id Optional id if an emoji button was selected or undefineed if was clicked outside the picker
123
142
  */
124
143
  var close = (0, _react.useCallback)(function (_id) {
125
- setSettings({
126
- isOpen: false,
127
- showFullPicker: !!allowAllEmojis && Array.isArray(pickerQuickReactionEmojiIds) && pickerQuickReactionEmojiIds.length === 0
128
- });
144
+ setSettings(_objectSpread(_objectSpread({}, settings), {}, {
145
+ isOpen: false
146
+ }));
129
147
  // ufo abort reaction experience
130
148
  _analytics.UFO.PickerRender.abort({
131
149
  metadata: {
@@ -134,7 +152,7 @@ var ReactionPicker = /*#__PURE__*/_react.default.memo(function (props) {
134
152
  reason: 'close dialog'
135
153
  }
136
154
  });
137
- }, [allowAllEmojis, pickerQuickReactionEmojiIds]);
155
+ }, [settings]);
138
156
 
139
157
  /**
140
158
  * Event handle rwhen selecting to show the custom emoji icons picker
@@ -176,25 +194,18 @@ var ReactionPicker = /*#__PURE__*/_react.default.memo(function (props) {
176
194
  // ufo reactions picker opened success
177
195
  _analytics.UFO.PickerRender.success();
178
196
  };
179
-
180
- /**
181
- * When picker is opened, re-calculate the picker position
182
- */
183
- (0, _react.useEffect)(function () {
184
- if (settings.isOpen) {
185
- if (updatePopper.current) {
186
- updatePopper.current();
187
- }
188
- }
189
- }, [settings]);
190
197
  var wrapperClassName = " ".concat(settings.isOpen ? 'isOpen' : '', " ").concat(miniMode ? 'miniMode' : '', " ").concat(className);
198
+ (0, _react.useLayoutEffect)(function () {
199
+ var _updatePopper$current;
200
+ (_updatePopper$current = updatePopper.current) === null || _updatePopper$current === void 0 ? void 0 : _updatePopper$current.call(updatePopper);
201
+ }, [settings]);
191
202
  return (0, _react2.jsx)("div", {
192
203
  className: wrapperClassName,
193
204
  css: styles.pickerStyle,
194
205
  "data-testid": RENDER_REACTIONPICKER_TESTID,
195
206
  ref: wrapperRef
196
207
  }, (0, _react2.jsx)(_popper.Manager, null, (0, _react2.jsx)(_popper.Reference, null, function (_ref) {
197
- var popperRef = _ref.ref;
208
+ var _ref2 = _ref.ref;
198
209
  return (
199
210
  // Render a button to open the <Selector /> panel
200
211
  (0, _react2.jsx)(_Trigger.Trigger, {
@@ -202,28 +213,48 @@ var ReactionPicker = /*#__PURE__*/_react.default.memo(function (props) {
202
213
  'aria-expanded': settings.isOpen,
203
214
  'aria-controls': PICKER_CONTROL_ID
204
215
  },
205
- ref: popperRef,
216
+ ref: function ref(node) {
217
+ if (node && settings.isOpen) {
218
+ if (typeof _ref2 === 'function') {
219
+ _ref2(node);
220
+ } else {
221
+ _ref2.current = node;
222
+ }
223
+ setTriggerRef(node);
224
+ }
225
+ },
206
226
  onClick: onTriggerClick,
207
227
  miniMode: miniMode,
208
228
  disabled: disabled,
209
229
  tooltipContent: settings.isOpen ? null : tooltipContent
210
230
  })
211
231
  );
212
- }), (0, _react2.jsx)(_popper.Popper, {
232
+ }), settings.isOpen && (0, _react2.jsx)(_popper.Popper, {
213
233
  placement: "bottom-start",
214
234
  modifiers: popperModifiers
215
- }, function (_ref2) {
216
- var ref = _ref2.ref,
217
- style = _ref2.style,
218
- update = _ref2.update;
235
+ }, function (_ref3) {
236
+ var _ref4 = _ref3.ref,
237
+ style = _ref3.style,
238
+ update = _ref3.update;
219
239
  updatePopper.current = update;
220
- return (0, _react2.jsx)(_react.Fragment, null, settings.isOpen && (0, _react2.jsx)("div", {
240
+ return (0, _react2.jsx)("div", {
221
241
  id: PICKER_CONTROL_ID,
222
242
  "data-testid": RENDER_REACTIONPICKERPANEL_TESTID,
223
243
  style: _objectSpread({
224
244
  zIndex: _constants.layers.layer()
225
245
  }, style),
226
- ref: ref
246
+ ref: function ref(node) {
247
+ if (node) {
248
+ if (typeof _ref4 === 'function') {
249
+ _ref4(node);
250
+ } else {
251
+ _ref4.current = node;
252
+ }
253
+ setPopupRef(node);
254
+ }
255
+ },
256
+ css: styles.popupWrapperStyle,
257
+ tabIndex: 0
227
258
  }, (0, _react2.jsx)("div", {
228
259
  css: styles.popupStyle
229
260
  }, settings.showFullPicker ? (0, _react2.jsx)(_picker.EmojiPicker, {
@@ -238,7 +269,7 @@ var ReactionPicker = /*#__PURE__*/_react.default.memo(function (props) {
238
269
  showMore: allowAllEmojis,
239
270
  onMoreClick: onSelectMoreClick,
240
271
  pickerQuickReactionEmojiIds: pickerQuickReactionEmojiIds
241
- })))));
272
+ }))));
242
273
  })));
243
274
  });
244
275
  exports.ReactionPicker = ReactionPicker;
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.popupStyle = exports.pickerStyle = exports.contentStyle = void 0;
6
+ exports.popupWrapperStyle = exports.popupStyle = exports.pickerStyle = exports.contentStyle = void 0;
7
7
  var _react = require("@emotion/react");
8
8
  var _constants = require("@atlaskit/theme/constants");
9
9
  var _colors = require("@atlaskit/theme/colors");
@@ -20,6 +20,12 @@ var contentStyle = (0, _react.css)({
20
20
  display: 'flex'
21
21
  });
22
22
  exports.contentStyle = contentStyle;
23
+ var popupWrapperStyle = (0, _react.css)({
24
+ ':focus': {
25
+ outline: 'none'
26
+ }
27
+ });
28
+ exports.popupWrapperStyle = popupWrapperStyle;
23
29
  var popupStyle = (0, _react.css)({
24
30
  background: "var(--ds-surface-overlay, ".concat(_colors.N0, ")"),
25
31
  borderRadius: "".concat((0, _constants.borderRadius)(), "px"),
@@ -23,6 +23,8 @@ var _Reaction = require("../Reaction");
23
23
  var _ReactionDialog = require("../ReactionDialog");
24
24
  var _ReactionPicker = require("../ReactionPicker");
25
25
  var styles = _interopRequireWildcard(require("./styles"));
26
+ var _analytics2 = require("../../analytics/analytics");
27
+ var _constants = require("../../shared/constants");
26
28
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
27
29
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
28
30
  /** @jsx jsx */
@@ -123,9 +125,11 @@ var Reactions = /*#__PURE__*/_react.default.memo(function (_ref) {
123
125
  if (status !== _types.ReactionStatus.ready) {
124
126
  renderTime.current = Date.now();
125
127
  } else {
126
- var _renderTime$current;
127
- _analytics.Analytics.createAndFireSafe(createAnalyticsEvent, _analytics.Analytics.createReactionsRenderedEvent, (_renderTime$current = renderTime.current) !== null && _renderTime$current !== void 0 ? _renderTime$current : Date.now() //renderTime.current can be null during unit test cases
128
- );
128
+ if ((0, _analytics2.isSampled)(_constants.SAMPLING_RATE_REACTIONS_RENDERED_EXP)) {
129
+ var _renderTime$current;
130
+ _analytics.Analytics.createAndFireSafe(createAnalyticsEvent, _analytics.Analytics.createReactionsRenderedEvent, (_renderTime$current = renderTime.current) !== null && _renderTime$current !== void 0 ? _renderTime$current : Date.now() //renderTime.current can be null during unit test cases
131
+ );
132
+ }
129
133
  renderTime.current = undefined;
130
134
  }
131
135
  }, [createAnalyticsEvent, status]);
@@ -5,7 +5,7 @@ var _typeof = require("@babel/runtime/helpers/typeof");
5
5
  Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
- exports.Trigger = exports.RENDER_TOOLTIP_TRIGGER_TESTID = void 0;
8
+ exports.Trigger = exports.RENDER_TRIGGER_BUTTON_TESTID = exports.RENDER_TOOLTIP_TRIGGER_TESTID = void 0;
9
9
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
10
10
  var _react = _interopRequireDefault(require("react"));
11
11
  var _react2 = require("@emotion/react");
@@ -22,6 +22,8 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
22
22
  */
23
23
  var RENDER_TOOLTIP_TRIGGER_TESTID = 'render-tooltip-trigger';
24
24
  exports.RENDER_TOOLTIP_TRIGGER_TESTID = RENDER_TOOLTIP_TRIGGER_TESTID;
25
+ var RENDER_TRIGGER_BUTTON_TESTID = 'render-trigger-button';
26
+ exports.RENDER_TRIGGER_BUTTON_TESTID = RENDER_TRIGGER_BUTTON_TESTID;
25
27
  /**
26
28
  * Render an emoji button to open the reactions select picker
27
29
  */
@@ -42,6 +44,7 @@ var Trigger = /*#__PURE__*/_react.default.forwardRef(function (props, ref) {
42
44
  testId: RENDER_TOOLTIP_TRIGGER_TESTID,
43
45
  content: tooltipContent
44
46
  }, (0, _react2.jsx)(_standardButton.default, (0, _extends2.default)({
47
+ testId: RENDER_TRIGGER_BUTTON_TESTID,
45
48
  css: styles.triggerStyle({
46
49
  miniMode: miniMode,
47
50
  disabled: disabled
@@ -39,6 +39,11 @@ var triggerStyle = function triggerStyle(_ref) {
39
39
  }), {}, {
40
40
  '&:hover': {
41
41
  background: "".concat("var(--ds-background-neutral-subtle-hovered, ".concat(_colors.N20, ")"))
42
+ },
43
+ '&:focus': {
44
+ boxShadow: "0 0 0 2px ".concat("var(--ds-border-focused, ".concat(_colors.B100, ")")),
45
+ transitionDuration: '0s, 0.2s',
46
+ outline: 'none'
42
47
  }
43
48
  }));
44
49
  };
@@ -25,13 +25,13 @@ function useCloseManager(ref, callback) {
25
25
  */
26
26
  function handleClickOutside(event) {
27
27
  if (ref.current && event.target instanceof Node && !ref.current.contains(event.target)) {
28
- callback();
28
+ callback('CLICK_OUTSIDE');
29
29
  }
30
30
  }
31
31
  function handleKeydown(event) {
32
32
  var key = event.key;
33
33
  if (key === 'Escape' || key === 'Esc') {
34
- callback();
34
+ callback('ESCAPE');
35
35
  }
36
36
  }
37
37
 
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.useFocusTrap = void 0;
8
+ var _react = require("react");
9
+ var _focusTrap = _interopRequireDefault(require("focus-trap"));
10
+ /**
11
+ * Custom hook to add focus trap
12
+ * used for focus trap in ReactionPicker
13
+ * copied from useFocusManager in @atlaskit/popup
14
+ */
15
+
16
+ var useFocusTrap = function useFocusTrap(_ref) {
17
+ var targetRef = _ref.targetRef,
18
+ initialFocusRef = _ref.initialFocusRef;
19
+ (0, _react.useEffect)(function () {
20
+ if (!targetRef) {
21
+ return;
22
+ }
23
+ var trapConfig = {
24
+ clickOutsideDeactivates: true,
25
+ escapeDeactivates: true,
26
+ initialFocus: initialFocusRef || targetRef,
27
+ fallbackFocus: targetRef,
28
+ returnFocusOnDeactivate: false
29
+ };
30
+ var focusTrap = (0, _focusTrap.default)(targetRef, trapConfig);
31
+
32
+ // wait for the popup to reposition itself before we focus
33
+ var frameId = requestAnimationFrame(function () {
34
+ frameId = null;
35
+ focusTrap.activate();
36
+ });
37
+ return function () {
38
+ if (frameId != null) {
39
+ cancelAnimationFrame(frameId);
40
+ frameId = null;
41
+ }
42
+ focusTrap.deactivate();
43
+ };
44
+ }, [targetRef, initialFocusRef]);
45
+ };
46
+ exports.useFocusTrap = useFocusTrap;
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.TOOLTIP_USERS_LIMIT = exports.NUMBER_OF_REACTIONS_TO_DISPLAY = exports.ExtendedReactionsByShortName = exports.ExtendedReactions = exports.DefaultReactionsByShortName = exports.DefaultReactions = void 0;
6
+ exports.TOOLTIP_USERS_LIMIT = exports.SAMPLING_RATE_REACTIONS_RENDERED_EXP = exports.NUMBER_OF_REACTIONS_TO_DISPLAY = exports.ExtendedReactionsByShortName = exports.ExtendedReactions = exports.DefaultReactionsByShortName = exports.DefaultReactions = void 0;
7
7
  /**
8
8
  * Initial list of emoji to pick from
9
9
  */
@@ -104,4 +104,8 @@ var TOOLTIP_USERS_LIMIT = 5;
104
104
  */
105
105
  exports.TOOLTIP_USERS_LIMIT = TOOLTIP_USERS_LIMIT;
106
106
  var NUMBER_OF_REACTIONS_TO_DISPLAY = 9;
107
- exports.NUMBER_OF_REACTIONS_TO_DISPLAY = NUMBER_OF_REACTIONS_TO_DISPLAY;
107
+
108
+ // This rate is used in fetching emoji resource
109
+ exports.NUMBER_OF_REACTIONS_TO_DISPLAY = NUMBER_OF_REACTIONS_TO_DISPLAY;
110
+ var SAMPLING_RATE_REACTIONS_RENDERED_EXP = 50;
111
+ exports.SAMPLING_RATE_REACTIONS_RENDERED_EXP = SAMPLING_RATE_REACTIONS_RENDERED_EXP;
@@ -13,7 +13,7 @@ var _analytics = require("../analytics");
13
13
  var Types = _interopRequireWildcard(require("../types"));
14
14
  var _batched = require("./batched");
15
15
  var utils = _interopRequireWildcard(require("./utils"));
16
- var _constants = require("../analytics/constants");
16
+ var _constants = require("../shared/constants");
17
17
  var _ufo = require("../analytics/ufo");
18
18
  var _analytics2 = require("../analytics/analytics");
19
19
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "@atlaskit/reactions",
3
- "version": "22.2.5"
3
+ "version": "22.2.6"
4
4
  }
@@ -1,6 +1,24 @@
1
1
  import { createAndFireEvent } from '@atlaskit/analytics-next';
2
2
  import { UI_EVENT_TYPE, OPERATIONAL_EVENT_TYPE } from '@atlaskit/analytics-gas-types';
3
3
  import { name as packageName, version as packageVersion } from '../version.json';
4
+ /**
5
+ * TODO: move to utility package?
6
+ * A random sampling function
7
+ * sampling algorithm is from @atlassian/jira-coinflip at https://stash.atlassian.com/projects/JIRACLOUD/repos/jira-frontend/browse/src/packages/platform/app-framework/coinflip/src/index.tsx
8
+ * E.g. isSampled(2) will pass 50% of the time
9
+ * @param rate The chance that it will pass (1 in <rate> times)
10
+ * @returns bool, if it passes or not
11
+ */
12
+ // default sampling function to determine which one to be sampled
13
+ export const isSampled = rate => {
14
+ if (rate === 1) {
15
+ return true;
16
+ }
17
+ if (rate === 0) {
18
+ return false;
19
+ }
20
+ return Math.random() * rate <= 1;
21
+ };
4
22
  export const createAndFireEventInElementsChannel = createAndFireEvent('fabric-elements');
5
23
  export const createAndFireSafe = (createAnalyticsEvent, creator, ...args) => {
6
24
  if (createAnalyticsEvent) {
@@ -1,5 +1,5 @@
1
1
  /** @jsx jsx */
2
- import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
2
+ import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
3
3
  import { jsx } from '@emotion/react';
4
4
  import { FormattedMessage } from 'react-intl-next';
5
5
  import { EmojiPicker } from '@atlaskit/emoji/picker';
@@ -11,6 +11,7 @@ import { UFO } from '../../analytics';
11
11
  import { i18n } from '../../shared';
12
12
  import { useCloseManager } from '../../hooks';
13
13
  import * as styles from './styles';
14
+ import { useFocusTrap } from '../../hooks/useFocusTrap';
14
15
 
15
16
  /**
16
17
  * Test id for wrapper ReactionPicker div
@@ -71,13 +72,12 @@ export const ReactionPicker = /*#__PURE__*/React.memo(props => {
71
72
  tooltipContent = jsx(FormattedMessage, i18n.messages.addReaction),
72
73
  emojiPickerSize
73
74
  } = props;
75
+ const [triggerRef, setTriggerRef] = useState(null);
76
+ const [popupRef, setPopupRef] = useState(null);
74
77
  /**
75
78
  * Container <div /> reference (used by custom hook to detect click outside)
76
79
  */
77
80
  const wrapperRef = useRef(null);
78
- /**
79
- * a function you can ask Popper to recompute your tooltip's position. It will directly call the Popper#update method
80
- */
81
81
  const updatePopper = useRef();
82
82
  const [settings, setSettings] = useState({
83
83
  /**
@@ -93,19 +93,30 @@ export const ReactionPicker = /*#__PURE__*/React.memo(props => {
93
93
  /**
94
94
  * Custom hook triggers when user clicks outside the reactions picker
95
95
  */
96
- useCloseManager(wrapperRef, () => {
97
- onCancel();
96
+ useCloseManager(wrapperRef, callbackType => {
98
97
  close();
98
+ onCancel();
99
+ if (triggerRef && callbackType === 'ESCAPE') {
100
+ requestAnimationFrame(() => triggerRef.focus());
101
+ }
99
102
  }, true, settings.isOpen);
100
103
 
104
+ /**
105
+ * add focus lock to popup
106
+ */
107
+ useFocusTrap({
108
+ initialFocusRef: null,
109
+ targetRef: popupRef
110
+ });
111
+
101
112
  /**
102
113
  * Event callback when the picker is closed
103
114
  * @param _id Optional id if an emoji button was selected or undefineed if was clicked outside the picker
104
115
  */
105
116
  const close = useCallback(_id => {
106
117
  setSettings({
107
- isOpen: false,
108
- showFullPicker: !!allowAllEmojis && Array.isArray(pickerQuickReactionEmojiIds) && pickerQuickReactionEmojiIds.length === 0
118
+ ...settings,
119
+ isOpen: false
109
120
  });
110
121
  // ufo abort reaction experience
111
122
  UFO.PickerRender.abort({
@@ -115,7 +126,7 @@ export const ReactionPicker = /*#__PURE__*/React.memo(props => {
115
126
  reason: 'close dialog'
116
127
  }
117
128
  });
118
- }, [allowAllEmojis, pickerQuickReactionEmojiIds]);
129
+ }, [settings]);
119
130
 
120
131
  /**
121
132
  * Event handle rwhen selecting to show the custom emoji icons picker
@@ -157,25 +168,18 @@ export const ReactionPicker = /*#__PURE__*/React.memo(props => {
157
168
  // ufo reactions picker opened success
158
169
  UFO.PickerRender.success();
159
170
  };
160
-
161
- /**
162
- * When picker is opened, re-calculate the picker position
163
- */
164
- useEffect(() => {
165
- if (settings.isOpen) {
166
- if (updatePopper.current) {
167
- updatePopper.current();
168
- }
169
- }
170
- }, [settings]);
171
171
  const wrapperClassName = ` ${settings.isOpen ? 'isOpen' : ''} ${miniMode ? 'miniMode' : ''} ${className}`;
172
+ useLayoutEffect(() => {
173
+ var _updatePopper$current;
174
+ (_updatePopper$current = updatePopper.current) === null || _updatePopper$current === void 0 ? void 0 : _updatePopper$current.call(updatePopper);
175
+ }, [settings]);
172
176
  return jsx("div", {
173
177
  className: wrapperClassName,
174
178
  css: styles.pickerStyle,
175
179
  "data-testid": RENDER_REACTIONPICKER_TESTID,
176
180
  ref: wrapperRef
177
181
  }, jsx(Manager, null, jsx(Reference, null, ({
178
- ref: popperRef
182
+ ref
179
183
  }) =>
180
184
  // Render a button to open the <Selector /> panel
181
185
  jsx(Trigger, {
@@ -183,12 +187,21 @@ export const ReactionPicker = /*#__PURE__*/React.memo(props => {
183
187
  'aria-expanded': settings.isOpen,
184
188
  'aria-controls': PICKER_CONTROL_ID
185
189
  },
186
- ref: popperRef,
190
+ ref: node => {
191
+ if (node && settings.isOpen) {
192
+ if (typeof ref === 'function') {
193
+ ref(node);
194
+ } else {
195
+ ref.current = node;
196
+ }
197
+ setTriggerRef(node);
198
+ }
199
+ },
187
200
  onClick: onTriggerClick,
188
201
  miniMode: miniMode,
189
202
  disabled: disabled,
190
203
  tooltipContent: settings.isOpen ? null : tooltipContent
191
- })), jsx(Popper, {
204
+ })), settings.isOpen && jsx(Popper, {
192
205
  placement: "bottom-start",
193
206
  modifiers: popperModifiers
194
207
  }, ({
@@ -197,14 +210,25 @@ export const ReactionPicker = /*#__PURE__*/React.memo(props => {
197
210
  update
198
211
  }) => {
199
212
  updatePopper.current = update;
200
- return jsx(Fragment, null, settings.isOpen && jsx("div", {
213
+ return jsx("div", {
201
214
  id: PICKER_CONTROL_ID,
202
215
  "data-testid": RENDER_REACTIONPICKERPANEL_TESTID,
203
216
  style: {
204
217
  zIndex: layers.layer(),
205
218
  ...style
206
219
  },
207
- ref: ref
220
+ ref: node => {
221
+ if (node) {
222
+ if (typeof ref === 'function') {
223
+ ref(node);
224
+ } else {
225
+ ref.current = node;
226
+ }
227
+ setPopupRef(node);
228
+ }
229
+ },
230
+ css: styles.popupWrapperStyle,
231
+ tabIndex: 0
208
232
  }, jsx("div", {
209
233
  css: styles.popupStyle
210
234
  }, settings.showFullPicker ? jsx(EmojiPicker, {
@@ -219,6 +243,6 @@ export const ReactionPicker = /*#__PURE__*/React.memo(props => {
219
243
  showMore: allowAllEmojis,
220
244
  onMoreClick: onSelectMoreClick,
221
245
  pickerQuickReactionEmojiIds: pickerQuickReactionEmojiIds
222
- })))));
246
+ }))));
223
247
  })));
224
248
  });
@@ -11,6 +11,11 @@ export const pickerStyle = css({
11
11
  export const contentStyle = css({
12
12
  display: 'flex'
13
13
  });
14
+ export const popupWrapperStyle = css({
15
+ ':focus': {
16
+ outline: 'none'
17
+ }
18
+ });
14
19
  export const popupStyle = css({
15
20
  background: `var(--ds-surface-overlay, ${N0})`,
16
21
  borderRadius: `${borderRadius()}px`,
@@ -13,6 +13,8 @@ import { Reaction } from '../Reaction';
13
13
  import { ReactionsDialog } from '../ReactionDialog';
14
14
  import { ReactionPicker } from '../ReactionPicker';
15
15
  import * as styles from './styles';
16
+ import { isSampled } from '../../analytics/analytics';
17
+ import { SAMPLING_RATE_REACTIONS_RENDERED_EXP } from '../../shared/constants';
16
18
 
17
19
  /**
18
20
  * Set of all available UFO experiences relating to reactions dialog
@@ -97,9 +99,11 @@ export const Reactions = /*#__PURE__*/React.memo(({
97
99
  if (status !== ReactionStatus.ready) {
98
100
  renderTime.current = Date.now();
99
101
  } else {
100
- var _renderTime$current;
101
- Analytics.createAndFireSafe(createAnalyticsEvent, Analytics.createReactionsRenderedEvent, (_renderTime$current = renderTime.current) !== null && _renderTime$current !== void 0 ? _renderTime$current : Date.now() //renderTime.current can be null during unit test cases
102
- );
102
+ if (isSampled(SAMPLING_RATE_REACTIONS_RENDERED_EXP)) {
103
+ var _renderTime$current;
104
+ Analytics.createAndFireSafe(createAnalyticsEvent, Analytics.createReactionsRenderedEvent, (_renderTime$current = renderTime.current) !== null && _renderTime$current !== void 0 ? _renderTime$current : Date.now() //renderTime.current can be null during unit test cases
105
+ );
106
+ }
103
107
  renderTime.current = undefined;
104
108
  }
105
109
  }, [createAnalyticsEvent, status]);