// @flow import React, { Component } from "react" import PropTypes from 'prop-types' const KEYCODE_UP = 38; const KEYCODE_DOWN = 40; const IS_BROWSER = typeof document != 'undefined'; const RE_NUMBER = /^[+-]?((\.\d+)|(\d+(\.\d+)?))$/; const RE_INCOMPLETE_NUMBER = /^([+-]|\.0*|[+-]\.0*|[+-]?\d+\.)?$/; /** * Just a simple helper to provide support for older IEs. This is not exactly a * polyfill for classList.add but it does what we need with minimal effort. * Works with single className only! */ function addClass(element, className) { if (element.classList) { return element.classList.add(className) } if (!element.className.search(new RegExp("\\b" + className + "\\b"))) { element.className = " " + className } } /** * Just a simple helper to provide support for older IEs. This is not exactly a * polyfill for classList.remove but it does what we need with minimal effort. * Works with single className only! */ function removeClass(element, className) { if (element.className) { if (element.classList) { return element.classList.remove(className) } element.className = element.className.replace( new RegExp("\\b" + className + "\\b", "g"), "" ) } } /** * Lookup the object.prop and returns it. If it happens to be a function, * executes it with args and returns it's return value. It the prop does not * exist on the object, or if it equals undefined, or if it is a function that * returns undefined the defaultValue will be returned instead. * @param {Object} object The object to look into * @param {String} prop The property name * @param {*} defaultValue The default value * @param {*[]} args Any additional arguments to pass to the * function (if the prop is a function). * @return {*} Whatever happens to be the return value */ function access(object, prop, defaultValue, ...args) { let result = object[prop]; if (typeof result == "function") { result = result(...args); } return result === undefined ? defaultValue : result; } /* eslint-disable */ type NumericInputState = { selectionStart?: number | null; selectionEnd ?: number | null; btnDownHover ?: boolean; btnDownActive ?: boolean; btnUpHover ?: boolean; btnUpActive ?: boolean; value ?: number | null; stringValue ?: string; } /*eslint-enable*/ class NumericInput extends Component { static propTypes = { step : PropTypes.oneOfType([PropTypes.number, PropTypes.func]), min : PropTypes.oneOfType([PropTypes.number, PropTypes.func]), max : PropTypes.oneOfType([PropTypes.number, PropTypes.func]), precision : PropTypes.oneOfType([PropTypes.number, PropTypes.func]), maxLength : PropTypes.number, parse : PropTypes.func, format : PropTypes.func, className : PropTypes.string, disabled : PropTypes.bool, readOnly : PropTypes.bool, required : PropTypes.bool, snap : PropTypes.bool, noValidate : PropTypes.oneOfType([PropTypes.bool, PropTypes.string]), style : PropTypes.oneOfType([PropTypes.object, PropTypes.bool]), noStyle : PropTypes.bool, type : PropTypes.string, pattern : PropTypes.string, onFocus : PropTypes.func, onBlur : PropTypes.func, onKeyDown : PropTypes.func, onChange : PropTypes.func, onInvalid : PropTypes.func, onValid : PropTypes.func, onInput : PropTypes.func, onSelect : PropTypes.func, size : PropTypes.oneOfType([PropTypes.number, PropTypes.string]), value : PropTypes.oneOfType([PropTypes.number, PropTypes.string]), defaultValue : PropTypes.oneOfType([PropTypes.number, PropTypes.string]), strict : PropTypes.bool, componentClass: PropTypes.string, mobile(props, propName) { let prop = props[propName] if (prop !== true && prop !== false && prop !== 'auto' && typeof prop != 'function') { return new Error( 'The "mobile" prop must be true, false, "auto" or a function' ); } } }; /** * The default behavior is to start from 0, use step of 1 and display * integers */ static defaultProps = { step : 1, min : Number.MIN_SAFE_INTEGER || -9007199254740991, max : Number.MAX_SAFE_INTEGER || 9007199254740991, precision : null, parse : null, format : null, mobile : 'auto', strict : false, componentClass: "input", style : {} }; /** * This are the default styles that act as base for all the component * instances. One can modify this object to change the default styles * of all the widgets on the page. */ static style = { // The wrapper (span) wrap: { position: 'relative', display : 'inline-block' }, 'wrap.hasFormControl': { display : 'block' }, // The increase button arrow (i) arrowUp: { position : 'absolute', top : '50%', left : '50%', width : 0, height : 0, borderWidth: '0 0.6ex 0.6ex 0.6ex', borderColor: 'transparent transparent rgba(0, 0, 0, 0.7)', borderStyle: 'solid', margin : '-0.3ex 0 0 -0.56ex' }, // The decrease button arrow (i) arrowDown: { position : 'absolute', top : '50%', left : '50%', width : 0, height : 0, borderWidth: '0.6ex 0.6ex 0 0.6ex', borderColor: 'rgba(0, 0, 0, 0.7) transparent transparent', borderStyle: 'solid', margin : '-0.3ex 0 0 -0.56ex' }, // The vertical segment of the plus sign (for mobile only) plus: { position : 'absolute', top : '50%', left : '50%', width : 2, height : 10, background : 'rgba(0,0,0,.7)', margin : '-5px 0 0 -1px' }, // The horizontal segment of the plus/minus signs (for mobile only) minus: { position : 'absolute', top : '50%', left : '50%', width : 10, height : 2, background : 'rgba(0,0,0,.7)', margin : '-1px 0 0 -5px' }, // Common styles for the up/down buttons (b) btn: { position : 'absolute', right : 2, width : '2.26ex', borderColor: 'rgba(0,0,0,.1)', borderStyle: 'solid', textAlign : 'center', cursor : 'default', transition : 'all 0.1s', background : 'rgba(0,0,0,.1)', boxShadow : '-1px -1px 3px rgba(0,0,0,.1) inset,' + '1px 1px 3px rgba(255,255,255,.7) inset' }, btnUp: { top : 2, bottom : '50%', borderRadius: '2px 2px 0 0', borderWidth : '1px 1px 0 1px' }, 'btnUp.mobile': { width : '3.3ex', bottom : 2, boxShadow : 'none', borderRadius : 2, borderWidth : 1 }, btnDown: { top : '50%', bottom : 2, borderRadius: '0 0 2px 2px', borderWidth : '0 1px 1px 1px' }, 'btnDown.mobile': { width : '3.3ex', bottom : 2, left : 2, top : 2, right : 'auto', boxShadow : 'none', borderRadius : 2, borderWidth : 1 }, 'btn:hover': { background: 'rgba(0,0,0,.2)' }, 'btn:active': { background: 'rgba(0,0,0,.3)', boxShadow : '0 1px 3px rgba(0,0,0,.2) inset,' + '-1px -1px 4px rgba(255,255,255,.5) inset' }, 'btn:disabled': { opacity: 0.5, boxShadow: 'none', cursor: 'not-allowed' }, // The input (input[type="text"]) input: { paddingRight: '3ex', boxSizing : 'border-box', fontSize : 'inherit' }, // The input with bootstrap class 'input:not(.form-control)': { border : '1px solid #ccc', borderRadius : 2, paddingLeft : 4, display : 'block', WebkitAppearance : 'none', lineHeight : 'normal' }, 'input.mobile': { paddingLeft :' 3.4ex', paddingRight: '3.4ex', textAlign : 'center' }, 'input:focus': {}, 'input:disabled': { color : 'rgba(0, 0, 0, 0.3)', textShadow: '0 1px 0 rgba(255, 255, 255, 0.8)' } }; /** * When click and hold on a button - the speed of auto changing the value. * This is a static property and can be modified if needed. */ static SPEED = 50; /** * When click and hold on a button - the delay before auto changing the value. * This is a static property and can be modified if needed. */ static DELAY = 500; /** * The constant indicating up direction (or increasing in general) */ static DIRECTION_UP = "up"; /** * The constant indicating down direction (or decreasing in general) */ static DIRECTION_DOWN = "down"; /** * The step timer * @type {Number} */ _timer: number; /** * This holds the last known validation error. We need to compare that with * new errors and detect validation changes... * @type {[type]} */ _valid: string; _isStrict: boolean; _ignoreValueChange: boolean; _isMounted: boolean; _inputFocus: boolean; onTouchEnd: Function; refsWrapper: Object; refsInput: Object; /** * The state of the component * @type {Object} */ state: NumericInputState; /** * The stop method (need to declare it here to use it in the constructor) * @type {Function} */ stop: Function; /** * Set the initial state and bind this.stop to the instance. */ constructor(...args: Array) { super(...args); this._isStrict = !!this.props.strict; this.state = { btnDownHover : false, btnDownActive : false, btnUpHover : false, btnUpActive : false, stringValue : "", ...this._propsToState(this.props) }; this.stop = this.stop.bind(this); this.onTouchEnd = this.onTouchEnd.bind(this); this.refsInput = {}; this.refsWrapper = {}; } _propsToState(props: Object) { let out:NumericInputState = {}; if (props.hasOwnProperty("value")) { out.stringValue = String( props.value || props.value === 0 ? props.value : '' ).trim(); out.value = out.stringValue !== '' ? this._parse(props.value) : null } else if (!this._isMounted && props.hasOwnProperty("defaultValue")) { out.stringValue = String( props.defaultValue || props.defaultValue === 0 ? props.defaultValue : '' ).trim() out.value = props.defaultValue !== '' ? this._parse(props.defaultValue) : null } return out; } /** * Special care is taken for the "value" prop: * - If not provided - set it to null * - If the prop is a number - use it as is * - Otherwise: * 1. Convert it to string (falsy values become "") * 2. Then trim it. * 3. Then parse it to number (delegating to this.props.parse if any) */ componentWillReceiveProps(props: Object): void { this._isStrict = !!props.strict; let nextState = this._propsToState(props) if (Object.keys(nextState).length) { this._ignoreValueChange = true this.setState(nextState, () => { this._ignoreValueChange = false }) } } /** * Save the input selection right before rendering */ componentWillUpdate(): void { this.saveSelection() } /** * After the component has been rendered into the DOM, do whatever is * needed to "reconnect" it to the outer world, i.e. restore selection, * call some of the callbacks, validate etc. */ componentDidUpdate(prevProps: Object, prevState: Object): void { // Call the onChange if needed. This is placed here because there are // many reasons for changing the value and this is the common place // that can capture them all if (!this._ignoreValueChange // no onChange if re-rendered with different value prop && prevState.value !== this.state.value // no onChange if the value remains the same && (!isNaN(this.state.value) || this.state.value === null) // only if changing to number or null ) { this._invokeEventCallback("onChange", this.state.value, this.refsInput.value, this.refsInput) } // focus the input is needed (for example up/down buttons set // this._inputFocus to true) if (this._inputFocus) { this.refsInput.focus() // Restore selectionStart (if any) if (this.state.selectionStart || this.state.selectionStart === 0) { this.refsInput.selectionStart = this.state.selectionStart } // Restore selectionEnd (if any) if (this.state.selectionEnd || this.state.selectionEnd === 0) { this.refsInput.selectionEnd = this.state.selectionEnd } } this.checkValidity() } /** * This is used to clear the timer if any */ componentWillUnmount(): void { this._isMounted = false this.stop(); } /** * Adds getValueAsNumber and setValue methods to the input DOM element. */ componentDidMount(): void { this._isMounted = true this.refsInput.getValueAsNumber = () => this.state.value || 0 this.refsInput.setValue = (value) => { this.setState({ value: this._parse(value), stringValue: value }) } // This is a special case! If the component has the "autoFocus" prop // and the browser did focus it we have to pass that to the onFocus if (!this._inputFocus && IS_BROWSER && document.activeElement === this.refsInput) { this._inputFocus = true this.refsInput.focus() this._invokeEventCallback("onFocus", { target: this.refsInput, type : "focus" }) } this.checkValidity() } /** * Saves the input selection in the state so that it can be restored after * updates */ saveSelection(): void { this.state.selectionStart = this.refsInput.selectionStart this.state.selectionEnd = this.refsInput.selectionEnd } /** * Unless noValidate is set to true, the component will check the * existing validation state (if any) and will toggle the "has-error" * CSS class on the wrapper */ checkValidity(): void { let valid, validationError = "" let supportsValidation = !!this.refsInput.checkValidity // noValidate let noValidate = !!( this.props.noValidate && this.props.noValidate != "false" ) this.refsInput.noValidate = noValidate // If "noValidate" is set or "checkValidity" is not supported then // consider the element valid. Otherwise consider it invalid and // make some additional checks below valid = noValidate || !supportsValidation if (valid) { validationError = "" } else { // In some browsers once a pattern is set it cannot be removed. The // browser sets it to "" instead which results in validation // failures... if (this.refsInput.pattern === "") { this.refsInput.pattern = this.props.required ? ".+" : ".*" } // Now check validity if (supportsValidation) { this.refsInput.checkValidity() valid = this.refsInput.validity.valid if (!valid) { validationError = this.refsInput.validationMessage } } // Some browsers might fail to validate maxLength if (valid && supportsValidation && this.props.maxLength) { if (this.refsInput.value.length > this.props.maxLength) { validationError = "This value is too long" } } } validationError = validationError || ( valid ? "" : this.refsInput.validationMessage || "Unknown Error" ) let validStateChanged = this._valid !== validationError this._valid = validationError if (validationError) { addClass(this.refsWrapper, "has-error") if (validStateChanged) { this._invokeEventCallback( "onInvalid", validationError, this.state.value, this.refsInput.value ) } } else { removeClass(this.refsWrapper, "has-error") if (validStateChanged) { this._invokeEventCallback( "onValid", this.state.value, this.refsInput.value ) } } } /** * Used internally to parse the argument x to it's numeric representation. * If the argument cannot be converted to finite number returns 0; If a * "precision" prop is specified uses it round the number with that * precision (no fixed precision here because the return value is float, not * string). */ _toNumber(x: any): number { let n = parseFloat(x); if (isNaN(n) || !isFinite(n)) { n = 0; } if (this._isStrict) { let precision = access(this.props, "precision", null, this); let q = Math.pow(10, precision === null ? 10 : precision); let _min = +access(this.props, "min", NumericInput.defaultProps.min, this); let _max = +access(this.props, "max", NumericInput.defaultProps.max, this); n = Math.min( Math.max(n, _min), _max ); n = Math.round( n * q ) / q; } return n; } /** * This is used internally to parse any string into a number. It will * delegate to this.props.parse function if one is provided. Otherwise it * will just use parseFloat. * @private */ _parse(x: string): number { x = String(x); if (typeof this.props.parse == 'function') { return parseFloat(this.props.parse(x)); } return parseFloat(x); } /** * This is used internally to format a number to it's display representation. * It will invoke the this.props.format function if one is provided. * @private */ _format(n: number): string { let _n = this._toNumber(n); let precision = access(this.props, "precision", null, this); if (precision !== null) { _n = n.toFixed(precision); } _n += ""; if (this.props.format) { return this.props.format(_n); } return _n; } /** * The internal method that actually sets the new value on the input * @private */ _step(n: number, callback?: Function): boolean { let _isStrict = this._isStrict; this._isStrict = true; let _step = +access( this.props, "step", NumericInput.defaultProps.step, this, ( n > 0 ? NumericInput.DIRECTION_UP : NumericInput.DIRECTION_DOWN ) ); let _n = this._toNumber((this.state.value || 0) + _step * n); if (this.props.snap) { _n = Math.round(_n / _step) * _step } this._isStrict = _isStrict; if (_n !== this.state.value) { this.setState({ value: _n, stringValue: _n + "" }, callback); return true; } return false; } /** * This binds the Up/Down arrow key listeners */ _onKeyDown(...args: Array): void { args[0].persist() this._invokeEventCallback("onKeyDown", ...args) let e = args[0] if (!e.isDefaultPrevented()) { if (e.keyCode === KEYCODE_UP) { e.preventDefault(); this._step(e.ctrlKey || e.metaKey ? 0.1 : e.shiftKey ? 10 : 1); } else if (e.keyCode === KEYCODE_DOWN) { e.preventDefault(); this._step(e.ctrlKey || e.metaKey ? -0.1 : e.shiftKey ? -10 : -1); } else { let value = this.refsInput.value, length = value.length; if (e.keyCode === 8) { // backspace if (this.refsInput.selectionStart == this.refsInput.selectionEnd && this.refsInput.selectionEnd > 0 && value.length && value.charAt(this.refsInput.selectionEnd - 1) === ".") { e.preventDefault(); this.refsInput.selectionStart = this.refsInput.selectionEnd = this.refsInput.selectionEnd - 1; } } else if (e.keyCode === 46) { // delete if (this.refsInput.selectionStart == this.refsInput.selectionEnd && this.refsInput.selectionEnd < length + 1 && value.length && value.charAt(this.refsInput.selectionEnd) === ".") { e.preventDefault(); this.refsInput.selectionStart = this.refsInput.selectionEnd = this.refsInput.selectionEnd + 1; } } } } } /** * Stops the widget from auto-changing by clearing the timer (if any) */ stop(): void { if ( this._timer ) { clearTimeout( this._timer ); } } /** * Increments the value with one step and the enters a recursive calls * after DELAY. This is bound to the mousedown event on the "up" button * and will be stopped on mouseout/mouseup. * @param {Boolean} _recursive The method is passing this to itself while * it is in recursive mode. * @return void */ increase(_recursive: boolean = false, callback?: Function): void { this.stop(); this._step(1, callback); let _max = +access(this.props, "max", NumericInput.defaultProps.max, this); if (isNaN(this.state.value) || +this.state.value < _max) { this._timer = setTimeout(() => { this.increase(true); }, _recursive ? NumericInput.SPEED : NumericInput.DELAY); } } /** * Decrements the value with one step and the enters a recursive calls * after DELAY. This is bound to the mousedown event on the "down" button * and will be stopped on mouseout/mouseup. * @param {Boolean} _recursive The method is passing this to itself while * it is in recursive mode. * @return void */ decrease(_recursive: boolean = false, callback?: Function): void { this.stop(); this._step(-1, callback); let _min = +access(this.props, "min", NumericInput.defaultProps.min, this); if (isNaN(this.state.value) || +this.state.value > _min) { this._timer = setTimeout(() => { this.decrease(true); }, _recursive ? NumericInput.SPEED : NumericInput.DELAY); } } /** * Handles the mousedown event on the up/down buttons. Changes The * internal value and sets up a delay for auto increment/decrement * (until mouseup or mouseleave) */ onMouseDown(dir: "up"|"down", callback?: Function): void { if (dir == 'down') { this.decrease(false, callback); } else if (dir == 'up') { this.increase(false, callback); } } /** * Handles the touchstart event on the up/down buttons. Changes The * internal value and DOES NOT sets up a delay for auto increment/decrement. * Note that this calls e.preventDefault() so the event is not used for * creating a virtual mousedown after it */ onTouchStart(dir: "up"|"down", e: Event): void { e.preventDefault(); if (dir == 'down') { this.decrease(); } else if (dir == 'up') { this.increase(); } } onTouchEnd(e: Event): void { e.preventDefault(); this.stop(); } /** * Helper method to invoke event callback functions if they are provided * in the props. * @param {String} callbackName The name of the function prop * @param {*[]} args Any additional argument are passed thru */ _invokeEventCallback(callbackName: string, ...args: Array): void { if (typeof this.props[callbackName] == "function") { this.props[callbackName].call(null, ...args); } } /** * Renders an input wrapped in relative span and up/down buttons * @return {Component} */ render() { let props = this.props let state = this.state let css = {} let { // These are ignored in rendering step, min, max, precision, parse, format, mobile, snap, componentClass, value, type, style, defaultValue, onInvalid, onValid, strict, noStyle, // The rest are passed to the input ...rest } = this.props; noStyle = noStyle || style === false; // Build the styles for (let x in NumericInput.style) { css[x] = Object.assign( {}, NumericInput.style[x], style ? style[x] || {} : {} ); } let hasFormControl = props.className && (/\bform-control\b/).test( props.className ) if (mobile == 'auto') { mobile = IS_BROWSER && 'ontouchstart' in document } if (typeof mobile == "function") { mobile = mobile.call(this) } mobile = !!mobile let attrs = { wrap : { style : noStyle ? null : css.wrap, className : 'react-numeric-input', ref: (e)=>{if(e!=null && e!= undefined){this.refsWrapper=e;}}, onMouseUp : undefined, onMouseLeave: undefined }, input : { ref: (e)=>{if(e!=null && e!= undefined){this.refsInput=e;}}, type: 'text', style: noStyle ? null : Object.assign( {}, css.input, !hasFormControl ? css['input:not(.form-control)'] : {}, this._inputFocus ? css['input:focus'] : {} ), ...rest }, btnUp: { onMouseEnter: undefined, onMouseDown : undefined, onMouseUp : undefined, onMouseLeave: undefined, onTouchStart: undefined, onTouchEnd : undefined, style: noStyle ? null : Object.assign( {}, css.btn, css.btnUp, props.disabled || props.readOnly ? css['btn:disabled'] : state.btnUpActive ? css['btn:active'] : state.btnUpHover ? css['btn:hover'] : {} ) }, btnDown: { onMouseEnter: undefined, onMouseDown : undefined, onMouseUp : undefined, onMouseLeave: undefined, onTouchStart: undefined, onTouchEnd : undefined, style: noStyle ? null : Object.assign( {}, css.btn, css.btnDown, props.disabled || props.readOnly ? css['btn:disabled'] : state.btnDownActive ? css['btn:active'] : state.btnDownHover ? css['btn:hover'] : {} ) } }; let stringValue = String( // if state.stringValue is set and not empty state.stringValue || // else if state.value is set and not null|undefined (state.value || state.value === 0 ? state.value : "") || // or finally use "" "" ); let loose = !this._isStrict && (this._inputFocus || !this._isMounted) // incomplete number if (loose && RE_INCOMPLETE_NUMBER.test(stringValue)) { attrs.input.value = stringValue; } // Not a number and not empty (loose mode only) else if (loose && stringValue && !RE_NUMBER.test(stringValue)) { attrs.input.value = stringValue; } // number else if (state.value || state.value === 0) { attrs.input.value = this._format(state.value); } // empty else { attrs.input.value = ""; } if (hasFormControl && !noStyle) { Object.assign(attrs.wrap.style, css['wrap.hasFormControl']) } // mobile if (mobile && !noStyle) { Object.assign(attrs.input .style, css['input.mobile' ]) Object.assign(attrs.btnUp .style, css['btnUp.mobile' ]) Object.assign(attrs.btnDown.style, css['btnDown.mobile']) } // Attach event listeners if the widget is not disabled if (!props.disabled && !props.readOnly) { Object.assign(attrs.wrap, { onMouseUp : this.stop, onMouseLeave : this.stop }); Object.assign(attrs.btnUp, { onTouchStart: this.onTouchStart.bind(this, 'up'), onTouchEnd: this.onTouchEnd, onMouseEnter: () => { this.setState({ btnUpHover : true }); }, onMouseLeave: () => { this.stop(); this.setState({ btnUpHover : false, btnUpActive: false }); }, onMouseUp: () => { this.setState({ btnUpHover : true, btnUpActive : false }); }, onMouseDown: (...args) => { args[0].preventDefault(); args[0].persist(); this._inputFocus = true; this.setState({ btnUpHover : true, btnUpActive : true }, () => { this._invokeEventCallback("onFocus", ...args) this.onMouseDown('up'); }); } }); Object.assign(attrs.btnDown, { onTouchStart: this.onTouchStart.bind(this, 'down'), onTouchEnd: this.onTouchEnd, onMouseEnter: () => { this.setState({ btnDownHover : true }); }, onMouseLeave: () => { this.stop(); this.setState({ btnDownHover : false, btnDownActive: false }); }, onMouseUp: () => { this.setState({ btnDownHover : true, btnDownActive : false }); }, onMouseDown: (...args) => { args[0].preventDefault(); args[0].persist(); this._inputFocus = true; this.setState({ btnDownHover : true, btnDownActive : true }, () => { this._invokeEventCallback("onFocus", ...args) this.onMouseDown('down'); }); } }); Object.assign(attrs.input, { onChange : e => { const original = e.target.value; let val = this._parse(original) if (isNaN(val)) { val = null } this.setState({ value: this._isStrict ? this._toNumber(val) : val, stringValue: original }) }, onKeyDown: this._onKeyDown.bind(this), onInput: (...args) => { this.saveSelection() this._invokeEventCallback("onInput", ...args) }, onSelect: (...args) => { this.saveSelection() this._invokeEventCallback("onSelect", ...args) }, onFocus: (...args) => { args[0].persist(); this._inputFocus = true; const val = this._parse(args[0].target.value); this.setState({ value: val, stringValue: val || val === 0 ? val + "" : "" }, () => { this._invokeEventCallback("onFocus", ...args) }); }, onBlur: (...args) => { let _isStrict = this._isStrict this._isStrict = true args[0].persist(); this._inputFocus = false; const val = this._parse(args[0].target.value); this.setState({ value: val }, () => { this._invokeEventCallback("onBlur", ...args) this._isStrict = _isStrict }); } }); } else { if (!noStyle && props.disabled) { Object.assign(attrs.input.style, css['input:disabled']) } } const InputTag = componentClass || 'input'; if (mobile) { return ( ) } return ( ); } } module.exports = NumericInput;