UNPKG

@douyinfe/semi-ui

Version:

A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.

508 lines (506 loc) 20.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.InputNumber = void 0; var _noop2 = _interopRequireDefault(require("lodash/noop")); var _isString2 = _interopRequireDefault(require("lodash/isString")); var _isNaN2 = _interopRequireDefault(require("lodash/isNaN")); var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _classnames = _interopRequireDefault(require("classnames")); var _input = _interopRequireDefault(require("../input")); var _object = require("@douyinfe/semi-foundation/lib/cjs/utils/object"); var _isNullOrUndefined = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/utils/isNullOrUndefined")); var _isBothNaN = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/utils/isBothNaN")); var _foundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/inputNumber/foundation")); var _baseComponent = _interopRequireDefault(require("../_base/baseComponent")); var _constants = require("@douyinfe/semi-foundation/lib/cjs/inputNumber/constants"); var _semiIcons = require("@douyinfe/semi-icons"); require("@douyinfe/semi-foundation/lib/cjs/inputNumber/inputNumber.css"); var _localeConsumer = _interopRequireDefault(require("../locale/localeConsumer")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } var __rest = void 0 && (void 0).__rest || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; /* eslint-disable jsx-a11y/no-static-element-interactions */ class InputNumber extends _baseComponent.default { get adapter() { var _this = this; return Object.assign(Object.assign({}, super.adapter), { setValue: (value, cb) => this.setState({ value }, cb), setNumber: (number, cb) => this.setState({ number }, cb), setFocusing: (focusing, cb) => this.setState({ focusing }, cb), setHovering: hovering => this.setState({ hovering }), notifyChange: function () { return _this.props.onChange(...arguments); }, notifyNumberChange: function () { return _this.props.onNumberChange(...arguments); }, notifyBlur: e => this.props.onBlur(e), notifyFocus: e => this.props.onFocus(e), notifyUpClick: (value, e) => this.props.onUpClick(value, e), notifyDownClick: (value, e) => this.props.onDownClick(value, e), notifyKeyDown: e => this.props.onKeyDown(e), registerGlobalEvent: (eventName, handler) => { if (eventName && typeof handler === 'function') { this.adapter.unregisterGlobalEvent(eventName); this.adapter.setCache(eventName, handler); document.addEventListener(eventName, handler); } }, unregisterGlobalEvent: eventName => { if (eventName) { const handler = this.adapter.getCache(eventName); document.removeEventListener(eventName, handler); this.adapter.setCache(eventName, null); } }, getInputCharacter: index => { return this.inputNode.value[index]; }, recordCursorPosition: () => { // Record position try { if (this.inputNode) { this.cursorStart = this.inputNode.selectionStart; this.cursorEnd = this.inputNode.selectionEnd; this.currentValue = this.inputNode.value; this.cursorBefore = this.inputNode.value.substring(0, this.cursorStart); this.cursorAfter = this.inputNode.value.substring(this.cursorEnd); } } catch (e) { console.warn(e); // Fix error in Chrome: // Failed to read the 'selectionStart' property from 'HTMLInputElement' // http://stackoverflow.com/q/21177489/3040605 } }, restoreByAfter: str => { if ((0, _isNullOrUndefined.default)(str)) { return false; } const fullStr = this.inputNode.value; const index = fullStr.lastIndexOf(str); if (index === -1) { return false; } if (index + str.length === fullStr.length) { this.adapter.fixCaret(index, index); return true; } return false; }, restoreCursor: function () { let str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.cursorAfter; if ((0, _isNullOrUndefined.default)(str)) { return false; } // For loop from full str to the str with last char to map. e.g. 123 // -> 123 // -> 23 // -> 3 return Array.prototype.some.call(str, (_, start) => { const partStr = str.substring(start); return _this.adapter.restoreByAfter(partStr); }); }, fixCaret: (start, end) => { if (start === undefined || end === undefined || !this.inputNode || !this.inputNode.value) { return; } try { const currentStart = this.inputNode.selectionStart; const currentEnd = this.inputNode.selectionEnd; if (start !== currentStart || end !== currentEnd) { this.inputNode.setSelectionRange(start, end); } } catch (e) { // Fix error in Chrome: // Failed to read the 'selectionStart' property from 'HTMLInputElement' // http://stackoverflow.com/q/21177489/3040605 } }, setClickUpOrDown: value => { this.clickUpOrDown = value; }, updateStates: (states, callback) => { this.setState(states, callback); } }); } constructor(props) { super(props); this.setInputRef = node => { const { forwardedRef } = this.props; this.inputNode = node; if (forwardedRef && typeof forwardedRef === 'object') { forwardedRef.current = node; } else if (typeof forwardedRef === 'function') { forwardedRef(node); } }; this.handleInputFocus = e => this.foundation.handleInputFocus(e); this.handleInputChange = (value, event) => this.foundation.handleInputChange(value, event); this.handleInputBlur = e => this.foundation.handleInputBlur(e); this.handleInputKeyDown = e => this.foundation.handleInputKeyDown(e); this.handleInputMouseEnter = e => this.foundation.handleInputMouseEnter(e); this.handleInputMouseLeave = e => this.foundation.handleInputMouseLeave(e); this.handleInputMouseMove = e => this.foundation.handleInputMouseMove(e); this.handleUpClick = e => this.foundation.handleUpClick(e); this.handleDownClick = e => this.foundation.handleDownClick(e); this.handleMouseUp = e => this.foundation.handleMouseUp(e); this.handleMouseLeave = e => this.foundation.handleMouseLeave(e); this.renderButtons = () => { const { prefixCls, disabled, innerButtons, max, min } = this.props; const { hovering, focusing, number } = this.state; const notAllowedUp = disabled ? disabled : number === max; const notAllowedDown = disabled ? disabled : number === min; const suffixChildrenCls = (0, _classnames.default)(`${prefixCls}-number-suffix-btns`, { [`${prefixCls}-number-suffix-btns-inner`]: innerButtons, [`${prefixCls}-number-suffix-btns-inner-hover`]: innerButtons && hovering && !focusing }); const upClassName = (0, _classnames.default)(`${prefixCls}-number-button`, `${prefixCls}-number-button-up`, { [`${prefixCls}-number-button-up-disabled`]: disabled, [`${prefixCls}-number-button-up-not-allowed`]: notAllowedUp }); const downClassName = (0, _classnames.default)(`${prefixCls}-number-button`, `${prefixCls}-number-button-down`, { [`${prefixCls}-number-button-down-disabled`]: disabled, [`${prefixCls}-number-button-down-not-allowed`]: notAllowedDown }); return /*#__PURE__*/_react.default.createElement("div", { className: suffixChildrenCls }, /*#__PURE__*/_react.default.createElement("span", { className: upClassName, onMouseDown: notAllowedUp ? _noop2.default : this.handleUpClick, onMouseUp: this.handleMouseUp, onMouseLeave: this.handleMouseLeave }, /*#__PURE__*/_react.default.createElement(_semiIcons.IconChevronUp, { size: "extra-small" })), /*#__PURE__*/_react.default.createElement("span", { className: downClassName, onMouseDown: notAllowedDown ? _noop2.default : this.handleDownClick, onMouseUp: this.handleMouseUp, onMouseLeave: this.handleMouseLeave }, /*#__PURE__*/_react.default.createElement(_semiIcons.IconChevronDown, { size: "extra-small" }))); }; this.renderSuffix = () => { const { innerButtons, suffix } = this.props; const { hovering, focusing } = this.state; if (innerButtons && (hovering || focusing)) { const buttons = this.renderButtons(); return buttons; } return suffix; }; this.state = { value: '', number: null, focusing: Boolean(props.autofocus) || false, hovering: false }; this.inputNode = null; this.foundation = new _foundation.default(this.adapter); this.clickUpOrDown = false; } componentDidUpdate(prevProps) { const { value, preventScroll } = this.props; const { focusing } = this.state; let newValue; /** * To determine whether the front and back are equal * NaN need to check whether both are NaN */ if (value !== prevProps.value && !(0, _isBothNaN.default)(value, prevProps.value)) { if ((0, _isNullOrUndefined.default)(value) || value === '') { newValue = ''; this.foundation.updateStates({ value: newValue, number: null }); } else { let valueStr = value; if (typeof value === 'number') { valueStr = this.foundation.doFormat(value); } const parsedNum = this.foundation.doParse(valueStr, false, true, true); const toNum = typeof value === 'number' ? value : this.foundation.doParse(valueStr, false, false, false); /** * focusing 状态为输入状态,输入状态的受控值要特殊处理 * 如: * - 输入合法值 * 123 => input value 也应该是 123,同时需要设置 number 为 123 * - 输入非法值,只设置 input value,不设置非法的number * abc => input value 这时是 abc,但失焦后会进行格式化 * 100(超出范围) => input value 应该是 100,但不设置 number * * 保持输入态有三种方式 * 1. 输入框输入 * - 输入可以解析为合法数字,input value根据输入值确定,失焦时更新input value * - 输入不可解析为合法数字,进行格式化后显示在input框 * 2. 键盘点击上下按钮(input value根据受控值进行更改) * 3. keepFocus+鼠标点击上下按钮(input value根据受控值进行更改) * * The focusing state is the input state, and the controlled value of the input state needs special treatment * For example: * - input legal value * 123 = > input value should also be 123, and the number should be set to 123 * - input illegal value, only set the input value, do not set the illegal number * abc = > input value This is abc at this time, but it will be formatted after being out of focus * 100 (out of range) = > input value should be 100, but no number * * There are three ways to maintain the input state * 1. input box input * - input can be resolved into legal numbers, input value is determined according to the input value, and input value is updated when out of focus * - input cannot be resolved into legal numbers, and it will be displayed in the input box after formatting * 2. Keyboard click on the up and down button (input value is changed according to the controlled value) * 3.keepFocus + mouse click on the up and down button (input value is changed according to the controlled value) */ if (focusing) { if (this.foundation.isValidNumber(parsedNum) && parsedNum !== this.state.number) { const obj = { number: parsedNum }; /** * If you are clicking the button, it will automatically format once * We need to set the status to false after trigger focus event */ if (this.clickUpOrDown) { obj.value = this.foundation.doFormat(obj.number, true); newValue = obj.value; } this.foundation.updateStates(obj, () => this.adapter.restoreCursor()); } else if (!(0, _isNaN2.default)(toNum)) { // Update input content when controlled input is illegal and not NaN newValue = this.foundation.doFormat(toNum, false); this.foundation.updateStates({ value: newValue }); } else { // Update input content when controlled input NaN this.foundation.updateStates({ value: valueStr }); } } else if (this.foundation.isValidNumber(parsedNum)) { newValue = this.foundation.doFormat(parsedNum, true, true); this.foundation.updateStates({ number: parsedNum, value: newValue }); } else { // Invalid digital analog blurring effect instead of controlled failure newValue = ''; this.foundation.updateStates({ number: null, value: newValue }); } } if (newValue && (0, _isString2.default)(newValue) && newValue !== String(this.props.value)) { if (this.foundation._isCurrency()) { // 仅在解析后的数值而不是格式化的字符串变化时 notifyChange // notifyChange only when the parsed value changes, not the formatted string const parsedNewValue = this.foundation.doParse(newValue); const parsedPropValue = typeof this.props.value === 'string' ? this.foundation.doParse(this.props.value) : this.props.value; if (parsedNewValue !== parsedPropValue) { this.foundation.notifyChange(newValue, null); } } else { this.foundation.notifyChange(newValue, null); } } } if (!this.clickUpOrDown) { return; } if (this.props.keepFocus && this.state.focusing) { if (document.activeElement !== this.inputNode) { this.inputNode.focus({ preventScroll }); } } } render() { const _a = this.props, { disabled, className, prefixCls, min, max, step, shiftStep, precision, formatter, parser, forwardedRef, onUpClick, onDownClick, pressInterval, pressTimeout, suffix, size, hideButtons, innerButtons, style, onNumberChange, keepFocus, defaultValue } = _a, rest = __rest(_a, ["disabled", "className", "prefixCls", "min", "max", "step", "shiftStep", "precision", "formatter", "parser", "forwardedRef", "onUpClick", "onDownClick", "pressInterval", "pressTimeout", "suffix", "size", "hideButtons", "innerButtons", "style", "onNumberChange", "keepFocus", "defaultValue"]); const { value, number } = this.state; const inputNumberCls = (0, _classnames.default)(className, `${prefixCls}-number`, { [`${prefixCls}-number-size-${size}`]: size }); const buttons = this.renderButtons(); const ariaProps = { 'aria-disabled': disabled, step }; if (number) { ariaProps['aria-valuenow'] = number; } if (max !== Infinity) { ariaProps['aria-valuemax'] = max; } if (min !== -Infinity) { ariaProps['aria-valuemin'] = min; } const input = /*#__PURE__*/_react.default.createElement("div", { className: inputNumberCls, style: style, onMouseMove: e => this.handleInputMouseMove(e), onMouseEnter: e => this.handleInputMouseEnter(e), onMouseLeave: e => this.handleInputMouseLeave(e) }, /*#__PURE__*/_react.default.createElement(_input.default, Object.assign({ role: "spinbutton" }, ariaProps, rest, { size: size, disabled: disabled, ref: this.setInputRef, value: value, onFocus: this.handleInputFocus, onChange: this.handleInputChange, onBlur: this.handleInputBlur, onKeyDown: this.handleInputKeyDown, suffix: this.renderSuffix() })), hideButtons || innerButtons ? null : buttons); return input; } } exports.InputNumber = InputNumber; InputNumber.propTypes = { 'aria-label': _propTypes.default.string, 'aria-labelledby': _propTypes.default.string, 'aria-invalid': _propTypes.default.bool, 'aria-errormessage': _propTypes.default.string, 'aria-describedby': _propTypes.default.string, 'aria-required': _propTypes.default.bool, autofocus: _propTypes.default.bool, clearIcon: _propTypes.default.node, className: _propTypes.default.string, defaultValue: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), disabled: _propTypes.default.bool, formatter: _propTypes.default.func, forwardedRef: _propTypes.default.any, hideButtons: _propTypes.default.bool, innerButtons: _propTypes.default.bool, insetLabel: _propTypes.default.node, insetLabelId: _propTypes.default.string, keepFocus: _propTypes.default.bool, max: _propTypes.default.number, min: _propTypes.default.number, parser: _propTypes.default.func, precision: _propTypes.default.number, prefixCls: _propTypes.default.string, pressInterval: _propTypes.default.number, pressTimeout: _propTypes.default.number, preventScroll: _propTypes.default.bool, shiftStep: _propTypes.default.number, showCurrencySymbol: _propTypes.default.bool, step: _propTypes.default.number, style: _propTypes.default.object, suffix: _propTypes.default.any, value: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), onBlur: _propTypes.default.func, onChange: _propTypes.default.func, onDownClick: _propTypes.default.func, onKeyDown: _propTypes.default.func, onNumberChange: _propTypes.default.func, onUpClick: _propTypes.default.func }; InputNumber.defaultProps = { forwardedRef: _noop2.default, innerButtons: false, keepFocus: false, max: Infinity, min: -Infinity, prefixCls: _constants.cssClasses.PREFIX, pressInterval: _constants.numbers.DEFAULT_PRESS_TIMEOUT, pressTimeout: _constants.numbers.DEFAULT_PRESS_TIMEOUT, shiftStep: _constants.numbers.DEFAULT_SHIFT_STEP, showCurrencySymbol: true, size: _constants.strings.DEFAULT_SIZE, step: _constants.numbers.DEFAULT_STEP, onBlur: _noop2.default, onChange: _noop2.default, onDownClick: _noop2.default, onFocus: _noop2.default, onKeyDown: _noop2.default, onNumberChange: _noop2.default, onUpClick: _noop2.default }; var _default = exports.default = (0, _object.forwardStatics)(/*#__PURE__*/_react.default.forwardRef(function SemiInputNumber(props, ref) { return /*#__PURE__*/_react.default.createElement(_localeConsumer.default, { componentName: "InputNumber" }, (locale, localeCode, dateFnsLocale, currency) => (/*#__PURE__*/_react.default.createElement(InputNumber, Object.assign({ localeCode: localeCode, defaultCurrency: currency }, props, { forwardedRef: ref })))); }), InputNumber);