diff --git a/packages/browser-repl/src/components/ace-theme.js b/packages/browser-repl/src/components/ace-theme.js index e9b298e89a..eafb76ff0a 100644 --- a/packages/browser-repl/src/components/ace-theme.js +++ b/packages/browser-repl/src/components/ace-theme.js @@ -4,6 +4,7 @@ const foregroundColor = uiColors.gray.light3; const backgroundColor = uiColors.gray.dark3; const borderColor = uiColors.gray.dark1; const activeLineColor = uiColors.gray.dark2; +const selectionColor = uiColors.gray.dark1; const cursorColor = uiColors.green.base; @@ -40,7 +41,7 @@ const layoutCss = ` background: transparent; } .ace-mongosh .ace_marker-layer .ace_selection { - background: transparent; + background: ${selectionColor}; } `; diff --git a/packages/browser-repl/src/components/editor.spec.tsx b/packages/browser-repl/src/components/editor.spec.tsx index 138ca52422..1e4fbe5cfd 100644 --- a/packages/browser-repl/src/components/editor.spec.tsx +++ b/packages/browser-repl/src/components/editor.spec.tsx @@ -134,4 +134,14 @@ describe('', () => { execCommandBoundTo(aceEditor, 'Down'); expect(spy).not.to.have.been.called; }); + + it('sets the input ref for the editor', () => { + const spy = sinon.spy(); + const wrapper = mount(); + + const aceEditor = getAceEditorInstance(wrapper); + + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].editor).to.equal(aceEditor); + }); }); diff --git a/packages/browser-repl/src/components/editor.tsx b/packages/browser-repl/src/components/editor.tsx index a10c4b77ee..acf4744604 100644 --- a/packages/browser-repl/src/components/editor.tsx +++ b/packages/browser-repl/src/components/editor.tsx @@ -21,6 +21,7 @@ interface EditorProps { onArrowDownOnLastLine?(): void | Promise; onChange?(value: string): void | Promise; autocompleter?: Autocompleter; + setInputRef?(ref): void; value?: string; } @@ -30,6 +31,7 @@ export class Editor extends Component { onArrowUpOnFirstLine: PropTypes.func, onArrowDownOnLastLine: PropTypes.func, onChange: PropTypes.func, + setInputRef: PropTypes.func, value: PropTypes.string }; @@ -71,6 +73,11 @@ export class Editor extends Component { }} name={`mongosh-ace-${Date.now()}`} mode="javascript" + ref={(ref: any): void => { + if (this.props.setInputRef) { + this.props.setInputRef(ref); + } + }} theme="mongosh" onChange={this.props.onChange} onLoad={this.onEditorLoad} diff --git a/packages/browser-repl/src/components/shell-input.tsx b/packages/browser-repl/src/components/shell-input.tsx index 5ed935cf37..002ba4dda2 100644 --- a/packages/browser-repl/src/components/shell-input.tsx +++ b/packages/browser-repl/src/components/shell-input.tsx @@ -12,6 +12,7 @@ interface ShellInputProps { onInput?(code: string): void | Promise; history?: readonly string[]; autocompleter?: Autocompleter; + setInputRef?(ref): void; } interface ShellInputState { @@ -22,7 +23,8 @@ export class ShellInput extends Component { static propTypes = { onInput: PropTypes.func, history: PropTypes.arrayOf(PropTypes.string), - autocompleter: PropTypes.object + autocompleter: PropTypes.object, + setInputRef: PropTypes.func }; readonly state: ShellInputState = { @@ -120,6 +122,7 @@ export class ShellInput extends Component { onArrowUpOnFirstLine={this.historyBack} onArrowDownOnLastLine={this.historyNext} autocompleter={this.props.autocompleter} + setInputRef={this.props.setInputRef} />); const className = classnames(styles['shell-input']); diff --git a/packages/browser-repl/src/components/shell.spec.tsx b/packages/browser-repl/src/components/shell.spec.tsx index 0b79d0213e..32670c4f19 100644 --- a/packages/browser-repl/src/components/shell.spec.tsx +++ b/packages/browser-repl/src/components/shell.spec.tsx @@ -7,6 +7,8 @@ import { ShellInput } from './shell-input'; import { ShellOutput } from './shell-output'; import { ShellOutputEntry } from './shell-output-line'; +const styles = require('./shell.less'); + const wait: (ms?: number) => Promise = (ms = 10) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; @@ -17,6 +19,7 @@ describe('', () => { let fakeRuntime; let wrapper: ShallowWrapper | ReactWrapper; let scrollIntoView; + let elementFocus; let onInput; beforeEach(() => { @@ -27,6 +30,7 @@ describe('', () => { }; scrollIntoView = sinon.spy(Element.prototype, 'scrollIntoView'); + elementFocus = sinon.spy(HTMLElement.prototype, 'focus'); fakeRuntime = { evaluate: sinon.fake.returns({ value: 'some result' }) @@ -43,6 +47,7 @@ describe('', () => { afterEach(() => { scrollIntoView.restore(); + elementFocus.restore(); }); it('renders a ShellOutput component', () => { @@ -265,4 +270,30 @@ describe('', () => { expect(Element.prototype.scrollIntoView).to.have.been.calledTwice; }); + + it('focuses on the input when the background container is clicked', () => { + wrapper = mount(); + const container = wrapper.find(`.${styles.shell}`); + + const fakeMouseEvent: any = { + target: 'a', + currentTarget: 'a' + }; + container.prop('onClick')(fakeMouseEvent); + + expect(HTMLElement.prototype.focus).to.have.been.calledOnce; + }); + + it('does not focus on the input when an element that is not the background container is clicked', () => { + wrapper = mount(); + const container = wrapper.find(`.${styles.shell}`); + + const fakeMouseEvent: any = { + target: 'a', + currentTarget: 'b' + }; + container.prop('onClick')(fakeMouseEvent); + + expect(HTMLElement.prototype.focus).to.not.have.been.called; + }); }); diff --git a/packages/browser-repl/src/components/shell.tsx b/packages/browser-repl/src/components/shell.tsx index 9065bb4b05..fca501b8b6 100644 --- a/packages/browser-repl/src/components/shell.tsx +++ b/packages/browser-repl/src/components/shell.tsx @@ -98,6 +98,9 @@ export class Shell extends Component { }; private shellInputElement?: HTMLElement; + private shellInputRef?: { + editor?: HTMLElement; + }; readonly state: ShellState = { output: this.props.initialOutput.slice(-this.props.maxOutputLength), @@ -188,18 +191,34 @@ export class Shell extends Component { this.shellInputElement.scrollIntoView(); } + private onShellClicked = (event: React.MouseEvent): void => { + // Focus on input when clicking the shell background (not clicking output). + if (event.currentTarget === event.target) { + if (this.shellInputRef && this.shellInputRef.editor) { + this.shellInputRef.editor.focus(); + } + } + }; + render(): JSX.Element { - return ( - - - - { this.shellInputElement = el; }}> - + return ( + + + + + { this.shellInputElement = el; }}> + { this.shellInputRef = ref;}} + /> + - ); + ); } }