@atlaskit/emoji 70.7.2 → 70.9.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,19 @@
1
1
  # @atlaskit/emoji
2
2
 
3
+ ## 70.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`54ec962646a89`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/54ec962646a89) -
8
+ Close upload emoji on category selected + hide selected category on upload emoji open
9
+
10
+ ## 70.8.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [`7bcc8a1cb49b5`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/7bcc8a1cb49b5) -
15
+ Add unicode + emoji utilities
16
+
3
17
  ## 70.7.2
4
18
 
5
19
  ### Patch Changes
@@ -163,5 +163,5 @@ var getHeight = function getHeight(fitToHeight) {
163
163
  return getPixelRatio() > 1 ? fitToHeight * 2 : fitToHeight;
164
164
  };
165
165
  var shouldUseAltRepresentation = exports.shouldUseAltRepresentation = function shouldUseAltRepresentation(emoji, fitToHeight) {
166
- return !!(fitToHeight && emoji.altRepresentation && getHeight(fitToHeight) > emoji.representation.height);
166
+ return !!(fitToHeight && emoji.altRepresentation && emoji.representation && 'height' in emoji.representation && getHeight(fitToHeight) > emoji.representation.height);
167
167
  };
@@ -148,11 +148,12 @@ var CachingMediaEmoji = exports.CachingMediaEmoji = function CachingMediaEmoji(p
148
148
  onLoadError: handleLoadError
149
149
  }));
150
150
  }
151
+ var imageRepresentation = representation && 'height' in representation ? representation : undefined;
151
152
  return /*#__PURE__*/_react.default.createElement(_EmojiPlaceholder.default, {
152
153
  size: fitToHeight || placeholderSize,
153
154
  shortName: shortName,
154
155
  showTooltip: showTooltip,
155
- representation: representation
156
+ representation: imageRepresentation
156
157
  });
157
158
  };
158
159
  var _default_1 = /*#__PURE__*/(0, _react.memo)(CachingEmoji);
@@ -160,10 +160,14 @@ var EmojiPickerComponent = function EmojiPickerComponent(_ref) {
160
160
  if (isProgrammaticScroll.current && (0, _platformFeatureFlags.fg)('platform_emoji_picker_refresh')) {
161
161
  return;
162
162
  }
163
+ // Ignore scroll-driven category changes while the upload screen is open
164
+ if (uploading && (0, _platformFeatureFlags.fg)('platform_emoji_picker_refresh')) {
165
+ return;
166
+ }
163
167
  if (activeCategory !== category) {
164
168
  setActiveCategory(category);
165
169
  }
166
- }, [activeCategory]);
170
+ }, [activeCategory, uploading]);
167
171
  var calculateElapsedTime = function calculateElapsedTime() {
168
172
  return Date.now() - openTime.current;
169
173
  };
@@ -318,6 +322,12 @@ var EmojiPickerComponent = function EmojiPickerComponent(_ref) {
318
322
  if (!categoryId) {
319
323
  return;
320
324
  }
325
+
326
+ // If the upload screen is open, close it when a category is selected
327
+ if (uploading && (0, _platformFeatureFlags.fg)('platform_emoji_picker_refresh')) {
328
+ setUploading(false);
329
+ setUploadErrorMessage(undefined);
330
+ }
321
331
  emojiProvider.findInCategory(categoryId).then(function (emojisInCategory) {
322
332
  if (!disableCategories) {
323
333
  var newSelectedEmoji;
@@ -347,7 +357,7 @@ var EmojiPickerComponent = function EmojiPickerComponent(_ref) {
347
357
  }));
348
358
  }
349
359
  });
