1 | 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; }
|
2 |
|
3 | import { Background, getDefaultHeaderHeight, SafeAreaProviderCompat } from '@react-navigation/elements';
|
4 | import Color from 'color';
|
5 | import * as React from 'react';
|
6 | import { Animated, Platform, StyleSheet } from 'react-native';
|
7 | import { forModalPresentationIOS, forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators';
|
8 | import { DefaultTransition, ModalFadeTransition, ModalTransition } from '../../TransitionConfigs/TransitionPresets';
|
9 | import getDistanceForDirection from '../../utils/getDistanceForDirection';
|
10 | import { MaybeScreen, MaybeScreenContainer } from '../Screens';
|
11 | import { getIsModalPresentation } from './Card';
|
12 | import CardContainer from './CardContainer';
|
13 | const EPSILON = 0.01;
|
14 | const STATE_INACTIVE = 0;
|
15 | const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
|
16 | const STATE_ON_TOP = 2;
|
17 | const FALLBACK_DESCRIPTOR = Object.freeze({
|
18 | options: {}
|
19 | });
|
20 |
|
21 | const getInterpolationIndex = (scenes, index) => {
|
22 | const {
|
23 | cardStyleInterpolator
|
24 | } = scenes[index].descriptor.options;
|
25 |
|
26 | let interpolationIndex = 0;
|
27 |
|
28 | for (let i = index - 1; i >= 0; i--) {
|
29 | var _scenes$i;
|
30 |
|
31 | const cardStyleInterpolatorCurrent = (_scenes$i = scenes[i]) === null || _scenes$i === void 0 ? void 0 : _scenes$i.descriptor.options.cardStyleInterpolator;
|
32 |
|
33 | if (cardStyleInterpolatorCurrent !== cardStyleInterpolator) {
|
34 | break;
|
35 | }
|
36 |
|
37 | interpolationIndex++;
|
38 | }
|
39 |
|
40 | return interpolationIndex;
|
41 | };
|
42 |
|
43 | const getIsModal = (scene, interpolationIndex, isParentModal) => {
|
44 | if (isParentModal) {
|
45 | return true;
|
46 | }
|
47 |
|
48 | const {
|
49 | cardStyleInterpolator
|
50 | } = scene.descriptor.options;
|
51 | const isModalPresentation = getIsModalPresentation(cardStyleInterpolator);
|
52 | const isModal = isModalPresentation && interpolationIndex !== 0;
|
53 | return isModal;
|
54 | };
|
55 |
|
56 | const getHeaderHeights = (scenes, insets, isParentHeaderShown, isParentModal, layout, previous) => {
|
57 | return scenes.reduce((acc, curr, index) => {
|
58 | const {
|
59 | headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
|
60 | headerStyle
|
61 | } = curr.descriptor.options;
|
62 | const style = StyleSheet.flatten(headerStyle || {});
|
63 | const height = 'height' in style && typeof style.height === 'number' ? style.height : previous[curr.route.key];
|
64 | const interpolationIndex = getInterpolationIndex(scenes, index);
|
65 | const isModal = getIsModal(curr, interpolationIndex, isParentModal);
|
66 | acc[curr.route.key] = typeof height === 'number' ? height : getDefaultHeaderHeight(layout, isModal, headerStatusBarHeight);
|
67 | return acc;
|
68 | }, {});
|
69 | };
|
70 |
|
71 | const getDistanceFromOptions = (layout, descriptor) => {
|
72 | const {
|
73 | presentation,
|
74 | gestureDirection = presentation === 'modal' ? ModalTransition.gestureDirection : DefaultTransition.gestureDirection
|
75 | } = (descriptor === null || descriptor === void 0 ? void 0 : descriptor.options) || {};
|
76 | return getDistanceForDirection(layout, gestureDirection);
|
77 | };
|
78 |
|
79 | const getProgressFromGesture = (gesture, layout, descriptor) => {
|
80 | const distance = getDistanceFromOptions({
|
81 |
|
82 |
|
83 | width: Math.max(1, layout.width),
|
84 | height: Math.max(1, layout.height)
|
85 | }, descriptor);
|
86 |
|
87 | if (distance > 0) {
|
88 | return gesture.interpolate({
|
89 | inputRange: [0, distance],
|
90 | outputRange: [1, 0]
|
91 | });
|
92 | }
|
93 |
|
94 | return gesture.interpolate({
|
95 | inputRange: [distance, 0],
|
96 | outputRange: [0, 1]
|
97 | });
|
98 | };
|
99 |
|
100 | export default class CardStack extends React.Component {
|
101 | static getDerivedStateFromProps(props, state) {
|
102 | if (props.routes === state.routes && props.descriptors === state.descriptors) {
|
103 | return null;
|
104 | }
|
105 |
|
106 | const gestures = props.routes.reduce((acc, curr) => {
|
107 | const descriptor = props.descriptors[curr.key];
|
108 | const {
|
109 | animationEnabled
|
110 | } = (descriptor === null || descriptor === void 0 ? void 0 : descriptor.options) || {};
|
111 | acc[curr.key] = state.gestures[curr.key] || new Animated.Value(props.openingRouteKeys.includes(curr.key) && animationEnabled !== false ? getDistanceFromOptions(state.layout, descriptor) : 0);
|
112 | return acc;
|
113 | }, {});
|
114 | const scenes = props.routes.map((route, index, self) => {
|
115 | var _descriptor$options$h;
|
116 |
|
117 | const previousRoute = self[index - 1];
|
118 | const nextRoute = self[index + 1];
|
119 | const oldScene = state.scenes[index];
|
120 | const currentGesture = gestures[route.key];
|
121 | const previousGesture = previousRoute ? gestures[previousRoute.key] : undefined;
|
122 | const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
|
123 | const descriptor = props.descriptors[route.key] || state.descriptors[route.key] || (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
|
124 | const nextDescriptor = props.descriptors[nextRoute === null || nextRoute === void 0 ? void 0 : nextRoute.key] || state.descriptors[nextRoute === null || nextRoute === void 0 ? void 0 : nextRoute.key];
|
125 | const previousDescriptor = props.descriptors[previousRoute === null || previousRoute === void 0 ? void 0 : previousRoute.key] || state.descriptors[previousRoute === null || previousRoute === void 0 ? void 0 : previousRoute.key];
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | const optionsForTransitionConfig = index !== self.length - 1 && nextDescriptor && nextDescriptor.options.presentation !== 'transparentModal' ? nextDescriptor.options : descriptor.options;
|
133 | let defaultTransitionPreset = optionsForTransitionConfig.presentation === 'modal' ? ModalTransition : optionsForTransitionConfig.presentation === 'transparentModal' ? ModalFadeTransition : DefaultTransition;
|
134 | const {
|
135 | animationEnabled = Platform.OS !== 'web' && Platform.OS !== 'windows' && Platform.OS !== 'macos',
|
136 | gestureEnabled = Platform.OS === 'ios' && animationEnabled,
|
137 | gestureDirection = defaultTransitionPreset.gestureDirection,
|
138 | transitionSpec = defaultTransitionPreset.transitionSpec,
|
139 | cardStyleInterpolator = animationEnabled === false ? forNoAnimationCard : defaultTransitionPreset.cardStyleInterpolator,
|
140 | headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
141 | cardOverlayEnabled = Platform.OS !== 'ios' && optionsForTransitionConfig.presentation !== 'transparentModal' || getIsModalPresentation(cardStyleInterpolator)
|
142 | } = optionsForTransitionConfig;
|
143 | const headerMode = (_descriptor$options$h = descriptor.options.headerMode) !== null && _descriptor$options$h !== void 0 ? _descriptor$options$h : !(optionsForTransitionConfig.presentation === 'modal' || optionsForTransitionConfig.presentation === 'transparentModal' || (nextDescriptor === null || nextDescriptor === void 0 ? void 0 : nextDescriptor.options.presentation) === 'modal' || (nextDescriptor === null || nextDescriptor === void 0 ? void 0 : nextDescriptor.options.presentation) === 'transparentModal' || getIsModalPresentation(cardStyleInterpolator)) && Platform.OS === 'ios' && descriptor.options.header === undefined ? 'float' : 'screen';
|
144 | const scene = {
|
145 | route,
|
146 | descriptor: { ...descriptor,
|
147 | options: { ...descriptor.options,
|
148 | animationEnabled,
|
149 | cardOverlayEnabled,
|
150 | cardStyleInterpolator,
|
151 | gestureDirection,
|
152 | gestureEnabled,
|
153 | headerStyleInterpolator,
|
154 | transitionSpec,
|
155 | headerMode
|
156 | }
|
157 | },
|
158 | progress: {
|
159 | current: getProgressFromGesture(currentGesture, state.layout, descriptor),
|
160 | next: nextGesture && (nextDescriptor === null || nextDescriptor === void 0 ? void 0 : nextDescriptor.options.presentation) !== 'transparentModal' ? getProgressFromGesture(nextGesture, state.layout, nextDescriptor) : undefined,
|
161 | previous: previousGesture ? getProgressFromGesture(previousGesture, state.layout, previousDescriptor) : undefined
|
162 | },
|
163 | __memo: [state.layout, descriptor, nextDescriptor, previousDescriptor, currentGesture, nextGesture, previousGesture]
|
164 | };
|
165 |
|
166 | if (oldScene && scene.__memo.every((it, i) => {
|
167 |
|
168 | return oldScene.__memo[i] === it;
|
169 | })) {
|
170 | return oldScene;
|
171 | }
|
172 |
|
173 | return scene;
|
174 | });
|
175 | return {
|
176 | routes: props.routes,
|
177 | scenes,
|
178 | gestures,
|
179 | descriptors: props.descriptors,
|
180 | headerHeights: getHeaderHeights(scenes, props.insets, props.isParentHeaderShown, props.isParentModal, state.layout, state.headerHeights)
|
181 | };
|
182 | }
|
183 |
|
184 | constructor(_props) {
|
185 | super(_props);
|
186 |
|
187 | _defineProperty(this, "handleLayout", e => {
|
188 | const {
|
189 | height,
|
190 | width
|
191 | } = e.nativeEvent.layout;
|
192 | const layout = {
|
193 | width,
|
194 | height
|
195 | };
|
196 | this.setState((state, props) => {
|
197 | if (height === state.layout.height && width === state.layout.width) {
|
198 | return null;
|
199 | }
|
200 |
|
201 | return {
|
202 | layout,
|
203 | headerHeights: getHeaderHeights(state.scenes, props.insets, props.isParentHeaderShown, props.isParentModal, layout, state.headerHeights)
|
204 | };
|
205 | });
|
206 | });
|
207 |
|
208 | _defineProperty(this, "handleHeaderLayout", _ref => {
|
209 | let {
|
210 | route,
|
211 | height
|
212 | } = _ref;
|
213 | this.setState(_ref2 => {
|
214 | let {
|
215 | headerHeights
|
216 | } = _ref2;
|
217 | const previousHeight = headerHeights[route.key];
|
218 |
|
219 | if (previousHeight === height) {
|
220 | return null;
|
221 | }
|
222 |
|
223 | return {
|
224 | headerHeights: { ...headerHeights,
|
225 | [route.key]: height
|
226 | }
|
227 | };
|
228 | });
|
229 | });
|
230 |
|
231 | _defineProperty(this, "getFocusedRoute", () => {
|
232 | const {
|
233 | state
|
234 | } = this.props;
|
235 | return state.routes[state.index];
|
236 | });
|
237 |
|
238 | _defineProperty(this, "getPreviousScene", _ref3 => {
|
239 | let {
|
240 | route
|
241 | } = _ref3;
|
242 | const {
|
243 | getPreviousRoute
|
244 | } = this.props;
|
245 | const {
|
246 | scenes
|
247 | } = this.state;
|
248 | const previousRoute = getPreviousRoute({
|
249 | route
|
250 | });
|
251 |
|
252 | if (previousRoute) {
|
253 | const previousScene = scenes.find(scene => scene.descriptor.route.key === previousRoute.key);
|
254 | return previousScene;
|
255 | }
|
256 |
|
257 | return undefined;
|
258 | });
|
259 |
|
260 | this.state = {
|
261 | routes: [],
|
262 | scenes: [],
|
263 | gestures: {},
|
264 | layout: SafeAreaProviderCompat.initialMetrics.frame,
|
265 | descriptors: this.props.descriptors,
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 | headerHeights: {}
|
272 | };
|
273 | }
|
274 |
|
275 | render() {
|
276 | const {
|
277 | insets,
|
278 | state,
|
279 | routes,
|
280 | closingRouteKeys,
|
281 | onOpenRoute,
|
282 | onCloseRoute,
|
283 | renderHeader,
|
284 | renderScene,
|
285 | isParentHeaderShown,
|
286 | isParentModal,
|
287 | onTransitionStart,
|
288 | onTransitionEnd,
|
289 | onGestureStart,
|
290 | onGestureEnd,
|
291 | onGestureCancel,
|
292 | detachInactiveScreens = Platform.OS === 'web' || Platform.OS === 'android' || Platform.OS === 'ios'
|
293 | } = this.props;
|
294 | const {
|
295 | scenes,
|
296 | layout,
|
297 | gestures,
|
298 | headerHeights
|
299 | } = this.state;
|
300 | const focusedRoute = state.routes[state.index];
|
301 | const focusedHeaderHeight = headerHeights[focusedRoute.key];
|
302 | const isFloatHeaderAbsolute = this.state.scenes.slice(-2).some(scene => {
|
303 | var _scene$descriptor$opt;
|
304 |
|
305 | const options = (_scene$descriptor$opt = scene.descriptor.options) !== null && _scene$descriptor$opt !== void 0 ? _scene$descriptor$opt : {};
|
306 | const {
|
307 | headerMode,
|
308 | headerTransparent,
|
309 | headerShown = true
|
310 | } = options;
|
311 |
|
312 | if (headerTransparent || headerShown === false || headerMode === 'screen') {
|
313 | return true;
|
314 | }
|
315 |
|
316 | return false;
|
317 | });
|
318 | let activeScreensLimit = 1;
|
319 |
|
320 | for (let i = scenes.length - 1; i >= 0; i--) {
|
321 | const {
|
322 | options
|
323 | } = scenes[i].descriptor;
|
324 | const {
|
325 |
|
326 | detachPreviousScreen = options.presentation === 'transparentModal' ? false : getIsModalPresentation(options.cardStyleInterpolator) ? i !== scenes.map(scene => scene.descriptor.options.cardStyleInterpolator).lastIndexOf(forModalPresentationIOS) : true
|
327 | } = options;
|
328 |
|
329 | if (detachPreviousScreen === false) {
|
330 | activeScreensLimit++;
|
331 | } else {
|
332 |
|
333 |
|
334 |
|
335 | if (i <= scenes.length - 2) {
|
336 | break;
|
337 | }
|
338 | }
|
339 | }
|
340 |
|
341 | const floatingHeader = React.createElement(React.Fragment, {
|
342 | key: "header"
|
343 | }, renderHeader({
|
344 | mode: 'float',
|
345 | layout,
|
346 | scenes,
|
347 | getPreviousScene: this.getPreviousScene,
|
348 | getFocusedRoute: this.getFocusedRoute,
|
349 | onContentHeightChange: this.handleHeaderLayout,
|
350 | style: [styles.floating, isFloatHeaderAbsolute && [
|
351 | {
|
352 | height: focusedHeaderHeight
|
353 | }, styles.absolute]]
|
354 | }));
|
355 | return React.createElement(Background, null, isFloatHeaderAbsolute ? null : floatingHeader, React.createElement(MaybeScreenContainer, {
|
356 | enabled: detachInactiveScreens,
|
357 | style: styles.container,
|
358 | onLayout: this.handleLayout
|
359 | }, routes.map((route, index, self) => {
|
360 | var _scenes, _scenes2;
|
361 |
|
362 | const focused = focusedRoute.key === route.key;
|
363 | const gesture = gestures[route.key];
|
364 | const scene = scenes[index];
|
365 |
|
366 |
|
367 |
|
368 |
|
369 | let isScreenActive = 1;
|
370 |
|
371 | if (index < self.length - activeScreensLimit - 1) {
|
372 |
|
373 | isScreenActive = STATE_INACTIVE;
|
374 | } else {
|
375 | const sceneForActivity = scenes[self.length - 1];
|
376 | const outputValue = index === self.length - 1 ? STATE_ON_TOP
|
377 | : index >= self.length - activeScreensLimit ? STATE_TRANSITIONING_OR_BELOW_TOP
|
378 | : STATE_INACTIVE;
|
379 |
|
380 | isScreenActive = sceneForActivity ? sceneForActivity.progress.current.interpolate({
|
381 | inputRange: [0, 1 - EPSILON, 1],
|
382 | outputRange: [1, 1, outputValue],
|
383 | extrapolate: 'clamp'
|
384 | }) : STATE_TRANSITIONING_OR_BELOW_TOP;
|
385 | }
|
386 |
|
387 | const {
|
388 | headerShown = true,
|
389 | headerTransparent,
|
390 | headerStyle,
|
391 | headerTintColor
|
392 | } = scene.descriptor.options;
|
393 | const safeAreaInsetTop = insets.top;
|
394 | const safeAreaInsetRight = insets.right;
|
395 | const safeAreaInsetBottom = insets.bottom;
|
396 | const safeAreaInsetLeft = insets.left;
|
397 | const headerHeight = headerShown !== false ? headerHeights[route.key] : 0;
|
398 | let headerDarkContent;
|
399 |
|
400 | if (headerShown) {
|
401 | if (typeof headerTintColor === 'string') {
|
402 | headerDarkContent = Color(headerTintColor).isDark();
|
403 | } else {
|
404 | const flattenedHeaderStyle = StyleSheet.flatten(headerStyle);
|
405 |
|
406 | if (flattenedHeaderStyle && 'backgroundColor' in flattenedHeaderStyle && typeof flattenedHeaderStyle.backgroundColor === 'string') {
|
407 | headerDarkContent = !Color(flattenedHeaderStyle.backgroundColor).isDark();
|
408 | }
|
409 | }
|
410 | }
|
411 |
|
412 |
|
413 | const interpolationIndex = getInterpolationIndex(scenes, index);
|
414 | const isModal = getIsModal(scene, interpolationIndex, isParentModal);
|
415 | const isNextScreenTransparent = ((_scenes = scenes[index + 1]) === null || _scenes === void 0 ? void 0 : _scenes.descriptor.options.presentation) === 'transparentModal';
|
416 | const detachCurrentScreen = ((_scenes2 = scenes[index + 1]) === null || _scenes2 === void 0 ? void 0 : _scenes2.descriptor.options.detachPreviousScreen) !== false;
|
417 | return React.createElement(MaybeScreen, {
|
418 | key: route.key,
|
419 | style: StyleSheet.absoluteFill,
|
420 | enabled: detachInactiveScreens,
|
421 | active: isScreenActive,
|
422 | pointerEvents: "box-none"
|
423 | }, React.createElement(CardContainer, {
|
424 | index: index,
|
425 | interpolationIndex: interpolationIndex,
|
426 | modal: isModal,
|
427 | active: index === self.length - 1,
|
428 | focused: focused,
|
429 | closing: closingRouteKeys.includes(route.key),
|
430 | layout: layout,
|
431 | gesture: gesture,
|
432 | scene: scene,
|
433 | safeAreaInsetTop: safeAreaInsetTop,
|
434 | safeAreaInsetRight: safeAreaInsetRight,
|
435 | safeAreaInsetBottom: safeAreaInsetBottom,
|
436 | safeAreaInsetLeft: safeAreaInsetLeft,
|
437 | onGestureStart: onGestureStart,
|
438 | onGestureCancel: onGestureCancel,
|
439 | onGestureEnd: onGestureEnd,
|
440 | headerHeight: headerHeight,
|
441 | isParentHeaderShown: isParentHeaderShown,
|
442 | onHeaderHeightChange: this.handleHeaderLayout,
|
443 | getPreviousScene: this.getPreviousScene,
|
444 | getFocusedRoute: this.getFocusedRoute,
|
445 | headerDarkContent: headerDarkContent,
|
446 | hasAbsoluteFloatHeader: isFloatHeaderAbsolute && !headerTransparent,
|
447 | renderHeader: renderHeader,
|
448 | renderScene: renderScene,
|
449 | onOpenRoute: onOpenRoute,
|
450 | onCloseRoute: onCloseRoute,
|
451 | onTransitionStart: onTransitionStart,
|
452 | onTransitionEnd: onTransitionEnd,
|
453 | isNextScreenTransparent: isNextScreenTransparent,
|
454 | detachCurrentScreen: detachCurrentScreen
|
455 | }));
|
456 | })), isFloatHeaderAbsolute ? floatingHeader : null);
|
457 | }
|
458 |
|
459 | }
|
460 | const styles = StyleSheet.create({
|
461 | container: {
|
462 | flex: 1
|
463 | },
|
464 | absolute: {
|
465 | position: 'absolute',
|
466 | top: 0,
|
467 | left: 0,
|
468 | right: 0
|
469 | },
|
470 | floating: {
|
471 | zIndex: 1
|
472 | }
|
473 | });
|
474 |
|
\ | No newline at end of file |