-
Notifications
You must be signed in to change notification settings - Fork 290
/
Copy pathuse-input-number.ts
214 lines (181 loc) · 6.4 KB
/
use-input-number.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import { computed, reactive, toRefs, watch, ref, inject } from 'vue';
import type { SetupContext, Ref, CSSProperties } from 'vue';
import { InputNumberProps, UseEvent, UseRender, IState, UseExpose } from './input-number-types';
import { isNumber, isUndefined, useNamespace } from '@devui/shared/utils';
import { FORM_ITEM_TOKEN, FORM_TOKEN, FormItemContext } from '@devui/shared/components/form';
const ns = useNamespace('input-number');
export function useRender(props: InputNumberProps, ctx: SetupContext): UseRender {
const formContext = inject(FORM_TOKEN, undefined);
const { style, class: customClass, ...otherAttrs } = ctx.attrs;
const customStyle = { style: style as CSSProperties };
const formItemContext = inject(FORM_ITEM_TOKEN, undefined) as FormItemContext;
const isValidateError = computed(() => formItemContext?.validateState === 'error');
const inputNumberSize = computed(() => props.size || formContext?.size || 'md');
const wrapClass = computed(() => [
{
[ns.b()]: true,
},
customClass,
]);
const controlButtonsClass = computed(() => ({
[ns.e('control-buttons')]: true,
[ns.em('control-buttons', 'error')]: isValidateError.value,
disabled: props.disabled,
[ns.m(inputNumberSize.value)]: true,
}));
const inputWrapClass = computed(() => ({
[ns.e('input-wrap')]: true,
[ns.m(inputNumberSize.value)]: true,
}));
const inputInnerClass = computed(() => ({
[ns.e('input-box')]: true,
[ns.em('input-box', 'error')]: isValidateError.value,
disabled: props.disabled,
}));
return { wrapClass, customStyle, otherAttrs, controlButtonsClass, inputWrapClass, inputInnerClass };
}
export function useExpose(ctx: SetupContext): UseExpose {
const inputRef = ref();
const focus = () => {
inputRef.value.focus();
};
const blur = () => {
inputRef.value.blur();
};
const select = () => {
inputRef.value.select();
};
ctx.expose({ focus, blur, select });
return { inputRef };
}
function getPrecision(pre: number | undefined): number {
let precision = 0;
if (isUndefined(pre)) {
return precision;
}
const preString = pre.toString();
const dotIndex = preString.indexOf('.');
if (dotIndex !== -1) {
precision = preString.length - dotIndex - 1;
}
return precision;
}
export function useEvent(props: InputNumberProps, ctx: SetupContext, inputRef: Ref): UseEvent {
const { min, max, step, disabled } = toRefs(props);
const state = reactive<IState>({
currentValue: props.modelValue || '',
userInputValue: undefined,
});
const numPrecision = computed(() => {
if (!isUndefined(props.precision)) {
return props.precision;
} else {
return Math.max(getPrecision(props.modelValue), getPrecision(step.value));
}
});
const inputVal = computed(() => {
if (!isUndefined(state.userInputValue)) {
return props.formatter ? props.formatter(state.userInputValue ?? 0) : state.userInputValue;
}
let currentValue = state.currentValue;
if (currentValue === '' || isUndefined(currentValue) || Number.isNaN(currentValue)) {
return '';
}
if (isNumber(currentValue)) {
// todo 小数精度 确认是否应该以正则处理
currentValue = currentValue.toFixed(numPrecision.value);
}
return props.formatter ? props.formatter(currentValue ?? 0) : currentValue;
});
const toPrecision = (num: number) => {
return Number.parseFloat(num.toFixed(numPrecision.value));
};
const computeByStep = (val: number | string, addOrNot: 1 | -1 = 1) => {
if (!isNumber(val)) {
return state.currentValue;
}
return toPrecision(val + step.value * addOrNot);
};
const correctValue = (value: number | string | undefined | null) => {
// 校验正则
const valueStr = value + '';
if (props.reg && !valueStr.match(new RegExp(props.reg))) {
return undefined;
}
let newVal = Number(value);
// 不是0 是假值或者是NaN返回undefined
if (newVal !== 0 && (!Number(value) || Number.isNaN(newVal))) {
return undefined;
}
// 精度限制存在才做转换
if (!isUndefined(props.precision)) {
newVal = toPrecision(newVal);
}
// 判断数据是否大于或小于限定数值
if (newVal > max.value || newVal < min.value) {
newVal = newVal > max.value ? max.value : min.value;
}
return newVal;
};
const setCurrentValue = (value: number | string | undefined) => {
const oldVal = state.currentValue;
const newVal = correctValue(value);
state.userInputValue = undefined;
// 0 可以被更新
if (newVal !== 0 && !newVal) {
ctx.emit('update:modelValue', oldVal);
return;
}
// 新旧数据一致不做更新
if (oldVal === newVal) {
return;
}
ctx.emit('update:modelValue', newVal);
ctx.emit('input', newVal);
ctx.emit('change', newVal, oldVal);
state.currentValue = newVal;
};
const minDisabled = computed(() => isNumber(state.currentValue) && (computeByStep(state.currentValue, -1) as number) < props.min);
const maxDisabled = computed(() => isNumber(state.currentValue) && (computeByStep(state.currentValue) as number) > props.max);
const onAdd = () => {
if (disabled.value || maxDisabled.value) {
return;
}
inputRef.value.focus();
const newVal = computeByStep(state.currentValue || 0);
setCurrentValue(newVal);
};
const onSubtract = () => {
if (disabled.value || minDisabled.value) {
return;
}
inputRef.value.focus();
const newVal = computeByStep(state.currentValue || 0, -1);
setCurrentValue(newVal);
};
watch(
() => props.modelValue,
(val) => {
state.currentValue = correctValue(val);
},
{ immediate: true }
);
const onInput = (event: Event) => {
const value = (event.target as HTMLInputElement).value;
if (value[0] === '-') {
state.userInputValue = '-' + value.substring(1).replace(/[^0-9.]/g, '');
} else {
state.userInputValue = value.replace(/[^0-9.]/g, '');
}
inputRef.value.value = props.formatter ? props.formatter(state.userInputValue) : state.userInputValue;
};
const onChange = () => {
const value = state.userInputValue;
const newVal = value !== '' ? Number(value) : '';
if ((isNumber(newVal) && !Number.isNaN(newVal)) || value === '') {
setCurrentValue(newVal);
}
state.userInputValue = undefined;
};
return { inputVal, minDisabled, maxDisabled, onAdd, onSubtract, onInput, onChange };
}