350
- }, [disableCategories, emojiPickerList, emojiProvider, fireAnalytics, selectedTone]);
360
+ }, [disableCategories, emojiPickerList, emojiProvider, fireAnalytics, selectedTone, uploading]);
351
361
  var recordUsageOnSelection = (0, _react.useMemo)(function () {
352
362
  return (0, _RecordSelectionDefault.createRecordSelectionDefault)(emojiProvider, onSelectWrapper, function (analytic) {
353
363
  return fireAnalytics(analytic(_types.SearchSourceTypes.PICKER));
@@ -575,7 +585,7 @@ var EmojiPickerComponent = function EmojiPickerComponent(_ref) {
575
585
  onKeyDown: suppressKeyPress,
576
586
  className: (0, _runtime.ax)(["_16jlkb7n _1o9zkb7n _i0dlf1ug _1reo15vq _18m915vq _1e0c1txw _2lx21bp4 _1bah1yb4 _1tkeidpf"])
577
587
  }, /*#__PURE__*/React.createElement(_CategorySelector.default, {
578
- activeCategoryId: activeCategory,
588
+ activeCategoryId: uploading && (0, _platformFeatureFlags.fg)('platform_emoji_picker_refresh') ? null : activeCategory,
579
589
  dynamicCategories: dynamicCategories,
580
590
  disableCategories: disableCategories,
581
591
  onCategorySelected: onCategorySelected
@@ -629,7 +639,7 @@ var EmojiPickerComponent = function EmojiPickerComponent(_ref) {
629
639
  "--_gsvyy7": (0, _runtime.ix)("".concat(emojiPickerWidth, "px"))
630
640
  }
631
641
  }, /*#__PURE__*/React.createElement(_CategorySelector.default, {
632
- activeCategoryId: activeCategory,
642
+ activeCategoryId: uploading && (0, _platformFeatureFlags.fg)('platform_emoji_picker_refresh') ? null : activeCategory,
633
643
  dynamicCategories: dynamicCategories,
634
644
  disableCategories: disableCategories,
635
645
  onCategorySelected: onCategorySelected
@@ -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: "70.7.1"
23
+ packageVersion: "70.8.0"
24
24
  }, attributes)
25
25
  };
26
26
  };
@@ -4,9 +4,25 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.emojiIdToEmoji = emojiIdToEmoji;
7
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
7
8
  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; } } }; }
8
9
  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; } }
9
10
  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; }
