From 51b9c85bb0d98c5e08ebd81b3d8f75350ec42d38 Mon Sep 17 00:00:00 2001 From: vlad-ignatov Date: Fri, 28 Jul 2017 01:15:49 -0400 Subject: [PATCH] Add new "snap" prop #35 --- __tests__/misc.test.jsx | 40 ++++++++++++++++++++++++++++++++- dist/react-numeric-input.js | 8 ++++++- dist/react-numeric-input.min.js | 2 +- index.js | 8 ++++++- src/NumericInput.jsx | 7 +++++- 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/__tests__/misc.test.jsx b/__tests__/misc.test.jsx index 41963db..c35a249 100644 --- a/__tests__/misc.test.jsx +++ b/__tests__/misc.test.jsx @@ -10,6 +10,8 @@ import TestUtils from 'react-dom/test-utils' describe ('NumericInput/misc', function() { + this.timeout(10000); + /** * Assert that the user can type a value lower than the current "min" * @see https://github.com/vlad-ignatov/react-numeric-input/issues/19 @@ -138,5 +140,41 @@ describe ('NumericInput/misc', function() { }) done(); - }) + }); + + it ('Can snap to steps', done => { + const KEYCODE_UP = 38; + const KEYCODE_DOWN = 40; + const tests = [ + [0.2 , KEYCODE_UP , "0.5" ], // 0.2 + 0.5 = 0.5 + [0.3 , KEYCODE_UP , "1.0" ], // 0.3 + 0.5 = 1.0 + [0.5 , KEYCODE_UP , "1.0" ], // 0.5 + 0.5 = 1.0 + [0.6 , KEYCODE_UP , "1.0" ], // 0.6 + 0.5 = 1.0 + [0.9 , KEYCODE_UP , "1.5" ], // 0.9 + 0.5 = 1.5 + [1.1 , KEYCODE_UP , "1.5" ], // 1.1 + 0.5 = 1.5 + [9.1 , KEYCODE_UP , "9.5" ], // 9.1 + 0.5 = 9.5 + [9.3 , KEYCODE_UP , "10.0" ], // 9.3 + 0.5 = 10.0 + [11.1 , KEYCODE_UP , "10.0" ], // 11.1 + 0.5 = 10.0 (<= max) + [11.1 , KEYCODE_DOWN, "10.0" ], // 11.1 - 0.5 = 10.0 (<= max) + [1.1 , KEYCODE_DOWN, "0.5" ], // 1.1 - 0.5 = 0.5 + [0.3 , KEYCODE_DOWN, "0.0" ], // 0.3 - 0.5 = 0.0 + [0.1 , KEYCODE_DOWN, "-0.5" ], // 0.1 - 0.5 = -0.5 + [-1.1 , KEYCODE_DOWN, "-1.5" ], // -1.1 - 0.5 = -1.5 + [-1.4 , KEYCODE_DOWN, "-2.0" ], // -1.4 - 0.5 = -2.0 + [-10.4, KEYCODE_DOWN, "-10.0"], // -10.4 - 0.5 = -2.0 (>= min) + [-10.4, KEYCODE_UP , "-10.0"], // -10.4 + 0.5 = -2.0 (>= min) + [-8.4 , KEYCODE_UP , "-8.0" ] // -8.4 + 0.5 = -8.0 + ]; + + tests.forEach(([inputValue, key, result]) => { + let widget = TestUtils.renderIntoDocument( + + ); + let input = widget.refs.input; + TestUtils.Simulate.keyDown(input, { keyCode: key }); + expect(input.value).toEqual(result); + }); + + done(); + }); }) diff --git a/dist/react-numeric-input.js b/dist/react-numeric-input.js index acce908..73cc552 100644 --- a/dist/react-numeric-input.js +++ b/dist/react-numeric-input.js @@ -457,6 +457,10 @@ return /******/ (function(modules) { // webpackBootstrap value: function _step(n, callback) { var _n = this._toNumber((this.state.value || 0) + this.props.step * n, false); + if (this.props.snap) { + _n = Math.round(_n / this.props.step) * this.props.step; + } + if (_n !== this.state.value) { this.setState({ value: _n, stringValue: _n }, callback); return true; @@ -651,6 +655,7 @@ return /******/ (function(modules) { // webpackBootstrap var parse = _props.parse; var format = _props.format; var mobile = _props.mobile; + var snap = _props.snap; var value = _props.value; var type = _props.type; var style = _props.style; @@ -658,7 +663,7 @@ return /******/ (function(modules) { // webpackBootstrap var onInvalid = _props.onInvalid; var onValid = _props.onValid; - var rest = _objectWithoutProperties(_props, ['step', 'min', 'max', 'precision', 'parse', 'format', 'mobile', 'value', 'type', 'style', 'defaultValue', 'onInvalid', 'onValid']); + var rest = _objectWithoutProperties(_props, ['step', 'min', 'max', 'precision', 'parse', 'format', 'mobile', 'snap', 'value', 'type', 'style', 'defaultValue', 'onInvalid', 'onValid']); // Build the styles @@ -930,6 +935,7 @@ return /******/ (function(modules) { // webpackBootstrap disabled: _propTypes2.default.bool, readOnly: _propTypes2.default.bool, required: _propTypes2.default.bool, + snap: _propTypes2.default.bool, noValidate: _propTypes2.default.oneOfType([_propTypes2.default.bool, _propTypes2.default.string]), style: _propTypes2.default.oneOfType([_propTypes2.default.object, _propTypes2.default.bool]), type: _propTypes2.default.string, diff --git a/dist/react-numeric-input.min.js b/dist/react-numeric-input.min.js index b4edeb5..c0ed7a5 100644 --- a/dist/react-numeric-input.min.js +++ b/dist/react-numeric-input.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("React"),require("prop-types")):"function"==typeof define&&define.amd?define(["React","prop-types"],e):"object"==typeof exports?exports.NumericInput=e(require("React"),require("prop-types")):t.NumericInput=e(t.React,t["prop-types"])}(this,function(t,e){return function(t){function e(o){if(n[o])return n[o].exports;var a=n[o]={exports:{},id:o,loaded:!1};return t[o].call(a.exports,a,a.exports,e),a.loaded=!0,a.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function o(t){return t&&t.__esModule?t:{"default":t}}function a(t,e){var n={};for(var o in t)e.indexOf(o)>=0||Object.prototype.hasOwnProperty.call(t,o)&&(n[o]=t[o]);return n}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function s(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function r(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function u(t,e){return t.classList?t.classList.add(e):void(t.className.search(new RegExp("\\b"+e+"\\b"))||(t.className=" "+e))}function l(t,e){if(t.className){if(t.classList)return t.classList.remove(e);t.className=t.className.replace(new RegExp("\\b"+e+"\\b","g"),"")}}var p=Object.assign||function(t){for(var e=1;ea;a++)o[a]=arguments[a];var r=s(this,(t=Object.getPrototypeOf(e)).call.apply(t,[this].concat(o)));return r.state={selectionStart:null,selectionEnd:null,value:"value"in r.props?r.props.value:r.props.defaultValue,btnDownHover:!1,btnDownActive:!1,btnUpHover:!1,btnUpActive:!1,inputFocus:!1},r.stop=r.stop.bind(r),r}return r(e,t),c(e,[{key:"componentWillReceiveProps",value:function(t){if(t.hasOwnProperty("value")){var e=String(t.value||0===t.value?t.value:"").replace(/^\s*|\s*$/,"");this.setState({value:"value"in t&&""!==e?this._parse(e):null,stringValue:e})}}},{key:"componentWillUpdate",value:function(){this.saveSelection()}},{key:"componentDidUpdate",value:function(t,e){e.value===this.state.value||isNaN(this.state.value)&&null!==this.state.value||this._invokeEventCallback("onChange",this.state.value,this.refs.input.value),this.state.inputFocus&&(this.refs.input.focus(),(this.state.selectionStart||0===this.state.selectionStart)&&(this.refs.input.selectionStart=this.state.selectionStart),(this.state.selectionEnd||0===this.state.selectionEnd)&&(this.refs.input.selectionEnd=this.state.selectionEnd)),this.checkValidity()}},{key:"componentWillUnmount",value:function(){this.stop()}},{key:"componentDidMount",value:function(){var t=this;this.refs.input.getValueAsNumber=function(){return t.state.value||0},this.refs.input.setValue=function(e){t.setState({value:t._parse(e),stringValue:e})},!this.state.inputFocus&&y&&document.activeElement===this.refs.input&&(this.state.inputFocus=!0,this.refs.input.focus(),this._invokeEventCallback("onFocus",{target:this.refs.input,type:"focus"})),this.checkValidity()}},{key:"saveSelection",value:function(){this.state.selectionStart=this.refs.input.selectionStart,this.state.selectionEnd=this.refs.input.selectionEnd}},{key:"checkValidity",value:function(){var t=void 0,e="",n=!!this.refs.input.checkValidity,o=!(!this.props.noValidate||"false"==this.props.noValidate);this.refs.input.noValidate=o,t=o||!n,t?e="":(""===this.refs.input.pattern&&(this.refs.input.pattern=this.props.required?".+":".*"),n&&(this.refs.input.checkValidity(),t=this.refs.input.validity.valid,t||(e=this.refs.input.validationMessage)),t&&n&&this.props.maxLength&&this.refs.input.value.length>this.props.maxLength&&(e="This value is too long")),e=e||(t?"":this.refs.input.validationMessage||"Unknown Error");var a=this._valid!==e;this._valid=e,e?(u(this.refs.wrapper,"has-error"),a&&this._invokeEventCallback("onInvalid",e,this.state.value,this.refs.input.value)):(l(this.refs.wrapper,"has-error"),a&&this._invokeEventCallback("onValid",this.state.value,this.refs.input.value))}},{key:"_toNumber",value:function(t,e){e=void 0===e?this.state.inputFocus&&!(this.state.btnDownActive||this.state.btnUpActive):!!e;var n=parseFloat(t),o=Math.pow(10,this.props.precision);return(isNaN(n)||!isFinite(n))&&(n=0),e?n:(n=Math.min(Math.max(n,this.props.min),this.props.max),n=Math.round(n*o)/o)}},{key:"_parse",value:function(t){return"function"==typeof this.props.parse?parseFloat(this.props.parse(t)):parseFloat(t)}},{key:"_format",value:function(t){var e=this._toNumber(t).toFixed(this.props.precision);return this.props.format?this.props.format(e):e}},{key:"_step",value:function(t,e){var n=this._toNumber((this.state.value||0)+this.props.step*t,!1);return n!==this.state.value?(this.setState({value:n,stringValue:n},e),!0):!1}},{key:"_onKeyDown",value:function(){for(var t=arguments.length,e=Array(t),n=0;t>n;n++)e[n]=arguments[n];e[0].persist(),this._invokeEventCallback.apply(this,["onKeyDown"].concat(e));var o=e[0];if(!o.isDefaultPrevented())if(o.keyCode===b)o.preventDefault(),this._step(o.ctrlKey||o.metaKey?.1:o.shiftKey?10:1);else if(o.keyCode===m)o.preventDefault(),this._step(o.ctrlKey||o.metaKey?-.1:o.shiftKey?-10:-1);else{var a=this.refs.input.value,i=a.length;8===o.keyCode?this.refs.input.selectionStart==this.refs.input.selectionEnd&&this.refs.input.selectionEnd>0&&a.length&&"."===a.charAt(this.refs.input.selectionEnd-1)&&(o.preventDefault(),this.refs.input.selectionStart=this.refs.input.selectionEnd=this.refs.input.selectionEnd-1):46===o.keyCode&&this.refs.input.selectionStart==this.refs.input.selectionEnd&&this.refs.input.selectionEndthis.props.min)&&(this._timer=setTimeout(function(){t.decrease(!0)},n?e.SPEED:e.DELAY))}},{key:"onMouseDown",value:function(t,e){"down"==t?this.decrease(!1,e):"up"==t&&this.increase(!1,e)}},{key:"onTouchStart",value:function(t,e){e.preventDefault(),"down"==t?this.decrease():"up"==t&&this.increase()}},{key:"_invokeEventCallback",value:function(t){if("function"==typeof this.props[t]){for(var e,n=arguments.length,o=Array(n>1?n-1:0),a=1;n>a;a++)o[a-1]=arguments[a];(e=this.props[t]).call.apply(e,[null].concat(o))}}},{key:"render",value:function(){var t=this,n=this.props,o=this.state,i={},s=this.props,r=(s.step,s.min,s.max,s.precision,s.parse,s.format,s.mobile),u=(s.value,s.type,s.style),l=(s.defaultValue,s.onInvalid,s.onValid,a(s,["step","min","max","precision","parse","format","mobile","value","type","style","defaultValue","onInvalid","onValid"]));for(var c in e.style)i[c]=p({},e.style[c],u?u[c]||{}:{});var f=n.className&&/\bform-control\b/.test(n.className);"auto"==r&&(r=y&&"ontouchstart"in document),"function"==typeof r&&(r=r.call(this)),r=!!r;var h={wrap:{style:u===!1?null:i.wrap,className:"react-numeric-input",ref:"wrapper",onMouseUp:void 0,onMouseLeave:void 0},input:p({ref:"input",type:"text",style:u===!1?null:p({},i.input,f?{}:i["input:not(.form-control)"],o.inputFocus?i["input:focus"]:{})},l),btnUp:{onMouseEnter:void 0,onMouseDown:void 0,onMouseUp:void 0,onMouseLeave:void 0,onTouchStart:void 0,onTouchEnd:void 0,style:u===!1?null:p({},i.btn,i.btnUp,n.disabled?i["btn:disabled"]:o.btnUpActive?i["btn:active"]:o.btnUpHover?i["btn:hover"]:{})},btnDown:{onMouseEnter:void 0,onMouseDown:void 0,onMouseUp:void 0,onMouseLeave:void 0,onTouchStart:void 0,onTouchEnd:void 0,style:u===!1?null:p({},i.btn,i.btnDown,n.disabled?i["btn:disabled"]:o.btnDownActive?i["btn:active"]:o.btnDownHover?i["btn:hover"]:{})}};return/^[+-.]{1,2}$/.test(o.stringValue)?h.input.value=o.stringValue:o.value||0===o.value?h.input.value=this._format(o.value):h.input.value="",f&&u!==!1&&p(h.wrap.style,i["wrap.hasFormControl"]),r&&u!==!1&&(p(h.input.style,i["input.mobile"]),p(h.btnUp.style,i["btnUp.mobile"]),p(h.btnDown.style,i["btnDown.mobile"])),n.disabled?u!==!1&&p(h.input.style,i["input:disabled"]):(p(h.wrap,{onMouseUp:this.stop,onMouseLeave:this.stop}),p(h.btnUp,{onTouchStart:this.onTouchStart.bind(this,"up"),onTouchEnd:this.stop,onMouseEnter:function(){t.setState({btnUpHover:!0})},onMouseLeave:function(){t.stop(),t.setState({btnUpHover:!1,btnUpActive:!1})},onMouseUp:function(){t.setState({btnUpHover:!0,btnUpActive:!1})},onMouseDown:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];n[0].preventDefault(),n[0].persist(),t.setState({btnUpHover:!0,btnUpActive:!0,inputFocus:!0},function(){t._invokeEventCallback.apply(t,["onFocus"].concat(n))}),t.onMouseDown("up")}}),p(h.btnDown,{onTouchStart:this.onTouchStart.bind(this,"down"),onTouchEnd:this.stop,onMouseEnter:function(){t.setState({btnDownHover:!0})},onMouseLeave:function(){t.stop(),t.setState({btnDownHover:!1,btnDownActive:!1})},onMouseUp:function(){t.setState({btnDownHover:!0,btnDownActive:!1})},onMouseDown:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];n[0].preventDefault(),n[0].persist(),t.setState({btnDownHover:!0,btnDownActive:!0,inputFocus:!0},function(){t._invokeEventCallback.apply(t,["onFocus"].concat(n))}),t.onMouseDown("down")}}),p(h.input,{onChange:function(e){var n=e.target.value,o=t._parse(n);isNaN(o)&&(o=null),t.setState({value:o,stringValue:n})},onKeyDown:this._onKeyDown.bind(this),onInput:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];t.saveSelection(),t._invokeEventCallback.apply(t,["onInput"].concat(n))},onSelect:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];t.saveSelection(),t._invokeEventCallback.apply(t,["onSelect"].concat(n))},onFocus:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];n[0].persist(),t.setState({inputFocus:!0},function(){var e=t._parse(n[0].target.value);t.setState({value:e,stringValue:e},function(){t._invokeEventCallback.apply(t,["onFocus"].concat(n))})})},onBlur:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];n[0].persist(),t.setState({inputFocus:!1},function(){var e=t._parse(n[0].target.value);t.setState({value:e},function(){t._invokeEventCallback.apply(t,["onBlur"].concat(n))})})}})),r?d["default"].createElement("span",h.wrap,d["default"].createElement("input",h.input),d["default"].createElement("b",h.btnUp,d["default"].createElement("i",{style:u===!1?null:i.minus}),d["default"].createElement("i",{style:u===!1?null:i.plus})),d["default"].createElement("b",h.btnDown,d["default"].createElement("i",{style:u===!1?null:i.minus}))):d["default"].createElement("span",h.wrap,d["default"].createElement("input",h.input),d["default"].createElement("b",h.btnUp,d["default"].createElement("i",{style:u===!1?null:i.arrowUp})),d["default"].createElement("b",h.btnDown,d["default"].createElement("i",{style:u===!1?null:i.arrowDown})))}}]),e}(f.Component);g.propTypes={step:v["default"].number,min:v["default"].number,max:v["default"].number,precision:v["default"].number,maxLength:v["default"].number,parse:v["default"].func,format:v["default"].func,className:v["default"].string,disabled:v["default"].bool,readOnly:v["default"].bool,required:v["default"].bool,noValidate:v["default"].oneOfType([v["default"].bool,v["default"].string]),style:v["default"].oneOfType([v["default"].object,v["default"].bool]),type:v["default"].string,pattern:v["default"].string,onFocus:v["default"].func,onBlur:v["default"].func,onKeyDown:v["default"].func,onChange:v["default"].func,onInvalid:v["default"].func,onValid:v["default"].func,onInput:v["default"].func,onSelect:v["default"].func,size:v["default"].oneOfType([v["default"].number,v["default"].string]),value:v["default"].oneOfType([v["default"].number,v["default"].string]),defaultValue:v["default"].oneOfType([v["default"].number,v["default"].string]),mobile:function(t,e){var n=t[e];return n!==!0&&n!==!1&&"auto"!==n&&"function"!=typeof n?new Error('The "mobile" prop must be true, false, "auto" or a function'):void 0}},g.defaultProps={step:1,min:Number.MIN_SAFE_INTEGER||-9007199254740991,max:Number.MAX_SAFE_INTEGER||9007199254740991,precision:0,parse:null,format:null,mobile:"auto",style:{}},g.style={wrap:{position:"relative",display:"inline-block"},"wrap.hasFormControl":{display:"block"},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"},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"},plus:{position:"absolute",top:"50%",left:"50%",width:2,height:10,background:"rgba(0,0,0,.7)",margin:"-5px 0 0 -1px"},minus:{position:"absolute",top:"50%",left:"50%",width:10,height:2,background:"rgba(0,0,0,.7)",margin:"-1px 0 0 -5px"},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:.5,boxShadow:"none",cursor:"not-allowed"},input:{paddingRight:"3ex",boxSizing:"border-box"},"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)"}},g.SPEED=50,g.DELAY=500,t.exports=g},function(e,n){e.exports=t},function(t,n){t.exports=e}])}); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("React"),require("prop-types")):"function"==typeof define&&define.amd?define(["React","prop-types"],e):"object"==typeof exports?exports.NumericInput=e(require("React"),require("prop-types")):t.NumericInput=e(t.React,t["prop-types"])}(this,function(t,e){return function(t){function e(o){if(n[o])return n[o].exports;var a=n[o]={exports:{},id:o,loaded:!1};return t[o].call(a.exports,a,a.exports,e),a.loaded=!0,a.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function o(t){return t&&t.__esModule?t:{"default":t}}function a(t,e){var n={};for(var o in t)e.indexOf(o)>=0||Object.prototype.hasOwnProperty.call(t,o)&&(n[o]=t[o]);return n}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function s(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function r(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function u(t,e){return t.classList?t.classList.add(e):void(t.className.search(new RegExp("\\b"+e+"\\b"))||(t.className=" "+e))}function l(t,e){if(t.className){if(t.classList)return t.classList.remove(e);t.className=t.className.replace(new RegExp("\\b"+e+"\\b","g"),"")}}var p=Object.assign||function(t){for(var e=1;ea;a++)o[a]=arguments[a];var r=s(this,(t=Object.getPrototypeOf(e)).call.apply(t,[this].concat(o)));return r.state={selectionStart:null,selectionEnd:null,value:"value"in r.props?r.props.value:r.props.defaultValue,btnDownHover:!1,btnDownActive:!1,btnUpHover:!1,btnUpActive:!1,inputFocus:!1},r.stop=r.stop.bind(r),r}return r(e,t),c(e,[{key:"componentWillReceiveProps",value:function(t){if(t.hasOwnProperty("value")){var e=String(t.value||0===t.value?t.value:"").replace(/^\s*|\s*$/,"");this.setState({value:"value"in t&&""!==e?this._parse(e):null,stringValue:e})}}},{key:"componentWillUpdate",value:function(){this.saveSelection()}},{key:"componentDidUpdate",value:function(t,e){e.value===this.state.value||isNaN(this.state.value)&&null!==this.state.value||this._invokeEventCallback("onChange",this.state.value,this.refs.input.value),this.state.inputFocus&&(this.refs.input.focus(),(this.state.selectionStart||0===this.state.selectionStart)&&(this.refs.input.selectionStart=this.state.selectionStart),(this.state.selectionEnd||0===this.state.selectionEnd)&&(this.refs.input.selectionEnd=this.state.selectionEnd)),this.checkValidity()}},{key:"componentWillUnmount",value:function(){this.stop()}},{key:"componentDidMount",value:function(){var t=this;this.refs.input.getValueAsNumber=function(){return t.state.value||0},this.refs.input.setValue=function(e){t.setState({value:t._parse(e),stringValue:e})},!this.state.inputFocus&&y&&document.activeElement===this.refs.input&&(this.state.inputFocus=!0,this.refs.input.focus(),this._invokeEventCallback("onFocus",{target:this.refs.input,type:"focus"})),this.checkValidity()}},{key:"saveSelection",value:function(){this.state.selectionStart=this.refs.input.selectionStart,this.state.selectionEnd=this.refs.input.selectionEnd}},{key:"checkValidity",value:function(){var t=void 0,e="",n=!!this.refs.input.checkValidity,o=!(!this.props.noValidate||"false"==this.props.noValidate);this.refs.input.noValidate=o,t=o||!n,t?e="":(""===this.refs.input.pattern&&(this.refs.input.pattern=this.props.required?".+":".*"),n&&(this.refs.input.checkValidity(),t=this.refs.input.validity.valid,t||(e=this.refs.input.validationMessage)),t&&n&&this.props.maxLength&&this.refs.input.value.length>this.props.maxLength&&(e="This value is too long")),e=e||(t?"":this.refs.input.validationMessage||"Unknown Error");var a=this._valid!==e;this._valid=e,e?(u(this.refs.wrapper,"has-error"),a&&this._invokeEventCallback("onInvalid",e,this.state.value,this.refs.input.value)):(l(this.refs.wrapper,"has-error"),a&&this._invokeEventCallback("onValid",this.state.value,this.refs.input.value))}},{key:"_toNumber",value:function(t,e){e=void 0===e?this.state.inputFocus&&!(this.state.btnDownActive||this.state.btnUpActive):!!e;var n=parseFloat(t),o=Math.pow(10,this.props.precision);return(isNaN(n)||!isFinite(n))&&(n=0),e?n:(n=Math.min(Math.max(n,this.props.min),this.props.max),n=Math.round(n*o)/o)}},{key:"_parse",value:function(t){return"function"==typeof this.props.parse?parseFloat(this.props.parse(t)):parseFloat(t)}},{key:"_format",value:function(t){var e=this._toNumber(t).toFixed(this.props.precision);return this.props.format?this.props.format(e):e}},{key:"_step",value:function(t,e){var n=this._toNumber((this.state.value||0)+this.props.step*t,!1);return this.props.snap&&(n=Math.round(n/this.props.step)*this.props.step),n!==this.state.value?(this.setState({value:n,stringValue:n},e),!0):!1}},{key:"_onKeyDown",value:function(){for(var t=arguments.length,e=Array(t),n=0;t>n;n++)e[n]=arguments[n];e[0].persist(),this._invokeEventCallback.apply(this,["onKeyDown"].concat(e));var o=e[0];if(!o.isDefaultPrevented())if(o.keyCode===b)o.preventDefault(),this._step(o.ctrlKey||o.metaKey?.1:o.shiftKey?10:1);else if(o.keyCode===m)o.preventDefault(),this._step(o.ctrlKey||o.metaKey?-.1:o.shiftKey?-10:-1);else{var a=this.refs.input.value,i=a.length;8===o.keyCode?this.refs.input.selectionStart==this.refs.input.selectionEnd&&this.refs.input.selectionEnd>0&&a.length&&"."===a.charAt(this.refs.input.selectionEnd-1)&&(o.preventDefault(),this.refs.input.selectionStart=this.refs.input.selectionEnd=this.refs.input.selectionEnd-1):46===o.keyCode&&this.refs.input.selectionStart==this.refs.input.selectionEnd&&this.refs.input.selectionEndthis.props.min)&&(this._timer=setTimeout(function(){t.decrease(!0)},n?e.SPEED:e.DELAY))}},{key:"onMouseDown",value:function(t,e){"down"==t?this.decrease(!1,e):"up"==t&&this.increase(!1,e)}},{key:"onTouchStart",value:function(t,e){e.preventDefault(),"down"==t?this.decrease():"up"==t&&this.increase()}},{key:"_invokeEventCallback",value:function(t){if("function"==typeof this.props[t]){for(var e,n=arguments.length,o=Array(n>1?n-1:0),a=1;n>a;a++)o[a-1]=arguments[a];(e=this.props[t]).call.apply(e,[null].concat(o))}}},{key:"render",value:function(){var t=this,n=this.props,o=this.state,i={},s=this.props,r=(s.step,s.min,s.max,s.precision,s.parse,s.format,s.mobile),u=(s.snap,s.value,s.type,s.style),l=(s.defaultValue,s.onInvalid,s.onValid,a(s,["step","min","max","precision","parse","format","mobile","snap","value","type","style","defaultValue","onInvalid","onValid"]));for(var c in e.style)i[c]=p({},e.style[c],u?u[c]||{}:{});var f=n.className&&/\bform-control\b/.test(n.className);"auto"==r&&(r=y&&"ontouchstart"in document),"function"==typeof r&&(r=r.call(this)),r=!!r;var h={wrap:{style:u===!1?null:i.wrap,className:"react-numeric-input",ref:"wrapper",onMouseUp:void 0,onMouseLeave:void 0},input:p({ref:"input",type:"text",style:u===!1?null:p({},i.input,f?{}:i["input:not(.form-control)"],o.inputFocus?i["input:focus"]:{})},l),btnUp:{onMouseEnter:void 0,onMouseDown:void 0,onMouseUp:void 0,onMouseLeave:void 0,onTouchStart:void 0,onTouchEnd:void 0,style:u===!1?null:p({},i.btn,i.btnUp,n.disabled?i["btn:disabled"]:o.btnUpActive?i["btn:active"]:o.btnUpHover?i["btn:hover"]:{})},btnDown:{onMouseEnter:void 0,onMouseDown:void 0,onMouseUp:void 0,onMouseLeave:void 0,onTouchStart:void 0,onTouchEnd:void 0,style:u===!1?null:p({},i.btn,i.btnDown,n.disabled?i["btn:disabled"]:o.btnDownActive?i["btn:active"]:o.btnDownHover?i["btn:hover"]:{})}};return/^[+-.]{1,2}$/.test(o.stringValue)?h.input.value=o.stringValue:o.value||0===o.value?h.input.value=this._format(o.value):h.input.value="",f&&u!==!1&&p(h.wrap.style,i["wrap.hasFormControl"]),r&&u!==!1&&(p(h.input.style,i["input.mobile"]),p(h.btnUp.style,i["btnUp.mobile"]),p(h.btnDown.style,i["btnDown.mobile"])),n.disabled?u!==!1&&p(h.input.style,i["input:disabled"]):(p(h.wrap,{onMouseUp:this.stop,onMouseLeave:this.stop}),p(h.btnUp,{onTouchStart:this.onTouchStart.bind(this,"up"),onTouchEnd:this.stop,onMouseEnter:function(){t.setState({btnUpHover:!0})},onMouseLeave:function(){t.stop(),t.setState({btnUpHover:!1,btnUpActive:!1})},onMouseUp:function(){t.setState({btnUpHover:!0,btnUpActive:!1})},onMouseDown:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];n[0].preventDefault(),n[0].persist(),t.setState({btnUpHover:!0,btnUpActive:!0,inputFocus:!0},function(){t._invokeEventCallback.apply(t,["onFocus"].concat(n))}),t.onMouseDown("up")}}),p(h.btnDown,{onTouchStart:this.onTouchStart.bind(this,"down"),onTouchEnd:this.stop,onMouseEnter:function(){t.setState({btnDownHover:!0})},onMouseLeave:function(){t.stop(),t.setState({btnDownHover:!1,btnDownActive:!1})},onMouseUp:function(){t.setState({btnDownHover:!0,btnDownActive:!1})},onMouseDown:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];n[0].preventDefault(),n[0].persist(),t.setState({btnDownHover:!0,btnDownActive:!0,inputFocus:!0},function(){t._invokeEventCallback.apply(t,["onFocus"].concat(n))}),t.onMouseDown("down")}}),p(h.input,{onChange:function(e){var n=e.target.value,o=t._parse(n);isNaN(o)&&(o=null),t.setState({value:o,stringValue:n})},onKeyDown:this._onKeyDown.bind(this),onInput:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];t.saveSelection(),t._invokeEventCallback.apply(t,["onInput"].concat(n))},onSelect:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];t.saveSelection(),t._invokeEventCallback.apply(t,["onSelect"].concat(n))},onFocus:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];n[0].persist(),t.setState({inputFocus:!0},function(){var e=t._parse(n[0].target.value);t.setState({value:e,stringValue:e},function(){t._invokeEventCallback.apply(t,["onFocus"].concat(n))})})},onBlur:function(){for(var e=arguments.length,n=Array(e),o=0;e>o;o++)n[o]=arguments[o];n[0].persist(),t.setState({inputFocus:!1},function(){var e=t._parse(n[0].target.value);t.setState({value:e},function(){t._invokeEventCallback.apply(t,["onBlur"].concat(n))})})}})),r?d["default"].createElement("span",h.wrap,d["default"].createElement("input",h.input),d["default"].createElement("b",h.btnUp,d["default"].createElement("i",{style:u===!1?null:i.minus}),d["default"].createElement("i",{style:u===!1?null:i.plus})),d["default"].createElement("b",h.btnDown,d["default"].createElement("i",{style:u===!1?null:i.minus}))):d["default"].createElement("span",h.wrap,d["default"].createElement("input",h.input),d["default"].createElement("b",h.btnUp,d["default"].createElement("i",{style:u===!1?null:i.arrowUp})),d["default"].createElement("b",h.btnDown,d["default"].createElement("i",{style:u===!1?null:i.arrowDown})))}}]),e}(f.Component);g.propTypes={step:v["default"].number,min:v["default"].number,max:v["default"].number,precision:v["default"].number,maxLength:v["default"].number,parse:v["default"].func,format:v["default"].func,className:v["default"].string,disabled:v["default"].bool,readOnly:v["default"].bool,required:v["default"].bool,snap:v["default"].bool,noValidate:v["default"].oneOfType([v["default"].bool,v["default"].string]),style:v["default"].oneOfType([v["default"].object,v["default"].bool]),type:v["default"].string,pattern:v["default"].string,onFocus:v["default"].func,onBlur:v["default"].func,onKeyDown:v["default"].func,onChange:v["default"].func,onInvalid:v["default"].func,onValid:v["default"].func,onInput:v["default"].func,onSelect:v["default"].func,size:v["default"].oneOfType([v["default"].number,v["default"].string]),value:v["default"].oneOfType([v["default"].number,v["default"].string]),defaultValue:v["default"].oneOfType([v["default"].number,v["default"].string]),mobile:function(t,e){var n=t[e];return n!==!0&&n!==!1&&"auto"!==n&&"function"!=typeof n?new Error('The "mobile" prop must be true, false, "auto" or a function'):void 0}},g.defaultProps={step:1,min:Number.MIN_SAFE_INTEGER||-9007199254740991,max:Number.MAX_SAFE_INTEGER||9007199254740991,precision:0,parse:null,format:null,mobile:"auto",style:{}},g.style={wrap:{position:"relative",display:"inline-block"},"wrap.hasFormControl":{display:"block"},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"},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"},plus:{position:"absolute",top:"50%",left:"50%",width:2,height:10,background:"rgba(0,0,0,.7)",margin:"-5px 0 0 -1px"},minus:{position:"absolute",top:"50%",left:"50%",width:10,height:2,background:"rgba(0,0,0,.7)",margin:"-1px 0 0 -5px"},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:.5,boxShadow:"none",cursor:"not-allowed"},input:{paddingRight:"3ex",boxSizing:"border-box"},"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)"}},g.SPEED=50,g.DELAY=500,t.exports=g},function(e,n){e.exports=t},function(t,n){t.exports=e}])}); \ No newline at end of file diff --git a/index.js b/index.js index 2231f6d..16a4616 100644 --- a/index.js +++ b/index.js @@ -293,6 +293,10 @@ module.exports = value: function _step(n, callback) { var _n = this._toNumber((this.state.value || 0) + this.props.step * n, false); + if (this.props.snap) { + _n = Math.round(_n / this.props.step) * this.props.step; + } + if (_n !== this.state.value) { this.setState({ value: _n, stringValue: _n }, callback); return true; @@ -424,6 +428,7 @@ module.exports = var parse = _props.parse; var format = _props.format; var mobile = _props.mobile; + var snap = _props.snap; var value = _props.value; var type = _props.type; var style = _props.style; @@ -431,7 +436,7 @@ module.exports = var onInvalid = _props.onInvalid; var onValid = _props.onValid; - var rest = _objectWithoutProperties(_props, ['step', 'min', 'max', 'precision', 'parse', 'format', 'mobile', 'value', 'type', 'style', 'defaultValue', 'onInvalid', 'onValid']); + var rest = _objectWithoutProperties(_props, ['step', 'min', 'max', 'precision', 'parse', 'format', 'mobile', 'snap', 'value', 'type', 'style', 'defaultValue', 'onInvalid', 'onValid']); for (var x in NumericInput.style) { css[x] = _extends({}, NumericInput.style[x], style ? style[x] || {} : {}); @@ -699,6 +704,7 @@ module.exports = disabled: _propTypes2.default.bool, readOnly: _propTypes2.default.bool, required: _propTypes2.default.bool, + snap: _propTypes2.default.bool, noValidate: _propTypes2.default.oneOfType([_propTypes2.default.bool, _propTypes2.default.string]), style: _propTypes2.default.oneOfType([_propTypes2.default.object, _propTypes2.default.bool]), type: _propTypes2.default.string, diff --git a/src/NumericInput.jsx b/src/NumericInput.jsx index 35e5699..ad34a17 100644 --- a/src/NumericInput.jsx +++ b/src/NumericInput.jsx @@ -62,6 +62,7 @@ class NumericInput extends Component 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]), type : PropTypes.string, @@ -581,6 +582,10 @@ class NumericInput extends Component false ); + if (this.props.snap) { + _n = Math.round(_n / this.props.step) * this.props.step + } + if (_n !== this.state.value) { this.setState({ value: _n, stringValue: _n }, callback); return true @@ -737,7 +742,7 @@ class NumericInput extends Component let { // These are ignored in rendering - step, min, max, precision, parse, format, mobile, + step, min, max, precision, parse, format, mobile, snap, value, type, style, defaultValue, onInvalid, onValid, // The rest are passed to the input