UNPKG

@ehfuse/forma

Version:

Advanced React state management library with individual field subscriptions - supports both forms and general state management with useFormaState

186 lines 7.23 kB
"use strict"; /** * useModal.ts * * 모달 상태 관리 및 뒤로가기 처리를 위한 React Hook * Modal state management and back navigation handling React Hook * * @license MIT License * @copyright 2025 KIM YOUNG JIN (ehfuse@gmail.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.useModal = useModal; const react_1 = require("react"); const GlobalFormaContext_1 = require("../contexts/GlobalFormaContext"); const useGlobalFormaState_1 = require("./useGlobalFormaState"); /** * 모달 고유 ID 생성 함수 * Generate unique modal ID */ function generateModalId() { return `modal_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * useModal - 모달 상태 관리 및 뒤로가기 처리 훅 * * 모바일 환경에서 모달이 열려있을 때 뒤로가기를 누르면 * 페이지가 뒤로 가는 것이 아니라 모달이 닫히도록 처리합니다. * * 같은 modalId를 사용하면 여러 컴포넌트에서 같은 모달 상태를 공유합니다. * * Handles modal state and back navigation. * When a modal is open and user presses back button, * the modal closes instead of navigating back. * * Using the same modalId shares modal state across multiple components. * * @param props - modalId: 모달 ID (같은 ID면 공유), initialOpen: 초기 열림 상태, onClose: 닫힐 때 콜백 * @returns isOpen, open, close, toggle, modalId * * @example * ```tsx * // 독립적인 모달 (modalId 자동 생성) * function MyComponent() { * const modal = useModal({ * onClose: () => console.log('Modal closed') * }); * * return ( * <> * <button onClick={modal.open}>Open Modal</button> * <Dialog open={modal.isOpen} onClose={modal.close}> * <DialogTitle>My Modal</DialogTitle> * <DialogContent>Content here</DialogContent> * </Dialog> * </> * ); * } * * // 공유 모달 (같은 modalId 사용) * function ComponentA() { * const modal = useModal({ modalId: 'shared-modal' }); * return <button onClick={modal.open}>Open from A</button>; * } * * function ComponentB() { * const modal = useModal({ modalId: 'shared-modal' }); * return ( * <Dialog open={modal.isOpen} onClose={modal.close}> * <DialogTitle>Shared Modal</DialogTitle> * </Dialog> * ); * } * ``` */ function useModal({ modalId: providedModalId, initialOpen = false, onClose: externalOnClose, } = {}) { // modalId가 제공되면 사용, 아니면 고유 ID 생성 const modalId = (0, react_1.useMemo)(() => providedModalId || generateModalId(), [providedModalId]); // 전역 상태로 모달 열림 상태 관리 (reactive!) const state = (0, useGlobalFormaState_1.useGlobalFormaState)({ stateId: `__modal_${modalId}__`, initialValues: { isOpen: initialOpen }, }); const isOpen = state.useValue("isOpen"); const { appendOpenModal, removeOpenModal } = (0, react_1.useContext)(GlobalFormaContext_1.GlobalFormaContext); // 이미 등록된 모달인지 추적 const isRegisteredRef = (0, react_1.useRef)(false); // 외부 onClose 함수 추적 const onCloseRef = (0, react_1.useRef)(externalOnClose); (0, react_1.useEffect)(() => { onCloseRef.current = externalOnClose; }, [externalOnClose]); // 모달 열기 const open = (0, react_1.useCallback)(() => { if (!isOpen) { state.setValue("isOpen", true); if (!isRegisteredRef.current) { appendOpenModal(modalId); isRegisteredRef.current = true; } } }, [isOpen, modalId, appendOpenModal, state]); // 모달 닫기 const close = (0, react_1.useCallback)(() => { if (isOpen) { // 모달이 등록되어 있으면 history.back()으로 처리 if (isRegisteredRef.current) { window.history.back(); // popstate 이벤트가 발생하면 FormContext의 handlePopState가 처리하여 // closeLastModal -> modal:close 이벤트 발생 -> handleCloseEvent 에서 state.setValue("isOpen", false) 호출 } else { // 등록되지 않은 경우 (open()이 호출되지 않고 직접 닫히는 경우) 직접 닫기 state.setValue("isOpen", false); if (onCloseRef.current) { onCloseRef.current(); } } } }, [isOpen, state]); // 모달 토글 const toggle = (0, react_1.useCallback)(() => { if (isOpen) { close(); } else { open(); } }, [isOpen, open, close]); // 컴포넌트 언마운트 시에만 정리 (0, react_1.useEffect)(() => { return () => { if (isRegisteredRef.current) { removeOpenModal(modalId); isRegisteredRef.current = false; } }; }, [modalId, removeOpenModal]); // 모달 닫기 이벤트 리스너 등록 (0, react_1.useEffect)(() => { // 외부에서 모달 닫기 요청을 처리하는 이벤트 리스너 const handleCloseEvent = () => { // 이벤트가 수신될때는 popstate가 발생한 것이므로 히스토리는 이미 삭제된 상태라서 닫기만 실행 state.setValue("isOpen", false); // 모달 제거 if (isRegisteredRef.current) { removeOpenModal(modalId); isRegisteredRef.current = false; } // 외부 onClose 콜백 호출 if (onCloseRef.current) { onCloseRef.current(); } }; // 모달 닫기 이벤트 리스너 등록 window.addEventListener(`modal:close:${modalId}`, handleCloseEvent); return () => { window.removeEventListener(`modal:close:${modalId}`, handleCloseEvent); }; }, [modalId, removeOpenModal, state]); return { isOpen, open, close, toggle, modalId, }; } //# sourceMappingURL=useModal.js.map