diff --git a/docs/demos/views/tab.vue b/docs/demos/views/tab.vue
index f44242dde..743e892bc 100644
--- a/docs/demos/views/tab.vue
+++ b/docs/demos/views/tab.vue
@@ -58,6 +58,14 @@
+
+
+
+
+ {{ $t('content') }} {{ index }}
+
+
+
@@ -71,7 +79,8 @@ export default {
title4: '样式风格',
title5: '点击事件',
title6: '粘性布局',
- title7: '自定义标签'
+ title7: '自定义标签',
+ title8: '滑动切换'
},
'en-US': {
tab: 'Tab ',
@@ -81,7 +90,8 @@ export default {
title4: 'Card Style',
title5: 'Click Event',
title6: 'Sticky',
- title7: 'Custom Tab'
+ title7: 'Custom Tab',
+ title8: 'Swipeable'
}
},
diff --git a/docs/markdown/en-US/tab.md b/docs/markdown/en-US/tab.md
index f7d2a4e21..8cdb54a83 100644
--- a/docs/markdown/en-US/tab.md
+++ b/docs/markdown/en-US/tab.md
@@ -124,6 +124,18 @@ Use title slot to custom tab title
```
+#### Swipeable
+
+In swipeable mode, you can switch tabs with swipe gestrue in the content
+
+```html
+
+
+ content {{ index }}
+
+
+```
+
### Tabs API
| Attribute | Description | Type | Default | Accepted Values |
@@ -132,6 +144,8 @@ Use title slot to custom tab title
| active | Index of active tab | `String` `Number` | `0` | - |
| duration | Toggle tab's animation time | `Number` | `0.2` | - | - |
| swipe-threshold | Set swipe tabs threshold | `Number` | `4` | - | - |
+| sticky | Whether to use sticky mode | `Boolean` | `false` | - |
+| swipeable | Whether to switch tabs with swipe gestrue in the content | `Boolean` | `false` | - |
### Tab API
diff --git a/docs/markdown/zh-CN/tab.md b/docs/markdown/zh-CN/tab.md
index 3ac38def3..c234922cc 100644
--- a/docs/markdown/zh-CN/tab.md
+++ b/docs/markdown/zh-CN/tab.md
@@ -124,6 +124,18 @@ export default {
```
+#### 滑动切换
+
+通过`swipeable`属性可以开启滑动切换tab
+
+```html
+
+
+ 内容 {{ index }}
+
+
+```
+
### Tabs API
| 参数 | 说明 | 类型 | 默认值 | 可选 |
@@ -133,6 +145,7 @@ export default {
| duration | 切换 tab 的动画时间 | `Number` | `0.2` | - |
| swipe-threshold | 滚动阀值,设置 Tab 超过多少个可滚动 | `Number` | `4` | - |
| sticky | 是否使用粘性定位布局 | `Boolean` | `false` | - |
+| swipeable | 是否可以滑动内容切换 | `Boolean` | `false` | - |
### Tab API
diff --git a/packages/stepper/index.vue b/packages/stepper/index.vue
index 7b925c0e3..4ab313c3c 100644
--- a/packages/stepper/index.vue
+++ b/packages/stepper/index.vue
@@ -103,6 +103,7 @@ export default create({
onInput(event) {
const { value } = event.target;
this.currentValue = value ? this.correctValue(+value) : value;
+ event.target.value = this.currentValue;
this.emitInput();
},
diff --git a/packages/tabs/index.vue b/packages/tabs/index.vue
index e55607139..f5bd1c9aa 100644
--- a/packages/tabs/index.vue
+++ b/packages/tabs/index.vue
@@ -26,7 +26,7 @@
-
@@ -63,7 +63,8 @@ export default create({
swipeThreshold: {
type: Number,
default: 4
- }
+ },
+ swipeable: Boolean
},
data() {
@@ -71,7 +72,11 @@ export default create({
tabs: [],
position: 'content-top',
curActive: 0,
- navBarStyle: {}
+ navBarStyle: {},
+ pos: {
+ x: 0,
+ y: 0
+ }
};
},
@@ -115,6 +120,9 @@ export default create({
if (this.sticky) {
this.scrollHandler(true);
}
+ if (this.swipeable) {
+ this.swipeableHandler(true);
+ }
this.scrollIntoView();
});
},
@@ -124,6 +132,10 @@ export default create({
if (this.sticky) {
this.scrollHandler(false);
}
+ /* istanbul ignore next */
+ if (this.swipeable) {
+ this.swipeableHandler(false);
+ }
},
methods: {
@@ -136,6 +148,46 @@ export default create({
}
},
+ // whether to bind content swipe listener
+ swipeableHandler(init) {
+ const swipeableEl = this.$refs.content;
+
+ this.touchMoveHandler = scrollUtils.debounce(this.watchTouchMove.bind(this), 500);
+
+ (init ? on : off)(swipeableEl, 'touchstart', this.recordTouchStartPosition, true);
+ (init ? on : off)(swipeableEl, 'touchmove', this.touchMoveHandler, true);
+ },
+
+ // record swipe touch start position
+ recordTouchStartPosition(e) {
+ this.pos = {
+ x: e.touches[0].clientX,
+ y: e.touches[0].clientY
+ };
+ },
+
+ // watch swipe touch move
+ watchTouchMove(e) {
+ const { pos } = this;
+ const dx = e.touches[0].clientX - pos.x;
+ const dy = e.touches[0].clientY - pos.y;
+ const isForward = dx > 0;
+ const isHorizontal = Math.abs(dy) < Math.abs(dx);
+ const minSwipeDistance = 50;
+
+ /* istanbul ignore else */
+ if (isHorizontal && Math.abs(dx) >= minSwipeDistance) {
+ const active = +this.curActive;
+
+ /* istanbul ignore else */
+ if (isForward && active !== 0) {
+ this.curActive = active - 1;
+ } else if (!isForward && active !== this.tabs.length - 1) {
+ this.curActive = active + 1;
+ }
+ }
+ },
+
// adjust tab position
onScroll() {
const scrollTop = scrollUtils.getScrollTop(this.scrollEl);
diff --git a/packages/vant-css/src/vant-icon-743c0e.ttf b/packages/vant-css/src/vant-icon-743c0e.ttf
new file mode 100644
index 000000000..efcd83914
Binary files /dev/null and b/packages/vant-css/src/vant-icon-743c0e.ttf differ
diff --git a/test/components/tabs.vue b/test/components/tabs.vue
index bb0773a75..71486b498 100644
--- a/test/components/tabs.vue
+++ b/test/components/tabs.vue
@@ -1,5 +1,12 @@
-
+
内容一
内容二
内容三
@@ -17,7 +24,8 @@ export default {
firstTabDisabled: {
type: Boolean
},
- sticky: Boolean
+ sticky: Boolean,
+ swipeable: Boolean
},
data() {
diff --git a/test/specs/tabs.spec.js b/test/specs/tabs.spec.js
index 7dacb080f..8e7a3c2b2 100644
--- a/test/specs/tabs.spec.js
+++ b/test/specs/tabs.spec.js
@@ -2,6 +2,7 @@ import Tabs from 'packages/tabs';
import { mount } from 'avoriaz';
import TabsTestComponent from '../components/tabs';
import MoreTabsTestComponent from '../components/more-tabs';
+import { triggerTouch } from '../utils';
describe('Tabs', () => {
let wrapper;
@@ -114,7 +115,7 @@ describe('Tabs', () => {
});
});
- it('sticky', (done) => {
+ it('create a sticky tabs', (done) => {
wrapper = mount(TabsTestComponent, {
attachToDocument: true,
propsData: {
@@ -129,4 +130,34 @@ describe('Tabs', () => {
done();
}, 30);
});
+
+ it('create a swipeable tabs', (done) => {
+ wrapper = mount(TabsTestComponent, {
+ attachToDocument: true,
+ propsData: {
+ swipeable: true
+ }
+ });
+
+ const tabsContainer = wrapper.find('.van-tabs')[0];
+ const tabContent = wrapper.find('.van-tabs__content')[0];
+
+ expect(tabsContainer.vNode.child.curActive).to.equal(0);
+
+ wrapper.vm.$nextTick(() => {
+ triggerTouch(tabContent, 'touchstart', 0, 0);
+ triggerTouch(tabContent, 'touchmove', -100, 0);
+
+ setTimeout(() => {
+ expect(tabsContainer.vNode.child.curActive).to.equal(1);
+
+ triggerTouch(tabContent, 'touchstart', 0, 0);
+ triggerTouch(tabContent, 'touchmove', 100, 0);
+ setTimeout(() => {
+ expect(tabsContainer.vNode.child.curActive).to.equal(0);
+ done();
+ }, 500);
+ }, 500);
+ })
+ });
});