Skip to content

Commit 938130e

Browse files
committed
feat: add speed on long press, fix issues with min max values improving UX
Refs: #20 #21
1 parent fd53d37 commit 938130e

File tree

2 files changed

+112
-54
lines changed

2 files changed

+112
-54
lines changed

index.js

Lines changed: 112 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import React, {Component} from "react";
22
import {Text, TextInput, TouchableHighlight, View} from "react-native";
33
import PropTypes from "prop-types";
44
import {Style} from "./style";
5-
import {debounce, isNumeric, isEmpty, isStringEmpty} from "./utils";
5+
import {debounce, isNumeric, isEmpty} from "./utils";
66

77
/**
88
* Default constants
99
*/
1010
const defaultColor = "#3E525F";
11-
const defaultLongPressStartTimeout = 1000;
12-
const defaultLongPressTimeout = 200;
11+
const defaultLongPressDelay = 750;
12+
const defaultLongPressSpeed = 7;
13+
const defaultDebounceTime = 500;
1314

1415
/**
1516
* Input Spinner
@@ -42,6 +43,10 @@ class InputSpinner extends Component {
4243
initialValue = this.parseNum(initialValue);
4344
initialValue = this.withinRange(initialValue, min, max);
4445

46+
// Set debounce
47+
this._debounceSetMax = debounce(this._setStateMax.bind(this), this.props.debounceTime);
48+
this._debounceSetMin = debounce(this._setStateMin.bind(this), this.props.debounceTime);
49+
4550
this.state = {
4651
min: min,
4752
max: max,
@@ -86,11 +91,34 @@ class InputSpinner extends Component {
8691
}
8792
}
8893

94+
/**
95+
* Set state to min
96+
* @param callback
97+
* @private
98+
*/
99+
_setStateMin(callback = null) {
100+
return this.setState({value: this.state.max}, callback);
101+
}
102+
103+
/**
104+
* Set state to max
105+
* @param callback
106+
* @private
107+
*/
108+
_setStateMax(callback = null) {
109+
return this.setState({value: this.state.max}, callback);
110+
}
111+
89112
/**
90113
* On value change
91-
* @param num
114+
* @param value
92115
*/
93-
async onChange(num) {
116+
async onChange(value) {
117+
let num = value;
118+
let parsedNum = value;
119+
if (isEmpty(value)) {
120+
num = this.state.min;
121+
}
94122
if (this.props.disabled) return;
95123
const current_value = this.state.value;
96124
const separator = !isEmpty(this.props.decimalSeparator)
@@ -102,29 +130,41 @@ class InputSpinner extends Component {
102130
) {
103131
this.decimalInput = true;
104132
}
105-
num = this.parseNum(String(num).replace(/^0+/, "")) || 0;
133+
num = parsedNum = this.parseNum(String(num).replace(/^0+/, "")) || 0;
106134
if (!this.minReached(num)) {
107135
if (this.maxReached(num)) {
108-
num = this.state.max;
136+
parsedNum = this.state.max;
137+
this.maxTimer = this._debounceSetMax();
109138
if (this.props.onMax) {
110139
this.props.onMax(this.state.max);
111140
}
112141
}
113142
} else {
143+
parsedNum = this.state.min;
144+
this.minTimer = this._debounceSetMin();
114145
if (this.props.onMin) {
115146
this.props.onMin(this.state.min);
116147
}
117-
num = this.state.min;
118148
}
119149
if (current_value !== num && this.props.onChange) {
120-
const res = await this.props.onChange(num);
121-
if (res === false) {
122-
return;
123-
} else if (isNumeric(res)) {
124-
num = this.parseNum(res);
150+
const res = await this.props.onChange(parsedNum);
151+
if (!isEmpty(value)) {
152+
if (res === false) {
153+
return;
154+
} else if (isNumeric(res)) {
155+
num = this.parseNum(res);
156+
}
125157
}
126158
}
127-
this.setState({value: num});
159+
if (!isEmpty(value)) {
160+
if(parsedNum === num) {
161+
clearTimeout(this.minTimer);
162+
clearTimeout(this.maxTimer);
163+
}
164+
this.setState({value: num});
165+
} else {
166+
this.setState({value: value});
167+
}
128168
}
129169

130170
/**
@@ -212,6 +252,9 @@ class InputSpinner extends Component {
212252
*/
213253
getValue() {
214254
let value = this.state.value;
255+
if (isEmpty(value)) {
256+
return "";
257+
}
215258
if (this.typeDecimal() && this.decimalInput) {
216259
this.decimalInput = false;
217260
value = this.parseNum(value).toFixed(1).replace(/0+$/, "");
@@ -226,11 +269,23 @@ class InputSpinner extends Component {
226269
return hasPlaceholder
227270
? ""
228271
: value.replace(
229-
".",
230-
!isEmpty(this.props.decimalSeparator)
231-
? this.props.decimalSeparator
232-
: ".",
233-
);
272+
".",
273+
!isEmpty(this.props.decimalSeparator)
274+
? this.props.decimalSeparator
275+
: ".",
276+
);
277+
}
278+
279+
/**
280+
* Get Placeholder
281+
* @returns {*}
282+
*/
283+
getPlaceholder() {
284+
if(isEmpty(this.props.placeholder)) {
285+
return this.state.min;
286+
} else {
287+
return this.state.placeholder;
288+
}
234289
}
235290

236291
/**
@@ -262,23 +317,33 @@ class InputSpinner extends Component {
262317
clearTimers() {
263318
if (this.increaseTimer) {
264319
clearTimeout(this.increaseTimer);
320+
this.increaseTimer = null;
265321
}
266322
if (this.decreaseTimer) {
267323
clearTimeout(this.decreaseTimer);
324+
this.decreaseTimer = null;
268325
}
269326
}
270327

328+
/**
329+
* Get time to wait before increase/decrease on long press
330+
* @returns {number}
331+
*/
332+
getLongPressWaitingTime() {
333+
return 1000 / this.withinRange(this.props.onLongPressSpeed, 1, 10);
334+
}
335+
271336
/**
272337
* Increase
273338
*/
274339
async increase() {
275340
if (this._isDisabledButtonRight()) return;
276341
let num = this.parseNum(this.state.value) + this.parseNum(this.state.step);
342+
if (this.maxReached(num)) {
343+
return;
344+
}
277345
if (this.props.onIncrease) {
278346
let increased_num = num;
279-
if (this.maxReached(num)) {
280-
increased_num = this.state.max;
281-
}
282347
const res = await this.props.onIncrease(increased_num);
283348
if (res === false) {
284349
return;
@@ -287,12 +352,12 @@ class InputSpinner extends Component {
287352
}
288353
}
289354

290-
let wait = this.props.onLongPressTimeout;
355+
let wait = this.getLongPressWaitingTime();
291356
if (this.increaseTimer === null) {
292-
wait = this.props.onLongPressStartTimeout;
357+
wait = this.props.onLongPressDelay;
293358
}
294359

295-
this.increaseTimer = setTimeout(this.increase, wait);
360+
this.increaseTimer = setTimeout(this.increase.bind(this), wait);
296361
this.onChange(num);
297362
}
298363

@@ -302,11 +367,11 @@ class InputSpinner extends Component {
302367
async decrease() {
303368
if (this._isDisabledButtonLeft()) return;
304369
let num = this.parseNum(this.state.value) - this.parseNum(this.state.step);
370+
if (this.minReached(num)) {
371+
return;
372+
}
305373
if (this.props.onDecrease) {
306374
let decreased_num = num;
307-
if (this.minReached(num)) {
308-
decreased_num = this.state.min;
309-
}
310375
const res = await this.props.onDecrease(decreased_num);
311376
if (res === false) {
312377
return;
@@ -315,12 +380,12 @@ class InputSpinner extends Component {
315380
}
316381
}
317382

318-
let wait = this.props.onLongPressTimeout;
319-
if (this.increaseTimer === null) {
320-
wait = this.props.onLongPressStartTimeout;
383+
let wait = this.getLongPressWaitingTime();
384+
if (this.decreaseTimer === null) {
385+
wait = this.props.onLongPressDelay;
321386
}
322387

323-
this.decreaseTimer = setTimeout(this.decrease, wait);
388+
this.decreaseTimer = setTimeout(this.decrease.bind(this), wait);
324389
this.onChange(num);
325390
}
326391

@@ -498,8 +563,8 @@ class InputSpinner extends Component {
498563
return this.maxReached()
499564
? this._getColorMax()
500565
: this.minReached()
501-
? this._getColorMin()
502-
: this.props.color;
566+
? this._getColorMin()
567+
: this.props.color;
503568
}
504569

505570
/**
@@ -539,8 +604,8 @@ class InputSpinner extends Component {
539604
return this.maxReached()
540605
? this._getColorMax()
541606
: this.minReached()
542-
? this._getColorMin()
543-
: color;
607+
? this._getColorMin()
608+
: color;
544609
}
545610

546611
/**
@@ -760,8 +825,8 @@ class InputSpinner extends Component {
760825
onShowUnderlay={this.onShowUnderlay.bind(this, "left")}
761826
disabled={this._isDisabledButtonLeft()}
762827
style={buttonStyle}
763-
onPressIn={this.decrease}
764-
onPressOut={this.clearTimers}
828+
onPressIn={this.decrease.bind(this)}
829+
onPressOut={this.clearTimers.bind(this)}
765830
{...this.props.leftButtonProps}>
766831
{this._renderLeftButtonElement()}
767832
</TouchableHighlight>
@@ -796,8 +861,8 @@ class InputSpinner extends Component {
796861
onShowUnderlay={this.onShowUnderlay.bind(this, "right")}
797862
disabled={this._isDisabledButtonRight()}
798863
style={buttonStyle}
799-
onPressIn={this.increase}
800-
onPressOut={this.clearTimers}
864+
onPressIn={this.increase.bind(this)}
865+
onPressOut={this.clearTimers.bind(this)}
801866
{...this.props.rightButtonProps}>
802867
{this._renderRightButtonElement()}
803868
</TouchableHighlight>
@@ -819,7 +884,7 @@ class InputSpinner extends Component {
819884
ref={(input) => (this.textInput = input)}
820885
style={this._getInputTextStyle()}
821886
value={this.getValue()}
822-
placeholder={this.props.placeholder}
887+
placeholder={this.getPlaceholder()}
823888
placeholderTextColor={this.props.placeholderTextColor}
824889
selectionColor={this.props.selectionColor}
825890
selectTextOnFocus={this.props.selectTextOnFocus}
@@ -891,8 +956,9 @@ InputSpinner.propTypes = {
891956
onIncrease: PropTypes.func,
892957
onDecrease: PropTypes.func,
893958
onSubmit: PropTypes.func,
894-
onLongPressStartTimeout: PropTypes.number,
895-
onLongPressTimeout: PropTypes.number,
959+
onLongPressDelay: PropTypes.number,
960+
onLongPressSpeed: PropTypes.number,
961+
debounceTime: PropTypes.number,
896962
buttonLeftDisabled: PropTypes.bool,
897963
buttonRightDisabled: PropTypes.bool,
898964
buttonLeftText: PropTypes.string,
@@ -948,8 +1014,9 @@ InputSpinner.defaultProps = {
9481014
returnKeyType: null,
9491015
width: 150,
9501016
height: 50,
951-
onLongPressStartTimeout: defaultLongPressStartTimeout,
952-
onLongPressTimeout: defaultLongPressTimeout,
1017+
onLongPressDelay: defaultLongPressDelay,
1018+
onLongPressSpeed: defaultLongPressSpeed,
1019+
debounceTime: defaultDebounceTime,
9531020
buttonLeftDisabled: false,
9541021
buttonRightDisabled: false,
9551022
buttonLeftText: null,

utils.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,3 @@
1-
/**
2-
* Is string empty
3-
* @param str
4-
* @returns {boolean|boolean}
5-
*/
6-
export const isStringEmpty = (str) => {
7-
return String(str) === "";
8-
};
9-
101
/**
112
* Is empty
123
* @param x

0 commit comments

Comments
 (0)