@@ -45,7 +45,27 @@ import { EVENTS } from './constants';
45
45
import { withBlockEditContext } from '../block-edit/context' ;
46
46
import { domToFormat , valueToString } from './format' ;
47
47
48
- const { BACKSPACE , DELETE , ENTER , rawShortcut } = keycodes ;
48
+ /**
49
+ * Browser dependencies
50
+ */
51
+
52
+ const { Node } = window ;
53
+
54
+ /**
55
+ * Module constants
56
+ */
57
+
58
+ const { LEFT , RIGHT , BACKSPACE , DELETE , ENTER , rawShortcut } = keycodes ;
59
+
60
+ /**
61
+ * Zero-width space character used by TinyMCE as a caret landing point for
62
+ * inline boundary nodes.
63
+ *
64
+ * @see tinymce/src/core/main/ts/text/Zwsp.ts
65
+ *
66
+ * @type {string }
67
+ */
68
+ const TINYMCE_ZWSP = '\uFEFF' ;
49
69
50
70
/**
51
71
* Returns true if the node is the inline node boundary. This is used in node
@@ -61,7 +81,7 @@ const { BACKSPACE, DELETE, ENTER, rawShortcut } = keycodes;
61
81
*/
62
82
export function isEmptyInlineBoundary ( node ) {
63
83
const text = node . nodeName === 'A' ? node . innerText : node . textContent ;
64
- return text === '\uFEFF' ;
84
+ return text === TINYMCE_ZWSP ;
65
85
}
66
86
67
87
/**
@@ -114,6 +134,7 @@ export class RichText extends Component {
114
134
this . onChange = this . onChange . bind ( this ) ;
115
135
this . onNewBlock = this . onNewBlock . bind ( this ) ;
116
136
this . onNodeChange = this . onNodeChange . bind ( this ) ;
137
+ this . onHorizontalNavigationKeyDown = this . onHorizontalNavigationKeyDown . bind ( this ) ;
117
138
this . onKeyDown = this . onKeyDown . bind ( this ) ;
118
139
this . onKeyUp = this . onKeyUp . bind ( this ) ;
119
140
this . changeFormats = this . changeFormats . bind ( this ) ;
@@ -438,27 +459,64 @@ export class RichText extends Component {
438
459
left : position . left - containerPosition . left + ( position . width / 2 ) + toolbarOffset . left ,
439
460
} ;
440
461
}
462
+ /**
463
+ * Handles a horizontal navigation key down event to stop propagation if it
464
+ * can be inferred that it will be handled by TinyMCE (notably transitions
465
+ * out of an inline boundary node).
466
+ *
467
+ * @param {KeyboardEvent } event Keydown event.
468
+ */
469
+ onHorizontalNavigationKeyDown ( event ) {
470
+ const { focusNode, focusOffset } = window . getSelection ( ) ;
471
+ const { nodeType, nodeValue } = focusNode ;
472
+
473
+ if ( nodeType !== Node . TEXT_NODE ) {
474
+ return ;
475
+ }
476
+
477
+ const isReverse = event . keyCode === LEFT ;
478
+
479
+ let offset = focusOffset ;
480
+ if ( isReverse ) {
481
+ offset -- ;
482
+ }
483
+
484
+ // [WORKAROUND]: When in a new paragraph in a new inline boundary node,
485
+ // while typing the zero-width space occurs as the first child instead
486
+ // of at the end of the inline boundary where the caret is. This should
487
+ // only be exempt when focusNode is not _only_ the ZWSP, which occurs
488
+ // when caret is placed on the right outside edge of inline boundary.
489
+ if ( ! isReverse && focusOffset === nodeValue . length &&
490
+ nodeValue . length > 1 && nodeValue [ 0 ] === TINYMCE_ZWSP ) {
491
+ offset = 0 ;
492
+ }
493
+
494
+ if ( nodeValue [ offset ] === TINYMCE_ZWSP ) {
495
+ event . stopPropagation ( ) ;
496
+ }
497
+ }
441
498
442
499
/**
443
500
* Handles a keydown event from tinyMCE.
444
501
*
445
- * @param {KeydownEvent } event The keydow event as triggered by tinyMCE .
502
+ * @param {KeyboardEvent } event Keydown event.
446
503
*/
447
504
onKeyDown ( event ) {
448
505
const dom = this . editor . dom ;
449
506
const rootNode = this . editor . getBody ( ) ;
507
+ const { keyCode } = event ;
450
508
451
509
if (
452
- ( event . keyCode === BACKSPACE && isHorizontalEdge ( rootNode , true ) ) ||
453
- ( event . keyCode === DELETE && isHorizontalEdge ( rootNode , false ) )
510
+ ( keyCode === BACKSPACE && isHorizontalEdge ( rootNode , true ) ) ||
511
+ ( keyCode === DELETE && isHorizontalEdge ( rootNode , false ) )
454
512
) {
455
513
if ( ! this . props . onMerge && ! this . props . onRemove ) {
456
514
return ;
457
515
}
458
516
459
517
this . onCreateUndoLevel ( ) ;
460
518
461
- const forward = event . keyCode === DELETE ;
519
+ const forward = keyCode === DELETE ;
462
520
463
521
if ( this . props . onMerge ) {
464
522
this . props . onMerge ( forward ) ;
@@ -476,9 +534,14 @@ export class RichText extends Component {
476
534
event . stopImmediatePropagation ( ) ;
477
535
}
478
536
537
+ const isHorizontalNavigation = keyCode === LEFT || keyCode === RIGHT ;
538
+ if ( isHorizontalNavigation ) {
539
+ this . onHorizontalNavigationKeyDown ( event ) ;
540
+ }
541
+
479
542
// If we click shift+Enter on inline RichTexts, we avoid creating two contenteditables
480
543
// We also split the content and call the onSplit prop if provided.
481
- if ( event . keyCode === ENTER ) {
544
+ if ( keyCode === ENTER ) {
482
545
if ( this . props . multiline ) {
483
546
if ( ! this . props . onSplit ) {
484
547
return ;
0 commit comments