feat(Stepper): add decimal-length prop (#4443)

This commit is contained in:
neverland 2019-09-12 17:35:16 +08:00 committed by GitHub
parent f14043c18d
commit 927bf464bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 174 additions and 138 deletions

View File

@ -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

View File

@ -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

View File

@ -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
};
},

View File

@ -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);
// allow input to be empty
if (value === '') {
return;
}
const formatted = this.filter(value);
if (!equal(value, formatted)) {
event.target.value = formatted;
}
this.emitChange(formatted);
},
emitChange(value) {
if (this.asyncChange) {
event.target.value = this.currentValue;
this.$emit('input', formatted);
this.$emit('change', formatted);
this.$emit('input', value);
this.$emit('change', value);
} else {
if (+value !== formatted) {
event.target.value = formatted;
}
this.currentValue = formatted;
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();
},

View File

@ -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>
`;

View File

@ -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>`;

View File

@ -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');
});