diff --git a/CHANGELOG.md b/CHANGELOG.md
index e2d52e1bc6b..ac1dee74d2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)
+- Converted `EuiCodeEditor` to Typescript ([#2836](https://github.com/elastic/eui/pull/2836))
- Converted `EuiCode` and `EuiCodeBlock` and to Typescript ([#2835](https://github.com/elastic/eui/pull/2835))
- Converted `EuiFilePicker` to TypeScript ([#2832](https://github.com/elastic/eui/issues/2832))
- Exported `EuiSelectOptionProps` type ([#2830](https://github.com/elastic/eui/pull/2830))
diff --git a/src/components/code_editor/__snapshots__/code_editor.test.js.snap b/src/components/code_editor/__snapshots__/code_editor.test.tsx.snap
similarity index 100%
rename from src/components/code_editor/__snapshots__/code_editor.test.js.snap
rename to src/components/code_editor/__snapshots__/code_editor.test.tsx.snap
diff --git a/src/components/code_editor/code_editor.test.js b/src/components/code_editor/code_editor.test.tsx
similarity index 92%
rename from src/components/code_editor/code_editor.test.js
rename to src/components/code_editor/code_editor.test.tsx
index 7a3c8a75319..ac4eb6d4de0 100644
--- a/src/components/code_editor/code_editor.test.js
+++ b/src/components/code_editor/code_editor.test.tsx
@@ -1,6 +1,5 @@
import React from 'react';
-import sinon from 'sinon';
-import { mount } from 'enzyme';
+import { mount, ReactWrapper } from 'enzyme';
import { EuiCodeEditor } from './code_editor';
import { keyCodes } from '../../services';
import {
@@ -46,7 +45,7 @@ describe('EuiCodeEditor', () => {
});
describe('behavior', () => {
- let component;
+ let component: ReactWrapper;
beforeEach(() => {
component = mount();
@@ -69,6 +68,7 @@ describe('EuiCodeEditor', () => {
test('should be enabled when the ui ace box loses focus', () => {
const hint = findTestSubject(component, 'codeEditorHint');
hint.simulate('keyup', { keyCode: keyCodes.ENTER });
+ // @ts-ignore
component.instance().onBlurAce();
expect(
findTestSubject(component, 'codeEditorHint').getDOMNode()
@@ -78,13 +78,15 @@ describe('EuiCodeEditor', () => {
describe('interaction', () => {
test('bluring the ace textbox should call a passed onBlur prop', () => {
- const blurSpy = sinon.spy();
+ const blurSpy = jest.fn().mockName('blurSpy');
const el = mount();
+ // @ts-ignore
el.instance().onBlurAce();
- expect(blurSpy.called).toBe(true);
+ expect(blurSpy).toHaveBeenCalled();
});
test('pressing escape in ace textbox will enable overlay', () => {
+ // @ts-ignore
component.instance().onKeydownAce({
preventDefault: () => {},
stopPropagation: () => {},
diff --git a/src/components/code_editor/code_editor.js b/src/components/code_editor/code_editor.tsx
similarity index 67%
rename from src/components/code_editor/code_editor.js
rename to src/components/code_editor/code_editor.tsx
index faa13e98a78..68a91dd2db9 100644
--- a/src/components/code_editor/code_editor.js
+++ b/src/components/code_editor/code_editor.tsx
@@ -1,14 +1,17 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
+import React, { Component, AriaAttributes, KeyboardEventHandler } from 'react';
import classNames from 'classnames';
-import AceEditor from 'react-ace';
+import AceEditor, { IAceEditorProps } from 'react-ace';
import { htmlIdGenerator, keyCodes } from '../../services';
import { EuiI18n } from '../i18n';
const DEFAULT_MODE = 'text';
-function setOrRemoveAttribute(element, attributeName, value) {
+function setOrRemoveAttribute(
+ element: HTMLTextAreaElement,
+ attributeName: SupportedAriaAttribute,
+ value: SupportedAriaAttributes[SupportedAriaAttribute]
+) {
if (value === null || value === undefined) {
element.removeAttribute(attributeName);
} else {
@@ -16,18 +19,54 @@ function setOrRemoveAttribute(element, attributeName, value) {
}
}
-export class EuiCodeEditor extends Component {
- state = {
+type SupportedAriaAttribute =
+ | 'aria-label'
+ | 'aria-labelledby'
+ | 'aria-describedby';
+type SupportedAriaAttributes = Pick;
+
+export interface EuiCodeEditorProps extends SupportedAriaAttributes {
+ width?: string;
+ height?: string;
+ onBlur?: IAceEditorProps['onBlur'];
+ onFocus?: IAceEditorProps['onFocus'];
+ isReadOnly?: boolean;
+ setOptions: IAceEditorProps['setOptions'];
+ cursorStart?: number;
+ 'data-test-subj'?: string;
+
+ /**
+ * Use string for a built-in mode or object for a custom mode
+ */
+ mode?: IAceEditorProps['mode'] | object;
+}
+
+export interface EuiCodeEditorState {
+ isHintActive: boolean;
+ isEditing: boolean;
+}
+
+export class EuiCodeEditor extends Component<
+ EuiCodeEditorProps,
+ EuiCodeEditorState
+> {
+ static defaultProps = {
+ setOptions: {},
+ };
+
+ state: EuiCodeEditorState = {
isHintActive: true,
isEditing: false,
};
idGenerator = htmlIdGenerator();
+ aceEditor: AceEditor | null = null;
+ editorHint: HTMLDivElement | null = null;
- aceEditorRef = aceEditor => {
+ aceEditorRef = (aceEditor: AceEditor | null) => {
if (aceEditor) {
this.aceEditor = aceEditor;
- const textbox = aceEditor.editor.textInput.getElement();
+ const textbox = aceEditor.editor.textInput.getElement() as HTMLTextAreaElement;
textbox.tabIndex = -1;
textbox.addEventListener('keydown', this.onKeydownAce);
setOrRemoveAttribute(textbox, 'aria-label', this.props['aria-label']);
@@ -44,38 +83,40 @@ export class EuiCodeEditor extends Component {
}
};
- onKeydownAce = ev => {
- if (ev.keyCode === keyCodes.ESCAPE) {
+ onKeydownAce = (event: KeyboardEvent) => {
+ if (event.keyCode === keyCodes.ESCAPE) {
// If the autocompletion context menu is open then we want to let ESCAPE close it but
// **not** exit out of editing mode.
- if (!this.aceEditor.editor.completer) {
- ev.preventDefault();
- ev.stopPropagation();
+ if (this.aceEditor !== null && !this.aceEditor.editor.completer) {
+ event.preventDefault();
+ event.stopPropagation();
this.stopEditing();
- this.editorHint.focus();
+ if (this.editorHint) {
+ this.editorHint.focus();
+ }
}
}
};
- onFocusAce = (...args) => {
+ onFocusAce: IAceEditorProps['onFocus'] = (event, editor) => {
this.setState({
isEditing: true,
});
if (this.props.onFocus) {
- this.props.onFocus(...args);
+ this.props.onFocus(event, editor);
}
};
- onBlurAce = (...args) => {
+ onBlurAce: IAceEditorProps['onBlur'] = (event, editor) => {
this.stopEditing();
if (this.props.onBlur) {
- this.props.onBlur(...args);
+ this.props.onBlur(event, editor);
}
};
- onKeyDownHint = ev => {
- if (ev.keyCode === keyCodes.ENTER) {
- ev.preventDefault();
+ onKeyDownHint: KeyboardEventHandler = event => {
+ if (event.keyCode === keyCodes.ENTER) {
+ event.preventDefault();
this.startEditing();
}
};
@@ -84,7 +125,9 @@ export class EuiCodeEditor extends Component {
this.setState({
isHintActive: false,
});
- this.aceEditor.editor.textInput.focus();
+ if (this.aceEditor !== null) {
+ this.aceEditor.editor.textInput.focus();
+ }
};
stopEditing() {
@@ -99,7 +142,9 @@ export class EuiCodeEditor extends Component {
}
setCustomMode() {
- this.aceEditor.editor.getSession().setMode(this.props.mode);
+ if (this.aceEditor !== null) {
+ this.aceEditor.editor.getSession().setMode(this.props.mode);
+ }
}
componentDidMount() {
@@ -108,7 +153,7 @@ export class EuiCodeEditor extends Component {
}
}
- componentDidUpdate(prevProps) {
+ componentDidUpdate(prevProps: EuiCodeEditorProps) {
if (this.props.mode !== prevProps.mode && this.isCustomMode()) {
this.setCustomMode();
}
@@ -161,7 +206,7 @@ export class EuiCodeEditor extends Component {
ref={hint => {
this.editorHint = hint;
}}
- tabIndex="0"
+ tabIndex={0}
role="button"
onClick={this.startEditing}
onKeyDown={this.onKeyDownHint}
@@ -206,7 +251,7 @@ export class EuiCodeEditor extends Component {