UNPKG

wix-style-react

Version:
217 lines 10.4 kB
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