2017-02-28 06:26:24 +00:00
|
|
|
import React, {PureComponent, PropTypes} from 'react';
|
2017-03-03 01:44:07 +00:00
|
|
|
import autobind from 'autobind-decorator';
|
2017-03-08 05:52:17 +00:00
|
|
|
import Editor from './editor';
|
|
|
|
import Input from '../base/debounced-input';
|
2017-03-01 21:15:56 +00:00
|
|
|
|
|
|
|
const MODE_INPUT = 'input';
|
|
|
|
const MODE_EDITOR = 'editor';
|
|
|
|
const TYPE_TEXT = 'text';
|
|
|
|
const NUNJUCKS_REGEX = /({%|%}|{{|}})/;
|
2017-02-27 21:00:13 +00:00
|
|
|
|
2017-03-03 01:44:07 +00:00
|
|
|
@autobind
|
2017-02-28 06:26:24 +00:00
|
|
|
class OneLineEditor extends PureComponent {
|
2017-02-27 21:00:13 +00:00
|
|
|
constructor (props) {
|
|
|
|
super(props);
|
2017-03-01 21:15:56 +00:00
|
|
|
|
2017-03-08 00:56:51 +00:00
|
|
|
let mode;
|
|
|
|
if (props.forceEditor) {
|
2017-03-01 21:15:56 +00:00
|
|
|
mode = MODE_EDITOR;
|
|
|
|
} else if (this._mayContainNunjucks(props.defaultValue)) {
|
|
|
|
mode = MODE_EDITOR;
|
2017-03-08 00:56:51 +00:00
|
|
|
} else {
|
|
|
|
mode = MODE_INPUT;
|
2017-03-01 21:15:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this.state = {mode};
|
2017-02-27 21:00:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
focus () {
|
2017-03-01 21:15:56 +00:00
|
|
|
if (this.state.mode === MODE_EDITOR) {
|
|
|
|
if (!this._editor.hasFocus()) {
|
2017-03-03 21:10:35 +00:00
|
|
|
this._editor.focus();
|
2017-03-01 21:15:56 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this._input.focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-03 21:10:35 +00:00
|
|
|
focusEnd () {
|
|
|
|
if (this.state.mode === MODE_EDITOR) {
|
2017-03-08 00:56:51 +00:00
|
|
|
if (!this._editor.hasFocus()) {
|
|
|
|
this._editor.focusEnd();
|
|
|
|
}
|
2017-03-03 21:10:35 +00:00
|
|
|
} else {
|
|
|
|
this._input.focus();
|
|
|
|
this._input.value = this._input.value + '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
selectAll () {
|
|
|
|
if (this.state.mode === MODE_EDITOR) {
|
|
|
|
this._editor.selectAll();
|
|
|
|
} else {
|
|
|
|
this._input.select();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-01 21:15:56 +00:00
|
|
|
getValue () {
|
|
|
|
if (this.state.mode === MODE_EDITOR) {
|
|
|
|
return this._editor.getValue();
|
|
|
|
} else {
|
|
|
|
return this._input.getValue();
|
2017-02-28 06:26:24 +00:00
|
|
|
}
|
2017-02-27 21:00:13 +00:00
|
|
|
}
|
|
|
|
|
2017-03-03 01:44:07 +00:00
|
|
|
_handleEditorFocus (e) {
|
2017-03-01 21:15:56 +00:00
|
|
|
this.props.onFocus && this.props.onFocus(e);
|
2017-03-03 01:44:07 +00:00
|
|
|
}
|
2017-03-01 21:15:56 +00:00
|
|
|
|
2017-03-03 01:44:07 +00:00
|
|
|
_handleInputFocus (e) {
|
2017-03-01 21:15:56 +00:00
|
|
|
if (this.props.blurOnFocus) {
|
|
|
|
e.target.blur();
|
|
|
|
} else {
|
|
|
|
// If we're focusing the whole thing, blur the input. This happens when
|
|
|
|
// the user tabs to the field.
|
|
|
|
const start = this._input.getSelectionStart();
|
|
|
|
const end = this._input.getSelectionEnd();
|
|
|
|
const focusedFromTabEvent = start === 0 && end > 0 && end === e.target.value.length;
|
|
|
|
|
|
|
|
if (focusedFromTabEvent) {
|
|
|
|
this._input.focusEnd();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Also call the regular callback
|
|
|
|
this.props.onFocus && this.props.onFocus(e);
|
2017-03-03 01:44:07 +00:00
|
|
|
}
|
2017-03-01 21:15:56 +00:00
|
|
|
|
2017-03-03 01:44:07 +00:00
|
|
|
_handleInputChange (value) {
|
2017-03-08 00:56:51 +00:00
|
|
|
this._convertToEditorAndFocus();
|
2017-02-27 21:00:13 +00:00
|
|
|
this.props.onChange && this.props.onChange(value);
|
2017-03-03 01:44:07 +00:00
|
|
|
}
|
2017-02-27 21:00:13 +00:00
|
|
|
|
2017-03-03 01:44:07 +00:00
|
|
|
_handleInputKeyDown (e) {
|
2017-03-01 21:15:56 +00:00
|
|
|
if (this.props.onKeyDown) {
|
|
|
|
this.props.onKeyDown(e, e.target.value);
|
|
|
|
}
|
2017-03-03 01:44:07 +00:00
|
|
|
}
|
2017-03-01 21:15:56 +00:00
|
|
|
|
2017-03-03 01:44:07 +00:00
|
|
|
_handleEditorBlur () {
|
2017-03-03 21:10:35 +00:00
|
|
|
// Clear selection on blur to match default <input> behavior
|
|
|
|
this._editor.clearSelection();
|
|
|
|
this.props.onBlur && this.props.onBlur();
|
|
|
|
|
2017-03-01 21:15:56 +00:00
|
|
|
if (this.props.forceEditor) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-08 00:56:51 +00:00
|
|
|
this._convertToInputIfNotFocused();
|
2017-03-03 01:44:07 +00:00
|
|
|
}
|
2017-03-01 21:15:56 +00:00
|
|
|
|
2017-03-03 01:44:07 +00:00
|
|
|
_handleEditorKeyDown (e) {
|
2017-03-01 00:10:23 +00:00
|
|
|
// submit form if needed
|
|
|
|
if (e.keyCode === 13) {
|
|
|
|
let node = e.target;
|
|
|
|
for (let i = 0; i < 20 && node; i++) {
|
|
|
|
if (node.tagName === 'FORM') {
|
2017-03-03 20:09:08 +00:00
|
|
|
node.dispatchEvent(new window.Event('submit'));
|
2017-03-01 00:10:23 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
node = node.parentNode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Also call the original if there was one
|
2017-03-01 21:15:56 +00:00
|
|
|
this.props.onKeyDown && this.props.onKeyDown(e, this.getValue());
|
2017-03-03 01:44:07 +00:00
|
|
|
}
|
2017-03-01 00:10:23 +00:00
|
|
|
|
2017-03-08 00:56:51 +00:00
|
|
|
_convertToEditorAndFocus () {
|
|
|
|
if (this.state.mode !== MODE_INPUT) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const start = this._input.getSelectionStart();
|
|
|
|
const end = this._input.getSelectionEnd();
|
|
|
|
|
|
|
|
// Wait for the editor to swap and restore cursor position
|
|
|
|
const check = () => {
|
|
|
|
if (this._editor) {
|
|
|
|
this._editor.setSelection(start, end);
|
|
|
|
} else {
|
|
|
|
setTimeout(check, 40);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Tell the component to show the editor
|
|
|
|
check();
|
|
|
|
this._convertToEditor();
|
|
|
|
}
|
|
|
|
|
|
|
|
_convertToEditor () {
|
|
|
|
if (this.state.mode === MODE_EDITOR) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.state.mode !== MODE_EDITOR) {
|
|
|
|
this.setState({mode: MODE_EDITOR});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_convertToInputIfNotFocused () {
|
|
|
|
if (this.state.mode === MODE_INPUT || this.props.forceEditor) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._editor.hasFocus()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._mayContainNunjucks(this.getValue())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({mode: MODE_INPUT});
|
|
|
|
}
|
|
|
|
|
2017-03-03 01:44:07 +00:00
|
|
|
_setEditorRef (n) {
|
|
|
|
this._editor = n;
|
|
|
|
}
|
|
|
|
|
|
|
|
_setInputRef (n) {
|
|
|
|
this._input = n;
|
|
|
|
}
|
|
|
|
|
|
|
|
_mayContainNunjucks (text) {
|
|
|
|
return !!text.match(NUNJUCKS_REGEX);
|
|
|
|
}
|
2017-02-27 21:00:13 +00:00
|
|
|
|
|
|
|
render () {
|
2017-03-01 21:15:56 +00:00
|
|
|
const {
|
|
|
|
defaultValue,
|
|
|
|
className,
|
|
|
|
onChange,
|
|
|
|
placeholder,
|
|
|
|
onBlur,
|
|
|
|
render,
|
2017-03-03 20:09:08 +00:00
|
|
|
type: originalType
|
2017-03-01 21:15:56 +00:00
|
|
|
} = this.props;
|
|
|
|
|
|
|
|
const {mode} = this.state;
|
|
|
|
|
|
|
|
const type = originalType || TYPE_TEXT;
|
|
|
|
const showEditor = type === TYPE_TEXT && mode === MODE_EDITOR;
|
|
|
|
|
|
|
|
if (showEditor) {
|
|
|
|
return (
|
|
|
|
<Editor
|
|
|
|
ref={this._setEditorRef}
|
|
|
|
defaultTabBehavior
|
|
|
|
hideLineNumbers
|
|
|
|
hideScrollbars
|
|
|
|
noDragDrop
|
|
|
|
noMatchBrackets
|
|
|
|
noStyleActiveLine
|
|
|
|
noLint
|
|
|
|
singleLine
|
|
|
|
tabIndex={0}
|
|
|
|
placeholder={placeholder}
|
|
|
|
onBlur={this._handleEditorBlur}
|
|
|
|
onKeyDown={this._handleEditorKeyDown}
|
|
|
|
onFocus={this._handleEditorFocus}
|
2017-03-08 00:56:51 +00:00
|
|
|
onMouseLeave={this._convertToInputIfNotFocused}
|
2017-03-03 01:44:07 +00:00
|
|
|
onChange={onChange}
|
2017-03-01 21:15:56 +00:00
|
|
|
render={render}
|
|
|
|
className="editor--single-line"
|
|
|
|
defaultValue={defaultValue}
|
|
|
|
lineWrapping={false}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
<Input
|
|
|
|
ref={this._setInputRef}
|
|
|
|
type={type}
|
|
|
|
className={'editor--single-line input ' + className || ''}
|
2017-03-08 00:56:51 +00:00
|
|
|
style={{
|
|
|
|
padding: '0 4px', // To match CodeMirror
|
|
|
|
width: '100%'
|
|
|
|
}}
|
2017-03-01 21:15:56 +00:00
|
|
|
placeholder={placeholder}
|
|
|
|
defaultValue={defaultValue}
|
|
|
|
onChange={this._handleInputChange}
|
2017-03-08 00:56:51 +00:00
|
|
|
onMouseEnter={this._convertToEditor}
|
2017-03-01 21:15:56 +00:00
|
|
|
onBlur={onBlur}
|
|
|
|
onFocus={this._handleInputFocus}
|
|
|
|
onKeyDown={this._handleInputKeyDown}
|
|
|
|
/>
|
2017-03-03 20:09:08 +00:00
|
|
|
);
|
2017-03-01 21:15:56 +00:00
|
|
|
}
|
2017-02-27 21:00:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
OneLineEditor.propTypes = Object.assign({}, Editor.propTypes, {
|
|
|
|
defaultValue: PropTypes.string.isRequired,
|
2017-03-01 21:15:56 +00:00
|
|
|
|
|
|
|
// Optional
|
|
|
|
type: PropTypes.string,
|
|
|
|
onBlur: PropTypes.func,
|
|
|
|
onKeyDown: PropTypes.func,
|
|
|
|
onFocus: PropTypes.func,
|
|
|
|
onChange: PropTypes.func,
|
|
|
|
render: PropTypes.func,
|
|
|
|
placeholder: PropTypes.string,
|
|
|
|
blurOnFocus: PropTypes.bool,
|
2017-03-08 00:56:51 +00:00
|
|
|
forceEditor: PropTypes.bool
|
2017-02-27 21:00:13 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
export default OneLineEditor;
|