UNPKG

@helpscout/hsds-react

Version:

React component library for Help Scout's Design System

381 lines (310 loc) 12.5 kB
"use strict"; 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;