UNPKG

react-number-field

Version:
404 lines (305 loc) 9.79 kB
'use strict'; var React = require('react') var Field = require('react-field') var assign = require('object-assign') var IS = require('i-s') import { findDOMNode } from 'react-dom' var getSelectionStart = require('./getSelectionStart') var getSelectionEnd = require('./getSelectionEnd') function emptyFn(){} var isNumeric = IS.numeric var isInt = IS.int var isFloat = IS.float function toUpperFirst(str){ return str? str.charAt(0).toUpperCase() + str.substring(1): '' } function noDot(value){ value = value + '' return value.indexOf('.') === -1 } /** * Returns true if the given value is >= than #minValue * @param {Number/String} value * @return {Boolean} */ function isMinValueRespected(value, props){ var minValue = props.minValue if (minValue == null || value === ''){ return true } return value >= minValue } /** * Returns true if the given value is <= than #maxValue * @param {Number/String} value * @return {Boolean} */ function isMaxValueRespected(value, props){ var maxValue = props.maxValue if (maxValue == null || value === ''){ return true } return value <= maxValue } function checkNumeric(value, props){ if (value === ''){ return true } if (props.numbersOnly){ var numeric = isNumeric(value) return numeric || (props.allowNegative && value === '-') || (props.allowFloat && value === '.') || (props.allowNegative && props.allowFloat && value == '-.') } } function checkFloat(value, props){ if (props.allowFloat === false){ return noDot(value) && isNumeric(value) && isInt(value * 1) } } function checkPositive(value, props){ if (props.allowNegative === false){ return isNumeric(value) && (value * 1 >= 0) } } module.exports = React.createClass({ displayName: 'ReactNumberField', propTypes: { onInvalid: React.PropTypes.func, validate : React.PropTypes.func, stepDelay : React.PropTypes.number, minValue : React.PropTypes.oneOfType([ React.PropTypes.number, React.PropTypes.string ]), maxValue : React.PropTypes.oneOfType([ React.PropTypes.number, React.PropTypes.string ]) }, getDefaultProps: function() { return { spinOnArrowKeys: true, numbersOnly : true, minValue : null, maxValue : null, step : 1, shiftStep : 10, requireFocusOnStep: true, stepOnWheel : true, allowNegative : true, allowFloat : true, stepDelay : 40, validations : [ checkNumeric, checkFloat, checkPositive, isMinValueRespected, isMaxValueRespected ] } }, getInitialState: function(){ return { defaultValue: this.props.defaultValue } }, getSelectedRange: function(dom){ return { start: getSelectionStart(dom), end : getSelectionEnd(dom) } }, getValidationFn: function(props){ var validations = [].concat(props.validations) if (typeof props.validate === 'function'){ validations.push(props.validate) } return function(value, properties){ return validations.reduce(function(a, b){ return a && b(value, properties) !== false }, true) } }, render: function(){ var props = this.prepareProps(this.props) const fieldProps = assign({}, props, { ref: 'field' }) const Factory = props.factory || Field return <Factory {...fieldProps} /> }, prepareProps: function(thisProps) { var props = assign({}, thisProps) props.value = this.prepareValue(props, this.state) var validationFn = this.validationFn = this.getValidationFn(thisProps) props.onWheel = this.handleWheel props.onKeyPress = this.handleKeyPress.bind(this, props) props.onKeyDown = this.handleKeyDown props.validate = validationFn props.onChange = this.handleChange props.onBlur = this.handleBlur props.onFocus = this.handleFocus return props }, handleFocus(event){ this.setState({ focused: true }) ;(this.props.onFocus || emptyFn)(event) }, handleBlur: function() { this.setState({ focused: false }) if (this.isSpinning()){ this.stopSpin() } ;(this.props.onBlur || emptyFn)(event) }, handleChange: function(value, event){ if (value && value.target){ event = value value = event.target.value } if (this.props.value === undefined){ this.setState({ defaultValue: value }) } ;(this.props.onChange || emptyFn)(...arguments) }, getValue: function() { var value = this.props.value === undefined? this.state.defaultValue: this.props.value return value }, prepareValue: function(props, state) { return this.getValue() }, handleKeyDown: function(event) { var key = event.key if (!key){ return } var name = 'handle' + toUpperFirst(key) + 'KeyDown' if (this[name]){ this[name](event) } }, handleArrowDownKeyDown: function(event){ this.handleArrowKeySpin(-1, event) }, handleArrowUpKeyDown: function(event){ this.handleArrowKeySpin(1, event) }, handleArrowKeySpin: function(direction, event) { if (this.isSpinning()){ event.preventDefault() return } if (this.props.spinOnArrowKeys && !this.isSpinning()){ this.startSpin(direction, { shiftKey: event.shiftKey, event: event }) event.preventDefault() global.addEventListener('keyup', this.onSpinKeyUp) } }, onSpinKeyUp: function() { this.props.spinOnArrowKeys && this.stopSpin() global.removeEventListener('keyup', this.onSpinKeyUp) }, handleKeyPress: function(props, event){ var validationFn = this.validationFn if (!event.which || event.which == 8/*backspace*/){ //not a printable character return } var input = event.target var value = props.value + '' var range = this.getSelectedRange(input) var beforeValue = value.substring(0, range.start) var afterValue = value.substring(range.end) var key = String.fromCharCode(event.which || event.keyCode) var newValue = beforeValue + key + afterValue var preventInvalid = true var valid if (newValue !== ''){ valid = validationFn(newValue, props, key) if (!valid && typeof props.onInvalid === 'function' && props.onInvalid(newValue, props, event) !== false){ preventInvalid = false } if (!valid && preventInvalid){ event.preventDefault() } } }, getInput: function() { return findDOMNode(this.refs.field) }, isFocused: function() { return !!this.state.focused }, getStepValue: function(props, direction, config){ config = config || {} var value = this.getValue() var stepValue = props.step if (value === ''){ return 0 } if (typeof value == 'string'){ value = parseFloat(value) } if (config.shiftKey && props.shiftStep){ stepValue = props.shiftStep } return value + direction * stepValue }, stepTo: function(direction, config) { config = config || {} var props = this.props var validationFn = this.validationFn if (props.step != null){ var stepFn = typeof props.stepFn === 'function'? props.stepFn: this.getStepValue var value = stepFn(props, direction, config) var valid = validationFn(value, props) if (valid){ this.notify(value, config.event) } } return value }, stopSpin: function(){ clearInterval(this.spinIntervalId) this.spinIntervalId = null }, startSpin: function(direction, config){ if (this.spinIntervalId){ clearInterval(this.spinIntervalId) } this.spinIntervalId = setInterval(this.stepTo.bind(this, direction, config), this.props.stepDelay) }, isSpinning: function() { return this.spinIntervalId != null }, handleWheel: function(event) { var props = this.props if ((props.requireFocusOnStep && this.isFocused() || !props.requireFocusOnStep) && props.stepOnWheel && props.step){ event.preventDefault() var nativeEvent = event.nativeEvent var y = nativeEvent.wheelDeltaY || nativeEvent.wheelDelta || -nativeEvent.deltaY y = y < 0? -1:1 this.stepTo(y, { shiftKey: event.shiftKey, event : event }) } }, notify: function(value, event) { this.handleChange(value, event) }, focus: function(value, event) { findDOMNode(this.refs.field).focus() } })