Skip to content

Commit

Permalink
feat: add swipeable functionality (#1618)
Browse files Browse the repository at this point in the history
* feat: add swipeable mixin

* feat(MdDrawer): swipeable drawer

* docs(MdDrawer): add swipeable prop

* feat: allow dynamic swipeElement

* feat: reset swiped value after touchend

* feat: tweaks of default values

* fix: wrong axis for restraints

* feat(MdTabs): swipeable tabs

* feat(MdTabs): allow swipeable only on tabs content

* feat: change naming to mdSwipeElement

* feat(MdDrawer): use parent of drawer as swipe element

* docs: add api docs for drawer and tabs
  • Loading branch information
Samuell1 authored Jul 12, 2018
1 parent e6a967b commit 4bed8cc
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 3 deletions.
37 changes: 37 additions & 0 deletions docs/app/pages/Components/Drawer/Drawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@
<api-table :headings="drawer.props.headings" :props="drawer.props.props" slot="props" />
<api-table :headings="drawer.events.headings" :props="drawer.events.props" slot="events" />
</api-item>

<api-item title="API - Swipeable">
<p>The following options can be applied to any <code>md-drawer</code> component that is using <code>md-swipeable</code> prop.</p>

<api-table :headings="swipeable.props.headings" :props="swipeable.props.props" slot="props" />
</api-item>
</page-container>
</template>

Expand All @@ -67,6 +73,31 @@
name: 'DocDrawer',
mixins: [examples],
data: () => ({
swipeable: {
props: {
headings: ['Name', 'Description', 'Default'],
props: [
{
name: 'md-swipe-threshold',
type: 'Number',
description: 'The minimal distance traveled to be considered swipe.',
defaults: '50'
},
{
name: 'md-swipe-restraint',
type: 'Number',
description: 'The maximum distance allowed at the same time in perpendicular direction.',
defaults: '100'
},
{
name: 'md-swipe-time',
type: 'Number',
description: 'The maximum time allowed to detect swipe.',
defaults: '400'
},
]
}
},
drawer: {
props: {
headings: ['Name', 'Description', 'Default'],
Expand All @@ -77,6 +108,12 @@
description: 'Option used to trigger the drawer visibility. Should be used with the <code>.sync</code> modifier.',
defaults: 'false'
},
{
name: 'md-swipeable',
type: 'Boolean',
description: 'Option used to enable touch support to be opened/closed by swipe. For more option see API - Swipeable',
defaults: 'false'
},
{
name: 'md-fixed',
type: 'Boolean',
Expand Down
2 changes: 1 addition & 1 deletion docs/app/pages/Components/Drawer/examples/Temporary.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</div>
</md-toolbar>

<md-drawer :md-active.sync="showNavigation">
<md-drawer :md-active.sync="showNavigation" md-swipeable>
<md-toolbar class="md-transparent" md-elevation="0">
<span class="md-title">My App name</span>
</md-toolbar>
Expand Down
37 changes: 37 additions & 0 deletions docs/app/pages/Components/Tabs/Tabs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@

<api-table :headings="tab.headings" :props="tab.props" slot="props" />
</api-item>

<api-item title="API - Swipeable">
<p>The following options can be applied to any <code>md-tabs</code> component that is using <code>md-swipeable</code> prop.</p>

<api-table :headings="swipeable.props.headings" :props="swipeable.props.props" slot="props" />
</api-item>
</div>
</page-container>
</template>
Expand All @@ -70,6 +76,31 @@
name: 'DocTabs',
mixins: [examples],
data: () => ({
swipeable: {
props: {
headings: ['Name', 'Description', 'Default'],
props: [
{
name: 'md-swipe-threshold',
type: 'Number',
description: 'The minimal distance traveled to be considered swipe.',
defaults: '50'
},
{
name: 'md-swipe-restraint',
type: 'Number',
description: 'The maximum distance allowed at the same time in perpendicular direction.',
defaults: '100'
},
{
name: 'md-swipe-time',
type: 'Number',
description: 'The maximum time allowed to detect swipe.',
defaults: '400'
},
]
}
},
tabs: {
props: {
headings: ['Name', 'Description', 'Default'],
Expand All @@ -80,6 +111,12 @@
description: 'Set the current selected tab. Works by providing the id of the desired <code>md-tab</code>.',
defaults: 'null'
},
{
name: 'md-swipeable',
type: 'Boolean',
description: 'Option used to enable touch support to move between tabs by swipe. For more option see API - Swipeable',
defaults: 'false'
},
{
name: 'md-sync-route',
type: 'Boolean',
Expand Down
11 changes: 11 additions & 0 deletions src/components/MdDrawer/MdDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@
import MdOverlay from 'components/MdOverlay/MdOverlay'
import MdPropValidator from 'core/utils/MdPropValidator'
import MdSwipeable from 'core/mixins/MdSwipeable/MdSwipeable'
export default new MdComponent({
name: 'MdDrawer',
mixins: [MdSwipeable],
components: {
MdOverlay
},
Expand Down Expand Up @@ -43,6 +46,11 @@
} else {
this.$emit('md-closed')
}
},
swiped (value) {
if (value === 'right' || value === 'left') {
this.$emit('update:mdActive', value === 'right')
}
}
},
computed: {
Expand Down Expand Up @@ -89,6 +97,9 @@
if (this.mdPermanent) {
return this.mdPermanent
}
},
mdSwipeElement () {
return this.$el.parentNode
}
},
methods: {
Expand Down
24 changes: 22 additions & 2 deletions src/components/MdTabs/MdTabs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<span class="md-tabs-indicator" :style="indicatorStyles" :class="indicatorClass" ref="indicator"></span>
</div>

<md-content class="md-tabs-content" :style="contentStyles" v-show="hasContent">
<md-content ref="tabsContent" class="md-tabs-content" :style="contentStyles" v-show="hasContent">
<div class="md-tabs-container" :style="containerStyles">
<slot />
</div>
Expand All @@ -41,10 +41,11 @@
import MdPropValidator from 'core/utils/MdPropValidator'
import MdObserveElement from 'core/utils/MdObserveElement'
import MdContent from 'components/MdContent/MdContent'
import MdSwipeable from 'core/mixins/MdSwipeable/MdSwipeable'
export default new MdComponent({
name: 'MdTabs',
mixins: [MdAssetIcon],
mixins: [MdAssetIcon, MdSwipeable],
components: {
MdContent
},
Expand Down Expand Up @@ -93,6 +94,9 @@
},
navigationClasses () {
return 'md-elevation-' + this.mdElevation
},
mdSwipeElement () {
return this.$refs.tabsContent.$el
}
},
watch: {
Expand All @@ -112,6 +116,15 @@
mdActiveTab (tab) {
this.activeTab = tab
this.$emit('md-changed', tab)
},
swiped (value) {
const { keys } = this.getItemsAndKeys()
const max = keys.length || 0
if (this.activeTabIndex < max && value === 'right') {
this.setSwipeActiveTabByIndex(this.activeTabIndex + 1)
} else if (this.activeTabIndex > 0 && value === 'left') {
this.setSwipeActiveTabByIndex(this.activeTabIndex - 1)
}
}
},
methods: {
Expand All @@ -137,6 +150,13 @@
this.activeTabIndex = [].indexOf.call(activeButton.parentNode.childNodes, activeButton)
}
},
setSwipeActiveTabByIndex (index) {
const { keys } = this.getItemsAndKeys()
if (keys) {
this.activeTab = keys[index]
}
},
setActiveTabByIndex (index) {
const { keys } = this.getItemsAndKeys()
Expand Down
87 changes: 87 additions & 0 deletions src/core/mixins/MdSwipeable/MdSwipeable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
export default {
props: {
mdSwipeable: Boolean,
mdSwipeThreshold: {
type: Number,
default: 150
},
mdSwipeRestraint: {
type: Number,
default: 100
},
mdSwipeTime: {
type: Number,
default: 300
}
},
data: () => ({
swipeStart: false,
swipeStartTime: null,
swiped: null,
touchPosition: {
startX: 0,
startY: 0
}
}),
computed: {
getSwipeElement () {
return this.mdSwipeElement || window
}
},
methods: {
handleTouchStart (event) {
this.touchPosition.startX = event.touches[0].screenX
this.touchPosition.startY = event.touches[0].screenY
this.swipeStartTime = new Date()

this.swipeStart = true
},
handleTouchMove (event) {
if (!this.swipeStart) {
return
}

const touchmoveX = event.touches[0].screenX
const touchmoveY = event.touches[0].screenY

const actualX = touchmoveX - this.touchPosition.startX
const actualY = touchmoveY - this.touchPosition.startY

const elapsedTime = new Date() - this.swipeStartTime

if (elapsedTime <= this.mdSwipeTime) {
if (Math.abs(actualX) >= this.mdSwipeThreshold && Math.abs(actualY) <= this.mdSwipeRestraint) {
this.swiped = actualX < 0
? 'left'
: 'right'
} else if (Math.abs(actualY) >= this.mdSwipeThreshold && Math.abs(actualX) <= this.mdSwipeRestraint) {
this.swiped = actualY < 0
? 'up'
: 'down'
}
}
},
handleTouchEnd () {
this.touchPosition = {
startX: 0,
startY: 0
}
this.swiped = null
this.swipeStart = false
},
},
mounted () {
if (this.mdSwipeable) {
this.getSwipeElement.addEventListener('touchstart', this.handleTouchStart, false)
this.getSwipeElement.addEventListener('touchend', this.handleTouchEnd, false)
this.getSwipeElement.addEventListener('touchmove', this.handleTouchMove, false)
}
},
beforeDestroy () {
if (this.mdSwipeable) {
this.getSwipeElement.removeEventListener('touchstart', this.handleTouchStart, false)
this.getSwipeElement.removeEventListener('touchend', this.handleTouchEnd, false)
this.getSwipeElement.removeEventListener('touchmove', this.handleTouchMove, false)
}
}
}

0 comments on commit 4bed8cc

Please sign in to comment.