1 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
2 |
|
3 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
4 |
|
5 | import * as React from 'react';
|
6 | import { StyleSheet, I18nManager, Platform, Keyboard, StatusBar } from 'react-native';
|
7 | import { PanGestureHandler, TapGestureHandler, State } from 'react-native-gesture-handler';
|
8 | import Animated from 'react-native-reanimated';
|
9 | import DrawerProgressContext from '../utils/DrawerProgressContext';
|
10 | const {
|
11 | Clock,
|
12 | Value,
|
13 | onChange,
|
14 | clockRunning,
|
15 | startClock,
|
16 | stopClock,
|
17 | interpolate,
|
18 | spring,
|
19 | abs,
|
20 | add,
|
21 | and,
|
22 | block,
|
23 | call,
|
24 | cond,
|
25 | divide,
|
26 | eq,
|
27 | event,
|
28 | greaterThan,
|
29 | lessThan,
|
30 | max,
|
31 | min,
|
32 | multiply,
|
33 | neq,
|
34 | or,
|
35 | set,
|
36 | sub
|
37 | } = Animated;
|
38 | const TRUE = 1;
|
39 | const FALSE = 0;
|
40 | const NOOP = 0;
|
41 | const UNSET = -1;
|
42 | const PROGRESS_EPSILON = 0.05;
|
43 | const DIRECTION_LEFT = 1;
|
44 | const DIRECTION_RIGHT = -1;
|
45 | const SWIPE_DISTANCE_THRESHOLD_DEFAULT = 60;
|
46 | const SWIPE_DISTANCE_MINIMUM = 5;
|
47 | const SPRING_CONFIG = {
|
48 | stiffness: 1000,
|
49 | damping: 500,
|
50 | mass: 3,
|
51 | overshootClamping: true,
|
52 | restDisplacementThreshold: 0.01,
|
53 | restSpeedThreshold: 0.01
|
54 | };
|
55 | export default class Drawer extends React.PureComponent {
|
56 | constructor(...args) {
|
57 | super(...args);
|
58 |
|
59 | _defineProperty(this, "clock", new Clock());
|
60 |
|
61 | _defineProperty(this, "isDrawerTypeFront", new Value(this.props.drawerType === 'front' ? TRUE : FALSE));
|
62 |
|
63 | _defineProperty(this, "isOpen", new Value(this.props.open ? TRUE : FALSE));
|
64 |
|
65 | _defineProperty(this, "nextIsOpen", new Value(UNSET));
|
66 |
|
67 | _defineProperty(this, "isSwiping", new Value(FALSE));
|
68 |
|
69 | _defineProperty(this, "gestureState", new Value(State.UNDETERMINED));
|
70 |
|
71 | _defineProperty(this, "touchX", new Value(0));
|
72 |
|
73 | _defineProperty(this, "velocityX", new Value(0));
|
74 |
|
75 | _defineProperty(this, "gestureX", new Value(0));
|
76 |
|
77 | _defineProperty(this, "offsetX", new Value(0));
|
78 |
|
79 | _defineProperty(this, "position", new Value(0));
|
80 |
|
81 | _defineProperty(this, "containerWidth", new Value(0));
|
82 |
|
83 | _defineProperty(this, "drawerWidth", new Value(0));
|
84 |
|
85 | _defineProperty(this, "drawerOpacity", new Value(0));
|
86 |
|
87 | _defineProperty(this, "drawerPosition", new Value(this.props.drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT));
|
88 |
|
89 | _defineProperty(this, "touchDistanceFromDrawer", cond(this.isDrawerTypeFront, cond(eq(this.drawerPosition, DIRECTION_LEFT), max(
|
90 | sub(sub(this.touchX, this.gestureX), this.drawerWidth), 0), min(multiply(
|
91 | sub(sub(this.containerWidth, this.drawerWidth), sub(this.touchX, this.gestureX)), DIRECTION_RIGHT), 0)), 0));
|
92 |
|
93 | _defineProperty(this, "swipeDistanceThreshold", new Value(this.props.swipeDistanceThreshold !== undefined ? this.props.swipeDistanceThreshold : SWIPE_DISTANCE_THRESHOLD_DEFAULT));
|
94 |
|
95 | _defineProperty(this, "swipeVelocityThreshold", new Value(this.props.swipeVelocityThreshold));
|
96 |
|
97 | _defineProperty(this, "currentOpenValue", this.props.open);
|
98 |
|
99 | _defineProperty(this, "pendingOpenValue", void 0);
|
100 |
|
101 | _defineProperty(this, "isStatusBarHidden", false);
|
102 |
|
103 | _defineProperty(this, "manuallyTriggerSpring", new Value(FALSE));
|
104 |
|
105 | _defineProperty(this, "transitionTo", isOpen => {
|
106 | const toValue = new Value(0);
|
107 | const frameTime = new Value(0);
|
108 | const state = {
|
109 | position: this.position,
|
110 | time: new Value(0),
|
111 | finished: new Value(FALSE),
|
112 | velocity: new Value(0)
|
113 | };
|
114 | return block([cond(clockRunning(this.clock), NOOP, [
|
115 |
|
116 | set(toValue, multiply(isOpen, this.drawerWidth, this.drawerPosition)), set(frameTime, 0), set(state.time, 0), set(state.finished, FALSE), set(state.velocity, this.velocityX), set(this.isOpen, isOpen), startClock(this.clock), set(this.manuallyTriggerSpring, FALSE)]), spring(this.clock, state, { ...SPRING_CONFIG,
|
117 | toValue
|
118 | }), cond(state.finished, [
|
119 | set(this.touchX, 0), set(this.gestureX, 0), set(this.velocityX, 0), set(this.offsetX, 0), // When the animation finishes, stop the clock
|
120 | stopClock(this.clock), call([this.isOpen], ([value]) => {
|
121 | const open = Boolean(value);
|
122 |
|
123 | if (open !== this.props.open) {
|
124 |
|
125 |
|
126 | this.toggleDrawer(this.props.open);
|
127 | }
|
128 | })])]);
|
129 | });
|
130 |
|
131 | _defineProperty(this, "dragX", block([onChange(this.isOpen, call([this.isOpen], ([value]) => {
|
132 | const open = Boolean(value);
|
133 | this.currentOpenValue = open;
|
134 |
|
135 | if (open !== this.props.open) {
|
136 |
|
137 | if (open) {
|
138 | this.props.onOpen();
|
139 | } else {
|
140 | this.props.onClose();
|
141 | }
|
142 |
|
143 | this.pendingOpenValue = open;
|
144 |
|
145 |
|
146 |
|
147 | this.forceUpdate();
|
148 | }
|
149 | })), onChange(this.nextIsOpen, cond(neq(this.nextIsOpen, UNSET), [
|
150 | cond(clockRunning(this.clock), stopClock(this.clock)),
|
151 | set(this.isOpen, this.nextIsOpen), set(this.gestureX, 0), set(this.nextIsOpen, UNSET)])), // This block must be after the this.isOpen listener since we check for current value
|
152 | onChange(this.isSwiping, // Listen to updates for this value only when it changes
|
153 | // Without `onChange`, this will fire even if the value didn't change
|
154 | // We don't want to call the listeners if the value didn't change
|
155 | call([this.isSwiping], ([value]) => {
|
156 | const {
|
157 | keyboardDismissMode
|
158 | } = this.props;
|
159 |
|
160 | if (value === TRUE) {
|
161 | if (keyboardDismissMode === 'on-drag') {
|
162 | Keyboard.dismiss();
|
163 | }
|
164 |
|
165 | this.toggleStatusBar(true);
|
166 | } else {
|
167 | this.toggleStatusBar(this.currentOpenValue);
|
168 | }
|
169 | })), cond(eq(this.gestureState, State.ACTIVE), [cond(this.isSwiping, NOOP, [
|
170 | set(this.isSwiping, TRUE), // Also update the drag offset to the last position
|
171 | set(this.offsetX, this.position)]), // Update position with previous offset + gesture distance
|
172 | set(this.position, add(this.offsetX, this.gestureX, this.touchDistanceFromDrawer)), // Stop animations while we're dragging
|
173 | stopClock(this.clock)], [set(this.isSwiping, FALSE), set(this.touchX, 0), this.transitionTo(cond(this.manuallyTriggerSpring, this.isOpen, cond(or(and(greaterThan(abs(this.gestureX), SWIPE_DISTANCE_MINIMUM), greaterThan(abs(this.velocityX), this.swipeVelocityThreshold)), greaterThan(abs(this.gestureX), this.swipeDistanceThreshold)), cond(eq(this.drawerPosition, DIRECTION_LEFT), // If swiped to right, open the drawer, otherwise close it
|
174 | greaterThan(cond(eq(this.velocityX, 0), this.gestureX, this.velocityX), 0), // If swiped to left, open the drawer, otherwise close it
|
175 | lessThan(cond(eq(this.velocityX, 0), this.gestureX, this.velocityX), 0)), this.isOpen)))]), this.position]));
|
176 |
|
177 | _defineProperty(this, "translateX", cond(eq(this.drawerPosition, DIRECTION_RIGHT), min(max(multiply(this.drawerWidth, -1), this.dragX), 0), max(min(this.drawerWidth, this.dragX), 0)));
|
178 |
|
179 | _defineProperty(this, "progress", cond( // Check if the drawer width is available to avoid division by zero
|
180 | eq(this.drawerWidth, 0), 0, abs(divide(this.translateX, this.drawerWidth))));
|
181 |
|
182 | _defineProperty(this, "handleGestureEvent", event([{
|
183 | nativeEvent: {
|
184 | x: this.touchX,
|
185 | translationX: this.gestureX,
|
186 | velocityX: this.velocityX
|
187 | }
|
188 | }]));
|
189 |
|
190 | _defineProperty(this, "handleGestureStateChange", event([{
|
191 | nativeEvent: {
|
192 | state: s => set(this.gestureState, s)
|
193 | }
|
194 | }]));
|
195 |
|
196 | _defineProperty(this, "handleTapStateChange", event([{
|
197 | nativeEvent: {
|
198 | oldState: s => cond(eq(s, State.ACTIVE), set(this.manuallyTriggerSpring, TRUE))
|
199 | }
|
200 | }]));
|
201 |
|
202 | _defineProperty(this, "handleContainerLayout", e => this.containerWidth.setValue(e.nativeEvent.layout.width));
|
203 |
|
204 | _defineProperty(this, "handleDrawerLayout", e => {
|
205 | this.drawerWidth.setValue(e.nativeEvent.layout.width);
|
206 | this.toggleDrawer(this.props.open);
|
207 |
|
208 |
|
209 |
|
210 | requestAnimationFrame(() => requestAnimationFrame(() => this.drawerOpacity.setValue(1)));
|
211 | });
|
212 |
|
213 | _defineProperty(this, "toggleDrawer", open => {
|
214 | if (this.currentOpenValue !== open) {
|
215 | this.nextIsOpen.setValue(open ? TRUE : FALSE);
|
216 |
|
217 |
|
218 | this.currentOpenValue = open;
|
219 | }
|
220 | });
|
221 |
|
222 | _defineProperty(this, "toggleStatusBar", hidden => {
|
223 | const {
|
224 | hideStatusBar,
|
225 | statusBarAnimation
|
226 | } = this.props;
|
227 |
|
228 | if (hideStatusBar && this.isStatusBarHidden !== hidden) {
|
229 | this.isStatusBarHidden = hidden;
|
230 | StatusBar.setHidden(hidden, statusBarAnimation);
|
231 | }
|
232 | });
|
233 | }
|
234 |
|
235 | componentDidUpdate(prevProps) {
|
236 | const {
|
237 | open,
|
238 | drawerPosition,
|
239 | drawerType,
|
240 | swipeDistanceThreshold,
|
241 | swipeVelocityThreshold,
|
242 | hideStatusBar
|
243 | } = this.props;
|
244 |
|
245 | if (
|
246 | typeof this.pendingOpenValue !== 'boolean' || open !== this.pendingOpenValue) {
|
247 | this.toggleDrawer(open);
|
248 | }
|
249 |
|
250 | this.pendingOpenValue = undefined;
|
251 |
|
252 | if (open !== prevProps.open && hideStatusBar) {
|
253 | this.toggleStatusBar(open);
|
254 | }
|
255 |
|
256 | if (prevProps.drawerPosition !== drawerPosition) {
|
257 | this.drawerPosition.setValue(drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT);
|
258 | }
|
259 |
|
260 | if (prevProps.drawerType !== drawerType) {
|
261 | this.isDrawerTypeFront.setValue(drawerType === 'front' ? TRUE : FALSE);
|
262 | }
|
263 |
|
264 | if (prevProps.swipeDistanceThreshold !== swipeDistanceThreshold) {
|
265 | this.swipeDistanceThreshold.setValue(swipeDistanceThreshold !== undefined ? swipeDistanceThreshold : SWIPE_DISTANCE_THRESHOLD_DEFAULT);
|
266 | }
|
267 |
|
268 | if (prevProps.swipeVelocityThreshold !== swipeVelocityThreshold) {
|
269 | this.swipeVelocityThreshold.setValue(swipeVelocityThreshold);
|
270 | }
|
271 | }
|
272 |
|
273 | componentWillUnmount() {
|
274 | this.toggleStatusBar(false);
|
275 | }
|
276 |
|
277 | render() {
|
278 | const {
|
279 | open,
|
280 | gestureEnabled,
|
281 | drawerPosition,
|
282 | drawerType,
|
283 | swipeEdgeWidth,
|
284 | sceneContainerStyle,
|
285 | drawerStyle,
|
286 | overlayStyle,
|
287 | onGestureRef,
|
288 | renderDrawerContent,
|
289 | renderSceneContent,
|
290 | gestureHandlerProps
|
291 | } = this.props;
|
292 | const right = drawerPosition === 'right';
|
293 | const contentTranslateX = drawerType === 'front' ? 0 : this.translateX;
|
294 | const drawerTranslateX = drawerType === 'back' ? I18nManager.isRTL ? multiply(this.drawerWidth, DIRECTION_RIGHT) : this.drawerWidth : this.translateX;
|
295 | const offset = I18nManager.isRTL ? '100%' : multiply(this.drawerWidth, -1);
|
296 |
|
297 |
|
298 | const hitSlop = right ?
|
299 |
|
300 | {
|
301 | right: 0,
|
302 | width: open ? undefined : swipeEdgeWidth
|
303 | } : {
|
304 | left: 0,
|
305 | width: open ? undefined : swipeEdgeWidth
|
306 | };
|
307 | return React.createElement(DrawerProgressContext.Provider, {
|
308 | value: this.progress
|
309 | }, React.createElement(PanGestureHandler, _extends({
|
310 | ref: onGestureRef,
|
311 | activeOffsetX: [-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM],
|
312 | failOffsetY: [-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM],
|
313 | onGestureEvent: this.handleGestureEvent,
|
314 | onHandlerStateChange: this.handleGestureStateChange,
|
315 | hitSlop: hitSlop,
|
316 | enabled: gestureEnabled
|
317 | }, gestureHandlerProps), React.createElement(Animated.View, {
|
318 | onLayout: this.handleContainerLayout,
|
319 | style: styles.main
|
320 | }, React.createElement(Animated.View, {
|
321 | style: [styles.content, {
|
322 | transform: [{
|
323 | translateX: contentTranslateX
|
324 | }]
|
325 | }, sceneContainerStyle],
|
326 | importantForAccessibility: open ? 'no-hide-descendants' : 'yes'
|
327 | }, renderSceneContent({
|
328 | progress: this.progress
|
329 | }), React.createElement(TapGestureHandler, {
|
330 | enabled: gestureEnabled,
|
331 | onHandlerStateChange: this.handleTapStateChange
|
332 | }, React.createElement(Animated.View, {
|
333 | style: [styles.overlay, {
|
334 | opacity: interpolate(this.progress, {
|
335 | inputRange: [PROGRESS_EPSILON, 1],
|
336 | outputRange: [0, 1]
|
337 | }),
|
338 |
|
339 |
|
340 |
|
341 | zIndex: cond(greaterThan(this.progress, PROGRESS_EPSILON), 0, -1)
|
342 | }, overlayStyle]
|
343 | }))), React.createElement(Animated.Code, {
|
344 | exec: block([onChange(this.manuallyTriggerSpring, [cond(eq(this.manuallyTriggerSpring, TRUE), [set(this.nextIsOpen, FALSE), call([], () => this.currentOpenValue = false)])])])
|
345 | }), /*#__PURE__*/React.createElement(Animated.View, {
|
346 | accessibilityViewIsModal: open,
|
347 | removeClippedSubviews: Platform.OS !== 'ios',
|
348 | onLayout: this.handleDrawerLayout,
|
349 | style: [styles.container, right ? {
|
350 | right: offset
|
351 | } : {
|
352 | left: offset
|
353 | }, {
|
354 | transform: [{
|
355 | translateX: drawerTranslateX
|
356 | }],
|
357 | opacity: this.drawerOpacity,
|
358 | zIndex: drawerType === 'back' ? -1 : 0
|
359 | }, drawerStyle]
|
360 | }, renderDrawerContent({
|
361 | progress: this.progress
|
362 | })))));
|
363 | }
|
364 |
|
365 | }
|
366 |
|
367 | _defineProperty(Drawer, "defaultProps", {
|
368 | gestureEnabled: true,
|
369 | drawerPostion: I18nManager.isRTL ? 'left' : 'right',
|
370 | drawerType: 'front',
|
371 | swipeEdgeWidth: 32,
|
372 | swipeVelocityThreshold: 500,
|
373 | keyboardDismissMode: 'on-drag',
|
374 | hideStatusBar: false,
|
375 | statusBarAnimation: 'slide'
|
376 | });
|
377 |
|
378 | const styles = StyleSheet.create({
|
379 | container: {
|
380 | backgroundColor: 'white',
|
381 | position: 'absolute',
|
382 | top: 0,
|
383 | bottom: 0,
|
384 | width: '80%',
|
385 | maxWidth: '100%'
|
386 | },
|
387 | overlay: { ...StyleSheet.absoluteFillObject,
|
388 | backgroundColor: 'rgba(0, 0, 0, 0.5)'
|
389 | },
|
390 | content: {
|
391 | flex: 1
|
392 | },
|
393 | main: {
|
394 | flex: 1,
|
395 | overflow: 'hidden'
|
396 | }
|
397 | });
|
398 |
|
\ | No newline at end of file |