11
+ var KEYCAP_COMBINING = 0x20e3;
12
+ var VARIATION_SELECTOR_16 = 0xfe0f;
13
+ var VARIATION_SELECTOR_15 = 0xfe0e;
14
+
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
+
10
26
  /**
11
27
  * Converts an emoji ID (a hyphen-separated string of hex Unicode code points,
12
28
  * e.g. `"1f44d"`, `"1f44d-1f3fb"`, `"1f468-200d-1f469-200d-1f467"`) into its
@@ -50,22 +66,35 @@ function emojiIdToEmoji(id) {
50
66
  if (codePoints.length === 0) {
51
67
  return undefined;
52
68
  }
53
-
54
- // Keycap sequences: digit/# /* followed by U+20E3 need U+FE0F between them.
55
- // The keycap base characters are: 0x23 (#), 0x2A (*), 0x30–0x39 (0–9).
56
- var KEYCAP_COMBINING = 0x20e3;
57
- var VARIATION_SELECTOR_16 = 0xfe0f;
58
69
  var processedCodePoints = [];
59
70
  for (var i = 0; i < codePoints.length; i++) {
60
71
  var cp = codePoints[i];
61
72
  var next = codePoints[i + 1];
62
73
  processedCodePoints.push(cp);
63
74
 
64
- // Insert U+FE0F before U+20E3 if not already present
75
+ // Insert U+FE0F before U+20E3 if not already present.
76
+ // Keycap sequences: digit/# /* followed by U+20E3 need U+FE0F between them.
65
77
  if (next === KEYCAP_COMBINING && cp !== VARIATION_SELECTOR_16) {
66
78
  processedCodePoints.push(VARIATION_SELECTOR_16);
67
79
  }
68
80
  }
81
+
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, _platformFeatureFlags.fg)('platform_twemoji_removal_unicode_emojis')) {
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
+ }
96
+ }
97
+ }
69
98
  try {
70
99
  return String.fromCodePoint.apply(String, processedCodePoints);
71
100
  } catch (_unused) {
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.toOptionalEmojiId = exports.toEmojiId = exports.isSpriteServiceRepresentation = exports.isSpriteRepresentation = exports.isPromise = exports.isMessagesKey = exports.isMediaRepresentation = exports.isMediaEmoji = exports.isLoadedMediaEmoji = exports.isImageRepresentation = exports.isEmojiVariationDescription = exports.isEmojiIdEqual = exports.isEmojiDescriptionWithVariations = exports.isEmojiDescription = exports.hasDataURLImage = exports.getCategoryId = exports.convertMediaToImageRepresentation = exports.convertMediaToImageEmoji = exports.convertImageToMediaRepresentation = exports.containsEmojiId = exports.buildEmojiDescriptionWithAltRepresentation = void 0;
7
+ exports.toOptionalEmojiId = exports.toEmojiId = exports.isUnicodeRepresentation = exports.isSpriteServiceRepresentation = exports.isSpriteRepresentation = exports.isPromise = exports.isMessagesKey = exports.isMediaRepresentation = exports.isMediaEmoji = exports.isLoadedMediaEmoji = exports.isImageRepresentation = exports.isEmojiVariationDescription = exports.isEmojiIdEqual = exports.isEmojiDescriptionWithVariations = exports.isEmojiDescription = exports.hasDataURLImage = exports.getCategoryId = exports.convertMediaToImageRepresentation = exports.convertMediaToImageEmoji = exports.convertImageToMediaRepresentation = exports.containsEmojiId = exports.buildEmojiDescriptionWithAltRepresentation = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
9
  var _i18n = require("../components/i18n");
10
10
  var _constants = require("./constants");
@@ -22,6 +22,9 @@ var isImageRepresentation = exports.isImageRepresentation = function isImageRepr
22
22
  var isMediaRepresentation = exports.isMediaRepresentation = function isMediaRepresentation(rep) {
23
23
  return !!(rep && rep.mediaPath);
24
24
  };
25
+ var isUnicodeRepresentation = exports.isUnicodeRepresentation = function isUnicodeRepresentation(rep) {
26
+ return !!(rep && rep.unicodeEmoji);
27
+ };
25
28
  var isPromise = exports.isPromise = function isPromise(p) {
26
29
  return !!(p && p.then);
27
30
  };
@@ -156,4 +156,4 @@ export const denormaliseEmojiServiceResponse = emojiData => {
156
156
  };
157
157
  };
158
158
  const getHeight = fitToHeight => getPixelRatio() > 1 ? fitToHeight * 2 : fitToHeight;
159
- export const shouldUseAltRepresentation = (emoji, fitToHeight) => !!(fitToHeight && emoji.altRepresentation && getHeight(fitToHeight) > emoji.representation.height);
159
+ export const shouldUseAltRepresentation = (emoji, fitToHeight) => !!(fitToHeight && emoji.altRepresentation && emoji.representation && 'height' in emoji.representation && getHeight(fitToHeight) > emoji.representation.height);
@@ -135,11 +135,12 @@ export const CachingMediaEmoji = props => {
135
135
  onLoadError: handleLoadError
136
136
  }));
137
137
  }
138
+ const imageRepresentation = representation && 'height' in representation ? representation : undefined;
138
139
  return /*#__PURE__*/React.createElement(EmojiPlaceholder, {
139
140
  size: fitToHeight || placeholderSize,
140
141
  shortName: shortName,
141
142
  showTooltip: showTooltip,
142
- representation: representation
143
+ representation: imageRepresentation
143
144
  });
144
145
  };
145
146
  const _default_1 = /*#__PURE__*/memo(CachingEmoji);
@@ -102,10 +102,14 @@ const EmojiPickerComponent = ({
102
102
  if (isProgrammaticScroll.current && fg('platform_emoji_picker_refresh')) {
103
103
  return;
104
104
  }
105
+ // Ignore scroll-driven category changes while the upload screen is open
106
+ if (uploading && fg('platform_emoji_picker_refresh')) {
107
+ return;
108
+ }
105
109
  if (activeCategory !== category) {
106
110
  setActiveCategory(category);
107
111
  }
108
- }, [activeCategory]);
112
+ }, [activeCategory, uploading]);
109
113
  const calculateElapsedTime = () => {
110
114
  return Date.now() - openTime.current;
111
115
  };
