1 | import React, { PureComponent } from 'react'
|
2 | import PropTypes from 'prop-types'
|
3 | import icons from '../icons'
|
4 | import Input from './Input'
|
5 | import { inputClass } from '../styles'
|
6 |
|
7 | class 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 |
|
59 | if (e.target.value === '' && this.props.allowNull) {
|
60 | value = null
|
61 | }
|
62 |
|
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 |
|
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 |
|
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 |
|
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 |
|
169 | Number.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 |
|
184 | Number.defaultProps = {
|
185 | step: 1,
|
186 | allowNull: false,
|
187 | hideArrow: false,
|
188 | }
|
189 |
|
190 | export default Number
|