rmc-drawer
Version:
drawer ui component for react
520 lines (440 loc) • 18.6 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _defineProperty2 = require('babel-runtime/helpers/defineProperty');
var _defineProperty3 = _interopRequireDefault(_defineProperty2);
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _typeof2 = require('babel-runtime/helpers/typeof');
var _typeof3 = _interopRequireDefault(_typeof2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
function getOffset(ele) {
var el = ele;
var _x = 0;
var _y = 0;
while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
_x += el.offsetLeft - el.scrollLeft;
_y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: _y, left: _x };
}
var CANCEL_DISTANCE_ON_SCROLL = 20;
var Drawer = function (_React$Component) {
(0, _inherits3['default'])(Drawer, _React$Component);
function Drawer(props) {
(0, _classCallCheck3['default'])(this, Drawer);
var _this = (0, _possibleConstructorReturn3['default'])(this, (Drawer.__proto__ || Object.getPrototypeOf(Drawer)).call(this, props));
_this.onOverlayClicked = function () {
if (_this.props.open) {
// see https://github.com/react-component/drawer/issues/9
setTimeout(function () {
_this.props.onOpenChange(false, { overlayClicked: true });
}, 0);
}
};
_this.onTouchStart = function (ev) {
// filter out if a user starts swiping with a second finger
if (!_this.isTouching()) {
var touch = ev.targetTouches[0];
_this.setState({
touchIdentifier: !_this.notTouch ? touch.identifier : null,
touchStartX: touch.clientX,
touchStartY: touch.clientY,
touchCurrentX: touch.clientX,
touchCurrentY: touch.clientY
});
}
};
_this.onTouchMove = function (ev) {
// ev.preventDefault(); // cannot touchmove with FastClick
if (_this.isTouching()) {
for (var ind = 0; ind < ev.targetTouches.length; ind++) {
// we only care about the finger that we are tracking
if (ev.targetTouches[ind].identifier === _this.state.touchIdentifier) {
_this.setState({
touchCurrentX: ev.targetTouches[ind].clientX,
touchCurrentY: ev.targetTouches[ind].clientY
});
break;
}
}
}
};
_this.onTouchEnd = function () {
_this.notTouch = false;
if (_this.isTouching()) {
// trigger a change to open if sidebar has been dragged beyond dragToggleDistance
var touchWidth = _this.touchSidebarWidth();
if (_this.props.open && touchWidth < _this.state.sidebarWidth - _this.props.dragToggleDistance || !_this.props.open && touchWidth > _this.props.dragToggleDistance) {
_this.props.onOpenChange(!_this.props.open);
}
var touchHeight = _this.touchSidebarHeight();
if (_this.props.open && touchHeight < _this.state.sidebarHeight - _this.props.dragToggleDistance || !_this.props.open && touchHeight > _this.props.dragToggleDistance) {
_this.props.onOpenChange(!_this.props.open);
}
_this.setState({
touchIdentifier: null,
touchStartX: null,
touchStartY: null,
touchCurrentX: null,
touchCurrentY: null
});
}
};
_this.onScroll = function () {
if (_this.isTouching() && _this.inCancelDistanceOnScroll()) {
_this.setState({
touchIdentifier: null,
touchStartX: null,
touchStartY: null,
touchCurrentX: null,
touchCurrentY: null
});
}
};
_this.inCancelDistanceOnScroll = function () {
var cancelDistanceOnScroll = void 0;
switch (_this.props.position) {
case 'right':
cancelDistanceOnScroll = Math.abs(_this.state.touchCurrentX - _this.state.touchStartX) < CANCEL_DISTANCE_ON_SCROLL;
break;
case 'bottom':
cancelDistanceOnScroll = Math.abs(_this.state.touchCurrentY - _this.state.touchStartY) < CANCEL_DISTANCE_ON_SCROLL;
break;
case 'top':
cancelDistanceOnScroll = Math.abs(_this.state.touchStartY - _this.state.touchCurrentY) < CANCEL_DISTANCE_ON_SCROLL;
break;
case 'left':
default:
cancelDistanceOnScroll = Math.abs(_this.state.touchStartX - _this.state.touchCurrentX) < CANCEL_DISTANCE_ON_SCROLL;
}
return cancelDistanceOnScroll;
};
_this.isTouching = function () {
return _this.state.touchIdentifier !== null;
};
_this.saveSidebarSize = function () {
var sidebar = _reactDom2['default'].findDOMNode(_this.refs.sidebar);
var width = sidebar.offsetWidth;
var height = sidebar.offsetHeight;
var sidebarTop = getOffset(_reactDom2['default'].findDOMNode(_this.refs.sidebar)).top;
var dragHandleTop = getOffset(_reactDom2['default'].findDOMNode(_this.refs.dragHandle)).top;
if (width !== _this.state.sidebarWidth) {
_this.setState({ sidebarWidth: width });
}
if (height !== _this.state.sidebarHeight) {
_this.setState({ sidebarHeight: height });
}
if (sidebarTop !== _this.state.sidebarTop) {
_this.setState({ sidebarTop: sidebarTop });
}
if (dragHandleTop !== _this.state.dragHandleTop) {
_this.setState({ dragHandleTop: dragHandleTop });
}
};
_this.touchSidebarWidth = function () {
// if the sidebar is open and start point of drag is inside the sidebar
// we will only drag the distance they moved their finger
// otherwise we will move the sidebar to be below the finger.
if (_this.props.position === 'right') {
if (_this.props.open && window.innerWidth - _this.state.touchStartX < _this.state.sidebarWidth) {
if (_this.state.touchCurrentX > _this.state.touchStartX) {
return _this.state.sidebarWidth + _this.state.touchStartX - _this.state.touchCurrentX;
}
return _this.state.sidebarWidth;
}
return Math.min(window.innerWidth - _this.state.touchCurrentX, _this.state.sidebarWidth);
}
if (_this.props.position === 'left') {
if (_this.props.open && _this.state.touchStartX < _this.state.sidebarWidth) {
if (_this.state.touchCurrentX > _this.state.touchStartX) {
return _this.state.sidebarWidth;
}
return _this.state.sidebarWidth - _this.state.touchStartX + _this.state.touchCurrentX;
}
return Math.min(_this.state.touchCurrentX, _this.state.sidebarWidth);
}
};
_this.touchSidebarHeight = function () {
// if the sidebar is open and start point of drag is inside the sidebar
// we will only drag the distance they moved their finger
// otherwise we will move the sidebar to be below the finger.
if (_this.props.position === 'bottom') {
if (_this.props.open && window.innerHeight - _this.state.touchStartY < _this.state.sidebarHeight) {
if (_this.state.touchCurrentY > _this.state.touchStartY) {
return _this.state.sidebarHeight + _this.state.touchStartY - _this.state.touchCurrentY;
}
return _this.state.sidebarHeight;
}
return Math.min(window.innerHeight - _this.state.touchCurrentY, _this.state.sidebarHeight);
}
if (_this.props.position === 'top') {
var touchStartOffsetY = _this.state.touchStartY - _this.state.sidebarTop;
if (_this.props.open && touchStartOffsetY < _this.state.sidebarHeight) {
if (_this.state.touchCurrentY > _this.state.touchStartY) {
return _this.state.sidebarHeight;
}
return _this.state.sidebarHeight - _this.state.touchStartY + _this.state.touchCurrentY;
}
return Math.min(_this.state.touchCurrentY - _this.state.dragHandleTop, _this.state.sidebarHeight);
}
};
_this.renderStyle = function (_ref) {
var sidebarStyle = _ref.sidebarStyle,
isTouching = _ref.isTouching,
overlayStyle = _ref.overlayStyle,
contentStyle = _ref.contentStyle;
if (_this.props.position === 'right' || _this.props.position === 'left') {
sidebarStyle.transform = 'translateX(0%)';
sidebarStyle.WebkitTransform = 'translateX(0%)';
if (isTouching) {
var percentage = _this.touchSidebarWidth() / _this.state.sidebarWidth;
// slide open to what we dragged
if (_this.props.position === 'right') {
sidebarStyle.transform = 'translateX(' + (1 - percentage) * 100 + '%)';
sidebarStyle.WebkitTransform = 'translateX(' + (1 - percentage) * 100 + '%)';
}
if (_this.props.position === 'left') {
sidebarStyle.transform = 'translateX(-' + (1 - percentage) * 100 + '%)';
sidebarStyle.WebkitTransform = 'translateX(-' + (1 - percentage) * 100 + '%)';
}
// fade overlay to match distance of drag
overlayStyle.opacity = percentage;
overlayStyle.visibility = 'visible';
}
if (contentStyle) {
contentStyle[_this.props.position] = _this.state.sidebarWidth + 'px';
}
}
if (_this.props.position === 'top' || _this.props.position === 'bottom') {
sidebarStyle.transform = 'translateY(0%)';
sidebarStyle.WebkitTransform = 'translateY(0%)';
if (isTouching) {
var _percentage = _this.touchSidebarHeight() / _this.state.sidebarHeight;
// slide open to what we dragged
if (_this.props.position === 'bottom') {
sidebarStyle.transform = 'translateY(' + (1 - _percentage) * 100 + '%)';
sidebarStyle.WebkitTransform = 'translateY(' + (1 - _percentage) * 100 + '%)';
}
if (_this.props.position === 'top') {
sidebarStyle.transform = 'translateY(-' + (1 - _percentage) * 100 + '%)';
sidebarStyle.WebkitTransform = 'translateY(-' + (1 - _percentage) * 100 + '%)';
}
// fade overlay to match distance of drag
overlayStyle.opacity = _percentage;
overlayStyle.visibility = 'visible';
}
if (contentStyle) {
contentStyle[_this.props.position] = _this.state.sidebarHeight + 'px';
}
}
};
_this.state = {
// the detected width of the sidebar in pixels
sidebarWidth: 0,
sidebarHeight: 0,
sidebarTop: 0,
dragHandleTop: 0,
// keep track of touching params
touchIdentifier: null,
touchStartX: null,
touchStartY: null,
touchCurrentX: null,
touchCurrentY: null,
// if touch is supported by the browser
touchSupported: (typeof window === 'undefined' ? 'undefined' : (0, _typeof3['default'])(window)) === 'object' && 'ontouchstart' in window
};
return _this;
}
(0, _createClass3['default'])(Drawer, [{
key: 'componentDidMount',
value: function componentDidMount() {
this.saveSidebarSize();
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
// filter out the updates when we're touching
if (!this.isTouching()) {
this.saveSidebarSize();
}
}
// This logic helps us prevents the user from sliding the sidebar horizontally
// while scrolling the sidebar vertically. When a scroll event comes in, we're
// cancelling the ongoing gesture if it did not move horizontally much.
// True if the on going gesture X distance is less than the cancel distance
// calculate the sidebarWidth based on current touch info
// calculate the sidebarHeight based on current touch info
}, {
key: 'render',
value: function render() {
var _rootCls,
_this2 = this;
var _props = this.props,
className = _props.className,
style = _props.style,
prefixCls = _props.prefixCls,
position = _props.position,
transitions = _props.transitions,
touch = _props.touch,
enableDragHandle = _props.enableDragHandle,
sidebar = _props.sidebar,
children = _props.children,
docked = _props.docked,
open = _props.open;
var sidebarStyle = (0, _extends3['default'])({}, this.props.sidebarStyle);
var contentStyle = (0, _extends3['default'])({}, this.props.contentStyle);
var overlayStyle = (0, _extends3['default'])({}, this.props.overlayStyle);
var rootCls = (_rootCls = {}, (0, _defineProperty3['default'])(_rootCls, className, !!className), (0, _defineProperty3['default'])(_rootCls, prefixCls, true), (0, _defineProperty3['default'])(_rootCls, prefixCls + '-' + position, true), _rootCls);
var rootProps = { style: style };
var isTouching = this.isTouching();
if (isTouching) {
this.renderStyle({ sidebarStyle: sidebarStyle, isTouching: true, overlayStyle: overlayStyle });
} else if (docked) {
if (this.state.sidebarWidth !== 0) {
rootCls[prefixCls + '-docked'] = true;
this.renderStyle({ sidebarStyle: sidebarStyle, contentStyle: contentStyle });
}
} else if (open) {
rootCls[prefixCls + '-open'] = true;
this.renderStyle({ sidebarStyle: sidebarStyle });
overlayStyle.opacity = 1;
overlayStyle.visibility = 'visible';
}
if (isTouching || !transitions) {
sidebarStyle.transition = 'none';
sidebarStyle.WebkitTransition = 'none';
contentStyle.transition = 'none';
overlayStyle.transition = 'none';
}
var dragHandle = null;
if (this.state.touchSupported && touch) {
if (open) {
rootProps.onTouchStart = function (ev) {
_this2.notTouch = true;
_this2.onTouchStart(ev);
};
rootProps.onTouchMove = this.onTouchMove;
rootProps.onTouchEnd = this.onTouchEnd;
rootProps.onTouchCancel = this.onTouchEnd;
rootProps.onScroll = this.onScroll;
} else if (enableDragHandle) {
dragHandle = _react2['default'].createElement('div', { className: prefixCls + '-draghandle', style: this.props.dragHandleStyle,
onTouchStart: this.onTouchStart, onTouchMove: this.onTouchMove,
onTouchEnd: this.onTouchEnd, onTouchCancel: this.onTouchEnd,
ref: 'dragHandle'
});
}
}
// const evt = {};
// // FastClick use touchstart instead of click
// if (this.state.touchSupported) {
// evt.onTouchStart = () => {
// this.notTouch = true;
// this.onOverlayClicked();
// };
// evt.onTouchEnd = () => {
// this.notTouch = false;
// this.setState({
// touchIdentifier: null,
// });
// };
// } else {
// evt.onClick = this.onOverlayClicked;
// }
return _react2['default'].createElement(
'div',
(0, _extends3['default'])({ className: (0, _classnames2['default'])(rootCls) }, rootProps),
_react2['default'].createElement(
'div',
{ className: prefixCls + '-sidebar', style: sidebarStyle,
ref: 'sidebar'
},
sidebar
),
_react2['default'].createElement('div', { className: prefixCls + '-overlay',
style: overlayStyle,
role: 'presentation',
ref: 'overlay',
onClick: this.onOverlayClicked
}),
_react2['default'].createElement(
'div',
{ className: prefixCls + '-content', style: contentStyle,
ref: 'content'
},
dragHandle,
children
)
);
}
}]);
return Drawer;
}(_react2['default'].Component);
Drawer.propTypes = {
prefixCls: _propTypes2['default'].string,
className: _propTypes2['default'].string,
// main content to render
children: _propTypes2['default'].node.isRequired,
// styles
// styles: PropTypes.shape({
// dragHandle: PropTypes.object,
// }),
style: _propTypes2['default'].object,
sidebarStyle: _propTypes2['default'].object,
contentStyle: _propTypes2['default'].object,
overlayStyle: _propTypes2['default'].object,
dragHandleStyle: _propTypes2['default'].object,
// sidebar content to render
sidebar: _propTypes2['default'].node.isRequired,
// boolean if sidebar should be docked
docked: _propTypes2['default'].bool,
// boolean if sidebar should slide open
open: _propTypes2['default'].bool,
// boolean if transitions should be disabled
transitions: _propTypes2['default'].bool,
// boolean if touch gestures are enabled
touch: _propTypes2['default'].bool,
enableDragHandle: _propTypes2['default'].bool,
// where to place the sidebar
position: _propTypes2['default'].oneOf(['left', 'right', 'top', 'bottom']),
// distance we have to drag the sidebar to toggle open state
dragToggleDistance: _propTypes2['default'].number,
// callback called when the overlay is clicked
onOpenChange: _propTypes2['default'].func
};
Drawer.defaultProps = {
prefixCls: 'rmc-drawer',
sidebarStyle: {},
contentStyle: {},
overlayStyle: {},
dragHandleStyle: {},
docked: false,
open: false,
transitions: true,
touch: true,
enableDragHandle: true,
position: 'left',
dragToggleDistance: 30,
onOpenChange: function onOpenChange() {}
};
exports['default'] = Drawer;
module.exports = exports['default'];