1 | import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
2 | import _extends from "@babel/runtime/helpers/esm/extends";
|
3 | import * as React from 'react';
|
4 | import PropTypes from 'prop-types';
|
5 | import * as ReactDOM from 'react-dom';
|
6 | import { elementTypeAcceptingRef } from '@material-ui/utils';
|
7 | import { getThemeProps } from '@material-ui/styles';
|
8 | import Drawer, { getAnchor, isHorizontal } from '../Drawer/Drawer';
|
9 | import ownerDocument from '../utils/ownerDocument';
|
10 | import useEventCallback from '../utils/useEventCallback';
|
11 | import { duration } from '../styles/transitions';
|
12 | import useTheme from '../styles/useTheme';
|
13 | import { getTransitionProps } from '../transitions/utils';
|
14 | import NoSsr from '../NoSsr';
|
15 | import SwipeArea from './SwipeArea';
|
16 |
|
17 |
|
18 | const UNCERTAINTY_THRESHOLD = 3;
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | let nodeThatClaimedTheSwipe = null;
|
24 |
|
25 | export function reset() {
|
26 | nodeThatClaimedTheSwipe = null;
|
27 | }
|
28 |
|
29 | function calculateCurrentX(anchor, touches) {
|
30 | return anchor === 'right' ? document.body.offsetWidth - touches[0].pageX : touches[0].pageX;
|
31 | }
|
32 |
|
33 | function calculateCurrentY(anchor, touches) {
|
34 | return anchor === 'bottom' ? window.innerHeight - touches[0].clientY : touches[0].clientY;
|
35 | }
|
36 |
|
37 | function getMaxTranslate(horizontalSwipe, paperInstance) {
|
38 | return horizontalSwipe ? paperInstance.clientWidth : paperInstance.clientHeight;
|
39 | }
|
40 |
|
41 | function getTranslate(currentTranslate, startLocation, open, maxTranslate) {
|
42 | return Math.min(Math.max(open ? startLocation - currentTranslate : maxTranslate + startLocation - currentTranslate, 0), maxTranslate);
|
43 | }
|
44 |
|
45 | function getDomTreeShapes(element, rootNode) {
|
46 |
|
47 | let domTreeShapes = [];
|
48 |
|
49 | while (element && element !== rootNode) {
|
50 | const style = window.getComputedStyle(element);
|
51 |
|
52 | if (
|
53 | style.getPropertyValue('position') === 'absolute' ||
|
54 | style.getPropertyValue('overflow-x') === 'hidden') {
|
55 | domTreeShapes = [];
|
56 | } else if (element.clientWidth > 0 && element.scrollWidth > element.clientWidth || element.clientHeight > 0 && element.scrollHeight > element.clientHeight) {
|
57 |
|
58 |
|
59 | domTreeShapes.push(element);
|
60 | }
|
61 |
|
62 | element = element.parentElement;
|
63 | }
|
64 |
|
65 | return domTreeShapes;
|
66 | }
|
67 |
|
68 | function findNativeHandler({
|
69 | domTreeShapes,
|
70 | start,
|
71 | current,
|
72 | anchor
|
73 | }) {
|
74 |
|
75 | const axisProperties = {
|
76 | scrollPosition: {
|
77 | x: 'scrollLeft',
|
78 | y: 'scrollTop'
|
79 | },
|
80 | scrollLength: {
|
81 | x: 'scrollWidth',
|
82 | y: 'scrollHeight'
|
83 | },
|
84 | clientLength: {
|
85 | x: 'clientWidth',
|
86 | y: 'clientHeight'
|
87 | }
|
88 | };
|
89 | return domTreeShapes.some(shape => {
|
90 |
|
91 | let goingForward = current >= start;
|
92 |
|
93 | if (anchor === 'top' || anchor === 'left') {
|
94 | goingForward = !goingForward;
|
95 | }
|
96 |
|
97 | const axis = anchor === 'left' || anchor === 'right' ? 'x' : 'y';
|
98 | const scrollPosition = shape[axisProperties.scrollPosition[axis]];
|
99 | const areNotAtStart = scrollPosition > 0;
|
100 | const areNotAtEnd = scrollPosition + shape[axisProperties.clientLength[axis]] < shape[axisProperties.scrollLength[axis]];
|
101 |
|
102 | if (goingForward && areNotAtEnd || !goingForward && areNotAtStart) {
|
103 | return shape;
|
104 | }
|
105 |
|
106 | return null;
|
107 | });
|
108 | }
|
109 |
|
110 | const iOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);
|
111 | const transitionDurationDefault = {
|
112 | enter: duration.enteringScreen,
|
113 | exit: duration.leavingScreen
|
114 | };
|
115 | const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
|
116 | const SwipeableDrawer = React.forwardRef(function SwipeableDrawer(inProps, ref) {
|
117 | const theme = useTheme();
|
118 | const props = getThemeProps({
|
119 | name: 'MuiSwipeableDrawer',
|
120 | props: _extends({}, inProps),
|
121 | theme
|
122 | });
|
123 |
|
124 | const {
|
125 | anchor = 'left',
|
126 | disableBackdropTransition = false,
|
127 | disableDiscovery = false,
|
128 | disableSwipeToOpen = iOS,
|
129 | hideBackdrop,
|
130 | hysteresis = 0.52,
|
131 | minFlingVelocity = 450,
|
132 | ModalProps: {
|
133 | BackdropProps
|
134 | } = {},
|
135 | onClose,
|
136 | onOpen,
|
137 | open,
|
138 | PaperProps = {},
|
139 | SwipeAreaProps,
|
140 | swipeAreaWidth = 20,
|
141 | transitionDuration = transitionDurationDefault,
|
142 | variant = 'temporary'
|
143 | } = props,
|
144 | ModalPropsProp = _objectWithoutPropertiesLoose(props.ModalProps, ["BackdropProps"]),
|
145 | other = _objectWithoutPropertiesLoose(props, ["anchor", "disableBackdropTransition", "disableDiscovery", "disableSwipeToOpen", "hideBackdrop", "hysteresis", "minFlingVelocity", "ModalProps", "onClose", "onOpen", "open", "PaperProps", "SwipeAreaProps", "swipeAreaWidth", "transitionDuration", "variant"]);
|
146 |
|
147 | const [maybeSwiping, setMaybeSwiping] = React.useState(false);
|
148 | const swipeInstance = React.useRef({
|
149 | isSwiping: null
|
150 | });
|
151 | const swipeAreaRef = React.useRef();
|
152 | const backdropRef = React.useRef();
|
153 | const paperRef = React.useRef();
|
154 | const touchDetected = React.useRef(false);
|
155 |
|
156 | const calculatedDurationRef = React.useRef();
|
157 |
|
158 | useEnhancedEffect(() => {
|
159 | calculatedDurationRef.current = null;
|
160 | }, [open]);
|
161 | const setPosition = React.useCallback((translate, options = {}) => {
|
162 | const {
|
163 | mode = null,
|
164 | changeTransition = true
|
165 | } = options;
|
166 | const anchorRtl = getAnchor(theme, anchor);
|
167 | const rtlTranslateMultiplier = ['right', 'bottom'].indexOf(anchorRtl) !== -1 ? 1 : -1;
|
168 | const horizontalSwipe = isHorizontal(anchor);
|
169 | const transform = horizontalSwipe ? `translate(${rtlTranslateMultiplier * translate}px, 0)` : `translate(0, ${rtlTranslateMultiplier * translate}px)`;
|
170 | const drawerStyle = paperRef.current.style;
|
171 | drawerStyle.webkitTransform = transform;
|
172 | drawerStyle.transform = transform;
|
173 | let transition = '';
|
174 |
|
175 | if (mode) {
|
176 | transition = theme.transitions.create('all', getTransitionProps({
|
177 | timeout: transitionDuration
|
178 | }, {
|
179 | mode
|
180 | }));
|
181 | }
|
182 |
|
183 | if (changeTransition) {
|
184 | drawerStyle.webkitTransition = transition;
|
185 | drawerStyle.transition = transition;
|
186 | }
|
187 |
|
188 | if (!disableBackdropTransition && !hideBackdrop) {
|
189 | const backdropStyle = backdropRef.current.style;
|
190 | backdropStyle.opacity = 1 - translate / getMaxTranslate(horizontalSwipe, paperRef.current);
|
191 |
|
192 | if (changeTransition) {
|
193 | backdropStyle.webkitTransition = transition;
|
194 | backdropStyle.transition = transition;
|
195 | }
|
196 | }
|
197 | }, [anchor, disableBackdropTransition, hideBackdrop, theme, transitionDuration]);
|
198 | const handleBodyTouchEnd = useEventCallback(event => {
|
199 | if (!touchDetected.current) {
|
200 | return;
|
201 | }
|
202 |
|
203 | nodeThatClaimedTheSwipe = null;
|
204 | touchDetected.current = false;
|
205 | setMaybeSwiping(false);
|
206 |
|
207 | if (!swipeInstance.current.isSwiping) {
|
208 | swipeInstance.current.isSwiping = null;
|
209 | return;
|
210 | }
|
211 |
|
212 | swipeInstance.current.isSwiping = null;
|
213 | const anchorRtl = getAnchor(theme, anchor);
|
214 | const horizontal = isHorizontal(anchor);
|
215 | let current;
|
216 |
|
217 | if (horizontal) {
|
218 | current = calculateCurrentX(anchorRtl, event.changedTouches);
|
219 | } else {
|
220 | current = calculateCurrentY(anchorRtl, event.changedTouches);
|
221 | }
|
222 |
|
223 | const startLocation = horizontal ? swipeInstance.current.startX : swipeInstance.current.startY;
|
224 | const maxTranslate = getMaxTranslate(horizontal, paperRef.current);
|
225 | const currentTranslate = getTranslate(current, startLocation, open, maxTranslate);
|
226 | const translateRatio = currentTranslate / maxTranslate;
|
227 |
|
228 | if (Math.abs(swipeInstance.current.velocity) > minFlingVelocity) {
|
229 |
|
230 | calculatedDurationRef.current = Math.abs((maxTranslate - currentTranslate) / swipeInstance.current.velocity) * 1000;
|
231 | }
|
232 |
|
233 | if (open) {
|
234 | if (swipeInstance.current.velocity > minFlingVelocity || translateRatio > hysteresis) {
|
235 | onClose();
|
236 | } else {
|
237 |
|
238 | setPosition(0, {
|
239 | mode: 'exit'
|
240 | });
|
241 | }
|
242 |
|
243 | return;
|
244 | }
|
245 |
|
246 | if (swipeInstance.current.velocity < -minFlingVelocity || 1 - translateRatio > hysteresis) {
|
247 | onOpen();
|
248 | } else {
|
249 |
|
250 | setPosition(getMaxTranslate(horizontal, paperRef.current), {
|
251 | mode: 'enter'
|
252 | });
|
253 | }
|
254 | });
|
255 | const handleBodyTouchMove = useEventCallback(event => {
|
256 |
|
257 | if (!paperRef.current || !touchDetected.current) {
|
258 | return;
|
259 | }
|
260 |
|
261 |
|
262 | if (nodeThatClaimedTheSwipe != null && nodeThatClaimedTheSwipe !== swipeInstance.current) {
|
263 | return;
|
264 | }
|
265 |
|
266 | const anchorRtl = getAnchor(theme, anchor);
|
267 | const horizontalSwipe = isHorizontal(anchor);
|
268 | const currentX = calculateCurrentX(anchorRtl, event.touches);
|
269 | const currentY = calculateCurrentY(anchorRtl, event.touches);
|
270 |
|
271 | if (open && paperRef.current.contains(event.target) && nodeThatClaimedTheSwipe == null) {
|
272 | const domTreeShapes = getDomTreeShapes(event.target, paperRef.current);
|
273 | const nativeHandler = findNativeHandler({
|
274 | domTreeShapes,
|
275 | start: horizontalSwipe ? swipeInstance.current.startX : swipeInstance.current.startY,
|
276 | current: horizontalSwipe ? currentX : currentY,
|
277 | anchor
|
278 | });
|
279 |
|
280 | if (nativeHandler) {
|
281 | nodeThatClaimedTheSwipe = nativeHandler;
|
282 | return;
|
283 | }
|
284 |
|
285 | nodeThatClaimedTheSwipe = swipeInstance.current;
|
286 | }
|
287 |
|
288 |
|
289 | if (swipeInstance.current.isSwiping == null) {
|
290 | const dx = Math.abs(currentX - swipeInstance.current.startX);
|
291 | const dy = Math.abs(currentY - swipeInstance.current.startY);
|
292 |
|
293 | if (dx > dy) {
|
294 | if (event.cancelable) {
|
295 | event.preventDefault();
|
296 | }
|
297 | }
|
298 |
|
299 | const definitelySwiping = horizontalSwipe ? dx > dy && dx > UNCERTAINTY_THRESHOLD : dy > dx && dy > UNCERTAINTY_THRESHOLD;
|
300 |
|
301 | if (definitelySwiping === true || (horizontalSwipe ? dy > UNCERTAINTY_THRESHOLD : dx > UNCERTAINTY_THRESHOLD)) {
|
302 | swipeInstance.current.isSwiping = definitelySwiping;
|
303 |
|
304 | if (!definitelySwiping) {
|
305 | handleBodyTouchEnd(event);
|
306 | return;
|
307 | }
|
308 |
|
309 |
|
310 | swipeInstance.current.startX = currentX;
|
311 | swipeInstance.current.startY = currentY;
|
312 |
|
313 | if (!disableDiscovery && !open) {
|
314 | if (horizontalSwipe) {
|
315 | swipeInstance.current.startX -= swipeAreaWidth;
|
316 | } else {
|
317 | swipeInstance.current.startY -= swipeAreaWidth;
|
318 | }
|
319 | }
|
320 | }
|
321 | }
|
322 |
|
323 | if (!swipeInstance.current.isSwiping) {
|
324 | return;
|
325 | }
|
326 |
|
327 | const maxTranslate = getMaxTranslate(horizontalSwipe, paperRef.current);
|
328 | let startLocation = horizontalSwipe ? swipeInstance.current.startX : swipeInstance.current.startY;
|
329 |
|
330 | if (open && !swipeInstance.current.paperHit) {
|
331 | startLocation = Math.min(startLocation, maxTranslate);
|
332 | }
|
333 |
|
334 | const translate = getTranslate(horizontalSwipe ? currentX : currentY, startLocation, open, maxTranslate);
|
335 |
|
336 | if (open) {
|
337 | if (!swipeInstance.current.paperHit) {
|
338 | const paperHit = horizontalSwipe ? currentX < maxTranslate : currentY < maxTranslate;
|
339 |
|
340 | if (paperHit) {
|
341 | swipeInstance.current.paperHit = true;
|
342 | swipeInstance.current.startX = currentX;
|
343 | swipeInstance.current.startY = currentY;
|
344 | } else {
|
345 | return;
|
346 | }
|
347 | } else if (translate === 0) {
|
348 | swipeInstance.current.startX = currentX;
|
349 | swipeInstance.current.startY = currentY;
|
350 | }
|
351 | }
|
352 |
|
353 | if (swipeInstance.current.lastTranslate === null) {
|
354 | swipeInstance.current.lastTranslate = translate;
|
355 | swipeInstance.current.lastTime = performance.now() + 1;
|
356 | }
|
357 |
|
358 | const velocity = (translate - swipeInstance.current.lastTranslate) / (performance.now() - swipeInstance.current.lastTime) * 1e3;
|
359 |
|
360 | swipeInstance.current.velocity = swipeInstance.current.velocity * 0.4 + velocity * 0.6;
|
361 | swipeInstance.current.lastTranslate = translate;
|
362 | swipeInstance.current.lastTime = performance.now();
|
363 |
|
364 | if (event.cancelable) {
|
365 | event.preventDefault();
|
366 | }
|
367 |
|
368 | setPosition(translate);
|
369 | });
|
370 | const handleBodyTouchStart = useEventCallback(event => {
|
371 |
|
372 |
|
373 | if (event.defaultPrevented) {
|
374 | return;
|
375 | }
|
376 |
|
377 |
|
378 | if (event.muiHandled) {
|
379 | return;
|
380 | }
|
381 |
|
382 |
|
383 | if (open && !backdropRef.current.contains(event.target) && !paperRef.current.contains(event.target)) {
|
384 | return;
|
385 | }
|
386 |
|
387 | const anchorRtl = getAnchor(theme, anchor);
|
388 | const horizontalSwipe = isHorizontal(anchor);
|
389 | const currentX = calculateCurrentX(anchorRtl, event.touches);
|
390 | const currentY = calculateCurrentY(anchorRtl, event.touches);
|
391 |
|
392 | if (!open) {
|
393 | if (disableSwipeToOpen || event.target !== swipeAreaRef.current) {
|
394 | return;
|
395 | }
|
396 |
|
397 | if (horizontalSwipe) {
|
398 | if (currentX > swipeAreaWidth) {
|
399 | return;
|
400 | }
|
401 | } else if (currentY > swipeAreaWidth) {
|
402 | return;
|
403 | }
|
404 | }
|
405 |
|
406 | event.muiHandled = true;
|
407 | nodeThatClaimedTheSwipe = null;
|
408 | swipeInstance.current.startX = currentX;
|
409 | swipeInstance.current.startY = currentY;
|
410 | setMaybeSwiping(true);
|
411 |
|
412 | if (!open && paperRef.current) {
|
413 |
|
414 | setPosition(getMaxTranslate(horizontalSwipe, paperRef.current) + (disableDiscovery ? 20 : -swipeAreaWidth), {
|
415 | changeTransition: false
|
416 | });
|
417 | }
|
418 |
|
419 | swipeInstance.current.velocity = 0;
|
420 | swipeInstance.current.lastTime = null;
|
421 | swipeInstance.current.lastTranslate = null;
|
422 | swipeInstance.current.paperHit = false;
|
423 | touchDetected.current = true;
|
424 | });
|
425 | React.useEffect(() => {
|
426 | if (variant === 'temporary') {
|
427 | const doc = ownerDocument(paperRef.current);
|
428 | doc.addEventListener('touchstart', handleBodyTouchStart);
|
429 | doc.addEventListener('touchmove', handleBodyTouchMove, {
|
430 | passive: false
|
431 | });
|
432 | doc.addEventListener('touchend', handleBodyTouchEnd);
|
433 | return () => {
|
434 | doc.removeEventListener('touchstart', handleBodyTouchStart);
|
435 | doc.removeEventListener('touchmove', handleBodyTouchMove, {
|
436 | passive: false
|
437 | });
|
438 | doc.removeEventListener('touchend', handleBodyTouchEnd);
|
439 | };
|
440 | }
|
441 |
|
442 | return undefined;
|
443 | }, [variant, handleBodyTouchStart, handleBodyTouchMove, handleBodyTouchEnd]);
|
444 | React.useEffect(() => () => {
|
445 |
|
446 | if (nodeThatClaimedTheSwipe === swipeInstance.current) {
|
447 | nodeThatClaimedTheSwipe = null;
|
448 | }
|
449 | }, []);
|
450 | React.useEffect(() => {
|
451 | if (!open) {
|
452 | setMaybeSwiping(false);
|
453 | }
|
454 | }, [open]);
|
455 | const handleBackdropRef = React.useCallback(instance => {
|
456 |
|
457 | backdropRef.current = ReactDOM.findDOMNode(instance);
|
458 | }, []);
|
459 | return React.createElement(React.Fragment, null, React.createElement(Drawer, _extends({
|
460 | open: variant === 'temporary' && maybeSwiping ? true : open,
|
461 | variant: variant,
|
462 | ModalProps: _extends({
|
463 | BackdropProps: _extends({}, BackdropProps, {
|
464 | ref: handleBackdropRef
|
465 | })
|
466 | }, ModalPropsProp),
|
467 | PaperProps: _extends({}, PaperProps, {
|
468 | style: _extends({
|
469 | pointerEvents: variant === 'temporary' && !open ? 'none' : ''
|
470 | }, PaperProps.style),
|
471 | ref: paperRef
|
472 | }),
|
473 | anchor: anchor,
|
474 | transitionDuration: calculatedDurationRef.current || transitionDuration,
|
475 | onClose: onClose,
|
476 | ref: ref
|
477 | }, other)), !disableSwipeToOpen && variant === 'temporary' && React.createElement(NoSsr, null, React.createElement(SwipeArea, _extends({
|
478 | anchor: anchor,
|
479 | ref: swipeAreaRef,
|
480 | width: swipeAreaWidth
|
481 | }, SwipeAreaProps))));
|
482 | });
|
483 | process.env.NODE_ENV !== "production" ? SwipeableDrawer.propTypes = {
|
484 | |
485 |
|
486 |
|
487 | anchor: PropTypes.oneOf(['left', 'top', 'right', 'bottom']),
|
488 |
|
489 | |
490 |
|
491 |
|
492 | children: PropTypes.node,
|
493 |
|
494 | |
495 |
|
496 |
|
497 |
|
498 | disableBackdropTransition: PropTypes.bool,
|
499 |
|
500 | |
501 |
|
502 |
|
503 |
|
504 | disableDiscovery: PropTypes.bool,
|
505 |
|
506 | |
507 |
|
508 |
|
509 |
|
510 | disableSwipeToOpen: PropTypes.bool,
|
511 |
|
512 | |
513 |
|
514 |
|
515 | hideBackdrop: PropTypes.bool,
|
516 |
|
517 | |
518 |
|
519 |
|
520 |
|
521 | hysteresis: PropTypes.number,
|
522 |
|
523 | |
524 |
|
525 |
|
526 |
|
527 |
|
528 | minFlingVelocity: PropTypes.number,
|
529 |
|
530 | |
531 |
|
532 |
|
533 | ModalProps: PropTypes.shape({
|
534 | BackdropProps: PropTypes.shape({
|
535 | component: elementTypeAcceptingRef
|
536 | })
|
537 | }),
|
538 |
|
539 | |
540 |
|
541 |
|
542 |
|
543 |
|
544 | onClose: PropTypes.func.isRequired,
|
545 |
|
546 | |
547 |
|
548 |
|
549 |
|
550 |
|
551 | onOpen: PropTypes.func.isRequired,
|
552 |
|
553 | |
554 |
|
555 |
|
556 | open: PropTypes.bool.isRequired,
|
557 |
|
558 | |
559 |
|
560 |
|
561 | PaperProps: PropTypes.shape({
|
562 | component: elementTypeAcceptingRef,
|
563 | style: PropTypes.object
|
564 | }),
|
565 |
|
566 | |
567 |
|
568 |
|
569 | SwipeAreaProps: PropTypes.object,
|
570 |
|
571 | |
572 |
|
573 |
|
574 |
|
575 | swipeAreaWidth: PropTypes.number,
|
576 |
|
577 | |
578 |
|
579 |
|
580 |
|
581 | transitionDuration: PropTypes.oneOfType([PropTypes.number, PropTypes.shape({
|
582 | enter: PropTypes.number,
|
583 | exit: PropTypes.number
|
584 | })]),
|
585 |
|
586 | |
587 |
|
588 |
|
589 | variant: PropTypes.oneOf(['permanent', 'persistent', 'temporary'])
|
590 | } : void 0;
|
591 | export default SwipeableDrawer; |
\ | No newline at end of file |