Skip to content
This repository was archived by the owner on Oct 27, 2021. It is now read-only.

Commit

Permalink
feat: add SwipeAction
Browse files Browse the repository at this point in the history
  • Loading branch information
pengshanglong committed May 13, 2020
1 parent 05fc5d4 commit 5b0c2f1
Show file tree
Hide file tree
Showing 4 changed files with 423 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@tarojs/runtime": "3.0.0-beta.6",
"@tarojs/taro": "3.0.0-beta.6",
"classnames": "^2.2.6",
"lodash": "^4.17.15",
"vue": "^2.5.0",
"vue-template-compiler": "^2.5.0",
"vuex": "^3.0.0"
Expand Down
291 changes: 291 additions & 0 deletions src/components/swipe-action/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
import classNames from 'classnames'
import _inRange from 'lodash/inRange'
import _isEmpty from 'lodash/isEmpty'
import SwipeActionOptions from './options/index'
import { delayGetClientRect, delayGetScrollOffset, isTest, uuid } from '../../utils/common'

export default {
name: 'SwipeAction',
props: {
isOpened: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
autoClose: {
type: Boolean,
default: false,
},
options: {
type: Array,
default: () => [],
validator: (options) => {
return options.every((item) => {
if (typeof item === 'object') {
if (!item.text) return false
if (item.style && typeof item.style !== 'string' && typeof item.style !== 'object')
return false
if (
item.className &&
typeof item.className !== 'string' &&
typeof item.className !== 'array' &&
typeof item.className !== 'object'
)
return false

return true
} else {
return false
}
})
},
},
onClick: {
type: Function,
default: () => () => {},
},
onOpened: {
type: Function,
default: () => () => {},
},
onClosed: {
type: Function,
default: () => () => {},
},
className: {
type: [Array, String],
default: () => '',
},
},
data() {
const { isOpened } = this
return {
endValue: 0,
startX: 0,
startY: 0,
maxOffsetSize: 0,
domInfo: {
top: 0,
bottom: 0,
left: 0,
right: 0,
},
isMoving: false,
isTouching: false,
state: {
componentId: isTest() ? 'tabs-AOTU2018' : uuid(),
offsetSize: 0,
_isOpened: !!isOpened,
},
}
},
methods: {
setState(newState) {
const ks = Object.keys(newState)
if (Array.isArray(ks)) {
ks.forEach((k) => {
if (k in this.state) {
this.state[k] = newState[k]
}
})
}
},
getDomInfo() {
return Promise.all([
delayGetClientRect({
self: this,
delayTime: 0,
selectorStr: `#swipeAction-${this.state.componentId}`,
}),
delayGetScrollOffset({ delayTime: 0 }),
]).then(([rect, scrollOffset]) => {
rect[0].top += scrollOffset[0].scrollTop
rect[0].bottom += scrollOffset[0].scrollTop
this.domInfo = rect[0]
})
},
/**
*
* @param {boolean} isOpened
*/
_reset(isOpened) {
this.isMoving = false
this.isTouching = false

if (isOpened) {
this.endValue = -this.maxOffsetSize
this.setState({
_isOpened: true,
offsetSize: -this.maxOffsetSize,
})
} else {
this.endValue = 0
this.setState({
offsetSize: 0,
_isOpened: false,
})
}
},
/**
*
* @param {number} value
*/
computeTransform(value) {
return value ? `translate3d(${value}px,0,0)` : null
},
/**
*
* @param {event} event
*/
handleOpened(event) {
const { onOpened } = this
if (typeof onOpened === 'function' && !this.state._isOpened) {
onOpened(event)
}
},
/**
*
* @param {event} event
*/
handleClosed(event) {
const { onClosed } = this
if (typeof onClosed === 'function' && this.state._isOpened) {
onClosed(event)
}
},
handleTouchStart(e) {
const { clientX, clientY } = e.touches[0]

if (this.disabled) return

this.getDomInfo()

this.startX = clientX
this.startY = clientY
this.isTouching = true
},
handleTouchMove(e) {
if (_isEmpty(this.domInfo)) {
return
}

const { startX, startY } = this
const { top, bottom, left, right } = this.domInfo
const { clientX, clientY, pageX, pageY } = e.touches[0]

const x = Math.abs(clientX - startX)
const y = Math.abs(clientY - startY)

const inDom = _inRange(pageX, left, right) && _inRange(pageY, top, bottom)

if (!this.isMoving && inDom) {
this.isMoving =
y === 0 || x / y >= Number.parseFloat(Math.tan((45 * Math.PI) / 180).toFixed(2))
}

if (this.isTouching && this.isMoving) {
e.preventDefault()

const offsetSize = clientX - this.startX
const isRight = offsetSize > 0

if (this.state.offsetSize === 0 && isRight) return

const value = this.endValue + offsetSize
this.setState({
offsetSize: value >= 0 ? 0 : value,
})
}
},
handleTouchEnd(event) {
this.isTouching = false

const { offsetSize } = this.state

this.endValue = offsetSize

const breakpoint = this.maxOffsetSize / 2
const absOffsetSize = Math.abs(offsetSize)

if (absOffsetSize > breakpoint) {
this._reset(true)
this.handleOpened(event)
return
}

this._reset(false) // TODO: Check behavior
this.handleClosed(event)
},
/**
*
* @param {{width: number}} param0
*/
handleDomInfo({ width }) {
const { _isOpened } = this.state

this.maxOffsetSize = width
this._reset(_isOpened)
},
/**
*
* @param {{text: string, style?: object | string, className?: object | string | string[]}} item
* @param {number} index
* @param {event} event
*/
handleClick(item, index, event) {
const { onClick, autoClose } = this

if (typeof onClick === 'function') {
onClick(item, index, event)
}
if (autoClose) {
this._reset(false)
this.handleClosed(event)
}
},
},
render() {
const { offsetSize, componentId } = this.state
const { options } = this
const rootClass = classNames('at-swipe-action', this.className)
const transform = this.computeTransform(offsetSize)
const transformStyle = transform ? { transform } : {}

return (
<view
id={`swipeAction-${componentId}`}
class={rootClass}
onTouchMove={this.handleTouchMove}
onTouchEnd={this.handleTouchEnd}
onTouchStart={this.handleTouchStart}>
<view
class={classNames('at-swipe-action__content', {
animtion: !this.isTouching,
})}
style={transformStyle}>
{this.$slots.default}
</view>

{Array.isArray(options) && options.length > 0 ? (
<SwipeActionOptions
options={options}
componentId={componentId}
onQueryedDom={this.handleDomInfo}>
{options.map((item, key) => (
<view
key={`${item.text}-${key}`}
style={item.style}
onTap={(e) => this.handleClick(item, key, e)}
class={classNames('at-swipe-action__option', item.className)}>
<view class="option__text">{item.text}</view>
</view>
))}
</SwipeActionOptions>
) : null}
</view>
)
},
}
36 changes: 36 additions & 0 deletions src/components/swipe-action/options/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import classNames from 'classnames'
import { delayQuerySelector } from '../../../utils/common'

export default {
name: 'SwipeActionOptions',
props: {
componentId: {
type: String,
default: '',
},
onQueryedDom: {
type: Function,
default: () => () => {},
},
className: {
type: [Array, String],
default: () => '',
},
},
methods: {
trrigerOptionsDomUpadte() {
delayQuerySelector(this, `#swipeActionOptions-${this.componentId}`).then((res) => {
this.onQueryedDom(res[0])
})
},
},
render() {
const rootClass = classNames('at-swipe-action__options', this.className)

return (
<view id={`swipeActionOptions-${this.componentId}`} class={rootClass}>
{this.$slots.default}
</view>
)
},
}
Loading

0 comments on commit 5b0c2f1

Please # to comment.