taro-material
Version:
Mini Program components that implement Google's Material Design.
229 lines (183 loc) • 5.94 kB
JavaScript
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
};