Skip to content

Commit a1f7749

Browse files
committed
Editor: Don't move focus to the Classic editor when using the keyboard to switch editor mode.
Pointing device (mouse, touch, etc.) users benefit from focus being moved to the editor when switching it from Visual to Code mode and vice-versa, as they can just keep typing without loosing the previous caret position or selection. However, moving focus is confusing and distracting to keyboard and screen reader users as they loose their place. In this case, focus should stay on the switch buttons. Previous caret position or selection is preserved in both cases. Developed in #13560 Props shreya0shrivastava, dannerjamanca, arkaprabhachowdhury, afercia. Fixes #42540. git-svn-id: https://develop.svn.wordpress.org/trunk@63759 602fd350-edb4-49c9-b593-d223f7449a82
1 parent f27699d commit a1f7749

1 file changed

Lines changed: 77 additions & 5 deletions

File tree

‎src/js/_enqueues/wp/editor/base.js‎

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ window.wp = window.wp || {};
2121
*/
2222
function SwitchEditors() {
2323
var tinymce, $$,
24-
exports = {};
24+
exports = {},
25+
isPointingDevice = false;
2526

2627
/**
2728
* Initializes the editor utility functions.
@@ -42,7 +43,15 @@ window.wp = window.wp || {};
4243
var id, mode,
4344
target = $$( event.target );
4445

46+
4547
if ( target.hasClass( 'wp-switch-editor' ) ) {
48+
/*
49+
* Determine whether the click event is fired by using
50+
* a pointing device including Safari fallback and
51+
* unknown hardware pointers.
52+
*/
53+
isPointingDevice = event.detail > 0 || ( event.pointerType !== undefined && event.pointerType !== '' );
54+
4655
id = target.attr( 'data-wp-editor-id' );
4756
mode = target.hasClass( 'switch-tmce' ) ? 'tmce' : 'html';
4857
switchEditor( id, mode );
@@ -118,6 +127,30 @@ window.wp = window.wp || {};
118127
addHTMLBookmarkInTextAreaContent( $textarea );
119128

120129
if ( editor ) {
130+
// Store the original TinyMCE editor focus() method.
131+
const originalEditorFocusInstance = editor.focus;
132+
133+
/*
134+
* Override the editor's focus method to conditionally skip
135+
* focusing the editor based on the input device. Note that
136+
* editor.focus() aleady uses a `skipFocus` parameter. When
137+
* it is true, it calls activateEditor(editor) instead of
138+
* focusEditor(editor).
139+
*/
140+
editor.focus = function ( skipFocus ) {
141+
if ( ! isPointingDevice) {
142+
skipFocus = true;
143+
}
144+
145+
originalEditorFocusInstance.call( editor, skipFocus );
146+
};
147+
148+
/*
149+
* The editor show() method calls several other methods that
150+
* end up setting focus to the editor. We want to skip
151+
* setting focus when switching editors and the user is
152+
* using a keyboard or a non-pointing device.
153+
*/
121154
editor.show();
122155

123156
// No point to resize the iframe in iOS.
@@ -534,7 +567,9 @@ window.wp = window.wp || {};
534567
endNode = editor.$( '.mce_SELRES_end' ).attr( 'data-mce-bogus', 1 );
535568

536569
if ( startNode.length ) {
537-
editor.focus();
570+
if ( isPointingDevice ) {
571+
editor.focus();
572+
}
538573

539574
if ( ! endNode.length ) {
540575
editor.selection.select( startNode[0] );
@@ -838,16 +873,53 @@ window.wp = window.wp || {};
838873
start = selection.start,
839874
end = selection.end || selection.start;
840875

841-
if ( textArea.focus ) {
842-
// Wait for the Visual editor to be hidden, then focus and scroll to the position.
876+
/*
877+
* Guard against the scenarios where editor.getElement() may return
878+
* something that isn't a standard textarea element e.g. the editor
879+
* may have been removed/destroyed/mutated.
880+
*/
881+
if ( ! textArea.focus ) {
882+
return;
883+
}
884+
885+
/**
886+
* Applies the selection range to the textarea.
887+
*/
888+
function applySelection() {
889+
/*
890+
* In Safari, the focus event fires before the browser has fully
891+
* completed the focus transition. Calling setTimeout with a 0ms
892+
* delay queues the callback function task into the task queue
893+
* so that the callback is executed after all pending tasks have
894+
* cleared. Safe for other browsers.
895+
*/
843896
setTimeout( function() {
897+
// Guard against the editor being destroyed during the timeout.
898+
if ( ! textArea.isConnected ) {
899+
return;
900+
}
901+
844902
textArea.setSelectionRange( start, end );
903+
}, 0 );
904+
}
905+
906+
// Logic for pointing devices.
907+
if ( isPointingDevice ) {
908+
setTimeout( function() {
909+
applySelection();
845910
if ( textArea.blur ) {
846-
// Defocus before focusing.
847911
textArea.blur();
848912
}
849913
textArea.focus();
850914
}, 100 );
915+
} else {
916+
/*
917+
* For non-pointing devices: wait until users move focus into the
918+
* textarea (e.g. via keyboard Tab), then restore the selection.
919+
* By using `once`, the listener is invoked at most once after
920+
* being added and it's automatically removed when invoked.
921+
*/
922+
textArea.addEventListener( 'focus', applySelection, { once: true } );
851923
}
852924
}
853925

0 commit comments

Comments
 (0)