You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Drawer.js 16 KiB

3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
  6. var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
  7. var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
  8. var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
  9. var _createClass2 = require('babel-runtime/helpers/createClass');
  10. var _createClass3 = _interopRequireDefault(_createClass2);
  11. var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
  12. var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
  13. var _inherits2 = require('babel-runtime/helpers/inherits');
  14. var _inherits3 = _interopRequireDefault(_inherits2);
  15. var _simpleAssign = require('simple-assign');
  16. var _simpleAssign2 = _interopRequireDefault(_simpleAssign);
  17. var _react = require('react');
  18. var _react2 = _interopRequireDefault(_react);
  19. var _propTypes = require('prop-types');
  20. var _propTypes2 = _interopRequireDefault(_propTypes);
  21. var _reactDom = require('react-dom');
  22. var _reactDom2 = _interopRequireDefault(_reactDom);
  23. var _reactEventListener = require('react-event-listener');
  24. var _reactEventListener2 = _interopRequireDefault(_reactEventListener);
  25. var _keycode = require('keycode');
  26. var _keycode2 = _interopRequireDefault(_keycode);
  27. var _autoPrefix = require('../utils/autoPrefix');
  28. var _autoPrefix2 = _interopRequireDefault(_autoPrefix);
  29. var _transitions = require('../styles/transitions');
  30. var _transitions2 = _interopRequireDefault(_transitions);
  31. var _Overlay = require('../internal/Overlay');
  32. var _Overlay2 = _interopRequireDefault(_Overlay);
  33. var _Paper = require('../Paper');
  34. var _Paper2 = _interopRequireDefault(_Paper);
  35. var _propTypes3 = require('../utils/propTypes');
  36. var _propTypes4 = _interopRequireDefault(_propTypes3);
  37. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  38. var openNavEventHandler = null;
  39. var Drawer = function (_Component) {
  40. (0, _inherits3.default)(Drawer, _Component);
  41. function Drawer() {
  42. var _ref;
  43. var _temp, _this, _ret;
  44. (0, _classCallCheck3.default)(this, Drawer);
  45. for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
  46. args[_key] = arguments[_key];
  47. }
  48. return _ret = (_temp = (_this = (0, _possibleConstructorReturn3.default)(this, (_ref = Drawer.__proto__ || (0, _getPrototypeOf2.default)(Drawer)).call.apply(_ref, [this].concat(args))), _this), _this.handleClickOverlay = function (event) {
  49. event.preventDefault();
  50. _this.close('clickaway');
  51. }, _this.handleKeyUp = function (event) {
  52. if (_this.state.open && !_this.props.docked && (0, _keycode2.default)(event) === 'esc') {
  53. _this.close('escape');
  54. }
  55. }, _this.onBodyTouchStart = function (event) {
  56. var swipeAreaWidth = _this.props.swipeAreaWidth;
  57. var touchStartX = _this.context.muiTheme.isRtl ? document.body.offsetWidth - event.touches[0].pageX : event.touches[0].pageX;
  58. var touchStartY = event.touches[0].pageY;
  59. // Open only if swiping from far left (or right) while closed
  60. if (swipeAreaWidth !== null && !_this.state.open) {
  61. if (_this.props.openSecondary) {
  62. // If openSecondary is true calculate from the far right
  63. if (touchStartX < document.body.offsetWidth - swipeAreaWidth) return;
  64. } else {
  65. // If openSecondary is false calculate from the far left
  66. if (touchStartX > swipeAreaWidth) return;
  67. }
  68. }
  69. if (!_this.state.open && (openNavEventHandler !== _this.onBodyTouchStart || _this.props.disableSwipeToOpen)) {
  70. return;
  71. }
  72. _this.maybeSwiping = true;
  73. _this.touchStartX = touchStartX;
  74. _this.touchStartY = touchStartY;
  75. document.body.addEventListener('touchmove', _this.onBodyTouchMove);
  76. document.body.addEventListener('touchend', _this.onBodyTouchEnd);
  77. document.body.addEventListener('touchcancel', _this.onBodyTouchEnd);
  78. }, _this.onBodyTouchMove = function (event) {
  79. var currentX = _this.context.muiTheme.isRtl ? document.body.offsetWidth - event.touches[0].pageX : event.touches[0].pageX;
  80. var currentY = event.touches[0].pageY;
  81. if (_this.state.swiping) {
  82. event.preventDefault();
  83. _this.setPosition(_this.getTranslateX(currentX));
  84. } else if (_this.maybeSwiping) {
  85. var dXAbs = Math.abs(currentX - _this.touchStartX);
  86. var dYAbs = Math.abs(currentY - _this.touchStartY);
  87. // If the user has moved his thumb ten pixels in either direction,
  88. // we can safely make an assumption about whether he was intending
  89. // to swipe or scroll.
  90. var threshold = 10;
  91. if (dXAbs > threshold && dYAbs <= threshold) {
  92. _this.swipeStartX = currentX;
  93. _this.setState({
  94. swiping: _this.state.open ? 'closing' : 'opening'
  95. });
  96. _this.setPosition(_this.getTranslateX(currentX));
  97. } else if (dXAbs <= threshold && dYAbs > threshold) {
  98. _this.onBodyTouchEnd();
  99. }
  100. }
  101. }, _this.onBodyTouchEnd = function (event) {
  102. if (_this.state.swiping) {
  103. var currentX = _this.context.muiTheme.isRtl ? document.body.offsetWidth - event.changedTouches[0].pageX : event.changedTouches[0].pageX;
  104. var translateRatio = _this.getTranslateX(currentX) / _this.getMaxTranslateX();
  105. _this.maybeSwiping = false;
  106. var swiping = _this.state.swiping;
  107. _this.setState({
  108. swiping: null
  109. });
  110. // We have to open or close after setting swiping to null,
  111. // because only then CSS transition is enabled.
  112. if (translateRatio > 0.5) {
  113. if (swiping === 'opening') {
  114. _this.setPosition(_this.getMaxTranslateX());
  115. } else {
  116. _this.close('swipe');
  117. }
  118. } else {
  119. if (swiping === 'opening') {
  120. _this.open('swipe');
  121. } else {
  122. _this.setPosition(0);
  123. }
  124. }
  125. } else {
  126. _this.maybeSwiping = false;
  127. }
  128. _this.removeBodyTouchListeners();
  129. }, _temp), (0, _possibleConstructorReturn3.default)(_this, _ret);
  130. }
  131. (0, _createClass3.default)(Drawer, [{
  132. key: 'componentWillMount',
  133. value: function componentWillMount() {
  134. this.maybeSwiping = false;
  135. this.touchStartX = null;
  136. this.touchStartY = null;
  137. this.swipeStartX = null;
  138. this.setState({
  139. open: this.props.open !== null ? this.props.open : this.props.docked,
  140. swiping: null
  141. });
  142. }
  143. }, {
  144. key: 'componentDidMount',
  145. value: function componentDidMount() {
  146. this.enableSwipeHandling();
  147. }
  148. }, {
  149. key: 'componentWillReceiveProps',
  150. value: function componentWillReceiveProps(nextProps) {
  151. // If controlled then the open prop takes precedence.
  152. if (nextProps.open !== null) {
  153. this.setState({
  154. open: nextProps.open
  155. });
  156. // Otherwise, if docked is changed, change the open state for when uncontrolled.
  157. } else if (this.props.docked !== nextProps.docked) {
  158. this.setState({
  159. open: nextProps.docked
  160. });
  161. }
  162. }
  163. }, {
  164. key: 'componentDidUpdate',
  165. value: function componentDidUpdate() {
  166. this.enableSwipeHandling();
  167. }
  168. }, {
  169. key: 'componentWillUnmount',
  170. value: function componentWillUnmount() {
  171. this.disableSwipeHandling();
  172. this.removeBodyTouchListeners();
  173. }
  174. }, {
  175. key: 'getStyles',
  176. value: function getStyles() {
  177. var muiTheme = this.context.muiTheme;
  178. var theme = muiTheme.drawer;
  179. var x = this.getTranslateMultiplier() * (this.state.open ? 0 : this.getMaxTranslateX());
  180. var styles = {
  181. root: {
  182. height: '100%',
  183. width: this.getTranslatedWidth() || theme.width,
  184. position: 'fixed',
  185. zIndex: muiTheme.zIndex.drawer,
  186. left: 0,
  187. top: 0,
  188. transform: 'translate(' + x + 'px, 0)',
  189. transition: !this.state.swiping && _transitions2.default.easeOut(null, 'transform', null),
  190. backgroundColor: theme.color,
  191. overflow: 'auto',
  192. WebkitOverflowScrolling: 'touch' // iOS momentum scrolling
  193. },
  194. overlay: {
  195. zIndex: muiTheme.zIndex.drawerOverlay,
  196. pointerEvents: this.state.open ? 'auto' : 'none' // Bypass mouse events when left nav is closing.
  197. },
  198. rootWhenOpenRight: {
  199. left: 'auto',
  200. right: 0
  201. }
  202. };
  203. return styles;
  204. }
  205. }, {
  206. key: 'shouldShow',
  207. value: function shouldShow() {
  208. return this.state.open || !!this.state.swiping; // component is swiping
  209. }
  210. }, {
  211. key: 'close',
  212. value: function close(reason) {
  213. if (this.props.open === null) this.setState({ open: false });
  214. if (this.props.onRequestChange) this.props.onRequestChange(false, reason);
  215. return this;
  216. }
  217. }, {
  218. key: 'open',
  219. value: function open(reason) {
  220. if (this.props.open === null) this.setState({ open: true });
  221. if (this.props.onRequestChange) this.props.onRequestChange(true, reason);
  222. return this;
  223. }
  224. }, {
  225. key: 'getTranslatedWidth',
  226. value: function getTranslatedWidth() {
  227. if (typeof this.props.width === 'string') {
  228. if (!/^\d+(\.\d+)?%$/.test(this.props.width)) {
  229. throw new Error('Not a valid percentage format.');
  230. }
  231. var width = parseFloat(this.props.width) / 100.0;
  232. // We are doing our best on the Server to render a consistent UI, hence the
  233. // default value of 10000
  234. return typeof window !== 'undefined' ? width * window.innerWidth : 10000;
  235. } else {
  236. return this.props.width;
  237. }
  238. }
  239. }, {
  240. key: 'getMaxTranslateX',
  241. value: function getMaxTranslateX() {
  242. var width = this.getTranslatedWidth() || this.context.muiTheme.drawer.width;
  243. return width + 10;
  244. }
  245. }, {
  246. key: 'getTranslateMultiplier',
  247. value: function getTranslateMultiplier() {
  248. return this.props.openSecondary ? 1 : -1;
  249. }
  250. }, {
  251. key: 'enableSwipeHandling',
  252. value: function enableSwipeHandling() {
  253. if (!this.props.docked) {
  254. document.body.addEventListener('touchstart', this.onBodyTouchStart);
  255. if (!openNavEventHandler) {
  256. openNavEventHandler = this.onBodyTouchStart;
  257. }
  258. } else {
  259. this.disableSwipeHandling();
  260. }
  261. }
  262. }, {
  263. key: 'disableSwipeHandling',
  264. value: function disableSwipeHandling() {
  265. document.body.removeEventListener('touchstart', this.onBodyTouchStart);
  266. if (openNavEventHandler === this.onBodyTouchStart) {
  267. openNavEventHandler = null;
  268. }
  269. }
  270. }, {
  271. key: 'removeBodyTouchListeners',
  272. value: function removeBodyTouchListeners() {
  273. document.body.removeEventListener('touchmove', this.onBodyTouchMove);
  274. document.body.removeEventListener('touchend', this.onBodyTouchEnd);
  275. document.body.removeEventListener('touchcancel', this.onBodyTouchEnd);
  276. }
  277. }, {
  278. key: 'setPosition',
  279. value: function setPosition(translateX) {
  280. var rtlTranslateMultiplier = this.context.muiTheme.isRtl ? -1 : 1;
  281. var drawer = _reactDom2.default.findDOMNode(this.refs.clickAwayableElement);
  282. var transformCSS = 'translate(' + this.getTranslateMultiplier() * rtlTranslateMultiplier * translateX + 'px, 0)';
  283. this.refs.overlay.setOpacity(1 - translateX / this.getMaxTranslateX());
  284. _autoPrefix2.default.set(drawer.style, 'transform', transformCSS);
  285. }
  286. }, {
  287. key: 'getTranslateX',
  288. value: function getTranslateX(currentX) {
  289. return Math.min(Math.max(this.state.swiping === 'closing' ? this.getTranslateMultiplier() * (currentX - this.swipeStartX) : this.getMaxTranslateX() - this.getTranslateMultiplier() * (this.swipeStartX - currentX), 0), this.getMaxTranslateX());
  290. }
  291. }, {
  292. key: 'render',
  293. value: function render() {
  294. var _props = this.props,
  295. children = _props.children,
  296. className = _props.className,
  297. containerClassName = _props.containerClassName,
  298. containerStyle = _props.containerStyle,
  299. docked = _props.docked,
  300. openSecondary = _props.openSecondary,
  301. overlayClassName = _props.overlayClassName,
  302. overlayStyle = _props.overlayStyle,
  303. style = _props.style,
  304. zDepth = _props.zDepth;
  305. var styles = this.getStyles();
  306. var overlay = void 0;
  307. if (!docked) {
  308. overlay = _react2.default.createElement(_Overlay2.default, {
  309. ref: 'overlay',
  310. show: this.shouldShow(),
  311. className: overlayClassName,
  312. style: (0, _simpleAssign2.default)(styles.overlay, overlayStyle),
  313. transitionEnabled: !this.state.swiping,
  314. onClick: this.handleClickOverlay
  315. });
  316. }
  317. return _react2.default.createElement(
  318. 'div',
  319. {
  320. className: className,
  321. style: style
  322. },
  323. _react2.default.createElement(_reactEventListener2.default, { target: 'window', onKeyUp: this.handleKeyUp }),
  324. overlay,
  325. _react2.default.createElement(
  326. _Paper2.default,
  327. {
  328. ref: 'clickAwayableElement',
  329. zDepth: zDepth,
  330. rounded: false,
  331. transitionEnabled: !this.state.swiping,
  332. className: containerClassName,
  333. style: (0, _simpleAssign2.default)(styles.root, openSecondary && styles.rootWhenOpenRight, containerStyle)
  334. },
  335. children
  336. )
  337. );
  338. }
  339. }]);
  340. return Drawer;
  341. }(_react.Component);
  342. Drawer.defaultProps = {
  343. disableSwipeToOpen: false,
  344. docked: true,
  345. open: null,
  346. openSecondary: false,
  347. swipeAreaWidth: 30,
  348. width: null,
  349. zDepth: 2
  350. };
  351. Drawer.contextTypes = {
  352. muiTheme: _propTypes2.default.object.isRequired
  353. };
  354. Drawer.propTypes = process.env.NODE_ENV !== "production" ? {
  355. /**
  356. * The contents of the `Drawer`
  357. */
  358. children: _propTypes2.default.node,
  359. /**
  360. * The CSS class name of the root element.
  361. */
  362. className: _propTypes2.default.string,
  363. /**
  364. * The CSS class name of the container element.
  365. */
  366. containerClassName: _propTypes2.default.string,
  367. /**
  368. * Override the inline-styles of the container element.
  369. */
  370. containerStyle: _propTypes2.default.object,
  371. /**
  372. * If true, swiping sideways when the `Drawer` is closed will not open it.
  373. */
  374. disableSwipeToOpen: _propTypes2.default.bool,
  375. /**
  376. * If true, the `Drawer` will be docked. In this state, the overlay won't show and
  377. * clicking on a menu item will not close the `Drawer`.
  378. */
  379. docked: _propTypes2.default.bool,
  380. /**
  381. * Callback function fired when the `open` state of the `Drawer` is requested to be changed.
  382. *
  383. * @param {boolean} open If true, the `Drawer` was requested to be opened.
  384. * @param {string} reason The reason for the open or close request. Possible values are
  385. * 'swipe' for open requests; 'clickaway' (on overlay clicks),
  386. * 'escape' (on escape key press), and 'swipe' for close requests.
  387. */
  388. onRequestChange: _propTypes2.default.func,
  389. /**
  390. * If true, the `Drawer` is opened. Providing a value will turn the `Drawer`
  391. * into a controlled component.
  392. */
  393. open: _propTypes2.default.bool,
  394. /**
  395. * If true, the `Drawer` is positioned to open from the opposite side.
  396. */
  397. openSecondary: _propTypes2.default.bool,
  398. /**
  399. * The CSS class name to add to the `Overlay` component that is rendered behind the `Drawer`.
  400. */
  401. overlayClassName: _propTypes2.default.string,
  402. /**
  403. * Override the inline-styles of the `Overlay` component that is rendered behind the `Drawer`.
  404. */
  405. overlayStyle: _propTypes2.default.object,
  406. /**
  407. * Override the inline-styles of the root element.
  408. */
  409. style: _propTypes2.default.object,
  410. /**
  411. * The width of the left most (or right most) area in pixels where the `Drawer` can be
  412. * swiped open from. Setting this to `null` spans that area to the entire page
  413. * (**CAUTION!** Setting this property to `null` might cause issues with sliders and
  414. * swipeable `Tabs`: use at your own risk).
  415. */
  416. swipeAreaWidth: _propTypes2.default.number,
  417. /**
  418. * The width of the `Drawer` in pixels or percentage in string format ex. `50%` to fill
  419. * half of the window or `100%` and so on. Defaults to using the values from theme.
  420. */
  421. width: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]),
  422. /**
  423. * The zDepth of the `Drawer`.
  424. */
  425. zDepth: _propTypes4.default.zDepth
  426. } : {};
  427. exports.default = Drawer;