2021-12-19 15:15:27 +00:00
|
|
|
// base rc-input-number@7.3.4
|
|
|
|
import type { DecimalClass, ValueType } from './utils/MiniDecimal';
|
2021-12-27 13:59:52 +00:00
|
|
|
import getMiniDecimal, { toFixed } from './utils/MiniDecimal';
|
2021-12-19 15:15:27 +00:00
|
|
|
import StepHandler from './StepHandler';
|
2021-12-27 13:59:52 +00:00
|
|
|
import { getNumberPrecision, num2str, validateNumber } from './utils/numberUtil';
|
2021-12-19 15:15:27 +00:00
|
|
|
import useCursor from './hooks/useCursor';
|
|
|
|
import useFrame from './hooks/useFrame';
|
|
|
|
import type { HTMLAttributes, PropType } from 'vue';
|
|
|
|
import { watch, computed, ref, defineComponent } from 'vue';
|
|
|
|
import type { ChangeEvent, KeyboardEventHandler } from '../../_util/EventInterface';
|
|
|
|
import KeyCode from '../../_util/KeyCode';
|
|
|
|
import classNames from '../../_util/classNames';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* We support `stringMode` which need handle correct type when user call in onChange
|
|
|
|
* format max or min value
|
|
|
|
* 1. if isInvalid return null
|
|
|
|
* 2. if precision is undefined, return decimal
|
|
|
|
* 3. format with precision
|
|
|
|
* I. if max > 0, round down with precision. Example: max= 3.5, precision=0 afterFormat: 3
|
|
|
|
* II. if max < 0, round up with precision. Example: max= -3.5, precision=0 afterFormat: -4
|
|
|
|
* III. if min > 0, round up with precision. Example: min= 3.5, precision=0 afterFormat: 4
|
|
|
|
* IV. if min < 0, round down with precision. Example: max= -3.5, precision=0 afterFormat: -3
|
|
|
|
*/
|
|
|
|
const getDecimalValue = (stringMode: boolean, decimalValue: DecimalClass) => {
|
|
|
|
if (stringMode || decimalValue.isEmpty()) {
|
|
|
|
return decimalValue.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
return decimalValue.toNumber();
|
|
|
|
};
|
2021-12-27 13:59:52 +00:00
|
|
|
const getDecimalIfValidate = (value: ValueType) => {
|
2021-12-19 15:15:27 +00:00
|
|
|
const decimal = getMiniDecimal(value);
|
2021-12-27 13:59:52 +00:00
|
|
|
return decimal.isInvalidate() ? null : decimal;
|
2021-12-19 15:15:27 +00:00
|
|
|
};
|
|
|
|
|
2022-03-26 14:52:54 +00:00
|
|
|
export const inputNumberProps = () => ({
|
2021-12-19 15:15:27 +00:00
|
|
|
/** value will show as string */
|
|
|
|
stringMode: { type: Boolean as PropType<boolean> },
|
|
|
|
|
|
|
|
defaultValue: { type: [String, Number] as PropType<ValueType> },
|
|
|
|
value: { type: [String, Number] as PropType<ValueType> },
|
|
|
|
|
|
|
|
prefixCls: { type: String as PropType<string> },
|
|
|
|
min: { type: [String, Number] as PropType<ValueType> },
|
|
|
|
max: { type: [String, Number] as PropType<ValueType> },
|
|
|
|
step: { type: [String, Number] as PropType<ValueType>, default: 1 },
|
|
|
|
tabindex: { type: Number as PropType<number> },
|
|
|
|
controls: { type: Boolean as PropType<boolean>, default: true },
|
|
|
|
readonly: { type: Boolean as PropType<boolean> },
|
|
|
|
disabled: { type: Boolean as PropType<boolean> },
|
|
|
|
autofocus: { type: Boolean as PropType<boolean> },
|
|
|
|
keyboard: { type: Boolean as PropType<boolean>, default: true },
|
|
|
|
|
|
|
|
/** Parse display value to validate number */
|
|
|
|
parser: { type: Function as PropType<(displayValue: string | undefined) => ValueType> },
|
|
|
|
/** Transform `value` to display value show in input */
|
|
|
|
formatter: {
|
|
|
|
type: Function as PropType<
|
|
|
|
(value: ValueType | undefined, info: { userTyping: boolean; input: string }) => string
|
|
|
|
>,
|
|
|
|
},
|
|
|
|
/** Syntactic sugar of `formatter`. Config precision of display. */
|
|
|
|
precision: { type: Number as PropType<number> },
|
|
|
|
/** Syntactic sugar of `formatter`. Config decimal separator of display. */
|
|
|
|
decimalSeparator: { type: String as PropType<string> },
|
|
|
|
|
|
|
|
onInput: { type: Function as PropType<(text: string) => void> },
|
|
|
|
onChange: { type: Function as PropType<(value: ValueType) => void> },
|
|
|
|
onPressEnter: { type: Function as PropType<KeyboardEventHandler> },
|
|
|
|
|
|
|
|
onStep: {
|
|
|
|
type: Function as PropType<
|
|
|
|
(value: ValueType, info: { offset: ValueType; type: 'up' | 'down' }) => void
|
|
|
|
>,
|
|
|
|
},
|
2022-01-03 14:23:24 +00:00
|
|
|
onBlur: { type: Function as PropType<(e: InputEvent) => void> },
|
|
|
|
onFocus: { type: Function as PropType<(e: InputEvent) => void> },
|
2022-03-26 14:52:54 +00:00
|
|
|
});
|
2021-12-19 15:15:27 +00:00
|
|
|
|
|
|
|
export default defineComponent({
|
|
|
|
name: 'InnerInputNumber',
|
|
|
|
inheritAttrs: false,
|
2022-03-26 14:52:54 +00:00
|
|
|
props: inputNumberProps(),
|
2021-12-19 15:15:27 +00:00
|
|
|
slots: ['upHandler', 'downHandler'],
|
|
|
|
setup(props, { attrs, slots, emit, expose }) {
|
|
|
|
const inputRef = ref<HTMLInputElement>();
|
|
|
|
const focus = ref(false);
|
|
|
|
const userTypingRef = ref(false);
|
|
|
|
const compositionRef = ref(false);
|
|
|
|
const decimalValue = ref(getMiniDecimal(props.value));
|
|
|
|
|
|
|
|
function setUncontrolledDecimalValue(newDecimal: DecimalClass) {
|
|
|
|
if (props.value === undefined) {
|
|
|
|
decimalValue.value = newDecimal;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ====================== Parser & Formatter ======================
|
|
|
|
/**
|
|
|
|
* `precision` is used for formatter & onChange.
|
|
|
|
* It will auto generate by `value` & `step`.
|
|
|
|
* But it will not block user typing.
|
|
|
|
*
|
|
|
|
* Note: Auto generate `precision` is used for legacy logic.
|
|
|
|
* We should remove this since we already support high precision with BigInt.
|
|
|
|
*
|
|
|
|
* @param number Provide which number should calculate precision
|
|
|
|
* @param userTyping Change by user typing
|
|
|
|
*/
|
|
|
|
const getPrecision = (numStr: string, userTyping: boolean) => {
|
|
|
|
if (userTyping) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (props.precision >= 0) {
|
|
|
|
return props.precision;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Math.max(getNumberPrecision(numStr), getNumberPrecision(props.step));
|
|
|
|
};
|
|
|
|
|
|
|
|
// >>> Parser
|
|
|
|
const mergedParser = (num: string | number) => {
|
|
|
|
const numStr = String(num);
|
|
|
|
|
|
|
|
if (props.parser) {
|
|
|
|
return props.parser(numStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
let parsedStr = numStr;
|
|
|
|
if (props.decimalSeparator) {
|
|
|
|
parsedStr = parsedStr.replace(props.decimalSeparator, '.');
|
|
|
|
}
|
|
|
|
|
|
|
|
// [Legacy] We still support auto convert `$ 123,456` to `123456`
|
|
|
|
return parsedStr.replace(/[^\w.-]+/g, '');
|
|
|
|
};
|
|
|
|
|
|
|
|
// >>> Formatter
|
|
|
|
const inputValue = ref<string | number>('');
|
|
|
|
|
|
|
|
const mergedFormatter = (number: string, userTyping: boolean) => {
|
|
|
|
if (props.formatter) {
|
|
|
|
return props.formatter(number, { userTyping, input: String(inputValue.value) });
|
|
|
|
}
|
|
|
|
|
|
|
|
let str = typeof number === 'number' ? num2str(number) : number;
|
|
|
|
|
|
|
|
// User typing will not auto format with precision directly
|
|
|
|
if (!userTyping) {
|
|
|
|
const mergedPrecision = getPrecision(str, userTyping);
|
|
|
|
|
|
|
|
if (validateNumber(str) && (props.decimalSeparator || mergedPrecision >= 0)) {
|
|
|
|
// Separator
|
|
|
|
const separatorStr = props.decimalSeparator || '.';
|
|
|
|
|
|
|
|
str = toFixed(str, separatorStr, mergedPrecision);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return str;
|
|
|
|
};
|
|
|
|
// ========================== InputValue ==========================
|
|
|
|
/**
|
|
|
|
* Input text value control
|
|
|
|
*
|
|
|
|
* User can not update input content directly. It update with follow rules by priority:
|
|
|
|
* 1. controlled `value` changed
|
|
|
|
* * [SPECIAL] Typing like `1.` should not immediately convert to `1`
|
|
|
|
* 2. User typing with format (not precision)
|
|
|
|
* 3. Blur or Enter trigger revalidate
|
|
|
|
*/
|
|
|
|
const initValue = (() => {
|
|
|
|
const initValue = props.value;
|
|
|
|
if (decimalValue.value.isInvalidate() && ['string', 'number'].includes(typeof initValue)) {
|
|
|
|
return Number.isNaN(initValue) ? '' : initValue;
|
|
|
|
}
|
|
|
|
return mergedFormatter(decimalValue.value.toString(), false);
|
|
|
|
})();
|
|
|
|
inputValue.value = initValue;
|
|
|
|
|
|
|
|
// Should always be string
|
|
|
|
function setInputValue(newValue: DecimalClass, userTyping: boolean) {
|
|
|
|
inputValue.value = mergedFormatter(
|
|
|
|
// Invalidate number is sometime passed by external control, we should let it go
|
|
|
|
// Otherwise is controlled by internal interactive logic which check by userTyping
|
|
|
|
// You can ref 'show limited value when input is not focused' test for more info.
|
|
|
|
newValue.isInvalidate() ? newValue.toString(false) : newValue.toString(!userTyping),
|
|
|
|
userTyping,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// >>> Max & Min limit
|
2021-12-27 13:59:52 +00:00
|
|
|
const maxDecimal = computed(() => getDecimalIfValidate(props.max));
|
|
|
|
const minDecimal = computed(() => getDecimalIfValidate(props.min));
|
2021-12-19 15:15:27 +00:00
|
|
|
|
|
|
|
const upDisabled = computed(() => {
|
|
|
|
if (!maxDecimal.value || !decimalValue.value || decimalValue.value.isInvalidate()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return maxDecimal.value.lessEquals(decimalValue.value);
|
|
|
|
});
|
|
|
|
|
|
|
|
const downDisabled = computed(() => {
|
|
|
|
if (!minDecimal.value || !decimalValue.value || decimalValue.value.isInvalidate()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return decimalValue.value.lessEquals(minDecimal.value);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Cursor controller
|
|
|
|
const [recordCursor, restoreCursor] = useCursor(inputRef, focus);
|
|
|
|
|
|
|
|
// ============================= Data =============================
|
|
|
|
/**
|
|
|
|
* Find target value closet within range.
|
|
|
|
* e.g. [11, 28]:
|
|
|
|
* 3 => 11
|
|
|
|
* 23 => 23
|
|
|
|
* 99 => 28
|
|
|
|
*/
|
|
|
|
const getRangeValue = (target: DecimalClass) => {
|
|
|
|
// target > max
|
|
|
|
if (maxDecimal.value && !target.lessEquals(maxDecimal.value)) {
|
|
|
|
return maxDecimal.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
// target < min
|
|
|
|
if (minDecimal.value && !minDecimal.value.lessEquals(target)) {
|
|
|
|
return minDecimal.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check value is in [min, max] range
|
|
|
|
*/
|
|
|
|
const isInRange = (target: DecimalClass) => !getRangeValue(target);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Trigger `onChange` if value validated and not equals of origin.
|
|
|
|
* Return the value that re-align in range.
|
|
|
|
*/
|
|
|
|
const triggerValueUpdate = (newValue: DecimalClass, userTyping: boolean): DecimalClass => {
|
|
|
|
let updateValue = newValue;
|
|
|
|
|
|
|
|
let isRangeValidate = isInRange(updateValue) || updateValue.isEmpty();
|
|
|
|
|
|
|
|
// Skip align value when trigger value is empty.
|
|
|
|
// We just trigger onChange(null)
|
|
|
|
// This should not block user typing
|
|
|
|
if (!updateValue.isEmpty() && !userTyping) {
|
|
|
|
// Revert value in range if needed
|
|
|
|
updateValue = getRangeValue(updateValue) || updateValue;
|
|
|
|
isRangeValidate = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!props.readonly && !props.disabled && isRangeValidate) {
|
|
|
|
const numStr = updateValue.toString();
|
|
|
|
const mergedPrecision = getPrecision(numStr, userTyping);
|
|
|
|
if (mergedPrecision >= 0) {
|
|
|
|
updateValue = getMiniDecimal(toFixed(numStr, '.', mergedPrecision));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trigger event
|
|
|
|
if (!updateValue.equals(decimalValue.value)) {
|
|
|
|
setUncontrolledDecimalValue(updateValue);
|
|
|
|
props.onChange?.(
|
|
|
|
updateValue.isEmpty() ? null : getDecimalValue(props.stringMode, updateValue),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Reformat input if value is not controlled
|
|
|
|
if (props.value === undefined) {
|
|
|
|
setInputValue(updateValue, userTyping);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return updateValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
return decimalValue.value;
|
|
|
|
};
|
|
|
|
|
|
|
|
// ========================== User Input ==========================
|
|
|
|
const onNextPromise = useFrame();
|
|
|
|
|
|
|
|
// >>> Collect input value
|
|
|
|
const collectInputValue = (inputStr: string) => {
|
|
|
|
recordCursor();
|
|
|
|
|
|
|
|
// Update inputValue incase input can not parse as number
|
|
|
|
inputValue.value = inputStr;
|
|
|
|
// Parse number
|
|
|
|
if (!compositionRef.value) {
|
|
|
|
const finalValue = mergedParser(inputStr);
|
|
|
|
const finalDecimal = getMiniDecimal(finalValue);
|
|
|
|
if (!finalDecimal.isNaN()) {
|
|
|
|
triggerValueUpdate(finalDecimal, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trigger onInput later to let user customize value if they want do handle something after onChange
|
|
|
|
props.onInput?.(inputStr);
|
|
|
|
|
|
|
|
// optimize for chinese input experience
|
|
|
|
// https://github.com/ant-design/ant-design/issues/8196
|
|
|
|
onNextPromise(() => {
|
|
|
|
let nextInputStr = inputStr;
|
|
|
|
if (!props.parser) {
|
|
|
|
nextInputStr = inputStr.replace(/。/g, '.');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nextInputStr !== inputStr) {
|
|
|
|
collectInputValue(nextInputStr);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// >>> Composition
|
|
|
|
const onCompositionStart = () => {
|
|
|
|
compositionRef.value = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const onCompositionEnd = () => {
|
|
|
|
compositionRef.value = false;
|
|
|
|
|
|
|
|
collectInputValue(inputRef.value.value);
|
|
|
|
};
|
|
|
|
|
|
|
|
// >>> Input
|
|
|
|
const onInternalInput = (e: ChangeEvent) => {
|
|
|
|
collectInputValue(e.target.value);
|
|
|
|
};
|
|
|
|
|
|
|
|
// ============================= Step =============================
|
|
|
|
const onInternalStep = (up: boolean) => {
|
|
|
|
// Ignore step since out of range
|
|
|
|
if ((up && upDisabled.value) || (!up && downDisabled.value)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear typing status since it may caused by up & down key.
|
|
|
|
// We should sync with input value.
|
|
|
|
userTypingRef.value = false;
|
|
|
|
|
|
|
|
let stepDecimal = getMiniDecimal(props.step);
|
|
|
|
if (!up) {
|
|
|
|
stepDecimal = stepDecimal.negate();
|
|
|
|
}
|
|
|
|
|
|
|
|
const target = (decimalValue.value || getMiniDecimal(0)).add(stepDecimal.toString());
|
|
|
|
|
|
|
|
const updatedValue = triggerValueUpdate(target, false);
|
|
|
|
|
|
|
|
props.onStep?.(getDecimalValue(props.stringMode, updatedValue), {
|
|
|
|
offset: props.step,
|
|
|
|
type: up ? 'up' : 'down',
|
|
|
|
});
|
|
|
|
|
|
|
|
inputRef.value?.focus();
|
|
|
|
};
|
|
|
|
|
|
|
|
// ============================ Flush =============================
|
|
|
|
/**
|
|
|
|
* Flush current input content to trigger value change & re-formatter input if needed
|
|
|
|
*/
|
|
|
|
const flushInputValue = (userTyping: boolean) => {
|
|
|
|
const parsedValue = getMiniDecimal(mergedParser(inputValue.value));
|
|
|
|
let formatValue: DecimalClass = parsedValue;
|
|
|
|
|
|
|
|
if (!parsedValue.isNaN()) {
|
|
|
|
// Only validate value or empty value can be re-fill to inputValue
|
|
|
|
// Reassign the formatValue within ranged of trigger control
|
|
|
|
formatValue = triggerValueUpdate(parsedValue, userTyping);
|
|
|
|
} else {
|
|
|
|
formatValue = decimalValue.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (props.value !== undefined) {
|
|
|
|
// Reset back with controlled value first
|
|
|
|
setInputValue(decimalValue.value, false);
|
|
|
|
} else if (!formatValue.isNaN()) {
|
|
|
|
// Reset input back since no validate value
|
|
|
|
setInputValue(formatValue, false);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onKeyDown: KeyboardEventHandler = event => {
|
|
|
|
const { which } = event;
|
|
|
|
userTypingRef.value = true;
|
|
|
|
|
|
|
|
if (which === KeyCode.ENTER) {
|
|
|
|
if (!compositionRef.value) {
|
|
|
|
userTypingRef.value = false;
|
|
|
|
}
|
|
|
|
flushInputValue(false);
|
|
|
|
props.onPressEnter?.(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (props.keyboard === false) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do step
|
|
|
|
if (!compositionRef.value && [KeyCode.UP, KeyCode.DOWN].includes(which)) {
|
|
|
|
onInternalStep(KeyCode.UP === which);
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const onKeyUp = () => {
|
|
|
|
userTypingRef.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
// >>> Focus & Blur
|
|
|
|
const onBlur = () => {
|
|
|
|
flushInputValue(false);
|
|
|
|
focus.value = false;
|
|
|
|
userTypingRef.value = false;
|
|
|
|
emit('blur');
|
|
|
|
};
|
|
|
|
|
|
|
|
// ========================== Controlled ==========================
|
|
|
|
// Input by precision
|
|
|
|
watch(
|
|
|
|
() => props.precision,
|
|
|
|
() => {
|
|
|
|
if (!decimalValue.value.isInvalidate()) {
|
|
|
|
setInputValue(decimalValue.value, false);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ flush: 'post' },
|
|
|
|
);
|
|
|
|
|
|
|
|
// Input by value
|
|
|
|
watch(
|
|
|
|
() => props.value,
|
|
|
|
() => {
|
|
|
|
const newValue = getMiniDecimal(props.value);
|
|
|
|
decimalValue.value = newValue;
|
|
|
|
|
|
|
|
const currentParsedValue = getMiniDecimal(mergedParser(inputValue.value));
|
|
|
|
|
|
|
|
// When user typing from `1.2` to `1.`, we should not convert to `1` immediately.
|
|
|
|
// But let it go if user set `formatter`
|
|
|
|
if (!newValue.equals(currentParsedValue) || !userTypingRef.value || props.formatter) {
|
|
|
|
// Update value as effect
|
|
|
|
setInputValue(newValue, userTypingRef.value);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ flush: 'post' },
|
|
|
|
);
|
|
|
|
|
|
|
|
// ============================ Cursor ============================
|
|
|
|
watch(
|
|
|
|
inputValue,
|
|
|
|
() => {
|
|
|
|
if (props.formatter) {
|
|
|
|
restoreCursor();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ flush: 'post' },
|
|
|
|
);
|
|
|
|
|
|
|
|
watch(
|
|
|
|
() => props.disabled,
|
|
|
|
val => {
|
|
|
|
if (val) {
|
|
|
|
focus.value = false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
expose({
|
|
|
|
focus: () => {
|
|
|
|
inputRef.value?.focus();
|
|
|
|
},
|
|
|
|
blur: () => {
|
|
|
|
inputRef.value?.blur();
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
const {
|
|
|
|
prefixCls = 'rc-input-number',
|
|
|
|
min,
|
|
|
|
max,
|
|
|
|
step = 1,
|
|
|
|
defaultValue,
|
|
|
|
value,
|
|
|
|
disabled,
|
|
|
|
readonly,
|
|
|
|
keyboard,
|
|
|
|
controls = true,
|
|
|
|
autofocus,
|
|
|
|
|
|
|
|
stringMode,
|
|
|
|
|
|
|
|
parser,
|
|
|
|
formatter,
|
|
|
|
precision,
|
|
|
|
decimalSeparator,
|
|
|
|
|
|
|
|
onChange,
|
|
|
|
onInput,
|
|
|
|
onPressEnter,
|
|
|
|
onStep,
|
|
|
|
|
|
|
|
class: className,
|
|
|
|
style,
|
|
|
|
|
|
|
|
...inputProps
|
|
|
|
} = { ...(attrs as HTMLAttributes), ...props };
|
|
|
|
const { upHandler, downHandler } = slots;
|
|
|
|
const inputClassName = `${prefixCls}-input`;
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
class={classNames(prefixCls, className, {
|
|
|
|
[`${prefixCls}-focused`]: focus.value,
|
|
|
|
[`${prefixCls}-disabled`]: disabled,
|
|
|
|
[`${prefixCls}-readonly`]: readonly,
|
|
|
|
[`${prefixCls}-not-a-number`]: decimalValue.value.isNaN(),
|
|
|
|
[`${prefixCls}-out-of-range`]:
|
|
|
|
!decimalValue.value.isInvalidate() && !isInRange(decimalValue.value),
|
|
|
|
})}
|
|
|
|
style={style}
|
|
|
|
onKeydown={onKeyDown}
|
|
|
|
onKeyup={onKeyUp}
|
|
|
|
>
|
|
|
|
{controls && (
|
|
|
|
<StepHandler
|
|
|
|
prefixCls={prefixCls}
|
|
|
|
upDisabled={upDisabled.value}
|
|
|
|
downDisabled={downDisabled.value}
|
|
|
|
onStep={onInternalStep}
|
|
|
|
v-slots={{ upNode: upHandler, downNode: downHandler }}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
<div class={`${inputClassName}-wrap`}>
|
|
|
|
<input
|
|
|
|
autofocus={autofocus}
|
|
|
|
autocomplete="off"
|
|
|
|
role="spinbutton"
|
|
|
|
aria-valuemin={min as any}
|
|
|
|
aria-valuemax={max as any}
|
|
|
|
aria-valuenow={
|
|
|
|
decimalValue.value.isInvalidate() ? null : (decimalValue.value.toString() as any)
|
|
|
|
}
|
|
|
|
step={step}
|
|
|
|
{...inputProps}
|
|
|
|
ref={inputRef}
|
|
|
|
class={inputClassName}
|
|
|
|
value={inputValue.value}
|
|
|
|
disabled={disabled}
|
|
|
|
readonly={readonly}
|
|
|
|
onFocus={() => {
|
|
|
|
focus.value = true;
|
|
|
|
emit('focus');
|
|
|
|
}}
|
|
|
|
onInput={onInternalInput}
|
|
|
|
onBlur={onBlur}
|
|
|
|
onCompositionstart={onCompositionStart}
|
|
|
|
onCompositionend={onCompositionEnd}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
},
|
|
|
|
});
|