@@ -262,6 +266,12 @@ const EmojiPickerComponent = ({
262
266
  if (!categoryId) {
263
267
  return;
264
268
  }
269
+
270
+ // If the upload screen is open, close it when a category is selected
271
+ if (uploading && fg('platform_emoji_picker_refresh')) {
272
+ setUploading(false);
273
+ setUploadErrorMessage(undefined);
274
+ }
265
275
  emojiProvider.findInCategory(categoryId).then(emojisInCategory => {
266
276
  if (!disableCategories) {
267
277
  let newSelectedEmoji;
@@ -291,7 +301,7 @@ const EmojiPickerComponent = ({
291
301
  }));
292
302
  }
293
303
  });
294
- }, [disableCategories, emojiPickerList, emojiProvider, fireAnalytics, selectedTone]);
304
+ }, [disableCategories, emojiPickerList, emojiProvider, fireAnalytics, selectedTone, uploading]);
295
305
  const recordUsageOnSelection = useMemo(() => createRecordSelectionDefault(emojiProvider, onSelectWrapper, analytic => fireAnalytics(analytic(SearchSourceTypes.PICKER))), [emojiProvider, fireAnalytics, onSelectWrapper]);
296
306
  const formattedErrorMessage = useMemo(() => uploadErrorMessage ? /*#__PURE__*/React.createElement(FormattedMessage, uploadErrorMessage) : null, [uploadErrorMessage]);
