@atlaskit/editor-plugin-emoji 13.1.11 → 13.2.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,18 @@
1
1
  # @atlaskit/editor-plugin-emoji
2
2
 
3
+ ## 13.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`a86fac88f3a5e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a86fac88f3a5e) -
8
+ Use lazy-loaded VanillaTooltip for emoji tooltips in the editor and render an ADS Tooltip
9
+ positioned above the emoji in the emoji element, gated behind the
10
+ platform_editor_emoji_hover_show_tooltip experiment.
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies
15
+
3
16
  ## 13.1.11
4
17
 
5
18
  ### Patch Changes
@@ -11,11 +11,14 @@ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/
11
11
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
12
12
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
13
13
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
14
+ var _bindEventListener = require("bind-event-listener");
14
15
  var _isEqual = _interopRequireDefault(require("lodash/isEqual"));
16
+ var _uniqueId = _interopRequireDefault(require("lodash/uniqueId"));
15
17
  var _browserApis = require("@atlaskit/browser-apis");
16
18
  var _coreUtils = require("@atlaskit/editor-common/core-utils");
17
19
  var _emoji = require("@atlaskit/editor-common/emoji");
18
20
  var _monitoring = require("@atlaskit/editor-common/monitoring");
21
+ var _vanillaTooltip = require("@atlaskit/editor-common/vanilla-tooltip");
19
22
  var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity");
20
23
  var _model = require("@atlaskit/editor-prosemirror/model");
21
24
  var _emojiIdToEmoji = require("@atlaskit/emoji/emoji-id-to-emoji");
