mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
feat(Stepper): add decimal-length prop (#4443)
This commit is contained in:
parent
f14043c18d
commit
927bf464bf
@ -51,6 +51,18 @@ export default {
|
||||
<van-stepper v-model="value" disabled />
|
||||
```
|
||||
|
||||
### Decimal Length
|
||||
|
||||
```html
|
||||
<van-stepper v-model="value" step="0.2" :decimal-length="1" />
|
||||
```
|
||||
|
||||
### Custom Size
|
||||
|
||||
```html
|
||||
<van-stepper v-model="value" input-width="40px" button-size="32px" />
|
||||
```
|
||||
|
||||
### Async Change
|
||||
|
||||
```html
|
||||
@ -82,16 +94,6 @@ export default {
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Size
|
||||
|
||||
```html
|
||||
<van-stepper
|
||||
v-model="value"
|
||||
input-width="40px"
|
||||
button-size="32px"
|
||||
/>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
@ -110,6 +112,7 @@ export default {
|
||||
| button-size | Button size | *string \| number* | `28px` | 2.0.5 |
|
||||
| show-plus | Whether to show plus button | *boolean* | `true` | 2.1.2 |
|
||||
| show-minus | Whether to show minus button | *boolean* | `true` | 2.1.2 |
|
||||
| decimal-length | Decimal length | *number* | - | 2.2.1 |
|
||||
|
||||
### Events
|
||||
|
||||
|
@ -65,6 +65,22 @@ export default {
|
||||
<van-stepper v-model="value" disabled />
|
||||
```
|
||||
|
||||
### 固定小数位数
|
||||
|
||||
通过设置`decimal-length`属性可以保留固定的小数位数
|
||||
|
||||
```html
|
||||
<van-stepper v-model="value" step="0.2" :decimal-length="1" />
|
||||
```
|
||||
|
||||
### 自定义大小
|
||||
|
||||
通过`input-width`属性设置输入框宽度,通过`button-size`属性设置按钮大小和输入框高度
|
||||
|
||||
```html
|
||||
<van-stepper v-model="value" input-width="40px" button-size="32px" />
|
||||
```
|
||||
|
||||
### 异步变更
|
||||
|
||||
如果需要异步地修改输入框的值,可以设置`async-change`属性,并在`change`事件中手动修改`value`
|
||||
@ -100,25 +116,13 @@ export default {
|
||||
}
|
||||
```
|
||||
|
||||
### 自定义大小
|
||||
|
||||
通过`input-width`属性设置输入框宽度,通过`button-size`属性设置按钮大小和输入框高度
|
||||
|
||||
```html
|
||||
<van-stepper
|
||||
v-model="value"
|
||||
input-width="40px"
|
||||
button-size="32px"
|
||||
/>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| v-model | 当前输入值 | *string \| number* | 最小值 | - |
|
||||
| v-model | 当前输入值 | *string \| number* | min | - |
|
||||
| min | 最小值 | *string \| number* | `1` | - |
|
||||
| max | 最大值 | *string \| number* | - | - |
|
||||
| step | 步长 | *string \| number* | `1` | - |
|
||||
@ -127,9 +131,10 @@ export default {
|
||||
| disable-input | 是否禁用输入框 | *boolean* | `false` | - |
|
||||
| async-change | 是否开启异步变更,开启后需要手动控制输入值 | *boolean* | `false` | - |
|
||||
| input-width | 输入框宽度,默认单位为`px` | *string \| number* | `32px` | - |
|
||||
| button-size | 按钮大小,默认单位为`px`,输入框高度会和按钮大小保持一致 | *string \| number* | `28px` | 2.0.5 |
|
||||
| button-size | 按钮大小以及输入框高度,默认单位为`px` | *string \| number* | `28px` | 2.0.5 |
|
||||
| show-plus | 是否显示增加按钮 | *boolean* | `true` | 2.1.2 |
|
||||
| show-minus | 是否显示减少按钮 | *boolean* | `true` | 2.1.2 |
|
||||
| decimal-length | 固定显示的小数位数 | *number* | - | 2.2.1 |
|
||||
|
||||
### Events
|
||||
|
||||
|
@ -1,73 +1,35 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<van-cell
|
||||
center
|
||||
:title="$t('basicUsage')"
|
||||
>
|
||||
<van-cell center :title="$t('basicUsage')">
|
||||
<van-stepper v-model="stepper1" />
|
||||
</van-cell>
|
||||
|
||||
<van-cell
|
||||
center
|
||||
:title="$t('step')"
|
||||
>
|
||||
<van-stepper
|
||||
v-model="stepper2"
|
||||
step="2"
|
||||
/>
|
||||
<van-cell center :title="$t('step')">
|
||||
<van-stepper v-model="stepper2" step="2" />
|
||||
</van-cell>
|
||||
|
||||
<van-cell
|
||||
center
|
||||
:title="$t('range')"
|
||||
>
|
||||
<van-stepper
|
||||
v-model="stepper3"
|
||||
:min="5"
|
||||
:max="8"
|
||||
/>
|
||||
<van-cell center :title="$t('range')">
|
||||
<van-stepper v-model="stepper3" :min="5" :max="8" />
|
||||
</van-cell>
|
||||
|
||||
<van-cell
|
||||
center
|
||||
:title="$t('integer')"
|
||||
>
|
||||
<van-stepper
|
||||
v-model="stepper4"
|
||||
integer
|
||||
/>
|
||||
<van-cell center :title="$t('integer')">
|
||||
<van-stepper v-model="stepper4" integer />
|
||||
</van-cell>
|
||||
|
||||
<van-cell
|
||||
center
|
||||
:title="$t('disabled')"
|
||||
>
|
||||
<van-stepper
|
||||
v-model="stepper5"
|
||||
disabled
|
||||
/>
|
||||
<van-cell center :title="$t('disabled')">
|
||||
<van-stepper v-model="stepper5" disabled />
|
||||
</van-cell>
|
||||
|
||||
<van-cell
|
||||
center
|
||||
:title="$t('asyncChange')"
|
||||
>
|
||||
<van-stepper
|
||||
:value="stepper6"
|
||||
async-change
|
||||
@change="onChange"
|
||||
/>
|
||||
<van-cell center :title="$t('decimalLength')">
|
||||
<van-stepper v-model="stepper8" :decimal-length="1" step="0.2" />
|
||||
</van-cell>
|
||||
|
||||
<van-cell
|
||||
center
|
||||
:title="$t('customSize')"
|
||||
>
|
||||
<van-stepper
|
||||
v-model="stepper7"
|
||||
button-size="32px"
|
||||
input-width="40px"
|
||||
/>
|
||||
<van-cell center :title="$t('customSize')">
|
||||
<van-stepper v-model="stepper7" button-size="32px" input-width="40px" />
|
||||
</van-cell>
|
||||
|
||||
<van-cell center :title="$t('asyncChange')">
|
||||
<van-stepper :value="stepper6" async-change @change="onChange" />
|
||||
</van-cell>
|
||||
</demo-section>
|
||||
</template>
|
||||
@ -80,14 +42,16 @@ export default {
|
||||
range: '限制输入范围',
|
||||
integer: '限制输入整数',
|
||||
asyncChange: '异步变更',
|
||||
customSize: '自定义大小'
|
||||
customSize: '自定义大小',
|
||||
decimalLength: '固定小数位数'
|
||||
},
|
||||
'en-US': {
|
||||
step: 'Step',
|
||||
range: 'Range',
|
||||
integer: 'Integer',
|
||||
asyncChange: 'Async Change',
|
||||
customSize: 'Custom Size'
|
||||
customSize: 'Custom Size',
|
||||
decimalLength: 'Decimal Length'
|
||||
}
|
||||
},
|
||||
|
||||
@ -99,7 +63,8 @@ export default {
|
||||
stepper4: 1,
|
||||
stepper5: 1,
|
||||
stepper6: 1,
|
||||
stepper7: 1
|
||||
stepper7: 1,
|
||||
stepper8: 1
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -7,6 +7,10 @@ const [createComponent, bem] = createNamespace('stepper');
|
||||
const LONG_PRESS_START_TIME = 600;
|
||||
const LONG_PRESS_INTERVAL = 200;
|
||||
|
||||
function equal(value1, value2) {
|
||||
return String(value1) === String(value2);
|
||||
}
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
value: null,
|
||||
@ -16,6 +20,7 @@ export default createComponent({
|
||||
buttonSize: [Number, String],
|
||||
asyncChange: Boolean,
|
||||
disableInput: Boolean,
|
||||
decimalLength: Number,
|
||||
min: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
@ -43,8 +48,10 @@ export default createComponent({
|
||||
},
|
||||
|
||||
data() {
|
||||
const value = this.range(isDef(this.value) ? this.value : this.defaultValue);
|
||||
if (value !== +this.value) {
|
||||
const defaultValue = isDef(this.value) ? this.value : this.defaultValue;
|
||||
const value = this.format(defaultValue);
|
||||
|
||||
if (!equal(value, this.value)) {
|
||||
this.$emit('input', value);
|
||||
}
|
||||
|
||||
@ -90,7 +97,7 @@ export default createComponent({
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
if (val !== this.currentValue) {
|
||||
if (!equal(val, this.currentValue)) {
|
||||
this.currentValue = this.format(val);
|
||||
}
|
||||
},
|
||||
@ -103,29 +110,54 @@ export default createComponent({
|
||||
|
||||
methods: {
|
||||
// filter illegal characters
|
||||
format(value) {
|
||||
filter(value) {
|
||||
value = String(value).replace(/[^0-9.-]/g, '');
|
||||
return value === '' ? 0 : this.integer ? Math.floor(value) : +value;
|
||||
|
||||
if (this.integer && value.indexOf('.') !== -1) {
|
||||
value = value.split('.')[0];
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
// limit value range
|
||||
range(value) {
|
||||
return Math.max(Math.min(this.max, this.format(value)), this.min);
|
||||
format(value) {
|
||||
value = this.filter(value);
|
||||
|
||||
// format range
|
||||
value = value === '' ? 0 : +value;
|
||||
value = Math.max(Math.min(this.max, value), this.min);
|
||||
|
||||
// format decimal
|
||||
if (isDef(this.decimalLength)) {
|
||||
value = value.toFixed(this.decimalLength);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
onInput(event) {
|
||||
const { value } = event.target;
|
||||
const formatted = this.format(value);
|
||||
|
||||
if (this.asyncChange) {
|
||||
event.target.value = this.currentValue;
|
||||
this.$emit('input', formatted);
|
||||
this.$emit('change', formatted);
|
||||
} else {
|
||||
if (+value !== formatted) {
|
||||
// allow input to be empty
|
||||
if (value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
const formatted = this.filter(value);
|
||||
|
||||
if (!equal(value, formatted)) {
|
||||
event.target.value = formatted;
|
||||
}
|
||||
this.currentValue = formatted;
|
||||
|
||||
this.emitChange(formatted);
|
||||
},
|
||||
|
||||
emitChange(value) {
|
||||
if (this.asyncChange) {
|
||||
this.$emit('input', value);
|
||||
this.$emit('change', value);
|
||||
} else {
|
||||
this.currentValue = value;
|
||||
}
|
||||
},
|
||||
|
||||
@ -138,15 +170,17 @@ export default createComponent({
|
||||
}
|
||||
|
||||
const diff = type === 'minus' ? -this.step : +this.step;
|
||||
const value = Math.round((this.currentValue + diff) * 100) / 100;
|
||||
|
||||
if (this.asyncChange) {
|
||||
this.$emit('input', value);
|
||||
this.$emit('change', value);
|
||||
} else {
|
||||
this.currentValue = this.range(value);
|
||||
let value = +this.currentValue + diff;
|
||||
|
||||
// avoid float number
|
||||
if (!isDef(this.decimalLength)) {
|
||||
value = Math.round(value * 100) / 100;
|
||||
}
|
||||
|
||||
value = this.format(value);
|
||||
|
||||
this.emitChange(value);
|
||||
this.$emit(type);
|
||||
},
|
||||
|
||||
@ -155,14 +189,11 @@ export default createComponent({
|
||||
},
|
||||
|
||||
onBlur(event) {
|
||||
this.currentValue = this.range(this.currentValue);
|
||||
const value = this.format(event.target.value);
|
||||
event.target.value = value;
|
||||
this.currentValue = value;
|
||||
this.$emit('blur', event);
|
||||
|
||||
// fix edge case when input is empty and min is 0
|
||||
if (this.currentValue === 0) {
|
||||
event.target.value = this.currentValue;
|
||||
}
|
||||
|
||||
resetScroll();
|
||||
},
|
||||
|
||||
|
@ -33,9 +33,9 @@ exports[`renders demo correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--center">
|
||||
<div class="van-cell__title"><span>异步变更</span></div>
|
||||
<div class="van-cell__title"><span>固定小数位数</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-stepper"><button class="van-stepper__minus van-stepper__minus--disabled"></button><input type="number" role="spinbutton" aria-valuemax="Infinity" aria-valuemin="1" aria-valuenow="1" class="van-stepper__input"><button class="van-stepper__plus"></button></div>
|
||||
<div class="van-stepper"><button class="van-stepper__minus van-stepper__minus--disabled"></button><input type="number" role="spinbutton" aria-valuemax="Infinity" aria-valuemin="1" aria-valuenow="1.0" class="van-stepper__input"><button class="van-stepper__plus"></button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--center">
|
||||
@ -44,5 +44,11 @@ exports[`renders demo correctly 1`] = `
|
||||
<div class="van-stepper"><button class="van-stepper__minus van-stepper__minus--disabled" style="width: 32px; height: 32px;"></button><input type="number" role="spinbutton" aria-valuemax="Infinity" aria-valuemin="1" aria-valuenow="1" class="van-stepper__input" style="width: 40px; height: 32px;"><button class="van-stepper__plus" style="width: 32px; height: 32px;"></button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--center">
|
||||
<div class="van-cell__title"><span>异步变更</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-stepper"><button class="van-stepper__minus van-stepper__minus--disabled"></button><input type="number" role="spinbutton" aria-valuemax="Infinity" aria-valuemin="1" aria-valuenow="1" class="van-stepper__input"><button class="van-stepper__plus"></button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -4,6 +4,6 @@ exports[`disable stepper input 1`] = `<div class="van-stepper"><button class="va
|
||||
|
||||
exports[`disabled stepper 1`] = `<div class="van-stepper"><button class="van-stepper__minus van-stepper__minus--disabled"></button><input type="number" role="spinbutton" aria-valuemax="Infinity" aria-valuemin="1" aria-valuenow="1" disabled="disabled" class="van-stepper__input"><button class="van-stepper__plus van-stepper__plus--disabled"></button></div>`;
|
||||
|
||||
exports[`input width 1`] = `<div class="van-stepper"><button class="van-stepper__minus van-stepper__minus--disabled"></button><input type="number" role="spinbutton" aria-valuemax="Infinity" aria-valuemin="1" aria-valuenow="1" class="van-stepper__input" style="width: 10rem;"><button class="van-stepper__plus"></button></div>`;
|
||||
exports[`input-width prop 1`] = `<div class="van-stepper"><button class="van-stepper__minus van-stepper__minus--disabled"></button><input type="number" role="spinbutton" aria-valuemax="Infinity" aria-valuemin="1" aria-valuenow="1" class="van-stepper__input" style="width: 10rem;"><button class="van-stepper__plus"></button></div>`;
|
||||
|
||||
exports[`show-plus & show-minus props 1`] = `<div class="van-stepper"><button class="van-stepper__minus van-stepper__minus--disabled" style="display: none;"></button><input type="number" role="spinbutton" aria-valuemax="Infinity" aria-valuemin="1" aria-valuenow="1" class="van-stepper__input"><button class="van-stepper__plus" style="display: none;"></button></div>`;
|
||||
|
@ -60,35 +60,45 @@ test('long press', async () => {
|
||||
expect(wrapper.emitted('input')).toEqual([[2], [3], [4]]);
|
||||
});
|
||||
|
||||
test('correct value when value is not correct', () => {
|
||||
test('filter value during user input', () => {
|
||||
const wrapper = mount(Stepper, {
|
||||
propsData: {
|
||||
value: 50,
|
||||
max: 30,
|
||||
min: 10
|
||||
value: 1
|
||||
}
|
||||
});
|
||||
|
||||
const input = wrapper.find('input');
|
||||
input.element.value = 1;
|
||||
input.trigger('input');
|
||||
input.element.value = 'abc';
|
||||
input.trigger('input');
|
||||
wrapper.setData({ value: 'abc' });
|
||||
input.trigger('input');
|
||||
wrapper.setData({ value: '' });
|
||||
input.trigger('input');
|
||||
wrapper.setData({ value: 40 });
|
||||
input.trigger('input');
|
||||
wrapper.setData({ value: 30 });
|
||||
const input = wrapper.find('.van-stepper__input');
|
||||
input.element.value = '';
|
||||
input.trigger('input');
|
||||
expect(wrapper.emitted('input')).toBeFalsy();
|
||||
|
||||
expect(wrapper.emitted('input')).toEqual([[30], [1], [0], [40], [30]]);
|
||||
input.element.value = 'a';
|
||||
input.trigger('input');
|
||||
expect(input.element.value).toEqual('');
|
||||
expect(wrapper.emitted('input')).toBeFalsy();
|
||||
|
||||
input.element.value = '2';
|
||||
input.trigger('input');
|
||||
expect(input.element.value).toEqual('2');
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual('2');
|
||||
});
|
||||
|
||||
test('shoud watch value and format it', () => {
|
||||
const wrapper = mount(Stepper, {
|
||||
propsData: {
|
||||
value: 1,
|
||||
max: 5
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.setData({ value: 10 });
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual(5);
|
||||
});
|
||||
|
||||
test('only allow interger', () => {
|
||||
const wrapper = mount(Stepper, {
|
||||
propsData: {
|
||||
value: 1,
|
||||
integer: true
|
||||
}
|
||||
});
|
||||
@ -98,7 +108,8 @@ test('only allow interger', () => {
|
||||
input.trigger('input');
|
||||
input.trigger('blur');
|
||||
|
||||
expect(wrapper.emitted('input')).toEqual([[1]]);
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual('1');
|
||||
expect(wrapper.emitted('input')[1][0]).toEqual(1);
|
||||
});
|
||||
|
||||
test('stepper focus', () => {
|
||||
@ -121,16 +132,15 @@ test('stepper blur', () => {
|
||||
});
|
||||
|
||||
const input = wrapper.find('input');
|
||||
input.trigger('blur');
|
||||
input.element.value = '';
|
||||
input.trigger('input');
|
||||
input.trigger('blur');
|
||||
|
||||
expect(wrapper.emitted('input')).toEqual([[0], [3]]);
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual(3);
|
||||
expect(wrapper.emitted('blur')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('input width', () => {
|
||||
test('input-width prop', () => {
|
||||
const wrapper = mount(Stepper, {
|
||||
propsData: {
|
||||
inputWidth: '10rem'
|
||||
@ -139,7 +149,7 @@ test('input width', () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('async change', () => {
|
||||
test('async-change prop', () => {
|
||||
const wrapper = mount(Stepper, {
|
||||
propsData: {
|
||||
value: 1,
|
||||
@ -157,8 +167,8 @@ test('async change', () => {
|
||||
input.element.value = '3';
|
||||
input.trigger('input');
|
||||
|
||||
expect(wrapper.emitted('input')[1][0]).toEqual(3);
|
||||
expect(wrapper.emitted('change')[1][0]).toEqual(3);
|
||||
expect(wrapper.emitted('input')[1][0]).toEqual('3');
|
||||
expect(wrapper.emitted('change')[1][0]).toEqual('3');
|
||||
});
|
||||
|
||||
test('min value is 0', () => {
|
||||
@ -174,7 +184,7 @@ test('min value is 0', () => {
|
||||
input.trigger('input');
|
||||
input.trigger('blur');
|
||||
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual(0);
|
||||
expect(input.element.value).toEqual('0');
|
||||
});
|
||||
|
||||
test('show-plus & show-minus props', () => {
|
||||
@ -187,3 +197,19 @@ test('show-plus & show-minus props', () => {
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('decimal-length prop', () => {
|
||||
const wrapper = mount(Stepper, {
|
||||
propsData: {
|
||||
value: 1,
|
||||
step: 0.2,
|
||||
decimalLength: 2
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.emitted('input')[0][0]).toEqual('1.00');
|
||||
|
||||
const plus = wrapper.find('.van-stepper__plus');
|
||||
plus.trigger('click');
|
||||
expect(wrapper.emitted('input')[1][0]).toEqual('1.20');
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user