UNPKG

react-number-field

Version:
385 lines (288 loc) 10 kB
'use strict'; var _reactDom = require('react-dom'); var React = require('react'); var Field = require('react-field'); var assign = require('object-assign'); var IS = require('i-s'); 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 getDefaultProps() { 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 getInitialState() { return { defaultValue: this.props.defaultValue }; }, getSelectedRange: function getSelectedRange(dom) { return { start: getSelectionStart(dom), end: getSelectionEnd(dom) }; }, getValidationFn: function getValidationFn(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 render() { var props = this.prepareProps(this.props); var fieldProps = assign({}, props, { ref: 'field' }); var Factory = props.factory || Field; return React.createElement(Factory, fieldProps); }, prepareProps: function prepareProps(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: function handleFocus(event) { this.setState({ focused: true });(this.props.onFocus || emptyFn)(event); }, handleBlur: function handleBlur() { this.setState({ focused: false }); if (this.isSpinning()) { this.stopSpin(); } ;(this.props.onBlur || emptyFn)(event); }, handleChange: function handleChange(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).apply(undefined, arguments); }, getValue: function getValue() { var value = this.props.value === undefined ? this.state.defaultValue : this.props.value; return value; }, prepareValue: function prepareValue(props, state) { return this.getValue(); }, handleKeyDown: function handleKeyDown(event) { var key = event.key; if (!key) { return; } var name = 'handle' + toUpperFirst(key) + 'KeyDown'; if (this[name]) { this[name](event); } }, handleArrowDownKeyDown: function handleArrowDownKeyDown(event) { this.handleArrowKeySpin(-1, event); }, handleArrowUpKeyDown: function handleArrowUpKeyDown(event) { this.handleArrowKeySpin(1, event); }, handleArrowKeySpin: function handleArrowKeySpin(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 onSpinKeyUp() { this.props.spinOnArrowKeys && this.stopSpin(); global.removeEventListener('keyup', this.onSpinKeyUp); }, handleKeyPress: function handleKeyPress(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 getInput() { return (0, _reactDom.findDOMNode)(this.refs.field); }, isFocused: function isFocused() { return !!this.state.focused; }, getStepValue: function getStepValue(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 stepTo(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 stopSpin() { clearInterval(this.spinIntervalId); this.spinIntervalId = null; }, startSpin: function startSpin(direction, config) { if (this.spinIntervalId) { clearInterval(this.spinIntervalId); } this.spinIntervalId = setInterval(this.stepTo.bind(this, direction, config), this.props.stepDelay); }, isSpinning: function isSpinning() { return this.spinIntervalId != null; }, handleWheel: function handleWheel(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 notify(value, event) { this.handleChange(value, event); }, focus: function focus(value, event) { (0, _reactDom.findDOMNode)(this.refs.field).focus(); } });