@tiptap/extension-emoji 3.27.0 → 3.27.2

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/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "@tiptap/extension-emoji",
3
- "version": "3.27.0",
3
+ "version": "3.27.2",
4
4
  "description": "emoji extension for tiptap",
5
5
  "keywords": [
6
6
  "tiptap",
7
7
  "tiptap extension"
8
8
  ],
9
9
  "homepage": "https://tiptap.dev/api/nodes/emoji",
10
+ "bugs": {
11
+ "url": "https://github.com/ueberdosis/tiptap/issues"
12
+ },
10
13
  "license": "MIT",
11
14
  "repository": {
12
15
  "type": "git",
13
16
  "url": "https://github.com/ueberdosis/tiptap",
14
17
  "directory": "packages/extension-emoji"
15
18
  },
16
- "bugs": {
17
- "url": "https://github.com/ueberdosis/tiptap/issues"
18
- },
19
19
  "funding": {
20
20
  "type": "github",
21
21
  "url": "https://github.com/sponsors/ueberdosis"
@@ -47,14 +47,14 @@
47
47
  "emoji-datasource": "^16.0.0",
48
48
  "json5": "^2.2.3",
49
49
  "tsm": "^2.3.0",
50
- "@tiptap/core": "^3.27.0",
51
- "@tiptap/pm": "^3.27.0",
52
- "@tiptap/suggestion": "^3.27.0"
50
+ "@tiptap/core": "^3.27.2",
51
+ "@tiptap/pm": "^3.27.2",
52
+ "@tiptap/suggestion": "^3.27.2"
53
53
  },
54
54
  "peerDependencies": {
55
- "@tiptap/core": "3.27.0",
56
- "@tiptap/pm": "3.27.0",
57
- "@tiptap/suggestion": "3.27.0"
55
+ "@tiptap/core": "3.27.2",
56
+ "@tiptap/pm": "3.27.2",
57
+ "@tiptap/suggestion": "3.27.2"
58
58
  },
59
59
  "scripts": {
60
60
  "build": "tsup"
package/src/emoji.ts CHANGED
@@ -4,13 +4,14 @@ import {
4
4
  findChildrenInRange,
5
5
  getChangedRanges,
6
6
  InputRule,
7
+ isFirefox,
7
8
  mergeAttributes,
8
9
  Node,
9
10
  nodeInputRule,
10
11
  PasteRule,
11
12
  } from '@tiptap/core'
12
13
  import type { Transaction } from '@tiptap/pm/state'
13
- import { Plugin, PluginKey } from '@tiptap/pm/state'
14
+ import { Plugin, PluginKey, TextSelection } from '@tiptap/pm/state'
14
15
  import type { SuggestionOptions } from '@tiptap/suggestion'
15
16
  import { Suggestion } from '@tiptap/suggestion'
16
17
  import emojiRegex from 'emoji-regex'
@@ -385,8 +386,75 @@ export const Emoji = Node.create<EmojiOptions, EmojiStorage>({
385
386
  new Plugin({
386
387
  key: new PluginKey('emoji'),
387
388
  props: {
388
- // double click to select emoji doesn’t work by default
389
- // that’s why we simulate this behavior
389
+ // Firefox cannot move the cursor past emoji nodes at paragraph
390
+ // boundaries using arrow keys. We intercept plain (unmodified)
391
+ // ArrowLeft/ArrowRight and manually resolve the correct position.
392
+ // Only handles emoji nodes — other inline non-selectable nodes
393
+ // (mentions, hard breaks) are out of scope for this extension.
394
+ // See: https://github.com/ProseMirror/prosemirror/issues/1220
395
+ handleKeyDown: (view, event) => {
396
+ if (!isFirefox()) {
397
+ return false
398
+ }
399
+
400
+ const isLeft = event.key === 'ArrowLeft'
401
+ const isRight = event.key === 'ArrowRight'
402
+
403
+ if (!isLeft && !isRight) {
404
+ return false
405
+ }
406
+
407
+ // Don't interfere with selection extension (Shift), word
408
+ // navigation (Ctrl/Alt), or OS shortcuts (Meta).
409
+ if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
410
+ return false
411
+ }
412
+
413
+ const { state } = view
414
+ const { selection } = state
415
+
416
+ if (!selection.empty || !(selection instanceof TextSelection)) {
417
+ return false
418
+ }
419
+
420
+ const $pos = selection.$from
421
+ const emojiType = this.type
422
+
423
+ if (isLeft) {
424
+ const nodeBefore = $pos.nodeBefore
425
+
426
+ if (!nodeBefore || nodeBefore.type !== emojiType) {
427
+ return false
428
+ }
429
+
430
+ // Cursor is right after an emoji node. Move past it to the left.
431
+ const newPos = $pos.pos - nodeBefore.nodeSize
432
+
433
+ if (newPos >= $pos.start()) {
434
+ view.dispatch(state.tr.setSelection(TextSelection.create(state.doc, newPos)))
435
+ return true
436
+ }
437
+ } else {
438
+ const nodeAfter = $pos.nodeAfter
439
+
440
+ if (!nodeAfter || nodeAfter.type !== emojiType) {
441
+ return false
442
+ }
443
+
444
+ // Cursor is right before an emoji node. Skip over it to the right.
445
+ const newPos = $pos.pos + nodeAfter.nodeSize
446
+
447
+ if (newPos <= $pos.end()) {
448
+ view.dispatch(state.tr.setSelection(TextSelection.create(state.doc, newPos)))
449
+ return true
450
+ }
451
+ }
452
+
453
+ return false
454
+ },
455
+
456
+ // double click to select emoji doesn't work by default
457
+ // that's why we simulate this behavior
390
458
  handleDoubleClickOn: (view, pos, node) => {
391
459
  if (node.type !== this.type) {
392
460
  return false