UNPKG

4.87 kBJavaScriptView Raw
1import React, { PureComponent } from 'react'
2import PropTypes from 'prop-types'
3import icons from '../icons'
4import Input from './Input'
5import { inputClass } from '../styles'
6
7class Number extends PureComponent {
8 constructor(props) {
9 super(props)
10
11 this.handleBlur = this.handleBlur.bind(this)
12 this.handleChange = this.handleChange.bind(this)
13 this.handleAddClick = this.handleCalc.bind(this, props.step)
14 this.handleSubClick = this.handleCalc.bind(this, -props.step)
15 this.handleMouseUp = this.handleMouseUp.bind(this)
16 this.handleKeyDown = this.handleKeyDown.bind(this)
17 this.handleKeyUp = this.handleKeyUp.bind(this)
18 }
19
20 componentWillUnmount() {
21 this.hold = false
22 if (this.keyPressTimeOut) clearTimeout(this.keyPressTimeOut)
23 }
24
25 handleChange(value, check, isEmpty) {
26 if (isEmpty) {
27 this.props.onChange(value)
28 return
29 }
30
31 if (!check) {
32 if (new RegExp('^-?\\d*\\.?\\d*$').test(value)) {
33 this.props.onChange(value)
34 }
35 return
36 }
37
38 if (typeof this.props.digits === 'number') {
39 value = parseFloat(value.toFixed(this.props.digits))
40 } else {
41 const stepStr = this.props.step.toString()
42 const dot = stepStr.lastIndexOf('.')
43 if (dot >= 0) value = parseFloat(value.toFixed(stepStr.length - dot))
44 }
45
46 const { min, max } = this.props
47
48 if (max !== undefined && value > max) value = max
49 if (min !== undefined && value < min) value = min
50
51 if (value !== this.props.value) {
52 this.props.onChange(value)
53 }
54 }
55
56 handleBlur(e) {
57 let value = parseFloat(e.target.value)
58 // for the empty
59 if (e.target.value === '' && this.props.allowNull) {
60 value = null
61 }
62 // eslint-disable-next-line no-restricted-globals
63 if (isNaN(value)) value = 0
64 this.handleChange(value, true, value === null)
65 this.props.onBlur(e)
66 }
67
68 changeValue(mod) {
69 if (this.props.disabled) return
70 let val = this.props.value
71 if (val === 0) val = '0'
72 let value = parseFloat(`${val || ''}`.replace(/,/g, ''))
73 // eslint-disable-next-line
74 if (isNaN(value)) value = 0
75 this.handleChange(value + mod, true)
76 }
77
78 longPress(mod) {
79 if (!this.hold) return
80 setTimeout(() => {
81 this.changeValue(mod)
82 this.longPress(mod)
83 }, 50)
84 }
85
86 handleKeyDown(e) {
87 const { step } = this.props
88 this.hold = true
89 if (e.keyCode !== 38 && e.keyCode !== 40) return
90 e.preventDefault()
91 const mod = e.keyCode === 38 ? step : -step
92 this.changeValue(mod)
93 if (this.keyPressTimeOut) clearTimeout(this.keyPressTimeOut)
94 this.keyPressTimeOut = setTimeout(() => {
95 this.longPress(mod)
96 }, 600)
97 }
98
99 handleCalc(mod) {
100 const { onMouseDown } = this.props
101 if (onMouseDown) onMouseDown()
102 this.hold = true
103 this.changeValue(mod)
104 this.keyPressTimeOut = setTimeout(() => {
105 this.longPress(mod)
106 }, 1000)
107 }
108
109 handleKeyUp() {
110 this.hold = false
111 if (this.keyPressTimeOut) clearTimeout(this.keyPressTimeOut)
112 }
113
114 handleMouseUp() {
115 const { onMouseUp } = this.props
116 if (onMouseUp) onMouseUp()
117 this.hold = false
118 if (this.keyPressTimeOut) clearTimeout(this.keyPressTimeOut)
119 }
120
121 renderArrowGroup() {
122 const { hideArrow } = this.props
123 if (hideArrow) return []
124 return [
125 <a
126 key="up"
127 // do not need the tab to focus
128 tabIndex={-1}
129 className={inputClass('number-up')}
130 onMouseDown={this.handleAddClick}
131 onMouseUp={this.handleMouseUp}
132 onMouseLeave={this.handleMouseUp}
133 >
134 {icons.AngleRight}
135 </a>,
136
137 <a
138 key="down"
139 // do not need the tab to focus
140 tabIndex={-1}
141 className={inputClass('number-down')}
142 onMouseDown={this.handleSubClick}
143 onMouseUp={this.handleMouseUp}
144 onMouseLeave={this.handleMouseUp}
145 >
146 {icons.AngleRight}
147 </a>,
148 ]
149 }
150
151 render() {
152 const { onChange, allowNull, hideArrow, ...other } = this.props
153 return [
154 <Input
155 key="input"
156 {...other}
157 className={inputClass({ number: !hideArrow })}
158 onChange={this.handleChange}
159 onKeyDown={this.handleKeyDown}
160 onKeyUp={this.handleKeyUp}
161 onBlur={this.handleBlur}
162 type="number"
163 />,
164 ...this.renderArrowGroup(),
165 ]
166 }
167}
168
169Number.propTypes = {
170 disabled: PropTypes.bool,
171 min: PropTypes.number,
172 max: PropTypes.number,
173 onMouseDown: PropTypes.func,
174 onMouseUp: PropTypes.func,
175 onBlur: PropTypes.func.isRequired,
176 onChange: PropTypes.func.isRequired,
177 step: PropTypes.number,
178 digits: PropTypes.number,
179 allowNull: PropTypes.bool,
180 hideArrow: PropTypes.bool,
181 value: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
182}
183
184Number.defaultProps = {
185 step: 1,
186 allowNull: false,
187 hideArrow: false,
188}
189
190export default Number