297
307
  const onFileChooserClicked = useCallback(() => {
@@ -485,7 +495,7 @@ const EmojiPickerComponent = ({
485
495
  onKeyDown: suppressKeyPress,
486
496
  className: ax(["_16jlkb7n _1o9zkb7n _i0dlf1ug _1reo15vq _18m915vq _1e0c1txw _2lx21bp4 _1bah1yb4 _1tkeidpf"])
487
497
  }, /*#__PURE__*/React.createElement(CategorySelector, {
488
- activeCategoryId: activeCategory,
498
+ activeCategoryId: uploading && fg('platform_emoji_picker_refresh') ? null : activeCategory,
489
499
  dynamicCategories: dynamicCategories,
490
500
  disableCategories: disableCategories,
491
501
  onCategorySelected: onCategorySelected
@@ -535,7 +545,7 @@ const EmojiPickerComponent = ({
535
545
  onKeyDown: suppressKeyPress,
536
546
  className: ax(["_19itahnd _2rkofajl _1e0c1txw _2lx21bp4 _1bah1yb4 _bfhk1bhr _16qs130s _4t3iaq3k _1bsb1edt _1ul91edt _c71l1y6z _kqswh2mm", showPreview || uploading && fg('platform_emoji_picker_refresh') ? withPreviewHeight[size] : withoutPreviewHeight[size]])
537
547
  }, /*#__PURE__*/React.createElement(CategorySelector, {
538
- activeCategoryId: activeCategory,
548
+ activeCategoryId: uploading && fg('platform_emoji_picker_refresh') ? null : activeCategory,
539
549
  dynamicCategories: dynamicCategories,
540
550
  disableCategories: disableCategories,
541
551
  onCategorySelected: onCategorySelected
@@ -9,7 +9,7 @@ const createEvent = (eventType, action, actionSubject, actionSubjectId, attribut
9
9
  actionSubjectId,
10
10
  attributes: {
11
11
  packageName: "@atlaskit/emoji",
12
- packageVersion: "70.7.1",
12
+ packageVersion: "70.8.0",
13
13
  ...attributes
14
14
  }
15
15
  });
@@ -1,3 +1,19 @@
1
+ import { fg } from '@atlaskit/platform-feature-flags';
2
+ const KEYCAP_COMBINING = 0x20e3;
3
+ const VARIATION_SELECTOR_16 = 0xfe0f;
4
+ const VARIATION_SELECTOR_15 = 0xfe0e;
5
+
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]);
16
+
1
17
  /**
2
18
  * Converts an emoji ID (a hyphen-separated string of hex Unicode code points,
3
19
  * e.g. `"1f44d"`, `"1f44d-1f3fb"`, `"1f468-200d-1f469-200d-1f467"`) into its
@@ -32,22 +48,35 @@ export function emojiIdToEmoji(id) {
32
48
  if (codePoints.length === 0) {
33
49
  return undefined;
34
50
  }
35
-
36
- // Keycap sequences: digit/# /* followed by U+20E3 need U+FE0F between them.
37
- // The keycap base characters are: 0x23 (#), 0x2A (*), 0x30–0x39 (0–9).
38
- const KEYCAP_COMBINING = 0x20e3;
39
- const VARIATION_SELECTOR_16 = 0xfe0f;
40
51
  const processedCodePoints = [];
41
52
  for (let i = 0; i < codePoints.length; i++) {
42
53
  const cp = codePoints[i];
43
54
  const next = codePoints[i + 1];
44
55
  processedCodePoints.push(cp);
45
56
 
46
- // Insert U+FE0F before U+20E3 if not already present
57
+ // Insert U+FE0F before U+20E3 if not already present.
58
+ // Keycap sequences: digit/# /* followed by U+20E3 need U+FE0F between them.
47
59
  if (next === KEYCAP_COMBINING && cp !== VARIATION_SELECTOR_16) {
48
60
  processedCodePoints.push(VARIATION_SELECTOR_16);
49
61
  }
50
62
  }
63
+
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 (fg('platform_twemoji_removal_unicode_emojis')) {
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
+ }
78
+ }
79
+ }
51
80
  try {
52
81
  return String.fromCodePoint(...processedCodePoints);
53
82
  } catch {
@@ -4,6 +4,7 @@ export const isSpriteServiceRepresentation = rep => !!(rep && rep.spriteRef);
4
4
  export const isSpriteRepresentation = rep => !!(rep && rep.sprite);
5
5
  export const isImageRepresentation = rep => !!(rep && rep.imagePath);
6
6
  export const isMediaRepresentation = rep => !!(rep && rep.mediaPath);
7
+ export const isUnicodeRepresentation = rep => !!(rep && rep.unicodeEmoji);
7
8
  export const isPromise = p => !!(p && p.then);
8
9
  export const isEmojiDescription = possibleEmojiDescription => possibleEmojiDescription && possibleEmojiDescription.shortName && possibleEmojiDescription.type;
9
10
  export const isMediaEmoji = emoji => isMediaRepresentation(emoji.representation);
@@ -156,5 +156,5 @@ var getHeight = function getHeight(fitToHeight) {
156
156
  return getPixelRatio() > 1 ? fitToHeight * 2 : fitToHeight;
157
157
  };
158
158
  export var shouldUseAltRepresentation = function shouldUseAltRepresentation(emoji, fitToHeight) {
159
- return !!(fitToHeight && emoji.altRepresentation && getHeight(fitToHeight) > emoji.representation.height);
159
+ return !!(fitToHeight && emoji.altRepresentation && emoji.representation && 'height' in emoji.representation && getHeight(fitToHeight) > emoji.representation.height);
160
160
  };
@@ -140,11 +140,12 @@ export var CachingMediaEmoji = function CachingMediaEmoji(props) {
140
140
  onLoadError: handleLoadError
141
141
  }));
142
142
  }
143
+ var imageRepresentation = representation && 'height' in representation ? representation : undefined;
143
144
  return /*#__PURE__*/React.createElement(EmojiPlaceholder, {
144
145
  size: fitToHeight || placeholderSize,
145
146
  shortName: shortName,
146
147
  showTooltip: showTooltip,
147
- representation: representation
148
+ representation: imageRepresentation
148
149
  });
149
150
  };
150
151
  var _default_1 = /*#__PURE__*/memo(CachingEmoji);
@@ -151,10 +151,14 @@ var EmojiPickerComponent = function EmojiPickerComponent(_ref) {
151
151
  if (isProgrammaticScroll.current && fg('platform_emoji_picker_refresh')) {
152
152
  return;
153
153
  }
154
+ // Ignore scroll-driven category changes while the upload screen is open
155
+ if (uploading && fg('platform_emoji_picker_refresh')) {
156
+ return;
157
+ }
154
158
  if (activeCategory !== category) {
155
159
  setActiveCategory(category);
156
160
  }
157
- }, [activeCategory]);
161
+ }, [activeCategory, uploading]);
158
162
  var calculateElapsedTime = function calculateElapsedTime() {
159
163
  return Date.now() - openTime.current;
160
164
  };
@@ -309,6 +313,12 @@ var EmojiPickerComponent = function EmojiPickerComponent(_ref) {
309
313
  if (!categoryId) {
310
314
  return;
311
315
  }
316
+
317
+ // If the upload screen is open, close it when a category is selected
318
+ if (uploading && fg('platform_emoji_picker_refresh')) {
319
+ setUploading(false);
320
+ setUploadErrorMessage(undefined);
321
+ }
312
322
  emojiProvider.findInCategory(categoryId).then(function (emojisInCategory) {
313
323
  if (!disableCategories) {
314
324
  var newSelectedEmoji;
@@ -338,7 +348,7 @@ var EmojiPickerComponent = function EmojiPickerComponent(_ref) {
338
348
  }));
339
349
  }
340
350
  });
