@atlaskit/reactions 37.0.0 → 37.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @atlaskit/reactions
2
2
 
3
+ ## 37.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`5157e2c52cfcd`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/5157e2c52cfcd) -
8
+ Update i18n NPM package versions for editor,editor-extensions,activity-platform,media,elements
9
+ (Group 3)
10
+
11
+ ### Patch Changes
12
+
13
+ - [`87589cc4647ba`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/87589cc4647ba) -
14
+ Keep keyboard focus when the focused reaction is removed or re-rendered, including with quick
15
+ reactions and in the summary view, instead of losing it to the document body. Behind feature gate
16
+ platform_a11y_reactions_removal_focus.
17
+ - Updated dependencies
18
+
19
+ ## 37.0.1
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+
3
25
  ## 37.0.0
4
26
 
5
27
  ### Major Changes
@@ -11,7 +11,7 @@ var _createAndFireEvents = _interopRequireDefault(require("@atlaskit/analytics-n
11
11
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
12
12
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // eslint-disable-next-line @typescript-eslint/consistent-type-imports
13
13
  var packageName = "@atlaskit/reactions";
14
- var packageVersion = "36.7.26";
14
+ var packageVersion = "37.0.1";
15
15
  /**
16
16
  * TODO: move to utility package?
17
17
  * A random sampling function
@@ -28,7 +28,11 @@ var _Reaction = require("./Reaction");
28
28
  var _ReactionPicker = require("./ReactionPicker");
29
29
  var _ReactionsDialog = require("./ReactionsDialog");
30
30
  var _ReactionSummaryView = require("./ReactionSummaryView");
31
+ var _Trigger = require("./Trigger");
31
32
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
33
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, 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 o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
34
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
35
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
32
36
  var wrapperStyle = null;
33
37
  var noFlexWrapStyles = null;
34
38
  var noContainerPositionStyles = null;
@@ -313,6 +317,114 @@ var Reactions = exports.Reactions = /*#__PURE__*/_react.default.memo(function (_
313
317
  return (b === null || b === void 0 ? void 0 : b.count) - (a === null || a === void 0 ? void 0 : a.count);
314
318
  });
315
319
  }, [memorizedReactions]);
