UNPKG

rc-drawer

Version:
526 lines (484 loc) 17.5 kB
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;