@helpscout/hsds-react
Version:
React component library for Help Scout's Design System
381 lines (310 loc) • 12.5 kB
JavaScript
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = exports.EditableFieldComposite = void 0;
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose"));
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _getValidProps = _interopRequireDefault(require("@helpscout/react-utils/dist/getValidProps"));
var _fastDeepEqual = _interopRequireDefault(require("fast-deep-equal"));
var _classnames = _interopRequireDefault(require("classnames"));
var _Keys = require("../../constants/Keys");
var _EditableField = require("./EditableField.css");
var _EditableField2 = require("./EditableField.utils");
var _jsxRuntime = require("react/jsx-runtime");
var EditableFieldComposite = /*#__PURE__*/function (_React$PureComponent) {
(0, _inheritsLoose2.default)(EditableFieldComposite, _React$PureComponent);
function EditableFieldComposite(_props) {
var _this;
_this = _React$PureComponent.call(this, _props) || this;
_this._isMounted = false;
_this.groupRef = void 0;
_this.maskRef = void 0;
_this.setGroupNode = function (node) {
_this.groupRef = node;
};
_this.setMaskNode = function (node) {
_this.maskRef = node;
};
_this.getChildrenFromProps = function (props) {
var fields = [];
var maskItems = [];
_react.default.Children.forEach(props.children, function (child) {
maskItems.push({
name: child.props.name,
text: child.props.value ? child.props.value : ''
});
fields.push( /*#__PURE__*/_react.default.cloneElement(child, {
key: child.props.name,
inline: true,
size: props.size,
onInputFocus: _this.handleFieldFocus(child.props.onInputFocus),
onInputBlur: _this.handleFieldBlur(child.props.onInputBlur),
onKeyDown: _this.handleFieldKeyEvent(child.props.onInputKeyDown),
onKeyPress: _this.handleFieldKeyEvent(child.props.onInputKeyPress),
onKeyUp: _this.handleFieldKeyEvent(child.props.onInputKeyUp),
onChange: _this.handleChange(child.props.onChange),
onEnter: _this.handleEnter(child.props.onEnter),
onEscape: _this.handleEscape(child.props.onEscape)
}));
});
return {
fields: fields,
maskItems: maskItems
};
};
_this.handleFieldFocus = function (passedFn) {
return function () {
passedFn && passedFn();
_this.setState({
inputState: 'focused',
hasActiveFields: true
});
_this.maskRef.removeAttribute('tabindex');
};
};
_this.handleFieldKeyEvent = function (passedFn) {
return function () {
passedFn && passedFn.apply(void 0, arguments);
};
};
_this.handleFieldBlur = function (passedFn) {
return function () {
passedFn && passedFn();
_this.setState({
inputState: 'blurred'
});
};
};
_this.handleChange = function (passedFn) {
return function (_ref) {
var name = _ref.name,
value = _ref.value;
passedFn && passedFn();
var maskItems = _this.state.maskItems;
_this.setState({
maskItems: maskItems.map(function (m) {
if (name && name.includes(m.name)) {
return (0, _extends2.default)({}, m, {
text: value[0].value
});
}
return m;
})
});
};
};
_this.handleEnter = function (passedFn) {
return function () {
passedFn && passedFn();
_this.setState({
inputState: null,
hasActiveFields: false
}, function () {
_this.maskRef.setAttribute('tabindex', '0');
_this.maskRef.focus();
});
};
};
_this.handleEscape = function (passedFn) {
return function (_ref2) {
var value = _ref2.value,
name = _ref2.name;
var maskItems = _this.state.maskItems;
passedFn && passedFn();
_this.setState({
inputState: null,
hasActiveFields: false,
maskItems: maskItems.map(function (m) {
if (name.includes(m.name)) {
return (0, _extends2.default)({}, m, {
text: value[0].value
});
}
return m;
})
}, function () {
_this.maskRef.setAttribute('tabindex', '0');
_this.maskRef.focus();
});
};
};
_this.handleMaskClick = function (name) {
if (name === 'placeholder') {
var input = _this.groupRef.querySelector('input');
input && input.focus();
return;
}
var inputs = _this.groupRef.querySelectorAll('input');
if (inputs) {
for (var index = 0; index < inputs.length; index++) {
var element = inputs[index];
if (element.id.includes(name)) {
element.setSelectionRange && element.setSelectionRange(element.value.length, element.value.length);
element.focus();
return;
}
}
}
};
_this.handleMaskKeyDown = function (event) {
var isEnter = event.key === _Keys.key.ENTER;
var isEscape = event.key === _Keys.key.ESCAPE;
if (isEnter) {
var input = _this.groupRef.querySelector('input');
input && input.focus();
_this.maskRef.removeAttribute('tabindex');
} else if (isEscape) {
_this.maskRef.removeAttribute('tabindex');
}
};
_this.renderMaskContent = function () {
var _this$props = _this.props,
placeholder = _this$props.placeholder,
separator = _this$props.separator;
var maskItems = _this.state.maskItems;
var maskItemsWithValue = maskItems.filter(function (m) {
return Boolean(m.text);
});
if (maskItemsWithValue.length > 0) {
return maskItemsWithValue.map(function (m, index, self) {
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("span", {
className: _EditableField2.COMPOSITE_CLASSNAMES.maskItem,
onClick: function onClick() {
_this.handleMaskClick(m.name);
},
children: [m.text, index !== self.length - 1 ? separator + "\xA0" : '']
}, m.name);
});
}
return /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
className: (0, _classnames.default)(_EditableField2.COMPOSITE_CLASSNAMES.maskItem, _EditableField2.STATES_CLASSNAMES.isPlaceholder),
onClick: function onClick() {
_this.handleMaskClick('placeholder');
},
children: placeholder
});
};
var _this$getChildrenFrom = _this.getChildrenFromProps(_props),
_fields = _this$getChildrenFrom.fields,
_maskItems = _this$getChildrenFrom.maskItems;
_this.state = {
fields: _fields,
hasActiveFields: false,
inputState: null,
maskItems: _maskItems
};
return _this;
}
var _proto = EditableFieldComposite.prototype;
_proto.componentDidMount = function componentDidMount() {
this._isMounted = true;
};
_proto.componentDidUpdate = function componentDidUpdate(prevProps) {
var _this2 = this;
var getProps = function getProps(children) {
return _react.default.Children.map(children, function (child) {
return child.props;
});
};
if (!(0, _fastDeepEqual.default)(getProps(this.props.children), getProps(prevProps.children))) {
var _this$getChildrenFrom2 = this.getChildrenFromProps(this.props),
fields = _this$getChildrenFrom2.fields,
maskItems = _this$getChildrenFrom2.maskItems;
this.setState({
fields: fields,
maskItems: maskItems
});
}
if (this.state.inputState === 'blurred') {
/**
* Beware: Trickery ahead
*
* Scenario: we have 2 fields: "name" an "city"
*
* When do we hide the mask? On focus, always
* When do we show the mask? Can't be always on blur, because although it would technically work,
* we would get a flash of the mask when we move the focus from "name" to "city"
*
* We know that moving the focus from one input (name) to another (city) triggers this sequence:
* blur name, focus city
*
* Editable Field already takes care of adding a class when it's active, we just need to wait a bit
* so that the focus event gets triggered right after the blur, and that is when we act
*/
setTimeout(function () {
if (!_this2._isMounted) return;
var hasActiveFields = false;
var Fields = _this2.groupRef.querySelectorAll("." + _EditableField2.EDITABLEFIELD_CLASSNAMES.field);
Fields.forEach(function (field) {
if (field && field.classList.contains(_EditableField2.STATES_CLASSNAMES.isActive)) {
hasActiveFields = true;
}
});
if (!hasActiveFields) {
// Let's remove the transition from all but the first focus indicator
var focusIndicators = Array.from(_this2.groupRef.querySelectorAll("." + _EditableField2.INPUT_CLASSNAMES.focusIndicator)).slice(1);
focusIndicators.forEach(function (fi) {
fi.style.transition = 'none';
});
_this2.setState({
inputState: null,
hasActiveFields: hasActiveFields
});
}
}, 100);
} else if (this.state.inputState === 'focused') {
// Let's reinstate the transition for the focus indicators
var focusIndicators = Array.from(this.groupRef.querySelectorAll("." + _EditableField2.INPUT_CLASSNAMES.focusIndicator));
focusIndicators.forEach(function (fi) {
fi.removeAttribute('style');
});
}
};
_proto.componentWillUnmount = function componentWillUnmount() {
this._isMounted = false;
};
_proto.render = function render() {
var _this$props2 = this.props,
size = _this$props2.size,
className = _this$props2.className,
rest = (0, _objectWithoutPropertiesLoose2.default)(_this$props2, ["size", "className"]);
var _this$state = this.state,
fields = _this$state.fields,
hasActiveFields = _this$state.hasActiveFields;
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_EditableField.EditableFieldCompositeUI, (0, _extends2.default)({}, (0, _getValidProps.default)(rest), {
className: (0, _classnames.default)(className && className, EditableFieldComposite.className, hasActiveFields && _EditableField2.STATES_CLASSNAMES.hasActiveFields, size === 'lg' && _EditableField2.STATES_CLASSNAMES.isLarge),
ref: this.setGroupNode,
children: [fields, /*#__PURE__*/(0, _jsxRuntime.jsx)(_EditableField.ComposedMaskUI, {
className: (0, _classnames.default)(_EditableField2.COMPOSITE_CLASSNAMES.mask, hasActiveFields && _EditableField2.STATES_CLASSNAMES.isHidden),
ref: this.setMaskNode,
onKeyDown: this.handleMaskKeyDown,
children: this.renderMaskContent()
})]
}));
};
return EditableFieldComposite;
}(_react.default.PureComponent);
exports.EditableFieldComposite = EditableFieldComposite;
EditableFieldComposite.className = _EditableField2.COMPOSITE_CLASSNAMES.component;
EditableFieldComposite.defaultProps = {
'data-cy': 'EditableFieldComposite',
size: 'md',
separator: ''
};
EditableFieldComposite.propTypes = {
/** Data attr for Cypress tests. */
'data-cy': _propTypes.default.string,
/** The className of the component. */
className: _propTypes.default.string,
/** This is the text you see when all fields are empty. */
placeholder: _propTypes.default.string,
/** Default is "md", pass "lg" for the large option. */
size: _propTypes.default.oneOf(['md', 'lg']),
/** By default, each value will be separated with a non-breaking space, but you can pass any string here, like ",". */
separator: _propTypes.default.string
};
var _default = EditableFieldComposite;
exports.default = _default;
;