rc-drawer
Version:
drawer component for react
526 lines (484 loc) • 17.5 kB
JavaScript
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn';
import _inherits from 'babel-runtime/helpers/inherits';
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import ContainerRender from 'rc-util/es/ContainerRender';
import getScrollBarSize from 'rc-util/es/getScrollBarSize';
import { dataToArray, transitionEnd, addEventListener, removeEventListener, transformArguments } from './utils';
var IS_REACT_16 = 'createPortal' in ReactDOM;
var currentDrawer = {};
var windowIsUndefined = typeof window === 'undefined';
var Drawer = function (_React$PureComponent) {
_inherits(Drawer, _React$PureComponent);
function Drawer(props) {
_classCallCheck(this, Drawer);
var _this = _possibleConstructorReturn(this, _React$PureComponent.call(this, props));
_initialiseProps.call(_this);
_this.levelDom = [];
_this.contextDom = null;
_this.maskDom = null;
_this.handlerdom = null;
_this.mousePos = null;
_this.firstEnter = false; // 记录首次进入.
_this.timeout = null;
_this.drawerId = Number((Date.now() + Math.random()).toString().replace('.', Math.round(Math.random() * 9))).toString(16);
if (props.onIconClick || props.parent || props.iconChild || props.width // eslint-disable-line react/prop-types
|| props.handleChild) {
// eslint-disable-line react/prop-types
console.warn( // eslint-disable-line no-console
'rc-drawer-menu API has been changed, please look at the releases, ' + 'https://github.com/react-component/drawer-menu/releases');
}
var open = props.open !== undefined ? props.open : !!props.defaultOpen;
currentDrawer[_this.drawerId] = open;
_this.state = {
open: open
};
return _this;
}
Drawer.prototype.componentDidMount = function componentDidMount() {
if (!windowIsUndefined) {
var passiveSupported = false;
window.addEventListener('test', null, Object.defineProperty({}, 'passive', {
get: function get() {
passiveSupported = true;
return null;
}
}));
this.passive = passiveSupported ? { passive: false } : false;
}
var open = this.getOpen();
if (this.props.handler || open) {
this.getDefault(this.props);
this.forceUpdate();
}
};
Drawer.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
var open = nextProps.open,
placement = nextProps.placement;
if (open !== undefined && open !== this.props.open) {
this.isOpenChange = true;
// 没渲染 dom 时,获取默认数据;
if (!this.container) {
this.getDefault(nextProps);
}
this.setState({
open: open
});
}
if (placement !== this.props.placement) {
// test 的 bug, 有动画过场,删除 dom
this.contextDom = null;
}
if (this.props.level !== nextProps.level) {
this.getParentAndLevelDom(nextProps);
}
};
Drawer.prototype.componentDidUpdate = function componentDidUpdate() {
// dom 没渲染时,重走一遍。
if (!this.firstEnter && this.container) {
this.forceUpdate();
this.firstEnter = true;
}
};
Drawer.prototype.componentWillUnmount = function componentWillUnmount() {
if (this.container) {
this.setLevelDomTransform(false, true);
// 拦不住。。直接删除;
if (this.props.getContainer) {
this.container.parentNode.removeChild(this.container);
}
}
clearTimeout(this.timeout);
delete currentDrawer[this.drawerId];
// suppport react15
if (IS_REACT_16) {
return;
}
this.renderComponent({
afterClose: this.removeContainer,
onClose: function onClose() {},
visible: false
});
};
Drawer.prototype.render = function render() {
var _this2 = this;
var _props = this.props,
getContainer = _props.getContainer,
wrapperClassName = _props.wrapperClassName;
var open = this.getOpen();
currentDrawer[this.drawerId] = open ? this.container : open;
var children = this.getChildToRender(this.firstEnter ? open : false);
if (!getContainer) {
return React.createElement(
'div',
{
className: wrapperClassName,
ref: function ref(c) {
_this2.container = c;
}
},
children
);
}
if (!this.container || !open && !this.firstEnter) {
return null;
}
// suppport react15
if (!IS_REACT_16) {
return React.createElement(
ContainerRender,
{
parent: this,
visible: true,
autoMount: true,
autoDestroy: false,
getComponent: this.getChildToRender,
getContainer: this.getContainer
},
function (_ref) {
var renderComponent = _ref.renderComponent,
removeContainer = _ref.removeContainer;
_this2.renderComponent = renderComponent;
_this2.removeContainer = removeContainer;
return null;
}
);
}
return ReactDOM.createPortal(children, this.container);
};
return Drawer;
}(React.PureComponent);
Drawer.defaultProps = {
prefixCls: 'drawer',
placement: 'left',
getContainer: 'body',
level: 'all',
duration: '.3s',
ease: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)',
onChange: function onChange() {},
onMaskClick: function onMaskClick() {},
onHandleClick: function onHandleClick() {},
handler: React.createElement(
'div',
{ className: 'drawer-handle' },
React.createElement('i', { className: 'drawer-handle-icon' })
),
showMask: true,
maskStyle: {},
wrapperClassName: '',
className: ''
};
var _initialiseProps = function _initialiseProps() {
var _this3 = this;
this.onMaskTouchEnd = function (e) {
_this3.props.onMaskClick(e);
_this3.onTouchEnd(e, true);
};
this.onIconTouchEnd = function (e) {
_this3.props.onHandleClick(e);
_this3.onTouchEnd(e);
};
this.onTouchEnd = function (e, close) {
if (_this3.props.open !== undefined) {
return;
}
var open = close || _this3.state.open;
_this3.isOpenChange = true;
_this3.setState({
open: !open
});
};
this.onWrapperTransitionEnd = function () {
_this3.dom.style.transition = '';
};
this.getDefault = function (props) {
_this3.getParentAndLevelDom(props);
if (props.getContainer || props.parent) {
_this3.container = _this3.defaultGetContainer();
}
};
this.getContainer = function () {
return _this3.container;
};
this.getParentAndLevelDom = function (props) {
if (windowIsUndefined) {
return;
}
var level = props.level,
getContainer = props.getContainer;
_this3.levelDom = [];
if (getContainer) {
if (typeof getContainer === 'string') {
var dom = document.querySelectorAll(getContainer)[0];
_this3.parent = dom;
}
if (typeof getContainer === 'function') {
_this3.parent = getContainer();
}
if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) {
_this3.parent = getContainer;
}
}
if (!getContainer && _this3.container) {
_this3.parent = _this3.container.parentNode;
}
if (level === 'all') {
var children = Array.prototype.slice.call(_this3.parent.children);
children.forEach(function (child) {
if (child.nodeName !== 'SCRIPT' && child.nodeName !== 'STYLE' && child !== _this3.container) {
_this3.levelDom.push(child);
}
});
} else if (level) {
dataToArray(level).forEach(function (key) {
document.querySelectorAll(key).forEach(function (item) {
_this3.levelDom.push(item);
});
});
}
};
this.setLevelDomTransform = function (open, openTransition, placementName, value) {
var _props2 = _this3.props,
placement = _props2.placement,
levelMove = _props2.levelMove,
duration = _props2.duration,
ease = _props2.ease,
onChange = _props2.onChange;
if (!windowIsUndefined) {
// self = this;
_this3.levelDom.forEach(function (dom) {
if (_this3.isOpenChange || openTransition) {
/* eslint no-param-reassign: "error" */
dom.style.transition = 'transform ' + duration + ' ' + ease;
addEventListener(dom, transitionEnd, _this3.trnasitionEnd);
var levelValue = open ? value : 0;
if (levelMove) {
var $levelMove = transformArguments(levelMove, { target: dom, open: open });
levelValue = open ? $levelMove[0] : $levelMove[1] || 0;
}
var placementPos = placement === 'left' || placement === 'top' ? levelValue : -levelValue;
dom.style.transform = placementPos ? placementName + '(' + placementPos + 'px)' : '';
}
});
// 处理 body 滚动
var eventArray = ['touchstart'];
var domArray = [document.body, _this3.maskDom, _this3.handlerdom, _this3.contextDom];
var right = getScrollBarSize();
var widthTransition = 'width ' + duration + ' ' + ease;
var trannsformTransition = 'transform ' + duration + ' ' + ease;
if (open && document.body.style.overflow !== 'hidden') {
document.body.style.overflow = 'hidden';
if (right) {
document.body.style.position = 'relative';
document.body.style.width = 'calc(100% - ' + right + 'px)';
_this3.dom.style.transition = 'none';
switch (placement) {
case 'right':
_this3.dom.style.transform = 'translateX(-' + right + 'px)';
break;
case 'top':
case 'bottom':
_this3.dom.style.width = 'calc(100% - ' + right + 'px)';
break;
default:
break;
}
clearTimeout(_this3.timeout);
_this3.timeout = setTimeout(function () {
_this3.dom.style.transition = trannsformTransition + ',' + widthTransition;
_this3.dom.style.width = '';
_this3.dom.style.transform = '';
});
}
// 手机禁滚
if (document.body.addEventListener) {
domArray.forEach(function (item, i) {
if (!item) {
return;
}
item.addEventListener(eventArray[i] || 'touchmove', i ? _this3.removeMoveHandler : _this3.removeStartHandler, _this3.passive);
});
}
} else if (!Object.keys(currentDrawer).some(function (key) {
return currentDrawer[key];
})) {
document.body.style.overflow = '';
if (_this3.isOpenChange && right) {
document.body.style.position = '';
document.body.style.width = '';
_this3.dom.style.transition = 'none';
switch (placement) {
case 'right':
{
_this3.dom.style.transform = 'translateX(' + right + 'px)';
_this3.maskDom.style.right = right + 'px';
_this3.maskDom.style.width = 'calc(100% + ' + right + 'px)';
break;
}
case 'top':
case 'bottom':
{
_this3.dom.style.width = 'calc(100% + ' + right + 'px)';
break;
}
default:
break;
}
clearTimeout(_this3.timeout);
_this3.timeout = setTimeout(function () {
_this3.dom.style.transition = trannsformTransition + ',' + widthTransition;
_this3.dom.style.transform = '';
_this3.dom.style.width = '';
});
}
if (document.body.removeEventListener) {
domArray.forEach(function (item, i) {
if (!item) {
return;
}
item.removeEventListener(eventArray[i] || 'touchmove', i ? _this3.removeMoveHandler : _this3.removeStartHandler, _this3.passive);
});
}
}
}
if (onChange && _this3.isOpenChange && _this3.firstEnter) {
onChange(open);
_this3.isOpenChange = false;
}
};
this.getChildToRender = function (open) {
var _classnames;
var _props3 = _this3.props,
className = _props3.className,
prefixCls = _props3.prefixCls,
style = _props3.style,
placement = _props3.placement,
children = _props3.children,
handler = _props3.handler,
showMask = _props3.showMask,
maskStyle = _props3.maskStyle;
var wrapperClassname = classnames(prefixCls, (_classnames = {}, _classnames[prefixCls + '-' + placement] = true, _classnames[prefixCls + '-open'] = open, _classnames[className] = !!className, _classnames));
var value = _this3.contextDom ? _this3.contextDom.getBoundingClientRect()[placement === 'left' || placement === 'right' ? 'width' : 'height'] : 0;
var placementName = 'translate' + (placement === 'left' || placement === 'right' ? 'X' : 'Y');
// 百分比与像素动画不同步,第一次打用后全用像素动画。
var defaultValue = !_this3.contextDom ? '100%' : value + 'px';
var placementPos = placement === 'left' || placement === 'top' ? '-' + defaultValue : defaultValue;
var transform = open ? '' : placementName + '(' + placementPos + ')';
if (_this3.isOpenChange === undefined || _this3.isOpenChange) {
_this3.setLevelDomTransform(open, false, placementName, value);
}
var handlerCildren = handler && React.cloneElement(handler, {
onClick: function onClick(e) {
if (handler.props.onClick) {
handler.props.onClick();
}
_this3.onIconTouchEnd(e);
},
ref: function ref(c) {
_this3.handlerdom = c;
}
});
return React.createElement(
'div',
{
className: wrapperClassname,
style: style,
ref: function ref(c) {
_this3.dom = c;
},
onTransitionEnd: _this3.onWrapperTransitionEnd
},
showMask && React.createElement('div', {
className: prefixCls + '-mask',
onClick: _this3.onMaskTouchEnd,
style: maskStyle,
ref: function ref(c) {
_this3.maskDom = c;
}
}),
React.createElement(
'div',
{
className: prefixCls + '-content-wrapper',
style: { transform: transform }
},
React.createElement(
'div',
{
className: prefixCls + '-content',
ref: function ref(c) {
_this3.contextDom = c;
},
onTouchStart: open ? _this3.removeStartHandler : null // 跑用例用
, onTouchMove: open ? _this3.removeMoveHandler : null // 跑用例用
},
children
),
handlerCildren
)
);
};
this.getOpen = function () {
return _this3.props.open !== undefined ? _this3.props.open : _this3.state.open;
};
this.removeStartHandler = function (e) {
if (e.touches.length > 1) {
return;
}
_this3.startPos = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
};
};
this.removeMoveHandler = function (e) {
if (e.changedTouches.length > 1) {
return;
}
var currentTarget = e.currentTarget;
var differX = e.changedTouches[0].clientX - _this3.startPos.x;
var differY = e.changedTouches[0].clientY - _this3.startPos.y;
if (currentTarget === _this3.maskDom || currentTarget === _this3.handlerdom || currentTarget === _this3.contextDom && ((currentTarget.scrollTop + currentTarget.offsetHeight >= currentTarget.scrollHeight && differY < 0 || currentTarget.scrollTop <= 0 && differY > 0) && Math.max(Math.abs(differX), Math.abs(differY)) === differY || (currentTarget.scrollLeft + currentTarget.offsetWidth >= currentTarget.scrollWidth && differX < 0 || currentTarget.scrollLeft <= 0 && differX > 0) && Math.max(Math.abs(differX), Math.abs(differY)) === differX)) {
e.preventDefault();
}
};
this.trnasitionEnd = function (e) {
removeEventListener(e.target, transitionEnd, _this3.trnasitionEnd);
e.target.style.transition = '';
};
this.defaultGetContainer = function () {
if (windowIsUndefined) {
return null;
}
var container = document.createElement('div');
_this3.parent.appendChild(container);
if (_this3.props.wrapperClassName) {
container.className = _this3.props.wrapperClassName;
}
return container;
};
};
Drawer.propTypes = {
wrapperClassName: PropTypes.string,
className: PropTypes.string,
children: PropTypes.node,
style: PropTypes.object,
defaultOpen: PropTypes.bool,
open: PropTypes.bool,
prefixCls: PropTypes.string,
placement: PropTypes.string,
level: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
levelMove: PropTypes.oneOfType([PropTypes.number, PropTypes.func, PropTypes.array]),
ease: PropTypes.string,
duration: PropTypes.string,
getContainer: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object, PropTypes.bool]),
handler: PropTypes.any,
onChange: PropTypes.func,
onMaskClick: PropTypes.func,
onHandleClick: PropTypes.func,
showMask: PropTypes.bool,
maskStyle: PropTypes.object
};
export default Drawer;