UNPKG

wix-style-react

Version:
523 lines (427 loc) • 19.3 kB
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _class, _temp2; function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import Ticker from './Ticker'; import Unit from './Unit'; import Group from './Group'; import InputSuffix, { getVisibleSuffixCount } from './InputSuffix'; import styles from './Input.scss'; /** General input container */ var Input = (_temp2 = _class = function (_Component) { _inherits(Input, _Component); function Input() { var _ref; var _temp, _this, _ret; _classCallCheck(this, Input); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Input.__proto__ || Object.getPrototypeOf(Input)).call.apply(_ref, [this].concat(args))), _this), _this.state = { focus: false }, _this.onCompositionChange = function (isComposing) { if (_this.props.onCompositionChange) { _this.props.onCompositionChange(isComposing); } _this.isComposing = isComposing; }, _this.handleSuffixOnClear = function (e) { _this.focus(); _this.clear(e); }, _this.focus = function () { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _this._onFocus(); _this.input && _this.input.focus(options); }, _this.blur = function () { _this.input && _this.input.blur(); }, _this.select = function () { _this.input && _this.input.select(); }, _this._onFocus = function (e) { _this.setState({ focus: true }); _this.props.onFocus && _this.props.onFocus(e); if (_this.props.autoSelect) { // Set timeout is needed here since onFocus is called before react // gets the reference for the input (specifically when autoFocus // is on. So setTimeout ensures we have the ref.input needed in select) setTimeout(function () { /** here we trying to cover edge case with chrome forms autofill, after user will trigger chrome form autofill, onFocus will be called for each input, each input will cause this.select, select may(mostly all time) cause new onFocus, which will cause new this.select, ..., we have recursion which will all time randomly cause inputs to become focused. To prevent this, we check, that current input node is equal to focused node. */ if (document && document.activeElement === _this.input) { _this.select(); } }, 0); } }, _this._onBlur = function (e) { _this.setState({ focus: false }); if (_this.props.onBlur) { _this.props.onBlur(e); } }, _this._onClick = function (e) { _this.props.onInputClicked && _this.props.onInputClicked(e); }, _this._onKeyDown = function (e) { if (_this.isComposing) { return; } _this.props.onKeyDown && _this.props.onKeyDown(e); if (e.keyCode === 13 /* enter */) { _this.props.onEnterPressed && _this.props.onEnterPressed(e); } else if (e.keyCode === 27 /* esc */) { _this.props.onEscapePressed && _this.props.onEscapePressed(e); } }, _this._isInvalidNumber = function (value) { return _this.props.type === 'number' && !/^[\d.,\-+]*$/.test(value); }, _this._onChange = function (e) { if (_this._isInvalidNumber(e.target.value)) { return; } _this.props.onChange && _this.props.onChange(e); }, _this._onKeyPress = function (e) { if (_this._isInvalidNumber(e.key)) { e.preventDefault(); } }, _this.clear = function (event) { var onClear = _this.props.onClear; var prevValue = _this.input.value; _this.input.value = ''; if (prevValue) { if (!event) { /* We cannot dispatch a proper new event, * using this.input.dispatchEvent(new Event('change'))), * because react listens only to SyntheticEvents. * There is this react-trigger-changes library which is a hack for testing only (https://github.com/vitalyq/react-trigger-change). * The solution of creating a new pseudo event object, works for passing along tha target.value, but e.preventDefault() and e.stopPropagation() won't work. */ event = { target: _this.input }; } /* FIXME: The event (e) could be any event type, and even it's target may not be the input. * So it would be better to do e.target = this.input. * We don't use `clear` in WSR except in InputWithTags which does not pass an event, so it's ok. * But if some consumer is using <Input/> directly, then this might be a breaking change. */ event.target = _extends({}, event.target, { value: '' }); _this._onChange(event); } onClear && onClear(); }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(Input, [{ key: 'componentDidMount', value: function componentDidMount() { var _props = this.props, autoFocus = _props.autoFocus, value = _props.value; if (autoFocus) { this._onFocus(); // Multiply by 2 to ensure the cursor always ends up at the end; // Opera sometimes sees a carriage return as 2 characters. value && this.input.setSelectionRange(value.length * 2, value.length * 2); } } }, { key: 'render', value: function render() { var _this2 = this, _classNames; var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var _props2 = this.props, id = _props2.id, name = _props2.name, value = _props2.value, placeholder = _props2.placeholder, help = _props2.help, helpMessage = _props2.helpMessage, unit = _props2.unit, magnifyingGlass = _props2.magnifyingGlass, menuArrow = _props2.menuArrow, defaultValue = _props2.defaultValue, tabIndex = _props2.tabIndex, clearButton = _props2.clearButton, onClear = _props2.onClear, autoFocus = _props2.autoFocus, onKeyUp = _props2.onKeyUp, onPaste = _props2.onPaste, readOnly = _props2.readOnly, prefix = _props2.prefix, suffix = _props2.suffix, type = _props2.type, maxLength = _props2.maxLength, textOverflow = _props2.textOverflow, theme = _props2.theme, disabled = _props2.disabled, status = _props2.status, statusMessage = _props2.statusMessage, tooltipPlacement = _props2.tooltipPlacement, onTooltipShow = _props2.onTooltipShow, autocomplete = _props2.autocomplete, required = _props2.required, error = _props2.error, errorMessage = _props2.errorMessage; var onIconClicked = function onIconClicked(e) { if (!disabled) { _this2.input.focus(); _this2._onFocus(); _this2._onClick(e); } }; var suffixStatus = status; var suffixStatusMessage = statusMessage && statusMessage !== '' ? statusMessage : ''; // Check for deprecated fields and use them if provided if (error) { suffixStatus = Input.StatusError; suffixStatusMessage = errorMessage; } var hasErrors = suffixStatus === Input.StatusError; var isClearButtonVisible = (!!clearButton || !!onClear) && !!value && !hasErrors && !disabled; var visibleSuffixCount = getVisibleSuffixCount({ status: suffixStatus, statusMessage: suffixStatusMessage, disabled: disabled, help: help, magnifyingGlass: magnifyingGlass, isClearButtonVisible: isClearButtonVisible, menuArrow: menuArrow, unit: unit, suffix: suffix }); var inputClassNames = classNames(styles.input, (_classNames = {}, _defineProperty(_classNames, styles.withPrefix, !!prefix), _defineProperty(_classNames, styles.withSuffix, visibleSuffixCount), _defineProperty(_classNames, styles.withSuffixes, visibleSuffixCount > 1), _classNames)); var ariaAttribute = {}; Object.keys(this.props).filter(function (key) { return key.startsWith('aria'); }).map(function (key) { return ariaAttribute['aria-' + key.substr(4).toLowerCase()] = _this2.props[key]; }); /* eslint-disable no-unused-vars */ var className = props.className, inputElementProps = _objectWithoutProperties(props, ['className']); var inputElement = React.createElement('input', _extends({ style: { textOverflow: textOverflow }, ref: function ref(input) { return _this2.input = input; }, className: inputClassNames, id: id, name: name, disabled: disabled, defaultValue: defaultValue, value: value, onChange: this._onChange, onKeyPress: this._onKeyPress, maxLength: maxLength, onFocus: this._onFocus, onBlur: this._onBlur, onKeyDown: this._onKeyDown, onDoubleClick: this._onDoubleClick, onPaste: onPaste, placeholder: placeholder, tabIndex: tabIndex, autoFocus: autoFocus, onClick: this._onClick, onKeyUp: onKeyUp, readOnly: readOnly, type: type, required: required, autoComplete: autocomplete, onCompositionStart: function onCompositionStart() { return _this2.onCompositionChange(true); }, onCompositionEnd: function onCompositionEnd() { return _this2.onCompositionChange(false); } }, ariaAttribute, inputElementProps)); //needs additional wrapper with class .prefixSuffixWrapper to fix inputs with prefix in ie11 //https://github.com/wix/wix-style-react/issues/1693 //https://github.com/wix/wix-style-react/issues/1691 return React.createElement( 'div', { className: styles.inputWrapper }, prefix && React.createElement( 'div', { className: styles.prefixSuffixWrapper }, React.createElement( 'div', { className: styles.prefix }, prefix ) ), inputElement, visibleSuffixCount > 0 && React.createElement( 'div', { className: styles.prefixSuffixWrapper }, React.createElement(InputSuffix, { status: suffixStatus, statusMessage: suffixStatusMessage, theme: theme, disabled: disabled, help: help, helpMessage: helpMessage, onIconClicked: onIconClicked, magnifyingGlass: magnifyingGlass, isClearButtonVisible: isClearButtonVisible, onClear: this.handleSuffixOnClear, menuArrow: menuArrow, unit: unit, focused: this.state.focus, suffix: suffix, tooltipPlacement: tooltipPlacement, onTooltipShow: onTooltipShow }) ) ); } /** * Clears the input. * * Fires onChange ONLY if the input value was not empty. * Then fires onClear. * * @param [Event] event and event to delegate to the onChange call. If no event is provided, a pseudo event object is created with only target.value */ }]); return Input; }(Component), _class.Ticker = Ticker, _class.Unit = Unit, _class.Group = Group, _class.StatusError = 'error', _class.StatusLoading = 'loading', _temp2); Input.displayName = 'Input'; Input.defaultProps = { autoSelect: true, size: 'normal', theme: 'normal', statusMessage: '', errorMessage: '', helpMessage: '', roundInput: false, textOverflow: 'clip', maxLength: 524288, withSelection: false, clearButton: false }; var borderRadiusValidator = function borderRadiusValidator(props, propName) { var value = props[propName]; if (typeof value === 'string') { throw new Error('Passing a string (for className) is deprecated. Use new className prop.'); } else if (typeof value === 'undefined' || typeof value === 'boolean') { return null; } else { return new Error('Invalid type. boolean expected.'); } }; Input.propTypes = { ariaControls: PropTypes.string, ariaDescribedby: PropTypes.string, ariaLabel: PropTypes.string, /** Standard React Input autoFocus (focus the element on mount) */ autoFocus: PropTypes.bool, /** Standard React Input autoSelect (select the entire text of the element on focus) */ autoSelect: PropTypes.bool, /** Sets value of autocomplete attribute (consult the [HTML spec](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-autocomplete) for possible values */ autocomplete: PropTypes.string, /** Specifies a data-hook for tests */ dataHook: PropTypes.string, /** Default value for those who wants to use this component un-controlled */ defaultValue: PropTypes.string, /** when set to true this component is disabled */ disabled: PropTypes.bool, /** Input status - use to display an status indication for the user. for example: 'error' or 'loading' */ status: PropTypes.oneOf([Input.StatusError, Input.StatusLoading]), /** The status (error/loading) message to display when hovering the status icon, if not given or empty there will be no tooltip */ statusMessage: PropTypes.node, /** Is input has errors * @deprecated * @see status */ error: PropTypes.bool, /** Error message to display * @deprecated * @see statusMessage */ errorMessage: PropTypes.node, forceFocus: PropTypes.bool, forceHover: PropTypes.bool, /** Adding a suffix help icon */ help: PropTypes.bool, /** The help message to display when hovering the help icon, if not given or empty there will be no tooltip */ helpMessage: PropTypes.node, id: PropTypes.string, /** Should the component include a magnifyingGlass */ magnifyingGlass: PropTypes.bool, /** Input max length */ maxLength: PropTypes.number, /** Should the component include a menu arrow */ menuArrow: PropTypes.bool, /** Displays clear button (X) on a non-empty input */ clearButton: PropTypes.bool, /** A single CSS class name to be appended to ther Input's wrapper element. */ className: PropTypes.string, name: PropTypes.string, /** When set to true, this input will have no rounded corners on its left */ noLeftBorderRadius: borderRadiusValidator, /** When set to true, this input will have no rounded corners on its right */ noRightBorderRadius: borderRadiusValidator, /** Standard input onBlur callback */ onBlur: PropTypes.func, /** Standard input onChange callback */ onChange: PropTypes.func, /** Displays clear button (X) on a non-empty input and calls callback with no arguments */ onClear: PropTypes.func, onCompositionChange: PropTypes.func, /** Called when user presses -enter- */ onEnterPressed: PropTypes.func, /** Called when user presses -escape- */ onEscapePressed: PropTypes.func, /** Standard input onFocus callback */ onFocus: PropTypes.func, /** Standard input onClick callback */ onInputClicked: PropTypes.func, /** Standard input onKeyDown callback */ onKeyDown: PropTypes.func, onKeyUp: PropTypes.func, /** called when user pastes text from clipboard (using mouse or keyboard shortcut) */ onPaste: PropTypes.func, /** onShow prop for the error and help tooltips (supported only for amaterial theme for now) */ onTooltipShow: PropTypes.func, /** Placeholder to display */ placeholder: PropTypes.string, /** Component you want to show as the prefix of the input */ prefix: PropTypes.node, /** Sets the input to readOnly */ readOnly: PropTypes.bool, /** When set to true, this input will be rounded */ roundInput: PropTypes.bool, /** Flip the magnify glass image so it be more suitable to rtl */ rtl: PropTypes.bool, /** Specifies the size of the input */ size: PropTypes.oneOf(['small', 'normal', 'large']), /** Component you want to show as the suffix of the input */ suffix: PropTypes.node, /** Standard component tabIndex */ tabIndex: PropTypes.number, /** Text overflow behaviour */ textOverflow: PropTypes.string, /** The theme of the input */ theme: PropTypes.oneOf(['normal', 'tags', 'paneltitle', 'material', 'amaterial', 'flat', 'flatdark']), /** The material design style floating label for input (supported only for amaterial theme for now) */ title: PropTypes.string, /** Placement of the error and help tooltips (supported only for amaterial theme for now) */ tooltipPlacement: PropTypes.string, type: PropTypes.string, unit: PropTypes.string, /** Inputs value */ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), withSelection: PropTypes.bool, required: PropTypes.bool }; export default Input;