feat: control editor focus (#43882)

* refactor: MultifileEditor to functional component.

* fix: make editor acquire focus once on mount

Now the editors can leave the dom (e.g. if a tab is clicked), but an
editor will only call for focus if the MultifileEditor itself remounts
This commit is contained in:
Oliver Eyton-Williams
2021-10-19 17:52:51 +02:00
committed by GitHub
parent 2bddbbff42
commit 6c20301204
2 changed files with 148 additions and 145 deletions

View File

@ -1,5 +1,5 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { useRef } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex'; import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
@ -73,14 +73,7 @@ const mapDispatchToProps = {
updateFile updateFile
}; };
class MultifileEditor extends Component { const MultifileEditor = props => {
focusOnHotkeys() {
if (this.props.containerRef.current) {
this.props.containerRef.current.focus();
}
}
render() {
const { const {
challengeFiles, challengeFiles,
containerRef, containerRef,
@ -92,7 +85,7 @@ class MultifileEditor extends Component {
title, title,
visibleEditors: { indexcss, indexhtml, indexjs, indexjsx }, visibleEditors: { indexcss, indexhtml, indexjs, indexjsx },
usesMultifileEditor usesMultifileEditor
} = this.props; } = props;
const editorTheme = theme === 'night' ? 'vs-dark-custom' : 'vs-custom'; const editorTheme = theme === 'night' ? 'vs-dark-custom' : 'vs-custom';
// TODO: the tabs mess up the rendering (scroll doesn't work properly and // TODO: the tabs mess up the rendering (scroll doesn't work properly and
// the in-editor description) // the in-editor description)
@ -126,6 +119,11 @@ class MultifileEditor extends Component {
// TODO: the tabs mess up the rendering (scroll doesn't work properly and // TODO: the tabs mess up the rendering (scroll doesn't work properly and
// the in-editor description) // the in-editor description)
const targetEditor = getTargetEditor(challengeFiles); const targetEditor = getTargetEditor(challengeFiles);
// Only one editor should be focused and that should happen once, after it has
// been mounted. This ref allows the editors to co-ordinate, without having to
// resort to redux.
const canFocusOnMountRef = useRef(true);
return ( return (
<ReflexContainer <ReflexContainer
orientation='horizontal' orientation='horizontal'
@ -138,6 +136,7 @@ class MultifileEditor extends Component {
{indexjsx && ( {indexjsx && (
<ReflexElement {...reflexProps} {...resizeProps}> <ReflexElement {...reflexProps} {...resizeProps}>
<Editor <Editor
canFocusOnMountRef={canFocusOnMountRef}
challengeFiles={challengeFiles} challengeFiles={challengeFiles}
containerRef={containerRef} containerRef={containerRef}
description={targetEditor === 'indexjsx' ? description : null} description={targetEditor === 'indexjsx' ? description : null}
@ -158,11 +157,10 @@ class MultifileEditor extends Component {
{indexhtml && ( {indexhtml && (
<ReflexElement {...reflexProps} {...resizeProps}> <ReflexElement {...reflexProps} {...resizeProps}>
<Editor <Editor
canFocusOnMountRef={canFocusOnMountRef}
challengeFiles={challengeFiles} challengeFiles={challengeFiles}
containerRef={containerRef} containerRef={containerRef}
description={ description={targetEditor === 'indexhtml' ? description : null}
targetEditor === 'indexhtml' ? description : null
}
editorRef={editorRef} editorRef={editorRef}
fileKey='indexhtml' fileKey='indexhtml'
initialTests={initialTests} initialTests={initialTests}
@ -180,6 +178,7 @@ class MultifileEditor extends Component {
{indexcss && ( {indexcss && (
<ReflexElement {...reflexProps} {...resizeProps}> <ReflexElement {...reflexProps} {...resizeProps}>
<Editor <Editor
canFocusOnMountRef={canFocusOnMountRef}
challengeFiles={challengeFiles} challengeFiles={challengeFiles}
containerRef={containerRef} containerRef={containerRef}
description={targetEditor === 'indexcss' ? description : null} description={targetEditor === 'indexcss' ? description : null}
@ -201,6 +200,7 @@ class MultifileEditor extends Component {
{indexjs && ( {indexjs && (
<ReflexElement {...reflexProps} {...resizeProps}> <ReflexElement {...reflexProps} {...resizeProps}>
<Editor <Editor
canFocusOnMountRef={canFocusOnMountRef}
challengeFiles={challengeFiles} challengeFiles={challengeFiles}
containerRef={containerRef} containerRef={containerRef}
description={targetEditor === 'indexjs' ? description : null} description={targetEditor === 'indexjs' ? description : null}
@ -219,8 +219,7 @@ class MultifileEditor extends Component {
</ReflexElement> </ReflexElement>
</ReflexContainer> </ReflexContainer>
); );
} };
}
MultifileEditor.displayName = 'MultifileEditor'; MultifileEditor.displayName = 'MultifileEditor';
MultifileEditor.propTypes = propTypes; MultifileEditor.propTypes = propTypes;

View File

@ -57,6 +57,7 @@ interface EditorProps {
executeChallenge: (options?: { showCompletionModal: boolean }) => void; executeChallenge: (options?: { showCompletionModal: boolean }) => void;
ext: ExtTypes; ext: ExtTypes;
fileKey: FileKeyTypes; fileKey: FileKeyTypes;
canFocusOnMountRef: MutableRefObject<boolean>;
initialEditorContent: string; initialEditorContent: string;
initialExt: string; initialExt: string;
initTests: (tests: Test[]) => void; initTests: (tests: Test[]) => void;
@ -631,12 +632,15 @@ const Editor = (props: EditorProps): JSX.Element => {
function focusIfTargetEditor() { function focusIfTargetEditor() {
const { editor } = dataRef.current; const { editor } = dataRef.current;
if (!editor) return; const { canFocusOnMountRef } = props;
if (!editor || !canFocusOnMountRef.current) return;
if (!props.usesMultifileEditor) { if (!props.usesMultifileEditor) {
// Only one editor? Focus it. // Only one editor? Focus it.
editor.focus(); editor.focus();
canFocusOnMountRef.current = false;
} else if (hasEditableRegion()) { } else if (hasEditableRegion()) {
editor.focus(); editor.focus();
canFocusOnMountRef.current = false;
} }
} }