refactor(Stepper): refactor with composition api

This commit is contained in:
chenjiahan 2020-09-09 11:57:12 +08:00
parent f94c8ccbb9
commit a7c28548fc

View File

@ -1,14 +1,19 @@
import { createNamespace, isDef, addUnit, getSizeStyle } from '../utils'; import { ref, computed, watch } from 'vue';
// Utils
import { isNaN } from '../utils/validate/number';
import { formatNumber } from '../utils/format/number';
import { resetScroll } from '../utils/dom/reset-scroll'; import { resetScroll } from '../utils/dom/reset-scroll';
import { preventDefault } from '../utils/dom/event'; import { preventDefault } from '../utils/dom/event';
import { formatNumber } from '../utils/format/number'; import { createNamespace, isDef, addUnit, getSizeStyle } from '../utils';
import { isNaN } from '../utils/validate/number';
import { FieldMixin } from '../mixins/field'; // Composition
import { useParentField } from '../composition/use-parent-field';
const [createComponent, bem] = createNamespace('stepper'); const [createComponent, bem] = createNamespace('stepper');
const LONG_PRESS_START_TIME = 600;
const LONG_PRESS_INTERVAL = 200; const LONG_PRESS_INTERVAL = 200;
const LONG_PRESS_START_TIME = 600;
function equal(value1, value2) { function equal(value1, value2) {
return String(value1) === String(value2); return String(value1) === String(value2);
@ -21,8 +26,6 @@ function add(num1, num2) {
} }
export default createComponent({ export default createComponent({
mixins: [FieldMixin],
props: { props: {
theme: String, theme: String,
integer: Boolean, integer: Boolean,
@ -81,247 +84,227 @@ export default createComponent({
'update:modelValue', 'update:modelValue',
], ],
data() { setup(props, { emit }) {
const defaultValue = this.modelValue ?? this.defaultValue; const format = (value) => {
const value = this.format(defaultValue); const { min, max, allowEmpty, decimalLength } = props;
if (!equal(value, this.modelValue)) { if (allowEmpty && value === '') {
this.$emit('update:modelValue', value);
}
return {
currentValue: value,
};
},
computed: {
minusDisabled() {
return (
this.disabled || this.disableMinus || this.currentValue <= +this.min
);
},
plusDisabled() {
return (
this.disabled || this.disablePlus || this.currentValue >= +this.max
);
},
inputStyle() {
const style = {};
if (this.inputWidth) {
style.width = addUnit(this.inputWidth);
}
if (this.buttonSize) {
style.height = addUnit(this.buttonSize);
}
return style;
},
buttonStyle() {
return getSizeStyle(this.buttonSize);
},
},
watch: {
max: 'check',
min: 'check',
integer: 'check',
decimalLength: 'check',
modelValue(val) {
if (!equal(val, this.currentValue)) {
this.currentValue = this.format(val);
}
},
currentValue(val) {
this.$emit('update:modelValue', val);
this.$emit('change', val, { name: this.name });
},
},
methods: {
check() {
const val = this.format(this.currentValue);
if (!equal(val, this.currentValue)) {
this.currentValue = val;
}
},
// formatNumber illegal characters
formatNumber(value) {
return formatNumber(String(value), !this.integer);
},
format(value) {
if (this.allowEmpty && value === '') {
return value; return value;
} }
value = this.formatNumber(value); value = formatNumber(String(value), !props.integer);
// format range
value = value === '' ? 0 : +value; value = value === '' ? 0 : +value;
value = isNaN(value) ? this.min : value; value = isNaN(value) ? min : value;
value = Math.max(Math.min(this.max, value), this.min); value = Math.max(Math.min(max, value), min);
// format decimal // format decimal
if (isDef(this.decimalLength)) { if (isDef(decimalLength)) {
value = value.toFixed(this.decimalLength); value = value.toFixed(decimalLength);
} }
return value; return value;
}, };
onInput(event) { const getInitialValue = () => {
const defaultValue = props.modelValue ?? props.defaultValue;
const value = format(defaultValue);
if (!equal(value, props.modelValue)) {
emit('update:modelValue', value);
}
return value;
};
let actionType;
const inputRef = ref();
const current = ref(getInitialValue());
const minusDisabled = computed(
() => props.disabled || props.disableMinus || current.value <= +props.min
);
const plusDisabled = computed(
() => props.disabled || props.disablePlus || current.value >= +props.max
);
const inputStyle = computed(() => ({
width: addUnit(props.inputWidth),
height: addUnit(props.buttonSize),
}));
const buttonStyle = computed(() => getSizeStyle(props.buttonSize));
const check = () => {
const value = format(current.value);
if (!equal(value, current.value)) {
current.value = value;
}
};
const emitChange = (value) => {
if (props.asyncChange) {
emit('update:modelValue', value);
emit('change', value, { name: props.name });
} else {
current.value = value;
}
};
const onChange = () => {
if (props[`${actionType}Disabled`]) {
emit('overlimit', actionType);
return;
}
const diff = actionType === 'minus' ? -props.step : +props.step;
const value = format(add(+current.value, diff));
emitChange(value);
emit(actionType);
};
const onInput = (event) => {
const { value } = event.target; const { value } = event.target;
const { decimalLength } = props;
let formatted = this.formatNumber(value); let formatted = formatNumber(String(value), !props.integer);
// limit max decimal length // limit max decimal length
if (isDef(this.decimalLength) && formatted.indexOf('.') !== -1) { if (isDef(decimalLength) && formatted.indexOf('.') !== -1) {
const pair = formatted.split('.'); const pair = formatted.split('.');
formatted = `${pair[0]}.${pair[1].slice(0, this.decimalLength)}`; formatted = `${pair[0]}.${pair[1].slice(0, decimalLength)}`;
} }
if (!equal(value, formatted)) { if (!equal(value, formatted)) {
event.target.value = formatted; event.target.value = formatted;
} }
this.emitChange(formatted); emitChange(formatted);
}, };
emitChange(value) { const onFocus = (event) => {
if (this.asyncChange) {
this.$emit('update:modelValue', value);
this.$emit('change', value, { name: this.name });
} else {
this.currentValue = value;
}
},
onChange() {
const { type } = this;
if (this[`${type}Disabled`]) {
this.$emit('overlimit', type);
return;
}
const diff = type === 'minus' ? -this.step : +this.step;
const value = this.format(add(+this.currentValue, diff));
this.emitChange(value);
this.$emit(type);
},
onFocus(event) {
// readonly not work in lagacy mobile safari // readonly not work in lagacy mobile safari
if (this.disableInput && this.$refs.input) { if (props.disableInput && inputRef.value) {
this.$refs.input.blur(); inputRef.value.blur();
} else { } else {
this.$emit('focus', event); emit('focus', event);
} }
}, };
onBlur(event) { const onBlur = (event) => {
const value = this.format(event.target.value); const value = format(event.target.value);
event.target.value = value; event.target.value = value;
this.currentValue = value; current.value = value;
this.$emit('blur', event); emit('blur', event);
resetScroll(); resetScroll();
}, };
longPressStep() { let isLongPress;
this.longPressTimer = setTimeout(() => { let longPressTimer;
this.onChange();
this.longPressStep(this.type); const longPressStep = () => {
longPressTimer = setTimeout(() => {
onChange();
longPressStep(actionType);
}, LONG_PRESS_INTERVAL); }, LONG_PRESS_INTERVAL);
}, };
onTouchStart() { const onTouchStart = () => {
if (!this.longPress) { if (props.longPress) {
return; isLongPress = false;
clearTimeout(longPressTimer);
longPressTimer = setTimeout(() => {
isLongPress = true;
onChange();
longPressStep();
}, LONG_PRESS_START_TIME);
} }
};
clearTimeout(this.longPressTimer); const onTouchEnd = (event) => {
this.isLongPress = false; if (props.longPress) {
clearTimeout(longPressTimer);
this.longPressTimer = setTimeout(() => { if (isLongPress) {
this.isLongPress = true; preventDefault(event);
this.onChange(); }
this.longPressStep();
}, LONG_PRESS_START_TIME);
},
onTouchEnd(event) {
if (!this.longPress) {
return;
} }
};
clearTimeout(this.longPressTimer);
if (this.isLongPress) {
preventDefault(event);
}
},
},
render() {
const createListeners = (type) => ({ const createListeners = (type) => ({
onClick: (e) => { onClick: (e) => {
// disable double tap scrolling on mobile safari // disable double tap scrolling on mobile safari
e.preventDefault(); e.preventDefault();
this.type = type; actionType = type;
this.onChange(); onChange();
}, },
onTouchstart: () => { onTouchstart: () => {
this.type = type; actionType = type;
this.onTouchStart(); onTouchStart();
}, },
onTouchend: this.onTouchEnd, onTouchend: onTouchEnd,
onTouchcancel: this.onTouchEnd, onTouchcancel: onTouchEnd,
}); });
return ( watch(
<div class={bem([this.theme])}> [
() => props.max,
() => props.min,
() => props.integer,
() => props.decimalLength,
],
check
);
watch(
() => props.modelValue,
(value) => {
if (!equal(value, current.value)) {
current.value = value;
}
}
);
watch(current, (value) => {
emit('update:modelValue', value);
emit('change', value, { name: props.name });
});
useParentField(() => props.modelValue);
return () => (
<div class={bem([props.theme])}>
<button <button
vShow={this.showMinus} vShow={props.showMinus}
type="button" type="button"
style={this.buttonStyle} style={buttonStyle.value}
class={bem('minus', { disabled: this.minusDisabled })} class={bem('minus', { disabled: minusDisabled.value })}
{...createListeners('minus')} {...createListeners('minus')}
/> />
<input <input
ref="input" ref="input"
type={this.integer ? 'tel' : 'text'} type={props.integer ? 'tel' : 'text'}
role="spinbutton" role="spinbutton"
class={bem('input')} class={bem('input')}
value={this.currentValue} value={current.value}
style={this.inputStyle} style={inputStyle.value}
disabled={this.disabled} disabled={props.disabled}
readonly={this.disableInput} readonly={props.disableInput}
// set keyboard in mordern browers // set keyboard in mordern browers
inputmode={this.integer ? 'numeric' : 'decimal'} inputmode={props.integer ? 'numeric' : 'decimal'}
placeholder={this.placeholder} placeholder={props.placeholder}
aria-valuemax={this.max} aria-valuemax={props.max}
aria-valuemin={this.min} aria-valuemin={props.min}
aria-valuenow={this.currentValue} aria-valuenow={current.value}
onInput={this.onInput} onInput={onInput}
onFocus={this.onFocus} onFocus={onFocus}
onBlur={this.onBlur} onBlur={onBlur}
/> />
<button <button
vShow={this.showPlus} vShow={props.showPlus}
type="button" type="button"
style={this.buttonStyle} style={buttonStyle.value}
class={bem('plus', { disabled: this.plusDisabled })} class={bem('plus', { disabled: plusDisabled.value })}
{...createListeners('plus')} {...createListeners('plus')}
/> />
</div> </div>