wix-style-react
Version:
wix-style-react
217 lines • 10.4 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import { Editor, EditorState } from 'draft-js';
import EditorUtilities from './EditorUtilities';
import { sizeTypes, inputToTagsSize, dataHooks } from './constants';
import { st, classes, vars } from './VariableInput.st.css';
import StatusIndicator from '../StatusIndicator';
import { StatusContext, getStatusFromContext, } from '../FormField/StatusContext';
import { WixStyleReactContext } from '../WixStyleReactProvider/context';
/** Input with variables as tags */
class VariableInput extends React.PureComponent {
constructor(props) {
super(props);
this._handlePastedText = (text, html, editorState) => {
/** We need to prevent new line when `multilne` is false,
* here we are removing any new lines while pasting text */
if (/\r|\n/.exec(text)) {
text = text.replace(/(\r\n|\n|\r)/gm, '');
this._onEditorChange(EditorUtilities.insertText(editorState, text));
return true;
}
return false;
};
this._isEmpty = () => this.state.editorState.getCurrentContent().getPlainText().length === 0;
this._inputToTagSize = inputSize => {
return inputToTagsSize[inputSize] || VariableInput.defaultProps.size;
};
this._toString = () => {
const { variableTemplate: { prefix, suffix }, } = this.props;
const { editorState } = this.state;
return EditorUtilities.convertToString({
editorState,
prefix,
suffix,
});
};
this._onBlur = () => {
const { onBlur = () => { } } = this.props;
onBlur(this._toString());
};
this._onFocus = () => {
const { onFocus = () => { } } = this.props;
onFocus(this._toString());
};
this._onSubmit = () => {
const { onSubmit = () => { } } = this.props;
onSubmit(this._toString());
};
this._onChange = () => {
const { onChange = () => { } } = this.props;
onChange(this._toString());
};
this._onEditorChange = editorState => {
this._setEditorState(editorState);
};
this._setEditorState = (editorState, onStateChanged = () => { }) => {
const { editorState: editorStateBefore } = this.state;
const { variableTemplate: { prefix, suffix }, } = this.props;
let updateEditorState = EditorUtilities.moveToEdge(editorState);
let triggerCallback = () => { };
if (EditorUtilities.isBlured(editorStateBefore, updateEditorState)) {
// onChange is called after the editor blur handler
// and we can't reflect the changes there, we moved the logic here.
triggerCallback = this._onBlur;
if (EditorUtilities.hasUnparsedEntity(updateEditorState, prefix, suffix)) {
updateEditorState = this._stringToContentState(EditorUtilities.convertToString({
editorState: updateEditorState,
prefix,
suffix,
}));
}
}
else if (EditorUtilities.isContentChanged(editorStateBefore, updateEditorState)) {
triggerCallback = this._onChange;
}
this.setState({ editorState: updateEditorState }, () => {
triggerCallback();
onStateChanged();
});
};
this._stringToContentState = str => {
const { variableParser = () => { }, variableTagPropsParser, variableTemplate: { prefix, suffix }, } = this.props;
const { editorState } = this.state;
const content = EditorUtilities.stringToContentState({
str,
variableParser,
variableTagPropsParser,
prefix,
suffix,
});
return EditorUtilities.pushAndKeepSelection({
editorState,
content,
});
};
this._setStringValue = (str, afterUpdated = () => { }) => {
const updatedEditorState = EditorState.moveSelectionToEnd(this._stringToContentState(str));
this._setEditorState(updatedEditorState, () => {
afterUpdated(updatedEditorState);
});
};
/** Set value to display in the input */
this.setValue = value => {
this._setStringValue(value, () => {
this._onSubmit();
});
};
/** Insert variable at the input cursor position */
this.insertVariable = value => {
const { variableParser, variableTagPropsParser, variableTemplate: { prefix, suffix }, } = this.props;
const { editorState } = this.state;
const text = variableParser(value);
const tagProps = variableTagPropsParser(value);
const newState = text
? EditorUtilities.insertEntity(editorState, { text, value, tagProps })
: EditorUtilities.insertText(editorState, `${prefix}${value}${suffix} `);
this._setEditorState(newState, () => {
this._onSubmit();
});
};
const { size, disabled } = props;
const decorator = EditorUtilities.decoratorFactory({
tag: { size: this._inputToTagSize(size), disabled },
});
this.state = {
editorState: EditorState.createEmpty(decorator),
};
}
componentDidMount() {
const { initialValue } = this.props;
this._setStringValue(initialValue);
this.editorRef = React.createRef();
}
render() {
const { dataHook, multiline, rows, size, disabled, readOnly, placeholder, status, statusMessage, className, } = this.props;
const singleLineProps = {
handlePastedText: this._handlePastedText,
handleReturn: () => 'handled',
};
const finalStatus = getStatusFromContext(this.context, status);
return (React.createElement(WixStyleReactContext.Consumer, null, ({ newColorsBranding }) => (React.createElement("div", { "data-hook": dataHook, className: st(classes.root, {
disabled,
readOnly,
size,
status: finalStatus,
singleLine: !multiline,
newColorsBranding,
}, className), style: { [vars.rows]: rows } },
React.createElement(Editor, { ref: this.editorRef, editorState: this.state.editorState, onChange: this._onEditorChange, onFocus: this._onFocus, placeholder: placeholder, readOnly: disabled || readOnly, ...(readOnly && { tabIndex: 0 }), ...(!multiline && singleLineProps) }),
(status || finalStatus === 'loading') && (React.createElement("span", { className: classes.indicatorWrapper },
React.createElement(StatusIndicator, { dataHook: dataHooks.indicator, status: finalStatus, message: statusMessage })))))));
}
}
VariableInput.contextType = StatusContext;
VariableInput.displayName = 'VariableInput';
VariableInput.propTypes = {
/** Specifies a CSS class name to be appended to the component’s root element */
className: PropTypes.string,
/** Applies a data-hook HTML attribute that can be used in the tests */
dataHook: PropTypes.string,
/** Specifies whether input should be disabled or not */
disabled: PropTypes.bool,
/** Specifies whether input is read only */
readOnly: PropTypes.bool,
/** Defines an initial value to display */
initialValue: PropTypes.string,
/** Specifies whether component allow multiple lines or not. If false, text won’t wrap and horizontal scroll will appear inside of a component. */
multiline: PropTypes.bool,
/** Defines a callback function that is called each time value is changed:
* `onChange(value: String): void` */
onChange: PropTypes.func,
/** Defines a callback function that is called on value submit, in other words after `insertVariable()` and `setValue()`
* `onSubmit(value: String): void` */
onSubmit: PropTypes.func,
/** Defines a callback function that is called on focus out:
* `onBlur(value: String): void` */
onBlur: PropTypes.func,
/** Defines a callback function that is called on focus in:
* `onFocus(value: String): void` */
onFocus: PropTypes.func,
/** Specify the status of a field */
status: PropTypes.oneOf(['error', 'warning', 'loading']),
/** Defines the message to display on status icon hover. If not given or empty there will be no tooltip. */
statusMessage: PropTypes.node,
/** Sets a placeholder message to display */
placeholder: PropTypes.string,
/** Set the height of a component to fit the given number of rows */
rows: PropTypes.number,
/** Controls the size of the input and variable tags */
size: PropTypes.oneOf(['small', 'medium', 'large']),
/** Defines the variable keys that component will parse and convert to <Tag/> components on blur and while using `insertVariable`.
* For each key `variableParser` will be called and should return a proper text for that key or false in case the key is invalid.
* `variableParser(key: String): String|boolean` */
variableParser: PropTypes.func,
/** A function callback that is being called on each keyboard enter and expects a return of object with properties meant for Tag component.
* It is designed to dynamically determine the styling or properties applied to variable tags within the input field.` */
variableTagPropsParser: PropTypes.func,
/** Defines a template for variable recognition. Typed text strings with matching prefix and suffix symbols will be converted to <Tag/> components. */
variableTemplate: PropTypes.shape({
prefix: PropTypes.string,
suffix: PropTypes.string,
}),
};
VariableInput.defaultProps = {
initialValue: '',
multiline: true,
rows: 1,
size: sizeTypes.medium,
variableParser: () => { },
variableTagPropsParser: () => ({}),
variableTemplate: {
prefix: '{{',
suffix: '}}',
},
};
export default VariableInput;
//# sourceMappingURL=VariableInput.js.map