320
+ var wrapperRef = (0, _react.useRef)(null);
321
+ /**
322
+ * The reaction button the user activated while it had focus, with the order of the reactions shown alongside it
323
+ */
324
+ var activatedReactionRef = (0, _react.useRef)(null);
325
+ var trackActivatedReaction = (0, _react.useCallback)(function (emojiId, event, shownReactions) {
326
+ var button = event.currentTarget;
327
+ // Only keyboard and screen reader users, or mouse clicks that focus the button, can lose focus with it
328
+ if (button.ownerDocument.activeElement !== button) {
329
+ activatedReactionRef.current = null;
330
+ return;
331
+ }
332
+ activatedReactionRef.current = {
333
+ button: button,
334
+ emojiId: emojiId,
335
+ // Read now, as a removed button no longer has ancestors
336
+ list: button.closest('ul'),
337
+ shownEmojiIds: shownReactions.map(function (reaction) {
338
+ return reaction.emojiId;
339
+ })
340
+ };
341
+ // Focus moved away by the user while the button is still shown was not lost with it.
342
+ // Chrome fires focusout before detaching a removed button, so check once the removal has finished.
343
+ button.addEventListener('focusout', function () {
344
+ return queueMicrotask(function () {
345
+ var _activatedReactionRef;
346
+ if (button.isConnected && ((_activatedReactionRef = activatedReactionRef.current) === null || _activatedReactionRef === void 0 ? void 0 : _activatedReactionRef.button) === button) {
347
+ activatedReactionRef.current = null;
348
+ }
349
+ });
350
+ }, {
351
+ once: true
352
+ });
353
+ }, []);
354
+ var handleTrackedReactionClick = (0, _react.useCallback)(function (emojiId, event) {
355
+ trackActivatedReaction(emojiId, event, memorizedReactions);
356
+ onReactionClick(emojiId, event);
357
+ }, [memorizedReactions, onReactionClick, trackActivatedReaction]);
358
+ var handleTrackedSummaryViewReactionClick = (0, _react.useCallback)(function (emojiId, event) {
359
+ trackActivatedReaction(emojiId, event, sortedReactions);
360
+ handleSummaryViewReactionClick(emojiId, event);
361
+ }, [handleSummaryViewReactionClick, sortedReactions, trackActivatedReaction]);
362
+
363
+ /**
364
+ * Restore focus when the activated reaction button leaves the DOM, either because the reaction was removed or
365
+ * because the reactions re-rendered in a different layout, rather than losing it to the document body
366
+ */
367
+ (0, _react.useEffect)(function () {
368
+ if (!(0, _fg.fg)('platform_a11y_reactions_removal_focus')) {
369
+ return;
370
+ }
371
+ var activatedReaction = activatedReactionRef.current;
372
+ // Only the first update after an activation can be its result
373
+ activatedReactionRef.current = null;
374
+ var wrapper = wrapperRef.current;
375
+ if (!activatedReaction || activatedReaction.button.isConnected || !wrapper) {
376
+ return;
377
+ }
378
+ var activeElement = wrapper.ownerDocument.activeElement;
379
+ // Never take focus from where the user has since moved it
380
+ if (activeElement && activeElement !== wrapper.ownerDocument.body) {
381
+ return;
382
+ }
383
+
384
+ // The summary view tray can be portalled outside the wrapper, so search the activated button's list first
385
+ var list = activatedReaction.list;
386
+ var findReactionButton = function findReactionButton(emojiId) {
387
+ return [list !== null && list !== void 0 && list.isConnected ? list : null, wrapper].flatMap(function (root) {
388
+ return root ? Array.from(root.querySelectorAll('button[data-emoji-id]')) : [];
389
+ }).find(function (button) {
390
+ return button.dataset.emojiId === emojiId;
391
+ });
392
+ };
393
+ var emojiId = activatedReaction.emojiId,
394
+ shownEmojiIds = activatedReaction.shownEmojiIds;
395
+ var index = shownEmojiIds.indexOf(emojiId);
396
+ var focusCandidates = [
397
+ // The same reaction re-rendered, e.g. a quick reaction going back to a zero count
398
+ function () {
399
+ return findReactionButton(emojiId);
400
+ }].concat((0, _toConsumableArray2.default)(shownEmojiIds.slice(index + 1).map(function (id) {
401
+ return function () {
402
+ return findReactionButton(id);
403
+ };
404
+ })), [function () {
405
+ return wrapper.querySelector("[data-testid=\"".concat(_Trigger.RENDER_TRIGGER_BUTTON_TESTID, "\"]"));
406
+ }], (0, _toConsumableArray2.default)(shownEmojiIds.slice(0, index).reverse().map(function (id) {
407
+ return function () {
408
+ return findReactionButton(id);
409
+ };
410
+ })));
411
+ var _iterator = _createForOfIteratorHelper(focusCandidates),
412
+ _step;
413
+ try {
414
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
415
+ var findCandidate = _step.value;
416
+ var candidate = findCandidate();
417
+ if (candidate !== null && candidate !== void 0 && candidate.isConnected) {
418
+ candidate.focus();
419
+ return;
420
+ }
421
+ }
422
+ } catch (err) {
423
+ _iterator.e(err);
424
+ } finally {
425
+ _iterator.f();
426
+ }
427
+ }, [memorizedReactions]);
316
428
 