@@ -43,6 +46,7 @@ function isSingleEmoji(fallbackText) {
43
46
  /**
44
47
  * Emoji node view for renderering emoji nodes
45
48
  */
49
+ var EMOJI_TOOLTIP_CLASS = 'emoji-tooltip-editor';
46
50
  var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
47
51
  /**
48
52
  * Prosemirror node view for rendering emoji nodes. This class is responsible for
@@ -64,7 +68,11 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
64
68
  emojiNodeDataProvider = _ref.emojiNodeDataProvider;
65
69
  (0, _classCallCheck2.default)(this, EmojiNodeView);
66
70
  (0, _defineProperty2.default)(this, "renderingFallback", false);
67
- (0, _defineProperty2.default)(this, "destroy", function () {});
71
+ (0, _defineProperty2.default)(this, "destroy", function () {
72
+ if (_this.tooltipInstance || _this.tooltipTarget || _this.destroyLazyTooltipListeners) {
73
+ _this.destroyTooltip();
74
+ }
75
+ });
68
76
  this.node = node;
69
77
  this.intl = intl;
70
78
  var _DOMSerializer$render = _model.DOMSerializer.renderSpec(document, (0, _emojiNodeSpec.emojiToDom)(this.node)),
@@ -138,12 +146,25 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
138
146
  this.destroy = function () {
139
147
  unsubscribe();
140
148
  subscribeToConnection === null || subscribeToConnection === void 0 || subscribeToConnection();
149
+ _this.destroyTooltip();
141
150
  };
142
151
  }
143
152
  }
144
153
 
145
154
  /** Type guard to check if a Node is an HTMLElement in a safe way. */
146
155
  return (0, _createClass2.default)(EmojiNodeView, [{
156
+ key: "destroyTooltip",
157
+ value: function destroyTooltip() {
158
+ var _this$destroyLazyTool, _this$tooltipInstance, _this$tooltipTarget, _this$tooltipTarget2;
159
+ (_this$destroyLazyTool = this.destroyLazyTooltipListeners) === null || _this$destroyLazyTool === void 0 || _this$destroyLazyTool.call(this);
160
+ (_this$tooltipInstance = this.tooltipInstance) === null || _this$tooltipInstance === void 0 || _this$tooltipInstance.destroy();
161
+ (_this$tooltipTarget = this.tooltipTarget) === null || _this$tooltipTarget === void 0 || _this$tooltipTarget.removeAttribute('popovertarget');
162
+ (_this$tooltipTarget2 = this.tooltipTarget) === null || _this$tooltipTarget2 === void 0 || _this$tooltipTarget2.removeAttribute('aria-describedby');
163
+ this.destroyLazyTooltipListeners = undefined;
164
+ this.tooltipInstance = undefined;
165
+ this.tooltipTarget = undefined;
166
+ }
167
+ }, {
147
168
  key: "isHTMLElement",
148
169
  value: function isHTMLElement(element) {
149
170
  if (element === null) {
@@ -216,6 +237,8 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
216
237
  // Pay attention, this method should be called only when the emoji provider returns
217
238
  // emoji data to prevent rendering empty emoji during loading.
218
239
  function cleanUpAndRenderCommonAttributes() {
240
+ this.destroyTooltip();
241
+
219
242
  // Clean up the DOM before rendering the new emoji
220
243
  if (this.domElement) {
221
244
  this.domElement.innerHTML = '';
@@ -225,6 +248,83 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
225
248
  this.domElement.removeAttribute('aria-busy');
226
249
  }
227
250
  }
251
+
252
+ /**
253
+ * Lazily creates a VanillaTooltip on the given element showing the emoji shortName.
254
+ * Gated behind the platform_editor_emoji_hover_show_tooltip experiment.
255
+ * When the tooltip is active, the native `title` attribute is removed to avoid
256
+ * showing both the browser tooltip and the custom tooltip.
257
+ */
258
+ }, {
259
+ key: "createTooltip",
260
+ value: function createTooltip(element, shortName) {
261
+ var _this2 = this;
262
+ if (!(0, _expValEquals.expValEquals)('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true)) {
263
+ return;
264
+ }
265
+ if ((0, _coreUtils.isSSR)()) {
266
+ return;
267
+ }
268
+ var initTooltip = function initTooltip(event) {
269
+ var _this2$destroyLazyToo;
270
+ (_this2$destroyLazyToo = _this2.destroyLazyTooltipListeners) === null || _this2$destroyLazyToo === void 0 || _this2$destroyLazyToo.call(_this2);
271
+ _this2.destroyLazyTooltipListeners = undefined;
272
+ var tooltipId = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? "emoji-tooltip-".concat(crypto.randomUUID()) : (0, _uniqueId.default)('emoji-tooltip-');
273
+ try {
274
+ var tooltipInstance = new _vanillaTooltip.VanillaTooltip(
275
+ // VanillaTooltip types expect HTMLButtonElement but works with any HTMLElement
276
+ element, shortName, tooltipId, EMOJI_TOOLTIP_CLASS,
277
+ // default timeout
278
+ 300,
279
+ // Inline styles are required because the Popover API promotes the tooltip
280
+ // to the browser's top layer, where ancestor CSS selectors cannot reach it.
281
+ {
282
+ boxSizing: 'border-box',
283
+ maxWidth: '240px',
284
+ backgroundColor: "var(--ds-background-neutral-bold, #292A2E)",
285
+ border: 'none',
286
+ borderRadius: "var(--ds-radius-small, 3px)",
287
+ color: "var(--ds-text-inverse, #FFFFFF)",
288
+ font: "var(--ds-font-body-small, normal 400 12px/16px \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
289
+ fontFamily: "var(--ds-font-family-body, \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
290
+ insetBlockStart: "var(--ds-space-0, 0px)",
291
+ insetInlineStart: "var(--ds-space-0, 0px)",
292
+ overflowWrap: 'break-word',
293
+ paddingBlockStart: "var(--ds-space-050, 4px)",
294
+ paddingBlockEnd: "var(--ds-space-050, 4px)",
295
+ paddingInlineEnd: "var(--ds-space-075, 6px)",
296
+ paddingInlineStart: "var(--ds-space-075, 6px)",
297
+ whiteSpace: 'normal'
298
+ });
299
+ _this2.tooltipInstance = tooltipInstance;
300
+ _this2.tooltipTarget = element;
301
+ element.removeAttribute('title');
302
+ element.dispatchEvent(new Event(event.type));
303
+ } catch (error) {
304
+ element.removeAttribute('popovertarget');
305
+ element.removeAttribute('aria-describedby');
306
+ EmojiNodeView.logError(error instanceof Error ? error : new Error(String(error)));
307
+ }
308
+ };
309
+ var unbindMouseEnter = (0, _bindEventListener.bind)(element, {
310
+ type: 'mouseenter',
311
+ listener: initTooltip,
312
+ options: {
313
+ once: true
314
+ }
315
+ });
316
+ var unbindFocus = (0, _bindEventListener.bind)(element, {
317
+ type: 'focus',
318
+ listener: initTooltip,
319
+ options: {
320
+ once: true
321
+ }
322
+ });
323
+ this.destroyLazyTooltipListeners = function () {
324
+ unbindMouseEnter();
325
+ unbindFocus();
326
+ };
327
+ }
228
328
  }, {
229
329
  key: "renderFallback",
230
330
  value: function renderFallback() {
@@ -254,6 +354,7 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
254
354
  fallbackElement.setAttribute('data-testid', "fallback-emoji-".concat(shortName));
255
355
  fallbackElement.setAttribute('data-emoji-type', 'fallback');
256
356
  this.dom.appendChild(fallbackElement);
357
+ this.createTooltip(fallbackElement, shortName);
257
358
  }
258
359
  }, {
259
360
  key: "renderEmoji",
@@ -281,6 +382,7 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
281
382
  if (EmojiNodeView.shouldRecordUnicodeEmojiExposure(description, representation)) {
282
383
  (0, _expValEquals.expValEquals)('platform_use_unicode_emojis', 'isEnabled', true);
283
384
  }
385
+ this.createTooltip(containerElement, description.shortName);
284
386
  }
285
387
  }, {
286
388
  key: "createUnicodeEmojiElement",
@@ -330,7 +432,7 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
330
432
  }, {
331
433
  key: "createImageEmojiElement",
332
434
  value: function createImageEmojiElement(emojiDescription, representation) {
333
- var _this2 = this;
435
+ var _this3 = this;
334
436
  var doc = (0, _browserApis.getDocument)();
335
437
  if (!doc) {
336
438
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
@@ -349,13 +451,13 @@ var EmojiNodeView = exports.EmojiNodeView = /*#__PURE__*/function () {
349
451
  // If there's an error (ie. offline) render the ascii fallback if possible, otherwise
350
452
  // mark the node to refresh when returning online.
351
453
  // Create a check that confirms if this.node.attrs.text if an ascii emoji
352
- if (isSingleEmoji(_this2.node.attrs.text)) {
353
- _this2.renderFallback();
454
+ if (isSingleEmoji(_this3.node.attrs.text)) {
455
+ _this3.renderFallback();
354
456
  } else {
355
- _this2.renderingFallback = true;
457
+ _this3.renderingFallback = true;
356
458
  }
357
459
  } else {
358
- _this2.renderFallback();
460
+ _this3.renderFallback();
359
461
  }
360
462
  };
361
463
  return imageElement;
@@ -1,9 +1,12 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { bind } from 'bind-event-listener';
2
3
  import isEqual from 'lodash/isEqual';
4
+ import uniqueId from 'lodash/uniqueId';
3
5
  import { getDocument } from '@atlaskit/browser-apis';
4
6
  import { isSSR } from '@atlaskit/editor-common/core-utils';
5
7
  import { messages, EmojiSharedCssClassName, defaultEmojiHeight } from '@atlaskit/editor-common/emoji';
6
8
  import { logException } from '@atlaskit/editor-common/monitoring';
9
+ import { VanillaTooltip } from '@atlaskit/editor-common/vanilla-tooltip';
7
10
  import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
8
11
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
9
12
  import { emojiIdToEmoji } from '@atlaskit/emoji/emoji-id-to-emoji';
@@ -31,7 +34,18 @@ export function isSingleEmoji(fallbackText) {
31
34
  /**
32
35
  * Emoji node view for renderering emoji nodes
33
36
  */
37
+ const EMOJI_TOOLTIP_CLASS = 'emoji-tooltip-editor';
34
38
  export class EmojiNodeView {
39
+ destroyTooltip() {
40
+ var _this$destroyLazyTool, _this$tooltipInstance, _this$tooltipTarget, _this$tooltipTarget2;
41
+ (_this$destroyLazyTool = this.destroyLazyTooltipListeners) === null || _this$destroyLazyTool === void 0 ? void 0 : _this$destroyLazyTool.call(this);
42
+ (_this$tooltipInstance = this.tooltipInstance) === null || _this$tooltipInstance === void 0 ? void 0 : _this$tooltipInstance.destroy();
43
+ (_this$tooltipTarget = this.tooltipTarget) === null || _this$tooltipTarget === void 0 ? void 0 : _this$tooltipTarget.removeAttribute('popovertarget');
44
+ (_this$tooltipTarget2 = this.tooltipTarget) === null || _this$tooltipTarget2 === void 0 ? void 0 : _this$tooltipTarget2.removeAttribute('aria-describedby');
45
+ this.destroyLazyTooltipListeners = undefined;
46
+ this.tooltipInstance = undefined;
47
+ this.tooltipTarget = undefined;
48
+ }
35
49
  static logError(error) {
36
50
  void logException(error, {
37
51
  location: 'editor-plugin-emoji/EmojiNodeView'
@@ -57,7 +71,11 @@ export class EmojiNodeView {
57
71
  emojiNodeDataProvider
58
72
  }) {
59
73
  _defineProperty(this, "renderingFallback", false);
60
- _defineProperty(this, "destroy", () => {});
74
+ _defineProperty(this, "destroy", () => {
75
+ if (this.tooltipInstance || this.tooltipTarget || this.destroyLazyTooltipListeners) {
76
+ this.destroyTooltip();
77
+ }
78
+ });
61
79
  this.node = node;
62
80
  this.intl = intl;
63
81
  const {
@@ -134,6 +152,7 @@ export class EmojiNodeView {
134
152
  this.destroy = () => {
135
153
  unsubscribe();
136
154
  subscribeToConnection === null || subscribeToConnection === void 0 ? void 0 : subscribeToConnection();
155
+ this.destroyTooltip();
137
156
  };
138
157
  }
139
158
  }
@@ -189,6 +208,8 @@ export class EmojiNodeView {
189
208
  // Pay attention, this method should be called only when the emoji provider returns
190
209
  // emoji data to prevent rendering empty emoji during loading.
191
210
  cleanUpAndRenderCommonAttributes() {
211
+ this.destroyTooltip();
212
+
192
213
  // Clean up the DOM before rendering the new emoji
193
214
  if (this.domElement) {
194
215
  this.domElement.innerHTML = '';
@@ -198,6 +219,80 @@ export class EmojiNodeView {
198
219
  this.domElement.removeAttribute('aria-busy');
199
220
  }
200
221
  }
222
+
223
+ /**
224
+ * Lazily creates a VanillaTooltip on the given element showing the emoji shortName.
225
+ * Gated behind the platform_editor_emoji_hover_show_tooltip experiment.
226
+ * When the tooltip is active, the native `title` attribute is removed to avoid
227
+ * showing both the browser tooltip and the custom tooltip.
228
+ */
229
+ createTooltip(element, shortName) {
230
+ if (!expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true)) {
231
+ return;
232
+ }
233
+ if (isSSR()) {
234
+ return;
235
+ }
236
+ const initTooltip = event => {
237
+ var _this$destroyLazyTool2;
238
+ (_this$destroyLazyTool2 = this.destroyLazyTooltipListeners) === null || _this$destroyLazyTool2 === void 0 ? void 0 : _this$destroyLazyTool2.call(this);
239
+ this.destroyLazyTooltipListeners = undefined;
240
+ const tooltipId = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? `emoji-tooltip-${crypto.randomUUID()}` : uniqueId('emoji-tooltip-');
241
+ try {
242
+ const tooltipInstance = new VanillaTooltip(
243
+ // VanillaTooltip types expect HTMLButtonElement but works with any HTMLElement
244
+ element, shortName, tooltipId, EMOJI_TOOLTIP_CLASS,
245
+ // default timeout
246
+ 300,
247
+ // Inline styles are required because the Popover API promotes the tooltip
248
+ // to the browser's top layer, where ancestor CSS selectors cannot reach it.
249
+ {
250
+ boxSizing: 'border-box',
251
+ maxWidth: '240px',
252
+ backgroundColor: "var(--ds-background-neutral-bold, #292A2E)",
253
+ border: 'none',
254
+ borderRadius: "var(--ds-radius-small, 3px)",
255
+ color: "var(--ds-text-inverse, #FFFFFF)",
256
+ font: "var(--ds-font-body-small, normal 400 12px/16px \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
257
+ fontFamily: "var(--ds-font-family-body, \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
258
+ insetBlockStart: "var(--ds-space-0, 0px)",
259
+ insetInlineStart: "var(--ds-space-0, 0px)",
260
+ overflowWrap: 'break-word',
261
+ paddingBlockStart: "var(--ds-space-050, 4px)",
262
+ paddingBlockEnd: "var(--ds-space-050, 4px)",
263
+ paddingInlineEnd: "var(--ds-space-075, 6px)",
264
+ paddingInlineStart: "var(--ds-space-075, 6px)",
265
+ whiteSpace: 'normal'
266
+ });
267
+ this.tooltipInstance = tooltipInstance;
268
+ this.tooltipTarget = element;
269
+ element.removeAttribute('title');
270
+ element.dispatchEvent(new Event(event.type));
271
+ } catch (error) {
272
+ element.removeAttribute('popovertarget');
273
+ element.removeAttribute('aria-describedby');
274
+ EmojiNodeView.logError(error instanceof Error ? error : new Error(String(error)));
275
+ }
276
+ };
277
+ const unbindMouseEnter = bind(element, {
278
+ type: 'mouseenter',
279
+ listener: initTooltip,
280
+ options: {
281
+ once: true
282
+ }
283
+ });
284
+ const unbindFocus = bind(element, {
285
+ type: 'focus',
286
+ listener: initTooltip,
287
+ options: {
288
+ once: true
289
+ }
290
+ });
291
+ this.destroyLazyTooltipListeners = () => {
292
+ unbindMouseEnter();
293
+ unbindFocus();
294
+ };
295
+ }
201
296
  renderFallback() {
202
297
  this.renderingFallback = true;
203
298
  this.cleanUpAndRenderCommonAttributes();
@@ -226,6 +321,7 @@ export class EmojiNodeView {
226
321
  fallbackElement.setAttribute('data-testid', `fallback-emoji-${shortName}`);
227
322
  fallbackElement.setAttribute('data-emoji-type', 'fallback');
228
323
  this.dom.appendChild(fallbackElement);
324
+ this.createTooltip(fallbackElement, shortName);
229
325
  }
230
326
  renderEmoji(description, representation) {
231
327
  this.renderingFallback = false;
@@ -251,6 +347,7 @@ export class EmojiNodeView {
251
347
  if (EmojiNodeView.shouldRecordUnicodeEmojiExposure(description, representation)) {
252
348
  expValEquals('platform_use_unicode_emojis', 'isEnabled', true);
253
349
  }
350
+ this.createTooltip(containerElement, description.shortName);
254
351
  }
255
352
  createUnicodeEmojiElement(emoji) {
256
353
  let doc = getDocument();
@@ -3,11 +3,14 @@ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
3
  import _createClass from "@babel/runtime/helpers/createClass";
4
4
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
5
5
  import _regeneratorRuntime from "@babel/runtime/regenerator";
6
+ import { bind } from 'bind-event-listener';
6
7
  import isEqual from 'lodash/isEqual';
8
+ import uniqueId from 'lodash/uniqueId';
7
9
  import { getDocument } from '@atlaskit/browser-apis';
8
10
  import { isSSR } from '@atlaskit/editor-common/core-utils';
9
11
  import { messages, EmojiSharedCssClassName, defaultEmojiHeight } from '@atlaskit/editor-common/emoji';
10
12
  import { logException } from '@atlaskit/editor-common/monitoring';
13
+ import { VanillaTooltip } from '@atlaskit/editor-common/vanilla-tooltip';
11
14
  import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
12
15
  import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
13
16
  import { emojiIdToEmoji } from '@atlaskit/emoji/emoji-id-to-emoji';
@@ -35,6 +38,7 @@ export function isSingleEmoji(fallbackText) {
35
38
  /**
36
39
  * Emoji node view for renderering emoji nodes
37
40
  */
41
+ var EMOJI_TOOLTIP_CLASS = 'emoji-tooltip-editor';
38
42
  export var EmojiNodeView = /*#__PURE__*/function () {
39
43
  /**
40
44
  * Prosemirror node view for rendering emoji nodes. This class is responsible for
@@ -56,7 +60,11 @@ export var EmojiNodeView = /*#__PURE__*/function () {
56
60
  emojiNodeDataProvider = _ref.emojiNodeDataProvider;
57
61
  _classCallCheck(this, EmojiNodeView);
58
62
  _defineProperty(this, "renderingFallback", false);
59
- _defineProperty(this, "destroy", function () {});
63
+ _defineProperty(this, "destroy", function () {
64
+ if (_this.tooltipInstance || _this.tooltipTarget || _this.destroyLazyTooltipListeners) {
65
+ _this.destroyTooltip();
66
+ }
67
+ });
60
68
  this.node = node;
61
69
  this.intl = intl;
62
70
  var _DOMSerializer$render = DOMSerializer.renderSpec(document, emojiToDom(this.node)),
@@ -130,12 +138,25 @@ export var EmojiNodeView = /*#__PURE__*/function () {
130
138
  this.destroy = function () {
131
139
  unsubscribe();
132
140
  subscribeToConnection === null || subscribeToConnection === void 0 || subscribeToConnection();
141
+ _this.destroyTooltip();
133
142
  };
134
143
  }
135
144
  }
136
145
 
137
146
  /** Type guard to check if a Node is an HTMLElement in a safe way. */
138
147
  return _createClass(EmojiNodeView, [{
148
+ key: "destroyTooltip",
149
+ value: function destroyTooltip() {
150
+ var _this$destroyLazyTool, _this$tooltipInstance, _this$tooltipTarget, _this$tooltipTarget2;
151
+ (_this$destroyLazyTool = this.destroyLazyTooltipListeners) === null || _this$destroyLazyTool === void 0 || _this$destroyLazyTool.call(this);
152
+ (_this$tooltipInstance = this.tooltipInstance) === null || _this$tooltipInstance === void 0 || _this$tooltipInstance.destroy();
153
+ (_this$tooltipTarget = this.tooltipTarget) === null || _this$tooltipTarget === void 0 || _this$tooltipTarget.removeAttribute('popovertarget');
154
+ (_this$tooltipTarget2 = this.tooltipTarget) === null || _this$tooltipTarget2 === void 0 || _this$tooltipTarget2.removeAttribute('aria-describedby');
155
+ this.destroyLazyTooltipListeners = undefined;
156
+ this.tooltipInstance = undefined;
157
+ this.tooltipTarget = undefined;
158
+ }
159
+ }, {
139
160
  key: "isHTMLElement",
140
161
  value: function isHTMLElement(element) {
141
162
  if (element === null) {
@@ -208,6 +229,8 @@ export var EmojiNodeView = /*#__PURE__*/function () {
208
229
  // Pay attention, this method should be called only when the emoji provider returns
209
230
  // emoji data to prevent rendering empty emoji during loading.
210
231
  function cleanUpAndRenderCommonAttributes() {
232
+ this.destroyTooltip();
233
+
211
234
  // Clean up the DOM before rendering the new emoji
212
235
  if (this.domElement) {
213
236
  this.domElement.innerHTML = '';
@@ -217,6 +240,83 @@ export var EmojiNodeView = /*#__PURE__*/function () {
217
240
  this.domElement.removeAttribute('aria-busy');
218
241
  }
219
242
  }
243
+
244
+ /**
245
+ * Lazily creates a VanillaTooltip on the given element showing the emoji shortName.
246
+ * Gated behind the platform_editor_emoji_hover_show_tooltip experiment.
247
+ * When the tooltip is active, the native `title` attribute is removed to avoid
248
+ * showing both the browser tooltip and the custom tooltip.
249
+ */
250
+ }, {
251
+ key: "createTooltip",
252
+ value: function createTooltip(element, shortName) {
253
+ var _this2 = this;
254
+ if (!expValEquals('platform_editor_emoji_hover_show_tooltip', 'isEnabled', true)) {
255
+ return;
256
+ }
257
+ if (isSSR()) {
258
+ return;
259
+ }
260
+ var initTooltip = function initTooltip(event) {
261
+ var _this2$destroyLazyToo;
262
+ (_this2$destroyLazyToo = _this2.destroyLazyTooltipListeners) === null || _this2$destroyLazyToo === void 0 || _this2$destroyLazyToo.call(_this2);
263
+ _this2.destroyLazyTooltipListeners = undefined;
264
+ var tooltipId = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? "emoji-tooltip-".concat(crypto.randomUUID()) : uniqueId('emoji-tooltip-');
265
+ try {
266
+ var tooltipInstance = new VanillaTooltip(
267
+ // VanillaTooltip types expect HTMLButtonElement but works with any HTMLElement
268
+ element, shortName, tooltipId, EMOJI_TOOLTIP_CLASS,
269
+ // default timeout
270
+ 300,
271
+ // Inline styles are required because the Popover API promotes the tooltip
272
+ // to the browser's top layer, where ancestor CSS selectors cannot reach it.
273
+ {
274
+ boxSizing: 'border-box',
275
+ maxWidth: '240px',
276
+ backgroundColor: "var(--ds-background-neutral-bold, #292A2E)",
277
+ border: 'none',
278
+ borderRadius: "var(--ds-radius-small, 3px)",
279
+ color: "var(--ds-text-inverse, #FFFFFF)",
280
+ font: "var(--ds-font-body-small, normal 400 12px/16px \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
281
+ fontFamily: "var(--ds-font-family-body, \"Atlassian Sans\", ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Ubuntu, \"Helvetica Neue\", sans-serif)",
282
+ insetBlockStart: "var(--ds-space-0, 0px)",
283
+ insetInlineStart: "var(--ds-space-0, 0px)",
284
+ overflowWrap: 'break-word',
285
+ paddingBlockStart: "var(--ds-space-050, 4px)",
286
+ paddingBlockEnd: "var(--ds-space-050, 4px)",
287
+ paddingInlineEnd: "var(--ds-space-075, 6px)",
288
+ paddingInlineStart: "var(--ds-space-075, 6px)",
289
+ whiteSpace: 'normal'
290
+ });
291
+ _this2.tooltipInstance = tooltipInstance;
292
+ _this2.tooltipTarget = element;
293
+ element.removeAttribute('title');
294
+ element.dispatchEvent(new Event(event.type));
295
+ } catch (error) {
296
+ element.removeAttribute('popovertarget');
297
+ element.removeAttribute('aria-describedby');
298
+ EmojiNodeView.logError(error instanceof Error ? error : new Error(String(error)));
299
+ }
300
+ };
301
+ var unbindMouseEnter = bind(element, {
302
+ type: 'mouseenter',
303
+ listener: initTooltip,
304
+ options: {
305
+ once: true
306
+ }
307
+ });
308
+ var unbindFocus = bind(element, {
309
+ type: 'focus',
310
+ listener: initTooltip,
311
+ options: {
312
+ once: true
313
+ }
314
+ });
315
+ this.destroyLazyTooltipListeners = function () {
316
+ unbindMouseEnter();
317
+ unbindFocus();
318
+ };
319
+ }
220
320
  }, {
221
321
  key: "renderFallback",
222
322
  value: function renderFallback() {
@@ -246,6 +346,7 @@ export var EmojiNodeView = /*#__PURE__*/function () {
246
346
  fallbackElement.setAttribute('data-testid', "fallback-emoji-".concat(shortName));
247
347
  fallbackElement.setAttribute('data-emoji-type', 'fallback');
248
348
  this.dom.appendChild(fallbackElement);
349
+ this.createTooltip(fallbackElement, shortName);
249
350
  }
250
351
  }, {
251
352
  key: "renderEmoji",
@@ -273,6 +374,7 @@ export var EmojiNodeView = /*#__PURE__*/function () {
273
374
  if (EmojiNodeView.shouldRecordUnicodeEmojiExposure(description, representation)) {
274
375
  expValEquals('platform_use_unicode_emojis', 'isEnabled', true);
275
376
  }
377
+ this.createTooltip(containerElement, description.shortName);
276
378
  }
277
379
  }, {
278
380
  key: "createUnicodeEmojiElement",
@@ -322,7 +424,7 @@ export var EmojiNodeView = /*#__PURE__*/function () {
322
424
  }, {
323
425
  key: "createImageEmojiElement",
324
426
  value: function createImageEmojiElement(emojiDescription, representation) {
325
- var _this2 = this;
427
+ var _this3 = this;
326
428
  var doc = getDocument();
327
429
  if (!doc) {
328
430
  // eslint-disable-next-line @atlaskit/platform/no-direct-document-usage
@@ -341,13 +443,13 @@ export var EmojiNodeView = /*#__PURE__*/function () {
341
443
  // If there's an error (ie. offline) render the ascii fallback if possible, otherwise
342
444
  // mark the node to refresh when returning online.
343
445
  // Create a check that confirms if this.node.attrs.text if an ascii emoji
344
- if (isSingleEmoji(_this2.node.attrs.text)) {
345
- _this2.renderFallback();
446
+ if (isSingleEmoji(_this3.node.attrs.text)) {
447
+ _this3.renderFallback();
346
448
  } else {
347
- _this2.renderingFallback = true;
449
+ _this3.renderingFallback = true;
348
450
  }
349
451
  } else {
350
- _this2.renderFallback();
452
+ _this3.renderFallback();
351
453
  }
352
454
  };
353
455
  return imageElement;
@@ -18,16 +18,17 @@ interface Params {
18
18
  * isSingleEmoji('😀') // true
19
19
  */
20
20
  export declare function isSingleEmoji(fallbackText: string): boolean;
21
- /**
22
- * Emoji node view for renderering emoji nodes
23
- */
24
21
  export declare class EmojiNodeView implements NodeView {
25
22
  dom: Node;
26
23
  domElement: HTMLElement | undefined;
27
24
  private readonly node;
28
25
  private readonly intl;
29
26
  private renderingFallback;
27
+ private tooltipInstance;
28
+ private tooltipTarget;
29
+ private destroyLazyTooltipListeners;
30
30
  readonly destroy: () => void;
31
+ private destroyTooltip;
31
32
  private static logError;
32
33
  /**
33
34
  * Prosemirror node view for rendering emoji nodes. This class is responsible for
@@ -49,6 +50,13 @@ export declare class EmojiNodeView implements NodeView {
49
50
  private static isEmojiRepresentationSupported;
50
51
  private static shouldRecordUnicodeEmojiExposure;
51
52
  private cleanUpAndRenderCommonAttributes;
53
+ /**
54
+ * Lazily creates a VanillaTooltip on the given element showing the emoji shortName.
55
+ * Gated behind the platform_editor_emoji_hover_show_tooltip experiment.
56
+ * When the tooltip is active, the native `title` attribute is removed to avoid
57
+ * showing both the browser tooltip and the custom tooltip.
58
+ */
59
+ private createTooltip;
52
60
  private renderFallback;
53
61
  private renderEmoji;
54
62
  private createUnicodeEmojiElement;
@@ -4,7 +4,7 @@ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
4
4
  import type { EmojiResource, OptionalEmojiDescriptionWithVariations } from '@atlaskit/emoji';
5
5
  import { NodeDataProvider } from '@atlaskit/node-data-provider';
6
6
  export declare class EmojiNodeDataProvider extends NodeDataProvider<EmojiDefinition, OptionalEmojiDescriptionWithVariations> {
7
- name: "emojiNodeDataProvider";
7
+ name: 'emojiNodeDataProvider';
8
8
  private readonly emojiResource;
9
9
  private readonly emojiProvider;
10
10
  constructor(resource: EmojiResource);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-emoji",
3
- "version": "13.1.11",
3
+ "version": "13.2.0",
4
4
  "description": "Emoji plugin for @atlaskit/editor-core",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -32,20 +32,21 @@
32
32
  "@atlaskit/editor-plugin-type-ahead": "^13.0.0",
33
33
  "@atlaskit/editor-prosemirror": "^8.0.0",
34
34
  "@atlaskit/editor-shared-styles": "^4.0.0",
35
- "@atlaskit/emoji": "^71.13.0",
35
+ "@atlaskit/emoji": "^71.14.0",
36
36
  "@atlaskit/icon": "^37.2.0",
37
37
  "@atlaskit/node-data-provider": "^13.0.0",
38
38
  "@atlaskit/platform-feature-flags": "^2.1.0",
39
39
  "@atlaskit/prosemirror-input-rules": "^4.0.0",
40
- "@atlaskit/tmp-editor-statsig": "^134.0.0",
40
+ "@atlaskit/tmp-editor-statsig": "^135.0.0",
41
41
  "@atlaskit/tokens": "^16.3.0",
42
42
  "@babel/runtime": "^7.0.0",
43
43
  "@emotion/react": "^11.7.1",
44
+ "bind-event-listener": "^3.0.0",
44
45
  "lodash": "^4.17.21",
45
46
  "react-loadable": "^5.1.0"
46
47
  },
47
48
  "peerDependencies": {
48
- "@atlaskit/editor-common": "^116.40.0",
49
+ "@atlaskit/editor-common": "^116.41.0",
49
50
  "react": "^18.2.0",
50
51
  "react-dom": "^18.2.0",
51
52
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"