@wix/design-system
Version:
@wix/design-system
448 lines (445 loc) • 18.7 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports["default"] = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _react = _interopRequireDefault(require("react"));
var _draftJs = require("draft-js");
var _EditorUtilities = _interopRequireDefault(require("./EditorUtilities"));
var _constants = require("./constants");
var _VariableInputSt = require("./VariableInput.st.css.js");
var _StatusIndicator = _interopRequireDefault(require("../StatusIndicator"));
var _StatusContext = require("../FormField/StatusContext");
var _jsxFileName = "/home/builduser/work/57e038ea7326c1ec/packages/wix-design-system/dist/cjs/VariableInput/VariableInput.jsx";
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2["default"])(o), (0, _possibleConstructorReturn2["default"])(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2["default"])(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
/**
* Error Boundary to catch Draft.js crashes on mobile devices with IME/autocorrect
*
* Known Issue: Draft.js has a fundamental incompatibility with Android's Input Method Editor (IME).
* When Android autocorrect modifies the DOM directly (e.g., on SPACE key press), Draft.js attempts
* to reconcile its virtual state with DOM nodes that have already been removed/modified by the IME,
* resulting in "Failed to execute 'removeChild' on 'Node'" errors.
*
* This Error Boundary prevents the entire component from crashing and allows it to auto-recover.
* While console warnings will still appear, the user experience remains functional.
*
* TODO: After Draft-JS migration, make sure this error is no longer thrown and then remove this error boundary.
*/
var EditorErrorBoundary = /*#__PURE__*/function (_React$Component) {
function EditorErrorBoundary(props) {
var _this;
(0, _classCallCheck2["default"])(this, EditorErrorBoundary);
_this = _callSuper(this, EditorErrorBoundary, [props]);
_this.state = {
hasError: false,
errorCount: 0
};
return _this;
}
(0, _inherits2["default"])(EditorErrorBoundary, _React$Component);
return (0, _createClass2["default"])(EditorErrorBoundary, [{
key: "componentDidCatch",
value: function componentDidCatch(error, errorInfo) {
console.warn('VariableInput: Draft.js error caught (likely mobile IME conflict):', error.message);
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps, prevState) {
// Auto-recover with max retry limit to prevent infinite loops
if (this.state.hasError && this.state.errorCount < 5) {
this.setState({
hasError: false,
errorCount: this.state.errorCount + 1
});
}
}
}, {
key: "render",
value: function render() {
return this.props.children;
}
}], [{
key: "getDerivedStateFromError",
value: function getDerivedStateFromError() {
return {
hasError: true
};
}
}]);
}(_react["default"].Component);
/** Input with variables as tags */
var VariableInput = /*#__PURE__*/function (_React$PureComponent) {
function VariableInput(props) {
var _this2;
(0, _classCallCheck2["default"])(this, VariableInput);
_this2 = _callSuper(this, VariableInput, [props]);
_this2._handleCompositionStart = function () {
_this2.isComposing = true;
};
_this2._handleCompositionUpdate = function (e) {
// During IME composition, manually trigger onChange with current DOM text
// This fixes Android autosuggestion issue where draft-js doesn't fire onChange
if (_this2.props.onChange && e.target) {
var text = e.target.textContent || '';
_this2.props.onChange(text);
}
};
_this2._handleCompositionEnd = function () {
_this2.isComposing = false;
};
_this2._handleInput = function (e) {
if (_this2.isComposing && _this2.props.onChange && e.target) {
var text = e.target.textContent || '';
_this2.props.onChange(text);
}
};
_this2._handlePastedText = function (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, '');
_this2._onEditorChange(_EditorUtilities["default"].insertText(editorState, text));
return true;
}
return false;
};
_this2._isEmpty = function () {
return _this2.state.editorState.getCurrentContent().getPlainText().length === 0;
};
_this2._inputToTagSize = function (inputSize) {
return _constants.inputToTagsSize[inputSize] || VariableInput.defaultProps.size;
};
_this2._toString = function () {
var _this2$props$variable = _this2.props.variableTemplate,
prefix = _this2$props$variable.prefix,
suffix = _this2$props$variable.suffix;
var editorState = _this2.state.editorState;
return _EditorUtilities["default"].convertToString({
editorState: editorState,
prefix: prefix,
suffix: suffix
});
};
_this2._onBlur = function () {
var _this2$props$onBlur = _this2.props.onBlur,
onBlur = _this2$props$onBlur === void 0 ? function () {} : _this2$props$onBlur;
onBlur(_this2._toString());
};
_this2._onMouseDown = function () {
_this2._isMouseDown = true;
};
_this2._onMouseUp = function () {
_this2._isMouseDown = false;
};
_this2._onFocus = function () {
var _this2$props$onFocus = _this2.props.onFocus,
onFocus = _this2$props$onFocus === void 0 ? function () {} : _this2$props$onFocus;
onFocus(_this2._toString());
};
_this2._onSubmit = function () {
var _this2$props$onSubmit = _this2.props.onSubmit,
onSubmit = _this2$props$onSubmit === void 0 ? function () {} : _this2$props$onSubmit;
onSubmit(_this2._toString());
};
_this2._onChange = function () {
var _this2$props$onChange = _this2.props.onChange,
onChange = _this2$props$onChange === void 0 ? function () {} : _this2$props$onChange;
// Only call onChange if not composing (during composition, manual handlers will call it)
if (!_this2.isComposing) {
onChange(_this2._toString());
}
};
_this2._onEditorChange = function (editorState) {
var prevHasFocus = _this2.state.editorState.getSelection().getHasFocus();
var newHasFocus = editorState.getSelection().getHasFocus();
var becomesFocused = !prevHasFocus && newHasFocus;
var isKeyboard = !_this2._isMouseDown;
if (becomesFocused && isKeyboard) {
var selectionAtEnd = _draftJs.EditorState.moveSelectionToEnd(editorState).getSelection();
editorState = _draftJs.EditorState.forceSelection(editorState, selectionAtEnd);
}
_this2._setEditorState(editorState);
};
_this2._setEditorState = function (editorState) {
var onStateChanged = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
var editorStateBefore = _this2.state.editorState;
var _this2$props$variable2 = _this2.props.variableTemplate,
prefix = _this2$props$variable2.prefix,
suffix = _this2$props$variable2.suffix;
var updateEditorState = _EditorUtilities["default"].moveToEdge(editorState);
var triggerCallback = function triggerCallback() {};
if (_EditorUtilities["default"].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 = _this2._onBlur;
if (_EditorUtilities["default"].hasUnparsedEntity(updateEditorState, prefix, suffix)) {
updateEditorState = _this2._stringToContentState(_EditorUtilities["default"].convertToString({
editorState: updateEditorState,
prefix: prefix,
suffix: suffix
}));
}
} else if (_EditorUtilities["default"].isContentChanged(editorStateBefore, updateEditorState)) {
triggerCallback = _this2._onChange;
}
_this2.setState({
editorState: updateEditorState
}, function () {
triggerCallback();
onStateChanged();
});
};
_this2._stringToContentState = function (str) {
var _this2$props = _this2.props,
_this2$props$variable3 = _this2$props.variableParser,
variableParser = _this2$props$variable3 === void 0 ? function () {} : _this2$props$variable3,
variableTagPropsParser = _this2$props.variableTagPropsParser,
_this2$props$variable4 = _this2$props.variableTemplate,
prefix = _this2$props$variable4.prefix,
suffix = _this2$props$variable4.suffix;
var editorState = _this2.state.editorState;
var content = _EditorUtilities["default"].stringToContentState({
str: str,
variableParser: variableParser,
variableTagPropsParser: variableTagPropsParser,
prefix: prefix,
suffix: suffix
});
return _EditorUtilities["default"].pushAndKeepSelection({
editorState: editorState,
content: content
});
};
_this2._setStringValue = function (str) {
var afterUpdated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
var updatedEditorState = _draftJs.EditorState.moveSelectionToEnd(_this2._stringToContentState(str));
_this2._setEditorState(updatedEditorState, function () {
afterUpdated(updatedEditorState);
});
};
/** Set value to display in the input */
_this2.setValue = function (value) {
_this2._setStringValue(value, function () {
_this2._onSubmit();
});
};
/** Insert variable at the input cursor position */
_this2.insertVariable = function (value) {
var _this2$props2 = _this2.props,
variableParser = _this2$props2.variableParser,
variableTagPropsParser = _this2$props2.variableTagPropsParser,
_this2$props2$variabl = _this2$props2.variableTemplate,
prefix = _this2$props2$variabl.prefix,
suffix = _this2$props2$variabl.suffix;
var editorState = _this2.state.editorState;
var text = variableParser(value);
var tagProps = variableTagPropsParser(value);
var newState = text ? _EditorUtilities["default"].insertEntity(editorState, {
text: text,
value: value,
tagProps: tagProps
}) : _EditorUtilities["default"].insertText(editorState, "".concat(prefix).concat(value).concat(suffix, " "));
_this2._setEditorState(newState, function () {
_this2._onSubmit();
});
};
/** Insert Text at the input cursor position */
_this2.insertText = function (value) {
var editorState = _this2.state.editorState;
var newState = _EditorUtilities["default"].insertText(editorState, value);
_this2._setEditorState(newState, function () {
_this2._onSubmit();
});
};
/**
* Focus the input at the end of the content.
* Optionally, if variableKey is provided, focus after that specific variable.
*/
_this2.focus = function () {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
variableKey = _ref.variableKey;
var editorState = _this2.state.editorState;
var selectionToFocus;
if (variableKey) {
var targetRange = _EditorUtilities["default"].findEntityRangeByKey(editorState, variableKey);
if (targetRange) {
var blockKey = targetRange.blockKey,
end = targetRange.end;
selectionToFocus = new _draftJs.SelectionState({
anchorKey: blockKey,
anchorOffset: end,
focusKey: blockKey,
focusOffset: end
});
}
}
if (!selectionToFocus) {
selectionToFocus = _draftJs.EditorState.moveSelectionToEnd(editorState).getSelection();
}
var newEditorState = _draftJs.EditorState.forceSelection(editorState, selectionToFocus.merge({
hasFocus: true
}));
_this2.setState({
editorState: newEditorState
}, function () {
_this2._onFocus();
});
};
var size = props.size,
disabled = props.disabled;
var decorator = _EditorUtilities["default"].decoratorFactory({
tag: {
size: _this2._inputToTagSize(size),
disabled: disabled
}
});
_this2.state = {
editorState: _draftJs.EditorState.createEmpty(decorator)
};
_this2.editorRef = /*#__PURE__*/_react["default"].createRef();
// IME composition tracking for Android autosuggestions
_this2.isComposing = false;
// Track mouse-initiated focus to avoid overriding click cursor position
_this2._isMouseDown = false;
return _this2;
}
(0, _inherits2["default"])(VariableInput, _React$PureComponent);
return (0, _createClass2["default"])(VariableInput, [{
key: "componentDidMount",
value: function componentDidMount() {
var initialValue = this.props.initialValue;
this._setStringValue(initialValue);
// Add IME composition event listeners to handle Android autosuggestions
if (this.editorRef.current) {
var editorNode = this.editorRef.current.editor;
if (editorNode) {
editorNode.addEventListener('compositionstart', this._handleCompositionStart);
editorNode.addEventListener('compositionupdate', this._handleCompositionUpdate);
editorNode.addEventListener('compositionend', this._handleCompositionEnd);
editorNode.addEventListener('input', this._handleInput);
}
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
if (this.editorRef.current) {
var editorNode = this.editorRef.current.editor;
if (editorNode) {
editorNode.removeEventListener('compositionstart', this._handleCompositionStart);
editorNode.removeEventListener('compositionupdate', this._handleCompositionUpdate);
editorNode.removeEventListener('compositionend', this._handleCompositionEnd);
editorNode.removeEventListener('input', this._handleInput);
}
}
}
}, {
key: "render",
value: function render() {
var _this$props = this.props,
dataHook = _this$props.dataHook,
multiline = _this$props.multiline,
rows = _this$props.rows,
size = _this$props.size,
disabled = _this$props.disabled,
readOnly = _this$props.readOnly,
placeholder = _this$props.placeholder,
status = _this$props.status,
statusMessage = _this$props.statusMessage,
className = _this$props.className;
var singleLineProps = {
handlePastedText: this._handlePastedText,
handleReturn: function handleReturn() {
return 'handled';
}
};
var finalStatus = (0, _StatusContext.getStatusFromContext)(this.context, status);
return /*#__PURE__*/_react["default"].createElement("div", {
"data-hook": dataHook,
onMouseDown: this._onMouseDown,
onMouseUp: this._onMouseUp,
className: (0, _VariableInputSt.st)(_VariableInputSt.classes.root, {
disabled: disabled,
readOnly: readOnly,
size: size,
status: finalStatus,
singleLine: !multiline
}, className),
style: (0, _defineProperty2["default"])({}, _VariableInputSt.vars.rows, rows),
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 165,
columnNumber: 7
}
}, /*#__PURE__*/_react["default"].createElement(EditorErrorBoundary, {
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 182,
columnNumber: 9
}
}, /*#__PURE__*/_react["default"].createElement(_draftJs.Editor, (0, _extends2["default"])({
ref: this.editorRef,
spellCheck: this.props.spellCheck,
editorState: this.state.editorState,
onChange: this._onEditorChange,
onFocus: this._onFocus,
placeholder: placeholder,
readOnly: disabled || readOnly
}, readOnly && {
tabIndex: 0
}, !multiline && singleLineProps, {
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 183,
columnNumber: 11
}
}))), (status || finalStatus === 'loading') && /*#__PURE__*/_react["default"].createElement("span", {
className: _VariableInputSt.classes.indicatorWrapper,
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 198,
columnNumber: 11
}
}, /*#__PURE__*/_react["default"].createElement(_StatusIndicator["default"], {
dataHook: _constants.dataHooks.indicator,
status: finalStatus,
message: statusMessage,
__self: this,
__source: {
fileName: _jsxFileName,
lineNumber: 199,
columnNumber: 13
}
})));
}
}]);
}(_react["default"].PureComponent);
VariableInput.contextType = _StatusContext.StatusContext;
VariableInput.displayName = 'VariableInput';
VariableInput.defaultProps = {
initialValue: '',
multiline: true,
rows: 1,
spellCheck: false,
size: _constants.sizeTypes.medium,
variableParser: function variableParser() {},
variableTagPropsParser: function variableTagPropsParser() {
return {};
},
variableTemplate: {
prefix: '{{',
suffix: '}}'
}
};
var _default = exports["default"] = VariableInput;