341
- }, [disableCategories, emojiPickerList, emojiProvider, fireAnalytics, selectedTone]);
351
+ }, [disableCategories, emojiPickerList, emojiProvider, fireAnalytics, selectedTone, uploading]);
342
352
  var recordUsageOnSelection = useMemo(function () {
343
353
  return createRecordSelectionDefault(emojiProvider, onSelectWrapper, function (analytic) {
344
354
  return fireAnalytics(analytic(SearchSourceTypes.PICKER));
@@ -566,7 +576,7 @@ var EmojiPickerComponent = function EmojiPickerComponent(_ref) {
566
576
  onKeyDown: suppressKeyPress,
567
577
  className: ax(["_16jlkb7n _1o9zkb7n _i0dlf1ug _1reo15vq _18m915vq _1e0c1txw _2lx21bp4 _1bah1yb4 _1tkeidpf"])
568
578
  }, /*#__PURE__*/React.createElement(CategorySelector, {
569
- activeCategoryId: activeCategory,
579
+ activeCategoryId: uploading && fg('platform_emoji_picker_refresh') ? null : activeCategory,
570
580
  dynamicCategories: dynamicCategories,
571
581
  disableCategories: disableCategories,
572
582
  onCategorySelected: onCategorySelected
@@ -620,7 +630,7 @@ var EmojiPickerComponent = function EmojiPickerComponent(_ref) {
620
630
  "--_gsvyy7": ix("".concat(emojiPickerWidth, "px"))
621
631
  }
622
632
  }, /*#__PURE__*/React.createElement(CategorySelector, {
623
- activeCategoryId: activeCategory,
633
+ activeCategoryId: uploading && fg('platform_emoji_picker_refresh') ? null : activeCategory,
624
634
  dynamicCategories: dynamicCategories,
625
635
  disableCategories: disableCategories,
626
636
  onCategorySelected: onCategorySelected
@@ -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: "70.7.1"
17
+ packageVersion: "70.8.0"
18
18
  }, attributes)
19
19
  };
20
20
  };
@@ -1,6 +1,22 @@
1
1
  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; } } }; }
2
2
  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; } }
3
3
  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; }
