UNPKG

taro-material

Version:

Mini Program components that implement Google's Material Design.

229 lines (183 loc) 5.94 kB
import Nerv from "nervjs"; import Taro from "@tarojs/taro-h5"; 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 };