UNPKG

taro-material

Version:

Mini Program components that implement Google's Material Design.

254 lines (207 loc) 6.2 kB
import Taro from '@tarojs/taro'; import { View, Text } from '@tarojs/components'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import _isNil from 'lodash/isNil'; import _isEmpty from 'lodash/isEmpty'; import _inRange from 'lodash/inRange'; import _isFunction from 'lodash/isFunction'; import AtComponent from '../../common/component'; import AtSwipeActionOptions from './options/index'; import { delayGetClientRect, delayGetScrollOffset } from '../../common/utils'; let id = 0; export default class AtSwipeAction extends AtComponent { constructor(props) { super(...arguments); const { isOpened } = props; this.endValue = 0; this.startX = null; this.startY = null; this.maxOffsetSize = 0; this.domInfo = {}; this.isMoving = false; this.isTouching = false; this.state = { componentId: ++id, offsetSize: 0, _isOpened: isOpened, }; } getDomInfo() { this.domInfo = {}; 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]; }); } componentWillReceiveProps(nextProps) { const { isOpened } = nextProps; const { _isOpened } = this.state; if (isOpened !== _isOpened) { this._reset(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, }); } } computeTransform = value => { if (Taro.getEnv() === Taro.ENV_TYPE.ALIPAY) { return !_isNil(value) ? `translate3d(${value}px,0,0)` : null; } return value ? `translate3d(${value}px,0,0)` : null; }; handleOpened = () => { if (_isFunction(this.props.onOpened) && !this.state._isOpened) { this.props.onOpened(); } }; handleClosed = () => { if (_isFunction(this.props.onClosed) && this.state._isOpened) { this.props.onClosed(); } }; handleTouchStart = e => { const { clientX, clientY } = e.touches[0]; if (this.props.disabled) return; this.getDomInfo(); this.startX = clientX; this.startY = clientY; this.isTouching = true; }; handleTouchMove = e => { e.preventDefault(); 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 >= Math.tan((45 * Math.PI) / 180).toFixed(2); } if (this.isTouching && this.isMoving) { 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 = () => { 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); return this.handleOpened(); } this._reset(); this.handleClosed(); }; handleDomInfo = ({ width }) => { const { _isOpened } = this.state; this.maxOffsetSize = width; this._reset(_isOpened); }; handleClick = (item, index, ...arg) => { const { onClick, autoClose } = this.props; if (_isFunction(onClick)) { onClick(item, index, ...arg); } if (autoClose) { this._reset(); this.handleClosed(); } }; render() { const { offsetSize, componentId } = this.state; const { options } = this.props; const rootClass = classNames('at-swipe-action', this.props.className); return ( <View id={`swipeAction-${componentId}`} className={rootClass} onTouchMove={this.handleTouchMove} onTouchEnd={this.handleTouchEnd} onTouchStart={this.handleTouchStart} > <View className={classNames('at-swipe-action__content', { animtion: !this.isTouching, })} style={{ transform: this.computeTransform(offsetSize), }} > {this.props.children} </View> {Array.isArray(options) && options.length > 0 ? ( <AtSwipeActionOptions componentId={id} onQueryedDom={this.handleDomInfo}> {options.map((item, key) => ( <View key={key} style={item.style} onClick={this.handleClick.bind(this, item, key)} className={classNames('at-swipe-action__option', item.className)} > <Text className="option__text">{item.text}</Text> </View> ))} </AtSwipeActionOptions> ) : null} </View> ); } } AtSwipeAction.defaultProps = { isTest: false, options: [], isOpened: false, disabled: false, autoClose: false, }; AtSwipeAction.propTypes = { isTest: PropTypes.bool, isOpened: PropTypes.bool, disabled: PropTypes.bool, autoClose: PropTypes.bool, options: PropTypes.arrayOf( PropTypes.shape({ text: PropTypes.string, style: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), className: PropTypes.oneOfType([PropTypes.object, PropTypes.string, PropTypes.array]), }), ), onClick: PropTypes.func, onOpened: PropTypes.func, onClosed: PropTypes.func, };