Make WordPress Core

Changeset 63759


Ignore:
Timestamp:
09/18/2026 10:33:37 AM (10 hours ago)
Author:
afercia
Message:

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 https://github.com/WordPress/wordpress-develop/pull/13560

Props shreya0shrivastava, dannerjamanca, arkaprabhachowdhury, afercia.
Fixes #42540.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/js/_enqueues/wp/editor/base.js

    r63600 r63759  
    2222        function SwitchEditors() {
    2323                var tinymce, $$,
    24                         exports = {};
     24                        exports = {},
     25                        isPointingDevice = false;
    2526
    2627                /**
     
    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';
     
    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
     
    535568
    536569                        if ( startNode.length ) {
    537                                 editor.focus();
     570                                if ( isPointingDevice ) {
     571                                        editor.focus();
     572                                }
    538573
    539574                                if ( ! endNode.length ) {
     
    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                }
Note: See TracChangeset for help on using the changeset viewer.