UNPKG

@gorhom/bottom-sheet

Version:

A performant interactive bottom sheet with fully configurable options 🚀

422 lines (400 loc) • 14.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _portal = require("@gorhom/portal"); var _react = _interopRequireWildcard(require("react")); var _hooks = require("../../hooks"); var _utilities = require("../../utilities"); var _id = require("../../utilities/id"); var _bottomSheet = _interopRequireDefault(require("../bottomSheet")); var _constants = require("./constants"); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /** biome-ignore-all lint/correctness/useHookAtTopLevel: random error needs extra time to debug */ const INITIAL_STATE = { mount: false, data: undefined }; function BottomSheetModalComponent(props, ref) { const { // modal props name, stackBehavior = _constants.DEFAULT_STACK_BEHAVIOR, enableDismissOnClose = _constants.DEFAULT_ENABLE_DISMISS_ON_CLOSE, onDismiss: _providedOnDismiss, onAnimate: _providedOnAnimate, // bottom sheet props index = 0, snapPoints, enablePanDownToClose = true, animateOnMount = true, containerComponent: ContainerComponent = _react.default.Fragment, // callbacks onChange: _providedOnChange, // components children: Content, ...bottomSheetProps } = props; //#region state const [{ mount, data }, setState] = (0, _react.useState)(INITIAL_STATE); const mountRef = (0, _react.useRef)(mount); mountRef.current = mount; //#endregion //#region hooks const { hostName, containerLayoutState, mountSheet, unmountSheet, willUnmountSheet } = (0, _hooks.useBottomSheetModalInternal)(); const { removePortal: unmountPortal } = (0, _portal.usePortal)(hostName); //#endregion //#region refs const bottomSheetRef = (0, _react.useRef)(null); const statusRef = (0, _react.useRef)(_constants.MODAL_STATUS.INITIAL); const currentIndexRef = (0, _react.useRef)(!animateOnMount ? index : -1); const nextIndexRef = (0, _react.useRef)(null); const restoreIndexRef = (0, _react.useRef)(-1); //#endregion //#region variables const key = (0, _react.useMemo)(() => name || `bottom-sheet-modal-${(0, _id.id)()}`, [name]); //#endregion //#region private methods const resetVariables = (0, _react.useCallback)(function resetVariables() { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: resetVariables.name }); } currentIndexRef.current = -1; restoreIndexRef.current = -1; statusRef.current = _constants.MODAL_STATUS.INITIAL; }, []); const unmount = (0, _react.useCallback)(function unmount() { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: unmount.name }); } const hadReactMount = mountRef.current; // reset variables resetVariables(); // unmount sheet and portal unmountSheet(key); unmountPortal(key); // unmount the node, if sheet is still mounted in React state if (hadReactMount) { setState(INITIAL_STATE); } // fire `onDismiss` callback if (_providedOnDismiss) { _providedOnDismiss(); } }, [key, resetVariables, unmountSheet, unmountPortal, _providedOnDismiss]); //#endregion //#region bottom sheet methods const handleSnapToIndex = (0, _react.useCallback)((...args) => { if (statusRef.current === _constants.MODAL_STATUS.MINIMIZED || statusRef.current === _constants.MODAL_STATUS.MINIMIZING) { return; } bottomSheetRef.current?.snapToIndex(...args); }, []); const handleSnapToPosition = (0, _react.useCallback)((...args) => { if ([_constants.MODAL_STATUS.MINIMIZED, _constants.MODAL_STATUS.MINIMIZING, _constants.MODAL_STATUS.DISMISSED, _constants.MODAL_STATUS.DISMISSING].includes(statusRef.current)) { return; } bottomSheetRef.current?.snapToPosition(...args); }, []); const handleExpand = (0, _react.useCallback)((...args) => { if ([_constants.MODAL_STATUS.MINIMIZED, _constants.MODAL_STATUS.MINIMIZING, _constants.MODAL_STATUS.DISMISSED, _constants.MODAL_STATUS.DISMISSING].includes(statusRef.current)) { return; } bottomSheetRef.current?.expand(...args); }, []); const handleCollapse = (0, _react.useCallback)((...args) => { if ([_constants.MODAL_STATUS.MINIMIZED, _constants.MODAL_STATUS.MINIMIZING, _constants.MODAL_STATUS.DISMISSED, _constants.MODAL_STATUS.DISMISSING].includes(statusRef.current)) { return; } bottomSheetRef.current?.collapse(...args); }, []); const handleClose = (0, _react.useCallback)((...args) => { if ([_constants.MODAL_STATUS.MINIMIZED, _constants.MODAL_STATUS.MINIMIZING, _constants.MODAL_STATUS.DISMISSED, _constants.MODAL_STATUS.DISMISSING, _constants.MODAL_STATUS.CLOSED].includes(statusRef.current)) { return; } bottomSheetRef.current?.close(...args); }, []); const handleForceClose = (0, _react.useCallback)((...args) => { if ([_constants.MODAL_STATUS.MINIMIZED, _constants.MODAL_STATUS.MINIMIZING, _constants.MODAL_STATUS.DISMISSED, _constants.MODAL_STATUS.DISMISSING, _constants.MODAL_STATUS.CLOSED].includes(statusRef.current)) { return; } bottomSheetRef.current?.forceClose(...args); }, []); //#endregion //#region bottom sheet modal methods // biome-ignore lint/correctness/useExhaustiveDependencies(ref): ref is a stable object const handlePresent = (0, _react.useCallback)(function handlePresent(_data) { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: handlePresent.name, params: { currentIndexRef: currentIndexRef.current, nextIndexRef: nextIndexRef.current, status: statusRef.current } }); } requestAnimationFrame(() => { if (mount && bottomSheetRef.current) { statusRef.current = _constants.MODAL_STATUS.ANIMATING; bottomSheetRef.current.snapToIndex(index); } setState({ mount: true, data: _data }); mountSheet(key, ref, stackBehavior); }); }, [index, key, stackBehavior, mount, mountSheet]); const handleDismiss = (0, _react.useCallback)(function handleDismiss(animationConfigs) { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: handleDismiss.name, params: { status: statusRef.current } }); } /** * if the modal position is already in a closed position, * then we unmount the node and early exit. */ if ([_constants.MODAL_STATUS.CLOSED, _constants.MODAL_STATUS.MINIMIZED].includes(statusRef.current) || statusRef.current === _constants.MODAL_STATUS.DISMISSING && currentIndexRef.current === -1) { statusRef.current = _constants.MODAL_STATUS.DISMISSED; unmount(); return; } statusRef.current = _constants.MODAL_STATUS.DISMISSING; willUnmountSheet(key); bottomSheetRef.current?.forceClose(animationConfigs); }, [willUnmountSheet, unmount, key]); const handleMinimize = (0, _react.useCallback)(function handleMinimize() { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: handleMinimize.name, params: { index, currentIndexRef: currentIndexRef.current, status: statusRef.current } }); } /** * if the modal is minimized or animating to a minimized position, * then we early exit the method. */ if (statusRef.current === _constants.MODAL_STATUS.MINIMIZED || statusRef.current === _constants.MODAL_STATUS.MINIMIZING) { return; } /** * if modal got minimized before it finish its mounting * animation, we set the `restoreIndexRef` to the * provided index. */ if (currentIndexRef.current === -1) { restoreIndexRef.current = index; } else { restoreIndexRef.current = currentIndexRef.current; } statusRef.current = _constants.MODAL_STATUS.MINIMIZING; bottomSheetRef.current?.close(); }, [index]); const handleRestore = (0, _react.useCallback)(function handleRestore() { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: handleRestore.name, params: { status: statusRef.current } }); } /** * we only restore if the modal is minimized or going to be. */ const minimizedOrGoingToBe = [_constants.MODAL_STATUS.MINIMIZING, _constants.MODAL_STATUS.MINIMIZED].includes(statusRef.current); if (!minimizedOrGoingToBe) { return; } bottomSheetRef.current?.snapToIndex(restoreIndexRef.current); }, []); //#endregion //#region callbacks const handlePortalOnUnmount = (0, _react.useCallback)(function handlePortalOnUnmount() { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: 'handlePortalOnUnmount', params: { status: statusRef.current } }); } if (statusRef.current === _constants.MODAL_STATUS.INITIAL) { return; } /** * if modal is already in minimized/closed position, then * unmount its node and early exit the method. */ if (statusRef.current === _constants.MODAL_STATUS.MINIMIZED || statusRef.current === _constants.MODAL_STATUS.DISMISSED || currentIndexRef.current === -1) { unmount(); return; } statusRef.current = _constants.MODAL_STATUS.DISMISSING; willUnmountSheet(key); bottomSheetRef.current?.close(); }, [key, unmount, willUnmountSheet]); const handlePortalRender = (0, _react.useCallback)(function handlePortalRender(render) { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: 'handlePortalRender', params: { status: statusRef.current } }); } if ([_constants.MODAL_STATUS.DISMISSING].includes(statusRef.current)) { return; } render(); }, []); const handleBottomSheetOnChange = (0, _react.useCallback)(function handleBottomSheetOnChange(_index, _position, _type) { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: handleBottomSheetOnChange.name, category: 'callback', params: { status: statusRef.current } }); } currentIndexRef.current = _index; nextIndexRef.current = null; statusRef.current = _index === -1 ? _constants.MODAL_STATUS.MINIMIZED : _constants.MODAL_STATUS.PRESENTED; if (_providedOnChange) { _providedOnChange(_index, _position, _type); } }, [_providedOnChange]); const handleBottomSheetOnAnimate = (0, _react.useCallback)((fromIndex, toIndex, fromPosition, toPosition) => { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: 'handleBottomSheetOnAnimate', category: 'callback', params: { status: statusRef.current } }); } nextIndexRef.current = toIndex; /** * we do not want to override the pre-set status for minimizing or dismissing, * as they need to be set manually. */ const currentStatusIsDismissingOrMinimizing = [_constants.MODAL_STATUS.DISMISSING, _constants.MODAL_STATUS.MINIMIZING].includes(statusRef.current); if (!(currentStatusIsDismissingOrMinimizing && toIndex === -1)) { statusRef.current = _constants.MODAL_STATUS.ANIMATING; } if (_providedOnAnimate) { _providedOnAnimate(fromIndex, toIndex, fromPosition, toPosition); } }, [_providedOnAnimate]); const handleBottomSheetOnClose = (0, _react.useCallback)(function handleBottomSheetOnClose() { if (__DEV__) { (0, _utilities.print)({ component: 'BottomSheetModal', method: 'handleBottomSheetOnClose', category: 'callback', params: { status: statusRef.current } }); } if (statusRef.current === _constants.MODAL_STATUS.DISMISSING) { statusRef.current = _constants.MODAL_STATUS.DISMISSED; } else if (statusRef.current === _constants.MODAL_STATUS.MINIMIZING) { statusRef.current = _constants.MODAL_STATUS.MINIMIZED; } else { statusRef.current = enableDismissOnClose ? _constants.MODAL_STATUS.DISMISSED : _constants.MODAL_STATUS.CLOSED; } if (statusRef.current !== _constants.MODAL_STATUS.DISMISSED) { return; } unmount(); }, [enableDismissOnClose, unmount]); //#endregion //#region expose methods (0, _react.useImperativeHandle)(ref, () => ({ // sheet snapToIndex: handleSnapToIndex, snapToPosition: handleSnapToPosition, expand: handleExpand, collapse: handleCollapse, close: handleClose, forceClose: handleForceClose, // modal methods dismiss: handleDismiss, present: handlePresent, // internal minimize: handleMinimize, restore: handleRestore, status: statusRef })); //#endregion // render return mount ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_portal.Portal, { name: key, hostName: hostName, handleOnMount: handlePortalRender, handleOnUpdate: handlePortalRender, handleOnUnmount: handlePortalOnUnmount, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ContainerComponent, { children: /*#__PURE__*/(0, _react.createElement)(_bottomSheet.default, { ...bottomSheetProps, ref: bottomSheetRef, key: key, index: index, snapPoints: snapPoints, enablePanDownToClose: enablePanDownToClose, animateOnMount: animateOnMount, containerLayoutState: containerLayoutState, onChange: handleBottomSheetOnChange, onClose: handleBottomSheetOnClose, onAnimate: handleBottomSheetOnAnimate, $modal: true }, typeof Content === 'function' ? Content({ data }) : Content) }, key) }, key) : null; } const BottomSheetModal = /*#__PURE__*/(0, _react.memo)(/*#__PURE__*/(0, _react.forwardRef)(BottomSheetModalComponent)); BottomSheetModal.displayName = 'BottomSheetModal'; var _default = exports.default = BottomSheetModal; //# sourceMappingURL=BottomSheetModal.js.map