317
429
  /**
318
430
  * event handler to open dialog with selected reaction
@@ -346,7 +458,7 @@ var Reactions = exports.Reactions = /*#__PURE__*/_react.default.memo(function (_
346
458
  rootElement: rootElement,
347
459
  reaction: reaction,
348
460
  emojiProvider: emojiProvider,
349
- onClick: onReactionClick,
461
+ onClick: (0, _fg.fg)('platform_a11y_reactions_removal_focus') ? handleTrackedReactionClick : onReactionClick,
350
462
  onMouseEnter: handleReactionMouseEnter,
351
463
  onFocused: handleReactionFocused,
352
464
  flash: flash[reaction.emojiId],
@@ -376,6 +488,7 @@ var Reactions = exports.Reactions = /*#__PURE__*/_react.default.memo(function (_
376
488
  return /*#__PURE__*/_react.default.createElement(_ufoSegment.default, {
377
489
  name: "reactions"
378
490
  }, /*#__PURE__*/_react.default.createElement("div", {
491
+ ref: wrapperRef,
379
492
  "data-testid": RENDER_REACTIONS_TESTID,
380
493
  className: (0, _runtime.ax)(["_1e0c1txw _1n261g80 _kqswh2mm _4cvr1h6o _1pfhr5cr _1fohidpf", noWrap && "_1n261q9c", noRelativeContainer && "_kqswpfqs"])
381
494
  }, !onlyRenderPicker && (shouldShowSummaryView ? /*#__PURE__*/_react.default.createElement("div", {
@@ -386,7 +499,7 @@ var Reactions = exports.Reactions = /*#__PURE__*/_react.default.memo(function (_
386
499
  flash: flash,
387
500
  particleEffectByEmoji: particleEffectByEmoji,
388
501
  onSelection: handleOnSelection,
389
- onReactionClick: handleSummaryViewReactionClick,
502
+ onReactionClick: (0, _fg.fg)('platform_a11y_reactions_removal_focus') ? handleTrackedSummaryViewReactionClick : handleSummaryViewReactionClick,
390
503
  onReactionFocused: handleReactionFocused,
391
504
  onReactionMouseEnter: handleReactionMouseEnter,
392
505
  placement: summaryViewPlacement,
@@ -2,7 +2,7 @@ import { UI_EVENT_TYPE, OPERATIONAL_EVENT_TYPE } from '@atlaskit/analytics-gas-t
2
2
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
3
3
  import createAndFireEvent from '@atlaskit/analytics-next/createAndFireEvents';
4
4
  const packageName = "@atlaskit/reactions";
5
- const packageVersion = "36.7.26";
5
+ const packageVersion = "37.0.1";
6
6
  /**
7
7
  * TODO: move to utility package?
8
8
  * A random sampling function
@@ -16,6 +16,7 @@ import { Reaction } from './Reaction';
16
16
  import { ReactionPicker } from './ReactionPicker';
17
17
  import { ReactionsDialog } from './ReactionsDialog';
18
18
  import { ReactionSummaryView } from './ReactionSummaryView';
19
+ import { RENDER_TRIGGER_BUTTON_TESTID } from './Trigger';
19
20
  const wrapperStyle = null;
20
21
  const noFlexWrapStyles = null;
21
22
  const noContainerPositionStyles = null;
@@ -261,6 +262,89 @@ export const Reactions = /*#__PURE__*/React.memo(({
261
262
  const sortedReactions = useMemo(() => {
262
263
  return [...memorizedReactions].sort((a, b) => (b === null || b === void 0 ? void 0 : b.count) - (a === null || a === void 0 ? void 0 : a.count));
263
264
  }, [memorizedReactions]);
265
+ const wrapperRef = useRef(null);
266
+ /**
267
+ * The reaction button the user activated while it had focus, with the order of the reactions shown alongside it
268
+ */
269
+ const activatedReactionRef = useRef(null);
270
+ const trackActivatedReaction = useCallback((emojiId, event, shownReactions) => {
271
+ const button = event.currentTarget;
272
+ // Only keyboard and screen reader users, or mouse clicks that focus the button, can lose focus with it
273
+ if (button.ownerDocument.activeElement !== button) {
274
+ activatedReactionRef.current = null;
275
+ return;
276
+ }
277
+ activatedReactionRef.current = {
278
+ button,
279
+ emojiId,
280
+ // Read now, as a removed button no longer has ancestors
281
+ list: button.closest('ul'),
282
+ shownEmojiIds: shownReactions.map(reaction => reaction.emojiId)
283
+ };
284
+ // Focus moved away by the user while the button is still shown was not lost with it.
285
+ // Chrome fires focusout before detaching a removed button, so check once the removal has finished.
286
+ button.addEventListener('focusout', () => queueMicrotask(() => {
287
+ var _activatedReactionRef;
288
+ if (button.isConnected && ((_activatedReactionRef = activatedReactionRef.current) === null || _activatedReactionRef === void 0 ? void 0 : _activatedReactionRef.button) === button) {
289
+ activatedReactionRef.current = null;
290
+ }
291
+ }), {
292
+ once: true
293
+ });
294
+ }, []);
295
+ const handleTrackedReactionClick = useCallback((emojiId, event) => {
296
+ trackActivatedReaction(emojiId, event, memorizedReactions);
297
+ onReactionClick(emojiId, event);
298
+ }, [memorizedReactions, onReactionClick, trackActivatedReaction]);
299
+ const handleTrackedSummaryViewReactionClick = useCallback((emojiId, event) => {
300
+ trackActivatedReaction(emojiId, event, sortedReactions);
301
+ handleSummaryViewReactionClick(emojiId, event);
302
+ }, [handleSummaryViewReactionClick, sortedReactions, trackActivatedReaction]);
303
+
304
+ /**
305
+ * Restore focus when the activated reaction button leaves the DOM, either because the reaction was removed or
306
+ * because the reactions re-rendered in a different layout, rather than losing it to the document body
307
+ */
308
+ useEffect(() => {
309
+ if (!fg('platform_a11y_reactions_removal_focus')) {
310
+ return;
311
+ }
312
+ const activatedReaction = activatedReactionRef.current;
313
+ // Only the first update after an activation can be its result
314
+ activatedReactionRef.current = null;
315
+ const wrapper = wrapperRef.current;
316
+ if (!activatedReaction || activatedReaction.button.isConnected || !wrapper) {
317
+ return;
318
+ }
319
+ const {
320
+ activeElement
321
+ } = wrapper.ownerDocument;
322
+ // Never take focus from where the user has since moved it
323
+ if (activeElement && activeElement !== wrapper.ownerDocument.body) {
324
+ return;
325
+ }
326
+
327
+ // The summary view tray can be portalled outside the wrapper, so search the activated button's list first
328
+ const {
329
+ list
330
+ } = activatedReaction;
331
+ const findReactionButton = emojiId => [list !== null && list !== void 0 && list.isConnected ? list : null, wrapper].flatMap(root => root ? Array.from(root.querySelectorAll('button[data-emoji-id]')) : []).find(button => button.dataset.emojiId === emojiId);
332
+ const {
333
+ emojiId,
334
+ shownEmojiIds
335
+ } = activatedReaction;
336
+ const index = shownEmojiIds.indexOf(emojiId);
337
+ const focusCandidates = [
338
+ // The same reaction re-rendered, e.g. a quick reaction going back to a zero count
339
+ () => findReactionButton(emojiId), ...shownEmojiIds.slice(index + 1).map(id => () => findReactionButton(id)), () => wrapper.querySelector(`[data-testid="${RENDER_TRIGGER_BUTTON_TESTID}"]`), ...shownEmojiIds.slice(0, index).reverse().map(id => () => findReactionButton(id))];
340
+ for (const findCandidate of focusCandidates) {
341
+ const candidate = findCandidate();
342
+ if (candidate !== null && candidate !== void 0 && candidate.isConnected) {
343
+ candidate.focus();
344
+ return;
345
+ }
346
+ }
347
+ }, [memorizedReactions]);
264
348
 
265
349
  /**
266
350
  * event handler to open dialog with selected reaction
@@ -291,7 +375,7 @@ export const Reactions = /*#__PURE__*/React.memo(({
291
375
  rootElement: rootElement,
292
376
  reaction: reaction,
293
377
  emojiProvider: emojiProvider,
294
- onClick: onReactionClick,
378
+ onClick: fg('platform_a11y_reactions_removal_focus') ? handleTrackedReactionClick : onReactionClick,
295
379
  onMouseEnter: handleReactionMouseEnter,
296
380
  onFocused: handleReactionFocused,
297
381
  flash: flash[reaction.emojiId],
@@ -316,6 +400,7 @@ export const Reactions = /*#__PURE__*/React.memo(({
316
400
  return /*#__PURE__*/React.createElement(UFOSegment, {
317
401
  name: "reactions"
318
402
  }, /*#__PURE__*/React.createElement("div", {
403
+ ref: wrapperRef,
319
404
  "data-testid": RENDER_REACTIONS_TESTID,
320
405
  className: ax(["_1e0c1txw _1n261g80 _kqswh2mm _4cvr1h6o _1pfhr5cr _1fohidpf", noWrap && "_1n261q9c", noRelativeContainer && "_kqswpfqs"])
321
406
  }, !onlyRenderPicker && (shouldShowSummaryView ? /*#__PURE__*/React.createElement("div", {
@@ -326,7 +411,7 @@ export const Reactions = /*#__PURE__*/React.memo(({
326
411
  flash: flash,
327
412
  particleEffectByEmoji: particleEffectByEmoji,
328
413
  onSelection: handleOnSelection,
329
- onReactionClick: handleSummaryViewReactionClick,
414
+ onReactionClick: fg('platform_a11y_reactions_removal_focus') ? handleTrackedSummaryViewReactionClick : handleSummaryViewReactionClick,
330
415
  onReactionFocused: handleReactionFocused,
331
416
  onReactionMouseEnter: handleReactionMouseEnter,
332
417
  placement: summaryViewPlacement,
@@ -5,7 +5,7 @@ import { UI_EVENT_TYPE, OPERATIONAL_EVENT_TYPE } from '@atlaskit/analytics-gas-t
5
5
  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
6
6
  import createAndFireEvent from '@atlaskit/analytics-next/createAndFireEvents';
7
7
  var packageName = "@atlaskit/reactions";
8
- var packageVersion = "36.7.26";
8
+ var packageVersion = "37.0.1";
9
9
  /**
10
10
  * TODO: move to utility package?
11
11
  * A random sampling function
@@ -3,6 +3,9 @@ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
3
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
4
4
  import "./Reactions.compiled.css";
5
5
  import { ax, ix } from "@compiled/react/runtime";
6
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, 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 o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
7
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
8
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
6
9
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
10
  import { FormattedMessage, useIntl } from 'react-intl';
8
11
  import { useAnalyticsEvents } from '@atlaskit/analytics-next/useAnalyticsEvents';
@@ -18,6 +21,7 @@ import { Reaction } from './Reaction';
18
21
  import { ReactionPicker } from './ReactionPicker';
19
22
  import { ReactionsDialog } from './ReactionsDialog';
20
23
  import { ReactionSummaryView } from './ReactionSummaryView';
24
+ import { RENDER_TRIGGER_BUTTON_TESTID } from './Trigger';
21
25
  var wrapperStyle = null;
22
26
  var noFlexWrapStyles = null;
23
27
  var noContainerPositionStyles = null;
@@ -302,6 +306,114 @@ export var Reactions = /*#__PURE__*/React.memo(function (_ref) {
302
306
  return (b === null || b === void 0 ? void 0 : b.count) - (a === null || a === void 0 ? void 0 : a.count);
303
307
  });
304
308
  }, [memorizedReactions]);
309
+ var wrapperRef = useRef(null);
310
+ /**
311
+ * The reaction button the user activated while it had focus, with the order of the reactions shown alongside it
312
+ */
313
+ var activatedReactionRef = useRef(null);
314
+ var trackActivatedReaction = useCallback(function (emojiId, event, shownReactions) {
315
+ var button = event.currentTarget;
316
+ // Only keyboard and screen reader users, or mouse clicks that focus the button, can lose focus with it
317
+ if (button.ownerDocument.activeElement !== button) {
318
+ activatedReactionRef.current = null;
319
+ return;
320
+ }
321
+ activatedReactionRef.current = {
322
+ button: button,
323
+ emojiId: emojiId,
324
+ // Read now, as a removed button no longer has ancestors
325
+ list: button.closest('ul'),
326
+ shownEmojiIds: shownReactions.map(function (reaction) {
327
+ return reaction.emojiId;
328
+ })
329
+ };
330
+ // Focus moved away by the user while the button is still shown was not lost with it.
331
+ // Chrome fires focusout before detaching a removed button, so check once the removal has finished.
332
+ button.addEventListener('focusout', function () {
333
+ return queueMicrotask(function () {
334
+ var _activatedReactionRef;
335
+ if (button.isConnected && ((_activatedReactionRef = activatedReactionRef.current) === null || _activatedReactionRef === void 0 ? void 0 : _activatedReactionRef.button) === button) {
336
+ activatedReactionRef.current = null;
337
+ }
338
+ });
339
+ }, {
340
+ once: true
341
+ });
342
+ }, []);
343
+ var handleTrackedReactionClick = useCallback(function (emojiId, event) {
344
+ trackActivatedReaction(emojiId, event, memorizedReactions);
345
+ onReactionClick(emojiId, event);
346
+ }, [memorizedReactions, onReactionClick, trackActivatedReaction]);
347
+ var handleTrackedSummaryViewReactionClick = useCallback(function (emojiId, event) {
348
+ trackActivatedReaction(emojiId, event, sortedReactions);
349
+ handleSummaryViewReactionClick(emojiId, event);
350
+ }, [handleSummaryViewReactionClick, sortedReactions, trackActivatedReaction]);
351
+
352
+ /**
353
+ * Restore focus when the activated reaction button leaves the DOM, either because the reaction was removed or
354
+ * because the reactions re-rendered in a different layout, rather than losing it to the document body
355
+ */
356
+ useEffect(function () {
357
+ if (!fg('platform_a11y_reactions_removal_focus')) {
358
+ return;
359
+ }
360
+ var activatedReaction = activatedReactionRef.current;
361
+ // Only the first update after an activation can be its result
362
+ activatedReactionRef.current = null;
363
+ var wrapper = wrapperRef.current;
364
+ if (!activatedReaction || activatedReaction.button.isConnected || !wrapper) {
365
+ return;
366
+ }
367
+ var activeElement = wrapper.ownerDocument.activeElement;
368
+ // Never take focus from where the user has since moved it
369
+ if (activeElement && activeElement !== wrapper.ownerDocument.body) {
370
+ return;
371
+ }
372
+
373
+ // The summary view tray can be portalled outside the wrapper, so search the activated button's list first
374
+ var list = activatedReaction.list;
375
+ var findReactionButton = function findReactionButton(emojiId) {
376
+ return [list !== null && list !== void 0 && list.isConnected ? list : null, wrapper].flatMap(function (root) {
377
+ return root ? Array.from(root.querySelectorAll('button[data-emoji-id]')) : [];
378
+ }).find(function (button) {
379
+ return button.dataset.emojiId === emojiId;
380
+ });
381
+ };
382
+ var emojiId = activatedReaction.emojiId,
383
+ shownEmojiIds = activatedReaction.shownEmojiIds;
384
+ var index = shownEmojiIds.indexOf(emojiId);
385
+ var focusCandidates = [
386
+ // The same reaction re-rendered, e.g. a quick reaction going back to a zero count
387
+ function () {
388
+ return findReactionButton(emojiId);
389
+ }].concat(_toConsumableArray(shownEmojiIds.slice(index + 1).map(function (id) {
390
+ return function () {
391
+ return findReactionButton(id);
392
+ };
393
+ })), [function () {
394
+ return wrapper.querySelector("[data-testid=\"".concat(RENDER_TRIGGER_BUTTON_TESTID, "\"]"));
395
+ }], _toConsumableArray(shownEmojiIds.slice(0, index).reverse().map(function (id) {
396
+ return function () {
397
+ return findReactionButton(id);
398
+ };
399
+ })));
400
+ var _iterator = _createForOfIteratorHelper(focusCandidates),
401
+ _step;
402
+ try {
403
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
404
+ var findCandidate = _step.value;
405
+ var candidate = findCandidate();
406
+ if (candidate !== null && candidate !== void 0 && candidate.isConnected) {
407
+ candidate.focus();
408
+ return;
409
+ }
410
+ }
411
+ } catch (err) {
412
+ _iterator.e(err);
413
+ } finally {
414
+ _iterator.f();
415
+ }
416
+ }, [memorizedReactions]);
305
417
 
306
418
  /**
307
419
  * event handler to open dialog with selected reaction
@@ -335,7 +447,7 @@ export var Reactions = /*#__PURE__*/React.memo(function (_ref) {
335
447
  rootElement: rootElement,
336
448
  reaction: reaction,
337
449
  emojiProvider: emojiProvider,
338
- onClick: onReactionClick,
450
+ onClick: fg('platform_a11y_reactions_removal_focus') ? handleTrackedReactionClick : onReactionClick,
339
451
  onMouseEnter: handleReactionMouseEnter,
340
452
  onFocused: handleReactionFocused,
341
453
  flash: flash[reaction.emojiId],
@@ -365,6 +477,7 @@ export var Reactions = /*#__PURE__*/React.memo(function (_ref) {
365
477
  return /*#__PURE__*/React.createElement(UFOSegment, {
366
478
  name: "reactions"
367
479
  }, /*#__PURE__*/React.createElement("div", {
480
+ ref: wrapperRef,
368
481
  "data-testid": RENDER_REACTIONS_TESTID,
369
482
  className: ax(["_1e0c1txw _1n261g80 _kqswh2mm _4cvr1h6o _1pfhr5cr _1fohidpf", noWrap && "_1n261q9c", noRelativeContainer && "_kqswpfqs"])
370
483
  }, !onlyRenderPicker && (shouldShowSummaryView ? /*#__PURE__*/React.createElement("div", {
@@ -375,7 +488,7 @@ export var Reactions = /*#__PURE__*/React.memo(function (_ref) {
375
488
  flash: flash,
376
489
  particleEffectByEmoji: particleEffectByEmoji,
377
490
  onSelection: handleOnSelection,
378
- onReactionClick: handleSummaryViewReactionClick,
491
+ onReactionClick: fg('platform_a11y_reactions_removal_focus') ? handleTrackedSummaryViewReactionClick : handleSummaryViewReactionClick,
379
492
  onReactionFocused: handleReactionFocused,
380
493
  onReactionMouseEnter: handleReactionMouseEnter,
381
494
  placement: summaryViewPlacement,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/reactions",
3
- "version": "37.0.0",
3
+ "version": "37.1.0",
4
4
  "description": "Reactions component",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -33,14 +33,14 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@atlaskit/afm-i18n-platform-elements-reactions": "2.270.0",
36
+ "@atlaskit/afm-i18n-platform-elements-reactions": "2.287.0",
37
37
  "@atlaskit/analytics-gas-types": "^6.2.0",
38
38
  "@atlaskit/analytics-namespaced-context": "^8.2.0",
39
39
  "@atlaskit/analytics-next": "^12.6.0",
40
40
  "@atlaskit/avatar": "^30.0.0",
41
- "@atlaskit/button": "^25.5.0",
41
+ "@atlaskit/button": "^25.6.0",
42
42
  "@atlaskit/css": "^1.1.0",
43
- "@atlaskit/emoji": "^73.0.0",
43
+ "@atlaskit/emoji": "^73.1.0",
44
44
  "@atlaskit/feature-gate-js-client": "^7.0.0",
45
45
  "@atlaskit/heading": "^8.0.0",
46
46
  "@atlaskit/icon": "^38.0.0",
@@ -58,8 +58,8 @@
58
58
  "@atlaskit/spinner": "^20.4.0",
59
59
  "@atlaskit/tabs": "^21.3.0",
60
60
  "@atlaskit/theme": "^28.2.0",
61
- "@atlaskit/tmp-editor-statsig": "^243.0.0",
62
- "@atlaskit/tokens": "^20.6.0",
61
+ "@atlaskit/tmp-editor-statsig": "^245.0.0",
62
+ "@atlaskit/tokens": "^20.7.0",
63
63
  "@atlaskit/tooltip": "^24.4.0",
64
64
  "@atlaskit/ufo": "^2.0.0",
65
65
  "@atlaskit/util-service-support": "^7.4.0",