UNPKG

react-native-screens

Version:
292 lines (284 loc) 12.6 kB
"use strict"; 'use client'; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.ScreenContext = exports.InnerScreen = void 0; var _react = _interopRequireDefault(require("react")); var _reactNative = require("react-native"); var _TransitionProgressContext = _interopRequireDefault(require("../TransitionProgressContext")); var _DelayedFreeze = _interopRequireDefault(require("./helpers/DelayedFreeze")); var _core = require("../core"); var _ScreenNativeComponent = _interopRequireDefault(require("../fabric/ScreenNativeComponent")); var _ModalScreenNativeComponent = _interopRequireDefault(require("../fabric/ModalScreenNativeComponent")); var _usePrevious = require("./helpers/usePrevious"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } // Native components const AnimatedNativeScreen = _reactNative.Animated.createAnimatedComponent(_ScreenNativeComponent.default); const AnimatedNativeModalScreen = _reactNative.Animated.createAnimatedComponent(_ModalScreenNativeComponent.default); // Incomplete type, all accessible properties available at: // react-native/Libraries/Components/View/ReactNativeViewViewConfig.js // This value must be kept in sync with native side. const SHEET_FIT_TO_CONTENTS = [-1]; const SHEET_COMPAT_LARGE = [1.0]; const SHEET_COMPAT_MEDIUM = [0.5]; const SHEET_COMPAT_ALL = [0.5, 1.0]; const SHEET_DIMMED_ALWAYS = -1; // const SHEET_DIMMED_NEVER = 9999; function assertDetentsArrayIsSorted(array) { for (let i = 1; i < array.length; i++) { if (array[i - 1] > array[i]) { throw new Error('[RNScreens] The detent array is not sorted in ascending order!'); } } } // These exist to transform old 'legacy' values used by the formsheet API to the new API shape. // We can get rid of it, once we get rid of support for legacy values: 'large', 'medium', 'all'. function resolveSheetAllowedDetents(allowedDetentsCompat) { if (Array.isArray(allowedDetentsCompat)) { if (_reactNative.Platform.OS === 'android' && allowedDetentsCompat.length > 3) { if (__DEV__) { console.warn('[RNScreens] Sheets API on Android do accept only up to 3 values. Any surplus value are ignored.'); } allowedDetentsCompat = allowedDetentsCompat.slice(0, 3); } if (__DEV__) { assertDetentsArrayIsSorted(allowedDetentsCompat); } return allowedDetentsCompat; } else if (allowedDetentsCompat === 'fitToContents') { return SHEET_FIT_TO_CONTENTS; } else if (allowedDetentsCompat === 'large') { return SHEET_COMPAT_LARGE; } else if (allowedDetentsCompat === 'medium') { return SHEET_COMPAT_MEDIUM; } else if (allowedDetentsCompat === 'all') { return SHEET_COMPAT_ALL; } else { // Safe default, only large detent is allowed. return SHEET_COMPAT_LARGE; } } function resolveSheetLargestUndimmedDetent(lud, lastDetentIndex) { if (typeof lud === 'number') { if (!isIndexInClosedRange(lud, SHEET_DIMMED_ALWAYS, lastDetentIndex)) { if (__DEV__) { throw new Error("[RNScreens] Provided value of 'sheetLargestUndimmedDetentIndex' prop is out of bounds of 'sheetAllowedDetents' array."); } // Return default in production return SHEET_DIMMED_ALWAYS; } return lud; } else if (lud === 'last') { return lastDetentIndex; } else if (lud === 'none' || lud === 'all') { return SHEET_DIMMED_ALWAYS; } else if (lud === 'large') { return 1; } else if (lud === 'medium') { return 0; } else { // Safe default, every detent is dimmed return SHEET_DIMMED_ALWAYS; } } function resolveSheetInitialDetentIndex(index, lastDetentIndex) { if (index === 'last') { index = lastDetentIndex; } else if (index == null) { // Intentional check for undefined & null ^ index = 0; } if (!isIndexInClosedRange(index, 0, lastDetentIndex)) { if (__DEV__) { throw new Error("[RNScreens] Provided value of 'sheetInitialDetentIndex' prop is out of bounds of 'sheetAllowedDetents' array."); } // Return default in production return 0; } return index; } function isIndexInClosedRange(value, lowerBound, upperBound) { return Number.isInteger(value) && value >= lowerBound && value <= upperBound; } const InnerScreen = exports.InnerScreen = /*#__PURE__*/_react.default.forwardRef(function InnerScreen(props, ref) { const innerRef = _react.default.useRef(null); _react.default.useImperativeHandle(ref, () => innerRef.current, []); const prevActivityState = (0, _usePrevious.usePrevious)(props.activityState); const setRef = ref => { innerRef.current = ref; props.onComponentRef?.(ref); }; const closing = _react.default.useRef(new _reactNative.Animated.Value(0)).current; const progress = _react.default.useRef(new _reactNative.Animated.Value(0)).current; const goingForward = _react.default.useRef(new _reactNative.Animated.Value(0)).current; const { enabled = (0, _core.screensEnabled)(), freezeOnBlur = (0, _core.freezeEnabled)(), shouldFreeze, ...rest } = props; // To maintain default behavior of formSheet stack presentation style and to have reasonable // defaults for new medium-detent iOS API we need to set defaults here const { // formSheet presentation related props sheetAllowedDetents = [1.0], sheetLargestUndimmedDetentIndex = SHEET_DIMMED_ALWAYS, sheetGrabberVisible = false, sheetCornerRadius = -1.0, sheetExpandsWhenScrolledToEdge = true, sheetElevation = 24, sheetInitialDetentIndex = 0, // Other stackPresentation, // Events for override onAppear, onDisappear, onWillAppear, onWillDisappear } = rest; if (enabled && _core.isNativePlatformSupported) { const resolvedSheetAllowedDetents = resolveSheetAllowedDetents(sheetAllowedDetents); const resolvedSheetLargestUndimmedDetent = resolveSheetLargestUndimmedDetent(sheetLargestUndimmedDetentIndex, resolvedSheetAllowedDetents.length - 1); const resolvedSheetInitialDetentIndex = resolveSheetInitialDetentIndex(sheetInitialDetentIndex, resolvedSheetAllowedDetents.length - 1); // Due to how Yoga resolves layout, we need to have different components for modal nad non-modal screens (there is a need for different // shadow nodes). const shouldUseModalScreenComponent = _reactNative.Platform.select({ ios: !(stackPresentation === undefined || stackPresentation === 'push' || stackPresentation === 'containedModal' || stackPresentation === 'containedTransparentModal'), android: false, default: false }); const AnimatedScreen = shouldUseModalScreenComponent ? AnimatedNativeModalScreen : AnimatedNativeScreen; let { // Filter out active prop in this case because it is unused and // can cause problems depending on react-native version: // https://github.com/react-navigation/react-navigation/issues/4886 active, activityState, children, isNativeStack, gestureResponseDistance, onGestureCancel, style, ...props } = rest; if (active !== undefined && activityState === undefined) { console.warn('It appears that you are using old version of react-navigation library. Please update @react-navigation/bottom-tabs, @react-navigation/stack and @react-navigation/drawer to version 5.10.0 or above to take full advantage of new functionality added to react-native-screens'); activityState = active !== 0 ? 2 : 0; // in the new version, we need one of the screens to have value of 2 after the transition } if (isNativeStack && prevActivityState !== undefined && activityState !== undefined) { if (prevActivityState > activityState) { throw new Error('[RNScreens] activityState cannot be decreased in NativeStack'); } } const handleRef = ref => { // Workaround is necessary to prevent React Native from hiding frozen screens. // See this PR: https://github.com/grahammendick/navigation/pull/860 if (ref?.viewConfig?.validAttributes?.style) { ref.viewConfig.validAttributes.style = { ...ref.viewConfig.validAttributes.style, display: null }; setRef(ref); } else if (ref?._viewConfig?.validAttributes?.style) { ref._viewConfig.validAttributes.style = { ...ref._viewConfig.validAttributes.style, display: null }; setRef(ref); } }; const freeze = freezeOnBlur && (shouldFreeze !== undefined ? shouldFreeze : activityState === 0); return /*#__PURE__*/_react.default.createElement(_DelayedFreeze.default, { freeze: freeze }, /*#__PURE__*/_react.default.createElement(AnimatedScreen, _extends({}, props, { /** * This messy override is to conform NativeProps used by codegen and * our Public API. To see reasoning go to this PR: * https://github.com/software-mansion/react-native-screens/pull/2423#discussion_r1810616995 */ onAppear: onAppear, onDisappear: onDisappear, onWillAppear: onWillAppear, onWillDisappear: onWillDisappear, onGestureCancel: onGestureCancel ?? (() => { // for internal use }) // // Hierarchy of screens is handled on the native side and setting zIndex value causes this issue: // https://github.com/software-mansion/react-native-screens/issues/2345 // With below change of zIndex, we force RN diffing mechanism to NOT include detaching and attaching mutation in one transaction. // Detailed information can be found here https://github.com/software-mansion/react-native-screens/pull/2351 , style: [style, { zIndex: undefined }], activityState: activityState, sheetAllowedDetents: resolvedSheetAllowedDetents, sheetLargestUndimmedDetent: resolvedSheetLargestUndimmedDetent, sheetElevation: sheetElevation, sheetGrabberVisible: sheetGrabberVisible, sheetCornerRadius: sheetCornerRadius, sheetExpandsWhenScrolledToEdge: sheetExpandsWhenScrolledToEdge, sheetInitialDetent: resolvedSheetInitialDetentIndex, gestureResponseDistance: { start: gestureResponseDistance?.start ?? -1, end: gestureResponseDistance?.end ?? -1, top: gestureResponseDistance?.top ?? -1, bottom: gestureResponseDistance?.bottom ?? -1 } // This prevents showing blank screen when navigating between multiple screens with freezing // https://github.com/software-mansion/react-native-screens/pull/1208 , ref: handleRef, onTransitionProgress: !isNativeStack ? undefined : _reactNative.Animated.event([{ nativeEvent: { progress, closing, goingForward } }], { useNativeDriver: true }) }), !isNativeStack ? // see comment of this prop in types.tsx for information why it is needed children : /*#__PURE__*/_react.default.createElement(_TransitionProgressContext.default.Provider, { value: { progress, closing, goingForward } }, children))); } else { // same reason as above let { active, activityState, style, // eslint-disable-next-line @typescript-eslint/no-unused-vars onComponentRef, ...props } = rest; if (active !== undefined && activityState === undefined) { activityState = active !== 0 ? 2 : 0; } return /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, _extends({ style: [style, { display: activityState !== 0 ? 'flex' : 'none' }], ref: setRef }, props)); } }); // context to be used when the user wants to use enhanced implementation // e.g. to use `useReanimatedTransitionProgress` (see `reanimated` folder in repo) const ScreenContext = exports.ScreenContext = /*#__PURE__*/_react.default.createContext(InnerScreen); const Screen = /*#__PURE__*/_react.default.forwardRef((props, ref) => { const ScreenWrapper = _react.default.useContext(ScreenContext) || InnerScreen; return /*#__PURE__*/_react.default.createElement(ScreenWrapper, _extends({}, props, { ref: ref })); }); Screen.displayName = 'Screen'; var _default = exports.default = Screen; //# sourceMappingURL=Screen.js.map