@atlaskit/emoji 71.4.3 → 71.4.4

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,16 @@
1
1
  # @atlaskit/emoji
2
2
 
3
+ ## 71.4.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`e3db0f49354a5`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/e3db0f49354a5) -
8
+ Use Twemoji for flag emoji and apply Unicode 14 emoji presentation selectors for native emoji
9
+ rendering.
10
+ - [`ff291d024e356`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ff291d024e356) -
11
+ EDITOR-7032 Show emoji shortName (with fallback to name) in tooltip on hover renderer
12
+ - Updated dependencies
13
+
3
14
  ## 71.4.3
4
15
 
5
16
  ### Patch Changes
@@ -501,29 +501,37 @@ var EmojiNodeWrapper = exports.EmojiNodeWrapper = /*#__PURE__*/(0, _react.forwar
501
501
  var ariaLabel = intl && (0, _platformFeatureFlags.fg)('platform_change_emoji_button_label') ? intl.formatMessage(_i18n.messages.changeEmojiShortnameButtonLabel, {
502
502
  shortName: (_emoji$name = emoji.name) !== null && _emoji$name !== void 0 ? _emoji$name : emoji.shortName
503
503
  }) : editorEmoji ? undefined : emoji.shortName;
504
- return /*#__PURE__*/_react.default.createElement("span", (0, _extends2.default)({
504
+ var tooltipContent = showTooltip && (0, _expValEquals.expValEquals)('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? emoji.shortName || emoji.name || undefined : undefined;
505
+ var emojiSpan = /*#__PURE__*/_react.default.createElement("span", (0, _extends2.default)({
505
506
  role: editorEmoji ? undefined : shouldBeInteractive ? 'button' : ariaLabel ? 'img' : 'presentation',
506
507
  "aria-label": ariaLabel,
507
508
  ref: ref,
508
509
  "data-testid": "".concat(type, "-emoji-").concat(emoji.shortName),
509
510
  "data-emoji-type": type,
510
- tabIndex: shouldBeInteractive ? tabIndex || 0 : undefined,
511
+ tabIndex: shouldBeInteractive ? tabIndex || 0 : tooltipContent ? 0 : undefined,
511
512
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
512
513
  className: (0, _runtime.ax)([type === 'sprite' ? "_19pk1n1a _2hwxidpf _otyr1n1a _18u0idpf _1e0c1o8l _jr50g2xd _ylp71o8l _pkaxgktf _1hdcgktf _109tnkob _tn8j1dpa _3naf1dpa _nt751r31 _49pcglyw _1hvw1o36 _7ehi1y54 _491113zc" : type === 'unicode' ? "_19pk1n1a _2hwxidpf _otyr1n1a _18u0idpf _bfhk1j28 _1e0c1o8l _s7n4nkob _tn8j1dpa _3naf1dpa _160jewfl _1theewfl _1kogh2mm _qyp0ewfl _nt751r31 _49pcglyw _1hvw1o36 _7ehi1y54 _491113zc" : "_2rkofajl _19pk1n1a _2hwxidpf _otyr1n1a _18u0idpf _bfhk1j28 _1e0c1o8l _s7n4nkob _v4pn1ule _tn8j1dpa _3naf1dpa _160jewfl _1theewfl _1kogh2mm _qyp0ewfl _nt751r31 _49pcglyw _1hvw1o36 _7ehi1y54 _491113zc", className]),
513
- onKeyDown: function onKeyDown(event) {
514
+ onKeyDown: shouldBeInteractive || !(0, _expValEquals.expValEquals)('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? function (event) {
514
515
  return handleKeyDown(props, event);
515
- },
516
- onMouseDown: function onMouseDown(event) {
516
+ } : undefined,
517
+ onMouseDown: shouldBeInteractive || !(0, _expValEquals.expValEquals)('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? function (event) {
517
518
  handleMouseDown(props, event);
518
- },
519
+ } : undefined,
519
520
  onMouseEnter: function onMouseEnter(event) {
520
521
  handleMouseMove(props, event);
521
522
  },
522
523
  onFocus: function onFocus(event) {
523
524
  handleFocus(props, event);
524
525
  },
525
- title: showTooltip ? emoji.shortName : undefined // TODO: COLLAB-2351 - use @atlaskit/Tooltip in future for non-deletable emoji if enabled showTooltip
526
+ title: showTooltip && !(0, _expValEquals.expValEquals)('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? emoji.shortName : undefined
526
527
  }, other), children);
528
+ if (tooltipContent) {
529
+ return /*#__PURE__*/_react.default.createElement(_tooltip.default, {
530
+ content: tooltipContent,
531
+ tag: "span"
532
+ }, emojiSpan);
533
+ }
534
+ return emojiSpan;
527
535
  });
528
536
  var Emoji = exports.Emoji = function Emoji(props) {
529
537
  var emoji = props.emoji;
@@ -20,7 +20,7 @@ var createEvent = function createEvent(eventType, action, actionSubject, actionS
20
20
  actionSubjectId: actionSubjectId,
21
21
  attributes: _objectSpread({
22
22
  packageName: "@atlaskit/emoji",
23
- packageVersion: "71.4.2"
23
+ packageVersion: "71.4.3"
24
24
  }, attributes)
25
25
  };
26
26
  };
@@ -11,36 +11,25 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
11
11
  var KEYCAP_COMBINING = 0x20e3;
12
12
  var VARIATION_SELECTOR_16 = 0xfe0f;
13
13
  var VARIATION_SELECTOR_15 = 0xfe0e;
14
+ var EMOJI_MODIFIER_START = 0x1f3fb;
15
+ var EMOJI_MODIFIER_END = 0x1f3ff;
16
+ var REGIONAL_INDICATOR_START = 0x1f1e6;
17
+ var REGIONAL_INDICATOR_END = 0x1f1ff;
18
+ var FLAG_BASES = new Set([0x1f38c, 0x1f3c1, 0x1f3f3, 0x1f3f4, 0x1f6a9]);
14
19
 
15
- // Text-presentation-default emoji: BMP characters (U+0000–U+FFFF) that are
16
- // valid emoji but lack the Emoji_Presentation property render as text without
17
- // U+FE0F. Supplementary-plane characters (U+10000+) always render as emoji
18
- // regardless of the Emoji_Presentation property, so we skip those.
19
- //
20
- // This set is derived from Unicode 14.0 emoji-data.txt: all BMP code points
21
- // that have the Emoji property but NOT the Emoji_Presentation property.
22
- // Using an explicit lookup table avoids the \p{} Unicode property escape
23
- // syntax (TS1501) which requires a specific TypeScript build target.
24
- var TEXT_DEFAULT_EMOJI = new Set([0x0023, 0x002a, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x00a9, 0x00ae, 0x203c, 0x2049, 0x2122, 0x2139, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x21a9, 0x21aa, 0x2328, 0x23cf, 0x23ed, 0x23ee, 0x23ef, 0x23f1, 0x23f2, 0x23f8, 0x23f9, 0x23fa, 0x24c2, 0x25aa, 0x25ab, 0x25b6, 0x25c0, 0x25fb, 0x25fc, 0x2600, 0x2601, 0x2602, 0x2603, 0x2604, 0x260e, 0x2611, 0x2618, 0x261d, 0x2620, 0x2622, 0x2623, 0x2626, 0x262a, 0x262e, 0x262f, 0x2638, 0x2639, 0x263a, 0x2640, 0x2642, 0x265f, 0x2660, 0x2663, 0x2665, 0x2666, 0x2668, 0x267b, 0x267e, 0x2692, 0x2694, 0x2695, 0x2696, 0x2697, 0x2699, 0x269b, 0x269c, 0x26a0, 0x26a7, 0x26b0, 0x26b1, 0x26c8, 0x26cf, 0x26d1, 0x26d3, 0x26e9, 0x26f0, 0x26f1, 0x26f4, 0x26f7, 0x26f8, 0x26f9, 0x2702, 0x2708, 0x2709, 0x270c, 0x270d, 0x270f, 0x2712, 0x2714, 0x2716, 0x271d, 0x2721, 0x2733, 0x2734, 0x2744, 0x2747, 0x2763, 0x2764, 0x27a1, 0x2934, 0x2935, 0x2b05, 0x2b06, 0x2b07, 0x3030, 0x303d, 0x3297, 0x3299]);
25
-
26
- /**
27
- * Converts an emoji ID (a hyphen-separated string of hex Unicode code points,
28
- * e.g. `"1f44d"`, `"1f44d-1f3fb"`, `"1f468-200d-1f469-200d-1f467"`) into its
29
- * corresponding Unicode string.
30
- *
31
- * Special handling:
32
- * - Keycap sequences: U+20E3 (COMBINING ENCLOSING KEYCAP) must be preceded by
33
- * U+FE0F (VARIATION SELECTOR-16) to form a well-defined keycap emoji.
34
- * - Text-default emoji (characters whose default presentation is text) must be
35
- * followed by U+FE0F to force emoji presentation.
36
- *
37
- * @returns The Unicode string for the emoji, or `undefined` if the input is
38
- * invalid (empty string, non-hex segments, or out-of-range code points).
39
- */
40
- function emojiIdToEmoji(id) {
41
- if (!id) {
42
- return undefined;
43
- }
20
+ // Emoji variation bases from Unicode 14.0 emoji-variation-sequences.txt.
21
+ // These are the code points with a defined U+FE0F emoji-style variant.
22
+ var EMOJI_VARIATION_BASES = new Set([0x0023, 0x002a, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x00a9, 0x00ae, 0x203c, 0x2049, 0x2122, 0x2139, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x21a9, 0x21aa, 0x2328, 0x23cf, 0x23ed, 0x23ee, 0x23ef, 0x23f1, 0x23f2, 0x23f8, 0x23f9, 0x23fa, 0x24c2, 0x25aa, 0x25ab, 0x25b6, 0x25c0, 0x25fb, 0x25fc, 0x2600, 0x2601, 0x2602, 0x2603, 0x2604, 0x260e, 0x2611, 0x2618, 0x261d, 0x2620, 0x2622, 0x2623, 0x2626, 0x262a, 0x262e, 0x262f, 0x2638, 0x2639, 0x263a, 0x2640, 0x2642, 0x265f, 0x2660, 0x2663, 0x2665, 0x2666, 0x2668, 0x267b, 0x267e, 0x2692, 0x2694, 0x2695, 0x2696, 0x2697, 0x2699, 0x269b, 0x269c, 0x26a0, 0x26a7, 0x26b0, 0x26b1, 0x26c8, 0x26cf, 0x26d1, 0x26d3, 0x26e9, 0x26f0, 0x26f1, 0x26f4, 0x26f7, 0x26f8, 0x26f9, 0x2702, 0x2708, 0x2709, 0x270c, 0x270d, 0x270f, 0x2712, 0x2714, 0x2716, 0x271d, 0x2721, 0x2733, 0x2734, 0x2744, 0x2747, 0x2763, 0x2764, 0x27a1, 0x2934, 0x2935, 0x2b05, 0x2b06, 0x2b07, 0x3030, 0x303d, 0x3297, 0x3299, 0x1f170, 0x1f171, 0x1f17e, 0x1f17f, 0x1f202, 0x1f237, 0x1f321, 0x1f324, 0x1f325, 0x1f326, 0x1f327, 0x1f328, 0x1f329, 0x1f32a, 0x1f32b, 0x1f32c, 0x1f336, 0x1f37d, 0x1f396, 0x1f397, 0x1f399, 0x1f39a, 0x1f39b, 0x1f39e, 0x1f39f, 0x1f3cb, 0x1f3cc, 0x1f3cd, 0x1f3ce, 0x1f3d4, 0x1f3d5, 0x1f3d6, 0x1f3d7, 0x1f3d8, 0x1f3d9, 0x1f3da, 0x1f3db, 0x1f3dc, 0x1f3dd, 0x1f3de, 0x1f3df, 0x1f3f3, 0x1f3f5, 0x1f3f7, 0x1f43f, 0x1f441, 0x1f4fd, 0x1f549, 0x1f54a, 0x1f56f, 0x1f570, 0x1f573, 0x1f574, 0x1f575, 0x1f576, 0x1f577, 0x1f578, 0x1f579, 0x1f587, 0x1f58a, 0x1f58b, 0x1f58c, 0x1f58d, 0x1f590, 0x1f5a5, 0x1f5a8, 0x1f5b1, 0x1f5b2, 0x1f5bc, 0x1f5c2, 0x1f5c3, 0x1f5c4, 0x1f5d1, 0x1f5d2, 0x1f5d3, 0x1f5dc, 0x1f5dd, 0x1f5de, 0x1f5e1, 0x1f5e3, 0x1f5e8, 0x1f5ef, 0x1f5f3, 0x1f5fa, 0x1f6cb, 0x1f6cd, 0x1f6ce, 0x1f6cf, 0x1f6e0, 0x1f6e1, 0x1f6e2, 0x1f6e3, 0x1f6e4, 0x1f6e5, 0x1f6e9, 0x1f6f0, 0x1f6f3, 0x231a, 0x231b, 0x23e9, 0x23ea, 0x23f3, 0x25fd, 0x25fe, 0x2614, 0x2615, 0x2648, 0x2649, 0x264a, 0x264b, 0x264c, 0x264d, 0x264e, 0x264f, 0x2650, 0x2651, 0x2652, 0x2653, 0x267f, 0x2693, 0x26a1, 0x26aa, 0x26ab, 0x26bd, 0x26be, 0x26c4, 0x26c5, 0x26d4, 0x26ea, 0x26f2, 0x26f3, 0x26f5, 0x26fa, 0x26fd, 0x2753, 0x2757, 0x2b1b, 0x2b1c, 0x2b50, 0x2b55, 0x1f004, 0x1f21a, 0x1f22f, 0x1f30d, 0x1f30e, 0x1f30f, 0x1f315, 0x1f31c, 0x1f378, 0x1f393, 0x1f3a7, 0x1f3ac, 0x1f3ad, 0x1f3ae, 0x1f3c2, 0x1f3c4, 0x1f3c6, 0x1f3ca, 0x1f3e0, 0x1f3ed, 0x1f408, 0x1f415, 0x1f41f, 0x1f426, 0x1f442, 0x1f446, 0x1f447, 0x1f448, 0x1f449, 0x1f44d, 0x1f44e, 0x1f453, 0x1f46a, 0x1f47d, 0x1f4a3, 0x1f4b0, 0x1f4b3, 0x1f4bb, 0x1f4bf, 0x1f4cb, 0x1f4da, 0x1f4df, 0x1f4e4, 0x1f4e5, 0x1f4e6, 0x1f4ea, 0x1f4eb, 0x1f4ec, 0x1f4ed, 0x1f4f7, 0x1f4f9, 0x1f4fa, 0x1f4fb, 0x1f508, 0x1f50d, 0x1f512, 0x1f513, 0x1f550, 0x1f551, 0x1f552, 0x1f553, 0x1f554, 0x1f555, 0x1f556, 0x1f557, 0x1f558, 0x1f559, 0x1f55a, 0x1f55b, 0x1f55c, 0x1f55d, 0x1f55e, 0x1f55f, 0x1f560, 0x1f561, 0x1f562, 0x1f563, 0x1f564, 0x1f565, 0x1f566, 0x1f567, 0x1f610, 0x1f687, 0x1f68d, 0x1f691, 0x1f694, 0x1f698, 0x1f6ad, 0x1f6b2, 0x1f6b9, 0x1f6ba, 0x1f6bc]);
23
+ var isEmojiModifier = function isEmojiModifier(codePoint) {
24
+ return typeof codePoint === 'number' && codePoint >= EMOJI_MODIFIER_START && codePoint <= EMOJI_MODIFIER_END;
25
+ };
26
+ var shouldAddEmojiPresentationSelector = function shouldAddEmojiPresentationSelector(codePoint, nextCodePoint) {
27
+ return EMOJI_VARIATION_BASES.has(codePoint) && nextCodePoint !== VARIATION_SELECTOR_16 && nextCodePoint !== VARIATION_SELECTOR_15 && nextCodePoint !== KEYCAP_COMBINING && !isEmojiModifier(nextCodePoint);
28
+ };
29
+ var isRegionalIndicator = function isRegionalIndicator(codePoint) {
30
+ return codePoint >= REGIONAL_INDICATOR_START && codePoint <= REGIONAL_INDICATOR_END;
31
+ };
32
+ var parseEmojiIdCodePoints = function parseEmojiIdCodePoints(id) {
44
33
  var segments = id.split('-');
45
34
  var codePoints = [];
46
35
  var _iterator = _createForOfIteratorHelper(segments),
@@ -51,19 +40,53 @@ function emojiIdToEmoji(id) {
51
40
  if (!/^[0-9a-fA-F]+$/.test(segment)) {
52
41
  return undefined;
53
42
  }
54
- var _cp = parseInt(segment, 16);
55
- // Valid Unicode scalar values: 0x00000x10FFFF, excluding surrogates 0xD800–0xDFFF
56
- if (isNaN(_cp) || _cp < 0 || _cp > 0x10ffff || _cp >= 0xd800 && _cp <= 0xdfff) {
43
+ var cp = parseInt(segment, 16);
44
+ // Valid Unicode scalar values: U+0000U+10FFFF, excluding surrogates.
45
+ if (isNaN(cp) || cp < 0 || cp > 0x10ffff || cp >= 0xd800 && cp <= 0xdfff) {
57
46
  return undefined;
58
47
  }
59
- codePoints.push(_cp);
48
+ codePoints.push(cp);
60
49
  }
61
50
  } catch (err) {
62
51
  _iterator.e(err);
63
52
  } finally {
64
53
  _iterator.f();
65
54
  }
66
- if (codePoints.length === 0) {
55
+ return codePoints.length ? codePoints : undefined;
56
+ };
57
+ var isFlagEmoji = function isFlagEmoji(codePoints) {
58
+ if (codePoints.length === 2 && codePoints.every(isRegionalIndicator)) {
59
+ return true;
60
+ }
61
+ return FLAG_BASES.has(codePoints[0]);
62
+ };
63
+
64
+ /**
65
+ * Converts an emoji ID (a hyphen-separated string of hex Unicode code points,
66
+ * e.g. `"1f44d"`, `"1f44d-1f3fb"`, `"1f468-200d-1f469-200d-1f467"`) into its
67
+ * corresponding Unicode string.
68
+ *
69
+ * Special handling:
70
+ * - Keycap sequences: U+20E3 (COMBINING ENCLOSING KEYCAP) must be preceded by
71
+ * U+FE0F (VARIATION SELECTOR-16) to form a well-defined keycap emoji.
72
+ * - Emoji variation bases are followed by U+FE0F to force emoji presentation.
73
+ * - Flags return `undefined` so callers use Twemoji instead of native emoji.
74
+ *
75
+ * @returns The Unicode string for the emoji, or `undefined` if the input is
76
+ * invalid (empty string, non-hex segments, or out-of-range code points).
77
+ */
78
+ function emojiIdToEmoji(id) {
79
+ if (!(0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_use_unicode_emojis', 'isEnabled', true)) {
80
+ return undefined;
81
+ }
82
+ if (!id) {
83
+ return undefined;
84
+ }
85
+ var codePoints = parseEmojiIdCodePoints(id);
86
+ if (!codePoints) {
87
+ return undefined;
88
+ }
89
+ if (isFlagEmoji(codePoints)) {
67
90
  return undefined;
68
91
  }
69
92
  var processedCodePoints = [];
@@ -77,22 +100,11 @@ function emojiIdToEmoji(id) {
77
100
  if (next === KEYCAP_COMBINING && cp !== VARIATION_SELECTOR_16) {
78
101
  processedCodePoints.push(VARIATION_SELECTOR_16);
79
102
  }
80
- }
81
103
 
82
- // Text-presentation-default emoji: characters that are valid emoji but whose
83
- // default rendering is text (e.g. etc.) must be followed by
84
- // U+FE0F (VARIATION SELECTOR-16) to force emoji presentation.
85
- if ((0, _expValEqualsNoExposure.expValEqualsNoExposure)('platform_use_unicode_emojis', 'isEnabled', true)) {
86
- var lastCodePoint = processedCodePoints[processedCodePoints.length - 1];
87
- var alreadyHasVariationSelector = lastCodePoint === VARIATION_SELECTOR_16 || lastCodePoint === VARIATION_SELECTOR_15 ||
88
- // Keycap sequences end with U+20E3 — they already have U+FE0F inserted inline
89
- // and must not have another variation selector appended at the end.
90
- lastCodePoint === KEYCAP_COMBINING;
91
- if (!alreadyHasVariationSelector) {
92
- var baseCodePoint = codePoints[0];
93
- if (codePoints.length === 1 && baseCodePoint <= 0xffff && TEXT_DEFAULT_EMOJI.has(baseCodePoint)) {
94
- processedCodePoints.push(VARIATION_SELECTOR_16);
95
- }
104
+ // Emoji variation bases need U+FE0F immediately after the base code point,
105
+ // including when the base appears inside a ZWJ sequence.
106
+ if (shouldAddEmojiPresentationSelector(cp, next)) {
107
+ processedCodePoints.push(VARIATION_SELECTOR_16);
96
108
  }
97
109
  }
98
110
  try {
@@ -469,27 +469,35 @@ export const EmojiNodeWrapper = /*#__PURE__*/forwardRef((props, ref) => {
469
469
  const ariaLabel = intl && fg('platform_change_emoji_button_label') ? intl.formatMessage(messages.changeEmojiShortnameButtonLabel, {
470
470
  shortName: (_emoji$name = emoji.name) !== null && _emoji$name !== void 0 ? _emoji$name : emoji.shortName
471
471
  }) : editorEmoji ? undefined : emoji.shortName;
472
- return /*#__PURE__*/React.createElement("span", _extends({
472
+ const tooltipContent = showTooltip && expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? emoji.shortName || emoji.name || undefined : undefined;
473
+ const emojiSpan = /*#__PURE__*/React.createElement("span", _extends({
473
474
  role: editorEmoji ? undefined : shouldBeInteractive ? 'button' : ariaLabel ? 'img' : 'presentation',
474
475
  "aria-label": ariaLabel,
475
476
  ref: ref,
476
477
  "data-testid": `${type}-emoji-${emoji.shortName}`,
477
478
  "data-emoji-type": type,
478
- tabIndex: shouldBeInteractive ? tabIndex || 0 : undefined,
479
+ tabIndex: shouldBeInteractive ? tabIndex || 0 : tooltipContent ? 0 : undefined,
479
480
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
480
481
  className: ax([type === 'sprite' ? "_19pk1n1a _2hwxidpf _otyr1n1a _18u0idpf _1e0c1o8l _jr50g2xd _ylp71o8l _pkaxgktf _1hdcgktf _109tnkob _tn8j1dpa _3naf1dpa _nt751r31 _49pcglyw _1hvw1o36 _7ehi1y54 _491113zc" : type === 'unicode' ? "_19pk1n1a _2hwxidpf _otyr1n1a _18u0idpf _bfhk1j28 _1e0c1o8l _s7n4nkob _tn8j1dpa _3naf1dpa _160jewfl _1theewfl _1kogh2mm _qyp0ewfl _nt751r31 _49pcglyw _1hvw1o36 _7ehi1y54 _491113zc" : "_2rkofajl _19pk1n1a _2hwxidpf _otyr1n1a _18u0idpf _bfhk1j28 _1e0c1o8l _s7n4nkob _v4pn1ule _tn8j1dpa _3naf1dpa _160jewfl _1theewfl _1kogh2mm _qyp0ewfl _nt751r31 _49pcglyw _1hvw1o36 _7ehi1y54 _491113zc", className]),
481
- onKeyDown: event => handleKeyDown(props, event),
482
- onMouseDown: event => {
482
+ onKeyDown: shouldBeInteractive || !expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? event => handleKeyDown(props, event) : undefined,
483
+ onMouseDown: shouldBeInteractive || !expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? event => {
483
484
  handleMouseDown(props, event);
484
- },
485
+ } : undefined,
485
486
  onMouseEnter: event => {
486
487
  handleMouseMove(props, event);
487
488
  },
488
489
  onFocus: event => {
489
490
  handleFocus(props, event);
490
491
  },
491
- title: showTooltip ? emoji.shortName : undefined // TODO: COLLAB-2351 - use @atlaskit/Tooltip in future for non-deletable emoji if enabled showTooltip
492
+ title: showTooltip && !expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? emoji.shortName : undefined
492
493
  }, other), children);
494
+ if (tooltipContent) {
495
+ return /*#__PURE__*/React.createElement(Tooltip, {
496
+ content: tooltipContent,
497
+ tag: "span"
498
+ }, emojiSpan);
499
+ }
500
+ return emojiSpan;
493
501
  });
494
502
  export const Emoji = props => {
495
503
  const {
@@ -9,7 +9,7 @@ const createEvent = (eventType, action, actionSubject, actionSubjectId, attribut
9
9
  actionSubjectId,
10
10
  attributes: {
11
11
  packageName: "@atlaskit/emoji",
12
- packageVersion: "71.4.2",
12
+ packageVersion: "71.4.3",
13
13
  ...attributes
14
14
  }
15
15
  });
@@ -2,17 +2,40 @@ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equ
2
2
  const KEYCAP_COMBINING = 0x20e3;
3
3
  const VARIATION_SELECTOR_16 = 0xfe0f;
4
4
  const VARIATION_SELECTOR_15 = 0xfe0e;
5
+ const EMOJI_MODIFIER_START = 0x1f3fb;
6
+ const EMOJI_MODIFIER_END = 0x1f3ff;
7
+ const REGIONAL_INDICATOR_START = 0x1f1e6;
8
+ const REGIONAL_INDICATOR_END = 0x1f1ff;
9
+ const FLAG_BASES = new Set([0x1f38c, 0x1f3c1, 0x1f3f3, 0x1f3f4, 0x1f6a9]);
5
10
 
6
- // Text-presentation-default emoji: BMP characters (U+0000–U+FFFF) that are
7
- // valid emoji but lack the Emoji_Presentation property render as text without
8
- // U+FE0F. Supplementary-plane characters (U+10000+) always render as emoji
9
- // regardless of the Emoji_Presentation property, so we skip those.
10
- //
11
- // This set is derived from Unicode 14.0 emoji-data.txt: all BMP code points
12
- // that have the Emoji property but NOT the Emoji_Presentation property.
13
- // Using an explicit lookup table avoids the \p{} Unicode property escape
14
- // syntax (TS1501) which requires a specific TypeScript build target.
15
- const TEXT_DEFAULT_EMOJI = new Set([0x0023, 0x002a, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x00a9, 0x00ae, 0x203c, 0x2049, 0x2122, 0x2139, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x21a9, 0x21aa, 0x2328, 0x23cf, 0x23ed, 0x23ee, 0x23ef, 0x23f1, 0x23f2, 0x23f8, 0x23f9, 0x23fa, 0x24c2, 0x25aa, 0x25ab, 0x25b6, 0x25c0, 0x25fb, 0x25fc, 0x2600, 0x2601, 0x2602, 0x2603, 0x2604, 0x260e, 0x2611, 0x2618, 0x261d, 0x2620, 0x2622, 0x2623, 0x2626, 0x262a, 0x262e, 0x262f, 0x2638, 0x2639, 0x263a, 0x2640, 0x2642, 0x265f, 0x2660, 0x2663, 0x2665, 0x2666, 0x2668, 0x267b, 0x267e, 0x2692, 0x2694, 0x2695, 0x2696, 0x2697, 0x2699, 0x269b, 0x269c, 0x26a0, 0x26a7, 0x26b0, 0x26b1, 0x26c8, 0x26cf, 0x26d1, 0x26d3, 0x26e9, 0x26f0, 0x26f1, 0x26f4, 0x26f7, 0x26f8, 0x26f9, 0x2702, 0x2708, 0x2709, 0x270c, 0x270d, 0x270f, 0x2712, 0x2714, 0x2716, 0x271d, 0x2721, 0x2733, 0x2734, 0x2744, 0x2747, 0x2763, 0x2764, 0x27a1, 0x2934, 0x2935, 0x2b05, 0x2b06, 0x2b07, 0x3030, 0x303d, 0x3297, 0x3299]);
11
+ // Emoji variation bases from Unicode 14.0 emoji-variation-sequences.txt.
12
+ // These are the code points with a defined U+FE0F emoji-style variant.
13
+ const EMOJI_VARIATION_BASES = new Set([0x0023, 0x002a, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x00a9, 0x00ae, 0x203c, 0x2049, 0x2122, 0x2139, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x21a9, 0x21aa, 0x2328, 0x23cf, 0x23ed, 0x23ee, 0x23ef, 0x23f1, 0x23f2, 0x23f8, 0x23f9, 0x23fa, 0x24c2, 0x25aa, 0x25ab, 0x25b6, 0x25c0, 0x25fb, 0x25fc, 0x2600, 0x2601, 0x2602, 0x2603, 0x2604, 0x260e, 0x2611, 0x2618, 0x261d, 0x2620, 0x2622, 0x2623, 0x2626, 0x262a, 0x262e, 0x262f, 0x2638, 0x2639, 0x263a, 0x2640, 0x2642, 0x265f, 0x2660, 0x2663, 0x2665, 0x2666, 0x2668, 0x267b, 0x267e, 0x2692, 0x2694, 0x2695, 0x2696, 0x2697, 0x2699, 0x269b, 0x269c, 0x26a0, 0x26a7, 0x26b0, 0x26b1, 0x26c8, 0x26cf, 0x26d1, 0x26d3, 0x26e9, 0x26f0, 0x26f1, 0x26f4, 0x26f7, 0x26f8, 0x26f9, 0x2702, 0x2708, 0x2709, 0x270c, 0x270d, 0x270f, 0x2712, 0x2714, 0x2716, 0x271d, 0x2721, 0x2733, 0x2734, 0x2744, 0x2747, 0x2763, 0x2764, 0x27a1, 0x2934, 0x2935, 0x2b05, 0x2b06, 0x2b07, 0x3030, 0x303d, 0x3297, 0x3299, 0x1f170, 0x1f171, 0x1f17e, 0x1f17f, 0x1f202, 0x1f237, 0x1f321, 0x1f324, 0x1f325, 0x1f326, 0x1f327, 0x1f328, 0x1f329, 0x1f32a, 0x1f32b, 0x1f32c, 0x1f336, 0x1f37d, 0x1f396, 0x1f397, 0x1f399, 0x1f39a, 0x1f39b, 0x1f39e, 0x1f39f, 0x1f3cb, 0x1f3cc, 0x1f3cd, 0x1f3ce, 0x1f3d4, 0x1f3d5, 0x1f3d6, 0x1f3d7, 0x1f3d8, 0x1f3d9, 0x1f3da, 0x1f3db, 0x1f3dc, 0x1f3dd, 0x1f3de, 0x1f3df, 0x1f3f3, 0x1f3f5, 0x1f3f7, 0x1f43f, 0x1f441, 0x1f4fd, 0x1f549, 0x1f54a, 0x1f56f, 0x1f570, 0x1f573, 0x1f574, 0x1f575, 0x1f576, 0x1f577, 0x1f578, 0x1f579, 0x1f587, 0x1f58a, 0x1f58b, 0x1f58c, 0x1f58d, 0x1f590, 0x1f5a5, 0x1f5a8, 0x1f5b1, 0x1f5b2, 0x1f5bc, 0x1f5c2, 0x1f5c3, 0x1f5c4, 0x1f5d1, 0x1f5d2, 0x1f5d3, 0x1f5dc, 0x1f5dd, 0x1f5de, 0x1f5e1, 0x1f5e3, 0x1f5e8, 0x1f5ef, 0x1f5f3, 0x1f5fa, 0x1f6cb, 0x1f6cd, 0x1f6ce, 0x1f6cf, 0x1f6e0, 0x1f6e1, 0x1f6e2, 0x1f6e3, 0x1f6e4, 0x1f6e5, 0x1f6e9, 0x1f6f0, 0x1f6f3, 0x231a, 0x231b, 0x23e9, 0x23ea, 0x23f3, 0x25fd, 0x25fe, 0x2614, 0x2615, 0x2648, 0x2649, 0x264a, 0x264b, 0x264c, 0x264d, 0x264e, 0x264f, 0x2650, 0x2651, 0x2652, 0x2653, 0x267f, 0x2693, 0x26a1, 0x26aa, 0x26ab, 0x26bd, 0x26be, 0x26c4, 0x26c5, 0x26d4, 0x26ea, 0x26f2, 0x26f3, 0x26f5, 0x26fa, 0x26fd, 0x2753, 0x2757, 0x2b1b, 0x2b1c, 0x2b50, 0x2b55, 0x1f004, 0x1f21a, 0x1f22f, 0x1f30d, 0x1f30e, 0x1f30f, 0x1f315, 0x1f31c, 0x1f378, 0x1f393, 0x1f3a7, 0x1f3ac, 0x1f3ad, 0x1f3ae, 0x1f3c2, 0x1f3c4, 0x1f3c6, 0x1f3ca, 0x1f3e0, 0x1f3ed, 0x1f408, 0x1f415, 0x1f41f, 0x1f426, 0x1f442, 0x1f446, 0x1f447, 0x1f448, 0x1f449, 0x1f44d, 0x1f44e, 0x1f453, 0x1f46a, 0x1f47d, 0x1f4a3, 0x1f4b0, 0x1f4b3, 0x1f4bb, 0x1f4bf, 0x1f4cb, 0x1f4da, 0x1f4df, 0x1f4e4, 0x1f4e5, 0x1f4e6, 0x1f4ea, 0x1f4eb, 0x1f4ec, 0x1f4ed, 0x1f4f7, 0x1f4f9, 0x1f4fa, 0x1f4fb, 0x1f508, 0x1f50d, 0x1f512, 0x1f513, 0x1f550, 0x1f551, 0x1f552, 0x1f553, 0x1f554, 0x1f555, 0x1f556, 0x1f557, 0x1f558, 0x1f559, 0x1f55a, 0x1f55b, 0x1f55c, 0x1f55d, 0x1f55e, 0x1f55f, 0x1f560, 0x1f561, 0x1f562, 0x1f563, 0x1f564, 0x1f565, 0x1f566, 0x1f567, 0x1f610, 0x1f687, 0x1f68d, 0x1f691, 0x1f694, 0x1f698, 0x1f6ad, 0x1f6b2, 0x1f6b9, 0x1f6ba, 0x1f6bc]);
14
+ const isEmojiModifier = codePoint => typeof codePoint === 'number' && codePoint >= EMOJI_MODIFIER_START && codePoint <= EMOJI_MODIFIER_END;
15
+ const shouldAddEmojiPresentationSelector = (codePoint, nextCodePoint) => EMOJI_VARIATION_BASES.has(codePoint) && nextCodePoint !== VARIATION_SELECTOR_16 && nextCodePoint !== VARIATION_SELECTOR_15 && nextCodePoint !== KEYCAP_COMBINING && !isEmojiModifier(nextCodePoint);
16
+ const isRegionalIndicator = codePoint => codePoint >= REGIONAL_INDICATOR_START && codePoint <= REGIONAL_INDICATOR_END;
17
+ const parseEmojiIdCodePoints = id => {
18
+ const segments = id.split('-');
19
+ const codePoints = [];
20
+ for (const segment of segments) {
21
+ if (!/^[0-9a-fA-F]+$/.test(segment)) {
22
+ return undefined;
23
+ }
24
+ const cp = parseInt(segment, 16);
25
+ // Valid Unicode scalar values: U+0000–U+10FFFF, excluding surrogates.
26
+ if (isNaN(cp) || cp < 0 || cp > 0x10ffff || cp >= 0xd800 && cp <= 0xdfff) {
27
+ return undefined;
28
+ }
29
+ codePoints.push(cp);
30
+ }
31
+ return codePoints.length ? codePoints : undefined;
32
+ };
33
+ const isFlagEmoji = codePoints => {
34
+ if (codePoints.length === 2 && codePoints.every(isRegionalIndicator)) {
35
+ return true;
36
+ }
37
+ return FLAG_BASES.has(codePoints[0]);
38
+ };
16
39
 
17
40
  /**
18
41
  * Converts an emoji ID (a hyphen-separated string of hex Unicode code points,
@@ -22,30 +45,24 @@ const TEXT_DEFAULT_EMOJI = new Set([0x0023, 0x002a, 0x0030, 0x0031, 0x0032, 0x00
22
45
  * Special handling:
23
46
  * - Keycap sequences: U+20E3 (COMBINING ENCLOSING KEYCAP) must be preceded by
24
47
  * U+FE0F (VARIATION SELECTOR-16) to form a well-defined keycap emoji.
25
- * - Text-default emoji (characters whose default presentation is text) must be
26
- * followed by U+FE0F to force emoji presentation.
48
+ * - Emoji variation bases are followed by U+FE0F to force emoji presentation.
49
+ * - Flags return `undefined` so callers use Twemoji instead of native emoji.
27
50
  *
28
51
  * @returns The Unicode string for the emoji, or `undefined` if the input is
29
52
  * invalid (empty string, non-hex segments, or out-of-range code points).
30
53
  */
31
54
  export function emojiIdToEmoji(id) {
55
+ if (!expValEqualsNoExposure('platform_use_unicode_emojis', 'isEnabled', true)) {
56
+ return undefined;
57
+ }
32
58
  if (!id) {
33
59
  return undefined;
34
60
  }
35
- const segments = id.split('-');
36
- const codePoints = [];
37
- for (const segment of segments) {
38
- if (!/^[0-9a-fA-F]+$/.test(segment)) {
39
- return undefined;
40
- }
41
- const cp = parseInt(segment, 16);
42
- // Valid Unicode scalar values: 0x0000–0x10FFFF, excluding surrogates 0xD800–0xDFFF
43
- if (isNaN(cp) || cp < 0 || cp > 0x10ffff || cp >= 0xd800 && cp <= 0xdfff) {
44
- return undefined;
45
- }
46
- codePoints.push(cp);
61
+ const codePoints = parseEmojiIdCodePoints(id);
62
+ if (!codePoints) {
63
+ return undefined;
47
64
  }
48
- if (codePoints.length === 0) {
65
+ if (isFlagEmoji(codePoints)) {
49
66
  return undefined;
50
67
  }
51
68
  const processedCodePoints = [];
@@ -59,22 +76,11 @@ export function emojiIdToEmoji(id) {
59
76
  if (next === KEYCAP_COMBINING && cp !== VARIATION_SELECTOR_16) {
60
77
  processedCodePoints.push(VARIATION_SELECTOR_16);
61
78
  }
62
- }
63
79
 
64
- // Text-presentation-default emoji: characters that are valid emoji but whose
65
- // default rendering is text (e.g. etc.) must be followed by
66
- // U+FE0F (VARIATION SELECTOR-16) to force emoji presentation.
67
- if (expValEqualsNoExposure('platform_use_unicode_emojis', 'isEnabled', true)) {
68
- const lastCodePoint = processedCodePoints[processedCodePoints.length - 1];
69
- const alreadyHasVariationSelector = lastCodePoint === VARIATION_SELECTOR_16 || lastCodePoint === VARIATION_SELECTOR_15 ||
70
- // Keycap sequences end with U+20E3 — they already have U+FE0F inserted inline
71
- // and must not have another variation selector appended at the end.
72
- lastCodePoint === KEYCAP_COMBINING;
73
- if (!alreadyHasVariationSelector) {
74
- const baseCodePoint = codePoints[0];
75
- if (codePoints.length === 1 && baseCodePoint <= 0xffff && TEXT_DEFAULT_EMOJI.has(baseCodePoint)) {
76
- processedCodePoints.push(VARIATION_SELECTOR_16);
77
- }
80
+ // Emoji variation bases need U+FE0F immediately after the base code point,
81
+ // including when the base appears inside a ZWJ sequence.
82
+ if (shouldAddEmojiPresentationSelector(cp, next)) {
83
+ processedCodePoints.push(VARIATION_SELECTOR_16);
78
84
  }
79
85
  }
80
86
  try {
@@ -492,29 +492,37 @@ export var EmojiNodeWrapper = /*#__PURE__*/forwardRef(function (props, ref) {
492
492
  var ariaLabel = intl && fg('platform_change_emoji_button_label') ? intl.formatMessage(messages.changeEmojiShortnameButtonLabel, {
493
493
  shortName: (_emoji$name = emoji.name) !== null && _emoji$name !== void 0 ? _emoji$name : emoji.shortName
494
494
  }) : editorEmoji ? undefined : emoji.shortName;
495
- return /*#__PURE__*/React.createElement("span", _extends({
495
+ var tooltipContent = showTooltip && expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? emoji.shortName || emoji.name || undefined : undefined;
496
+ var emojiSpan = /*#__PURE__*/React.createElement("span", _extends({
496
497
  role: editorEmoji ? undefined : shouldBeInteractive ? 'button' : ariaLabel ? 'img' : 'presentation',
497
498
  "aria-label": ariaLabel,
498
499
  ref: ref,
499
500
  "data-testid": "".concat(type, "-emoji-").concat(emoji.shortName),
500
501
  "data-emoji-type": type,
501
- tabIndex: shouldBeInteractive ? tabIndex || 0 : undefined,
502
+ tabIndex: shouldBeInteractive ? tabIndex || 0 : tooltipContent ? 0 : undefined,
502
503
  // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
503
504
  className: ax([type === 'sprite' ? "_19pk1n1a _2hwxidpf _otyr1n1a _18u0idpf _1e0c1o8l _jr50g2xd _ylp71o8l _pkaxgktf _1hdcgktf _109tnkob _tn8j1dpa _3naf1dpa _nt751r31 _49pcglyw _1hvw1o36 _7ehi1y54 _491113zc" : type === 'unicode' ? "_19pk1n1a _2hwxidpf _otyr1n1a _18u0idpf _bfhk1j28 _1e0c1o8l _s7n4nkob _tn8j1dpa _3naf1dpa _160jewfl _1theewfl _1kogh2mm _qyp0ewfl _nt751r31 _49pcglyw _1hvw1o36 _7ehi1y54 _491113zc" : "_2rkofajl _19pk1n1a _2hwxidpf _otyr1n1a _18u0idpf _bfhk1j28 _1e0c1o8l _s7n4nkob _v4pn1ule _tn8j1dpa _3naf1dpa _160jewfl _1theewfl _1kogh2mm _qyp0ewfl _nt751r31 _49pcglyw _1hvw1o36 _7ehi1y54 _491113zc", className]),
504
- onKeyDown: function onKeyDown(event) {
505
+ onKeyDown: shouldBeInteractive || !expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? function (event) {
505
506
  return handleKeyDown(props, event);
506
- },
507
- onMouseDown: function onMouseDown(event) {
507
+ } : undefined,
508
+ onMouseDown: shouldBeInteractive || !expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? function (event) {
508
509
  handleMouseDown(props, event);
509
- },
510
+ } : undefined,
510
511
  onMouseEnter: function onMouseEnter(event) {
511
512
  handleMouseMove(props, event);
512
513
  },
513
514
  onFocus: function onFocus(event) {
514
515
  handleFocus(props, event);
515
516
  },
516
- title: showTooltip ? emoji.shortName : undefined // TODO: COLLAB-2351 - use @atlaskit/Tooltip in future for non-deletable emoji if enabled showTooltip
517
+ title: showTooltip && !expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true) ? emoji.shortName : undefined
517
518
  }, other), children);
519
+ if (tooltipContent) {
520
+ return /*#__PURE__*/React.createElement(Tooltip, {
521
+ content: tooltipContent,
522
+ tag: "span"
523
+ }, emojiSpan);
524
+ }
525
+ return emojiSpan;
518
526
  });
519
527
  export var Emoji = function Emoji(props) {
520
528
  var emoji = props.emoji;
@@ -14,7 +14,7 @@ var createEvent = function createEvent(eventType, action, actionSubject, actionS
14
14
  actionSubjectId: actionSubjectId,
15
15
  attributes: _objectSpread({
16
16
  packageName: "@atlaskit/emoji",
17
- packageVersion: "71.4.2"
17
+ packageVersion: "71.4.3"
18
18
  }, attributes)
19
19
  };
20
20
  };
@@ -5,36 +5,25 @@ import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equ
5
5
  var KEYCAP_COMBINING = 0x20e3;
6
6
  var VARIATION_SELECTOR_16 = 0xfe0f;
7
7
  var VARIATION_SELECTOR_15 = 0xfe0e;
8
+ var EMOJI_MODIFIER_START = 0x1f3fb;
9
+ var EMOJI_MODIFIER_END = 0x1f3ff;
10
+ var REGIONAL_INDICATOR_START = 0x1f1e6;
11
+ var REGIONAL_INDICATOR_END = 0x1f1ff;
12
+ var FLAG_BASES = new Set([0x1f38c, 0x1f3c1, 0x1f3f3, 0x1f3f4, 0x1f6a9]);
8
13
 
9
- // Text-presentation-default emoji: BMP characters (U+0000–U+FFFF) that are
10
- // valid emoji but lack the Emoji_Presentation property render as text without
11
- // U+FE0F. Supplementary-plane characters (U+10000+) always render as emoji
12
- // regardless of the Emoji_Presentation property, so we skip those.
13
- //
14
- // This set is derived from Unicode 14.0 emoji-data.txt: all BMP code points
15
- // that have the Emoji property but NOT the Emoji_Presentation property.
16
- // Using an explicit lookup table avoids the \p{} Unicode property escape
17
- // syntax (TS1501) which requires a specific TypeScript build target.
18
- var TEXT_DEFAULT_EMOJI = new Set([0x0023, 0x002a, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x00a9, 0x00ae, 0x203c, 0x2049, 0x2122, 0x2139, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x21a9, 0x21aa, 0x2328, 0x23cf, 0x23ed, 0x23ee, 0x23ef, 0x23f1, 0x23f2, 0x23f8, 0x23f9, 0x23fa, 0x24c2, 0x25aa, 0x25ab, 0x25b6, 0x25c0, 0x25fb, 0x25fc, 0x2600, 0x2601, 0x2602, 0x2603, 0x2604, 0x260e, 0x2611, 0x2618, 0x261d, 0x2620, 0x2622, 0x2623, 0x2626, 0x262a, 0x262e, 0x262f, 0x2638, 0x2639, 0x263a, 0x2640, 0x2642, 0x265f, 0x2660, 0x2663, 0x2665, 0x2666, 0x2668, 0x267b, 0x267e, 0x2692, 0x2694, 0x2695, 0x2696, 0x2697, 0x2699, 0x269b, 0x269c, 0x26a0, 0x26a7, 0x26b0, 0x26b1, 0x26c8, 0x26cf, 0x26d1, 0x26d3, 0x26e9, 0x26f0, 0x26f1, 0x26f4, 0x26f7, 0x26f8, 0x26f9, 0x2702, 0x2708, 0x2709, 0x270c, 0x270d, 0x270f, 0x2712, 0x2714, 0x2716, 0x271d, 0x2721, 0x2733, 0x2734, 0x2744, 0x2747, 0x2763, 0x2764, 0x27a1, 0x2934, 0x2935, 0x2b05, 0x2b06, 0x2b07, 0x3030, 0x303d, 0x3297, 0x3299]);
19
-
20
- /**
21
- * Converts an emoji ID (a hyphen-separated string of hex Unicode code points,
22
- * e.g. `"1f44d"`, `"1f44d-1f3fb"`, `"1f468-200d-1f469-200d-1f467"`) into its
23
- * corresponding Unicode string.
24
- *
25
- * Special handling:
26
- * - Keycap sequences: U+20E3 (COMBINING ENCLOSING KEYCAP) must be preceded by
27
- * U+FE0F (VARIATION SELECTOR-16) to form a well-defined keycap emoji.
28
- * - Text-default emoji (characters whose default presentation is text) must be
29
- * followed by U+FE0F to force emoji presentation.
30
- *
31
- * @returns The Unicode string for the emoji, or `undefined` if the input is
32
- * invalid (empty string, non-hex segments, or out-of-range code points).
33
- */
34
- export function emojiIdToEmoji(id) {
35
- if (!id) {
36
- return undefined;
37
- }
14
+ // Emoji variation bases from Unicode 14.0 emoji-variation-sequences.txt.
15
+ // These are the code points with a defined U+FE0F emoji-style variant.
16
+ var EMOJI_VARIATION_BASES = new Set([0x0023, 0x002a, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x00a9, 0x00ae, 0x203c, 0x2049, 0x2122, 0x2139, 0x2194, 0x2195, 0x2196, 0x2197, 0x2198, 0x2199, 0x21a9, 0x21aa, 0x2328, 0x23cf, 0x23ed, 0x23ee, 0x23ef, 0x23f1, 0x23f2, 0x23f8, 0x23f9, 0x23fa, 0x24c2, 0x25aa, 0x25ab, 0x25b6, 0x25c0, 0x25fb, 0x25fc, 0x2600, 0x2601, 0x2602, 0x2603, 0x2604, 0x260e, 0x2611, 0x2618, 0x261d, 0x2620, 0x2622, 0x2623, 0x2626, 0x262a, 0x262e, 0x262f, 0x2638, 0x2639, 0x263a, 0x2640, 0x2642, 0x265f, 0x2660, 0x2663, 0x2665, 0x2666, 0x2668, 0x267b, 0x267e, 0x2692, 0x2694, 0x2695, 0x2696, 0x2697, 0x2699, 0x269b, 0x269c, 0x26a0, 0x26a7, 0x26b0, 0x26b1, 0x26c8, 0x26cf, 0x26d1, 0x26d3, 0x26e9, 0x26f0, 0x26f1, 0x26f4, 0x26f7, 0x26f8, 0x26f9, 0x2702, 0x2708, 0x2709, 0x270c, 0x270d, 0x270f, 0x2712, 0x2714, 0x2716, 0x271d, 0x2721, 0x2733, 0x2734, 0x2744, 0x2747, 0x2763, 0x2764, 0x27a1, 0x2934, 0x2935, 0x2b05, 0x2b06, 0x2b07, 0x3030, 0x303d, 0x3297, 0x3299, 0x1f170, 0x1f171, 0x1f17e, 0x1f17f, 0x1f202, 0x1f237, 0x1f321, 0x1f324, 0x1f325, 0x1f326, 0x1f327, 0x1f328, 0x1f329, 0x1f32a, 0x1f32b, 0x1f32c, 0x1f336, 0x1f37d, 0x1f396, 0x1f397, 0x1f399, 0x1f39a, 0x1f39b, 0x1f39e, 0x1f39f, 0x1f3cb, 0x1f3cc, 0x1f3cd, 0x1f3ce, 0x1f3d4, 0x1f3d5, 0x1f3d6, 0x1f3d7, 0x1f3d8, 0x1f3d9, 0x1f3da, 0x1f3db, 0x1f3dc, 0x1f3dd, 0x1f3de, 0x1f3df, 0x1f3f3, 0x1f3f5, 0x1f3f7, 0x1f43f, 0x1f441, 0x1f4fd, 0x1f549, 0x1f54a, 0x1f56f, 0x1f570, 0x1f573, 0x1f574, 0x1f575, 0x1f576, 0x1f577, 0x1f578, 0x1f579, 0x1f587, 0x1f58a, 0x1f58b, 0x1f58c, 0x1f58d, 0x1f590, 0x1f5a5, 0x1f5a8, 0x1f5b1, 0x1f5b2, 0x1f5bc, 0x1f5c2, 0x1f5c3, 0x1f5c4, 0x1f5d1, 0x1f5d2, 0x1f5d3, 0x1f5dc, 0x1f5dd, 0x1f5de, 0x1f5e1, 0x1f5e3, 0x1f5e8, 0x1f5ef, 0x1f5f3, 0x1f5fa, 0x1f6cb, 0x1f6cd, 0x1f6ce, 0x1f6cf, 0x1f6e0, 0x1f6e1, 0x1f6e2, 0x1f6e3, 0x1f6e4, 0x1f6e5, 0x1f6e9, 0x1f6f0, 0x1f6f3, 0x231a, 0x231b, 0x23e9, 0x23ea, 0x23f3, 0x25fd, 0x25fe, 0x2614, 0x2615, 0x2648, 0x2649, 0x264a, 0x264b, 0x264c, 0x264d, 0x264e, 0x264f, 0x2650, 0x2651, 0x2652, 0x2653, 0x267f, 0x2693, 0x26a1, 0x26aa, 0x26ab, 0x26bd, 0x26be, 0x26c4, 0x26c5, 0x26d4, 0x26ea, 0x26f2, 0x26f3, 0x26f5, 0x26fa, 0x26fd, 0x2753, 0x2757, 0x2b1b, 0x2b1c, 0x2b50, 0x2b55, 0x1f004, 0x1f21a, 0x1f22f, 0x1f30d, 0x1f30e, 0x1f30f, 0x1f315, 0x1f31c, 0x1f378, 0x1f393, 0x1f3a7, 0x1f3ac, 0x1f3ad, 0x1f3ae, 0x1f3c2, 0x1f3c4, 0x1f3c6, 0x1f3ca, 0x1f3e0, 0x1f3ed, 0x1f408, 0x1f415, 0x1f41f, 0x1f426, 0x1f442, 0x1f446, 0x1f447, 0x1f448, 0x1f449, 0x1f44d, 0x1f44e, 0x1f453, 0x1f46a, 0x1f47d, 0x1f4a3, 0x1f4b0, 0x1f4b3, 0x1f4bb, 0x1f4bf, 0x1f4cb, 0x1f4da, 0x1f4df, 0x1f4e4, 0x1f4e5, 0x1f4e6, 0x1f4ea, 0x1f4eb, 0x1f4ec, 0x1f4ed, 0x1f4f7, 0x1f4f9, 0x1f4fa, 0x1f4fb, 0x1f508, 0x1f50d, 0x1f512, 0x1f513, 0x1f550, 0x1f551, 0x1f552, 0x1f553, 0x1f554, 0x1f555, 0x1f556, 0x1f557, 0x1f558, 0x1f559, 0x1f55a, 0x1f55b, 0x1f55c, 0x1f55d, 0x1f55e, 0x1f55f, 0x1f560, 0x1f561, 0x1f562, 0x1f563, 0x1f564, 0x1f565, 0x1f566, 0x1f567, 0x1f610, 0x1f687, 0x1f68d, 0x1f691, 0x1f694, 0x1f698, 0x1f6ad, 0x1f6b2, 0x1f6b9, 0x1f6ba, 0x1f6bc]);
17
+ var isEmojiModifier = function isEmojiModifier(codePoint) {
18
+ return typeof codePoint === 'number' && codePoint >= EMOJI_MODIFIER_START && codePoint <= EMOJI_MODIFIER_END;
19
+ };
20
+ var shouldAddEmojiPresentationSelector = function shouldAddEmojiPresentationSelector(codePoint, nextCodePoint) {
21
+ return EMOJI_VARIATION_BASES.has(codePoint) && nextCodePoint !== VARIATION_SELECTOR_16 && nextCodePoint !== VARIATION_SELECTOR_15 && nextCodePoint !== KEYCAP_COMBINING && !isEmojiModifier(nextCodePoint);
22
+ };
23
+ var isRegionalIndicator = function isRegionalIndicator(codePoint) {
24
+ return codePoint >= REGIONAL_INDICATOR_START && codePoint <= REGIONAL_INDICATOR_END;
25
+ };
26
+ var parseEmojiIdCodePoints = function parseEmojiIdCodePoints(id) {
38
27
  var segments = id.split('-');
39
28
  var codePoints = [];
40
29
  var _iterator = _createForOfIteratorHelper(segments),
@@ -45,19 +34,53 @@ export function emojiIdToEmoji(id) {
45
34
  if (!/^[0-9a-fA-F]+$/.test(segment)) {
46
35
  return undefined;
47
36
  }
48
- var _cp = parseInt(segment, 16);
49
- // Valid Unicode scalar values: 0x00000x10FFFF, excluding surrogates 0xD800–0xDFFF
50
- if (isNaN(_cp) || _cp < 0 || _cp > 0x10ffff || _cp >= 0xd800 && _cp <= 0xdfff) {
37
+ var cp = parseInt(segment, 16);
38
+ // Valid Unicode scalar values: U+0000U+10FFFF, excluding surrogates.
39
+ if (isNaN(cp) || cp < 0 || cp > 0x10ffff || cp >= 0xd800 && cp <= 0xdfff) {
51
40
  return undefined;
52
41
  }
53
- codePoints.push(_cp);
42
+ codePoints.push(cp);
54
43
  }
55
44
  } catch (err) {
56
45
  _iterator.e(err);
57
46
  } finally {
58
47
  _iterator.f();
59
48
  }
60
- if (codePoints.length === 0) {
49
+ return codePoints.length ? codePoints : undefined;
50
+ };
51
+ var isFlagEmoji = function isFlagEmoji(codePoints) {
52
+ if (codePoints.length === 2 && codePoints.every(isRegionalIndicator)) {
53
+ return true;
54
+ }
55
+ return FLAG_BASES.has(codePoints[0]);
56
+ };
57
+
58
+ /**
59
+ * Converts an emoji ID (a hyphen-separated string of hex Unicode code points,
60
+ * e.g. `"1f44d"`, `"1f44d-1f3fb"`, `"1f468-200d-1f469-200d-1f467"`) into its
61
+ * corresponding Unicode string.
62
+ *
63
+ * Special handling:
64
+ * - Keycap sequences: U+20E3 (COMBINING ENCLOSING KEYCAP) must be preceded by
65
+ * U+FE0F (VARIATION SELECTOR-16) to form a well-defined keycap emoji.
66
+ * - Emoji variation bases are followed by U+FE0F to force emoji presentation.
67
+ * - Flags return `undefined` so callers use Twemoji instead of native emoji.
68
+ *
69
+ * @returns The Unicode string for the emoji, or `undefined` if the input is
70
+ * invalid (empty string, non-hex segments, or out-of-range code points).
71
+ */
72
+ export function emojiIdToEmoji(id) {
73
+ if (!expValEqualsNoExposure('platform_use_unicode_emojis', 'isEnabled', true)) {
74
+ return undefined;
75
+ }
76
+ if (!id) {
77
+ return undefined;
78
+ }
79
+ var codePoints = parseEmojiIdCodePoints(id);
80
+ if (!codePoints) {
81
+ return undefined;
82
+ }
83
+ if (isFlagEmoji(codePoints)) {
61
84
  return undefined;
62
85
  }
63
86
  var processedCodePoints = [];
@@ -71,22 +94,11 @@ export function emojiIdToEmoji(id) {
71
94
  if (next === KEYCAP_COMBINING && cp !== VARIATION_SELECTOR_16) {
72
95
  processedCodePoints.push(VARIATION_SELECTOR_16);
73
96
  }
74
- }
75
97
 
76
- // Text-presentation-default emoji: characters that are valid emoji but whose
77
- // default rendering is text (e.g. etc.) must be followed by
78
- // U+FE0F (VARIATION SELECTOR-16) to force emoji presentation.
79
- if (expValEqualsNoExposure('platform_use_unicode_emojis', 'isEnabled', true)) {
80
- var lastCodePoint = processedCodePoints[processedCodePoints.length - 1];
81
- var alreadyHasVariationSelector = lastCodePoint === VARIATION_SELECTOR_16 || lastCodePoint === VARIATION_SELECTOR_15 ||
82
- // Keycap sequences end with U+20E3 — they already have U+FE0F inserted inline
83
- // and must not have another variation selector appended at the end.
84
- lastCodePoint === KEYCAP_COMBINING;
85
- if (!alreadyHasVariationSelector) {
86
- var baseCodePoint = codePoints[0];
87
- if (codePoints.length === 1 && baseCodePoint <= 0xffff && TEXT_DEFAULT_EMOJI.has(baseCodePoint)) {
88
- processedCodePoints.push(VARIATION_SELECTOR_16);
89
- }
98
+ // Emoji variation bases need U+FE0F immediately after the base code point,
99
+ // including when the base appears inside a ZWJ sequence.
100
+ if (shouldAddEmojiPresentationSelector(cp, next)) {
101
+ processedCodePoints.push(VARIATION_SELECTOR_16);
90
102
  }
91
103
  }
92
104
  try {
@@ -6,8 +6,8 @@
6
6
  * Special handling:
7
7
  * - Keycap sequences: U+20E3 (COMBINING ENCLOSING KEYCAP) must be preceded by
8
8
  * U+FE0F (VARIATION SELECTOR-16) to form a well-defined keycap emoji.
9
- * - Text-default emoji (characters whose default presentation is text) must be
10
- * followed by U+FE0F to force emoji presentation.
9
+ * - Emoji variation bases are followed by U+FE0F to force emoji presentation.
10
+ * - Flags return `undefined` so callers use Twemoji instead of native emoji.
11
11
  *
12
12
  * @returns The Unicode string for the emoji, or `undefined` if the input is
13
13
  * invalid (empty string, non-hex segments, or out-of-range code points).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/emoji",
3
- "version": "71.4.3",
3
+ "version": "71.4.4",
4
4
  "description": "Fabric emoji React components",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -38,7 +38,7 @@
38
38
  "@atlaskit/form": "^16.0.0",
39
39
  "@atlaskit/heading": "^6.0.0",
40
40
  "@atlaskit/icon": "^36.0.0",
41
- "@atlaskit/icon-lab": "^7.0.0",
41
+ "@atlaskit/icon-lab": "^7.1.0",
42
42
  "@atlaskit/image": "^4.0.0",
43
43
  "@atlaskit/media-client": "^37.1.0",
44
44
  "@atlaskit/media-client-react": "^6.0.0",
@@ -49,8 +49,8 @@
49
49
  "@atlaskit/spinner": "^20.0.0",
50
50
  "@atlaskit/textfield": "^9.0.0",
51
51
  "@atlaskit/theme": "^26.0.0",
52
- "@atlaskit/tmp-editor-statsig": "^109.2.0",
53
- "@atlaskit/tokens": "^14.0.0",
52
+ "@atlaskit/tmp-editor-statsig": "^110.0.0",
53
+ "@atlaskit/tokens": "^15.0.0",
54
54
  "@atlaskit/tooltip": "^23.0.0",
55
55
  "@atlaskit/ufo": "^1.0.0",
56
56
  "@atlaskit/util-service-support": "^7.0.0",
@@ -61,7 +61,7 @@
61
61
  "js-search": "^2.0.0",
62
62
  "lru_map": "^0.4.1",
63
63
  "react-focus-lock": "^2.9.5",
64
- "react-intersection-observer": "^8.26.2",
64
+ "react-intersection-observer": "^9.16.0",
65
65
  "react-loadable": "^5.1.0",
66
66
  "use-debounce": "^3.4.0",
67
67
  "uuid": "^3.1.0"