4
+ import { fg } from '@atlaskit/platform-feature-flags';
5
+ var KEYCAP_COMBINING = 0x20e3;
6
+ var VARIATION_SELECTOR_16 = 0xfe0f;
7
+ var VARIATION_SELECTOR_15 = 0xfe0e;
8
+
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
+
4
20
  /**
5
21
  * Converts an emoji ID (a hyphen-separated string of hex Unicode code points,
6
22
  * e.g. `"1f44d"`, `"1f44d-1f3fb"`, `"1f468-200d-1f469-200d-1f467"`) into its
@@ -44,22 +60,35 @@ export function emojiIdToEmoji(id) {
44
60
  if (codePoints.length === 0) {
45
61
  return undefined;
46
62
  }
47
-
48
- // Keycap sequences: digit/# /* followed by U+20E3 need U+FE0F between them.
49
- // The keycap base characters are: 0x23 (#), 0x2A (*), 0x30–0x39 (0–9).
50
- var KEYCAP_COMBINING = 0x20e3;
51
- var VARIATION_SELECTOR_16 = 0xfe0f;
52
63
  var processedCodePoints = [];
53
64
  for (var i = 0; i < codePoints.length; i++) {
54
65
  var cp = codePoints[i];
55
66
  var next = codePoints[i + 1];
56
67
  processedCodePoints.push(cp);
57
68
 
58
- // Insert U+FE0F before U+20E3 if not already present
69
+ // Insert U+FE0F before U+20E3 if not already present.
70
+ // Keycap sequences: digit/# /* followed by U+20E3 need U+FE0F between them.
59
71
  if (next === KEYCAP_COMBINING && cp !== VARIATION_SELECTOR_16) {
60
72
  processedCodePoints.push(VARIATION_SELECTOR_16);
61
73
  }
62
74
  }
75
+
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 (fg('platform_twemoji_removal_unicode_emojis')) {
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
+ }
90
+ }
91
+ }
63
92
  try {
64
93
  return String.fromCodePoint.apply(String, processedCodePoints);
65
94
  } catch (_unused) {
@@ -15,6 +15,9 @@ export var isImageRepresentation = function isImageRepresentation(rep) {
15
15
  export var isMediaRepresentation = function isMediaRepresentation(rep) {
16
16
  return !!(rep && rep.mediaPath);
17
17
  };
18
+ export var isUnicodeRepresentation = function isUnicodeRepresentation(rep) {
19
+ return !!(rep && rep.unicodeEmoji);
20
+ };
18
21
  export var isPromise = function isPromise(p) {
19
22
  return !!(p && p.then);
20
23
  };
@@ -212,7 +212,10 @@ export interface ImageRepresentation extends EmojiImageRepresentation {
212
212
  export interface MediaApiRepresentation extends EmojiImageRepresentation {
213
213
  mediaPath: string;
214
214
  }
215
- export type EmojiRepresentation = SpriteRepresentation | ImageRepresentation | MediaApiRepresentation | undefined;
215
+ export interface UnicodeRepresentation {
216
+ unicodeEmoji: string;
217
+ }
218
+ export type EmojiRepresentation = SpriteRepresentation | ImageRepresentation | MediaApiRepresentation | UnicodeRepresentation | undefined;
216
219
  export interface EmojiDescription extends EmojiId {
217
220
  altRepresentation?: EmojiRepresentation;
218
221
  ascii?: string[];
@@ -1,10 +1,11 @@
1
1
  import { messages } from '../components/i18n';
2
2
  import type { CategoryId } from '../components/picker/categories';
3
- import type { EmojiDescription, EmojiDescriptionWithVariations, EmojiId, EmojiImageRepresentation, EmojiRepresentation, EmojiServiceRepresentation, EmojiVariationDescription, ImageRepresentation, MediaApiRepresentation, OptionalEmojiDescription, SpriteRepresentation, SpriteServiceRepresentation } from '../types';
3
+ import type { EmojiDescription, EmojiDescriptionWithVariations, EmojiId, EmojiImageRepresentation, EmojiRepresentation, EmojiServiceRepresentation, EmojiVariationDescription, ImageRepresentation, MediaApiRepresentation, OptionalEmojiDescription, SpriteRepresentation, SpriteServiceRepresentation, UnicodeRepresentation } from '../types';
4
4
  export declare const isSpriteServiceRepresentation: (rep: EmojiServiceRepresentation) => rep is SpriteServiceRepresentation;
5
5
  export declare const isSpriteRepresentation: (rep: EmojiRepresentation) => rep is SpriteRepresentation;
6
6
  export declare const isImageRepresentation: (rep: EmojiRepresentation | EmojiServiceRepresentation | EmojiImageRepresentation) => rep is ImageRepresentation;
7
7
  export declare const isMediaRepresentation: (rep: EmojiRepresentation | EmojiImageRepresentation) => rep is MediaApiRepresentation;
8
+ export declare const isUnicodeRepresentation: (rep: EmojiRepresentation) => rep is UnicodeRepresentation;
8
9
  export declare const isPromise: <T>(p: any) => p is Promise<T>;
9
10
  export declare const isEmojiDescription: (possibleEmojiDescription: any) => possibleEmojiDescription is EmojiDescription;
10
11
  export declare const isMediaEmoji: (emoji: EmojiDescription) => boolean;
@@ -212,7 +212,10 @@ export interface ImageRepresentation extends EmojiImageRepresentation {
212
212
  export interface MediaApiRepresentation extends EmojiImageRepresentation {
213
213
  mediaPath: string;
214
214
  }
215
- export type EmojiRepresentation = SpriteRepresentation | ImageRepresentation | MediaApiRepresentation | undefined;
215
+ export interface UnicodeRepresentation {
216
+ unicodeEmoji: string;
217
+ }
218
+ export type EmojiRepresentation = SpriteRepresentation | ImageRepresentation | MediaApiRepresentation | UnicodeRepresentation | undefined;
216
219
  export interface EmojiDescription extends EmojiId {
217
220
  altRepresentation?: EmojiRepresentation;
218
221
  ascii?: string[];
@@ -1,10 +1,11 @@
1
1
  import { messages } from '../components/i18n';
2
2
  import type { CategoryId } from '../components/picker/categories';
3
- import type { EmojiDescription, EmojiDescriptionWithVariations, EmojiId, EmojiImageRepresentation, EmojiRepresentation, EmojiServiceRepresentation, EmojiVariationDescription, ImageRepresentation, MediaApiRepresentation, OptionalEmojiDescription, SpriteRepresentation, SpriteServiceRepresentation } from '../types';
3
+ import type { EmojiDescription, EmojiDescriptionWithVariations, EmojiId, EmojiImageRepresentation, EmojiRepresentation, EmojiServiceRepresentation, EmojiVariationDescription, ImageRepresentation, MediaApiRepresentation, OptionalEmojiDescription, SpriteRepresentation, SpriteServiceRepresentation, UnicodeRepresentation } from '../types';
4
4
  export declare const isSpriteServiceRepresentation: (rep: EmojiServiceRepresentation) => rep is SpriteServiceRepresentation;
5
5
  export declare const isSpriteRepresentation: (rep: EmojiRepresentation) => rep is SpriteRepresentation;
6
6
  export declare const isImageRepresentation: (rep: EmojiRepresentation | EmojiServiceRepresentation | EmojiImageRepresentation) => rep is ImageRepresentation;
7
7
  export declare const isMediaRepresentation: (rep: EmojiRepresentation | EmojiImageRepresentation) => rep is MediaApiRepresentation;
8
+ export declare const isUnicodeRepresentation: (rep: EmojiRepresentation) => rep is UnicodeRepresentation;
8
9
  export declare const isPromise: <T>(p: any) => p is Promise<T>;
9
10
  export declare const isEmojiDescription: (possibleEmojiDescription: any) => possibleEmojiDescription is EmojiDescription;
10
11
  export declare const isMediaEmoji: (emoji: EmojiDescription) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/emoji",
3
- "version": "70.7.2",
3
+ "version": "70.9.0",
4
4
  "description": "Fabric emoji React components",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -44,8 +44,8 @@
44
44
  "@atlaskit/css": "^0.19.0",
45
45
  "@atlaskit/form": "^15.5.0",
46
46
  "@atlaskit/heading": "^5.4.0",
47
- "@atlaskit/icon": "^34.5.0",
48
- "@atlaskit/media-client": "^36.2.0",
47
+ "@atlaskit/icon": "^34.6.0",
48
+ "@atlaskit/media-client": "^36.3.0",
49
49
  "@atlaskit/media-client-react": "^5.1.0",
50
50
  "@atlaskit/platform-feature-flags": "^1.1.0",
51
51
  "@atlaskit/pragmatic-drag-and-drop": "^1.8.0",