├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── Dev ├── demo.tsx └── index.tsx ├── LICENSE ├── README.md ├── example ├── README.md ├── index.tsx ├── package.json ├── tsconfig.json ├── typings.json └── webpack.config.js ├── package.json ├── server.js ├── src ├── NumberInput.tsx └── index.tsx ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # typings 40 | typings 41 | 42 | # d* 43 | dist 44 | demo 45 | 46 | # index.html 47 | index.* 48 | 49 | NumberInput.* 50 | 51 | # vscode 52 | .vscode 53 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | demo 2 | example 3 | node_modules 4 | src 5 | Dev 6 | screenshots 7 | typings 8 | .git 9 | .vscode 10 | index.html 11 | server.js 12 | tsconfig.json 13 | typings.json 14 | webpack.config.js 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v2.0.0](https://github.com/NoHomey/material-ui-number-input/releases/tag/2.0.0) 4 | 5 | ### Properties 6 | 7 | - `value` is now of type 'string' 8 | - `onChange` changed signature to `function (event: React.FormEvent, value: string, complete: boolean) => void` 9 | - `error` is new prop of type `React.PropTypes.oneOf(['none', 'invalidSymbol', 'incompleteNumber', 'singleMinus', 'singleFloatingPoint', 'singleZero', 'min', 'max', 'required'])` and it's the controlled error so `onError` can be called only when error changes. Default value is `'none'` 10 | 11 | ### Changes 12 | 13 | - replacing state double with controlled input 14 | - `value` is now watched for changes 15 | - `onChange` is now called every time when input value must change, third argument provides infomration is value a complete number. 16 | 17 | ### Bug fixes 18 | 19 | - `onKeyDown` is now called when `.` and `-` are pressed and emitted error is `'incompleteNumber'` 20 | 21 | ### Implementation 22 | 23 | - `shouldComponentUpdate` is now not overrided 24 | 25 | ## [v3.0.0](https://github.com/NoHomey/material-ui-number-input/releases/tag/3.0.0) 26 | 27 | ### Properties 28 | 29 | #### Deprecated 30 | 31 | - `showDefaultValue` 32 | - `error` 33 | 34 | #### New 35 | 36 | - `defaultValue` is of type `number` and is the same as `TextField` and `input` `defaultValue` prop 37 | - `onValid`is function with signature `function(value: number) => void` called when input's value is a valid number 38 | - `useStrategy` is of type `React.PropTypes.oneOf(['ignore', 'warn', 'allow'])` with defualt value `'allow'` and sets used error strategy refer to [Strategy](https://github.com/NoHomey/material-ui-number-input/#strategies) and [Errors](https://github.com/NoHomey/material-ui-number-input/#errors) 39 | 40 | #### Changed 41 | 42 | - `onError` signature changed to `function(event: React.FromEvent, value: string) => void` 43 | - `onChange` signature changed to `function(error: 'none' | 'invalidSymbol' | 'incompleteNumber' | 'singleMinus' | 'singleFloatingPoint' | 'singleZero'| 'min' | 'max' | 'required' | 'clean';) => void` 44 | 45 | ### Errors 46 | 47 | - `'clean'` equivalent of `'required'` when `required` prop is `false` 48 | 49 | ### Implementation 50 | 51 | - `error` is moved from `props` to `state` 52 | - re-exposing public method `getInputNode` of `TextField` 53 | - using polyfillied Object.assign ('object-assign') 54 | 55 | ## [v3.1.0](https://github.com/NoHomey/material-ui-number-input/releases/tag/3.1.0) 56 | 57 | ### Properties 58 | 59 | 60 | #### Deprecated 61 | 62 | - `useStrategy` has been renamed to `strategy` 63 | 64 | ### README changes 65 | 66 | ## [v3.1.2](https://github.com/NoHomey/material-ui-number-input/releases/tag/3.1.2) 67 | 68 | ### Bug fixes 69 | 70 | - fixing when `event.preventDefault()` is called and when `event` is delegated 71 | 72 | ## [v3.2.0](https://github.com/NoHomey/material-ui-number-input/releases/tag/3.2.0) 73 | 74 | ### Breaking changes 75 | 76 | - When `strategy` is `'ignore'` and `error` is `min` or `max` instead of clearing input field it's value is overwritten with `String(props[error])` 77 | 78 | ## [v4.0.0](https://github.com/NoHomey/material-ui-number-input/releases/tag/4.0.0) 79 | 80 | ### Bug fixes 81 | 82 | - Fixing all bugs introduced after `v3.0.0` which prevented valid numbers to be entered when `strategy` is `'ignore'` or `'warn'`. 83 | - Fixing a bug after `v3.2.0`. Now `onValid` is emitted when input's value is beeing overwritten when ''min'' or `'max'` errors are cathced and `strategy` is not `'allow'`. 84 | - Fixing a bug after `v3.2.0` where input value was not overwritten when `'min'` or `'max'` errors are cathced and `strategy` is not `'allow'` and input is `uncontrolled`. 85 | - Fixing a bug where input's value is not beeing validated before and after initial `render` depending on that is the input `controlled` or not. 86 | 87 | ## [v4.0.1](https://github.com/NoHomey/material-ui-number-input/releases/tag/4.0.1) 88 | 89 | ### Bug fixes 90 | 91 | - Fixing a bug where valid numbers are prevented from beeing entered. This bug occures when `(min * 10) < max` (not fixed in `v4.0.0`) 92 | - Fixing a bug where valid numbers are prevented from beeing entered. This bug occures when `Number(checkedValue) === 0` (not fixed in `v4.0.0`) 93 | 94 | ## [v4.0.2](https://github.com/NoHomey/material-ui-number-input/releases/tag/4.0.2) 95 | 96 | ### Bug fixes 97 | 98 | - Ensure `onBlur` is called if a handler is passed via `props` 99 | 100 | ### New 101 | 102 | - Adding new `public` method `getTextField` with signature `() => TextField` which returns underling `TextField` `ref` 103 | 104 | ## [v5.0.0](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.0) 105 | 106 | ### Properties 107 | 108 | #### New 109 | 110 | - `onRequestValue`is function with signature `function(value: string) => void` called with correct number value when `strategy` is `'warn'` or `'ignore'` and `value` is provided. 111 | 112 | ### Implementation 113 | 114 | - Droping alot of the logic for correcting value when `strategy` is `'warn'` or `'ignore'` and simplified it by introducing new prop `onRequestValue` which should ensure correct behavior when consumed by third party libraries such as `'react-material-ui-keyboard'` 115 | 116 | ## [v5.0.1](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.1) 117 | 118 | - Re-exporting `default` from `'./NumberInput'` 119 | 120 | ## [v5.0.2](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.2) 121 | 122 | - Using npm badge for README.md#Install 123 | 124 | ## [v5.0.3](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.3) 125 | 126 | - Fixing spelling bug onReqestValue -> onRequestValue, Opps sry ... 127 | 128 | ## [v5.0.4](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.4) 129 | 130 | - Performance improvment 131 | 132 | ## [v5.0.5](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.5) 133 | 134 | - Performance improvments and memory optimizations 135 | 136 | ### Bug fixes 137 | 138 | - If `strategy` is not `'allow'` and entered number is less than `min` but it is decimal number, input value will be overriden if it's not controlled else `onRequestValue` will be emitted and `'min'` error will be emitted if `strategy` is `'warn'` 139 | 140 | - If `strategy` is not `'allow'` and `min` is greater than `0` and entered number is `0`, input value will be overriden if it's not controlled else `onRequestValue` will be emitted and `'min'` error will be emitted if `strategy` is `'warn'` 141 | 142 | ## [v5.0.6](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.6) 143 | 144 | ## TypeScript Users Only 145 | 146 | - `propTypes` is now `React.ValidationMap` instead of just `Object` making `React.createElement` callable 147 | 148 | ## [v5.0.7](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.7) 149 | 150 | ### Bug fixes 151 | 152 | - Fixing a bug which caused input value overriding to be skipped when `strategy` is `'ignore'` instead of when it's `'allow'` 153 | 154 | ## [v5.0.8](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.8) 155 | 156 | - NPM dependencies update 157 | 158 | 159 | ## [v5.0.9](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.9) 160 | 161 | - NPM dependencies update 162 | - Moving to TypeScript v2 and replacing typings with @types 163 | 164 | ## [v5.0.10](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.10) 165 | 166 | ### Bug fixes 167 | 168 | - Fixing when `'singleZero'` is emitted. `'singleZero'` is emited accordingly to [Errors](https://github.com/NoHomey/material-ui-number-input#singlezero) now. In all other cases when `'singleZero'` was emitted now `'invalidSymbol'` is emitted instead. 169 | 170 | ## [v5.0.11](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.11) 171 | 172 | ### Bug fixes 173 | 174 | - Fixing `'allow'` to properly be masked as `'min'` when error is emitted `onBlur`. 175 | - If there is error `onBlur` it will be emitted no matter dose the strategy is `'warn'` or `'allow'`. 176 | 177 | ## [v5.0.12](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.12) 178 | 179 | ### Bug fixes 180 | 181 | - Fixing, in addition of `value` the following `props` are checked for changes: `min`, `max`,`required` and `strategy` before take decision to call `onError`, `onValid` and `onRequestValue` accordingly when `componentWillReceiveProps`. 182 | 183 | ## [v5.0.13](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.13) 184 | 185 | ### Bug fixes 186 | 187 | - Fixing the bug that should have been fixed with `v5.0.12` and the one introduced with `v5.0.12` which caused events to be one state behind actual input. 188 | 189 | ## [v5.0.14](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.14) 190 | 191 | ### Bug fixes 192 | 193 | - Fixing a bug which caused `onError` to be called based on old `props`. 194 | - Fixing the [bug](https://github.com/NoHomey/material-ui-number-input/blob/master/CHANGELOG.md#bug-fixes-9) that should be fixed with `5.0.12` 195 | 196 | ## [v5.0.15](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.15) 197 | 198 | ### Bug fixes 199 | 200 | - Fixing `Cannot resolve module 'material-ui-number-input'` due to `$ npm run npm` command has not been ran before releasing previos version. 201 | 202 | ## [v5.0.16](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.16) 203 | 204 | ### Properties 205 | 206 | - Displayed by the browser keyboard is now configurable (adding support for the `input` `inputMode` prop) 207 | 208 | ## [v5.0.17](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.17) 209 | 210 | ### Bug fixes 211 | 212 | - Allowing negative min values, closes #7 213 | 214 | ## [v5.0.18](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.18) 215 | 216 | - Syncing npm and github versions. 217 | 218 | ## [v5.0.19](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.19) 219 | 220 | ### Bug fixes 221 | 222 | - Fixing empty npm package, closes #9 223 | 224 | ## [v5.0.20](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.20) 225 | 226 | - Dummy version 227 | 228 | ## [v5.0.21](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.21) 229 | 230 | - Shipping #13 (Fixes #12) 231 | 232 | ## [v5.0.22](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.22) 233 | 234 | ### Bug fixes 235 | 236 | - Fixed: 237 | 238 | ``` 239 | Uncaught TypeError: value.match is not a function 240 | at Function.NumberInput.validateValue (NumberInput.js:103) 241 | ``` 242 | 243 | (closed #14) 244 | 245 | ## [v5.0.23](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.23) 246 | 247 | - Updating package dependencies 248 | 249 | - Switching from `React.PropTypes` to `prop-types` package 250 | 251 | ## [v5.0.24](https://github.com/NoHomey/material-ui-number-input/releases/tag/5.0.24) 252 | 253 | ### Props 254 | 255 | - Removing the unused prop `inputMode` 256 | 257 | 258 | -------------------------------------------------------------------------------- /Dev/demo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 3 | import { NumberInput, NumberInputChangeHandler, NumberInputError, NumberInputErrorHandler, NumberInputValidHandler, NumberInputReqestValueHandller } from './../src/index'; 4 | 5 | interface DemoState { 6 | value?: string; 7 | errorText?: string; 8 | } 9 | 10 | export default class Demo extends React.Component { 11 | private onChange: NumberInputChangeHandler; 12 | private onError: NumberInputErrorHandler; 13 | private onValid: NumberInputValidHandler; 14 | private onRequestValue: NumberInputReqestValueHandller; 15 | 16 | public constructor(props: Object) { 17 | super(props); 18 | this.state = { value: '10' }; 19 | this.onChange = (event: React.FormEvent, value: string): void => { 20 | console.log(`onChange ${event.currentTarget.value}, ${value} type: ${typeof value}`); 21 | this.setState({ value: value }); 22 | }; 23 | this.onError = (error: NumberInputError): void => { 24 | let errorText: string = ''; 25 | switch(error) { 26 | case 'required': 27 | errorText = 'This field is required'; 28 | break; 29 | case 'invalidSymbol': 30 | errorText = 'You are tring to enter none number symbol'; 31 | break; 32 | case 'incompleteNumber': 33 | errorText = 'Number is incomplete'; 34 | break; 35 | case 'singleMinus': 36 | errorText = 'Minus can be use only for negativity'; 37 | break; 38 | case 'singleFloatingPoint': 39 | errorText = 'There is already a floating point'; 40 | break; 41 | case 'singleZero': 42 | errorText = 'Floating point is expected'; 43 | break; 44 | case 'min': 45 | errorText = 'You are tring to enter number less than 11'; 46 | break; 47 | case 'max': 48 | errorText = 'You are tring to enter number greater than 150'; 49 | break; 50 | } 51 | this.setState({ errorText: errorText }); 52 | } 53 | this.onValid = (value: number): void => { 54 | console.debug(`${value} is a valid number!`); 55 | } 56 | this.onRequestValue = (value: string): void => { 57 | console.log(`request ${JSON.stringify(value)}`); 58 | this.setState({ value: value }) 59 | } 60 | } 61 | 62 | public render(): JSX.Element { 63 | const { state, onChange, onError, onValid, onRequestValue } = this; 64 | const { value, errorText } = state; 65 | return ( 66 | 67 |
68 | 69 | 81 |
82 |
83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Dev/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render as ReactDomRender } from 'react-dom'; 3 | import * as injectTapEventPlugin from 'react-tap-event-plugin'; 4 | import Demo from './demo'; 5 | 6 | injectTapEventPlugin(); 7 | let bootstrapNode = document.createElement('div'); 8 | ReactDomRender(, bootstrapNode); 9 | document.body.appendChild(bootstrapNode); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ivo Stratev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # material-ui-number-input 2 | 3 | # This project is no longer maintained. It is considered DEPRECATED! 4 | 5 | The better TextField for number inputs. 6 | 7 | # Install 8 | 9 | [![NPM](https://nodei.co/npm/material-ui-number-input.png?downloads=true&stars=true)](https://nodei.co/npm/material-ui-number-input/) 10 | 11 | # Changelog 12 | 13 | **Check [Change log](CHANGELOG.md) for changes.** 14 | 15 | # Properties 16 | 17 | | Name | Type | Default | TextField | Description | 18 | | ----------------------- | ---------- | --------- | --------- | ------------------------------------------------------- | 19 | | children | *node* | | ✓ | | 20 | | className | *string* | | ✓ | The css class name of the root element. | 21 | | disabled | *bool* | *false* | ✓ | Disables the input field if set to true.| 22 | | floatingLabelFixed | *bool* | *false* | ✓ | If true, the floating label will float even when there is no value. | 23 | | id | *string* | | ✓ | The id prop for the input field. | 24 | | name | *string* | | ✓ | Name applied to the input. | 25 | | fullWidth | *bool* | *false* | ✓ | If true, the field receives the property width 100%. | 26 | | underlineShow | *bool* | *true* | ✓ | If true, shows the underline for the input field. | 27 | | defaultValue | *number* | | ✓ | The number to use for the default value. Must be in range [min, max] if any is setted. | 28 | | strategy | *'ignore' \| 'warn' \| 'allow'* | *'allow'* | ❌ | Strategy to use when user presses key and when value prop change it's value. | 29 | | min | *number* | | ❌ | The number used as minimum value limit. Must be less than max. | 30 | | max | *number* | | ❌ | The number used as maximum value limit. Must be greater than min. | 31 | | required | *bool* | *false* | ❌ | If true and if input is left empty than instead of 'clean', 'required' error will be emited throughout onError handler if useStrategy is not 'ignore'. | 32 | | value | *string* | | ✓ | The value of the input field. | 33 | | onChange | *function* | | ✓ | Callback function that is fired when input filed must change it's value. **Signature:** `function(event: React.FormEvent, value: string) => void`. | 34 | | onError | *function* | | ❌ | Callback function that is fired when input error status changes and strategy is not 'ignore'. **Signature:** `function(error: 'none' | 'invalidSymbol' | 'incompleteNumber' | 'singleMinus' | 'singleFloatingPoint' | 'singleZero' | 'min' | 'max' | 'required' | 'clean') => void`. | 35 | | onValid | *function*| | ❌ | Callback function that is fired when input's value is a valid number. **Signature:** `function(value: number) => void` | 36 | | onRequestValue\* | *function* | | ❌ | Callback function that is fired when strategy is 'warn' or 'ignore', input is controlled and an invalid number value is passed. It provides valid number value which needs to be setted. **Signature:** `function(value: string) => void` | 37 | | errorText | *node* | | ✓ | The error content to display. | 38 | | errorStyle | *object* | | ✓ | The style object to use to override error styles. | 39 | | floatingLabelFocusStyle | *object* | | ✓ | The style object to use to override floating label styles when focused. | 40 | | floatingLabelStyle | *object* | | ✓ | The style object to use to override floating label styles. | 41 | | floatingLabelText | *node* | | ✓ | The content to use for the floating label element. | 42 | | hintStyle | *object* | | ✓ | Override the inline-styles of the TextField's hint text element. | 43 | | hintText | *node* | | ✓ | The hint content to display. | 44 | | inputStyle | *object* | | ✓ | Override the inline-styles of the TextField's input element. When multiLine is false: define the style of the input element. When multiLine is true: define the style of the container of the textarea. | 45 | | style | *object* | | ✓ | Override the inline-styles of the root element. | 46 | | underlineDisabledStyle | *object* | | ✓ | Override the inline-styles of the TextField's underline element when disabled. | 47 | | underlineFocusStyle | *object* | | ✓ | Override the inline-styles of the TextField's underline element when focussed. | 48 | | underlineStyle | *object* | | ✓ | Override the inline-styles of the TextField's underline element. | 49 | 50 | \* onRequestValue is required when strategy is 'warn' or 'ignore' and input is controlled in order to ensure correct strategy behaviour. 51 | 52 | # Strategies 53 | 54 | | strategy | onError fired | onRequestValue fired | 55 | | -------- | ------------- | ------------------- | 56 | | 'allow' | ✓ | | 57 | | 'warn' | ✓ | ✓\* | 58 | | 'ignore' | | ✓\* | 59 | 60 | \* Fired when input is controlled (`value` is provided). If input is not controlled it's value will be automaticlly corrected when it get's invalid number value. 61 | 62 | # Errors 63 | 64 | ## 'none' 65 | 66 | Fired when input's value is valid (there is no error). 67 | 68 | ## 'required' 69 | 70 | Fired when `required` prop is `true` and user leaves empty the input or it gets cleared. 71 | 72 | ## 'clean' 73 | 74 | Fired when `required` prop is `false` and user leaves empty the input or it gets cleared. 75 | 76 | ## 'invalidSymbol' 77 | 78 | Fired when user enters none special key which is different than `-`,`.`,`[0-9]`. 79 | 80 | ## 'incompleteNumber' 81 | 82 | Fired wehn user enters `-` as first char in the input or when user enters the first `.`. 83 | 84 | ## 'singleMinus' 85 | 86 | Fired when user enters `-` not as a first char. 87 | 88 | ## 'singleFloatingPoint' 89 | 90 | Fired when user enters `.` and there is already one entered. 91 | 92 | ## 'singleZero' 93 | 94 | Fired when user has entered `0` as first char and enters a digit key. 95 | 96 | ## 'min' 97 | 98 | Fired when user enters number less than `min` prop value. 99 | 100 | ## 'max' 101 | 102 | Fired when user enters number greater than `max` prop value. 103 | 104 | # public methods 105 | 106 | `NumberInput` re-exposes public method `getInputNode(): HTMLInputElement` from `TextField`. 107 | 108 | `TextField` methods: `blur`, `focus`, `select` and `getValue` are not exposed as they and `getInputNode` will be removed in material-ui 0.16 and replaced with public member `input` which is public and now but `getInputNode` is prefered until 0.16 is released. If you want to use any of those methods call them on input retunrned from `getInputNode` with the excpetion of `getValue` instead use `value` property. 109 | 110 | # Example 111 | 112 | ```js 113 | import * as React from 'react'; 114 | import NumberInput from 'material-ui-number-input'; 115 | 116 | class Demo extends React.Component { 117 | constructor(props) { 118 | super(props); 119 | 120 | this.onKeyDown = (event) => { 121 | console.log(`onKeyDown ${event.key}`); 122 | }; 123 | 124 | this.onChange = (event, value) => { 125 | const e = event; 126 | console.log(`onChange ${e.target.value}, ${value}`); 127 | }; 128 | 129 | this.onError = (error) => { 130 | let errorText; 131 | console.log(error); 132 | switch (error) { 133 | case 'required': 134 | errorText = 'This field is required'; 135 | break; 136 | case 'invalidSymbol': 137 | errorText = 'You are tring to enter none number symbol'; 138 | break; 139 | case 'incompleteNumber': 140 | errorText = 'Number is incomplete'; 141 | break; 142 | case 'singleMinus': 143 | errorText = 'Minus can be use only for negativity'; 144 | break; 145 | case 'singleFloatingPoint': 146 | errorText = 'There is already a floating point'; 147 | break; 148 | case 'singleZero': 149 | errorText = 'Floating point is expected'; 150 | break; 151 | case 'min': 152 | errorText = 'You are tring to enter number less than -10'; 153 | break; 154 | case 'max': 155 | errorText = 'You are tring to enter number greater than 12'; 156 | break; 157 | } 158 | this.setState({ errorText: errorText }); 159 | }; 160 | 161 | this.onValid = (value) => { 162 | console.debug(`${value} is a valid number`); 163 | }; 164 | 165 | this.onRequestValue = (value) => { 166 | console.log(`request ${JSON.stringify(value)}`); 167 | this.setState({ value: value }) 168 | } 169 | } 170 | 171 | render() { 172 | const { state, onChange, onError, onKeyDown, onValid, onRequestValue } = this; 173 | return ( 174 | 188 | ); 189 | } 190 | } 191 | ``` 192 | 193 | # Written in Typescript and Typescript Ready! ([check example](example/index.tsx)) 194 | 195 | # Supports propTypes for regular JavaScript users 196 | 197 | # Testing 198 | 199 | ## Tests will be added soon 200 | 201 | # Contributing 202 | 203 | 1. Fork the repository 204 | 2. `npm install` 205 | 3. `npm run typings` 206 | 4. Make changes 207 | 5. `npm start` 208 | 6. open `http://localhost:3000` 209 | 7. Make a Pull Request 210 | 211 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm start 4 | // Open ./demo/index.html in your favorite browser 5 | ``` 6 | -------------------------------------------------------------------------------- /example/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render as ReactDomRender } from 'react-dom'; 3 | import * as injectTapEventPlugin from 'react-tap-event-plugin'; 4 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 5 | import { NumberInput, NumberInputChangeHandler, NumberInputError, EventValue, NumberInputErrorHandler, NumberInputValidHandler, NumberInputReqestValueHandller } from 'material-ui-number-input'; 6 | 7 | const { div, link, input } = React.DOM; 8 | 9 | interface DemoState { 10 | value?: string; 11 | errorText?: string; 12 | } 13 | 14 | export default class Demo extends React.Component { 15 | private onKeyDown: React.KeyboardEventHandler; 16 | private onChange: NumberInputChangeHandler; 17 | private onError: NumberInputErrorHandler; 18 | private onValid: NumberInputValidHandler; 19 | private onRequestValue: NumberInputReqestValueHandller; 20 | 21 | public constructor(props: void) { 22 | super(props); 23 | this.state = { value: '30' }; 24 | this.onKeyDown = (event: React.KeyboardEvent): void => { 25 | console.log(`onKeyDown ${event.key}`); 26 | } 27 | this.onChange = (event: React.FormEvent, value: string): void => { 28 | const e: EventValue = event; 29 | console.log(`onChange ${e.target.value}, ${value}`); 30 | this.setState({ value: value }); 31 | }; 32 | this.onError = (error: NumberInputError): void => { 33 | let errorText: string; 34 | switch(error) { 35 | case 'required': 36 | errorText = 'This field is required'; 37 | break; 38 | case 'invalidSymbol': 39 | errorText = 'You are tring to enter none number symbol'; 40 | break; 41 | case 'incompleteNumber': 42 | errorText = 'Number is incomplete'; 43 | break; 44 | case 'singleMinus': 45 | errorText = 'Minus can be use only for negativity'; 46 | break; 47 | case 'singleFloatingPoint': 48 | errorText = 'There is already a floating point'; 49 | break; 50 | case 'singleZero': 51 | errorText = 'Floating point is expected'; 52 | break; 53 | case 'min': 54 | errorText = 'You are tring to enter number less than 11'; 55 | break; 56 | case 'max': 57 | errorText = 'You are tring to enter number greater than 150'; 58 | break; 59 | } 60 | this.setState({ errorText: errorText }); 61 | } 62 | this.onValid = (value: number): void => { 63 | console.debug(`${value} is a valid number!`); 64 | } 65 | this.onRequestValue = (value: string): void => { 66 | console.log(`request ${JSON.stringify(value)}`); 67 | this.setState({ value: value }) 68 | } 69 | } 70 | 71 | public render(): JSX.Element { 72 | const { state, onChange, onError, onValid, onKeyDown, onRequestValue } = this; 73 | const { value, errorText } = state; 74 | return ( 75 | 76 |
77 | 78 | 90 |
91 |
92 | ); 93 | } 94 | } 95 | 96 | injectTapEventPlugin(); 97 | let bootstrapNode = document.createElement('div'); 98 | ReactDomRender(, bootstrapNode); 99 | document.body.appendChild(bootstrapNode); -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./demo/index.html", 3 | "scripts": { 4 | "test": "echo \"Error: no test specified\" && exit 1", 5 | "postinstall": "typings install", 6 | "start": "webpack" 7 | }, 8 | "author": "Ivo Stratev", 9 | "license": "MIT", 10 | "dependencies": { 11 | "material-ui": "^0.15.4", 12 | "react": "^15.3.1", 13 | "react-dom": "^15.3.1", 14 | "react-tap-event-plugin": "^1.0.0", 15 | "material-ui-number-input": "^5.0.0" 16 | }, 17 | "devDependencies": { 18 | "html-webpack-plugin": "^2.22.0", 19 | "ts-loader": "^0.8.2", 20 | "typescript": "^1.8.10", 21 | "typings": "^1.3.1", 22 | "webpack": "^1.13.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "moduleResolution": "node", 5 | "target": "es5", 6 | "jsx": "react", 7 | "noImplicitAny": true 8 | }, 9 | "files": [ 10 | "typings/index.d.ts", 11 | "index.tsx" 12 | ], 13 | "exclude": [ 14 | "node_modules" 15 | ] 16 | } 17 | 18 | -------------------------------------------------------------------------------- /example/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "node": "registry:dt/node", 4 | "react": "registry:dt/react", 5 | "react-dom": "registry:dt/react-dom", 6 | "react-tap-event-plugin": "registry:dt/react-tap-event-plugin", 7 | "material-ui": "registry:dt/material-ui" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const webpack = require('webpack'); 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | entry: [ 7 | "./index.tsx" 8 | ], 9 | 10 | output: { 11 | path: path.join(__dirname, "demo"), 12 | filename: "[name].js", 13 | publicPath: '' 14 | }, 15 | 16 | plugins: [ 17 | new HtmlWebpackPlugin({ title: "Material-Ui NumberInput" }) 18 | ], 19 | 20 | resolve: { 21 | extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"] 22 | }, 23 | 24 | module: { 25 | loaders: [ 26 | { 27 | test: /\.tsx?$/, 28 | loaders: ["ts-loader"] 29 | } 30 | ], 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "material-ui-number-input", 3 | "version": "5.0.24", 4 | "description": "The better TextField for number inputs.", 5 | "main": "./index.js", 6 | "typings": "./index.d.ts", 7 | "scripts": { 8 | "test": "tsc", 9 | "start": "webpack && mv ./demo/index.html ./ && node server.js", 10 | "npm": "tsc -d -p . && mv ./src/*.js . && mv ./src/*.d.ts .", 11 | "clean": "rm ./src/*.js" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "material-ui", 16 | "input", 17 | "number", 18 | "ux", 19 | "material-design", 20 | "input-number", 21 | "user-expects" 22 | ], 23 | "author": "Ivo Stratev", 24 | "license": "MIT", 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/NoHomey/material-ui-number-input.git" 28 | }, 29 | "peerDependencies": { 30 | "material-ui": "^0.18.6", 31 | "react": "^15.6.1", 32 | "react-dom": "^15.6.1", 33 | "react-tap-event-plugin": "^2.0.1" 34 | }, 35 | "devDependencies": { 36 | "@types/material-ui": "^0.17.17", 37 | "@types/node": "^8.0.14", 38 | "@types/object-assign": "^4.0.30", 39 | "@types/prop-types": "^15.5.1", 40 | "@types/react": "^15.0.38", 41 | "@types/react-dom": "^15.5.1", 42 | "@types/react-tap-event-plugin": "^0.0.30", 43 | "express": "^4.15.3", 44 | "html-webpack-plugin": "^2.29.0", 45 | "material-ui": "^0.18.6", 46 | "react": "^15.6.1", 47 | "react-dom": "^15.6.1", 48 | "react-hot-loader": "^1.3.1", 49 | "react-tap-event-plugin": "^2.0.1", 50 | "ts-loader": "^2.3.1", 51 | "typescript": "^2.4.2", 52 | "webpack": "^2.3.3", 53 | "webpack-dev-middleware": "^1.11.0", 54 | "webpack-hot-middleware": "^2.18.2" 55 | }, 56 | "dependencies": { 57 | "bind-decorator": "^1.0.11", 58 | "object-assign": "^4.1.1", 59 | "prop-types": "^15.5.10" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | var config = require('./webpack.config'); 5 | 6 | config.plugins.pop(); 7 | 8 | var app = express(); 9 | var compiler = webpack(config); 10 | 11 | app.use(require('webpack-dev-middleware')(compiler, { 12 | noInfo: true, 13 | publicPath: config.output.publicPath 14 | })); 15 | 16 | app.use(require('webpack-hot-middleware')(compiler)); 17 | 18 | app.get('*', function (req, res) { 19 | res.sendFile(path.join(__dirname, 'index.html')); 20 | }); 21 | 22 | app.listen(3000, 'localhost', function (err) { 23 | if (err) { 24 | console.log(err); 25 | return; 26 | } 27 | 28 | console.log('Listening at http://localhost:3000'); 29 | }); 30 | -------------------------------------------------------------------------------- /src/NumberInput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as PropTypes from "prop-types"; 3 | import TextField from 'material-ui/TextField'; 4 | import ObjectAssign = require('object-assign'); 5 | import bind from 'bind-decorator'; 6 | 7 | export type NumberInputError = 'none' | 'invalidSymbol' | 'incompleteNumber' | 'singleMinus' 8 | | 'singleFloatingPoint' | 'singleZero'| 'min' | 'max' | 'required' | 'clean'; 9 | 10 | export type NumberInputChangeHandler = (event: React.FormEvent, value: string) => void; 11 | 12 | export type NumberInputValidHandler = (valid: number) => void; 13 | 14 | export type NumberInputErrorHandler = (error: NumberInputError) => void; 15 | 16 | export type NumberInputReqestValueHandller = (value: string) => void; 17 | 18 | export interface NumberInputProps { 19 | className?: string; 20 | disabled?: boolean; 21 | floatingLabelFixed?: boolean; 22 | id?: string; 23 | name?: string; 24 | fullWidth?: boolean; 25 | underlineShow?: boolean; 26 | defaultValue?: number; 27 | min?: number; 28 | max?: number; 29 | required?: boolean; 30 | strategy?: 'ignore' | 'warn' | 'allow'; 31 | value?: string; 32 | errorText?: React.ReactNode; 33 | errorStyle?: React.CSSProperties; 34 | floatingLabelFocusStyle?: React.CSSProperties; 35 | floatingLabelStyle?: React.CSSProperties; 36 | floatingLabelText?: React.ReactNode; 37 | hintStyle?: React.CSSProperties; 38 | hintText?: React.ReactNode; 39 | inputStyle?: React.CSSProperties; 40 | style?: React.CSSProperties; 41 | underlineDisabledStyle?: React.CSSProperties; 42 | underlineFocusStyle?: React.CSSProperties; 43 | underlineStyle?: React.CSSProperties; 44 | onBlur?: React.FocusEventHandler; 45 | onChange?: NumberInputChangeHandler; 46 | onError?: NumberInputErrorHandler; 47 | onValid?: NumberInputValidHandler; 48 | onRequestValue?: NumberInputReqestValueHandller; 49 | onFocus?: React.FocusEventHandler; 50 | onKeyDown?: React.KeyboardEventHandler; 51 | } 52 | 53 | export type NumberInputErrorExtended = NumberInputError | 'limit' | 'allow'; 54 | 55 | namespace errorNames { 56 | export const none: 'none' = 'none'; 57 | export const invalidSymbol: 'invalidSymbol' = 'invalidSymbol'; 58 | export const incompleteNumber: 'incompleteNumber' = 'incompleteNumber'; 59 | export const singleMinus: 'singleMinus' = 'singleMinus'; 60 | export const singleFloatingPoint: 'singleFloatingPoint' = 'singleFloatingPoint'; 61 | export const singleZero: 'singleZero' = 'singleZero'; 62 | export const min: 'min' = 'min'; 63 | export const max: 'max' = 'max'; 64 | export const required: 'required' = 'required'; 65 | export const clean: 'clean' = 'clean'; 66 | export const allow: 'allow' = 'allow'; 67 | export const limit: 'limit' = 'limit'; 68 | } 69 | 70 | namespace strategies { 71 | export const ignore: 'ignore' = 'ignore'; 72 | export const warn: 'warn' = 'warn'; 73 | export const allow: 'allow' = 'allow'; 74 | } 75 | 76 | namespace typeofs { 77 | export const stringType: string = 'string'; 78 | export const numberType: string = 'number'; 79 | } 80 | 81 | namespace constants { 82 | export const emptyString: string = ''; 83 | export const dash: string = '-'; 84 | export const dot: string = '.'; 85 | export const zero: number = 0; 86 | export const one: number = 1; 87 | export const text: string = 'text'; 88 | export const zeroString: string = '0'; 89 | export const minusOne: number = -1; 90 | export const boolTrue: boolean = true; 91 | export const boolFalse: boolean = false; 92 | } 93 | 94 | export class NumberInput extends React.Component { 95 | private static getValidValue(value: string): string { 96 | const match: RegExpMatchArray | null = value.match(NumberInput.allowed); 97 | return match !== null ? (match.index === constants.zero ? match[constants.zero] : match.join(constants.emptyString)) : constants.emptyString; 98 | } 99 | 100 | private static deleteOwnProps(props: any): void { 101 | let prop: string; 102 | for(let index: number = 0; index < NumberInput.deleteProps.length; ++index) { 103 | prop = NumberInput.deleteProps[index]; 104 | if(props.hasOwnProperty(prop)) { 105 | delete props[prop]; 106 | } 107 | } 108 | } 109 | 110 | private static validateNumberValue(value: number, props: NumberInputProps): number { 111 | const { max, min } = props; 112 | if((typeof max === typeofs.numberType) && (value > max!)) { 113 | return constants.one; 114 | } 115 | if((typeof min === typeofs.numberType) && (value < min!)) { 116 | return constants.minusOne; 117 | } 118 | return constants.zero; 119 | } 120 | 121 | private static validateValue(value: string, props: NumberInputProps): NumberInputErrorExtended { 122 | const { required, strategy, min } = props; 123 | if(value === constants.emptyString) { 124 | return required ? errorNames.required : errorNames.clean; 125 | } else { 126 | if(value.match(NumberInput.validSymbols)) { 127 | if(value.match(NumberInput.stricAllowed)) { 128 | if(value.match(NumberInput.validNumber)) { 129 | const numberValue: number = Number(value); 130 | const floatingPoint: number = value.indexOf(constants.dot); 131 | const decimal: boolean = floatingPoint > constants.minusOne; 132 | const whole: number = decimal ? Number(value.substring(constants.zero, floatingPoint)) : min!; 133 | switch(NumberInput.validateNumberValue(numberValue, props)) { 134 | case constants.one: return errorNames.max; 135 | case constants.minusOne: return ((strategy !== strategies.allow) && (min! > constants.zero) && (numberValue > constants.zero) && (!decimal || (decimal && (whole > min!)))) ? errorNames.allow : errorNames.min; 136 | default: return errorNames.none; 137 | } 138 | } else { 139 | return (strategy !== strategies.allow) && (value === constants.dash) && (min! >= constants.zero) 140 | ? errorNames.limit : (min! < 0 ? errorNames.allow : errorNames.incompleteNumber); 141 | } 142 | } else { 143 | switch(value[value.length - constants.one]) { 144 | case constants.dash: return errorNames.singleMinus; 145 | case constants.dot: return errorNames.singleFloatingPoint; 146 | case constants.zeroString: return errorNames.singleZero; 147 | default : return errorNames.invalidSymbol; 148 | } 149 | } 150 | } else { 151 | return errorNames.invalidSymbol; 152 | } 153 | } 154 | } 155 | 156 | private static overrideRequestedValue(error: string, value: string, props: NumberInputProps): string { 157 | switch(error) { 158 | case errorNames.min: return String(props.min); 159 | case errorNames.max: return String(props.max); 160 | default: return props.strategy !== strategies.allow && value === constants.dash && props.min! >= 0 ? constants.emptyString : value; 161 | } 162 | } 163 | 164 | private static overrideError(error: NumberInputErrorExtended, props: NumberInputProps): NumberInputError { 165 | switch(error) { 166 | case errorNames.allow: return errorNames.none; 167 | case errorNames.limit: return props.required ? errorNames.required : errorNames.clean; 168 | default: return error; 169 | } 170 | } 171 | 172 | private static revertAllowToMin(error: NumberInputErrorExtended): NumberInputErrorExtended { 173 | return error === errorNames.allow ? errorNames.min : error; 174 | } 175 | 176 | private static emitValid(error: string, overridenError: string): boolean { 177 | return (error !== errorNames.allow) && (overridenError === errorNames.none); 178 | } 179 | 180 | private static validSymbols: RegExp = /(\-|\.|\d)+/; 181 | private static stricAllowed: RegExp = /^-?((0|([1-9]\d{0,}))(\.\d{0,})?)?$/; 182 | private static validNumber: RegExp = /^-?((0(\.\d+)?)|([1-9]\d{0,}(\.\d+)?))$/; 183 | private static allowed: RegExp = /-?((0|([1-9]\d{0,}))(\.\d{0,})?)?/; 184 | private static deleteProps: Array = ['strategy', 'onError', 'onValid', 'onRequestValue']; 185 | 186 | public static propTypes: React.ValidationMap = { 187 | className: PropTypes.string, 188 | disabled: PropTypes.bool, 189 | errorStyle: PropTypes.object, 190 | errorText: PropTypes.node, 191 | floatingLabelFixed: PropTypes.bool, 192 | floatingLabelFocusStyle: PropTypes.object, 193 | floatingLabelStyle: PropTypes.object, 194 | floatingLabelText: PropTypes.node, 195 | fullWidth: PropTypes.bool, 196 | hintStyle: PropTypes.object, 197 | hintText: PropTypes.node, 198 | id: PropTypes.string, 199 | inputStyle: PropTypes.object, 200 | name: PropTypes.string, 201 | onBlur: PropTypes.func, 202 | onChange: PropTypes.func, 203 | onFocus: PropTypes.func, 204 | onValid: PropTypes.func, 205 | onError: PropTypes.func, 206 | onRequestValue: PropTypes.func, 207 | onKeyDown: PropTypes.func, 208 | style: PropTypes.object, 209 | underlineDisabledStyle: PropTypes.object, 210 | underlineFocusStyle: PropTypes.object, 211 | underlineShow: PropTypes.bool, 212 | underlineStyle: PropTypes.object, 213 | defaultValue: PropTypes.number, 214 | min: PropTypes.number, 215 | max: PropTypes.number, 216 | required: PropTypes.bool, 217 | strategy: PropTypes.oneOf([ 218 | strategies.ignore, 219 | strategies.warn, 220 | strategies.allow 221 | ]), 222 | value: PropTypes.string 223 | }; 224 | public static defaultProps: NumberInputProps = { 225 | required: constants.boolFalse, 226 | strategy: strategies.allow 227 | }; 228 | public textField: TextField; 229 | private error: NumberInputErrorExtended; 230 | private lastValid: string; 231 | private constProps: Object; 232 | 233 | private emitEvents(nextError: NumberInputErrorExtended, value: string, valid: boolean, props: NumberInputProps): void { 234 | const { onError, onValid } = props; 235 | if((this.error !== nextError) && (props.strategy !== strategies.ignore)) { 236 | if(onError) { 237 | onError(nextError as NumberInputError); 238 | } 239 | this.error = nextError; 240 | } 241 | if(onValid && valid && (this.lastValid !== value)) { 242 | onValid(Number(value)); 243 | this.lastValid = value; 244 | } 245 | } 246 | 247 | private takeActionForValue(value: string, props: NumberInputProps): void { 248 | const { strategy, onRequestValue, value: propsValue } = props; 249 | const error: NumberInputErrorExtended = NumberInput.validateValue(value, props); 250 | const valid: string = NumberInput.overrideRequestedValue(error, NumberInput.getValidValue(value), props); 251 | const overridenError: NumberInputError = NumberInput.overrideError(error, props); 252 | const emitValid: boolean = NumberInput.emitValid(error, overridenError); 253 | this.emitEvents(overridenError, valid, emitValid, props); 254 | if((strategy !== strategies.allow) && (valid !== value)) { 255 | if(typeof propsValue !== typeofs.stringType) { 256 | this.getInputNode().value = valid; 257 | } else if(onRequestValue) { 258 | onRequestValue(valid); 259 | } 260 | } 261 | } 262 | 263 | private shouldTakeActionForValue(props: NumberInputProps): boolean { 264 | const { min, max, required, strategy } = this.props; 265 | return (min !== props.min) || (max !== props.max) || (required !== props.required) || (strategy !== props.strategy); 266 | } 267 | 268 | @bind 269 | private refTextField(textField: TextField): void { 270 | this.textField = textField; 271 | } 272 | 273 | @bind 274 | private onChange(event: React.FormEvent): void { 275 | const { value } = event.currentTarget; 276 | const { onChange } = this.props; 277 | if(onChange) { 278 | onChange(event, value!); 279 | } 280 | if(typeof this.props.value !== typeofs.stringType) { 281 | this.takeActionForValue(value!, this.props); 282 | } 283 | } 284 | 285 | @bind 286 | private onBlur(event: React.FocusEvent): void { 287 | const { value } = event.currentTarget; 288 | const { props } = this; 289 | const { onBlur } = props; 290 | const error: NumberInputError = NumberInput.overrideError(NumberInput.revertAllowToMin(NumberInput.validateValue(value!, props)), props); 291 | this.emitEvents(error, value!, constants.boolFalse, props); 292 | if(onBlur) { 293 | onBlur(event); 294 | } 295 | } 296 | 297 | public getInputNode(): HTMLInputElement { 298 | return this.textField.getInputNode(); 299 | } 300 | 301 | public getTextField(): TextField { 302 | return this.textField; 303 | } 304 | 305 | public constructor(props: NumberInputProps) { 306 | super(props); 307 | this.constProps = { 308 | type: constants.text, 309 | onChange: this.onChange, 310 | onBlur: this.onBlur, 311 | ref: this.refTextField 312 | }; 313 | } 314 | 315 | public componentDidMount(): void { 316 | const { props } = this; 317 | const { value } = props; 318 | this.takeActionForValue(typeof value === typeofs.stringType ? value! : this.getInputNode().value, props); 319 | } 320 | 321 | public componentWillReceiveProps(props: NumberInputProps): void { 322 | const { value } = props; 323 | if((value !== this.props.value) || this.shouldTakeActionForValue(props)) { 324 | this.takeActionForValue(value!, props); 325 | } 326 | } 327 | 328 | public render(): JSX.Element { 329 | const { props, constProps } = this; 330 | const { value, defaultValue } = props; 331 | let inputProps: any = ObjectAssign({}, props, constProps, { 332 | defaultValue: typeof defaultValue === typeofs.numberType ? String(defaultValue) : undefined, 333 | value: value, 334 | }); 335 | if(typeof inputProps.value !== typeofs.stringType) { 336 | delete inputProps.value; 337 | } 338 | if(inputProps.defaultValue === undefined) { 339 | delete inputProps.defaultValue; 340 | } 341 | NumberInput.deleteOwnProps(inputProps); 342 | return React.createElement(TextField, inputProps); 343 | } 344 | } 345 | 346 | export default NumberInput; 347 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import NumberInput from './NumberInput'; 2 | export * from './NumberInput'; 3 | export default NumberInput; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": true, 6 | "strictNullChecks": true, 7 | "noImplicitThis": true, 8 | "noUnusedParameters": true, 9 | "noUnusedLocals": true, 10 | "experimentalDecorators": true, 11 | "jsx": "react" 12 | }, 13 | "include": [ 14 | "src/**/*" 15 | ], 16 | "exclude": [ 17 | "node_modules" 18 | ] 19 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const webpack = require('webpack'); 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | entry: [ 7 | 'webpack-hot-middleware/client', 8 | "./Dev/index.tsx" 9 | ], 10 | 11 | output: { 12 | path: path.join(__dirname, "demo"), 13 | filename: "[name].js", 14 | publicPath: '' 15 | }, 16 | 17 | plugins: [ 18 | new webpack.HotModuleReplacementPlugin(), 19 | new HtmlWebpackPlugin({ title: "Material-Ui Number Input" }) 20 | ], 21 | 22 | resolve: { 23 | extensions: [".ts", ".tsx", ".js"] 24 | }, 25 | 26 | module: { 27 | loaders: [ 28 | { 29 | test: /\.tsx?$/, 30 | loaders: ["ts-loader"] 31 | } 32 | ], 33 | } 34 | }; 35 | --------------------------------------------------------------------------------