feat(Slider): add reverse prop (#9308)

* feat(Slider): add reverse prop

* chore: types

* chore: upd
This commit is contained in:
neverland 2021-08-22 20:52:50 +08:00 committed by GitHub
parent d0914f34ad
commit 2a8e2edf40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 217 additions and 58 deletions

View File

@ -85,7 +85,7 @@ exports[`should render demo and match snapshot 1`] = `
style="width: 50%; left: 0%;" style="width: 50%; left: 0%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="50" aria-valuenow="50"
@ -201,7 +201,7 @@ exports[`should render demo and match snapshot 1`] = `
style="width: 50%; left: 0%;" style="width: 50%; left: 0%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="50" aria-valuenow="50"

View File

@ -375,7 +375,7 @@ exports[`should render demo and match snapshot 1`] = `
style="width: 50%; left: 0%;" style="width: 50%; left: 0%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="50" aria-valuenow="50"

View File

@ -159,6 +159,7 @@ export default {
| active-color | Active color of bar | _string_ | `#1989fa` | | active-color | Active color of bar | _string_ | `#1989fa` |
| inactive-color | Inactive color of bar | _string_ | `#e5e5e5` | | inactive-color | Inactive color of bar | _string_ | `#e5e5e5` |
| range | Whether to enable dual thumb mode | _boolean_ | `false` | | range | Whether to enable dual thumb mode | _boolean_ | `false` |
| reverse `v3.2.1` | Whether to reverse slider | `false` |
| disabled | Whether to disable slider | _boolean_ | `false` | | disabled | Whether to disable slider | _boolean_ | `false` |
| readonly `v3.0.5` | Whether to be readonly | _boolean_ | `false` | | readonly `v3.0.5` | Whether to be readonly | _boolean_ | `false` |
| vertical | Whether to display slider vertically | _boolean_ | `false` | | vertical | Whether to display slider vertically | _boolean_ | `false` |

View File

@ -161,6 +161,7 @@ export default {
| active-color | 进度条激活态颜色 | _string_ | `#1989fa` | | active-color | 进度条激活态颜色 | _string_ | `#1989fa` |
| inactive-color | 进度条非激活态颜色 | _string_ | `#e5e5e5` | | inactive-color | 进度条非激活态颜色 | _string_ | `#e5e5e5` |
| range | 是否开启双滑块模式 | _boolean_ | `false` | | range | 是否开启双滑块模式 | _boolean_ | `false` |
| reverse `v3.2.1` | 是否将进度条反转 | `false` |
| disabled | 是否禁用滑块 | _boolean_ | `false` | | disabled | 是否禁用滑块 | _boolean_ | `false` |
| readonly `v3.0.5` | 是否为只读状态,只读状态下无法修改滑块的值 | _boolean_ | `false` | | readonly `v3.0.5` | 是否为只读状态,只读状态下无法修改滑块的值 | _boolean_ | `false` |
| vertical | 是否垂直展示 | _boolean_ | `false` | | vertical | 是否垂直展示 | _boolean_ | `false` |

View File

@ -17,13 +17,16 @@ import { useTouch } from '../composables/use-touch';
const [name, bem] = createNamespace('slider'); const [name, bem] = createNamespace('slider');
type SliderValue = number | [number, number]; type NumberRange = [number, number];
type SliderValue = number | NumberRange;
export default defineComponent({ export default defineComponent({
name, name,
props: { props: {
range: Boolean, range: Boolean,
reverse: Boolean,
disabled: Boolean, disabled: Boolean,
readonly: Boolean, readonly: Boolean,
vertical: Boolean, vertical: Boolean,
@ -70,7 +73,7 @@ export default defineComponent({
}; };
}); });
const isRange = (val: unknown): val is [number, number] => const isRange = (val: unknown): val is NumberRange =>
props.range && Array.isArray(val); props.range && Array.isArray(val);
// 计算选中条的长度百分比 // 计算选中条的长度百分比
@ -91,15 +94,27 @@ export default defineComponent({
return '0%'; return '0%';
}; };
const barStyle = computed<CSSProperties>(() => { const barStyle = computed(() => {
const mainAxis = props.vertical ? 'height' : 'width'; const mainAxis = props.vertical ? 'height' : 'width';
return { const style: CSSProperties = {
[mainAxis]: calcMainAxis(), [mainAxis]: calcMainAxis(),
left: props.vertical ? undefined : calcOffset(),
top: props.vertical ? calcOffset() : undefined,
background: props.activeColor, background: props.activeColor,
transition: dragStatus.value ? 'none' : undefined,
}; };
if (dragStatus.value) {
style.transition = 'none';
}
const getPositionKey = () => {
if (props.vertical) {
return props.reverse ? 'bottom' : 'top';
}
return props.reverse ? 'right' : 'left';
};
style[getPositionKey()] = calcOffset();
return style;
}); });
const format = (value: number) => { const format = (value: number) => {
@ -116,7 +131,7 @@ export default defineComponent({
JSON.stringify(newValue) === JSON.stringify(oldValue); JSON.stringify(newValue) === JSON.stringify(oldValue);
// 处理两个滑块重叠之后的情况 // 处理两个滑块重叠之后的情况
const handleOverlap = (value: [number, number]) => { const handleOverlap = (value: NumberRange) => {
if (value[0] > value[1]) { if (value[0] > value[1]) {
return value.slice(0).reverse(); return value.slice(0).reverse();
} }
@ -125,7 +140,7 @@ export default defineComponent({
const updateValue = (value: SliderValue, end?: boolean) => { const updateValue = (value: SliderValue, end?: boolean) => {
if (isRange(value)) { if (isRange(value)) {
value = handleOverlap(value).map(format) as [number, number]; value = handleOverlap(value).map(format) as NumberRange;
} else { } else {
value = format(value); value = format(value);
} }
@ -146,13 +161,24 @@ export default defineComponent({
return; return;
} }
const { min, vertical, modelValue } = props; const { min, reverse, vertical, modelValue } = props;
const rect = useRect(root); const rect = useRect(root);
const delta = vertical
? event.clientY - rect.top const getDelta = () => {
: event.clientX - rect.left; if (vertical) {
if (reverse) {
return rect.bottom - event.clientY;
}
return event.clientY - rect.top;
}
if (reverse) {
return rect.right - event.clientX;
}
return event.clientX - rect.left;
};
const total = vertical ? rect.height : rect.width; const total = vertical ? rect.height : rect.width;
const value = Number(min) + (delta / total) * scope.value; const value = Number(min) + (getDelta() / total) * scope.value;
if (isRange(modelValue)) { if (isRange(modelValue)) {
const [left, right] = modelValue; const [left, right] = modelValue;
@ -177,7 +203,7 @@ export default defineComponent({
currentValue = props.modelValue; currentValue = props.modelValue;
if (isRange(currentValue)) { if (isRange(currentValue)) {
startValue = currentValue.map(format) as [number, number]; startValue = currentValue.map(format) as NumberRange;
} else { } else {
startValue = format(currentValue); startValue = format(currentValue);
} }
@ -201,11 +227,15 @@ export default defineComponent({
const rect = useRect(root); const rect = useRect(root);
const delta = props.vertical ? touch.deltaY.value : touch.deltaX.value; const delta = props.vertical ? touch.deltaY.value : touch.deltaX.value;
const total = props.vertical ? rect.height : rect.width; const total = props.vertical ? rect.height : rect.width;
const diff = (delta / total) * scope.value;
let diff = (delta / total) * scope.value;
if (props.reverse) {
diff = -diff;
}
if (isRange(startValue)) { if (isRange(startValue)) {
(currentValue as [number, number])[buttonIndex] = const index = props.reverse ? 1 - buttonIndex : buttonIndex;
startValue[buttonIndex] + diff; (currentValue as NumberRange)[index] = startValue[index] + diff;
} else { } else {
currentValue = startValue + diff; currentValue = startValue + diff;
} }
@ -228,9 +258,9 @@ export default defineComponent({
const getButtonClassName = (index?: 0 | 1) => { const getButtonClassName = (index?: 0 | 1) => {
if (typeof index === 'number') { if (typeof index === 'number') {
const position = ['left', 'right']; const position = ['left', 'right'];
return bem(`button-wrapper-${position[index]}`); return bem(`button-wrapper`, position[index]);
} }
return bem('button-wrapper'); return bem('button-wrapper', props.reverse ? 'left' : 'right');
}; };
const renderButtonContent = (value: number, index?: 0 | 1) => { const renderButtonContent = (value: number, index?: 0 | 1) => {
@ -253,7 +283,7 @@ export default defineComponent({
const renderButton = (index?: 0 | 1) => { const renderButton = (index?: 0 | 1) => {
const currentValue = const currentValue =
typeof index === 'number' typeof index === 'number'
? (props.modelValue as [number, number])[index] ? (props.modelValue as NumberRange)[index]
: (props.modelValue as number); : (props.modelValue as number);
return ( return (

View File

@ -31,7 +31,7 @@
} }
&__bar { &__bar {
position: relative; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: var(--van-slider-active-background-color); background-color: var(--van-slider-active-background-color);
@ -46,21 +46,20 @@
border-radius: var(--van-slider-button-border-radius); border-radius: var(--van-slider-button-border-radius);
box-shadow: var(--van-slider-button-box-shadow); box-shadow: var(--van-slider-button-box-shadow);
&-wrapper, &-wrapper {
&-wrapper-right {
position: absolute; position: absolute;
top: 50%;
right: 0;
transform: translate3d(50%, -50%, 0);
cursor: grab; cursor: grab;
} top: 50%;
&-wrapper-left { &--right {
position: absolute; right: 0;
top: 50%; transform: translate3d(50%, -50%, 0);
left: 0; }
transform: translate3d(-50%, -50%, 0);
cursor: grab; &--left {
left: 0;
transform: translate3d(-50%, -50%, 0);
}
} }
} }
@ -68,9 +67,7 @@
cursor: not-allowed; cursor: not-allowed;
opacity: var(--van-slider-disabled-opacity); opacity: var(--van-slider-disabled-opacity);
.van-slider__button-wrapper, .van-slider__button-wrapper {
.van-slider__button-wrapper-left,
.van-slider__button-wrapper-right {
cursor: not-allowed; cursor: not-allowed;
} }
} }
@ -80,15 +77,14 @@
width: var(--van-slider-bar-height); width: var(--van-slider-bar-height);
height: 100%; height: 100%;
.van-slider__button-wrapper, .van-slider__button-wrapper--right {
.van-slider__button-wrapper-right {
top: auto; top: auto;
right: 50%; right: 50%;
bottom: 0; bottom: 0;
transform: translate3d(50%, 50%, 0); transform: translate3d(50%, 50%, 0);
} }
.van-slider__button-wrapper-left { .van-slider__button-wrapper--left {
top: 0; top: 0;
right: 50%; right: 50%;
left: auto; left: auto;

View File

@ -7,7 +7,7 @@ exports[`should render demo and match snapshot 1`] = `
style="width: 50%; left: 0%;" style="width: 50%; left: 0%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="50" aria-valuenow="50"
@ -26,7 +26,7 @@ exports[`should render demo and match snapshot 1`] = `
style="width: 40%; left: 20%;" style="width: 40%; left: 20%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper-left" class="van-slider__button-wrapper van-slider__button-wrapper--left"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="20" aria-valuenow="20"
@ -37,7 +37,7 @@ exports[`should render demo and match snapshot 1`] = `
</div> </div>
</div> </div>
<div role="slider" <div role="slider"
class="van-slider__button-wrapper-right" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="60" aria-valuenow="60"
@ -56,7 +56,7 @@ exports[`should render demo and match snapshot 1`] = `
style="width: 50%; left: 0%;" style="width: 50%; left: 0%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="-50" aria-valuemin="-50"
aria-valuenow="0" aria-valuenow="0"
@ -75,7 +75,7 @@ exports[`should render demo and match snapshot 1`] = `
style="width: 50%; left: 0%;" style="width: 50%; left: 0%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="-1" tabindex="-1"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="50" aria-valuenow="50"
@ -94,7 +94,7 @@ exports[`should render demo and match snapshot 1`] = `
style="width: 50%; left: 0%;" style="width: 50%; left: 0%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="50" aria-valuenow="50"
@ -112,10 +112,10 @@ exports[`should render demo and match snapshot 1`] = `
class="van-slider" class="van-slider"
> >
<div class="van-slider__bar" <div class="van-slider__bar"
style="width: 50%; left: 0%; background: rgb(238, 10, 36);" style="width: 50%; background: rgb(238, 10, 36); left: 0%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="50" aria-valuenow="50"
@ -131,10 +131,10 @@ exports[`should render demo and match snapshot 1`] = `
<div> <div>
<div class="van-slider"> <div class="van-slider">
<div class="van-slider__bar" <div class="van-slider__bar"
style="width: 50%; left: 0%; background: rgb(238, 10, 36);" style="width: 50%; background: rgb(238, 10, 36); left: 0%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="50" aria-valuenow="50"
@ -155,7 +155,7 @@ exports[`should render demo and match snapshot 1`] = `
style="height: 50%; top: 0%;" style="height: 50%; top: 0%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="50" aria-valuenow="50"
@ -174,7 +174,7 @@ exports[`should render demo and match snapshot 1`] = `
style="height: 40%; top: 20%;" style="height: 40%; top: 20%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper-left" class="van-slider__button-wrapper van-slider__button-wrapper--left"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="20" aria-valuenow="20"
@ -185,7 +185,7 @@ exports[`should render demo and match snapshot 1`] = `
</div> </div>
</div> </div>
<div role="slider" <div role="slider"
class="van-slider__button-wrapper-right" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="60" aria-valuenow="60"

View File

@ -6,7 +6,7 @@ exports[`should render left-button、right-button slot correctly 1`] = `
style="width: 50%; left: 30%;" style="width: 50%; left: 30%;"
> >
<div role="slider" <div role="slider"
class="van-slider__button-wrapper-left" class="van-slider__button-wrapper van-slider__button-wrapper--left"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="30" aria-valuenow="30"
@ -16,7 +16,7 @@ exports[`should render left-button、right-button slot correctly 1`] = `
left-30 left-30
</div> </div>
<div role="slider" <div role="slider"
class="van-slider__button-wrapper-right" class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0" tabindex="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuenow="80" aria-valuenow="80"
@ -28,3 +28,74 @@ exports[`should render left-button、right-button slot correctly 1`] = `
</div> </div>
</div> </div>
`; `;
exports[`should render reversed range slider correctly 1`] = `
<div class="van-slider">
<div class="van-slider__bar"
style="width: 15%; right: 25%;"
>
<div role="slider"
class="van-slider__button-wrapper van-slider__button-wrapper--left"
tabindex="0"
aria-valuemin="0"
aria-valuenow="25"
aria-valuemax="100"
aria-orientation="horizontal"
>
<div class="van-slider__button">
</div>
</div>
<div role="slider"
class="van-slider__button-wrapper van-slider__button-wrapper--right"
tabindex="0"
aria-valuemin="0"
aria-valuenow="40"
aria-valuemax="100"
aria-orientation="horizontal"
>
<div class="van-slider__button">
</div>
</div>
</div>
</div>
`;
exports[`should render reversed slider correctly 1`] = `
<div class="van-slider">
<div class="van-slider__bar"
style="width: 25%; right: 0%;"
>
<div role="slider"
class="van-slider__button-wrapper van-slider__button-wrapper--left"
tabindex="0"
aria-valuemin="0"
aria-valuenow="25"
aria-valuemax="100"
aria-orientation="horizontal"
>
<div class="van-slider__button">
</div>
</div>
</div>
</div>
`;
exports[`should render reversed vertical slider correctly 1`] = `
<div class="van-slider van-slider--vertical">
<div class="van-slider__bar"
style="height: 25%; bottom: 0%;"
>
<div role="slider"
class="van-slider__button-wrapper van-slider__button-wrapper--left"
tabindex="0"
aria-valuemin="0"
aria-valuenow="25"
aria-valuemax="100"
aria-orientation="vertical"
>
<div class="van-slider__button">
</div>
</div>
</div>
</div>
`;

View File

@ -219,3 +219,63 @@ test('should render left-button、right-button slot correctly', async () => {
await later(); await later();
expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.html()).toMatchSnapshot();
}); });
test('should render reversed slider correctly', () => {
const wrapper = mount(Slider, {
props: {
reverse: true,
modelValue: 25,
},
});
expect(wrapper.html()).toMatchSnapshot();
});
test('should render reversed vertical slider correctly', () => {
const wrapper = mount(Slider, {
props: {
reverse: true,
vertical: true,
modelValue: 25,
},
});
expect(wrapper.html()).toMatchSnapshot();
});
test('should render reversed range slider correctly', () => {
const wrapper = mount(Slider, {
props: {
range: true,
reverse: true,
modelValue: [25, 40],
},
});
expect(wrapper.html()).toMatchSnapshot();
});
test('should update modelValue correctly after clicking the reversed slider', () => {
const wrapper = mount(Slider, {
props: {
reverse: true,
modelValue: 50,
},
});
trigger(wrapper, 'click', 100, 0);
expect(wrapper.emitted('update:modelValue')!.pop()).toEqual([0]);
});
test('should update modelValue correctly after clicking the reversed vertical slider', () => {
const wrapper = mount(Slider, {
props: {
reverse: true,
vertical: true,
modelValue: 50,
},
});
trigger(wrapper, 'click', 0, 100);
expect(wrapper.emitted('update:modelValue')!.pop()).toEqual([0]);
});