Skip to content

Commit 2512df9

Browse files
committed
Writing Flow: Consider horizontal handled by stopPropagation in RichText
1 parent d70ec18 commit 2512df9

File tree

1 file changed

+70
-7
lines changed

1 file changed

+70
-7
lines changed

editor/components/rich-text/index.js

+70-7
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,27 @@ import { EVENTS } from './constants';
4545
import { withBlockEditContext } from '../block-edit/context';
4646
import { domToFormat, valueToString } from './format';
4747

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';
4969

5070
/**
5171
* 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;
6181
*/
6282
export function isEmptyInlineBoundary( node ) {
6383
const text = node.nodeName === 'A' ? node.innerText : node.textContent;
64-
return text === '\uFEFF';
84+
return text === TINYMCE_ZWSP;
6585
}
6686

6787
/**
@@ -114,6 +134,7 @@ export class RichText extends Component {
114134
this.onChange = this.onChange.bind( this );
115135
this.onNewBlock = this.onNewBlock.bind( this );
116136
this.onNodeChange = this.onNodeChange.bind( this );
137+
this.onHorizontalNavigationKeyDown = this.onHorizontalNavigationKeyDown.bind( this );
117138
this.onKeyDown = this.onKeyDown.bind( this );
118139
this.onKeyUp = this.onKeyUp.bind( this );
119140
this.changeFormats = this.changeFormats.bind( this );
@@ -438,27 +459,64 @@ export class RichText extends Component {
438459
left: position.left - containerPosition.left + ( position.width / 2 ) + toolbarOffset.left,
439460
};
440461
}
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+
}
441498

442499
/**
443500
* Handles a keydown event from tinyMCE.
444501
*
445-
* @param {KeydownEvent} event The keydow event as triggered by tinyMCE.
502+
* @param {KeyboardEvent} event Keydown event.
446503
*/
447504
onKeyDown( event ) {
448505
const dom = this.editor.dom;
449506
const rootNode = this.editor.getBody();
507+
const { keyCode } = event;
450508

451509
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 ) )
454512
) {
455513
if ( ! this.props.onMerge && ! this.props.onRemove ) {
456514
return;
457515
}
458516

459517
this.onCreateUndoLevel();
460518

461-
const forward = event.keyCode === DELETE;
519+
const forward = keyCode === DELETE;
462520

463521
if ( this.props.onMerge ) {
464522
this.props.onMerge( forward );
@@ -476,9 +534,14 @@ export class RichText extends Component {
476534
event.stopImmediatePropagation();
477535
}
478536

537+
const isHorizontalNavigation = keyCode === LEFT || keyCode === RIGHT;
538+
if ( isHorizontalNavigation ) {
539+
this.onHorizontalNavigationKeyDown( event );
540+
}
541+
479542
// If we click shift+Enter on inline RichTexts, we avoid creating two contenteditables
480543
// We also split the content and call the onSplit prop if provided.
481-
if ( event.keyCode === ENTER ) {
544+
if ( keyCode === ENTER ) {
482545
if ( this.props.multiline ) {
483546
if ( ! this.props.onSplit ) {
484547
return;

0 commit comments

Comments
 (0)