import React, {
ForwardRefRenderFunction,
forwardRef,
useCallback,
useImperativeHandle,
useState,
useEffect,
useRef,
} from 'react';
import {
GestureResponderEvent,
NativeSyntheticEvent,
StyleSheet,
TextInput,
TextInputFocusEventData,
TouchableOpacity,
View,
} from 'react-native';
import { IconXAdd1, IconXReduce1 } from '../../icons';
import type { StepperProps, StepperRef } from './interface';
import { isNil, isString, isUndefined, toString } from 'lodash';
import { formatNumber } from '../Input/utils';
import { createStyle, buttonStyle } from './style/stepper.style';
import Big from 'big.js';
import { useTheme } from '../Theme';
const StepperButton: React.FC<{
position: 'suffix' | 'prefix';
children: React.ReactNode;
style?: object;
disabled?: boolean;
onPress: (e: GestureResponderEvent) => void;
}> = ({ children, disabled, position, style, onPress }) => {
return (
{children}
);
};
const parserFn = (value: string, decimalSeparator: string) => {
const withDotDecimal = value.replace(decimalSeparator, '.');
return Number(withDotDecimal);
};
const Stepper: ForwardRefRenderFunction = (
props,
ref
) => {
const {
style,
value,
defaultValue,
disableMinus,
disablePlus,
inputReadOnly,
step = 1,
min = 0,
max = Number.MAX_VALUE,
digits,
allowEmpty,
integer,
selectionColor = '#ff7a00',
inputWidth = 100,
placeholder,
prefixIcon,
suffixIcon,
immediate,
decimalSeparator = '.',
parser = parserFn,
onChange,
beforeChange,
onBlur,
onFocus,
onMinus,
onPlus,
size = 'default',
disabled = false,
status,
...resetProps
} = props;
const token = useTheme();
const styles = createStyle(inputWidth, token, size);
const format = useCallback(
(data?: string | number) => {
if (allowEmpty) {
if (data === '' || isUndefined(data)) {
return data;
}
}
data = formatNumber(String(data), !integer, true, decimalSeparator);
data = parser(data, decimalSeparator);
data = data === '' ? 0 : +data;
data = isNaN(data) ? min : data;
data = Math.max(Math.min(max, data), min);
if (!isNil(digits)) {
data = data.toFixed(+digits);
}
return data;
},
[min, max, allowEmpty, digits, integer, decimalSeparator]
);
const [current, setCurrent] = useState(
format(value ?? defaultValue)
);
const [currentCache, setCurrentCache] = useState();
const textInputRef = useRef(null);
const [isFocus, setIsFocus] = useState(false);
const doChange = useCallback(
(data?: number | string, entering?: boolean) => {
if (!entering || immediate) {
onChange?.(isString(data) ? Number(data) : data);
}
setCurrent(() => data);
},
[immediate]
);
const innerChange = useCallback(
(data?: number | string, entering?: boolean) => {
const allowChange = beforeChange?.(data) ?? true;
if (allowChange && allowChange instanceof Promise) {
const originVal = current;
setCurrent(() => data); //先设置 避免鸿蒙抖动
allowChange
?.then?.((res) => {
if (res) {
doChange(data, entering); // 正式通知
} else {
setCurrent(() => originVal); // 否则还原
}
})
.catch(() => {});
} else {
if (allowChange) doChange(data, entering);
}
},
[immediate, onChange, beforeChange]
);
const getTruthfulCurrent = useCallback(() => {
return (
(isString(current) ? parser(current, decimalSeparator) : current) || 0
);
}, [current, parser, decimalSeparator]);
const getVirtualCurrent = useCallback(
(data: string) => {
return data.replace('.', decimalSeparator);
},
[current, parser, decimalSeparator]
);
const onSuffixPress = useCallback(
(e: GestureResponderEvent) => {
const data = format(
getVirtualCurrent(Big(getTruthfulCurrent()).add(step).toString())
);
innerChange(data);
onPlus?.(e, data);
},
[
current,
step,
innerChange,
format,
onPlus,
getTruthfulCurrent,
getVirtualCurrent,
]
);
const onPrefixPress = useCallback(
(e: GestureResponderEvent) => {
const data = format(
getVirtualCurrent(Big(getTruthfulCurrent()).minus(step).toString())
);
innerChange(data);
onMinus?.(e, data);
},
[
current,
step,
innerChange,
format,
onMinus,
getTruthfulCurrent,
getVirtualCurrent,
]
);
const handleInput = useCallback(
(text: string) => {
if (text === '') {
innerChange(undefined, true);
} else {
innerChange(text, true);
}
},
[innerChange, format]
);
const handleFocus = useCallback(
(e: NativeSyntheticEvent) => {
setCurrentCache(toString(current));
!(inputReadOnly || disabled) && setIsFocus(true);
onFocus?.(e);
},
[current, onFocus]
);
const handleBlur = useCallback(
(e: NativeSyntheticEvent) => {
if (currentCache !== toString(current)) {
const data = format(current);
innerChange(data);
}
setIsFocus(false);
onBlur?.(e);
},
[current, currentCache, format, innerChange, onBlur]
);
useEffect(() => {
const data = value ?? defaultValue;
setCurrent(() =>
getVirtualCurrent(String(format(getVirtualCurrent(String(data || '')))))
);
}, [value]);
useImperativeHandle(ref, () => {
return {
focus: () => {
textInputRef.current?.focus();
},
blur: () => {
textInputRef.current?.blur();
},
};
});
return (
{prefixIcon ?? (
)}
{suffixIcon ?? (
)}
);
};
export default forwardRef(Stepper);